3
0
Mirror von https://github.com/PaperMC/Velocity.git synchronisiert 2024-09-28 22:21:13 +02:00
Co-authored-by: RednedEpic <redned235@gmail.com>
Co-authored-by: Gero <gecam59@gmail.com>
Dieser Commit ist enthalten in:
Paul 2023-10-10 13:44:16 +01:00 committet von GitHub
Ursprung 19abb9094e
Commit 768ecdb0c3
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: 4AEE18F83AFDEB23
38 geänderte Dateien mit 2272 neuen und 595 gelöschten Zeilen

2
.gitignore vendored
Datei anzeigen

@ -83,6 +83,8 @@ gradle-app.setting
logs/ logs/
/velocity.toml /velocity.toml
/forwarding.secret /forwarding.secret
forwarding.secret
velocity.toml
server-icon.png server-icon.png
/bin/ /bin/
run/ run/

Datei anzeigen

@ -62,7 +62,8 @@ public enum ProtocolVersion {
MINECRAFT_1_19_1(760, "1.19.1", "1.19.2"), MINECRAFT_1_19_1(760, "1.19.1", "1.19.2"),
MINECRAFT_1_19_3(761, "1.19.3"), MINECRAFT_1_19_3(761, "1.19.3"),
MINECRAFT_1_19_4(762, "1.19.4"), MINECRAFT_1_19_4(762, "1.19.4"),
MINECRAFT_1_20(763, "1.20", "1.20.1"); MINECRAFT_1_20(763, "1.20", "1.20.1"),
MINECRAFT_1_20_2(764, "1.20.2");
private static final int SNAPSHOT_BIT = 30; private static final int SNAPSHOT_BIT = 30;

Datei anzeigen

@ -36,6 +36,7 @@ import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.client.HandshakeSessionHandler; import com.velocitypowered.proxy.connection.client.HandshakeSessionHandler;
import com.velocitypowered.proxy.connection.client.InitialLoginSessionHandler; import com.velocitypowered.proxy.connection.client.InitialLoginSessionHandler;
import com.velocitypowered.proxy.connection.client.StatusSessionHandler; import com.velocitypowered.proxy.connection.client.StatusSessionHandler;
import com.velocitypowered.proxy.network.Connections;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.VelocityConnectionEvent; import com.velocitypowered.proxy.protocol.VelocityConnectionEvent;
@ -46,6 +47,7 @@ import com.velocitypowered.proxy.protocol.netty.MinecraftCompressorAndLengthEnco
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder; import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder; import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder;
import com.velocitypowered.proxy.protocol.netty.PlayPacketQueueHandler;
import com.velocitypowered.proxy.util.except.QuietDecoderException; import com.velocitypowered.proxy.util.except.QuietDecoderException;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel; import io.netty.channel.Channel;
@ -60,6 +62,9 @@ import io.netty.util.ReferenceCountUtil;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
@ -78,7 +83,8 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
private final Channel channel; private final Channel channel;
private SocketAddress remoteAddress; private SocketAddress remoteAddress;
private StateRegistry state; private StateRegistry state;
private @Nullable MinecraftSessionHandler sessionHandler; private Map<StateRegistry, MinecraftSessionHandler> sessionHandlers;
private @Nullable MinecraftSessionHandler activeSessionHandler;
private ProtocolVersion protocolVersion; private ProtocolVersion protocolVersion;
private @Nullable MinecraftConnectionAssociation association; private @Nullable MinecraftConnectionAssociation association;
public final VelocityServer server; public final VelocityServer server;
@ -96,12 +102,14 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
this.remoteAddress = channel.remoteAddress(); this.remoteAddress = channel.remoteAddress();
this.server = server; this.server = server;
this.state = StateRegistry.HANDSHAKE; this.state = StateRegistry.HANDSHAKE;
this.sessionHandlers = new HashMap<>();
} }
@Override @Override
public void channelActive(ChannelHandlerContext ctx) throws Exception { public void channelActive(ChannelHandlerContext ctx) throws Exception {
if (sessionHandler != null) { if (activeSessionHandler != null) {
sessionHandler.connected(); activeSessionHandler.connected();
} }
if (association != null && server.getConfiguration().isLogPlayerConnections()) { if (association != null && server.getConfiguration().isLogPlayerConnections()) {
@ -111,12 +119,12 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
@Override @Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception { public void channelInactive(ChannelHandlerContext ctx) throws Exception {
if (sessionHandler != null) { if (activeSessionHandler != null) {
sessionHandler.disconnected(); activeSessionHandler.disconnected();
} }
if (association != null && !knownDisconnect if (association != null && !knownDisconnect
&& !(sessionHandler instanceof StatusSessionHandler) && !(activeSessionHandler instanceof StatusSessionHandler)
&& server.getConfiguration().isLogPlayerConnections()) { && server.getConfiguration().isLogPlayerConnections()) {
logger.info("{} has disconnected", association); logger.info("{} has disconnected", association);
} }
@ -125,12 +133,12 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
@Override @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try { try {
if (sessionHandler == null) { if (activeSessionHandler == null) {
// No session handler available, do nothing // No session handler available, do nothing
return; return;
} }
if (sessionHandler.beforeHandle()) { if (activeSessionHandler.beforeHandle()) {
return; return;
} }
@ -140,15 +148,15 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
if (msg instanceof MinecraftPacket) { if (msg instanceof MinecraftPacket) {
MinecraftPacket pkt = (MinecraftPacket) msg; MinecraftPacket pkt = (MinecraftPacket) msg;
if (!pkt.handle(sessionHandler)) { if (!pkt.handle(activeSessionHandler)) {
sessionHandler.handleGeneric((MinecraftPacket) msg); activeSessionHandler.handleGeneric((MinecraftPacket) msg);
} }
} else if (msg instanceof HAProxyMessage) { } else if (msg instanceof HAProxyMessage) {
HAProxyMessage proxyMessage = (HAProxyMessage) msg; HAProxyMessage proxyMessage = (HAProxyMessage) msg;
this.remoteAddress = new InetSocketAddress(proxyMessage.sourceAddress(), this.remoteAddress = new InetSocketAddress(proxyMessage.sourceAddress(),
proxyMessage.sourcePort()); proxyMessage.sourcePort());
} else if (msg instanceof ByteBuf) { } else if (msg instanceof ByteBuf) {
sessionHandler.handleUnknown((ByteBuf) msg); activeSessionHandler.handleUnknown((ByteBuf) msg);
} }
} finally { } finally {
ReferenceCountUtil.release(msg); ReferenceCountUtil.release(msg);
@ -157,20 +165,21 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
@Override @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
if (sessionHandler != null) { if (activeSessionHandler != null) {
sessionHandler.readCompleted(); activeSessionHandler.readCompleted();
} }
} }
@Override @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (ctx.channel().isActive()) { if (ctx.channel().isActive()) {
if (sessionHandler != null) { if (activeSessionHandler != null) {
try { try {
sessionHandler.exception(cause); activeSessionHandler.exception(cause);
} catch (Exception ex) { } catch (Exception ex) {
logger.error("{}: exception handling exception in {}", logger.error("{}: exception handling exception in {}",
(association != null ? association : channel.remoteAddress()), sessionHandler, cause); (association != null ? association : channel.remoteAddress()), activeSessionHandler,
cause);
} }
} }
@ -178,13 +187,14 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
if (cause instanceof ReadTimeoutException) { if (cause instanceof ReadTimeoutException) {
logger.error("{}: read timed out", association); logger.error("{}: read timed out", association);
} else { } else {
boolean frontlineHandler = sessionHandler instanceof InitialLoginSessionHandler boolean frontlineHandler = activeSessionHandler instanceof InitialLoginSessionHandler
|| sessionHandler instanceof HandshakeSessionHandler || activeSessionHandler instanceof HandshakeSessionHandler
|| sessionHandler instanceof StatusSessionHandler; || activeSessionHandler instanceof StatusSessionHandler;
boolean isQuietDecoderException = cause instanceof QuietDecoderException; boolean isQuietDecoderException = cause instanceof QuietDecoderException;
boolean willLog = !isQuietDecoderException && !frontlineHandler; boolean willLog = !isQuietDecoderException && !frontlineHandler;
if (willLog) { if (willLog) {
logger.error("{}: exception encountered in {}", association, sessionHandler, cause); logger.error("{}: exception encountered in {}", association, activeSessionHandler,
cause);
} else { } else {
knownDisconnect = true; knownDisconnect = true;
} }
@ -197,8 +207,8 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
@Override @Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
if (sessionHandler != null) { if (activeSessionHandler != null) {
sessionHandler.writabilityChanged(); activeSessionHandler.writabilityChanged();
} }
} }
@ -323,7 +333,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
} }
/** /**
* Determines whether or not the channel should continue reading data automaticaly. * Determines whether or not the channel should continue reading data automatically.
* *
* @param autoReading whether or not we should read data automatically * @param autoReading whether or not we should read data automatically
*/ */
@ -341,10 +351,12 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
} }
} }
// Ideally only used by the state switch
/** /**
* Changes the state of the Minecraft connection. * Sets the new state for the connection.
* *
* @param state the new state * @param state the state to use
*/ */
public void setState(StateRegistry state) { public void setState(StateRegistry state) {
ensureInEventLoop(); ensureInEventLoop();
@ -352,6 +364,25 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
this.state = state; this.state = state;
this.channel.pipeline().get(MinecraftEncoder.class).setState(state); this.channel.pipeline().get(MinecraftEncoder.class).setState(state);
this.channel.pipeline().get(MinecraftDecoder.class).setState(state); this.channel.pipeline().get(MinecraftDecoder.class).setState(state);
if (state == StateRegistry.CONFIG) {
// Activate the play packet queue
addPlayPacketQueueHandler();
} else if (this.channel.pipeline().get(Connections.PLAY_PACKET_QUEUE) != null) {
// Remove the queue
this.channel.pipeline().remove(Connections.PLAY_PACKET_QUEUE);
}
}
/**
* Adds the play packet queue handler.
*/
public void addPlayPacketQueueHandler() {
if (this.channel.pipeline().get(Connections.PLAY_PACKET_QUEUE) == null) {
this.channel.pipeline().addAfter(Connections.MINECRAFT_ENCODER, Connections.PLAY_PACKET_QUEUE,
new PlayPacketQueueHandler(this.protocolVersion,
channel.pipeline().get(MinecraftEncoder.class).getDirection()));
}
} }
public ProtocolVersion getProtocolVersion() { public ProtocolVersion getProtocolVersion() {
@ -382,32 +413,81 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
} }
} }
public @Nullable MinecraftSessionHandler getSessionHandler() { public @Nullable MinecraftSessionHandler getActiveSessionHandler() {
return sessionHandler; return activeSessionHandler;
}
public @Nullable MinecraftSessionHandler getSessionHandlerForRegistry(StateRegistry registry) {
return this.sessionHandlers.getOrDefault(registry, null);
} }
/** /**
* Sets the session handler for this connection. * Sets the session handler for this connection.
* *
* @param registry the registry of the handler
* @param sessionHandler the handler to use * @param sessionHandler the handler to use
*/ */
public void setSessionHandler(MinecraftSessionHandler sessionHandler) { public void setActiveSessionHandler(StateRegistry registry,
MinecraftSessionHandler sessionHandler) {
Preconditions.checkNotNull(registry);
ensureInEventLoop(); ensureInEventLoop();
if (this.sessionHandler != null) { if (this.activeSessionHandler != null) {
this.sessionHandler.deactivated(); this.activeSessionHandler.deactivated();
} }
this.sessionHandler = sessionHandler; this.sessionHandlers.put(registry, sessionHandler);
this.activeSessionHandler = sessionHandler;
setState(registry);
sessionHandler.activated(); sessionHandler.activated();
} }
/**
* Switches the active session handler to the respective registry one.
*
* @param registry the registry of the handler
* @return true if successful and handler is present
*/
public boolean setActiveSessionHandler(StateRegistry registry) {
Preconditions.checkNotNull(registry);
ensureInEventLoop();
MinecraftSessionHandler handler = getSessionHandlerForRegistry(registry);
if (handler != null) {
boolean flag = true;
if (this.activeSessionHandler != null
&& (flag = !Objects.equals(handler, this.activeSessionHandler))) {
this.activeSessionHandler.deactivated();
}
this.activeSessionHandler = handler;
setState(registry);
if (flag) {
handler.activated();
}
}
return handler != null;
}
/**
* Adds a secondary session handler for this connection.
*
* @param registry the registry of the handler
* @param sessionHandler the handler to use
*/
public void addSessionHandler(StateRegistry registry, MinecraftSessionHandler sessionHandler) {
Preconditions.checkNotNull(registry);
Preconditions.checkArgument(registry != state, "Handler would overwrite handler");
ensureInEventLoop();
this.sessionHandlers.put(registry, sessionHandler);
}
private void ensureOpen() { private void ensureOpen() {
Preconditions.checkState(!isClosed(), "Connection is closed."); Preconditions.checkState(!isClosed(), "Connection is closed.");
} }
/** /**
* Sets the compression threshold on the connection. You are responsible for sending * Sets the compression threshold on the connection. You are responsible for sending {@link
* {@link com.velocitypowered.proxy.protocol.packet.SetCompression} beforehand. * com.velocitypowered.proxy.protocol.packet.SetCompression} beforehand.
* *
* @param threshold the compression threshold to use * @param threshold the compression threshold to use
*/ */
@ -497,5 +577,4 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
public void setType(ConnectionType connectionType) { public void setType(ConnectionType connectionType) {
this.connectionType = connectionType; this.connectionType = connectionType;
} }
} }

Datei anzeigen

@ -31,8 +31,10 @@ import com.velocitypowered.proxy.protocol.packet.KeepAlive;
import com.velocitypowered.proxy.protocol.packet.LegacyHandshake; import com.velocitypowered.proxy.protocol.packet.LegacyHandshake;
import com.velocitypowered.proxy.protocol.packet.LegacyPing; import com.velocitypowered.proxy.protocol.packet.LegacyPing;
import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem; import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem;
import com.velocitypowered.proxy.protocol.packet.LoginAcknowledged;
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage; import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage;
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse; import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
import com.velocitypowered.proxy.protocol.packet.PingIdentify;
import com.velocitypowered.proxy.protocol.packet.PluginMessage; import com.velocitypowered.proxy.protocol.packet.PluginMessage;
import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfo; import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfo;
import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest; import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest;
@ -55,6 +57,11 @@ import com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedPlayerCommand;
import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyChat; import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyChat;
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerChat; import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerChat;
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerCommand; import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerCommand;
import com.velocitypowered.proxy.protocol.packet.config.ActiveFeatures;
import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdate;
import com.velocitypowered.proxy.protocol.packet.config.RegistrySync;
import com.velocitypowered.proxy.protocol.packet.config.StartUpdate;
import com.velocitypowered.proxy.protocol.packet.config.TagsUpdate;
import com.velocitypowered.proxy.protocol.packet.title.LegacyTitlePacket; import com.velocitypowered.proxy.protocol.packet.title.LegacyTitlePacket;
import com.velocitypowered.proxy.protocol.packet.title.TitleActionbarPacket; import com.velocitypowered.proxy.protocol.packet.title.TitleActionbarPacket;
import com.velocitypowered.proxy.protocol.packet.title.TitleClearPacket; import com.velocitypowered.proxy.protocol.packet.title.TitleClearPacket;
@ -279,4 +286,32 @@ public interface MinecraftSessionHandler {
default boolean handle(UpsertPlayerInfo packet) { default boolean handle(UpsertPlayerInfo packet) {
return false; return false;
} }
default boolean handle(LoginAcknowledged packet) {
return false;
}
default boolean handle(ActiveFeatures packet) {
return false;
}
default boolean handle(FinishedUpdate packet) {
return false;
}
default boolean handle(RegistrySync packet) {
return false;
}
default boolean handle(TagsUpdate packet) {
return false;
}
default boolean handle(StartUpdate packet) {
return false;
}
default boolean handle(PingIdentify pingIdentify) {
return false;
}
} }

Datei anzeigen

@ -39,8 +39,11 @@ import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo; import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo;
import com.velocitypowered.proxy.connection.util.ConnectionMessages; import com.velocitypowered.proxy.connection.util.ConnectionMessages;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.packet.AvailableCommands; import com.velocitypowered.proxy.protocol.packet.AvailableCommands;
import com.velocitypowered.proxy.protocol.packet.BossBar; import com.velocitypowered.proxy.protocol.packet.BossBar;
import com.velocitypowered.proxy.protocol.packet.ClientSettings;
import com.velocitypowered.proxy.protocol.packet.Disconnect; import com.velocitypowered.proxy.protocol.packet.Disconnect;
import com.velocitypowered.proxy.protocol.packet.KeepAlive; import com.velocitypowered.proxy.protocol.packet.KeepAlive;
import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem; import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem;
@ -51,6 +54,7 @@ import com.velocitypowered.proxy.protocol.packet.ResourcePackResponse;
import com.velocitypowered.proxy.protocol.packet.ServerData; import com.velocitypowered.proxy.protocol.packet.ServerData;
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse; import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse;
import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo; import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo;
import com.velocitypowered.proxy.protocol.packet.config.StartUpdate;
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil; import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil; import io.netty.buffer.ByteBufUtil;
@ -68,10 +72,10 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
private static final Pattern PLAUSIBLE_SHA1_HASH = Pattern.compile("^[a-z0-9]{40}$"); private static final Pattern PLAUSIBLE_SHA1_HASH = Pattern.compile("^[a-z0-9]{40}$");
private static final Logger logger = LogManager.getLogger(BackendPlaySessionHandler.class); private static final Logger logger = LogManager.getLogger(BackendPlaySessionHandler.class);
private static final boolean BACKPRESSURE_LOG = Boolean private static final boolean BACKPRESSURE_LOG =
.getBoolean("velocity.log-server-backpressure"); Boolean.getBoolean("velocity.log-server-backpressure");
private static final int MAXIMUM_PACKETS_TO_FLUSH = Integer private static final int MAXIMUM_PACKETS_TO_FLUSH =
.getInteger("velocity.max-packets-per-flush", 8192); Integer.getInteger("velocity.max-packets-per-flush", 8192);
private final VelocityServer server; private final VelocityServer server;
private final VelocityServerConnection serverConn; private final VelocityServerConnection serverConn;
@ -86,7 +90,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
this.serverConn = serverConn; this.serverConn = serverConn;
this.playerConnection = serverConn.getPlayer().getConnection(); this.playerConnection = serverConn.getPlayer().getConnection();
MinecraftSessionHandler psh = playerConnection.getSessionHandler(); MinecraftSessionHandler psh = playerConnection.getActiveSessionHandler();
if (!(psh instanceof ClientPlaySessionHandler)) { if (!(psh instanceof ClientPlaySessionHandler)) {
throw new IllegalStateException( throw new IllegalStateException(
"Initializing BackendPlaySessionHandler with no backing client play session handler!"); "Initializing BackendPlaySessionHandler with no backing client play session handler!");
@ -119,12 +123,28 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
return false; return false;
} }
@Override
public boolean handle(StartUpdate packet) {
MinecraftConnection smc = serverConn.ensureConnected();
smc.setAutoReading(false);
// Even when not auto reading messages are still decoded. Decode them with the correct state
smc.getChannel().pipeline().get(MinecraftDecoder.class).setState(StateRegistry.CONFIG);
serverConn.getPlayer().switchToConfigState();
return true;
}
@Override @Override
public boolean handle(KeepAlive packet) { public boolean handle(KeepAlive packet) {
serverConn.getPendingPings().put(packet.getRandomId(), System.currentTimeMillis()); serverConn.getPendingPings().put(packet.getRandomId(), System.currentTimeMillis());
return false; // forwards on return false; // forwards on
} }
@Override
public boolean handle(ClientSettings packet) {
serverConn.ensureConnected().write(packet);
return true;
}
@Override @Override
public boolean handle(Disconnect packet) { public boolean handle(Disconnect packet) {
serverConn.disconnect(); serverConn.disconnect();
@ -221,20 +241,16 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
} }
byte[] copy = ByteBufUtil.getBytes(packet.content()); byte[] copy = ByteBufUtil.getBytes(packet.content());
PluginMessageEvent event = new PluginMessageEvent(serverConn, serverConn.getPlayer(), id, PluginMessageEvent event = new PluginMessageEvent(serverConn, serverConn.getPlayer(), id, copy);
copy); server.getEventManager().fire(event).thenAcceptAsync(pme -> {
server.getEventManager().fire(event) if (pme.getResult().isAllowed() && !playerConnection.isClosed()) {
.thenAcceptAsync(pme -> { PluginMessage copied = new PluginMessage(packet.getChannel(), Unpooled.wrappedBuffer(copy));
if (pme.getResult().isAllowed() && !playerConnection.isClosed()) { playerConnection.write(copied);
PluginMessage copied = new PluginMessage(packet.getChannel(), }
Unpooled.wrappedBuffer(copy)); }, playerConnection.eventLoop()).exceptionally((ex) -> {
playerConnection.write(copied); logger.error("Exception while handling plugin message {}", packet, ex);
} return null;
}, playerConnection.eventLoop()) });
.exceptionally((ex) -> {
logger.error("Exception while handling plugin message {}", packet, ex);
return null;
});
return true; return true;
} }
@ -283,18 +299,13 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
@Override @Override
public boolean handle(ServerData packet) { public boolean handle(ServerData packet) {
server.getServerListPingHandler().getInitialPing(this.serverConn.getPlayer()) server.getServerListPingHandler().getInitialPing(this.serverConn.getPlayer()).thenComposeAsync(
.thenComposeAsync( ping -> server.getEventManager()
ping -> server.getEventManager() .fire(new ProxyPingEvent(this.serverConn.getPlayer(), ping)),
.fire(new ProxyPingEvent(this.serverConn.getPlayer(), ping)), playerConnection.eventLoop()).thenAcceptAsync(pingEvent -> this.playerConnection.write(
playerConnection.eventLoop() new ServerData(pingEvent.getPing().getDescriptionComponent(),
) pingEvent.getPing().getFavicon().orElse(null), packet.isSecureChatEnforced())),
.thenAcceptAsync(pingEvent -> playerConnection.eventLoop());
this.playerConnection.write(
new ServerData(pingEvent.getPing().getDescriptionComponent(),
pingEvent.getPing().getFavicon().orElse(null),
packet.isSecureChatEnforced())
), playerConnection.eventLoop());
return true; return true;
} }

