diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 016ad98d..1ba39fbd 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -8,7 +8,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - name: Checkout repository and submodules + uses: actions/checkout@v3 + with: + submodules: recursive - name: Set up JDK 17 uses: actions/setup-java@v2 diff --git a/spigot/src/main/java/org/geysermc/floodgate/addon/data/SpigotDataHandler.java b/spigot/src/main/java/org/geysermc/floodgate/addon/data/SpigotDataHandler.java index bc5742db..55dfc36e 100644 --- a/spigot/src/main/java/org/geysermc/floodgate/addon/data/SpigotDataHandler.java +++ b/spigot/src/main/java/org/geysermc/floodgate/addon/data/SpigotDataHandler.java @@ -31,6 +31,7 @@ import com.mojang.authlib.GameProfile; import io.netty.channel.Channel; import io.netty.util.AttributeKey; +import java.lang.reflect.InvocationTargetException; import java.net.InetSocketAddress; import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.config.FloodgateConfig; @@ -57,9 +58,27 @@ protected void setNewIp(Channel channel, InetSocketAddress newIp) { } @Override - protected Object setHostname(Object handshakePacket, String hostname) { - setValue(handshakePacket, ClassNames.HANDSHAKE_HOST, hostname); - return handshakePacket; + protected Object setHostname(Object handshakePacket, String hostname) throws IllegalStateException { + if (ClassNames.IS_PRE_1_20_2) { + // 1.20.1 and below + setValue(handshakePacket, ClassNames.HANDSHAKE_HOST, hostname); + + return handshakePacket; + } else { + // 1.20.2 and above + try { + Object[] components = new Object[]{ + ClassNames.HANDSHAKE_PORT.get(handshakePacket), + hostname, + ClassNames.HANDSHAKE_PROTOCOL.get(handshakePacket), + ClassNames.HANDSHAKE_INTENTION.get(handshakePacket) + }; + + return ClassNames.HANDSHAKE_PACKET_CONSTRUCTOR.newInstance(components); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new IllegalStateException("Failed to create new Handshake packet", e); + } + } } @Override @@ -157,16 +176,23 @@ private boolean checkAndHandleLogin(Object packet) throws Exception { // we have to fake the offline player (login) cycle // just like on Spigot: - // LoginListener#initUUID - // new LoginHandler().fireEvents(); + Object loginHandler = ClassNames.LOGIN_HANDLER_CONSTRUCTOR.newInstance(packetListener); - // and the tick of LoginListener will do the rest + if (ClassNames.IS_PRE_1_20_2) { + // 1.20.1 and below - ClassNames.INIT_UUID.invoke(packetListener); + // LoginListener#initUUID + // new LoginHandler().fireEvents(); - Object loginHandler = - ClassNames.LOGIN_HANDLER_CONSTRUCTOR.newInstance(packetListener); - ClassNames.FIRE_LOGIN_EVENTS.invoke(loginHandler); + // and the tick of LoginListener will do the rest + + ClassNames.INIT_UUID.invoke(packetListener); + ClassNames.FIRE_LOGIN_EVENTS.invoke(loginHandler); + } else { + // 1.20.2 and above we directly register the profile + + ClassNames.FIRE_LOGIN_EVENTS_GAME_PROFILE.invoke(loginHandler, gameProfile); + } ctx.pipeline().remove(this); return true; diff --git a/spigot/src/main/java/org/geysermc/floodgate/util/ClassNames.java b/spigot/src/main/java/org/geysermc/floodgate/util/ClassNames.java index 11f757f1..77e00cc7 100644 --- a/spigot/src/main/java/org/geysermc/floodgate/util/ClassNames.java +++ b/spigot/src/main/java/org/geysermc/floodgate/util/ClassNames.java @@ -29,11 +29,13 @@ import static org.geysermc.floodgate.util.ReflectionUtils.getBooleanValue; import static org.geysermc.floodgate.util.ReflectionUtils.getClassOrFallback; import static org.geysermc.floodgate.util.ReflectionUtils.getClassSilently; +import static org.geysermc.floodgate.util.ReflectionUtils.getConstructor; import static org.geysermc.floodgate.util.ReflectionUtils.getField; import static org.geysermc.floodgate.util.ReflectionUtils.getFieldOfType; import static org.geysermc.floodgate.util.ReflectionUtils.getMethod; import static org.geysermc.floodgate.util.ReflectionUtils.getValue; import static org.geysermc.floodgate.util.ReflectionUtils.invoke; +import static org.geysermc.floodgate.util.ReflectionUtils.makeAccessible; import com.google.common.base.Preconditions; import com.mojang.authlib.GameProfile; @@ -58,27 +60,35 @@ public class ClassNames { public static final Class LOGIN_START_PACKET; public static final Class LOGIN_LISTENER; public static final Class LOGIN_HANDLER; + @Nullable public static final Class CLIENT_INTENT; public static final Constructor CRAFT_OFFLINE_PLAYER_CONSTRUCTOR; public static final Constructor LOGIN_HANDLER_CONSTRUCTOR; + @Nullable public static final Constructor HANDSHAKE_PACKET_CONSTRUCTOR; public static final Field SOCKET_ADDRESS; public static final Field HANDSHAKE_HOST; public static final Field LOGIN_PROFILE; public static final Field PACKET_LISTENER; + @Nullable public static final Field HANDSHAKE_PORT; + @Nullable public static final Field HANDSHAKE_PROTOCOL; + @Nullable public static final Field HANDSHAKE_INTENTION; + @Nullable public static final Field PAPER_DISABLE_USERNAME_VALIDATION; @Nullable public static final BooleanSupplier PAPER_VELOCITY_SUPPORT; public static final Method GET_PROFILE_METHOD; public static final Method LOGIN_DISCONNECT; public static final Method NETWORK_EXCEPTION_CAUGHT; - public static final Method INIT_UUID; - public static final Method FIRE_LOGIN_EVENTS; + @Nullable public static final Method INIT_UUID; + @Nullable public static final Method FIRE_LOGIN_EVENTS; + @Nullable public static final Method FIRE_LOGIN_EVENTS_GAME_PROFILE; public static final Field BUNGEE; public static final boolean IS_FOLIA; + public static final boolean IS_PRE_1_20_2; static { String version = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3]; @@ -153,14 +163,24 @@ public class ClassNames { ); // there are multiple no-arg void methods + // Pre 1.20.2 uses initUUID so if it's null, we're on 1.20.2 or later INIT_UUID = getMethod(LOGIN_LISTENER, "initUUID"); - checkNotNull(INIT_UUID, "initUUID from LoginListener"); + IS_PRE_1_20_2 = INIT_UUID != null; - Class packetListenerClass = getClassOrFallback( - "net.minecraft.network.PacketListener", - nmsPackage + "PacketListener" - ); - PACKET_LISTENER = getFieldOfType(networkManager, packetListenerClass); + if (IS_PRE_1_20_2) { + Class packetListenerClass = getClassOrFallback( + "net.minecraft.network.PacketListener", + nmsPackage + "PacketListener" + ); + + PACKET_LISTENER = getFieldOfType(networkManager, packetListenerClass); + } else { + // We get the field by name on 1.20.2+ as there are now multiple fields of this type in network manager + + // PacketListener packetListener of NetworkManager + PACKET_LISTENER = getField(networkManager, "q"); + makeAccessible(PACKET_LISTENER); + } checkNotNull(PACKET_LISTENER, "Packet listener"); LOGIN_HANDLER = getClassOrFallback( @@ -173,8 +193,11 @@ public class ClassNames { checkNotNull(LOGIN_HANDLER_CONSTRUCTOR, "LoginHandler constructor"); FIRE_LOGIN_EVENTS = getMethod(LOGIN_HANDLER, "fireEvents"); - checkNotNull(FIRE_LOGIN_EVENTS, "fireEvents from LoginHandler"); + // LoginHandler().fireEvents(GameProfile) + FIRE_LOGIN_EVENTS_GAME_PROFILE = getMethod(LOGIN_HANDLER, "fireEvents", GameProfile.class); + checkNotNull(FIRE_LOGIN_EVENTS, FIRE_LOGIN_EVENTS_GAME_PROFILE, + "fireEvents from LoginHandler", "fireEvents(GameProfile) from LoginHandler"); PAPER_DISABLE_USERNAME_VALIDATION = getField(LOGIN_LISTENER, "iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation"); @@ -229,9 +252,53 @@ public class ClassNames { IS_FOLIA = ReflectionUtils.getClassSilently( "io.papermc.paper.threadedregions.RegionizedServer" ) != null; + + if (!IS_PRE_1_20_2) { + // PacketHandshakingInSetProtocol is now a record + // This means its fields are now private and final + // We therefore must use reflection to obtain the constructor + CLIENT_INTENT = getClassOrFallback( + "net.minecraft.network.protocol.handshake.ClientIntent", + nmsPackage + "ClientIntent" + ); + checkNotNull(CLIENT_INTENT, "Client intent enum"); + + HANDSHAKE_PACKET_CONSTRUCTOR = getConstructor(HANDSHAKE_PACKET, false, int.class, + String.class, int.class, CLIENT_INTENT); + checkNotNull(HANDSHAKE_PACKET_CONSTRUCTOR, "Handshake packet constructor"); + + HANDSHAKE_PORT = getField(HANDSHAKE_PACKET, "a"); + checkNotNull(HANDSHAKE_PORT, "Handshake port"); + makeAccessible(HANDSHAKE_PORT); + + HANDSHAKE_PROTOCOL = getField(HANDSHAKE_PACKET, "c"); + checkNotNull(HANDSHAKE_PROTOCOL, "Handshake protocol"); + makeAccessible(HANDSHAKE_PROTOCOL); + + HANDSHAKE_INTENTION = getFieldOfType(HANDSHAKE_PACKET, CLIENT_INTENT); + checkNotNull(HANDSHAKE_INTENTION, "Handshake intention"); + makeAccessible(HANDSHAKE_INTENTION); + } else { + CLIENT_INTENT = null; + HANDSHAKE_PACKET_CONSTRUCTOR = null; + HANDSHAKE_PORT = null; + HANDSHAKE_PROTOCOL = null; + HANDSHAKE_INTENTION = null; + } } private static T checkNotNull(@CheckForNull T toCheck, @CheckForNull String objectName) { return Preconditions.checkNotNull(toCheck, objectName + " cannot be null"); } + + // Ensure one of two is not null + private static T checkNotNull( + @CheckForNull T toCheck, + @CheckForNull T toCheck2, + @CheckForNull String objectName, + @CheckForNull String objectName2 + ) { + return Preconditions.checkNotNull(toCheck != null ? toCheck : toCheck2, + objectName2 + " cannot be null if " + objectName + " is null"); + } } diff --git a/velocity/build.gradle.kts b/velocity/build.gradle.kts index 5cd7064a..934ca10c 100644 --- a/velocity/build.gradle.kts +++ b/velocity/build.gradle.kts @@ -1,8 +1,15 @@ -var velocityVersion = "3.1.1" +var velocityVersion = "3.2.0-SNAPSHOT" var log4jVersion = "2.11.2" var gsonVersion = "2.8.8" var guavaVersion = "25.1-jre" +indra { + javaVersions { + // For Velocity API + target(11) + } +} + dependencies { api(projects.core) implementation("cloud.commandframework", "cloud-velocity", Versions.cloudVersion) diff --git a/velocity/src/main/java/org/geysermc/floodgate/addon/data/VelocityProxyDataHandler.java b/velocity/src/main/java/org/geysermc/floodgate/addon/data/VelocityProxyDataHandler.java index a623fdaf..b069543e 100644 --- a/velocity/src/main/java/org/geysermc/floodgate/addon/data/VelocityProxyDataHandler.java +++ b/velocity/src/main/java/org/geysermc/floodgate/addon/data/VelocityProxyDataHandler.java @@ -76,7 +76,13 @@ public final class VelocityProxyDataHandler extends CommonDataHandler { SERVER_LOGIN_PACKET = getPrefixedClass("protocol.packet.ServerLogin"); checkNotNull(SERVER_LOGIN_PACKET, "ServerLogin packet class cannot be null"); - GET_SESSION_HANDLER = getMethodByName(minecraftConnection, "getSessionHandler", true); + + Method sessionHandler = getMethodByName(minecraftConnection, "getSessionHandler", true); + if (sessionHandler == null) { + // We are 1.20.2+ + sessionHandler = getMethodByName(minecraftConnection, "getActiveSessionHandler", true); + } + GET_SESSION_HANDLER = sessionHandler; checkNotNull(GET_SESSION_HANDLER, "getSessionHandler method cannot be null"); INITIAL_LOGIN_SESSION_HANDLER = diff --git a/velocity/src/main/resources/velocity-plugin.json b/velocity/src/main/resources/velocity-plugin.json index a4df39d2..dc7d5e1f 100644 --- a/velocity/src/main/resources/velocity-plugin.json +++ b/velocity/src/main/resources/velocity-plugin.json @@ -1 +1 @@ -{"id": "${id}", "name": "${name}", "version": "${version}", "description": "${description}", "url": "$url}", "authors": ["${author}"], "main": "org.geysermc.floodgate.VelocityPlugin"} \ No newline at end of file +{"id": "${id}", "name": "${name}", "version": "${version}", "description": "${description}", "url": "${url}", "authors": ["${author}"], "main": "org.geysermc.floodgate.VelocityPlugin"} \ No newline at end of file