From c781693ab20a15d321d3e472339c3785fd2960da Mon Sep 17 00:00:00 2001 From: Aurorawr Date: Thu, 2 Oct 2025 23:58:58 +0100 Subject: [PATCH 1/3] Add native Mojmap support for the spigot module (#591) * Add native Mojmap support for the spigot module * Don't remove spigot class name for CraftPlayer * Also include CraftServer and CraftOfflinePlayer --- .../floodgate/util/ReflectionUtils.java | 78 +++++++++++++++++++ .../geysermc/floodgate/util/ClassNames.java | 33 +++++--- 2 files changed, 100 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/org/geysermc/floodgate/util/ReflectionUtils.java b/core/src/main/java/org/geysermc/floodgate/util/ReflectionUtils.java index 98aa8f97..7b87ead2 100644 --- a/core/src/main/java/org/geysermc/floodgate/util/ReflectionUtils.java +++ b/core/src/main/java/org/geysermc/floodgate/util/ReflectionUtils.java @@ -135,6 +135,39 @@ public final class ReflectionUtils { return clazz; } + public static Class getClassOrFallback(String className, String fallbackClassName1, String fallbackClassName2) { + Class clazz = getClassSilently(className); + + if (clazz != null) { + if (Constants.DEBUG_MODE) { + System.out.println("Found class (primary): " + clazz.getName()); + } + return clazz; + } + + clazz = getClassSilently(fallbackClassName1); + + if (clazz != null) { + if (Constants.DEBUG_MODE) { + System.out.println("Found class (fallback1): " + clazz.getName()); + } + return clazz; + } + + // do throw an exception when both classes couldn't be found + clazz = ReflectionUtils.getClassOrThrow(fallbackClassName2); + if (Constants.DEBUG_MODE) { + System.out.println("Found class (fallback2): " + clazz.getName()); + } + return clazz; + } + + @Nullable + @SuppressWarnings("unchecked") + public static Class getCastedClassOrFallback(String className, String fallbackClassName) { + return (Class) getClassOrFallback(className, fallbackClassName); + } + @Nullable public static Constructor getConstructor(Class clazz, boolean declared, Class... parameters) { try { @@ -210,6 +243,28 @@ public final class ReflectionUtils { return getField(clazz, fieldName, false); } + /** + * Get a field from a class, it doesn't matter if the field is public or not. This method will + * first try to get a declared field and if that failed it'll try to get a public field. + * + * @param clazz the class to get the field from + * @param fieldName the name of the field + * @param fallbackFieldName the fallback incase fieldName doesn't exist + * @return the field if found from the name or fallback, otherwise null + */ + @Nullable + public static Field getField(Class clazz, String fieldName, String fallbackFieldName) { + Field field = getField(clazz, fieldName, true); + if (field != null) { + return field; + } + field = getField(clazz, fieldName, false); + if (field != null) { + return field; + } + return getField(clazz, fallbackFieldName); + } + /** * Get a field from a class without having to provide a field name. * @@ -418,6 +473,29 @@ public final class ReflectionUtils { return getMethod(clazz, methodName, false, arguments); } + /** + * Get a method from a class, it doesn't matter if the method is public or not. This method will + * first try to get a declared method and if that fails it'll try to get a public method. + * + * @param clazz the class to get the method from + * @param methodName the name of the method to find + * @param fallbackName the fallback instead methodName does not exist + * @param arguments the classes of the method arguments + * @return the requested method if it has been found, otherwise null + */ + @Nullable + public static Method getMethod(Class clazz, String methodName, String fallbackName, Class... arguments) { + Method method = getMethod(clazz, methodName, true, arguments); + if (method != null) { + return method; + } + method = getMethod(clazz, methodName, false, arguments); + if (method != null) { + return method; + } + return getMethod(clazz, fallbackName, arguments); + } + /** * Get a method from a class, it doesn't matter if the method is public or not. This method will * first try to get a declared method and if that fails it'll try to get a public method. 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 45df8c47..191a9cdf 100644 --- a/spigot/src/main/java/org/geysermc/floodgate/util/ClassNames.java +++ b/spigot/src/main/java/org/geysermc/floodgate/util/ClassNames.java @@ -114,8 +114,10 @@ public class ClassNames { // SpigotSkinApplier - Class craftPlayerClass = ReflectionUtils.getClass( - "org.bukkit.craftbukkit." + version + "entity.CraftPlayer"); + Class craftPlayerClass = getClassOrFallback( + "org.bukkit.craftbukkit.entity.CraftPlayer", + "org.bukkit.craftbukkit." + version + "entity.CraftPlayer" + ); GET_PROFILE_METHOD = getMethod(craftPlayerClass, "getProfile"); checkNotNull(GET_PROFILE_METHOD, "Get profile method"); @@ -135,21 +137,27 @@ public class ClassNames { ); SERVER_CONNECTION = getClassOrFallback( + "net.minecraft.server.network.ServerConnectionListener", "net.minecraft.server.network.ServerConnection", nmsPackage + "ServerConnection" ); // WhitelistUtils - Class craftServerClass = ReflectionUtils.getClass( - "org.bukkit.craftbukkit." + version + "CraftServer"); - Class craftOfflinePlayerClass = ReflectionUtils.getCastedClass( - "org.bukkit.craftbukkit." + version + "CraftOfflinePlayer"); + Class craftServerClass = getClassOrFallback( + "org.bukkit.craftbukkit.CraftServer", + "org.bukkit.craftbukkit." + version + "CraftServer" + ); + Class craftOfflinePlayerClass = ReflectionUtils.getCastedClassOrFallback( + "org.bukkit.craftbukkit.CraftOfflinePlayer", + "org.bukkit.craftbukkit." + version + "CraftOfflinePlayer" + ); CRAFT_OFFLINE_PLAYER_CONSTRUCTOR = ReflectionUtils.getConstructor( craftOfflinePlayerClass, true, craftServerClass, GameProfile.class); // SpigotDataHandler Class networkManager = getClassOrFallback( + "net.minecraft.network.Connection", "net.minecraft.network.NetworkManager", nmsPackage + "NetworkManager" ); @@ -157,6 +165,7 @@ public class ClassNames { SOCKET_ADDRESS = getFieldOfType(networkManager, SocketAddress.class, false); HANDSHAKE_PACKET = getClassOrFallback( + "net.minecraft.network.protocol.handshake.ClientIntentionPacket", "net.minecraft.network.protocol.handshake.PacketHandshakingInSetProtocol", nmsPackage + "PacketHandshakingInSetProtocol" ); @@ -165,11 +174,13 @@ public class ClassNames { checkNotNull(HANDSHAKE_HOST, "Handshake host"); LOGIN_START_PACKET = getClassOrFallback( + "net.minecraft.network.protocol.login.ServerboundHelloPacket", "net.minecraft.network.protocol.login.PacketLoginInStart", nmsPackage + "PacketLoginInStart" ); LOGIN_LISTENER = getClassOrFallback( + "net.minecraft.server.network.ServerLoginPacketListenerImpl", "net.minecraft.server.network.LoginListener", nmsPackage + "LoginListener" ); @@ -210,7 +221,7 @@ public class ClassNames { // 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"); + PACKET_LISTENER = getField(networkManager, "packetListener", "q"); makeAccessible(PACKET_LISTENER); } checkNotNull(PACKET_LISTENER, "Packet listener"); @@ -218,7 +229,7 @@ public class ClassNames { if (IS_POST_LOGIN_HANDLER) { makeAccessible(CALL_PLAYER_PRE_LOGIN_EVENTS); - START_CLIENT_VERIFICATION = getMethod(LOGIN_LISTENER, "b", GameProfile.class); + START_CLIENT_VERIFICATION = getMethod(LOGIN_LISTENER, "startClientVerification", "b", GameProfile.class); checkNotNull(START_CLIENT_VERIFICATION, "startClientVerification"); makeAccessible(START_CLIENT_VERIFICATION); @@ -314,15 +325,15 @@ public class ClassNames { String.class, int.class, CLIENT_INTENT); checkNotNull(HANDSHAKE_PACKET_CONSTRUCTOR, "Handshake packet constructor"); - Field a = getField(HANDSHAKE_PACKET, "a"); + Field a = getField(HANDSHAKE_PACKET, "STREAM_CODEC", "a"); checkNotNull(a, "Handshake \"a\" field (protocol version, or stream codec)"); if (a.getType().isPrimitive()) { // 1.20.2 - 1.20.4: a is the protocol version (int) HANDSHAKE_PROTOCOL = a; HANDSHAKE_PORT = getField(HANDSHAKE_PACKET, "c"); } else { // 1.20.5: a is the stream_codec thing, so everything is shifted - HANDSHAKE_PROTOCOL = getField(HANDSHAKE_PACKET, "b"); - HANDSHAKE_PORT = getField(HANDSHAKE_PACKET, "d"); + HANDSHAKE_PROTOCOL = getField(HANDSHAKE_PACKET, "protocolVersion", "b"); + HANDSHAKE_PORT = getField(HANDSHAKE_PACKET, "port", "d"); } checkNotNull(HANDSHAKE_PROTOCOL, "Handshake protocol"); From 7883304aa1ae69c4027fcfc41de936af72756427 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 2 Oct 2025 19:35:31 -0400 Subject: [PATCH 2/3] Restored compatibility with 1.16.5 --- .../floodgate/util/ReflectionUtils.java | 39 ++++++++++--------- .../pluginmessage/SpigotSkinApplier.java | 14 ++++++- .../geysermc/floodgate/util/ClassNames.java | 22 ++++++----- 3 files changed, 46 insertions(+), 29 deletions(-) diff --git a/core/src/main/java/org/geysermc/floodgate/util/ReflectionUtils.java b/core/src/main/java/org/geysermc/floodgate/util/ReflectionUtils.java index 7b87ead2..158aeb6b 100644 --- a/core/src/main/java/org/geysermc/floodgate/util/ReflectionUtils.java +++ b/core/src/main/java/org/geysermc/floodgate/util/ReflectionUtils.java @@ -30,6 +30,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.Arrays; import javax.annotation.Nullable; import lombok.Getter; import lombok.Setter; @@ -135,31 +136,31 @@ public final class ReflectionUtils { return clazz; } - public static Class getClassOrFallback(String className, String fallbackClassName1, String fallbackClassName2) { - Class clazz = getClassSilently(className); - - if (clazz != null) { - if (Constants.DEBUG_MODE) { - System.out.println("Found class (primary): " + clazz.getName()); + public static Class getClassOrFallback(String... classNames) { + for (String className : classNames) { + Class clazz = getClassSilently(className); + if (clazz != null) { + if (Constants.DEBUG_MODE) { + System.out.println("Found class: " + clazz.getName() + " for choices: " + + Arrays.toString(classNames)); + } + return clazz; } - return clazz; } - clazz = getClassSilently(fallbackClassName1); + throw new IllegalStateException("Could not find class between these choices: " + + Arrays.toString(classNames)); + } - if (clazz != null) { - if (Constants.DEBUG_MODE) { - System.out.println("Found class (fallback1): " + clazz.getName()); + @Nullable + public static Class getClassOrFallbackSilently(String... classNames) { + for (String className : classNames) { + Class clazz = getClassSilently(className); + if (clazz != null) { + return clazz; } - return clazz; } - - // do throw an exception when both classes couldn't be found - clazz = ReflectionUtils.getClassOrThrow(fallbackClassName2); - if (Constants.DEBUG_MODE) { - System.out.println("Found class (fallback2): " + clazz.getName()); - } - return clazz; + return null; } @Nullable diff --git a/spigot/src/main/java/org/geysermc/floodgate/pluginmessage/SpigotSkinApplier.java b/spigot/src/main/java/org/geysermc/floodgate/pluginmessage/SpigotSkinApplier.java index 249e61b7..06b00e99 100644 --- a/spigot/src/main/java/org/geysermc/floodgate/pluginmessage/SpigotSkinApplier.java +++ b/spigot/src/main/java/org/geysermc/floodgate/pluginmessage/SpigotSkinApplier.java @@ -31,6 +31,7 @@ import com.google.inject.Inject; import com.google.inject.Singleton; import com.mojang.authlib.GameProfile; import com.mojang.authlib.properties.Property; +import com.mojang.authlib.properties.PropertyMap; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.checkerframework.checker.nullness.qual.NonNull; @@ -87,7 +88,12 @@ public final class SpigotSkinApplier implements SkinApplier { return; } - replaceSkin(player, floodgatePlayer, event.newSkin()); + if (ClassNames.GAME_PROFILE_FIELD != null) { + replaceSkin(player, floodgatePlayer, event.newSkin()); + } else { + // We're on a version with mutable GameProfiles + replaceSkinOld(profile.getProperties(), event.newSkin()); + } versionSpecificMethods.maybeSchedule(() -> { for (Player p : Bukkit.getOnlinePlayers()) { @@ -105,4 +111,10 @@ public final class SpigotSkinApplier implements SkinApplier { Object entityHuman = ReflectionUtils.invoke(player, ClassNames.GET_ENTITY_HUMAN_METHOD); ReflectionUtils.setValue(entityHuman, ClassNames.GAME_PROFILE_FIELD, profile); } + + private void replaceSkinOld(PropertyMap properties, SkinData skinData) { + properties.removeAll("textures"); + Property property = new Property("textures", skinData.value(), skinData.signature()); + properties.put("textures", property); + } } 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 191a9cdf..52be11ea 100644 --- a/spigot/src/main/java/org/geysermc/floodgate/util/ClassNames.java +++ b/spigot/src/main/java/org/geysermc/floodgate/util/ClassNames.java @@ -28,6 +28,7 @@ package org.geysermc.floodgate.util; import static org.geysermc.floodgate.util.ReflectionUtils.castedStaticBooleanValue; import static org.geysermc.floodgate.util.ReflectionUtils.getBooleanValue; import static org.geysermc.floodgate.util.ReflectionUtils.getClassOrFallback; +import static org.geysermc.floodgate.util.ReflectionUtils.getClassOrFallbackSilently; import static org.geysermc.floodgate.util.ReflectionUtils.getClassSilently; import static org.geysermc.floodgate.util.ReflectionUtils.getConstructor; import static org.geysermc.floodgate.util.ReflectionUtils.getField; @@ -77,8 +78,8 @@ public class ClassNames { public static final Method GET_PROFILE_METHOD; - public static final Method GET_ENTITY_HUMAN_METHOD; - public static final Field GAME_PROFILE_FIELD; + @Nullable public static final Method GET_ENTITY_HUMAN_METHOD; + @Nullable public static final Field GAME_PROFILE_FIELD; public static final Method LOGIN_DISCONNECT; public static final Method NETWORK_EXCEPTION_CAUGHT; @@ -122,13 +123,16 @@ public class ClassNames { checkNotNull(GET_PROFILE_METHOD, "Get profile method"); GET_ENTITY_HUMAN_METHOD = getMethod(craftPlayerClass, "getHandle"); - checkNotNull(GET_ENTITY_HUMAN_METHOD, "getHandle method"); - Class entityHumanClass = getClassOrFallback("net.minecraft.world.entity.player.EntityHuman", - "net.minecraft.world.entity.player.Player"); - checkNotNull(entityHumanClass, "EntityHuman class"); - // Since 1.21.9: Spigot obfuscates field name - GAME_PROFILE_FIELD = getFieldOfType(entityHumanClass, GameProfile.class); - checkNotNull(GAME_PROFILE_FIELD, "EntityHuman.gameProfile field"); + Class entityHumanClass = getClassOrFallbackSilently( + "net.minecraft.world.entity.player.EntityHuman", + "net.minecraft.world.entity.player.Player" + ); + if (entityHumanClass != null) { + // Spigot obfuscates field name + GAME_PROFILE_FIELD = getFieldOfType(entityHumanClass, GameProfile.class); + } else { + GAME_PROFILE_FIELD = null; + } // SpigotInjector MINECRAFT_SERVER = getClassOrFallback( From 8a77e1fdd13b8455784d26697a51b59c7f0d654f Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 2 Oct 2025 19:37:19 -0400 Subject: [PATCH 3/3] Import cleanup --- .../org/geysermc/floodgate/pluginmessage/SpigotSkinApplier.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/spigot/src/main/java/org/geysermc/floodgate/pluginmessage/SpigotSkinApplier.java b/spigot/src/main/java/org/geysermc/floodgate/pluginmessage/SpigotSkinApplier.java index 06b00e99..c7721c1e 100644 --- a/spigot/src/main/java/org/geysermc/floodgate/pluginmessage/SpigotSkinApplier.java +++ b/spigot/src/main/java/org/geysermc/floodgate/pluginmessage/SpigotSkinApplier.java @@ -25,8 +25,6 @@ package org.geysermc.floodgate.pluginmessage; -import com.google.common.collect.Multimap; -import com.google.common.collect.MultimapBuilder; import com.google.inject.Inject; import com.google.inject.Singleton; import com.mojang.authlib.GameProfile;