mirror of
https://github.com/GeyserMC/Floodgate.git
synced 2025-12-28 02:59:16 +00:00
Adds experimental Velocity support, fixed bugs and changed some class names
This commit is contained in:
@@ -3,8 +3,8 @@
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>floodgate-parent</artifactId>
|
||||
<groupId>org.geysermc</groupId>
|
||||
<artifactId>floodgate-parent</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
@@ -22,7 +22,7 @@
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>bukkit</artifactId>
|
||||
<version>1.8.8-R0.1-SNAPSHOT</version>
|
||||
<version>${bukkit-version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
package org.geysermc.floodgate;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class BukkitFloodgateAPI extends FloodgateAPI {
|
||||
/**
|
||||
* See {@link FloodgateAPI#getPlayer(UUID)}
|
||||
*/
|
||||
public static FloodgatePlayer getPlayer(Player player) {
|
||||
return getPlayer(player.getUniqueId());
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ public class BukkitPlugin extends JavaPlugin {
|
||||
if (!getDataFolder().exists()) {
|
||||
getDataFolder().mkdir();
|
||||
}
|
||||
ReflectionUtil.setServerVersion(getServer().getClass().getPackage().getName().split("\\.")[3]);
|
||||
ReflectionUtil.setPrefix("net.minecraft.server." + getServer().getClass().getPackage().getName().split("\\.")[3]);
|
||||
|
||||
configuration = FloodgateConfig.load(getLogger(), getDataFolder().toPath().resolve("config.yml"));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.geysermc.floodgate;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class FloodgateAPI extends AbstractFloodgateAPI {
|
||||
/**
|
||||
* See {@link AbstractFloodgateAPI#getPlayer(UUID)}
|
||||
*/
|
||||
public static FloodgatePlayer getPlayer(Player player) {
|
||||
return getPlayer(player.getUniqueId());
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link AbstractFloodgateAPI#isBedrockPlayer(UUID)}
|
||||
*/
|
||||
public static boolean isBedrockPlayer(Player player) {
|
||||
return isBedrockPlayer(player.getUniqueId());
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,9 @@ import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToMessageDecoder;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.geysermc.floodgate.HandshakeHandler.HandshakeResult;
|
||||
import org.geysermc.floodgate.injector.BukkitInjector;
|
||||
import org.geysermc.floodgate.util.BedrockData;
|
||||
import org.geysermc.floodgate.util.EncryptionUtil;
|
||||
import org.geysermc.floodgate.util.ReflectionUtil;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
@@ -17,12 +17,13 @@ import java.net.SocketAddress;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.geysermc.floodgate.util.BedrockData.FLOODGATE_IDENTIFIER;
|
||||
import static org.geysermc.floodgate.util.ReflectionUtil.*;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class PacketHandler extends MessageToMessageDecoder<Object> {
|
||||
private static BukkitPlugin plugin = BukkitPlugin.getInstance();
|
||||
private static HandshakeHandler handshakeHandler;
|
||||
|
||||
private static Class<?> networkManagerClass;
|
||||
private static Class<?> handshakePacketClass;
|
||||
private static Field hostField;
|
||||
@@ -47,54 +48,52 @@ public class PacketHandler extends MessageToMessageDecoder<Object> {
|
||||
|
||||
private final ChannelFuture future;
|
||||
private Object networkManager;
|
||||
private FloodgatePlayer floodgatePlayer;
|
||||
private FloodgatePlayer fPlayer;
|
||||
private boolean bungee;
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, Object packet, List<Object> out) throws Exception {
|
||||
boolean isHandhake = handshakePacketClass.isInstance(packet);
|
||||
boolean isLogin = loginStartPacketClass.isInstance(packet);
|
||||
|
||||
try {
|
||||
if (isHandhake) {
|
||||
networkManager = ctx.channel().pipeline().get("packet_handler");
|
||||
|
||||
String[] host = getCastedValue(packet, hostField, String.class).split("\0");
|
||||
bungee = host.length == 6 || host.length == 7; // 4 = normal | 6, 7 = bungee
|
||||
|
||||
if ((host.length == 4 || bungee) && host[1].equals(FLOODGATE_IDENTIFIER)) {
|
||||
BedrockData bedrockData = EncryptionUtil.decryptBedrockData(
|
||||
plugin.getConfiguration().getPrivateKey(),
|
||||
host[2] + '\0' + host[3]
|
||||
);
|
||||
|
||||
if (bedrockData.getDataLength() != BedrockData.EXPECTED_LENGTH) {
|
||||
HandshakeResult result = handshakeHandler.handle(getCastedValue(packet, hostField, String.class));
|
||||
switch (result.getResultType()) {
|
||||
case SUCCESS:
|
||||
break;
|
||||
case INVALID_DATA_LENGTH:
|
||||
plugin.getLogger().info(String.format(
|
||||
plugin.getConfiguration().getMessages().getInvalidArgumentsLength(),
|
||||
BedrockData.EXPECTED_LENGTH, bedrockData.getDataLength()
|
||||
BedrockData.EXPECTED_LENGTH, result.getBedrockData().getDataLength()
|
||||
));
|
||||
ctx.close(); //todo add option to see disconnect message?
|
||||
}
|
||||
|
||||
FloodgatePlayer player = floodgatePlayer = new FloodgatePlayer(bedrockData);
|
||||
FloodgateAPI.players.put(player.getJavaUniqueId(), player);
|
||||
|
||||
if (bungee) {
|
||||
setValue(packet, hostField, host[0] + '\0' +
|
||||
bedrockData.getIp() + '\0' + player.getJavaUniqueId() +
|
||||
(host.length == 7 ? '\0' + host[6] : "")
|
||||
);
|
||||
} else {
|
||||
// Use a spoofedUUID for initUUID (just like Bungeecord)
|
||||
setValue(networkManager, "spoofedUUID", player.getJavaUniqueId());
|
||||
// Use the player his IP for stuff instead of Geyser his IP
|
||||
SocketAddress newAddress = new InetSocketAddress(
|
||||
bedrockData.getIp(),
|
||||
((InetSocketAddress) ctx.channel().remoteAddress()).getPort()
|
||||
);
|
||||
setValue(networkManager, getFieldOfType(networkManagerClass, SocketAddress.class, false), newAddress);
|
||||
}
|
||||
plugin.getLogger().info("Added " + player.getJavaUsername() + " " + player.getJavaUniqueId());
|
||||
ctx.close();
|
||||
default: // only continue when SUCCESS
|
||||
return;
|
||||
}
|
||||
|
||||
fPlayer = result.getFloodgatePlayer();
|
||||
BedrockData bedrockData = result.getBedrockData();
|
||||
String[] data = result.getHandshakeData();
|
||||
|
||||
if (bungee = (data.length == 6 || data.length == 7)) {
|
||||
setValue(packet, hostField, data[0] + '\0' +
|
||||
bedrockData.getIp() + '\0' + fPlayer.getJavaUniqueId() +
|
||||
(data.length == 7 ? '\0' + data[6] : "")
|
||||
);
|
||||
} else {
|
||||
// Use a spoofedUUID for initUUID (just like Bungeecord)
|
||||
setValue(networkManager, "spoofedUUID", fPlayer.getJavaUniqueId());
|
||||
// Use the player his IP for stuff instead of Geyser his IP
|
||||
SocketAddress newAddress = new InetSocketAddress(
|
||||
bedrockData.getIp(),
|
||||
((InetSocketAddress) ctx.channel().remoteAddress()).getPort()
|
||||
);
|
||||
setValue(networkManager, getFieldOfType(networkManagerClass, SocketAddress.class, false), newAddress);
|
||||
}
|
||||
plugin.getLogger().info("Added " + fPlayer.getJavaUsername() + " " + fPlayer.getJavaUniqueId());
|
||||
out.add(packet);
|
||||
} else if (isLogin) {
|
||||
if (!bungee) {
|
||||
@@ -102,7 +101,7 @@ public class PacketHandler extends MessageToMessageDecoder<Object> {
|
||||
Object loginListener = packetListenerField.get(networkManager);
|
||||
|
||||
// Set the player his GameProfile
|
||||
Object gameProfile = gameProfileConstructor.newInstance(floodgatePlayer.getJavaUniqueId(), floodgatePlayer.getJavaUsername());
|
||||
Object gameProfile = gameProfileConstructor.newInstance(fPlayer.getJavaUniqueId(), fPlayer.getJavaUsername());
|
||||
setValue(loginListener, gameProfileField, gameProfile);
|
||||
|
||||
initUUIDMethod.invoke(loginListener); // LoginListener#initUUID
|
||||
@@ -113,7 +112,7 @@ public class PacketHandler extends MessageToMessageDecoder<Object> {
|
||||
// out.add(packet); don't let this packet through as we want to skip the login cycle
|
||||
}
|
||||
} finally {
|
||||
if (isHandhake && bungee || isLogin && !bungee || floodgatePlayer == null) {
|
||||
if (isHandhake && bungee || isLogin && !bungee || fPlayer == null) {
|
||||
// remove the injection of the client because we're finished
|
||||
BukkitInjector.removeInjectedClient(future, ctx.channel());
|
||||
}
|
||||
@@ -127,8 +126,10 @@ public class PacketHandler extends MessageToMessageDecoder<Object> {
|
||||
}
|
||||
|
||||
static {
|
||||
networkManagerClass = getNMSClass("NetworkManager");
|
||||
loginStartPacketClass = getNMSClass("PacketLoginInStart");
|
||||
handshakeHandler = new HandshakeHandler(plugin.getConfiguration().getPrivateKey(), false);
|
||||
|
||||
networkManagerClass = getPrefixedClass("NetworkManager");
|
||||
loginStartPacketClass = getPrefixedClass("PacketLoginInStart");
|
||||
|
||||
gameProfileClass = ReflectionUtil.getClass("com.mojang.authlib.GameProfile");
|
||||
assert gameProfileClass != null;
|
||||
@@ -138,11 +139,11 @@ public class PacketHandler extends MessageToMessageDecoder<Object> {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
handshakePacketClass = getNMSClass("PacketHandshakingInSetProtocol");
|
||||
handshakePacketClass = getPrefixedClass("PacketHandshakingInSetProtocol");
|
||||
assert handshakePacketClass != null;
|
||||
hostField = getFieldOfType(handshakePacketClass, String.class, true);
|
||||
|
||||
loginListenerClass = getNMSClass("LoginListener");
|
||||
loginListenerClass = getPrefixedClass("LoginListener");
|
||||
assert loginListenerClass != null;
|
||||
gameProfileField = getFieldOfType(loginListenerClass, gameProfileClass, true);
|
||||
initUUIDMethod = getMethod(loginListenerClass, "initUUID");
|
||||
@@ -161,10 +162,10 @@ public class PacketHandler extends MessageToMessageDecoder<Object> {
|
||||
}
|
||||
}
|
||||
|
||||
Class<?> packetListenerClass = getNMSClass("PacketListener");
|
||||
Class<?> packetListenerClass = getPrefixedClass("PacketListener");
|
||||
packetListenerField = getFieldOfType(networkManagerClass, packetListenerClass, true);
|
||||
|
||||
loginHandlerClass = getNMSClass("LoginListener$LoginHandler");
|
||||
loginHandlerClass = getPrefixedClass("LoginListener$LoginHandler");
|
||||
assert loginHandlerClass != null;
|
||||
try {
|
||||
loginHandlerConstructor = makeAccessible(loginHandlerClass.getDeclaredConstructor(loginListenerClass));
|
||||
|
||||
@@ -20,6 +20,7 @@ public class BukkitInjector {
|
||||
private static String injectedFieldName;
|
||||
|
||||
public static boolean inject() throws Exception {
|
||||
if (isInjected()) return true;
|
||||
if (getServerConnection() != null) {
|
||||
for (Field f : serverConnection.getClass().getDeclaredFields()) {
|
||||
if (f.getType() == List.class) {
|
||||
@@ -102,7 +103,7 @@ public class BukkitInjector {
|
||||
|
||||
public static Object getServerConnection() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
|
||||
if (serverConnection != null) return serverConnection;
|
||||
Class<?> minecraftServer = ReflectionUtil.getNMSClass("MinecraftServer");
|
||||
Class<?> minecraftServer = ReflectionUtil.getPrefixedClass("MinecraftServer");
|
||||
assert minecraftServer != null;
|
||||
|
||||
Object minecraftServerInstance = ReflectionUtil.invokeStatic(minecraftServer, "getServer");
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
name: ${project.name}
|
||||
description: ${project.description}
|
||||
version: ${project.version}
|
||||
author: ${project.organization.name}
|
||||
website: ${project.url}
|
||||
main: org.geysermc.floodgate.BukkitPlugin
|
||||
@@ -3,14 +3,21 @@
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>floodgate-parent</artifactId>
|
||||
<groupId>org.geysermc</groupId>
|
||||
<artifactId>floodgate-parent</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>floodgate-bungee</artifactId>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>bungeecord-repo</id>
|
||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.geysermc.floodgate;
|
||||
|
||||
import lombok.Getter;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
|
||||
import net.md_5.bungee.api.event.PreLoginEvent;
|
||||
import net.md_5.bungee.api.event.ServerConnectEvent;
|
||||
@@ -10,34 +9,31 @@ import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
import net.md_5.bungee.event.EventPriority;
|
||||
import net.md_5.bungee.protocol.packet.Handshake;
|
||||
import org.geysermc.floodgate.HandshakeHandler.HandshakeResult;
|
||||
import org.geysermc.floodgate.util.BedrockData;
|
||||
import org.geysermc.floodgate.util.EncryptionUtil;
|
||||
import org.geysermc.floodgate.util.ReflectionUtil;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import static org.geysermc.floodgate.util.BedrockData.FLOODGATE_IDENTIFIER;
|
||||
|
||||
public class BungeePlugin extends Plugin implements Listener {
|
||||
@Getter private static BungeePlugin instance;
|
||||
@Getter private BungeeFloodgateConfig config = null;
|
||||
private static Field extraHandshakeData;
|
||||
|
||||
@Getter private BungeeFloodgateConfig config;
|
||||
private HandshakeHandler handshakeHandler;
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
instance = this;
|
||||
if (!getDataFolder().exists()) {
|
||||
getDataFolder().mkdir();
|
||||
}
|
||||
|
||||
config = FloodgateConfig.load(getLogger(), getDataFolder().toPath().resolve("config.yml"), BungeeFloodgateConfig.class);
|
||||
handshakeHandler = new HandshakeHandler(config.getPrivateKey(), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -48,87 +44,73 @@ public class BungeePlugin extends Plugin implements Listener {
|
||||
@EventHandler(priority = EventPriority.LOW)
|
||||
public void onServerConnect(ServerConnectEvent e) {
|
||||
// Passes the information through to the connecting server if enabled
|
||||
if (config.isSendFloodgateData() && BungeeFloodgateAPI.isBedrockPlayer(e.getPlayer())) {
|
||||
if (config.isSendFloodgateData() && FloodgateAPI.isBedrockPlayer(e.getPlayer())) {
|
||||
Handshake handshake = ReflectionUtil.getCastedValue(e.getPlayer().getPendingConnection(), "handshake", Handshake.class);
|
||||
handshake.setHost(
|
||||
handshake.getHost().split("\0")[0] + '\0' + // Ensures that only the hostname remains!
|
||||
FLOODGATE_IDENTIFIER + '\0' + BungeeFloodgateAPI.getEncryptedData(e.getPlayer().getUniqueId())
|
||||
FLOODGATE_IDENTIFIER + '\0' + FloodgateAPI.getEncryptedData(e.getPlayer().getUniqueId())
|
||||
);
|
||||
// Bungeecord will add his data after our data
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOW)
|
||||
public void onPreLogin(PreLoginEvent event) {
|
||||
// only need to check when server is in online mode :D
|
||||
if (ProxyServer.getInstance().getConfig().isOnlineMode()) {
|
||||
event.registerIntent(this);
|
||||
event.registerIntent(this);
|
||||
getProxy().getScheduler().runAsync(this, () -> {
|
||||
String extraData = ReflectionUtil.getCastedValue(event.getConnection(), extraHandshakeData, String.class);
|
||||
|
||||
getProxy().getScheduler().runAsync(this, () -> {
|
||||
String extraData = ReflectionUtil.getCastedValue(event.getConnection(), extraHandshakeData, String.class);
|
||||
String[] data = extraData.split("\0");
|
||||
HandshakeResult result = handshakeHandler.handle(extraData);
|
||||
switch (result.getResultType()) {
|
||||
case SUCCESS:
|
||||
break;
|
||||
case EXCEPTION:
|
||||
event.setCancelReason(config.getMessages().getInvalidKey());
|
||||
case INVALID_DATA_LENGTH:
|
||||
event.setCancelReason(String.format(
|
||||
config.getMessages().getInvalidArgumentsLength(),
|
||||
BedrockData.EXPECTED_LENGTH, result.getBedrockData().getDataLength()
|
||||
));
|
||||
default: // only continue when SUCCESS
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.length == 4 && data[1].equals(FLOODGATE_IDENTIFIER)) {
|
||||
try {
|
||||
BedrockData bedrockData = EncryptionUtil.decryptBedrockData(
|
||||
config.getPrivateKey(), data[2] + '\0' + data[3]
|
||||
);
|
||||
FloodgatePlayer player = result.getFloodgatePlayer();
|
||||
FloodgateAPI.addEncryptedData(player.getJavaUniqueId(), result.getHandshakeData()[2] + '\0' + result.getHandshakeData()[3]);
|
||||
|
||||
if (bedrockData.getDataLength() != BedrockData.EXPECTED_LENGTH) {
|
||||
event.setCancelReason(String.format(
|
||||
config.getMessages().getInvalidArgumentsLength(),
|
||||
BedrockData.EXPECTED_LENGTH, bedrockData.getDataLength()
|
||||
));
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
event.getConnection().setOnlineMode(false);
|
||||
event.getConnection().setUniqueId(player.getJavaUniqueId());
|
||||
|
||||
FloodgatePlayer player = new FloodgatePlayer(bedrockData);
|
||||
FloodgateAPI.players.put(player.getJavaUniqueId(), player);
|
||||
BungeeFloodgateAPI.addEncryptedData(player.getJavaUniqueId(), data[2] + '\0' + data[3]);
|
||||
|
||||
event.getConnection().setOnlineMode(false);
|
||||
event.getConnection().setUniqueId(player.getJavaUniqueId());
|
||||
|
||||
ReflectionUtil.setValue(event.getConnection(), "name", player.getJavaUsername());
|
||||
Object channelWrapper = ReflectionUtil.getValue(event.getConnection(), "ch");
|
||||
SocketAddress remoteAddress = ReflectionUtil.getCastedValue(channelWrapper, "remoteAddress", SocketAddress.class);
|
||||
if (!(remoteAddress instanceof InetSocketAddress)) {
|
||||
getLogger().info(
|
||||
"Player " + player.getUsername() + " doesn't use a InetSocketAddress. " +
|
||||
"It uses " + remoteAddress.getClass().getSimpleName() + ". Ignoring the player, I guess."
|
||||
);
|
||||
return;
|
||||
}
|
||||
ReflectionUtil.setValue(
|
||||
channelWrapper, "remoteAddress",
|
||||
new InetSocketAddress(bedrockData.getIp(), ((InetSocketAddress) remoteAddress).getPort())
|
||||
);
|
||||
|
||||
System.out.println("Added " + player.getUsername() + " " + player.getJavaUniqueId());
|
||||
} catch (NullPointerException | NoSuchPaddingException | NoSuchAlgorithmException |
|
||||
InvalidKeyException | BadPaddingException | IllegalBlockSizeException e) {
|
||||
event.setCancelReason(config.getMessages().getInvalidKey());
|
||||
event.setCancelled(true);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
event.completeIntent(this);
|
||||
});
|
||||
}
|
||||
ReflectionUtil.setValue(event.getConnection(), "name", player.getJavaUsername());
|
||||
Object channelWrapper = ReflectionUtil.getValue(event.getConnection(), "ch");
|
||||
SocketAddress remoteAddress = ReflectionUtil.getCastedValue(channelWrapper, "remoteAddress", SocketAddress.class);
|
||||
if (!(remoteAddress instanceof InetSocketAddress)) {
|
||||
getLogger().info(
|
||||
"Player " + player.getUsername() + " doesn't use a InetSocketAddress. " +
|
||||
"It uses " + remoteAddress.getClass().getSimpleName() + ". Ignoring the player, I guess."
|
||||
);
|
||||
return;
|
||||
}
|
||||
ReflectionUtil.setValue(
|
||||
channelWrapper, "remoteAddress",
|
||||
new InetSocketAddress(result.getBedrockData().getIp(), ((InetSocketAddress) remoteAddress).getPort())
|
||||
);
|
||||
event.completeIntent(this);
|
||||
});
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerDisconnect(PlayerDisconnectEvent event) {
|
||||
FloodgatePlayer player = BungeeFloodgateAPI.getPlayerByConnection(event.getPlayer().getPendingConnection());
|
||||
FloodgatePlayer player = FloodgateAPI.getPlayerByConnection(event.getPlayer().getPendingConnection());
|
||||
if (player != null) {
|
||||
FloodgateAPI.players.remove(player.getJavaUniqueId());
|
||||
BungeeFloodgateAPI.removeEncryptedData(player.getJavaUniqueId());
|
||||
FloodgateAPI.removeEncryptedData(player.getJavaUniqueId());
|
||||
System.out.println("Removed " + player.getUsername() + " " + event.getPlayer().getUniqueId());
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
Class<?> initial_handler = ReflectionUtil.getClass("net.md_5.bungee.connection.InitialHandler");
|
||||
extraHandshakeData = ReflectionUtil.getField(initial_handler, "getExtraDataInHandshake");
|
||||
extraHandshakeData = ReflectionUtil.getField(initial_handler, "extraDataInHandshake");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,11 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class BungeeFloodgateAPI extends FloodgateAPI {
|
||||
public class FloodgateAPI extends AbstractFloodgateAPI {
|
||||
private static Map<UUID, String> encryptedData = new HashMap<>();
|
||||
|
||||
static void addEncryptedData(UUID uuid, String encryptedData) {
|
||||
BungeeFloodgateAPI.encryptedData.put(uuid, encryptedData); // just override it I guess
|
||||
FloodgateAPI.encryptedData.put(uuid, encryptedData); // just override it I guess
|
||||
}
|
||||
|
||||
static void removeEncryptedData(UUID uuid) {
|
||||
@@ -23,7 +23,7 @@ public class BungeeFloodgateAPI extends FloodgateAPI {
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link FloodgateAPI#getPlayer(UUID)}
|
||||
* See {@link AbstractFloodgateAPI#getPlayer(UUID)}
|
||||
*/
|
||||
public static FloodgatePlayer getPlayerByConnection(PendingConnection connection) {
|
||||
return getPlayer(connection.getUniqueId());
|
||||
@@ -1,11 +1,9 @@
|
||||
# In Floodgate bedrock player data is send encrypted
|
||||
# The following value should point to the key Floodgate generated.
|
||||
# The public key should be used for the Geyser(s) and the private key for
|
||||
# - bungeecord
|
||||
# - bukkit (craftbukkit, spigot etc.)
|
||||
# The public key should be used for the Geyser(s) and the private key for the Floodgate(s)
|
||||
key-file-name: key.pem
|
||||
|
||||
# Should Bungeecord send the bedrock player data to the servers it is connecting to?
|
||||
# Should Velocity send the bedrock player data to the servers it is connecting to?
|
||||
# This requires Floodgate to be installed on the servers.
|
||||
# You'll get kicked if you don't use the plugin. The default value is false because of it
|
||||
send-floodgate-data: false
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
name: ${project.name}
|
||||
description: ${project.description}
|
||||
version: ${project.version}
|
||||
author: ${project.organization.name}
|
||||
main: org.geysermc.floodgate.BungeePlugin
|
||||
@@ -1,12 +1,10 @@
|
||||
package org.geysermc.floodgate;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonEnumDefaultValue;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
class FloodgateAPI {
|
||||
abstract class AbstractFloodgateAPI {
|
||||
static final Map<UUID, FloodgatePlayer> players = new HashMap<>();
|
||||
|
||||
/**
|
||||
@@ -33,27 +31,4 @@ class FloodgateAPI {
|
||||
public static UUID createJavaPlayerId(long xuid) {
|
||||
return new UUID(0, xuid);
|
||||
}
|
||||
|
||||
public enum DeviceOS {
|
||||
@JsonEnumDefaultValue
|
||||
UNKOWN,
|
||||
ANDROID,
|
||||
IOS,
|
||||
OSX,
|
||||
FIREOS,
|
||||
GEARVR,
|
||||
HOLOLENS,
|
||||
WIN10,
|
||||
WIN32,
|
||||
DEDICATED,
|
||||
ORBIS,
|
||||
NX,
|
||||
SWITCH;
|
||||
|
||||
private static final DeviceOS[] VALUES = values();
|
||||
|
||||
public static DeviceOS getById(int id) {
|
||||
return id < VALUES.length ? VALUES[id] : VALUES[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,7 +80,10 @@ public class FloodgateConfig {
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "Error while loading config", e);
|
||||
}
|
||||
if (config == null) return null;
|
||||
|
||||
if (config == null) {
|
||||
throw new RuntimeException("Failed to load config file! Try to delete the data folder of Floodgate");
|
||||
}
|
||||
|
||||
try {
|
||||
config.privateKey = EncryptionUtil.getKeyFromFile(
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.geysermc.floodgate;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.geysermc.floodgate.util.BedrockData;
|
||||
import org.geysermc.floodgate.util.DeviceOS;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -26,7 +27,7 @@ public class FloodgatePlayer {
|
||||
/**
|
||||
* The operation system of the bedrock client
|
||||
*/
|
||||
private FloodgateAPI.DeviceOS deviceOS;
|
||||
private DeviceOS deviceOS;
|
||||
/**
|
||||
* The language code of the bedrock client
|
||||
*/
|
||||
@@ -41,8 +42,8 @@ public class FloodgatePlayer {
|
||||
username = data.getUsername();
|
||||
javaUsername = "*" + data.getUsername().substring(0, Math.min(data.getUsername().length(), 15));
|
||||
xuid = data.getXuid();
|
||||
deviceOS = FloodgateAPI.DeviceOS.getById(data.getDeviceId());
|
||||
deviceOS = DeviceOS.getById(data.getDeviceId());
|
||||
languageCode = data.getLanguageCode();
|
||||
javaUniqueId = FloodgateAPI.createJavaPlayerId(Long.parseLong(data.getXuid()));
|
||||
javaUniqueId = AbstractFloodgateAPI.createJavaPlayerId(Long.parseLong(data.getXuid()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package org.geysermc.floodgate;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import org.geysermc.floodgate.util.BedrockData;
|
||||
import org.geysermc.floodgate.util.EncryptionUtil;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
|
||||
import static org.geysermc.floodgate.util.BedrockData.EXPECTED_LENGTH;
|
||||
import static org.geysermc.floodgate.util.BedrockData.FLOODGATE_IDENTIFIER;
|
||||
|
||||
public class HandshakeHandler {
|
||||
private PrivateKey privateKey;
|
||||
private boolean bungee;
|
||||
|
||||
public HandshakeHandler(@NonNull PrivateKey privateKey, boolean bungee) {
|
||||
this.privateKey = privateKey;
|
||||
this.bungee = bungee;
|
||||
}
|
||||
|
||||
public HandshakeResult handle(@NonNull String handshakeData) {
|
||||
try {
|
||||
String[] data = handshakeData.split("\0");
|
||||
boolean isBungeeData = data.length == 6 || data.length == 7;
|
||||
|
||||
if (bungee && isBungeeData || !isBungeeData && data.length != 4 || !data[1].equals(FLOODGATE_IDENTIFIER)) {
|
||||
return ResultType.NOT_FLOODGATE_DATA.getCachedResult();
|
||||
}
|
||||
|
||||
BedrockData bedrockData = EncryptionUtil.decryptBedrockData(
|
||||
privateKey, data[2] + '\0' + data[3]
|
||||
);
|
||||
|
||||
if (bedrockData.getDataLength() != EXPECTED_LENGTH) {
|
||||
return ResultType.INVALID_DATA_LENGTH.getCachedResult();
|
||||
}
|
||||
|
||||
FloodgatePlayer player = new FloodgatePlayer(bedrockData);
|
||||
AbstractFloodgateAPI.players.put(player.getJavaUniqueId(), player);
|
||||
return new HandshakeResult(ResultType.SUCCESS, data, bedrockData, player);
|
||||
} catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
|
||||
e.printStackTrace();
|
||||
return ResultType.EXCEPTION.getCachedResult();
|
||||
}
|
||||
}
|
||||
|
||||
@AllArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
@Getter
|
||||
public static class HandshakeResult {
|
||||
private ResultType resultType;
|
||||
private String[] handshakeData;
|
||||
private BedrockData bedrockData;
|
||||
private FloodgatePlayer floodgatePlayer;
|
||||
}
|
||||
|
||||
public enum ResultType {
|
||||
EXCEPTION,
|
||||
NOT_FLOODGATE_DATA,
|
||||
INVALID_DATA_LENGTH,
|
||||
SUCCESS;
|
||||
|
||||
@Getter private HandshakeResult cachedResult;
|
||||
|
||||
ResultType() {
|
||||
cachedResult = new HandshakeResult(this, null, null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.geysermc.floodgate.util;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonEnumDefaultValue;
|
||||
|
||||
public enum DeviceOS {
|
||||
@JsonEnumDefaultValue
|
||||
UNKNOWN,
|
||||
ANDROID,
|
||||
IOS,
|
||||
OSX,
|
||||
FIREOS,
|
||||
GEARVR,
|
||||
HOLOLENS,
|
||||
WIN10,
|
||||
WIN32,
|
||||
DEDICATED,
|
||||
ORBIS,
|
||||
NX,
|
||||
SWITCH;
|
||||
|
||||
private static final DeviceOS[] VALUES = values();
|
||||
|
||||
public static DeviceOS getById(int id) {
|
||||
return id < VALUES.length ? VALUES[id] : VALUES[0];
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,24 @@
|
||||
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;
|
||||
|
||||
public class ReflectionUtil {
|
||||
/*
|
||||
* Used in the Bukkit version
|
||||
/**
|
||||
* Prefix without dot<br>
|
||||
* Example net.minecraft.server.v1_8R3.PacketHandhakingInSetProtocol will become:<br>
|
||||
* net.minecraft.server.v1_8R3
|
||||
*/
|
||||
private static String nmsPackage = null;
|
||||
@Getter @Setter
|
||||
private static String prefix = null;
|
||||
|
||||
public static Class<?> getNMSClass(String className) {
|
||||
return getClass(nmsPackage + className);
|
||||
}
|
||||
|
||||
public static void setServerVersion(String serverVersion) {
|
||||
nmsPackage = "net.minecraft.server." + serverVersion + ".";
|
||||
public static Class<?> getPrefixedClass(String className) {
|
||||
return getClass(prefix +"."+ className);
|
||||
}
|
||||
|
||||
public static Class<?> getClass(String className) {
|
||||
@@ -101,8 +103,27 @@ public class ReflectionUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public static Object invokeStatic(Class<?> clazz, String method) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
return clazz.getDeclaredMethod(method).invoke(null);
|
||||
public static Object invoke(Object instance, Method method) {
|
||||
try {
|
||||
return method.invoke(instance);
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T invokeCasted(Object instance, Method method, Class<T> cast) {
|
||||
return (T) invoke(instance, method);
|
||||
}
|
||||
|
||||
public static Object invokeStatic(Class<?> clazz, String method) {
|
||||
try {
|
||||
return getMethod(clazz, method).invoke(null);
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static <T extends AccessibleObject> T makeAccessible(T accessibleObject) {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
# In Floodgate bedrock player data is send encrypted
|
||||
# The following value should point to the key Floodgate generated.
|
||||
# The public key should be used for the Geyser(s) and the private key for
|
||||
# - bungeecord
|
||||
# - bukkit (craftbukkit, spigot etc.)
|
||||
# The public key should be used for the Geyser(s) and the private key for the Floodgate(s)
|
||||
key-file-name: key.pem
|
||||
|
||||
disconnect:
|
||||
|
||||
41
pom.xml
41
pom.xml
@@ -11,15 +11,19 @@
|
||||
<module>bungee</module>
|
||||
<module>common</module>
|
||||
<module>bukkit</module>
|
||||
<module>velocity</module>
|
||||
</modules>
|
||||
<packaging>pom</packaging>
|
||||
<name>floodgate</name>
|
||||
<description>Allows Bedrock players to join Java edition servers while keeping online mode</description>
|
||||
<url>https://geysermc.org</url>
|
||||
<url>https://github.com/GeyserMC/Floodgate/</url>
|
||||
|
||||
<properties>
|
||||
<geyser-version>1.0-SNAPSHOT</geyser-version>
|
||||
<bukkit-version>1.8.8-R0.1-SNAPSHOT</bukkit-version>
|
||||
<bungee-version>1.15-SNAPSHOT</bungee-version>
|
||||
<velocity-version>1.1.0-SNAPSHOT</velocity-version>
|
||||
|
||||
<outputName>${project.name}</outputName>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
@@ -29,46 +33,15 @@
|
||||
|
||||
<organization>
|
||||
<name>GeyserMC</name>
|
||||
<url>https://github.com/GeyserMC/Floodgate/blob/master/pom.xml</url>
|
||||
<url>https://geysermc.org/</url>
|
||||
</organization>
|
||||
|
||||
<scm>
|
||||
<connection>scm:git:https://github.com/GeyserMC/Floodgate.git</connection>
|
||||
<developerConnection>scm:git:git@github.com:GeyserMC/Floodgate.git</developerConnection>
|
||||
<url>https://github.com/GeyserMC/Floodgate</url>
|
||||
<url>https://github.com/GeyserMC/Floodgate/</url>
|
||||
</scm>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>bungeecord-repo</id>
|
||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>nukkitx-release-repo</id>
|
||||
<url>https://repo.nukkitx.com/maven-releases/</url>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>nukkitx-snapshot-repo</id>
|
||||
<url>https://repo.nukkitx.com/maven-snapshots/</url>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>Lucko.me</id>
|
||||
<url>https://nexus.lucko.me/repository/maven-snapshots/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<distributionManagement>
|
||||
<repository>
|
||||
<id>releases</id>
|
||||
|
||||
87
velocity/pom.xml
Normal file
87
velocity/pom.xml
Normal file
@@ -0,0 +1,87 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>org.geysermc</groupId>
|
||||
<artifactId>floodgate-parent</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<url>${parent.url}</url>
|
||||
|
||||
<artifactId>floodgate-velocity</artifactId>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>velocity-repo</id>
|
||||
<url>https://repo.velocitypowered.com/snapshots/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>sponge-repo</id>
|
||||
<url>https://repo.spongepowered.org/maven/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.javassist</groupId>
|
||||
<artifactId>javassist</artifactId>
|
||||
<version>3.26.0-GA</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.openhft</groupId>
|
||||
<artifactId>compiler</artifactId>
|
||||
<version>2.3.5</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-all</artifactId>
|
||||
<version>4.1.45.Final</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.velocitypowered</groupId>
|
||||
<artifactId>velocity-api</artifactId>
|
||||
<version>${velocity-version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.geysermc</groupId>
|
||||
<artifactId>floodgate-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.2.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<finalName>${outputName}</finalName>
|
||||
<shadedArtifactAttached>true</shadedArtifactAttached>
|
||||
<minimizeJar>true</minimizeJar>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.geysermc.floodgate;
|
||||
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class FloodgateAPI extends AbstractFloodgateAPI {
|
||||
private static Map<UUID, String> encryptedData = new HashMap<>();
|
||||
|
||||
static void addEncryptedData(UUID uuid, String encryptedData) {
|
||||
FloodgateAPI.encryptedData.put(uuid, encryptedData); // just override it I guess
|
||||
}
|
||||
|
||||
static void removeEncryptedData(UUID uuid) {
|
||||
encryptedData.remove(uuid);
|
||||
}
|
||||
|
||||
public static String getEncryptedData(UUID uuid) {
|
||||
return encryptedData.get(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link AbstractFloodgateAPI#getPlayer(UUID)}
|
||||
*/
|
||||
public static FloodgatePlayer getPlayer(Player player) {
|
||||
return getPlayer(player.getUniqueId());
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link AbstractFloodgateAPI#isBedrockPlayer(UUID)}
|
||||
*/
|
||||
public static boolean isBedrockPlayer(Player player) {
|
||||
return isBedrockPlayer(player.getUniqueId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.geysermc.floodgate;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Getter;
|
||||
|
||||
public class VelocityFloodgateConfig extends FloodgateConfig {
|
||||
@JsonProperty(value = "send-floodgate-data")
|
||||
@Getter
|
||||
private boolean sendFloodgateData;
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
package org.geysermc.floodgate;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.inject.Inject;
|
||||
import com.velocitypowered.api.event.Subscribe;
|
||||
import com.velocitypowered.api.event.connection.ConnectionHandshakeEvent;
|
||||
import com.velocitypowered.api.event.connection.PreLoginEvent;
|
||||
import com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult;
|
||||
import com.velocitypowered.api.event.player.GameProfileRequestEvent;
|
||||
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
|
||||
import com.velocitypowered.api.proxy.InboundConnection;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.api.util.GameProfile;
|
||||
import lombok.Getter;
|
||||
import net.kyori.text.TextComponent;
|
||||
import org.geysermc.floodgate.HandshakeHandler.HandshakeResult;
|
||||
import org.geysermc.floodgate.injector.VelocityInjector;
|
||||
import org.geysermc.floodgate.util.ReflectionUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static org.geysermc.floodgate.util.ReflectionUtil.getField;
|
||||
import static org.geysermc.floodgate.util.ReflectionUtil.getPrefixedClass;
|
||||
|
||||
public class VelocityPlugin {
|
||||
@Getter private VelocityFloodgateConfig config;
|
||||
private HandshakeHandler handshakeHandler;
|
||||
|
||||
private Set<InboundConnection> workingSet;
|
||||
private Cache<InboundConnection, FloodgatePlayer> playerCache;
|
||||
private Cache<InboundConnection, String> playersToKick;
|
||||
|
||||
private Logger logger;
|
||||
private boolean injectSucceed;
|
||||
|
||||
@Inject
|
||||
public VelocityPlugin(ProxyServer server, Logger logger) {
|
||||
// we're too late if we would do this in the init event
|
||||
injectSucceed = false;
|
||||
try {
|
||||
injectSucceed = VelocityInjector.inject(this);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
this.logger = logger;
|
||||
if (!injectSucceed) return;
|
||||
|
||||
this.workingSet = new HashSet<>();
|
||||
this.playersToKick = CacheBuilder.newBuilder()
|
||||
.maximumSize(3000)
|
||||
.expireAfterWrite(50, TimeUnit.SECONDS)
|
||||
.build();
|
||||
this.playerCache = CacheBuilder.newBuilder()
|
||||
.maximumSize(500)
|
||||
.expireAfterAccess(50, TimeUnit.SECONDS)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onProxyInitialization(ProxyInitializeEvent event) {
|
||||
if (injectSucceed) {
|
||||
logger.info("Floodgate injection process succeeded!");
|
||||
} else {
|
||||
logger.severe("Failed to inject! Floodgate won't do anything.");
|
||||
return;
|
||||
}
|
||||
|
||||
File dataFolder = new File("plugins/floodgate/");
|
||||
if (!dataFolder.exists()) {
|
||||
dataFolder.mkdir();
|
||||
}
|
||||
|
||||
config = FloodgateConfig.load(logger, dataFolder.toPath().resolve("config.yml"), VelocityFloodgateConfig.class);
|
||||
handshakeHandler = new HandshakeHandler(config.getPrivateKey(), true);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onConnectionHandshake(ConnectionHandshakeEvent event) {
|
||||
if (!injectSucceed) return;
|
||||
workingSet.add(event.getConnection());
|
||||
|
||||
try {
|
||||
Object cachedHandshake = ReflectionUtil.getValue(event.getConnection(), handshakeField);
|
||||
String handshakeData = ReflectionUtil.getCastedValue(cachedHandshake, handshakeAddressField, String.class);
|
||||
|
||||
HandshakeResult result = handshakeHandler.handle(handshakeData);
|
||||
switch (result.getResultType()) {
|
||||
case SUCCESS:
|
||||
break;
|
||||
case EXCEPTION:
|
||||
playersToKick.put(event.getConnection(), config.getMessages().getInvalidKey());
|
||||
case INVALID_DATA_LENGTH:
|
||||
playersToKick.put(event.getConnection(), config.getMessages().getInvalidArgumentsLength());
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
FloodgatePlayer player = result.getFloodgatePlayer();
|
||||
FloodgateAPI.addEncryptedData(player.getJavaUniqueId(), result.getHandshakeData()[2] + '\0' + result.getHandshakeData()[3]);
|
||||
playerCache.put(event.getConnection(), player);
|
||||
logger.info("Added " + player.getJavaUsername() + " " + player.getJavaUniqueId());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
workingSet.remove(event.getConnection());
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onPreLogin(PreLoginEvent event) {
|
||||
if (!injectSucceed) return;
|
||||
|
||||
if (workingSet.contains(event.getConnection())) {
|
||||
int count = 70;
|
||||
// 70 * 70 / 1000 = 4.9 seconds to get removed from working cache
|
||||
while (count-- != 0 && workingSet.contains(event.getConnection())) {
|
||||
// should be 'just fine' because the event system is multithreaded
|
||||
try {
|
||||
Thread.sleep(70);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (workingSet.contains(event.getConnection())) {
|
||||
String message = "Took more then 4.9 seconds (after PreLoginEvent) to finish Handshake data for "+event.getUsername();
|
||||
logger.warning(message);
|
||||
event.setResult(PreLoginComponentResult.denied(TextComponent.of(message)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
FloodgatePlayer player = playerCache.getIfPresent(event.getConnection());
|
||||
if (player != null) {
|
||||
System.out.println(event.getUsername());
|
||||
event.setResult(PreLoginComponentResult.forceOfflineMode());
|
||||
return;
|
||||
}
|
||||
|
||||
String message = playersToKick.getIfPresent(event.getConnection());
|
||||
if (message != null) {
|
||||
playersToKick.invalidate(event.getConnection());
|
||||
event.setResult(PreLoginComponentResult.denied(TextComponent.of(message)));
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onGameProfileRequest(GameProfileRequestEvent event) {
|
||||
if (!injectSucceed) return;
|
||||
|
||||
FloodgatePlayer player = playerCache.getIfPresent(event.getConnection());
|
||||
if (player != null) {
|
||||
playerCache.invalidate(event.getConnection());
|
||||
event.setGameProfile(new GameProfile(player.getJavaUniqueId(), player.getJavaUsername(), new ArrayList<>()));
|
||||
}
|
||||
}
|
||||
|
||||
private Field handshakeField;
|
||||
private Field handshakeAddressField;
|
||||
|
||||
public void initReflection() {
|
||||
ReflectionUtil.setPrefix("com.velocitypowered.proxy");
|
||||
Class<?> IIC = getPrefixedClass("connection.client.InitialInboundConnection");
|
||||
Class<?> HandshakePacket = getPrefixedClass("protocol.packet.Handshake");
|
||||
|
||||
handshakeField = getField(IIC, "handshake");
|
||||
handshakeAddressField = getField(HandshakePacket, "serverAddress");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package org.geysermc.floodgate.injector;
|
||||
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import javassist.ClassPool;
|
||||
import javassist.CtClass;
|
||||
import javassist.CtField;
|
||||
import javassist.Modifier;
|
||||
import javassist.bytecode.Descriptor;
|
||||
import lombok.Getter;
|
||||
import org.geysermc.floodgate.FloodgateAPI;
|
||||
import org.geysermc.floodgate.VelocityPlugin;
|
||||
import org.geysermc.floodgate.util.ReflectionUtil;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import static org.geysermc.floodgate.util.BedrockData.FLOODGATE_IDENTIFIER;
|
||||
|
||||
public class VelocityInjector {
|
||||
@Getter private static boolean injected = false;
|
||||
|
||||
public static boolean inject(VelocityPlugin plugin) throws Exception {
|
||||
if (isInjected()) return true;
|
||||
// Tnx Velocity for making it difficult x)
|
||||
// But I made my way in >:)
|
||||
// Please note that doing this is not recommended
|
||||
|
||||
// This method will edit the source code of MinecraftConnection to allow us to edit any outgoing packet.
|
||||
|
||||
ClassPool classPool = ClassPool.getDefault();
|
||||
CtClass ctClass = classPool.get("com.velocitypowered.proxy.connection.MinecraftConnection");
|
||||
ctClass.defrost();
|
||||
|
||||
// create dataConsumer field
|
||||
CtClass biConsumerClass = classPool.get("java.util.function.BiConsumer");
|
||||
CtField dataConsumer = new CtField(biConsumerClass, "dataConsumer", ctClass);
|
||||
dataConsumer.setModifiers(Modifier.PUBLIC | Modifier.STATIC);
|
||||
ctClass.addField(dataConsumer);
|
||||
|
||||
// override write and delayedWrite in MinecraftConnection
|
||||
String voidObjectDescriptor = Descriptor.ofMethod(CtClass.voidType, new CtClass[] { classPool.get("java.lang.Object") });
|
||||
String codeToInsert =
|
||||
"if (msg != null && dataConsumer != null && msg instanceof com.velocitypowered.proxy.protocol.MinecraftPacket) {\n" +
|
||||
" dataConsumer.accept(this, (com.velocitypowered.proxy.protocol.MinecraftPacket)msg);\n" +
|
||||
"}\n";
|
||||
|
||||
ctClass.getMethod("write", voidObjectDescriptor).insertAt(1, codeToInsert);
|
||||
ctClass.getMethod("delayedWrite", voidObjectDescriptor).insertAt(1, codeToInsert);
|
||||
|
||||
Class<?> clazz = ctClass.toClass();
|
||||
|
||||
// The most important part is done,
|
||||
// So now we call initReflection to let us use getPrefixedClass
|
||||
plugin.initReflection();
|
||||
|
||||
// handshake handle stuff
|
||||
Class<?> handshakePacket = ReflectionUtil.getPrefixedClass("protocol.packet.Handshake");
|
||||
Field serverAddress = ReflectionUtil.getField(handshakePacket, "serverAddress");
|
||||
|
||||
Field sessionHandlerField = ReflectionUtil.getField(clazz, "sessionHandler");
|
||||
Class<?> loginHandler = ReflectionUtil.getPrefixedClass("connection.backend.LoginSessionHandler");
|
||||
|
||||
Field serverConnField = ReflectionUtil.getField(loginHandler, "serverConn");
|
||||
|
||||
Class<?> serverConnection = ReflectionUtil.getPrefixedClass("connection.backend.VelocityServerConnection");
|
||||
Method playerGetMethod = ReflectionUtil.getMethod(serverConnection, "getPlayer");
|
||||
assert playerGetMethod != null;
|
||||
|
||||
// create and set our custom made Consumer
|
||||
|
||||
BiConsumer<Object, Object> biConsumer = (minecraftConnection, packet) -> {
|
||||
// This consumer is only called when the server is sending data (outgoing).
|
||||
// So when we get a handshake packet it'll be Velocity trying to connect to a server.
|
||||
// And that is exactly what we need, to edit the outgoing handshake packet to include Floodgate data
|
||||
if (plugin.getConfig().isSendFloodgateData()) {
|
||||
try {
|
||||
if (handshakePacket.isInstance(packet)) {
|
||||
String address = ReflectionUtil.getCastedValue(packet, serverAddress, String.class);
|
||||
|
||||
Object sessionHandler = ReflectionUtil.getValue(minecraftConnection, sessionHandlerField);
|
||||
if (loginHandler.isInstance(sessionHandler)) {
|
||||
Object serverConn = ReflectionUtil.getValue(sessionHandler, serverConnField);
|
||||
Player connectedPlayer = ReflectionUtil.invokeCasted(serverConn, playerGetMethod, Player.class);
|
||||
|
||||
String encryptedData = FloodgateAPI.getEncryptedData(connectedPlayer.getUniqueId());
|
||||
if (encryptedData == null) return; // we know enough, this is not a Floodgate player
|
||||
|
||||
String[] splitted = address.split("\0");
|
||||
String remaining = address.substring(splitted[0].length());
|
||||
ReflectionUtil.setValue(packet, serverAddress, splitted[0] + '\0' + FLOODGATE_IDENTIFIER + '\0' + encryptedData + remaining);
|
||||
// keep the same system as we have on Bungeecord. Our data is before the Bungee data
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Class<?> minecraftConnection = ReflectionUtil.getPrefixedClass("connection.MinecraftConnection");
|
||||
ReflectionUtil.setValue(null, ReflectionUtil.getField(minecraftConnection, "dataConsumer"), biConsumer);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
1
velocity/src/main/resources/velocity-plugin.json
Normal file
1
velocity/src/main/resources/velocity-plugin.json
Normal file
@@ -0,0 +1 @@
|
||||
{"id": "floodgate", "name": "${project.artifactId}", "version": "${project.version}", "description": "${project.description}", "url": "${project.url}", "authors": ["${project.organization.name}"], "main": "org.geysermc.floodgate.VelocityPlugin"}
|
||||
Reference in New Issue
Block a user