diff --git a/ProtocolLib/dependency-reduced-pom.xml b/ProtocolLib/dependency-reduced-pom.xml index 8bf4ad99..00d4c784 100644 --- a/ProtocolLib/dependency-reduced-pom.xml +++ b/ProtocolLib/dependency-reduced-pom.xml @@ -4,7 +4,7 @@ com.comphenix.protocol ProtocolLib ProtocolLib - 1.7.1 + 1.8.0 Provides read/write access to the Minecraft protocol. http://dev.bukkit.org/server-mods/protocollib/ @@ -139,7 +139,7 @@ org.bukkit craftbukkit - 1.3.2-R1.0 + 1.4.5-R0.3-SNAPSHOT provided diff --git a/ProtocolLib/pom.xml b/ProtocolLib/pom.xml index 1b769aac..421b5e48 100644 --- a/ProtocolLib/pom.xml +++ b/ProtocolLib/pom.xml @@ -2,7 +2,7 @@ 4.0.0 com.comphenix.protocol ProtocolLib - 1.7.1 + 1.8.0 jar Provides read/write access to the Minecraft protocol. @@ -202,7 +202,7 @@ org.bukkit craftbukkit - 1.3.2-R1.0 + 1.4.5-R0.3-SNAPSHOT provided diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java index 3b8245c9..5719a097 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java @@ -10,7 +10,6 @@ import java.util.WeakHashMap; import java.util.logging.Level; import java.util.logging.Logger; -import net.minecraft.server.Packet; import net.sf.cglib.proxy.Factory; import org.bukkit.ChatColor; @@ -29,6 +28,7 @@ import com.comphenix.protocol.injector.GamePhase; import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.PrettyPrinter; import com.comphenix.protocol.utility.ChatExtensions; +import com.comphenix.protocol.utility.MinecraftReflection; import com.google.common.collect.DiscreteDomains; import com.google.common.collect.Range; import com.google.common.collect.Ranges; @@ -394,7 +394,7 @@ class CommandPacket extends CommandBase { // Detailed will print the packet's content too if (detailed) { try { - Packet packet = event.getPacket().getHandle(); + Object packet = event.getPacket().getHandle(); Class clazz = packet.getClass(); // Get the first Minecraft super class @@ -404,7 +404,7 @@ class CommandPacket extends CommandBase { } logger.info(shortDescription + ":\n" + - PrettyPrinter.printObject(packet, clazz, Packet.class) + PrettyPrinter.printObject(packet, clazz, MinecraftReflection.getPacketClass()) ); } catch (IllegalAccessException e) { diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandProtocol.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandProtocol.java index fa20d46d..d4c6aa05 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandProtocol.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandProtocol.java @@ -47,6 +47,7 @@ class CommandProtocol extends CommandBase { return true; } + @SuppressWarnings("deprecation") public void checkVersion(final CommandSender sender) { // Perform on an async thread plugin.getServer().getScheduler().scheduleAsyncDelayedTask(plugin, new Runnable() { @@ -64,6 +65,7 @@ class CommandProtocol extends CommandBase { updateFinished(); } + @SuppressWarnings("deprecation") public void updateVersion(final CommandSender sender) { // Perform on an async thread plugin.getServer().getScheduler().scheduleAsyncDelayedTask(plugin, new Runnable() { diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/MinecraftVersion.java b/ProtocolLib/src/main/java/com/comphenix/protocol/MinecraftVersion.java new file mode 100644 index 00000000..6544ac11 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/MinecraftVersion.java @@ -0,0 +1,161 @@ +package com.comphenix.protocol; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.bukkit.Server; + +import com.google.common.base.Objects; +import com.google.common.collect.ComparisonChain; + +/** + * Determine the current Minecraft version. + * + * @author Kristian + */ +class MinecraftVersion implements Comparable { + /** + * Regular expression used to parse version strings. + */ + private static final String VERSION_PATTERN = ".*\\(MC:\\s*((?:\\d+\\.)*\\d)\\s*\\)"; + + private final int major; + private final int minor; + private final int build; + + /** + * Determine the current Minecraft version. + * @param server - the Bukkit server that will be used to examine the MC version. + */ + public MinecraftVersion(Server server) { + this(extractVersion(server.getVersion())); + } + + /** + * Construct a version object from the format major.minor.build. + * @param versionOnly - the version in text form. + */ + public MinecraftVersion(String versionOnly) { + int[] numbers = parseVersion(versionOnly); + + this.major = numbers[0]; + this.minor = numbers[1]; + this.build = numbers[2]; + } + + /** + * Construct a version object directly. + * @param major - major version number. + * @param minor - minor version number. + * @param build - build version number. + */ + public MinecraftVersion(int major, int minor, int build) { + this.major = major; + this.minor = minor; + this.build = build; + } + + private int[] parseVersion(String version) { + String[] elements = version.split("\\."); + int[] numbers = new int[3]; + + // Make sure it's even a valid version + if (elements.length < 1) + throw new IllegalStateException("Corrupt MC version: " + version); + + // The String 1 or 1.2 is interpreted as 1.0.0 and 1.2.0 respectively. + for (int i = 0; i < Math.min(numbers.length, elements.length); i++) + numbers[i] = Integer.parseInt(elements[i].trim()); + return numbers; + } + + /** + * Major version number + * @return Current major version number. + */ + public int getMajor() { + return major; + } + + /** + * Minor version number + * @return Current minor version number. + */ + public int getMinor() { + return minor; + } + + /** + * Build version number + * @return Current build version number. + */ + public int getBuild() { + return build; + } + + /** + * Retrieve the version String (major.minor.build) only. + * @return A normal version string. + */ + public String getVersion() { + return String.format("%s.%s.%s", major, minor, build); + } + + @Override + public int compareTo(MinecraftVersion o) { + if (o == null) + return 1; + + return ComparisonChain.start(). + compare(major, o.major). + compare(minor, o.minor). + compare(build, o.build). + result(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) + return false; + if (obj == this) + return true; + + if (obj instanceof MinecraftVersion) { + MinecraftVersion other = (MinecraftVersion) obj; + + return major == other.major && + minor == other.minor && + build == other.build; + } + + return false; + } + + @Override + public int hashCode() { + return Objects.hashCode(major, minor, build); + } + + @Override + public String toString() { + // Convert to a String that we can parse back again + return String.format("(MC: %s)", getVersion()); + } + + /** + * Extract the Minecraft version from CraftBukkit itself. + * @param server - the server object representing CraftBukkit. + * @return The underlying MC version. + * @throws IllegalStateException If we could not parse the version string. + */ + public static String extractVersion(String text) { + Pattern versionPattern = Pattern.compile(VERSION_PATTERN); + Matcher version = versionPattern.matcher(text); + + if (version.matches() && version.group(1) != null) { + return version.group(1); + } else { + throw new IllegalStateException("Cannot parse version String '" + text + "'"); + } + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java index 7e771e0b..f135fa97 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java @@ -18,6 +18,8 @@ class ProtocolConfig { private static final String METRICS_ENABLED = "metrics"; + private static final String IGNORE_VERSION_CHECK = "ignore version check"; + private static final String BACKGROUND_COMPILER_ENABLED = "background compiler"; private static final String UPDATER_NOTIFY = "notify"; @@ -148,6 +150,26 @@ class ProtocolConfig { public long getAutoLastTime() { return updater.getLong(UPDATER_LAST_TIME, 0); } + + /** + * The version of Minecraft to ignore the built-in safety feature. + * @return The version to ignore ProtocolLib's satefy. + */ + public String getIgnoreVersionCheck() { + return global.getString(IGNORE_VERSION_CHECK, ""); + } + + /** + * Sets under which version of Minecraft the version safety feature will be ignored. + *

