Merge branch '1.6.2'
Dieser Commit ist enthalten in:
Commit
cd0f8a6fa5
@ -11,12 +11,12 @@
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<name>net.sourceforge.metrics.builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>net.sourceforge.metrics.builder</name>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.comphenix.protocol</groupId>
|
||||
<artifactId>ProtocolLib</artifactId>
|
||||
<version>2.4.5</version>
|
||||
<version>2.4.8-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
<description>Provides read/write access to the Minecraft protocol.</description>
|
||||
|
||||
@ -29,6 +29,11 @@
|
||||
<id>bukkit-rep</id>
|
||||
<url>http://repo.bukkit.org/content/groups/public</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>comphenix-releases</id>
|
||||
<name>Comphenix Maven Releases</name>
|
||||
<url>http://repo.comphenix.net/content/repositories/releases/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<build>
|
||||
@ -200,10 +205,16 @@
|
||||
<version>2.2.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.comphenix.executors</groupId>
|
||||
<artifactId>BukkitExecutors</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>1.5.1-R0.2-SNAPSHOT</version>
|
||||
<version>1.6.2-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
* <p>
|
||||
@ -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);
|
||||
|
@ -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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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());
|
||||
}
|
||||
}
|
||||
|
@ -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<String, AtomicInteger> warningCount = new ConcurrentHashMap<String, AtomicInteger>();
|
||||
|
||||
protected String prefix;
|
||||
protected String supportURL;
|
||||
|
||||
protected AtomicInteger internalErrorCount = new AtomicInteger();
|
||||
|
||||
protected int maxErrorCount;
|
||||
protected Logger logger;
|
||||
|
||||
protected WeakReference<Plugin> pluginReference;
|
||||
protected String pluginName;
|
||||
|
||||
// Whether or not Apache Commons is not present
|
||||
protected boolean apacheCommonsMissing;
|
||||
|
||||
// Map of global objects
|
||||
protected Map<String, Object> globalParameters = new HashMap<String, Object>();
|
||||
|
||||
/**
|
||||
* 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>(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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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<String> 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<String, AtomicInteger> warningCount = new ConcurrentHashMap<String, AtomicInteger>();
|
||||
|
||||
protected String prefix;
|
||||
protected String supportURL;
|
||||
|
||||
protected AtomicInteger internalErrorCount = new AtomicInteger();
|
||||
|
||||
protected int maxErrorCount;
|
||||
protected Logger logger;
|
||||
|
||||
protected WeakReference<Plugin> pluginReference;
|
||||
protected String pluginName;
|
||||
|
||||
// Whether or not Apache Commons is not present
|
||||
protected boolean apacheCommonsMissing;
|
||||
|
||||
// Map of global objects
|
||||
protected Map<String, Object> globalParameters = new HashMap<String, Object>();
|
||||
|
||||
/**
|
||||
* 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>(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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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<String> 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;
|
||||
}
|
||||
}
|
||||
|
@ -46,6 +46,39 @@ public class ReportType {
|
||||
return errorFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the class of the given sender.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* Note that the sender may be a class (for static callers), in which
|
||||
* case it will be used directly instead of its getClass() method.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
@ -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.");
|
||||
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
* <p>
|
||||
* Typical conversions include:
|
||||
* <ul>
|
||||
* <li>org.bukkit.entity.Player -> net.minecraft.server.EntityPlayer</li>
|
||||
* <li>org.bukkit.World -> net.minecraft.server.WorldServer</li>
|
||||
* </ul>
|
||||
*
|
||||
* @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<Class<?>, Unwrapper> unwrapperCache = new ConcurrentHashMap<Class<?>, 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<Object>) 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<Object> wrappedObject) {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Collection<Object> 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.
|
||||
* <p>
|
||||
* Typical conversions include:
|
||||
* <ul>
|
||||
* <li>org.bukkit.entity.Player -> net.minecraft.server.EntityPlayer</li>
|
||||
* <li>org.bukkit.World -> net.minecraft.server.WorldServer</li>
|
||||
* </ul>
|
||||
*
|
||||
* @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<Class<?>, Unwrapper> unwrapperCache = new ConcurrentHashMap<Class<?>, 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<Object>) 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<Object> wrappedObject) {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Collection<Object> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<PacketListener> queuedListeners = Sets.newSetFromMap(Maps.<PacketListener, Boolean>newConcurrentMap());
|
||||
private final List<QueuedPacket> queuedPackets = Collections.synchronizedList(Lists.<QueuedPacket>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.
|
||||
* <p>
|
||||
* 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<PacketListener> 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<PacketListener> 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<Integer> getSendingFilters() {
|
||||
if (delegate != null) {
|
||||
return delegate.getSendingFilters();
|
||||
} else {
|
||||
// Linear scan is fast enough here
|
||||
Set<Integer> sending = Sets.newHashSet();
|
||||
|
||||
for (PacketListener listener : queuedListeners) {
|
||||
sending.addAll(listener.getSendingWhitelist().getWhitelist());
|
||||
}
|
||||
return sending;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Integer> getReceivingFilters() {
|
||||
if (delegate != null) {
|
||||
return delegate.getReceivingFilters();
|
||||
} else {
|
||||
Set<Integer> recieving = Sets.newHashSet();
|
||||
|
||||
for (PacketListener listener : queuedListeners) {
|
||||
recieving.addAll(listener.getReceivingWhitelist().getWhitelist());
|
||||
}
|
||||
return recieving;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateEntity(Entity entity, List<Player> 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<Player> 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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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);
|
||||
}
|
@ -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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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<WorldInitEvent>() {
|
||||
@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;
|
||||
}
|
||||
}
|
@ -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<GamePhase> isInjectionNecessary = new Predicate<GamePhase>() {
|
||||
@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)
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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 <K, V> Multimap<K, V> inverseMultimap(final Map<V, K> map, final Predicate<Map.Entry<V, K>> filter) {
|
||||
final MapContainer container = new MapContainer(map);
|
||||
|
||||
return new ForwardingMultimap<K, V>() {
|
||||
// The cached multimap
|
||||
private Multimap<K, V> inverseMultimap;
|
||||
|
||||
@Override
|
||||
protected Multimap<K, V> delegate() {
|
||||
if (container.hasChanged()) {
|
||||
inverseMultimap = HashMultimap.create();
|
||||
|
||||
// Construct the inverse map
|
||||
for (Map.Entry<V, K> entry : map.entrySet()) {
|
||||
if (filter.apply(entry)) {
|
||||
inverseMultimap.put(entry.getValue(), entry.getKey());
|
||||
}
|
||||
}
|
||||
container.setChanged(false);
|
||||
}
|
||||
return inverseMultimap;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static <K, V> Map<K, V> inverseMap(final Map<V, K> map, final Predicate<Map.Entry<V, K>> filter) {
|
||||
final MapContainer container = new MapContainer(map);
|
||||
|
||||
return new ForwardingMap<K, V>() {
|
||||
// The cached map
|
||||
private Map<K, V> inverseMap;
|
||||
|
||||
@Override
|
||||
protected Map<K, V> delegate() {
|
||||
if (container.hasChanged()) {
|
||||
inverseMap = Maps.newHashMap();
|
||||
|
||||
// Construct the inverse map
|
||||
for (Map.Entry<V, K> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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<Class, Integer> packetToID;
|
||||
|
||||
// Whether or not certain packets are sent by the client or the server
|
||||
private static ImmutableSet<Integer> serverPackets;
|
||||
private static ImmutableSet<Integer> clientPackets;
|
||||
|
||||
// The underlying sets
|
||||
private static Set<Integer> serverPacketsRef;
|
||||
private static Set<Integer> clientPacketsRef;
|
||||
|
||||
// New proxy values
|
||||
private static Map<Integer, Class> overwrittenPackets = new HashMap<Integer, Class>();
|
||||
|
||||
// Vanilla packets
|
||||
private static Map<Integer, Class> previousValues = new HashMap<Integer, Class>();
|
||||
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
public static Map<Class, Integer> getPacketToID() {
|
||||
// Initialize it, if we haven't already
|
||||
if (packetToID == null) {
|
||||
try {
|
||||
Field packetsField = getPacketRegistry().getFieldByType("packetsField", Map.class);
|
||||
packetToID = (Map<Class, Integer>) 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<Class, Integer> 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<Integer, Class> getOverwrittenPackets() {
|
||||
return overwrittenPackets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the vanilla classes handling each packet ID.
|
||||
* @return Vanilla classes.
|
||||
*/
|
||||
public static Map<Integer, Class> 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<Integer> 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<Integer> 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<Field> sets = getPacketRegistry().getFieldListByType(Set.class);
|
||||
|
||||
try {
|
||||
if (sets.size() > 1) {
|
||||
serverPacketsRef = (Set<Integer>) FieldUtils.readStaticField(sets.get(0), true);
|
||||
clientPacketsRef = (Set<Integer>) 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<Integer, Class> 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<Class, Integer> 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<Class, Integer> packetToID;
|
||||
|
||||
// Packet IDs to classes, grouped by whether or not they're vanilla or custom defined
|
||||
private static Multimap<Integer, Class> customIdToPacket;
|
||||
private static Map<Integer, Class> vanillaIdToPacket;
|
||||
|
||||
// Whether or not certain packets are sent by the client or the server
|
||||
private static ImmutableSet<Integer> serverPackets;
|
||||
private static ImmutableSet<Integer> clientPackets;
|
||||
|
||||
// The underlying sets
|
||||
private static Set<Integer> serverPacketsRef;
|
||||
private static Set<Integer> clientPacketsRef;
|
||||
|
||||
// New proxy values
|
||||
private static Map<Integer, Class> overwrittenPackets = new HashMap<Integer, Class>();
|
||||
|
||||
// Vanilla packets
|
||||
private static Map<Integer, Class> previousValues = new HashMap<Integer, Class>();
|
||||
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
public static Map<Class, Integer> getPacketToID() {
|
||||
// Initialize it, if we haven't already
|
||||
if (packetToID == null) {
|
||||
try {
|
||||
Field packetsField = getPacketRegistry().getFieldByType("packetsField", Map.class);
|
||||
packetToID = (Map<Class, Integer>) 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<Map.Entry<Class, Integer>>() {
|
||||
@Override
|
||||
public boolean apply(@Nullable Entry<Class, Integer> 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<Map.Entry<Class, Integer>>() {
|
||||
@Override
|
||||
public boolean apply(@Nullable Entry<Class, Integer> entry) {
|
||||
return MinecraftReflection.isMinecraftClass(entry.getKey());
|
||||
}
|
||||
});
|
||||
}
|
||||
return packetToID;
|
||||
}
|
||||
|
||||
private static Map<Class, Integer> 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<Integer, Class> getOverwrittenPackets() {
|
||||
return overwrittenPackets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the vanilla classes handling each packet ID.
|
||||
* @return Vanilla classes.
|
||||
*/
|
||||
public static Map<Integer, Class> 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<Integer> 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<Integer> 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<Field> sets = getPacketRegistry().getFieldListByType(Set.class);
|
||||
|
||||
try {
|
||||
if (sets.size() > 1) {
|
||||
serverPacketsRef = (Set<Integer>) FieldUtils.readStaticField(sets.get(0), true);
|
||||
clientPacketsRef = (Set<Integer>) 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<Integer, Class> 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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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<Object, Object> 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<Object, Object> 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);
|
||||
}
|
||||
}
|
||||
|
@ -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<VolatileField> listFields;
|
||||
private List<ReplacedArrayList<Object>> 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<VolatileField>();
|
||||
this.replacedLists = new ArrayList<ReplacedArrayList<Object>>();
|
||||
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<Field> 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<Field> 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<Object> list = (List<Object>) listFieldRef.getValue();
|
||||
|
||||
// Careful not to inject twice
|
||||
if (list instanceof ReplacedArrayList) {
|
||||
replacedLists.add((ReplacedArrayList<Object>) list);
|
||||
} else {
|
||||
ReplacedArrayList<Object> injectedList = createReplacement(list);
|
||||
|
||||
replacedLists.add(injectedList);
|
||||
listFieldRef.setValue(injectedList);
|
||||
listFields.add(listFieldRef);
|
||||
}
|
||||
}
|
||||
|
||||
// Hack to avoid the "moved to quickly" error
|
||||
private ReplacedArrayList<Object> createReplacement(List<Object> list) {
|
||||
return new ReplacedArrayList<Object>(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<Object> 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<Object> replacedList : replacedLists) {
|
||||
replacedList.removeMapping(oldHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Undoes everything.
|
||||
*/
|
||||
public void cleanupAll() {
|
||||
if (replacedLists.size() > 0) {
|
||||
// Repair the underlying lists
|
||||
for (ReplacedArrayList<Object> 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<VolatileField> listFields;
|
||||
private List<ReplacedArrayList<Object>> 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<VolatileField>();
|
||||
this.replacedLists = new ArrayList<ReplacedArrayList<Object>>();
|
||||
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.
|
||||
* <p>
|
||||
* 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<Field> 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<Field> 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<Object> list = (List<Object>) listFieldRef.getValue();
|
||||
|
||||
// Careful not to inject twice
|
||||
if (list instanceof ReplacedArrayList) {
|
||||
replacedLists.add((ReplacedArrayList<Object>) list);
|
||||
} else {
|
||||
ReplacedArrayList<Object> injectedList = createReplacement(list);
|
||||
|
||||
replacedLists.add(injectedList);
|
||||
listFieldRef.setValue(injectedList);
|
||||
listFields.add(listFieldRef);
|
||||
}
|
||||
}
|
||||
|
||||
// Hack to avoid the "moved to quickly" error
|
||||
private ReplacedArrayList<Object> createReplacement(List<Object> list) {
|
||||
return new ReplacedArrayList<Object>(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<Object> 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<Object> replacedList : replacedLists) {
|
||||
replacedList.removeMapping(oldHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Undoes everything.
|
||||
*/
|
||||
public void cleanupAll() {
|
||||
if (replacedLists.size() > 0) {
|
||||
// Repair the underlying lists
|
||||
for (ReplacedArrayList<Object> replacedList : replacedLists) {
|
||||
replacedList.revertAll();
|
||||
}
|
||||
for (VolatileField field : listFields) {
|
||||
field.revertValue();
|
||||
}
|
||||
|
||||
listFields.clear();
|
||||
replacedLists.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<Object, PlayerInjector> 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<Object, PlayerInjector> 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();
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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();
|
||||
}
|
@ -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<SendPacketCommand> syncronizedQueue = Collections.synchronizedList(new ArrayList<SendPacketCommand>());
|
||||
private List<QueuedSendPacket> syncronizedQueue = Collections.synchronizedList(new ArrayList<QueuedSendPacket>());
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
@ -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<SocketAddress, SocketInjector> addressLookup = new BlockingHashMap<SocketAddress, SocketInjector>();
|
||||
protected ConcurrentMap<InputStream, SocketAddress> 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.
|
||||
* <p>
|
||||
* 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<SocketAddress, SocketInjector> addressLookup = new BlockingHashMap<SocketAddress, SocketInjector>();
|
||||
protected ConcurrentMap<InputStream, SocketAddress> 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.
|
||||
* <p>
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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.
|
||||
* <p>
|
||||
* Supported methods include:
|
||||
* <ul>
|
||||
* <li>getPlayer()</li>
|
||||
* <li>getAddress()</li>
|
||||
* <li>getServer()</li>
|
||||
* <li>chat(String)</li>
|
||||
* <li>sendMessage(String)</li>
|
||||
* <li>sendMessage(String[])</li>
|
||||
* <li>kickPlayer(String)</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* Supported methods include:
|
||||
* <ul>
|
||||
* <li>getPlayer()</li>
|
||||
* <li>getAddress()</li>
|
||||
* <li>getServer()</li>
|
||||
* <li>chat(String)</li>
|
||||
* <li>sendMessage(String)</li>
|
||||
* <li>sendMessage(String[])</li>
|
||||
* <li>kickPlayer(String)</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
@ -114,12 +114,7 @@ class DummyPlayerHandler implements PlayerInjectionHandler {
|
||||
public void checkListener(Set<PacketListener> listeners) {
|
||||
// Yes, really
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postWorldLoaded() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void updatePlayer(Player player) {
|
||||
// Do nothing
|
||||
|
@ -161,14 +161,26 @@ public class FuzzyMethodContract extends AbstractFuzzyMember<MethodInfo> {
|
||||
/**
|
||||
* Add a new required parameter whose type must be a superclass of the given type.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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<MethodInfo> {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new required parameter whose type must be a derived class of the given class.
|
||||
* <p>
|
||||
* 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.
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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<Plugin> 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<Plugin> plugins) {
|
||||
Server mockServer = mock(Server.class);
|
||||
PluginManager manager = mock(PluginManager.class);
|
||||
|
||||
when(mockServer.getPluginManager()).thenReturn(manager);
|
||||
when(manager.getPlugin(anyString())).thenAnswer(new Answer<Plugin>() {
|
||||
@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;
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
In neuem Issue referenzieren
Einen Benutzer sperren