Datei anzeigen

@ -0,0 +1,229 @@
/*
* Copyright (C) 2019-2023 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.connection.backend;
import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent;
import com.velocitypowered.api.event.player.ServerResourcePackSendEvent;
import com.velocitypowered.api.proxy.player.ResourcePackInfo;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.client.ClientConfigSessionHandler;
import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo;
import com.velocitypowered.proxy.connection.util.ConnectionMessages;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.packet.Disconnect;
import com.velocitypowered.proxy.protocol.packet.KeepAlive;
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest;
import com.velocitypowered.proxy.protocol.packet.ResourcePackResponse;
import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdate;
import com.velocitypowered.proxy.protocol.packet.config.RegistrySync;
import com.velocitypowered.proxy.protocol.packet.config.StartUpdate;
import com.velocitypowered.proxy.protocol.packet.config.TagsUpdate;
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
import java.io.IOException;
import java.util.concurrent.CompletableFuture;
import java.util.regex.Pattern;
import net.kyori.adventure.text.Component;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* A special session handler that catches "last minute" disconnects. This version is to accommodate
* 1.20.2+ switching. Yes, some of this is exceptionally stupid.
*/
public class ConfigSessionHandler implements MinecraftSessionHandler {
private static final Pattern PLAUSIBLE_SHA1_HASH = Pattern.compile("^[a-z0-9]{40}$");
private static final Logger logger = LogManager.getLogger(ConfigSessionHandler.class);
private final VelocityServer server;
private final VelocityServerConnection serverConn;
private final CompletableFuture<Impl> resultFuture;
private ResourcePackInfo resourcePackToApply;
private State state;
/**
* Creates the new transition handler.
*
* @param server the Velocity server instance
* @param serverConn the server connection
* @param resultFuture the result future
*/
ConfigSessionHandler(VelocityServer server, VelocityServerConnection serverConn,
CompletableFuture<Impl> resultFuture) {
this.server = server;
this.serverConn = serverConn;
this.resultFuture = resultFuture;
this.state = State.START;
}
@Override
public void activated() {
resourcePackToApply = serverConn.getPlayer().getAppliedResourcePack();
serverConn.getPlayer().clearAppliedResourcePack();
}
@Override
public boolean beforeHandle() {
if (!serverConn.isActive()) {
// Obsolete connection
serverConn.disconnect();
return true;
}
return false;
}
@Override
public boolean handle(StartUpdate packet) {
serverConn.ensureConnected().write(packet);
return true;
}
@Override
public boolean handle(TagsUpdate packet) {
serverConn.getPlayer().getConnection().write(packet);
return true;
}
@Override
public boolean handle(KeepAlive packet) {
serverConn.ensureConnected().write(packet);
return true;
}
@Override
public boolean handle(ResourcePackRequest packet) {
final MinecraftConnection playerConnection = serverConn.getPlayer().getConnection();
ServerResourcePackSendEvent event =
new ServerResourcePackSendEvent(packet.toServerPromptedPack(), this.serverConn);
server.getEventManager().fire(event).thenAcceptAsync(serverResourcePackSendEvent -> {
if (playerConnection.isClosed()) {
return;
}
if (serverResourcePackSendEvent.getResult().isAllowed()) {
ResourcePackInfo toSend = serverResourcePackSendEvent.getProvidedResourcePack();
if (toSend != serverResourcePackSendEvent.getReceivedResourcePack()) {
((VelocityResourcePackInfo) toSend).setOriginalOrigin(
ResourcePackInfo.Origin.DOWNSTREAM_SERVER);
}
resourcePackToApply = null;
serverConn.getPlayer().queueResourcePack(toSend);
} else if (serverConn.getConnection() != null) {
serverConn.getConnection().write(new ResourcePackResponse(packet.getHash(),
PlayerResourcePackStatusEvent.Status.DECLINED));
}
}, playerConnection.eventLoop()).exceptionally((ex) -> {
if (serverConn.getConnection() != null) {
serverConn.getConnection().write(new ResourcePackResponse(packet.getHash(),
PlayerResourcePackStatusEvent.Status.DECLINED));
}
logger.error("Exception while handling resource pack send for {}", playerConnection, ex);
return null;
});
return true;
}
@Override
public boolean handle(FinishedUpdate packet) {
MinecraftConnection smc = serverConn.ensureConnected();
ClientConfigSessionHandler configHandler =
(ClientConfigSessionHandler) serverConn.getPlayer().getConnection()
.getActiveSessionHandler();
smc.setAutoReading(false);
// Even when not auto reading messages are still decoded. Decode them with the correct state
smc.getChannel().pipeline().get(MinecraftDecoder.class).setState(StateRegistry.PLAY);
configHandler.handleBackendFinishUpdate(serverConn).thenAcceptAsync((unused) -> {
if (serverConn == serverConn.getPlayer().getConnectedServer()) {
smc.setActiveSessionHandler(StateRegistry.PLAY);
} else {
smc.setActiveSessionHandler(StateRegistry.PLAY,
new TransitionSessionHandler(server, serverConn, resultFuture));
}
if (serverConn.getPlayer().getAppliedResourcePack() == null && resourcePackToApply != null) {
serverConn.getPlayer().queueResourcePack(resourcePackToApply);
}
smc.setAutoReading(true);
}, smc.eventLoop());
return true;
}
@Override
public boolean handle(Disconnect packet) {
serverConn.disconnect();
resultFuture.complete(ConnectionRequestResults.forDisconnect(packet, serverConn.getServer()));
return true;
}
@Override
public boolean handle(PluginMessage packet) {
if (PluginMessageUtil.isMcBrand(packet)) {
serverConn.getPlayer().getConnection().write(
PluginMessageUtil.rewriteMinecraftBrand(packet, server.getVersion(),
serverConn.getPlayer().getProtocolVersion()));
} else {
// TODO: Change this so its usable for mod loaders
serverConn.disconnect();
resultFuture.complete(ConnectionRequestResults.forDisconnect(
Component.translatable("multiplayer.disconnect.missing_tags"), serverConn.getServer()));
}
return true;
}
@Override
public boolean handle(RegistrySync packet) {
serverConn.getPlayer().getConnection().write(packet.retain());
return true;
}
@Override
public void disconnected() {
resultFuture.completeExceptionally(
new IOException("Unexpectedly disconnected from remote server"));
}
@Override
public void handleGeneric(MinecraftPacket packet) {
serverConn.getPlayer().getConnection().write(packet);
}
private void switchFailure(Throwable cause) {
logger.error("Unable to switch to new server {} for {}", serverConn.getServerInfo().getName(),
serverConn.getPlayer().getUsername(), cause);
serverConn.getPlayer().disconnect(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR);
resultFuture.completeExceptionally(cause);
}
/**
* Represents the state of the configuration stage.
*/
public static enum State {
START, NEGOTIATING, PLUGIN_MESSAGE_INTERRUPT, RESOURCE_PACK_INTERRUPT, COMPLETE
}
}

Datei anzeigen

