From 9df0dd48e486491493c1411da8f594ceeda588a8 Mon Sep 17 00:00:00 2001 From: Dan Mulloy Date: Fri, 3 Aug 2018 22:07:25 -0400 Subject: [PATCH] Fix backwards compat with nullable classes Fixes #499 --- .../protocol/utility/CachedPackage.java | 15 +- .../protocol/utility/MinecraftReflection.java | 4320 ++++++++--------- .../utility/MinecraftReflectionTest.java | 8 +- 3 files changed, 2171 insertions(+), 2172 deletions(-) diff --git a/modules/API/src/main/java/com/comphenix/protocol/utility/CachedPackage.java b/modules/API/src/main/java/com/comphenix/protocol/utility/CachedPackage.java index 6d0bd7ba..3f36acbb 100644 --- a/modules/API/src/main/java/com/comphenix/protocol/utility/CachedPackage.java +++ b/modules/API/src/main/java/com/comphenix/protocol/utility/CachedPackage.java @@ -63,21 +63,16 @@ class CachedPackage { * @return Class object. * @throws RuntimeException If we are unable to find the given class. */ - public Optional> getPackageClass(String className) { + public Optional> getPackageClass(final String className) { Preconditions.checkNotNull(className, "className cannot be null!"); - Optional> result = cache.get(className); - if (result == null) { + return cache.computeIfAbsent(className, x -> { try { - Class clazz = source.loadClass(combine(packageName, className)); - result = Optional.ofNullable(clazz); - cache.put(className, result); + return Optional.ofNullable(source.loadClass(combine(packageName, className))); } catch (ClassNotFoundException ex) { - cache.put(className, Optional.empty()); + return Optional.empty(); } - } - - return result; + }); } /** diff --git a/modules/API/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java b/modules/API/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java index d83729b9..3ee2308e 100644 --- a/modules/API/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java +++ b/modules/API/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java @@ -1,2160 +1,2160 @@ -/* - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2012 Kristian S. Stangeland - * - * 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.utility; - -import java.io.DataInputStream; -import java.io.DataOutput; -import java.lang.reflect.Array; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.net.InetAddress; -import java.net.ServerSocket; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentMap; -import java.util.logging.Level; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.annotation.Nonnull; - -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.Server; -import org.bukkit.inventory.ItemStack; - -import com.comphenix.protocol.PacketType; -import com.comphenix.protocol.ProtocolLibrary; -import com.comphenix.protocol.ProtocolLogger; -import com.comphenix.protocol.error.ErrorReporter; -import com.comphenix.protocol.error.Report; -import com.comphenix.protocol.error.ReportType; -import com.comphenix.protocol.injector.BukkitUnwrapper; -import com.comphenix.protocol.injector.packet.PacketRegistry; -import com.comphenix.protocol.reflect.ClassAnalyser; -import com.comphenix.protocol.reflect.ClassAnalyser.AsmMethod; -import com.comphenix.protocol.reflect.FuzzyReflection; -import com.comphenix.protocol.reflect.accessors.Accessors; -import com.comphenix.protocol.reflect.accessors.MethodAccessor; -import com.comphenix.protocol.reflect.fuzzy.AbstractFuzzyMatcher; -import com.comphenix.protocol.reflect.fuzzy.FuzzyClassContract; -import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract; -import com.comphenix.protocol.reflect.fuzzy.FuzzyMatchers; -import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; -import com.comphenix.protocol.utility.RemappedClassSource.RemapperUnavailableException; -import com.comphenix.protocol.utility.RemappedClassSource.RemapperUnavailableException.Reason; -import com.comphenix.protocol.wrappers.nbt.NbtFactory; -import com.comphenix.protocol.wrappers.nbt.NbtType; -import com.google.common.base.Joiner; -import com.google.common.collect.Maps; - -/** - * Methods and constants specifically used in conjuction with reflecting Minecraft object. - * - * @author Kristian - */ -public class MinecraftReflection { - public static final ReportType REPORT_CANNOT_FIND_MCPC_REMAPPER = new ReportType("Cannot find MCPC/Cauldron remapper."); - public static final ReportType REPORT_CANNOT_LOAD_CPC_REMAPPER = new ReportType("Unable to load MCPC/Cauldron remapper."); - public static final ReportType REPORT_NON_CRAFTBUKKIT_LIBRARY_PACKAGE = new ReportType("Cannot find standard Minecraft library location. Assuming MCPC/Cauldron."); - - /** - * Regular expression that matches a canonical Java class. - */ - private static final String CANONICAL_REGEX = "(\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*\\.)+\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"; - - /** - * Regular expression that matches a Minecraft object. - *

