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.ChannelFuture;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.handler.codec.MessageToMessageDecoder;
|
import io.netty.channel.SimpleChannelInboundHandler;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.geysermc.floodgate.HandshakeHandler.HandshakeResult;
|
import org.geysermc.floodgate.HandshakeHandler.HandshakeResult;
|
||||||
import org.geysermc.floodgate.injector.BukkitInjector;
|
import org.geysermc.floodgate.injector.BukkitInjector;
|
||||||
@@ -14,13 +14,12 @@ import java.lang.reflect.Field;
|
|||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import static org.geysermc.floodgate.util.ReflectionUtil.*;
|
import static org.geysermc.floodgate.util.ReflectionUtil.*;
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class PacketHandler extends MessageToMessageDecoder<Object> {
|
public class PacketHandler extends SimpleChannelInboundHandler<Object> {
|
||||||
private static BukkitPlugin plugin = BukkitPlugin.getInstance();
|
private static BukkitPlugin plugin = BukkitPlugin.getInstance();
|
||||||
private static HandshakeHandler handshakeHandler;
|
private static HandshakeHandler handshakeHandler;
|
||||||
|
|
||||||
@@ -52,7 +51,7 @@ public class PacketHandler extends MessageToMessageDecoder<Object> {
|
|||||||
private boolean bungee;
|
private boolean bungee;
|
||||||
|
|
||||||
@Override
|
@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 isHandhake = handshakePacketClass.isInstance(packet);
|
||||||
boolean isLogin = loginStartPacketClass.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);
|
setValue(networkManager, getFieldOfType(networkManagerClass, SocketAddress.class, false), newAddress);
|
||||||
}
|
}
|
||||||
plugin.getLogger().info("Added " + fPlayer.getJavaUsername() + " " + fPlayer.getJavaUniqueId());
|
plugin.getLogger().info("Added " + fPlayer.getJavaUsername() + " " + fPlayer.getJavaUniqueId());
|
||||||
out.add(packet);
|
|
||||||
} else if (isLogin) {
|
} else if (isLogin) {
|
||||||
if (!bungee) {
|
if (!bungee) {
|
||||||
// we have to fake the offline player cycle
|
// 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
|
setValue(loginListener, protocolStateField, readyToAcceptState); // LoginLister#protocolState = READY_TO_ACCEPT
|
||||||
// The tick of LoginListener will do the rest
|
// 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 {
|
} 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) {
|
if (isHandhake && bungee || isLogin && !bungee || fPlayer == null) {
|
||||||
// remove the injection of the client because we're finished
|
// remove the injection of the client because we're finished
|
||||||
BukkitInjector.removeInjectedClient(future, ctx.channel());
|
BukkitInjector.removeInjectedClient(future, ctx.channel());
|
||||||
|
|||||||
@@ -25,6 +25,12 @@
|
|||||||
<version>${bungee-version}</version>
|
<version>${bungee-version}</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.md-5</groupId>
|
||||||
|
<artifactId>bungeecord-protocol</artifactId>
|
||||||
|
<version>1.15-SNAPSHOT</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.geysermc</groupId>
|
<groupId>org.geysermc</groupId>
|
||||||
<artifactId>floodgate-common</artifactId>
|
<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;
|
private static Field extraHandshakeData;
|
||||||
|
|
||||||
@Getter private BungeeFloodgateConfig config;
|
@Getter private BungeeFloodgateConfig config;
|
||||||
|
private BungeeDebugger debugger;
|
||||||
private HandshakeHandler handshakeHandler;
|
private HandshakeHandler handshakeHandler;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -40,6 +41,16 @@ public class BungeePlugin extends Plugin implements Listener {
|
|||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
getProxy().getPluginManager().registerListener(this, this);
|
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)
|
@EventHandler(priority = EventPriority.LOW)
|
||||||
@@ -93,15 +104,15 @@ public class BungeePlugin extends Plugin implements Listener {
|
|||||||
SocketAddress remoteAddress = ReflectionUtil.getCastedValue(channelWrapper, "remoteAddress", SocketAddress.class);
|
SocketAddress remoteAddress = ReflectionUtil.getCastedValue(channelWrapper, "remoteAddress", SocketAddress.class);
|
||||||
if (!(remoteAddress instanceof InetSocketAddress)) {
|
if (!(remoteAddress instanceof InetSocketAddress)) {
|
||||||
getLogger().info(
|
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."
|
"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);
|
event.completeIntent(this);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -117,7 +128,8 @@ public class BungeePlugin extends Plugin implements Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static {
|
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");
|
extraHandshakeData = ReflectionUtil.getField(initial_handler, "extraDataInHandshake");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ public class FloodgateConfig {
|
|||||||
@JsonProperty(value = "disconnect")
|
@JsonProperty(value = "disconnect")
|
||||||
private DisconnectMessages messages;
|
private DisconnectMessages messages;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
private boolean debug;
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
PrivateKey privateKey = null;
|
PrivateKey privateKey = null;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
package org.geysermc.floodgate;
|
package org.geysermc.floodgate;
|
||||||
|
|
||||||
import lombok.AccessLevel;
|
import lombok.*;
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.NonNull;
|
|
||||||
import org.geysermc.floodgate.util.BedrockData;
|
import org.geysermc.floodgate.util.BedrockData;
|
||||||
import org.geysermc.floodgate.util.EncryptionUtil;
|
import org.geysermc.floodgate.util.EncryptionUtil;
|
||||||
|
|
||||||
@@ -53,7 +50,7 @@ public class HandshakeHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@AllArgsConstructor(access = AccessLevel.PROTECTED)
|
@AllArgsConstructor(access = AccessLevel.PROTECTED)
|
||||||
@Getter
|
@Getter @ToString
|
||||||
public static class HandshakeResult {
|
public static class HandshakeResult {
|
||||||
private ResultType resultType;
|
private ResultType resultType;
|
||||||
private String[] handshakeData;
|
private String[] handshakeData;
|
||||||
|
|||||||
@@ -3,10 +3,7 @@ package org.geysermc.floodgate.util;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
import java.lang.reflect.AccessibleObject;
|
import java.lang.reflect.*;
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
|
|
||||||
public class ReflectionUtil {
|
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) {
|
public static Method getMethod(Class<?> clazz, String method, Class<?>... args) {
|
||||||
try {
|
try {
|
||||||
return clazz.getMethod(method, args);
|
return clazz.getMethod(method, args);
|
||||||
|
|||||||
Reference in New Issue
Block a user