Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix support for 1.20.2+ clients to older servers on bungeecord #3534

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.MessageToMessageEncoder;
import java.util.List;

Expand All @@ -36,6 +37,10 @@ public BungeeEncodeHandler(UserConnection info) {
this.info = info;
}

public UserConnection getInfo() {
return info;
}

@Override
protected void encode(final ChannelHandlerContext ctx, ByteBuf bytebuf, List<Object> out) throws Exception {
if (!ctx.channel().isActive()) {
Expand Down Expand Up @@ -65,7 +70,8 @@ protected void encode(final ChannelHandlerContext ctx, ByteBuf bytebuf, List<Obj

private boolean handleCompressionOrder(ChannelHandlerContext ctx, ByteBuf buf) {
boolean needsCompress = false;
if (!handledCompression && ctx.pipeline().names().indexOf("compress") > ctx.pipeline().names().indexOf("via-encoder")) {
ChannelPipeline pipeline = ctx.pipeline();
if (!handledCompression && shouldFixCompressionOrder(pipeline)) {
// Need to decompress this packet due to bad order
ByteBuf decompressed = BungeePipelineUtil.decompress(ctx, buf);

Expand All @@ -79,18 +85,26 @@ private boolean handleCompressionOrder(ChannelHandlerContext ctx, ByteBuf buf) {
}

// Reorder the pipeline
ChannelHandler decoder = ctx.pipeline().get("via-decoder");
ChannelHandler encoder = ctx.pipeline().get("via-encoder");
ctx.pipeline().remove(decoder);
ctx.pipeline().remove(encoder);
ctx.pipeline().addAfter("decompress", "via-decoder", decoder);
ctx.pipeline().addAfter("compress", "via-encoder", encoder);
fixCompressionPipeline(pipeline);
needsCompress = true;
handledCompression = true;
}
return needsCompress;
}

public static boolean shouldFixCompressionOrder(ChannelPipeline pipeline) {
return pipeline.names().indexOf("compress") > pipeline.names().indexOf("via-encoder");
}

public static void fixCompressionPipeline(ChannelPipeline pipeline) {
ChannelHandler decoder = pipeline.get("via-decoder");
ChannelHandler encoder = pipeline.get("via-encoder");
pipeline.remove(decoder);
pipeline.remove(encoder);
pipeline.addAfter("decompress", "via-decoder", decoder);
pipeline.addAfter("compress", "via-encoder", encoder);
}

private void recompress(ChannelHandlerContext ctx, ByteBuf buf) {
ByteBuf compressed = BungeePipelineUtil.compress(ctx, buf);
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,19 @@
import com.viaversion.viaversion.api.protocol.ProtocolPathEntry;
import com.viaversion.viaversion.api.protocol.ProtocolPipeline;
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
import com.viaversion.viaversion.api.protocol.packet.State;
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
import com.viaversion.viaversion.api.type.Type;
import com.viaversion.viaversion.bungee.storage.BungeeStorage;
import com.viaversion.viaversion.protocols.base.ClientboundLoginPackets;
import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.packets.InventoryPackets;
import com.viaversion.viaversion.protocols.protocol1_20_2to1_20.Protocol1_20_2To1_20;
import com.viaversion.viaversion.protocols.protocol1_9to1_8.ClientboundPackets1_9;
import com.viaversion.viaversion.protocols.protocol1_9to1_8.Protocol1_9To1_8;
import com.viaversion.viaversion.protocols.protocol1_9to1_8.providers.EntityIdProvider;
import com.viaversion.viaversion.protocols.protocol1_9to1_8.storage.EntityTracker1_9;
import io.netty.channel.Channel;
import io.netty.channel.ChannelPipeline;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
Expand All @@ -46,6 +51,7 @@
import java.util.UUID;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.api.event.PostLoginEvent;
import net.md_5.bungee.api.event.ServerConnectEvent;
import net.md_5.bungee.api.event.ServerConnectedEvent;
import net.md_5.bungee.api.event.ServerSwitchEvent;
Expand All @@ -65,6 +71,11 @@ public class BungeeServerHandler implements Listener {
private static final Field entityRewrite;
private static final Field channelWrapper;

private static final Field channelWrapperChannel;
private static final Object GAME;
private static final Method setEncodeProtocol;
private static final Method setDecodeProtocol;

static {
try {
getHandshake = Class.forName("net.md_5.bungee.connection.InitialHandler").getDeclaredMethod("getHandshake");
Expand All @@ -73,16 +84,63 @@ public class BungeeServerHandler implements Listener {
setProtocol = Class.forName("net.md_5.bungee.protocol.packet.Handshake").getDeclaredMethod("setProtocolVersion", int.class);
getEntityMap = Class.forName("net.md_5.bungee.entitymap.EntityMap").getDeclaredMethod("getEntityMap", int.class);
setVersion = Class.forName("net.md_5.bungee.netty.ChannelWrapper").getDeclaredMethod("setVersion", int.class);

Class<?> clazzProtocol = Class.forName("net.md_5.bungee.protocol.Protocol");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can access this class directly

setEncodeProtocol = Class.forName("net.md_5.bungee.netty.ChannelWrapper").getDeclaredMethod("setEncodeProtocol", clazzProtocol);
setDecodeProtocol = Class.forName("net.md_5.bungee.netty.ChannelWrapper").getDeclaredMethod("setDecodeProtocol", clazzProtocol);
GAME = clazzProtocol.getField("GAME").get(null);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This constant as well

channelWrapper = Class.forName("net.md_5.bungee.UserConnection").getDeclaredField("ch");
channelWrapper.setAccessible(true);
entityRewrite = Class.forName("net.md_5.bungee.UserConnection").getDeclaredField("entityRewrite");
entityRewrite.setAccessible(true);

channelWrapperChannel = Class
.forName("net.md_5.bungee.netty.ChannelWrapper")
.getDeclaredField("ch");
channelWrapperChannel.setAccessible(true);
} catch (ReflectiveOperationException e) {
Via.getPlatform().getLogger().severe("Error initializing BungeeServerHandler, try updating BungeeCord or ViaVersion!");
throw new RuntimeException(e);
}
}

@EventHandler
public void onPostLogin(PostLoginEvent event) {
// On 1.20.2, bungeecord does not eagerly send out
// ClientboundLoginPackets.GAME_PROFILE before connecting to the game
// server. However, this leads to skipping over the pipeline handling
// that typically initializes connections, which in turn leads to
// ConnectionManager::getConnectedClient returning null in the code
// below.
//
// To work around this, we emulate the successful login here for 1.20.2+
// clients.
int clientVer = event.getPlayer().getPendingConnection().getVersion();
if (clientVer < ProtocolVersion.v1_20_2.getVersion()) {
return;
}

Channel c;
try {
Object ch = channelWrapper.get(event.getPlayer());
c = (Channel) channelWrapperChannel.get(ch);
Comment on lines +123 to +126
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

channel, channelWrapper
Same below

} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
return;
}

BungeeEncodeHandler encoder = (BungeeEncodeHandler) c.pipeline()
.get("via-encoder");

UserConnection userConnection = encoder.getInfo();
userConnection.getProtocolInfo().setUuid(
event.getPlayer().getUniqueId());
userConnection.getProtocolInfo().setUsername(
event.getPlayer().getName());
Comment on lines +137 to +139
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the line breaks within the setters


Via.getManager().getConnectionManager().onLoginSuccess(userConnection);
}

// Set the handshake version every time someone connects to any server
@EventHandler(priority = 120)
public void onServerConnect(ServerConnectEvent event) {
Expand All @@ -109,6 +167,32 @@ public void onServerConnect(ServerConnectEvent event) {
setProtocol.invoke(handshake, protocols == null ? clientProtocolVersion : serverProtocolVersion);
} catch (InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
return;
}

try {
Object ch = channelWrapper.get(event.getPlayer());
Channel c = (Channel) channelWrapperChannel.get(ch);

// Eagerly fix up compression order to avoid surprises when sending
// packets.
ChannelPipeline pipeline = c.pipeline();
if (BungeeEncodeHandler.shouldFixCompressionOrder(pipeline)) {
BungeeEncodeHandler.fixCompressionPipeline(pipeline);
}

if (serverProtocolVersion < ProtocolVersion.v1_20_2.getVersion()
&& ProtocolVersion.v1_20_2.getVersion() <= clientProtocolVersion
&& event.getPlayer().getServer() == null
&& user.getProtocolInfo().getClientState() == State.LOGIN) {
// Bungeecord does not set itself to the right mode if it was
// initially in 1.20.2+ mode
setEncodeProtocol.invoke(ch, GAME);
setDecodeProtocol.invoke(ch, GAME);
}
} catch (ReflectiveOperationException e) {
e.printStackTrace();
return;
}
}

Expand Down Expand Up @@ -283,10 +367,29 @@ public void checkServerChange(ServerConnectedEvent event, UserConnection user) t
}
}

Object wrapper = channelWrapper.get(player);
setVersion.invoke(wrapper, serverProtocolVersion);
{
Object wrapper = channelWrapper.get(player);
setVersion.invoke(wrapper, serverProtocolVersion);
}

Object entityMap = getEntityMap.invoke(null, serverProtocolVersion);
entityRewrite.set(player, entityMap);

// Complete the login process if bungeecord has skipped it
if (serverProtocolVersion < ProtocolVersion.v1_20_2.getVersion()
&& event.getPlayer().getServer() == null
&& user.getProtocolInfo().getClientState() == State.LOGIN) {
PacketWrapper wrapper = PacketWrapper.create(
ClientboundLoginPackets.GAME_PROFILE,
user);
wrapper.write(Type.UUID, event.getPlayer().getUniqueId());
wrapper.write(Type.STRING, event.getPlayer().getName());
wrapper.write(Type.VAR_INT, 0); // TODO: Properties
try {
wrapper.send(Protocol1_20_2To1_20.class, false);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ public void register() {
ProtocolInfo info = wrapper.user().getProtocolInfo();
if (info.getProtocolVersion() < ProtocolVersion.v1_20_2.getVersion()) { // On 1.20.2+, wait for the login ack
info.setState(State.PLAY);
} else {
// FIXME: Hack to not break on the UUID type when we send it
// manually.
return;
}

UUID uuid = passthroughLoginUUID(wrapper);
Expand Down