mirror of
https://github.com/GeyserMC/Floodgate.git
synced 2025-12-19 06:49:24 +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:
@@ -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 <T> Class<T> getCastedClassOrFallback(String className, String fallbackClassName) {
|
||||
return (Class<T>) getClassOrFallback(className, fallbackClassName);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static <T> Constructor<T> getConstructor(Class<T> clazz, boolean declared, Class<?>... parameters) {
|
||||
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
|
||||
public static <T> T newInstance(Constructor<T> 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.
|
||||
|
||||
@@ -2,4 +2,4 @@ org.gradle.configureondemand=true
|
||||
org.gradle.caching=true
|
||||
org.gradle.parallel=true
|
||||
|
||||
version=2.2.4-SNAPSHOT
|
||||
version=2.2.5-SNAPSHOT
|
||||
@@ -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<FloodgatePlayer> 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)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<String> kickMessageAttribute) {
|
||||
AttributeKey<String> 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
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<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<?> 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<OfflinePlayer> craftOfflinePlayerClass = ReflectionUtils.getCastedClass(
|
||||
"org.bukkit.craftbukkit." + version + "CraftOfflinePlayer");
|
||||
Class<?> craftServerClass = getClassOrFallback(
|
||||
"org.bukkit.craftbukkit.CraftServer",
|
||||
"org.bukkit.craftbukkit." + version + "CraftServer"
|
||||
);
|
||||
Class<OfflinePlayer> 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");
|
||||
|
||||
@@ -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<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 {
|
||||
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>)
|
||||
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<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) {
|
||||
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;
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user