From 0a8cba05f8e93f8f2bee8f397be4fcc244953cae Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 3 Oct 2025 14:55:57 -0400 Subject: [PATCH] Update to support 1.21.9 (#616) * Start work on 1.21.9 * Works on 1.21.9 * 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 * Restored compatibility with 1.16.5 * Import cleanup * Fix /fwhitelist on 1.21.9 (cherry picked from commit 06bb54395fa2debb75bc0ec9ba69e15cf5d3e06b) * Re-use existing name/uuid from old profile when applying new skin --------- Co-authored-by: Eclipse Co-authored-by: Aurorawr Co-authored-by: onebeastchris --- .../floodgate/util/ReflectionUtils.java | 88 +++++++++++++++++++ gradle.properties | 2 +- .../floodgate/addon/data/SpigotDataAddon.java | 6 +- .../addon/data/SpigotDataHandler.java | 15 ++-- .../pluginmessage/SpigotSkinApplier.java | 20 +++-- .../geysermc/floodgate/util/ClassNames.java | 66 +++++++++++--- .../util/SpigotVersionSpecificMethods.java | 58 +++++++++++- .../floodgate/util/WhitelistUtils.java | 30 +++++-- 8 files changed, 251 insertions(+), 34 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 709dfd43..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,6 +136,39 @@ public final class ReflectionUtils { return clazz; } + 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; + } + } + + throw new IllegalStateException("Could not find class between these choices: " + + Arrays.toString(classNames)); + } + + @Nullable + public static Class getClassOrFallbackSilently(String... classNames) { + for (String className : classNames) { + Class clazz = getClassSilently(className); + if (clazz != null) { + return clazz; + } + } + return null; + } + + @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 { @@ -151,6 +185,15 @@ public final class ReflectionUtils { } } + @Nullable + public static T newInstanceOrThrow(Constructor constructor, Object... parameters) { + try { + return constructor.newInstance(parameters); + } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + @Nullable public static T newInstance(Constructor constructor, Object... parameters) { try { @@ -201,6 +244,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. * @@ -409,6 +474,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/gradle.properties b/gradle.properties index 69e11adc..8c673b86 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,4 +2,4 @@ org.gradle.configureondemand=true org.gradle.caching=true org.gradle.parallel=true -version=2.2.4-SNAPSHOT \ No newline at end of file +version=2.2.5-SNAPSHOT \ No newline at end of file diff --git a/spigot/src/main/java/org/geysermc/floodgate/addon/data/SpigotDataAddon.java b/spigot/src/main/java/org/geysermc/floodgate/addon/data/SpigotDataAddon.java index 6e5c79f7..eb0fdaab 100644 --- a/spigot/src/main/java/org/geysermc/floodgate/addon/data/SpigotDataAddon.java +++ b/spigot/src/main/java/org/geysermc/floodgate/addon/data/SpigotDataAddon.java @@ -35,6 +35,7 @@ import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.config.FloodgateConfig; import org.geysermc.floodgate.player.FloodgateHandshakeHandler; +import org.geysermc.floodgate.util.SpigotVersionSpecificMethods; public final class SpigotDataAddon implements InjectorAddon { @Inject private FloodgateHandshakeHandler handshakeHandler; @@ -54,12 +55,15 @@ public final class SpigotDataAddon implements InjectorAddon { @Named("playerAttribute") private AttributeKey playerAttribute; + @Inject + private SpigotVersionSpecificMethods versionSpecificMethods; + @Override public void onInject(Channel channel, boolean toServer) { // we have to add the packet blocker in the data handler, otherwise ProtocolSupport breaks channel.pipeline().addBefore( packetHandlerName, "floodgate_data_handler", - new SpigotDataHandler(handshakeHandler, config, kickMessageAttribute) + new SpigotDataHandler(handshakeHandler, config, kickMessageAttribute, versionSpecificMethods) ); } 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 383fa7e9..8efa4b41 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 @@ -41,6 +41,7 @@ import org.geysermc.floodgate.player.FloodgateHandshakeHandler.HandshakeResult; import org.geysermc.floodgate.util.ClassNames; import org.geysermc.floodgate.util.Constants; import org.geysermc.floodgate.util.ProxyUtils; +import org.geysermc.floodgate.util.SpigotVersionSpecificMethods; public final class SpigotDataHandler extends CommonDataHandler { private static final Property DEFAULT_TEXTURE_PROPERTY = new Property( @@ -49,6 +50,7 @@ public final class SpigotDataHandler extends CommonDataHandler { Constants.DEFAULT_MINECRAFT_JAVA_SKIN_SIGNATURE ); + private final SpigotVersionSpecificMethods versionSpecificMethods; private Object networkManager; private FloodgatePlayer player; private boolean proxyData; @@ -56,8 +58,10 @@ public final class SpigotDataHandler extends CommonDataHandler { public SpigotDataHandler( FloodgateHandshakeHandler handshakeHandler, FloodgateConfig config, - AttributeKey kickMessageAttribute) { + AttributeKey kickMessageAttribute, + SpigotVersionSpecificMethods versionSpecificMethods) { super(handshakeHandler, config, kickMessageAttribute, new PacketBlocker()); + this.versionSpecificMethods = versionSpecificMethods; } @Override @@ -175,16 +179,15 @@ public final class SpigotDataHandler extends CommonDataHandler { } } - GameProfile gameProfile = new GameProfile( - player.getCorrectUniqueId(), player.getCorrectUsername() - ); - + Property texturesProperty = null; if (!player.isLinked()) { // Otherwise game server will try to fetch the skin from Mojang. // No need to worry that this overrides proxy data, because those won't reach this // method / are already removed (in the case of username validation) - gameProfile.getProperties().put("textures", DEFAULT_TEXTURE_PROPERTY); + texturesProperty = DEFAULT_TEXTURE_PROPERTY; } + GameProfile gameProfile = versionSpecificMethods.createGameProfile( + player.getCorrectUniqueId(), player.getCorrectUsername(), texturesProperty); // we have to fake the offline player (login) cycle 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 d312afdc..498a1d74 100644 --- a/spigot/src/main/java/org/geysermc/floodgate/pluginmessage/SpigotSkinApplier.java +++ b/spigot/src/main/java/org/geysermc/floodgate/pluginmessage/SpigotSkinApplier.java @@ -75,9 +75,7 @@ public final class SpigotSkinApplier implements SkinApplier { // Need to be careful here - getProperties() returns an authlib PropertyMap, which extends // MultiMap from Guava. Floodgate relocates Guava. - PropertyMap properties = profile.getProperties(); - - SkinData currentSkin = versionSpecificMethods.currentSkin(properties); + SkinData currentSkin = versionSpecificMethods.currentSkin(profile); SkinApplyEvent event = new SkinApplyEventImpl(floodgatePlayer, currentSkin, skinData); event.setCancelled(floodgatePlayer.isLinked()); @@ -88,7 +86,12 @@ public final class SpigotSkinApplier implements SkinApplier { return; } - replaceSkin(properties, event.newSkin()); + if (ClassNames.GAME_PROFILE_FIELD != null) { + replaceSkin(player, profile, event.newSkin()); + } else { + // We're on a version with mutable GameProfiles + replaceSkinOld(profile.getProperties(), event.newSkin()); + } versionSpecificMethods.maybeSchedule(() -> { for (Player p : Bukkit.getOnlinePlayers()) { @@ -99,7 +102,14 @@ public final class SpigotSkinApplier implements SkinApplier { }); } - private void replaceSkin(PropertyMap properties, SkinData skinData) { + private void replaceSkin(Player player, GameProfile oldProfile, SkinData skinData) { + Property skinProperty = new Property("textures", skinData.value(), skinData.signature()); + GameProfile profile = versionSpecificMethods.createGameProfile(oldProfile, skinProperty); + 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 029b89b1..14072a64 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; @@ -59,7 +60,9 @@ public class ClassNames { public static final Class LOGIN_LISTENER; @Nullable public static final Class CLIENT_INTENT; - public static final Constructor CRAFT_OFFLINE_PLAYER_CONSTRUCTOR; + @Nullable public static final Constructor CRAFT_OFFLINE_PLAYER_CONSTRUCTOR; + @Nullable public static final Constructor CRAFT_NEW_OFFLINE_PLAYER_CONSTRUCTOR; + @Nullable public static final Constructor NAME_AND_ID_CONSTRUCTOR; @Nullable public static final Constructor LOGIN_HANDLER_CONSTRUCTOR; @Nullable public static final Constructor HANDSHAKE_PACKET_CONSTRUCTOR; @@ -76,6 +79,10 @@ public class ClassNames { @Nullable public static final BooleanSupplier PAPER_VELOCITY_SUPPORT; public static final Method GET_PROFILE_METHOD; + + @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; @Nullable public static final Method INIT_UUID; @@ -110,11 +117,25 @@ 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"); + GET_ENTITY_HUMAN_METHOD = getMethod(craftPlayerClass, "getHandle"); + 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( "net.minecraft.server.MinecraftServer", @@ -122,21 +143,39 @@ 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); + if (CRAFT_OFFLINE_PLAYER_CONSTRUCTOR == null) { // Changed in 1.21.9 + Class nameAndIdClass = getClassSilently("net.minecraft.server.players.NameAndId"); + + CRAFT_NEW_OFFLINE_PLAYER_CONSTRUCTOR = ReflectionUtils.getConstructor( + craftOfflinePlayerClass, true, craftServerClass, nameAndIdClass); + + NAME_AND_ID_CONSTRUCTOR = ReflectionUtils.getConstructor(nameAndIdClass, true, GameProfile.class); + } else { + CRAFT_NEW_OFFLINE_PLAYER_CONSTRUCTOR = null; + NAME_AND_ID_CONSTRUCTOR = null; + } + // SpigotDataHandler Class networkManager = getClassOrFallback( + "net.minecraft.network.Connection", "net.minecraft.network.NetworkManager", nmsPackage + "NetworkManager" ); @@ -144,6 +183,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" ); @@ -152,11 +192,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" ); @@ -197,7 +239,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"); @@ -205,7 +247,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); @@ -301,15 +343,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"); diff --git a/spigot/src/main/java/org/geysermc/floodgate/util/SpigotVersionSpecificMethods.java b/spigot/src/main/java/org/geysermc/floodgate/util/SpigotVersionSpecificMethods.java index 21457ef4..65fa1ac1 100644 --- a/spigot/src/main/java/org/geysermc/floodgate/util/SpigotVersionSpecificMethods.java +++ b/spigot/src/main/java/org/geysermc/floodgate/util/SpigotVersionSpecificMethods.java @@ -25,9 +25,15 @@ package org.geysermc.floodgate.util; +import com.mojang.authlib.GameProfile; import com.mojang.authlib.properties.Property; import com.mojang.authlib.properties.PropertyMap; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; import java.util.concurrent.TimeUnit; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; @@ -42,6 +48,12 @@ public final class SpigotVersionSpecificMethods { private static final Method NEW_PROPERTY_VALUE; private static final Method NEW_PROPERTY_SIGNATURE; + private static final Method NEW_GAME_PROFILE_PROPERTIES; + private static final Constructor RECORD_GAME_PROFILE_CONSTRUCTOR; + private static final Constructor IMMUTABLE_PROPERTY_MAP_CONSTRUCTOR; + private static final Method MULTIMAP_FROM_MAP; + private static final Field PROFILE_NAME_FIELD; + private static final Field PROFILE_UUID_FIELD; static { GET_SPIGOT = ReflectionUtils.getMethod(Player.class, "spigot"); @@ -54,6 +66,18 @@ public final class SpigotVersionSpecificMethods { NEW_PROPERTY_VALUE = ReflectionUtils.getMethod(Property.class, "value"); NEW_PROPERTY_SIGNATURE = ReflectionUtils.getMethod(Property.class, "signature"); + NEW_GAME_PROFILE_PROPERTIES = ReflectionUtils.getMethod( + GameProfile.class, "properties"); + RECORD_GAME_PROFILE_CONSTRUCTOR = ReflectionUtils.getConstructor( + GameProfile.class, true, UUID.class, String.class, PropertyMap.class); + IMMUTABLE_PROPERTY_MAP_CONSTRUCTOR = (Constructor) + PropertyMap.class.getConstructors()[0]; + PROFILE_NAME_FIELD = ReflectionUtils.getField(GameProfile.class, "name"); + PROFILE_UUID_FIELD = ReflectionUtils.getField(GameProfile.class, "id"); + // Avoid relocation for this class. + Class multimaps = ReflectionUtils.getClass(String.join(".", "com", + "google", "common", "collect", "Multimaps")); + MULTIMAP_FROM_MAP = ReflectionUtils.getMethod(multimaps, "forMap", Map.class); } private final SpigotPlugin plugin; @@ -62,6 +86,31 @@ public final class SpigotVersionSpecificMethods { this.plugin = plugin; } + public GameProfile createGameProfile(GameProfile oldProfile, Property textureProperty) { + String name = (String) ReflectionUtils.getValue(oldProfile, PROFILE_NAME_FIELD); + UUID uuid = (UUID) ReflectionUtils.getValue(oldProfile, PROFILE_UUID_FIELD); + return createGameProfile(uuid, name, textureProperty); + } + + public GameProfile createGameProfile(UUID uuid, String name, Property texturesProperty) { + if (RECORD_GAME_PROFILE_CONSTRUCTOR != null && IMMUTABLE_PROPERTY_MAP_CONSTRUCTOR != null) { + if (texturesProperty != null) { + Map properties = new HashMap<>(); + properties.put("textures", texturesProperty); + Object multimap = ReflectionUtils.invoke(null, MULTIMAP_FROM_MAP, properties); + return ReflectionUtils.newInstanceOrThrow(RECORD_GAME_PROFILE_CONSTRUCTOR, uuid, + name, + ReflectionUtils.newInstanceOrThrow(IMMUTABLE_PROPERTY_MAP_CONSTRUCTOR, + multimap)); + } + } + GameProfile profile = new GameProfile(uuid, name); + if (texturesProperty != null) { + profile.getProperties().put("textures", texturesProperty); + } + return profile; + } + public String getLocale(Player player) { if (OLD_GET_LOCALE == null) { return player.getLocale(); @@ -80,7 +129,14 @@ public final class SpigotVersionSpecificMethods { hideAndShowPlayer0(on, target); } - public SkinApplyEvent.SkinData currentSkin(PropertyMap properties) { + public SkinApplyEvent.SkinData currentSkin(GameProfile profile) { + PropertyMap properties; + if (NEW_GAME_PROFILE_PROPERTIES != null) { + properties = ReflectionUtils.castedInvoke(profile, NEW_GAME_PROFILE_PROPERTIES); + } else { + properties = profile.getProperties(); + } + for (Property property : properties.get("textures")) { String value; String signature; diff --git a/spigot/src/main/java/org/geysermc/floodgate/util/WhitelistUtils.java b/spigot/src/main/java/org/geysermc/floodgate/util/WhitelistUtils.java index 3e675d64..9ddedd28 100644 --- a/spigot/src/main/java/org/geysermc/floodgate/util/WhitelistUtils.java +++ b/spigot/src/main/java/org/geysermc/floodgate/util/WhitelistUtils.java @@ -29,6 +29,7 @@ import com.mojang.authlib.GameProfile; import java.util.UUID; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; +import org.bukkit.Server; @SuppressWarnings("ConstantConditions") public final class WhitelistUtils { @@ -44,10 +45,7 @@ public final class WhitelistUtils { public static boolean addPlayer(UUID uuid, String username, SpigotVersionSpecificMethods versionSpecificMethods) { GameProfile profile = new GameProfile(uuid, username); - OfflinePlayer player = ReflectionUtils.newInstance( - ClassNames.CRAFT_OFFLINE_PLAYER_CONSTRUCTOR, - Bukkit.getServer(), profile - ); + OfflinePlayer player = getOfflinePlayer(profile); if (player.isWhitelisted()) { return false; } @@ -67,10 +65,7 @@ public final class WhitelistUtils { public static boolean removePlayer(UUID uuid, String username, SpigotVersionSpecificMethods versionSpecificMethods) { GameProfile profile = new GameProfile(uuid, username); - OfflinePlayer player = ReflectionUtils.newInstance( - ClassNames.CRAFT_OFFLINE_PLAYER_CONSTRUCTOR, - Bukkit.getServer(), profile - ); + OfflinePlayer player = getOfflinePlayer(profile); if (!player.isWhitelisted()) { return false; } @@ -81,4 +76,23 @@ public final class WhitelistUtils { static void setWhitelist(OfflinePlayer player, boolean whitelist, SpigotVersionSpecificMethods versionSpecificMethods) { versionSpecificMethods.maybeSchedule(() -> player.setWhitelisted(whitelist), true); // Whitelisting is on the global thread } + + static OfflinePlayer getOfflinePlayer(GameProfile profile) { + if (ClassNames.CRAFT_NEW_OFFLINE_PLAYER_CONSTRUCTOR != null) { + Object nameAndId = ReflectionUtils.newInstance( + ClassNames.NAME_AND_ID_CONSTRUCTOR, + profile + ); + + return ReflectionUtils.newInstance( + ClassNames.CRAFT_NEW_OFFLINE_PLAYER_CONSTRUCTOR, + Bukkit.getServer(), nameAndId + ); + } else { + return ReflectionUtils.newInstance( + ClassNames.CRAFT_OFFLINE_PLAYER_CONSTRUCTOR, + Bukkit.getServer(), profile + ); + } + } }