Package com.comphenix.protocol.utility

Source Code of com.comphenix.protocol.utility.MinecraftReflection

/*
*  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.io.IOException;
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.net.InetAddress;
import java.net.ServerSocket;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.Nonnull;

import net.minecraft.util.com.mojang.authlib.GameProfile;
import net.minecraft.util.io.netty.buffer.ByteBuf;
import net.sf.cglib.asm.ClassReader;
import net.sf.cglib.asm.MethodVisitor;
import net.sf.cglib.asm.Opcodes;

import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.inventory.ItemStack;

import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
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.compiler.EmptyClassVisitor;
import com.comphenix.protocol.reflect.compiler.EmptyMethodVisitor;
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.RemapperUnavaibleException;
import com.comphenix.protocol.utility.RemappedClassSource.RemapperUnavaibleException.Reason;
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
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 remapper.");
  public static final ReportType REPORT_CANNOT_LOAD_CPC_REMAPPER = new ReportType("Unable to load MCPC remapper.");
  public static final ReportType REPORT_NON_CRAFTBUKKIT_LIBRARY_PACKAGE = new ReportType("Cannot find standard Minecraft library location. Assuming MCPC.");
 
  /**
   * 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.
   * <p>
   * 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";
 
  /**
   * The package with all the library classes.
   */
  private static String MINECRAFT_LIBRARY_PACKAGE = "net.minecraft.util";
 
  /**
   * 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;
 
  // org.bukkit.craftbukkit
  private static Constructor<?> craftNMSConstructor;
  private static Constructor<?> craftBukkitConstructor;
 
  // Matches classes
  private static AbstractFuzzyMatcher<Class<?>> fuzzyMatcher;
 
  // New in 1.4.5
  private static Method craftNMSMethod;
  private static Method craftBukkitNMS;
  private static Method craftBukkitOBC;
  private static boolean craftItemStackFailed;

  // The NMS version
  private static String packageVersion;
 
  // net.minecraft.server
  private static Class<?> itemStackArrayClass;
 
  // Cache of getBukkitEntity
  private static ConcurrentMap<Class<?>, 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 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<Class<?>> 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";
            System.err.println("[ProtocolLib] Assuming package version: " + packageVersion);
          }
        }
       
        // Libigot patch
        handleLibigot();
       
        // Minecraft library package
        handleLibraryPackage();
       
        // 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 Minecraft library package string.
   * @return The library package.
   */
  private static String getMinecraftLibraryPackage() {
    getMinecraftPackage();
    return MINECRAFT_LIBRARY_PACKAGE;
  }
 
  private static void handleLibraryPackage() {
    try {
      MINECRAFT_LIBRARY_PACKAGE = "net.minecraft.util";
      // Try loading Google GSON
      getClassSource().loadClass(CachedPackage.combine(MINECRAFT_LIBRARY_PACKAGE, "com.google.gson.Gson"));
     
    } catch (Exception e) {
      // Assume it's MCPC
      MINECRAFT_LIBRARY_PACKAGE = "";
      ProtocolLibrary.getErrorReporter().reportWarning(MinecraftReflection.class,
        Report.newBuilder(REPORT_NON_CRAFTBUKKIT_LIBRARY_PACKAGE));
    }
  }

  /**
   * 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 a ChunkPosition.
   * @param obj - the object to test.
   * @return TRUE if it can, FALSE otherwise.
   */
  public static boolean isChunkPosition(Object obj) {
    return obj != null && getChunkPositionClass().isAssignableFrom(obj.getClass());
  }
 
  /**
   * 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 obj != null && getChunkCoordIntPair().isAssignableFrom(obj.getClass());
  }
 
  /**
   * 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 obj != null && getChunkCoordinatesClass().isAssignableFrom(obj.getClass());
  }
 
  /**
   * 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 obj != null && getPacketClass().isAssignableFrom(obj.getClass());
  }
 
  /**
   * 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 obj != null && getNetLoginHandlerClass().isAssignableFrom(obj.getClass());
  }
 
  /**
   * 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 obj != null && getNetServerHandlerClass().isAssignableFrom(obj.getClass());
  }
 
  /**
   * 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 obj != null && getEntityClass().isAssignableFrom(obj.getClass());
  }
 
  /**
   * 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 value != null && getItemStackClass().isAssignableFrom(value.getClass());
  }
 
  /**
   * 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 value != null && getCraftPlayerClass().isAssignableFrom(value.getClass());
  }
 
  /**
   * 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 obj != null && getEntityPlayerClass().isAssignableFrom(obj.getClass());
  }

  /**
   * 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 obj != null && getWatchableObjectClass().isAssignableFrom(obj.getClass());
  }
 
  /**
   * 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 obj != null && getDataWatcherClass().isAssignableFrom(obj.getClass());
  }
 
  /**
   * 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 obj != null && getIntHashMapClass().isAssignableFrom(obj.getClass());
  }
 
  /**
   * 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 obj != null && getCraftItemStackClass().isAssignableFrom(obj.getClass());
  }
 
  /**
   * Retrieve the EntityPlayer (NMS) class.
   * @return The entity class.
   */
  public static Class<?> getEntityPlayerClass() {
    try {
      return getMinecraftClass("EntityPlayer");
    } catch (RuntimeException e) {
      try {
        // A fairly stable method
        Method detect = FuzzyReflection.fromClass(getCraftBukkitClass("CraftServer")).
                  getMethodByName("detectListNameConflict");
       
        // EntityPlayer is then the first parameter
        return detect.getParameterTypes()[0];
       
      } catch (IllegalArgumentException ex) {
        // Last resort
        return fallbackMethodReturn("EntityPlayer", "entity.CraftPlayer", "getHandle");
      }
    }
  }
 
  /**
   * 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.");
   
    // Yay, we can actually refer to it directly
    return GameProfile.class;
  }
 
  /**
   * 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(ByteBuf.class).
                  returnTypeVoid()).
            method(FuzzyMethodContract.newBuilder().
                  parameterDerivedOf(ByteBuf.class, 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(getNetServerHandlerClass()).
          getMethod(FuzzyMethodContract.newBuilder().
              parameterMatches(paketContract, 0).
              parameterCount(1).
              build()
          );
   
      // Save and return
      Class<?> clazz = getTopmostClass(selected.getParameterTypes()[0]);
      return setMinecraftClass("Packet", clazz);
    }
  }
 
  /**
   * 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()
      );
    }
  }

  /**
   * 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");
    } catch (RuntimeException e) {
      // Analyse the ASM
      try {
        List<AsmMethod> methodCalls = ClassAnalyser.getDefault().getMethodCalls(
          PacketType.Play.Server.CHAT.getPacketClass(),
          MinecraftMethods.getPacketReadByteBufMethod()
        );
        Class<?> packetSerializer = getPacketDataSerializerClass();
       
        for (AsmMethod method : methodCalls) {
          Class<?> owner = method.getOwnerClass();
         
          if (isMinecraftClass(owner) && !owner.equals(packetSerializer)) {
            return setMinecraftClass("ChatSerializer", owner);
          }
        }
      } catch (Exception e1) {
        throw new IllegalStateException("Cannot find ChatSerializer class.", e);
      }
    }
    throw new IllegalStateException("Cannot find ChatSerializer class.");
  }
 
  /**
   * 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.OUT_SERVER_INFO.getPacketClass();
     
      // Find a server ping object
      AbstractFuzzyMatcher<Class<?>> 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");
    } catch (RuntimeException e) {
      Class<?> serverPing = getServerPingClass();
     
      // Find a server ping object
      AbstractFuzzyMatcher<Class<?>> serverDataContract = FuzzyClassContract.newBuilder().
        constructor(FuzzyMethodContract.newBuilder().parameterExactArray(String.class, int.class)).
        build().
      and(getMinecraftObjectMatcher());
     
      return setMinecraftClass("ServerPingServerData", getTypeFromField(serverPing, serverDataContract));
    }
  }
 
  /**
   * 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");
    } catch (RuntimeException e) {
      Class<?> serverPing = getServerPingClass();
     
      // Find a server ping object
      AbstractFuzzyMatcher<Class<?>> serverPlayerContract = FuzzyClassContract.newBuilder().
        constructor(FuzzyMethodContract.newBuilder().parameterExactArray(int.class, int.class)).
        field(FuzzyFieldContract.newBuilder().typeExact(GameProfile[].class)).
        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<Class<?>> fieldTypeMatcher) {
    final FuzzyFieldContract fieldMatcher = FuzzyFieldContract.matchType(fieldTypeMatcher);
   
    return FuzzyReflection.fromClass(clazz, true).
      getField(fieldMatcher).getType();
  }

  /**
   * Determine if this Minecraft version is using Netty.
   * <p>
   * 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();
      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) {
      // Try again
      useFallbackServer();
      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 NetServerHandler class (or PlayerConnection)
   * @return The NetServerHandler class.
   */
  public static Class<?> getNetServerHandlerClass() {
    try  {
      return getMinecraftClass("NetServerHandler", "PlayerConnection");
    } catch (RuntimeException e) {
      try {
        // Use the player connection field
        return setMinecraftClass("NetServerHandler",
          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("NetServerHandler", 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(getNetServerHandlerClass()).
          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<Class<?>> candidates = new HashSet<Class<?>>();
     
      // 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]);
    }
  }
   
  /**
   * 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()
           );
    }
  }
 
  /**
   * Retrieve the ChunkPosition class.
   * @return The ChunkPosition class.
   */
  public static Class<?> getChunkPositionClass() {
    try {
      return getMinecraftClass("ChunkPosition");
    } catch (RuntimeException e) {
      Class<?> normalChunkGenerator = getCraftBukkitClass("generator.NormalChunkGenerator");
     
      // ChunkPosition a(net.minecraft.server.World world, String string, int i, int i1, int i2) {
      FuzzyMethodContract selected = FuzzyMethodContract.newBuilder().
           banModifier(Modifier.STATIC).
           parameterMatches(getMinecraftObjectMatcher(), 0).
           parameterExactType(String.class, 1).
           parameterExactType(int.class, 2).
           parameterExactType(int.class, 3).
           parameterExactType(int.class, 4).
            build();
     
      return setMinecraftClass("ChunkPosition",
            FuzzyReflection.fromClass(normalChunkGenerator).
             getMethod(selected).getReturnType());
    }
  }
 
  /**
   * Retrieve the ChunkCoordinates class.
   * @return The ChunkPosition class.
   */
  public static Class<?> getChunkCoordinatesClass() {
    try {
      return getMinecraftClass("ChunkCoordinates");
    } catch (RuntimeException e) {
      return setMinecraftClass("ChunkCoordinates", WrappedDataWatcher.getTypeClass(6));
    }
  }
 
  /**
   * 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<Class<?>> 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.
   */
  public static Class<?> getWatchableObjectClass() {
    try {
      return getMinecraftClass("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("WatchableObject", selected.getParameterTypes()[1]);
    }
  }
 
  /**
   * 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(getNetServerHandlerClass()));
       
        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.
   * <p>
   * 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).
   * <p>
   * 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(getNetServerHandlerClass())).
      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.
   * <p>
   * 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");
    } catch (RuntimeException e) {
      final Class<?> packetUpdateAttributes = PacketRegistry.getPacketClassFromType(PacketType.Play.Server.UPDATE_ATTRIBUTES, true);
      final String packetSignature = packetUpdateAttributes.getCanonicalName().replace('.', '/');
     
      // HACK - class is found by inspecting code
      try {
        ClassReader reader = new ClassReader(packetUpdateAttributes.getCanonicalName());
       
        reader.accept(new EmptyClassVisitor() {
          @Override
          public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
            // The read method
            if (desc.startsWith("(Ljava/io/DataInput")) {
              return new EmptyMethodVisitor() {
                public void visitMethodInsn(int opcode, String owner, String name, String desc) {
                  if (opcode == Opcodes.INVOKESPECIAL && isConstructor(name)) {
                    String className = owner.replace('/', '.');
                   
                    // Use signature to distinguish between constructors
                    if (desc.startsWith("(L" + packetSignature)) {
                      setMinecraftClass("AttributeSnapshot", MinecraftReflection.getClass(className));
                    } else if (desc.startsWith("(Ljava/util/UUID;Ljava/lang/String")) {
                      setMinecraftClass("AttributeModifier", MinecraftReflection.getClass(className));
                    }
                  }              
                };
              };
            }
            return null;
          }
        }, 0);
       
      } catch (IOException e1) {
        throw new RuntimeException("Unable to read the content of Packet44UpdateAttributes.", e1);
      }
   
      // If our dirty ASM trick failed, this will throw an exception
      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<Field> 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) {
      // Initialize first
      getAttributeSnapshotClass();
      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(ByteBuf.class).
          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 an instance of the packet data serializer wrapper.
   * @param buffer - the buffer.
   * @return The instance.
   */
  public static ByteBuf getPacketDataSerializer(ByteBuf buffer) {
    Class<?> packetSerializer = getPacketDataSerializerClass();
   
    try {
      return (ByteBuf) packetSerializer.getConstructor(ByteBuf.class).newInstance(buffer);
    } catch (Exception e) {
      throw new RuntimeException("Cannot construct packet serializer.", e);
    }
  }
 
  /**
   * Retrieve the NMS tile entity class.
   * @return The tile entity class.
   */
  public static Class<?> getTileEntityClass() {
    return getMinecraftClass("TileEntity");
  }
 
  /**
   * Retrieve the google.gson.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.OUT_SERVER_INFO.getPacketClass()).
          getFieldByType(".*\\.google\\.gson\\.Gson").getType();
      return setMinecraftLibraryClass("com.google.gson.Gson", match);
    }
  }
 
  /**
   * Determine if a given method retrieved by ASM is a constructor.
   * @param name - the name of the method.
   * @return TRUE if it is, FALSE otherwise.
   */
  private static boolean isConstructor(String name) {
    return "<init>".equals(name);
  }
 
  /**
   * 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 a CraftItemStack from a given ItemStack.
   * @param bukkitItemStack - the Bukkit ItemStack to convert.
   * @return A CraftItemStack as an ItemStack.
   */
  public static ItemStack getBukkitItemStack(ItemStack bukkitItemStack) {
    // Delegate this task to the method that can execute it
    if (craftBukkitNMS != null)
      return getBukkitItemByMethod(bukkitItemStack);
   
    if (craftBukkitConstructor == null) {
      try {
        craftBukkitConstructor = getCraftItemStackClass().getConstructor(ItemStack.class);
      } catch (Exception e) {
        // See if this method works
        if (!craftItemStackFailed)
          return getBukkitItemByMethod(bukkitItemStack);
   
        throw new RuntimeException("Cannot find CraftItemStack(org.bukkit.inventory.ItemStack).", e);
      }
    }
   
    // Try to create the CraftItemStack
    try {
      return (ItemStack) craftBukkitConstructor.newInstance(bukkitItemStack);
    } catch (Exception e) {
      throw new RuntimeException("Cannot construct CraftItemStack.", e);
    }
  }

  private static ItemStack getBukkitItemByMethod(ItemStack bukkitItemStack) {
    if (craftBukkitNMS == null) {
      try {
        craftBukkitNMS = getCraftItemStackClass().getMethod("asNMSCopy", ItemStack.class);
        craftBukkitOBC = getCraftItemStackClass().getMethod("asCraftMirror", MinecraftReflection.getItemStackClass());
      } catch (Exception e) {
        craftItemStackFailed = true;
        throw new RuntimeException("Cannot find CraftItemStack.asCraftCopy(org.bukkit.inventory.ItemStack).", e);
      }
    }
   
    // Next, construct it
    try {
      Object nmsItemStack = craftBukkitNMS.invoke(null, bukkitItemStack);
      return (ItemStack) craftBukkitOBC.invoke(null, nmsItemStack);
    } catch (Exception e) {
      throw new RuntimeException("Cannot construct CraftItemStack.", e);
    }
  }

  /**
   * Retrieve the Bukkit ItemStack from a given net.minecraft.server ItemStack.
   * @param minecraftItemStack - the NMS ItemStack to wrap.
   * @return The wrapped ItemStack.
   */
  public static ItemStack getBukkitItemStack(Object minecraftItemStack) {
    // Delegate this task to the method that can execute it
    if (craftNMSMethod != null)
      return getBukkitItemByMethod(minecraftItemStack);
   
    if (craftNMSConstructor == null) {
      try {
        craftNMSConstructor = getCraftItemStackClass().getConstructor(minecraftItemStack.getClass());
      } catch (Exception e) {
        // Give it a try
        if (!craftItemStackFailed)
          return getBukkitItemByMethod(minecraftItemStack);
       
        throw new RuntimeException("Cannot find CraftItemStack(net.minecraft.server.ItemStack).", e);
      }
    }
   
    // Try to create the CraftItemStack
    try {
      return (ItemStack) craftNMSConstructor.newInstance(minecraftItemStack);
    } catch (Exception e) {
      throw new RuntimeException("Cannot construct CraftItemStack.", e);
    }
  }

  private static ItemStack getBukkitItemByMethod(Object minecraftItemStack) {
    if (craftNMSMethod == null) {
      try {
        craftNMSMethod = getCraftItemStackClass().getMethod("asCraftMirror", minecraftItemStack.getClass());
      } catch (Exception e) {
        craftItemStackFailed = true;
        throw new RuntimeException("Cannot find CraftItemStack.asCraftMirror(net.minecraft.server.ItemStack).", e);
      }
    }
   
    // Next, construct it
    try {
      return (ItemStack) craftNMSMethod.invoke(null, minecraftItemStack);
    } catch (Exception e) {
      throw new RuntimeException("Cannot construct CraftItemStack.", e);
    }
  }
 
  /**
   * Retrieve the net.minecraft.server ItemStack from a Bukkit ItemStack.
   * <p>
   * By convention, item stacks that contain air are usually represented as NULL.
   * @param stack - the Bukkit ItemStack to convert.
   * @return The NMS ItemStack, or NULL if the stack represents air.
   */
  public static Object getMinecraftItemStack(ItemStack stack) {
    // Make sure this is a CraftItemStack
    if (!isCraftItemStack(stack))
      stack = getBukkitItemStack(stack);
   
    BukkitUnwrapper unwrapper = new BukkitUnwrapper();
    return unwrapper.unwrapItem(stack);
  }
 
  /**
   * Retrieve the given class by name.
   * @param className - name of the class.
   * @return The class.
   */
  @SuppressWarnings("rawtypes")
  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.
   */
  @SuppressWarnings("rawtypes")
  public static Class getCraftBukkitClass(String className) {
    if (craftbukkitPackage == null)
      craftbukkitPackage = new CachedPackage(getCraftBukkitPackage(), getClassSource());
    return craftbukkitPackage.getPackageClass(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);
  }
 
  /**
   * 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(getMinecraftLibraryPackage(), getClassSource());
    return libraryPackage.getPackageClass(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(getMinecraftLibraryPackage(), getClassSource());
    libraryPackage.setPackageClass(className, clazz);
    return clazz;
  }
 
  /**
   * 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 (RemapperUnavaibleException 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 e1) {
      Class<?> success = null;
     
      // Try every alias too
      for (String alias : aliases) {
        try {
          success = getMinecraftClass(alias);
          break;
        } catch (RuntimeException e2) {
          // Swallov
        }
      }
     
      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))
          );
      }
    }
  }

  /**
   * 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();
  }
}
TOP

Related Classes of com.comphenix.protocol.utility.MinecraftReflection

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.