1
0
mirror of https://github.com/GeyserMC/Floodgate.git synced 2025-12-19 14:59:20 +00:00

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 06bb54395f)

* Re-use existing name/uuid from old profile when applying new skin

---------

Co-authored-by: Eclipse <eclipse@eclipseisoffline.xyz>
Co-authored-by: Aurorawr <auroranova8756@gmail.com>
Co-authored-by: onebeastchris <github@onechris.mozmail.com>
This commit is contained in:
Camotoy
2025-10-03 14:55:57 -04:00
committed by GitHub
parent d5be877ed9
commit 0a8cba05f8
8 changed files with 251 additions and 34 deletions

View File

@@ -30,6 +30,7 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Arrays;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@@ -135,6 +136,39 @@ public final class ReflectionUtils {
return clazz; 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 <T> Class<T> getCastedClassOrFallback(String className, String fallbackClassName) {
return (Class<T>) getClassOrFallback(className, fallbackClassName);
}
@Nullable @Nullable
public static <T> Constructor<T> getConstructor(Class<T> clazz, boolean declared, Class<?>... parameters) { public static <T> Constructor<T> getConstructor(Class<T> clazz, boolean declared, Class<?>... parameters) {
try { try {
@@ -151,6 +185,15 @@ public final class ReflectionUtils {
} }
} }
@Nullable
public static <T> T newInstanceOrThrow(Constructor<T> constructor, Object... parameters) {
try {
return constructor.newInstance(parameters);
} catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
@Nullable @Nullable
public static <T> T newInstance(Constructor<T> constructor, Object... parameters) { public static <T> T newInstance(Constructor<T> constructor, Object... parameters) {
try { try {
@@ -201,6 +244,28 @@ public final class ReflectionUtils {
return getField(clazz, fieldName, false); 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. * 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); 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 * 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. * first try to get a declared method and if that fails it'll try to get a public method.

View File

@@ -2,4 +2,4 @@ org.gradle.configureondemand=true
org.gradle.caching=true org.gradle.caching=true
org.gradle.parallel=true org.gradle.parallel=true
version=2.2.4-SNAPSHOT version=2.2.5-SNAPSHOT

View File

@@ -35,6 +35,7 @@ import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.config.FloodgateConfig; import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.player.FloodgateHandshakeHandler; import org.geysermc.floodgate.player.FloodgateHandshakeHandler;
import org.geysermc.floodgate.util.SpigotVersionSpecificMethods;
public final class SpigotDataAddon implements InjectorAddon { public final class SpigotDataAddon implements InjectorAddon {
@Inject private FloodgateHandshakeHandler handshakeHandler; @Inject private FloodgateHandshakeHandler handshakeHandler;
@@ -54,12 +55,15 @@ public final class SpigotDataAddon implements InjectorAddon {
@Named("playerAttribute") @Named("playerAttribute")
private AttributeKey<FloodgatePlayer> playerAttribute; private AttributeKey<FloodgatePlayer> playerAttribute;
@Inject
private SpigotVersionSpecificMethods versionSpecificMethods;
@Override @Override
public void onInject(Channel channel, boolean toServer) { public void onInject(Channel channel, boolean toServer) {
// we have to add the packet blocker in the data handler, otherwise ProtocolSupport breaks // we have to add the packet blocker in the data handler, otherwise ProtocolSupport breaks
channel.pipeline().addBefore( channel.pipeline().addBefore(
packetHandlerName, "floodgate_data_handler", packetHandlerName, "floodgate_data_handler",
new SpigotDataHandler(handshakeHandler, config, kickMessageAttribute) new SpigotDataHandler(handshakeHandler, config, kickMessageAttribute, versionSpecificMethods)
); );
} }

View File

@@ -41,6 +41,7 @@ import org.geysermc.floodgate.player.FloodgateHandshakeHandler.HandshakeResult;
import org.geysermc.floodgate.util.ClassNames; import org.geysermc.floodgate.util.ClassNames;
import org.geysermc.floodgate.util.Constants; import org.geysermc.floodgate.util.Constants;
import org.geysermc.floodgate.util.ProxyUtils; import org.geysermc.floodgate.util.ProxyUtils;
import org.geysermc.floodgate.util.SpigotVersionSpecificMethods;
public final class SpigotDataHandler extends CommonDataHandler { public final class SpigotDataHandler extends CommonDataHandler {
private static final Property DEFAULT_TEXTURE_PROPERTY = new Property( 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 Constants.DEFAULT_MINECRAFT_JAVA_SKIN_SIGNATURE
); );
private final SpigotVersionSpecificMethods versionSpecificMethods;
private Object networkManager; private Object networkManager;
private FloodgatePlayer player; private FloodgatePlayer player;
private boolean proxyData; private boolean proxyData;
@@ -56,8 +58,10 @@ public final class SpigotDataHandler extends CommonDataHandler {
public SpigotDataHandler( public SpigotDataHandler(
FloodgateHandshakeHandler handshakeHandler, FloodgateHandshakeHandler handshakeHandler,
FloodgateConfig config, FloodgateConfig config,
AttributeKey<String> kickMessageAttribute) { AttributeKey<String> kickMessageAttribute,
SpigotVersionSpecificMethods versionSpecificMethods) {
super(handshakeHandler, config, kickMessageAttribute, new PacketBlocker()); super(handshakeHandler, config, kickMessageAttribute, new PacketBlocker());
this.versionSpecificMethods = versionSpecificMethods;
} }
@Override @Override
@@ -175,16 +179,15 @@ public final class SpigotDataHandler extends CommonDataHandler {
} }
} }
GameProfile gameProfile = new GameProfile( Property texturesProperty = null;
player.getCorrectUniqueId(), player.getCorrectUsername()
);
if (!player.isLinked()) { if (!player.isLinked()) {
// Otherwise game server will try to fetch the skin from Mojang. // 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 // 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) // 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 // we have to fake the offline player (login) cycle

View File

@@ -75,9 +75,7 @@ public final class SpigotSkinApplier implements SkinApplier {
// Need to be careful here - getProperties() returns an authlib PropertyMap, which extends // Need to be careful here - getProperties() returns an authlib PropertyMap, which extends
// MultiMap from Guava. Floodgate relocates Guava. // MultiMap from Guava. Floodgate relocates Guava.
PropertyMap properties = profile.getProperties(); SkinData currentSkin = versionSpecificMethods.currentSkin(profile);
SkinData currentSkin = versionSpecificMethods.currentSkin(properties);
SkinApplyEvent event = new SkinApplyEventImpl(floodgatePlayer, currentSkin, skinData); SkinApplyEvent event = new SkinApplyEventImpl(floodgatePlayer, currentSkin, skinData);
event.setCancelled(floodgatePlayer.isLinked()); event.setCancelled(floodgatePlayer.isLinked());
@@ -88,7 +86,12 @@ public final class SpigotSkinApplier implements SkinApplier {
return; 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(() -> { versionSpecificMethods.maybeSchedule(() -> {
for (Player p : Bukkit.getOnlinePlayers()) { 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"); properties.removeAll("textures");
Property property = new Property("textures", skinData.value(), skinData.signature()); Property property = new Property("textures", skinData.value(), skinData.signature());
properties.put("textures", property); properties.put("textures", property);

View File

@@ -28,6 +28,7 @@ package org.geysermc.floodgate.util;
import static org.geysermc.floodgate.util.ReflectionUtils.castedStaticBooleanValue; import static org.geysermc.floodgate.util.ReflectionUtils.castedStaticBooleanValue;
import static org.geysermc.floodgate.util.ReflectionUtils.getBooleanValue; import static org.geysermc.floodgate.util.ReflectionUtils.getBooleanValue;
import static org.geysermc.floodgate.util.ReflectionUtils.getClassOrFallback; 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.getClassSilently;
import static org.geysermc.floodgate.util.ReflectionUtils.getConstructor; import static org.geysermc.floodgate.util.ReflectionUtils.getConstructor;
import static org.geysermc.floodgate.util.ReflectionUtils.getField; import static org.geysermc.floodgate.util.ReflectionUtils.getField;
@@ -59,7 +60,9 @@ public class ClassNames {
public static final Class<?> LOGIN_LISTENER; public static final Class<?> LOGIN_LISTENER;
@Nullable public static final Class<?> CLIENT_INTENT; @Nullable public static final Class<?> CLIENT_INTENT;
public static final Constructor<OfflinePlayer> CRAFT_OFFLINE_PLAYER_CONSTRUCTOR; @Nullable public static final Constructor<OfflinePlayer> CRAFT_OFFLINE_PLAYER_CONSTRUCTOR;
@Nullable public static final Constructor<OfflinePlayer> 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<?> LOGIN_HANDLER_CONSTRUCTOR;
@Nullable public static final Constructor<?> HANDSHAKE_PACKET_CONSTRUCTOR; @Nullable public static final Constructor<?> HANDSHAKE_PACKET_CONSTRUCTOR;
@@ -76,6 +79,10 @@ public class ClassNames {
@Nullable public static final BooleanSupplier PAPER_VELOCITY_SUPPORT; @Nullable public static final BooleanSupplier PAPER_VELOCITY_SUPPORT;
public static final Method GET_PROFILE_METHOD; 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 LOGIN_DISCONNECT;
public static final Method NETWORK_EXCEPTION_CAUGHT; public static final Method NETWORK_EXCEPTION_CAUGHT;
@Nullable public static final Method INIT_UUID; @Nullable public static final Method INIT_UUID;
@@ -110,11 +117,25 @@ public class ClassNames {
// SpigotSkinApplier // SpigotSkinApplier
Class<?> craftPlayerClass = ReflectionUtils.getClass( Class<?> craftPlayerClass = getClassOrFallback(
"org.bukkit.craftbukkit." + version + "entity.CraftPlayer"); "org.bukkit.craftbukkit.entity.CraftPlayer",
"org.bukkit.craftbukkit." + version + "entity.CraftPlayer"
);
GET_PROFILE_METHOD = getMethod(craftPlayerClass, "getProfile"); GET_PROFILE_METHOD = getMethod(craftPlayerClass, "getProfile");
checkNotNull(GET_PROFILE_METHOD, "Get profile method"); 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 // SpigotInjector
MINECRAFT_SERVER = getClassOrFallback( MINECRAFT_SERVER = getClassOrFallback(
"net.minecraft.server.MinecraftServer", "net.minecraft.server.MinecraftServer",
@@ -122,21 +143,39 @@ public class ClassNames {
); );
SERVER_CONNECTION = getClassOrFallback( SERVER_CONNECTION = getClassOrFallback(
"net.minecraft.server.network.ServerConnectionListener",
"net.minecraft.server.network.ServerConnection", "net.minecraft.server.network.ServerConnection",
nmsPackage + "ServerConnection" nmsPackage + "ServerConnection"
); );
// WhitelistUtils // WhitelistUtils
Class<?> craftServerClass = ReflectionUtils.getClass( Class<?> craftServerClass = getClassOrFallback(
"org.bukkit.craftbukkit." + version + "CraftServer"); "org.bukkit.craftbukkit.CraftServer",
Class<OfflinePlayer> craftOfflinePlayerClass = ReflectionUtils.getCastedClass( "org.bukkit.craftbukkit." + version + "CraftServer"
"org.bukkit.craftbukkit." + version + "CraftOfflinePlayer"); );
Class<OfflinePlayer> craftOfflinePlayerClass = ReflectionUtils.getCastedClassOrFallback(
"org.bukkit.craftbukkit.CraftOfflinePlayer",
"org.bukkit.craftbukkit." + version + "CraftOfflinePlayer"
);
CRAFT_OFFLINE_PLAYER_CONSTRUCTOR = ReflectionUtils.getConstructor( CRAFT_OFFLINE_PLAYER_CONSTRUCTOR = ReflectionUtils.getConstructor(
craftOfflinePlayerClass, true, craftServerClass, GameProfile.class); 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 // SpigotDataHandler
Class<?> networkManager = getClassOrFallback( Class<?> networkManager = getClassOrFallback(
"net.minecraft.network.Connection",
"net.minecraft.network.NetworkManager", "net.minecraft.network.NetworkManager",
nmsPackage + "NetworkManager" nmsPackage + "NetworkManager"
); );
@@ -144,6 +183,7 @@ public class ClassNames {
SOCKET_ADDRESS = getFieldOfType(networkManager, SocketAddress.class, false); SOCKET_ADDRESS = getFieldOfType(networkManager, SocketAddress.class, false);
HANDSHAKE_PACKET = getClassOrFallback( HANDSHAKE_PACKET = getClassOrFallback(
"net.minecraft.network.protocol.handshake.ClientIntentionPacket",
"net.minecraft.network.protocol.handshake.PacketHandshakingInSetProtocol", "net.minecraft.network.protocol.handshake.PacketHandshakingInSetProtocol",
nmsPackage + "PacketHandshakingInSetProtocol" nmsPackage + "PacketHandshakingInSetProtocol"
); );
@@ -152,11 +192,13 @@ public class ClassNames {
checkNotNull(HANDSHAKE_HOST, "Handshake host"); checkNotNull(HANDSHAKE_HOST, "Handshake host");
LOGIN_START_PACKET = getClassOrFallback( LOGIN_START_PACKET = getClassOrFallback(
"net.minecraft.network.protocol.login.ServerboundHelloPacket",
"net.minecraft.network.protocol.login.PacketLoginInStart", "net.minecraft.network.protocol.login.PacketLoginInStart",
nmsPackage + "PacketLoginInStart" nmsPackage + "PacketLoginInStart"
); );
LOGIN_LISTENER = getClassOrFallback( LOGIN_LISTENER = getClassOrFallback(
"net.minecraft.server.network.ServerLoginPacketListenerImpl",
"net.minecraft.server.network.LoginListener", "net.minecraft.server.network.LoginListener",
nmsPackage + "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 // 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 // PacketListener packetListener of NetworkManager
PACKET_LISTENER = getField(networkManager, "q"); PACKET_LISTENER = getField(networkManager, "packetListener", "q");
makeAccessible(PACKET_LISTENER); makeAccessible(PACKET_LISTENER);
} }
checkNotNull(PACKET_LISTENER, "Packet listener"); checkNotNull(PACKET_LISTENER, "Packet listener");
@@ -205,7 +247,7 @@ public class ClassNames {
if (IS_POST_LOGIN_HANDLER) { if (IS_POST_LOGIN_HANDLER) {
makeAccessible(CALL_PLAYER_PRE_LOGIN_EVENTS); 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"); checkNotNull(START_CLIENT_VERIFICATION, "startClientVerification");
makeAccessible(START_CLIENT_VERIFICATION); makeAccessible(START_CLIENT_VERIFICATION);
@@ -301,15 +343,15 @@ public class ClassNames {
String.class, int.class, CLIENT_INTENT); String.class, int.class, CLIENT_INTENT);
checkNotNull(HANDSHAKE_PACKET_CONSTRUCTOR, "Handshake packet constructor"); 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)"); 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) if (a.getType().isPrimitive()) { // 1.20.2 - 1.20.4: a is the protocol version (int)
HANDSHAKE_PROTOCOL = a; HANDSHAKE_PROTOCOL = a;
HANDSHAKE_PORT = getField(HANDSHAKE_PACKET, "c"); HANDSHAKE_PORT = getField(HANDSHAKE_PACKET, "c");
} else { // 1.20.5: a is the stream_codec thing, so everything is shifted } else { // 1.20.5: a is the stream_codec thing, so everything is shifted
HANDSHAKE_PROTOCOL = getField(HANDSHAKE_PACKET, "b"); HANDSHAKE_PROTOCOL = getField(HANDSHAKE_PACKET, "protocolVersion", "b");
HANDSHAKE_PORT = getField(HANDSHAKE_PACKET, "d"); HANDSHAKE_PORT = getField(HANDSHAKE_PACKET, "port", "d");
} }
checkNotNull(HANDSHAKE_PROTOCOL, "Handshake protocol"); checkNotNull(HANDSHAKE_PROTOCOL, "Handshake protocol");

View File

@@ -25,9 +25,15 @@
package org.geysermc.floodgate.util; package org.geysermc.floodgate.util;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property; import com.mojang.authlib.properties.Property;
import com.mojang.authlib.properties.PropertyMap; import com.mojang.authlib.properties.PropertyMap;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin; 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_VALUE;
private static final Method NEW_PROPERTY_SIGNATURE; private static final Method NEW_PROPERTY_SIGNATURE;
private static final Method NEW_GAME_PROFILE_PROPERTIES;
private static final Constructor<GameProfile> RECORD_GAME_PROFILE_CONSTRUCTOR;
private static final Constructor<PropertyMap> 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 { static {
GET_SPIGOT = ReflectionUtils.getMethod(Player.class, "spigot"); 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_VALUE = ReflectionUtils.getMethod(Property.class, "value");
NEW_PROPERTY_SIGNATURE = ReflectionUtils.getMethod(Property.class, "signature"); 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>)
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; private final SpigotPlugin plugin;
@@ -62,6 +86,31 @@ public final class SpigotVersionSpecificMethods {
this.plugin = plugin; 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<String, Property> 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) { public String getLocale(Player player) {
if (OLD_GET_LOCALE == null) { if (OLD_GET_LOCALE == null) {
return player.getLocale(); return player.getLocale();
@@ -80,7 +129,14 @@ public final class SpigotVersionSpecificMethods {
hideAndShowPlayer0(on, target); 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")) { for (Property property : properties.get("textures")) {
String value; String value;
String signature; String signature;

View File

@@ -29,6 +29,7 @@ import com.mojang.authlib.GameProfile;
import java.util.UUID; import java.util.UUID;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer; import org.bukkit.OfflinePlayer;
import org.bukkit.Server;
@SuppressWarnings("ConstantConditions") @SuppressWarnings("ConstantConditions")
public final class WhitelistUtils { public final class WhitelistUtils {
@@ -44,10 +45,7 @@ public final class WhitelistUtils {
public static boolean addPlayer(UUID uuid, String username, SpigotVersionSpecificMethods versionSpecificMethods) { public static boolean addPlayer(UUID uuid, String username, SpigotVersionSpecificMethods versionSpecificMethods) {
GameProfile profile = new GameProfile(uuid, username); GameProfile profile = new GameProfile(uuid, username);
OfflinePlayer player = ReflectionUtils.newInstance( OfflinePlayer player = getOfflinePlayer(profile);
ClassNames.CRAFT_OFFLINE_PLAYER_CONSTRUCTOR,
Bukkit.getServer(), profile
);
if (player.isWhitelisted()) { if (player.isWhitelisted()) {
return false; return false;
} }
@@ -67,10 +65,7 @@ public final class WhitelistUtils {
public static boolean removePlayer(UUID uuid, String username, SpigotVersionSpecificMethods versionSpecificMethods) { public static boolean removePlayer(UUID uuid, String username, SpigotVersionSpecificMethods versionSpecificMethods) {
GameProfile profile = new GameProfile(uuid, username); GameProfile profile = new GameProfile(uuid, username);
OfflinePlayer player = ReflectionUtils.newInstance( OfflinePlayer player = getOfflinePlayer(profile);
ClassNames.CRAFT_OFFLINE_PLAYER_CONSTRUCTOR,
Bukkit.getServer(), profile
);
if (!player.isWhitelisted()) { if (!player.isWhitelisted()) {
return false; return false;
} }
@@ -81,4 +76,23 @@ public final class WhitelistUtils {
static void setWhitelist(OfflinePlayer player, boolean whitelist, SpigotVersionSpecificMethods versionSpecificMethods) { static void setWhitelist(OfflinePlayer player, boolean whitelist, SpigotVersionSpecificMethods versionSpecificMethods) {
versionSpecificMethods.maybeSchedule(() -> player.setWhitelisted(whitelist), true); // Whitelisting is on the global thread 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
);
}
}
} }