+ * This is useful if a server operator has tested and verified that a version of ProtocolLib works, + * but doesn't want or can't upgrade to a newer version. + * + * @param ignoreVersion - the version of Minecraft where the satefy will be disabled. + */ + public void setIgnoreVersionCheck(String ignoreVersion) { + global.set(IGNORE_VERSION_CHECK, ignoreVersion); + } /** * Retrieve whether or not metrics is enabled. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java index e3f8e09d..2f5b4774 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java @@ -19,6 +19,7 @@ package com.comphenix.protocol; import java.io.IOException; import java.util.logging.Handler; +import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; @@ -43,6 +44,15 @@ import com.comphenix.protocol.reflect.compiler.BackgroundCompiler; * @author Kristian */ public class ProtocolLibrary extends JavaPlugin { + /** + * The minimum version ProtocolLib has been tested with. + */ + private static final String MINIMUM_MINECRAFT_VERSION = "1.0.0"; + + /** + * The maximum version ProtocolLib has been tested with, + */ + private static final String MAXIMUM_MINECRAFT_VERSION = "1.4.5"; /** * The number of milliseconds per second. @@ -120,7 +130,7 @@ public class ProtocolLibrary extends JavaPlugin { commandPacket = new CommandPacket(detailedReporter, this, logger, protocolManager); // Send logging information to player listeners too - broadcastUsers(PERMISSION_INFO); + setupBroadcastUsers(PERMISSION_INFO); } catch (Throwable e) { detailedReporter.reportDetailed(this, "Cannot load ProtocolLib.", e, protocolManager); @@ -142,7 +152,7 @@ public class ProtocolLibrary extends JavaPlugin { } } - private void broadcastUsers(final String permission) { + private void setupBroadcastUsers(final String permission) { // Guard against multiple calls if (redirectHandler != null) return; @@ -151,7 +161,10 @@ public class ProtocolLibrary extends JavaPlugin { redirectHandler = new Handler() { @Override public void publish(LogRecord record) { - commandPacket.broadcastMessageSilently(record.getMessage(), permission); + // Only display warnings and above + if (record.getLevel().intValue() >= Level.WARNING.intValue()) { + commandPacket.broadcastMessageSilently(record.getMessage(), permission); + } } @Override @@ -188,13 +201,13 @@ public class ProtocolLibrary extends JavaPlugin { logger.info("Structure compiler thread has been disabled."); } + // Handle unexpected Minecraft versions + verifyMinecraftVersion(); + // Set up command handlers registerCommand(CommandProtocol.NAME, commandProtocol); registerCommand(CommandPacket.NAME, commandPacket); - // Notify server managers of incompatible plugins - checkForIncompatibility(manager); - // Player login and logout events protocolManager.registerEvents(manager, this); @@ -220,6 +233,26 @@ public class ProtocolLibrary extends JavaPlugin { } } + // Used to check Minecraft version + private void verifyMinecraftVersion() { + try { + MinecraftVersion minimum = new MinecraftVersion(MINIMUM_MINECRAFT_VERSION); + MinecraftVersion maximum = new MinecraftVersion(MAXIMUM_MINECRAFT_VERSION); + MinecraftVersion current = new MinecraftVersion(getServer()); + + // Skip certain versions + if (!config.getIgnoreVersionCheck().equals(current.getVersion())) { + // We'll just warn the user for now + if (current.compareTo(minimum) < 0) + logger.warning("Version " + current + " is lower than the minimum " + minimum); + if (current.compareTo(maximum) > 0) + logger.warning("Version " + current + " has not yet been tested! Proceed with caution."); + } + } catch (Exception e) { + reporter.reportWarning(this, "Unable to retrieve current Minecraft version.", e); + } + } + private void registerCommand(String name, CommandExecutor executor) { try { if (executor == null) @@ -296,18 +329,6 @@ public class ProtocolLibrary extends JavaPlugin { } } - private void checkForIncompatibility(PluginManager manager) { - // Plugin authors: Notify me to remove these - String[] incompatiblePlugins = {}; - - for (String plugin : incompatiblePlugins) { - if (manager.getPlugin(plugin) != null) { - // Check for versions, ect. - reporter.reportWarning(this, "Detected incompatible plugin: " + plugin); - } - } - } - @Override public void onDisable() { // Disable compiler diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncListenerHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncListenerHandler.java index f37511f8..cefd8a0a 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncListenerHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncListenerHandler.java @@ -242,7 +242,7 @@ public class AsyncListenerHandler { final AsyncRunnable listenerLoop = getListenerLoop(); - filterManager.getScheduler().scheduleAsyncDelayedTask(listener.getPlugin(), new Runnable() { + scheduleAsync(new Runnable() { @Override public void run() { Thread thread = Thread.currentThread(); @@ -290,7 +290,7 @@ public class AsyncListenerHandler { final AsyncRunnable listenerLoop = getListenerLoop(); final Function delegateCopy = executor; - filterManager.getScheduler().scheduleAsyncDelayedTask(listener.getPlugin(), new Runnable() { + scheduleAsync(new Runnable() { @Override public void run() { delegateCopy.apply(listenerLoop); @@ -298,6 +298,11 @@ public class AsyncListenerHandler { }); } + @SuppressWarnings("deprecation") + private void scheduleAsync(Runnable runnable) { + filterManager.getScheduler().scheduleAsyncDelayedTask(listener.getPlugin(), runnable); + } + /** * Create a friendly thread name using the following convention: *

diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncMarker.java b/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncMarker.java index 7b6b038b..5b0e6a1c 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncMarker.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncMarker.java @@ -25,13 +25,12 @@ import java.util.Iterator; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; -import net.minecraft.server.Packet; - import com.comphenix.protocol.PacketStream; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.injector.PrioritizedListener; import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.utility.MinecraftReflection; import com.google.common.primitives.Longs; /** @@ -403,10 +402,10 @@ public class AsyncMarker implements Serializable, Comparable { if (isMinecraftAsync == null && !alwaysSync) { try { - isMinecraftAsync = FuzzyReflection.fromClass(Packet.class).getMethodByName("a_.*"); + isMinecraftAsync = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).getMethodByName("a_.*"); } catch (RuntimeException e) { // This will occur in 1.2.5 (or possibly in later versions) - List methods = FuzzyReflection.fromClass(Packet.class). + List methods = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()). getMethodListByParameters(boolean.class, new Class[] {}); // Try to look for boolean methods diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/error/DetailedErrorReporter.java b/ProtocolLib/src/main/java/com/comphenix/protocol/error/DetailedErrorReporter.java index 2257cbca..9b6ef329 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/error/DetailedErrorReporter.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/error/DetailedErrorReporter.java @@ -95,6 +95,21 @@ public class DetailedErrorReporter implements ErrorReporter { this.logger = logger; } + @Override + public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) { + reportMinimal(sender, methodName, error); + + // Print parameters, if they are given + if (parameters != null && parameters.length > 0) { + logger.log(Level.SEVERE, " Parameters:"); + + // Print each parameter + for (Object parameter : parameters) { + logger.log(Level.SEVERE, " " + getStringDescription(parameter)); + } + } + } + @Override public void reportMinimal(Plugin sender, String methodName, Throwable error) { logger.log(Level.SEVERE, "[" + PLUGIN_NAME + "] Unhandled exception occured in " + methodName + " for " + diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/error/ErrorReporter.java b/ProtocolLib/src/main/java/com/comphenix/protocol/error/ErrorReporter.java index de6e1e36..54b45f48 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/error/ErrorReporter.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/error/ErrorReporter.java @@ -12,6 +12,15 @@ public interface ErrorReporter { */ public abstract void reportMinimal(Plugin sender, String methodName, Throwable error); + /** + * Prints a small minimal error report about an exception from another plugin. + * @param sender - the other plugin. + * @param methodName - name of the caller method. + * @param error - the exception itself. + * @param parameters - any relevant parameters to print. + */ + public abstract void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters); + /** * Prints a warning message from the current plugin. * @param sender - the object containing the caller method. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java index 2ff67143..a7b4a265 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java @@ -29,6 +29,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Collection; import java.util.List; +import java.util.concurrent.ConcurrentMap; import org.bukkit.World; import org.bukkit.WorldType; @@ -39,12 +40,12 @@ import com.comphenix.protocol.injector.StructureCache; import com.comphenix.protocol.reflect.EquivalentConverter; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.wrappers.BukkitConverters; import com.comphenix.protocol.wrappers.ChunkPosition; import com.comphenix.protocol.wrappers.WrappedDataWatcher; import com.comphenix.protocol.wrappers.WrappedWatchableObject; - -import net.minecraft.server.Packet; +import com.google.common.collect.Maps; /** * Represents a Minecraft packet indirectly. @@ -59,15 +60,15 @@ public class PacketContainer implements Serializable { private static final long serialVersionUID = 2074805748222377230L; protected int id; - protected transient Packet handle; + protected transient Object handle; // Current structure modifier protected transient StructureModifier structureModifier; - + // Support for serialization - private static Method writeMethod; - private static Method readMethod; - + private static ConcurrentMap, Method> writeMethods = Maps.newConcurrentMap(); + private static ConcurrentMap, Method> readMethods = Maps.newConcurrentMap(); + /** * Creates a packet container for a new packet. * @param id - ID of the packet to create. @@ -81,7 +82,7 @@ public class PacketContainer implements Serializable { * @param id - ID of the given packet. * @param handle - contained packet. */ - public PacketContainer(int id, Packet handle) { + public PacketContainer(int id, Object handle) { this(id, handle, StructureCache.getStructure(id).withTarget(handle)); } @@ -91,7 +92,7 @@ public class PacketContainer implements Serializable { * @param handle - contained packet. * @param structure - structure modifier. */ - public PacketContainer(int id, Packet handle, StructureModifier structure) { + public PacketContainer(int id, Object handle, StructureModifier structure) { if (handle == null) throw new IllegalArgumentException("handle cannot be null."); @@ -100,11 +101,17 @@ public class PacketContainer implements Serializable { this.structureModifier = structure; } + /** + * For serialization. + */ + protected PacketContainer() { + } + /** * Retrieves the underlying Minecraft packet. * @return Underlying Minecraft packet. */ - public Packet getHandle() { + public Object getHandle() { return handle; } @@ -214,7 +221,7 @@ public class PacketContainer implements Serializable { public StructureModifier getItemModifier() { // Convert to and from the Bukkit wrapper return structureModifier.withType( - net.minecraft.server.ItemStack.class, BukkitConverters.getItemStackConverter()); + MinecraftReflection.getItemStackClass(), BukkitConverters.getItemStackConverter()); } /** @@ -230,23 +237,23 @@ public class PacketContainer implements Serializable { // Convert to and from the Bukkit wrapper return structureModifier.withType( - net.minecraft.server.ItemStack[].class, + MinecraftReflection.getItemStackArrayClass(), BukkitConverters.getIgnoreNull(new EquivalentConverter() { public Object getGeneric(ClassgenericType, ItemStack[] specific) { - net.minecraft.server.ItemStack[] result = new net.minecraft.server.ItemStack[specific.length]; + Object[] result = new Object[specific.length]; // Unwrap every item for (int i = 0; i < result.length; i++) { - result[i] = (net.minecraft.server.ItemStack) stackConverter.getGeneric( - net.minecraft.server.ItemStack.class, specific[i]); + result[i] = stackConverter.getGeneric( + MinecraftReflection.getItemStackClass(), specific[i]); } return result; } @Override public ItemStack[] getSpecific(Object generic) { - net.minecraft.server.ItemStack[] input = (net.minecraft.server.ItemStack[]) generic; + Object[] input = (Object[]) generic; ItemStack[] result = new ItemStack[input.length]; // Add the wrapper @@ -273,7 +280,7 @@ public class PacketContainer implements Serializable { public StructureModifier getWorldTypeModifier() { // Convert to and from the Bukkit wrapper return structureModifier.withType( - net.minecraft.server.WorldType.class, + MinecraftReflection.getWorldTypeClass(), BukkitConverters.getWorldTypeConverter()); } @@ -284,7 +291,7 @@ public class PacketContainer implements Serializable { public StructureModifier getDataWatcherModifier() { // Convert to and from the Bukkit wrapper return structureModifier.withType( - net.minecraft.server.DataWatcher.class, + MinecraftReflection.getDataWatcherClass(), BukkitConverters.getDataWatcherConverter()); } @@ -311,7 +318,7 @@ public class PacketContainer implements Serializable { public StructureModifier getPositionModifier() { // Convert to and from the Bukkit wrapper return structureModifier.withType( - net.minecraft.server.ChunkPosition.class, + MinecraftReflection.getChunkPositionClass(), ChunkPosition.getConverter()); } @@ -327,7 +334,7 @@ public class PacketContainer implements Serializable { return structureModifier.withType( Collection.class, BukkitConverters.getListConverter( - net.minecraft.server.ChunkPosition.class, + MinecraftReflection.getChunkPositionClass(), ChunkPosition.getConverter()) ); } @@ -344,7 +351,7 @@ public class PacketContainer implements Serializable { return structureModifier.withType( Collection.class, BukkitConverters.getListConverter( - net.minecraft.server.WatchableObject.class, + MinecraftReflection.getWatchableObjectClass(), BukkitConverters.getWatchableObjectConverter()) ); } @@ -399,14 +406,12 @@ public class PacketContainer implements Serializable { // We'll take care of NULL packets as well output.writeBoolean(handle != null); - - // Retrieve the write method by reflection - if (writeMethod == null) - writeMethod = FuzzyReflection.fromObject(handle).getMethodByParameters("write", DataOutputStream.class); - + try { // Call the write-method - writeMethod.invoke(handle, new DataOutputStream(output)); + getMethodLazily(writeMethods, handle.getClass(), "write", DataOutputStream.class). + invoke(handle, new DataOutputStream(output)); + } catch (IllegalArgumentException e) { throw new IOException("Minecraft packet doesn't support DataOutputStream", e); } catch (IllegalAccessException e) { @@ -429,13 +434,11 @@ public class PacketContainer implements Serializable { // Create a default instance of the packet handle = StructureCache.newPacket(id); - // Retrieve the read method by reflection - if (readMethod == null) - readMethod = FuzzyReflection.fromObject(handle).getMethodByParameters("read", DataInputStream.class); - // Call the read method try { - readMethod.invoke(handle, new DataInputStream(input)); + getMethodLazily(readMethods, handle.getClass(), "read", DataInputStream.class). + invoke(handle, new DataInputStream(input)); + } catch (IllegalArgumentException e) { throw new IOException("Minecraft packet doesn't support DataInputStream", e); } catch (IllegalAccessException e) { @@ -448,4 +451,30 @@ public class PacketContainer implements Serializable { structureModifier = structureModifier.withTarget(handle); } } + + /** + * Retrieve the cached method concurrently. + * @param lookup - a lazy lookup cache. + * @param handleClass - class type of the current packet. + * @param methodName - name of method to retrieve. + * @param parameterClass - the one parameter type in the method. + * @return Reflected method. + */ + private Method getMethodLazily(ConcurrentMap, Method> lookup, + Class handleClass, String methodName, Class parameterClass) { + Method method = lookup.get(handleClass); + + // Atomic operation + if (method == null) { + Method initialized = FuzzyReflection.fromClass(handleClass).getMethodByParameters(methodName, parameterClass); + method = lookup.putIfAbsent(handleClass, initialized); + + // Use our version if we succeeded + if (method == null) { + method = initialized; + } + } + + return method; + } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/EntityUtilities.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/EntityUtilities.java index ef9c9e8c..2362ed55 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/EntityUtilities.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/EntityUtilities.java @@ -28,17 +28,14 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import net.minecraft.server.EntityPlayer; -import net.minecraft.server.EntityTrackerEntry; - import org.bukkit.World; -import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.utility.MinecraftReflection; import com.google.common.collect.Lists; /** @@ -51,10 +48,11 @@ class EntityUtilities { private static Field entityTrackerField; private static Field trackedEntitiesField; private static Field trackedPlayersField; + private static Field trackerField; private static Method hashGetMethod; private static Method scanPlayersMethod; - + /* * While this function may look pretty bad, it's essentially just a reflection-warped * version of the following: @@ -142,9 +140,8 @@ class EntityUtilities { // Wrap every player - we also ensure that the underlying tracker list is immutable for (Object tracker : trackedPlayers) { - if (tracker instanceof EntityPlayer) { - EntityPlayer nmsPlayer = (EntityPlayer) tracker; - result.add(nmsPlayer.getBukkitEntity()); + if (MinecraftReflection.isMinecraftPlayer(tracker)) { + result.add((Player) MinecraftReflection.getBukkitEntity(tracker)); } } return result; @@ -164,7 +161,8 @@ class EntityUtilities { * @throws FieldAccessException */ private static Object getEntityTrackerEntry(World world, int entityID) throws FieldAccessException, IllegalArgumentException, IllegalAccessException, InvocationTargetException { - Object worldServer = ((CraftWorld) world).getHandle(); + BukkitUnwrapper unwrapper = new BukkitUnwrapper(); + Object worldServer = unwrapper.unwrapItem(world); // We have to rely on the class naming here. if (entityTrackerField == null) @@ -193,7 +191,7 @@ class EntityUtilities { // The Minecraft field that's NOT filled in by the constructor trackedEntitiesField = FuzzyReflection.fromObject(tracker, true). - getFieldByType(FuzzyReflection.MINECRAFT_OBJECT, ignoredTypes); + getFieldByType(MinecraftReflection.MINECRAFT_OBJECT, ignoredTypes); } // Read the entity hashmap @@ -247,17 +245,24 @@ class EntityUtilities { */ public static Entity getEntityFromID(World world, int entityID) throws FieldAccessException { try { - EntityTrackerEntry trackerEntry = (EntityTrackerEntry) getEntityTrackerEntry(world, entityID); - + Object trackerEntry = getEntityTrackerEntry(world, entityID); + Object tracker = null; + // Handle NULL cases - if (trackerEntry != null && trackerEntry.tracker != null) { - return trackerEntry.tracker.getBukkitEntity(); - } else { - return null; + if (trackerEntry != null) { + if (trackerField == null) + trackerField = trackerEntry.getClass().getField("tracker"); + tracker = FieldUtils.readField(trackerField, trackerEntry, true); } + // If the tracker is NULL, we'll just assume this entity doesn't exist + if (tracker != null) + return (Entity) MinecraftReflection.getBukkitEntity(tracker); + else + return null; + } catch (Exception e) { - throw new FieldAccessException("Cannot find entity from ID.", e); + throw new FieldAccessException("Cannot find entity from ID " + entityID + ".", e); } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/ListenerInvoker.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/ListenerInvoker.java index 52c52e7d..f27b64e5 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/ListenerInvoker.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/ListenerInvoker.java @@ -17,8 +17,6 @@ package com.comphenix.protocol.injector; -import net.minecraft.server.Packet; - import com.comphenix.protocol.events.PacketEvent; /** @@ -45,7 +43,7 @@ public interface ListenerInvoker { * @param packet - the packet. * @return The packet ID. */ - public abstract int getPacketID(Packet packet); + public abstract int getPacketID(Object packet); /** * Associate a given class with the given packet ID. Internal method. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/MinecraftRegistry.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/MinecraftRegistry.java index 34f7a341..5cdddd7c 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/MinecraftRegistry.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/MinecraftRegistry.java @@ -23,11 +23,12 @@ import java.util.List; import java.util.Map; import java.util.Set; -import net.minecraft.server.Packet; +import net.sf.cglib.proxy.Factory; import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.utility.MinecraftReflection; import com.google.common.base.Objects; import com.google.common.collect.ImmutableSet; @@ -77,7 +78,7 @@ class MinecraftRegistry { */ private static FuzzyReflection getPacketRegistry() { if (packetRegistry == null) - packetRegistry = FuzzyReflection.fromClass(Packet.class, true); + packetRegistry = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true); return packetRegistry; } @@ -166,7 +167,7 @@ class MinecraftRegistry { // Optimized lookup if (lookup.containsKey(packetID)) { - return lookup.get(packetID); + return removeEnhancer(lookup.get(packetID), forceVanilla); } // Will most likely not be used @@ -174,10 +175,27 @@ class MinecraftRegistry { if (Objects.equal(entry.getValue(), packetID)) { // Attempt to get the vanilla class here too if (!forceVanilla || entry.getKey().getName().startsWith("net.minecraft.server")) - return entry.getKey(); + return removeEnhancer(entry.getKey(), forceVanilla); } } throw new IllegalArgumentException("The packet ID " + packetID + " is not registered."); } + + /** + * Find the first superclass that is not a CBLib proxy object. + * @param clazz - the class whose hierachy we're going to search through. + * @param remove - whether or not to skip enhanced (proxy) classes. + * @return If remove is TRUE, the first superclass that is not a proxy. + */ + private static Class removeEnhancer(Class clazz, boolean remove) { + if (remove) { + // Get the underlying vanilla class + while (Factory.class.isAssignableFrom(clazz) && !clazz.equals(Object.class)) { + clazz = clazz.getSuperclass(); + } + } + + return clazz; + } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketConstructor.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketConstructor.java index 7c1f85e6..3d138a13 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketConstructor.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketConstructor.java @@ -21,8 +21,6 @@ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.List; -import net.minecraft.server.Packet; - import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.reflect.FieldAccessException; import com.google.common.collect.ImmutableList; @@ -157,7 +155,7 @@ public class PacketConstructor { } } - Packet nmsPacket = (Packet) constructorMethod.newInstance(values); + Object nmsPacket = constructorMethod.newInstance(values); return new PacketContainer(packetID, nmsPacket); } catch (IllegalArgumentException e) { diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java index 27f7c7c0..e43921ef 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java @@ -28,7 +28,6 @@ import java.util.concurrent.atomic.AtomicInteger; import javax.annotation.Nullable; -import net.minecraft.server.Packet; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; @@ -55,6 +54,7 @@ import com.comphenix.protocol.events.*; import com.comphenix.protocol.injector.player.PlayerInjectionHandler; import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.utility.MinecraftReflection; import com.google.common.base.Objects; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableSet; @@ -508,7 +508,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok if (packetCreation.compareAndSet(false, true)) incrementPhases(GamePhase.PLAYING); - Packet mcPacket = packet.getHandle(); + Object mcPacket = packet.getHandle(); // Make sure the packet isn't cancelled packetInjector.undoCancel(packet.getID(), mcPacket); @@ -686,9 +686,11 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok } @Override - public int getPacketID(Packet packet) { + public int getPacketID(Object packet) { if (packet == null) throw new IllegalArgumentException("Packet cannot be NULL."); + if (!MinecraftReflection.isPacketClass(packet)) + throw new IllegalArgumentException("The given object " + packet + " is not a packet."); return MinecraftRegistry.getPacketToID().get(packet.getClass()); } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketInjector.java index 8d3374dc..0a83c8ec 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketInjector.java @@ -27,7 +27,6 @@ import java.util.concurrent.ConcurrentHashMap; import org.bukkit.entity.Player; -import net.minecraft.server.Packet; import net.sf.cglib.proxy.Callback; import net.sf.cglib.proxy.Enhancer; @@ -37,6 +36,7 @@ import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.injector.player.PlayerInjectionHandler; import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.utility.MinecraftReflection; /** * This class is responsible for adding or removing proxy objects that intercepts recieved packets. @@ -80,7 +80,7 @@ class PacketInjector { * @param id - the id of the packet. * @param packet - packet to uncancel. */ - public void undoCancel(Integer id, Packet packet) { + public void undoCancel(Integer id, Object packet) { ReadPacketModifier modifier = readModifier.get(id); // See if this packet has been cancelled before @@ -92,7 +92,8 @@ class PacketInjector { private void initialize() throws IllegalAccessException { if (intHashMap == null) { // We're looking for the first static field with a Minecraft-object. This should be a IntHashMap. - Field intHashMapField = FuzzyReflection.fromClass(Packet.class, true).getFieldByType(FuzzyReflection.MINECRAFT_OBJECT); + Field intHashMapField = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true). + getFieldByType(MinecraftReflection.MINECRAFT_OBJECT); try { intHashMap = FieldUtils.readField(intHashMapField, (Object) null, true); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/ReadPacketModifier.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/ReadPacketModifier.java index ee32ddc2..ceb702c6 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/ReadPacketModifier.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/ReadPacketModifier.java @@ -29,7 +29,6 @@ import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; -import net.minecraft.server.Packet; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; @@ -61,7 +60,7 @@ class ReadPacketModifier implements MethodInterceptor { * Remove any packet overrides. * @param packet - the packet to rever */ - public void removeOverride(Packet packet) { + public void removeOverride(Object packet) { override.remove(packet); } @@ -70,7 +69,7 @@ class ReadPacketModifier implements MethodInterceptor { * @param packet - the given packet. * @return Overriden object. */ - public Object getOverride(Packet packet) { + public Object getOverride(Object packet) { return override.get(packet); } @@ -79,7 +78,7 @@ class ReadPacketModifier implements MethodInterceptor { * @param packet - the packet to check. * @return TRUE if it has been cancelled, FALSE otherwise. */ - public boolean hasCancelled(Packet packet) { + public boolean hasCancelled(Object packet) { return getOverride(packet) == CANCEL_MARKER; } @@ -121,12 +120,12 @@ class ReadPacketModifier implements MethodInterceptor { DataInputStream input = (DataInputStream) args[0]; // Let the people know - PacketContainer container = new PacketContainer(packetID, (Packet) thisObj); + PacketContainer container = new PacketContainer(packetID, thisObj); PacketEvent event = packetInjector.packetRecieved(container, input); // Handle override if (event != null) { - Packet result = event.getPacket().getHandle(); + Object result = event.getPacket().getHandle(); if (event.isCancelled()) { override.put(thisObj, CANCEL_MARKER); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/SortedPacketListenerList.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/SortedPacketListenerList.java index d3184b10..9c895e28 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/SortedPacketListenerList.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/SortedPacketListenerList.java @@ -48,7 +48,8 @@ public final class SortedPacketListenerList extends AbstractConcurrentListenerMu element.getListener().onPacketReceiving(event); } catch (Throwable e) { // Minecraft doesn't want your Exception. - reporter.reportMinimal(element.getListener().getPlugin(), "onPacketReceiving()", e); + reporter.reportMinimal(element.getListener().getPlugin(), "onPacketReceiving(PacketEvent)", e, + event.getPacket().getHandle()); } } } @@ -69,7 +70,8 @@ public final class SortedPacketListenerList extends AbstractConcurrentListenerMu element.getListener().onPacketSending(event); } catch (Throwable e) { // Minecraft doesn't want your Exception. - reporter.reportMinimal(element.getListener().getPlugin(), "onPacketSending()", e); + reporter.reportMinimal(element.getListener().getPlugin(), "onPacketSending(PacketEvent)", e, + event.getPacket().getHandle()); } } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/StructureCache.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/StructureCache.java index aeedc16a..bc4f7aa2 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/StructureCache.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/StructureCache.java @@ -22,12 +22,11 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import net.minecraft.server.Packet; - import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.compiler.BackgroundCompiler; import com.comphenix.protocol.reflect.compiler.CompileListener; import com.comphenix.protocol.reflect.compiler.CompiledStructureModifier; +import com.comphenix.protocol.utility.MinecraftReflection; /** * Caches structure modifiers. @@ -45,9 +44,9 @@ public class StructureCache { * @param id - packet ID. * @return Created packet. */ - public static Packet newPacket(int id) { + public static Object newPacket(int id) { try { - return (Packet) MinecraftRegistry.getPacketClassFromID(id, true).newInstance(); + return MinecraftRegistry.getPacketClassFromID(id, true).newInstance(); } catch (InstantiationException e) { return null; } catch (IllegalAccessException e) { @@ -79,7 +78,7 @@ public class StructureCache { if (result == null) { // Use the vanilla class definition final StructureModifier value = new StructureModifier( - MinecraftRegistry.getPacketClassFromID(id, true), Packet.class, true); + MinecraftRegistry.getPacketClassFromID(id, true), MinecraftReflection.getPacketClass(), true); result = structureModifiers.putIfAbsent(id, value); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedArrayList.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedArrayList.java index fd5a0b80..60f570b5 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedArrayList.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedArrayList.java @@ -25,7 +25,7 @@ import java.util.Set; import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.injector.player.NetworkFieldInjector.FakePacket; -import net.minecraft.server.Packet; +import net.sf.cglib.proxy.Callback; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; @@ -35,7 +35,7 @@ import net.sf.cglib.proxy.MethodProxy; * * @author Kristian */ -class InjectedArrayList extends ArrayList { +class InjectedArrayList extends ArrayList { /** * Silly Eclipse. @@ -43,19 +43,22 @@ class InjectedArrayList extends ArrayList { private static final long serialVersionUID = -1173865905404280990L; private transient PlayerInjector injector; - private transient Set ignoredPackets; + private transient Set ignoredPackets; private transient ClassLoader classLoader; - public InjectedArrayList(ClassLoader classLoader, PlayerInjector injector, Set ignoredPackets) { + private transient InvertedIntegerCallback callback; + + public InjectedArrayList(ClassLoader classLoader, PlayerInjector injector, Set ignoredPackets) { this.classLoader = classLoader; this.injector = injector; this.ignoredPackets = ignoredPackets; + this.callback = new InvertedIntegerCallback(); } @Override - public boolean add(Packet packet) { + public boolean add(Object packet) { - Packet result = null; + Object result = null; // Check for fake packets and ignored packets if (packet instanceof FakePacket) { @@ -90,11 +93,13 @@ class InjectedArrayList extends ArrayList { * @param source - packet to invert. * @return The inverted packet. */ - Packet createNegativePacket(Packet source) { + Object createNegativePacket(Object source) { ListenerInvoker invoker = injector.getInvoker(); int packetID = invoker.getPacketID(source); Class type = invoker.getPacketClassFromID(packetID, true); + + System.out.println(type.getName()); // We want to subtract the byte amount that were added to the running // total of outstanding packets. Otherwise, cancelling too many packets @@ -122,15 +127,20 @@ class InjectedArrayList extends ArrayList { ex.setCallbackType(InvertedIntegerCallback.class); Class proxyClass = ex.createClass(); - - // Temporarily associate the fake packet class - invoker.registerPacketClass(proxyClass, packetID); - - Packet fake = (Packet) Enhancer.create(proxyClass, new InvertedIntegerCallback()); + Enhancer.registerCallbacks(proxyClass, new Callback[] { callback }); - // Remove this association - invoker.unregisterPacketClass(proxyClass); - return fake; + try { + // Temporarily associate the fake packet class + invoker.registerPacketClass(proxyClass, packetID); + return proxyClass.newInstance(); + + } catch (Exception e) { + // Don't pollute the throws tree + throw new RuntimeException("Cannot create fake class.", e); + } finally { + // Remove this association + invoker.unregisterPacketClass(proxyClass); + } } /** diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerConnection.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerConnection.java index c9ae47ff..1d94a9c9 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerConnection.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerConnection.java @@ -22,7 +22,6 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; -import net.minecraft.server.NetLoginHandler; import net.sf.cglib.proxy.Factory; import org.bukkit.Server; @@ -32,6 +31,7 @@ import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.ObjectCloner; import com.comphenix.protocol.reflect.VolatileField; +import com.comphenix.protocol.utility.MinecraftReflection; /** * Used to ensure that the 1.3 server is referencing the correct server handler. @@ -229,7 +229,7 @@ class InjectedServerConnection { @Override protected void onInserting(Object inserting) { // Ready for some login handler injection? - if (inserting instanceof NetLoginHandler) { + if (MinecraftReflection.isLoginHandler(inserting)) { Object replaced = netLoginInjector.onNetLoginCreated(inserting); // Only replace if it has changed @@ -241,7 +241,7 @@ class InjectedServerConnection { @Override protected void onRemoved(Object removing) { // Clean up? - if (removing instanceof NetLoginHandler) { + if (MinecraftReflection.isLoginHandler(removing)) { netLoginInjector.cleanup(removing); } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkFieldInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkFieldInjector.java index dd65fa17..ffb6ca3d 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkFieldInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkFieldInjector.java @@ -40,8 +40,6 @@ import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.VolatileField; import com.google.common.collect.Sets; -import net.minecraft.server.Packet; - /** * Injection hook that overrides the packet queue lists in NetworkHandler. * @@ -58,7 +56,7 @@ class NetworkFieldInjector extends PlayerInjector { } // Packets to ignore - private Set ignoredPackets = Sets.newSetFromMap(new ConcurrentHashMap()); + private Set ignoredPackets = Sets.newSetFromMap(new ConcurrentHashMap()); // Overridden fields private List overridenLists = new ArrayList(); @@ -99,7 +97,7 @@ class NetworkFieldInjector extends PlayerInjector { } @Override - public void sendServerPacket(Packet packet, boolean filtered) throws InvocationTargetException { + public void sendServerPacket(Object packet, boolean filtered) throws InvocationTargetException { if (networkManager != null) { try { @@ -147,14 +145,14 @@ class NetworkFieldInjector extends PlayerInjector { VolatileField overwriter = new VolatileField(field, networkManager, true); @SuppressWarnings("unchecked") - List minecraftList = (List) overwriter.getOldValue(); + List minecraftList = (List) overwriter.getOldValue(); synchronized(syncObject) { // The list we'll be inserting - List hackedList = new InjectedArrayList(classLoader, this, ignoredPackets); + List hackedList = new InjectedArrayList(classLoader, this, ignoredPackets); // Add every previously stored packet - for (Packet packet : minecraftList) { + for (Object packet : minecraftList) { hackedList.add(packet); } @@ -172,8 +170,8 @@ class NetworkFieldInjector extends PlayerInjector { protected void cleanHook() { // Clean up for (VolatileField overriden : overridenLists) { - List minecraftList = (List) overriden.getOldValue(); - List hacketList = (List) overriden.getValue(); + List minecraftList = (List) overriden.getOldValue(); + List hacketList = (List) overriden.getValue(); if (minecraftList == hacketList) { return; @@ -183,7 +181,7 @@ class NetworkFieldInjector extends PlayerInjector { synchronized(syncObject) { try { // Copy over current packets - for (Packet packet : (List) overriden.getValue()) { + for (Object packet : (List) overriden.getValue()) { minecraftList.add(packet); } } finally { diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkObjectInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkObjectInjector.java index e391d832..951c4b23 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkObjectInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkObjectInjector.java @@ -19,7 +19,6 @@ package com.comphenix.protocol.injector.player; import java.lang.reflect.InvocationTargetException; -import net.minecraft.server.Packet; import net.sf.cglib.proxy.Callback; import net.sf.cglib.proxy.CallbackFilter; import net.sf.cglib.proxy.Enhancer; @@ -67,7 +66,7 @@ class NetworkObjectInjector extends PlayerInjector { } @Override - public void sendServerPacket(Packet packet, boolean filtered) throws InvocationTargetException { + public void sendServerPacket(Object packet, boolean filtered) throws InvocationTargetException { Object networkDelegate = filtered ? networkManagerRef.getValue() : networkManagerRef.getOldValue(); if (networkDelegate != null) { @@ -114,7 +113,7 @@ class NetworkObjectInjector extends PlayerInjector { Callback queueFilter = new MethodInterceptor() { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { - Packet packet = (Packet) args[0]; + Object packet = args[0]; if (packet != null) { packet = handlePacketSending(packet); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java index 3a346dfd..a7b42de2 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java @@ -21,7 +21,6 @@ import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import net.minecraft.server.Packet; import net.sf.cglib.proxy.Callback; import net.sf.cglib.proxy.CallbackFilter; import net.sf.cglib.proxy.Enhancer; @@ -43,6 +42,7 @@ import com.comphenix.protocol.reflect.ObjectCloner; import com.comphenix.protocol.reflect.VolatileField; import com.comphenix.protocol.reflect.instances.DefaultInstances; import com.comphenix.protocol.reflect.instances.ExistingGenerator; +import com.comphenix.protocol.utility.MinecraftReflection; /** * Represents a player hook into the NetServerHandler class. @@ -94,7 +94,7 @@ public class NetworkServerInjector extends PlayerInjector { } @Override - public void sendServerPacket(Packet packet, boolean filtered) throws InvocationTargetException { + public void sendServerPacket(Object packet, boolean filtered) throws InvocationTargetException { Object serverDeleage = filtered ? serverHandlerRef.getValue() : serverHandlerRef.getOldValue(); if (serverDeleage != null) { @@ -152,8 +152,7 @@ public class NetworkServerInjector extends PlayerInjector { Callback sendPacketCallback = new MethodInterceptor() { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { - - Packet packet = (Packet) args[0]; + Object packet = args[0]; if (packet != null) { packet = handlePacketSending(packet); @@ -237,7 +236,7 @@ public class NetworkServerInjector extends PlayerInjector { } private Class getFirstMinecraftSuperClass(Class clazz) { - if (clazz.getName().startsWith("net.minecraft.server.")) + if (clazz.getName().startsWith(MinecraftReflection.getMinecraftPackage())) return clazz; else if (clazz.equals(Object.class)) return clazz; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java index c5f832b9..3722e4f5 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java @@ -26,8 +26,6 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; -import net.minecraft.server.Packet; - import org.bukkit.Server; import org.bukkit.entity.Player; @@ -501,7 +499,7 @@ public class PlayerInjectionHandler { * @throws IllegalAccessException If the reflection machinery failed. * @throws InvocationTargetException If the underlying method caused an error. */ - public void processPacket(Player player, Packet mcPacket) throws IllegalAccessException, InvocationTargetException { + public void processPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException { PlayerInjector injector = getInjector(player); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java index 6eda9848..239f6b7a 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java @@ -25,12 +25,8 @@ import java.lang.reflect.Method; import java.net.Socket; import java.net.SocketAddress; -import net.minecraft.server.EntityPlayer; -import net.minecraft.server.NetLoginHandler; -import net.minecraft.server.Packet; import net.sf.cglib.proxy.Factory; -import org.bukkit.craftbukkit.entity.CraftPlayer; import org.bukkit.entity.Player; import com.comphenix.protocol.Packets; @@ -38,6 +34,7 @@ import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketListener; +import com.comphenix.protocol.injector.BukkitUnwrapper; import com.comphenix.protocol.injector.GamePhase; import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; @@ -45,6 +42,7 @@ import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.VolatileField; +import com.comphenix.protocol.utility.MinecraftReflection; abstract class PlayerInjector { @@ -122,9 +120,9 @@ abstract class PlayerInjector { * @param player - the player to retrieve. * @return Notch player object. */ - protected EntityPlayer getEntityPlayer(Player player) { - CraftPlayer craft = (CraftPlayer) player; - return craft.getHandle(); + protected Object getEntityPlayer(Player player) { + BukkitUnwrapper unwrapper = new BukkitUnwrapper(); + return unwrapper.unwrapItem(player); } /** @@ -138,7 +136,7 @@ abstract class PlayerInjector { //Dispatch to the correct injection method if (injectionSource instanceof Player) initializePlayer(injectionSource); - else if (injectionSource instanceof NetLoginHandler) + else if (MinecraftReflection.isLoginHandler(injectionSource)) initializeLogin(injectionSource); else throw new IllegalArgumentException("Cannot initialize a player hook using a " + injectionSource.getClass().getName()); @@ -150,7 +148,7 @@ abstract class PlayerInjector { */ public void initializePlayer(Object player) { - EntityPlayer notchEntity = getEntityPlayer((Player) player); + Object notchEntity = getEntityPlayer((Player) player); if (!hasInitialized) { // Do this first, in case we encounter an exception @@ -204,7 +202,7 @@ abstract class PlayerInjector { // And the queue method if (queueMethod == null) queueMethod = FuzzyReflection.fromClass(reference.getType()). - getMethodByParameters("queue", Packet.class ); + getMethodByParameters("queue", MinecraftReflection.getPacketClass()); // And the data input stream that we'll use to identify a player if (inputField == null) @@ -323,7 +321,7 @@ abstract class PlayerInjector { } } - private Field getProxyField(EntityPlayer notchEntity, Field serverField) { + private Field getProxyField(Object notchEntity, Field serverField) { try { Object handler = FieldUtils.readField(serverHandlerField, notchEntity, true); @@ -379,7 +377,7 @@ abstract class PlayerInjector { try { // Well, that sucks. Try just Minecraft objects then. netHandlerField = FuzzyReflection.fromClass(networkManager.getClass(), true). - getFieldByType(FuzzyReflection.MINECRAFT_OBJECT); + getFieldByType(MinecraftReflection.MINECRAFT_OBJECT); } catch (RuntimeException e2) { throw new IllegalAccessException("Cannot locate net handler. " + e2.getMessage()); @@ -398,10 +396,10 @@ abstract class PlayerInjector { * @return The stored entity player. * @throws IllegalAccessException If the reflection failed. */ - private EntityPlayer getEntityPlayer(Object netHandler) throws IllegalAccessException { + private Object getEntityPlayer(Object netHandler) throws IllegalAccessException { if (entityPlayerField == null) entityPlayerField = FuzzyReflection.fromObject(netHandler).getFieldByType(".*EntityPlayer"); - return (EntityPlayer) FieldUtils.readField(entityPlayerField, netHandler); + return FieldUtils.readField(entityPlayerField, netHandler); } /** @@ -410,15 +408,15 @@ abstract class PlayerInjector { * @throws IllegalAccessException If the reflection machinery failed. * @throws InvocationTargetException If the underlying method caused an error. */ - public void processPacket(Packet packet) throws IllegalAccessException, InvocationTargetException { + public void processPacket(Object packet) throws IllegalAccessException, InvocationTargetException { Object netHandler = getNetHandler(); // Get the process method if (processMethod == null) { try { - processMethod = FuzzyReflection.fromClass(Packet.class). - getMethodByParameters("processPacket", netHandlerField.getType()); + processMethod = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()). + getMethodByParameters("processPacket", netHandlerField.getType()); } catch (RuntimeException e) { throw new IllegalArgumentException("Cannot locate process packet method: " + e.getMessage()); } @@ -440,7 +438,7 @@ abstract class PlayerInjector { * @param filtered - whether or not the packet will be filtered by our listeners. * @param InvocationTargetException If an error occured when sending the packet. */ - public abstract void sendServerPacket(Packet packet, boolean filtered) throws InvocationTargetException; + public abstract void sendServerPacket(Object packet, boolean filtered) throws InvocationTargetException; /** * Inject a hook to catch packets sent to the current player. @@ -501,7 +499,7 @@ abstract class PlayerInjector { * @param packet - packet to sent. * @return The given packet, or the packet replaced by the listeners. */ - public Packet handlePacketSending(Packet packet) { + public Object handlePacketSending(Object packet) { try { // Get the packet ID too Integer id = invoker.getPacketID(packet); @@ -516,7 +514,7 @@ abstract class PlayerInjector { if (updateOnLogin) { if (id == Packets.Server.LOGIN) { try { - updatedPlayer = getEntityPlayer(getNetHandler()).getBukkitEntity(); + updatedPlayer = (Player) MinecraftReflection.getBukkitEntity(getEntityPlayer(getNetHandler())); } catch (IllegalAccessException e) { reporter.reportDetailed(this, "Cannot update player in PlayerEvent.", e, packet); } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Metrics.java b/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Metrics.java index 493bbeaf..3ee7d6e9 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Metrics.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Metrics.java @@ -234,7 +234,8 @@ class Metrics { * * @return True if statistics measuring is running, otherwise false. */ - public boolean start() { + @SuppressWarnings("deprecation") + public boolean start() { synchronized (optOutLock) { // Did we opt out? if (isOptOut()) { diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/FuzzyReflection.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/FuzzyReflection.java index ae986fdb..4c8cd1bf 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/FuzzyReflection.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/FuzzyReflection.java @@ -32,11 +32,6 @@ import java.util.regex.Pattern; * @author Kristian */ public class FuzzyReflection { - - /** - * Matches a Minecraft object. - */ - public static final String MINECRAFT_OBJECT = "net\\.minecraft(\\.\\w+)+"; // The class we're actually representing private Class source; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/CachedPackage.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/CachedPackage.java new file mode 100644 index 00000000..e69f956c --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/CachedPackage.java @@ -0,0 +1,46 @@ +package com.comphenix.protocol.utility; + +import java.util.Map; + +import com.google.common.collect.Maps; + +/** + * Represents a dynamic package and an arbitrary number of cached classes. + * + * @author Kristian + */ +class CachedPackage { + private Map> cache; + private String packageName; + + public CachedPackage(String packageName) { + this.packageName = packageName; + this.cache = Maps.newConcurrentMap(); + } + + /** + * Retrieve the class object of a specific class in the current package. + * @param className - the specific class. + * @return Class object. + * @throws RuntimeException If we are unable to find the given class. + */ + @SuppressWarnings("rawtypes") + public Class getPackageClass(String className) { + try { + Class result = cache.get(className); + + // Concurrency is not a problem - we don't care if we look up a class twice + if (result == null) { + // Look up the class dynamically + result = CachedPackage.class.getClassLoader(). + loadClass(packageName + "." + className); + cache.put(className, result); + } + + return result; + + } catch (ClassNotFoundException e) { + throw new RuntimeException("Cannot find class " + className, e); + } + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java new file mode 100644 index 00000000..ded29674 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java @@ -0,0 +1,463 @@ +package com.comphenix.protocol.utility; + +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +import javax.annotation.Nonnull; + +import org.bukkit.Bukkit; +import org.bukkit.Server; +import org.bukkit.inventory.ItemStack; + +import com.comphenix.protocol.injector.BukkitUnwrapper; + +/** + * Methods and constants specifically used in conjuction with reflecting Minecraft object. + * + * @author Kristian + */ +public class MinecraftReflection { + /** + * Regular expression that matches a Minecraft object. + */ + public static final String MINECRAFT_OBJECT = "net\\.minecraft(\\.\\w+)+"; + + /** + * The package name of all the classes that belongs to the native code in Minecraft. + */ + private static String MINECRAFT_PREFIX_PACKAGE = "net.minecraft.server"; + + private static String MINECRAFT_FULL_PACKAGE = null; + private static String CRAFTBUKKIT_PACKAGE = null; + + private static CachedPackage minecraftPackage; + private static CachedPackage craftbukkitPackage; + + // org.bukkit.craftbukkit + private static Class craftItemStackClass; + private static Constructor craftNMSConstructor; + private static Constructor craftBukkitConstructor; + + // net.minecraft.server + private static Class itemStackArrayClass; + + /** + * 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; + + Server craftServer = Bukkit.getServer(); + + // This server should have a "getHandle" method that we can use + if (craftServer != null) { + try { + Class craftClass = craftServer.getClass(); + Method getHandle = craftClass.getMethod("getHandle"); + + Class returnType = getHandle.getReturnType(); + String returnName = returnType.getCanonicalName(); + + // The return type will tell us the full package, regardless of formating + CRAFTBUKKIT_PACKAGE = getPackage(craftClass.getCanonicalName()); + MINECRAFT_FULL_PACKAGE = getPackage(returnName); + 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); + } + + } else { + throw new IllegalStateException("Could not find Bukkit. Is it running?"); + } + } + + /** + * 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; + } + + /** + * 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 + 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) { + return fullName.substring(0, fullName.lastIndexOf(".")); + } + + /** + * 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) + throw new IllegalArgumentException("Cannot determine the type of a null object."); + + // Doesn't matter if we don't check for the version here + return obj.getClass().getName().startsWith(MINECRAFT_PREFIX_PACKAGE); + } + + /** + * 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) + throw new IllegalArgumentException("Cannot determine the type of a null object."); + + String javaName = obj.getClass().getName(); + return javaName.startsWith(MINECRAFT_PREFIX_PACKAGE) && javaName.endsWith(className); + } + + /** + * 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 { + return nmsObject.getClass().getMethod("getBukkitEntity").invoke(nmsObject); + } catch (Exception e) { + throw new RuntimeException("Cannot get Bukkit entity from " + nmsObject, e); + } + } + + /** + * Determine if a given object is a ChunkPosition. + * @param obj - the object to test. + * @return TRUE if it can, FALSE otherwise. + */ + @SuppressWarnings("unchecked") + public static boolean isChunkPosition(Object obj) { + return getChunkPositionClass().isAssignableFrom(obj.getClass()); + } + + /** + * Determine if a given object is a ChunkCoordinate. + * @param obj - the object to test. + * @return TRUE if it can, FALSE otherwise. + */ + @SuppressWarnings("unchecked") + public static boolean isChunkCoordinates(Object obj) { + return 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. + */ + @SuppressWarnings("unchecked") + public static boolean isPacketClass(Object obj) { + return getPacketClass().isAssignableFrom(obj.getClass()); + } + + /** + * Determine if the given object is a NetLoginHandler. + * @param obj - the given object. + * @return TRUE if it is, FALSE otherwise. + */ + @SuppressWarnings("unchecked") + public static boolean isLoginHandler(Object obj) { + return getNetLoginHandlerClass().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. + */ + @SuppressWarnings("unchecked") + public static boolean isMinecraftEntity(Object obj) { + return getEntityClass().isAssignableFrom(obj.getClass()); + } + + /** + * Determine if the given object is a NMS ItemStack. + * @param obj - the given object. + * @return TRUE if it is, FALSE otherwise. + */ + @SuppressWarnings("unchecked") + public static boolean isItemStack(Object value) { + return getItemStackClass().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. + */ + @SuppressWarnings("unchecked") + public static boolean isMinecraftPlayer(Object obj) { + return 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. + */ + @SuppressWarnings("unchecked") + public static boolean isWatchableObject(Object obj) { + return 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. + */ + @SuppressWarnings("unchecked") + public static boolean isDataWatcher(Object obj) { + return getDataWatcherClass().isAssignableFrom(obj.getClass()); + } + + /** + * Determine if the given object is a CraftItemStack instancey. + * @param obj - the given object. + * @return TRUE if it is, FALSE otherwise. + */ + @SuppressWarnings("unchecked") + public static boolean isCraftItemStack(Object obj) { + return getCraftItemStackClass().isAssignableFrom(obj.getClass()); + } + + /** + * Retrieve the EntityPlayer (NMS) class. + * @return The entity class. + */ + @SuppressWarnings("rawtypes") + public static Class getEntityPlayerClass() { + return getMinecraftClass("EntityPlayer"); + } + + /** + * Retrieve the entity (NMS) class. + * @return The entity class. + */ + @SuppressWarnings("rawtypes") + public static Class getEntityClass() { + return getMinecraftClass("Entity"); + } + + /** + * Retrieve the packet class. + * @return The packet class. + */ + @SuppressWarnings("rawtypes") + public static Class getPacketClass() { + return getMinecraftClass("Packet"); + } + + /** + * Retrieve the NetLoginHandler class. + * @return The NetLoginHandler class. + */ + @SuppressWarnings("rawtypes") + public static Class getNetLoginHandlerClass() { + return getMinecraftClass("NetLoginHandler"); + } + + /** + * Retrieve the NetLoginHandler class. + * @return The NetLoginHandler class. + */ + @SuppressWarnings("rawtypes") + public static Class getItemStackClass() { + return getMinecraftClass("ItemStack"); + } + + /** + * Retrieve the WorldType class. + * @return The WorldType class. + */ + @SuppressWarnings("rawtypes") + public static Class getWorldTypeClass() { + return getMinecraftClass("WorldType"); + } + + /** + * Retrieve the DataWatcher class. + * @return The DataWatcher class. + */ + @SuppressWarnings("rawtypes") + public static Class getDataWatcherClass() { + return getMinecraftClass("DataWatcher"); + } + + /** + * Retrieve the ChunkPosition class. + * @return The ChunkPosition class. + */ + @SuppressWarnings("rawtypes") + public static Class getChunkPositionClass() { + return getMinecraftClass("ChunkPosition"); + } + + /** + * Retrieve the ChunkPosition class. + * @return The ChunkPosition class. + */ + @SuppressWarnings("rawtypes") + public static Class getChunkCoordinatesClass() { + return getMinecraftClass("ChunkCoordinates"); + } + + /** + * Retrieve the WatchableObject class. + * @return The WatchableObject class. + */ + @SuppressWarnings("rawtypes") + public static Class getWatchableObjectClass() { + return getMinecraftClass("WatchableObject"); + } + + /** + * Retrieve the ItemStack[] class. + * @return The ItemStack[] class. + */ + @SuppressWarnings("rawtypes") + 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. + */ + @SuppressWarnings("rawtypes") + 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. + */ + @SuppressWarnings("rawtypes") + public static Class getCraftItemStackClass() { + if (craftItemStackClass == null) + craftItemStackClass = getCraftBukkitClass("inventory.CraftItemStack"); + return craftItemStackClass; + } + + /** + * Retrieve a CraftItemStack from a given ItemStack. + * @param bukkitItemStack - the Bukkit ItemStack to convert. + * @return A CraftItemStack as an ItemStack. + */ + @SuppressWarnings("unchecked") + public static ItemStack getBukkitItemStack(ItemStack bukkitItemStack) { + if (craftBukkitConstructor == null) { + try { + craftBukkitConstructor = getCraftItemStackClass().getConstructor(ItemStack.class); + } catch (Exception e) { + 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); + } + } + + /** + * Retrieve the Bukkit ItemStack from a given net.minecraft.server ItemStack. + * @param minecraftItemStack - the NMS ItemStack to wrap. + * @return The wrapped ItemStack. + */ + @SuppressWarnings("unchecked") + public static ItemStack getBukkitItemStack(Object minecraftItemStack) { + if (craftNMSConstructor == null) { + try { + craftNMSConstructor = getCraftItemStackClass().getConstructor(minecraftItemStack.getClass()); + } catch (Exception e) { + throw new RuntimeException("Cannot find CraftItemStack(net.mineraft.server.ItemStack).", e); + } + } + + // Try to create the CraftItemStack + try { + return (ItemStack) craftNMSConstructor.newInstance(minecraftItemStack); + } catch (Exception e) { + throw new RuntimeException("Cannot construct CraftItemStack.", e); + } + } + + /** + * Retrieve the net.minecraft.server ItemStack from a Bukkit ItemStack. + * @param stack - the Bukkit ItemStack to convert. + * @return The NMS ItemStack. + */ + 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 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()); + 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. + */ + @SuppressWarnings("rawtypes") + public static Class getMinecraftClass(String className) { + if (minecraftPackage == null) + minecraftPackage = new CachedPackage(getMinecraftPackage()); + return minecraftPackage.getPackageClass(className); + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java index ce2af3e8..7b9bfe49 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java @@ -1,16 +1,13 @@ package com.comphenix.protocol.wrappers; import java.lang.ref.WeakReference; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.List; -import net.minecraft.server.DataWatcher; -import net.minecraft.server.WatchableObject; - import org.bukkit.World; import org.bukkit.WorldType; -import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.entity.Entity; import org.bukkit.inventory.ItemStack; @@ -19,6 +16,7 @@ import com.comphenix.protocol.ProtocolManager; import com.comphenix.protocol.reflect.EquivalentConverter; import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.instances.DefaultInstances; +import com.comphenix.protocol.utility.MinecraftReflection; /** * Contains several useful equivalent converters for normal Bukkit types. @@ -29,9 +27,13 @@ public class BukkitConverters { // Check whether or not certain classes exists private static boolean hasWorldType = false; + // Used to access the world type + private static Method worldTypeName; + private static Method worldTypeGetType; + static { try { - Class.forName("net.minecraft.server.WorldType"); + Class.forName(MinecraftReflection.getMinecraftPackage() + ".WorldType"); hasWorldType = true; } catch (ClassNotFoundException e) { } @@ -100,8 +102,8 @@ public class BukkitConverters { } public WrappedWatchableObject getSpecific(Object generic) { - if (generic instanceof WatchableObject) - return new WrappedWatchableObject((WatchableObject) generic); + if (MinecraftReflection.isWatchableObject(generic)) + return new WrappedWatchableObject(generic); else if (generic instanceof WrappedWatchableObject) return (WrappedWatchableObject) generic; else @@ -128,8 +130,8 @@ public class BukkitConverters { @Override public WrappedDataWatcher getSpecific(Object generic) { - if (generic instanceof DataWatcher) - return new WrappedDataWatcher((DataWatcher) generic); + if (MinecraftReflection.isDataWatcher(generic)) + return new WrappedDataWatcher(generic); else if (generic instanceof WrappedDataWatcher) return (WrappedDataWatcher) generic; else @@ -153,15 +155,35 @@ public class BukkitConverters { return null; return getIgnoreNull(new EquivalentConverter() { + @SuppressWarnings("unchecked") @Override public Object getGeneric(Class genericType, WorldType specific) { - return net.minecraft.server.WorldType.getType(specific.getName()); + try { + if (worldTypeGetType == null) + worldTypeGetType = MinecraftReflection.getWorldTypeClass().getMethod("getType", String.class); + + // Convert to the Bukkit world type + return worldTypeGetType.invoke(this, specific.getName()); + + } catch (Exception e) { + throw new FieldAccessException("Cannot find the WorldType.getType() method.", e); + } } + @SuppressWarnings("unchecked") @Override public WorldType getSpecific(Object generic) { - net.minecraft.server.WorldType type = (net.minecraft.server.WorldType) generic; - return WorldType.getByName(type.name()); + try { + if (worldTypeName == null) + worldTypeName = MinecraftReflection.getWorldTypeClass().getMethod("name"); + + // Dynamically call the namne method + String name = (String) worldTypeName.invoke(generic); + return WorldType.getByName(name); + + } catch (Exception e) { + throw new FieldAccessException("Cannot call the name method in WorldType.", e); + } } @Override @@ -221,12 +243,12 @@ public class BukkitConverters { public static EquivalentConverter getItemStackConverter() { return getIgnoreNull(new EquivalentConverter() { public Object getGeneric(Class genericType, ItemStack specific) { - return toStackNMS(specific); + return MinecraftReflection.getMinecraftItemStack(specific); } @Override public ItemStack getSpecific(Object generic) { - return new CraftItemStack((net.minecraft.server.ItemStack) generic); + return MinecraftReflection.getBukkitItemStack(generic); } @Override @@ -236,20 +258,6 @@ public class BukkitConverters { }); } - /** - * Convert an item stack to the NMS equivalent. - * @param stack - Bukkit stack to convert. - * @return A bukkit stack. - */ - private static net.minecraft.server.ItemStack toStackNMS(ItemStack stack) { - // We must be prepared for an object that simply implements ItemStcak - if (stack instanceof CraftItemStack) { - return ((CraftItemStack) stack).getHandle(); - } else { - return (new CraftItemStack(stack)).getHandle(); - } - } - /** * Wraps a given equivalent converter in NULL checks, ensuring that such values are ignored. * @param delegate - the underlying equivalent converter. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/ChunkPosition.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/ChunkPosition.java index a00a139a..fd7bc679 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/ChunkPosition.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/ChunkPosition.java @@ -1,10 +1,13 @@ package com.comphenix.protocol.wrappers; +import java.lang.reflect.Constructor; + import org.bukkit.util.Vector; import com.comphenix.protocol.reflect.EquivalentConverter; import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.utility.MinecraftReflection; import com.google.common.base.Objects; /** @@ -19,6 +22,8 @@ public class ChunkPosition { */ public static ChunkPosition ORIGIN = new ChunkPosition(0, 0, 0); + private static Constructor chunkPositionConstructor; + // Use protected members, like Bukkit protected final int x; protected final int y; @@ -128,27 +133,35 @@ public class ChunkPosition { */ public static EquivalentConverter getConverter() { return new EquivalentConverter() { + @SuppressWarnings("unchecked") @Override public Object getGeneric(Class genericType, ChunkPosition specific) { - return new net.minecraft.server.ChunkPosition(specific.x, specific.y, specific.z); + if (chunkPositionConstructor == null) { + try { + chunkPositionConstructor = MinecraftReflection.getChunkPositionClass(). + getConstructor(int.class, int.class, int.class); + } catch (Exception e) { + throw new RuntimeException("Cannot find chunk position constructor.", e); + } + } + + // Construct the underlying ChunkPosition + try { + return chunkPositionConstructor.newInstance(specific.x, specific.y, specific.z); + } catch (Exception e) { + throw new RuntimeException("Cannot construct ChunkPosition.", e); + } } @Override public ChunkPosition getSpecific(Object generic) { - if (generic instanceof net.minecraft.server.ChunkPosition) { - net.minecraft.server.ChunkPosition other = (net.minecraft.server.ChunkPosition) generic; + if (MinecraftReflection.isChunkPosition(generic)) { + // Use a structure modifier + intModifier = new StructureModifier(generic.getClass(), null, false).withType(int.class); - try { - if (intModifier == null) - return new ChunkPosition(other.x, other.y, other.z); - } catch (LinkageError e) { - // It could happen. If it does, use a structure modifier instead - intModifier = new StructureModifier(other.getClass(), null, false).withType(int.class); - - // Damn it all - if (intModifier.size() < 3) { - throw new IllegalStateException("Cannot read class " + other.getClass() + " for its integer fields."); - } + // Damn it all + if (intModifier.size() < 3) { + throw new IllegalStateException("Cannot read class " + generic.getClass() + " for its integer fields."); } if (intModifier.size() >= 3) { diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedChunkCoordinate.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedChunkCoordinate.java index 8385770d..2b6bb244 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedChunkCoordinate.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedChunkCoordinate.java @@ -1,9 +1,9 @@ package com.comphenix.protocol.wrappers; +import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.utility.MinecraftReflection; import com.google.common.base.Objects; -import net.minecraft.server.ChunkCoordinates; - /** * Allows access to a chunk coordinate. * @@ -16,23 +16,42 @@ public class WrappedChunkCoordinate implements Comparable intModifier; + /** * Create a new empty wrapper. */ + @SuppressWarnings("rawtypes") public WrappedChunkCoordinate() { - this(new ChunkCoordinates()); + try { + this.handle = (Comparable) MinecraftReflection.getChunkCoordinatesClass().newInstance(); + initializeModifier(); + } catch (Exception e) { + throw new RuntimeException("Cannot construct chunk coordinate."); + } } /** * Create a wrapper for a specific chunk coordinates. * @param handle - the NMS chunk coordinates. */ - public WrappedChunkCoordinate(ChunkCoordinates handle) { + @SuppressWarnings("rawtypes") + public WrappedChunkCoordinate(Comparable handle) { if (handle == null) throw new IllegalArgumentException("handle cannot be NULL"); this.handle = handle; + initializeModifier(); + } + + // Ensure that the structure modifier is initialized + private void initializeModifier() { + if (intModifier == null) { + intModifier = new StructureModifier(handle.getClass(), null, false).withType(int.class); + } } /** @@ -56,7 +75,7 @@ public class WrappedChunkCoordinate implements Comparable { /** * Used to assign integer IDs to given types. @@ -55,7 +58,7 @@ public class WrappedDataWatcher { private static boolean hasInitialized; // The underlying DataWatcher we're modifying - protected DataWatcher handle; + protected Object handle; // Lock private ReadWriteLock readWriteLock; @@ -69,7 +72,13 @@ public class WrappedDataWatcher { */ public WrappedDataWatcher() { // Just create a new watcher - this(new DataWatcher()); + try { + this.handle = MinecraftReflection.getDataWatcherClass().newInstance(); + initialize(); + + } catch (Exception e) { + throw new RuntimeException("Unable to construct DataWatcher.", e); + } } /** @@ -77,27 +86,42 @@ public class WrappedDataWatcher { * @param handle - the data watcher to wrap. * @throws FieldAccessException If we're unable to wrap a DataWatcher. */ - public WrappedDataWatcher(DataWatcher handle) { - this.handle = handle; + public WrappedDataWatcher(Object handle) { + if (handle == null) + throw new IllegalArgumentException("Handle cannot be NULL."); + if (!MinecraftReflection.isDataWatcher(handle)) + throw new IllegalArgumentException("The value " + handle + " is not a DataWatcher."); - try { - initialize(); - } catch (FieldAccessException e) { - throw new RuntimeException("Cannot initialize wrapper.", e); - } + this.handle = handle; + initialize(); } /** - * Create a new data watcher from a list of watchable objects. + * Create a new data watcher for a list of watchable objects. + *

+ * Note that the watchable objects are not cloned, and will be modified in place. Use "deepClone" if + * that is not desirable. + *

+ * The {@link #removeObject(int)} method will not modify the given list, however. + * * @param watchableObjects - list of watchable objects that will be copied. * @throws FieldAccessException Unable to read watchable objects. */ public WrappedDataWatcher(List watchableObjects) throws FieldAccessException { this(); + + Lock writeLock = getReadWriteLock().writeLock(); + Map map = getWatchableObjectMap(); - // Fill the underlying map - for (WrappedWatchableObject watched : watchableObjects) { - setObject(watched.getIndex(), watched.getValue()); + writeLock.lock(); + + try { + // Add the watchable objects by reference + for (WrappedWatchableObject watched : watchableObjects) { + map.put(watched.getIndex(), watched.handle); + } + } finally { + writeLock.unlock(); } } @@ -105,7 +129,7 @@ public class WrappedDataWatcher { * Retrieves the underlying data watcher. * @return The underlying data watcher. */ - public DataWatcher getHandle() { + public Object getHandle() { return handle; } @@ -215,7 +239,7 @@ public class WrappedDataWatcher { */ public Object getObject(int index) throws FieldAccessException { // The get method will take care of concurrency - WatchableObject watchable = getWatchedObject(index); + Object watchable = getWatchedObject(index); if (watchable != null) { return new WrappedWatchableObject(watchable).getValue(); @@ -230,15 +254,16 @@ public class WrappedDataWatcher { * @throws FieldAccessException If reflection failed. */ public List getWatchableObjects() throws FieldAccessException { + Lock readLock = getReadWriteLock().readLock(); + readLock.lock(); + try { - getReadWriteLock().readLock().lock(); - List result = new ArrayList(); // Add each watchable object to the list for (Object watchable : getWatchableObjectMap().values()) { if (watchable != null) { - result.add(new WrappedWatchableObject((WatchableObject) watchable)); + result.add(new WrappedWatchableObject(watchable)); } else { result.add(null); } @@ -246,7 +271,7 @@ public class WrappedDataWatcher { return result; } finally { - getReadWriteLock().readLock().unlock(); + readLock.unlock(); } } @@ -266,6 +291,20 @@ public class WrappedDataWatcher { } } + /** + * Clone the content of the current DataWatcher. + * @return A cloned data watcher. + */ + public WrappedDataWatcher deepClone() { + WrappedDataWatcher clone = new WrappedDataWatcher(); + + // Make a new copy instead + for (WrappedWatchableObject watchable : this) { + clone.setObject(watchable.getIndex(), watchable.getClonedValue()); + } + return clone; + } + /** * Retrieve the number of watched objects. * @return Watched object count. @@ -282,6 +321,23 @@ public class WrappedDataWatcher { } } + /** + * Remove a given object from the underlying DataWatcher. + * @param index - index of the object to remove. + * @return The watchable object that was removed, or NULL If none could be found. + */ + public WrappedWatchableObject removeObject(int index) { + Lock writeLock = getReadWriteLock().writeLock(); + writeLock.lock(); + + try { + Object removed = getWatchableObjectMap().remove(index); + return removed != null ? new WrappedWatchableObject(removed) : null; + } finally { + writeLock.unlock(); + } + } + /** * Set a watched byte. * @param index - index of the watched byte. @@ -305,7 +361,7 @@ public class WrappedDataWatcher { writeLock.lock(); try { - WatchableObject watchable = getWatchedObject(index); + Object watchable = getWatchedObject(index); if (watchable != null) { new WrappedWatchableObject(watchable).setValue(newValue, update); @@ -325,18 +381,18 @@ public class WrappedDataWatcher { } } - private WatchableObject getWatchedObject(int index) throws FieldAccessException { + private Object getWatchedObject(int index) throws FieldAccessException { // We use the get-method first and foremost if (getKeyValueMethod != null) { try { - return (WatchableObject) getKeyValueMethod.invoke(handle, index); + return getKeyValueMethod.invoke(handle, index); } catch (Exception e) { throw new FieldAccessException("Cannot invoke get key method for index " + index, e); } } else { try { getReadWriteLock().readLock().lock(); - return (WatchableObject) getWatchableObjectMap().get(index); + return getWatchableObjectMap().get(index); } finally { getReadWriteLock().readLock().unlock(); @@ -388,8 +444,8 @@ public class WrappedDataWatcher { */ public static WrappedDataWatcher getEntityWatcher(Entity entity) throws FieldAccessException { if (entityDataField == null) - entityDataField = FuzzyReflection.fromClass(net.minecraft.server.Entity.class, true). - getFieldByType("datawatcher", DataWatcher.class); + entityDataField = FuzzyReflection.fromClass(MinecraftReflection.getEntityClass(), true). + getFieldByType("datawatcher", MinecraftReflection.getDataWatcherClass()); BukkitUnwrapper unwrapper = new BukkitUnwrapper(); @@ -397,7 +453,7 @@ public class WrappedDataWatcher { Object nsmWatcher = FieldUtils.readField(entityDataField, unwrapper.unwrapItem(entity), true); if (nsmWatcher != null) - return new WrappedDataWatcher((DataWatcher) nsmWatcher); + return new WrappedDataWatcher(nsmWatcher); else return null; @@ -417,7 +473,7 @@ public class WrappedDataWatcher { else return; - FuzzyReflection fuzzy = FuzzyReflection.fromClass(DataWatcher.class, true); + FuzzyReflection fuzzy = FuzzyReflection.fromClass(MinecraftReflection.getDataWatcherClass(), true); for (Field lookup : fuzzy.getFieldListByType(Map.class)) { if (Modifier.isStatic(lookup.getModifiers())) { @@ -476,4 +532,20 @@ public class WrappedDataWatcher { // Use fallback method } } + + @Override + public Iterator iterator() { + // We'll wrap the iterator instead of creating a new list every time + return Iterators.transform(getWatchableObjectMap().values().iterator(), + new Function() { + + @Override + public WrappedWatchableObject apply(@Nullable Object item) { + if (item != null) + return new WrappedWatchableObject(item); + else + return null; + } + }); + } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedWatchableObject.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedWatchableObject.java index 2124d2a3..c3af7039 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedWatchableObject.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedWatchableObject.java @@ -1,13 +1,15 @@ package com.comphenix.protocol.wrappers; +import java.lang.reflect.Constructor; + +import org.bukkit.inventory.ItemStack; + import com.comphenix.protocol.reflect.EquivalentConverter; import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.instances.DefaultInstances; - -import net.minecraft.server.ChunkCoordinates; -import net.minecraft.server.ItemStack; -import net.minecraft.server.WatchableObject; +import com.comphenix.protocol.utility.MinecraftReflection; +import com.google.common.base.Objects; /** * Represents a watchable object. @@ -22,7 +24,13 @@ public class WrappedWatchableObject { // The field containing the value itself private static StructureModifier baseModifier; - protected WatchableObject handle; + // Used to create new watchable objects + private static Constructor watchableConstructor; + + // The watchable object class type + private static Class watchableObjectClass; + + protected Object handle; protected StructureModifier modifier; // Type of the stored value @@ -32,7 +40,7 @@ public class WrappedWatchableObject { * Wrap a given raw Minecraft watchable object. * @param handle - the raw watchable object to wrap. */ - public WrappedWatchableObject(WatchableObject handle) { + public WrappedWatchableObject(Object handle) { load(handle); } @@ -41,6 +49,7 @@ public class WrappedWatchableObject { * @param index - the index. * @param value - non-null value of specific types. */ + @SuppressWarnings("unchecked") public WrappedWatchableObject(int index, Object value) { if (value == null) throw new IllegalArgumentException("Value cannot be NULL."); @@ -49,24 +58,44 @@ public class WrappedWatchableObject { Integer typeID = WrappedDataWatcher.getTypeID(value.getClass()); if (typeID != null) { - load(new WatchableObject(typeID, index, getUnwrapped(value))); + if (watchableConstructor == null) { + try { + watchableConstructor = MinecraftReflection.getWatchableObjectClass(). + getConstructor(int.class, int.class, Object.class); + } catch (Exception e) { + throw new RuntimeException("Cannot get the WatchableObject(int, int, Object) constructor.", e); + } + } + + // Create the object + try { + load(watchableConstructor.newInstance(typeID, index, getUnwrapped(value))); + } catch (Exception e) { + throw new RuntimeException("Cannot construct underlying WatchableObject.", e); + } } else { throw new IllegalArgumentException("Cannot watch the type " + value.getClass()); } } // Wrap a NMS object - private void load(WatchableObject handle) { + private void load(Object handle) { initialize(); this.handle = handle; this.modifier = baseModifier.withTarget(handle); + + // Make sure the type is correct + if (!watchableObjectClass.isAssignableFrom(handle.getClass())) { + throw new ClassCastException("Cannot cast the class " + handle.getClass().getName() + + " to " + watchableObjectClass.getName()); + } } /** * Retrieves the underlying watchable object. * @return The underlying watchable object. */ - public WatchableObject getHandle() { + public Object getHandle() { return handle; } @@ -76,7 +105,8 @@ public class WrappedWatchableObject { private static void initialize() { if (!hasInitialized) { hasInitialized = true; - baseModifier = new StructureModifier(WatchableObject.class, null, false); + watchableObjectClass = MinecraftReflection.getWatchableObjectClass(); + baseModifier = new StructureModifier(watchableObjectClass, null, false); } } @@ -204,12 +234,13 @@ public class WrappedWatchableObject { * @param value - the raw NMS object to wrap. * @return The wrapped object. */ + @SuppressWarnings("rawtypes") static Object getWrapped(Object value) { // Handle the special cases - if (value instanceof net.minecraft.server.ItemStack) { + if (MinecraftReflection.isItemStack(value)) { return BukkitConverters.getItemStackConverter().getSpecific(value); - } else if (value instanceof ChunkCoordinates) { - return new WrappedChunkCoordinate((ChunkCoordinates) value); + } else if (MinecraftReflection.isChunkCoordinates(value)) { + return new WrappedChunkCoordinate((Comparable) value); } else { return value; } @@ -221,9 +252,9 @@ public class WrappedWatchableObject { * @return The wrapped class type. */ static Class getWrappedType(Class unwrapped) { - if (unwrapped.equals(net.minecraft.server.ChunkPosition.class)) + if (unwrapped.equals(MinecraftReflection.getChunkPositionClass())) return ChunkPosition.class; - else if (unwrapped.equals(ChunkCoordinates.class)) + else if (unwrapped.equals(MinecraftReflection.getChunkCoordinatesClass())) return WrappedChunkCoordinate.class; else return unwrapped; @@ -240,7 +271,7 @@ public class WrappedWatchableObject { return ((WrappedChunkCoordinate) wrapped).getHandle(); else if (wrapped instanceof ItemStack) return BukkitConverters.getItemStackConverter().getGeneric( - net.minecraft.server.ItemStack.class, (org.bukkit.inventory.ItemStack) wrapped); + MinecraftReflection.getItemStackClass(), (ItemStack) wrapped); else return wrapped; } @@ -252,9 +283,9 @@ public class WrappedWatchableObject { */ static Class getUnwrappedType(Class wrapped) { if (wrapped.equals(ChunkPosition.class)) - return net.minecraft.server.ChunkPosition.class; + return MinecraftReflection.getChunkPositionClass(); else if (wrapped.equals(WrappedChunkCoordinate.class)) - return ChunkCoordinates.class; + return MinecraftReflection.getChunkCoordinatesClass(); else return wrapped; } @@ -265,7 +296,8 @@ public class WrappedWatchableObject { * @throws FieldAccessException If we're unable to use reflection. */ public WrappedWatchableObject deepClone() throws FieldAccessException { - WrappedWatchableObject clone = new WrappedWatchableObject(DefaultInstances.DEFAULT.getDefault(WatchableObject.class)); + @SuppressWarnings("unchecked") + WrappedWatchableObject clone = new WrappedWatchableObject(DefaultInstances.DEFAULT.getDefault(MinecraftReflection.getWatchableObjectClass())); clone.setDirtyState(getDirtyState()); clone.setIndex(getIndex()); @@ -275,18 +307,48 @@ public class WrappedWatchableObject { } // Helper - private Object getClonedValue() throws FieldAccessException { + Object getClonedValue() throws FieldAccessException { Object value = getValue(); // Only a limited set of references types are supported - if (value instanceof net.minecraft.server.ChunkPosition) { + if (MinecraftReflection.isChunkPosition(value)) { EquivalentConverter converter = ChunkPosition.getConverter(); - return converter.getGeneric(net.minecraft.server.ChunkPosition.class, converter.getSpecific(value)); - } else if (value instanceof ItemStack) { - return ((ItemStack) value).cloneItemStack(); + return converter.getGeneric(MinecraftReflection.getChunkPositionClass(), converter.getSpecific(value)); + } else if (MinecraftReflection.isItemStack(value)) { + return MinecraftReflection.getMinecraftItemStack(MinecraftReflection.getBukkitItemStack(value).clone()); } else { // A string or primitive wrapper, which are all immutable. return value; } } + + @Override + public boolean equals(Object obj) { + // Quick checks + if (obj == this) + return true; + if (obj == null) + return false; + + if (obj instanceof WrappedWatchableObject) { + WrappedWatchableObject other = (WrappedWatchableObject) obj; + + return Objects.equal(getIndex(), other.getIndex()) && + Objects.equal(getTypeID(), other.getTypeID()) && + Objects.equal(getValue(), other.getValue()); + } + + // No, this is not equivalent + return false; + } + + @Override + public int hashCode() { + return Objects.hashCode(getIndex(), getTypeID(), getValue()); + } + + @Override + public String toString() { + return String.format("[%s: %s (%s)]", getIndex(), getValue(), getType().getSimpleName()); + } } diff --git a/ProtocolLib/src/main/resources/config.yml b/ProtocolLib/src/main/resources/config.yml index 15828ee1..0b5a4f64 100644 --- a/ProtocolLib/src/main/resources/config.yml +++ b/ProtocolLib/src/main/resources/config.yml @@ -12,4 +12,7 @@ global: metrics: true # Automatically compile structure modifiers - background compiler: true \ No newline at end of file + background compiler: true + + # Disable version checking for the given Minecraft version. Backup your world first! + ignore version check: \ No newline at end of file diff --git a/ProtocolLib/src/main/resources/plugin.yml b/ProtocolLib/src/main/resources/plugin.yml index 69f13146..cd537899 100644 --- a/ProtocolLib/src/main/resources/plugin.yml +++ b/ProtocolLib/src/main/resources/plugin.yml @@ -1,9 +1,9 @@ name: ProtocolLib -version: 1.7.1 +version: 1.8.0 description: Provides read/write access to the Minecraft protocol. author: Comphenix website: http://www.comphenix.net/ProtocolLib - +load: startup main: com.comphenix.protocol.ProtocolLibrary database: false diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/MinecraftVersionTest.java b/ProtocolLib/src/test/java/com/comphenix/protocol/MinecraftVersionTest.java new file mode 100644 index 00000000..b69c29fd --- /dev/null +++ b/ProtocolLib/src/test/java/com/comphenix/protocol/MinecraftVersionTest.java @@ -0,0 +1,27 @@ +package com.comphenix.protocol; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class MinecraftVersionTest { + + @Test + public void testComparision() { + MinecraftVersion within = new MinecraftVersion(1, 2, 5); + MinecraftVersion outside = new MinecraftVersion(1, 7, 0); + + MinecraftVersion lower = new MinecraftVersion(1, 0, 0); + MinecraftVersion highest = new MinecraftVersion(1, 4, 5); + + // Make sure this is valid + assertTrue(lower.compareTo(within) < 0 && within.compareTo(highest) < 0); + assertFalse(outside.compareTo(within) < 0 && outside.compareTo(highest) < 0); + } + + public void testParsing() { + assertEquals(MinecraftVersion.extractVersion("CraftBukkit R3.0 (MC: 1.4.3)"), "1.4.3"); + assertEquals(MinecraftVersion.extractVersion("CraftBukkit Test Beta 1 (MC: 1.10.01 )"), "1.10.01"); + assertEquals(MinecraftVersion.extractVersion("Hello (MC: 2.3.4 ) "), "2.3.4"); + } +} diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/reflect/StructureModifierTest.java b/ProtocolLib/src/test/java/com/comphenix/protocol/reflect/StructureModifierTest.java deleted file mode 100644 index af570e39..00000000 --- a/ProtocolLib/src/test/java/com/comphenix/protocol/reflect/StructureModifierTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.comphenix.protocol.reflect; - -import static org.junit.Assert.*; - -import net.minecraft.server.Packet103SetSlot; - -import org.junit.Test; - -import com.avaje.ebeaninternal.server.cluster.Packet; -import com.comphenix.protocol.reflect.StructureModifier; - -public class StructureModifierTest { - - @Test - public void test() throws FieldAccessException { - - Packet103SetSlot move = new Packet103SetSlot(); - StructureModifier modifier = new StructureModifier( - Packet103SetSlot.class, Packet.class, true); - - move.a = 1; - int value = (Integer) modifier.withTarget(move).withType(int.class).read(0); - - assertEquals(1, value); - } -}