@ -27,6 +27,7 @@ import com.velocitypowered.proxy.config.VelocityConfiguration;
import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.VelocityConstants; import com.velocitypowered.proxy.connection.VelocityConstants;
import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
@ -34,6 +35,7 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.Disconnect; import com.velocitypowered.proxy.protocol.packet.Disconnect;
import com.velocitypowered.proxy.protocol.packet.EncryptionRequest; import com.velocitypowered.proxy.protocol.packet.EncryptionRequest;
import com.velocitypowered.proxy.protocol.packet.LoginAcknowledged;
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage; import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage;
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse; import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess; import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess;
@ -59,8 +61,8 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
private static final Logger logger = LogManager.getLogger(LoginSessionHandler.class); private static final Logger logger = LogManager.getLogger(LoginSessionHandler.class);
private static final Component MODERN_IP_FORWARDING_FAILURE = Component private static final Component MODERN_IP_FORWARDING_FAILURE =
.translatable("velocity.error.modern-forwarding-failed"); Component.translatable("velocity.error.modern-forwarding-failed");
private final VelocityServer server; private final VelocityServer server;
private final VelocityServerConnection serverConn; private final VelocityServerConnection serverConn;
@ -150,10 +152,28 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
// Move into the PLAY phase. // Move into the PLAY phase.
MinecraftConnection smc = serverConn.ensureConnected(); MinecraftConnection smc = serverConn.ensureConnected();
smc.setState(StateRegistry.PLAY); if (smc.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) < 0) {
smc.setActiveSessionHandler(StateRegistry.PLAY,
new TransitionSessionHandler(server, serverConn, resultFuture));
} else {
smc.setAutoReading(false);
CompletableFuture<Void> switchFuture;
if (serverConn.getPlayer().getConnection()
.getActiveSessionHandler() instanceof ClientPlaySessionHandler) {
switchFuture = ((ClientPlaySessionHandler) serverConn.getPlayer().getConnection()
.getActiveSessionHandler()).doSwitch();
} else {
switchFuture = CompletableFuture.completedFuture(null);
}
switchFuture.thenAcceptAsync((unused) -> {
smc.write(new LoginAcknowledged());
// Sync backend
smc.setActiveSessionHandler(StateRegistry.CONFIG,
new ConfigSessionHandler(server, serverConn, resultFuture));
smc.setAutoReading(true);
}, smc.eventLoop());
}
// Switch to the transition handler.
smc.setSessionHandler(new TransitionSessionHandler(server, serverConn, resultFuture));
return true; return true;
} }
@ -165,12 +185,12 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
@Override @Override
public void disconnected() { public void disconnected() {
if (server.getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.LEGACY) { if (server.getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.LEGACY) {
resultFuture.completeExceptionally( resultFuture.completeExceptionally(new QuietRuntimeException(
new QuietRuntimeException("The connection to the remote server was unexpectedly closed.\n" "The connection to the remote server was unexpectedly closed.\n"
+ "This is usually because the remote server does not have BungeeCord IP forwarding " + "This is usually because the remote server "
+ "does not have BungeeCord IP forwarding "
+ "correctly enabled.\nSee https://velocitypowered.com/wiki/users/forwarding/ " + "correctly enabled.\nSee https://velocitypowered.com/wiki/users/forwarding/ "
+ "for instructions on how to configure player info forwarding correctly.") + "for instructions on how to configure player info forwarding correctly."));
);
} else { } else {
resultFuture.completeExceptionally( resultFuture.completeExceptionally(
new QuietRuntimeException("The connection to the remote server was unexpectedly closed.") new QuietRuntimeException("The connection to the remote server was unexpectedly closed.")

Datei anzeigen

@ -32,6 +32,7 @@ import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.connection.util.ConnectionMessages; import com.velocitypowered.proxy.connection.util.ConnectionMessages;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.Disconnect; import com.velocitypowered.proxy.protocol.packet.Disconnect;
import com.velocitypowered.proxy.protocol.packet.JoinGame; import com.velocitypowered.proxy.protocol.packet.JoinGame;
import com.velocitypowered.proxy.protocol.packet.KeepAlive; import com.velocitypowered.proxy.protocol.packet.KeepAlive;
@ -120,17 +121,21 @@ public class TransitionSessionHandler implements MinecraftSessionHandler {
// Change the client to use the ClientPlaySessionHandler if required. // Change the client to use the ClientPlaySessionHandler if required.
ClientPlaySessionHandler playHandler; ClientPlaySessionHandler playHandler;
if (player.getConnection().getSessionHandler() instanceof ClientPlaySessionHandler) { if (player.getConnection()
playHandler = (ClientPlaySessionHandler) player.getConnection().getSessionHandler(); .getActiveSessionHandler() instanceof ClientPlaySessionHandler) {
playHandler =
(ClientPlaySessionHandler) player.getConnection().getActiveSessionHandler();
} else { } else {
playHandler = new ClientPlaySessionHandler(server, player); playHandler = new ClientPlaySessionHandler(server, player);
player.getConnection().setSessionHandler(playHandler); player.getConnection().setActiveSessionHandler(StateRegistry.PLAY, playHandler);
} }
assert playHandler != null;
playHandler.handleBackendJoinGame(packet, serverConn); playHandler.handleBackendJoinGame(packet, serverConn);
// Set the new play session handler for the server. We will have nothing more to do // Set the new play session handler for the server. We will have nothing more to do
// with this connection once this task finishes up. // with this connection once this task finishes up.
smc.setSessionHandler(new BackendPlaySessionHandler(server, serverConn)); smc.setActiveSessionHandler(StateRegistry.PLAY,
new BackendPlaySessionHandler(server, serverConn));
// Clean up disabling auto-read while the connected event was being processed. // Clean up disabling auto-read while the connected event was being processed.
smc.setAutoReading(true); smc.setAutoReading(true);
@ -138,12 +143,15 @@ public class TransitionSessionHandler implements MinecraftSessionHandler {
// Now set the connected server. // Now set the connected server.
serverConn.getPlayer().setConnectedServer(serverConn); serverConn.getPlayer().setConnectedServer(serverConn);
if (player.getClientSettingsPacket() != null) {
serverConn.ensureConnected().write(player.getClientSettingsPacket());
}
// We're done! :) // We're done! :)
server.getEventManager().fireAndForget(new ServerPostConnectEvent(player, server.getEventManager().fireAndForget(new ServerPostConnectEvent(player,
previousServer)); previousServer));
resultFuture.complete(ConnectionRequestResults.successful(serverConn.getServer())); resultFuture.complete(ConnectionRequestResults.successful(serverConn.getServer()));
}, smc.eventLoop()) }, smc.eventLoop()).exceptionally(exc -> {
.exceptionally(exc -> {
logger.error("Unable to switch to new server {} for {}", logger.error("Unable to switch to new server {} for {}",
serverConn.getServerInfo().getName(), serverConn.getServerInfo().getName(),
player.getUsername(), exc); player.getUsername(), exc);

Datei anzeigen

@ -34,6 +34,7 @@ import com.velocitypowered.proxy.config.PlayerInfoForwarding;
import com.velocitypowered.proxy.connection.ConnectionTypes; import com.velocitypowered.proxy.connection.ConnectionTypes;
import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation; import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;
@ -91,8 +92,8 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
/** /**
* Connects to the server. * Connects to the server.
* *
* @return a {@link com.velocitypowered.api.proxy.ConnectionRequestBuilder.Result} representing * @return a {@link com.velocitypowered.api.proxy.ConnectionRequestBuilder.Result}
* whether or not the connect succeeded * representing whether the connection succeeded
*/ */
public CompletableFuture<Impl> connect() { public CompletableFuture<Impl> connect() {
CompletableFuture<Impl> result = new CompletableFuture<>(); CompletableFuture<Impl> result = new CompletableFuture<>();
@ -108,15 +109,21 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
future.channel().pipeline().addLast(HANDLER, connection); future.channel().pipeline().addLast(HANDLER, connection);
// Kick off the connection process // Kick off the connection process
connection.setSessionHandler( if (!connection.setActiveSessionHandler(StateRegistry.HANDSHAKE)) {
new LoginSessionHandler(server, VelocityServerConnection.this, result)); MinecraftSessionHandler handler =
new LoginSessionHandler(server, VelocityServerConnection.this, result);
connection.setActiveSessionHandler(StateRegistry.HANDSHAKE, handler);
connection.addSessionHandler(StateRegistry.LOGIN, handler);
}
// Set the connection phase, which may, for future forge (or whatever), be determined // Set the connection phase, which may, for future forge (or whatever), be
// determined
// at this point already // at this point already
connectionPhase = connection.getType().getInitialBackendPhase(); connectionPhase = connection.getType().getInitialBackendPhase();
startHandshake(); startHandshake();
} else { } else {
// Complete the result immediately. ConnectedPlayer will reset the in-flight connection. // Complete the result immediately. ConnectedPlayer will reset the in-flight
// connection.
result.completeExceptionally(future.cause()); result.completeExceptionally(future.cause());
} }
}); });
@ -137,10 +144,8 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
// BungeeCord IP forwarding is simply a special injection after the "address" in the handshake, // BungeeCord IP forwarding is simply a special injection after the "address" in the handshake,
// separated by \0 (the null byte). In order, you send the original host, the player's IP, their // separated by \0 (the null byte). In order, you send the original host, the player's IP, their
// UUID (undashed), and if you are in online-mode, their login properties (from Mojang). // UUID (undashed), and if you are in online-mode, their login properties (from Mojang).
StringBuilder data = new StringBuilder() StringBuilder data = new StringBuilder().append(proxyPlayer.getVirtualHost().orElseGet(() ->
.append(proxyPlayer.getVirtualHost() registeredServer.getServerInfo().getAddress()).getHostString())
.orElseGet(() -> registeredServer.getServerInfo().getAddress())
.getHostString())
.append('\0') .append('\0')
.append(getPlayerRemoteAddressAsString()) .append(getPlayerRemoteAddressAsString())
.append('\0') .append('\0')
@ -157,12 +162,10 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
private String createBungeeGuardForwardingAddress(byte[] forwardingSecret) { private String createBungeeGuardForwardingAddress(byte[] forwardingSecret) {
// Append forwarding secret as a BungeeGuard token. // Append forwarding secret as a BungeeGuard token.
Property property = new Property("bungeeguard-token", Property property =
new String(forwardingSecret, StandardCharsets.UTF_8), ""); new Property("bungeeguard-token", new String(forwardingSecret, StandardCharsets.UTF_8), "");
return createLegacyForwardingAddress(properties -> ImmutableList.<Property>builder() return createLegacyForwardingAddress(
.addAll(properties) properties -> ImmutableList.<Property>builder().addAll(properties).add(property).build());
.add(property)
.build());
} }
private void startHandshake() { private void startHandshake() {
@ -171,9 +174,9 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
// Initiate the handshake. // Initiate the handshake.
ProtocolVersion protocolVersion = proxyPlayer.getConnection().getProtocolVersion(); ProtocolVersion protocolVersion = proxyPlayer.getConnection().getProtocolVersion();
String playerVhost = proxyPlayer.getVirtualHost() String playerVhost =
.orElseGet(() -> registeredServer.getServerInfo().getAddress()) proxyPlayer.getVirtualHost().orElseGet(() -> registeredServer.getServerInfo().getAddress())
.getHostString(); .getHostString();
Handshake handshake = new Handshake(); Handshake handshake = new Handshake();
handshake.setNextStatus(StateRegistry.LOGIN_ID); handshake.setNextStatus(StateRegistry.LOGIN_ID);
@ -193,7 +196,7 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
mc.delayedWrite(handshake); mc.delayedWrite(handshake);
mc.setProtocolVersion(protocolVersion); mc.setProtocolVersion(protocolVersion);
mc.setState(StateRegistry.LOGIN); mc.setActiveSessionHandler(StateRegistry.LOGIN);
if (proxyPlayer.getIdentifiedKey() == null if (proxyPlayer.getIdentifiedKey() == null
&& proxyPlayer.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19_3) >= 0) { && proxyPlayer.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19_3) >= 0) {
mc.delayedWrite(new ServerLogin(proxyPlayer.getUsername(), proxyPlayer.getUniqueId())); mc.delayedWrite(new ServerLogin(proxyPlayer.getUsername(), proxyPlayer.getUniqueId()));

Datei anzeigen

@ -26,6 +26,7 @@ import com.velocitypowered.api.event.connection.PostLoginEvent;
import com.velocitypowered.api.event.permission.PermissionsSetupEvent; import com.velocitypowered.api.event.permission.PermissionsSetupEvent;
import com.velocitypowered.api.event.player.GameProfileRequestEvent; import com.velocitypowered.api.event.player.GameProfileRequestEvent;
import com.velocitypowered.api.event.player.PlayerChooseInitialServerEvent; import com.velocitypowered.api.event.player.PlayerChooseInitialServerEvent;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.permission.PermissionFunction; import com.velocitypowered.api.permission.PermissionFunction;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey; import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.RegisteredServer;
@ -38,6 +39,7 @@ import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.crypto.IdentifiedKeyImpl; import com.velocitypowered.proxy.crypto.IdentifiedKeyImpl;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.LoginAcknowledged;
import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess; import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess;
import com.velocitypowered.proxy.protocol.packet.SetCompression; import com.velocitypowered.proxy.protocol.packet.SetCompression;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
@ -64,6 +66,7 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
private GameProfile profile; private GameProfile profile;
private @MonotonicNonNull ConnectedPlayer connectedPlayer; private @MonotonicNonNull ConnectedPlayer connectedPlayer;
private final boolean onlineMode; private final boolean onlineMode;
private State loginState = State.START; // 1.20.2+
AuthSessionHandler(VelocityServer server, LoginInboundConnection inbound, AuthSessionHandler(VelocityServer server, LoginInboundConnection inbound,
GameProfile profile, boolean onlineMode) { GameProfile profile, boolean onlineMode) {
@ -95,8 +98,9 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
inbound.getIdentifiedKey()); inbound.getIdentifiedKey());
this.connectedPlayer = player; this.connectedPlayer = player;
if (!server.canRegisterConnection(player)) { if (!server.canRegisterConnection(player)) {
player.disconnect0(Component.translatable("velocity.error.already-connected-proxy", player.disconnect0(
NamedTextColor.RED), true); Component.translatable("velocity.error.already-connected-proxy", NamedTextColor.RED),
true);
return CompletableFuture.completedFuture(null); return CompletableFuture.completedFuture(null);
} }
@ -109,16 +113,14 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
// wait for permissions to load, then set the players permission function // wait for permissions to load, then set the players permission function
final PermissionFunction function = event.createFunction(player); final PermissionFunction function = event.createFunction(player);
if (function == null) { if (function == null) {
logger.error( logger.error("A plugin permission provider {} provided an invalid permission "
"A plugin permission provider {} provided an invalid permission function" + "function for player {}. This is a bug in the plugin, not in "
+ " for player {}. This is a bug in the plugin, not in Velocity. Falling" + "Velocity. Falling back to the default permission function.",
+ " back to the default permission function.", event.getProvider().getClass().getName(), player.getUsername());
event.getProvider().getClass().getName(),
player.getUsername());
} else { } else {
player.setPermissionFunction(function); player.setPermissionFunction(function);
} }
completeLoginProtocolPhaseAndInitialize(player); startLoginCompletion(player);
} }
}, mcConnection.eventLoop()); }, mcConnection.eventLoop());
}, mcConnection.eventLoop()).exceptionally((ex) -> { }, mcConnection.eventLoop()).exceptionally((ex) -> {
@ -127,7 +129,7 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
}); });
} }
private void completeLoginProtocolPhaseAndInitialize(ConnectedPlayer player) { private void startLoginCompletion(ConnectedPlayer player) {
int threshold = server.getConfiguration().getCompressionThreshold(); int threshold = server.getConfiguration().getCompressionThreshold();
if (threshold >= 0 && mcConnection.getProtocolVersion().compareTo(MINECRAFT_1_8) >= 0) { if (threshold >= 0 && mcConnection.getProtocolVersion().compareTo(MINECRAFT_1_8) >= 0) {
mcConnection.write(new SetCompression(threshold)); mcConnection.write(new SetCompression(threshold));
@ -165,64 +167,87 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
} }
} }
ServerLoginSuccess success = new ServerLoginSuccess(); completeLoginProtocolPhaseAndInitialize(player);
success.setUsername(player.getUsername()); }
success.setProperties(player.getGameProfileProperties());
success.setUuid(playerUniqueId);
mcConnection.write(success);
@Override
public boolean handle(LoginAcknowledged packet) {
if (loginState != State.SUCCESS_SENT) {
inbound.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_data"));
} else {
loginState = State.ACKNOWLEDGED;
mcConnection.setActiveSessionHandler(StateRegistry.CONFIG,
new ClientConfigSessionHandler(server, connectedPlayer));
server.getEventManager().fire(new PostLoginEvent(connectedPlayer))
.thenCompose((ignored) -> connectToInitialServer(connectedPlayer)).exceptionally((ex) -> {
logger.error("Exception while connecting {} to initial server", connectedPlayer, ex);
return null;
});
}
return true;
}
private void completeLoginProtocolPhaseAndInitialize(ConnectedPlayer player) {
mcConnection.setAssociation(player); mcConnection.setAssociation(player);
mcConnection.setState(StateRegistry.PLAY);
server.getEventManager().fire(new LoginEvent(player)) server.getEventManager().fire(new LoginEvent(player)).thenAcceptAsync(event -> {
.thenAcceptAsync(event -> { if (mcConnection.isClosed()) {
if (mcConnection.isClosed()) { // The player was disconnected
// The player was disconnected server.getEventManager().fireAndForget(new DisconnectEvent(player,
server.getEventManager().fireAndForget(new DisconnectEvent(player, DisconnectEvent.LoginStatus.CANCELLED_BY_USER_BEFORE_COMPLETE));
DisconnectEvent.LoginStatus.CANCELLED_BY_USER_BEFORE_COMPLETE)); return;
return; }
}
Optional<Component> reason = event.getResult().getReasonComponent(); Optional<Component> reason = event.getResult().getReasonComponent();
if (reason.isPresent()) { if (reason.isPresent()) {
player.disconnect0(reason.get(), true); player.disconnect0(reason.get(), true);
} else { } else {
if (!server.registerConnection(player)) { if (!server.registerConnection(player)) {
player.disconnect0(Component.translatable("velocity.error.already-connected-proxy"), player.disconnect0(Component.translatable("velocity.error.already-connected-proxy"),
true); true);
return; return;
} }
mcConnection.setSessionHandler(new InitialConnectSessionHandler(player, server)); ServerLoginSuccess success = new ServerLoginSuccess();
server.getEventManager().fire(new PostLoginEvent(player)) success.setUsername(player.getUsername());
.thenCompose((ignored) -> connectToInitialServer(player)) success.setProperties(player.getGameProfileProperties());
.exceptionally((ex) -> { success.setUuid(player.getUniqueId());
logger.error("Exception while connecting {} to initial server", player, ex); mcConnection.write(success);
return null;
}); loginState = State.SUCCESS_SENT;
} if (inbound.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) < 0) {
}, mcConnection.eventLoop()) loginState = State.ACKNOWLEDGED;
.exceptionally((ex) -> { mcConnection.setActiveSessionHandler(StateRegistry.PLAY,
logger.error("Exception while completing login initialisation phase for {}", player, ex); new InitialConnectSessionHandler(player, server));
return null; server.getEventManager().fire(new PostLoginEvent(player))
}); .thenCompose((ignored) -> connectToInitialServer(player)).exceptionally((ex) -> {
logger.error("Exception while connecting {} to initial server", player, ex);
return null;
});
}
}
}, mcConnection.eventLoop()).exceptionally((ex) -> {
logger.error("Exception while completing login initialisation phase for {}", player, ex);
return null;
});
} }
private CompletableFuture<Void> connectToInitialServer(ConnectedPlayer player) { private CompletableFuture<Void> connectToInitialServer(ConnectedPlayer player) {
Optional<RegisteredServer> initialFromConfig = player.getNextServerToTry(); Optional<RegisteredServer> initialFromConfig = player.getNextServerToTry();
PlayerChooseInitialServerEvent event = new PlayerChooseInitialServerEvent(player, PlayerChooseInitialServerEvent event =
initialFromConfig.orElse(null)); new PlayerChooseInitialServerEvent(player, initialFromConfig.orElse(null));
return server.getEventManager().fire(event) return server.getEventManager().fire(event).thenRunAsync(() -> {
.thenRunAsync(() -> { Optional<RegisteredServer> toTry = event.getInitialServer();
Optional<RegisteredServer> toTry = event.getInitialServer(); if (!toTry.isPresent()) {
if (!toTry.isPresent()) { player.disconnect0(
player.disconnect0(Component.translatable("velocity.error.no-available-servers", Component.translatable("velocity.error.no-available-servers", NamedTextColor.RED),
NamedTextColor.RED), true); true);
return; return;
} }
player.createConnectionRequest(toTry.get()).fireAndForget(); player.createConnectionRequest(toTry.get()).fireAndForget();
}, mcConnection.eventLoop()); }, mcConnection.eventLoop());
} }
@Override @Override
@ -237,4 +262,8 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
} }
this.inbound.cleanup(); this.inbound.cleanup();
} }
static enum State {
START, SUCCESS_SENT, ACKNOWLEDGED
}
} }

Datei anzeigen

@ -0,0 +1,161 @@
/*
* Copyright (C) 2018-2023 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.connection.client;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.ClientSettings;
import com.velocitypowered.proxy.protocol.packet.KeepAlive;
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
import com.velocitypowered.proxy.protocol.packet.ResourcePackResponse;
import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdate;
import io.netty.buffer.ByteBuf;
import java.util.concurrent.CompletableFuture;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* Handles the client config stage.
*/
public class ClientConfigSessionHandler implements MinecraftSessionHandler {
private static final Logger logger = LogManager.getLogger(ClientConfigSessionHandler.class);
private final VelocityServer server;
private final ConnectedPlayer player;
private CompletableFuture<Void> configSwitchFuture;
/**
* Constructs a client config session handler.
*
* @param server the Velocity server instance
* @param player the player
*/
public ClientConfigSessionHandler(VelocityServer server, ConnectedPlayer player) {
this.server = server;
this.player = player;
}
@Override
public void activated() {
configSwitchFuture = new CompletableFuture<>();
}
@Override
public void deactivated() {
}
@Override
public boolean handle(KeepAlive packet) {
VelocityServerConnection serverConnection = player.getConnectedServer();
if (serverConnection != null) {
Long sentTime = serverConnection.getPendingPings().remove(packet.getRandomId());
if (sentTime != null) {
MinecraftConnection smc = serverConnection.getConnection();
if (smc != null) {
player.setPing(System.currentTimeMillis() - sentTime);
smc.write(packet);
}
}
}
return true;
}
@Override
public boolean handle(ClientSettings packet) {
player.setClientSettingsPacket(packet);
return true;
}
@Override
public boolean handle(ResourcePackResponse packet) {
if (player.getConnectionInFlight() != null) {
player.getConnectionInFlight().ensureConnected().write(packet);
}
return player.onResourcePackResponse(packet.getStatus());
}
@Override
public boolean handle(FinishedUpdate packet) {
player.getConnection()
.setActiveSessionHandler(StateRegistry.PLAY, new ClientPlaySessionHandler(server, player));
configSwitchFuture.complete(null);
return true;
}
@Override
public void handleGeneric(MinecraftPacket packet) {
VelocityServerConnection serverConnection = player.getConnectedServer();
if (serverConnection == null) {
// No server connection yet, probably transitioning.
return;
}
MinecraftConnection smc = serverConnection.getConnection();
if (smc != null && serverConnection.getPhase().consideredComplete()) {
if (packet instanceof PluginMessage) {
((PluginMessage) packet).retain();
}
smc.write(packet);
}
}
@Override
public void handleUnknown(ByteBuf buf) {
VelocityServerConnection serverConnection = player.getConnectedServer();
if (serverConnection == null) {
// No server connection yet, probably transitioning.
return;
}
MinecraftConnection smc = serverConnection.getConnection();
if (smc != null && !smc.isClosed() && serverConnection.getPhase().consideredComplete()) {
smc.write(buf.retain());
}
}
@Override
public void disconnected() {
player.teardown();
}
@Override
public void exception(Throwable throwable) {
player.disconnect(
Component.translatable("velocity.error.player-connection-error", NamedTextColor.RED));
}
/**
* Handles the backend finishing the config stage.
*
* @param serverConn the server connection
* @return a future that completes when the config stage is finished
*/
public CompletableFuture<Void> handleBackendFinishUpdate(VelocityServerConnection serverConn) {
player.getConnection().write(new FinishedUpdate());
serverConn.ensureConnected().write(new FinishedUpdate());
return configSwitchFuture;
}
}

Datei anzeigen