- * Replaced by the method {@link #getMinecraftObjectRegex()}. - */ - @Deprecated - public static final String MINECRAFT_OBJECT = "net\\.minecraft\\." + CANONICAL_REGEX; - - /** - * Regular expression computed dynamically. - */ - private static String DYNAMIC_PACKAGE_MATCHER = null; - - /** - * The Entity package in Forge 1.5.2 - */ - private static final String FORGE_ENTITY_PACKAGE = "net.minecraft.entity"; - - /** - * The package name of all the classes that belongs to the native code in Minecraft. - */ - private static String MINECRAFT_PREFIX_PACKAGE = "net.minecraft.server"; - - /** - * Represents a regular expression that will match the version string in a package: - * org.bukkit.craftbukkit.v1_6_R2 -> v1_6_R2 - */ - private static final Pattern PACKAGE_VERSION_MATCHER = Pattern.compile(".*\\.(v\\d+_\\d+_\\w*\\d+)"); - - private static String MINECRAFT_FULL_PACKAGE = null; - private static String CRAFTBUKKIT_PACKAGE = null; - - // Package private for the purpose of unit testing - static CachedPackage minecraftPackage; - static CachedPackage craftbukkitPackage; - static CachedPackage libraryPackage; - - // Matches classes - private static AbstractFuzzyMatcher> fuzzyMatcher; - - // The NMS version - private static String packageVersion; - - // Item stacks - /* private static Method craftNMSMethod; - private static Method craftBukkitNMS; - private static Method craftBukkitOBC; - private static boolean craftItemStackFailed; - - private static Constructor craftNMSConstructor; - private static Constructor craftBukkitConstructor; */ - - // net.minecraft.server - private static Class itemStackArrayClass; - - // Cache of getBukkitEntity - private static ConcurrentMap, MethodAccessor> getBukkitEntityCache = Maps.newConcurrentMap(); - - // The current class source - private static ClassSource classSource; - - /** - * Whether or not we're currently initializing the reflection handler. - */ - private static boolean initializing; - - // Whether or not we are using netty - private static Boolean cachedNetty; - private static Boolean cachedWatcherObject; - - private MinecraftReflection() { - // No need to make this constructable. - } - - /** - * Retrieve a regular expression that can match Minecraft package objects. - * @return Minecraft package matcher. - */ - public static String getMinecraftObjectRegex() { - if (DYNAMIC_PACKAGE_MATCHER == null) - getMinecraftPackage(); - return DYNAMIC_PACKAGE_MATCHER; - } - - /** - * Retrieve a abstract fuzzy class matcher for Minecraft objects. - * @return A matcher for Minecraft objects. - */ - public static AbstractFuzzyMatcher> getMinecraftObjectMatcher() { - if (fuzzyMatcher == null) - fuzzyMatcher = FuzzyMatchers.matchRegex(getMinecraftObjectRegex(), 50); - return fuzzyMatcher; - } - - /** - * Retrieve the name of the Minecraft server package. - * @return Full canonical name of the Minecraft server package. - */ - public static String getMinecraftPackage() { - // Speed things up - if (MINECRAFT_FULL_PACKAGE != null) - return MINECRAFT_FULL_PACKAGE; - if (initializing) - throw new IllegalStateException("Already initializing minecraft package!"); - initializing = true; - - Server craftServer = Bukkit.getServer(); - - // This server should have a "getHandle" method that we can use - if (craftServer != null) { - try { - // The return type will tell us the full package, regardless of formating - Class craftClass = craftServer.getClass(); - CRAFTBUKKIT_PACKAGE = getPackage(craftClass.getCanonicalName()); - - // Parse the package version - Matcher packageMatcher = PACKAGE_VERSION_MATCHER.matcher(CRAFTBUKKIT_PACKAGE); - if (packageMatcher.matches()) { - packageVersion = packageMatcher.group(1); - } else { - MinecraftVersion version = new MinecraftVersion(craftServer); - - // See if we need a package version - if (MinecraftVersion.SCARY_UPDATE.compareTo(version) <= 0) { - // Just assume R1 - it's probably fine - packageVersion = "v" + version.getMajor() + "_" + version.getMinor() + "_R1"; - ProtocolLogger.log(Level.WARNING, "Assuming package version: " + packageVersion); - } - } - - // Libigot patch - handleLibigot(); - - // Next, do the same for CraftEntity.getHandle() in order to get the correct Minecraft package - Class craftEntity = getCraftEntityClass(); - Method getHandle = craftEntity.getMethod("getHandle"); - - MINECRAFT_FULL_PACKAGE = getPackage(getHandle.getReturnType().getCanonicalName()); - - // Pretty important invariantt - if (!MINECRAFT_FULL_PACKAGE.startsWith(MINECRAFT_PREFIX_PACKAGE)) { - // See if we got the Forge entity package - if (MINECRAFT_FULL_PACKAGE.equals(FORGE_ENTITY_PACKAGE)) { - // USe the standard NMS versioned package - MINECRAFT_FULL_PACKAGE = CachedPackage.combine(MINECRAFT_PREFIX_PACKAGE, packageVersion); - } else { - // Assume they're the same instead - MINECRAFT_PREFIX_PACKAGE = MINECRAFT_FULL_PACKAGE; - } - - // The package is usualy flat, so go with that assumption - String matcher = - (MINECRAFT_PREFIX_PACKAGE.length() > 0 ? - Pattern.quote(MINECRAFT_PREFIX_PACKAGE + ".") : "") + CANONICAL_REGEX; - - // We'll still accept the default location, however - setDynamicPackageMatcher("(" + matcher + ")|(" + MINECRAFT_OBJECT + ")"); - - } else { - // Use the standard matcher - setDynamicPackageMatcher(MINECRAFT_OBJECT); - } - - return MINECRAFT_FULL_PACKAGE; - - } catch (SecurityException e) { - throw new RuntimeException("Security violation. Cannot get handle method.", e); - } catch (NoSuchMethodException e) { - throw new IllegalStateException("Cannot find getHandle() method on server. Is this a modified CraftBukkit version?", e); - } finally { - initializing = false; - } - - } else { - initializing = false; - throw new IllegalStateException("Could not find Bukkit. Is it running?"); - } - } - - /** - * Retrieve the package version of the underlying CraftBukkit server. - * @return The package version, or NULL if not applicable (before 1.4.6). - */ - public static String getPackageVersion() { - getMinecraftPackage(); - return packageVersion; - } - - /** - * Update the dynamic package matcher. - * @param regex - the Minecraft package regex. - */ - private static void setDynamicPackageMatcher(String regex) { - DYNAMIC_PACKAGE_MATCHER = regex; - - // Ensure that the matcher is regenerated - fuzzyMatcher = null; - } - - // Patch for Libigot - private static void handleLibigot() { - try { - getCraftEntityClass(); - } catch (RuntimeException e) { - // Try reverting the package to the old format - craftbukkitPackage = null; - CRAFTBUKKIT_PACKAGE = "org.bukkit.craftbukkit"; - - // This might fail too - getCraftEntityClass(); - } - } - - /** - * Used during debugging and testing. - * @param minecraftPackage - the current Minecraft package. - * @param craftBukkitPackage - the current CraftBukkit package. - */ - public static void setMinecraftPackage(String minecraftPackage, String craftBukkitPackage) { - MINECRAFT_FULL_PACKAGE = minecraftPackage; - CRAFTBUKKIT_PACKAGE = craftBukkitPackage; - - // Make sure it exists - if (getMinecraftServerClass() == null) { - throw new IllegalArgumentException("Cannot find MinecraftServer for package " + minecraftPackage); - } - - // Standard matcher - setDynamicPackageMatcher(MINECRAFT_OBJECT); - } - - /** - * Retrieve the name of the root CraftBukkit package. - * @return Full canonical name of the root CraftBukkit package. - */ - public static String getCraftBukkitPackage() { - // Ensure it has been initialized - if (CRAFTBUKKIT_PACKAGE == null) - getMinecraftPackage(); - return CRAFTBUKKIT_PACKAGE; - } - - /** - * Retrieve the package name from a given canonical Java class name. - * @param fullName - full Java class name. - * @return The package name. - */ - private static String getPackage(String fullName) { - int index = fullName.lastIndexOf("."); - - if (index > 0) - return fullName.substring(0, index); - else - return ""; // Default package - } - - /** - * Dynamically retrieve the Bukkit entity from a given entity. - * @param nmsObject - the NMS entity. - * @return A bukkit entity. - * @throws RuntimeException If we were unable to retrieve the Bukkit entity. - */ - public static Object getBukkitEntity(Object nmsObject) { - if (nmsObject == null) - return null; - - // We will have to do this dynamically, unfortunately - try { - Class clazz = nmsObject.getClass(); - MethodAccessor accessor = getBukkitEntityCache.get(clazz); - - if (accessor == null) { - MethodAccessor created = Accessors.getMethodAccessor(clazz, "getBukkitEntity"); - accessor = getBukkitEntityCache.putIfAbsent(clazz, created); - - // We won the race - if (accessor == null) { - accessor = created; - } - } - return accessor.invoke(nmsObject); - } catch (Exception e) { - throw new IllegalArgumentException("Cannot get Bukkit entity from " + nmsObject, e); - } - } - - /** - * Determine if a given object can be found within the package net.minecraft.server. - * @param obj - the object to test. - * @return TRUE if it can, FALSE otherwise. - */ - public static boolean isMinecraftObject(@Nonnull Object obj) { - if (obj == null) - return false; - - // Doesn't matter if we don't check for the version here - return obj.getClass().getName().startsWith(MINECRAFT_PREFIX_PACKAGE); - } - - /** - * Determine if the given class is found within the package net.minecraft.server, or any equivalent package. - * @param clazz - the class to test. - * @return TRUE if it can, FALSE otherwise. - */ - public static boolean isMinecraftClass(@Nonnull Class clazz) { - if (clazz == null) - throw new IllegalArgumentException("clazz cannot be NULL."); - - return getMinecraftObjectMatcher().isMatch(clazz, null); - } - - /** - * Determine if a given object is found in net.minecraft.server, and has the given name. - * @param obj - the object to test. - * @param className - the class name to test. - * @return TRUE if it can, FALSE otherwise. - */ - public static boolean isMinecraftObject(@Nonnull Object obj, String className) { - if (obj == null) - return false; - - String javaName = obj.getClass().getName(); - return javaName.startsWith(MINECRAFT_PREFIX_PACKAGE) && javaName.endsWith(className); - } - - /** - * Determine if a given Object is compatible with a given Class. That is, - * whether or not the Object is an instance of that Class or one of its - * subclasses. If either is null, false is returned. - * - * @param clazz Class to test for, may be null - * @param object the Object to test, may be null - * @return True if it is, false if not - * @see Class#isAssignableFrom(Class) - */ - public static boolean is(Class clazz, Object object) { - if (clazz == null || object == null) { - return false; - } - - return clazz.isAssignableFrom(object.getClass()); - } - - /** - * Equivalent to {@link #is(Class, Object)} but we don't call getClass again - */ - public static boolean is(Class clazz, Class test) { - if (clazz == null || test == null) { - return false; - } - - return clazz.isAssignableFrom(test); - } - - /** - * Determine if a given object is a ChunkPosition. - * @param obj - the object to test. - * @return TRUE if it can, FALSE otherwise. - */ - public static boolean isChunkPosition(Object obj) { - return is(getChunkPositionClass(), obj); - } - - /** - * Determine if a given object is a BlockPosition. - * @param obj - the object to test. - * @return TRUE if it can, FALSE otherwise. - */ - public static boolean isBlockPosition(Object obj) { - return is(getBlockPositionClass(), obj); - } - - /** - * Determine if the given object is an NMS ChunkCoordIntPar. - * @param obj - the object. - * @return TRUE if it can, FALSE otherwise. - */ - public static boolean isChunkCoordIntPair(Object obj) { - return is(getChunkCoordIntPair(), obj); - } - - /** - * Determine if a given object is a ChunkCoordinate. - * @param obj - the object to test. - * @return TRUE if it can, FALSE otherwise. - */ - public static boolean isChunkCoordinates(Object obj) { - return is(getChunkCoordinatesClass(), obj); - } - - /** - * Determine if the given object is actually a Minecraft packet. - * @param obj - the given object. - * @return TRUE if it is, FALSE otherwise. - */ - public static boolean isPacketClass(Object obj) { - return is(getPacketClass(), obj); - } - - /** - * Determine if the given object is a NetLoginHandler (PendingConnection) - * @param obj - the given object. - * @return TRUE if it is, FALSE otherwise. - */ - public static boolean isLoginHandler(Object obj) { - return is(getNetLoginHandlerClass(), obj); - } - - /** - * Determine if the given object is assignable to a NetServerHandler (PlayerConnection) - * @param obj - the given object. - * @return TRUE if it is, FALSE otherwise. - */ - public static boolean isServerHandler(Object obj) { - return is(getPlayerConnectionClass(), obj); - } - - /** - * Determine if the given object is actually a Minecraft packet. - * @param obj - the given object. - * @return TRUE if it is, FALSE otherwise. - */ - public static boolean isMinecraftEntity(Object obj) { - return is(getEntityClass(), obj); - } - - /** - * Determine if the given object is a NMS ItemStack. - * @param value - the given object. - * @return TRUE if it is, FALSE otherwise. - */ - public static boolean isItemStack(Object value) { - return is(getItemStackClass(), value); - } - - /** - * Determine if the given object is a CraftPlayer class. - * @param value - the given object. - * @return TRUE if it is, FALSE otherwise. - */ - public static boolean isCraftPlayer(Object value) { - return is(getCraftPlayerClass(), value); - } - - /** - * Determine if the given object is a Minecraft player entity. - * @param obj - the given object. - * @return TRUE if it is, FALSE otherwise. - */ - public static boolean isMinecraftPlayer(Object obj) { - return is(getEntityPlayerClass(), obj); - } - - /** - * Determine if the given object is a watchable object. - * @param obj - the given object. - * @return TRUE if it is, FALSE otherwise. - */ - public static boolean isWatchableObject(Object obj) { - return is(getWatchableObjectClass(), obj); - } - - /** - * Determine if the given object is a data watcher object. - * @param obj - the given object. - * @return TRUE if it is, FALSE otherwise. - */ - public static boolean isDataWatcher(Object obj) { - return is(getDataWatcherClass(), obj); - } - - /** - * Determine if the given object is an IntHashMap object. - * @param obj - the given object. - * @return TRUE if it is, FALSE otherwise. - */ - public static boolean isIntHashMap(Object obj) { - return is(getIntHashMapClass(), obj); - } - - /** - * Determine if the given object is a CraftItemStack instancey. - * @param obj - the given object. - * @return TRUE if it is, FALSE otherwise. - */ - public static boolean isCraftItemStack(Object obj) { - return is(getCraftItemStackClass(), obj); - } - - /** - * Retrieve the EntityPlayer (NMS) class. - * @return The entity class. - */ - public static Class getEntityPlayerClass() { - try { - return getMinecraftClass("EntityPlayer"); - } catch (RuntimeException e) { - try { - // Grab CraftPlayer's handle - Method getHandle = FuzzyReflection - .fromClass(getCraftBukkitClass("entity.CraftPlayer")) - .getMethodByName("getHandle"); - - // EntityPlayer is the return type - return setMinecraftClass("EntityPlayer", getHandle.getReturnType()); - } catch (IllegalArgumentException e1) { - throw new RuntimeException("Could not find EntityPlayer class.", e1); - } - } - } - - /** - * Retrieve the EntityHuman class. - * @return The entity human class. - */ - public static Class getEntityHumanClass() { - // Assume its the direct superclass - return getEntityPlayerClass().getSuperclass(); - } - - /** - * Retrieve the GameProfile class in 1.7.2 and later. - * - * @return The game profile class. - * @throws IllegalStateException If we are running 1.6.4 or earlier. - */ - public static Class getGameProfileClass() { - if (!isUsingNetty()) - throw new IllegalStateException("GameProfile does not exist in version 1.6.4 and earlier."); - - try { - return getClass("com.mojang.authlib.GameProfile"); - } catch (Throwable ex) { - try { - return getClass("net.minecraft.util.com.mojang.authlib.GameProfile"); - } catch (Throwable ex1) { - FuzzyReflection reflection = FuzzyReflection.fromClass(PacketType.Login.Client.START.getPacketClass(), true); - FuzzyFieldContract contract = FuzzyFieldContract.newBuilder() - .banModifier(Modifier.STATIC) - .typeMatches(FuzzyMatchers.matchRegex("(.*)(GameProfile)", 1)) - .build(); - return reflection.getField(contract).getType(); - } - } - } - - /** - * Retrieve the entity (NMS) class. - * @return The entity class. - */ - public static Class getEntityClass() { - try { - return getMinecraftClass("Entity"); - } catch (RuntimeException e) { - return fallbackMethodReturn("Entity", "entity.CraftEntity", "getHandle"); - } - } - - /** - * Retrieve the CraftChatMessage in Minecraft 1.7.2. - * @return The CraftChatMessage class. - */ - public static Class getCraftChatMessage() { - return getCraftBukkitClass("util.CraftChatMessage"); - } - - /** - * Retrieve the WorldServer (NMS) class. - * @return The WorldServer class. - */ - public static Class getWorldServerClass() { - try { - return getMinecraftClass("WorldServer"); - } catch (RuntimeException e) { - return fallbackMethodReturn("WorldServer", "CraftWorld", "getHandle"); - } - } - - /** - * Retrieve the World (NMS) class. - * @return The world class. - */ - public static Class getNmsWorldClass() { - try { - return getMinecraftClass("World"); - } catch (RuntimeException e) { - return setMinecraftClass("World", getWorldServerClass().getSuperclass()); - } - } - - /** - * Fallback on the return value of a named method in order to get a NMS class. - * @param nmsClass - the expected name of the Minecraft class. - * @param craftClass - a CraftBukkit class to look at. - * @param methodName - the method we will use. - * @return The return value of this method, which will be saved to the package cache. - */ - private static Class fallbackMethodReturn(String nmsClass, String craftClass, String methodName) { - Class result = FuzzyReflection.fromClass(getCraftBukkitClass(craftClass)). - getMethodByName(methodName).getReturnType(); - - // Save the result - return setMinecraftClass(nmsClass, result); - } - - /** - * Retrieve the packet class. - * @return The packet class. - */ - public static Class getPacketClass() { - try { - return getMinecraftClass("Packet"); - } catch (RuntimeException e) { - FuzzyClassContract paketContract = null; - - // What kind of class we're looking for (sanity check) - if (isUsingNetty()) { - paketContract = FuzzyClassContract.newBuilder(). - method(FuzzyMethodContract.newBuilder(). - parameterDerivedOf(getByteBufClass()). - returnTypeVoid()). - method(FuzzyMethodContract.newBuilder(). - parameterDerivedOf(getByteBufClass(), 0). - parameterExactType(byte[].class, 1). - returnTypeVoid()). - build(); - } else { - paketContract = FuzzyClassContract.newBuilder(). - field(FuzzyFieldContract.newBuilder(). - typeDerivedOf(Map.class). - requireModifier(Modifier.STATIC)). - field(FuzzyFieldContract.newBuilder(). - typeDerivedOf(Set.class). - requireModifier(Modifier.STATIC)). - method(FuzzyMethodContract.newBuilder(). - parameterSuperOf(DataInputStream.class). - returnTypeVoid()). - build(); - } - - // Select a method with one Minecraft object parameter - Method selected = FuzzyReflection.fromClass(getPlayerConnectionClass()). - getMethod(FuzzyMethodContract.newBuilder(). - parameterMatches(paketContract, 0). - parameterCount(1). - build() - ); - - // Save and return - Class clazz = getTopmostClass(selected.getParameterTypes()[0]); - return setMinecraftClass("Packet", clazz); - } - } - - public static Class getByteBufClass() { - try { - return getClass("io.netty.buffer.ByteBuf"); - } catch (Throwable ex) { - return getClass("net.minecraft.util.io.netty.buffer.ByteBuf"); - } - } - - /** - * Retrieve the EnumProtocol class in 1.7.2. - * @return The Enum protocol class. - */ - public static Class getEnumProtocolClass() { - try { - return getMinecraftClass("EnumProtocol"); - } catch (RuntimeException e) { - Method protocolMethod = FuzzyReflection.fromClass(getNetworkManagerClass()).getMethod( - FuzzyMethodContract.newBuilder(). - parameterCount(1). - parameterDerivedOf(Enum.class, 0). - build() - ); - return setMinecraftClass("EnumProtocol", protocolMethod.getParameterTypes()[0]); - } - } - - /** - * Retrieve the IChatBaseComponent class. - * @return The IChatBaseComponent. - */ - public static Class getIChatBaseComponentClass() { - try { - return getMinecraftClass("IChatBaseComponent"); - } catch (RuntimeException e) { - return setMinecraftClass("IChatBaseComponent", - Accessors.getMethodAccessor(getCraftChatMessage(), "fromString", String.class). - getMethod().getReturnType().getComponentType() - ); - } - } - - public static Class getIChatBaseComponentArrayClass() { - return getArrayClass(getIChatBaseComponentClass()); - } - - /** - * Retrieve the NMS chat component text class. - * @return The chat component class. - */ - public static Class getChatComponentTextClass() { - try { - return getMinecraftClass("ChatComponentText"); - } catch (RuntimeException e) { - try { - Method getScoreboardDisplayName = FuzzyReflection.fromClass(getEntityClass()). - getMethodByParameters("getScoreboardDisplayName", getIChatBaseComponentClass(), new Class[0]); - Class baseClass = getIChatBaseComponentClass(); - - for (AsmMethod method : ClassAnalyser.getDefault().getMethodCalls(getScoreboardDisplayName)) { - Class owner = method.getOwnerClass(); - - if (isMinecraftClass(owner) && baseClass.isAssignableFrom(owner)) { - return setMinecraftClass("ChatComponentText", owner); - } - } - } catch (Exception e1) { - throw new IllegalStateException("Cannot find ChatComponentText class.", e); - } - } - throw new IllegalStateException("Cannot find ChatComponentText class."); - } - - /** - * Attempt to find the ChatSerializer class. - * @return The serializer class. - * @throws IllegalStateException If the class could not be found or deduced. - */ - public static Class getChatSerializerClass() { - try { - return getMinecraftClass("ChatSerializer", "IChatBaseComponent$ChatSerializer"); - } catch (RuntimeException e) { - // TODO: Figure out a functional fallback - throw new IllegalStateException("Could not find ChatSerializer class.", e); - } - } - - /** - * Retrieve the ServerPing class in Minecraft 1.7.2. - * @return The ServerPing class. - */ - public static Class getServerPingClass() { - if (!isUsingNetty()) - throw new IllegalStateException("ServerPing is only supported in 1.7.2."); - - try { - return getMinecraftClass("ServerPing"); - } catch (RuntimeException e) { - Class statusServerInfo = PacketType.Status.Server.SERVER_INFO.getPacketClass(); - - // Find a server ping object - AbstractFuzzyMatcher> serverPingContract = FuzzyClassContract.newBuilder(). - field(FuzzyFieldContract.newBuilder().typeExact(String.class).build()). - field(FuzzyFieldContract.newBuilder().typeDerivedOf(getIChatBaseComponentClass()).build()). - build(). - and(getMinecraftObjectMatcher()); - - return setMinecraftClass("ServerPing", - FuzzyReflection.fromClass(statusServerInfo, true). - getField(FuzzyFieldContract.matchType(serverPingContract)).getType()); - } - } - - /** - * Retrieve the ServerPingServerData class in Minecraft 1.7.2. - * @return The ServerPingServerData class. - */ - public static Class getServerPingServerDataClass() { - if (!isUsingNetty()) - throw new IllegalStateException("ServerPingServerData is only supported in 1.7.2."); - - try { - return getMinecraftClass("ServerPingServerData", "ServerPing$ServerData"); - } catch (RuntimeException e) { - FuzzyReflection fuzzy = FuzzyReflection.fromClass(getServerPingClass(), true); - return setMinecraftClass("ServerPingServerData", fuzzy.getFieldByType("(.*)(ServerData)(.*)").getType()); - } - } - - /** - * Retrieve the ServerPingPlayerSample class in Minecraft 1.7.2. - * @return The ServerPingPlayerSample class. - */ - public static Class getServerPingPlayerSampleClass() { - if (!isUsingNetty()) - throw new IllegalStateException("ServerPingPlayerSample is only supported in 1.7.2."); - - try { - return getMinecraftClass("ServerPingPlayerSample", "ServerPing$ServerPingPlayerSample"); - } catch (RuntimeException e) { - Class serverPing = getServerPingClass(); - - // Find a server ping object - AbstractFuzzyMatcher> serverPlayerContract = FuzzyClassContract.newBuilder(). - constructor(FuzzyMethodContract.newBuilder().parameterExactArray(int.class, int.class)). - field(FuzzyFieldContract.newBuilder().typeExact(getArrayClass(getGameProfileClass()))). - build(). - and(getMinecraftObjectMatcher()); - - return setMinecraftClass("ServerPingPlayerSample", getTypeFromField(serverPing, serverPlayerContract)); - } - } - - /** - * Retrieve the type of the field whose type matches. - * @param clazz - the declaring type. - * @param fieldTypeMatcher - the field type matcher. - * @return The type of the field. - */ - private static Class getTypeFromField(Class clazz, AbstractFuzzyMatcher> fieldTypeMatcher) { - final FuzzyFieldContract fieldMatcher = FuzzyFieldContract.matchType(fieldTypeMatcher); - - return FuzzyReflection.fromClass(clazz, true). - getField(fieldMatcher).getType(); - } - - /** - * Determine if this Minecraft version is using Netty. - *

- * Spigot is ignored in this consideration. - * @return TRUE if it does, FALSE otherwise. - */ - public static boolean isUsingNetty() { - if (cachedNetty == null) { - try { - cachedNetty = getEnumProtocolClass() != null; - } catch (RuntimeException e) { - cachedNetty = false; - } - } - return cachedNetty; - } - - /** - * Retrieve the least derived class, except Object. - * @return Least derived super class. - */ - private static Class getTopmostClass(Class clazz) { - while (true) { - Class superClass = clazz.getSuperclass(); - - if (superClass == Object.class || superClass == null) - return clazz; - else - clazz = superClass; - } - } - - /** - * Retrieve the MinecraftServer class. - * @return MinecraftServer class. - */ - public static Class getMinecraftServerClass() { - try { - return getMinecraftClass("MinecraftServer"); - } catch (RuntimeException e) { - useFallbackServer(); - - // Reset cache and try again - setMinecraftClass("MinecraftServer", null); - return getMinecraftClass("MinecraftServer"); - } - } - - /** - * Retrieve the NMS statistics class. - * @return The statistics class. - */ - public static Class getStatisticClass() { - // TODO: Implement fallback - return getMinecraftClass("Statistic"); - } - - /** - * Retrieve the NMS statistic list class. - * @return The statistic list class. - */ - public static Class getStatisticListClass() { - // TODO: Implement fallback - return getMinecraftClass("StatisticList"); - } - - /** - * Fallback method that can determine the MinecraftServer and the ServerConfigurationManager. - */ - private static void useFallbackServer() { - // Get the first constructor that matches CraftServer(MINECRAFT_OBJECT, ANY) - Constructor selected = FuzzyReflection.fromClass(getCraftBukkitClass("CraftServer")). - getConstructor(FuzzyMethodContract.newBuilder(). - parameterMatches(getMinecraftObjectMatcher(), 0). - parameterCount(2). - build() - ); - Class[] params = selected.getParameterTypes(); - - // Jackpot - two classes at the same time! - setMinecraftClass("MinecraftServer", params[0]); - setMinecraftClass("ServerConfigurationManager", params[1]); - } - - /** - * Retrieve the player list class (or ServerConfigurationManager), - * @return The player list class. - */ - public static Class getPlayerListClass() { - try { - return getMinecraftClass("ServerConfigurationManager", "PlayerList"); - } catch (RuntimeException e) { - useFallbackServer(); - - // Reset cache and try again - setMinecraftClass("ServerConfigurationManager", null); - return getMinecraftClass("ServerConfigurationManager"); - } - } - - /** - * Retrieve the NetLoginHandler class (or PendingConnection) - * @return The NetLoginHandler class. - */ - public static Class getNetLoginHandlerClass() { - try { - return getMinecraftClass("NetLoginHandler", "PendingConnection"); - } catch (RuntimeException e) { - Method selected = FuzzyReflection.fromClass(getPlayerListClass()). - getMethod(FuzzyMethodContract.newBuilder(). - parameterMatches( - FuzzyMatchers.matchExact(getEntityPlayerClass()).inverted(), 0 - ). - parameterExactType(String.class, 1). - parameterExactType(String.class, 2). - build() - ); - - // Save the pending connection reference - return setMinecraftClass("NetLoginHandler", selected.getParameterTypes()[0]); - } - } - - /** - * Retrieve the PlayerConnection class (or NetServerHandler) - * @return The PlayerConnection class. - */ - public static Class getPlayerConnectionClass() { - try { - return getMinecraftClass("PlayerConnection", "NetServerHandler"); - } catch (RuntimeException e) { - try { - // Use the player connection field - return setMinecraftClass("PlayerConnection", - FuzzyReflection.fromClass(getEntityPlayerClass()). - getFieldByType("playerConnection", getNetHandlerClass()).getType() - ); - - } catch (RuntimeException e1) { - // Okay, this must be on 1.7.2 - Class playerClass = getEntityPlayerClass(); - - FuzzyClassContract playerConnection = FuzzyClassContract.newBuilder(). - field(FuzzyFieldContract.newBuilder().typeExact(playerClass).build()). - constructor(FuzzyMethodContract.newBuilder(). - parameterCount(3). - parameterSuperOf(getMinecraftServerClass(), 0). - parameterSuperOf(getEntityPlayerClass(), 2). - build() - ). - method(FuzzyMethodContract.newBuilder(). - parameterCount(1). - parameterExactType(String.class). - build() - ). - build(); - - // If not, use duck typing - Class fieldType = FuzzyReflection.fromClass(getEntityPlayerClass(), true).getField( - FuzzyFieldContract.newBuilder().typeMatches(playerConnection).build() - ).getType(); - - return setMinecraftClass("PlayerConnection", fieldType); - } - } - } - - /** - * Retrieve the NetworkManager class or its interface. - * @return The NetworkManager class or its interface. - */ - public static Class getNetworkManagerClass() { - try { - return getMinecraftClass("INetworkManager", "NetworkManager"); - } catch (RuntimeException e) { - Constructor selected = FuzzyReflection.fromClass(getPlayerConnectionClass()). - getConstructor(FuzzyMethodContract.newBuilder(). - parameterSuperOf(getMinecraftServerClass(), 0). - parameterSuperOf(getEntityPlayerClass(), 2). - build() - ); - - // And we're done - return setMinecraftClass("INetworkManager", selected.getParameterTypes()[1]); - } - } - - /** - * Retrieve the NetHandler class (or Connection) - * @return The NetHandler class. - */ - public static Class getNetHandlerClass() { - try { - return getMinecraftClass("NetHandler", "Connection"); - } catch (RuntimeException e) { - // Try getting the net login handler - return setMinecraftClass("NetHandler", getNetLoginHandlerClass().getSuperclass()); - } - } - - /** - * Retrieve the NMS ItemStack class. - * @return The ItemStack class. - */ - public static Class getItemStackClass() { - try { - return getMinecraftClass("ItemStack"); - } catch (RuntimeException e) { - // Use the handle reference - return setMinecraftClass("ItemStack", - FuzzyReflection.fromClass(getCraftItemStackClass(), true).getFieldByName("handle").getType()); - } - } - - /** - * Retrieve the Block (NMS) class. - * @return Block (NMS) class. - */ - public static Class getBlockClass() { - try { - return getMinecraftClass("Block"); - } catch (RuntimeException e) { - FuzzyReflection reflect = FuzzyReflection.fromClass(getItemStackClass()); - Set> candidates = new HashSet>(); - - // Minecraft objects in the constructor - for (Constructor constructor : reflect.getConstructors()) { - for (Class clazz : constructor.getParameterTypes()) { - if (isMinecraftClass(clazz)) { - candidates.add(clazz); - } - } - } - - // Useful constructors - Method selected = - reflect.getMethod(FuzzyMethodContract.newBuilder(). - parameterMatches(FuzzyMatchers.matchAnyOf(candidates)). - returnTypeExact(float.class). - build()); - return setMinecraftClass("Block", selected.getParameterTypes()[0]); - } - } - - public static Class getItemClass() { - return getNullableNMS("Item"); - } - - public static Class getFluidTypeClass() { - return getNullableNMS("FluidType"); - } - - /** - * Retrieve the WorldType class. - * @return The WorldType class. - */ - public static Class getWorldTypeClass() { - try { - return getMinecraftClass("WorldType"); - } catch (RuntimeException e) { - // Get the first constructor that matches CraftServer(MINECRAFT_OBJECT, ANY) - Method selected = FuzzyReflection.fromClass(getMinecraftServerClass(), true). - getMethod(FuzzyMethodContract.newBuilder(). - parameterExactType(String.class, 0). - parameterExactType(String.class, 1). - parameterMatches(getMinecraftObjectMatcher()). - parameterExactType(String.class, 4). - parameterCount(5). - build() - ); - return setMinecraftClass("WorldType", selected.getParameterTypes()[3]); - } - } - - /** - * Retrieve the DataWatcher class. - * @return The DataWatcher class. - */ - public static Class getDataWatcherClass() { - try { - return getMinecraftClass("DataWatcher"); - } catch (RuntimeException e) { - // Describe the DataWatcher - FuzzyClassContract dataWatcherContract = FuzzyClassContract.newBuilder(). - field(FuzzyFieldContract.newBuilder(). - requireModifier(Modifier.STATIC). - typeDerivedOf(Map.class)). - field(FuzzyFieldContract.newBuilder(). - banModifier(Modifier.STATIC). - typeDerivedOf(Map.class)). - method(FuzzyMethodContract.newBuilder(). - parameterExactType(int.class). - parameterExactType(Object.class). - returnTypeVoid()). - build(); - FuzzyFieldContract fieldContract = FuzzyFieldContract.newBuilder(). - typeMatches(dataWatcherContract). - build(); - - // Get such a field and save the result - return setMinecraftClass("DataWatcher", - FuzzyReflection.fromClass(getEntityClass(), true). - getField(fieldContract). - getType() - ); - } - } - - /** - * Retrieves the ChunkPosition class. - * - * @return The ChunkPosition class. - */ - public static Class getChunkPositionClass() { - try { - return getMinecraftClass("ChunkPosition"); - } catch (RuntimeException e) { - return null; - } - } - - /** - * Retrieves the BlockPosition class. - * - * @return The BlockPosition class. - */ - public static Class getBlockPositionClass() { - try { - return getMinecraftClass("BlockPosition"); - } catch (RuntimeException e) { - try { - Class normalChunkGenerator = getCraftBukkitClass("generator.NormalChunkGenerator"); - - // BlockPosition findNearestMapFeature(World, String, BlockPosition) - FuzzyMethodContract selected = FuzzyMethodContract.newBuilder() - .banModifier(Modifier.STATIC) - .parameterMatches(getMinecraftObjectMatcher(), 0) - .parameterExactType(String.class, 1) - .parameterMatches(getMinecraftObjectMatcher(), 1) - .build(); - - return setMinecraftClass("BlockPosition", - FuzzyReflection.fromClass(normalChunkGenerator).getMethod(selected).getReturnType()); - } catch (Throwable ex) { - return null; - } - } - } - - /** - * Retrieves the Vec3D class. - * @return The Vec3D class. - */ - public static Class getVec3DClass() { - try { - return getMinecraftClass("Vec3D"); - } catch (RuntimeException e) { - // TODO: Figure out a fuzzy field contract - return null; - } - } - - /** - * Retrieve the ChunkCoordinates class. - * @return The ChunkPosition class. - */ - public static Class getChunkCoordinatesClass() { - // TODO Figure out a fallback - try { - return getMinecraftClass("ChunkCoordinates"); - } catch (RuntimeException e) { - return null; - } - } - - /** - * Retrieve the ChunkCoordIntPair class. - * @return The ChunkCoordIntPair class. - */ - public static Class getChunkCoordIntPair() { - if (!isUsingNetty()) - throw new IllegalArgumentException("Not supported on 1.6.4 and older."); - - try { - return getMinecraftClass("ChunkCoordIntPair"); - } catch (RuntimeException e) { - Class packet = PacketRegistry.getPacketClassFromType(PacketType.Play.Server.MULTI_BLOCK_CHANGE); - - AbstractFuzzyMatcher> chunkCoordIntContract = FuzzyClassContract.newBuilder(). - field(FuzzyFieldContract.newBuilder(). - typeDerivedOf(int.class)). - field(FuzzyFieldContract.newBuilder(). - typeDerivedOf(int.class)). - method(FuzzyMethodContract.newBuilder(). - parameterExactArray(int.class). - returnDerivedOf( getChunkPositionClass() )). - build().and(getMinecraftObjectMatcher()); - - Field field = FuzzyReflection.fromClass(packet, true).getField( - FuzzyFieldContract.matchType(chunkCoordIntContract)); - return setMinecraftClass("ChunkCoordIntPair", field.getType()); - } - } - - /** - * Retrieve the WatchableObject class. - * @return The WatchableObject class. - * @deprecated Replaced by {@link #getDataWatcherItemClass()} - */ - @Deprecated - public static Class getWatchableObjectClass() { - return getDataWatcherItemClass(); - } - - /** - * Retrieve the DataWatcher Item class. - * @return The class - */ - public static Class getDataWatcherItemClass() { - try { - return getMinecraftClass("DataWatcher$Item", "DataWatcher$WatchableObject", "WatchableObject"); - } catch (RuntimeException e) { - Method selected = FuzzyReflection.fromClass(getDataWatcherClass(), true). - getMethod(FuzzyMethodContract.newBuilder(). - requireModifier(Modifier.STATIC). - parameterDerivedOf(isUsingNetty() ? getPacketDataSerializerClass() : DataOutput.class, 0). - parameterMatches(getMinecraftObjectMatcher(), 1). - build()); - - // Use the second parameter - return setMinecraftClass("DataWatcher$Item", selected.getParameterTypes()[1]); - } - } - - public static Class getDataWatcherObjectClass() { - try { - return getMinecraftClass("DataWatcherObject"); - } catch (RuntimeException ex) { - return null; - } - } - - public static boolean watcherObjectExists() { - if (cachedWatcherObject == null) { - try { - return cachedWatcherObject = getDataWatcherObjectClass() != null; - } catch (Throwable ex) { - return cachedWatcherObject = false; - } - } - - return cachedWatcherObject; - } - - public static Class getDataWatcherSerializerClass() { - // TODO Implement a fallback - return getMinecraftClass("DataWatcherSerializer"); - } - - public static Class getDataWatcherRegistryClass() { - // TODO Implement a fallback - return getMinecraftClass("DataWatcherRegistry"); - } - - public static Class getMinecraftKeyClass() { - // TODO Implement a fallback - return getMinecraftClass("MinecraftKey"); - } - - public static Class getMobEffectListClass() { - // TODO Implement a fallback - return getMinecraftClass("MobEffectList"); - } - - public static Class getSoundEffectClass() { - try { - return getMinecraftClass("SoundEffect"); - } catch (RuntimeException ex) { - FuzzyReflection fuzzy = FuzzyReflection.fromClass(PacketType.Play.Server.NAMED_SOUND_EFFECT.getPacketClass(), true); - Field field = fuzzy.getFieldByType("(.*)(Sound)(.*)"); - return setMinecraftClass("SoundEffect", field.getType()); - } - } - - /** - * Retrieve the ServerConnection abstract class. - * @return The ServerConnection class. - */ - public static Class getServerConnectionClass() { - try { - return getMinecraftClass("ServerConnection"); - } catch (RuntimeException e) { - Method selected = null; - FuzzyClassContract.Builder serverConnectionContract = FuzzyClassContract.newBuilder(). - constructor(FuzzyMethodContract.newBuilder(). - parameterExactType(getMinecraftServerClass()). - parameterCount(1)); - - if (isUsingNetty()) { - serverConnectionContract. - method(FuzzyMethodContract.newBuilder(). - parameterDerivedOf(InetAddress.class, 0). - parameterDerivedOf(int.class, 1). - parameterCount(2) - ); - - selected = FuzzyReflection.fromClass(getMinecraftServerClass()). - getMethod(FuzzyMethodContract.newBuilder(). - requireModifier(Modifier.PUBLIC). - returnTypeMatches(serverConnectionContract.build()). - build()); - - } else { - serverConnectionContract. - method(FuzzyMethodContract.newBuilder(). - parameterExactType(getPlayerConnectionClass())); - - selected = FuzzyReflection.fromClass(getMinecraftServerClass()). - getMethod(FuzzyMethodContract.newBuilder(). - requireModifier(Modifier.ABSTRACT). - returnTypeMatches(serverConnectionContract.build()). - build()); - } - - // Use the return type - return setMinecraftClass("ServerConnection", selected.getReturnType()); - } - } - - /** - * Retrieve the NBT base class. - * @return The NBT base class. - */ - public static Class getNBTBaseClass() { - try { - return getMinecraftClass("NBTBase"); - } catch (RuntimeException e) { - Class nbtBase = null; - - if (isUsingNetty()) { - FuzzyClassContract tagCompoundContract = FuzzyClassContract.newBuilder(). - field(FuzzyFieldContract.newBuilder(). - typeDerivedOf(Map.class)). - method(FuzzyMethodContract.newBuilder(). - parameterDerivedOf(DataOutput.class). - parameterCount(1)). - build(); - - Method selected = FuzzyReflection.fromClass(getPacketDataSerializerClass()). - getMethod(FuzzyMethodContract.newBuilder(). - banModifier(Modifier.STATIC). - parameterCount(1). - parameterMatches(tagCompoundContract). - returnTypeVoid(). - build() - ); - nbtBase = selected.getParameterTypes()[0].getSuperclass(); - - } else { - FuzzyClassContract tagCompoundContract = FuzzyClassContract.newBuilder(). - constructor(FuzzyMethodContract.newBuilder(). - parameterExactType(String.class). - parameterCount(1)). - field(FuzzyFieldContract.newBuilder(). - typeDerivedOf(Map.class)). - build(); - - Method selected = FuzzyReflection.fromClass(getPacketClass()). - getMethod(FuzzyMethodContract.newBuilder(). - requireModifier(Modifier.STATIC). - parameterSuperOf(DataInputStream.class). - parameterCount(1). - returnTypeMatches(tagCompoundContract). - build() - ); - nbtBase = selected.getReturnType().getSuperclass(); - } - - // That can't be correct - if (nbtBase == null || nbtBase.equals(Object.class)) { - throw new IllegalStateException("Unable to find NBT base class: " + nbtBase); - } - - // Use the return type here too - return setMinecraftClass("NBTBase", nbtBase); - } - } - - /** - * Retrieve the NBT read limiter class. - *

- * This is only supported in 1.7.8 (released 2014) and higher. - * @return The NBT read limiter. - */ - public static Class getNBTReadLimiterClass() { - return getMinecraftClass("NBTReadLimiter"); - } - - /** - * Retrieve the NBT Compound class. - * @return The NBT Compond class. - */ - public static Class getNBTCompoundClass() { - try { - return getMinecraftClass("NBTTagCompound"); - } catch (RuntimeException e) { - return setMinecraftClass( - "NBTTagCompound", - NbtFactory.ofWrapper(NbtType.TAG_COMPOUND, "Test").getHandle().getClass() - ); - } - } - - /** - * Retrieve the EntityTracker (NMS) class. - * @return EntityTracker class. - */ - public static Class getEntityTrackerClass() { - try { - return getMinecraftClass("EntityTracker"); - } catch (RuntimeException e) { - FuzzyClassContract entityTrackerContract = FuzzyClassContract.newBuilder(). - field(FuzzyFieldContract.newBuilder(). - typeDerivedOf(Set.class)). - method(FuzzyMethodContract.newBuilder(). - parameterSuperOf(MinecraftReflection.getEntityClass()). - parameterCount(1). - returnTypeVoid()). - method(FuzzyMethodContract.newBuilder(). - parameterSuperOf(MinecraftReflection.getEntityClass(), 0). - parameterSuperOf(int.class, 1). - parameterSuperOf(int.class, 2). - parameterCount(3). - returnTypeVoid()). - build(); - - Field selected = FuzzyReflection.fromClass(MinecraftReflection.getWorldServerClass(), true). - getField(FuzzyFieldContract.newBuilder(). - typeMatches(entityTrackerContract). - build() - ); - - // Go by the defined type of this field - return setMinecraftClass("EntityTracker", selected.getType()); - } - } - - /** - * Retrieve the NetworkListenThread class (NMS). - *

- * Note that this class was removed after Minecraft 1.3.1. - * @return NetworkListenThread class. - */ - public static Class getNetworkListenThreadClass() { - try { - return getMinecraftClass("NetworkListenThread"); - } catch (RuntimeException e) { - FuzzyClassContract networkListenContract = FuzzyClassContract.newBuilder(). - field(FuzzyFieldContract.newBuilder(). - typeDerivedOf(ServerSocket.class)). - field(FuzzyFieldContract.newBuilder(). - typeDerivedOf(Thread.class)). - field(FuzzyFieldContract.newBuilder(). - typeDerivedOf(List.class)). - method(FuzzyMethodContract.newBuilder(). - parameterExactType(getPlayerConnectionClass())). - build(); - - Field selected = FuzzyReflection.fromClass(MinecraftReflection.getMinecraftServerClass(), true). - getField(FuzzyFieldContract.newBuilder(). - typeMatches(networkListenContract). - build() - ); - - // Go by the defined type of this field - return setMinecraftClass("NetworkListenThread", selected.getType()); - } - } - - /** - * Retrieve the attribute snapshot class. - *

- * This stores the final value of an attribute, along with all the associated computational steps. - * @return The attribute snapshot class. - */ - public static Class getAttributeSnapshotClass() { - try { - return getMinecraftClass("AttributeSnapshot", "PacketPlayOutUpdateAttributes$AttributeSnapshot"); - } catch (RuntimeException ex) { - try { - // It should be the parameter of a list in the update attributes packet - FuzzyReflection fuzzy = FuzzyReflection.fromClass(PacketType.Play.Server.UPDATE_ATTRIBUTES.getPacketClass(), true); - Field field = fuzzy.getFieldByType("attributes", Collection.class); - Type param = ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]; - return setMinecraftClass("AttributeSnapshot", (Class) param); - } catch (Throwable ex1) { - return getMinecraftClass("AttributeSnapshot"); - } - } - } - - /** - * Retrieve the IntHashMap class. - * @return IntHashMap class. - */ - public static Class getIntHashMapClass() { - try { - return getMinecraftClass("IntHashMap"); - } catch (RuntimeException e) { - final Class parent = getEntityTrackerClass(); - - // Expected structure of a IntHashMap - final FuzzyClassContract intHashContract = FuzzyClassContract.newBuilder(). - // add(int key, Object value) - method(FuzzyMethodContract.newBuilder(). - parameterCount(2). - parameterExactType(int.class, 0). - parameterExactType(Object.class, 1).requirePublic() - ). - // Object get(int key) - method(FuzzyMethodContract.newBuilder(). - parameterCount(1). - parameterExactType(int.class). - returnTypeExact(Object.class).requirePublic() - ). - // Finally, there should be an array of some kind - field(FuzzyFieldContract.newBuilder(). - typeMatches(FuzzyMatchers.matchArray(FuzzyMatchers.matchAll())) - ). - build(); - - final AbstractFuzzyMatcher intHashField = FuzzyFieldContract.newBuilder(). - typeMatches(getMinecraftObjectMatcher().and(intHashContract)). - build(); - - // Use the type of the first field that matches - return setMinecraftClass("IntHashMap", FuzzyReflection.fromClass(parent).getField(intHashField).getType()); - } - } - - /** - * Retrieve the attribute modifier class. - * @return Attribute modifier class. - */ - public static Class getAttributeModifierClass() { - try { - return getMinecraftClass("AttributeModifier"); - } catch (RuntimeException e) { - getAttributeSnapshotClass(); - - // Reset cache and try again - setMinecraftClass("AttributeModifier", null); - return getMinecraftClass("AttributeModifier"); - } - } - - /** - * Retrieve the net.minecraft.server.MobEffect class. - * @return The mob effect class. - */ - public static Class getMobEffectClass() { - try { - return getMinecraftClass("MobEffect"); - } catch (RuntimeException e) { - // It is the second parameter in Packet41MobEffect - Class packet = PacketRegistry.getPacketClassFromType(PacketType.Play.Server.ENTITY_EFFECT); - Constructor constructor = FuzzyReflection.fromClass(packet).getConstructor( - FuzzyMethodContract.newBuilder(). - parameterCount(2). - parameterExactType(int.class, 0). - parameterMatches(getMinecraftObjectMatcher(), 1). - build() - ); - return setMinecraftClass("MobEffect", constructor.getParameterTypes()[1]); - } - } - - /** - * Retrieve the packet data serializer class that overrides ByteBuf. - * @return The data serializer class. - */ - public static Class getPacketDataSerializerClass() { - try { - return getMinecraftClass("PacketDataSerializer"); - } catch (RuntimeException e) { - Class packet = getPacketClass(); - Method method = FuzzyReflection.fromClass(packet).getMethod( - FuzzyMethodContract.newBuilder(). - parameterCount(1). - parameterDerivedOf(getByteBufClass()). - returnTypeVoid(). - build() - ); - return setMinecraftClass("PacketDataSerializer", method.getParameterTypes()[0]); - } - } - - /** - * Retrieve the NBTCompressedStreamTools class. - * @return The NBTCompressedStreamTools class. - */ - public static Class getNbtCompressedStreamToolsClass() { - try { - return getMinecraftClass("NBTCompressedStreamTools"); - } catch (RuntimeException e) { - Class packetSerializer = getPacketDataSerializerClass(); - - // Get the write NBT compound method - Method writeNbt = FuzzyReflection.fromClass(packetSerializer). - getMethodByParameters("writeNbt", getNBTCompoundClass()); - - try { - // Now -- we inspect all the method calls within that method, and use the first foreign Minecraft class - for (AsmMethod method : ClassAnalyser.getDefault().getMethodCalls(writeNbt)) { - Class owner = method.getOwnerClass(); - - if (!packetSerializer.equals(owner) && isMinecraftClass(owner)) { - return setMinecraftClass("NBTCompressedStreamTools", owner); - } - } - } catch (Exception e1) { - throw new RuntimeException("Unable to analyse class.", e1); - } - throw new IllegalArgumentException("Unable to find NBTCompressedStreamTools."); - } - } - - /** - * Retrieve the NMS tile entity class. - * @return The tile entity class. - */ - public static Class getTileEntityClass() { - return getMinecraftClass("TileEntity"); - } - - /** - * Retrieve the Gson class used by Minecraft. - * @return The Gson class. - */ - public static Class getMinecraftGsonClass() { - try { - return getMinecraftLibraryClass("com.google.gson.Gson"); - } catch (RuntimeException e) { - Class match = FuzzyReflection.fromClass(PacketType.Status.Server.SERVER_INFO.getPacketClass(), true) - .getFieldByType("(.*)(google.gson.Gson)").getType(); - return setMinecraftLibraryClass("com.google.gson.Gson", match); - } - } - - /** - * Retrieve the ItemStack[] class. - * @return The ItemStack[] class. - */ - public static Class getItemStackArrayClass() { - if (itemStackArrayClass == null) - itemStackArrayClass = getArrayClass(getItemStackClass()); - return itemStackArrayClass; - } - - /** - * Retrieve the array class of a given component type. - * @param componentType - type of each element in the array. - * @return The class of the array. - */ - public static Class getArrayClass(Class componentType) { - // Bit of a hack, but it works - return Array.newInstance(componentType, 0).getClass(); - } - - /** - * Retrieve the CraftItemStack class. - * @return The CraftItemStack class. - */ - public static Class getCraftItemStackClass() { - return getCraftBukkitClass("inventory.CraftItemStack"); - } - - /** - * Retrieve the CraftPlayer class. - * @return CraftPlayer class. - */ - public static Class getCraftPlayerClass() { - return getCraftBukkitClass("entity.CraftPlayer"); - } - - /** - * Retrieve the CraftWorld class. - * @return The CraftWorld class. - */ - public static Class getCraftWorldClass() { - return getCraftBukkitClass("CraftWorld"); - } - - /** - * Retrieve the CraftEntity class. - * @return CraftEntity class. - */ - public static Class getCraftEntityClass() { - return getCraftBukkitClass("entity.CraftEntity"); - } - - /** - * Retrieve the CraftChatMessage introduced in 1.7.2 - * @return The CraftChatMessage class. - */ - public static Class getCraftMessageClass() { - return getCraftBukkitClass("util.CraftChatMessage"); - } - - /** - * Retrieve the PlayerInfoData class in 1.8. - * @return The PlayerInfoData class - */ - public static Class getPlayerInfoDataClass() { - return getMinecraftClass("PacketPlayOutPlayerInfo$PlayerInfoData", "PlayerInfoData"); - } - - /** - * Determine if the given object is a PlayerInfoData. - * @param obj - the given object. - * @return TRUE if it is, FALSE otherwise. - */ - public static boolean isPlayerInfoData(Object obj) { - return is(getPlayerInfoDataClass(), obj); - } - - /** - * Retrieve the IBlockData class in 1.8. - * @return The IBlockData class - */ - public static Class getIBlockDataClass() { - return getMinecraftClass("IBlockData"); - } - - /** - * Retrieve the MultiBlockChangeInfo class in 1.8. - * @return The MultiBlockChangeInfo class - */ - public static Class getMultiBlockChangeInfoClass() { - return getMinecraftClass("MultiBlockChangeInfo", "PacketPlayOutMultiBlockChange$MultiBlockChangeInfo"); - } - - /** - * Retrieve the MultiBlockChangeInfo array class in 1.8. - * @return The MultiBlockChangeInfo array class - */ - public static Class getMultiBlockChangeInfoArrayClass() { - return getArrayClass(getMultiBlockChangeInfoClass()); - } - - public static boolean signUpdateExists() { - try { - return getMinecraftClass("PacketPlayOutUpdateSign") != null; - } catch (RuntimeException ex) { - return false; - } - } - - public static Class getNonNullListClass() { - return getMinecraftClass("NonNullList"); - } - - public static Class getCraftSoundClass() { - return getCraftBukkitClass("CraftSound"); - } - - // ---- ItemStack conversions - private static Object itemStackAir = null; - private static Method asNMSCopy = null; - private static Method asCraftMirror = null; - - private static Boolean nullEnforced = null; - private static Method isEmpty = null; - - /** - * Retrieves the Bukkit equivalent of a NMS ItemStack. This method should - * preserve NBT data and will never return null when supplied with a valid - * ItemStack. Empty ItemStacks are treated as AIR. - * - * @param generic NMS ItemStack - * @return The Bukkit equivalent - */ - public static ItemStack getBukkitItemStack(Object generic) { - if (generic == null) { - // Convert null to AIR - 1.11 behavior - return new ItemStack(Material.AIR); - } - - if (generic instanceof ItemStack) { - ItemStack bukkit = (ItemStack) generic; - - // They're probably looking for the CraftItemStack - // If it's one already our work is done - if (is(getCraftItemStackClass(), generic)) { - return bukkit; - } - - // If not, convert it to one - Object nmsStack = getMinecraftItemStack((ItemStack) generic); - return getBukkitItemStack(nmsStack); - } - - if (!is(getItemStackClass(), generic)) { - // We can't do anything with non-ItemStacks - throw new IllegalArgumentException(generic + " is not an ItemStack!"); - } - - try { - // Check null enforcement - 1.11 behavior - if (nullEnforced == null) { - isEmpty = getItemStackClass().getMethod("isEmpty"); - nullEnforced = true; - } - - if (nullEnforced) { - if ((boolean) isEmpty.invoke(generic)) { - return new ItemStack(Material.AIR); - } - } - } catch (ReflectiveOperationException ex) { - nullEnforced = false; - } - - if (asCraftMirror == null) { - try { - asCraftMirror = getCraftItemStackClass().getMethod("asCraftMirror", getItemStackClass()); - } catch (ReflectiveOperationException ex) { - throw new RuntimeException("Failed to obtain CraftItemStack.asCraftMirror", ex); - } - } - - try { - // Convert to a craft mirror to preserve NBT data - return (ItemStack) asCraftMirror.invoke(nullEnforced, generic); - } catch (ReflectiveOperationException ex) { - throw new RuntimeException("Failed to obtain craft mirror of " + generic, ex); - } - } - - /** - * Retrieves the NMS equivalent of a Bukkit ItemStack. This method will - * never return null and should preserve NBT data. Null inputs are treated - * as empty (AIR) ItemStacks. - * - * @param specific Bukkit ItemStack - * @return The NMS equivalent - */ - public static Object getMinecraftItemStack(ItemStack specific) { - if (asNMSCopy == null) { - try { - asNMSCopy = getCraftItemStackClass().getMethod("asNMSCopy", ItemStack.class); - } catch (ReflectiveOperationException ex) { - throw new RuntimeException("Failed to obtain CraftItemStack.asNMSCopy", ex); - } - } - - if (is(getCraftItemStackClass(), specific)) { - // If it's already a CraftItemStack, use its handle - Object unwrapped = new BukkitUnwrapper().unwrapItem(specific); - if (unwrapped != null) { - return unwrapped; - } else { - if (itemStackAir == null) { - // Easiest way to get the Material.AIR ItemStack? - itemStackAir = getMinecraftItemStack(new ItemStack(Material.AIR)); - } - return itemStackAir; - } - } - - try { - // If not, grab a NMS copy - return asNMSCopy.invoke(null, specific); - } catch (ReflectiveOperationException ex) { - throw new RuntimeException("Failed to make NMS copy of " + specific, ex); - } - } - - /** - * Retrieve the given class by name. - * @param className - name of the class. - * @return The class. - */ - private static Class getClass(String className) { - try { - return MinecraftReflection.class.getClassLoader().loadClass(className); - } catch (ClassNotFoundException e) { - throw new RuntimeException("Cannot find class " + className, e); - } - } - - /** - * Retrieve the class object of a specific CraftBukkit class. - * @param className - the specific CraftBukkit class. - * @return Class object. - * @throws RuntimeException If we are unable to find the given class. - */ - public static Class getCraftBukkitClass(String className) { - if (craftbukkitPackage == null) - craftbukkitPackage = new CachedPackage(getCraftBukkitPackage(), getClassSource()); - return craftbukkitPackage.getPackageClass(className) - .orElseThrow(() -> new RuntimeException("Failed to find CraftBukkit class: " + className)); - } - - /** - * Retrieve the class object of a specific Minecraft class. - * @param className - the specific Minecraft class. - * @return Class object. - * @throws RuntimeException If we are unable to find the given class. - */ - public static Class getMinecraftClass(String className) { - if (minecraftPackage == null) - minecraftPackage = new CachedPackage(getMinecraftPackage(), getClassSource()); - return minecraftPackage.getPackageClass(className) - .orElseThrow(() -> new RuntimeException("Failed to find NMS class: " + className)); - } - - private static Class getNullableNMS(String className) { - if (minecraftPackage == null) - minecraftPackage = new CachedPackage(getMinecraftPackage(), getClassSource()); - return minecraftPackage.getPackageClass(className).orElse(null); - } - - /** - * Set the class object for the specific Minecraft class. - * @param className - name of the Minecraft class. - * @param clazz - the new class object. - * @return The provided clazz object. - */ - private static Class setMinecraftClass(String className, Class clazz) { - if (minecraftPackage == null) - minecraftPackage = new CachedPackage(getMinecraftPackage(), getClassSource()); - minecraftPackage.setPackageClass(className, clazz); - return clazz; - } - - /** - * Retrieve the current class source. - * @return The class source. - */ - private static ClassSource getClassSource() { - ErrorReporter reporter = ProtocolLibrary.getErrorReporter(); - - // Lazy pattern again - if (classSource == null) { - // Attempt to use MCPC - try { - return classSource = new RemappedClassSource().initialize(); - } catch (RemapperUnavailableException e) { - if (e.getReason() != Reason.MCPC_NOT_PRESENT) - reporter.reportWarning(MinecraftReflection.class, Report.newBuilder(REPORT_CANNOT_FIND_MCPC_REMAPPER)); - } catch (Exception e) { - reporter.reportWarning(MinecraftReflection.class, Report.newBuilder(REPORT_CANNOT_LOAD_CPC_REMAPPER)); - } - - // Just use the default class loader - classSource = ClassSource.fromClassLoader(); - } - - return classSource; - } - - /** - * Retrieve the first class that matches a specified Minecraft name. - * @param className - the specific Minecraft class. - * @param aliases - alternative names for this Minecraft class. - * @return Class object. - * @throws RuntimeException If we are unable to find any of the given classes. - */ - public static Class getMinecraftClass(String className, String... aliases) { - try { - // Try the main class first - return getMinecraftClass(className); - } catch (RuntimeException e) { - Class success = null; - - // Try every alias too - for (String alias : aliases) { - try { - success = getMinecraftClass(alias); - break; - } catch (RuntimeException e1) { - // e1.printStackTrace(); - } - } - - if (success != null) { - // Save it for later - minecraftPackage.setPackageClass(className, success); - return success; - } else { - // Hack failed - throw new RuntimeException(String.format("Unable to find %s (%s)", className, Joiner.on(", ").join(aliases))); - } - } - } - - /** - * Retrieve the class object of a specific Minecraft library class. - * @param className - the specific library Minecraft class. - * @return Class object. - * @throws RuntimeException If we are unable to find the given class. - */ - public static Class getMinecraftLibraryClass(String className) { - if (libraryPackage == null) - libraryPackage = new CachedPackage("", getClassSource()); - return libraryPackage.getPackageClass(className) - .orElseThrow(() -> new RuntimeException("Failed to find class: " + className)); - } - - /** - * Set the class object for the specific library class. - * @param className - name of the Minecraft library class. - * @param clazz - the new class object. - * @return The provided clazz object. - */ - private static Class setMinecraftLibraryClass(String className, Class clazz) { - if (libraryPackage == null) - libraryPackage = new CachedPackage("", getClassSource()); - libraryPackage.setPackageClass(className, clazz); - return clazz; - } - - /** - * Dynamically retrieve the NetworkManager name. - * @return Name of the NetworkManager class. - */ - public static String getNetworkManagerName() { - return getNetworkManagerClass().getSimpleName(); - } - - /** - * Dynamically retrieve the name of the current NetLoginHandler. - * @return Name of the NetLoginHandler class. - */ - public static String getNetLoginHandlerName() { - return getNetLoginHandlerClass().getSimpleName(); - } - - /** - * Retrieve an instance of the packet data serializer wrapper. - * @param buffer - the buffer. - * @return The instance. - */ - public static Object getPacketDataSerializer(Object buffer) { - Class packetSerializer = getPacketDataSerializerClass(); - - try { - return packetSerializer.getConstructor(getByteBufClass()).newInstance(buffer); - } catch (Exception e) { - throw new RuntimeException("Cannot construct packet serializer.", e); - } - } -} +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * 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.utility; + +import java.io.DataInputStream; +import java.io.DataOutput; +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentMap; +import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.annotation.Nonnull; + +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.Server; +import org.bukkit.inventory.ItemStack; + +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.ProtocolLibrary; +import com.comphenix.protocol.ProtocolLogger; +import com.comphenix.protocol.error.ErrorReporter; +import com.comphenix.protocol.error.Report; +import com.comphenix.protocol.error.ReportType; +import com.comphenix.protocol.injector.BukkitUnwrapper; +import com.comphenix.protocol.injector.packet.PacketRegistry; +import com.comphenix.protocol.reflect.ClassAnalyser; +import com.comphenix.protocol.reflect.ClassAnalyser.AsmMethod; +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.accessors.MethodAccessor; +import com.comphenix.protocol.reflect.fuzzy.AbstractFuzzyMatcher; +import com.comphenix.protocol.reflect.fuzzy.FuzzyClassContract; +import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract; +import com.comphenix.protocol.reflect.fuzzy.FuzzyMatchers; +import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; +import com.comphenix.protocol.utility.RemappedClassSource.RemapperUnavailableException; +import com.comphenix.protocol.utility.RemappedClassSource.RemapperUnavailableException.Reason; +import com.comphenix.protocol.wrappers.nbt.NbtFactory; +import com.comphenix.protocol.wrappers.nbt.NbtType; +import com.google.common.base.Joiner; +import com.google.common.collect.Maps; + +/** + * Methods and constants specifically used in conjuction with reflecting Minecraft object. + * + * @author Kristian + */ +public class MinecraftReflection { + public static final ReportType REPORT_CANNOT_FIND_MCPC_REMAPPER = new ReportType("Cannot find MCPC/Cauldron remapper."); + public static final ReportType REPORT_CANNOT_LOAD_CPC_REMAPPER = new ReportType("Unable to load MCPC/Cauldron remapper."); + public static final ReportType REPORT_NON_CRAFTBUKKIT_LIBRARY_PACKAGE = new ReportType("Cannot find standard Minecraft library location. Assuming MCPC/Cauldron."); + + /** + * Regular expression that matches a canonical Java class. + */ + private static final String CANONICAL_REGEX = "(\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*\\.)+\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"; + + /** + * Regular expression that matches a Minecraft object. + *

+ * Replaced by the method {@link #getMinecraftObjectRegex()}. + */ + @Deprecated + public static final String MINECRAFT_OBJECT = "net\\.minecraft\\." + CANONICAL_REGEX; + + /** + * Regular expression computed dynamically. + */ + private static String DYNAMIC_PACKAGE_MATCHER = null; + + /** + * The Entity package in Forge 1.5.2 + */ + private static final String FORGE_ENTITY_PACKAGE = "net.minecraft.entity"; + + /** + * The package name of all the classes that belongs to the native code in Minecraft. + */ + private static String MINECRAFT_PREFIX_PACKAGE = "net.minecraft.server"; + + /** + * Represents a regular expression that will match the version string in a package: + * org.bukkit.craftbukkit.v1_6_R2 -> v1_6_R2 + */ + private static final Pattern PACKAGE_VERSION_MATCHER = Pattern.compile(".*\\.(v\\d+_\\d+_\\w*\\d+)"); + + private static String MINECRAFT_FULL_PACKAGE = null; + private static String CRAFTBUKKIT_PACKAGE = null; + + // Package private for the purpose of unit testing + static CachedPackage minecraftPackage; + static CachedPackage craftbukkitPackage; + static CachedPackage libraryPackage; + + // Matches classes + private static AbstractFuzzyMatcher> fuzzyMatcher; + + // The NMS version + private static String packageVersion; + + // Item stacks + /* private static Method craftNMSMethod; + private static Method craftBukkitNMS; + private static Method craftBukkitOBC; + private static boolean craftItemStackFailed; + + private static Constructor craftNMSConstructor; + private static Constructor craftBukkitConstructor; */ + + // net.minecraft.server + private static Class itemStackArrayClass; + + // Cache of getBukkitEntity + private static ConcurrentMap, MethodAccessor> getBukkitEntityCache = Maps.newConcurrentMap(); + + // The current class source + private static ClassSource classSource; + + /** + * Whether or not we're currently initializing the reflection handler. + */ + private static boolean initializing; + + // Whether or not we are using netty + private static Boolean cachedNetty; + private static Boolean cachedWatcherObject; + + private MinecraftReflection() { + // No need to make this constructable. + } + + /** + * Retrieve a regular expression that can match Minecraft package objects. + * @return Minecraft package matcher. + */ + public static String getMinecraftObjectRegex() { + if (DYNAMIC_PACKAGE_MATCHER == null) + getMinecraftPackage(); + return DYNAMIC_PACKAGE_MATCHER; + } + + /** + * Retrieve a abstract fuzzy class matcher for Minecraft objects. + * @return A matcher for Minecraft objects. + */ + public static AbstractFuzzyMatcher> getMinecraftObjectMatcher() { + if (fuzzyMatcher == null) + fuzzyMatcher = FuzzyMatchers.matchRegex(getMinecraftObjectRegex(), 50); + return fuzzyMatcher; + } + + /** + * Retrieve the name of the Minecraft server package. + * @return Full canonical name of the Minecraft server package. + */ + public static String getMinecraftPackage() { + // Speed things up + if (MINECRAFT_FULL_PACKAGE != null) + return MINECRAFT_FULL_PACKAGE; + if (initializing) + throw new IllegalStateException("Already initializing minecraft package!"); + initializing = true; + + Server craftServer = Bukkit.getServer(); + + // This server should have a "getHandle" method that we can use + if (craftServer != null) { + try { + // The return type will tell us the full package, regardless of formating + Class craftClass = craftServer.getClass(); + CRAFTBUKKIT_PACKAGE = getPackage(craftClass.getCanonicalName()); + + // Parse the package version + Matcher packageMatcher = PACKAGE_VERSION_MATCHER.matcher(CRAFTBUKKIT_PACKAGE); + if (packageMatcher.matches()) { + packageVersion = packageMatcher.group(1); + } else { + MinecraftVersion version = new MinecraftVersion(craftServer); + + // See if we need a package version + if (MinecraftVersion.SCARY_UPDATE.compareTo(version) <= 0) { + // Just assume R1 - it's probably fine + packageVersion = "v" + version.getMajor() + "_" + version.getMinor() + "_R1"; + ProtocolLogger.log(Level.WARNING, "Assuming package version: " + packageVersion); + } + } + + // Libigot patch + handleLibigot(); + + // Next, do the same for CraftEntity.getHandle() in order to get the correct Minecraft package + Class craftEntity = getCraftEntityClass(); + Method getHandle = craftEntity.getMethod("getHandle"); + + MINECRAFT_FULL_PACKAGE = getPackage(getHandle.getReturnType().getCanonicalName()); + + // Pretty important invariantt + if (!MINECRAFT_FULL_PACKAGE.startsWith(MINECRAFT_PREFIX_PACKAGE)) { + // See if we got the Forge entity package + if (MINECRAFT_FULL_PACKAGE.equals(FORGE_ENTITY_PACKAGE)) { + // USe the standard NMS versioned package + MINECRAFT_FULL_PACKAGE = CachedPackage.combine(MINECRAFT_PREFIX_PACKAGE, packageVersion); + } else { + // Assume they're the same instead + MINECRAFT_PREFIX_PACKAGE = MINECRAFT_FULL_PACKAGE; + } + + // The package is usualy flat, so go with that assumption + String matcher = + (MINECRAFT_PREFIX_PACKAGE.length() > 0 ? + Pattern.quote(MINECRAFT_PREFIX_PACKAGE + ".") : "") + CANONICAL_REGEX; + + // We'll still accept the default location, however + setDynamicPackageMatcher("(" + matcher + ")|(" + MINECRAFT_OBJECT + ")"); + + } else { + // Use the standard matcher + setDynamicPackageMatcher(MINECRAFT_OBJECT); + } + + return MINECRAFT_FULL_PACKAGE; + + } catch (SecurityException e) { + throw new RuntimeException("Security violation. Cannot get handle method.", e); + } catch (NoSuchMethodException e) { + throw new IllegalStateException("Cannot find getHandle() method on server. Is this a modified CraftBukkit version?", e); + } finally { + initializing = false; + } + + } else { + initializing = false; + throw new IllegalStateException("Could not find Bukkit. Is it running?"); + } + } + + /** + * Retrieve the package version of the underlying CraftBukkit server. + * @return The package version, or NULL if not applicable (before 1.4.6). + */ + public static String getPackageVersion() { + getMinecraftPackage(); + return packageVersion; + } + + /** + * Update the dynamic package matcher. + * @param regex - the Minecraft package regex. + */ + private static void setDynamicPackageMatcher(String regex) { + DYNAMIC_PACKAGE_MATCHER = regex; + + // Ensure that the matcher is regenerated + fuzzyMatcher = null; + } + + // Patch for Libigot + private static void handleLibigot() { + try { + getCraftEntityClass(); + } catch (RuntimeException e) { + // Try reverting the package to the old format + craftbukkitPackage = null; + CRAFTBUKKIT_PACKAGE = "org.bukkit.craftbukkit"; + + // This might fail too + getCraftEntityClass(); + } + } + + /** + * Used during debugging and testing. + * @param minecraftPackage - the current Minecraft package. + * @param craftBukkitPackage - the current CraftBukkit package. + */ + public static void setMinecraftPackage(String minecraftPackage, String craftBukkitPackage) { + MINECRAFT_FULL_PACKAGE = minecraftPackage; + CRAFTBUKKIT_PACKAGE = craftBukkitPackage; + + // Make sure it exists + if (getMinecraftServerClass() == null) { + throw new IllegalArgumentException("Cannot find MinecraftServer for package " + minecraftPackage); + } + + // Standard matcher + setDynamicPackageMatcher(MINECRAFT_OBJECT); + } + + /** + * Retrieve the name of the root CraftBukkit package. + * @return Full canonical name of the root CraftBukkit package. + */ + public static String getCraftBukkitPackage() { + // Ensure it has been initialized + if (CRAFTBUKKIT_PACKAGE == null) + getMinecraftPackage(); + return CRAFTBUKKIT_PACKAGE; + } + + /** + * Retrieve the package name from a given canonical Java class name. + * @param fullName - full Java class name. + * @return The package name. + */ + private static String getPackage(String fullName) { + int index = fullName.lastIndexOf("."); + + if (index > 0) + return fullName.substring(0, index); + else + return ""; // Default package + } + + /** + * Dynamically retrieve the Bukkit entity from a given entity. + * @param nmsObject - the NMS entity. + * @return A bukkit entity. + * @throws RuntimeException If we were unable to retrieve the Bukkit entity. + */ + public static Object getBukkitEntity(Object nmsObject) { + if (nmsObject == null) + return null; + + // We will have to do this dynamically, unfortunately + try { + Class clazz = nmsObject.getClass(); + MethodAccessor accessor = getBukkitEntityCache.get(clazz); + + if (accessor == null) { + MethodAccessor created = Accessors.getMethodAccessor(clazz, "getBukkitEntity"); + accessor = getBukkitEntityCache.putIfAbsent(clazz, created); + + // We won the race + if (accessor == null) { + accessor = created; + } + } + return accessor.invoke(nmsObject); + } catch (Exception e) { + throw new IllegalArgumentException("Cannot get Bukkit entity from " + nmsObject, e); + } + } + + /** + * Determine if a given object can be found within the package net.minecraft.server. + * @param obj - the object to test. + * @return TRUE if it can, FALSE otherwise. + */ + public static boolean isMinecraftObject(@Nonnull Object obj) { + if (obj == null) + return false; + + // Doesn't matter if we don't check for the version here + return obj.getClass().getName().startsWith(MINECRAFT_PREFIX_PACKAGE); + } + + /** + * Determine if the given class is found within the package net.minecraft.server, or any equivalent package. + * @param clazz - the class to test. + * @return TRUE if it can, FALSE otherwise. + */ + public static boolean isMinecraftClass(@Nonnull Class clazz) { + if (clazz == null) + throw new IllegalArgumentException("clazz cannot be NULL."); + + return getMinecraftObjectMatcher().isMatch(clazz, null); + } + + /** + * Determine if a given object is found in net.minecraft.server, and has the given name. + * @param obj - the object to test. + * @param className - the class name to test. + * @return TRUE if it can, FALSE otherwise. + */ + public static boolean isMinecraftObject(@Nonnull Object obj, String className) { + if (obj == null) + return false; + + String javaName = obj.getClass().getName(); + return javaName.startsWith(MINECRAFT_PREFIX_PACKAGE) && javaName.endsWith(className); + } + + /** + * Determine if a given Object is compatible with a given Class. That is, + * whether or not the Object is an instance of that Class or one of its + * subclasses. If either is null, false is returned. + * + * @param clazz Class to test for, may be null + * @param object the Object to test, may be null + * @return True if it is, false if not + * @see Class#isAssignableFrom(Class) + */ + public static boolean is(Class clazz, Object object) { + if (clazz == null || object == null) { + return false; + } + + return clazz.isAssignableFrom(object.getClass()); + } + + /** + * Equivalent to {@link #is(Class, Object)} but we don't call getClass again + */ + public static boolean is(Class clazz, Class test) { + if (clazz == null || test == null) { + return false; + } + + return clazz.isAssignableFrom(test); + } + + /** + * Determine if a given object is a ChunkPosition. + * @param obj - the object to test. + * @return TRUE if it can, FALSE otherwise. + */ + public static boolean isChunkPosition(Object obj) { + return is(getChunkPositionClass(), obj); + } + + /** + * Determine if a given object is a BlockPosition. + * @param obj - the object to test. + * @return TRUE if it can, FALSE otherwise. + */ + public static boolean isBlockPosition(Object obj) { + return is(getBlockPositionClass(), obj); + } + + /** + * Determine if the given object is an NMS ChunkCoordIntPar. + * @param obj - the object. + * @return TRUE if it can, FALSE otherwise. + */ + public static boolean isChunkCoordIntPair(Object obj) { + return is(getChunkCoordIntPair(), obj); + } + + /** + * Determine if a given object is a ChunkCoordinate. + * @param obj - the object to test. + * @return TRUE if it can, FALSE otherwise. + */ + public static boolean isChunkCoordinates(Object obj) { + return is(getChunkCoordinatesClass(), obj); + } + + /** + * Determine if the given object is actually a Minecraft packet. + * @param obj - the given object. + * @return TRUE if it is, FALSE otherwise. + */ + public static boolean isPacketClass(Object obj) { + return is(getPacketClass(), obj); + } + + /** + * Determine if the given object is a NetLoginHandler (PendingConnection) + * @param obj - the given object. + * @return TRUE if it is, FALSE otherwise. + */ + public static boolean isLoginHandler(Object obj) { + return is(getNetLoginHandlerClass(), obj); + } + + /** + * Determine if the given object is assignable to a NetServerHandler (PlayerConnection) + * @param obj - the given object. + * @return TRUE if it is, FALSE otherwise. + */ + public static boolean isServerHandler(Object obj) { + return is(getPlayerConnectionClass(), obj); + } + + /** + * Determine if the given object is actually a Minecraft packet. + * @param obj - the given object. + * @return TRUE if it is, FALSE otherwise. + */ + public static boolean isMinecraftEntity(Object obj) { + return is(getEntityClass(), obj); + } + + /** + * Determine if the given object is a NMS ItemStack. + * @param value - the given object. + * @return TRUE if it is, FALSE otherwise. + */ + public static boolean isItemStack(Object value) { + return is(getItemStackClass(), value); + } + + /** + * Determine if the given object is a CraftPlayer class. + * @param value - the given object. + * @return TRUE if it is, FALSE otherwise. + */ + public static boolean isCraftPlayer(Object value) { + return is(getCraftPlayerClass(), value); + } + + /** + * Determine if the given object is a Minecraft player entity. + * @param obj - the given object. + * @return TRUE if it is, FALSE otherwise. + */ + public static boolean isMinecraftPlayer(Object obj) { + return is(getEntityPlayerClass(), obj); + } + + /** + * Determine if the given object is a watchable object. + * @param obj - the given object. + * @return TRUE if it is, FALSE otherwise. + */ + public static boolean isWatchableObject(Object obj) { + return is(getWatchableObjectClass(), obj); + } + + /** + * Determine if the given object is a data watcher object. + * @param obj - the given object. + * @return TRUE if it is, FALSE otherwise. + */ + public static boolean isDataWatcher(Object obj) { + return is(getDataWatcherClass(), obj); + } + + /** + * Determine if the given object is an IntHashMap object. + * @param obj - the given object. + * @return TRUE if it is, FALSE otherwise. + */ + public static boolean isIntHashMap(Object obj) { + return is(getIntHashMapClass(), obj); + } + + /** + * Determine if the given object is a CraftItemStack instancey. + * @param obj - the given object. + * @return TRUE if it is, FALSE otherwise. + */ + public static boolean isCraftItemStack(Object obj) { + return is(getCraftItemStackClass(), obj); + } + + /** + * Retrieve the EntityPlayer (NMS) class. + * @return The entity class. + */ + public static Class getEntityPlayerClass() { + try { + return getMinecraftClass("EntityPlayer"); + } catch (RuntimeException e) { + try { + // Grab CraftPlayer's handle + Method getHandle = FuzzyReflection + .fromClass(getCraftBukkitClass("entity.CraftPlayer")) + .getMethodByName("getHandle"); + + // EntityPlayer is the return type + return setMinecraftClass("EntityPlayer", getHandle.getReturnType()); + } catch (IllegalArgumentException e1) { + throw new RuntimeException("Could not find EntityPlayer class.", e1); + } + } + } + + /** + * Retrieve the EntityHuman class. + * @return The entity human class. + */ + public static Class getEntityHumanClass() { + // Assume its the direct superclass + return getEntityPlayerClass().getSuperclass(); + } + + /** + * Retrieve the GameProfile class in 1.7.2 and later. + * + * @return The game profile class. + * @throws IllegalStateException If we are running 1.6.4 or earlier. + */ + public static Class getGameProfileClass() { + if (!isUsingNetty()) + throw new IllegalStateException("GameProfile does not exist in version 1.6.4 and earlier."); + + try { + return getClass("com.mojang.authlib.GameProfile"); + } catch (Throwable ex) { + try { + return getClass("net.minecraft.util.com.mojang.authlib.GameProfile"); + } catch (Throwable ex1) { + FuzzyReflection reflection = FuzzyReflection.fromClass(PacketType.Login.Client.START.getPacketClass(), true); + FuzzyFieldContract contract = FuzzyFieldContract.newBuilder() + .banModifier(Modifier.STATIC) + .typeMatches(FuzzyMatchers.matchRegex("(.*)(GameProfile)", 1)) + .build(); + return reflection.getField(contract).getType(); + } + } + } + + /** + * Retrieve the entity (NMS) class. + * @return The entity class. + */ + public static Class getEntityClass() { + try { + return getMinecraftClass("Entity"); + } catch (RuntimeException e) { + return fallbackMethodReturn("Entity", "entity.CraftEntity", "getHandle"); + } + } + + /** + * Retrieve the CraftChatMessage in Minecraft 1.7.2. + * @return The CraftChatMessage class. + */ + public static Class getCraftChatMessage() { + return getCraftBukkitClass("util.CraftChatMessage"); + } + + /** + * Retrieve the WorldServer (NMS) class. + * @return The WorldServer class. + */ + public static Class getWorldServerClass() { + try { + return getMinecraftClass("WorldServer"); + } catch (RuntimeException e) { + return fallbackMethodReturn("WorldServer", "CraftWorld", "getHandle"); + } + } + + /** + * Retrieve the World (NMS) class. + * @return The world class. + */ + public static Class getNmsWorldClass() { + try { + return getMinecraftClass("World"); + } catch (RuntimeException e) { + return setMinecraftClass("World", getWorldServerClass().getSuperclass()); + } + } + + /** + * Fallback on the return value of a named method in order to get a NMS class. + * @param nmsClass - the expected name of the Minecraft class. + * @param craftClass - a CraftBukkit class to look at. + * @param methodName - the method we will use. + * @return The return value of this method, which will be saved to the package cache. + */ + private static Class fallbackMethodReturn(String nmsClass, String craftClass, String methodName) { + Class result = FuzzyReflection.fromClass(getCraftBukkitClass(craftClass)). + getMethodByName(methodName).getReturnType(); + + // Save the result + return setMinecraftClass(nmsClass, result); + } + + /** + * Retrieve the packet class. + * @return The packet class. + */ + public static Class getPacketClass() { + try { + return getMinecraftClass("Packet"); + } catch (RuntimeException e) { + FuzzyClassContract paketContract = null; + + // What kind of class we're looking for (sanity check) + if (isUsingNetty()) { + paketContract = FuzzyClassContract.newBuilder(). + method(FuzzyMethodContract.newBuilder(). + parameterDerivedOf(getByteBufClass()). + returnTypeVoid()). + method(FuzzyMethodContract.newBuilder(). + parameterDerivedOf(getByteBufClass(), 0). + parameterExactType(byte[].class, 1). + returnTypeVoid()). + build(); + } else { + paketContract = FuzzyClassContract.newBuilder(). + field(FuzzyFieldContract.newBuilder(). + typeDerivedOf(Map.class). + requireModifier(Modifier.STATIC)). + field(FuzzyFieldContract.newBuilder(). + typeDerivedOf(Set.class). + requireModifier(Modifier.STATIC)). + method(FuzzyMethodContract.newBuilder(). + parameterSuperOf(DataInputStream.class). + returnTypeVoid()). + build(); + } + + // Select a method with one Minecraft object parameter + Method selected = FuzzyReflection.fromClass(getPlayerConnectionClass()). + getMethod(FuzzyMethodContract.newBuilder(). + parameterMatches(paketContract, 0). + parameterCount(1). + build() + ); + + // Save and return + Class clazz = getTopmostClass(selected.getParameterTypes()[0]); + return setMinecraftClass("Packet", clazz); + } + } + + public static Class getByteBufClass() { + try { + return getClass("io.netty.buffer.ByteBuf"); + } catch (Throwable ex) { + return getClass("net.minecraft.util.io.netty.buffer.ByteBuf"); + } + } + + /** + * Retrieve the EnumProtocol class in 1.7.2. + * @return The Enum protocol class. + */ + public static Class getEnumProtocolClass() { + try { + return getMinecraftClass("EnumProtocol"); + } catch (RuntimeException e) { + Method protocolMethod = FuzzyReflection.fromClass(getNetworkManagerClass()).getMethod( + FuzzyMethodContract.newBuilder(). + parameterCount(1). + parameterDerivedOf(Enum.class, 0). + build() + ); + return setMinecraftClass("EnumProtocol", protocolMethod.getParameterTypes()[0]); + } + } + + /** + * Retrieve the IChatBaseComponent class. + * @return The IChatBaseComponent. + */ + public static Class getIChatBaseComponentClass() { + try { + return getMinecraftClass("IChatBaseComponent"); + } catch (RuntimeException e) { + return setMinecraftClass("IChatBaseComponent", + Accessors.getMethodAccessor(getCraftChatMessage(), "fromString", String.class). + getMethod().getReturnType().getComponentType() + ); + } + } + + public static Class getIChatBaseComponentArrayClass() { + return getArrayClass(getIChatBaseComponentClass()); + } + + /** + * Retrieve the NMS chat component text class. + * @return The chat component class. + */ + public static Class getChatComponentTextClass() { + try { + return getMinecraftClass("ChatComponentText"); + } catch (RuntimeException e) { + try { + Method getScoreboardDisplayName = FuzzyReflection.fromClass(getEntityClass()). + getMethodByParameters("getScoreboardDisplayName", getIChatBaseComponentClass(), new Class[0]); + Class baseClass = getIChatBaseComponentClass(); + + for (AsmMethod method : ClassAnalyser.getDefault().getMethodCalls(getScoreboardDisplayName)) { + Class owner = method.getOwnerClass(); + + if (isMinecraftClass(owner) && baseClass.isAssignableFrom(owner)) { + return setMinecraftClass("ChatComponentText", owner); + } + } + } catch (Exception e1) { + throw new IllegalStateException("Cannot find ChatComponentText class.", e); + } + } + throw new IllegalStateException("Cannot find ChatComponentText class."); + } + + /** + * Attempt to find the ChatSerializer class. + * @return The serializer class. + * @throws IllegalStateException If the class could not be found or deduced. + */ + public static Class getChatSerializerClass() { + try { + return getMinecraftClass("ChatSerializer", "IChatBaseComponent$ChatSerializer"); + } catch (RuntimeException e) { + // TODO: Figure out a functional fallback + throw new IllegalStateException("Could not find ChatSerializer class.", e); + } + } + + /** + * Retrieve the ServerPing class in Minecraft 1.7.2. + * @return The ServerPing class. + */ + public static Class getServerPingClass() { + if (!isUsingNetty()) + throw new IllegalStateException("ServerPing is only supported in 1.7.2."); + + try { + return getMinecraftClass("ServerPing"); + } catch (RuntimeException e) { + Class statusServerInfo = PacketType.Status.Server.SERVER_INFO.getPacketClass(); + + // Find a server ping object + AbstractFuzzyMatcher> serverPingContract = FuzzyClassContract.newBuilder(). + field(FuzzyFieldContract.newBuilder().typeExact(String.class).build()). + field(FuzzyFieldContract.newBuilder().typeDerivedOf(getIChatBaseComponentClass()).build()). + build(). + and(getMinecraftObjectMatcher()); + + return setMinecraftClass("ServerPing", + FuzzyReflection.fromClass(statusServerInfo, true). + getField(FuzzyFieldContract.matchType(serverPingContract)).getType()); + } + } + + /** + * Retrieve the ServerPingServerData class in Minecraft 1.7.2. + * @return The ServerPingServerData class. + */ + public static Class getServerPingServerDataClass() { + if (!isUsingNetty()) + throw new IllegalStateException("ServerPingServerData is only supported in 1.7.2."); + + try { + return getMinecraftClass("ServerPingServerData", "ServerPing$ServerData"); + } catch (RuntimeException e) { + FuzzyReflection fuzzy = FuzzyReflection.fromClass(getServerPingClass(), true); + return setMinecraftClass("ServerPingServerData", fuzzy.getFieldByType("(.*)(ServerData)(.*)").getType()); + } + } + + /** + * Retrieve the ServerPingPlayerSample class in Minecraft 1.7.2. + * @return The ServerPingPlayerSample class. + */ + public static Class getServerPingPlayerSampleClass() { + if (!isUsingNetty()) + throw new IllegalStateException("ServerPingPlayerSample is only supported in 1.7.2."); + + try { + return getMinecraftClass("ServerPingPlayerSample", "ServerPing$ServerPingPlayerSample"); + } catch (RuntimeException e) { + Class serverPing = getServerPingClass(); + + // Find a server ping object + AbstractFuzzyMatcher> serverPlayerContract = FuzzyClassContract.newBuilder(). + constructor(FuzzyMethodContract.newBuilder().parameterExactArray(int.class, int.class)). + field(FuzzyFieldContract.newBuilder().typeExact(getArrayClass(getGameProfileClass()))). + build(). + and(getMinecraftObjectMatcher()); + + return setMinecraftClass("ServerPingPlayerSample", getTypeFromField(serverPing, serverPlayerContract)); + } + } + + /** + * Retrieve the type of the field whose type matches. + * @param clazz - the declaring type. + * @param fieldTypeMatcher - the field type matcher. + * @return The type of the field. + */ + private static Class getTypeFromField(Class clazz, AbstractFuzzyMatcher> fieldTypeMatcher) { + final FuzzyFieldContract fieldMatcher = FuzzyFieldContract.matchType(fieldTypeMatcher); + + return FuzzyReflection.fromClass(clazz, true). + getField(fieldMatcher).getType(); + } + + /** + * Determine if this Minecraft version is using Netty. + *

+ * Spigot is ignored in this consideration. + * @return TRUE if it does, FALSE otherwise. + */ + public static boolean isUsingNetty() { + if (cachedNetty == null) { + try { + cachedNetty = getEnumProtocolClass() != null; + } catch (RuntimeException e) { + cachedNetty = false; + } + } + return cachedNetty; + } + + /** + * Retrieve the least derived class, except Object. + * @return Least derived super class. + */ + private static Class getTopmostClass(Class clazz) { + while (true) { + Class superClass = clazz.getSuperclass(); + + if (superClass == Object.class || superClass == null) + return clazz; + else + clazz = superClass; + } + } + + /** + * Retrieve the MinecraftServer class. + * @return MinecraftServer class. + */ + public static Class getMinecraftServerClass() { + try { + return getMinecraftClass("MinecraftServer"); + } catch (RuntimeException e) { + useFallbackServer(); + + // Reset cache and try again + setMinecraftClass("MinecraftServer", null); + return getMinecraftClass("MinecraftServer"); + } + } + + /** + * Retrieve the NMS statistics class. + * @return The statistics class. + */ + public static Class getStatisticClass() { + // TODO: Implement fallback + return getMinecraftClass("Statistic"); + } + + /** + * Retrieve the NMS statistic list class. + * @return The statistic list class. + */ + public static Class getStatisticListClass() { + // TODO: Implement fallback + return getMinecraftClass("StatisticList"); + } + + /** + * Fallback method that can determine the MinecraftServer and the ServerConfigurationManager. + */ + private static void useFallbackServer() { + // Get the first constructor that matches CraftServer(MINECRAFT_OBJECT, ANY) + Constructor selected = FuzzyReflection.fromClass(getCraftBukkitClass("CraftServer")). + getConstructor(FuzzyMethodContract.newBuilder(). + parameterMatches(getMinecraftObjectMatcher(), 0). + parameterCount(2). + build() + ); + Class[] params = selected.getParameterTypes(); + + // Jackpot - two classes at the same time! + setMinecraftClass("MinecraftServer", params[0]); + setMinecraftClass("ServerConfigurationManager", params[1]); + } + + /** + * Retrieve the player list class (or ServerConfigurationManager), + * @return The player list class. + */ + public static Class getPlayerListClass() { + try { + return getMinecraftClass("ServerConfigurationManager", "PlayerList"); + } catch (RuntimeException e) { + useFallbackServer(); + + // Reset cache and try again + setMinecraftClass("ServerConfigurationManager", null); + return getMinecraftClass("ServerConfigurationManager"); + } + } + + /** + * Retrieve the NetLoginHandler class (or PendingConnection) + * @return The NetLoginHandler class. + */ + public static Class getNetLoginHandlerClass() { + try { + return getMinecraftClass("NetLoginHandler", "PendingConnection"); + } catch (RuntimeException e) { + Method selected = FuzzyReflection.fromClass(getPlayerListClass()). + getMethod(FuzzyMethodContract.newBuilder(). + parameterMatches( + FuzzyMatchers.matchExact(getEntityPlayerClass()).inverted(), 0 + ). + parameterExactType(String.class, 1). + parameterExactType(String.class, 2). + build() + ); + + // Save the pending connection reference + return setMinecraftClass("NetLoginHandler", selected.getParameterTypes()[0]); + } + } + + /** + * Retrieve the PlayerConnection class (or NetServerHandler) + * @return The PlayerConnection class. + */ + public static Class getPlayerConnectionClass() { + try { + return getMinecraftClass("PlayerConnection", "NetServerHandler"); + } catch (RuntimeException e) { + try { + // Use the player connection field + return setMinecraftClass("PlayerConnection", + FuzzyReflection.fromClass(getEntityPlayerClass()). + getFieldByType("playerConnection", getNetHandlerClass()).getType() + ); + + } catch (RuntimeException e1) { + // Okay, this must be on 1.7.2 + Class playerClass = getEntityPlayerClass(); + + FuzzyClassContract playerConnection = FuzzyClassContract.newBuilder(). + field(FuzzyFieldContract.newBuilder().typeExact(playerClass).build()). + constructor(FuzzyMethodContract.newBuilder(). + parameterCount(3). + parameterSuperOf(getMinecraftServerClass(), 0). + parameterSuperOf(getEntityPlayerClass(), 2). + build() + ). + method(FuzzyMethodContract.newBuilder(). + parameterCount(1). + parameterExactType(String.class). + build() + ). + build(); + + // If not, use duck typing + Class fieldType = FuzzyReflection.fromClass(getEntityPlayerClass(), true).getField( + FuzzyFieldContract.newBuilder().typeMatches(playerConnection).build() + ).getType(); + + return setMinecraftClass("PlayerConnection", fieldType); + } + } + } + + /** + * Retrieve the NetworkManager class or its interface. + * @return The NetworkManager class or its interface. + */ + public static Class getNetworkManagerClass() { + try { + return getMinecraftClass("INetworkManager", "NetworkManager"); + } catch (RuntimeException e) { + Constructor selected = FuzzyReflection.fromClass(getPlayerConnectionClass()). + getConstructor(FuzzyMethodContract.newBuilder(). + parameterSuperOf(getMinecraftServerClass(), 0). + parameterSuperOf(getEntityPlayerClass(), 2). + build() + ); + + // And we're done + return setMinecraftClass("INetworkManager", selected.getParameterTypes()[1]); + } + } + + /** + * Retrieve the NetHandler class (or Connection) + * @return The NetHandler class. + */ + public static Class getNetHandlerClass() { + try { + return getMinecraftClass("NetHandler", "Connection"); + } catch (RuntimeException e) { + // Try getting the net login handler + return setMinecraftClass("NetHandler", getNetLoginHandlerClass().getSuperclass()); + } + } + + /** + * Retrieve the NMS ItemStack class. + * @return The ItemStack class. + */ + public static Class getItemStackClass() { + try { + return getMinecraftClass("ItemStack"); + } catch (RuntimeException e) { + // Use the handle reference + return setMinecraftClass("ItemStack", + FuzzyReflection.fromClass(getCraftItemStackClass(), true).getFieldByName("handle").getType()); + } + } + + /** + * Retrieve the Block (NMS) class. + * @return Block (NMS) class. + */ + public static Class getBlockClass() { + try { + return getMinecraftClass("Block"); + } catch (RuntimeException e) { + FuzzyReflection reflect = FuzzyReflection.fromClass(getItemStackClass()); + Set> candidates = new HashSet>(); + + // Minecraft objects in the constructor + for (Constructor constructor : reflect.getConstructors()) { + for (Class clazz : constructor.getParameterTypes()) { + if (isMinecraftClass(clazz)) { + candidates.add(clazz); + } + } + } + + // Useful constructors + Method selected = + reflect.getMethod(FuzzyMethodContract.newBuilder(). + parameterMatches(FuzzyMatchers.matchAnyOf(candidates)). + returnTypeExact(float.class). + build()); + return setMinecraftClass("Block", selected.getParameterTypes()[0]); + } + } + + public static Class getItemClass() { + return getNullableNMS("Item"); + } + + public static Class getFluidTypeClass() { + return getNullableNMS("FluidType"); + } + + /** + * Retrieve the WorldType class. + * @return The WorldType class. + */ + public static Class getWorldTypeClass() { + try { + return getMinecraftClass("WorldType"); + } catch (RuntimeException e) { + // Get the first constructor that matches CraftServer(MINECRAFT_OBJECT, ANY) + Method selected = FuzzyReflection.fromClass(getMinecraftServerClass(), true). + getMethod(FuzzyMethodContract.newBuilder(). + parameterExactType(String.class, 0). + parameterExactType(String.class, 1). + parameterMatches(getMinecraftObjectMatcher()). + parameterExactType(String.class, 4). + parameterCount(5). + build() + ); + return setMinecraftClass("WorldType", selected.getParameterTypes()[3]); + } + } + + /** + * Retrieve the DataWatcher class. + * @return The DataWatcher class. + */ + public static Class getDataWatcherClass() { + try { + return getMinecraftClass("DataWatcher"); + } catch (RuntimeException e) { + // Describe the DataWatcher + FuzzyClassContract dataWatcherContract = FuzzyClassContract.newBuilder(). + field(FuzzyFieldContract.newBuilder(). + requireModifier(Modifier.STATIC). + typeDerivedOf(Map.class)). + field(FuzzyFieldContract.newBuilder(). + banModifier(Modifier.STATIC). + typeDerivedOf(Map.class)). + method(FuzzyMethodContract.newBuilder(). + parameterExactType(int.class). + parameterExactType(Object.class). + returnTypeVoid()). + build(); + FuzzyFieldContract fieldContract = FuzzyFieldContract.newBuilder(). + typeMatches(dataWatcherContract). + build(); + + // Get such a field and save the result + return setMinecraftClass("DataWatcher", + FuzzyReflection.fromClass(getEntityClass(), true). + getField(fieldContract). + getType() + ); + } + } + + /** + * Retrieves the ChunkPosition class. + * + * @return The ChunkPosition class. + */ + public static Class getChunkPositionClass() { + try { + return getMinecraftClass("ChunkPosition"); + } catch (RuntimeException e) { + return null; + } + } + + /** + * Retrieves the BlockPosition class. + * + * @return The BlockPosition class. + */ + public static Class getBlockPositionClass() { + try { + return getMinecraftClass("BlockPosition"); + } catch (RuntimeException e) { + try { + Class normalChunkGenerator = getCraftBukkitClass("generator.NormalChunkGenerator"); + + // BlockPosition findNearestMapFeature(World, String, BlockPosition) + FuzzyMethodContract selected = FuzzyMethodContract.newBuilder() + .banModifier(Modifier.STATIC) + .parameterMatches(getMinecraftObjectMatcher(), 0) + .parameterExactType(String.class, 1) + .parameterMatches(getMinecraftObjectMatcher(), 1) + .build(); + + return setMinecraftClass("BlockPosition", + FuzzyReflection.fromClass(normalChunkGenerator).getMethod(selected).getReturnType()); + } catch (Throwable ex) { + return null; + } + } + } + + /** + * Retrieves the Vec3D class. + * @return The Vec3D class. + */ + public static Class getVec3DClass() { + try { + return getMinecraftClass("Vec3D"); + } catch (RuntimeException e) { + // TODO: Figure out a fuzzy field contract + return null; + } + } + + /** + * Retrieve the ChunkCoordinates class. + * @return The ChunkPosition class. + */ + public static Class getChunkCoordinatesClass() { + // TODO Figure out a fallback + try { + return getMinecraftClass("ChunkCoordinates"); + } catch (RuntimeException e) { + return null; + } + } + + /** + * Retrieve the ChunkCoordIntPair class. + * @return The ChunkCoordIntPair class. + */ + public static Class getChunkCoordIntPair() { + if (!isUsingNetty()) + throw new IllegalArgumentException("Not supported on 1.6.4 and older."); + + try { + return getMinecraftClass("ChunkCoordIntPair"); + } catch (RuntimeException e) { + Class packet = PacketRegistry.getPacketClassFromType(PacketType.Play.Server.MULTI_BLOCK_CHANGE); + + AbstractFuzzyMatcher> chunkCoordIntContract = FuzzyClassContract.newBuilder(). + field(FuzzyFieldContract.newBuilder(). + typeDerivedOf(int.class)). + field(FuzzyFieldContract.newBuilder(). + typeDerivedOf(int.class)). + method(FuzzyMethodContract.newBuilder(). + parameterExactArray(int.class). + returnDerivedOf( getChunkPositionClass() )). + build().and(getMinecraftObjectMatcher()); + + Field field = FuzzyReflection.fromClass(packet, true).getField( + FuzzyFieldContract.matchType(chunkCoordIntContract)); + return setMinecraftClass("ChunkCoordIntPair", field.getType()); + } + } + + /** + * Retrieve the WatchableObject class. + * @return The WatchableObject class. + * @deprecated Replaced by {@link #getDataWatcherItemClass()} + */ + @Deprecated + public static Class getWatchableObjectClass() { + return getDataWatcherItemClass(); + } + + /** + * Retrieve the DataWatcher Item class. + * @return The class + */ + public static Class getDataWatcherItemClass() { + try { + return getMinecraftClass("DataWatcher$Item", "DataWatcher$WatchableObject", "WatchableObject"); + } catch (RuntimeException e) { + Method selected = FuzzyReflection.fromClass(getDataWatcherClass(), true). + getMethod(FuzzyMethodContract.newBuilder(). + requireModifier(Modifier.STATIC). + parameterDerivedOf(isUsingNetty() ? getPacketDataSerializerClass() : DataOutput.class, 0). + parameterMatches(getMinecraftObjectMatcher(), 1). + build()); + + // Use the second parameter + return setMinecraftClass("DataWatcher$Item", selected.getParameterTypes()[1]); + } + } + + public static Class getDataWatcherObjectClass() { + try { + return getMinecraftClass("DataWatcherObject"); + } catch (RuntimeException ex) { + return null; + } + } + + public static boolean watcherObjectExists() { + if (cachedWatcherObject == null) { + try { + return cachedWatcherObject = getDataWatcherObjectClass() != null; + } catch (Throwable ex) { + return cachedWatcherObject = false; + } + } + + return cachedWatcherObject; + } + + public static Class getDataWatcherSerializerClass() { + // TODO Implement a fallback + return getMinecraftClass("DataWatcherSerializer"); + } + + public static Class getDataWatcherRegistryClass() { + // TODO Implement a fallback + return getMinecraftClass("DataWatcherRegistry"); + } + + public static Class getMinecraftKeyClass() { + // TODO Implement a fallback + return getMinecraftClass("MinecraftKey"); + } + + public static Class getMobEffectListClass() { + // TODO Implement a fallback + return getMinecraftClass("MobEffectList"); + } + + public static Class getSoundEffectClass() { + try { + return getMinecraftClass("SoundEffect"); + } catch (RuntimeException ex) { + FuzzyReflection fuzzy = FuzzyReflection.fromClass(PacketType.Play.Server.NAMED_SOUND_EFFECT.getPacketClass(), true); + Field field = fuzzy.getFieldByType("(.*)(Sound)(.*)"); + return setMinecraftClass("SoundEffect", field.getType()); + } + } + + /** + * Retrieve the ServerConnection abstract class. + * @return The ServerConnection class. + */ + public static Class getServerConnectionClass() { + try { + return getMinecraftClass("ServerConnection"); + } catch (RuntimeException e) { + Method selected = null; + FuzzyClassContract.Builder serverConnectionContract = FuzzyClassContract.newBuilder(). + constructor(FuzzyMethodContract.newBuilder(). + parameterExactType(getMinecraftServerClass()). + parameterCount(1)); + + if (isUsingNetty()) { + serverConnectionContract. + method(FuzzyMethodContract.newBuilder(). + parameterDerivedOf(InetAddress.class, 0). + parameterDerivedOf(int.class, 1). + parameterCount(2) + ); + + selected = FuzzyReflection.fromClass(getMinecraftServerClass()). + getMethod(FuzzyMethodContract.newBuilder(). + requireModifier(Modifier.PUBLIC). + returnTypeMatches(serverConnectionContract.build()). + build()); + + } else { + serverConnectionContract. + method(FuzzyMethodContract.newBuilder(). + parameterExactType(getPlayerConnectionClass())); + + selected = FuzzyReflection.fromClass(getMinecraftServerClass()). + getMethod(FuzzyMethodContract.newBuilder(). + requireModifier(Modifier.ABSTRACT). + returnTypeMatches(serverConnectionContract.build()). + build()); + } + + // Use the return type + return setMinecraftClass("ServerConnection", selected.getReturnType()); + } + } + + /** + * Retrieve the NBT base class. + * @return The NBT base class. + */ + public static Class getNBTBaseClass() { + try { + return getMinecraftClass("NBTBase"); + } catch (RuntimeException e) { + Class nbtBase = null; + + if (isUsingNetty()) { + FuzzyClassContract tagCompoundContract = FuzzyClassContract.newBuilder(). + field(FuzzyFieldContract.newBuilder(). + typeDerivedOf(Map.class)). + method(FuzzyMethodContract.newBuilder(). + parameterDerivedOf(DataOutput.class). + parameterCount(1)). + build(); + + Method selected = FuzzyReflection.fromClass(getPacketDataSerializerClass()). + getMethod(FuzzyMethodContract.newBuilder(). + banModifier(Modifier.STATIC). + parameterCount(1). + parameterMatches(tagCompoundContract). + returnTypeVoid(). + build() + ); + nbtBase = selected.getParameterTypes()[0].getSuperclass(); + + } else { + FuzzyClassContract tagCompoundContract = FuzzyClassContract.newBuilder(). + constructor(FuzzyMethodContract.newBuilder(). + parameterExactType(String.class). + parameterCount(1)). + field(FuzzyFieldContract.newBuilder(). + typeDerivedOf(Map.class)). + build(); + + Method selected = FuzzyReflection.fromClass(getPacketClass()). + getMethod(FuzzyMethodContract.newBuilder(). + requireModifier(Modifier.STATIC). + parameterSuperOf(DataInputStream.class). + parameterCount(1). + returnTypeMatches(tagCompoundContract). + build() + ); + nbtBase = selected.getReturnType().getSuperclass(); + } + + // That can't be correct + if (nbtBase == null || nbtBase.equals(Object.class)) { + throw new IllegalStateException("Unable to find NBT base class: " + nbtBase); + } + + // Use the return type here too + return setMinecraftClass("NBTBase", nbtBase); + } + } + + /** + * Retrieve the NBT read limiter class. + *

+ * This is only supported in 1.7.8 (released 2014) and higher. + * @return The NBT read limiter. + */ + public static Class getNBTReadLimiterClass() { + return getMinecraftClass("NBTReadLimiter"); + } + + /** + * Retrieve the NBT Compound class. + * @return The NBT Compond class. + */ + public static Class getNBTCompoundClass() { + try { + return getMinecraftClass("NBTTagCompound"); + } catch (RuntimeException e) { + return setMinecraftClass( + "NBTTagCompound", + NbtFactory.ofWrapper(NbtType.TAG_COMPOUND, "Test").getHandle().getClass() + ); + } + } + + /** + * Retrieve the EntityTracker (NMS) class. + * @return EntityTracker class. + */ + public static Class getEntityTrackerClass() { + try { + return getMinecraftClass("EntityTracker"); + } catch (RuntimeException e) { + FuzzyClassContract entityTrackerContract = FuzzyClassContract.newBuilder(). + field(FuzzyFieldContract.newBuilder(). + typeDerivedOf(Set.class)). + method(FuzzyMethodContract.newBuilder(). + parameterSuperOf(MinecraftReflection.getEntityClass()). + parameterCount(1). + returnTypeVoid()). + method(FuzzyMethodContract.newBuilder(). + parameterSuperOf(MinecraftReflection.getEntityClass(), 0). + parameterSuperOf(int.class, 1). + parameterSuperOf(int.class, 2). + parameterCount(3). + returnTypeVoid()). + build(); + + Field selected = FuzzyReflection.fromClass(MinecraftReflection.getWorldServerClass(), true). + getField(FuzzyFieldContract.newBuilder(). + typeMatches(entityTrackerContract). + build() + ); + + // Go by the defined type of this field + return setMinecraftClass("EntityTracker", selected.getType()); + } + } + + /** + * Retrieve the NetworkListenThread class (NMS). + *

+ * Note that this class was removed after Minecraft 1.3.1. + * @return NetworkListenThread class. + */ + public static Class getNetworkListenThreadClass() { + try { + return getMinecraftClass("NetworkListenThread"); + } catch (RuntimeException e) { + FuzzyClassContract networkListenContract = FuzzyClassContract.newBuilder(). + field(FuzzyFieldContract.newBuilder(). + typeDerivedOf(ServerSocket.class)). + field(FuzzyFieldContract.newBuilder(). + typeDerivedOf(Thread.class)). + field(FuzzyFieldContract.newBuilder(). + typeDerivedOf(List.class)). + method(FuzzyMethodContract.newBuilder(). + parameterExactType(getPlayerConnectionClass())). + build(); + + Field selected = FuzzyReflection.fromClass(MinecraftReflection.getMinecraftServerClass(), true). + getField(FuzzyFieldContract.newBuilder(). + typeMatches(networkListenContract). + build() + ); + + // Go by the defined type of this field + return setMinecraftClass("NetworkListenThread", selected.getType()); + } + } + + /** + * Retrieve the attribute snapshot class. + *

+ * This stores the final value of an attribute, along with all the associated computational steps. + * @return The attribute snapshot class. + */ + public static Class getAttributeSnapshotClass() { + try { + return getMinecraftClass("AttributeSnapshot", "PacketPlayOutUpdateAttributes$AttributeSnapshot"); + } catch (RuntimeException ex) { + try { + // It should be the parameter of a list in the update attributes packet + FuzzyReflection fuzzy = FuzzyReflection.fromClass(PacketType.Play.Server.UPDATE_ATTRIBUTES.getPacketClass(), true); + Field field = fuzzy.getFieldByType("attributes", Collection.class); + Type param = ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]; + return setMinecraftClass("AttributeSnapshot", (Class) param); + } catch (Throwable ex1) { + return getMinecraftClass("AttributeSnapshot"); + } + } + } + + /** + * Retrieve the IntHashMap class. + * @return IntHashMap class. + */ + public static Class getIntHashMapClass() { + try { + return getMinecraftClass("IntHashMap"); + } catch (RuntimeException e) { + final Class parent = getEntityTrackerClass(); + + // Expected structure of a IntHashMap + final FuzzyClassContract intHashContract = FuzzyClassContract.newBuilder(). + // add(int key, Object value) + method(FuzzyMethodContract.newBuilder(). + parameterCount(2). + parameterExactType(int.class, 0). + parameterExactType(Object.class, 1).requirePublic() + ). + // Object get(int key) + method(FuzzyMethodContract.newBuilder(). + parameterCount(1). + parameterExactType(int.class). + returnTypeExact(Object.class).requirePublic() + ). + // Finally, there should be an array of some kind + field(FuzzyFieldContract.newBuilder(). + typeMatches(FuzzyMatchers.matchArray(FuzzyMatchers.matchAll())) + ). + build(); + + final AbstractFuzzyMatcher intHashField = FuzzyFieldContract.newBuilder(). + typeMatches(getMinecraftObjectMatcher().and(intHashContract)). + build(); + + // Use the type of the first field that matches + return setMinecraftClass("IntHashMap", FuzzyReflection.fromClass(parent).getField(intHashField).getType()); + } + } + + /** + * Retrieve the attribute modifier class. + * @return Attribute modifier class. + */ + public static Class getAttributeModifierClass() { + try { + return getMinecraftClass("AttributeModifier"); + } catch (RuntimeException e) { + getAttributeSnapshotClass(); + + // Reset cache and try again + setMinecraftClass("AttributeModifier", null); + return getMinecraftClass("AttributeModifier"); + } + } + + /** + * Retrieve the net.minecraft.server.MobEffect class. + * @return The mob effect class. + */ + public static Class getMobEffectClass() { + try { + return getMinecraftClass("MobEffect"); + } catch (RuntimeException e) { + // It is the second parameter in Packet41MobEffect + Class packet = PacketRegistry.getPacketClassFromType(PacketType.Play.Server.ENTITY_EFFECT); + Constructor constructor = FuzzyReflection.fromClass(packet).getConstructor( + FuzzyMethodContract.newBuilder(). + parameterCount(2). + parameterExactType(int.class, 0). + parameterMatches(getMinecraftObjectMatcher(), 1). + build() + ); + return setMinecraftClass("MobEffect", constructor.getParameterTypes()[1]); + } + } + + /** + * Retrieve the packet data serializer class that overrides ByteBuf. + * @return The data serializer class. + */ + public static Class getPacketDataSerializerClass() { + try { + return getMinecraftClass("PacketDataSerializer"); + } catch (RuntimeException e) { + Class packet = getPacketClass(); + Method method = FuzzyReflection.fromClass(packet).getMethod( + FuzzyMethodContract.newBuilder(). + parameterCount(1). + parameterDerivedOf(getByteBufClass()). + returnTypeVoid(). + build() + ); + return setMinecraftClass("PacketDataSerializer", method.getParameterTypes()[0]); + } + } + + /** + * Retrieve the NBTCompressedStreamTools class. + * @return The NBTCompressedStreamTools class. + */ + public static Class getNbtCompressedStreamToolsClass() { + try { + return getMinecraftClass("NBTCompressedStreamTools"); + } catch (RuntimeException e) { + Class packetSerializer = getPacketDataSerializerClass(); + + // Get the write NBT compound method + Method writeNbt = FuzzyReflection.fromClass(packetSerializer). + getMethodByParameters("writeNbt", getNBTCompoundClass()); + + try { + // Now -- we inspect all the method calls within that method, and use the first foreign Minecraft class + for (AsmMethod method : ClassAnalyser.getDefault().getMethodCalls(writeNbt)) { + Class owner = method.getOwnerClass(); + + if (!packetSerializer.equals(owner) && isMinecraftClass(owner)) { + return setMinecraftClass("NBTCompressedStreamTools", owner); + } + } + } catch (Exception e1) { + throw new RuntimeException("Unable to analyse class.", e1); + } + throw new IllegalArgumentException("Unable to find NBTCompressedStreamTools."); + } + } + + /** + * Retrieve the NMS tile entity class. + * @return The tile entity class. + */ + public static Class getTileEntityClass() { + return getMinecraftClass("TileEntity"); + } + + /** + * Retrieve the Gson class used by Minecraft. + * @return The Gson class. + */ + public static Class getMinecraftGsonClass() { + try { + return getMinecraftLibraryClass("com.google.gson.Gson"); + } catch (RuntimeException e) { + Class match = FuzzyReflection.fromClass(PacketType.Status.Server.SERVER_INFO.getPacketClass(), true) + .getFieldByType("(.*)(google.gson.Gson)").getType(); + return setMinecraftLibraryClass("com.google.gson.Gson", match); + } + } + + /** + * Retrieve the ItemStack[] class. + * @return The ItemStack[] class. + */ + public static Class getItemStackArrayClass() { + if (itemStackArrayClass == null) + itemStackArrayClass = getArrayClass(getItemStackClass()); + return itemStackArrayClass; + } + + /** + * Retrieve the array class of a given component type. + * @param componentType - type of each element in the array. + * @return The class of the array. + */ + public static Class getArrayClass(Class componentType) { + // Bit of a hack, but it works + return Array.newInstance(componentType, 0).getClass(); + } + + /** + * Retrieve the CraftItemStack class. + * @return The CraftItemStack class. + */ + public static Class getCraftItemStackClass() { + return getCraftBukkitClass("inventory.CraftItemStack"); + } + + /** + * Retrieve the CraftPlayer class. + * @return CraftPlayer class. + */ + public static Class getCraftPlayerClass() { + return getCraftBukkitClass("entity.CraftPlayer"); + } + + /** + * Retrieve the CraftWorld class. + * @return The CraftWorld class. + */ + public static Class getCraftWorldClass() { + return getCraftBukkitClass("CraftWorld"); + } + + /** + * Retrieve the CraftEntity class. + * @return CraftEntity class. + */ + public static Class getCraftEntityClass() { + return getCraftBukkitClass("entity.CraftEntity"); + } + + /** + * Retrieve the CraftChatMessage introduced in 1.7.2 + * @return The CraftChatMessage class. + */ + public static Class getCraftMessageClass() { + return getCraftBukkitClass("util.CraftChatMessage"); + } + + /** + * Retrieve the PlayerInfoData class in 1.8. + * @return The PlayerInfoData class + */ + public static Class getPlayerInfoDataClass() { + return getMinecraftClass("PacketPlayOutPlayerInfo$PlayerInfoData", "PlayerInfoData"); + } + + /** + * Determine if the given object is a PlayerInfoData. + * @param obj - the given object. + * @return TRUE if it is, FALSE otherwise. + */ + public static boolean isPlayerInfoData(Object obj) { + return is(getPlayerInfoDataClass(), obj); + } + + /** + * Retrieve the IBlockData class in 1.8. + * @return The IBlockData class + */ + public static Class getIBlockDataClass() { + return getMinecraftClass("IBlockData"); + } + + /** + * Retrieve the MultiBlockChangeInfo class in 1.8. + * @return The MultiBlockChangeInfo class + */ + public static Class getMultiBlockChangeInfoClass() { + return getMinecraftClass("MultiBlockChangeInfo", "PacketPlayOutMultiBlockChange$MultiBlockChangeInfo"); + } + + /** + * Retrieve the MultiBlockChangeInfo array class in 1.8. + * @return The MultiBlockChangeInfo array class + */ + public static Class getMultiBlockChangeInfoArrayClass() { + return getArrayClass(getMultiBlockChangeInfoClass()); + } + + public static boolean signUpdateExists() { + try { + return getMinecraftClass("PacketPlayOutUpdateSign") != null; + } catch (RuntimeException ex) { + return false; + } + } + + public static Class getNonNullListClass() { + return getMinecraftClass("NonNullList"); + } + + public static Class getCraftSoundClass() { + return getCraftBukkitClass("CraftSound"); + } + + // ---- ItemStack conversions + private static Object itemStackAir = null; + private static Method asNMSCopy = null; + private static Method asCraftMirror = null; + + private static Boolean nullEnforced = null; + private static Method isEmpty = null; + + /** + * Retrieves the Bukkit equivalent of a NMS ItemStack. This method should + * preserve NBT data and will never return null when supplied with a valid + * ItemStack. Empty ItemStacks are treated as AIR. + * + * @param generic NMS ItemStack + * @return The Bukkit equivalent + */ + public static ItemStack getBukkitItemStack(Object generic) { + if (generic == null) { + // Convert null to AIR - 1.11 behavior + return new ItemStack(Material.AIR); + } + + if (generic instanceof ItemStack) { + ItemStack bukkit = (ItemStack) generic; + + // They're probably looking for the CraftItemStack + // If it's one already our work is done + if (is(getCraftItemStackClass(), generic)) { + return bukkit; + } + + // If not, convert it to one + Object nmsStack = getMinecraftItemStack((ItemStack) generic); + return getBukkitItemStack(nmsStack); + } + + if (!is(getItemStackClass(), generic)) { + // We can't do anything with non-ItemStacks + throw new IllegalArgumentException(generic + " is not an ItemStack!"); + } + + try { + // Check null enforcement - 1.11 behavior + if (nullEnforced == null) { + isEmpty = getItemStackClass().getMethod("isEmpty"); + nullEnforced = true; + } + + if (nullEnforced) { + if ((boolean) isEmpty.invoke(generic)) { + return new ItemStack(Material.AIR); + } + } + } catch (ReflectiveOperationException ex) { + nullEnforced = false; + } + + if (asCraftMirror == null) { + try { + asCraftMirror = getCraftItemStackClass().getMethod("asCraftMirror", getItemStackClass()); + } catch (ReflectiveOperationException ex) { + throw new RuntimeException("Failed to obtain CraftItemStack.asCraftMirror", ex); + } + } + + try { + // Convert to a craft mirror to preserve NBT data + return (ItemStack) asCraftMirror.invoke(nullEnforced, generic); + } catch (ReflectiveOperationException ex) { + throw new RuntimeException("Failed to obtain craft mirror of " + generic, ex); + } + } + + /** + * Retrieves the NMS equivalent of a Bukkit ItemStack. This method will + * never return null and should preserve NBT data. Null inputs are treated + * as empty (AIR) ItemStacks. + * + * @param specific Bukkit ItemStack + * @return The NMS equivalent + */ + public static Object getMinecraftItemStack(ItemStack specific) { + if (asNMSCopy == null) { + try { + asNMSCopy = getCraftItemStackClass().getMethod("asNMSCopy", ItemStack.class); + } catch (ReflectiveOperationException ex) { + throw new RuntimeException("Failed to obtain CraftItemStack.asNMSCopy", ex); + } + } + + if (is(getCraftItemStackClass(), specific)) { + // If it's already a CraftItemStack, use its handle + Object unwrapped = new BukkitUnwrapper().unwrapItem(specific); + if (unwrapped != null) { + return unwrapped; + } else { + if (itemStackAir == null) { + // Easiest way to get the Material.AIR ItemStack? + itemStackAir = getMinecraftItemStack(new ItemStack(Material.AIR)); + } + return itemStackAir; + } + } + + try { + // If not, grab a NMS copy + return asNMSCopy.invoke(null, specific); + } catch (ReflectiveOperationException ex) { + throw new RuntimeException("Failed to make NMS copy of " + specific, ex); + } + } + + /** + * Retrieve the given class by name. + * @param className - name of the class. + * @return The class. + */ + private static Class getClass(String className) { + try { + return MinecraftReflection.class.getClassLoader().loadClass(className); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Cannot find class " + className, e); + } + } + + /** + * Retrieve the class object of a specific CraftBukkit class. + * @param className - the specific CraftBukkit class. + * @return Class object. + * @throws RuntimeException If we are unable to find the given class. + */ + public static Class getCraftBukkitClass(String className) { + if (craftbukkitPackage == null) + craftbukkitPackage = new CachedPackage(getCraftBukkitPackage(), getClassSource()); + return craftbukkitPackage.getPackageClass(className) + .orElseThrow(() -> new RuntimeException("Failed to find CraftBukkit class: " + className)); + } + + /** + * Retrieve the class object of a specific Minecraft class. + * @param className - the specific Minecraft class. + * @return Class object. + * @throws RuntimeException If we are unable to find the given class. + */ + public static Class getMinecraftClass(String className) { + if (minecraftPackage == null) + minecraftPackage = new CachedPackage(getMinecraftPackage(), getClassSource()); + return minecraftPackage.getPackageClass(className) + .orElseThrow(() -> new RuntimeException("Failed to find NMS class: " + className)); + } + + static Class getNullableNMS(String className) { + if (minecraftPackage == null) + minecraftPackage = new CachedPackage(getMinecraftPackage(), getClassSource()); + return minecraftPackage.getPackageClass(className).orElse(null); + } + + /** + * Set the class object for the specific Minecraft class. + * @param className - name of the Minecraft class. + * @param clazz - the new class object. + * @return The provided clazz object. + */ + private static Class setMinecraftClass(String className, Class clazz) { + if (minecraftPackage == null) + minecraftPackage = new CachedPackage(getMinecraftPackage(), getClassSource()); + minecraftPackage.setPackageClass(className, clazz); + return clazz; + } + + /** + * Retrieve the current class source. + * @return The class source. + */ + private static ClassSource getClassSource() { + ErrorReporter reporter = ProtocolLibrary.getErrorReporter(); + + // Lazy pattern again + if (classSource == null) { + // Attempt to use MCPC + try { + return classSource = new RemappedClassSource().initialize(); + } catch (RemapperUnavailableException e) { + if (e.getReason() != Reason.MCPC_NOT_PRESENT) + reporter.reportWarning(MinecraftReflection.class, Report.newBuilder(REPORT_CANNOT_FIND_MCPC_REMAPPER)); + } catch (Exception e) { + reporter.reportWarning(MinecraftReflection.class, Report.newBuilder(REPORT_CANNOT_LOAD_CPC_REMAPPER)); + } + + // Just use the default class loader + classSource = ClassSource.fromClassLoader(); + } + + return classSource; + } + + /** + * Retrieve the first class that matches a specified Minecraft name. + * @param className - the specific Minecraft class. + * @param aliases - alternative names for this Minecraft class. + * @return Class object. + * @throws RuntimeException If we are unable to find any of the given classes. + */ + public static Class getMinecraftClass(String className, String... aliases) { + try { + // Try the main class first + return getMinecraftClass(className); + } catch (RuntimeException e) { + Class success = null; + + // Try every alias too + for (String alias : aliases) { + try { + success = getMinecraftClass(alias); + break; + } catch (RuntimeException e1) { + // e1.printStackTrace(); + } + } + + if (success != null) { + // Save it for later + minecraftPackage.setPackageClass(className, success); + return success; + } else { + // Hack failed + throw new RuntimeException(String.format("Unable to find %s (%s)", className, Joiner.on(", ").join(aliases))); + } + } + } + + /** + * Retrieve the class object of a specific Minecraft library class. + * @param className - the specific library Minecraft class. + * @return Class object. + * @throws RuntimeException If we are unable to find the given class. + */ + public static Class getMinecraftLibraryClass(String className) { + if (libraryPackage == null) + libraryPackage = new CachedPackage("", getClassSource()); + return libraryPackage.getPackageClass(className) + .orElseThrow(() -> new RuntimeException("Failed to find class: " + className)); + } + + /** + * Set the class object for the specific library class. + * @param className - name of the Minecraft library class. + * @param clazz - the new class object. + * @return The provided clazz object. + */ + private static Class setMinecraftLibraryClass(String className, Class clazz) { + if (libraryPackage == null) + libraryPackage = new CachedPackage("", getClassSource()); + libraryPackage.setPackageClass(className, clazz); + return clazz; + } + + /** + * Dynamically retrieve the NetworkManager name. + * @return Name of the NetworkManager class. + */ + public static String getNetworkManagerName() { + return getNetworkManagerClass().getSimpleName(); + } + + /** + * Dynamically retrieve the name of the current NetLoginHandler. + * @return Name of the NetLoginHandler class. + */ + public static String getNetLoginHandlerName() { + return getNetLoginHandlerClass().getSimpleName(); + } + + /** + * Retrieve an instance of the packet data serializer wrapper. + * @param buffer - the buffer. + * @return The instance. + */ + public static Object getPacketDataSerializer(Object buffer) { + Class packetSerializer = getPacketDataSerializerClass(); + + try { + return packetSerializer.getConstructor(getByteBufClass()).newInstance(buffer); + } catch (Exception e) { + throw new RuntimeException("Cannot construct packet serializer.", e); + } + } +} diff --git a/modules/ProtocolLib/src/test/java/com/comphenix/protocol/utility/MinecraftReflectionTest.java b/modules/ProtocolLib/src/test/java/com/comphenix/protocol/utility/MinecraftReflectionTest.java index a07887ad..143985ee 100644 --- a/modules/ProtocolLib/src/test/java/com/comphenix/protocol/utility/MinecraftReflectionTest.java +++ b/modules/ProtocolLib/src/test/java/com/comphenix/protocol/utility/MinecraftReflectionTest.java @@ -1,8 +1,7 @@ package com.comphenix.protocol.utility; import static com.comphenix.protocol.utility.TestUtils.assertItemsEqual; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -75,6 +74,11 @@ public class MinecraftReflectionTest { MinecraftReflection.getBukkitEntity("Hello"); } + @Test + public void testNullable() { + assertNull(MinecraftReflection.getNullableNMS("ProtocolLib")); + } + @Test public void testAttributeSnapshot() { assertEquals(AttributeSnapshot.class, MinecraftReflection.getAttributeSnapshotClass());