From 1c06a8d51d928293b77d30261955badc996dd393 Mon Sep 17 00:00:00 2001 From: Pasqual Koschmieder Date: Mon, 6 Oct 2025 10:43:55 +0200 Subject: [PATCH] fix: game profile construction on bukkit with ProtocolLib --- .../protocol/ProtocolLibPacketAdapter.java | 10 +- .../protocol/ProtocolLibProfileFactory.java | 119 ++++++++++++++++++ 2 files changed, 120 insertions(+), 9 deletions(-) create mode 100644 bukkit/src/main/java/com/github/juliarn/npclib/bukkit/protocol/ProtocolLibProfileFactory.java diff --git a/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/protocol/ProtocolLibPacketAdapter.java b/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/protocol/ProtocolLibPacketAdapter.java index e6f4a1c..14b6f2a 100644 --- a/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/protocol/ProtocolLibPacketAdapter.java +++ b/bukkit/src/main/java/com/github/juliarn/npclib/bukkit/protocol/ProtocolLibPacketAdapter.java @@ -40,13 +40,11 @@ import com.comphenix.protocol.wrappers.WrappedDataWatcher; import com.comphenix.protocol.wrappers.WrappedEnumEntityUseAction; import com.comphenix.protocol.wrappers.WrappedGameProfile; -import com.comphenix.protocol.wrappers.WrappedSignedProperty; import com.comphenix.protocol.wrappers.WrappedWatchableObject; import com.github.juliarn.npclib.api.Npc; import com.github.juliarn.npclib.api.Platform; import com.github.juliarn.npclib.api.PlatformVersionAccessor; import com.github.juliarn.npclib.api.event.InteractNpcEvent; -import com.github.juliarn.npclib.api.profile.ProfileProperty; import com.github.juliarn.npclib.api.protocol.OutboundPacket; import com.github.juliarn.npclib.api.protocol.PlatformPacketAdapter; import com.github.juliarn.npclib.api.protocol.chat.Component; @@ -364,14 +362,8 @@ final class ProtocolLibPacketAdapter implements PlatformPacketAdapter): Object + private static final MethodHandle CONSTRUCT_PROFILE_PROPERTY_MODERN; // (String, String, String): Object + + static { + MethodHandle constructGameProfileModern; + MethodHandle constructGameProfilePropertyModern; + try { + Class gameProfileClass = Class.forName("com.mojang.authlib.GameProfile"); + Class gameProfilePropertyClass = Class.forName("com.mojang.authlib.properties.Property"); + Class gameProfilePropertyMapClass = Class.forName("com.mojang.authlib.properties.PropertyMap"); + + // resolve constructor for constructing a profile property + MethodHandles.Lookup lookup = MethodHandles.publicLookup(); // everything should be public api + MethodHandle constructProfileProperty = lookup.findConstructor( + gameProfilePropertyClass, + MethodType.methodType(void.class, String.class, String.class, String.class)); + MethodType propertyAsObject = constructProfileProperty.type().changeReturnType(Object.class); + constructGameProfilePropertyModern = constructProfileProperty.asType(propertyAsObject); + + // resolve constructor for constructing a game profile + MethodHandle constructGameProfile = lookup.findConstructor( + gameProfileClass, + MethodType.methodType(void.class, UUID.class, String.class, gameProfilePropertyMapClass)); + MethodHandle constructGamePropertiesMap = lookup.findConstructor( + gameProfilePropertyMapClass, + MethodType.methodType(void.class, Multimap.class)); + MethodHandle constructFull = MethodHandles.filterArguments(constructGameProfile, 2, constructGamePropertiesMap); + MethodType constructFullAsObject = constructFull.type().changeReturnType(Object.class); + constructGameProfileModern = constructFull.asType(constructFullAsObject); + } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException ignored) { + constructGameProfileModern = null; + constructGameProfilePropertyModern = null; + } + + CONSTRUCT_PROFILE_MODERN = constructGameProfileModern; + CONSTRUCT_PROFILE_PROPERTY_MODERN = constructGameProfilePropertyModern; + } + + private ProtocolLibProfileFactory() { + throw new UnsupportedOperationException(); + } + + /** + * Wraps the given resolved profile into a ProtocolLib {@link WrappedGameProfile}. + * + * @param profile the profile to wrap. + * @return the given profile wrapped into a {@link WrappedGameProfile}. + * @throws NullPointerException if the given profile is null. + * @throws IllegalArgumentException if the given profile is invalid. + * @throws IllegalStateException if the profile construction failed for some reason. + */ + public static @NotNull WrappedGameProfile wrapProfile(@NotNull Profile.Resolved profile) { + if (CONSTRUCT_PROFILE_MODERN != null) { + // constructs a modern (authlib >= v7) profile using the authlib.GameProfile constructor + try { + Multimap properties = HashMultimap.create(); + for (ProfileProperty prop : profile.properties()) { + Object property = CONSTRUCT_PROFILE_PROPERTY_MODERN.invokeExact(prop.name(), prop.value(), prop.signature()); + properties.put(prop.name(), property); + } + Object gameProfile = CONSTRUCT_PROFILE_MODERN.invokeExact(profile.uniqueId(), profile.name(), properties); + return WrappedGameProfile.fromHandle(gameProfile); + } catch (Throwable thrown) { + throw new IllegalStateException("Unable to wrap resolved profile into game profile", thrown); + } + } else { + // use the wrapped profile offered by ProtocolLib + WrappedGameProfile wrappedGameProfile = new WrappedGameProfile(profile.uniqueId(), profile.name()); + for (ProfileProperty prop : profile.properties()) { + WrappedSignedProperty wrapped = new WrappedSignedProperty(prop.name(), prop.value(), prop.signature()); + wrappedGameProfile.getProperties().put(prop.name(), wrapped); + } + return wrappedGameProfile; + } + } +}