@ -17,9 +17,6 @@
package com.velocitypowered.proxy.connection.client; package com.velocitypowered.proxy.connection.client;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_13;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_16;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8;
import static com.velocitypowered.proxy.protocol.util.PluginMessageUtil.constructChannelsPacket; import static com.velocitypowered.proxy.protocol.util.PluginMessageUtil.constructChannelsPacket;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
@ -67,6 +64,7 @@ import com.velocitypowered.proxy.protocol.packet.chat.session.SessionChatHandler
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionCommandHandler; import com.velocitypowered.proxy.protocol.packet.chat.session.SessionCommandHandler;
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerChat; import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerChat;
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerCommand; import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerCommand;
import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdate;
import com.velocitypowered.proxy.protocol.packet.title.GenericTitlePacket; import com.velocitypowered.proxy.protocol.packet.title.GenericTitlePacket;
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil; import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
import com.velocitypowered.proxy.util.CharacterUtil; import com.velocitypowered.proxy.util.CharacterUtil;
@ -80,6 +78,7 @@ import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Queue; import java.util.Queue;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
@ -105,6 +104,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
private final CommandHandler<? extends MinecraftPacket> commandHandler; private final CommandHandler<? extends MinecraftPacket> commandHandler;
private final ChatTimeKeeper timeKeeper = new ChatTimeKeeper(); private final ChatTimeKeeper timeKeeper = new ChatTimeKeeper();
private CompletableFuture<Void> configSwitchFuture;
/** /**
* Constructs a client play session handler. * Constructs a client play session handler.
* *
@ -151,8 +152,9 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
@Override @Override
public void activated() { public void activated() {
Collection<String> channels = server.getChannelRegistrar() configSwitchFuture = new CompletableFuture<>();
.getChannelsForProtocol(player.getProtocolVersion()); Collection<String> channels =
server.getChannelRegistrar().getChannelsForProtocol(player.getProtocolVersion());
if (!channels.isEmpty()) { if (!channels.isEmpty()) {
PluginMessage register = constructChannelsPacket(player.getProtocolVersion(), channels); PluginMessage register = constructChannelsPacket(player.getProtocolVersion(), channels);
player.getConnection().write(register); player.getConnection().write(register);
@ -185,7 +187,13 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
@Override @Override
public boolean handle(ClientSettings packet) { public boolean handle(ClientSettings packet) {
player.setPlayerSettings(packet); player.setPlayerSettings(packet);
return false; // will forward onto the server VelocityServerConnection serverConnection = player.getConnectedServer();
if (serverConnection == null) {
// No server connection yet, probably transitioning.
return true;
}
player.getConnectedServer().ensureConnected().write(packet);
return true; // will forward onto the server
} }
@Override @Override
@ -288,10 +296,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
MinecraftConnection backendConn = serverConn != null ? serverConn.getConnection() : null; MinecraftConnection backendConn = serverConn != null ? serverConn.getConnection() : null;
if (serverConn != null && backendConn != null) { if (serverConn != null && backendConn != null) {
if (backendConn.getState() != StateRegistry.PLAY) { if (backendConn.getState() != StateRegistry.PLAY) {
logger.warn( logger.warn("A plugin message was received while the backend server was not "
"A plugin message was received while the backend server was not " + "ready. Channel: {}. Packet discarded.", packet.getChannel());
+ "ready. Channel: {}. Packet discarded.",
packet.getChannel());
} else if (PluginMessageUtil.isRegister(packet)) { } else if (PluginMessageUtil.isRegister(packet)) {
List<String> channels = PluginMessageUtil.getChannels(packet); List<String> channels = PluginMessageUtil.getChannels(packet);
List<ChannelIdentifier> channelIdentifiers = new ArrayList<>(); List<ChannelIdentifier> channelIdentifiers = new ArrayList<>();
@ -377,6 +383,26 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
return player.onResourcePackResponse(packet.getStatus()); return player.onResourcePackResponse(packet.getStatus());
} }
@Override
public boolean handle(FinishedUpdate packet) {
// Complete client switch
player.getConnection().setActiveSessionHandler(StateRegistry.CONFIG);
VelocityServerConnection serverConnection = player.getConnectedServer();
if (serverConnection != null) {
MinecraftConnection smc = serverConnection.ensureConnected();
CompletableFuture.runAsync(() -> {
smc.write(packet);
smc.setActiveSessionHandler(StateRegistry.CONFIG);
smc.setAutoReading(true);
}, smc.eventLoop()).exceptionally((ex) -> {
logger.error("Error forwarding config state acknowledgement to server:", ex);
return null;
});
}
configSwitchFuture.complete(null);
return true;
}
@Override @Override
public void handleGeneric(MinecraftPacket packet) { public void handleGeneric(MinecraftPacket packet) {
VelocityServerConnection serverConnection = player.getConnectedServer(); VelocityServerConnection serverConnection = player.getConnectedServer();
@ -440,6 +466,33 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
} }
} }
/**
* Handles switching stages for swapping between servers.
*
* @return a future that completes when the switch is complete
*/
public CompletableFuture<Void> doSwitch() {
VelocityServerConnection existingConnection = player.getConnectedServer();
if (existingConnection != null) {
// Shut down the existing server connection.
player.setConnectedServer(null);
existingConnection.disconnect();
// Send keep alive to try to avoid timeouts
player.sendKeepAlive();
// Reset Tablist header and footer to prevent desync
player.clearHeaderAndFooter();
}
spawned = false;
player.switchToConfigState();
return configSwitchFuture;
}
/** /**
* Handles the {@code JoinGame} packet. This function is responsible for handling the client-side * Handles the {@code JoinGame} packet. This function is responsible for handling the client-side
* switching servers in Velocity. * switching servers in Velocity.
@ -482,14 +535,6 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
} }
serverBossBars.clear(); serverBossBars.clear();
// Tell the server about the proxy's plugin message channels.
ProtocolVersion serverVersion = serverMc.getProtocolVersion();
final Collection<String> channels = server.getChannelRegistrar()
.getChannelsForProtocol(serverMc.getProtocolVersion());
if (!channels.isEmpty()) {
serverMc.delayedWrite(constructChannelsPacket(serverVersion, channels));
}
// If we had plugin messages queued during login/FML handshake, send them now. // If we had plugin messages queued during login/FML handshake, send them now.
PluginMessage pm; PluginMessage pm;
while ((pm = loginPluginMessages.poll()) != null) { while ((pm = loginPluginMessages.poll()) != null) {
@ -497,7 +542,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
} }
// Clear any title from the previous server. // Clear any title from the previous server.
if (player.getProtocolVersion().compareTo(MINECRAFT_1_8) >= 0) { if (player.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
player.getConnection().delayedWrite( player.getConnection().delayedWrite(
GenericTitlePacket.constructTitlePacket(GenericTitlePacket.ActionType.RESET, GenericTitlePacket.constructTitlePacket(GenericTitlePacket.ActionType.RESET,
player.getProtocolVersion())); player.getProtocolVersion()));
@ -520,7 +565,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
// improving compatibility with mods. // improving compatibility with mods.
final Respawn respawn = Respawn.fromJoinGame(joinGame); final Respawn respawn = Respawn.fromJoinGame(joinGame);
if (player.getProtocolVersion().compareTo(MINECRAFT_1_16) < 0) { if (player.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_16) < 0) {
// Before Minecraft 1.16, we could not switch to the same dimension without sending an // Before Minecraft 1.16, we could not switch to the same dimension without sending an
// additional respawn. On older versions of Minecraft this forces the client to perform // additional respawn. On older versions of Minecraft this forces the client to perform
// garbage collection which adds additional latency. // garbage collection which adds additional latency.
@ -562,7 +607,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
String commandLabel = command.substring(0, commandEndPosition); String commandLabel = command.substring(0, commandEndPosition);
if (!server.getCommandManager().hasCommand(commandLabel)) { if (!server.getCommandManager().hasCommand(commandLabel)) {
if (player.getProtocolVersion().compareTo(MINECRAFT_1_13) < 0) { if (player.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_13) < 0) {
// Outstanding tab completes are recorded for use with 1.12 clients and below to provide // Outstanding tab completes are recorded for use with 1.12 clients and below to provide
// additional tab completion support. // additional tab completion support.
outstandingTabComplete = packet; outstandingTabComplete = packet;
@ -604,7 +649,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
} }
private boolean handleRegularTabComplete(TabCompleteRequest packet) { private boolean handleRegularTabComplete(TabCompleteRequest packet) {
if (player.getProtocolVersion().compareTo(MINECRAFT_1_13) < 0) { if (player.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_13) < 0) {
// Outstanding tab completes are recorded for use with 1.12 clients and below to provide // Outstanding tab completes are recorded for use with 1.12 clients and below to provide
// additional tab completion support. // additional tab completion support.
outstandingTabComplete = packet; outstandingTabComplete = packet;
@ -635,7 +680,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
String command = request.getCommand().substring(1); String command = request.getCommand().substring(1);
server.getCommandManager().offerBrigadierSuggestions(player, command) server.getCommandManager().offerBrigadierSuggestions(player, command)
.thenAcceptAsync(offers -> { .thenAcceptAsync(offers -> {
boolean legacy = player.getProtocolVersion().compareTo(MINECRAFT_1_13) < 0; boolean legacy =
player.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_13) < 0;
try { try {
for (Suggestion suggestion : offers.getList()) { for (Suggestion suggestion : offers.getList()) {
String offer = suggestion.getText(); String offer = suggestion.getText();
@ -659,9 +705,9 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
} }
}, player.getConnection().eventLoop()).exceptionally((ex) -> { }, player.getConnection().eventLoop()).exceptionally((ex) -> {
logger.error( logger.error(
"Exception while finishing command tab completion, with request {} and response {}", "Exception while finishing command tab completion,"
request, + " with request {} and response {}",
response, ex); request, response, ex);
return null; return null;
}); });
} }
@ -680,9 +726,9 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
player.getConnection().write(response); player.getConnection().write(response);
}, player.getConnection().eventLoop()).exceptionally((ex) -> { }, player.getConnection().eventLoop()).exceptionally((ex) -> {
logger.error( logger.error(
"Exception while finishing regular tab completion, with request {} and response{}", "Exception while finishing regular tab completion,"
request, + " with request {} and response{}",
response, ex); request, response, ex);
return null; return null;
}); });
} }
@ -702,5 +748,4 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
} }
} }
} }
} }

Datei anzeigen

@ -59,6 +59,7 @@ import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
import com.velocitypowered.proxy.connection.util.VelocityInboundConnection; import com.velocitypowered.proxy.connection.util.VelocityInboundConnection;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
import com.velocitypowered.proxy.protocol.packet.ClientSettings; import com.velocitypowered.proxy.protocol.packet.ClientSettings;
import com.velocitypowered.proxy.protocol.packet.Disconnect; import com.velocitypowered.proxy.protocol.packet.Disconnect;
import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter; import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter;
@ -69,6 +70,7 @@ import com.velocitypowered.proxy.protocol.packet.chat.ChatQueue;
import com.velocitypowered.proxy.protocol.packet.chat.ChatType; import com.velocitypowered.proxy.protocol.packet.chat.ChatType;
import com.velocitypowered.proxy.protocol.packet.chat.builder.ChatBuilderFactory; import com.velocitypowered.proxy.protocol.packet.chat.builder.ChatBuilderFactory;
import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyChat; import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyChat;
import com.velocitypowered.proxy.protocol.packet.config.StartUpdate;
import com.velocitypowered.proxy.protocol.packet.title.GenericTitlePacket; import com.velocitypowered.proxy.protocol.packet.title.GenericTitlePacket;
import com.velocitypowered.proxy.server.VelocityRegisteredServer; import com.velocitypowered.proxy.server.VelocityRegisteredServer;
import com.velocitypowered.proxy.tablist.InternalTabList; import com.velocitypowered.proxy.tablist.InternalTabList;
@ -123,12 +125,9 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
private static final int MAX_PLUGIN_CHANNELS = 1024; private static final int MAX_PLUGIN_CHANNELS = 1024;
private static final PlainTextComponentSerializer PASS_THRU_TRANSLATE = private static final PlainTextComponentSerializer PASS_THRU_TRANSLATE =
PlainTextComponentSerializer.builder() PlainTextComponentSerializer.builder().flattener(
.flattener(ComponentFlattener.basic().toBuilder() ComponentFlattener.basic().toBuilder().mapper(KeybindComponent.class, c -> "")
.mapper(KeybindComponent.class, c -> "") .mapper(TranslatableComponent.class, TranslatableComponent::key).build()).build();
.mapper(TranslatableComponent.class, TranslatableComponent::key)
.build())
.build();
static final PermissionProvider DEFAULT_PERMISSIONS = s -> PermissionFunction.ALWAYS_UNDEFINED; static final PermissionProvider DEFAULT_PERMISSIONS = s -> PermissionFunction.ALWAYS_UNDEFINED;
private static final Logger logger = LogManager.getLogger(ConnectedPlayer.class); private static final Logger logger = LogManager.getLogger(ConnectedPlayer.class);
@ -159,17 +158,17 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
private final Queue<ResourcePackInfo> outstandingResourcePacks = new ArrayDeque<>(); private final Queue<ResourcePackInfo> outstandingResourcePacks = new ArrayDeque<>();
private @Nullable ResourcePackInfo pendingResourcePack; private @Nullable ResourcePackInfo pendingResourcePack;
private @Nullable ResourcePackInfo appliedResourcePack; private @Nullable ResourcePackInfo appliedResourcePack;
private final @NotNull Pointers pointers = Player.super.pointers().toBuilder() private final @NotNull Pointers pointers =
.withDynamic(Identity.UUID, this::getUniqueId) Player.super.pointers().toBuilder().withDynamic(Identity.UUID, this::getUniqueId)
.withDynamic(Identity.NAME, this::getUsername) .withDynamic(Identity.NAME, this::getUsername)
.withDynamic(Identity.DISPLAY_NAME, () -> Component.text(this.getUsername())) .withDynamic(Identity.DISPLAY_NAME, () -> Component.text(this.getUsername()))
.withDynamic(Identity.LOCALE, this::getEffectiveLocale) .withDynamic(Identity.LOCALE, this::getEffectiveLocale)
.withStatic(PermissionChecker.POINTER, getPermissionChecker()) .withStatic(PermissionChecker.POINTER, getPermissionChecker())
.withStatic(FacetPointers.TYPE, Type.PLAYER) .withStatic(FacetPointers.TYPE, Type.PLAYER).build();
.build();
private @Nullable String clientBrand; private @Nullable String clientBrand;
private @Nullable Locale effectiveLocale; private @Nullable Locale effectiveLocale;
private @Nullable IdentifiedKey playerKey; private @Nullable IdentifiedKey playerKey;
private @Nullable ClientSettings clientSettingsPacket;
private final ChatQueue chatQueue; private final ChatQueue chatQueue;
private final ChatBuilderFactory chatBuilderFactory; private final ChatBuilderFactory chatBuilderFactory;
@ -278,11 +277,19 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
return settings == null ? ClientSettingsWrapper.DEFAULT : this.settings; return settings == null ? ClientSettingsWrapper.DEFAULT : this.settings;
} }
public ClientSettings getClientSettingsPacket() {
return clientSettingsPacket;
}
@Override @Override
public boolean hasSentPlayerSettings() { public boolean hasSentPlayerSettings() {
return settings != null; return settings != null;
} }
public void setClientSettingsPacket(ClientSettings clientSettingsPacket) {
this.clientSettingsPacket = clientSettingsPacket;
}
void setPlayerSettings(ClientSettings settings) { void setPlayerSettings(ClientSettings settings) {
ClientSettingsWrapper cs = new ClientSettingsWrapper(settings); ClientSettingsWrapper cs = new ClientSettingsWrapper(settings);
this.settings = cs; this.settings = cs;
@ -674,8 +681,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
ServerKickResult result; ServerKickResult result;
if (kickedFromCurrent) { if (kickedFromCurrent) {
Optional<RegisteredServer> next = getNextServerToTry(rs); Optional<RegisteredServer> next = getNextServerToTry(rs);
result = next.map(RedirectPlayer::create) result =
.orElseGet(() -> DisconnectPlayer.create(friendlyReason)); next.map(RedirectPlayer::create).orElseGet(() -> DisconnectPlayer.create(friendlyReason));
} else { } else {
// If we were kicked by going to another server, the connection should not be in flight // If we were kicked by going to another server, the connection should not be in flight
if (connectionInFlight != null && connectionInFlight.getServer().equals(rs)) { if (connectionInFlight != null && connectionInFlight.getServer().equals(rs)) {
@ -689,86 +696,83 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
} }
private void handleKickEvent(KickedFromServerEvent originalEvent, Component friendlyReason, private void handleKickEvent(KickedFromServerEvent originalEvent, Component friendlyReason,
boolean kickedFromCurrent) { boolean kickedFromCurrent) {
server.getEventManager().fire(originalEvent) server.getEventManager().fire(originalEvent).thenAcceptAsync(event -> {
.thenAcceptAsync(event -> { // There can't be any connection in flight now.
// There can't be any connection in flight now. connectionInFlight = null;
connectionInFlight = null;
// Make sure we clear the current connected server as the connection is invalid. // Make sure we clear the current connected server as the connection is invalid.
VelocityServerConnection previousConnection = connectedServer; VelocityServerConnection previousConnection = connectedServer;
if (kickedFromCurrent) { if (kickedFromCurrent) {
connectedServer = null; connectedServer = null;
} }
if (!isActive()) { if (!isActive()) {
// If the connection is no longer active, it makes no sense to try and recover it. // If the connection is no longer active, it makes no sense to try and recover it.
return; return;
} }
if (event.getResult() instanceof DisconnectPlayer) { if (event.getResult() instanceof DisconnectPlayer) {
DisconnectPlayer res = (DisconnectPlayer) event.getResult(); DisconnectPlayer res = (DisconnectPlayer) event.getResult();
disconnect(res.getReasonComponent()); disconnect(res.getReasonComponent());
} else if (event.getResult() instanceof RedirectPlayer) { } else if (event.getResult() instanceof RedirectPlayer) {
RedirectPlayer res = (RedirectPlayer) event.getResult(); RedirectPlayer res = (RedirectPlayer) event.getResult();
createConnectionRequest(res.getServer(), previousConnection) createConnectionRequest(res.getServer(), previousConnection).connect()
.connect() .whenCompleteAsync((status, throwable) -> {
.whenCompleteAsync((status, throwable) -> { if (throwable != null) {
if (throwable != null) { handleConnectionException(
handleConnectionException(status != null ? status.getAttemptedConnection() status != null ? status.getAttemptedConnection() : res.getServer(), throwable,
: res.getServer(), throwable, true); true);
return; return;
}
switch (status.getStatus()) {
// Impossible/nonsensical cases
case ALREADY_CONNECTED:
logger.error("{}: already connected to {}", this,
status.getAttemptedConnection().getServerInfo().getName());
break;
case CONNECTION_IN_PROGRESS:
// Fatal case
case CONNECTION_CANCELLED:
Component fallbackMsg = res.getMessageComponent();
if (fallbackMsg == null) {
fallbackMsg = friendlyReason;
} }
disconnect(status.getReasonComponent().orElse(fallbackMsg));
switch (status.getStatus()) { break;
// Impossible/nonsensical cases case SERVER_DISCONNECTED:
case ALREADY_CONNECTED: Component reason = status.getReasonComponent()
logger.error("{}: already connected to {}", .orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR);
this, handleConnectionException(res.getServer(),
status.getAttemptedConnection().getServerInfo().getName() Disconnect.create(reason, getProtocolVersion()), ((Impl) status).isSafe());
); break;
break; case SUCCESS:
case CONNECTION_IN_PROGRESS: Component requestedMessage = res.getMessageComponent();
// Fatal case if (requestedMessage == null) {
case CONNECTION_CANCELLED: requestedMessage = friendlyReason;
Component fallbackMsg = res.getMessageComponent();
if (fallbackMsg == null) {
fallbackMsg = friendlyReason;
}
disconnect(status.getReasonComponent().orElse(fallbackMsg));
break;
case SERVER_DISCONNECTED:
Component reason = status.getReasonComponent()
.orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR);
handleConnectionException(res.getServer(), Disconnect.create(reason,
getProtocolVersion()), ((Impl) status).isSafe());
break;
case SUCCESS:
Component requestedMessage = res.getMessageComponent();
if (requestedMessage == null) {
requestedMessage = friendlyReason;
}
if (requestedMessage != Component.empty()) {
sendMessage(requestedMessage);
}
break;
default:
// The only remaining value is successful (no need to do anything!)
break;
} }
}, connection.eventLoop()); if (requestedMessage != Component.empty()) {
} else if (event.getResult() instanceof Notify) { sendMessage(requestedMessage);
Notify res = (Notify) event.getResult(); }
if (event.kickedDuringServerConnect() && previousConnection != null) { break;
sendMessage(Identity.nil(), res.getMessageComponent()); default:
} else { // The only remaining value is successful (no need to do anything!)
disconnect(res.getMessageComponent()); break;
} }
} else { }, connection.eventLoop());
// In case someone gets creative, assume we want to disconnect the player. } else if (event.getResult() instanceof Notify) {
disconnect(friendlyReason); Notify res = (Notify) event.getResult();
} if (event.kickedDuringServerConnect() && previousConnection != null) {
}, connection.eventLoop()); sendMessage(Identity.nil(), res.getMessageComponent());
} else {
disconnect(res.getMessageComponent());
}
} else {
// In case someone gets creative, assume we want to disconnect the player.
disconnect(friendlyReason);
}
}, connection.eventLoop());
} }
/** /**
@ -1021,6 +1025,13 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
return pendingResourcePack; return pendingResourcePack;
} }
/**
* Clears the applied resource pack field.
*/
public void clearAppliedResourcePack() {
appliedResourcePack = null;
}
/** /**
* Processes a client response to a sent resource-pack. * Processes a client response to a sent resource-pack.
*/ */
@ -1068,18 +1079,42 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
&& queued.getOriginalOrigin() != ResourcePackInfo.Origin.DOWNSTREAM_SERVER; && queued.getOriginalOrigin() != ResourcePackInfo.Origin.DOWNSTREAM_SERVER;
} }
/**
* Gives an indication about the previous resource pack responses.
*/
public @Nullable Boolean getPreviousResourceResponse() {
return previousResourceResponse;
}
/** /**
* Sends a {@link KeepAlive} packet to the player with a random ID. The response will be ignored * Sends a {@link KeepAlive} packet to the player with a random ID. The response will be ignored
* by Velocity as it will not match the ID last sent by the server. * by Velocity as it will not match the ID last sent by the server.
*/ */
public void sendKeepAlive() { public void sendKeepAlive() {
if (connection.getState() == StateRegistry.PLAY) { if (connection.getState() == StateRegistry.PLAY
|| connection.getState() == StateRegistry.CONFIG) {
KeepAlive keepAlive = new KeepAlive(); KeepAlive keepAlive = new KeepAlive();
keepAlive.setRandomId(ThreadLocalRandom.current().nextLong()); keepAlive.setRandomId(ThreadLocalRandom.current().nextLong());
connection.write(keepAlive); connection.write(keepAlive);
} }
} }
/**
* Switches the connection to the client into config state.
*/
public void switchToConfigState() {
CompletableFuture.runAsync(() -> {
connection.write(new StartUpdate());
connection.getChannel().pipeline()
.get(MinecraftEncoder.class).setState(StateRegistry.CONFIG);
// Make sure we don't send any play packets to the player after update start
connection.addPlayPacketQueueHandler();
}, connection.eventLoop()).exceptionally((ex) -> {
logger.error("Error switching player connection to config state:", ex);
return null;
});
}
/** /**
* Gets the current "phase" of the connection, mostly used for tracking modded negotiation for * Gets the current "phase" of the connection, mostly used for tracking modded negotiation for
* legacy forge servers and provides methods for performing phase specific actions. * legacy forge servers and provides methods for performing phase specific actions.
@ -1147,37 +1182,34 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
} }
private CompletableFuture<Impl> internalConnect() { private CompletableFuture<Impl> internalConnect() {
return this.getInitialStatus() return this.getInitialStatus().thenCompose(initialCheck -> {
.thenCompose(initialCheck -> { if (initialCheck.isPresent()) {
if (initialCheck.isPresent()) { return completedFuture(plainResult(initialCheck.get(), toConnect));
return completedFuture(plainResult(initialCheck.get(), toConnect)); }
}
ServerPreConnectEvent event = new ServerPreConnectEvent(ConnectedPlayer.this, ServerPreConnectEvent event =
toConnect, previousServer); new ServerPreConnectEvent(ConnectedPlayer.this, toConnect, previousServer);
return server.getEventManager().fire(event) return server.getEventManager().fire(event).thenComposeAsync(newEvent -> {
.thenComposeAsync(newEvent -> { Optional<RegisteredServer> newDest = newEvent.getResult().getServer();
Optional<RegisteredServer> newDest = newEvent.getResult().getServer(); if (!newDest.isPresent()) {
if (!newDest.isPresent()) { return completedFuture(
return completedFuture( plainResult(ConnectionRequestBuilder.Status.CONNECTION_CANCELLED, toConnect));
plainResult(ConnectionRequestBuilder.Status.CONNECTION_CANCELLED, toConnect) }
);
}
RegisteredServer realDestination = newDest.get(); RegisteredServer realDestination = newDest.get();
Optional<ConnectionRequestBuilder.Status> check = checkServer(realDestination); Optional<ConnectionRequestBuilder.Status> check = checkServer(realDestination);
if (check.isPresent()) { if (check.isPresent()) {
return completedFuture(plainResult(check.get(), realDestination)); return completedFuture(plainResult(check.get(), realDestination));
} }
VelocityRegisteredServer vrs = (VelocityRegisteredServer) realDestination; VelocityRegisteredServer vrs = (VelocityRegisteredServer) realDestination;
VelocityServerConnection con = new VelocityServerConnection(vrs, VelocityServerConnection con =
previousServer, ConnectedPlayer.this, server); new VelocityServerConnection(vrs, previousServer, ConnectedPlayer.this, server);
connectionInFlight = con; connectionInFlight = con;
return con.connect().whenCompleteAsync( return con.connect().whenCompleteAsync((result, exception) -> this.resetIfInFlightIs(con),
(result, exception) -> this.resetIfInFlightIs(con), connection.eventLoop()); connection.eventLoop());
}, connection.eventLoop()); }, connection.eventLoop());
}); });
} }
private void resetIfInFlightIs(VelocityServerConnection establishedConnection) { private void resetIfInFlightIs(VelocityServerConnection establishedConnection) {
@ -1188,50 +1220,46 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
@Override @Override
public CompletableFuture<Result> connect() { public CompletableFuture<Result> connect() {
return this.internalConnect() return this.internalConnect().whenCompleteAsync((status, throwable) -> {
.whenCompleteAsync((status, throwable) -> { if (status != null && !status.isSuccessful()) {
if (status != null && !status.isSuccessful()) { if (!status.isSafe()) {
if (!status.isSafe()) { handleConnectionException(status.getAttemptedConnection(), throwable, false);
handleConnectionException(status.getAttemptedConnection(), throwable, false); }
} }
} }, connection.eventLoop()).thenApply(x -> x);
}, connection.eventLoop())
.thenApply(x -> x);
} }
@Override @Override
public CompletableFuture<Boolean> connectWithIndication() { public CompletableFuture<Boolean> connectWithIndication() {
return internalConnect() return internalConnect().whenCompleteAsync((status, throwable) -> {
.whenCompleteAsync((status, throwable) -> { if (throwable != null) {
if (throwable != null) { // TODO: The exception handling from this is not very good. Find a better way.
// TODO: The exception handling from this is not very good. Find a better way. handleConnectionException(status != null ? status.getAttemptedConnection() : toConnect,
handleConnectionException(status != null ? status.getAttemptedConnection() throwable, true);
: toConnect, throwable, true); return;
return; }
}
switch (status.getStatus()) { switch (status.getStatus()) {
case ALREADY_CONNECTED: case ALREADY_CONNECTED:
sendMessage(Identity.nil(), ConnectionMessages.ALREADY_CONNECTED); sendMessage(Identity.nil(), ConnectionMessages.ALREADY_CONNECTED);
break; break;
case CONNECTION_IN_PROGRESS: case CONNECTION_IN_PROGRESS:
sendMessage(Identity.nil(), ConnectionMessages.IN_PROGRESS); sendMessage(Identity.nil(), ConnectionMessages.IN_PROGRESS);
break; break;
case CONNECTION_CANCELLED: case CONNECTION_CANCELLED:
// Ignored; the plugin probably already handled this. // Ignored; the plugin probably already handled this.
break; break;
case SERVER_DISCONNECTED: case SERVER_DISCONNECTED:
Component reason = status.getReasonComponent() Component reason = status.getReasonComponent()
.orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR); .orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR);
handleConnectionException(toConnect, Disconnect.create(reason, handleConnectionException(toConnect, Disconnect.create(reason, getProtocolVersion()),
getProtocolVersion()), status.isSafe()); status.isSafe());
break; break;
default: default:
// The only remaining value is successful (no need to do anything!) // The only remaining value is successful (no need to do anything!)
break; break;
} }
}, connection.eventLoop()) }, connection.eventLoop()).thenApply(Result::isSuccessful);
.thenApply(Result::isSuccessful);
} }
@Override @Override

Datei anzeigen

@ -47,8 +47,8 @@ import org.checkerframework.checker.nullness.qual.Nullable;
/** /**
* The initial handler used when a connection is established to the proxy. This will either * The initial handler used when a connection is established to the proxy. This will either
* transition to {@link StatusSessionHandler} or {@link InitialLoginSessionHandler} as soon * transition to {@link StatusSessionHandler} or {@link InitialLoginSessionHandler} as soon as the
* as the handshake packet is received. * handshake packet is received.
*/ */
public class HandshakeSessionHandler implements MinecraftSessionHandler { public class HandshakeSessionHandler implements MinecraftSessionHandler {
@ -65,9 +65,9 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
@Override @Override
public boolean handle(LegacyPing packet) { public boolean handle(LegacyPing packet) {
connection.setProtocolVersion(ProtocolVersion.LEGACY); connection.setProtocolVersion(ProtocolVersion.LEGACY);
StatusSessionHandler handler = new StatusSessionHandler(server, StatusSessionHandler handler =
new LegacyInboundConnection(connection, packet)); new StatusSessionHandler(server, new LegacyInboundConnection(connection, packet));
connection.setSessionHandler(handler); connection.setActiveSessionHandler(StateRegistry.STATUS, handler);
handler.handle(packet); handler.handle(packet);
return true; return true;
} }
@ -90,13 +90,13 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
LOGGER.error("{} provided invalid protocol {}", ic, handshake.getNextStatus()); LOGGER.error("{} provided invalid protocol {}", ic, handshake.getNextStatus());
connection.close(true); connection.close(true);
} else { } else {
connection.setState(nextState);
connection.setProtocolVersion(handshake.getProtocolVersion()); connection.setProtocolVersion(handshake.getProtocolVersion());
connection.setAssociation(ic); connection.setAssociation(ic);
switch (nextState) { switch (nextState) {
case STATUS: case STATUS:
connection.setSessionHandler(new StatusSessionHandler(server, ic)); connection.setActiveSessionHandler(StateRegistry.STATUS,
new StatusSessionHandler(server, ic));
break; break;
case LOGIN: case LOGIN:
this.handleLogin(handshake, ic); this.handleLogin(handshake, ic);
@ -140,14 +140,15 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
// and lower, otherwise IP information will never get forwarded. // and lower, otherwise IP information will never get forwarded.
if (server.getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN if (server.getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN
&& handshake.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_13) < 0) { && handshake.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_13) < 0) {
ic.disconnectQuietly(Component.translatable( ic.disconnectQuietly(
"velocity.error.modern-forwarding-needs-new-client")); Component.translatable("velocity.error.modern-forwarding-needs-new-client"));
return; return;
} }
LoginInboundConnection lic = new LoginInboundConnection(ic); LoginInboundConnection lic = new LoginInboundConnection(ic);
server.getEventManager().fireAndForget(new ConnectionHandshakeEvent(lic)); server.getEventManager().fireAndForget(new ConnectionHandshakeEvent(lic));
connection.setSessionHandler(new InitialLoginSessionHandler(server, connection, lic)); connection.setActiveSessionHandler(StateRegistry.LOGIN,
new InitialLoginSessionHandler(server, connection, lic));
} }
private ConnectionType getHandshakeConnectionType(Handshake handshake) { private ConnectionType getHandshakeConnectionType(Handshake handshake) {

Datei anzeigen

@ -34,6 +34,7 @@ import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.crypto.IdentifiedKeyImpl; import com.velocitypowered.proxy.crypto.IdentifiedKeyImpl;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.packet.EncryptionRequest; import com.velocitypowered.proxy.protocol.packet.EncryptionRequest;
import com.velocitypowered.proxy.protocol.packet.EncryptionResponse; import com.velocitypowered.proxy.protocol.packet.EncryptionResponse;
@ -120,47 +121,45 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
this.login = packet; this.login = packet;
PreLoginEvent event = new PreLoginEvent(inbound, login.getUsername()); PreLoginEvent event = new PreLoginEvent(inbound, login.getUsername());
server.getEventManager().fire(event) server.getEventManager().fire(event).thenRunAsync(() -> {
.thenRunAsync(() -> { if (mcConnection.isClosed()) {
if (mcConnection.isClosed()) { // The player was disconnected
// The player was disconnected return;
return; }
PreLoginComponentResult result = event.getResult();
Optional<Component> disconnectReason = result.getReasonComponent();
if (disconnectReason.isPresent()) {
// The component is guaranteed to be provided if the connection was denied.
inbound.disconnect(disconnectReason.get());
return;
}
inbound.loginEventFired(() -> {
if (mcConnection.isClosed()) {
// The player was disconnected
return;
}
mcConnection.eventLoop().execute(() -> {
if (!result.isForceOfflineMode()
&& (server.getConfiguration().isOnlineMode() || result.isOnlineModeAllowed())) {
// Request encryption.
EncryptionRequest request = generateEncryptionRequest();
this.verify = Arrays.copyOf(request.getVerifyToken(), 4);
mcConnection.write(request);
this.currentState = LoginState.ENCRYPTION_REQUEST_SENT;
} else {
mcConnection.setActiveSessionHandler(StateRegistry.LOGIN,
new AuthSessionHandler(server, inbound,
GameProfile.forOfflinePlayer(login.getUsername()), false));
} }
PreLoginComponentResult result = event.getResult();
Optional<Component> disconnectReason = result.getReasonComponent();
if (disconnectReason.isPresent()) {
// The component is guaranteed to be provided if the connection was denied.
inbound.disconnect(disconnectReason.get());
return;
}
inbound.loginEventFired(() -> {
if (mcConnection.isClosed()) {
// The player was disconnected
return;
}
mcConnection.eventLoop().execute(() -> {
if (!result.isForceOfflineMode() && (server.getConfiguration().isOnlineMode()
|| result.isOnlineModeAllowed())) {
// Request encryption.
EncryptionRequest request = generateEncryptionRequest();
this.verify = Arrays.copyOf(request.getVerifyToken(), 4);
mcConnection.write(request);
this.currentState = LoginState.ENCRYPTION_REQUEST_SENT;
} else {
mcConnection.setSessionHandler(new AuthSessionHandler(
server, inbound, GameProfile.forOfflinePlayer(login.getUsername()), false
));
}
});
});
}, mcConnection.eventLoop())
.exceptionally((ex) -> {
logger.error("Exception in pre-login stage", ex);
return null;
}); });
});
}, mcConnection.eventLoop()).exceptionally((ex) -> {
logger.error("Exception in pre-login stage", ex);
return null;
});
return true; return true;
} }
@ -246,13 +245,12 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
} }
} }
// All went well, initialize the session. // All went well, initialize the session.
mcConnection.setSessionHandler(new AuthSessionHandler( mcConnection.setActiveSessionHandler(StateRegistry.LOGIN,
server, inbound, profile, true new AuthSessionHandler(server, inbound, profile, true));
));
} else if (profileResponse.getStatusCode() == 204) { } else if (profileResponse.getStatusCode() == 204) {
// Apparently an offline-mode user logged onto this online-mode proxy. // Apparently an offline-mode user logged onto this online-mode proxy.
inbound.disconnect(Component.translatable("velocity.error.online-mode-only", inbound.disconnect(
NamedTextColor.RED)); Component.translatable("velocity.error.online-mode-only", NamedTextColor.RED));
} else { } else {
// Something else went wrong // Something else went wrong
logger.error( logger.error(

Datei anzeigen

@ -76,9 +76,9 @@ public enum LegacyForgeHandshakeClientPhase implements ClientConnectionPhase {
}, },
/** /**
* The Mod list is sent to the server, captured by Velocity. Transition to * The Mod list is sent to the server, captured by Velocity. Transition to {@link
* {@link #WAITING_SERVER_DATA} when an ACK is sent, which indicates to the server to start * #WAITING_SERVER_DATA} when an ACK is sent, which indicates to the server to start sending state
* sending state data. * data.
*/ */
MOD_LIST(LegacyForgeConstants.ACK_DISCRIMINATOR) { MOD_LIST(LegacyForgeConstants.ACK_DISCRIMINATOR) {
@Override @Override
@ -138,11 +138,10 @@ public enum LegacyForgeHandshakeClientPhase implements ClientConnectionPhase {
/** /**
* The handshake is complete. The handshake can be reset. * The handshake is complete. The handshake can be reset.
* *
* <p>Note that a successful connection to a server does not mean that * <p>Note that a successful connection to a server does not mean that we will be in this state.
* we will be in this state. After a handshake reset, if the next server is vanilla we will still * After a handshake reset, if the next server is vanilla we will still be in the {@link
* be in the {@link #NOT_STARTED} phase, which means we must NOT send a reset packet. This is * #NOT_STARTED} phase, which means we must NOT send a reset packet. This is handled by overriding
* handled by overriding the {@link #resetConnectionPhase(ConnectedPlayer)} in this element (it is * the {@link #resetConnectionPhase(ConnectedPlayer)} in this element (it is usually a no-op).
* usually a no-op).</p>
*/ */
COMPLETE(null) { COMPLETE(null) {
@Override @Override
@ -165,7 +164,7 @@ public enum LegacyForgeHandshakeClientPhase implements ClientConnectionPhase {
// just in case the timing is awful // just in case the timing is awful
player.sendKeepAlive(); player.sendKeepAlive();
MinecraftSessionHandler handler = backendConn.getSessionHandler(); MinecraftSessionHandler handler = backendConn.getActiveSessionHandler();
if (handler instanceof ClientPlaySessionHandler) { if (handler instanceof ClientPlaySessionHandler) {
((ClientPlaySessionHandler) handler).flushQueuedMessages(); ((ClientPlaySessionHandler) handler).flushQueuedMessages();
} }
@ -182,8 +181,8 @@ public enum LegacyForgeHandshakeClientPhase implements ClientConnectionPhase {
* *
* @param packetToAdvanceOn The ID of the packet discriminator that indicates that the client has * @param packetToAdvanceOn The ID of the packet discriminator that indicates that the client has
* moved onto a new phase, and as such, Velocity should do so too * moved onto a new phase, and as such, Velocity should do so too
* (inspecting {@link #nextPhase()}. A null indicates there is no further * (inspecting {@link #nextPhase()}. A null indicates there is
* phase to transition to. * no further phase to transition to.
*/ */
LegacyForgeHandshakeClientPhase(Integer packetToAdvanceOn) { LegacyForgeHandshakeClientPhase(Integer packetToAdvanceOn) {
this.packetToAdvanceOn = packetToAdvanceOn; this.packetToAdvanceOn = packetToAdvanceOn;

Datei anzeigen

@ -0,0 +1,128 @@
/*
* Copyright (C) 2018-2023 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.connection.registry;
import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo;
import com.velocitypowered.proxy.protocol.packet.config.RegistrySync;
import net.kyori.adventure.key.Key;
import org.jetbrains.annotations.Nullable;
/**
* Holds the registry data that is sent
* to the client during the config stage.
*/
public class ClientConfigData {
private final @Nullable VelocityResourcePackInfo resourcePackInfo;
private final DataTag tag;
private final RegistrySync registry;
private final Key[] features;
private final String brand;
private ClientConfigData(@Nullable VelocityResourcePackInfo resourcePackInfo, DataTag tag,
RegistrySync registry, Key[] features, String brand) {
this.resourcePackInfo = resourcePackInfo;
this.tag = tag;
this.registry = registry;
this.features = features;
this.brand = brand;
}
public RegistrySync getRegistry() {
return registry;
}
public DataTag getTag() {
return tag;
}
public Key[] getFeatures() {
return features;
}
public @Nullable VelocityResourcePackInfo getResourcePackInfo() {
return resourcePackInfo;
}
public String getBrand() {
return brand;
}
/**
* Creates a new builder.
*
* @return ClientConfigData.Builder
*/
public static ClientConfigData.Builder builder() {
return new Builder();
}
/**
* Builder for ClientConfigData.
*/
public static class Builder {
private VelocityResourcePackInfo resourcePackInfo;
private DataTag tag;
private RegistrySync registry;
private Key[] features;
private String brand;
private Builder() {
}
/**
* Clears the builder.
*/
public void clear() {
this.resourcePackInfo = null;
this.tag = null;
this.registry = null;
this.features = null;
this.brand = null;
}
public Builder resourcePack(@Nullable VelocityResourcePackInfo resourcePackInfo) {
this.resourcePackInfo = resourcePackInfo;
return this;
}
public Builder dataTag(DataTag tag) {
this.tag = tag;
return this;
}
public Builder registry(RegistrySync registry) {
this.registry = registry;
return this;
}
public Builder features(Key[] features) {
this.features = features;
return this;
}
public Builder brand(String brand) {
this.brand = brand;
return this;
}
public ClientConfigData build() {
return new ClientConfigData(resourcePackInfo, tag, registry, features, brand);
}
}
}

Datei anzeigen

@ -0,0 +1,95 @@
/*
* Copyright (C) 2019-2023 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.connection.registry;
import com.google.common.collect.ImmutableList;
import java.util.List;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.key.Keyed;
import org.jetbrains.annotations.NotNull;
/**
* Represents a data tag.
*/
public class DataTag {
private final ImmutableList<DataTag.Set> entrySets;
public DataTag(ImmutableList<DataTag.Set> entrySets) {
this.entrySets = entrySets;
}
/**
* Returns the entry sets.
*
* @return List of entry sets
*/
public List<Set> getEntrySets() {
return entrySets;
}
/**
* Represents a data tag set.
*/
public static class Set implements Keyed {
private final Key key;
private final ImmutableList<Entry> entries;
public Set(Key key, ImmutableList<Entry> entries) {
this.key = key;
this.entries = entries;
}
/**
* Returns the entries.
*
* @return List of entries
*/
public List<Entry> getEntries() {
return entries;
}
@Override
public @NotNull Key key() {
return key;
}
}
/**
* Represents a data tag entry.
*/
public static class Entry implements Keyed {
private final Key key;
private final int[] elements;
public Entry(Key key, int[] elements) {
this.key = key;
this.elements = elements;
}
public int[] getElements() {
return elements;
}
@Override
public @NotNull Key key() {
return key;
}
}
}

Datei anzeigen

@ -35,6 +35,7 @@ public class Connections {
public static final String MINECRAFT_DECODER = "minecraft-decoder"; public static final String MINECRAFT_DECODER = "minecraft-decoder";
public static final String MINECRAFT_ENCODER = "minecraft-encoder"; public static final String MINECRAFT_ENCODER = "minecraft-encoder";
public static final String READ_TIMEOUT = "read-timeout"; public static final String READ_TIMEOUT = "read-timeout";
public static final String PLAY_PACKET_QUEUE = "play-packet-queue";
private Connections() { private Connections() {
throw new AssertionError(); throw new AssertionError();

Datei anzeigen

@ -29,6 +29,7 @@ import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.client.HandshakeSessionHandler; import com.velocitypowered.proxy.connection.client.HandshakeSessionHandler;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.LegacyPingDecoder; import com.velocitypowered.proxy.protocol.netty.LegacyPingDecoder;
import com.velocitypowered.proxy.protocol.netty.LegacyPingEncoder; import com.velocitypowered.proxy.protocol.netty.LegacyPingEncoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
@ -67,7 +68,8 @@ public class ServerChannelInitializer extends ChannelInitializer<Channel> {
.addLast(MINECRAFT_ENCODER, new MinecraftEncoder(ProtocolUtils.Direction.CLIENTBOUND)); .addLast(MINECRAFT_ENCODER, new MinecraftEncoder(ProtocolUtils.Direction.CLIENTBOUND));
final MinecraftConnection connection = new MinecraftConnection(ch, this.server); final MinecraftConnection connection = new MinecraftConnection(ch, this.server);
connection.setSessionHandler(new HandshakeSessionHandler(connection, this.server)); connection.setActiveSessionHandler(StateRegistry.HANDSHAKE,
new HandshakeSessionHandler(connection, this.server));
ch.pipeline().addLast(Connections.HANDLER, connection); ch.pipeline().addLast(Connections.HANDLER, connection);
if (this.server.getConfiguration().isProxyProtocol()) { if (this.server.getConfiguration().isProxyProtocol()) {

Datei anzeigen

@ -41,6 +41,7 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.nbt.BinaryTagIO; import net.kyori.adventure.nbt.BinaryTagIO;
import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
@ -202,8 +203,7 @@ public enum ProtocolUtils {
buf.readableBytes()); buf.readableBytes());
String str = buf.toString(buf.readerIndex(), length, StandardCharsets.UTF_8); String str = buf.toString(buf.readerIndex(), length, StandardCharsets.UTF_8);
buf.skipBytes(length); buf.skipBytes(length);
checkFrame(str.length() <= cap, "Got a too-long string (got %s, max %s)", checkFrame(str.length() <= cap, "Got a too-long string (got %s, max %s)", str.length(), cap);
str.length(), cap);
return str; return str;
} }
@ -219,6 +219,59 @@ public enum ProtocolUtils {
buf.writeCharSequence(str, StandardCharsets.UTF_8); buf.writeCharSequence(str, StandardCharsets.UTF_8);
} }
/**
* Reads a standard Mojang Text namespaced:key from the buffer.
*
* @param buf the buffer to read from
* @return the decoded key
*/
public static Key readKey(ByteBuf buf) {
return Key.key(readString(buf), Key.DEFAULT_SEPARATOR);
}
/**
* Writes a standard Mojang Text namespaced:key to the buffer.
*
* @param buf the buffer to write to
* @param key the key to write
*/
public static void writeKey(ByteBuf buf, Key key) {
writeString(buf, key.asString());
}
/**
* Reads a standard Mojang Text namespaced:key array from the buffer.
*
* @param buf the buffer to read from
* @return the decoded key array
*/
public static Key[] readKeyArray(ByteBuf buf) {
int length = readVarInt(buf);
checkFrame(length >= 0, "Got a negative-length array (%s)", length);
checkFrame(buf.isReadable(length),
"Trying to read an array that is too long (wanted %s, only have %s)", length,
buf.readableBytes());
Key[] ret = new Key[length];
for (int i = 0; i < ret.length; i++) {
ret[i] = ProtocolUtils.readKey(buf);
}
return ret;
}
/**
* Writes a standard Mojang Text namespaced:key array to the buffer.
*
* @param buf the buffer to write to
* @param keys the keys to write
*/
public static void writeKeyArray(ByteBuf buf, Key[] keys) {
writeVarInt(buf, keys.length);
for (Key key : keys) {
writeKey(buf, key);
}
}
public static byte[] readByteArray(ByteBuf buf) { public static byte[] readByteArray(ByteBuf buf) {
return readByteArray(buf, DEFAULT_MAX_STRING_SIZE); return readByteArray(buf, DEFAULT_MAX_STRING_SIZE);
} }
@ -368,6 +421,38 @@ public enum ProtocolUtils {
} }
} }
/**
* Reads an Integer array from the {@code buf}.
*
* @param buf the buffer to read from
* @return the Integer array from the buffer
*/
public static int[] readVarIntArray(ByteBuf buf) {
int length = readVarInt(buf);
checkFrame(length >= 0, "Got a negative-length array (%s)", length);
checkFrame(buf.isReadable(length),
"Trying to read an array that is too long (wanted %s, only have %s)", length,
buf.readableBytes());
int[] ret = new int[length];
for (int i = 0; i < length; i++) {
ret[i] = readVarInt(buf);
}
return ret;
}
/**
* Writes an Integer Array to the {@code buf}.
*
* @param buf the buffer to write to
* @param intArray the array to write
*/
public static void writeVarIntArray(ByteBuf buf, int[] intArray) {
writeVarInt(buf, intArray.length);
for (int i = 0; i < intArray.length; i++) {
writeVarInt(buf, intArray[i]);
}
}
/** /**
* Writes a list of {@link com.velocitypowered.api.util.GameProfile.Property} to the buffer. * Writes a list of {@link com.velocitypowered.api.util.GameProfile.Property} to the buffer.
* *

Datei anzeigen

@ -33,6 +33,7 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_1; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_1;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_3; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_3;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_4; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_4;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_2;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_7_2; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_7_2;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9;
@ -55,8 +56,10 @@ import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter;
import com.velocitypowered.proxy.protocol.packet.JoinGame; import com.velocitypowered.proxy.protocol.packet.JoinGame;
import com.velocitypowered.proxy.protocol.packet.KeepAlive; import com.velocitypowered.proxy.protocol.packet.KeepAlive;
import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem; import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem;
import com.velocitypowered.proxy.protocol.packet.LoginAcknowledged;
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage; import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage;
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse; import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
import com.velocitypowered.proxy.protocol.packet.PingIdentify;
import com.velocitypowered.proxy.protocol.packet.PluginMessage; import com.velocitypowered.proxy.protocol.packet.PluginMessage;
import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfo; import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfo;
import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest; import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest;
@ -79,6 +82,11 @@ import com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedPlayerCommand;
import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyChat; import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyChat;
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerChat; import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerChat;
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerCommand; import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerCommand;
import com.velocitypowered.proxy.protocol.packet.config.ActiveFeatures;
import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdate;
import com.velocitypowered.proxy.protocol.packet.config.RegistrySync;
import com.velocitypowered.proxy.protocol.packet.config.StartUpdate;
import com.velocitypowered.proxy.protocol.packet.config.TagsUpdate;
import com.velocitypowered.proxy.protocol.packet.title.LegacyTitlePacket; import com.velocitypowered.proxy.protocol.packet.title.LegacyTitlePacket;
import com.velocitypowered.proxy.protocol.packet.title.TitleActionbarPacket; import com.velocitypowered.proxy.protocol.packet.title.TitleActionbarPacket;
import com.velocitypowered.proxy.protocol.packet.title.TitleClearPacket; import com.velocitypowered.proxy.protocol.packet.title.TitleClearPacket;
@ -98,9 +106,7 @@ import java.util.Objects;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
/** /** Registry of all Minecraft protocol states and the packets for each state. */
* Registry of all Minecraft protocol states and the packets for each state.
*/
public enum StateRegistry { public enum StateRegistry {
HANDSHAKE { HANDSHAKE {
@ -111,15 +117,46 @@ public enum StateRegistry {
}, },
STATUS { STATUS {
{ {
serverbound.register(StatusRequest.class, () -> StatusRequest.INSTANCE, serverbound.register(
map(0x00, MINECRAFT_1_7_2, false)); StatusRequest.class, () -> StatusRequest.INSTANCE, map(0x00, MINECRAFT_1_7_2, false));
serverbound.register(StatusPing.class, StatusPing::new, serverbound.register(StatusPing.class, StatusPing::new, map(0x01, MINECRAFT_1_7_2, false));
map(0x01, MINECRAFT_1_7_2, false));
clientbound.register(StatusResponse.class, StatusResponse::new, clientbound.register(
map(0x00, MINECRAFT_1_7_2, false)); StatusResponse.class, StatusResponse::new, map(0x00, MINECRAFT_1_7_2, false));
clientbound.register(StatusPing.class, StatusPing::new, clientbound.register(StatusPing.class, StatusPing::new, map(0x01, MINECRAFT_1_7_2, false));
map(0x01, MINECRAFT_1_7_2, false)); }
},
CONFIG {
{
serverbound.register(
ClientSettings.class, ClientSettings::new, map(0x00, MINECRAFT_1_20_2, false));
serverbound.register(
PluginMessage.class, PluginMessage::new, map(0x01, MINECRAFT_1_20_2, false));
serverbound.register(
FinishedUpdate.class, FinishedUpdate::new, map(0x02, MINECRAFT_1_20_2, false));
serverbound.register(KeepAlive.class, KeepAlive::new, map(0x03, MINECRAFT_1_20_2, false));
serverbound.register(
PingIdentify.class, PingIdentify::new, map(0x04, MINECRAFT_1_20_2, false));
serverbound.register(
ResourcePackResponse.class,
ResourcePackResponse::new,
map(0x05, MINECRAFT_1_20_2, false));
clientbound.register(
PluginMessage.class, PluginMessage::new, map(0x00, MINECRAFT_1_20_2, false));
clientbound.register(Disconnect.class, Disconnect::new, map(0x01, MINECRAFT_1_20_2, false));
clientbound.register(
FinishedUpdate.class, FinishedUpdate::new, map(0x02, MINECRAFT_1_20_2, false));
clientbound.register(KeepAlive.class, KeepAlive::new, map(0x03, MINECRAFT_1_20_2, false));
clientbound.register(
PingIdentify.class, PingIdentify::new, map(0x04, MINECRAFT_1_20_2, false));
clientbound.register(
RegistrySync.class, RegistrySync::new, map(0x05, MINECRAFT_1_20_2, false));
clientbound.register(
ResourcePackRequest.class, ResourcePackRequest::new, map(0x06, MINECRAFT_1_20_2, false));
clientbound.register(
ActiveFeatures.class, ActiveFeatures::new, map(0x07, MINECRAFT_1_20_2, false));
clientbound.register(TagsUpdate.class, TagsUpdate::new, map(0x08, MINECRAFT_1_20_2, false));
} }
}, },
PLAY { PLAY {
@ -137,8 +174,11 @@ public enum StateRegistry {
map(0x08, MINECRAFT_1_19, false), map(0x08, MINECRAFT_1_19, false),
map(0x09, MINECRAFT_1_19_1, false), map(0x09, MINECRAFT_1_19_1, false),
map(0x08, MINECRAFT_1_19_3, false), map(0x08, MINECRAFT_1_19_3, false),
map(0x09, MINECRAFT_1_19_4, false)); map(0x09, MINECRAFT_1_19_4, false),
serverbound.register(LegacyChat.class, LegacyChat::new, map(0x0A, MINECRAFT_1_20_2, false));
serverbound.register(
LegacyChat.class,
LegacyChat::new,
map(0x01, MINECRAFT_1_7_2, false), map(0x01, MINECRAFT_1_7_2, false),
map(0x02, MINECRAFT_1_9, false), map(0x02, MINECRAFT_1_9, false),
map(0x03, MINECRAFT_1_12, false), map(0x03, MINECRAFT_1_12, false),
@ -152,9 +192,13 @@ public enum StateRegistry {
map(0x05, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false)); map(0x05, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false));
serverbound.register(SessionPlayerCommand.class, SessionPlayerCommand::new, serverbound.register(SessionPlayerCommand.class, SessionPlayerCommand::new,
map(0x04, MINECRAFT_1_19_3, false)); map(0x04, MINECRAFT_1_19_3, false));
serverbound.register(SessionPlayerChat.class, SessionPlayerChat::new, serverbound.register(
map(0x05, MINECRAFT_1_19_3, false)); SessionPlayerChat.class,
serverbound.register(ClientSettings.class, ClientSettings::new, SessionPlayerChat::new,
map(0x05, MINECRAFT_1_19_3, MINECRAFT_1_20_2, false));
serverbound.register(
ClientSettings.class,
ClientSettings::new,
map(0x15, MINECRAFT_1_7_2, false), map(0x15, MINECRAFT_1_7_2, false),
map(0x04, MINECRAFT_1_9, false), map(0x04, MINECRAFT_1_9, false),
map(0x05, MINECRAFT_1_12, false), map(0x05, MINECRAFT_1_12, false),
@ -163,8 +207,11 @@ public enum StateRegistry {
map(0x07, MINECRAFT_1_19, false), map(0x07, MINECRAFT_1_19, false),
map(0x08, MINECRAFT_1_19_1, false), map(0x08, MINECRAFT_1_19_1, false),
map(0x07, MINECRAFT_1_19_3, false), map(0x07, MINECRAFT_1_19_3, false),
map(0x08, MINECRAFT_1_19_4, false)); map(0x08, MINECRAFT_1_19_4, false),
serverbound.register(PluginMessage.class, PluginMessage::new, map(0x09, MINECRAFT_1_20_2, false));
serverbound.register(
PluginMessage.class,
PluginMessage::new,
map(0x17, MINECRAFT_1_7_2, false), map(0x17, MINECRAFT_1_7_2, false),
map(0x09, MINECRAFT_1_9, false), map(0x09, MINECRAFT_1_9, false),
map(0x0A, MINECRAFT_1_12, false), map(0x0A, MINECRAFT_1_12, false),
@ -175,8 +222,11 @@ public enum StateRegistry {
map(0x0C, MINECRAFT_1_19, false), map(0x0C, MINECRAFT_1_19, false),
map(0x0D, MINECRAFT_1_19_1, false), map(0x0D, MINECRAFT_1_19_1, false),
map(0x0C, MINECRAFT_1_19_3, false), map(0x0C, MINECRAFT_1_19_3, false),
map(0x0D, MINECRAFT_1_19_4, false)); map(0x0D, MINECRAFT_1_19_4, false),
serverbound.register(KeepAlive.class, KeepAlive::new, map(0x0F, MINECRAFT_1_20_2, false));
serverbound.register(
KeepAlive.class,
KeepAlive::new,
map(0x00, MINECRAFT_1_7_2, false), map(0x00, MINECRAFT_1_7_2, false),
map(0x0B, MINECRAFT_1_9, false), map(0x0B, MINECRAFT_1_9, false),
map(0x0C, MINECRAFT_1_12, false), map(0x0C, MINECRAFT_1_12, false),
@ -188,8 +238,11 @@ public enum StateRegistry {
map(0x11, MINECRAFT_1_19, false), map(0x11, MINECRAFT_1_19, false),
map(0x12, MINECRAFT_1_19_1, false), map(0x12, MINECRAFT_1_19_1, false),
map(0x11, MINECRAFT_1_19_3, false), map(0x11, MINECRAFT_1_19_3, false),
map(0x12, MINECRAFT_1_19_4, false)); map(0x12, MINECRAFT_1_19_4, false),
serverbound.register(ResourcePackResponse.class, ResourcePackResponse::new, map(0x14, MINECRAFT_1_20_2, false));
serverbound.register(
ResourcePackResponse.class,
ResourcePackResponse::new,
map(0x19, MINECRAFT_1_8, false), map(0x19, MINECRAFT_1_8, false),
map(0x16, MINECRAFT_1_9, false), map(0x16, MINECRAFT_1_9, false),
map(0x18, MINECRAFT_1_12, false), map(0x18, MINECRAFT_1_12, false),
@ -198,16 +251,24 @@ public enum StateRegistry {
map(0x20, MINECRAFT_1_16, false), map(0x20, MINECRAFT_1_16, false),
map(0x21, MINECRAFT_1_16_2, false), map(0x21, MINECRAFT_1_16_2, false),
map(0x23, MINECRAFT_1_19, false), map(0x23, MINECRAFT_1_19, false),
map(0x24, MINECRAFT_1_19_1, false)); map(0x24, MINECRAFT_1_19_1, false),
map(0x27, MINECRAFT_1_20_2, false));
serverbound.register(
FinishedUpdate.class, FinishedUpdate::new, map(0x0B, MINECRAFT_1_20_2, false));
clientbound.register(BossBar.class, BossBar::new, clientbound.register(
BossBar.class,
BossBar::new,
map(0x0C, MINECRAFT_1_9, false), map(0x0C, MINECRAFT_1_9, false),
map(0x0D, MINECRAFT_1_15, false), map(0x0D, MINECRAFT_1_15, false),
map(0x0C, MINECRAFT_1_16, false), map(0x0C, MINECRAFT_1_16, false),
map(0x0D, MINECRAFT_1_17, false), map(0x0D, MINECRAFT_1_17, false),
map(0x0A, MINECRAFT_1_19, false), map(0x0A, MINECRAFT_1_19, false),
map(0x0B, MINECRAFT_1_19_4, false)); map(0x0B, MINECRAFT_1_19_4, false),
clientbound.register(LegacyChat.class, LegacyChat::new, map(0x0A, MINECRAFT_1_20_2, false));
clientbound.register(
LegacyChat.class,
LegacyChat::new,
map(0x02, MINECRAFT_1_7_2, true), map(0x02, MINECRAFT_1_7_2, true),
map(0x0F, MINECRAFT_1_9, true), map(0x0F, MINECRAFT_1_9, true),
map(0x0E, MINECRAFT_1_13, true), map(0x0E, MINECRAFT_1_13, true),
@ -224,8 +285,11 @@ public enum StateRegistry {
map(0x11, MINECRAFT_1_17, false), map(0x11, MINECRAFT_1_17, false),
map(0x0E, MINECRAFT_1_19, false), map(0x0E, MINECRAFT_1_19, false),
map(0x0D, MINECRAFT_1_19_3, false), map(0x0D, MINECRAFT_1_19_3, false),
map(0x0F, MINECRAFT_1_19_4, false)); map(0x0F, MINECRAFT_1_19_4, false),
clientbound.register(AvailableCommands.class, AvailableCommands::new, map(0x10, MINECRAFT_1_20_2, false));
clientbound.register(
AvailableCommands.class,
AvailableCommands::new,
map(0x11, MINECRAFT_1_13, false), map(0x11, MINECRAFT_1_13, false),
map(0x12, MINECRAFT_1_15, false), map(0x12, MINECRAFT_1_15, false),
map(0x11, MINECRAFT_1_16, false), map(0x11, MINECRAFT_1_16, false),
@ -233,8 +297,11 @@ public enum StateRegistry {
map(0x12, MINECRAFT_1_17, false), map(0x12, MINECRAFT_1_17, false),
map(0x0F, MINECRAFT_1_19, false), map(0x0F, MINECRAFT_1_19, false),
map(0x0E, MINECRAFT_1_19_3, false), map(0x0E, MINECRAFT_1_19_3, false),
map(0x10, MINECRAFT_1_19_4, false)); map(0x10, MINECRAFT_1_19_4, false),
clientbound.register(PluginMessage.class, PluginMessage::new, map(0x11, MINECRAFT_1_20_2, false));
clientbound.register(
PluginMessage.class,
PluginMessage::new,
map(0x3F, MINECRAFT_1_7_2, false), map(0x3F, MINECRAFT_1_7_2, false),
map(0x18, MINECRAFT_1_9, false), map(0x18, MINECRAFT_1_9, false),
map(0x19, MINECRAFT_1_13, false), map(0x19, MINECRAFT_1_13, false),
@ -246,8 +313,11 @@ public enum StateRegistry {
map(0x15, MINECRAFT_1_19, false), map(0x15, MINECRAFT_1_19, false),
map(0x16, MINECRAFT_1_19_1, false), map(0x16, MINECRAFT_1_19_1, false),
map(0x15, MINECRAFT_1_19_3, false), map(0x15, MINECRAFT_1_19_3, false),
map(0x17, MINECRAFT_1_19_4, false)); map(0x17, MINECRAFT_1_19_4, false),
clientbound.register(Disconnect.class, Disconnect::new, map(0x18, MINECRAFT_1_20_2, false));
clientbound.register(
Disconnect.class,
Disconnect::new,
map(0x40, MINECRAFT_1_7_2, false), map(0x40, MINECRAFT_1_7_2, false),
map(0x1A, MINECRAFT_1_9, false), map(0x1A, MINECRAFT_1_9, false),
map(0x1B, MINECRAFT_1_13, false), map(0x1B, MINECRAFT_1_13, false),
@ -259,8 +329,11 @@ public enum StateRegistry {
map(0x17, MINECRAFT_1_19, false), map(0x17, MINECRAFT_1_19, false),
map(0x19, MINECRAFT_1_19_1, false), map(0x19, MINECRAFT_1_19_1, false),
map(0x17, MINECRAFT_1_19_3, false), map(0x17, MINECRAFT_1_19_3, false),
map(0x1A, MINECRAFT_1_19_4, false)); map(0x1A, MINECRAFT_1_19_4, false),
clientbound.register(KeepAlive.class, KeepAlive::new, map(0x1B, MINECRAFT_1_20_2, false));
clientbound.register(
KeepAlive.class,
KeepAlive::new,
map(0x00, MINECRAFT_1_7_2, false), map(0x00, MINECRAFT_1_7_2, false),
map(0x1F, MINECRAFT_1_9, false), map(0x1F, MINECRAFT_1_9, false),
map(0x21, MINECRAFT_1_13, false), map(0x21, MINECRAFT_1_13, false),
@ -272,8 +345,11 @@ public enum StateRegistry {
map(0x1E, MINECRAFT_1_19, false), map(0x1E, MINECRAFT_1_19, false),
map(0x20, MINECRAFT_1_19_1, false), map(0x20, MINECRAFT_1_19_1, false),
map(0x1F, MINECRAFT_1_19_3, false), map(0x1F, MINECRAFT_1_19_3, false),
map(0x23, MINECRAFT_1_19_4, false)); map(0x23, MINECRAFT_1_19_4, false),
clientbound.register(JoinGame.class, JoinGame::new, map(0x24, MINECRAFT_1_20_2, false));
clientbound.register(
JoinGame.class,
JoinGame::new,
map(0x01, MINECRAFT_1_7_2, false), map(0x01, MINECRAFT_1_7_2, false),
map(0x23, MINECRAFT_1_9, false), map(0x23, MINECRAFT_1_9, false),
map(0x25, MINECRAFT_1_13, false), map(0x25, MINECRAFT_1_13, false),
@ -285,8 +361,11 @@ public enum StateRegistry {
map(0x23, MINECRAFT_1_19, false), map(0x23, MINECRAFT_1_19, false),
map(0x25, MINECRAFT_1_19_1, false), map(0x25, MINECRAFT_1_19_1, false),
map(0x24, MINECRAFT_1_19_3, false), map(0x24, MINECRAFT_1_19_3, false),
map(0x28, MINECRAFT_1_19_4, false)); map(0x28, MINECRAFT_1_19_4, false),
clientbound.register(Respawn.class, Respawn::new, map(0x29, MINECRAFT_1_20_2, false));
clientbound.register(
Respawn.class,
Respawn::new,
map(0x07, MINECRAFT_1_7_2, true), map(0x07, MINECRAFT_1_7_2, true),
map(0x33, MINECRAFT_1_9, true), map(0x33, MINECRAFT_1_9, true),
map(0x34, MINECRAFT_1_12, true), map(0x34, MINECRAFT_1_12, true),
@ -300,8 +379,11 @@ public enum StateRegistry {
map(0x3B, MINECRAFT_1_19, true), map(0x3B, MINECRAFT_1_19, true),
map(0x3E, MINECRAFT_1_19_1, true), map(0x3E, MINECRAFT_1_19_1, true),
map(0x3D, MINECRAFT_1_19_3, true), map(0x3D, MINECRAFT_1_19_3, true),
map(0x41, MINECRAFT_1_19_4, true)); map(0x41, MINECRAFT_1_19_4, true),
clientbound.register(ResourcePackRequest.class, ResourcePackRequest::new, map(0x43, MINECRAFT_1_20_2, true));
clientbound.register(
ResourcePackRequest.class,
ResourcePackRequest::new,
map(0x48, MINECRAFT_1_8, false), map(0x48, MINECRAFT_1_8, false),
map(0x32, MINECRAFT_1_9, false), map(0x32, MINECRAFT_1_9, false),
map(0x33, MINECRAFT_1_12, false), map(0x33, MINECRAFT_1_12, false),
@ -315,8 +397,11 @@ public enum StateRegistry {
map(0x3A, MINECRAFT_1_19, false), map(0x3A, MINECRAFT_1_19, false),
map(0x3D, MINECRAFT_1_19_1, false), map(0x3D, MINECRAFT_1_19_1, false),
map(0x3C, MINECRAFT_1_19_3, false), map(0x3C, MINECRAFT_1_19_3, false),
map(0x40, MINECRAFT_1_19_4, false)); map(0x40, MINECRAFT_1_19_4, false),
clientbound.register(HeaderAndFooter.class, HeaderAndFooter::new, map(0x42, MINECRAFT_1_20_2, false));
clientbound.register(
HeaderAndFooter.class,
HeaderAndFooter::new,
map(0x47, MINECRAFT_1_8, true), map(0x47, MINECRAFT_1_8, true),
map(0x48, MINECRAFT_1_9, true), map(0x48, MINECRAFT_1_9, true),
map(0x47, MINECRAFT_1_9_4, true), map(0x47, MINECRAFT_1_9_4, true),
@ -331,8 +416,11 @@ public enum StateRegistry {
map(0x60, MINECRAFT_1_19, true), map(0x60, MINECRAFT_1_19, true),
map(0x63, MINECRAFT_1_19_1, true), map(0x63, MINECRAFT_1_19_1, true),
map(0x61, MINECRAFT_1_19_3, true), map(0x61, MINECRAFT_1_19_3, true),
map(0x65, MINECRAFT_1_19_4, true)); map(0x65, MINECRAFT_1_19_4, true),
clientbound.register(LegacyTitlePacket.class, LegacyTitlePacket::new, map(0x68, MINECRAFT_1_20_2, true));
clientbound.register(
LegacyTitlePacket.class,
LegacyTitlePacket::new,
map(0x45, MINECRAFT_1_8, true), map(0x45, MINECRAFT_1_8, true),
map(0x45, MINECRAFT_1_9, true), map(0x45, MINECRAFT_1_9, true),
map(0x47, MINECRAFT_1_12, true), map(0x47, MINECRAFT_1_12, true),
@ -346,31 +434,46 @@ public enum StateRegistry {
map(0x58, MINECRAFT_1_18, true), map(0x58, MINECRAFT_1_18, true),
map(0x5B, MINECRAFT_1_19_1, true), map(0x5B, MINECRAFT_1_19_1, true),
map(0x59, MINECRAFT_1_19_3, true), map(0x59, MINECRAFT_1_19_3, true),
map(0x5D, MINECRAFT_1_19_4, true)); map(0x5D, MINECRAFT_1_19_4, true),
clientbound.register(TitleTextPacket.class, TitleTextPacket::new, map(0x5F, MINECRAFT_1_20_2, true));
clientbound.register(
TitleTextPacket.class,
TitleTextPacket::new,
map(0x59, MINECRAFT_1_17, true), map(0x59, MINECRAFT_1_17, true),
map(0x5A, MINECRAFT_1_18, true), map(0x5A, MINECRAFT_1_18, true),
map(0x5D, MINECRAFT_1_19_1, true), map(0x5D, MINECRAFT_1_19_1, true),
map(0x5B, MINECRAFT_1_19_3, true), map(0x5B, MINECRAFT_1_19_3, true),
map(0x5F, MINECRAFT_1_19_4, true)); map(0x5F, MINECRAFT_1_19_4, true),
clientbound.register(TitleActionbarPacket.class, TitleActionbarPacket::new, map(0x61, MINECRAFT_1_20_2, true));
clientbound.register(
TitleActionbarPacket.class,
TitleActionbarPacket::new,
map(0x41, MINECRAFT_1_17, true), map(0x41, MINECRAFT_1_17, true),
map(0x40, MINECRAFT_1_19, true), map(0x40, MINECRAFT_1_19, true),
map(0x43, MINECRAFT_1_19_1, true), map(0x43, MINECRAFT_1_19_1, true),
map(0x42, MINECRAFT_1_19_3, true), map(0x42, MINECRAFT_1_19_3, true),
map(0x46, MINECRAFT_1_19_4, true)); map(0x46, MINECRAFT_1_19_4, true),
clientbound.register(TitleTimesPacket.class, TitleTimesPacket::new, map(0x48, MINECRAFT_1_20_2, true));
clientbound.register(
TitleTimesPacket.class,
TitleTimesPacket::new,
map(0x5A, MINECRAFT_1_17, true), map(0x5A, MINECRAFT_1_17, true),
map(0x5B, MINECRAFT_1_18, true), map(0x5B, MINECRAFT_1_18, true),
map(0x5E, MINECRAFT_1_19_1, true), map(0x5E, MINECRAFT_1_19_1, true),
map(0x5C, MINECRAFT_1_19_3, true), map(0x5C, MINECRAFT_1_19_3, true),
map(0x60, MINECRAFT_1_19_4, true)); map(0x60, MINECRAFT_1_19_4, true),
clientbound.register(TitleClearPacket.class, TitleClearPacket::new, map(0x62, MINECRAFT_1_20_2, true));
clientbound.register(
TitleClearPacket.class,
TitleClearPacket::new,
map(0x10, MINECRAFT_1_17, true), map(0x10, MINECRAFT_1_17, true),
map(0x0D, MINECRAFT_1_19, true), map(0x0D, MINECRAFT_1_19, true),
map(0x0C, MINECRAFT_1_19_3, true), map(0x0C, MINECRAFT_1_19_3, true),
map(0x0E, MINECRAFT_1_19_4, true)); map(0x0E, MINECRAFT_1_19_4, true),
clientbound.register(LegacyPlayerListItem.class, LegacyPlayerListItem::new, map(0x0F, MINECRAFT_1_20_2, true));
clientbound.register(
LegacyPlayerListItem.class,
LegacyPlayerListItem::new,
map(0x38, MINECRAFT_1_7_2, false), map(0x38, MINECRAFT_1_7_2, false),
map(0x2D, MINECRAFT_1_9, false), map(0x2D, MINECRAFT_1_9, false),
map(0x2E, MINECRAFT_1_12_1, false), map(0x2E, MINECRAFT_1_12_1, false),
@ -384,44 +487,59 @@ public enum StateRegistry {
map(0x37, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false)); map(0x37, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false));
clientbound.register(RemovePlayerInfo.class, RemovePlayerInfo::new, clientbound.register(RemovePlayerInfo.class, RemovePlayerInfo::new,
map(0x35, MINECRAFT_1_19_3, false), map(0x35, MINECRAFT_1_19_3, false),
map(0x39, MINECRAFT_1_19_4, false)); map(0x39, MINECRAFT_1_19_4, false),
clientbound.register(UpsertPlayerInfo.class, UpsertPlayerInfo::new, map(0x3B, MINECRAFT_1_20_2, false));
clientbound.register(
UpsertPlayerInfo.class,
UpsertPlayerInfo::new,
map(0x36, MINECRAFT_1_19_3, false), map(0x36, MINECRAFT_1_19_3, false),
map(0x3A, MINECRAFT_1_19_4, false)); map(0x3A, MINECRAFT_1_19_4, false),
clientbound.register(SystemChat.class, SystemChat::new, map(0x3C, MINECRAFT_1_20_2, false));
clientbound.register(
SystemChat.class,
SystemChat::new,
map(0x5F, MINECRAFT_1_19, true), map(0x5F, MINECRAFT_1_19, true),
map(0x62, MINECRAFT_1_19_1, true), map(0x62, MINECRAFT_1_19_1, true),
map(0x60, MINECRAFT_1_19_3, true), map(0x60, MINECRAFT_1_19_3, true),
map(0x64, MINECRAFT_1_19_4, true)); map(0x64, MINECRAFT_1_19_4, true),
clientbound.register(PlayerChatCompletion.class, PlayerChatCompletion::new, map(0x67, MINECRAFT_1_20_2, true));
clientbound.register(
PlayerChatCompletion.class,
PlayerChatCompletion::new,
map(0x15, MINECRAFT_1_19_1, true), map(0x15, MINECRAFT_1_19_1, true),
map(0x14, MINECRAFT_1_19_3, true), map(0x14, MINECRAFT_1_19_3, true),
map(0x16, MINECRAFT_1_19_4, true)); map(0x16, MINECRAFT_1_19_4, true),
clientbound.register(ServerData.class, ServerData::new, map(0x17, MINECRAFT_1_20_2, true));
clientbound.register(
ServerData.class,
ServerData::new,
map(0x3F, MINECRAFT_1_19, false), map(0x3F, MINECRAFT_1_19, false),
map(0x42, MINECRAFT_1_19_1, false), map(0x42, MINECRAFT_1_19_1, false),
map(0x41, MINECRAFT_1_19_3, false), map(0x41, MINECRAFT_1_19_3, false),
map(0x45, MINECRAFT_1_19_4, false)); map(0x45, MINECRAFT_1_19_4, false),
map(0x47, MINECRAFT_1_20_2, false));
clientbound.register(StartUpdate.class, StartUpdate::new, map(0x65, MINECRAFT_1_20_2, false));
} }
}, },
LOGIN { LOGIN {
{ {
serverbound.register(ServerLogin.class, ServerLogin::new, serverbound.register(ServerLogin.class, ServerLogin::new, map(0x00, MINECRAFT_1_7_2, false));
map(0x00, MINECRAFT_1_7_2, false)); serverbound.register(
serverbound.register(EncryptionResponse.class, EncryptionResponse::new, EncryptionResponse.class, EncryptionResponse::new, map(0x01, MINECRAFT_1_7_2, false));
map(0x01, MINECRAFT_1_7_2, false)); serverbound.register(
serverbound.register(LoginPluginResponse.class, LoginPluginResponse::new, LoginPluginResponse.class, LoginPluginResponse::new, map(0x02, MINECRAFT_1_13, false));
map(0x02, MINECRAFT_1_13, false)); serverbound.register(
clientbound.register(Disconnect.class, Disconnect::new, LoginAcknowledged.class, LoginAcknowledged::new, map(0x03, MINECRAFT_1_20_2, false));
map(0x00, MINECRAFT_1_7_2, false));
clientbound.register(EncryptionRequest.class, EncryptionRequest::new, clientbound.register(Disconnect.class, Disconnect::new, map(0x00, MINECRAFT_1_7_2, false));
map(0x01, MINECRAFT_1_7_2, false)); clientbound.register(
clientbound.register(ServerLoginSuccess.class, ServerLoginSuccess::new, EncryptionRequest.class, EncryptionRequest::new, map(0x01, MINECRAFT_1_7_2, false));
map(0x02, MINECRAFT_1_7_2, false)); clientbound.register(
clientbound.register(SetCompression.class, SetCompression::new, ServerLoginSuccess.class, ServerLoginSuccess::new, map(0x02, MINECRAFT_1_7_2, false));
map(0x03, MINECRAFT_1_8, false)); clientbound.register(
clientbound.register(LoginPluginMessage.class, LoginPluginMessage::new, SetCompression.class, SetCompression::new, map(0x03, MINECRAFT_1_8, false));
map(0x04, MINECRAFT_1_13, false)); clientbound.register(
LoginPluginMessage.class, LoginPluginMessage::new, map(0x04, MINECRAFT_1_13, false));
} }
}; };
@ -435,9 +553,7 @@ public enum StateRegistry {
return (direction == SERVERBOUND ? serverbound : clientbound).getProtocolRegistry(version); return (direction == SERVERBOUND ? serverbound : clientbound).getProtocolRegistry(version);
} }
/** /** Packet registry. */
* Packet registry.
*/
public static class PacketRegistry { public static class PacketRegistry {
private final Direction direction; private final Direction direction;
@ -505,19 +621,24 @@ public enum StateRegistry {
} }
ProtocolRegistry registry = this.versions.get(protocol); ProtocolRegistry registry = this.versions.get(protocol);
if (registry == null) { if (registry == null) {
throw new IllegalArgumentException("Unknown protocol version " throw new IllegalArgumentException(
+ current.protocolVersion); "Unknown protocol version " + current.protocolVersion);
} }
if (registry.packetIdToSupplier.containsKey(current.id)) { if (registry.packetIdToSupplier.containsKey(current.id)) {
throw new IllegalArgumentException("Can not register class " + clazz.getSimpleName() throw new IllegalArgumentException(
+ " with id " + current.id + " for " + registry.version "Can not register class "
+ " because another packet is already registered"); + clazz.getSimpleName()
+ " with id "
+ current.id
+ " for "
+ registry.version
+ " because another packet is already registered");
} }
if (registry.packetClassToId.containsKey(clazz)) { if (registry.packetClassToId.containsKey(clazz)) {
throw new IllegalArgumentException(clazz.getSimpleName() throw new IllegalArgumentException(
+ " is already registered for version " + registry.version); clazz.getSimpleName() + " is already registered for version " + registry.version);
} }
if (!current.encodeOnly) { if (!current.encodeOnly) {
@ -528,9 +649,7 @@ public enum StateRegistry {
} }
} }
/** /** Protocol registry. */
* Protocol registry.
*/
public class ProtocolRegistry { public class ProtocolRegistry {
public final ProtocolVersion version; public final ProtocolVersion version;
@ -575,12 +694,20 @@ public enum StateRegistry {
} }
return id; return id;
} }
/**
* Checks if the registry contains a packet with the specified {@code id}.
*
* @param packet the packet to check
* @return {@code true} if the packet is registered, {@code false} otherwise
*/
public boolean containsPacket(final MinecraftPacket packet) {
return this.packetClassToId.containsKey(packet.getClass());
}
} }
} }
/** /** Packet mapping. */
* Packet mapping.
*/
public static final class PacketMapping { public static final class PacketMapping {
private final int id; private final int id;
@ -599,9 +726,12 @@ public enum StateRegistry {
@Override @Override
public String toString() { public String toString() {
return "PacketMapping{" return "PacketMapping{"
+ "id=" + id + "id="
+ ", protocolVersion=" + protocolVersion + id
+ ", encodeOnly=" + encodeOnly + ", protocolVersion="
+ protocolVersion
+ ", encodeOnly="
+ encodeOnly
+ '}'; + '}';
} }

Datei anzeigen

@ -62,4 +62,8 @@ public class MinecraftEncoder extends MessageToByteEncoder<MinecraftPacket> {
this.state = state; this.state = state;
this.setProtocolVersion(registry.version); this.setProtocolVersion(registry.version);
} }
public ProtocolUtils.Direction getDirection() {
return direction;
}
} }

Datei anzeigen

@ -0,0 +1,108 @@
/*
* Copyright (C) 2023 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol.netty;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.internal.PlatformDependent;
import java.util.Queue;
import org.jetbrains.annotations.NotNull;
/**
* Queues up any pending PLAY packets while the client is in the CONFIG state.
*
* <p>Much of the Velocity API (i.e. chat messages) utilize PLAY packets, however the client is
* incapable of receiving these packets during the CONFIG state. Certain events such as the
* ServerPreConnectEvent may be called during this time, and we need to ensure that any API that
* uses these packets will work as expected.
*
* <p>This handler will queue up any packets that are sent to the client during this time, and send
* them once the client has (re)entered the PLAY state.
*/
public class PlayPacketQueueHandler extends ChannelDuplexHandler {
private final StateRegistry.PacketRegistry.ProtocolRegistry registry;
private final Queue<MinecraftPacket> queue = PlatformDependent.newMpscQueue();
/**
* Provides registries for client & server bound packets.
*
* @param version the protocol version
*/
public PlayPacketQueueHandler(ProtocolVersion version, ProtocolUtils.Direction direction) {
this.registry =
StateRegistry.CONFIG.getProtocolRegistry(direction, version);
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
throws Exception {
if (!(msg instanceof MinecraftPacket)) {
ctx.write(msg, promise);
return;
}
// If the packet exists in the CONFIG state, we want to always
// ensure that it gets sent out to the client
if (this.registry.containsPacket(((MinecraftPacket) msg))) {
ctx.write(msg, promise);
return;
}
// Otherwise, queue the packet
this.queue.offer((MinecraftPacket) msg);
}
@Override
public void channelInactive(@NotNull ChannelHandlerContext ctx) throws Exception {
this.releaseQueue(ctx, false);
super.channelInactive(ctx);
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
this.releaseQueue(ctx, ctx.channel().isActive());
}
private void releaseQueue(ChannelHandlerContext ctx, boolean active) {
if (this.queue.isEmpty()) {
return;
}
// Send out all the queued packets
MinecraftPacket packet;
while ((packet = this.queue.poll()) != null) {
if (active) {
ctx.write(packet, ctx.voidPromise());
} else {
ReferenceCountUtil.release(packet);
}
}
if (active) {
ctx.flush();
}
}
}

Datei anzeigen

@ -23,10 +23,10 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import java.util.Objects; import java.util.Objects;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
public class ClientSettings implements MinecraftPacket { public class ClientSettings implements MinecraftPacket {
private @Nullable String locale; private @Nullable String locale;
private byte viewDistance; private byte viewDistance;
private int chatVisibility; private int chatVisibility;
@ -120,16 +120,10 @@ public class ClientSettings implements MinecraftPacket {
@Override @Override
public String toString() { public String toString() {
return "ClientSettings{" return "ClientSettings{" + "locale='" + locale + '\'' + ", viewDistance=" + viewDistance +
+ "locale='" + locale + '\'' ", chatVisibility=" + chatVisibility + ", chatColors=" + chatColors + ", skinParts=" +
+ ", viewDistance=" + viewDistance skinParts + ", mainHand=" + mainHand + ", chatFilteringEnabled=" + chatFilteringEnabled +
+ ", chatVisibility=" + chatVisibility ", clientListingAllowed=" + clientListingAllowed + '}';
+ ", chatColors=" + chatColors
+ ", skinParts=" + skinParts
+ ", mainHand=" + mainHand
+ ", chatFilteringEnabled=" + chatFilteringEnabled
+ ", clientListingAllowed=" + clientListingAllowed
+ '}';
} }
@Override @Override

Datei anzeigen

@ -42,6 +42,7 @@ public class JoinGame implements MinecraftPacket {
private int viewDistance; // 1.14+ private int viewDistance; // 1.14+
private boolean reducedDebugInfo; private boolean reducedDebugInfo;
private boolean showRespawnScreen; private boolean showRespawnScreen;
private boolean doLimitedCrafting; // 1.20.2+
private ImmutableSet<String> levelNames; // 1.16+ private ImmutableSet<String> levelNames; // 1.16+
private CompoundBinaryTag registry; // 1.16+ private CompoundBinaryTag registry; // 1.16+
private DimensionInfo dimensionInfo; // 1.16+ private DimensionInfo dimensionInfo; // 1.16+
@ -143,6 +144,14 @@ public class JoinGame implements MinecraftPacket {
this.isHardcore = isHardcore; this.isHardcore = isHardcore;
} }
public boolean getDoLimitedCrafting() {
return doLimitedCrafting;
}
public void setDoLimitedCrafting(boolean doLimitedCrafting) {
this.doLimitedCrafting = doLimitedCrafting;
}
public CompoundBinaryTag getCurrentDimensionData() { public CompoundBinaryTag getCurrentDimensionData() {
return currentDimensionData; return currentDimensionData;
} }
@ -177,32 +186,24 @@ public class JoinGame implements MinecraftPacket {
@Override @Override
public String toString() { public String toString() {
return "JoinGame{" return "JoinGame{" + "entityId=" + entityId + ", gamemode=" + gamemode + ", dimension=" +
+ "entityId=" + entityId dimension + ", partialHashedSeed=" + partialHashedSeed + ", difficulty=" + difficulty +
+ ", gamemode=" + gamemode ", isHardcore=" + isHardcore + ", maxPlayers=" + maxPlayers + ", levelType='" + levelType +
+ ", dimension=" + dimension '\'' + ", viewDistance=" + viewDistance + ", reducedDebugInfo=" + reducedDebugInfo +
+ ", partialHashedSeed=" + partialHashedSeed ", showRespawnScreen=" + showRespawnScreen + ", doLimitedCrafting=" + doLimitedCrafting +
+ ", difficulty=" + difficulty ", levelNames=" + levelNames + ", registry='" + registry + '\'' + ", dimensionInfo='" +
+ ", isHardcore=" + isHardcore dimensionInfo + '\'' + ", currentDimensionData='" + currentDimensionData + '\'' +
+ ", maxPlayers=" + maxPlayers ", previousGamemode=" + previousGamemode + ", simulationDistance=" + simulationDistance +
+ ", levelType='" + levelType + '\'' ", lastDeathPosition='" + lastDeathPosition + '\'' + ", portalCooldown=" + portalCooldown +
+ ", viewDistance=" + viewDistance '}';
+ ", reducedDebugInfo=" + reducedDebugInfo
+ ", showRespawnScreen=" + showRespawnScreen
+ ", levelNames=" + levelNames
+ ", registry='" + registry + '\''
+ ", dimensionInfo='" + dimensionInfo + '\''
+ ", currentDimensionData='" + currentDimensionData + '\''
+ ", previousGamemode=" + previousGamemode
+ ", simulationDistance=" + simulationDistance
+ ", lastDeathPosition='" + lastDeathPosition + '\''
+ ", portalCooldown=" + portalCooldown
+ '}';
} }
@Override @Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0) {
// haha funny, they made 1.20.2 more complicated
this.decode1202Up(buf, version);
} else if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) {
// Minecraft 1.16 and above have significantly more complicated logic for reading this packet, // Minecraft 1.16 and above have significantly more complicated logic for reading this packet,
// so separate it out. // so separate it out.
this.decode116Up(buf, version); this.decode116Up(buf, version);
@ -295,9 +296,46 @@ public class JoinGame implements MinecraftPacket {
} }
} }
private void decode1202Up(ByteBuf buf, ProtocolVersion version) {
this.entityId = buf.readInt();
this.isHardcore = buf.readBoolean();
this.levelNames = ImmutableSet.copyOf(ProtocolUtils.readStringArray(buf));
this.maxPlayers = ProtocolUtils.readVarInt(buf);
this.viewDistance = ProtocolUtils.readVarInt(buf);
this.simulationDistance = ProtocolUtils.readVarInt(buf);
this.reducedDebugInfo = buf.readBoolean();
this.showRespawnScreen = buf.readBoolean();
this.doLimitedCrafting = buf.readBoolean();
String dimensionIdentifier = ProtocolUtils.readString(buf);
String levelName = ProtocolUtils.readString(buf);
this.partialHashedSeed = buf.readLong();
this.gamemode = buf.readByte();
this.previousGamemode = buf.readByte();
boolean isDebug = buf.readBoolean();
boolean isFlat = buf.readBoolean();
this.dimensionInfo = new DimensionInfo(dimensionIdentifier, levelName, isFlat, isDebug);
// optional death location
if (buf.readBoolean()) {
this.lastDeathPosition = Pair.of(ProtocolUtils.readString(buf), buf.readLong());
}
this.portalCooldown = ProtocolUtils.readVarInt(buf);
}
@Override @Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0) {
// haha funny, they made 1.20.2 more complicated
this.encode1202Up(buf, version);
} else if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) {
// Minecraft 1.16 and above have significantly more complicated logic for reading this packet, // Minecraft 1.16 and above have significantly more complicated logic for reading this packet,
// so separate it out. // so separate it out.
this.encode116Up(buf, version); this.encode116Up(buf, version);
@ -396,6 +434,43 @@ public class JoinGame implements MinecraftPacket {
} }
} }
private void encode1202Up(ByteBuf buf, ProtocolVersion version) {
buf.writeInt(entityId);
buf.writeBoolean(isHardcore);
ProtocolUtils.writeStringArray(buf, levelNames.toArray(String[]::new));
ProtocolUtils.writeVarInt(buf, maxPlayers);
ProtocolUtils.writeVarInt(buf, viewDistance);
ProtocolUtils.writeVarInt(buf, simulationDistance);
buf.writeBoolean(reducedDebugInfo);
buf.writeBoolean(showRespawnScreen);
buf.writeBoolean(doLimitedCrafting);
ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier());
ProtocolUtils.writeString(buf, dimensionInfo.getLevelName());
buf.writeLong(partialHashedSeed);
buf.writeByte(gamemode);
buf.writeByte(previousGamemode);
buf.writeBoolean(dimensionInfo.isDebugType());
buf.writeBoolean(dimensionInfo.isFlat());
// optional death location
if (lastDeathPosition != null) {
buf.writeBoolean(true);
ProtocolUtils.writeString(buf, lastDeathPosition.key());
buf.writeLong(lastDeathPosition.value());
} else {
buf.writeBoolean(false);
}
ProtocolUtils.writeVarInt(buf, portalCooldown);
}
@Override @Override
public boolean handle(MinecraftSessionHandler handler) { public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this); return handler.handle(this);

Datei anzeigen

@ -0,0 +1,48 @@
/*
* Copyright (C) 2023 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public class LoginAcknowledged implements MinecraftPacket {
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
}
@Override
public int expectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
return 0;
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
}

Datei anzeigen

@ -0,0 +1,49 @@
/*
* Copyright (C) 2018-2021 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public class PingIdentify implements MinecraftPacket {
private int id;
@Override
public String toString() {
return "Ping{" + "id=" + id + '}';
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
id = buf.readInt();
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
buf.writeInt(id);
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
}

Datei anzeigen

@ -17,17 +17,23 @@
package com.velocitypowered.proxy.protocol.packet; package com.velocitypowered.proxy.protocol.packet;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.player.ResourcePackInfo;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.regex.Pattern;
public class ResourcePackRequest implements MinecraftPacket { public class ResourcePackRequest implements MinecraftPacket {
private @MonotonicNonNull String url; private @MonotonicNonNull String url;
@ -35,6 +41,8 @@ public class ResourcePackRequest implements MinecraftPacket {
private boolean isRequired; // 1.17+ private boolean isRequired; // 1.17+
private @Nullable Component prompt; // 1.17+ private @Nullable Component prompt; // 1.17+
private static final Pattern PLAUSIBLE_SHA1_HASH = Pattern.compile("^[a-z0-9]{40}$"); // 1.20.2+
public @Nullable String getUrl() { public @Nullable String getUrl() {
return url; return url;
} }
@ -99,6 +107,19 @@ public class ResourcePackRequest implements MinecraftPacket {
} }
} }
public VelocityResourcePackInfo toServerPromptedPack() {
ResourcePackInfo.Builder builder =
new VelocityResourcePackInfo.BuilderImpl(Preconditions.checkNotNull(url)).setPrompt(prompt)
.setShouldForce(isRequired).setOrigin(ResourcePackInfo.Origin.DOWNSTREAM_SERVER);
if (hash != null && !hash.isEmpty()) {
if (PLAUSIBLE_SHA1_HASH.matcher(hash).matches()) {
builder.setHash(ByteBufUtil.decodeHexDump(hash));
}
}
return (VelocityResourcePackInfo) builder.build();
}
@Override @Override
public boolean handle(MinecraftSessionHandler handler) { public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this); return handler.handle(this);
@ -106,11 +127,7 @@ public class ResourcePackRequest implements MinecraftPacket {
@Override @Override
public String toString() { public String toString() {
return "ResourcePackRequest{" return "ResourcePackRequest{" + "url='" + url + '\'' + ", hash='" + hash + '\'' +
+ "url='" + url + '\'' ", isRequired=" + isRequired + ", prompt='" + prompt + '\'' + '}';
+ ", hash='" + hash + '\''
+ ", isRequired=" + isRequired
+ ", prompt='" + prompt + '\''
+ '}';
} }
} }

Datei anzeigen

@ -73,9 +73,6 @@ public class ResourcePackResponse implements MinecraftPacket {
@Override @Override
public String toString() { public String toString() {
return "ResourcePackResponse{" return "ResourcePackResponse{" + "hash=" + hash + ", " + "status=" + status + '}';
+ "hash=" + hash + ", "
+ "status=" + status
+ '}';
} }
} }

Datei anzeigen

@ -74,10 +74,7 @@ public class ServerLogin implements MinecraftPacket {
@Override @Override
public String toString() { public String toString() {
return "ServerLogin{" return "ServerLogin{" + "username='" + username + '\'' + "playerKey='" + playerKey + '\'' + '}';
+ "username='" + username + '\''
+ "playerKey='" + playerKey + '\''
+ '}';
} }
@Override @Override
@ -98,6 +95,11 @@ public class ServerLogin implements MinecraftPacket {
} }
} }
if (version.compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0) {
this.holderUuid = ProtocolUtils.readUuid(buf);
return;
}
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
if (buf.readBoolean()) { if (buf.readBoolean()) {
holderUuid = ProtocolUtils.readUuid(buf); holderUuid = ProtocolUtils.readUuid(buf);
@ -125,6 +127,11 @@ public class ServerLogin implements MinecraftPacket {
} }
} }
if (version.compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0) {
ProtocolUtils.writeUuid(buf, this.holderUuid);
return;
}
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
if (playerKey != null && playerKey.getSignatureHolder() != null) { if (playerKey != null && playerKey.getSignatureHolder() != null) {
buf.writeBoolean(true); buf.writeBoolean(true);

Datei anzeigen

@ -0,0 +1,63 @@
/*
* Copyright (C) 2018-2023 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol.packet.config;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import net.kyori.adventure.key.Key;
public class ActiveFeatures implements MinecraftPacket {
private Key[] activeFeatures;
public ActiveFeatures(Key[] activeFeatures) {
this.activeFeatures = activeFeatures;
}
public ActiveFeatures() {
this.activeFeatures = new Key[0];
}
public void setActiveFeatures(Key[] activeFeatures) {
this.activeFeatures = activeFeatures;
}
public Key[] getActiveFeatures() {
return activeFeatures;
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
activeFeatures = ProtocolUtils.readKeyArray(buf);
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
ProtocolUtils.writeKeyArray(buf, activeFeatures);
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
}

Datei anzeigen

@ -0,0 +1,48 @@
/*
* Copyright (C) 2018-2023 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol.packet.config;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public class FinishedUpdate implements MinecraftPacket {
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
}
@Override
public int expectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
return 0;
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
}

Datei anzeigen

@ -0,0 +1,50 @@
/*
* Copyright (C) 2018-2023 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol.packet.config;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder;
import io.netty.buffer.ByteBuf;
public class RegistrySync extends DeferredByteBufHolder implements MinecraftPacket {
public RegistrySync() {
super(null);
}
// NBT change in 1.20.2 makes it difficult to parse this packet.
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
this.replace(buf.readRetainedSlice(buf.readableBytes()));
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
buf.writeBytes(content());
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
}

Datei anzeigen

@ -0,0 +1,48 @@
/*
* Copyright (C) 2018-2023 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol.packet.config;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public class StartUpdate implements MinecraftPacket {
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
}
@Override
public int expectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
return 0;
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
}

Datei anzeigen

@ -0,0 +1,82 @@
/*
* Copyright (C) 2018-2023 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol.packet.config;
import com.google.common.collect.ImmutableMap;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import java.util.Map;
public class TagsUpdate implements MinecraftPacket {
private Map<String, Map<String, int[]>> tags;
public TagsUpdate(Map<String, Map<String, int[]>> tags) {
this.tags = tags;
}
public TagsUpdate() {
this.tags = Map.of();
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
ImmutableMap.Builder<String, Map<String, int[]>> builder = ImmutableMap.builder();
int size = ProtocolUtils.readVarInt(buf);
for (int i = 0; i < size; i++) {
String key = ProtocolUtils.readString(buf);
int innerSize = ProtocolUtils.readVarInt(buf);
ImmutableMap.Builder<String, int[]> innerBuilder = ImmutableMap.builder();
for (int j = 0; j < innerSize; j++) {
String innerKey = ProtocolUtils.readString(buf);
int[] innerValue = ProtocolUtils.readVarIntArray(buf);
innerBuilder.put(innerKey, innerValue);
}
builder.put(key, innerBuilder.build());
}
tags = builder.build();
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
ProtocolUtils.writeVarInt(buf, tags.size());
for (Map.Entry<String, Map<String, int[]>> entry : tags.entrySet()) {
ProtocolUtils.writeString(buf, entry.getKey());
// Oh, joy
ProtocolUtils.writeVarInt(buf, entry.getValue().size());
for (Map.Entry<String, int[]> innerEntry : entry.getValue().entrySet()) {
// Yea, object oriented programming be damned
ProtocolUtils.writeString(buf, innerEntry.getKey());
ProtocolUtils.writeVarIntArray(buf, innerEntry.getValue());
}
}
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
}

Datei anzeigen

@ -32,8 +32,8 @@ import java.io.IOException;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
/** /**
* Session handler used to implement * Session handler used to implement {@link VelocityRegisteredServer#ping(EventLoop,
* {@link VelocityRegisteredServer#ping(EventLoop, ProtocolVersion)}. * ProtocolVersion)}.
*/ */
public class PingSessionHandler implements MinecraftSessionHandler { public class PingSessionHandler implements MinecraftSessionHandler {
@ -60,6 +60,7 @@ public class PingSessionHandler implements MinecraftSessionHandler {
handshake.setProtocolVersion(version); handshake.setProtocolVersion(version);
connection.delayedWrite(handshake); connection.delayedWrite(handshake);
connection.setActiveSessionHandler(StateRegistry.STATUS);
connection.setState(StateRegistry.STATUS); connection.setState(StateRegistry.STATUS);
connection.delayedWrite(StatusRequest.INSTANCE); connection.delayedWrite(StatusRequest.INSTANCE);

Datei anzeigen

@ -37,6 +37,7 @@ import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder; import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder; import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder;
@ -94,8 +95,8 @@ public class VelocityRegisteredServer implements RegisteredServer, ForwardingAud
} }
/** /**
* Pings the specified server using the specified event {@code loop}, claiming to be * Pings the specified server using the specified event {@code loop}, claiming to be {@code
* {@code version}. * version}.
* *
* @param loop the event loop to use * @param loop the event loop to use
* @param pingOptions the options to apply to this ping * @param pingOptions the options to apply to this ping
@ -106,35 +107,30 @@ public class VelocityRegisteredServer implements RegisteredServer, ForwardingAud
throw new IllegalStateException("No Velocity proxy instance available"); throw new IllegalStateException("No Velocity proxy instance available");
} }
CompletableFuture<ServerPing> pingFuture = new CompletableFuture<>(); CompletableFuture<ServerPing> pingFuture = new CompletableFuture<>();
server.createBootstrap(loop) server.createBootstrap(loop).handler(new ChannelInitializer<Channel>() {
.handler(new ChannelInitializer<Channel>() { @Override
@Override protected void initChannel(Channel ch) throws Exception {
protected void initChannel(Channel ch) throws Exception { ch.pipeline().addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder())
ch.pipeline() .addLast(READ_TIMEOUT, new ReadTimeoutHandler(
.addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder()) pingOptions.getTimeout() == 0
.addLast(READ_TIMEOUT, ? server.getConfiguration().getReadTimeout()
new ReadTimeoutHandler(pingOptions.getTimeout() == 0 : pingOptions.getTimeout(), TimeUnit.MILLISECONDS))
? server.getConfiguration().getReadTimeout() : pingOptions.getTimeout(), .addLast(FRAME_ENCODER, MinecraftVarintLengthEncoder.INSTANCE)
TimeUnit.MILLISECONDS)) .addLast(MINECRAFT_DECODER, new MinecraftDecoder(ProtocolUtils.Direction.CLIENTBOUND))
.addLast(FRAME_ENCODER, MinecraftVarintLengthEncoder.INSTANCE) .addLast(MINECRAFT_ENCODER, new MinecraftEncoder(ProtocolUtils.Direction.SERVERBOUND));
.addLast(MINECRAFT_DECODER,
new MinecraftDecoder(ProtocolUtils.Direction.CLIENTBOUND))
.addLast(MINECRAFT_ENCODER,
new MinecraftEncoder(ProtocolUtils.Direction.SERVERBOUND));
ch.pipeline().addLast(HANDLER, new MinecraftConnection(ch, server)); ch.pipeline().addLast(HANDLER, new MinecraftConnection(ch, server));
} }
}) }).connect(serverInfo.getAddress()).addListener((ChannelFutureListener) future -> {
.connect(serverInfo.getAddress()) if (future.isSuccess()) {
.addListener((ChannelFutureListener) future -> { MinecraftConnection conn = future.channel().pipeline().get(MinecraftConnection.class);
if (future.isSuccess()) { conn.setActiveSessionHandler(StateRegistry.HANDSHAKE,
MinecraftConnection conn = future.channel().pipeline().get(MinecraftConnection.class); new PingSessionHandler(pingFuture, VelocityRegisteredServer.this, conn,
conn.setSessionHandler(new PingSessionHandler( pingOptions.getProtocolVersion()));
pingFuture, VelocityRegisteredServer.this, conn, pingOptions.getProtocolVersion())); } else {
} else { pingFuture.completeExceptionally(future.cause());
pingFuture.completeExceptionally(future.cause()); }
} });
});
return pingFuture; return pingFuture;
} }