From addfacb19c76d34dabc83394922f588a39e91956 Mon Sep 17 00:00:00 2001 From: Dan Mulloy Date: Sun, 29 Mar 2015 22:56:11 -0400 Subject: [PATCH] Add backwards compatibility for versions 1.0 thru 1.7.10 --- ProtocolLib/.gitignore | 2 + ProtocolLib/pom.xml | 75 ++++- .../comphenix/protocol/ProtocolLibrary.java | 2 +- .../injector/netty/ChannelInjector.java | 7 +- .../injector/netty/NettyProtocolRegistry.java | 92 ++++-- .../protocol/injector/netty/WirePacket.java | 15 +- .../protocol/reflect/accessors/Accessors.java | 24 +- .../reflect/cloning/BukkitCloner.java | 11 +- .../reflect/instances/DefaultInstances.java | 1 - .../protocol/utility/MinecraftReflection.java | 13 +- .../protocol/wrappers/WrappedBlockData.java | 15 +- .../protocol/wrappers/WrappedDataWatcher.java | 299 +++++++++--------- .../protocol/wrappers/WrappedGameProfile.java | 176 ++++++----- 13 files changed, 475 insertions(+), 257 deletions(-) diff --git a/ProtocolLib/.gitignore b/ProtocolLib/.gitignore index 71db6a9a..fdd8d4c3 100644 --- a/ProtocolLib/.gitignore +++ b/ProtocolLib/.gitignore @@ -163,3 +163,5 @@ pip-log.txt # Mac crap .DS_Store +/target +/target diff --git a/ProtocolLib/pom.xml b/ProtocolLib/pom.xml index 65af8f85..ac561748 100644 --- a/ProtocolLib/pom.xml +++ b/ProtocolLib/pom.xml @@ -12,7 +12,7 @@ cp1252 - 1.5 + ProtocolLib @@ -75,6 +75,7 @@ org.spigotmc:spigot org.spigotmc:spigot-api junit:junit + com.google* @@ -99,7 +100,7 @@ false - ${project.name} + ${jarName} @@ -186,6 +187,62 @@ + + + backwards-compat + + + ProtocolLib-Legacy + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 2.3 + + + package + + shade + + + false + false + + + + net.sf + com.comphenix.net.sf + + + com.google.common + com.comphenix.protocol.compat.com.google.common + + + io.netty + net.minecraft.util.io.netty + + + + + + org.spigotmc:spigot + org.spigotmc:spigot-api + junit:junit + + + + + + + + + + + @@ -264,14 +321,24 @@ org.powermock powermock-module-junit4 - ${powermock.version} + 1.5 test org.powermock powermock-api-mockito - ${powermock.version} + 1.5 test + + com.google.guava + guava + 17.0 + + + io.netty + netty-all + 4.0.26.Final + \ No newline at end of file diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java index ee17d303..33757919 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java @@ -83,7 +83,7 @@ public class ProtocolLibrary extends JavaPlugin { /** * The minimum version ProtocolLib has been tested with. */ - public static final String MINIMUM_MINECRAFT_VERSION = "1.8"; + public static final String MINIMUM_MINECRAFT_VERSION = "1.0"; /** * The maximum version ProtocolLib has been tested with, diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/ChannelInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/ChannelInjector.java index 51d51c0b..5d0f702f 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/ChannelInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/ChannelInjector.java @@ -50,9 +50,9 @@ import com.comphenix.protocol.utility.MinecraftFields; import com.comphenix.protocol.utility.MinecraftMethods; import com.comphenix.protocol.utility.MinecraftProtocolVersion; import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.wrappers.WrappedGameProfile; import com.google.common.base.Preconditions; import com.google.common.collect.MapMaker; -import com.mojang.authlib.GameProfile; /** * Represents a channel injector. @@ -563,13 +563,14 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector { PACKET_LOGIN_CLIENT = loginClass; } if (loginClient == null) { - loginClient = Accessors.getFieldAccessor(PACKET_LOGIN_CLIENT, GameProfile.class, true); + loginClient = Accessors.getFieldAccessor(PACKET_LOGIN_CLIENT, MinecraftReflection.getGameProfileClass(), true); LOGIN_GAME_PROFILE = loginClient; } // See if we are dealing with the login packet if (loginClass.equals(packetClass)) { - GameProfile profile = (GameProfile) loginClient.get(packet); + // GameProfile profile = (GameProfile) loginClient.get(packet); + WrappedGameProfile profile = WrappedGameProfile.fromHandle(loginClient.get(packet)); // Save the channel injector factory.cacheInjector(profile.getName(), this); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/NettyProtocolRegistry.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/NettyProtocolRegistry.java index 4b5f10f6..3a70993d 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/NettyProtocolRegistry.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/NettyProtocolRegistry.java @@ -12,8 +12,10 @@ import com.comphenix.protocol.PacketType.Sender; import com.comphenix.protocol.injector.packet.MapContainer; import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.utility.MinecraftVersion; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; +import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; @@ -109,6 +111,12 @@ public class NettyProtocolRegistry { private synchronized void initialize() { Object[] protocols = enumProtocol.getEnumConstants(); + // TODO: Fins a better less than 1.7 check + if (MinecraftVersion.getCurrentVersion().compareTo(MinecraftVersion.BOUNTIFUL_UPDATE) <= 0) { + initialize17(); + return; + } + // ID to Packet class maps Map>> serverMaps = Maps.newLinkedHashMap(); Map>> clientMaps = Maps.newLinkedHashMap(); @@ -140,13 +148,13 @@ public class NettyProtocolRegistry { result.containers.add(new MapContainer(map)); } -// // Heuristic - there are more server packets than client packets -// if (sum(clientMaps) > sum(serverMaps)) { -// // Swap if this is violated -// List>> temp = serverMaps; -// serverMaps = clientMaps; -// clientMaps = temp; -// } + // Heuristic - there are more server packets than client packets + /* if (sum(clientMaps) > sum(serverMaps)) { + // Swap if this is violated + List>> temp = serverMaps; + serverMaps = clientMaps; + clientMaps = temp; + } */ for (int i = 0; i < protocols.length; i++) { Object protocol = protocols[i]; @@ -159,6 +167,50 @@ public class NettyProtocolRegistry { if (clientMaps.containsKey(protocol)) associatePackets(result, clientMaps.get(protocol), equivalent, Sender.CLIENT); } + + // Exchange (thread safe, as we have only one writer) + this.register = result; + } + + private synchronized void initialize17() { + final Object[] protocols = enumProtocol.getEnumConstants(); + List>> serverMaps = Lists.newArrayList(); + List>> clientMaps = Lists.newArrayList(); + StructureModifier modifier = null; + + // Result + Register result = new Register(); + + for (Object protocol : protocols) { + if (modifier == null) + modifier = new StructureModifier(protocol.getClass().getSuperclass(), false); + StructureModifier>> maps = modifier.withTarget(protocol).withType(Map.class); + + serverMaps.add(maps.read(0)); + clientMaps.add(maps.read(1)); + } + // Maps we have to occationally check have changed + for (Map> map : Iterables.concat(serverMaps, clientMaps)) { + result.containers.add(new MapContainer(map)); + } + + // Heuristic - there are more server packets than client packets + if (sum(clientMaps) > sum(serverMaps)) { + // Swap if this is violated + List>> temp = serverMaps; + serverMaps = clientMaps; + clientMaps = temp; + } + + for (int i = 0; i < protocols.length; i++) { + Enum enumProtocol = (Enum) protocols[i]; + Protocol equivalent = Protocol.fromVanilla(enumProtocol); + + // Associate known types + associatePackets(result, serverMaps.get(i), equivalent, Sender.SERVER); + associatePackets(result, clientMaps.get(i), equivalent, Sender.CLIENT); + } + // Exchange (thread safe, as we have only one writer) this.register = result; } @@ -175,16 +227,16 @@ public class NettyProtocolRegistry { } } -// /** -// * Retrieve the number of mapping in all the maps. -// * @param maps - iterable of maps. -// * @return The sum of all the entries. -// */ -// private int sum(Iterable>> maps) { -// int count = 0; -// -// for (Map> map : maps) -// count += map.size(); -// return count; -// } -} + /** + * Retrieve the number of mapping in all the maps. + * @param maps - iterable of maps. + * @return The sum of all the entries. + */ + private int sum(Iterable>> maps) { + int count = 0; + + for (Map> map : maps) + count += map.size(); + return count; + } +} \ No newline at end of file diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/WirePacket.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/WirePacket.java index e73acbac..34f2102f 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/WirePacket.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/WirePacket.java @@ -1,5 +1,18 @@ /** - * (c) 2015 dmulloy2 + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2015 dmulloy2 + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA */ package com.comphenix.protocol.injector.netty; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/accessors/Accessors.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/accessors/Accessors.java index bd94432e..d08d6685 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/accessors/Accessors.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/accessors/Accessors.java @@ -52,7 +52,7 @@ public final class Accessors { * @return The field accessor. * @throws IllegalArgumentException If the field cannot be found. */ - public static FieldAccessor getFieldAccessor(Class instanceClass, Class fieldClass, boolean forceAccess) { + public static FieldAccessor getFieldAccessor(Class instanceClass, Class fieldClass, boolean forceAccess) { // Get a field accessor Field field = FuzzyReflection.fromClass(instanceClass, forceAccess).getFieldByType(null, fieldClass); return Accessors.getFieldAccessor(field); @@ -65,7 +65,7 @@ public final class Accessors { * @param forceAccess - whether or not to look for private and protected fields. * @return The accessors. */ - public static FieldAccessor[] getFieldAccessorArray(Class instanceClass, Class fieldClass, boolean forceAccess) { + public static FieldAccessor[] getFieldAccessorArray(Class instanceClass, Class fieldClass, boolean forceAccess) { List fields = FuzzyReflection.fromClass(instanceClass, forceAccess).getFieldListByType(fieldClass); FieldAccessor[] accessors = new FieldAccessor[fields.size()]; @@ -83,7 +83,7 @@ public final class Accessors { * @return The value of that field. * @throws IllegalArgumentException If the field cannot be found. */ - public static FieldAccessor getFieldAccessor(Class instanceClass, String fieldName, boolean forceAccess) { + public static FieldAccessor getFieldAccessor(Class instanceClass, String fieldName, boolean forceAccess) { return Accessors.getFieldAccessor(ExactReflection.fromClass(instanceClass, true).getField(fieldName)); } @@ -120,13 +120,27 @@ public final class Accessors { // Verify the type if (fieldType.isAssignableFrom(accessor.getField().getType())) { - return accessor; + return accessor; } return null; } catch (IllegalArgumentException e) { return null; } } + + /** + * Retrieve a method accessor for a field with the given name and equivalent type, or NULL. + * @param clazz - the declaration class. + * @param methodName - the method name. + * @return The method accessor, or NULL if not found. + */ + public static MethodAccessor getMethodAcccessorOrNull(Class clazz, String methodName) { + try { + return Accessors.getMethodAccessor(clazz, methodName); + } catch (IllegalArgumentException e) { + return null; + } + } /** * Find a specific constructor in a class. @@ -145,7 +159,7 @@ public final class Accessors { /** * Retrieve a field accessor that will cache the content of the field. *

