mirror of
https://github.com/GeyserMC/Floodgate.git
synced 2025-12-19 14:59:20 +00:00
Fixes #9 and added a debug option for the bungee plugin
This commit is contained in:
@@ -2,7 +2,7 @@ package org.geysermc.floodgate;
|
||||
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToMessageDecoder;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.geysermc.floodgate.HandshakeHandler.HandshakeResult;
|
||||
import org.geysermc.floodgate.injector.BukkitInjector;
|
||||
@@ -14,13 +14,12 @@ import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.geysermc.floodgate.util.ReflectionUtil.*;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class PacketHandler extends MessageToMessageDecoder<Object> {
|
||||
public class PacketHandler extends SimpleChannelInboundHandler<Object> {
|
||||
private static BukkitPlugin plugin = BukkitPlugin.getInstance();
|
||||
private static HandshakeHandler handshakeHandler;
|
||||
|
||||
@@ -52,7 +51,7 @@ public class PacketHandler extends MessageToMessageDecoder<Object> {
|
||||
private boolean bungee;
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, Object packet, List<Object> out) throws Exception {
|
||||
protected void channelRead0(ChannelHandlerContext ctx, Object packet) throws Exception {
|
||||
boolean isHandhake = handshakePacketClass.isInstance(packet);
|
||||
boolean isLogin = loginStartPacketClass.isInstance(packet);
|
||||
|
||||
@@ -94,7 +93,6 @@ public class PacketHandler extends MessageToMessageDecoder<Object> {
|
||||
setValue(networkManager, getFieldOfType(networkManagerClass, SocketAddress.class, false), newAddress);
|
||||
}
|
||||
plugin.getLogger().info("Added " + fPlayer.getJavaUsername() + " " + fPlayer.getJavaUniqueId());
|
||||
out.add(packet);
|
||||
} else if (isLogin) {
|
||||
if (!bungee) {
|
||||
// we have to fake the offline player cycle
|
||||
@@ -109,9 +107,12 @@ public class PacketHandler extends MessageToMessageDecoder<Object> {
|
||||
setValue(loginListener, protocolStateField, readyToAcceptState); // LoginLister#protocolState = READY_TO_ACCEPT
|
||||
// The tick of LoginListener will do the rest
|
||||
}
|
||||
// out.add(packet); don't let this packet through as we want to skip the login cycle
|
||||
}
|
||||
} finally {
|
||||
// don't let the packet through if the packet is the login packet
|
||||
// because we want to skip the login cycle
|
||||
if (!isLogin) ctx.fireChannelRead(packet);
|
||||
|
||||
if (isHandhake && bungee || isLogin && !bungee || fPlayer == null) {
|
||||
// remove the injection of the client because we're finished
|
||||
BukkitInjector.removeInjectedClient(future, ctx.channel());
|
||||
|
||||
@@ -25,6 +25,12 @@
|
||||
<version>${bungee-version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-protocol</artifactId>
|
||||
<version>1.15-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.geysermc</groupId>
|
||||
<artifactId>floodgate-common</artifactId>
|
||||
|
||||
108
bungee/src/main/java/org/geysermc/floodgate/BungeeDebugger.java
Normal file
108
bungee/src/main/java/org/geysermc/floodgate/BungeeDebugger.java
Normal file
@@ -0,0 +1,108 @@
|
||||
package org.geysermc.floodgate;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.channel.socket.ServerSocketChannel;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.md_5.bungee.protocol.MinecraftEncoder;
|
||||
import net.md_5.bungee.protocol.Varint21LengthFieldPrepender;
|
||||
import org.geysermc.floodgate.util.ReflectionUtil;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
|
||||
public class BungeeDebugger {
|
||||
public BungeeDebugger() {
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
Class<?> pipelineUtils = ReflectionUtil.getPrefixedClass("netty.PipelineUtils");
|
||||
Field framePrepender = ReflectionUtil.getField(pipelineUtils, "framePrepender");
|
||||
ReflectionUtil.setFinalValue(null, framePrepender, new CustomVarint21LengthFieldPrepender());
|
||||
}
|
||||
|
||||
@ChannelHandler.Sharable
|
||||
private static class CustomVarint21LengthFieldPrepender extends Varint21LengthFieldPrepender {
|
||||
@Override
|
||||
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
|
||||
// we're getting called before the encoder and decoder are added,
|
||||
// so we have to wait with a nice while loop :D
|
||||
ctx.executor().execute(() -> {
|
||||
System.out.println("Channel: " + ctx.channel().isActive() + " " + ctx.channel().isOpen() + " " + ctx.channel().isRegistered());
|
||||
while (ctx.channel().isOpen()) {
|
||||
System.out.println("Trying to find decoder for " + getHostString(ctx, true) + " " + getParentName(ctx, true));
|
||||
if (ctx.channel().pipeline().get(MinecraftEncoder.class) != null) {
|
||||
System.out.println("Found decoder for " + getHostString(ctx, true));
|
||||
ctx.channel().pipeline().addLast(
|
||||
"floodgate-debug-init",
|
||||
new BungeeChannelInitializer(
|
||||
ctx.channel().parent() instanceof ServerSocketChannel
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static String getHostString(ChannelHandlerContext ctx, boolean alwaysString) {
|
||||
SocketAddress address = ctx.channel().remoteAddress();
|
||||
return address != null ? ((InetSocketAddress) address).getHostString() : (alwaysString ? "null" : null);
|
||||
}
|
||||
|
||||
public static String getParentName(ChannelHandlerContext ctx, boolean alwaysString) {
|
||||
Channel parent = ctx.channel().parent();
|
||||
return parent != null ? parent.getClass().getSimpleName() : (alwaysString ? "null" : null);
|
||||
}
|
||||
|
||||
public static int executorsCount(ChannelHandlerContext ctx) {
|
||||
return ((MultithreadEventLoopGroup) ctx.channel().eventLoop().parent()).executorCount();
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
private static class BungeeChannelInitializer extends ChannelInitializer<Channel> {
|
||||
private final boolean player;
|
||||
|
||||
@Override
|
||||
protected void initChannel(Channel channel) throws Exception {
|
||||
System.out.println("Init " + (player ? "Bungee" : "Server"));
|
||||
// can't add our debugger when the inbound-boss is missing
|
||||
if (channel.pipeline().get("packet-encoder") == null) return;
|
||||
if (channel.pipeline().get("packet-decoder") == null) return;
|
||||
channel.pipeline()
|
||||
.addBefore("packet-decoder", "floodgate-debug-in", new BungeeInPacketHandler(player))
|
||||
.addBefore("packet-encoder", "floodgate-debug-out", new BungeeOutPacketHandler(player));
|
||||
}
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
private static class BungeeInPacketHandler extends SimpleChannelInboundHandler<ByteBuf> {
|
||||
private final boolean player;
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
|
||||
int index = msg.readerIndex();
|
||||
System.out.println((player ? "Player -> Bungee" : "Server -> Bungee") + ":\n" + ByteBufUtil.prettyHexDump(msg));
|
||||
msg.readerIndex(index);
|
||||
ctx.fireChannelRead(msg.retain());
|
||||
}
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
private static class BungeeOutPacketHandler extends MessageToByteEncoder<ByteBuf> {
|
||||
private final boolean player;
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception {
|
||||
int index = msg.readerIndex();
|
||||
System.out.println((player ? "Bungee -> Player" : "Bungee -> Server") + ":\n" + ByteBufUtil.prettyHexDump(msg));
|
||||
msg.readerIndex(index);
|
||||
out.writeBytes(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ public class BungeePlugin extends Plugin implements Listener {
|
||||
private static Field extraHandshakeData;
|
||||
|
||||
@Getter private BungeeFloodgateConfig config;
|
||||
private BungeeDebugger debugger;
|
||||
private HandshakeHandler handshakeHandler;
|
||||
|
||||
@Override
|
||||
@@ -40,6 +41,16 @@ public class BungeePlugin extends Plugin implements Listener {
|
||||
@Override
|
||||
public void onEnable() {
|
||||
getProxy().getPluginManager().registerListener(this, this);
|
||||
if (config.isDebug()) {
|
||||
debugger = new BungeeDebugger();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
if (config.isDebug()) {
|
||||
getLogger().warning("Please note that it is not possible to reload this plugin when debug mode is enabled. At least for now");
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOW)
|
||||
@@ -93,15 +104,15 @@ public class BungeePlugin extends Plugin implements Listener {
|
||||
SocketAddress remoteAddress = ReflectionUtil.getCastedValue(channelWrapper, "remoteAddress", SocketAddress.class);
|
||||
if (!(remoteAddress instanceof InetSocketAddress)) {
|
||||
getLogger().info(
|
||||
"Player " + player.getUsername() + " doesn't use a InetSocketAddress. " +
|
||||
"Player " + player.getUsername() + " doesn't use an InetSocketAddress. " +
|
||||
"It uses " + remoteAddress.getClass().getSimpleName() + ". Ignoring the player, I guess."
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
ReflectionUtil.setValue(
|
||||
channelWrapper, "remoteAddress",
|
||||
new InetSocketAddress(result.getBedrockData().getIp(), ((InetSocketAddress) remoteAddress).getPort())
|
||||
);
|
||||
}
|
||||
ReflectionUtil.setValue(
|
||||
channelWrapper, "remoteAddress",
|
||||
new InetSocketAddress(result.getBedrockData().getIp(), ((InetSocketAddress) remoteAddress).getPort())
|
||||
);
|
||||
event.completeIntent(this);
|
||||
});
|
||||
}
|
||||
@@ -117,7 +128,8 @@ public class BungeePlugin extends Plugin implements Listener {
|
||||
}
|
||||
|
||||
static {
|
||||
Class<?> initial_handler = ReflectionUtil.getClass("net.md_5.bungee.connection.InitialHandler");
|
||||
ReflectionUtil.setPrefix("net.md_5.bungee");
|
||||
Class<?> initial_handler = ReflectionUtil.getPrefixedClass("connection.InitialHandler");
|
||||
extraHandshakeData = ReflectionUtil.getField(initial_handler, "extraDataInHandshake");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,9 @@ public class FloodgateConfig {
|
||||
@JsonProperty(value = "disconnect")
|
||||
private DisconnectMessages messages;
|
||||
|
||||
@JsonProperty
|
||||
private boolean debug;
|
||||
|
||||
@JsonIgnore
|
||||
PrivateKey privateKey = null;
|
||||
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package org.geysermc.floodgate;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.*;
|
||||
import org.geysermc.floodgate.util.BedrockData;
|
||||
import org.geysermc.floodgate.util.EncryptionUtil;
|
||||
|
||||
@@ -53,7 +50,7 @@ public class HandshakeHandler {
|
||||
}
|
||||
|
||||
@AllArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
@Getter
|
||||
@Getter @ToString
|
||||
public static class HandshakeResult {
|
||||
private ResultType resultType;
|
||||
private String[] handshakeData;
|
||||
|
||||
@@ -3,10 +3,7 @@ package org.geysermc.floodgate.util;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.lang.reflect.AccessibleObject;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.*;
|
||||
|
||||
public class ReflectionUtil {
|
||||
/**
|
||||
@@ -94,6 +91,39 @@ public class ReflectionUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean setFinalValue(Object instance, Field field, Object value) {
|
||||
try {
|
||||
makeAccessible(field);
|
||||
|
||||
Field modifiersField = null;
|
||||
int modifiers = field.getModifiers();
|
||||
if (Modifier.isFinal(modifiers)) {
|
||||
try {
|
||||
modifiersField = Field.class.getDeclaredField("modifiers");
|
||||
} catch (NoSuchFieldException e) {
|
||||
// Java 12 compatibility, thanks to https://github.com/powermock/powermock/pull/1010
|
||||
Method getDeclaredFields0 = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
|
||||
makeAccessible(getDeclaredFields0);
|
||||
Field[] fields = (Field[]) getDeclaredFields0.invoke(Field.class, false);
|
||||
for (Field classField : fields) {
|
||||
if ("modifiers".equals(classField.getName())) {
|
||||
modifiersField = classField;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
assert modifiersField != null;
|
||||
makeAccessible(modifiersField);
|
||||
modifiersField.setInt(field, modifiers & ~Modifier.FINAL);
|
||||
}
|
||||
setValue(instance, field, value);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static Method getMethod(Class<?> clazz, String method, Class<?>... args) {
|
||||
try {
|
||||
return clazz.getMethod(method, args);
|
||||
|
||||
Reference in New Issue
Block a user