diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/Packets.java b/ProtocolLib/src/main/java/com/comphenix/protocol/Packets.java
index b4337dca..bbbad165 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/Packets.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/Packets.java
@@ -95,6 +95,7 @@ public final class Packets {
public static final int MOB_EFFECT = 41;
public static final int REMOVE_MOB_EFFECT = 42;
public static final int SET_EXPERIENCE = 43;
+ public static final int UPDATE_ATTRIBUTES = 44;
public static final int MAP_CHUNK = 51;
public static final int MULTI_BLOCK_CHANGE = 52;
public static final int BLOCK_CHANGE = 53;
@@ -199,6 +200,7 @@ public final class Packets {
public static final int BLOCK_ITEM_SWITCH = 16;
public static final int ARM_ANIMATION = 18;
public static final int ENTITY_ACTION = 19;
+ public static final int PLAYER_INPUT = 27;
public static final int CLOSE_WINDOW = 101;
public static final int WINDOW_CLICK = 102;
public static final int TRANSACTION = 106;
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java
index a9c4315f..29dfebaf 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java
@@ -41,6 +41,7 @@ import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.DelayedSingleTask;
+import com.comphenix.protocol.injector.InternalManager;
import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
import com.comphenix.protocol.metrics.Statistics;
@@ -85,7 +86,7 @@ public class ProtocolLibrary extends JavaPlugin {
/**
* The maximum version ProtocolLib has been tested with,
*/
- private static final String MAXIMUM_MINECRAFT_VERSION = "1.5.2";
+ private static final String MAXIMUM_MINECRAFT_VERSION = "1.6.1";
/**
* The number of milliseconds per second.
@@ -95,7 +96,7 @@ public class ProtocolLibrary extends JavaPlugin {
private static final String PERMISSION_INFO = "protocol.info";
// There should only be one protocol manager, so we'll make it static
- private static PacketFilterManager protocolManager;
+ private static InternalManager protocolManager;
// Error reporter
private static ErrorReporter reporter = new BasicErrorReporter();
@@ -172,8 +173,14 @@ public class ProtocolLibrary extends JavaPlugin {
updater = new Updater(this, logger, "protocollib", getFile(), "protocol.info");
unhookTask = new DelayedSingleTask(this);
- protocolManager = new PacketFilterManager(
- getClassLoader(), getServer(), this, version, unhookTask, reporter);
+ protocolManager = PacketFilterManager.newBuilder().
+ classLoader(getClassLoader()).
+ server(getServer()).
+ library(this).
+ minecraftVersion(version).
+ unhookTask(unhookTask).
+ reporter(reporter).
+ build();
// Setup error reporter
detailedReporter.addGlobalParameter("manager", protocolManager);
@@ -181,7 +188,7 @@ public class ProtocolLibrary extends JavaPlugin {
// Update injection hook
try {
PlayerInjectHooks hook = config.getInjectionMethod();
-
+
// Only update the hook if it's different
if (!protocolManager.getPlayerHook().equals(hook)) {
logger.info("Changing player hook from " + protocolManager.getPlayerHook() + " to " + hook);
@@ -216,7 +223,7 @@ public class ProtocolLibrary extends JavaPlugin {
@Override
protected Report filterReport(Object sender, Report report, boolean detailed) {
- String canonicalName = ReportType.getReportName(sender.getClass(), report.getType());
+ String canonicalName = ReportType.getReportName(sender, report.getType());
String reportName = Iterables.getLast(Splitter.on("#").split(canonicalName)).toUpperCase();
if (config != null && config.getModificationCount() != lastModCount) {
@@ -302,9 +309,6 @@ public class ProtocolLibrary extends JavaPlugin {
return;
}
- // Perform logic when the world has loaded
- protocolManager.postWorldLoaded();
-
// Initialize background compiler
if (backgroundCompiler == null && config.isBackgroundCompilerEnabled()) {
backgroundCompiler = new BackgroundCompiler(getClassLoader(), reporter);
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncFilterManager.java b/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncFilterManager.java
index 1b3bd8cc..6d8c1345 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncFilterManager.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncFilterManager.java
@@ -69,12 +69,12 @@ public class AsyncFilterManager implements AsynchronousManager {
// Default scheduler
private final BukkitScheduler scheduler;
- // Our protocol manager
- private final ProtocolManager manager;
-
// Current packet index
private final AtomicInteger currentSendingIndex = new AtomicInteger();
+ // Our protocol manager
+ private ProtocolManager manager;
+
/**
* Initialize a asynchronous filter manager.
*
@@ -83,7 +83,7 @@ public class AsyncFilterManager implements AsynchronousManager {
* @param scheduler - task scheduler.
* @param manager - protocol manager.
*/
- public AsyncFilterManager(ErrorReporter reporter, BukkitScheduler scheduler, ProtocolManager manager) {
+ public AsyncFilterManager(ErrorReporter reporter, BukkitScheduler scheduler) {
// Initialize timeout listeners
this.serverTimeoutListeners = new SortedPacketListenerList();
this.clientTimeoutListeners = new SortedPacketListenerList();
@@ -95,12 +95,26 @@ public class AsyncFilterManager implements AsynchronousManager {
this.playerSendingHandler.initializeScheduler();
this.scheduler = scheduler;
- this.manager = manager;
-
this.reporter = reporter;
this.mainThread = Thread.currentThread();
}
+ /**
+ * Retrieve the protocol manager.
+ * @return The protocol manager.
+ */
+ public ProtocolManager getManager() {
+ return manager;
+ }
+
+ /**
+ * Set the associated protocol manager.
+ * @param manager - the new manager.
+ */
+ public void setManager(ProtocolManager manager) {
+ this.manager = manager;
+ }
+
@Override
public AsyncListenerHandler registerAsyncHandler(PacketListener listener) {
return registerAsyncHandler(listener, true);
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/error/DelegatedErrorReporter.java b/ProtocolLib/src/main/java/com/comphenix/protocol/error/DelegatedErrorReporter.java
index 7584f5d0..6d855320 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/error/DelegatedErrorReporter.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/error/DelegatedErrorReporter.java
@@ -1,80 +1,80 @@
-package com.comphenix.protocol.error;
-
-import org.bukkit.plugin.Plugin;
-
-import com.comphenix.protocol.error.Report.ReportBuilder;
-
-/**
- * Construct an error reporter that delegates to another error reporter.
- * @author Kristian
- */
-public class DelegatedErrorReporter implements ErrorReporter {
- private final ErrorReporter delegated;
-
- /**
- * Construct a new error reporter that forwards all reports to a given reporter.
- * @param delegated - the delegated reporter.
- */
- public DelegatedErrorReporter(ErrorReporter delegated) {
- this.delegated = delegated;
- }
-
- /**
- * Retrieve the underlying error reporter.
- * @return Underlying error reporter.
- */
- public ErrorReporter getDelegated() {
- return delegated;
- }
-
- @Override
- public void reportMinimal(Plugin sender, String methodName, Throwable error) {
- delegated.reportMinimal(sender, methodName, error);
- }
-
- @Override
- public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) {
- delegated.reportMinimal(sender, methodName, error, parameters);
- }
-
- @Override
- public void reportWarning(Object sender, Report report) {
- Report transformed = filterReport(sender, report, false);
-
- if (transformed != null) {
- delegated.reportWarning(sender, transformed);
- }
- }
-
- @Override
- public void reportDetailed(Object sender, Report report) {
- Report transformed = filterReport(sender, report, true);
-
- if (transformed != null) {
- delegated.reportDetailed(sender, transformed);
- }
- }
-
- /**
- * Invoked before an error report is passed on to the underlying error reporter.
- *
- * To cancel a report, return NULL.
- * @param sender - the sender component.
- * @param report - the error report.
- * @param detailed - whether or not the report will be displayed in detail.
- * @return The report to pass on, or NULL to cancel it.
- */
- protected Report filterReport(Object sender, Report report, boolean detailed) {
- return report;
- }
-
- @Override
- public void reportWarning(Object sender, ReportBuilder reportBuilder) {
- reportWarning(sender, reportBuilder.build());
- }
-
- @Override
- public void reportDetailed(Object sender, ReportBuilder reportBuilder) {
- reportDetailed(sender, reportBuilder.build());
- }
-}
+package com.comphenix.protocol.error;
+
+import org.bukkit.plugin.Plugin;
+
+import com.comphenix.protocol.error.Report.ReportBuilder;
+
+/**
+ * Construct an error reporter that delegates to another error reporter.
+ * @author Kristian
+ */
+public class DelegatedErrorReporter implements ErrorReporter {
+ private final ErrorReporter delegated;
+
+ /**
+ * Construct a new error reporter that forwards all reports to a given reporter.
+ * @param delegated - the delegated reporter.
+ */
+ public DelegatedErrorReporter(ErrorReporter delegated) {
+ this.delegated = delegated;
+ }
+
+ /**
+ * Retrieve the underlying error reporter.
+ * @return Underlying error reporter.
+ */
+ public ErrorReporter getDelegated() {
+ return delegated;
+ }
+
+ @Override
+ public void reportMinimal(Plugin sender, String methodName, Throwable error) {
+ delegated.reportMinimal(sender, methodName, error);
+ }
+
+ @Override
+ public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) {
+ delegated.reportMinimal(sender, methodName, error, parameters);
+ }
+
+ @Override
+ public void reportWarning(Object sender, Report report) {
+ Report transformed = filterReport(sender, report, false);
+
+ if (transformed != null) {
+ delegated.reportWarning(sender, transformed);
+ }
+ }
+
+ @Override
+ public void reportDetailed(Object sender, Report report) {
+ Report transformed = filterReport(sender, report, true);
+
+ if (transformed != null) {
+ delegated.reportDetailed(sender, transformed);
+ }
+ }
+
+ /**
+ * Invoked before an error report is passed on to the underlying error reporter.
+ *
+ * To cancel a report, return NULL.
+ * @param sender - the sender instance or class.
+ * @param report - the error report.
+ * @param detailed - whether or not the report will be displayed in detail.
+ * @return The report to pass on, or NULL to cancel it.
+ */
+ protected Report filterReport(Object sender, Report report, boolean detailed) {
+ return report;
+ }
+
+ @Override
+ public void reportWarning(Object sender, ReportBuilder reportBuilder) {
+ reportWarning(sender, reportBuilder.build());
+ }
+
+ @Override
+ public void reportDetailed(Object sender, ReportBuilder reportBuilder) {
+ reportDetailed(sender, reportBuilder.build());
+ }
+}
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 6953d437..9020da58 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/error/DetailedErrorReporter.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/error/DetailedErrorReporter.java
@@ -1,499 +1,499 @@
-/*
- * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
- * Copyright (C) 2012 Kristian S. Stangeland
- *
- * This program is free software; you can redistribute it and/or modify it under the terms of the
- * GNU General Public License as published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
- * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with this program;
- * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
- * 02111-1307 USA
- */
-
-package com.comphenix.protocol.error;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.lang.ref.WeakReference;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import org.apache.commons.lang.builder.ToStringBuilder;
-import org.apache.commons.lang.builder.ToStringStyle;
-import org.bukkit.Bukkit;
-import org.bukkit.plugin.Plugin;
-
-import com.comphenix.protocol.error.Report.ReportBuilder;
-import com.comphenix.protocol.events.PacketAdapter;
-import com.comphenix.protocol.reflect.PrettyPrinter;
-import com.google.common.primitives.Primitives;
-
-/**
- * Internal class used to handle exceptions.
- *
- * @author Kristian
- */
-public class DetailedErrorReporter implements ErrorReporter {
- /**
- * Report format for printing the current exception count.
- */
- public static final ReportType REPORT_EXCEPTION_COUNT = new ReportType("Internal exception count: %s!");
-
- public static final String SECOND_LEVEL_PREFIX = " ";
- public static final String DEFAULT_PREFIX = " ";
- public static final String DEFAULT_SUPPORT_URL = "http://dev.bukkit.org/server-mods/protocollib/";
-
- // Users that are informed about errors in the chat
- public static final String ERROR_PERMISSION = "protocol.info";
-
- // We don't want to spam the server
- public static final int DEFAULT_MAX_ERROR_COUNT = 20;
-
- // Prevent spam per plugin too
- private ConcurrentMap warningCount = new ConcurrentHashMap();
-
- protected String prefix;
- protected String supportURL;
-
- protected AtomicInteger internalErrorCount = new AtomicInteger();
-
- protected int maxErrorCount;
- protected Logger logger;
-
- protected WeakReference pluginReference;
- protected String pluginName;
-
- // Whether or not Apache Commons is not present
- protected boolean apacheCommonsMissing;
-
- // Map of global objects
- protected Map globalParameters = new HashMap();
-
- /**
- * Create a default error reporting system.
- */
- public DetailedErrorReporter(Plugin plugin) {
- this(plugin, DEFAULT_PREFIX, DEFAULT_SUPPORT_URL);
- }
-
- /**
- * Create a central error reporting system.
- * @param plugin - the plugin owner.
- * @param prefix - default line prefix.
- * @param supportURL - URL to report the error.
- */
- public DetailedErrorReporter(Plugin plugin, String prefix, String supportURL) {
- this(plugin, prefix, supportURL, DEFAULT_MAX_ERROR_COUNT, getBukkitLogger());
- }
-
- /**
- * Create a central error reporting system.
- * @param plugin - the plugin owner.
- * @param prefix - default line prefix.
- * @param supportURL - URL to report the error.
- * @param maxErrorCount - number of errors to print before giving up.
- * @param logger - current logger.
- */
- public DetailedErrorReporter(Plugin plugin, String prefix, String supportURL, int maxErrorCount, Logger logger) {
- if (plugin == null)
- throw new IllegalArgumentException("Plugin cannot be NULL.");
-
- this.pluginReference = new WeakReference(plugin);
- this.pluginName = plugin.getName();
- this.prefix = prefix;
- this.supportURL = supportURL;
- this.maxErrorCount = maxErrorCount;
- this.logger = logger;
- }
-
- // Attempt to get the logger.
- private static Logger getBukkitLogger() {
- try {
- return Bukkit.getLogger();
- } catch (Throwable e) {
- return Logger.getLogger("Minecraft");
- }
- }
-
- @Override
- public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) {
- if (reportMinimalNoSpam(sender, methodName, error)) {
- // Print parameters, if they are given
- if (parameters != null && parameters.length > 0) {
- logger.log(Level.SEVERE, printParameters(parameters));
- }
- }
- }
-
- @Override
- public void reportMinimal(Plugin sender, String methodName, Throwable error) {
- reportMinimalNoSpam(sender, methodName, error);
- }
-
- /**
- * Report a problem with a given method and plugin, ensuring that we don't exceed the maximum number of error reports.
- * @param sender - the component that observed this exception.
- * @param methodName - the method name.
- * @param error - the error itself.
- * @return TRUE if the error was printed, FALSE if it was suppressed.
- */
- public boolean reportMinimalNoSpam(Plugin sender, String methodName, Throwable error) {
- String pluginName = PacketAdapter.getPluginName(sender);
- AtomicInteger counter = warningCount.get(pluginName);
-
- // Thread safe pattern
- if (counter == null) {
- AtomicInteger created = new AtomicInteger();
- counter = warningCount.putIfAbsent(pluginName, created);
-
- if (counter == null) {
- counter = created;
- }
- }
-
- final int errorCount = counter.incrementAndGet();
-
- // See if we should print the full error
- if (errorCount < getMaxErrorCount()) {
- logger.log(Level.SEVERE, "[" + pluginName + "] Unhandled exception occured in " +
- methodName + " for " + pluginName, error);
- return true;
-
- } else {
- // Nope - only print the error count occationally
- if (isPowerOfTwo(errorCount)) {
- logger.log(Level.SEVERE, "[" + pluginName + "] Unhandled exception number " + errorCount + " occured in " +
- methodName + " for " + pluginName, error);
- }
- return false;
- }
- }
-
- /**
- * Determine if a given number is a power of two.
- *
- * That is, if there exists an N such that 2^N = number.
- * @param number - the number to check.
- * @return TRUE if the given number is a power of two, FALSE otherwise.
- */
- private boolean isPowerOfTwo(int number) {
- return (number & (number - 1)) == 0;
- }
-
- @Override
- public void reportWarning(Object sender, ReportBuilder reportBuilder) {
- if (reportBuilder == null)
- throw new IllegalArgumentException("reportBuilder cannot be NULL.");
-
- reportWarning(sender, reportBuilder.build());
- }
-
- @Override
- public void reportWarning(Object sender, Report report) {
- String message = "[" + pluginName + "] [" + getSenderName(sender) + "] " + report.getReportMessage();
-
- // Print the main warning
- if (report.getException() != null) {
- logger.log(Level.WARNING, message, report.getException());
- } else {
- logger.log(Level.WARNING, message);
- }
-
- // Parameters?
- if (report.hasCallerParameters()) {
- // Write it
- logger.log(Level.WARNING, printParameters(report.getCallerParameters()));
- }
- }
-
- /**
- * Retrieve the name of a sender class.
- * @param sender - sender object.
- * @return The name of the sender's class.
- */
- private String getSenderName(Object sender) {
- if (sender != null)
- return sender.getClass().getSimpleName();
- else
- return "NULL";
- }
-
- @Override
- public void reportDetailed(Object sender, ReportBuilder reportBuilder) {
- reportDetailed(sender, reportBuilder.build());
- }
-
- @Override
- public void reportDetailed(Object sender, Report report) {
- final Plugin plugin = pluginReference.get();
- final int errorCount = internalErrorCount.incrementAndGet();
-
- // Do not overtly spam the server!
- if (errorCount > getMaxErrorCount()) {
- // Only allow the error count at rare occations
- if (isPowerOfTwo(errorCount)) {
- // Permit it - but print the number of exceptions first
- reportWarning(this, Report.newBuilder(REPORT_EXCEPTION_COUNT).messageParam(errorCount).build());
- } else {
- // NEVER SPAM THE CONSOLE
- return;
- }
- }
-
- StringWriter text = new StringWriter();
- PrintWriter writer = new PrintWriter(text);
-
- // Helpful message
- writer.println("[" + pluginName + "] INTERNAL ERROR: " + report.getReportMessage());
- writer.println("If this problem hasn't already been reported, please open a ticket");
- writer.println("at " + supportURL + " with the following data:");
-
- // Now, let us print important exception information
- writer.println(" ===== STACK TRACE =====");
-
- if (report.getException() != null) {
- report.getException().printStackTrace(writer);
- }
-
- // Data dump!
- writer.println(" ===== DUMP =====");
-
- // Relevant parameters
- if (report.hasCallerParameters()) {
- printParameters(writer, report.getCallerParameters());
- }
-
- // Global parameters
- for (String param : globalParameters()) {
- writer.println(SECOND_LEVEL_PREFIX + param + ":");
- writer.println(addPrefix(getStringDescription(getGlobalParameter(param)),
- SECOND_LEVEL_PREFIX + SECOND_LEVEL_PREFIX));
- }
-
- // Now, for the sender itself
- writer.println("Sender:");
- writer.println(addPrefix(getStringDescription(sender), SECOND_LEVEL_PREFIX));
-
- // And plugin
- if (plugin != null) {
- writer.println("Version:");
- writer.println(addPrefix(plugin.toString(), SECOND_LEVEL_PREFIX));
- }
-
- // Add the server version too
- if (Bukkit.getServer() != null) {
- writer.println("Server:");
- writer.println(addPrefix(Bukkit.getServer().getVersion(), SECOND_LEVEL_PREFIX));
-
- // Inform of this occurrence
- if (ERROR_PERMISSION != null) {
- Bukkit.getServer().broadcast(
- String.format("Error %s (%s) occured in %s.", report.getReportMessage(), report.getException(), sender),
- ERROR_PERMISSION
- );
- }
- }
-
- // Make sure it is reported
- logger.severe(addPrefix(text.toString(), prefix));
- }
-
- private String printParameters(Object... parameters) {
- StringWriter writer = new StringWriter();
-
- // Print and retrieve the string buffer
- printParameters(new PrintWriter(writer), parameters);
- return writer.toString();
- }
-
- private void printParameters(PrintWriter writer, Object[] parameters) {
- writer.println("Parameters: ");
-
- // We *really* want to get as much information as possible
- for (Object param : parameters) {
- writer.println(addPrefix(getStringDescription(param), SECOND_LEVEL_PREFIX));
- }
- }
-
- /**
- * Adds the given prefix to every line in the text.
- * @param text - text to modify.
- * @param prefix - prefix added to every line in the text.
- * @return The modified text.
- */
- protected String addPrefix(String text, String prefix) {
- return text.replaceAll("(?m)^", prefix);
- }
-
- /**
- * Retrieve a string representation of the given object.
- * @param value - object to convert.
- * @return String representation.
- */
- protected String getStringDescription(Object value) {
- // We can't only rely on toString.
- if (value == null) {
- return "[NULL]";
- } if (isSimpleType(value)) {
- return value.toString();
- } else {
- try {
- if (!apacheCommonsMissing)
- return (ToStringBuilder.reflectionToString(value, ToStringStyle.MULTI_LINE_STYLE, false, null));
- } catch (Throwable ex) {
- // Apache is probably missing
- apacheCommonsMissing = true;
- }
-
- // Use our custom object printer instead
- try {
- return PrettyPrinter.printObject(value, value.getClass(), Object.class);
- } catch (IllegalAccessException e) {
- return "[Error: " + e.getMessage() + "]";
- }
- }
- }
-
- /**
- * Determine if the given object is a wrapper for a primitive/simple type or not.
- * @param test - the object to test.
- * @return TRUE if this object is simple enough to simply be printed, FALSE othewise.
- */
- protected boolean isSimpleType(Object test) {
- return test instanceof String || Primitives.isWrapperType(test.getClass());
- }
-
- /**
- * Retrieve the current number of errors printed through {@link #reportDetailed(Object, Report)}.
- * @return Number of errors printed.
- */
- public int getErrorCount() {
- return internalErrorCount.get();
- }
-
- /**
- * Set the number of errors printed.
- * @param errorCount - new number of errors printed.
- */
- public void setErrorCount(int errorCount) {
- internalErrorCount.set(errorCount);
- }
-
- /**
- * Retrieve the maximum number of errors we can print before we begin suppressing errors.
- * @return Maximum number of errors.
- */
- public int getMaxErrorCount() {
- return maxErrorCount;
- }
-
- /**
- * Set the maximum number of errors we can print before we begin suppressing errors.
- * @param maxErrorCount - new max count.
- */
- public void setMaxErrorCount(int maxErrorCount) {
- this.maxErrorCount = maxErrorCount;
- }
-
- /**
- * Adds the given global parameter. It will be included in every error report.
- *
- * Both key and value must be non-null.
- * @param key - name of parameter.
- * @param value - the global parameter itself.
- */
- public void addGlobalParameter(String key, Object value) {
- if (key == null)
- throw new IllegalArgumentException("key cannot be NULL.");
- if (value == null)
- throw new IllegalArgumentException("value cannot be NULL.");
-
- globalParameters.put(key, value);
- }
-
- /**
- * Retrieve a global parameter by its key.
- * @param key - key of the parameter to retrieve.
- * @return The value of the global parameter, or NULL if not found.
- */
- public Object getGlobalParameter(String key) {
- if (key == null)
- throw new IllegalArgumentException("key cannot be NULL.");
-
- return globalParameters.get(key);
- }
-
- /**
- * Reset all global parameters.
- */
- public void clearGlobalParameters() {
- globalParameters.clear();
- }
-
- /**
- * Retrieve a set of every registered global parameter.
- * @return Set of all registered global parameters.
- */
- public Set globalParameters() {
- return globalParameters.keySet();
- }
-
- /**
- * Retrieve the support URL that will be added to all detailed reports.
- * @return Support URL.
- */
- public String getSupportURL() {
- return supportURL;
- }
-
- /**
- * Set the support URL that will be added to all detailed reports.
- * @param supportURL - the new support URL.
- */
- public void setSupportURL(String supportURL) {
- this.supportURL = supportURL;
- }
-
- /**
- * Retrieve the prefix to apply to every line in the error reports.
- * @return Error report prefix.
- */
- public String getPrefix() {
- return prefix;
- }
-
- /**
- * Set the prefix to apply to every line in the error reports.
- * @param prefix - new prefix.
- */
- public void setPrefix(String prefix) {
- this.prefix = prefix;
- }
-
- /**
- * Retrieve the current logger that is used to print all reports.
- * @return The current logger.
- */
- public Logger getLogger() {
- return logger;
- }
-
- /**
- * Set the current logger that is used to print all reports.
- * @param logger - new logger.
- */
- public void setLogger(Logger logger) {
- this.logger = logger;
- }
-}
+/*
+ * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
+ * Copyright (C) 2012 Kristian S. Stangeland
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program;
+ * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA
+ */
+
+package com.comphenix.protocol.error;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+import org.bukkit.Bukkit;
+import org.bukkit.plugin.Plugin;
+
+import com.comphenix.protocol.error.Report.ReportBuilder;
+import com.comphenix.protocol.events.PacketAdapter;
+import com.comphenix.protocol.reflect.PrettyPrinter;
+import com.google.common.primitives.Primitives;
+
+/**
+ * Internal class used to handle exceptions.
+ *
+ * @author Kristian
+ */
+public class DetailedErrorReporter implements ErrorReporter {
+ /**
+ * Report format for printing the current exception count.
+ */
+ public static final ReportType REPORT_EXCEPTION_COUNT = new ReportType("Internal exception count: %s!");
+
+ public static final String SECOND_LEVEL_PREFIX = " ";
+ public static final String DEFAULT_PREFIX = " ";
+ public static final String DEFAULT_SUPPORT_URL = "http://dev.bukkit.org/server-mods/protocollib/";
+
+ // Users that are informed about errors in the chat
+ public static final String ERROR_PERMISSION = "protocol.info";
+
+ // We don't want to spam the server
+ public static final int DEFAULT_MAX_ERROR_COUNT = 20;
+
+ // Prevent spam per plugin too
+ private ConcurrentMap warningCount = new ConcurrentHashMap();
+
+ protected String prefix;
+ protected String supportURL;
+
+ protected AtomicInteger internalErrorCount = new AtomicInteger();
+
+ protected int maxErrorCount;
+ protected Logger logger;
+
+ protected WeakReference pluginReference;
+ protected String pluginName;
+
+ // Whether or not Apache Commons is not present
+ protected boolean apacheCommonsMissing;
+
+ // Map of global objects
+ protected Map globalParameters = new HashMap();
+
+ /**
+ * Create a default error reporting system.
+ */
+ public DetailedErrorReporter(Plugin plugin) {
+ this(plugin, DEFAULT_PREFIX, DEFAULT_SUPPORT_URL);
+ }
+
+ /**
+ * Create a central error reporting system.
+ * @param plugin - the plugin owner.
+ * @param prefix - default line prefix.
+ * @param supportURL - URL to report the error.
+ */
+ public DetailedErrorReporter(Plugin plugin, String prefix, String supportURL) {
+ this(plugin, prefix, supportURL, DEFAULT_MAX_ERROR_COUNT, getBukkitLogger());
+ }
+
+ /**
+ * Create a central error reporting system.
+ * @param plugin - the plugin owner.
+ * @param prefix - default line prefix.
+ * @param supportURL - URL to report the error.
+ * @param maxErrorCount - number of errors to print before giving up.
+ * @param logger - current logger.
+ */
+ public DetailedErrorReporter(Plugin plugin, String prefix, String supportURL, int maxErrorCount, Logger logger) {
+ if (plugin == null)
+ throw new IllegalArgumentException("Plugin cannot be NULL.");
+
+ this.pluginReference = new WeakReference(plugin);
+ this.pluginName = plugin.getName();
+ this.prefix = prefix;
+ this.supportURL = supportURL;
+ this.maxErrorCount = maxErrorCount;
+ this.logger = logger;
+ }
+
+ // Attempt to get the logger.
+ private static Logger getBukkitLogger() {
+ try {
+ return Bukkit.getLogger();
+ } catch (Throwable e) {
+ return Logger.getLogger("Minecraft");
+ }
+ }
+
+ @Override
+ public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) {
+ if (reportMinimalNoSpam(sender, methodName, error)) {
+ // Print parameters, if they are given
+ if (parameters != null && parameters.length > 0) {
+ logger.log(Level.SEVERE, printParameters(parameters));
+ }
+ }
+ }
+
+ @Override
+ public void reportMinimal(Plugin sender, String methodName, Throwable error) {
+ reportMinimalNoSpam(sender, methodName, error);
+ }
+
+ /**
+ * Report a problem with a given method and plugin, ensuring that we don't exceed the maximum number of error reports.
+ * @param sender - the component that observed this exception.
+ * @param methodName - the method name.
+ * @param error - the error itself.
+ * @return TRUE if the error was printed, FALSE if it was suppressed.
+ */
+ public boolean reportMinimalNoSpam(Plugin sender, String methodName, Throwable error) {
+ String pluginName = PacketAdapter.getPluginName(sender);
+ AtomicInteger counter = warningCount.get(pluginName);
+
+ // Thread safe pattern
+ if (counter == null) {
+ AtomicInteger created = new AtomicInteger();
+ counter = warningCount.putIfAbsent(pluginName, created);
+
+ if (counter == null) {
+ counter = created;
+ }
+ }
+
+ final int errorCount = counter.incrementAndGet();
+
+ // See if we should print the full error
+ if (errorCount < getMaxErrorCount()) {
+ logger.log(Level.SEVERE, "[" + pluginName + "] Unhandled exception occured in " +
+ methodName + " for " + pluginName, error);
+ return true;
+
+ } else {
+ // Nope - only print the error count occationally
+ if (isPowerOfTwo(errorCount)) {
+ logger.log(Level.SEVERE, "[" + pluginName + "] Unhandled exception number " + errorCount + " occured in " +
+ methodName + " for " + pluginName, error);
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Determine if a given number is a power of two.
+ *
+ * That is, if there exists an N such that 2^N = number.
+ * @param number - the number to check.
+ * @return TRUE if the given number is a power of two, FALSE otherwise.
+ */
+ private boolean isPowerOfTwo(int number) {
+ return (number & (number - 1)) == 0;
+ }
+
+ @Override
+ public void reportWarning(Object sender, ReportBuilder reportBuilder) {
+ if (reportBuilder == null)
+ throw new IllegalArgumentException("reportBuilder cannot be NULL.");
+
+ reportWarning(sender, reportBuilder.build());
+ }
+
+ @Override
+ public void reportWarning(Object sender, Report report) {
+ String message = "[" + pluginName + "] [" + getSenderName(sender) + "] " + report.getReportMessage();
+
+ // Print the main warning
+ if (report.getException() != null) {
+ logger.log(Level.WARNING, message, report.getException());
+ } else {
+ logger.log(Level.WARNING, message);
+ }
+
+ // Parameters?
+ if (report.hasCallerParameters()) {
+ // Write it
+ logger.log(Level.WARNING, printParameters(report.getCallerParameters()));
+ }
+ }
+
+ /**
+ * Retrieve the name of a sender class.
+ * @param sender - sender object.
+ * @return The name of the sender's class.
+ */
+ private String getSenderName(Object sender) {
+ if (sender != null)
+ return ReportType.getSenderClass(sender).getSimpleName();
+ else
+ return "NULL";
+ }
+
+ @Override
+ public void reportDetailed(Object sender, ReportBuilder reportBuilder) {
+ reportDetailed(sender, reportBuilder.build());
+ }
+
+ @Override
+ public void reportDetailed(Object sender, Report report) {
+ final Plugin plugin = pluginReference.get();
+ final int errorCount = internalErrorCount.incrementAndGet();
+
+ // Do not overtly spam the server!
+ if (errorCount > getMaxErrorCount()) {
+ // Only allow the error count at rare occations
+ if (isPowerOfTwo(errorCount)) {
+ // Permit it - but print the number of exceptions first
+ reportWarning(this, Report.newBuilder(REPORT_EXCEPTION_COUNT).messageParam(errorCount).build());
+ } else {
+ // NEVER SPAM THE CONSOLE
+ return;
+ }
+ }
+
+ StringWriter text = new StringWriter();
+ PrintWriter writer = new PrintWriter(text);
+
+ // Helpful message
+ writer.println("[" + pluginName + "] INTERNAL ERROR: " + report.getReportMessage());
+ writer.println("If this problem hasn't already been reported, please open a ticket");
+ writer.println("at " + supportURL + " with the following data:");
+
+ // Now, let us print important exception information
+ writer.println(" ===== STACK TRACE =====");
+
+ if (report.getException() != null) {
+ report.getException().printStackTrace(writer);
+ }
+
+ // Data dump!
+ writer.println(" ===== DUMP =====");
+
+ // Relevant parameters
+ if (report.hasCallerParameters()) {
+ printParameters(writer, report.getCallerParameters());
+ }
+
+ // Global parameters
+ for (String param : globalParameters()) {
+ writer.println(SECOND_LEVEL_PREFIX + param + ":");
+ writer.println(addPrefix(getStringDescription(getGlobalParameter(param)),
+ SECOND_LEVEL_PREFIX + SECOND_LEVEL_PREFIX));
+ }
+
+ // Now, for the sender itself
+ writer.println("Sender:");
+ writer.println(addPrefix(getStringDescription(sender), SECOND_LEVEL_PREFIX));
+
+ // And plugin
+ if (plugin != null) {
+ writer.println("Version:");
+ writer.println(addPrefix(plugin.toString(), SECOND_LEVEL_PREFIX));
+ }
+
+ // Add the server version too
+ if (Bukkit.getServer() != null) {
+ writer.println("Server:");
+ writer.println(addPrefix(Bukkit.getServer().getVersion(), SECOND_LEVEL_PREFIX));
+
+ // Inform of this occurrence
+ if (ERROR_PERMISSION != null) {
+ Bukkit.getServer().broadcast(
+ String.format("Error %s (%s) occured in %s.", report.getReportMessage(), report.getException(), sender),
+ ERROR_PERMISSION
+ );
+ }
+ }
+
+ // Make sure it is reported
+ logger.severe(addPrefix(text.toString(), prefix));
+ }
+
+ private String printParameters(Object... parameters) {
+ StringWriter writer = new StringWriter();
+
+ // Print and retrieve the string buffer
+ printParameters(new PrintWriter(writer), parameters);
+ return writer.toString();
+ }
+
+ private void printParameters(PrintWriter writer, Object[] parameters) {
+ writer.println("Parameters: ");
+
+ // We *really* want to get as much information as possible
+ for (Object param : parameters) {
+ writer.println(addPrefix(getStringDescription(param), SECOND_LEVEL_PREFIX));
+ }
+ }
+
+ /**
+ * Adds the given prefix to every line in the text.
+ * @param text - text to modify.
+ * @param prefix - prefix added to every line in the text.
+ * @return The modified text.
+ */
+ protected String addPrefix(String text, String prefix) {
+ return text.replaceAll("(?m)^", prefix);
+ }
+
+ /**
+ * Retrieve a string representation of the given object.
+ * @param value - object to convert.
+ * @return String representation.
+ */
+ protected String getStringDescription(Object value) {
+ // We can't only rely on toString.
+ if (value == null) {
+ return "[NULL]";
+ } if (isSimpleType(value) || value instanceof Class>) {
+ return value.toString();
+ } else {
+ try {
+ if (!apacheCommonsMissing)
+ return (ToStringBuilder.reflectionToString(value, ToStringStyle.MULTI_LINE_STYLE, false, null));
+ } catch (Throwable ex) {
+ // Apache is probably missing
+ apacheCommonsMissing = true;
+ }
+
+ // Use our custom object printer instead
+ try {
+ return PrettyPrinter.printObject(value, value.getClass(), Object.class);
+ } catch (IllegalAccessException e) {
+ return "[Error: " + e.getMessage() + "]";
+ }
+ }
+ }
+
+ /**
+ * Determine if the given object is a wrapper for a primitive/simple type or not.
+ * @param test - the object to test.
+ * @return TRUE if this object is simple enough to simply be printed, FALSE othewise.
+ */
+ protected boolean isSimpleType(Object test) {
+ return test instanceof String || Primitives.isWrapperType(test.getClass());
+ }
+
+ /**
+ * Retrieve the current number of errors printed through {@link #reportDetailed(Object, Report)}.
+ * @return Number of errors printed.
+ */
+ public int getErrorCount() {
+ return internalErrorCount.get();
+ }
+
+ /**
+ * Set the number of errors printed.
+ * @param errorCount - new number of errors printed.
+ */
+ public void setErrorCount(int errorCount) {
+ internalErrorCount.set(errorCount);
+ }
+
+ /**
+ * Retrieve the maximum number of errors we can print before we begin suppressing errors.
+ * @return Maximum number of errors.
+ */
+ public int getMaxErrorCount() {
+ return maxErrorCount;
+ }
+
+ /**
+ * Set the maximum number of errors we can print before we begin suppressing errors.
+ * @param maxErrorCount - new max count.
+ */
+ public void setMaxErrorCount(int maxErrorCount) {
+ this.maxErrorCount = maxErrorCount;
+ }
+
+ /**
+ * Adds the given global parameter. It will be included in every error report.
+ *
+ * Both key and value must be non-null.
+ * @param key - name of parameter.
+ * @param value - the global parameter itself.
+ */
+ public void addGlobalParameter(String key, Object value) {
+ if (key == null)
+ throw new IllegalArgumentException("key cannot be NULL.");
+ if (value == null)
+ throw new IllegalArgumentException("value cannot be NULL.");
+
+ globalParameters.put(key, value);
+ }
+
+ /**
+ * Retrieve a global parameter by its key.
+ * @param key - key of the parameter to retrieve.
+ * @return The value of the global parameter, or NULL if not found.
+ */
+ public Object getGlobalParameter(String key) {
+ if (key == null)
+ throw new IllegalArgumentException("key cannot be NULL.");
+
+ return globalParameters.get(key);
+ }
+
+ /**
+ * Reset all global parameters.
+ */
+ public void clearGlobalParameters() {
+ globalParameters.clear();
+ }
+
+ /**
+ * Retrieve a set of every registered global parameter.
+ * @return Set of all registered global parameters.
+ */
+ public Set globalParameters() {
+ return globalParameters.keySet();
+ }
+
+ /**
+ * Retrieve the support URL that will be added to all detailed reports.
+ * @return Support URL.
+ */
+ public String getSupportURL() {
+ return supportURL;
+ }
+
+ /**
+ * Set the support URL that will be added to all detailed reports.
+ * @param supportURL - the new support URL.
+ */
+ public void setSupportURL(String supportURL) {
+ this.supportURL = supportURL;
+ }
+
+ /**
+ * Retrieve the prefix to apply to every line in the error reports.
+ * @return Error report prefix.
+ */
+ public String getPrefix() {
+ return prefix;
+ }
+
+ /**
+ * Set the prefix to apply to every line in the error reports.
+ * @param prefix - new prefix.
+ */
+ public void setPrefix(String prefix) {
+ this.prefix = prefix;
+ }
+
+ /**
+ * Retrieve the current logger that is used to print all reports.
+ * @return The current logger.
+ */
+ public Logger getLogger() {
+ return logger;
+ }
+
+ /**
+ * Set the current logger that is used to print all reports.
+ * @param logger - new logger.
+ */
+ public void setLogger(Logger logger) {
+ this.logger = logger;
+ }
+}
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/error/ReportType.java b/ProtocolLib/src/main/java/com/comphenix/protocol/error/ReportType.java
index e795677c..8e98a5a9 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/error/ReportType.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/error/ReportType.java
@@ -46,6 +46,39 @@ public class ReportType {
return errorFormat;
}
+ /**
+ * Retrieve the class of the given sender.
+ *
+ * If the sender is already a Class, we return it.
+ * @param sender - the sender to look up.
+ * @return The class of the sender.
+ */
+ public static Class> getSenderClass(Object sender) {
+ if (sender == null)
+ throw new IllegalArgumentException("sender cannot be NUll.");
+ else if (sender instanceof Class>)
+ return (Class>) sender;
+ else
+ return sender.getClass();
+ }
+
+ /**
+ * Retrieve the full canonical name of a given report type.
+ *
+ * Note that the sender may be a class (for static callers), in which
+ * case it will be used directly instead of its getClass() method.
+ *
+ * It is thus not advisable for class classes to report reports.
+ * @param sender - the sender, or its class.
+ * @param type - the report type.
+ * @return The full canonical name.
+ */
+ public static String getReportName(Object sender, ReportType type) {
+ if (sender == null)
+ throw new IllegalArgumentException("sender cannot be NUll.");
+ return getReportName(getSenderClass(sender), type);
+ }
+
/**
* Retrieve the full canonical name of a given report type.
*
@@ -54,7 +87,7 @@ public class ReportType {
* @param type - the report instance.
* @return The full canonical name.
*/
- public static String getReportName(Class> sender, ReportType type) {
+ private static String getReportName(Class> sender, ReportType type) {
if (sender == null)
throw new IllegalArgumentException("sender cannot be NUll.");
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 46d5e7f2..144a0a97 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java
@@ -17,7 +17,9 @@
package com.comphenix.protocol.events;
+import java.io.DataInput;
import java.io.DataInputStream;
+import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
@@ -49,6 +51,7 @@ import com.comphenix.protocol.reflect.cloning.CollectionCloner;
import com.comphenix.protocol.reflect.cloning.FieldCloner;
import com.comphenix.protocol.reflect.cloning.ImmutableDetector;
import com.comphenix.protocol.reflect.cloning.AggregateCloner.BuilderParameters;
+import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.StreamSerializer;
@@ -456,7 +459,7 @@ public class PacketContainer implements Serializable {
try {
// Call the write-method
- getMethodLazily(writeMethods, handle.getClass(), "write", DataOutputStream.class).
+ getMethodLazily(writeMethods, handle.getClass(), "write", DataOutput.class).
invoke(handle, new DataOutputStream(output));
} catch (IllegalArgumentException e) {
@@ -483,7 +486,7 @@ public class PacketContainer implements Serializable {
// Call the read method
try {
- getMethodLazily(readMethods, handle.getClass(), "read", DataInputStream.class).
+ getMethodLazily(readMethods, handle.getClass(), "read", DataInput.class).
invoke(handle, new DataInputStream(input));
} catch (IllegalArgumentException e) {
@@ -513,7 +516,12 @@ public class PacketContainer implements Serializable {
// Atomic operation
if (method == null) {
- Method initialized = FuzzyReflection.fromClass(handleClass).getMethodByParameters(methodName, parameterClass);
+ Method initialized = FuzzyReflection.fromClass(handleClass).getMethod(
+ FuzzyMethodContract.newBuilder().
+ parameterCount(1).
+ parameterDerivedOf(parameterClass).
+ returnTypeVoid().
+ build());
method = lookup.putIfAbsent(handleClass, initialized);
// Use our version if we succeeded
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/BukkitUnwrapper.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/BukkitUnwrapper.java
index 7a1a724d..b7db2d7f 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/BukkitUnwrapper.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/BukkitUnwrapper.java
@@ -1,213 +1,213 @@
-/*
- * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
- * Copyright (C) 2012 Kristian S. Stangeland
- *
- * This program is free software; you can redistribute it and/or modify it under the terms of the
- * GNU General Public License as published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
- * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with this program;
- * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
- * 02111-1307 USA
- */
-
-package com.comphenix.protocol.injector;
-
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.Collection;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-import com.comphenix.protocol.ProtocolLibrary;
-import com.comphenix.protocol.error.ErrorReporter;
-import com.comphenix.protocol.error.Report;
-import com.comphenix.protocol.error.ReportType;
-import com.comphenix.protocol.injector.PacketConstructor.Unwrapper;
-import com.comphenix.protocol.reflect.FieldUtils;
-import com.comphenix.protocol.reflect.instances.DefaultInstances;
-import com.google.common.primitives.Primitives;
-
-/**
- * Represents an object capable of converting wrapped Bukkit objects into NMS objects.
- *
- * Typical conversions include:
- *
- * org.bukkit.entity.Player -> net.minecraft.server.EntityPlayer
- * org.bukkit.World -> net.minecraft.server.WorldServer
- *
- *
- * @author Kristian
- */
-public class BukkitUnwrapper implements Unwrapper {
- public static final ReportType REPORT_ILLEGAL_ARGUMENT = new ReportType("Illegal argument.");
- public static final ReportType REPORT_SECURITY_LIMITATION = new ReportType("Security limitation.");
- public static final ReportType REPORT_CANNOT_FIND_UNWRAP_METHOD = new ReportType("Cannot find method.");
-
- public static final ReportType REPORT_CANNOT_READ_FIELD_HANDLE = new ReportType("Cannot read field 'handle'.");
-
- private static Map, Unwrapper> unwrapperCache = new ConcurrentHashMap, Unwrapper>();
-
- // The current error reporter
- private final ErrorReporter reporter;
-
- /**
- * Construct a new Bukkit unwrapper with ProtocolLib's default error reporter.
- */
- public BukkitUnwrapper() {
- this(ProtocolLibrary.getErrorReporter());
- }
-
- /**
- * Construct a new Bukkit unwrapper with the given error reporter.
- * @param reporter - the error reporter to use.
- */
- public BukkitUnwrapper(ErrorReporter reporter) {
- this.reporter = reporter;
- }
-
- @SuppressWarnings("unchecked")
- @Override
- public Object unwrapItem(Object wrappedObject) {
- // Special case
- if (wrappedObject == null)
- return null;
- Class> currentClass = wrappedObject.getClass();
-
- // Next, check for types that doesn't have a getHandle()
- if (wrappedObject instanceof Collection) {
- return handleCollection((Collection) wrappedObject);
- } else if (Primitives.isWrapperType(currentClass) || wrappedObject instanceof String) {
- return null;
- }
-
- Unwrapper specificUnwrapper = getSpecificUnwrapper(currentClass);
-
- // Retrieve the handle
- if (specificUnwrapper != null)
- return specificUnwrapper.unwrapItem(wrappedObject);
- else
- return null;
- }
-
- // Handle a collection of items
- private Object handleCollection(Collection wrappedObject) {
-
- @SuppressWarnings("unchecked")
- Collection copy = DefaultInstances.DEFAULT.getDefault(wrappedObject.getClass());
-
- if (copy != null) {
- // Unwrap every element
- for (Object element : wrappedObject) {
- copy.add(unwrapItem(element));
- }
- return copy;
-
- } else {
- // Impossible
- return null;
- }
- }
-
- /**
- * Retrieve a cached class unwrapper for the given class.
- * @param type - the type of the class.
- * @return An unwrapper for the given class.
- */
- private Unwrapper getSpecificUnwrapper(Class> type) {
- // See if we're already determined this
- if (unwrapperCache.containsKey(type)) {
- // We will never remove from the cache, so this ought to be thread safe
- return unwrapperCache.get(type);
- }
-
- try {
- final Method find = type.getMethod("getHandle");
-
- // It's thread safe, as getMethod should return the same handle
- Unwrapper methodUnwrapper = new Unwrapper() {
- @Override
- public Object unwrapItem(Object wrappedObject) {
-
- try {
- return find.invoke(wrappedObject);
-
- } catch (IllegalArgumentException e) {
- reporter.reportDetailed(this,
- Report.newBuilder(REPORT_ILLEGAL_ARGUMENT).error(e).callerParam(wrappedObject, find)
- );
- } catch (IllegalAccessException e) {
- // Should not occur either
- return null;
- } catch (InvocationTargetException e) {
- // This is really bad
- throw new RuntimeException("Minecraft error.", e);
- }
-
- return null;
- }
- };
-
- unwrapperCache.put(type, methodUnwrapper);
- return methodUnwrapper;
-
- } catch (SecurityException e) {
- reporter.reportDetailed(this,
- Report.newBuilder(REPORT_SECURITY_LIMITATION).error(e).callerParam(type)
- );
- } catch (NoSuchMethodException e) {
- // Try getting the field unwrapper too
- Unwrapper fieldUnwrapper = getFieldUnwrapper(type);
-
- if (fieldUnwrapper != null)
- return fieldUnwrapper;
- else
- reporter.reportDetailed(this,
- Report.newBuilder(REPORT_CANNOT_FIND_UNWRAP_METHOD).error(e).callerParam(type));
- }
-
- // Default method
- return null;
- }
-
- /**
- * Retrieve a cached unwrapper using the handle field.
- * @param type - a cached field unwrapper.
- * @return The cached field unwrapper.
- */
- private Unwrapper getFieldUnwrapper(Class> type) {
- final Field find = FieldUtils.getField(type, "handle", true);
-
- // See if we succeeded
- if (find != null) {
- Unwrapper fieldUnwrapper = new Unwrapper() {
- @Override
- public Object unwrapItem(Object wrappedObject) {
- try {
- return FieldUtils.readField(find, wrappedObject, true);
- } catch (IllegalAccessException e) {
- reporter.reportDetailed(this,
- Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).error(e).callerParam(wrappedObject, find)
- );
- return null;
- }
- }
- };
-
- unwrapperCache.put(type, fieldUnwrapper);
- return fieldUnwrapper;
-
- } else {
- // Inform about this too
- reporter.reportDetailed(this,
- Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).callerParam(find)
- );
- return null;
- }
- }
-}
+/*
+ * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
+ * Copyright (C) 2012 Kristian S. Stangeland
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program;
+ * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA
+ */
+
+package com.comphenix.protocol.injector;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import com.comphenix.protocol.ProtocolLibrary;
+import com.comphenix.protocol.error.ErrorReporter;
+import com.comphenix.protocol.error.Report;
+import com.comphenix.protocol.error.ReportType;
+import com.comphenix.protocol.injector.PacketConstructor.Unwrapper;
+import com.comphenix.protocol.reflect.FieldUtils;
+import com.comphenix.protocol.reflect.instances.DefaultInstances;
+import com.google.common.primitives.Primitives;
+
+/**
+ * Represents an object capable of converting wrapped Bukkit objects into NMS objects.
+ *
+ * Typical conversions include:
+ *
+ * org.bukkit.entity.Player -> net.minecraft.server.EntityPlayer
+ * org.bukkit.World -> net.minecraft.server.WorldServer
+ *
+ *
+ * @author Kristian
+ */
+public class BukkitUnwrapper implements Unwrapper {
+ public static final ReportType REPORT_ILLEGAL_ARGUMENT = new ReportType("Illegal argument.");
+ public static final ReportType REPORT_SECURITY_LIMITATION = new ReportType("Security limitation.");
+ public static final ReportType REPORT_CANNOT_FIND_UNWRAP_METHOD = new ReportType("Cannot find method.");
+
+ public static final ReportType REPORT_CANNOT_READ_FIELD_HANDLE = new ReportType("Cannot read field 'handle'.");
+
+ private static Map, Unwrapper> unwrapperCache = new ConcurrentHashMap, Unwrapper>();
+
+ // The current error reporter
+ private final ErrorReporter reporter;
+
+ /**
+ * Construct a new Bukkit unwrapper with ProtocolLib's default error reporter.
+ */
+ public BukkitUnwrapper() {
+ this(ProtocolLibrary.getErrorReporter());
+ }
+
+ /**
+ * Construct a new Bukkit unwrapper with the given error reporter.
+ * @param reporter - the error reporter to use.
+ */
+ public BukkitUnwrapper(ErrorReporter reporter) {
+ this.reporter = reporter;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Object unwrapItem(Object wrappedObject) {
+ // Special case
+ if (wrappedObject == null)
+ return null;
+ Class> currentClass = wrappedObject.getClass();
+
+ // Next, check for types that doesn't have a getHandle()
+ if (wrappedObject instanceof Collection) {
+ return handleCollection((Collection) wrappedObject);
+ } else if (Primitives.isWrapperType(currentClass) || wrappedObject instanceof String) {
+ return null;
+ }
+
+ Unwrapper specificUnwrapper = getSpecificUnwrapper(currentClass);
+
+ // Retrieve the handle
+ if (specificUnwrapper != null)
+ return specificUnwrapper.unwrapItem(wrappedObject);
+ else
+ return null;
+ }
+
+ // Handle a collection of items
+ private Object handleCollection(Collection wrappedObject) {
+
+ @SuppressWarnings("unchecked")
+ Collection copy = DefaultInstances.DEFAULT.getDefault(wrappedObject.getClass());
+
+ if (copy != null) {
+ // Unwrap every element
+ for (Object element : wrappedObject) {
+ copy.add(unwrapItem(element));
+ }
+ return copy;
+
+ } else {
+ // Impossible
+ return null;
+ }
+ }
+
+ /**
+ * Retrieve a cached class unwrapper for the given class.
+ * @param type - the type of the class.
+ * @return An unwrapper for the given class.
+ */
+ private Unwrapper getSpecificUnwrapper(Class> type) {
+ // See if we're already determined this
+ if (unwrapperCache.containsKey(type)) {
+ // We will never remove from the cache, so this ought to be thread safe
+ return unwrapperCache.get(type);
+ }
+
+ try {
+ final Method find = type.getMethod("getHandle");
+
+ // It's thread safe, as getMethod should return the same handle
+ Unwrapper methodUnwrapper = new Unwrapper() {
+ @Override
+ public Object unwrapItem(Object wrappedObject) {
+
+ try {
+ return find.invoke(wrappedObject);
+
+ } catch (IllegalArgumentException e) {
+ reporter.reportDetailed(this,
+ Report.newBuilder(REPORT_ILLEGAL_ARGUMENT).error(e).callerParam(wrappedObject, find)
+ );
+ } catch (IllegalAccessException e) {
+ // Should not occur either
+ return null;
+ } catch (InvocationTargetException e) {
+ // This is really bad
+ throw new RuntimeException("Minecraft error.", e);
+ }
+
+ return null;
+ }
+ };
+
+ unwrapperCache.put(type, methodUnwrapper);
+ return methodUnwrapper;
+
+ } catch (SecurityException e) {
+ reporter.reportDetailed(this,
+ Report.newBuilder(REPORT_SECURITY_LIMITATION).error(e).callerParam(type)
+ );
+ } catch (NoSuchMethodException e) {
+ // Try getting the field unwrapper too
+ Unwrapper fieldUnwrapper = getFieldUnwrapper(type);
+
+ if (fieldUnwrapper != null)
+ return fieldUnwrapper;
+ else
+ reporter.reportDetailed(this,
+ Report.newBuilder(REPORT_CANNOT_FIND_UNWRAP_METHOD).error(e).callerParam(type));
+ }
+
+ // Default method
+ return null;
+ }
+
+ /**
+ * Retrieve a cached unwrapper using the handle field.
+ * @param type - a cached field unwrapper.
+ * @return The cached field unwrapper.
+ */
+ private Unwrapper getFieldUnwrapper(Class> type) {
+ final Field find = FieldUtils.getField(type, "handle", true);
+
+ // See if we succeeded
+ if (find != null) {
+ Unwrapper fieldUnwrapper = new Unwrapper() {
+ @Override
+ public Object unwrapItem(Object wrappedObject) {
+ try {
+ return FieldUtils.readField(find, wrappedObject, true);
+ } catch (IllegalAccessException e) {
+ reporter.reportDetailed(this,
+ Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).error(e).callerParam(wrappedObject, find)
+ );
+ return null;
+ }
+ }
+ };
+
+ unwrapperCache.put(type, fieldUnwrapper);
+ return fieldUnwrapper;
+
+ } else {
+ // Inform about this too
+ reporter.reportDetailed(this,
+ Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).callerParam(find)
+ );
+ return null;
+ }
+ }
+}
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/DelayedPacketManager.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/DelayedPacketManager.java
new file mode 100644
index 00000000..006f650b
--- /dev/null
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/DelayedPacketManager.java
@@ -0,0 +1,389 @@
+package com.comphenix.protocol.injector;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import javax.annotation.Nonnull;
+
+import org.bukkit.World;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.Plugin;
+import org.bukkit.plugin.PluginManager;
+
+import com.comphenix.protocol.AsynchronousManager;
+import com.comphenix.protocol.ProtocolManager;
+import com.comphenix.protocol.error.ErrorReporter;
+import com.comphenix.protocol.error.Report;
+import com.comphenix.protocol.error.ReportType;
+import com.comphenix.protocol.events.ConnectionSide;
+import com.comphenix.protocol.events.PacketContainer;
+import com.comphenix.protocol.events.PacketListener;
+import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
+import com.comphenix.protocol.reflect.FieldAccessException;
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+/**
+ * A protocol manager that delays all packet listener registrations and unregistrations until
+ * an underlying protocol manager can be constructed.
+ *
+ * @author Kristian
+ */
+public class DelayedPacketManager implements ProtocolManager, InternalManager {
+ // Registering packet IDs that are not supported
+ public static final ReportType REPORT_CANNOT_SEND_QUEUED_PACKET = new ReportType("Cannot send queued packet %s.");
+ public static final ReportType REPORT_CANNOT_REGISTER_QUEUED_LISTENER = new ReportType("Cannot register queued listener %s.");
+
+ /**
+ * Represents a packet that will be transmitted later.
+ * @author Kristian
+ *
+ */
+ private static class QueuedPacket {
+ private final Player player;
+ private final PacketContainer packet;
+ private final boolean filtered;
+ private final ConnectionSide side;
+
+ public QueuedPacket(Player player, PacketContainer packet, boolean filtered, ConnectionSide side) {
+ this.player = player;
+ this.packet = packet;
+ this.filtered = filtered;
+ this.side = side;
+ }
+
+ /**
+ * Retrieve the packet that will be transmitted or receieved.
+ * @return The packet.
+ */
+ public PacketContainer getPacket() {
+ return packet;
+ }
+
+ /**
+ * Retrieve the player that will send or recieve the packet.
+ * @return The source.
+ */
+ public Player getPlayer() {
+ return player;
+ }
+
+ /**
+ * Retrieve whether or not the packet will the sent or received.
+ * @return The connection side.
+ */
+ public ConnectionSide getSide() {
+ return side;
+ }
+
+ /**
+ * Determine if the packet should be intercepted by packet listeners.
+ * @return TRUE if it should, FALSE otherwise.
+ */
+ public boolean isFiltered() {
+ return filtered;
+ }
+ }
+
+ private volatile InternalManager delegate;
+
+ // Packet listeners that will be registered
+ private final Set queuedListeners = Sets.newSetFromMap(Maps.newConcurrentMap());
+ private final List queuedPackets = Collections.synchronizedList(Lists.newArrayList());
+
+ private AsynchronousManager asyncManager;
+ private ErrorReporter reporter;
+
+ // The current hook
+ private PlayerInjectHooks hook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
+
+ // If we have been closed
+ private boolean closed;
+
+ // Queued registration
+ private PluginManager queuedManager;
+ private Plugin queuedPlugin;
+
+ public DelayedPacketManager(@Nonnull ErrorReporter reporter) {
+ Preconditions.checkNotNull(reporter, "reporter cannot be NULL.");
+
+ this.reporter = reporter;
+ }
+
+ /**
+ * Retrieve the underlying protocol manager.
+ * @return The underlying manager.
+ */
+ public InternalManager getDelegate() {
+ return delegate;
+ }
+
+ /**
+ * Update the delegate to the underlying manager.
+ *
+ * This will prompt this packet manager to immediately transmit and
+ * register all queued packets an listeners.
+ * @param delegate - delegate to the new manager.
+ */
+ protected void setDelegate(InternalManager delegate) {
+ this.delegate = delegate;
+
+ if (delegate != null) {
+ // Update the hook if needed
+ if (!Objects.equal(delegate.getPlayerHook(), hook)) {
+ delegate.setPlayerHook(hook);
+ }
+ // Register events as well
+ if (queuedManager != null && queuedPlugin != null) {
+ delegate.registerEvents(queuedManager, queuedPlugin);
+ }
+
+ for (PacketListener listener : queuedListeners) {
+ try {
+ delegate.addPacketListener(listener);
+ } catch (IllegalArgumentException e) {
+ // Inform about this plugin error
+ reporter.reportWarning(this,
+ Report.newBuilder(REPORT_CANNOT_REGISTER_QUEUED_LISTENER).
+ callerParam(delegate).messageParam(listener).error(e));
+ }
+ }
+
+ synchronized (queuedPackets) {
+ for (QueuedPacket packet : queuedPackets) {
+ try {
+ // Attempt to send it now
+ switch (packet.getSide()) {
+ case CLIENT_SIDE:
+ delegate.recieveClientPacket(packet.getPlayer(), packet.getPacket(), packet.isFiltered());
+ break;
+ case SERVER_SIDE:
+ delegate.sendServerPacket(packet.getPlayer(), packet.getPacket(), packet.isFiltered());
+ break;
+ default:
+
+ }
+ } catch (Exception e) {
+ // Inform about this plugin error
+ reporter.reportWarning(this,
+ Report.newBuilder(REPORT_CANNOT_SEND_QUEUED_PACKET).
+ callerParam(delegate).messageParam(packet).error(e));
+ }
+ }
+ }
+
+ // Don't keep this around anymore
+ queuedListeners.clear();
+ queuedPackets.clear();
+ }
+ }
+
+ @Override
+ public void setPlayerHook(PlayerInjectHooks playerHook) {
+ this.hook = playerHook;
+ }
+
+ @Override
+ public PlayerInjectHooks getPlayerHook() {
+ return hook;
+ }
+
+ @Override
+ public void sendServerPacket(Player reciever, PacketContainer packet) throws InvocationTargetException {
+ sendServerPacket(reciever, packet, true);
+ }
+
+ @Override
+ public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) throws InvocationTargetException {
+ if (delegate != null) {
+ delegate.sendServerPacket(reciever, packet, filters);
+ } else {
+ queuedPackets.add(new QueuedPacket(reciever, packet, filters, ConnectionSide.SERVER_SIDE));
+ }
+ }
+
+ @Override
+ public void recieveClientPacket(Player sender, PacketContainer packet) throws IllegalAccessException, InvocationTargetException {
+ recieveClientPacket(sender, packet, true);
+ }
+
+ @Override
+ public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters) throws IllegalAccessException, InvocationTargetException {
+ if (delegate != null) {
+ delegate.recieveClientPacket(sender, packet, filters);
+ } else {
+ queuedPackets.add(new QueuedPacket(sender, packet, filters, ConnectionSide.CLIENT_SIDE));
+ }
+ }
+
+ @Override
+ public ImmutableSet getPacketListeners() {
+ if (delegate != null)
+ return delegate.getPacketListeners();
+ else
+ return ImmutableSet.copyOf(queuedListeners);
+ }
+
+ @Override
+ public void addPacketListener(PacketListener listener) {
+ if (delegate != null)
+ delegate.addPacketListener(listener);
+ else
+ queuedListeners.add(listener);
+ }
+
+ @Override
+ public void removePacketListener(PacketListener listener) {
+ if (delegate != null)
+ delegate.removePacketListener(listener);
+ else
+ queuedListeners.remove(listener);
+ }
+
+ @Override
+ public void removePacketListeners(Plugin plugin) {
+ if (delegate != null) {
+ delegate.removePacketListeners(plugin);
+ } else {
+ for (Iterator it = queuedListeners.iterator(); it.hasNext(); ) {
+ // Remove listeners of the same plugin
+ if (Objects.equal(it.next().getPlugin(), plugin)) {
+ it.remove();
+ }
+ }
+ }
+ }
+
+ @Override
+ public PacketContainer createPacket(int id) {
+ if (delegate != null)
+ return delegate.createPacket(id);
+ return createPacket(id, true);
+ }
+
+ @Override
+ public PacketContainer createPacket(int id, boolean forceDefaults) {
+ if (delegate != null) {
+ return delegate.createPacket(id);
+ } else {
+ // Fallback implementation
+ PacketContainer packet = new PacketContainer(id);
+
+ // Use any default values if possible
+ if (forceDefaults) {
+ try {
+ packet.getModifier().writeDefaults();
+ } catch (FieldAccessException e) {
+ throw new RuntimeException("Security exception.", e);
+ }
+ }
+ return packet;
+ }
+ }
+
+ @Override
+ public PacketConstructor createPacketConstructor(int id, Object... arguments) {
+ if (delegate != null)
+ return delegate.createPacketConstructor(id, arguments);
+ else
+ return PacketConstructor.DEFAULT.withPacket(id, arguments);
+ }
+
+ @Override
+ public Set getSendingFilters() {
+ if (delegate != null) {
+ return delegate.getSendingFilters();
+ } else {
+ // Linear scan is fast enough here
+ Set sending = Sets.newHashSet();
+
+ for (PacketListener listener : queuedListeners) {
+ sending.addAll(listener.getSendingWhitelist().getWhitelist());
+ }
+ return sending;
+ }
+ }
+
+ @Override
+ public Set getReceivingFilters() {
+ if (delegate != null) {
+ return delegate.getReceivingFilters();
+ } else {
+ Set recieving = Sets.newHashSet();
+
+ for (PacketListener listener : queuedListeners) {
+ recieving.addAll(listener.getReceivingWhitelist().getWhitelist());
+ }
+ return recieving;
+ }
+ }
+
+ @Override
+ public void updateEntity(Entity entity, List observers) throws FieldAccessException {
+ if (delegate != null)
+ delegate.updateEntity(entity, observers);
+ else
+ EntityUtilities.updateEntity(entity, observers);
+ }
+
+ @Override
+ public Entity getEntityFromID(World container, int id) throws FieldAccessException {
+ if (delegate != null)
+ return delegate.getEntityFromID(container, id);
+ else
+ return EntityUtilities.getEntityFromID(container, id);
+ }
+
+ @Override
+ public List getEntityTrackers(Entity entity) throws FieldAccessException {
+ if (delegate != null)
+ return delegate.getEntityTrackers(entity);
+ else
+ return EntityUtilities.getEntityTrackers(entity);
+ }
+
+ @Override
+ public boolean isClosed() {
+ return closed || (delegate != null && delegate.isClosed());
+ }
+
+ @Override
+ public AsynchronousManager getAsynchronousManager() {
+ if (delegate != null)
+ return delegate.getAsynchronousManager();
+ else
+ return asyncManager;
+ }
+
+ /**
+ * Update the asynchronous manager. This must be set.
+ * @param asyncManager - the asynchronous manager.
+ */
+ public void setAsynchronousManager(AsynchronousManager asyncManager) {
+ this.asyncManager = asyncManager;
+ }
+
+ @Override
+ public void registerEvents(PluginManager manager, Plugin plugin) {
+ if (delegate != null) {
+ delegate.registerEvents(manager, plugin);
+ } else {
+ queuedManager = manager;
+ queuedPlugin = plugin;
+ }
+ }
+
+ @Override
+ public void close() {
+ if (delegate != null)
+ delegate.close();
+ closed = true;
+ }
+}
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/InternalManager.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/InternalManager.java
new file mode 100644
index 00000000..fe37d341
--- /dev/null
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/InternalManager.java
@@ -0,0 +1,38 @@
+package com.comphenix.protocol.injector;
+
+import org.bukkit.plugin.Plugin;
+import org.bukkit.plugin.PluginManager;
+
+import com.comphenix.protocol.ProtocolManager;
+import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
+
+/**
+ * Yields access to the internal hook configuration.
+ *
+ * @author Kristian
+ */
+public interface InternalManager extends ProtocolManager {
+ /**
+ * Retrieves how the server packets are read.
+ * @return Injection method for reading server packets.
+ */
+ public PlayerInjectHooks getPlayerHook();
+
+ /**
+ * Sets how the server packets are read.
+ * @param playerHook - the new injection method for reading server packets.
+ */
+ public void setPlayerHook(PlayerInjectHooks playerHook);
+
+ /**
+ * Register this protocol manager on Bukkit.
+ * @param manager - Bukkit plugin manager that provides player join/leave events.
+ * @param plugin - the parent plugin.
+ */
+ public void registerEvents(PluginManager manager, final Plugin plugin);
+
+ /**
+ * Called when ProtocolLib is closing.
+ */
+ public void close();
+}
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 52b702fc..e0da7b83 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/ListenerInvoker.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/ListenerInvoker.java
@@ -1,68 +1,68 @@
-/*
- * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
- * Copyright (C) 2012 Kristian S. Stangeland
- *
- * This program is free software; you can redistribute it and/or modify it under the terms of the
- * GNU General Public License as published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
- * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with this program;
- * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
- * 02111-1307 USA
- */
-
-package com.comphenix.protocol.injector;
-
-import com.comphenix.protocol.events.PacketEvent;
-
-/**
- * Represents an object that initiate the packet listeners.
- *
- * @author Kristian
- */
-public interface ListenerInvoker {
-
- /**
- * Invokes the given packet event for every registered listener.
- * @param event - the packet event to invoke.
- */
- public abstract void invokePacketRecieving(PacketEvent event);
-
- /**
- * Invokes the given packet event for every registered listener.
- * @param event - the packet event to invoke.
- */
- public abstract void invokePacketSending(PacketEvent event);
-
- /**
- * Retrieve the associated ID of a packet.
- * @param packet - the packet.
- * @return The packet ID.
- */
- public abstract int getPacketID(Object packet);
-
- /**
- * Associate a given class with the given packet ID. Internal method.
- * @param clazz - class to associate.
- */
- public abstract void unregisterPacketClass(Class> clazz);
-
- /**
- * Register a given class in the packet registry. Internal method.
- * @param clazz - class to register.
- * @param packetID - the the new associated packet ID.
- */
- public abstract void registerPacketClass(Class> clazz, int packetID);
-
- /**
- * Retrieves the correct packet class from a given packet ID.
- * @param packetID - the packet ID.
- * @param forceVanilla - whether or not to look for vanilla classes, not injected classes.
- * @return The associated class.
- */
- public abstract Class> getPacketClassFromID(int packetID, boolean forceVanilla);
+/*
+ * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
+ * Copyright (C) 2012 Kristian S. Stangeland
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program;
+ * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA
+ */
+
+package com.comphenix.protocol.injector;
+
+import com.comphenix.protocol.events.PacketEvent;
+
+/**
+ * Represents an object that initiate the packet listeners.
+ *
+ * @author Kristian
+ */
+public interface ListenerInvoker {
+
+ /**
+ * Invokes the given packet event for every registered listener.
+ * @param event - the packet event to invoke.
+ */
+ public abstract void invokePacketRecieving(PacketEvent event);
+
+ /**
+ * Invokes the given packet event for every registered listener.
+ * @param event - the packet event to invoke.
+ */
+ public abstract void invokePacketSending(PacketEvent event);
+
+ /**
+ * Retrieve the associated ID of a packet.
+ * @param packet - the packet.
+ * @return The packet ID.
+ */
+ public abstract int getPacketID(Object packet);
+
+ /**
+ * Associate a given class with the given packet ID. Internal method.
+ * @param clazz - class to associate.
+ */
+ public abstract void unregisterPacketClass(Class> clazz);
+
+ /**
+ * Register a given class in the packet registry. Internal method.
+ * @param clazz - class to register.
+ * @param packetID - the the new associated packet ID.
+ */
+ public abstract void registerPacketClass(Class> clazz, int packetID);
+
+ /**
+ * Retrieves the correct packet class from a given packet ID.
+ * @param packetID - the packet ID.
+ * @param forceVanilla - whether or not to look for vanilla classes, not injected classes.
+ * @return The associated class.
+ */
+ public abstract Class> getPacketClassFromID(int packetID, boolean forceVanilla);
}
\ No newline at end of file
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterBuilder.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterBuilder.java
new file mode 100644
index 00000000..1edad3c8
--- /dev/null
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterBuilder.java
@@ -0,0 +1,255 @@
+package com.comphenix.protocol.injector;
+
+import javax.annotation.Nonnull;
+
+import org.bukkit.Server;
+import org.bukkit.event.world.WorldInitEvent;
+import org.bukkit.plugin.Plugin;
+
+import com.comphenix.executors.BukkitFutures;
+import com.comphenix.protocol.async.AsyncFilterManager;
+import com.comphenix.protocol.error.ErrorReporter;
+import com.comphenix.protocol.error.Report;
+import com.comphenix.protocol.error.ReportType;
+import com.comphenix.protocol.injector.player.InjectedServerConnection;
+import com.comphenix.protocol.injector.spigot.SpigotPacketInjector;
+import com.comphenix.protocol.utility.MinecraftReflection;
+import com.comphenix.protocol.utility.MinecraftVersion;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+
+public class PacketFilterBuilder {
+ public static final ReportType REPORT_TEMPORARY_EVENT_ERROR = new ReportType("Unable to register or handle temporary event.");
+
+ private ClassLoader classLoader;
+ private Server server;
+ private Plugin library;
+ private MinecraftVersion mcVersion;
+ private DelayedSingleTask unhookTask;
+ private ErrorReporter reporter;
+
+ // Whether or not we need to enable Netty
+ private AsyncFilterManager asyncManager;
+ private boolean nettyEnabled;
+
+ /**
+ * Update the current class loader.
+ * @param classLoader - current class loader.
+ * @return This builder, for chaining.
+ */
+ public PacketFilterBuilder classLoader(@Nonnull ClassLoader classLoader) {
+ if (classLoader == null)
+ throw new IllegalArgumentException("classLoader cannot be NULL.");
+ this.classLoader = classLoader;
+ return this;
+ }
+
+ /**
+ * Set the current server.
+ * @param server - current server.
+ * @return This builder, for chaining.
+ */
+ public PacketFilterBuilder server(@Nonnull Server server) {
+ if (server == null)
+ throw new IllegalArgumentException("server cannot be NULL.");
+ this.server = server;
+ return this;
+ }
+
+ /**
+ * Set a reference to the plugin instance of ProtocolLib.
+ * @param library - plugin instance.
+ * @return This builder, for chaining.
+ */
+ public PacketFilterBuilder library(@Nonnull Plugin library) {
+ if (library == null)
+ throw new IllegalArgumentException("library cannot be NULL.");
+ this.library = library;
+ return this;
+ }
+
+ /**
+ * Set the current Minecraft version.
+ * @param mcVersion - Minecraft version.
+ * @return This builder, for chaining.
+ */
+ public PacketFilterBuilder minecraftVersion(@Nonnull MinecraftVersion mcVersion) {
+ if (mcVersion == null)
+ throw new IllegalArgumentException("minecraftVersion cannot be NULL.");
+ this.mcVersion = mcVersion;
+ return this;
+ }
+
+ /**
+ * Set the task used to delay unhooking when ProtocolLib is no in use.
+ * @param unhookTask - the unhook task.
+ * @return This builder, for chaining.
+ */
+ public PacketFilterBuilder unhookTask(@Nonnull DelayedSingleTask unhookTask) {
+ if (unhookTask == null)
+ throw new IllegalArgumentException("unhookTask cannot be NULL.");
+ this.unhookTask = unhookTask;
+ return this;
+ }
+
+ /**
+ * Set the error reporter.
+ * @param reporter - new error reporter.
+ * @return This builder, for chaining.
+ */
+ public PacketFilterBuilder reporter(@Nonnull ErrorReporter reporter) {
+ if (reporter == null)
+ throw new IllegalArgumentException("reporter cannot be NULL.");
+ this.reporter = reporter;
+ return this;
+ }
+
+ /**
+ * Determine if we should prepare to hook Netty in Spigot.
+ *
+ * This is calculated in the {@link #build()} method.
+ * @return TRUE if we should, FALSE otherwise.
+ */
+ public boolean isNettyEnabled() {
+ return nettyEnabled;
+ }
+
+ /**
+ * Retrieve the class loader set in this builder.
+ * @return The class loader.
+ */
+ public ClassLoader getClassLoader() {
+ return classLoader;
+ }
+
+ /**
+ * Retrieve the current CraftBukkit server.
+ * @return Current server.
+ */
+ public Server getServer() {
+ return server;
+ }
+
+ /**
+ * Retrieve a reference to the current ProtocolLib instance.
+ * @return ProtocolLib.
+ */
+ public Plugin getLibrary() {
+ return library;
+ }
+
+ /**
+ * Retrieve the current Minecraft version.
+ * @return Current version.
+ */
+ public MinecraftVersion getMinecraftVersion() {
+ return mcVersion;
+ }
+
+ /**
+ * Retrieve the task that is used to delay unhooking when ProtocolLib is no in use.
+ * @return The unhook task.
+ */
+ public DelayedSingleTask getUnhookTask() {
+ return unhookTask;
+ }
+
+ /**
+ * Retrieve the error reporter.
+ * @return Error reporter.
+ */
+ public ErrorReporter getReporter() {
+ return reporter;
+ }
+
+ /**
+ * Retrieve the asynchronous manager.
+ *
+ * This is first constructed the {@link #build()} method.
+ * @return The asynchronous manager.
+ */
+ public AsyncFilterManager getAsyncManager() {
+ return asyncManager;
+ }
+
+ /**
+ * Create a new packet filter manager.
+ * @return A new packet filter manager.
+ */
+ public InternalManager build() {
+ if (reporter == null)
+ throw new IllegalArgumentException("reporter cannot be NULL.");
+ if (classLoader == null)
+ throw new IllegalArgumentException("classLoader cannot be NULL.");
+
+ asyncManager = new AsyncFilterManager(reporter, server.getScheduler());
+ nettyEnabled = false;
+
+ // Spigot
+ if (SpigotPacketInjector.canUseSpigotListener()) {
+ // If the server hasn't loaded yet - wait
+ if (InjectedServerConnection.getServerConnection(reporter, server) == null) {
+ // We need to delay this until we know if Netty is enabled
+ final DelayedPacketManager delayed = new DelayedPacketManager(reporter);
+
+ // They must reference each other
+ delayed.setAsynchronousManager(asyncManager);
+ asyncManager.setManager(delayed);
+
+ Futures.addCallback(BukkitFutures.nextEvent(library, WorldInitEvent.class),
+ new FutureCallback() {
+ @Override
+ public void onSuccess(WorldInitEvent event) {
+ // Nevermind
+ if (delayed.isClosed())
+ return;
+
+ try {
+ registerSpigot(delayed);
+ } catch (Exception e) {
+ onFailure(e);
+ }
+ }
+
+ @Override
+ public void onFailure(Throwable error) {
+ reporter.reportWarning(PacketFilterBuilder.this, Report
+ .newBuilder(REPORT_TEMPORARY_EVENT_ERROR).error(error));
+ }
+ });
+
+ System.out.println("Delaying due to Spigot");
+
+ // Let plugins use this version instead
+ return delayed;
+ } else {
+ nettyEnabled = !MinecraftReflection.isMinecraftObject(
+ InjectedServerConnection.getServerConnection(reporter, server));
+ }
+ }
+
+ // Otherwise - construct the packet filter manager right away
+ return buildInternal();
+ }
+
+ private void registerSpigot(DelayedPacketManager delayed) {
+ // Use netty if we have a non-standard ServerConnection class
+ nettyEnabled = !MinecraftReflection.isMinecraftObject(
+ InjectedServerConnection.getServerConnection(reporter, server));
+
+ // Switch to the standard manager
+ delayed.setDelegate(buildInternal());
+ }
+
+ /**
+ * Construct a new packet filter manager without checking for Netty.
+ * @return A new packet filter manager.
+ */
+ private PacketFilterManager buildInternal() {
+ PacketFilterManager manager = new PacketFilterManager(this);
+
+ // It's a cyclic reference, but it's too late to fix now
+ asyncManager.setManager(manager);
+ return manager;
+ }
+}
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 5666765b..2848cfe5 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java
@@ -63,12 +63,12 @@ import com.comphenix.protocol.injector.spigot.SpigotPacketInjector;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.utility.MinecraftReflection;
-import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet;
-public final class PacketFilterManager implements ProtocolManager, ListenerInvoker {
+public final class PacketFilterManager implements ProtocolManager, ListenerInvoker, InternalManager {
+
public static final ReportType REPORT_CANNOT_LOAD_PACKET_LIST = new ReportType("Cannot load server and client packet list.");
public static final ReportType REPORT_CANNOT_INITIALIZE_PACKET_INJECTOR = new ReportType("Unable to initialize packet injector");
@@ -85,6 +85,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
public static final ReportType REPORT_CANNOT_INJECT_PLAYER = new ReportType("Unable to inject player.");
public static final ReportType REPORT_CANNOT_UNREGISTER_PLUGIN = new ReportType("Unable to handle disabled plugin.");
+ public static final ReportType REPORT_PLUGIN_VERIFIER_ERROR = new ReportType("Verifier error: %s");
/**
* Sets the inject hook type. Different types allow for maximum compatibility.
@@ -172,37 +173,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
/**
* Only create instances of this class if protocol lib is disabled.
*/
- public PacketFilterManager(ClassLoader classLoader, Server server, Plugin library, DelayedSingleTask unhookTask, ErrorReporter reporter) {
- this(classLoader, server, library, new MinecraftVersion(server), unhookTask, reporter);
- }
-
- /**
- * Only create instances of this class if protocol lib is disabled.
- */
- public PacketFilterManager(ClassLoader classLoader, Server server, Plugin library,
- MinecraftVersion mcVersion, DelayedSingleTask unhookTask, ErrorReporter reporter) {
-
- if (reporter == null)
- throw new IllegalArgumentException("reporter cannot be NULL.");
- if (classLoader == null)
- throw new IllegalArgumentException("classLoader cannot be NULL.");
-
- // Just boilerplate
- final DelayedSingleTask finalUnhookTask = unhookTask;
-
- // Listener containers
- this.recievedListeners = new SortedPacketListenerList();
- this.sendingListeners = new SortedPacketListenerList();
-
- // References
- this.unhookTask = unhookTask;
- this.server = server;
- this.classLoader = classLoader;
- this.reporter = reporter;
-
- // The plugin verifier
- this.pluginVerifier = new PluginVerifier(library);
-
+ public PacketFilterManager(PacketFilterBuilder builder) {
// Used to determine if injection is needed
Predicate isInjectionNecessary = new Predicate() {
@Override
@@ -213,58 +184,66 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
result &= getPhaseLoginCount() > 0;
// Note that we will still hook players if the unhooking has been delayed
if (phase.hasPlaying())
- result &= getPhasePlayingCount() > 0 || finalUnhookTask.isRunning();
+ result &= getPhasePlayingCount() > 0 || unhookTask.isRunning();
return result;
}
};
+ // Listener containers
+ this.recievedListeners = new SortedPacketListenerList();
+ this.sendingListeners = new SortedPacketListenerList();
+
+ // References
+ this.unhookTask = builder.getUnhookTask();
+ this.server = builder.getServer();
+ this.classLoader = builder.getClassLoader();
+ this.reporter = builder.getReporter();
+
+ // The plugin verifier
+ this.pluginVerifier = new PluginVerifier(builder.getLibrary());
+
+ // Use the correct injection type
+ if (builder.isNettyEnabled()) {
+ spigotInjector = new SpigotPacketInjector(classLoader, reporter, this, server);
+ this.playerInjection = spigotInjector.getPlayerHandler();
+ this.packetInjector = spigotInjector.getPacketInjector();
+
+ } else {
+ // Initialize standard injection mangers
+ this.playerInjection = PlayerInjectorBuilder.newBuilder().
+ invoker(this).
+ server(server).
+ reporter(reporter).
+ classLoader(classLoader).
+ packetListeners(packetListeners).
+ injectionFilter(isInjectionNecessary).
+ version(builder.getMinecraftVersion()).
+ buildHandler();
+
+ this.packetInjector = PacketInjectorBuilder.newBuilder().
+ invoker(this).
+ reporter(reporter).
+ classLoader(classLoader).
+ playerInjection(playerInjection).
+ buildInjector();
+ }
+ this.asyncFilterManager = builder.getAsyncManager();
+
+ // Attempt to load the list of server and client packets
try {
- // Spigot
- if (SpigotPacketInjector.canUseSpigotListener()) {
- spigotInjector = new SpigotPacketInjector(classLoader, reporter, this, server);
- this.playerInjection = spigotInjector.getPlayerHandler();
- this.packetInjector = spigotInjector.getPacketInjector();
-
- } else {
- // Initialize standard injection mangers
- this.playerInjection = PlayerInjectorBuilder.newBuilder().
- invoker(this).
- server(server).
- reporter(reporter).
- classLoader(classLoader).
- packetListeners(packetListeners).
- injectionFilter(isInjectionNecessary).
- version(mcVersion).
- buildHandler();
-
- this.packetInjector = PacketInjectorBuilder.newBuilder().
- invoker(this).
- reporter(reporter).
- classLoader(classLoader).
- playerInjection(playerInjection).
- buildInjector();
- }
-
- this.asyncFilterManager = new AsyncFilterManager(reporter, server.getScheduler(), this);
-
- // Attempt to load the list of server and client packets
- try {
- knowsServerPackets = PacketRegistry.getServerPackets() != null;
- knowsClientPackets = PacketRegistry.getClientPackets() != null;
- } catch (FieldAccessException e) {
- reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_PACKET_LIST).error(e));
- }
-
+ knowsServerPackets = PacketRegistry.getServerPackets() != null;
+ knowsClientPackets = PacketRegistry.getClientPackets() != null;
} catch (FieldAccessException e) {
- reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_INITIALIZE_PACKET_INJECTOR).error(e));
+ reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_PACKET_LIST).error(e));
}
}
-
+
/**
- * Initiate logic that is performed after the world has loaded.
+ * Construct a new packet filter builder.
+ * @return New builder.
*/
- public void postWorldLoaded() {
- playerInjection.postWorldLoaded();
+ public static PacketFilterBuilder newBuilder() {
+ return new PacketFilterBuilder();
}
@Override
@@ -276,6 +255,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
* Retrieves how the server packets are read.
* @return Injection method for reading server packets.
*/
+ @Override
public PlayerInjectHooks getPlayerHook() {
return playerInjection.getPlayerHook();
}
@@ -284,6 +264,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
* Sets how the server packets are read.
* @param playerHook - the new injection method for reading server packets.
*/
+ @Override
public void setPlayerHook(PlayerInjectHooks playerHook) {
playerInjection.setPlayerHook(playerHook);
}
@@ -298,12 +279,16 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
* @param plugin - plugin to check.
*/
private void printPluginWarnings(Plugin plugin) {
- switch (pluginVerifier.verify(plugin)) {
- case NO_DEPEND:
- reporter.reportWarning(this, Report.newBuilder(REPORT_PLUGIN_DEPEND_MISSING).messageParam(plugin.getName()));
- case VALID:
- // Do nothing
- break;
+ try {
+ switch (pluginVerifier.verify(plugin)) {
+ case NO_DEPEND:
+ reporter.reportWarning(this, Report.newBuilder(REPORT_PLUGIN_DEPEND_MISSING).messageParam(plugin.getName()));
+ case VALID:
+ // Do nothing
+ break;
+ }
+ } catch (IllegalStateException e) {
+ reporter.reportWarning(this, Report.newBuilder(REPORT_PLUGIN_VERIFIER_ERROR).messageParam(e.getMessage()));
}
}
@@ -702,6 +687,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
* @param manager - Bukkit plugin manager that provides player join/leave events.
* @param plugin - the parent plugin.
*/
+ @Override
public void registerEvents(PluginManager manager, final Plugin plugin) {
if (spigotInjector != null && !spigotInjector.register(plugin))
throw new IllegalArgumentException("Spigot has already been registered.");
@@ -969,9 +955,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
return hasClosed;
}
- /**
- * Called when ProtocolLib is closing.
- */
+ @Override
public void close() {
// Guard
if (hasClosed)
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PluginVerifier.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PluginVerifier.java
index 2e66cf5d..30bf4e51 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PluginVerifier.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PluginVerifier.java
@@ -5,7 +5,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
-import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginLoadOrder;
@@ -61,7 +60,7 @@ class PluginVerifier {
* Reference to ProtocolLib.
*/
private final Plugin dependency;
-
+
/**
* Construct a new plugin verifier.
* @param dependency - reference to ProtocolLib, a dependency we require of plugins.
@@ -98,7 +97,7 @@ class PluginVerifier {
* @return The retrieved plugin, or NULL if not found.
*/
private Plugin getPluginOrDefault(String pluginName) {
- return Bukkit.getPluginManager().getPlugin(pluginName);
+ return dependency.getServer().getPluginManager().getPlugin(pluginName);
}
/**
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/InverseMaps.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/InverseMaps.java
new file mode 100644
index 00000000..96f8ed5f
--- /dev/null
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/InverseMaps.java
@@ -0,0 +1,130 @@
+package com.comphenix.protocol.injector.packet;
+
+import java.lang.reflect.Field;
+import java.util.Map;
+
+import com.comphenix.protocol.reflect.FieldUtils;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ForwardingMap;
+import com.google.common.collect.ForwardingMultimap;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+
+public class InverseMaps {
+ private InverseMaps() {
+ // Not constructable
+ }
+
+ public static Multimap inverseMultimap(final Map map, final Predicate> filter) {
+ final MapContainer container = new MapContainer(map);
+
+ return new ForwardingMultimap() {
+ // The cached multimap
+ private Multimap inverseMultimap;
+
+ @Override
+ protected Multimap delegate() {
+ if (container.hasChanged()) {
+ inverseMultimap = HashMultimap.create();
+
+ // Construct the inverse map
+ for (Map.Entry entry : map.entrySet()) {
+ if (filter.apply(entry)) {
+ inverseMultimap.put(entry.getValue(), entry.getKey());
+ }
+ }
+ container.setChanged(false);
+ }
+ return inverseMultimap;
+ }
+ };
+ }
+
+ public static Map inverseMap(final Map map, final Predicate> filter) {
+ final MapContainer container = new MapContainer(map);
+
+ return new ForwardingMap() {
+ // The cached map
+ private Map inverseMap;
+
+ @Override
+ protected Map delegate() {
+ if (container.hasChanged()) {
+ inverseMap = Maps.newHashMap();
+
+ // Construct the inverse map
+ for (Map.Entry entry : map.entrySet()) {
+ if (filter.apply(entry)) {
+ inverseMap.put(entry.getValue(), entry.getKey());
+ }
+ }
+ container.setChanged(false);
+ }
+ return inverseMap;
+ }
+ };
+ }
+
+ /**
+ * Represents a class that can detect if a map has changed.
+ * @author Kristian
+ */
+ private static class MapContainer {
+ // For detecting changes
+ private Field modCountField;
+ private int lastModCount;
+
+ // The object along with whether or not this is the initial run
+ private Object source;
+ private boolean changed;
+
+ public MapContainer(Object source) {
+ this.source = source;
+ this.changed = true;
+ this.modCountField = FieldUtils.getField(source.getClass(), "modCount", true);
+ }
+
+ /**
+ * Determine if the map has changed.
+ * @return TRUE if it has, FALSE otherwise.
+ */
+ public boolean hasChanged() {
+ // Check if unchanged
+ checkChanged();
+ return changed;
+ }
+
+ /**
+ * Mark the map as changed or unchanged.
+ * @param changed - TRUE if the map has changed, FALSE otherwise.
+ */
+ public void setChanged(boolean changed) {
+ this.changed = changed;
+ }
+
+ /**
+ * Check for modifications to the current map.
+ */
+ protected void checkChanged() {
+ if (!changed) {
+ if (getModificationCount() != lastModCount) {
+ lastModCount = getModificationCount();
+ changed = true;
+ }
+ }
+ }
+
+ /**
+ * Retrieve the current modification count.
+ * @return The current count, or something different than lastModCount if not accessible.
+ */
+ private int getModificationCount() {
+ try {
+ return modCountField != null ? modCountField.getInt(source) : lastModCount + 1;
+ } catch (Exception e) {
+ throw new RuntimeException("Unable to retrieve modCount.", e);
+ }
+ }
+ }
+}
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketRegistry.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketRegistry.java
index 72d70457..9448aff7 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketRegistry.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketRegistry.java
@@ -1,303 +1,333 @@
-/*
- * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
- * Copyright (C) 2012 Kristian S. Stangeland
- *
- * This program is free software; you can redistribute it and/or modify it under the terms of the
- * GNU General Public License as published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
- * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with this program;
- * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
- * 02111-1307 USA
- */
-
-package com.comphenix.protocol.injector.packet;
-
-import java.lang.reflect.Field;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import net.sf.cglib.proxy.Factory;
-
-import com.comphenix.protocol.ProtocolLibrary;
-import com.comphenix.protocol.error.Report;
-import com.comphenix.protocol.error.ReportType;
-import com.comphenix.protocol.reflect.FieldAccessException;
-import com.comphenix.protocol.reflect.FieldUtils;
-import com.comphenix.protocol.reflect.FuzzyReflection;
-import com.comphenix.protocol.reflect.fuzzy.FuzzyClassContract;
-import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
-import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
-import com.comphenix.protocol.utility.MinecraftReflection;
-import com.comphenix.protocol.wrappers.TroveWrapper;
-import com.google.common.base.Objects;
-import com.google.common.collect.ImmutableSet;
-
-/**
- * Static packet registry in Minecraft.
- *
- * @author Kristian
- */
-@SuppressWarnings("rawtypes")
-public class PacketRegistry {
- public static final ReportType REPORT_CANNOT_CORRECT_TROVE_MAP = new ReportType("Unable to correct no entry value.");
-
- public static final ReportType REPORT_INSUFFICIENT_SERVER_PACKETS = new ReportType("Too few server packets detected: %s");
- public static final ReportType REPORT_INSUFFICIENT_CLIENT_PACKETS = new ReportType("Too few client packets detected: %s");
-
- private static final int MIN_SERVER_PACKETS = 5;
- private static final int MIN_CLIENT_PACKETS = 5;
-
- // Fuzzy reflection
- private static FuzzyReflection packetRegistry;
-
- // The packet class to packet ID translator
- private static Map packetToID;
-
- // Whether or not certain packets are sent by the client or the server
- private static ImmutableSet serverPackets;
- private static ImmutableSet clientPackets;
-
- // The underlying sets
- private static Set serverPacketsRef;
- private static Set clientPacketsRef;
-
- // New proxy values
- private static Map overwrittenPackets = new HashMap();
-
- // Vanilla packets
- private static Map previousValues = new HashMap();
-
- @SuppressWarnings({ "unchecked" })
- public static Map getPacketToID() {
- // Initialize it, if we haven't already
- if (packetToID == null) {
- try {
- Field packetsField = getPacketRegistry().getFieldByType("packetsField", Map.class);
- packetToID = (Map) FieldUtils.readStaticField(packetsField, true);
- } catch (IllegalArgumentException e) {
- // Spigot 1.2.5 MCPC workaround
- try {
- packetToID = getSpigotWrapper();
- } catch (Exception e2) {
- // Very bad indeed
- throw new IllegalArgumentException(e.getMessage() + "; Spigot workaround failed.", e2);
- }
-
- } catch (IllegalAccessException e) {
- throw new RuntimeException("Unable to retrieve the packetClassToIdMap", e);
- }
- }
-
- return packetToID;
- }
-
- private static Map getSpigotWrapper() throws IllegalAccessException {
- // If it talks like a duck, etc.
- // Perhaps it would be nice to have a proper duck typing library as well
- FuzzyClassContract mapLike = FuzzyClassContract.newBuilder().
- method(FuzzyMethodContract.newBuilder().
- nameExact("size").returnTypeExact(int.class)).
- method(FuzzyMethodContract.newBuilder().
- nameExact("put").parameterCount(2)).
- method(FuzzyMethodContract.newBuilder().
- nameExact("get").parameterCount(1)).
- build();
-
- Field packetsField = getPacketRegistry().getField(
- FuzzyFieldContract.newBuilder().typeMatches(mapLike).build());
- Object troveMap = FieldUtils.readStaticField(packetsField, true);
-
- // Check for stupid no_entry_values
- try {
- Field field = FieldUtils.getField(troveMap.getClass(), "no_entry_value", true);
- Integer value = (Integer) FieldUtils.readField(field, troveMap, true);
-
- if (value >= 0 && value < 256) {
- // Someone forgot to set the no entry value. Let's help them.
- FieldUtils.writeField(field, troveMap, -1);
- }
- } catch (IllegalArgumentException e) {
- // Whatever
- ProtocolLibrary.getErrorReporter().reportWarning(PacketRegistry.class,
- Report.newBuilder(REPORT_CANNOT_CORRECT_TROVE_MAP).error(e));
- }
-
- // We'll assume this a Trove map
- return TroveWrapper.getDecoratedMap(troveMap);
- }
-
- /**
- * Retrieve the cached fuzzy reflection instance allowing access to the packet registry.
- * @return Reflected packet registry.
- */
- private static FuzzyReflection getPacketRegistry() {
- if (packetRegistry == null)
- packetRegistry = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true);
- return packetRegistry;
- }
-
- /**
- * Retrieve the injected proxy classes handlig each packet ID.
- * @return Injected classes.
- */
- public static Map getOverwrittenPackets() {
- return overwrittenPackets;
- }
-
- /**
- * Retrieve the vanilla classes handling each packet ID.
- * @return Vanilla classes.
- */
- public static Map getPreviousPackets() {
- return previousValues;
- }
-
- /**
- * Retrieve every known and supported server packet.
- * @return An immutable set of every known server packet.
- * @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft.
- */
- public static Set getServerPackets() throws FieldAccessException {
- initializeSets();
-
- // Sanity check. This is impossible!
- if (serverPackets != null && serverPackets.size() < MIN_SERVER_PACKETS)
- throw new FieldAccessException("Server packet list is empty. Seems to be unsupported");
- return serverPackets;
- }
-
- /**
- * Retrieve every known and supported client packet.
- * @return An immutable set of every known client packet.
- * @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft.
- */
- public static Set getClientPackets() throws FieldAccessException {
- initializeSets();
-
- // As above
- if (clientPackets != null && clientPackets.size() < MIN_CLIENT_PACKETS)
- throw new FieldAccessException("Client packet list is empty. Seems to be unsupported");
- return clientPackets;
- }
-
- @SuppressWarnings("unchecked")
- private static void initializeSets() throws FieldAccessException {
- if (serverPacketsRef == null || clientPacketsRef == null) {
- List sets = getPacketRegistry().getFieldListByType(Set.class);
-
- try {
- if (sets.size() > 1) {
- serverPacketsRef = (Set) FieldUtils.readStaticField(sets.get(0), true);
- clientPacketsRef = (Set) FieldUtils.readStaticField(sets.get(1), true);
-
- // Impossible
- if (serverPacketsRef == null || clientPacketsRef == null)
- throw new FieldAccessException("Packet sets are in an illegal state.");
-
- // NEVER allow callers to modify the underlying sets
- serverPackets = ImmutableSet.copyOf(serverPacketsRef);
- clientPackets = ImmutableSet.copyOf(clientPacketsRef);
-
- // Check sizes
- if (serverPackets.size() < MIN_SERVER_PACKETS)
- ProtocolLibrary.getErrorReporter().reportWarning(
- PacketRegistry.class, Report.newBuilder(REPORT_INSUFFICIENT_SERVER_PACKETS).messageParam(serverPackets.size())
- );
- if (clientPackets.size() < MIN_CLIENT_PACKETS)
- ProtocolLibrary.getErrorReporter().reportWarning(
- PacketRegistry.class, Report.newBuilder(REPORT_INSUFFICIENT_CLIENT_PACKETS).messageParam(clientPackets.size())
- );
-
- } else {
- throw new FieldAccessException("Cannot retrieve packet client/server sets.");
- }
-
- } catch (IllegalAccessException e) {
- throw new FieldAccessException("Cannot access field.", e);
- }
-
- } else {
- // Copy over again if it has changed
- if (serverPacketsRef != null && serverPacketsRef.size() != serverPackets.size())
- serverPackets = ImmutableSet.copyOf(serverPacketsRef);
- if (clientPacketsRef != null && clientPacketsRef.size() != clientPackets.size())
- clientPackets = ImmutableSet.copyOf(clientPacketsRef);
- }
- }
-
- /**
- * Retrieves the correct packet class from a given packet ID.
- * @param packetID - the packet ID.
- * @return The associated class.
- */
- public static Class getPacketClassFromID(int packetID) {
- return getPacketClassFromID(packetID, false);
- }
-
- /**
- * Retrieves the correct packet class from a given packet ID.
- * @param packetID - the packet ID.
- * @param forceVanilla - whether or not to look for vanilla classes, not injected classes.
- * @return The associated class.
- */
- public static Class getPacketClassFromID(int packetID, boolean forceVanilla) {
-
- Map lookup = forceVanilla ? previousValues : overwrittenPackets;
-
- // Optimized lookup
- if (lookup.containsKey(packetID)) {
- return removeEnhancer(lookup.get(packetID), forceVanilla);
- }
-
- // Will most likely not be used
- for (Map.Entry entry : getPacketToID().entrySet()) {
- if (Objects.equal(entry.getValue(), packetID)) {
- // Attempt to get the vanilla class here too
- if (!forceVanilla || MinecraftReflection.isMinecraftClass(entry.getKey()))
- return removeEnhancer(entry.getKey(), forceVanilla);
- }
- }
-
- throw new IllegalArgumentException("The packet ID " + packetID + " is not registered.");
- }
-
- /**
- * Retrieve the packet ID of a given packet.
- * @param packet - the type of packet to check.
- * @return The ID of the given packet.
- * @throws IllegalArgumentException If this is not a valid packet.
- */
- public static int getPacketID(Class> packet) {
- if (packet == null)
- throw new IllegalArgumentException("Packet type class cannot be NULL.");
- if (!MinecraftReflection.getPacketClass().isAssignableFrom(packet))
- throw new IllegalArgumentException("Type must be a packet.");
-
- // The registry contains both the overridden and original packets
- return getPacketToID().get(packet);
- }
-
- /**
- * 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;
- }
-}
+/*
+ * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
+ * Copyright (C) 2012 Kristian S. Stangeland
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program;
+ * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA
+ */
+
+package com.comphenix.protocol.injector.packet;
+
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
+
+import javax.annotation.Nullable;
+
+import net.sf.cglib.proxy.Factory;
+
+import com.comphenix.protocol.ProtocolLibrary;
+import com.comphenix.protocol.error.Report;
+import com.comphenix.protocol.error.ReportType;
+import com.comphenix.protocol.reflect.FieldAccessException;
+import com.comphenix.protocol.reflect.FieldUtils;
+import com.comphenix.protocol.reflect.FuzzyReflection;
+import com.comphenix.protocol.reflect.fuzzy.FuzzyClassContract;
+import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
+import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
+import com.comphenix.protocol.utility.MinecraftReflection;
+import com.comphenix.protocol.wrappers.TroveWrapper;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimap;
+
+/**
+ * Static packet registry in Minecraft.
+ *
+ * @author Kristian
+ */
+@SuppressWarnings("rawtypes")
+public class PacketRegistry {
+ public static final ReportType REPORT_CANNOT_CORRECT_TROVE_MAP = new ReportType("Unable to correct no entry value.");
+
+ public static final ReportType REPORT_INSUFFICIENT_SERVER_PACKETS = new ReportType("Too few server packets detected: %s");
+ public static final ReportType REPORT_INSUFFICIENT_CLIENT_PACKETS = new ReportType("Too few client packets detected: %s");
+
+ private static final int MIN_SERVER_PACKETS = 5;
+ private static final int MIN_CLIENT_PACKETS = 5;
+
+ // Fuzzy reflection
+ private static FuzzyReflection packetRegistry;
+
+ // The packet class to packet ID translator
+ private static Map packetToID;
+
+ // Packet IDs to classes, grouped by whether or not they're vanilla or custom defined
+ private static Multimap customIdToPacket;
+ private static Map vanillaIdToPacket;
+
+ // Whether or not certain packets are sent by the client or the server
+ private static ImmutableSet serverPackets;
+ private static ImmutableSet clientPackets;
+
+ // The underlying sets
+ private static Set serverPacketsRef;
+ private static Set clientPacketsRef;
+
+ // New proxy values
+ private static Map overwrittenPackets = new HashMap();
+
+ // Vanilla packets
+ private static Map previousValues = new HashMap();
+
+ @SuppressWarnings({ "unchecked" })
+ public static Map getPacketToID() {
+ // Initialize it, if we haven't already
+ if (packetToID == null) {
+ try {
+ Field packetsField = getPacketRegistry().getFieldByType("packetsField", Map.class);
+ packetToID = (Map) FieldUtils.readStaticField(packetsField, true);
+ } catch (IllegalArgumentException e) {
+ // Spigot 1.2.5 MCPC workaround
+ try {
+ packetToID = getSpigotWrapper();
+ } catch (Exception e2) {
+ // Very bad indeed
+ throw new IllegalArgumentException(e.getMessage() + "; Spigot workaround failed.", e2);
+ }
+
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException("Unable to retrieve the packetClassToIdMap", e);
+ }
+
+ // Create the inverse maps
+ customIdToPacket = InverseMaps.inverseMultimap(packetToID, new Predicate>() {
+ @Override
+ public boolean apply(@Nullable Entry entry) {
+ return !MinecraftReflection.isMinecraftClass(entry.getKey());
+ }
+ });
+
+ // And the vanilla pack - here we assume a unique ID to class mapping
+ vanillaIdToPacket = InverseMaps.inverseMap(packetToID, new Predicate>() {
+ @Override
+ public boolean apply(@Nullable Entry entry) {
+ return MinecraftReflection.isMinecraftClass(entry.getKey());
+ }
+ });
+ }
+ return packetToID;
+ }
+
+ private static Map getSpigotWrapper() throws IllegalAccessException {
+ // If it talks like a duck, etc.
+ // Perhaps it would be nice to have a proper duck typing library as well
+ FuzzyClassContract mapLike = FuzzyClassContract.newBuilder().
+ method(FuzzyMethodContract.newBuilder().
+ nameExact("size").returnTypeExact(int.class)).
+ method(FuzzyMethodContract.newBuilder().
+ nameExact("put").parameterCount(2)).
+ method(FuzzyMethodContract.newBuilder().
+ nameExact("get").parameterCount(1)).
+ build();
+
+ Field packetsField = getPacketRegistry().getField(
+ FuzzyFieldContract.newBuilder().typeMatches(mapLike).build());
+ Object troveMap = FieldUtils.readStaticField(packetsField, true);
+
+ // Check for stupid no_entry_values
+ try {
+ Field field = FieldUtils.getField(troveMap.getClass(), "no_entry_value", true);
+ Integer value = (Integer) FieldUtils.readField(field, troveMap, true);
+
+ if (value >= 0 && value < 256) {
+ // Someone forgot to set the no entry value. Let's help them.
+ FieldUtils.writeField(field, troveMap, -1);
+ }
+ } catch (IllegalArgumentException e) {
+ // Whatever
+ ProtocolLibrary.getErrorReporter().reportWarning(PacketRegistry.class,
+ Report.newBuilder(REPORT_CANNOT_CORRECT_TROVE_MAP).error(e));
+ }
+
+ // We'll assume this a Trove map
+ return TroveWrapper.getDecoratedMap(troveMap);
+ }
+
+ /**
+ * Retrieve the cached fuzzy reflection instance allowing access to the packet registry.
+ * @return Reflected packet registry.
+ */
+ private static FuzzyReflection getPacketRegistry() {
+ if (packetRegistry == null)
+ packetRegistry = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true);
+ return packetRegistry;
+ }
+
+ /**
+ * Retrieve the injected proxy classes handlig each packet ID.
+ * @return Injected classes.
+ */
+ public static Map getOverwrittenPackets() {
+ return overwrittenPackets;
+ }
+
+ /**
+ * Retrieve the vanilla classes handling each packet ID.
+ * @return Vanilla classes.
+ */
+ public static Map getPreviousPackets() {
+ return previousValues;
+ }
+
+ /**
+ * Retrieve every known and supported server packet.
+ * @return An immutable set of every known server packet.
+ * @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft.
+ */
+ public static Set getServerPackets() throws FieldAccessException {
+ initializeSets();
+
+ // Sanity check. This is impossible!
+ if (serverPackets != null && serverPackets.size() < MIN_SERVER_PACKETS)
+ throw new FieldAccessException("Server packet list is empty. Seems to be unsupported");
+ return serverPackets;
+ }
+
+ /**
+ * Retrieve every known and supported client packet.
+ * @return An immutable set of every known client packet.
+ * @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft.
+ */
+ public static Set getClientPackets() throws FieldAccessException {
+ initializeSets();
+
+ // As above
+ if (clientPackets != null && clientPackets.size() < MIN_CLIENT_PACKETS)
+ throw new FieldAccessException("Client packet list is empty. Seems to be unsupported");
+ return clientPackets;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static void initializeSets() throws FieldAccessException {
+ if (serverPacketsRef == null || clientPacketsRef == null) {
+ List sets = getPacketRegistry().getFieldListByType(Set.class);
+
+ try {
+ if (sets.size() > 1) {
+ serverPacketsRef = (Set) FieldUtils.readStaticField(sets.get(0), true);
+ clientPacketsRef = (Set) FieldUtils.readStaticField(sets.get(1), true);
+
+ // Impossible
+ if (serverPacketsRef == null || clientPacketsRef == null)
+ throw new FieldAccessException("Packet sets are in an illegal state.");
+
+ // NEVER allow callers to modify the underlying sets
+ serverPackets = ImmutableSet.copyOf(serverPacketsRef);
+ clientPackets = ImmutableSet.copyOf(clientPacketsRef);
+
+ // Check sizes
+ if (serverPackets.size() < MIN_SERVER_PACKETS)
+ ProtocolLibrary.getErrorReporter().reportWarning(
+ PacketRegistry.class, Report.newBuilder(REPORT_INSUFFICIENT_SERVER_PACKETS).messageParam(serverPackets.size())
+ );
+ if (clientPackets.size() < MIN_CLIENT_PACKETS)
+ ProtocolLibrary.getErrorReporter().reportWarning(
+ PacketRegistry.class, Report.newBuilder(REPORT_INSUFFICIENT_CLIENT_PACKETS).messageParam(clientPackets.size())
+ );
+
+ } else {
+ throw new FieldAccessException("Cannot retrieve packet client/server sets.");
+ }
+
+ } catch (IllegalAccessException e) {
+ throw new FieldAccessException("Cannot access field.", e);
+ }
+
+ } else {
+ // Copy over again if it has changed
+ if (serverPacketsRef != null && serverPacketsRef.size() != serverPackets.size())
+ serverPackets = ImmutableSet.copyOf(serverPacketsRef);
+ if (clientPacketsRef != null && clientPacketsRef.size() != clientPackets.size())
+ clientPackets = ImmutableSet.copyOf(clientPacketsRef);
+ }
+ }
+
+ /**
+ * Retrieves the correct packet class from a given packet ID.
+ * @param packetID - the packet ID.
+ * @return The associated class.
+ */
+ public static Class getPacketClassFromID(int packetID) {
+ return getPacketClassFromID(packetID, false);
+ }
+
+ /**
+ * Retrieves the correct packet class from a given packet ID.
+ * @param packetID - the packet ID.
+ * @param forceVanilla - whether or not to look for vanilla classes, not injected classes.
+ * @return The associated class.
+ */
+ public static Class getPacketClassFromID(int packetID, boolean forceVanilla) {
+ Map lookup = forceVanilla ? previousValues : overwrittenPackets;
+ Class> result = null;
+
+ // Optimized lookup
+ if (lookup.containsKey(packetID)) {
+ return removeEnhancer(lookup.get(packetID), forceVanilla);
+ }
+
+ // Refresh lookup tables
+ getPacketToID();
+
+ // See if we can look for non-vanilla classes
+ if (!forceVanilla) {
+ result = Iterables.getFirst(customIdToPacket.get(packetID), null);
+ }
+ if (result == null) {
+ result = vanillaIdToPacket.get(packetID);
+ }
+
+ // See if we got it
+ if (result != null)
+ return result;
+ else
+ throw new IllegalArgumentException("The packet ID " + packetID + " is not registered.");
+ }
+
+ /**
+ * Retrieve the packet ID of a given packet.
+ * @param packet - the type of packet to check.
+ * @return The ID of the given packet.
+ * @throws IllegalArgumentException If this is not a valid packet.
+ */
+ public static int getPacketID(Class> packet) {
+ if (packet == null)
+ throw new IllegalArgumentException("Packet type class cannot be NULL.");
+ if (!MinecraftReflection.getPacketClass().isAssignableFrom(packet))
+ throw new IllegalArgumentException("Type must be a packet.");
+
+ // The registry contains both the overridden and original packets
+ return getPacketToID().get(packet);
+ }
+
+ /**
+ * 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/packet/ProxyPacketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ProxyPacketInjector.java
index a6744fb0..800f264e 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ProxyPacketInjector.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ProxyPacketInjector.java
@@ -17,6 +17,7 @@
package com.comphenix.protocol.injector.packet;
+import java.io.DataInput;
import java.io.DataInputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
@@ -33,6 +34,8 @@ import net.sf.cglib.proxy.NoOp;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.error.ErrorReporter;
+import com.comphenix.protocol.error.Report;
+import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.ListenerInvoker;
@@ -50,6 +53,8 @@ import com.comphenix.protocol.utility.MinecraftReflection;
* @author Kristian
*/
class ProxyPacketInjector implements PacketInjector {
+ public static final ReportType REPORT_CANNOT_FIND_READ_PACKET_METHOD = new ReportType("Cannot find read packet method for ID %s.");
+
/**
* Represents a way to update the packet ID to class lookup table.
* @author Kristian
@@ -134,7 +139,7 @@ class ProxyPacketInjector implements PacketInjector {
*/
private static FuzzyMethodContract readPacket = FuzzyMethodContract.newBuilder().
returnTypeVoid().
- parameterExactType(DataInputStream.class).
+ parameterDerivedOf(DataInput.class).
parameterCount(1).
build();
@@ -154,6 +159,9 @@ class ProxyPacketInjector implements PacketInjector {
// Share callback filter
private CallbackFilter filter;
+
+ // Determine if the read packet method was found
+ private boolean readPacketIntercepted = false;
public ProxyPacketInjector(ClassLoader classLoader, ListenerInvoker manager,
PlayerInjectionHandler playerInjection, ErrorReporter reporter) throws FieldAccessException {
@@ -224,16 +232,20 @@ class ProxyPacketInjector implements PacketInjector {
}
if (filter == null) {
+ readPacketIntercepted = false;
+
filter = new CallbackFilter() {
@Override
public int accept(Method method) {
// Skip methods defined in Object
- if (method.getDeclaringClass().equals(Object.class))
+ if (method.getDeclaringClass().equals(Object.class)) {
return 0;
- else if (readPacket.isMatch(MethodInfo.fromMethod(method), null))
+ } else if (readPacket.isMatch(MethodInfo.fromMethod(method), null)) {
+ readPacketIntercepted = true;
return 1;
- else
+ } else {
return 2;
+ }
}
};
}
@@ -252,6 +264,12 @@ class ProxyPacketInjector implements PacketInjector {
// Add a static reference
Enhancer.registerStaticCallbacks(proxy, new Callback[] { NoOp.INSTANCE, modifierReadPacket, modifierRest });
+ // Check that we found the read method
+ if (!readPacketIntercepted) {
+ reporter.reportWarning(this,
+ Report.newBuilder(REPORT_CANNOT_FIND_READ_PACKET_METHOD).messageParam(packetID));
+ }
+
// Override values
previous.put(packetID, old);
registry.put(proxy, packetID);
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ReadPacketModifier.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ReadPacketModifier.java
index d88d8282..0c8ace3f 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ReadPacketModifier.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ReadPacketModifier.java
@@ -1,140 +1,140 @@
-/*
- * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
- * Copyright (C) 2012 Kristian S. Stangeland
- *
- * This program is free software; you can redistribute it and/or modify it under the terms of the
- * GNU General Public License as published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
- * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with this program;
- * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
- * 02111-1307 USA
- */
-
-package com.comphenix.protocol.injector.packet;
-
-import java.io.DataInputStream;
-import java.lang.reflect.Method;
-import java.util.Map;
-
-import com.comphenix.protocol.error.ErrorReporter;
-import com.comphenix.protocol.error.Report;
-import com.comphenix.protocol.error.ReportType;
-import com.comphenix.protocol.events.PacketContainer;
-import com.comphenix.protocol.events.PacketEvent;
-import com.google.common.collect.MapMaker;
-
-import net.sf.cglib.proxy.MethodInterceptor;
-import net.sf.cglib.proxy.MethodProxy;
-
-class ReadPacketModifier implements MethodInterceptor {
- public static final ReportType REPORT_CANNOT_HANDLE_CLIENT_PACKET = new ReportType("Cannot handle client packet.");
-
- // A cancel marker
- private static final Object CANCEL_MARKER = new Object();
-
- // Common for all packets of the same type
- private ProxyPacketInjector packetInjector;
- private int packetID;
-
- // Report errors
- private ErrorReporter reporter;
-
- // If this is a read packet data method
- private boolean isReadPacketDataMethod;
-
- // Whether or not a packet has been cancelled
- private static Map override = new MapMaker().weakKeys().makeMap();
-
- public ReadPacketModifier(int packetID, ProxyPacketInjector packetInjector, ErrorReporter reporter, boolean isReadPacketDataMethod) {
- this.packetID = packetID;
- this.packetInjector = packetInjector;
- this.reporter = reporter;
- this.isReadPacketDataMethod = isReadPacketDataMethod;
- }
-
- /**
- * Remove any packet overrides.
- * @param packet - the packet to rever
- */
- public static void removeOverride(Object packet) {
- override.remove(packet);
- }
-
- /**
- * Retrieve the packet that overrides the methods of the given packet.
- * @param packet - the given packet.
- * @return Overriden object.
- */
- public static Object getOverride(Object packet) {
- return override.get(packet);
- }
-
- /**
- * Determine if the given packet has been cancelled before.
- * @param packet - the packet to check.
- * @return TRUE if it has been cancelled, FALSE otherwise.
- */
- public static boolean hasCancelled(Object packet) {
- return getOverride(packet) == CANCEL_MARKER;
- }
-
- @Override
- public Object intercept(Object thisObj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
- // Atomic retrieval
- Object overridenObject = override.get(thisObj);
- Object returnValue = null;
-
- if (overridenObject != null) {
- // This packet has been cancelled
- if (overridenObject == CANCEL_MARKER) {
- // So, cancel all void methods
- if (method.getReturnType().equals(Void.TYPE))
- return null;
- else // Revert to normal for everything else
- overridenObject = thisObj;
- }
-
- returnValue = proxy.invokeSuper(overridenObject, args);
- } else {
- returnValue = proxy.invokeSuper(thisObj, args);
- }
-
- // Is this a readPacketData method?
- if (isReadPacketDataMethod) {
- try {
- // We need this in order to get the correct player
- DataInputStream input = (DataInputStream) args[0];
-
- // Let the people know
- PacketContainer container = new PacketContainer(packetID, thisObj);
- PacketEvent event = packetInjector.packetRecieved(container, input);
-
- // Handle override
- if (event != null) {
- Object result = event.getPacket().getHandle();
-
- if (event.isCancelled()) {
- override.put(thisObj, CANCEL_MARKER);
- } else if (!objectEquals(thisObj, result)) {
- override.put(thisObj, result);
- }
- }
- } catch (Throwable e) {
- // Minecraft cannot handle this error
- reporter.reportDetailed(this,
- Report.newBuilder(REPORT_CANNOT_HANDLE_CLIENT_PACKET).callerParam(args[0]).error(e)
- );
- }
- }
- return returnValue;
- }
-
- private boolean objectEquals(Object a, Object b) {
- return System.identityHashCode(a) != System.identityHashCode(b);
- }
-}
+/*
+ * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
+ * Copyright (C) 2012 Kristian S. Stangeland
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program;
+ * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA
+ */
+
+package com.comphenix.protocol.injector.packet;
+
+import java.io.DataInputStream;
+import java.lang.reflect.Method;
+import java.util.Map;
+
+import com.comphenix.protocol.error.ErrorReporter;
+import com.comphenix.protocol.error.Report;
+import com.comphenix.protocol.error.ReportType;
+import com.comphenix.protocol.events.PacketContainer;
+import com.comphenix.protocol.events.PacketEvent;
+import com.google.common.collect.MapMaker;
+
+import net.sf.cglib.proxy.MethodInterceptor;
+import net.sf.cglib.proxy.MethodProxy;
+
+class ReadPacketModifier implements MethodInterceptor {
+ public static final ReportType REPORT_CANNOT_HANDLE_CLIENT_PACKET = new ReportType("Cannot handle client packet.");
+
+ // A cancel marker
+ private static final Object CANCEL_MARKER = new Object();
+
+ // Common for all packets of the same type
+ private ProxyPacketInjector packetInjector;
+ private int packetID;
+
+ // Report errors
+ private ErrorReporter reporter;
+
+ // If this is a read packet data method
+ private boolean isReadPacketDataMethod;
+
+ // Whether or not a packet has been cancelled
+ private static Map override = new MapMaker().weakKeys().makeMap();
+
+ public ReadPacketModifier(int packetID, ProxyPacketInjector packetInjector, ErrorReporter reporter, boolean isReadPacketDataMethod) {
+ this.packetID = packetID;
+ this.packetInjector = packetInjector;
+ this.reporter = reporter;
+ this.isReadPacketDataMethod = isReadPacketDataMethod;
+ }
+
+ /**
+ * Remove any packet overrides.
+ * @param packet - the packet to rever
+ */
+ public static void removeOverride(Object packet) {
+ override.remove(packet);
+ }
+
+ /**
+ * Retrieve the packet that overrides the methods of the given packet.
+ * @param packet - the given packet.
+ * @return Overriden object.
+ */
+ public static Object getOverride(Object packet) {
+ return override.get(packet);
+ }
+
+ /**
+ * Determine if the given packet has been cancelled before.
+ * @param packet - the packet to check.
+ * @return TRUE if it has been cancelled, FALSE otherwise.
+ */
+ public static boolean hasCancelled(Object packet) {
+ return getOverride(packet) == CANCEL_MARKER;
+ }
+
+ @Override
+ public Object intercept(Object thisObj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
+ // Atomic retrieval
+ Object overridenObject = override.get(thisObj);
+ Object returnValue = null;
+
+ if (overridenObject != null) {
+ // This packet has been cancelled
+ if (overridenObject == CANCEL_MARKER) {
+ // So, cancel all void methods
+ if (method.getReturnType().equals(Void.TYPE))
+ return null;
+ else // Revert to normal for everything else
+ overridenObject = thisObj;
+ }
+
+ returnValue = proxy.invokeSuper(overridenObject, args);
+ } else {
+ returnValue = proxy.invokeSuper(thisObj, args);
+ }
+
+ // Is this a readPacketData method?
+ if (isReadPacketDataMethod) {
+ try {
+ // We need this in order to get the correct player
+ DataInputStream input = (DataInputStream) args[0];
+
+ // Let the people know
+ PacketContainer container = new PacketContainer(packetID, thisObj);
+ PacketEvent event = packetInjector.packetRecieved(container, input);
+
+ // Handle override
+ if (event != null) {
+ Object result = event.getPacket().getHandle();
+
+ if (event.isCancelled()) {
+ override.put(thisObj, CANCEL_MARKER);
+ } else if (!objectEquals(thisObj, result)) {
+ override.put(thisObj, result);
+ }
+ }
+ } catch (Throwable e) {
+ // Minecraft cannot handle this error
+ reporter.reportDetailed(this,
+ Report.newBuilder(REPORT_CANNOT_HANDLE_CLIENT_PACKET).callerParam(args[0]).error(e)
+ );
+ }
+ }
+ return returnValue;
+ }
+
+ private boolean objectEquals(Object a, Object b) {
+ return System.identityHashCode(a) != System.identityHashCode(b);
+ }
+}
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 3f09fc5a..7ae571c2 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
@@ -1,338 +1,438 @@
-/*
- * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
- * Copyright (C) 2012 Kristian S. Stangeland
- *
- * This program is free software; you can redistribute it and/or modify it under the terms of the
- * GNU General Public License as published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
- * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with this program;
- * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
- * 02111-1307 USA
- */
-
-package com.comphenix.protocol.injector.player;
-
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.List;
-
-import net.sf.cglib.proxy.Factory;
-
-import org.bukkit.Server;
-
-import com.comphenix.protocol.error.ErrorReporter;
-import com.comphenix.protocol.error.Report;
-import com.comphenix.protocol.error.ReportType;
-import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
-import com.comphenix.protocol.reflect.FieldUtils;
-import com.comphenix.protocol.reflect.FuzzyReflection;
-import com.comphenix.protocol.reflect.ObjectWriter;
-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.
- *
- * @author Kristian
- */
-class InjectedServerConnection {
- // A number of things can go wrong ...
- public static final ReportType REPORT_CANNOT_FIND_MINECRAFT_SERVER = new ReportType("Cannot extract minecraft server from Bukkit.");
- public static final ReportType REPORT_CANNOT_INJECT_SERVER_CONNECTION = new ReportType("Cannot inject into server connection. Bad things will happen.");
-
- public static final ReportType REPORT_CANNOT_FIND_LISTENER_THREAD = new ReportType("Cannot find listener thread in MinecraftServer.");
- public static final ReportType REPORT_CANNOT_READ_LISTENER_THREAD = new ReportType("Unable to read the listener thread.");
-
- public static final ReportType REPORT_CANNOT_FIND_SERVER_CONNECTION = new ReportType("Unable to retrieve server connection");
- public static final ReportType REPORT_UNEXPECTED_THREAD_COUNT = new ReportType("Unexpected number of threads in %s: %s");
- public static final ReportType REPORT_CANNOT_FIND_NET_HANDLER_THREAD = new ReportType("Unable to retrieve net handler thread.");
- public static final ReportType REPORT_INSUFFICENT_THREAD_COUNT = new ReportType("Unable to inject %s lists in %s.");
-
- public static final ReportType REPORT_CANNOT_COPY_OLD_TO_NEW = new ReportType("Cannot copy old %s to new.");
-
- private static Field listenerThreadField;
- private static Field minecraftServerField;
- private static Field listField;
- private static Field dedicatedThreadField;
-
- private static Method serverConnectionMethod;
-
- private List listFields;
- private List> replacedLists;
-
- // Used to inject net handlers
- private NetLoginInjector netLoginInjector;
-
- // Inject server connections
- private AbstractInputStreamLookup socketInjector;
-
- private Server server;
- private ErrorReporter reporter;
- private boolean hasAttempted;
- private boolean hasSuccess;
-
- private Object minecraftServer = null;
-
- public InjectedServerConnection(ErrorReporter reporter, AbstractInputStreamLookup socketInjector, Server server, NetLoginInjector netLoginInjector) {
- this.listFields = new ArrayList();
- this.replacedLists = new ArrayList>();
- this.reporter = reporter;
- this.server = server;
- this.socketInjector = socketInjector;
- this.netLoginInjector = netLoginInjector;
- }
-
- public void injectList() {
- // Only execute this method once
- if (!hasAttempted)
- hasAttempted = true;
- else
- return;
-
- if (minecraftServerField == null)
- minecraftServerField = FuzzyReflection.fromObject(server, true).
- getFieldByType("MinecraftServer", MinecraftReflection.getMinecraftServerClass());
-
- try {
- minecraftServer = FieldUtils.readField(minecraftServerField, server, true);
- } catch (IllegalAccessException e1) {
- reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_FIND_MINECRAFT_SERVER));
- return;
- }
-
- try {
- if (serverConnectionMethod == null)
- serverConnectionMethod = FuzzyReflection.fromClass(minecraftServerField.getType()).
- getMethodByParameters("getServerConnection",
- MinecraftReflection.getServerConnectionClass(), new Class[] {});
- // We're using Minecraft 1.3.1
- injectServerConnection();
-
- } catch (IllegalArgumentException e) {
-
- // Minecraft 1.2.5 or lower
- injectListenerThread();
-
- } catch (Exception e) {
- // Oh damn - inform the player
- reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_INJECT_SERVER_CONNECTION).error(e));
- }
- }
-
- private void injectListenerThread() {
- try {
- if (listenerThreadField == null)
- listenerThreadField = FuzzyReflection.fromObject(minecraftServer).
- getFieldByType("networkListenThread", MinecraftReflection.getNetworkListenThreadClass());
- } catch (RuntimeException e) {
- reporter.reportDetailed(this,
- Report.newBuilder(REPORT_CANNOT_FIND_LISTENER_THREAD).callerParam(minecraftServer).error(e)
- );
- return;
- }
-
- Object listenerThread = null;
-
- // Attempt to get the thread
- try {
- listenerThread = listenerThreadField.get(minecraftServer);
- } catch (Exception e) {
- reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_READ_LISTENER_THREAD).error(e));
- return;
- }
-
- // Inject the server socket too
- injectServerSocket(listenerThread);
-
- // Just inject every list field we can get
- injectEveryListField(listenerThread, 1);
- hasSuccess = true;
- }
-
- private void injectServerConnection() {
- Object serverConnection = null;
-
- // Careful - we might fail
- try {
- serverConnection = serverConnectionMethod.invoke(minecraftServer);
- } catch (Exception e) {
- reporter.reportDetailed(this,
- Report.newBuilder(REPORT_CANNOT_FIND_SERVER_CONNECTION).callerParam(minecraftServer).error(e)
- );
- return;
- }
-
- if (listField == null)
- listField = FuzzyReflection.fromClass(serverConnectionMethod.getReturnType(), true).
- getFieldByType("netServerHandlerList", List.class);
- if (dedicatedThreadField == null) {
- List matches = FuzzyReflection.fromObject(serverConnection, true).
- getFieldListByType(Thread.class);
-
- // Verify the field count
- if (matches.size() != 1)
- reporter.reportWarning(this,
- Report.newBuilder(REPORT_UNEXPECTED_THREAD_COUNT).messageParam(serverConnection.getClass(), matches.size())
- );
- else
- dedicatedThreadField = matches.get(0);
- }
-
- // Next, try to get the dedicated thread
- try {
- if (dedicatedThreadField != null) {
- Object dedicatedThread = FieldUtils.readField(dedicatedThreadField, serverConnection, true);
-
- // Inject server socket and NetServerHandlers.
- injectServerSocket(dedicatedThread);
- injectEveryListField(dedicatedThread, 1);
- }
- } catch (IllegalAccessException e) {
- reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_FIND_NET_HANDLER_THREAD).error(e));
- }
-
- injectIntoList(serverConnection, listField);
- hasSuccess = true;
- }
-
- private void injectServerSocket(Object container) {
- socketInjector.inject(container);
- }
-
- /**
- * Automatically inject into every List-compatible public or private field of the given object.
- * @param container - container object with the fields to inject.
- * @param minimum - the minimum number of fields we expect exists.
- */
- private void injectEveryListField(Object container, int minimum) {
- // Ok, great. Get every list field
- List lists = FuzzyReflection.fromObject(container, true).getFieldListByType(List.class);
-
- for (Field list : lists) {
- injectIntoList(container, list);
- }
-
- // Warn about unexpected errors
- if (lists.size() < minimum) {
- reporter.reportWarning(this, Report.newBuilder(REPORT_INSUFFICENT_THREAD_COUNT).messageParam(minimum, container.getClass()));
- }
- }
-
- @SuppressWarnings("unchecked")
- private void injectIntoList(Object instance, Field field) {
- VolatileField listFieldRef = new VolatileField(field, instance, true);
- List list = (List) listFieldRef.getValue();
-
- // Careful not to inject twice
- if (list instanceof ReplacedArrayList) {
- replacedLists.add((ReplacedArrayList) list);
- } else {
- ReplacedArrayList injectedList = createReplacement(list);
-
- replacedLists.add(injectedList);
- listFieldRef.setValue(injectedList);
- listFields.add(listFieldRef);
- }
- }
-
- // Hack to avoid the "moved to quickly" error
- private ReplacedArrayList createReplacement(List list) {
- return new ReplacedArrayList(list) {
- /**
- * Shut up Eclipse!
- */
- private static final long serialVersionUID = 2070481080950500367L;
-
- // Object writer we'll use
- private final ObjectWriter writer = new ObjectWriter();
-
- @Override
- protected void onReplacing(Object inserting, Object replacement) {
- // Is this a normal Minecraft object?
- if (!(inserting instanceof Factory)) {
- // If so, copy the content of the old element to the new
- try {
- writer.copyTo(inserting, replacement, inserting.getClass());
- } catch (Throwable e) {
- reporter.reportDetailed(InjectedServerConnection.this,
- Report.newBuilder(REPORT_CANNOT_COPY_OLD_TO_NEW).messageParam(inserting).callerParam(inserting, replacement).error(e)
- );
- }
- }
- }
-
- @Override
- protected void onInserting(Object inserting) {
- // Ready for some login handler injection?
- if (MinecraftReflection.isLoginHandler(inserting)) {
- Object replaced = netLoginInjector.onNetLoginCreated(inserting);
-
- // Only replace if it has changed
- if (inserting != replaced)
- addMapping(inserting, replaced, true);
- }
- }
-
- @Override
- protected void onRemoved(Object removing) {
- // Clean up?
- if (MinecraftReflection.isLoginHandler(removing)) {
- netLoginInjector.cleanup(removing);
- }
- }
- };
- }
-
- /**
- * Replace the server handler instance kept by the "keep alive" object.
- * @param oldHandler - old server handler.
- * @param newHandler - new, proxied server handler.
- */
- public void replaceServerHandler(Object oldHandler, Object newHandler) {
- if (!hasAttempted) {
- injectList();
- }
-
- if (hasSuccess) {
- for (ReplacedArrayList replacedList : replacedLists) {
- replacedList.addMapping(oldHandler, newHandler);
- }
- }
- }
-
- /**
- * Revert to the old vanilla server handler, if it has been replaced.
- * @param oldHandler - old vanilla server handler.
- */
- public void revertServerHandler(Object oldHandler) {
- if (hasSuccess) {
- for (ReplacedArrayList replacedList : replacedLists) {
- replacedList.removeMapping(oldHandler);
- }
- }
- }
-
- /**
- * Undoes everything.
- */
- public void cleanupAll() {
- if (replacedLists.size() > 0) {
- // Repair the underlying lists
- for (ReplacedArrayList replacedList : replacedLists) {
- replacedList.revertAll();
- }
- for (VolatileField field : listFields) {
- field.revertValue();
- }
-
- listFields.clear();
- replacedLists.clear();
- }
- }
-}
+/*
+ * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
+ * Copyright (C) 2012 Kristian S. Stangeland
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program;
+ * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA
+ */
+
+package com.comphenix.protocol.injector.player;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+import net.sf.cglib.proxy.Factory;
+
+import org.bukkit.Server;
+
+import com.comphenix.protocol.error.ErrorReporter;
+import com.comphenix.protocol.error.Report;
+import com.comphenix.protocol.error.ReportType;
+import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
+import com.comphenix.protocol.reflect.FieldAccessException;
+import com.comphenix.protocol.reflect.FieldUtils;
+import com.comphenix.protocol.reflect.FuzzyReflection;
+import com.comphenix.protocol.reflect.ObjectWriter;
+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.
+ *
+ * @author Kristian
+ */
+public class InjectedServerConnection {
+ // A number of things can go wrong ...
+ public static final ReportType REPORT_CANNOT_FIND_MINECRAFT_SERVER = new ReportType("Cannot extract minecraft server from Bukkit.");
+ public static final ReportType REPORT_CANNOT_INJECT_SERVER_CONNECTION = new ReportType("Cannot inject into server connection. Bad things will happen.");
+
+ public static final ReportType REPORT_CANNOT_FIND_LISTENER_THREAD = new ReportType("Cannot find listener thread in MinecraftServer.");
+ public static final ReportType REPORT_CANNOT_READ_LISTENER_THREAD = new ReportType("Unable to read the listener thread.");
+
+ public static final ReportType REPORT_CANNOT_FIND_SERVER_CONNECTION = new ReportType("Unable to retrieve server connection");
+ public static final ReportType REPORT_UNEXPECTED_THREAD_COUNT = new ReportType("Unexpected number of threads in %s: %s");
+ public static final ReportType REPORT_CANNOT_FIND_NET_HANDLER_THREAD = new ReportType("Unable to retrieve net handler thread.");
+ public static final ReportType REPORT_INSUFFICENT_THREAD_COUNT = new ReportType("Unable to inject %s lists in %s.");
+
+ public static final ReportType REPORT_CANNOT_COPY_OLD_TO_NEW = new ReportType("Cannot copy old %s to new.");
+
+ private static Field listenerThreadField;
+ private static Field minecraftServerField;
+ private static Field listField;
+ private static Field dedicatedThreadField;
+
+ private static Method serverConnectionMethod;
+
+ private List listFields;
+ private List> replacedLists;
+
+ // The current detected server socket
+ public enum ServerSocketType {
+ SERVER_CONNECTION,
+ LISTENER_THREAD,
+ }
+
+ // Used to inject net handlers
+ private NetLoginInjector netLoginInjector;
+
+ // Inject server connections
+ private AbstractInputStreamLookup socketInjector;
+
+ // Detected by the initializer
+ private ServerSocketType socketType;
+
+ private Server server;
+ private ErrorReporter reporter;
+ private boolean hasAttempted;
+ private boolean hasSuccess;
+
+ private Object minecraftServer = null;
+
+ public InjectedServerConnection(ErrorReporter reporter, AbstractInputStreamLookup socketInjector, Server server, NetLoginInjector netLoginInjector) {
+ this.listFields = new ArrayList();
+ this.replacedLists = new ArrayList>();
+ this.reporter = reporter;
+ this.server = server;
+ this.socketInjector = socketInjector;
+ this.netLoginInjector = netLoginInjector;
+ }
+
+ /**
+ * Retrieve the current server connection.
+ * @param reporter - error reproter.
+ * @param server - the current server.
+ * @return The current server connection, or NULL if it hasn't been initialized yet.
+ * @throws FieldAccessException Reflection error.
+ */
+ public static Object getServerConnection(ErrorReporter reporter, Server server) {
+ try {
+ // Now we are probably able to check for Netty
+ InjectedServerConnection inspector = new InjectedServerConnection(reporter, null, server, null);
+ return inspector.getServerConnection();
+ } catch (IllegalAccessException e) {
+ throw new FieldAccessException("Reflection error.", e);
+ } catch (IllegalArgumentException e) {
+ throw new FieldAccessException("Corrupt data.", e);
+ } catch (InvocationTargetException e) {
+ throw new FieldAccessException("Minecraft error.", e);
+ }
+ }
+
+ /**
+ * Initial reflective detective work. Will be automatically called by most methods in this class.
+ */
+ public void initialize() {
+ // Only execute this method once
+ if (!hasAttempted)
+ hasAttempted = true;
+ else
+ return;
+
+ if (minecraftServerField == null)
+ minecraftServerField = FuzzyReflection.fromObject(server, true).
+ getFieldByType("MinecraftServer", MinecraftReflection.getMinecraftServerClass());
+
+ try {
+ minecraftServer = FieldUtils.readField(minecraftServerField, server, true);
+ } catch (IllegalAccessException e1) {
+ reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_FIND_MINECRAFT_SERVER));
+ return;
+ }
+
+ try {
+ if (serverConnectionMethod == null)
+ serverConnectionMethod = FuzzyReflection.fromClass(minecraftServerField.getType()).
+ getMethodByParameters("getServerConnection",
+ MinecraftReflection.getServerConnectionClass(), new Class[] {});
+ // We're using Minecraft 1.3.1
+ socketType = ServerSocketType.SERVER_CONNECTION;
+
+ } catch (IllegalArgumentException e) {
+ // Minecraft 1.2.5 or lower
+ socketType = ServerSocketType.LISTENER_THREAD;
+
+ } catch (Exception e) {
+ // Oh damn - inform the player
+ reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_INJECT_SERVER_CONNECTION).error(e));
+ }
+ }
+
+ /**
+ * Retrieve the known server socket type.
+ *
+ * This depends on the version of CraftBukkit we are using.
+ * @return The server socket type.
+ */
+ public ServerSocketType getServerSocketType() {
+ return socketType;
+ }
+
+ /**
+ * Inject the connection interceptor into the correct server socket implementation.
+ */
+ public void injectList() {
+ initialize();
+
+ if (socketType == ServerSocketType.SERVER_CONNECTION) {
+ injectServerConnection();
+ } else if (socketType == ServerSocketType.LISTENER_THREAD) {
+ injectListenerThread();
+ } else {
+ // Damn it
+ throw new IllegalStateException("Unable to detected server connection.");
+ }
+ }
+
+ /**
+ * Retrieve the listener thread field.
+ */
+ private void initializeListenerField() {
+ if (listenerThreadField == null)
+ listenerThreadField = FuzzyReflection.fromObject(minecraftServer).
+ getFieldByType("networkListenThread", MinecraftReflection.getNetworkListenThreadClass());
+ }
+
+ /**
+ * Retrieve the listener thread object, or NULL the server isn't using this socket implementation.
+ * @return The listener thread, or NULL.
+ * @throws IllegalAccessException Cannot access field.
+ * @hrows RuntimeException Unexpected class structure - the field doesn't exist.
+ */
+ public Object getListenerThread() throws RuntimeException, IllegalAccessException {
+ initialize();
+
+ if (socketType == ServerSocketType.LISTENER_THREAD) {
+ initializeListenerField();
+ return listenerThreadField.get(minecraftServer);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Retrieve the server connection object, or NULL if the server isn't using it as the socket implementation.
+ * @return The socket connection, or NULL.
+ * @throws IllegalAccessException If the reflective operation failed.
+ * @throws IllegalArgumentException If the reflective operation failed.
+ * @throws InvocationTargetException If the reflective operation failed.
+ */
+ public Object getServerConnection() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+ initialize();
+
+ if (socketType == ServerSocketType.SERVER_CONNECTION)
+ return serverConnectionMethod.invoke(minecraftServer);
+ else
+ return null;
+ }
+
+ private void injectListenerThread() {
+ try {
+ initializeListenerField();
+ } catch (RuntimeException e) {
+ reporter.reportDetailed(this,
+ Report.newBuilder(REPORT_CANNOT_FIND_LISTENER_THREAD).callerParam(minecraftServer).error(e)
+ );
+ return;
+ }
+
+ Object listenerThread = null;
+
+ // Attempt to get the thread
+ try {
+ listenerThread = getListenerThread();
+ } catch (Exception e) {
+ reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_READ_LISTENER_THREAD).error(e));
+ return;
+ }
+
+ // Inject the server socket too
+ injectServerSocket(listenerThread);
+
+ // Just inject every list field we can get
+ injectEveryListField(listenerThread, 1);
+ hasSuccess = true;
+ }
+
+ private void injectServerConnection() {
+ Object serverConnection = null;
+
+ // Careful - we might fail
+ try {
+ serverConnection = getServerConnection();
+ } catch (Exception e) {
+ reporter.reportDetailed(this,
+ Report.newBuilder(REPORT_CANNOT_FIND_SERVER_CONNECTION).callerParam(minecraftServer).error(e)
+ );
+ return;
+ }
+
+ if (listField == null)
+ listField = FuzzyReflection.fromClass(serverConnectionMethod.getReturnType(), true).
+ getFieldByType("netServerHandlerList", List.class);
+ if (dedicatedThreadField == null) {
+ List matches = FuzzyReflection.fromObject(serverConnection, true).
+ getFieldListByType(Thread.class);
+
+ // Verify the field count
+ if (matches.size() != 1)
+ reporter.reportWarning(this,
+ Report.newBuilder(REPORT_UNEXPECTED_THREAD_COUNT).messageParam(serverConnection.getClass(), matches.size())
+ );
+ else
+ dedicatedThreadField = matches.get(0);
+ }
+
+ // Next, try to get the dedicated thread
+ try {
+ if (dedicatedThreadField != null) {
+ Object dedicatedThread = FieldUtils.readField(dedicatedThreadField, serverConnection, true);
+
+ // Inject server socket and NetServerHandlers.
+ injectServerSocket(dedicatedThread);
+ injectEveryListField(dedicatedThread, 1);
+ }
+ } catch (IllegalAccessException e) {
+ reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_FIND_NET_HANDLER_THREAD).error(e));
+ }
+
+ injectIntoList(serverConnection, listField);
+ hasSuccess = true;
+ }
+
+ private void injectServerSocket(Object container) {
+ socketInjector.inject(container);
+ }
+
+ /**
+ * Automatically inject into every List-compatible public or private field of the given object.
+ * @param container - container object with the fields to inject.
+ * @param minimum - the minimum number of fields we expect exists.
+ */
+ private void injectEveryListField(Object container, int minimum) {
+ // Ok, great. Get every list field
+ List lists = FuzzyReflection.fromObject(container, true).getFieldListByType(List.class);
+
+ for (Field list : lists) {
+ injectIntoList(container, list);
+ }
+
+ // Warn about unexpected errors
+ if (lists.size() < minimum) {
+ reporter.reportWarning(this, Report.newBuilder(REPORT_INSUFFICENT_THREAD_COUNT).messageParam(minimum, container.getClass()));
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private void injectIntoList(Object instance, Field field) {
+ VolatileField listFieldRef = new VolatileField(field, instance, true);
+ List list = (List) listFieldRef.getValue();
+
+ // Careful not to inject twice
+ if (list instanceof ReplacedArrayList) {
+ replacedLists.add((ReplacedArrayList) list);
+ } else {
+ ReplacedArrayList injectedList = createReplacement(list);
+
+ replacedLists.add(injectedList);
+ listFieldRef.setValue(injectedList);
+ listFields.add(listFieldRef);
+ }
+ }
+
+ // Hack to avoid the "moved to quickly" error
+ private ReplacedArrayList createReplacement(List list) {
+ return new ReplacedArrayList(list) {
+ /**
+ * Shut up Eclipse!
+ */
+ private static final long serialVersionUID = 2070481080950500367L;
+
+ // Object writer we'll use
+ private final ObjectWriter writer = new ObjectWriter();
+
+ @Override
+ protected void onReplacing(Object inserting, Object replacement) {
+ // Is this a normal Minecraft object?
+ if (!(inserting instanceof Factory)) {
+ // If so, copy the content of the old element to the new
+ try {
+ writer.copyTo(inserting, replacement, inserting.getClass());
+ } catch (Throwable e) {
+ reporter.reportDetailed(InjectedServerConnection.this,
+ Report.newBuilder(REPORT_CANNOT_COPY_OLD_TO_NEW).messageParam(inserting).callerParam(inserting, replacement).error(e)
+ );
+ }
+ }
+ }
+
+ @Override
+ protected void onInserting(Object inserting) {
+ // Ready for some login handler injection?
+ if (MinecraftReflection.isLoginHandler(inserting)) {
+ Object replaced = netLoginInjector.onNetLoginCreated(inserting);
+
+ // Only replace if it has changed
+ if (inserting != replaced)
+ addMapping(inserting, replaced, true);
+ }
+ }
+
+ @Override
+ protected void onRemoved(Object removing) {
+ // Clean up?
+ if (MinecraftReflection.isLoginHandler(removing)) {
+ netLoginInjector.cleanup(removing);
+ }
+ }
+ };
+ }
+
+ /**
+ * Replace the server handler instance kept by the "keep alive" object.
+ * @param oldHandler - old server handler.
+ * @param newHandler - new, proxied server handler.
+ */
+ public void replaceServerHandler(Object oldHandler, Object newHandler) {
+ if (!hasAttempted) {
+ injectList();
+ }
+
+ if (hasSuccess) {
+ for (ReplacedArrayList replacedList : replacedLists) {
+ replacedList.addMapping(oldHandler, newHandler);
+ }
+ }
+ }
+
+ /**
+ * Revert to the old vanilla server handler, if it has been replaced.
+ * @param oldHandler - old vanilla server handler.
+ */
+ public void revertServerHandler(Object oldHandler) {
+ if (hasSuccess) {
+ for (ReplacedArrayList replacedList : replacedLists) {
+ replacedList.removeMapping(oldHandler);
+ }
+ }
+ }
+
+ /**
+ * Undoes everything.
+ */
+ public void cleanupAll() {
+ if (replacedLists.size() > 0) {
+ // Repair the underlying lists
+ for (ReplacedArrayList replacedList : replacedLists) {
+ replacedList.revertAll();
+ }
+ for (VolatileField field : listFields) {
+ field.revertValue();
+ }
+
+ listFields.clear();
+ replacedLists.clear();
+ }
+ }
+}
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java
index 129a4bc1..5e008f79 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java
@@ -1,154 +1,154 @@
-/*
- * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
- * Copyright (C) 2012 Kristian S. Stangeland
- *
- * This program is free software; you can redistribute it and/or modify it under the terms of the
- * GNU General Public License as published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
- * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with this program;
- * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
- * 02111-1307 USA
- */
-
-package com.comphenix.protocol.injector.player;
-
-import java.util.concurrent.ConcurrentMap;
-
-import org.bukkit.Server;
-import org.bukkit.entity.Player;
-
-import com.comphenix.protocol.error.ErrorReporter;
-import com.comphenix.protocol.error.Report;
-import com.comphenix.protocol.error.ReportType;
-import com.comphenix.protocol.injector.GamePhase;
-import com.comphenix.protocol.injector.player.PlayerInjectionHandler.ConflictStrategy;
-import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
-import com.comphenix.protocol.utility.MinecraftReflection;
-import com.google.common.collect.Maps;
-
-/**
- * Injects every NetLoginHandler created by the server.
- *
- * @author Kristian
- */
-class NetLoginInjector {
- public static final ReportType REPORT_CANNOT_HOOK_LOGIN_HANDLER = new ReportType("Unable to hook %s.");
- public static final ReportType REPORT_CANNOT_CLEANUP_LOGIN_HANDLER = new ReportType("Cannot cleanup %s.");
-
- private ConcurrentMap injectedLogins = Maps.newConcurrentMap();
-
- // Handles every hook
- private ProxyPlayerInjectionHandler injectionHandler;
-
- // Create temporary players
- private TemporaryPlayerFactory playerFactory = new TemporaryPlayerFactory();
-
- // The current error reporter
- private ErrorReporter reporter;
- private Server server;
-
- public NetLoginInjector(ErrorReporter reporter, Server server, ProxyPlayerInjectionHandler injectionHandler) {
- this.reporter = reporter;
- this.server = server;
- this.injectionHandler = injectionHandler;
- }
-
- /**
- * Invoked when a NetLoginHandler has been created.
- * @param inserting - the new NetLoginHandler.
- * @return An injected NetLoginHandler, or the original object.
- */
- public Object onNetLoginCreated(Object inserting) {
- try {
- // Make sure we actually need to inject during this phase
- if (!injectionHandler.isInjectionNecessary(GamePhase.LOGIN))
- return inserting;
-
- Player temporary = playerFactory.createTemporaryPlayer(server);
- // Note that we bail out if there's an existing player injector
- PlayerInjector injector = injectionHandler.injectPlayer(
- temporary, inserting, ConflictStrategy.BAIL_OUT, GamePhase.LOGIN);
-
- if (injector != null) {
- // Update injector as well
- TemporaryPlayerFactory.setInjectorInPlayer(temporary, injector);
- injector.updateOnLogin = true;
-
- // Save the login
- injectedLogins.putIfAbsent(inserting, injector);
- }
-
- // NetServerInjector can never work (currently), so we don't need to replace the NetLoginHandler
- return inserting;
-
- } catch (Throwable e) {
- // Minecraft can't handle this, so we'll deal with it here
- reporter.reportDetailed(this,
- Report.newBuilder(REPORT_CANNOT_HOOK_LOGIN_HANDLER).
- messageParam(MinecraftReflection.getNetLoginHandlerName()).
- callerParam(inserting, injectionHandler).
- error(e)
- );
- return inserting;
- }
- }
-
- /**
- * Invoked when a NetLoginHandler should be reverted.
- * @param inserting - the original NetLoginHandler.
- * @return An injected NetLoginHandler, or the original object.
- */
- public synchronized void cleanup(Object removing) {
- PlayerInjector injected = injectedLogins.get(removing);
-
- if (injected != null) {
- try {
- PlayerInjector newInjector = null;
- Player player = injected.getPlayer();
-
- // Clean up list
- injectedLogins.remove(removing);
-
- // No need to clean up twice
- if (injected.isClean())
- return;
-
- // Hack to clean up other references
- newInjector = injectionHandler.getInjectorByNetworkHandler(injected.getNetworkManager());
- injectionHandler.uninjectPlayer(player);
-
- // Update NetworkManager
- if (newInjector != null) {
- if (injected instanceof NetworkObjectInjector) {
- newInjector.setNetworkManager(injected.getNetworkManager(), true);
- }
- }
-
- } catch (Throwable e) {
- // Don't leak this to Minecraft
- reporter.reportDetailed(this,
- Report.newBuilder(REPORT_CANNOT_CLEANUP_LOGIN_HANDLER).
- messageParam(MinecraftReflection.getNetLoginHandlerName()).
- callerParam(removing).
- error(e)
- );
- }
- }
- }
-
- /**
- * Remove all injected hooks.
- */
- public void cleanupAll() {
- for (PlayerInjector injector : injectedLogins.values()) {
- injector.cleanupAll();
- }
-
- injectedLogins.clear();
- }
-}
+/*
+ * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
+ * Copyright (C) 2012 Kristian S. Stangeland
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program;
+ * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA
+ */
+
+package com.comphenix.protocol.injector.player;
+
+import java.util.concurrent.ConcurrentMap;
+
+import org.bukkit.Server;
+import org.bukkit.entity.Player;
+
+import com.comphenix.protocol.error.ErrorReporter;
+import com.comphenix.protocol.error.Report;
+import com.comphenix.protocol.error.ReportType;
+import com.comphenix.protocol.injector.GamePhase;
+import com.comphenix.protocol.injector.player.PlayerInjectionHandler.ConflictStrategy;
+import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
+import com.comphenix.protocol.utility.MinecraftReflection;
+import com.google.common.collect.Maps;
+
+/**
+ * Injects every NetLoginHandler created by the server.
+ *
+ * @author Kristian
+ */
+class NetLoginInjector {
+ public static final ReportType REPORT_CANNOT_HOOK_LOGIN_HANDLER = new ReportType("Unable to hook %s.");
+ public static final ReportType REPORT_CANNOT_CLEANUP_LOGIN_HANDLER = new ReportType("Cannot cleanup %s.");
+
+ private ConcurrentMap injectedLogins = Maps.newConcurrentMap();
+
+ // Handles every hook
+ private ProxyPlayerInjectionHandler injectionHandler;
+
+ // Create temporary players
+ private TemporaryPlayerFactory playerFactory = new TemporaryPlayerFactory();
+
+ // The current error reporter
+ private ErrorReporter reporter;
+ private Server server;
+
+ public NetLoginInjector(ErrorReporter reporter, Server server, ProxyPlayerInjectionHandler injectionHandler) {
+ this.reporter = reporter;
+ this.server = server;
+ this.injectionHandler = injectionHandler;
+ }
+
+ /**
+ * Invoked when a NetLoginHandler has been created.
+ * @param inserting - the new NetLoginHandler.
+ * @return An injected NetLoginHandler, or the original object.
+ */
+ public Object onNetLoginCreated(Object inserting) {
+ try {
+ // Make sure we actually need to inject during this phase
+ if (!injectionHandler.isInjectionNecessary(GamePhase.LOGIN))
+ return inserting;
+
+ Player temporary = playerFactory.createTemporaryPlayer(server);
+ // Note that we bail out if there's an existing player injector
+ PlayerInjector injector = injectionHandler.injectPlayer(
+ temporary, inserting, ConflictStrategy.BAIL_OUT, GamePhase.LOGIN);
+
+ if (injector != null) {
+ // Update injector as well
+ TemporaryPlayerFactory.setInjectorInPlayer(temporary, injector);
+ injector.updateOnLogin = true;
+
+ // Save the login
+ injectedLogins.putIfAbsent(inserting, injector);
+ }
+
+ // NetServerInjector can never work (currently), so we don't need to replace the NetLoginHandler
+ return inserting;
+
+ } catch (Throwable e) {
+ // Minecraft can't handle this, so we'll deal with it here
+ reporter.reportDetailed(this,
+ Report.newBuilder(REPORT_CANNOT_HOOK_LOGIN_HANDLER).
+ messageParam(MinecraftReflection.getNetLoginHandlerName()).
+ callerParam(inserting, injectionHandler).
+ error(e)
+ );
+ return inserting;
+ }
+ }
+
+ /**
+ * Invoked when a NetLoginHandler should be reverted.
+ * @param inserting - the original NetLoginHandler.
+ * @return An injected NetLoginHandler, or the original object.
+ */
+ public synchronized void cleanup(Object removing) {
+ PlayerInjector injected = injectedLogins.get(removing);
+
+ if (injected != null) {
+ try {
+ PlayerInjector newInjector = null;
+ Player player = injected.getPlayer();
+
+ // Clean up list
+ injectedLogins.remove(removing);
+
+ // No need to clean up twice
+ if (injected.isClean())
+ return;
+
+ // Hack to clean up other references
+ newInjector = injectionHandler.getInjectorByNetworkHandler(injected.getNetworkManager());
+ injectionHandler.uninjectPlayer(player);
+
+ // Update NetworkManager
+ if (newInjector != null) {
+ if (injected instanceof NetworkObjectInjector) {
+ newInjector.setNetworkManager(injected.getNetworkManager(), true);
+ }
+ }
+
+ } catch (Throwable e) {
+ // Don't leak this to Minecraft
+ reporter.reportDetailed(this,
+ Report.newBuilder(REPORT_CANNOT_CLEANUP_LOGIN_HANDLER).
+ messageParam(MinecraftReflection.getNetLoginHandlerName()).
+ callerParam(removing).
+ error(e)
+ );
+ }
+ }
+ }
+
+ /**
+ * Remove all injected hooks.
+ */
+ public void cleanupAll() {
+ for (PlayerInjector injector : injectedLogins.values()) {
+ injector.cleanupAll();
+ }
+
+ injectedLogins.clear();
+ }
+}
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 85669c69..bb8f4a2b 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
@@ -161,9 +161,4 @@ public interface PlayerInjectionHandler {
* Close any lingering proxy injections.
*/
public abstract void close();
-
- /**
- * Perform any action that must be delayed until the world(s) has loaded.
- */
- public abstract void postWorldLoaded();
}
\ No newline at end of file
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 bf5a0cd9..79bb4c00 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
@@ -284,7 +284,7 @@ public abstract class PlayerInjector implements SocketInjector {
/**
* Retrieve the associated remote address of a player.
- * @return The associated remote address..
+ * @return The associated remote address.
* @throws IllegalAccessException If we're unable to read the socket address field.
*/
@Override
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java
index 1f3fa663..584cb8af 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java
@@ -21,6 +21,7 @@ import java.io.DataInputStream;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress;
+import java.net.Socket;
import java.net.SocketAddress;
import java.util.Map;
import java.util.Set;
@@ -51,6 +52,7 @@ import com.comphenix.protocol.injector.server.SocketInjector;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
+import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
@@ -142,14 +144,6 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
this.serverInjection = new InjectedServerConnection(reporter, inputStreamLookup, server, netLoginInjector);
serverInjection.injectList();
}
-
- @Override
- public void postWorldLoaded() {
- // This will actually create a socket and a seperate thread ...
- if (inputStreamLookup != null) {
- inputStreamLookup.postWorldLoaded();
- }
- }
/**
* Retrieves how the server packets are read.
@@ -350,6 +344,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
return null;
SocketInjector previous = inputStreamLookup.peekSocketInjector(address);
+ Socket socket = injector.getSocket();
// Close any previously associated hooks before we proceed
if (previous != null && !(player instanceof Factory)) {
@@ -363,8 +358,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
}
injector.injectManager();
- // Save injector
- inputStreamLookup.setSocketInjector(address, injector);
+ saveAddressLookup(address, socket, injector);
break;
}
@@ -413,6 +407,17 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
return injector;
}
+ private void saveAddressLookup(SocketAddress address, Socket socket, SocketInjector injector) {
+ SocketAddress socketAddress = socket != null ? socket.getRemoteSocketAddress() : null;
+
+ if (socketAddress != null && !Objects.equal(socketAddress, address)) {
+ // Save this version as well
+ inputStreamLookup.setSocketInjector(socketAddress, injector);
+ }
+ // Save injector
+ inputStreamLookup.setSocketInjector(address, injector);
+ }
+
private void cleanupHook(PlayerInjector injector) {
// Clean up as much as possible
try {
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/AbstractInputStreamLookup.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/AbstractInputStreamLookup.java
index 5fde4cdc..0af83561 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/AbstractInputStreamLookup.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/AbstractInputStreamLookup.java
@@ -1,87 +1,82 @@
-package com.comphenix.protocol.injector.server;
-
-import java.io.InputStream;
-import java.net.Socket;
-import java.net.SocketAddress;
-import org.bukkit.Server;
-import org.bukkit.entity.Player;
-
-import com.comphenix.protocol.error.ErrorReporter;
-
-public abstract class AbstractInputStreamLookup {
- // Error reporter
- protected final ErrorReporter reporter;
-
- // Reference to the server itself
- protected final Server server;
-
- protected AbstractInputStreamLookup(ErrorReporter reporter, Server server) {
- this.reporter = reporter;
- this.server = server;
- }
-
- /**
- * Inject the given server thread or dedicated connection.
- * @param container - class that contains a ServerSocket field.
- */
- public abstract void inject(Object container);
-
- /**
- * Invoked when the world has loaded.
- */
- public abstract void postWorldLoaded();
-
- /**
- * Retrieve the associated socket injector for a player.
- * @param input - the indentifying filtered input stream.
- * @return The socket injector we have associated with this player.
- */
- public abstract SocketInjector waitSocketInjector(InputStream input);
-
- /**
- * Retrieve an injector by its socket.
- * @param socket - the socket.
- * @return The socket injector.
- */
- public abstract SocketInjector waitSocketInjector(Socket socket);
-
- /**
- * Retrieve a injector by its address.
- * @param address - the address of the socket.
- * @return The socket injector, or NULL if not found.
- */
- public abstract SocketInjector waitSocketInjector(SocketAddress address);
-
- /**
- * Attempt to get a socket injector without blocking the thread.
- * @param address - the address to lookup.
- * @return The socket injector, or NULL if not found.
- */
- public abstract SocketInjector peekSocketInjector(SocketAddress address);
-
- /**
- * Associate a given socket address to the provided socket injector.
- * @param address - the socket address to associate.
- * @param injector - the injector.
- */
- public abstract void setSocketInjector(SocketAddress address, SocketInjector injector);
-
- /**
- * If a player can hold a reference to its parent injector, this method will update that reference.
- * @param previous - the previous injector.
- * @param current - the new injector.
- */
- protected void onPreviousSocketOverwritten(SocketInjector previous, SocketInjector current) {
- Player player = previous.getPlayer();
-
- // Default implementation
- if (player instanceof InjectorContainer) {
- TemporaryPlayerFactory.setInjectorInPlayer(player, current);
- }
- }
-
- /**
- * Invoked when the injection should be undone.
- */
- public abstract void cleanupAll();
+package com.comphenix.protocol.injector.server;
+
+import java.io.InputStream;
+import java.net.Socket;
+import java.net.SocketAddress;
+import org.bukkit.Server;
+import org.bukkit.entity.Player;
+
+import com.comphenix.protocol.error.ErrorReporter;
+
+public abstract class AbstractInputStreamLookup {
+ // Error reporter
+ protected final ErrorReporter reporter;
+
+ // Reference to the server itself
+ protected final Server server;
+
+ protected AbstractInputStreamLookup(ErrorReporter reporter, Server server) {
+ this.reporter = reporter;
+ this.server = server;
+ }
+
+ /**
+ * Inject the given server thread or dedicated connection.
+ * @param container - class that contains a ServerSocket field.
+ */
+ public abstract void inject(Object container);
+
+ /**
+ * Retrieve the associated socket injector for a player.
+ * @param input - the indentifying filtered input stream.
+ * @return The socket injector we have associated with this player.
+ */
+ public abstract SocketInjector waitSocketInjector(InputStream input);
+
+ /**
+ * Retrieve an injector by its socket.
+ * @param socket - the socket.
+ * @return The socket injector.
+ */
+ public abstract SocketInjector waitSocketInjector(Socket socket);
+
+ /**
+ * Retrieve a injector by its address.
+ * @param address - the address of the socket.
+ * @return The socket injector, or NULL if not found.
+ */
+ public abstract SocketInjector waitSocketInjector(SocketAddress address);
+
+ /**
+ * Attempt to get a socket injector without blocking the thread.
+ * @param address - the address to lookup.
+ * @return The socket injector, or NULL if not found.
+ */
+ public abstract SocketInjector peekSocketInjector(SocketAddress address);
+
+ /**
+ * Associate a given socket address to the provided socket injector.
+ * @param address - the socket address to associate.
+ * @param injector - the injector.
+ */
+ public abstract void setSocketInjector(SocketAddress address, SocketInjector injector);
+
+ /**
+ * If a player can hold a reference to its parent injector, this method will update that reference.
+ * @param previous - the previous injector.
+ * @param current - the new injector.
+ */
+ protected void onPreviousSocketOverwritten(SocketInjector previous, SocketInjector current) {
+ Player player = previous.getPlayer();
+
+ // Default implementation
+ if (player instanceof InjectorContainer) {
+ TemporaryPlayerFactory.setInjectorInPlayer(player, current);
+ }
+ }
+
+ /**
+ * Invoked when the injection should be undone.
+ */
+ public abstract void cleanupAll();
}
\ No newline at end of file
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/BukkitSocketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/BukkitSocketInjector.java
index 8e72db01..e4f55330 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/BukkitSocketInjector.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/BukkitSocketInjector.java
@@ -10,36 +10,14 @@ import java.util.List;
import org.bukkit.entity.Player;
public class BukkitSocketInjector implements SocketInjector {
- /**
- * Represents a single send packet command.
- * @author Kristian
- */
- static class SendPacketCommand {
- private final Object packet;
- private final boolean filtered;
-
- public SendPacketCommand(Object packet, boolean filtered) {
- this.packet = packet;
- this.filtered = filtered;
- }
-
- public Object getPacket() {
- return packet;
- }
-
- public boolean isFiltered() {
- return filtered;
- }
- }
-
private Player player;
// Queue of server packets
- private List syncronizedQueue = Collections.synchronizedList(new ArrayList());
+ private List syncronizedQueue = Collections.synchronizedList(new ArrayList());
/**
* Represents a temporary socket injector.
- * @param temporaryPlayer -
+ * @param temporaryPlayer - a temporary player.
*/
public BukkitSocketInjector(Player player) {
if (player == null)
@@ -65,7 +43,7 @@ public class BukkitSocketInjector implements SocketInjector {
@Override
public void sendServerPacket(Object packet, boolean filtered)
throws InvocationTargetException {
- SendPacketCommand command = new SendPacketCommand(packet, filtered);
+ QueuedSendPacket command = new QueuedSendPacket(packet, filtered);
// Queue until we can find something better
syncronizedQueue.add(command);
@@ -86,7 +64,7 @@ public class BukkitSocketInjector implements SocketInjector {
// Transmit all queued packets to a different injector.
try {
synchronized(syncronizedQueue) {
- for (SendPacketCommand command : syncronizedQueue) {
+ for (QueuedSendPacket command : syncronizedQueue) {
delegate.sendServerPacket(command.getPacket(), command.isFiltered());
}
syncronizedQueue.clear();
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamReflectLookup.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamReflectLookup.java
index 37f5d6b6..564687d0 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamReflectLookup.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamReflectLookup.java
@@ -1,191 +1,186 @@
-package com.comphenix.protocol.injector.server;
-
-import java.io.FilterInputStream;
-import java.io.InputStream;
-import java.lang.reflect.Field;
-import java.net.Socket;
-import java.net.SocketAddress;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.TimeUnit;
-
-import org.bukkit.Server;
-
-import com.comphenix.protocol.concurrency.BlockingHashMap;
-import com.comphenix.protocol.error.ErrorReporter;
-import com.comphenix.protocol.reflect.FieldAccessException;
-import com.comphenix.protocol.reflect.FieldUtils;
-import com.comphenix.protocol.reflect.FuzzyReflection;
-import com.google.common.collect.MapMaker;
-
-class InputStreamReflectLookup extends AbstractInputStreamLookup {
- // Used to access the inner input stream of a filtered input stream
- private static Field filteredInputField;
-
- // The default lookup timeout
- private static final long DEFAULT_TIMEOUT = 2000; // ms
-
- // Using weak keys and values ensures that we will not hold up garbage collection
- protected BlockingHashMap addressLookup = new BlockingHashMap();
- protected ConcurrentMap inputLookup = new MapMaker().weakValues().makeMap();
-
- // The timeout
- private final long injectorTimeout;
-
- public InputStreamReflectLookup(ErrorReporter reporter, Server server) {
- this(reporter, server, DEFAULT_TIMEOUT);
- }
-
- /**
- * Initialize a reflect lookup with a given default injector timeout.
- *
- * This timeout defines the maximum amount of time to wait until an injector has been discovered.
- * @param reporter - the error reporter.
- * @param server - the current Bukkit server.
- * @param injectorTimeout - the injector timeout.
- */
- public InputStreamReflectLookup(ErrorReporter reporter, Server server, long injectorTimeout) {
- super(reporter, server);
- this.injectorTimeout = injectorTimeout;
- }
-
- @Override
- public void inject(Object container) {
- // Do nothing
- }
-
- @Override
- public void postWorldLoaded() {
- // Nothing again
- }
-
- @Override
- public SocketInjector peekSocketInjector(SocketAddress address) {
- try {
- return addressLookup.get(address, 0, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
- // Whatever
- return null;
- }
- }
-
- @Override
- public SocketInjector waitSocketInjector(SocketAddress address) {
- try {
- // Note that we actually SWALLOW interrupts here - this is because Minecraft uses interrupts to
- // periodically wake up waiting readers and writers. We have to wait for the dedicated server thread
- // to catch up, so we'll swallow these interrupts.
- //
- // TODO: Consider if we should raise the thread priority of the dedicated server listener thread.
- return addressLookup.get(address, injectorTimeout, TimeUnit.MILLISECONDS, true);
- } catch (InterruptedException e) {
- // This cannot be!
- throw new IllegalStateException("Impossible exception occured!", e);
- }
- }
-
- @Override
- public SocketInjector waitSocketInjector(Socket socket) {
- return waitSocketInjector(socket.getRemoteSocketAddress());
- }
-
- @Override
- public SocketInjector waitSocketInjector(InputStream input) {
- try {
- SocketAddress address = waitSocketAddress(input);
-
- // Guard against NPE
- if (address != null)
- return waitSocketInjector(address);
- else
- return null;
- } catch (IllegalAccessException e) {
- throw new FieldAccessException("Cannot find or access socket field for " + input, e);
- }
- }
-
- /**
- * Use reflection to get the underlying socket address from an input stream.
- * @param stream - the socket stream to lookup.
- * @return The underlying socket address, or NULL if not found.
- * @throws IllegalAccessException Unable to access socket field.
- */
- private SocketAddress waitSocketAddress(InputStream stream) throws IllegalAccessException {
- // Extra check, just in case
- if (stream instanceof FilterInputStream)
- return waitSocketAddress(getInputStream((FilterInputStream) stream));
-
- SocketAddress result = inputLookup.get(stream);
-
- if (result == null) {
- Socket socket = lookupSocket(stream);
-
- // Save it
- result = socket.getRemoteSocketAddress();
- inputLookup.put(stream, result);
- }
- return result;
- }
-
- /**
- * Retrieve the underlying input stream that is associated with a given filter input stream.
- * @param filtered - the filter input stream.
- * @return The underlying input stream that is being filtered.
- * @throws FieldAccessException Unable to access input stream.
- */
- protected static InputStream getInputStream(FilterInputStream filtered) {
- if (filteredInputField == null)
- filteredInputField = FuzzyReflection.fromClass(FilterInputStream.class, true).
- getFieldByType("in", InputStream.class);
-
- InputStream current = filtered;
-
- try {
- // Iterate until we find the real input stream
- while (current instanceof FilterInputStream) {
- current = (InputStream) FieldUtils.readField(filteredInputField, current, true);
- }
- return current;
- } catch (IllegalAccessException e) {
- throw new FieldAccessException("Cannot access filtered input field.", e);
- }
- }
-
- @Override
- public void setSocketInjector(SocketAddress address, SocketInjector injector) {
- if (address == null)
- throw new IllegalArgumentException("address cannot be NULL");
- if (injector == null)
- throw new IllegalArgumentException("injector cannot be NULL.");
-
- SocketInjector previous = addressLookup.put(address, injector);
-
- // Any previous temporary players will also be associated
- if (previous != null) {
- // Update the reference to any previous injector
- onPreviousSocketOverwritten(previous, injector);
- }
- }
-
- @Override
- public void cleanupAll() {
- // Do nothing
- }
-
- /**
- * Lookup the underlying socket of a stream through reflection.
- * @param stream - the socket stream.
- * @return The underlying socket.
- * @throws IllegalAccessException If reflection failed.
- */
- private static Socket lookupSocket(InputStream stream) throws IllegalAccessException {
- if (stream instanceof FilterInputStream) {
- return lookupSocket(getInputStream((FilterInputStream) stream));
- } else {
- // Just do it
- Field socketField = FuzzyReflection.fromObject(stream, true).
- getFieldByType("socket", Socket.class);
-
- return (Socket) FieldUtils.readField(socketField, stream, true);
- }
- }
-}
+package com.comphenix.protocol.injector.server;
+
+import java.io.FilterInputStream;
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+
+import org.bukkit.Server;
+
+import com.comphenix.protocol.concurrency.BlockingHashMap;
+import com.comphenix.protocol.error.ErrorReporter;
+import com.comphenix.protocol.reflect.FieldAccessException;
+import com.comphenix.protocol.reflect.FieldUtils;
+import com.comphenix.protocol.reflect.FuzzyReflection;
+import com.google.common.collect.MapMaker;
+
+class InputStreamReflectLookup extends AbstractInputStreamLookup {
+ // Used to access the inner input stream of a filtered input stream
+ private static Field filteredInputField;
+
+ // The default lookup timeout
+ private static final long DEFAULT_TIMEOUT = 2000; // ms
+
+ // Using weak keys and values ensures that we will not hold up garbage collection
+ protected BlockingHashMap addressLookup = new BlockingHashMap();
+ protected ConcurrentMap inputLookup = new MapMaker().weakValues().makeMap();
+
+ // The timeout
+ private final long injectorTimeout;
+
+ public InputStreamReflectLookup(ErrorReporter reporter, Server server) {
+ this(reporter, server, DEFAULT_TIMEOUT);
+ }
+
+ /**
+ * Initialize a reflect lookup with a given default injector timeout.
+ *
+ * This timeout defines the maximum amount of time to wait until an injector has been discovered.
+ * @param reporter - the error reporter.
+ * @param server - the current Bukkit server.
+ * @param injectorTimeout - the injector timeout.
+ */
+ public InputStreamReflectLookup(ErrorReporter reporter, Server server, long injectorTimeout) {
+ super(reporter, server);
+ this.injectorTimeout = injectorTimeout;
+ }
+
+ @Override
+ public void inject(Object container) {
+ // Do nothing
+ }
+
+ @Override
+ public SocketInjector peekSocketInjector(SocketAddress address) {
+ try {
+ return addressLookup.get(address, 0, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ // Whatever
+ return null;
+ }
+ }
+
+ @Override
+ public SocketInjector waitSocketInjector(SocketAddress address) {
+ try {
+ // Note that we actually SWALLOW interrupts here - this is because Minecraft uses interrupts to
+ // periodically wake up waiting readers and writers. We have to wait for the dedicated server thread
+ // to catch up, so we'll swallow these interrupts.
+ //
+ // TODO: Consider if we should raise the thread priority of the dedicated server listener thread.
+ return addressLookup.get(address, injectorTimeout, TimeUnit.MILLISECONDS, true);
+ } catch (InterruptedException e) {
+ // This cannot be!
+ throw new IllegalStateException("Impossible exception occured!", e);
+ }
+ }
+
+ @Override
+ public SocketInjector waitSocketInjector(Socket socket) {
+ return waitSocketInjector(socket.getRemoteSocketAddress());
+ }
+
+ @Override
+ public SocketInjector waitSocketInjector(InputStream input) {
+ try {
+ SocketAddress address = waitSocketAddress(input);
+
+ // Guard against NPE
+ if (address != null)
+ return waitSocketInjector(address);
+ else
+ return null;
+ } catch (IllegalAccessException e) {
+ throw new FieldAccessException("Cannot find or access socket field for " + input, e);
+ }
+ }
+
+ /**
+ * Use reflection to get the underlying socket address from an input stream.
+ * @param stream - the socket stream to lookup.
+ * @return The underlying socket address, or NULL if not found.
+ * @throws IllegalAccessException Unable to access socket field.
+ */
+ private SocketAddress waitSocketAddress(InputStream stream) throws IllegalAccessException {
+ // Extra check, just in case
+ if (stream instanceof FilterInputStream)
+ return waitSocketAddress(getInputStream((FilterInputStream) stream));
+
+ SocketAddress result = inputLookup.get(stream);
+
+ if (result == null) {
+ Socket socket = lookupSocket(stream);
+
+ // Save it
+ result = socket.getRemoteSocketAddress();
+ inputLookup.put(stream, result);
+ }
+ return result;
+ }
+
+ /**
+ * Retrieve the underlying input stream that is associated with a given filter input stream.
+ * @param filtered - the filter input stream.
+ * @return The underlying input stream that is being filtered.
+ * @throws FieldAccessException Unable to access input stream.
+ */
+ protected static InputStream getInputStream(FilterInputStream filtered) {
+ if (filteredInputField == null)
+ filteredInputField = FuzzyReflection.fromClass(FilterInputStream.class, true).
+ getFieldByType("in", InputStream.class);
+
+ InputStream current = filtered;
+
+ try {
+ // Iterate until we find the real input stream
+ while (current instanceof FilterInputStream) {
+ current = (InputStream) FieldUtils.readField(filteredInputField, current, true);
+ }
+ return current;
+ } catch (IllegalAccessException e) {
+ throw new FieldAccessException("Cannot access filtered input field.", e);
+ }
+ }
+
+ @Override
+ public void setSocketInjector(SocketAddress address, SocketInjector injector) {
+ if (address == null)
+ throw new IllegalArgumentException("address cannot be NULL");
+ if (injector == null)
+ throw new IllegalArgumentException("injector cannot be NULL.");
+
+ SocketInjector previous = addressLookup.put(address, injector);
+
+ // Any previous temporary players will also be associated
+ if (previous != null) {
+ // Update the reference to any previous injector
+ onPreviousSocketOverwritten(previous, injector);
+ }
+ }
+
+ @Override
+ public void cleanupAll() {
+ // Do nothing
+ }
+
+ /**
+ * Lookup the underlying socket of a stream through reflection.
+ * @param stream - the socket stream.
+ * @return The underlying socket.
+ * @throws IllegalAccessException If reflection failed.
+ */
+ private static Socket lookupSocket(InputStream stream) throws IllegalAccessException {
+ if (stream instanceof FilterInputStream) {
+ return lookupSocket(getInputStream((FilterInputStream) stream));
+ } else {
+ // Just do it
+ Field socketField = FuzzyReflection.fromObject(stream, true).
+ getFieldByType("socket", Socket.class);
+
+ return (Socket) FieldUtils.readField(socketField, stream, true);
+ }
+ }
+}
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/QueuedSendPacket.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/QueuedSendPacket.java
new file mode 100644
index 00000000..e12f47a8
--- /dev/null
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/QueuedSendPacket.java
@@ -0,0 +1,31 @@
+package com.comphenix.protocol.injector.server;
+
+/**
+ * Represents a single send packet command.
+ * @author Kristian
+ */
+class QueuedSendPacket {
+ private final Object packet;
+ private final boolean filtered;
+
+ public QueuedSendPacket(Object packet, boolean filtered) {
+ this.packet = packet;
+ this.filtered = filtered;
+ }
+
+ /**
+ * Retrieve the underlying packet that will be sent.
+ * @return The underlying packet.
+ */
+ public Object getPacket() {
+ return packet;
+ }
+
+ /**
+ * Determine if the packet should be intercepted by packet listeners.
+ * @return TRUE if it should, FALSE otherwise.
+ */
+ public boolean isFiltered() {
+ return filtered;
+ }
+}
\ No newline at end of file
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactory.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactory.java
index 8c2e0e05..62db7f2a 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactory.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactory.java
@@ -1,197 +1,197 @@
-/*
- * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
- * Copyright (C) 2012 Kristian S. Stangeland
- *
- * This program is free software; you can redistribute it and/or modify it under the terms of the
- * GNU General Public License as published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
- * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with this program;
- * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
- * 02111-1307 USA
- */
-
-package com.comphenix.protocol.injector.server;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-import net.sf.cglib.proxy.Callback;
-import net.sf.cglib.proxy.CallbackFilter;
-import net.sf.cglib.proxy.Enhancer;
-import net.sf.cglib.proxy.MethodInterceptor;
-import net.sf.cglib.proxy.MethodProxy;
-import net.sf.cglib.proxy.NoOp;
-
-import org.bukkit.Server;
-import org.bukkit.entity.Player;
-
-import com.comphenix.protocol.injector.PacketConstructor;
-import com.comphenix.protocol.reflect.FieldAccessException;
-
-/**
- * Create fake player instances that represents pre-authenticated clients.
- */
-public class TemporaryPlayerFactory {
- // Helpful constructors
- private final PacketConstructor chatPacket;
-
- // Prevent too many class creations
- private static CallbackFilter callbackFilter;
-
- public TemporaryPlayerFactory() {
- chatPacket = PacketConstructor.DEFAULT.withPacket(3, new Object[] { "DEMO" });
- }
-
- /**
- * Retrieve the injector from a given player if it contains one.
- * @param player - the player that may contain a reference to a player injector.
- * @return The referenced player injector, or NULL if none can be found.
- */
- public static SocketInjector getInjectorFromPlayer(Player player) {
- if (player instanceof InjectorContainer) {
- return ((InjectorContainer) player).getInjector();
- }
- return null;
- }
-
- /**
- * Set the player injector, if possible.
- * @param player - the player to update.
- * @param injector - the injector to store.
- */
- public static void setInjectorInPlayer(Player player, SocketInjector injector) {
- ((InjectorContainer) player).setInjector(injector);
- }
-
- /**
- * Construct a temporary player that supports a subset of every player command.
- *
- * Supported methods include:
- *
- * getPlayer()
- * getAddress()
- * getServer()
- * chat(String)
- * sendMessage(String)
- * sendMessage(String[])
- * kickPlayer(String)
- *
- *
- * Note that a temporary player has not yet been assigned a name, and thus cannot be
- * uniquely identified. Use the address instead.
- * @param injector - the player injector used.
- * @param server - the current server.
- * @return A temporary player instance.
- */
- public Player createTemporaryPlayer(final Server server) {
-
- // Default implementation
- Callback implementation = new MethodInterceptor() {
- @Override
- public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
-
- String methodName = method.getName();
- SocketInjector injector = ((InjectorContainer) obj).getInjector();
-
- if (injector == null)
- throw new IllegalStateException("Unable to find injector.");
-
- // Use the socket to get the address
- if (methodName.equalsIgnoreCase("isOnline"))
- return injector.getSocket() != null && injector.getSocket().isConnected();
- if (methodName.equalsIgnoreCase("getName"))
- return "UNKNOWN[" + injector.getSocket().getRemoteSocketAddress() + "]";
- if (methodName.equalsIgnoreCase("getPlayer"))
- return injector.getUpdatedPlayer();
- if (methodName.equalsIgnoreCase("getAddress"))
- return injector.getAddress();
- if (methodName.equalsIgnoreCase("getServer"))
- return server;
-
- try {
- // Handle send message methods
- if (methodName.equalsIgnoreCase("chat") || methodName.equalsIgnoreCase("sendMessage")) {
- Object argument = args[0];
-
- // Dynamic overloading
- if (argument instanceof String) {
- return sendMessage(injector, (String) argument);
- } else if (argument instanceof String[]) {
- for (String message : (String[]) argument) {
- sendMessage(injector, message);
- }
- return null;
- }
- }
- } catch (InvocationTargetException e) {
- throw e.getCause();
- }
-
- // Also, handle kicking
- if (methodName.equalsIgnoreCase("kickPlayer")) {
- injector.disconnect((String) args[0]);
- return null;
- }
-
- // Ignore all other methods
- throw new UnsupportedOperationException(
- "The method " + method.getName() + " is not supported for temporary players.");
- }
- };
-
- // Shared callback filter
- if (callbackFilter == null) {
- callbackFilter = new CallbackFilter() {
- @Override
- public int accept(Method method) {
- // Do not override the object method or the superclass methods
- if (method.getDeclaringClass().equals(Object.class) ||
- method.getDeclaringClass().equals(InjectorContainer.class))
- return 0;
- else
- return 1;
- }
- };
- }
-
- // CGLib is amazing
- Enhancer ex = new Enhancer();
- ex.setSuperclass(InjectorContainer.class);
- ex.setInterfaces(new Class[] { Player.class });
- ex.setCallbacks(new Callback[] { NoOp.INSTANCE, implementation });
- ex.setCallbackFilter(callbackFilter);
-
- return (Player) ex.create();
- }
-
- /**
- * Construct a temporary player with the given associated socket injector.
- * @param server - the parent server.
- * @param injector - the referenced socket injector.
- * @return The temporary player.
- */
- public Player createTemporaryPlayer(Server server, SocketInjector injector) {
- Player temporary = createTemporaryPlayer(server);
-
- ((InjectorContainer) temporary).setInjector(injector);
- return temporary;
- }
-
- /**
- * Send a message to the given client.
- * @param injector - the injector representing the client.
- * @param message - a message.
- * @return Always NULL.
- * @throws InvocationTargetException If the message couldn't be sent.
- * @throws FieldAccessException If we were unable to construct the message packet.
- */
- private Object sendMessage(SocketInjector injector, String message) throws InvocationTargetException, FieldAccessException {
- injector.sendServerPacket(chatPacket.createPacket(message).getHandle(), false);
- return null;
- }
-}
+/*
+ * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
+ * Copyright (C) 2012 Kristian S. Stangeland
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program;
+ * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA
+ */
+
+package com.comphenix.protocol.injector.server;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import net.sf.cglib.proxy.Callback;
+import net.sf.cglib.proxy.CallbackFilter;
+import net.sf.cglib.proxy.Enhancer;
+import net.sf.cglib.proxy.MethodInterceptor;
+import net.sf.cglib.proxy.MethodProxy;
+import net.sf.cglib.proxy.NoOp;
+
+import org.bukkit.Server;
+import org.bukkit.entity.Player;
+
+import com.comphenix.protocol.injector.PacketConstructor;
+import com.comphenix.protocol.reflect.FieldAccessException;
+
+/**
+ * Create fake player instances that represents pre-authenticated clients.
+ */
+public class TemporaryPlayerFactory {
+ // Helpful constructors
+ private final PacketConstructor chatPacket;
+
+ // Prevent too many class creations
+ private static CallbackFilter callbackFilter;
+
+ public TemporaryPlayerFactory() {
+ chatPacket = PacketConstructor.DEFAULT.withPacket(3, new Object[] { "DEMO" });
+ }
+
+ /**
+ * Retrieve the injector from a given player if it contains one.
+ * @param player - the player that may contain a reference to a player injector.
+ * @return The referenced player injector, or NULL if none can be found.
+ */
+ public static SocketInjector getInjectorFromPlayer(Player player) {
+ if (player instanceof InjectorContainer) {
+ return ((InjectorContainer) player).getInjector();
+ }
+ return null;
+ }
+
+ /**
+ * Set the player injector, if possible.
+ * @param player - the player to update.
+ * @param injector - the injector to store.
+ */
+ public static void setInjectorInPlayer(Player player, SocketInjector injector) {
+ ((InjectorContainer) player).setInjector(injector);
+ }
+
+ /**
+ * Construct a temporary player that supports a subset of every player command.
+ *
+ * Supported methods include:
+ *
+ * getPlayer()
+ * getAddress()
+ * getServer()
+ * chat(String)
+ * sendMessage(String)
+ * sendMessage(String[])
+ * kickPlayer(String)
+ *
+ *
+ * Note that a temporary player has not yet been assigned a name, and thus cannot be
+ * uniquely identified. Use the address instead.
+ * @param injector - the player injector used.
+ * @param server - the current server.
+ * @return A temporary player instance.
+ */
+ public Player createTemporaryPlayer(final Server server) {
+
+ // Default implementation
+ Callback implementation = new MethodInterceptor() {
+ @Override
+ public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
+
+ String methodName = method.getName();
+ SocketInjector injector = ((InjectorContainer) obj).getInjector();
+
+ if (injector == null)
+ throw new IllegalStateException("Unable to find injector.");
+
+ // Use the socket to get the address
+ if (methodName.equalsIgnoreCase("isOnline"))
+ return injector.getSocket() != null && injector.getSocket().isConnected();
+ if (methodName.equalsIgnoreCase("getName"))
+ return "UNKNOWN[" + injector.getSocket().getRemoteSocketAddress() + "]";
+ if (methodName.equalsIgnoreCase("getPlayer"))
+ return injector.getUpdatedPlayer();
+ if (methodName.equalsIgnoreCase("getAddress"))
+ return injector.getAddress();
+ if (methodName.equalsIgnoreCase("getServer"))
+ return server;
+
+ try {
+ // Handle send message methods
+ if (methodName.equalsIgnoreCase("chat") || methodName.equalsIgnoreCase("sendMessage")) {
+ Object argument = args[0];
+
+ // Dynamic overloading
+ if (argument instanceof String) {
+ return sendMessage(injector, (String) argument);
+ } else if (argument instanceof String[]) {
+ for (String message : (String[]) argument) {
+ sendMessage(injector, message);
+ }
+ return null;
+ }
+ }
+ } catch (InvocationTargetException e) {
+ throw e.getCause();
+ }
+
+ // Also, handle kicking
+ if (methodName.equalsIgnoreCase("kickPlayer")) {
+ injector.disconnect((String) args[0]);
+ return null;
+ }
+
+ // Ignore all other methods
+ throw new UnsupportedOperationException(
+ "The method " + method.getName() + " is not supported for temporary players.");
+ }
+ };
+
+ // Shared callback filter
+ if (callbackFilter == null) {
+ callbackFilter = new CallbackFilter() {
+ @Override
+ public int accept(Method method) {
+ // Do not override the object method or the superclass methods
+ if (method.getDeclaringClass().equals(Object.class) ||
+ method.getDeclaringClass().equals(InjectorContainer.class))
+ return 0;
+ else
+ return 1;
+ }
+ };
+ }
+
+ // CGLib is amazing
+ Enhancer ex = new Enhancer();
+ ex.setSuperclass(InjectorContainer.class);
+ ex.setInterfaces(new Class[] { Player.class });
+ ex.setCallbacks(new Callback[] { NoOp.INSTANCE, implementation });
+ ex.setCallbackFilter(callbackFilter);
+
+ return (Player) ex.create();
+ }
+
+ /**
+ * Construct a temporary player with the given associated socket injector.
+ * @param server - the parent server.
+ * @param injector - the referenced socket injector.
+ * @return The temporary player.
+ */
+ public Player createTemporaryPlayer(Server server, SocketInjector injector) {
+ Player temporary = createTemporaryPlayer(server);
+
+ ((InjectorContainer) temporary).setInjector(injector);
+ return temporary;
+ }
+
+ /**
+ * Send a message to the given client.
+ * @param injector - the injector representing the client.
+ * @param message - a message.
+ * @return Always NULL.
+ * @throws InvocationTargetException If the message couldn't be sent.
+ * @throws FieldAccessException If we were unable to construct the message packet.
+ */
+ private Object sendMessage(SocketInjector injector, String message) throws InvocationTargetException, FieldAccessException {
+ injector.sendServerPacket(chatPacket.createPacket(message).getHandle(), false);
+ return null;
+ }
+}
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPlayerHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPlayerHandler.java
index 8df7f70c..dd64e3ad 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPlayerHandler.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPlayerHandler.java
@@ -114,12 +114,7 @@ class DummyPlayerHandler implements PlayerInjectionHandler {
public void checkListener(Set listeners) {
// Yes, really
}
-
- @Override
- public void postWorldLoaded() {
- // Do nothing
- }
-
+
@Override
public void updatePlayer(Player player) {
// Do nothing
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/fuzzy/FuzzyMethodContract.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/fuzzy/FuzzyMethodContract.java
index 2fe0da85..35b92d9a 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/fuzzy/FuzzyMethodContract.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/fuzzy/FuzzyMethodContract.java
@@ -161,14 +161,26 @@ public class FuzzyMethodContract extends AbstractFuzzyMember {
/**
* Add a new required parameter whose type must be a superclass of the given type.
*
- * If a parameter is of type Number, any derived class (Integer, Long, etc.) will match it.
- * @param type - a type or derived type of the matching parameter.
+ * If a method parameter is of type Number, then any derived class here (Integer, Long, etc.) will match it.
+ * @param type - a type or less derived type of the matching parameter.
* @return This builder, for chaining.
*/
public Builder parameterSuperOf(Class> type) {
member.paramMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchSuper(type)));
return this;
}
+
+ /**
+ * Add a new required parameter whose type must be a derived class of the given class.
+ *
+ * If the method parameter has the type Integer, then the class Number here will match it.
+ * @param type - a type or more derived type of the matching parameter.
+ * @return This builder, for chaining.
+ */
+ public Builder parameterDerivedOf(Class> type) {
+ member.paramMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchDerived(type)));
+ return this;
+ }
/**
* Add a new required parameter whose type must match the given class matcher.
@@ -204,6 +216,19 @@ public class FuzzyMethodContract extends AbstractFuzzyMember {
return this;
}
+ /**
+ * Add a new required parameter whose type must be a derived class of the given class.
+ *
+ * If the method parameter has the type Integer, then the class Number here will match it.
+ * @param type - a type or more derived type of the matching parameter.
+ * @param index - the expected position in the parameter list.
+ * @return This builder, for chaining.
+ */
+ public Builder parameterDerivedOf(Class> type, int index) {
+ member.paramMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchDerived(type), index));
+ return this;
+ }
+
/**
* Add a new required parameter whose type must match the given class matcher and index.
* @param classMatcher - the class matcher.
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java
index 9261f7bb..40fe3d58 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java
@@ -18,7 +18,7 @@
package com.comphenix.protocol.utility;
import java.io.DataInputStream;
-import java.io.DataOutputStream;
+import java.io.DataOutput;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
@@ -219,6 +219,11 @@ public class MinecraftReflection {
MINECRAFT_FULL_PACKAGE = minecraftPackage;
CRAFTBUKKIT_PACKAGE = craftBukkitPackage;
+ // Make sure it exists
+ if (getMinecraftServerClass() == null) {
+ throw new IllegalArgumentException("Cannot find MinecraftServer for package " + minecraftPackage);
+ }
+
// Standard matcher
setDynamicPackageMatcher(MINECRAFT_OBJECT);
}
@@ -287,7 +292,7 @@ public class MinecraftReflection {
public static boolean isMinecraftClass(@Nonnull Class> clazz) {
if (clazz == null)
throw new IllegalArgumentException("Class cannot be NULL.");
-
+
return getMinecraftObjectMatcher().isMatch(clazz, null);
}
@@ -792,7 +797,7 @@ public class MinecraftReflection {
Method selected = FuzzyReflection.fromClass(getDataWatcherClass(), true).
getMethod(FuzzyMethodContract.newBuilder().
requireModifier(Modifier.STATIC).
- parameterSuperOf(DataOutputStream.class, 0).
+ parameterDerivedOf(DataOutput.class, 0).
parameterMatches(getMinecraftObjectMatcher(), 1).
build());
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/StreamSerializer.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/StreamSerializer.java
index 2a441149..a1d6dda5 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/StreamSerializer.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/StreamSerializer.java
@@ -2,7 +2,9 @@ package com.comphenix.protocol.utility;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.io.DataInput;
import java.io.DataInputStream;
+import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
@@ -13,6 +15,7 @@ import org.bukkit.inventory.ItemStack;
import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
import com.comphenix.protocol.reflect.FuzzyReflection;
+import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
/**
* Utility methods for reading and writing Minecraft objects to streams.
@@ -37,11 +40,14 @@ public class StreamSerializer {
public ItemStack deserializeItemStack(@Nonnull DataInputStream input) throws IOException {
if (input == null)
throw new IllegalArgumentException("Input stream cannot be NULL.");
- if (readItemMethod == null)
- readItemMethod = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).
- getMethodByParameters("readPacket",
- MinecraftReflection.getItemStackClass(),
- new Class>[] {DataInputStream.class});
+ if (readItemMethod == null) {
+ readItemMethod = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).getMethod(
+ FuzzyMethodContract.newBuilder().
+ parameterCount(1).
+ parameterDerivedOf(DataInput.class).
+ returnDerivedOf(MinecraftReflection.getItemStackClass()).
+ build());
+ }
try {
Object nmsItem = readItemMethod.invoke(null, input);
@@ -88,10 +94,12 @@ public class StreamSerializer {
Object nmsItem = MinecraftReflection.getMinecraftItemStack(stack);
if (writeItemMethod == null)
- writeItemMethod = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).
- getMethodByParameters("writePacket", new Class>[] {
- MinecraftReflection.getItemStackClass(),
- DataOutputStream.class });
+ writeItemMethod = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).getMethod(
+ FuzzyMethodContract.newBuilder().
+ parameterCount(2).
+ parameterDerivedOf(MinecraftReflection.getItemStackClass(), 0).
+ parameterDerivedOf(DataOutput.class, 1).
+ build());
try {
writeItemMethod.invoke(null, nmsItem, output);
} catch (Exception e) {
diff --git a/ProtocolLib/src/main/resources/plugin.yml b/ProtocolLib/src/main/resources/plugin.yml
index 010f513f..cfc25b27 100644
--- a/ProtocolLib/src/main/resources/plugin.yml
+++ b/ProtocolLib/src/main/resources/plugin.yml
@@ -1,5 +1,5 @@
name: ProtocolLib
-version: 2.4.5
+version: 2.4.8-SNAPSHOT
description: Provides read/write access to the Minecraft protocol.
author: Comphenix
website: http://www.comphenix.net/ProtocolLib
diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/BukkitInitialization.java b/ProtocolLib/src/test/java/com/comphenix/protocol/BukkitInitialization.java
index 39b6b55d..988ac692 100644
--- a/ProtocolLib/src/test/java/com/comphenix/protocol/BukkitInitialization.java
+++ b/ProtocolLib/src/test/java/com/comphenix/protocol/BukkitInitialization.java
@@ -4,10 +4,10 @@ import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-import net.minecraft.server.v1_5_R2.StatisticList;
+import net.minecraft.server.v1_6_R2.StatisticList;
// Will have to be updated for every version though
-import org.bukkit.craftbukkit.v1_5_R2.inventory.CraftItemFactory;
+import org.bukkit.craftbukkit.v1_6_R2.inventory.CraftItemFactory;
import org.bukkit.Bukkit;
import org.bukkit.Material;
@@ -63,6 +63,6 @@ public class BukkitInitialization {
*/
public static void initializePackage() {
// Initialize reflection
- MinecraftReflection.setMinecraftPackage("net.minecraft.server.v1_5_R2", "org.bukkit.craftbukkit.v1_5_R2");
+ MinecraftReflection.setMinecraftPackage("net.minecraft.server.v1_6_R2", "org.bukkit.craftbukkit.v1_6_R2");
}
}
diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java b/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java
index d305adf4..917b160a 100644
--- a/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java
+++ b/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java
@@ -21,8 +21,9 @@ import static org.junit.Assert.*;
import java.lang.reflect.Array;
import java.util.List;
+import org.apache.commons.lang.SerializationUtils;
// Will have to be updated for every version though
-import org.bukkit.craftbukkit.v1_5_R2.inventory.CraftItemFactory;
+import org.bukkit.craftbukkit.v1_6_R2.inventory.CraftItemFactory;
import org.bukkit.Material;
import org.bukkit.WorldType;
@@ -305,6 +306,17 @@ public class PacketContainerTest {
watchableAccessor.write(0, list);
assertEquals(list, watchableAccessor.read(0));
}
+
+ @Test
+ public void testSerialization() {
+ PacketContainer chat = new PacketContainer(3);
+ chat.getStrings().write(0, "Test");
+
+ PacketContainer copy = (PacketContainer) SerializationUtils.clone(chat);
+
+ assertEquals(3, copy.getID());
+ assertEquals("Test", copy.getStrings().read(0));
+ }
@Test
public void testDeepClone() {
diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/injector/PluginVerifierTest.java b/ProtocolLib/src/test/java/com/comphenix/protocol/injector/PluginVerifierTest.java
new file mode 100644
index 00000000..daa3cacd
--- /dev/null
+++ b/ProtocolLib/src/test/java/com/comphenix/protocol/injector/PluginVerifierTest.java
@@ -0,0 +1,89 @@
+package com.comphenix.protocol.injector;
+
+import static org.junit.Assert.*;
+
+import java.util.Arrays;
+import java.util.List;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.bukkit.Server;
+import org.bukkit.plugin.Plugin;
+import org.bukkit.plugin.PluginDescriptionFile;
+import org.bukkit.plugin.PluginLoadOrder;
+import org.bukkit.plugin.PluginManager;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import com.comphenix.protocol.injector.PluginVerifier.VerificationResult;
+import com.google.common.base.Objects;
+import com.google.common.collect.Lists;
+
+// Damn final classes
+@RunWith(org.powermock.modules.junit4.PowerMockRunner.class)
+@PrepareForTest(PluginDescriptionFile.class)
+public class PluginVerifierTest {
+ @Test
+ public void testDependecies() {
+ List plugins = Lists.newArrayList();
+ Server server = mockServer(plugins);
+
+ Plugin library = mockPlugin(server, "ProtocolLib", PluginLoadOrder.POSTWORLD);
+ Plugin skillPlugin = mockPlugin(server, "SkillPlugin", "RaidCraft-API", "RCPermissions", "RCConversations");
+ Plugin raidCraftAPI = mockPlugin(server, "RaidCraft-API", "WorldGuard", "WorldEdit");
+ Plugin conversations = mockPlugin(server, "RCConversations", "RaidCraft-API");
+ Plugin permissions = mockPlugin(server, "RCPermissions", "RaidCraft-API");
+
+ // Add the plugins
+ plugins.addAll(Arrays.asList(library, skillPlugin, raidCraftAPI, conversations, permissions));
+ PluginVerifier verifier = new PluginVerifier(library);
+
+ // Verify the root - it should have no dependencies on ProtocolLib
+ assertEquals(VerificationResult.NO_DEPEND, verifier.verify(skillPlugin));
+ }
+
+ private Server mockServer(final List plugins) {
+ Server mockServer = mock(Server.class);
+ PluginManager manager = mock(PluginManager.class);
+
+ when(mockServer.getPluginManager()).thenReturn(manager);
+ when(manager.getPlugin(anyString())).thenAnswer(new Answer() {
+ @Override
+ public Plugin answer(InvocationOnMock invocation) throws Throwable {
+ String name = (String) invocation.getArguments()[0];
+
+ for (Plugin plugin : plugins) {
+ if (Objects.equal(name, plugin.getName())) {
+ return plugin;
+ }
+ }
+ return null;
+ }
+ });
+ return mockServer;
+ }
+
+ private Plugin mockPlugin(Server server, String name,String... depend) {
+ return mockPlugin(server, name, PluginLoadOrder.POSTWORLD, depend);
+ }
+
+ private Plugin mockPlugin(Server server, String name, PluginLoadOrder order, String... depend) {
+ Plugin plugin = mock(Plugin.class);
+ PluginDescriptionFile file = mock(PluginDescriptionFile.class);
+
+ when(plugin.getServer()).thenReturn(server);
+ when(plugin.getName()).thenReturn(name);
+ when(plugin.toString()).thenReturn(name);
+ when(plugin.getDescription()).thenReturn(file);
+
+ // This is the difficult part
+ when(file.getLoad()).thenReturn(order);
+ when(file.getDepend()).thenReturn(Arrays.asList(depend));
+ when(file.getSoftDepend()).thenReturn(null);
+ return plugin;
+ }
+}
diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/utility/StreamSerializerTest.java b/ProtocolLib/src/test/java/com/comphenix/protocol/utility/StreamSerializerTest.java
new file mode 100644
index 00000000..0895023b
--- /dev/null
+++ b/ProtocolLib/src/test/java/com/comphenix/protocol/utility/StreamSerializerTest.java
@@ -0,0 +1,36 @@
+package com.comphenix.protocol.utility;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+
+import org.bukkit.Material;
+import org.bukkit.craftbukkit.v1_6_R2.inventory.CraftItemFactory;
+import org.bukkit.inventory.ItemStack;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+
+import com.comphenix.protocol.BukkitInitialization;
+
+@RunWith(org.powermock.modules.junit4.PowerMockRunner.class)
+@PrepareForTest(CraftItemFactory.class)
+public class StreamSerializerTest {
+ @BeforeClass
+ public static void initializeBukkit() throws IllegalAccessException {
+ BukkitInitialization.initializeItemMeta();
+ }
+
+ @Test
+ public void testSerializer() throws IOException {
+ ItemStack before = new ItemStack(Material.GOLD_AXE);
+
+ StreamSerializer serializer = new StreamSerializer();
+ String data = serializer.serializeItemStack(before);
+ ItemStack after = serializer.deserializeItemStack(data);
+
+ assertEquals(before.getType(), after.getType());
+ assertEquals(before.getAmount(), after.getAmount());
+ }
+}