- * Note that we don't check if the underlying field has changed after the value has been cached, + * Note that we don't check if the underlying field has changed after the value has been cached, * so it's best to use this on final fields. * @param inner - the accessor. * @return A cached field accessor. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/cloning/BukkitCloner.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/cloning/BukkitCloner.java index dedaf7f2..3098a60d 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/cloning/BukkitCloner.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/cloning/BukkitCloner.java @@ -40,12 +40,21 @@ public class BukkitCloner implements Cloner { List> classes = Lists.newArrayList(); classes.add(MinecraftReflection.getItemStackClass()); - classes.add(MinecraftReflection.getBlockPositionClass()); classes.add(MinecraftReflection.getDataWatcherClass()); + // Try to add position classes + try { + classes.add(MinecraftReflection.getBlockPositionClass()); + } catch (Throwable ex) { } + + try { + classes.add(MinecraftReflection.getChunkPositionClass()); + } catch (Throwable ex) { } + if (MinecraftReflection.isUsingNetty()) { classes.add(MinecraftReflection.getServerPingClass()); } + this.clonableClasses = classes.toArray(new Class[0]); } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/instances/DefaultInstances.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/instances/DefaultInstances.java index 3e13de80..ee14eef3 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/instances/DefaultInstances.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/instances/DefaultInstances.java @@ -290,7 +290,6 @@ public class DefaultInstances implements InstanceProvider { return createInstance(type, minimum, types, params); } - } catch (Exception e) { // Nope, we couldn't create this type. Might for instance be NotConstructableException. } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java index f599f543..6c7bcb01 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java @@ -596,8 +596,17 @@ public class MinecraftReflection { if (!isUsingNetty()) throw new IllegalStateException("GameProfile does not exist in version 1.6.4 and earlier."); - // Yay, we can actually refer to it directly - return GameProfile.class; + try { + return GameProfile.class; + } catch (Throwable ex) { + // As far as I can tell, the named entity spawn packet is the only packet that uses GameProfiles + FuzzyReflection reflection = FuzzyReflection.fromClass(PacketType.Play.Server.NAMED_ENTITY_SPAWN.getPacketClass(), true); + FuzzyFieldContract contract = FuzzyFieldContract.newBuilder() + .banModifier(Modifier.STATIC) + .typeMatches(FuzzyMatchers.matchRegex("(.*)(GameProfile)", 1)) + .build(); + return reflection.getField(contract).getType(); + } } /** diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedBlockData.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedBlockData.java index d72f05d7..768642cd 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedBlockData.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedBlockData.java @@ -1,5 +1,18 @@ /** - * (c) 2015 dmulloy2 + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2015 dmulloy2 + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA */ package com.comphenix.protocol.wrappers; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java index 43e2c169..4c61e71f 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java @@ -31,24 +31,29 @@ import java.util.Set; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.logging.Level; import javax.annotation.Nullable; import org.bukkit.entity.Entity; import org.bukkit.inventory.ItemStack; +import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.injector.BukkitUnwrapper; import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; import com.comphenix.protocol.reflect.accessors.FieldAccessor; import com.comphenix.protocol.reflect.accessors.ReadOnlyFieldAccessor; import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.wrappers.collection.ConvertedMap; import com.google.common.base.Function; import com.google.common.base.Objects; +import com.google.common.base.Preconditions; import com.google.common.collect.Iterators; +import com.google.common.collect.Maps; /** * Wraps a DataWatcher that is used to transmit arbitrary key-value pairs with a given entity. @@ -56,127 +61,127 @@ import com.google.common.collect.Iterators; * @author Kristian */ public class WrappedDataWatcher extends AbstractWrapper implements Iterable { -// /** -// * Every custom watchable type in Spigot #1628 and above. -// * @author Kristian -// */ -// public enum CustomType { -// BYTE_SHORT("org.spigotmc.ProtocolData$ByteShort", 0, short.class), -// DUAL_BYTE("org.spigotmc.ProtocolData$DualByte", 0, byte.class, byte.class), -// HIDDEN_BYTE("org.spigotmc.ProtocolData$HiddenByte", 0, byte.class), -// INT_BYTE("org.spigotmc.ProtocolData$IntByte", 2, int.class, byte.class), -// DUAL_INT("org.spigotmc.ProtocolData$DualInt", 2, int.class, int.class); -// -// private Class spigotClass; -// private ConstructorAccessor constructor; -// private FieldAccessor secondaryValue; -// private int typeId; -// -// private CustomType(String className, int typeId, Class... parameters) { -// try { -// this.spigotClass = Class.forName(className); -// this.constructor = Accessors.getConstructorAccessor(spigotClass, parameters); -// this.secondaryValue = parameters.length > 1 ? Accessors.getFieldAccessor(spigotClass, "value2", true) : null; -// -// } catch (ClassNotFoundException e) { -// ProtocolLibrary.log(Level.WARNING, "Unable to find " + className); -// this.spigotClass = null; -// } -// this.typeId = typeId; -// } -// -// /** -// * Construct a new instance of this Spigot type. -// * @param value - the value. Cannot be NULL. -// * @return The instance to construct. -// */ -// Object newInstance(Object value) { -// return newInstance(value, null); -// } -// -// /** -// * Construct a new instance of this Spigot type. -// *

-// * The secondary value may be NULL if this custom type does not contain a secondary value. -// * @param value - the value. -// * @param secondary - optional secondary value. -// * @return -// */ -// Object newInstance(Object value, Object secondary) { -// Preconditions.checkNotNull(value, "value cannot be NULL."); -// -// if (hasSecondary()) { -// return constructor.invoke(value, secondary); -// } else { -// if (secondary != null) { -// throw new IllegalArgumentException("Cannot construct " + this + " with a secondary value"); -// } -// return constructor.invoke(value); -// } -// } -// -// /** -// * Set the secondary value of a given type. -// * @param instance - the instance. -// * @param secondary - the secondary value. -// */ -// void setSecondary(Object instance, Object secondary) { -// if (!hasSecondary()) { -// throw new IllegalArgumentException(this + " does not have a secondary value."); -// } -// secondaryValue.set(instance, secondary); -// } -// -// /** -// * Get the secondary value of a type. -// * @param instance - the instance. -// * @return The secondary value. -// */ -// Object getSecondary(Object instance) { -// if (!hasSecondary()) { -// throw new IllegalArgumentException(this + " does not have a secondary value."); -// } -// return secondaryValue.get(instance); -// } -// -// /** -// * Determine if this type has a secondary value. -// * @return TRUE if it does, FALSE otherwise. -// */ -// public boolean hasSecondary() { -// return secondaryValue != null; -// } -// -// /** -// * Underlying Spigot class. -// * @return The class. -// */ -// public Class getSpigotClass() { -// return spigotClass; -// } -// -// /** -// * The equivalent type ID. -// * @return The equivalent ID. -// */ -// public int getTypeId() { -// return typeId; -// } -// -// /** -// * Retrieve the custom Spigot type of a value. -// * @param value - the value. -// * @return The Spigot type, or NULL if not found. -// */ -// public static CustomType fromValue(Object value) { -// for (CustomType type : CustomType.values()) { -// if (type.getSpigotClass().isInstance(value)) { -// return type; -// } -// } -// return null; -// } -// } + /** + * Every custom watchable type in Spigot #1628 and above. + * @author Kristian + */ + public enum CustomType { + BYTE_SHORT("org.spigotmc.ProtocolData$ByteShort", 0, short.class), + DUAL_BYTE("org.spigotmc.ProtocolData$DualByte", 0, byte.class, byte.class), + HIDDEN_BYTE("org.spigotmc.ProtocolData$HiddenByte", 0, byte.class), + INT_BYTE("org.spigotmc.ProtocolData$IntByte", 2, int.class, byte.class), + DUAL_INT("org.spigotmc.ProtocolData$DualInt", 2, int.class, int.class); + + private Class spigotClass; + private ConstructorAccessor constructor; + private FieldAccessor secondaryValue; + private int typeId; + + private CustomType(String className, int typeId, Class... parameters) { + try { + this.spigotClass = Class.forName(className); + this.constructor = Accessors.getConstructorAccessor(spigotClass, parameters); + this.secondaryValue = parameters.length > 1 ? Accessors.getFieldAccessor(spigotClass, "value2", true) : null; + + } catch (ClassNotFoundException e) { + ProtocolLibrary.log(Level.WARNING, "Unable to find " + className); + this.spigotClass = null; + } + this.typeId = typeId; + } + + /** + * Construct a new instance of this Spigot type. + * @param value - the value. Cannot be NULL. + * @return The instance to construct. + */ + Object newInstance(Object value) { + return newInstance(value, null); + } + + /** + * Construct a new instance of this Spigot type. + *

+ * The secondary value may be NULL if this custom type does not contain a secondary value. + * @param value - the value. + * @param secondary - optional secondary value. + * @return + */ + Object newInstance(Object value, Object secondary) { + Preconditions.checkNotNull(value, "value cannot be NULL."); + + if (hasSecondary()) { + return constructor.invoke(value, secondary); + } else { + if (secondary != null) { + throw new IllegalArgumentException("Cannot construct " + this + " with a secondary value"); + } + return constructor.invoke(value); + } + } + + /** + * Set the secondary value of a given type. + * @param instance - the instance. + * @param secondary - the secondary value. + */ + void setSecondary(Object instance, Object secondary) { + if (!hasSecondary()) { + throw new IllegalArgumentException(this + " does not have a secondary value."); + } + secondaryValue.set(instance, secondary); + } + + /** + * Get the secondary value of a type. + * @param instance - the instance. + * @return The secondary value. + */ + Object getSecondary(Object instance) { + if (!hasSecondary()) { + throw new IllegalArgumentException(this + " does not have a secondary value."); + } + return secondaryValue.get(instance); + } + + /** + * Determine if this type has a secondary value. + * @return TRUE if it does, FALSE otherwise. + */ + public boolean hasSecondary() { + return secondaryValue != null; + } + + /** + * Underlying Spigot class. + * @return The class. + */ + public Class getSpigotClass() { + return spigotClass; + } + + /** + * The equivalent type ID. + * @return The equivalent ID. + */ + public int getTypeId() { + return typeId; + } + + /** + * Retrieve the custom Spigot type of a value. + * @param value - the value. + * @return The Spigot type, or NULL if not found. + */ + public static CustomType fromValue(Object value) { + for (CustomType type : CustomType.values()) { + if (type.getSpigotClass().isInstance(value)) { + return type; + } + } + return null; + } + } /** * Used to assign integer IDs to given types. @@ -601,20 +606,20 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable, Integer>) TYPE_MAP_ACCESSOR.get(null); @@ -737,17 +742,17 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable, Integer> initializeCustom() { -// Map, Integer> map = Maps.newHashMap(); -// -// for (CustomType type : CustomType.values()) { -// if (type.getSpigotClass() != null) { -// map.put(type.getSpigotClass(), type.getTypeId()); -// } -// } -// return map; -// } + // For Spigot's bountiful update patch + private static Map, Integer> initializeCustom() { + Map, Integer> map = Maps.newHashMap(); + + for (CustomType type : CustomType.values()) { + if (type.getSpigotClass() != null) { + map.put(type.getSpigotClass(), type.getTypeId()); + } + } + return map; + } // TODO: Remove, as this was fixed in build #1189 of Spigot private static void initializeSpigot(FuzzyReflection fuzzy) { diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedGameProfile.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedGameProfile.java index 271ea372..b4bc044d 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedGameProfile.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedGameProfile.java @@ -14,13 +14,12 @@ import com.comphenix.protocol.injector.BukkitUnwrapper; import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; import com.comphenix.protocol.reflect.accessors.FieldAccessor; +import com.comphenix.protocol.reflect.accessors.MethodAccessor; import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.wrappers.collection.ConvertedMultimap; import com.google.common.base.Charsets; import com.google.common.base.Objects; import com.google.common.collect.Multimap; -import com.mojang.authlib.GameProfile; -import com.mojang.authlib.properties.Property; /** * Represents a wrapper for a game profile. @@ -28,62 +27,79 @@ import com.mojang.authlib.properties.Property; */ public class WrappedGameProfile extends AbstractWrapper { public static final ReportType REPORT_INVALID_UUID = new ReportType("Plugin %s created a profile with '%s' as an UUID."); - - // Version 1.7.2 and 1.7.8 respectively - private static final ConstructorAccessor CREATE_STRING_STRING = Accessors.getConstructorAccessorOrNull(GameProfile.class, String.class, String.class); - private static final FieldAccessor GET_UUID_STRING = Accessors.getFieldAcccessorOrNull(GameProfile.class, "id", String.class); - + + private static final Class GAME_PROFILE = MinecraftReflection.getGameProfileClass(); + + private static final ConstructorAccessor CREATE_STRING_STRING = Accessors.getConstructorAccessorOrNull( + GAME_PROFILE, String.class, String.class); + private static final ConstructorAccessor CREATE_UUID_STRING = Accessors.getConstructorAccessorOrNull( + GAME_PROFILE, UUID.class, String.class); + + private static final FieldAccessor GET_UUID_STRING = Accessors.getFieldAcccessorOrNull( + GAME_PROFILE, "id", String.class); + + private static final MethodAccessor GET_ID = Accessors.getMethodAcccessorOrNull( + GAME_PROFILE, "getId"); + private static final MethodAccessor GET_NAME = Accessors.getMethodAcccessorOrNull( + GAME_PROFILE, "getName"); + private static final MethodAccessor GET_PROPERTIES = Accessors.getMethodAcccessorOrNull( + GAME_PROFILE, "getProperties"); + private static final MethodAccessor IS_COMPLETE = Accessors.getMethodAcccessorOrNull( + GAME_PROFILE, "isComplete"); + // Fetching game profile private static FieldAccessor PLAYER_PROFILE; private static FieldAccessor OFFLINE_PROFILE; - + // Property map private Multimap propertyMap; - + // Parsed UUID private volatile UUID parsedUUID; - + // Profile from a handle private WrappedGameProfile(Object profile) { - super(GameProfile.class); + super(GAME_PROFILE); setHandle(profile); } - + /** * Retrieve the associated game profile of a player. *

* Note that this may not exist in the current Minecraft version. + * * @param player - the player. * @return The game profile. */ public static WrappedGameProfile fromPlayer(Player player) { FieldAccessor accessor = PLAYER_PROFILE; - Object nmsPlayer = BukkitUnwrapper.getInstance().unwrapItem(player); - if (accessor == null) { - accessor = Accessors.getFieldAccessor(MinecraftReflection.getEntityHumanClass(), GameProfile.class, true); + accessor = Accessors.getFieldAccessor(MinecraftReflection.getEntityHumanClass(), GAME_PROFILE, true); PLAYER_PROFILE = accessor; } + + Object nmsPlayer = BukkitUnwrapper.getInstance().unwrapItem(player); return WrappedGameProfile.fromHandle(PLAYER_PROFILE.get(nmsPlayer)); } - + /** * Retrieve the associated game profile of an offline player. *

* Note that this may not exist in the current Minecraft version. + * * @param player - the offline player. * @return The game profile. */ public static WrappedGameProfile fromOfflinePlayer(OfflinePlayer player) { FieldAccessor accessor = OFFLINE_PROFILE; - if (accessor == null) { - accessor = Accessors.getFieldAccessor(player.getClass(), GameProfile.class, true); + accessor = Accessors.getFieldAccessor(player.getClass(), GAME_PROFILE, true); OFFLINE_PROFILE = accessor; } + return WrappedGameProfile.fromHandle(OFFLINE_PROFILE.get(player)); } - + /** * Construct a new game profile with the given properties. *

@@ -91,49 +107,58 @@ public class WrappedGameProfile extends AbstractWrapper { * IDs that cannot be parsed as an UUID will be hashed and form a version 3 UUID instead. *

* This method is deprecated for Minecraft 1.7.8 and above. + * * @param id - the UUID of the player. * @param name - the name of the player. */ @Deprecated public WrappedGameProfile(String id, String name) { - super(GameProfile.class); - + super(GAME_PROFILE); + if (CREATE_STRING_STRING != null) { setHandle(CREATE_STRING_STRING.invoke(id, name)); + } else if (CREATE_UUID_STRING != null) { + setHandle(CREATE_UUID_STRING.invoke(parseUUID(id), name)); } else { - setHandle(new GameProfile(parseUUID(id), name)); + throw new IllegalArgumentException("Unsupported GameProfile constructor."); } } - + /** * Construct a new game profile with the given properties. *

* Note that at least one of the parameters must be non-null. + * * @param uuid - the UUID of the player, or NULL. * @param name - the name of the player, or NULL. */ public WrappedGameProfile(UUID uuid, String name) { - super(GameProfile.class); - + super(GAME_PROFILE); + if (CREATE_STRING_STRING != null) { setHandle(CREATE_STRING_STRING.invoke(uuid != null ? uuid.toString() : null, name)); + } else if (CREATE_UUID_STRING != null) { + setHandle(CREATE_UUID_STRING.invoke(uuid, name)); } else { - setHandle(new GameProfile(uuid, name)); + throw new IllegalArgumentException("Unsupported GameProfile constructor."); } } - + /** * Construct a wrapper around an existing game profile. + * * @param profile - the underlying profile, or NULL. */ public static WrappedGameProfile fromHandle(Object handle) { if (handle == null) return null; + return new WrappedGameProfile(handle); } - + /** * Parse an UUID using very lax rules, as specified in {@link #WrappedGameProfile(UUID, String)}. + * * @param id - text. * @return The corresponding UUID. * @throws IllegalArgumentException If we cannot parse the text. @@ -143,13 +168,10 @@ public class WrappedGameProfile extends AbstractWrapper { return id != null ? UUID.fromString(id) : null; } catch (IllegalArgumentException e) { // Warn once every hour (per plugin) - ProtocolLibrary.getErrorReporter().reportWarning( - WrappedGameProfile.class, - Report.newBuilder(REPORT_INVALID_UUID). - rateLimit(1, TimeUnit.HOURS). - messageParam(PluginContext.getPluginCaller(new Exception()), id) - ); - + ProtocolLibrary.getErrorReporter() + .reportWarning(WrappedGameProfile.class, Report.newBuilder(REPORT_INVALID_UUID) + .rateLimit(1, TimeUnit.HOURS) + .messageParam(PluginContext.getPluginCaller(new Exception()), id)); return UUID.nameUUIDFromBytes(id.getBytes(Charsets.UTF_8)); } } @@ -157,70 +179,86 @@ public class WrappedGameProfile extends AbstractWrapper { /** * Retrieve the UUID of the player. *

- * Note that Minecraft 1.7.5 and earlier doesn't use UUIDs internally, and it may not be possible - * to convert the string to an UUID. + * Note that Minecraft 1.7.5 and earlier doesn't use UUIDs internally, and it may not be possible to convert the string to an UUID. *

* We use the same lax conversion as in {@link #WrappedGameProfile(String, String)}. + * * @return The UUID, or NULL if the UUID is NULL. * @throws IllegalStateException If we cannot parse the internal ID as an UUID. */ public UUID getUUID() { UUID uuid = parsedUUID; - + if (uuid == null) { try { if (GET_UUID_STRING != null) { uuid = parseUUID(getId()); + } else if (GET_ID != null) { + uuid = (UUID) GET_ID.invoke(handle); } else { - uuid = getProfile().getId(); + throw new IllegalStateException("Unsupported getId() method"); } + // Cache for later parsedUUID = uuid; } catch (IllegalArgumentException e) { - throw new IllegalStateException("Cannot parse ID " + getId() + " as an UUID in player profile " + getName()); + throw new IllegalStateException("Cannot parse ID " + getId() + " as an UUID in player profile " + getName(), e); } } + return uuid; } - + /** * Retrieve the textual representation of the player's UUID. *

* Note that there's nothing stopping plugins from creating non-standard UUIDs. *

* In Minecraft 1.7.8 and later, this simply returns the string form of {@link #getUUID()}. + * * @return The UUID of the player, or NULL if not computed. */ public String getId() { - if (GET_UUID_STRING != null) + if (GET_UUID_STRING != null) { return (String) GET_UUID_STRING.get(handle); - final GameProfile profile = getProfile(); - return profile.getId() != null ? profile.getId().toString() : null; + } else if (GET_ID != null) { + UUID uuid = (UUID) GET_ID.invoke(handle); + return uuid != null ? uuid.toString() : null; + } else { + throw new IllegalStateException("Unsupported getId() method"); + } } /** * Retrieve the name of the player. + * * @return The player name. */ public String getName() { - return getProfile().getName(); + if (GET_NAME != null) { + return (String) GET_NAME.invoke(handle); + } else { + throw new IllegalStateException("Unsupported getName() method"); + } } - + /** * Retrieve the property map of signed values. + * * @return Property map. */ + @SuppressWarnings({ "unchecked", "rawtypes" }) public Multimap getProperties() { Multimap result = propertyMap; if (result == null) { - result = new ConvertedMultimap( - GuavaWrappers.getBukkitMultimap(getProfile().getProperties())) { + Multimap properties = (Multimap) GET_PROPERTIES.invoke(handle); + result = new ConvertedMultimap(GuavaWrappers.getBukkitMultimap(properties)) { @Override - protected Property toInner(WrappedSignedProperty outer) { - return (Property) outer.handle; + protected Object toInner(WrappedSignedProperty outer) { + return outer.handle; } - + @Override protected Object toInnerObject(Object outer) { if (outer instanceof WrappedSignedProperty) { @@ -228,9 +266,9 @@ public class WrappedGameProfile extends AbstractWrapper { } return outer; } - + @Override - protected WrappedSignedProperty toOuter(Property inner) { + protected WrappedSignedProperty toOuter(Object inner) { return WrappedSignedProperty.fromHandle(inner); } }; @@ -238,61 +276,57 @@ public class WrappedGameProfile extends AbstractWrapper { } return result; } - - /** - * Retrieve the underlying GameProfile. - * @return The GameProfile. - */ - private GameProfile getProfile() { - return (GameProfile) handle; - } /** * Construct a new game profile with the same ID, but different name. + * * @param name - the new name of the profile to create. * @return The new game profile. */ public WrappedGameProfile withName(String name) { return new WrappedGameProfile(getId(), name); } - + /** * Construct a new game profile with the same name, but different id. + * * @param id - the new id of the profile to create. * @return The new game profile. */ public WrappedGameProfile withId(String id) { return new WrappedGameProfile(id, getName()); } - + /** * Determine if the game profile contains both an UUID and a name. + * * @return TRUE if it does, FALSE otherwise. */ public boolean isComplete() { - return getProfile().isComplete(); + return (Boolean) IS_COMPLETE.invoke(handle); } - + @Override public String toString() { - return String.valueOf(getProfile()); + return String.valueOf(getHandle()); } - + @Override public int hashCode() { // Mojang's hashCode() is broken, it doesn't handle NULL id or name. So we implement our own return Objects.hashCode(getId(), getName()); } - + @Override public boolean equals(Object obj) { if (obj == this) return true; - + if (obj instanceof WrappedGameProfile) { WrappedGameProfile other = (WrappedGameProfile) obj; - return Objects.equal(getProfile(), other.getProfile()); + return Objects.equal(getHandle(), other.getHandle()); } + return false; } -} +} \ No newline at end of file