Compare commits

..
25 Commits
Author SHA1 Message Date
Kristian S. Stangeland 0dc2bfef0c Handle exceptions in injected code.
This is very important, otherwise the server may crash unnecessarily.
2012-10-29 17:20:04 +01:00
Kristian S. Stangeland f9c0f212c1 Put UnsupportedListener at the package level. 2012-10-29 16:57:07 +01:00
Kristian S. Stangeland cbf4def71b Prevented map chunk listeners from crashing the server.
If the NetServerHandler injector fails and we revert to the network
field injector instead, any map chunk listener will crash the server
due to complications with Bukkit's ChunkCompressionThread. 

We avoid this by disabling map chunk listening entirely.
2012-10-29 16:54:53 +01:00
Kristian S. Stangeland 8636215b98 Add a couple of extra error handlers. 2012-10-29 04:21:12 +01:00
Kristian S. Stangeland 2239c1ea32 Add a better error reporter. 2012-10-29 04:17:53 +01:00
Kristian S. Stangeland 5e036185b8 Removed DEBUG message. 2012-10-29 02:39:05 +01:00
Kristian S. Stangeland 03da0e1974 Increment to 1.5.0 Release 2012-10-29 02:37:10 +01:00
Kristian S. Stangeland dfba924561 Clean up every static field, preempting potential ClassLoader leaks.
Note that this method is using some fairly ugly hacks and must be 
maintained/updated manually whenever a new class is added or removed.
2012-10-29 02:25:29 +01:00
Kristian S. Stangeland bdc41221db Fixed a serious misuse causing the creation of too many new classes.
The different injectors using CGLib was using a custom CallbackFilter
to optimize the resulting generated class. Unfortunately, the custom
filter didn't properly implement equals() and hashCode(), and so every
time a player logged, a new injector class had to be generated. 

This was fixed by making the injectors share a single CallbackFilter.

In addition, I've begun adding cleanup code that will reset all static
fields once the plugin has unloaded.
2012-10-29 01:42:22 +01:00
Kristian S. Stangeland eaf0b73c00 Increment version to 1.4.4-SNAPSHOT (skipping release of 1.4.3) 2012-10-28 06:10:31 +01:00
Kristian S. Stangeland cb2c7b4e0a Use a class cache when constructing the packet interceptor. 2012-10-28 06:09:29 +01:00
Kristian S. Stangeland 4e9b5009c8 Don't print messages to the console without a plugin prefix.
It's bad practice.
2012-10-23 00:54:41 +02:00
Kristian S. Stangeland 7536815e58 Set as SNAPSHOT in the plugin-file as well. 2012-10-23 00:41:53 +02:00
Kristian S. Stangeland 96ad954cf7 Prevent the PlayerQuitEvent from being fired twice. FIXES Ticket-2. 2012-10-23 00:37:35 +02:00
Kristian S. Stangeland 0aac8ebf0a Increment to 1.4.3 snapshot. 2012-10-21 22:40:26 +02:00
Kristian S. Stangeland a60dc3d778 Make the client packet reader thread-safe. 2012-10-21 22:39:56 +02:00
Kristian S. Stangeland 01c481dc93 Release of 1.4.2 2012-10-21 20:42:29 +02:00
Kristian S. Stangeland a51ad1f50f Properly remove previous hook, even if socket is NULL. FIXES Ticket-1. 2012-10-21 20:31:20 +02:00
Kristian S. Stangeland c08106a923 Increment to 1.4.2 snapshot. 2012-10-21 16:33:52 +02:00
Kristian S. Stangeland 63c468eff0 Packet constructor can now handle primitive parameter types. 2012-10-21 16:33:41 +02:00
Kristian S. Stangeland 43c0b5d8f1 Don't save NULL injectors in a ConcurrentHashMap. 2012-10-21 12:11:59 +02:00
Kristian S. Stangeland 331fc94190 Sending or receiving packets now work without listeners. 2012-10-19 16:27:49 +02:00
Kristian S. Stangeland caed0dcb11 Removed the spammy "Entity does not exist" error. 2012-10-19 00:54:03 +02:00
Kristian S. Stangeland 90f2caa5e8 Incremented to 1.4.1 2012-10-17 09:54:28 +02:00
Kristian S. Stangeland 69a5675161 Fixed a bug causing a StackOverflowException on packet construction. 2012-10-17 09:53:59 +02:00
34 changed files with 1276 additions and 348 deletions

No files matched your search

+1 -1
View File
@@ -4,7 +4,7 @@
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId>
<name>ProtocolLib</name>
<version>1.4.0</version>
<version>1.5.0</version>
<description>Provides read/write access to the Minecraft protocol.</description>
<url>http://dev.bukkit.org/server-mods/protocollib/</url>
<developers>
+1 -1
View File
@@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId>
<version>1.4.0</version>
<version>1.5.0</version>
<packaging>jar</packaging>
<description>Provides read/write access to the Minecraft protocol.</description>
@@ -18,11 +18,11 @@
package com.comphenix.protocol;
import java.util.Set;
import java.util.logging.Logger;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.async.AsyncListenerHandler;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
@@ -81,10 +81,10 @@ public interface AsynchronousManager {
public abstract PacketStream getPacketStream();
/**
* Retrieve the default error logger.
* @return Default logger.
* Retrieve the default error reporter.
* @return Default reporter.
*/
public abstract Logger getLogger();
public abstract ErrorReporter getErrorReporter();
/**
* Remove listeners, close threads and transmit every delayed packet.
@@ -0,0 +1,134 @@
package com.comphenix.protocol;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import com.comphenix.protocol.async.AsyncListenerHandler;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.BukkitUnwrapper;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.MethodUtils;
import com.comphenix.protocol.reflect.ObjectCloner;
import com.comphenix.protocol.reflect.PrimitiveUtils;
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
import com.comphenix.protocol.reflect.compiler.StructureCompiler;
import com.comphenix.protocol.reflect.instances.CollectionGenerator;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.reflect.instances.PrimitiveGenerator;
/**
* Used to fix ClassLoader leaks that may lead to filling up the permanent generation.
*
* @author Kristian
*/
class CleanupStaticMembers {
private ClassLoader loader;
private ErrorReporter reporter;
public CleanupStaticMembers(ClassLoader loader, ErrorReporter reporter) {
this.loader = loader;
this.reporter = reporter;
}
/**
* Ensure that the previous ClassLoader is not leaking.
*/
public void resetAll() {
// This list must always be updated
Class<?>[] publicClasses = {
AsyncListenerHandler.class, ListeningWhitelist.class, PacketContainer.class,
BukkitUnwrapper.class, DefaultInstances.class, CollectionGenerator.class,
PrimitiveGenerator.class, FuzzyReflection.class, MethodUtils.class,
BackgroundCompiler.class, StructureCompiler.class,
ObjectCloner.class, PrimitiveUtils.class, Packets.Server.class,
Packets.Client.class
};
String[] internalClasses = {
"com.comphenix.protocol.events.SerializedOfflinePlayer",
"com.comphenix.protocol.injector.player.InjectedServerConnection",
"com.comphenix.protocol.injector.player.NetworkFieldInjector",
"com.comphenix.protocol.injector.player.NetworkObjectInjector",
"com.comphenix.protocol.injector.player.NetworkServerInjector",
"com.comphenix.protocol.injector.player.PlayerInjector",
"com.comphenix.protocol.injector.player.TemporaryPlayerFactory",
"com.comphenix.protocol.injector.EntityUtilities",
"com.comphenix.protocol.injector.MinecraftRegistry",
"com.comphenix.protocol.injector.PacketInjector",
"com.comphenix.protocol.injector.ReadPacketModifier",
"com.comphenix.protocol.injector.StructureCache",
"com.comphenix.protocol.reflect.compiler.BoxingHelper",
"com.comphenix.protocol.reflect.compiler.MethodDescriptor"
};
resetClasses(publicClasses);
resetClasses(getClasses(loader, internalClasses));
}
private void resetClasses(Class<?>[] classes) {
// Reset each class one by one
for (Class<?> clazz : classes) {
resetClass(clazz);
}
}
private void resetClass(Class<?> clazz) {
for (Field field : clazz.getFields()) {
Class<?> type = field.getType();
// Only check static non-primitive fields. We also skip strings.
if (Modifier.isStatic(field.getModifiers()) &&
!type.isPrimitive() && !type.equals(String.class)) {
try {
setFinalStatic(field, null);
} catch (IllegalAccessException e) {
// Just inform the player
reporter.reportWarning(this, "Unable to reset field " + field.getName() + ": " + e.getMessage(), e);
}
}
}
}
// HACK! HAACK!
private static void setFinalStatic(Field field, Object newValue) throws IllegalAccessException {
int modifier = field.getModifiers();
boolean isFinal = Modifier.isFinal(modifier);
Field modifiersField = isFinal ? FieldUtils.getField(Field.class, "modifiers", true) : null;
// We have to remove the final field first
if (isFinal) {
FieldUtils.writeField(modifiersField, field, modifier & ~Modifier.FINAL, true);
}
// Now we can safely modify the field
FieldUtils.writeStaticField(field, newValue, true);
// Revert modifier
if (isFinal) {
FieldUtils.writeField(modifiersField, field, modifier, true);
}
}
private Class<?>[] getClasses(ClassLoader loader, String[] names) {
List<Class<?>> output = new ArrayList<Class<?>>();
for (String name : names) {
try {
output.add(loader.loadClass(name));
} catch (ClassNotFoundException e) {
// Warn the user
reporter.reportWarning(this, "Unable to unload class " + name, e);
}
}
return output.toArray(new Class<?>[0]);
}
}
@@ -18,7 +18,6 @@
package com.comphenix.protocol;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bukkit.Server;
@@ -26,6 +25,8 @@ import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import com.comphenix.protocol.async.AsyncFilterManager;
import com.comphenix.protocol.error.DetailedErrorReporter;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.MonitorAdapter;
import com.comphenix.protocol.events.PacketEvent;
@@ -44,9 +45,12 @@ public class ProtocolLibrary extends JavaPlugin {
// There should only be one protocol manager, so we'll make it static
private static PacketFilterManager protocolManager;
// Error logger
// Information logger
private Logger logger;
// Error reporter
private ErrorReporter reporter;
// Metrics and statistisc
private Statistics statistisc;
@@ -67,39 +71,55 @@ public class ProtocolLibrary extends JavaPlugin {
@Override
public void onLoad() {
logger = getLoggerSafely();
unhookTask = new DelayedSingleTask(this);
protocolManager = new PacketFilterManager(getClassLoader(), getServer(), unhookTask, logger);
// Add global parameters
DetailedErrorReporter reporter = new DetailedErrorReporter();
try {
logger = getLoggerSafely();
unhookTask = new DelayedSingleTask(this);
protocolManager = new PacketFilterManager(getClassLoader(), getServer(), unhookTask, reporter);
reporter.addGlobalParameter("manager", protocolManager);
} catch (Throwable e) {
reporter.reportDetailed(this, "Cannot load ProtocolLib.", e, protocolManager);
}
}
@Override
public void onEnable() {
Server server = getServer();
PluginManager manager = server.getPluginManager();
// Initialize background compiler
if (backgroundCompiler == null) {
backgroundCompiler = new BackgroundCompiler(getClassLoader());
BackgroundCompiler.setInstance(backgroundCompiler);
}
// Notify server managers of incompatible plugins
checkForIncompatibility(manager);
// Player login and logout events
protocolManager.registerEvents(manager, this);
try {
Server server = getServer();
PluginManager manager = server.getPluginManager();
// Initialize background compiler
if (backgroundCompiler == null) {
backgroundCompiler = new BackgroundCompiler(getClassLoader());
BackgroundCompiler.setInstance(backgroundCompiler);
}
// Notify server managers of incompatible plugins
checkForIncompatibility(manager);
// Worker that ensures that async packets are eventually sent
createAsyncTask(server);
//toggleDebugListener();
// Player login and logout events
protocolManager.registerEvents(manager, this);
// Worker that ensures that async packets are eventually sent
createAsyncTask(server);
//toggleDebugListener();
} catch (Throwable e) {
reporter.reportDetailed(this, "Cannot enable ProtocolLib.", e);
disablePlugin();
return;
}
// Try to enable statistics
try {
statistisc = new Statistics(this);
} catch (IOException e) {
logger.log(Level.SEVERE, "Unable to enable metrics.", e);
reporter.reportDetailed(this, "Unable to enable metrics.", e, statistisc);
} catch (Throwable e) {
logger.log(Level.SEVERE, "Metrics cannot be enabled. Incompatible Bukkit version.", e);
reporter.reportDetailed(this, "Metrics cannot be enabled. Incompatible Bukkit version.", e, statistisc);
}
}
@@ -117,7 +137,7 @@ public class ProtocolLibrary extends JavaPlugin {
public void onPacketReceiving(PacketEvent event) {
Object handle = event.getPacket().getHandle();
System.out.println(String.format(
logger.info(String.format(
"RECEIVING %s@%s from %s.",
handle.getClass().getSimpleName(), handle.hashCode(), event.getPlayer().getName()
));
@@ -126,7 +146,7 @@ public class ProtocolLibrary extends JavaPlugin {
public void onPacketSending(PacketEvent event) {
Object handle = event.getPacket().getHandle();
System.out.println(String.format(
logger.info(String.format(
"SENDING %s@%s from %s.",
handle.getClass().getSimpleName(), handle.hashCode(), event.getPlayer().getName()
));
@@ -136,6 +156,13 @@ public class ProtocolLibrary extends JavaPlugin {
debugListener = !debugListener;
}
/**
* Disable the current plugin.
*/
private void disablePlugin() {
getServer().getPluginManager().disablePlugin(this);
}
private void createAsyncTask(Server server) {
try {
if (asyncPacketTask >= 0)
@@ -154,7 +181,7 @@ public class ProtocolLibrary extends JavaPlugin {
} catch (Throwable e) {
if (asyncPacketTask == -1) {
logger.log(Level.SEVERE, "Unable to create packet timeout task.", e);
reporter.reportDetailed(this, "Unable to create packet timeout task.", e);
}
}
}
@@ -166,7 +193,7 @@ public class ProtocolLibrary extends JavaPlugin {
for (String plugin : incompatiblePlugins) {
if (manager.getPlugin(plugin) != null) {
// Check for versions, ect.
logger.severe("Detected incompatible plugin: " + plugin);
reporter.reportWarning(this, "Detected incompatible plugin: " + plugin);
}
}
}
@@ -190,6 +217,10 @@ public class ProtocolLibrary extends JavaPlugin {
protocolManager.close();
protocolManager = null;
statistisc = null;
// Leaky ClassLoader begone!
CleanupStaticMembers cleanup = new CleanupStaticMembers(getClassLoader(), reporter);
cleanup.resetAll();
}
/**
@@ -21,7 +21,6 @@ import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitScheduler;
@@ -29,6 +28,7 @@ import org.bukkit.scheduler.BukkitScheduler;
import com.comphenix.protocol.AsynchronousManager;
import com.comphenix.protocol.PacketStream;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
@@ -49,7 +49,7 @@ public class AsyncFilterManager implements AsynchronousManager {
private PacketProcessingQueue clientProcessingQueue;
private PacketSendingQueue clientQueue;
private Logger logger;
private ErrorReporter reporter;
// The likely main thread
private Thread mainThread;
@@ -66,7 +66,7 @@ public class AsyncFilterManager implements AsynchronousManager {
// Whether or not we're currently cleaning up
private volatile boolean cleaningUp;
public AsyncFilterManager(Logger logger, BukkitScheduler scheduler, ProtocolManager manager) {
public AsyncFilterManager(ErrorReporter reporter, BukkitScheduler scheduler, ProtocolManager manager) {
// Server packets are synchronized already
this.serverQueue = new PacketSendingQueue(false);
@@ -80,7 +80,7 @@ public class AsyncFilterManager implements AsynchronousManager {
this.scheduler = scheduler;
this.manager = manager;
this.logger = logger;
this.reporter = reporter;
this.mainThread = Thread.currentThread();
}
@@ -267,10 +267,10 @@ public class AsyncFilterManager implements AsynchronousManager {
}
@Override
public Logger getLogger() {
return logger;
public ErrorReporter getErrorReporter() {
return reporter;
}
@Override
public void cleanupAll() {
cleaningUp = true;
@@ -22,7 +22,6 @@ import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import org.bukkit.plugin.Plugin;
@@ -54,7 +53,7 @@ public class AsyncListenerHandler {
private static final AtomicInteger nextID = new AtomicInteger();
// Default queue capacity
private static int DEFAULT_CAPACITY = 1024;
private static final int DEFAULT_CAPACITY = 1024;
// Cancel the async handler
private volatile boolean cancelled;
@@ -113,10 +112,6 @@ public class AsyncListenerHandler {
PacketListener getNullPacketListener() {
return nullPacketListener;
}
private String getPluginName() {
return PacketAdapter.getPluginName(listener);
}
/**
* Retrieve the plugin associated with this async listener.
@@ -442,8 +437,7 @@ public class AsyncListenerHandler {
} catch (Throwable e) {
// Minecraft doesn't want your Exception.
filterManager.getLogger().log(Level.SEVERE,
"Unhandled exception occured in onAsyncPacket() for " + getPluginName(), e);
filterManager.getErrorReporter().reportMinimal(listener.getPlugin(), "onAsyncPacket()", e);
}
// Now, get the next non-cancelled listener
@@ -205,7 +205,7 @@ class PacketSendingQueue {
} catch (PlayerLoggedOutException e) {
System.out.println(String.format(
"Warning: Dropped packet index %s of ID %s",
"[ProtocolLib] Warning: Dropped packet index %s of ID %s",
marker.getOriginalSendingIndex(), event.getPacketID()
));
@@ -0,0 +1,268 @@
package com.comphenix.protocol.error;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
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.events.PacketAdapter;
import com.google.common.primitives.Primitives;
/**
* Internal class used to handle exceptions.
*
* @author Kristian
*/
public class DetailedErrorReporter implements ErrorReporter {
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/";
public static final String PLUGIN_NAME = "ProtocolLib";
// We don't want to spam the server
public static final int DEFAULT_MAX_ERROR_COUNT = 20;
protected String prefix;
protected String supportURL;
protected int errorCount;
protected int maxErrorCount;
protected Logger logger;
// 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() {
this(DEFAULT_PREFIX, DEFAULT_SUPPORT_URL);
}
/**
* Create a central error reporting system.
* @param prefix - default line prefix.
* @param supportURL - URL to report the error.
*/
public DetailedErrorReporter(String prefix, String supportURL) {
this(prefix, supportURL, DEFAULT_MAX_ERROR_COUNT, getBukkitLogger());
}
// Attempt to get the logger.
private static Logger getBukkitLogger() {
try {
return Bukkit.getLogger();
} catch (Throwable e) {
return Logger.getLogger("Minecraft");
}
}
/**
* Create a central error reporting system.
* @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(String prefix, String supportURL, int maxErrorCount, Logger logger) {
this.prefix = prefix;
this.supportURL = supportURL;
this.maxErrorCount = maxErrorCount;
this.logger = logger;
}
@Override
public void reportMinimal(Plugin sender, String methodName, Throwable error) {
logger.log(Level.SEVERE, "[" + PLUGIN_NAME + "] Unhandled exception occured in " + methodName + " for " +
PacketAdapter.getPluginName(sender), error);
}
@Override
public void reportWarning(Object sender, String message) {
logger.log(Level.WARNING, "[" + PLUGIN_NAME + "] [" + getSenderName(sender) + "] " + message);
}
@Override
public void reportWarning(Object sender, String message, Throwable error) {
logger.log(Level.WARNING, "[" + PLUGIN_NAME + "] [" + getSenderName(sender) + "] " + message, error);
}
private String getSenderName(Object sender) {
if (sender != null)
return sender.getClass().getSimpleName();
else
return "NULL";
}
@Override
public void reportDetailed(Object sender, String message, Throwable error, Object... parameters) {
// Do not overtly spam the server!
if (++errorCount > maxErrorCount) {
String maxReached = String.format("Reached maxmimum error count. Cannot pass error %s from %s.", error, sender);
logger.severe(maxReached);
return;
}
StringWriter text = new StringWriter();
PrintWriter writer = new PrintWriter(text);
// Helpful message
writer.println("[ProtocolLib] INTERNAL ERROR: " + message);
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 (error != null)
error.printStackTrace(writer);
// Data dump!
writer.println(" ===== DUMP =====");
// Relevant parameters
if (parameters != null && parameters.length > 0) {
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));
}
}
// 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));
// Add the server version too
if (Bukkit.getServer() != null) {
writer.println("Server:");
writer.println(addPrefix(Bukkit.getServer().getVersion(), SECOND_LEVEL_PREFIX));
}
// Make sure it is reported
logger.severe(addPrefix(text.toString(), 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);
}
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
logger.warning("Cannot find Apache Commons. Object introspection disabled.");
apacheCommonsMissing = true;
}
// Just use toString()
return String.format("%s", value);
}
}
/**
* 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());
}
public int getErrorCount() {
return errorCount;
}
public void setErrorCount(int errorCount) {
this.errorCount = errorCount;
}
public int getMaxErrorCount() {
return maxErrorCount;
}
public void setMaxErrorCount(int maxErrorCount) {
this.maxErrorCount = maxErrorCount;
}
/**
* Adds the given global parameter. It will be included in every error report.
* @param key - name of parameter.
* @param value - the global parameter itself.
*/
public void addGlobalParameter(String key, Object value) {
globalParameters.put(key, value);
}
public Object getGlobalParameter(String key) {
return globalParameters.get(key);
}
public void clearGlobalParameters() {
globalParameters.clear();
}
public Set<String> globalParameters() {
return globalParameters.keySet();
}
public String getSupportURL() {
return supportURL;
}
public void setSupportURL(String supportURL) {
this.supportURL = supportURL;
}
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public Logger getLogger() {
return logger;
}
public void setLogger(Logger logger) {
this.logger = logger;
}
}
@@ -0,0 +1,39 @@
package com.comphenix.protocol.error;
import org.bukkit.plugin.Plugin;
public interface ErrorReporter {
/**
* Prints a small minimal error report about an exception from another plugin.
* @param sender - the other plugin.
* @param methodName - name of the caller method.
* @param error - the exception itself.
*/
public abstract void reportMinimal(Plugin sender, String methodName, Throwable error);
/**
* Prints a warning message from the current plugin.
* @param sender - the object containing the caller method.
* @param message - error message.
*/
public abstract void reportWarning(Object sender, String message);
/**
* Prints a warning message from the current plugin.
* @param sender - the object containing the caller method.
* @param message - error message.
* @param error - the exception that was thrown.
*/
public abstract void reportWarning(Object sender, String message, Throwable error);
/**
* Prints a detailed error report about an unhandled exception.
* @param sender - the object containing the caller method.
* @param message - an error message to include.
* @param error - the exception that was thrown in the caller method.
* @param parameters - parameters from the caller method.
*/
public abstract void reportDetailed(Object sender, String message, Throwable error, Object... parameters);
}
@@ -168,12 +168,20 @@ public abstract class PacketAdapter implements PacketListener {
/**
* Retrieves the name of the plugin that has been associated with the listener.
* @param listener - the listener.
* @return Name of the associated plugin.
*/
public static String getPluginName(PacketListener listener) {
Plugin plugin = listener.getPlugin();
return getPluginName(listener.getPlugin());
}
/**
* Retrieves the name of the given plugin.
* @param plugin - the plugin.
* @return Name of the given plugin.
*/
public static String getPluginName(Plugin plugin) {
// Try to get the plugin name
try {
if (plugin == null)
@@ -303,7 +303,6 @@ public class PacketContainer implements Serializable {
}
}
System.out.println("Entity doesn't exist.");
return null;
}
@@ -46,4 +46,25 @@ public interface ListenerInvoker {
* @return The packet ID.
*/
public abstract int getPacketID(Packet packet);
/**
* Associate a given class with the given packet ID. Internal method.
* @param clazz - class to associate.
* @param packetID - the packet ID.
*/
public abstract void unregisterPacketClass(Class<?> clazz);
/**
* Remove a given class from the packet registry. Internal method.
* @param clazz - class to remove.
*/
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);
}
@@ -157,7 +157,7 @@ class MinecraftRegistry {
/**
* Retrieves the correct packet class from a given packet ID.
* @param packetID - the packet ID.
* @param vanilla - whether or not to look for vanilla classes, not injected classes.
* @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) {
@@ -172,7 +172,9 @@ class MinecraftRegistry {
// Will most likely not be used
for (Map.Entry<Class, Integer> entry : getPacketToID().entrySet()) {
if (Objects.equal(entry.getValue(), packetID)) {
return entry.getKey();
// Attempt to get the vanilla class here too
if (!forceVanilla || entry.getKey().getName().startsWith("net.minecraft.server"))
return entry.getKey();
}
}
@@ -25,6 +25,7 @@ import net.minecraft.server.Packet;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.PrimitiveUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
@@ -40,7 +41,7 @@ public class PacketConstructor {
* <p>
* Remember to call withPacket().
*/
public static final PacketConstructor DEFAULT = new PacketConstructor(null);
public static PacketConstructor DEFAULT = new PacketConstructor(null);
// The constructor method that's actually responsible for creating the packet
private Constructor<?> constructorMethod;
@@ -176,7 +177,17 @@ public class PacketConstructor {
// Determine if the types are similar
if (params.length == types.length) {
for (int i = 0; i < params.length; i++) {
if (!params[i].isAssignableFrom(types[i])) {
Class<?> inputType = types[i];
Class<?> paramType = params[i];
// The input type is always wrapped
if (PrimitiveUtils.isPrimitive(paramType)) {
// Wrap it
paramType = PrimitiveUtils.wrap(paramType);
}
// Compare assignability
if (!paramType.isAssignableFrom(inputType)) {
return false;
}
}
@@ -23,9 +23,8 @@ import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
@@ -50,6 +49,7 @@ import com.comphenix.protocol.AsynchronousManager;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.async.AsyncFilterManager;
import com.comphenix.protocol.async.AsyncMarker;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.*;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
import com.comphenix.protocol.reflect.FieldAccessException;
@@ -117,8 +117,8 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
// The default class loader
private ClassLoader classLoader;
// Error logger
private Logger logger;
// Error repoter
private ErrorReporter reporter;
// The current server
private Server server;
@@ -134,13 +134,16 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
private AtomicInteger phaseLoginCount = new AtomicInteger(0);
private AtomicInteger phasePlayingCount = new AtomicInteger(0);
// Whether or not plugins are using the send/receive methods
private AtomicBoolean packetCreation = new AtomicBoolean();
/**
* Only create instances of this class if protocol lib is disabled.
* @param unhookTask
*/
public PacketFilterManager(ClassLoader classLoader, Server server, DelayedSingleTask unhookTask, Logger logger) {
if (logger == null)
throw new IllegalArgumentException("logger cannot be NULL.");
public PacketFilterManager(ClassLoader classLoader, Server server, DelayedSingleTask unhookTask, ErrorReporter reporter) {
if (reporter == null)
throw new IllegalArgumentException("reporter cannot be NULL.");
if (classLoader == null)
throw new IllegalArgumentException("classLoader cannot be NULL.");
@@ -151,7 +154,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
this.unhookTask = unhookTask;
this.server = server;
this.classLoader = classLoader;
this.logger = logger;
this.reporter = reporter;
// Used to determine if injection is needed
Predicate<GamePhase> isInjectionNecessary = new Predicate<GamePhase>() {
@@ -170,20 +173,20 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
try {
// Initialize injection mangers
this.playerInjection = new PlayerInjectionHandler(classLoader, logger, isInjectionNecessary, this, server);
this.packetInjector = new PacketInjector(classLoader, this, playerInjection);
this.asyncFilterManager = new AsyncFilterManager(logger, server.getScheduler(), this);
this.playerInjection = new PlayerInjectionHandler(classLoader, reporter, isInjectionNecessary, this, packetListeners, server);
this.packetInjector = new PacketInjector(classLoader, this, playerInjection, reporter);
this.asyncFilterManager = new AsyncFilterManager(reporter, server.getScheduler(), this);
// Attempt to load the list of server and client packets
try {
this.serverPackets = MinecraftRegistry.getServerPackets();
this.clientPackets = MinecraftRegistry.getClientPackets();
} catch (FieldAccessException e) {
logger.log(Level.WARNING, "Cannot load server and client packet list.", e);
reporter.reportWarning(this, "Cannot load server and client packet list.", e);
}
} catch (IllegalAccessException e) {
logger.log(Level.SEVERE, "Unable to initialize packet injector.", e);
reporter.reportWarning(this, "Unable to initialize packet injector.", e);
}
}
@@ -206,15 +209,8 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
*/
public void setPlayerHook(PlayerInjectHooks playerHook) {
playerInjection.setPlayerHook(playerHook);
// Make sure the current listeners are compatible
playerInjection.checkListener(packetListeners);
}
public Logger getLogger() {
return logger;
}
@Override
public ImmutableSet<PacketListener> getPacketListeners() {
return ImmutableSet.copyOf(packetListeners);
@@ -394,9 +390,9 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
// Process synchronous events
if (sending)
packetListeners.invokePacketSending(logger, event);
packetListeners.invokePacketSending(reporter, event);
else
packetListeners.invokePacketRecieving(logger, event);
packetListeners.invokePacketRecieving(reporter, event);
// To cancel asynchronous processing, use the async marker
if (!event.isCancelled() && !hasAsyncCancelled(event.getAsyncMarker())) {
@@ -434,7 +430,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
if (serverPackets != null && serverPackets.contains(packetID))
playerInjection.addPacketHandler(packetID);
else
logger.warning(String.format(
reporter.reportWarning(this, String.format(
"[%s] Unsupported server packet ID in current Minecraft version: %s",
PacketAdapter.getPluginName(listener), packetID
));
@@ -445,7 +441,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
if (clientPackets != null && clientPackets.contains(packetID))
packetInjector.addPacketHandler(packetID);
else
logger.warning(String.format(
reporter.reportWarning(this, String.format(
"[%s] Unsupported client packet ID in current Minecraft version: %s",
PacketAdapter.getPluginName(listener), packetID
));
@@ -481,6 +477,9 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
throw new IllegalArgumentException("reciever cannot be NULL.");
if (packet == null)
throw new IllegalArgumentException("packet cannot be NULL.");
// We may have to enable player injection indefinitely after this
if (packetCreation.compareAndSet(false, true))
incrementPhases(GamePhase.PLAYING);
playerInjection.sendServerPacket(reciever, packet, filters);
}
@@ -492,11 +491,13 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
@Override
public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters) throws IllegalAccessException, InvocationTargetException {
if (sender == null)
throw new IllegalArgumentException("sender cannot be NULL.");
if (packet == null)
throw new IllegalArgumentException("packet cannot be NULL.");
// And here too
if (packetCreation.compareAndSet(false, true))
incrementPhases(GamePhase.PLAYING);
Packet mcPacket = packet.getHandle();
@@ -585,26 +586,43 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
manager.registerEvents(new Listener() {
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void onPrePlayerJoin(PlayerJoinEvent event) {
// Let's clean up the other injection first.
playerInjection.uninjectPlayer(event.getPlayer().getAddress());
try {
// Let's clean up the other injection first.
playerInjection.uninjectPlayer(event.getPlayer().getAddress());
} catch (Exception e) {
reporter.reportDetailed(PacketFilterManager.this, "Unable to uninject net handler for player.", e, event);
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPlayerJoin(PlayerJoinEvent event) {
// This call will be ignored if no listeners are registered
playerInjection.injectPlayer(event.getPlayer());
try {
// This call will be ignored if no listeners are registered
playerInjection.injectPlayer(event.getPlayer());
} catch (Exception e) {
reporter.reportDetailed(PacketFilterManager.this, "Unable to inject player.", e, event);
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPlayerQuit(PlayerQuitEvent event) {
playerInjection.uninjectPlayer(event.getPlayer());
try {
playerInjection.handleDisconnect(event.getPlayer());
playerInjection.uninjectPlayer(event.getPlayer());
} catch (Exception e) {
reporter.reportDetailed(PacketFilterManager.this, "Unable to uninject logged off player.", e, event);
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPluginDisabled(PluginDisableEvent event) {
// Clean up in case the plugin forgets
if (event.getPlugin() != plugin) {
removePacketListeners(event.getPlugin());
try {
// Clean up in case the plugin forgets
if (event.getPlugin() != plugin) {
removePacketListeners(event.getPlugin());
}
} catch (Exception e) {
reporter.reportDetailed(PacketFilterManager.this, "Unable handle disabled plugin.", e, event);
}
}
@@ -640,6 +658,21 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
return MinecraftRegistry.getPacketToID().get(packet.getClass());
}
@Override
public void registerPacketClass(Class<?> clazz, int packetID) {
MinecraftRegistry.getPacketToID().put(clazz, packetID);
}
@Override
public void unregisterPacketClass(Class<?> clazz) {
MinecraftRegistry.getPacketToID().remove(clazz);
}
@Override
public Class<?> getPacketClassFromID(int packetID, boolean forceVanilla) {
return MinecraftRegistry.getPacketClassFromID(packetID, forceVanilla);
}
// Yes, this is crazy.
@SuppressWarnings({ "unchecked", "rawtypes" })
private void registerOld(PluginManager manager, Plugin plugin) {
@@ -680,10 +713,14 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
Object event = args[0];
// Check for the correct event
if (event instanceof PlayerJoinEvent)
playerInjection.injectPlayer(((PlayerJoinEvent) event).getPlayer());
else if (event instanceof PlayerQuitEvent)
playerInjection.uninjectPlayer(((PlayerQuitEvent) event).getPlayer());
if (event instanceof PlayerJoinEvent) {
Player player = ((PlayerJoinEvent) event).getPlayer();
playerInjection.injectPlayer(player);
} else if (event instanceof PlayerQuitEvent) {
Player player = ((PlayerQuitEvent) event).getPlayer();
playerInjection.handleDisconnect(player);
playerInjection.uninjectPlayer(player);
}
}
return null;
}
@@ -31,6 +31,7 @@ import net.minecraft.server.Packet;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
@@ -51,6 +52,9 @@ class PacketInjector {
// The packet filter manager
private ListenerInvoker manager;
// Error reporter
private ErrorReporter reporter;
// Allows us to determine the sender
private PlayerInjectionHandler playerInjection;
@@ -61,11 +65,12 @@ class PacketInjector {
private ClassLoader classLoader;
public PacketInjector(ClassLoader classLoader, ListenerInvoker manager,
PlayerInjectionHandler playerInjection) throws IllegalAccessException {
PlayerInjectionHandler playerInjection, ErrorReporter reporter) throws IllegalAccessException {
this.classLoader = classLoader;
this.manager = manager;
this.playerInjection = playerInjection;
this.reporter = reporter;
this.readModifier = new ConcurrentHashMap<Integer, ReadPacketModifier>();
initialize();
}
@@ -129,12 +134,11 @@ class PacketInjector {
// Subclass the specific packet class
ex.setSuperclass(old);
ex.setCallbackType(ReadPacketModifier.class);
ex.setUseCache(false);
ex.setClassLoader(classLoader);
Class proxy = ex.createClass();
// Create the proxy handler
ReadPacketModifier modifier = new ReadPacketModifier(packetID, this);
ReadPacketModifier modifier = new ReadPacketModifier(packetID, this, reporter);
readModifier.put(packetID, modifier);
// Add a static reference
@@ -142,10 +146,10 @@ class PacketInjector {
try {
// Override values
putMethod.invoke(intHashMap, packetID, proxy);
previous.put(packetID, old);
registry.put(proxy, packetID);
overwritten.put(packetID, proxy);
putMethod.invoke(intHashMap, packetID, proxy);
return true;
} catch (IllegalArgumentException e) {
@@ -20,9 +20,12 @@ package com.comphenix.protocol.injector;
import java.io.DataInputStream;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
@@ -35,16 +38,23 @@ class ReadPacketModifier implements MethodInterceptor {
@SuppressWarnings("rawtypes")
private static Class[] parameters = { DataInputStream.class };
// A cancel marker
private static final Object CANCEL_MARKER = new Object();
// Common for all packets of the same type
private PacketInjector packetInjector;
private int packetID;
// Whether or not a packet has been cancelled
private static WeakHashMap<Object, Object> override = new WeakHashMap<Object, Object>();
// Report errors
private ErrorReporter reporter;
public ReadPacketModifier(int packetID, PacketInjector packetInjector) {
// Whether or not a packet has been cancelled
private static Map<Object, Object> override = Collections.synchronizedMap(new WeakHashMap<Object, Object>());
public ReadPacketModifier(int packetID, PacketInjector packetInjector, ErrorReporter reporter) {
this.packetID = packetID;
this.packetInjector = packetInjector;
this.reporter = reporter;
}
/**
@@ -75,11 +85,12 @@ class ReadPacketModifier implements MethodInterceptor {
return proxy.invokeSuper(thisObj, args);
}
if (override.containsKey(thisObj)) {
Object overridenObject = override.get(thisObj);
// Atomic retrieval
Object overridenObject = override.get(thisObj);
if (overridenObject != null) {
// This packet has been cancelled
if (overridenObject == null) {
if (overridenObject == CANCEL_MARKER) {
// So, cancel all void methods
if (method.getReturnType().equals(Void.TYPE))
return null;
@@ -96,27 +107,32 @@ class ReadPacketModifier implements MethodInterceptor {
if (returnValue == null &&
Arrays.equals(method.getParameterTypes(), parameters)) {
// We need this in order to get the correct player
DataInputStream input = (DataInputStream) args[0];
// Let the people know
PacketContainer container = new PacketContainer(packetID, (Packet) thisObj);
PacketEvent event = packetInjector.packetRecieved(container, input);
// Handle override
if (event != null) {
Packet result = event.getPacket().getHandle();
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, (Packet) thisObj);
PacketEvent event = packetInjector.packetRecieved(container, input);
if (event.isCancelled()) {
override.put(thisObj, null);
} else if (!objectEquals(thisObj, result)) {
override.put(thisObj, result);
}
// Update DataInputStream next time
if (!event.isCancelled() && packetID == Packets.Server.KEY_RESPONSE) {
packetInjector.scheduleDataInputRefresh(event.getPlayer());
// Handle override
if (event != null) {
Packet result = event.getPacket().getHandle();
if (event.isCancelled()) {
override.put(thisObj, CANCEL_MARKER);
} else if (!objectEquals(thisObj, result)) {
override.put(thisObj, result);
}
// Update DataInputStream next time
if (!event.isCancelled() && packetID == Packets.Server.KEY_RESPONSE) {
packetInjector.scheduleDataInputRefresh(event.getPlayer());
}
}
} catch (Throwable e) {
// Minecraft cannot handle this error
reporter.reportDetailed(this, "Cannot handle clienet packet.", e, args[0]);
}
}
@@ -18,11 +18,9 @@
package com.comphenix.protocol.injector;
import java.util.Collection;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.comphenix.protocol.concurrency.AbstractConcurrentListenerMultimap;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
@@ -35,10 +33,10 @@ class SortedPacketListenerList extends AbstractConcurrentListenerMultimap<Packet
/**
* Invokes the given packet event for every registered listener.
* @param logger - the logger that will be used to inform about listener exceptions.
* @param reporter - the error reporter that will be used to inform about listener exceptions.
* @param event - the packet event to invoke.
*/
public void invokePacketRecieving(Logger logger, PacketEvent event) {
public void invokePacketRecieving(ErrorReporter reporter, PacketEvent event) {
Collection<PrioritizedListener<PacketListener>> list = getListener(event.getPacketID());
if (list == null)
@@ -50,19 +48,17 @@ class SortedPacketListenerList extends AbstractConcurrentListenerMultimap<Packet
element.getListener().onPacketReceiving(event);
} catch (Throwable e) {
// Minecraft doesn't want your Exception.
logger.log(Level.SEVERE,
"Exception occured in onPacketReceiving() for " +
PacketAdapter.getPluginName(element.getListener()), e);
reporter.reportMinimal(element.getListener().getPlugin(), "onPacketReceiving()", e);
}
}
}
/**
* Invokes the given packet event for every registered listener.
* @param logger - the logger that will be used to inform about listener exceptions.
* @param reporter - the error reporter that will be used to inform about listener exceptions.
* @param event - the packet event to invoke.
*/
public void invokePacketSending(Logger logger, PacketEvent event) {
public void invokePacketSending(ErrorReporter reporter, PacketEvent event) {
Collection<PrioritizedListener<PacketListener>> list = getListener(event.getPacketID());
if (list == null)
@@ -73,9 +69,7 @@ class SortedPacketListenerList extends AbstractConcurrentListenerMultimap<Packet
element.getListener().onPacketSending(event);
} catch (Throwable e) {
// Minecraft doesn't want your Exception.
logger.log(Level.SEVERE,
"Exception occured in onPacketSending() for " +
PacketAdapter.getPluginName(element.getListener()), e);
reporter.reportMinimal(element.getListener().getPlugin(), "onPacketSending()", e);
}
}
}
@@ -22,6 +22,7 @@ import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Set;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.player.NetworkFieldInjector.FakePacket;
import net.minecraft.server.Packet;
@@ -73,7 +74,7 @@ class InjectedArrayList extends ArrayList<Packet> {
// We'll use the FakePacket marker instead of preventing the filters
injector.sendServerPacket(createNegativePacket(packet), true);
}
// Collection.add contract
return true;
@@ -90,8 +91,10 @@ class InjectedArrayList extends ArrayList<Packet> {
* @return The inverted packet.
*/
Packet createNegativePacket(Packet source) {
Enhancer ex = new Enhancer();
Class<?> type = source.getClass();
ListenerInvoker invoker = injector.getInvoker();
int packetID = invoker.getPacketID(source);
Class<?> type = invoker.getPacketClassFromID(packetID, true);
// We want to subtract the byte amount that were added to the running
// total of outstanding packets. Otherwise, cancelling too many packets
@@ -111,22 +114,38 @@ class InjectedArrayList extends ArrayList<Packet> {
// }
// ect.
// }
Enhancer ex = new Enhancer();
ex.setSuperclass(type);
ex.setInterfaces(new Class[] { FakePacket.class } );
ex.setUseCache(true);
ex.setClassLoader(classLoader);
ex.setSuperclass(type);
ex.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
if (method.getReturnType().equals(int.class) && args.length == 0) {
Integer result = (Integer) proxy.invokeSuper(obj, args);
return -result;
} else {
return proxy.invokeSuper(obj, args);
}
}
});
ex.setCallbackType(InvertedIntegerCallback.class);
Class<?> proxyClass = ex.createClass();
// Temporarily associate the fake packet class
invoker.registerPacketClass(proxyClass, packetID);
Packet fake = (Packet) Enhancer.create(proxyClass, new InvertedIntegerCallback());
return (Packet) ex.create();
// Remove this association
invoker.unregisterPacketClass(proxyClass);
return fake;
}
/**
* Inverts the integer result of every integer method.
* @author Kristian
*/
private class InvertedIntegerCallback implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
if (method.getReturnType().equals(int.class) && args.length == 0) {
Integer result = (Integer) proxy.invokeSuper(obj, args);
return -result;
} else {
return proxy.invokeSuper(obj, args);
}
}
}
}
@@ -21,14 +21,13 @@ import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.minecraft.server.NetLoginHandler;
import net.sf.cglib.proxy.Factory;
import org.bukkit.Server;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.ObjectCloner;
@@ -55,16 +54,16 @@ class InjectedServerConnection {
private NetLoginInjector netLoginInjector;
private Server server;
private Logger logger;
private ErrorReporter reporter;
private boolean hasAttempted;
private boolean hasSuccess;
private Object minecraftServer = null;
public InjectedServerConnection(Logger logger, Server server, NetLoginInjector netLoginInjector) {
public InjectedServerConnection(ErrorReporter reporter, Server server, NetLoginInjector netLoginInjector) {
this.listFields = new ArrayList<VolatileField>();
this.replacedLists = new ArrayList<ReplacedArrayList<Object>>();
this.logger = logger;
this.reporter = reporter;
this.server = server;
this.netLoginInjector = netLoginInjector;
}
@@ -83,7 +82,7 @@ class InjectedServerConnection {
try {
minecraftServer = FieldUtils.readField(minecraftServerField, server, true);
} catch (IllegalAccessException e1) {
logger.log(Level.WARNING, "Cannot extract minecraft server from Bukkit.");
reporter.reportWarning(this, "Cannot extract minecraft server from Bukkit.");
return;
}
@@ -95,15 +94,13 @@ class InjectedServerConnection {
injectServerConnection();
} catch (IllegalArgumentException e) {
// DEBUG
logger.log(Level.WARNING, "Reverting to old 1.2.5 server connection injection.", e);
// Minecraft 1.2.5 or lower
injectListenerThread();
} catch (Exception e) {
// Oh damn - inform the player
logger.log(Level.SEVERE, "Cannot inject into server connection. Bad things will happen.", e);
reporter.reportDetailed(this, "Cannot inject into server connection. Bad things will happen.", e);
}
}
@@ -115,7 +112,7 @@ class InjectedServerConnection {
listenerThreadField = FuzzyReflection.fromObject(minecraftServer).
getFieldByType(".*NetworkListenThread");
} catch (RuntimeException e) {
logger.log(Level.SEVERE, "Cannot find listener thread in MinecraftServer.", e);
reporter.reportDetailed(this, "Cannot find listener thread in MinecraftServer.", e, minecraftServer);
return;
}
@@ -125,7 +122,7 @@ class InjectedServerConnection {
try {
listenerThread = listenerThreadField.get(minecraftServer);
} catch (Exception e) {
logger.log(Level.WARNING, "Unable to read the listener thread.", e);
reporter.reportWarning(this, "Unable to read the listener thread.", e);
return;
}
@@ -142,7 +139,7 @@ class InjectedServerConnection {
try {
serverConnection = serverConnectionMethod.invoke(minecraftServer);
} catch (Exception ex) {
logger.log(Level.WARNING, "Unable to retrieve server connection", ex);
reporter.reportDetailed(this, "Unable to retrieve server connection", ex, minecraftServer);
return;
}
@@ -154,7 +151,7 @@ class InjectedServerConnection {
// Verify the field count
if (matches.size() != 1)
logger.log(Level.WARNING, "Unexpected number of threads in " + serverConnection.getClass().getName());
reporter.reportWarning(this, "Unexpected number of threads in " + serverConnection.getClass().getName());
else
dedicatedThreadField = matches.get(0);
}
@@ -164,7 +161,7 @@ class InjectedServerConnection {
if (dedicatedThreadField != null)
injectEveryListField(FieldUtils.readField(dedicatedThreadField, serverConnection, true), 1);
} catch (IllegalAccessException e) {
logger.log(Level.WARNING, "Unable to retrieve net handler thread.", e);
reporter.reportWarning(this, "Unable to retrieve net handler thread.", e);
}
injectIntoList(serverConnection, listField);
@@ -186,7 +183,7 @@ class InjectedServerConnection {
// Warn about unexpected errors
if (lists.size() < minimum) {
logger.log(Level.WARNING, "Unable to inject " + minimum + " lists in " + container.getClass().getName());
reporter.reportWarning(this, "Unable to inject " + minimum + " lists in " + container.getClass().getName());
}
}
@@ -220,7 +217,12 @@ class InjectedServerConnection {
// Is this a normal Minecraft object?
if (!(inserting instanceof Factory)) {
// If so, copy the content of the old element to the new
ObjectCloner.copyTo(inserting, replacement, inserting.getClass());
try {
ObjectCloner.copyTo(inserting, replacement, inserting.getClass());
} catch (Throwable e) {
reporter.reportDetailed(InjectedServerConnection.this, "Cannot copy old " + inserting +
" to new.", e, inserting, replacement);
}
}
}
@@ -241,7 +243,7 @@ class InjectedServerConnection {
// Clean up?
if (removing instanceof NetLoginHandler) {
netLoginInjector.cleanup(removing);
}
}
}
};
}
@@ -4,12 +4,11 @@ import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.player.TemporaryPlayerFactory.InjectContainer;
import com.google.common.collect.Maps;
@@ -27,16 +26,16 @@ class NetLoginInjector {
private PlayerInjectionHandler injectionHandler;
private Server server;
// The current logger
private Logger logger;
// The current error rerporter
private ErrorReporter reporter;
private ReadWriteLock injectionLock = new ReentrantReadWriteLock();
// Used to create fake players
private TemporaryPlayerFactory tempPlayerFactory = new TemporaryPlayerFactory();
public NetLoginInjector(Logger logger, PlayerInjectionHandler injectionHandler, Server server) {
this.logger = logger;
public NetLoginInjector(ErrorReporter reporter, PlayerInjectionHandler injectionHandler, Server server) {
this.reporter = reporter;
this.injectionHandler = injectionHandler;
this.server = server;
}
@@ -63,12 +62,15 @@ class NetLoginInjector {
InjectContainer container = (InjectContainer) fakePlayer;
container.setInjector(injector);
// 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
logger.log(Level.WARNING, "Unable to hook NetLoginHandler.", e);
reporter.reportDetailed(this, "Unable to hook NetLoginHandler.", e, inserting);
return inserting;
} finally {
@@ -93,8 +95,34 @@ class NetLoginInjector {
PlayerInjector injected = injectedLogins.get(removing);
if (injected != null) {
injected.cleanupAll();
injectedLogins.remove(removing);
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());
// Update NetworkManager
if (newInjector == null) {
injectionHandler.uninjectPlayer(player);
} else {
injectionHandler.uninjectPlayer(player, false);
if (injected instanceof NetworkObjectInjector)
newInjector.setNetworkManager(injected.getNetworkManager(), true);
}
} catch (Throwable e) {
// Don't leak this to Minecraft
reporter.reportDetailed(this, "Cannot cleanup NetLoginHandler.", e, removing);
}
}
}
@@ -24,11 +24,11 @@ import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import org.bukkit.entity.Player;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
@@ -73,10 +73,10 @@ class NetworkFieldInjector extends PlayerInjector {
// Used to construct proxy objects
private ClassLoader classLoader;
public NetworkFieldInjector(ClassLoader classLoader, Logger logger, Player player,
public NetworkFieldInjector(ClassLoader classLoader, ErrorReporter reporter, Player player,
ListenerInvoker manager, IntegerSet sendingFilters) throws IllegalAccessException {
super(logger, player, manager);
super(reporter, player, manager);
this.classLoader = classLoader;
this.sendingFilters = sendingFilters;
}
@@ -123,11 +123,14 @@ class NetworkFieldInjector extends PlayerInjector {
}
@Override
public void checkListener(PacketListener listener) {
public UnsupportedListener checkListener(PacketListener listener) {
int[] unsupported = { Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK };
// Unfortunately, we don't support chunk packets
if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(),
Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK)) {
throw new IllegalStateException("The NETWORK_FIELD_INJECTOR hook doesn't support map chunk listeners.");
if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(), unsupported)) {
return new UnsupportedListener("The NETWORK_FIELD_INJECTOR hook doesn't support map chunk listeners.", unsupported);
} else {
return null;
}
}
@@ -166,7 +169,7 @@ class NetworkFieldInjector extends PlayerInjector {
}
@SuppressWarnings("unchecked")
public void cleanupAll() {
protected void cleanHook() {
// Clean up
for (VolatileField overriden : overridenLists) {
List<Packet> minecraftList = (List<Packet>) overriden.getOldValue();
@@ -191,6 +194,11 @@ class NetworkFieldInjector extends PlayerInjector {
overridenLists.clear();
}
@Override
public void handleDisconnect() {
// No need to do anything
}
@Override
public boolean canInject(GamePhase phase) {
// All phases should work
@@ -28,11 +28,11 @@ import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
import java.util.logging.Logger;
import org.bukkit.entity.Player;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
@@ -50,10 +50,13 @@ class NetworkObjectInjector extends PlayerInjector {
// Used to construct proxy objects
private ClassLoader classLoader;
// Shared callback filter - avoid creating a new class every time
private static CallbackFilter callbackFilter;
public NetworkObjectInjector(ClassLoader classLoader, Logger logger, Player player,
public NetworkObjectInjector(ClassLoader classLoader, ErrorReporter reporter, Player player,
ListenerInvoker invoker, IntegerSet sendingFilters) throws IllegalAccessException {
super(logger, player, invoker);
super(reporter, player, invoker);
this.sendingFilters = sendingFilters;
this.classLoader = classLoader;
}
@@ -85,11 +88,14 @@ class NetworkObjectInjector extends PlayerInjector {
}
@Override
public void checkListener(PacketListener listener) {
public UnsupportedListener checkListener(PacketListener listener) {
int[] unsupported = { Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK };
// Unfortunately, we don't support chunk packets
if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(),
Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK)) {
throw new IllegalStateException("The NETWORK_FIELD_INJECTOR hook doesn't support map chunk listeners.");
if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(), unsupported)) {
return new UnsupportedListener("The NETWORK_OBJECT_INJECTOR hook doesn't support map chunk listeners.", unsupported);
} else {
return null;
}
}
@@ -131,20 +137,25 @@ class NetworkObjectInjector extends PlayerInjector {
}
};
// Share callback filter - that way, we avoid generating a new class every time.
if (callbackFilter == null) {
callbackFilter = new CallbackFilter() {
@Override
public int accept(Method method) {
if (method.equals(queueMethod))
return 0;
else
return 1;
}
};
}
// Create our proxy object
Enhancer ex = new Enhancer();
ex.setClassLoader(classLoader);
ex.setSuperclass(networkInterface);
ex.setCallbacks(new Callback[] { queueFilter, dispatch });
ex.setCallbackFilter(new CallbackFilter() {
@Override
public int accept(Method method) {
if (method.equals(queueMethod))
return 0;
else
return 1;
}
});
ex.setCallbackFilter(callbackFilter);
// Inject it, if we can.
networkManagerRef.setValue(ex.create());
@@ -152,13 +163,18 @@ class NetworkObjectInjector extends PlayerInjector {
}
@Override
public void cleanupAll() {
protected void cleanHook() {
// Clean up
if (networkManagerRef != null && networkManagerRef.isCurrentSet()) {
networkManagerRef.revertValue();
}
}
@Override
public void handleDisconnect() {
// No need to do anything
}
@Override
public boolean canInject(GamePhase phase) {
// Works for all phases
@@ -17,9 +17,9 @@
package com.comphenix.protocol.injector.player;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.logging.Logger;
import net.minecraft.server.Packet;
import net.sf.cglib.proxy.Callback;
@@ -32,6 +32,7 @@ import net.sf.cglib.proxy.NoOp;
import org.bukkit.entity.Player;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.ListenerInvoker;
@@ -50,6 +51,9 @@ import com.comphenix.protocol.reflect.instances.ExistingGenerator;
*/
public class NetworkServerInjector extends PlayerInjector {
private volatile static CallbackFilter callbackFilter;
private static Field disconnectField;
private static Method sendPacketMethod;
private InjectedServerConnection serverInjection;
@@ -59,12 +63,15 @@ public class NetworkServerInjector extends PlayerInjector {
// Used to create proxy objects
private ClassLoader classLoader;
// Whether or not the player has disconnected
private boolean hasDisconnected;
public NetworkServerInjector(
ClassLoader classLoader, Logger logger, Player player,
ClassLoader classLoader, ErrorReporter reporter, Player player,
ListenerInvoker invoker, IntegerSet sendingFilters,
InjectedServerConnection serverInjection) throws IllegalAccessException {
super(logger, player, invoker);
super(reporter, player, invoker);
this.classLoader = classLoader;
this.sendingFilters = sendingFilters;
this.serverInjection = serverInjection;
@@ -164,18 +171,24 @@ public class NetworkServerInjector extends PlayerInjector {
};
Callback noOpCallback = NoOp.INSTANCE;
// Share callback filter - that way, we avoid generating a new class for
// every logged in player.
if (callbackFilter == null) {
callbackFilter = new CallbackFilter() {
@Override
public int accept(Method method) {
if (method.equals(sendPacketMethod))
return 0;
else
return 1;
}
};
}
ex.setClassLoader(classLoader);
ex.setSuperclass(serverClass);
ex.setCallbacks(new Callback[] { sendPacketCallback, noOpCallback });
ex.setCallbackFilter(new CallbackFilter() {
@Override
public int accept(Method method) {
if (method.equals(sendPacketMethod))
return 0;
else
return 1;
}
});
ex.setCallbackFilter(callbackFilter);
// Find the Minecraft NetServerHandler superclass
Class<?> minecraftSuperClass = getFirstMinecraftSuperClass(serverHandler.getClass());
@@ -202,6 +215,7 @@ public class NetworkServerInjector extends PlayerInjector {
if (proxyObject != null) {
// This will be done by InjectedServerConnection instead
//copyTo(serverHandler, proxyObject);
serverInjection.replaceServerHandler(serverHandler, proxyObject);
serverHandlerRef.setValue(proxyObject);
return true;
@@ -232,7 +246,7 @@ public class NetworkServerInjector extends PlayerInjector {
}
@Override
public void cleanupAll() {
protected void cleanHook() {
if (serverHandlerRef != null && serverHandlerRef.isCurrentSet()) {
ObjectCloner.copyTo(serverHandlerRef.getValue(), serverHandlerRef.getOldValue(), serverHandler.getClass());
serverHandlerRef.revertValue();
@@ -250,14 +264,46 @@ public class NetworkServerInjector extends PlayerInjector {
} catch (IllegalAccessException e) {
e.printStackTrace();
}
// Prevent the PlayerQuitEvent from being sent twice
if (hasDisconnected) {
setDisconnect(serverHandlerRef.getValue(), true);
}
}
serverInjection.revertServerHandler(serverHandler);
}
@Override
public void checkListener(PacketListener listener) {
public void handleDisconnect() {
hasDisconnected = true;
}
/**
* Set the disconnected field in a NetServerHandler.
* @param handler - the NetServerHandler.
* @param value - the new value.
*/
private void setDisconnect(Object handler, boolean value) {
// Set it
try {
// Load the field
if (disconnectField == null) {
disconnectField = FuzzyReflection.fromObject(handler).getFieldByName("disconnected.*");
}
FieldUtils.writeField(disconnectField, handler, value);
} catch (IllegalArgumentException e) {
reporter.reportDetailed(this, "Unable to find disconnect field. Is ProtocolLib up to date?", e, handler);
} catch (IllegalAccessException e) {
reporter.reportWarning(this, "Unable to update disconnected field. Player quit event may be sent twice.");
}
}
@Override
public UnsupportedListener checkListener(PacketListener listener) {
// We support everything
return null;
}
@Override
@@ -24,14 +24,13 @@ import java.net.Socket;
import java.net.SocketAddress;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.minecraft.server.Packet;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketListener;
@@ -72,8 +71,8 @@ public class PlayerInjectionHandler {
private volatile PlayerInjectHooks loginPlayerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
private volatile PlayerInjectHooks playingPlayerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
// Error logger
private Logger logger;
// Error reporter
private ErrorReporter reporter;
// Whether or not we're closing
private boolean hasClosed;
@@ -84,21 +83,25 @@ public class PlayerInjectionHandler {
// Enabled packet filters
private IntegerSet sendingFilters = new IntegerSet(MAXIMUM_PACKET_ID + 1);
// List of packet listeners
private Set<PacketListener> packetListeners;
// The class loader we're using
private ClassLoader classLoader;
// Used to filter injection attempts
private Predicate<GamePhase> injectionFilter;
public PlayerInjectionHandler(ClassLoader classLoader, Logger logger, Predicate<GamePhase> injectionFilter,
ListenerInvoker invoker, Server server) {
public PlayerInjectionHandler(ClassLoader classLoader, ErrorReporter reporter, Predicate<GamePhase> injectionFilter,
ListenerInvoker invoker, Set<PacketListener> packetListeners, Server server) {
this.classLoader = classLoader;
this.logger = logger;
this.reporter = reporter;
this.invoker = invoker;
this.injectionFilter = injectionFilter;
this.netLoginInjector = new NetLoginInjector(logger, this, server);
this.serverInjection = new InjectedServerConnection(logger, server, netLoginInjector);
this.packetListeners = packetListeners;
this.netLoginInjector = new NetLoginInjector(reporter, this, server);
this.serverInjection = new InjectedServerConnection(reporter, server, netLoginInjector);
serverInjection.injectList();
}
@@ -144,6 +147,9 @@ public class PlayerInjectionHandler {
loginPlayerHook = playerHook;
if (phase.hasPlaying())
playingPlayerHook = playerHook;
// Make sure the current listeners are compatible
checkListener(packetListeners);
}
/**
@@ -173,11 +179,11 @@ public class PlayerInjectionHandler {
// Construct the correct player hook
switch (hook) {
case NETWORK_HANDLER_FIELDS:
return new NetworkFieldInjector(classLoader, logger, player, invoker, sendingFilters);
return new NetworkFieldInjector(classLoader, reporter, player, invoker, sendingFilters);
case NETWORK_MANAGER_OBJECT:
return new NetworkObjectInjector(classLoader, logger, player, invoker, sendingFilters);
return new NetworkObjectInjector(classLoader, reporter, player, invoker, sendingFilters);
case NETWORK_SERVER_OBJECT:
return new NetworkServerInjector(classLoader, logger, player, invoker, sendingFilters, serverInjection);
return new NetworkServerInjector(classLoader, reporter, player, invoker, sendingFilters, serverInjection);
default:
throw new IllegalArgumentException("Cannot construct a player injector.");
}
@@ -198,7 +204,7 @@ public class PlayerInjectionHandler {
if (injector != null) {
return injector.getPlayer();
} else {
System.out.println("Unable to find stream: " + inputStream);
reporter.reportWarning(this, "Unable to find stream: " + inputStream);
return null;
}
@@ -281,28 +287,27 @@ public class PlayerInjectionHandler {
DataInputStream inputStream = injector.getInputStream(false);
Socket socket = injector.getSocket();
SocketAddress address = socket.getRemoteSocketAddress();
SocketAddress address = socket != null ? socket.getRemoteSocketAddress() : null;
// Make sure the current player is not logged out
if (socket.isClosed()) {
if (socket != null && socket.isClosed()) {
throw new PlayerLoggedOutException();
}
PlayerInjector previous = addressLookup.get(address);
// Guard against NPE here too
PlayerInjector previous = address != null ? addressLookup.get(address) : null;
// Close any previously associated hooks before we proceed
if (previous != null) {
uninjectPlayer(previous.getPlayer());
// Remove the "hooked" network manager in our instance as well
if (previous instanceof NetworkObjectInjector) {
injector.setNetworkManager(previous.getNetworkManager(), true);
}
uninjectPlayer(previous.getPlayer(), false, true);
}
injector.injectManager();
dataInputLookup.put(inputStream, injector);
addressLookup.put(address, injector);
if (inputStream != null)
dataInputLookup.put(inputStream, injector);
if (address != null)
addressLookup.put(address, injector);
break;
}
@@ -311,7 +316,8 @@ public class PlayerInjectionHandler {
} catch (Exception e) {
// Mark this injection attempt as a failure
logger.log(Level.SEVERE, "Player hook " + tempHook.toString() + " failed.", e);
reporter.reportDetailed(this, "Player hook " + tempHook.toString() + " failed.",
e, player, injectionPoint, phase);
hookFailed = true;
}
@@ -319,7 +325,7 @@ public class PlayerInjectionHandler {
tempHook = PlayerInjectHooks.values()[tempHook.ordinal() - 1];
if (hookFailed)
logger.log(Level.INFO, "Switching to " + tempHook.toString() + " instead.");
reporter.reportWarning(this, "Switching to " + tempHook.toString() + " instead.");
// Check for UTTER FAILURE
if (tempHook == PlayerInjectHooks.NONE) {
@@ -340,8 +346,10 @@ public class PlayerInjectionHandler {
if (permanentHook != getPlayerHook(phase))
setPlayerHook(phase, tempHook);
// Save last injector
playerInjection.put(player, injector);
// Save injector
if (injector != null) {
playerInjection.put(player, injector);
}
}
return injector;
@@ -352,16 +360,50 @@ public class PlayerInjectionHandler {
try {
if (injector != null)
injector.cleanupAll();
} catch (Exception e2) {
logger.log(Level.WARNING, "Cleaing up after player hook failed.", e2);
} catch (Exception ex) {
reporter.reportDetailed(this, "Cleaing up after player hook failed.", ex, injector);
}
}
/**
* Invoke special routines for handling disconnect before a player is uninjected.
* @param player - player to process.
*/
public void handleDisconnect(Player player) {
PlayerInjector injector = getInjector(player);
if (injector != null) {
injector.handleDisconnect();
}
}
/**
* Unregisters the given player.
* @param player - player to unregister.
* @return TRUE if a player has been uninjected, FALSE otherwise.
*/
public void uninjectPlayer(Player player) {
public boolean uninjectPlayer(Player player) {
return uninjectPlayer(player, true, false);
}
/**
* Unregisters the given player.
* @param player - player to unregister.
* @param removeAuxiliary - TRUE to remove auxiliary information, such as input stream and address.
* @return TRUE if a player has been uninjected, FALSE otherwise.
*/
public boolean uninjectPlayer(Player player, boolean removeAuxiliary) {
return uninjectPlayer(player, removeAuxiliary, false);
}
/**
* Unregisters the given player.
* @param player - player to unregister.
* @param removeAuxiliary - TRUE to remove auxiliary information, such as input stream and address.
* @param prepareNextHook - whether or not we need to fix any lingering hooks.
* @return TRUE if a player has been uninjected, FALSE otherwise.
*/
private boolean uninjectPlayer(Player player, boolean removeAuxiliary, boolean prepareNextHook) {
if (!hasClosed && player != null) {
PlayerInjector injector = playerInjection.remove(player);
@@ -371,26 +413,54 @@ public class PlayerInjectionHandler {
InetSocketAddress address = player.getAddress();
injector.cleanupAll();
dataInputLookup.remove(input);
// Remove the "hooked" network manager in our instance as well
if (prepareNextHook && injector instanceof NetworkObjectInjector) {
try {
PlayerInjector dummyInjector = getHookInstance(player, PlayerInjectHooks.NETWORK_SERVER_OBJECT);
dummyInjector.initializePlayer(player);
dummyInjector.setNetworkManager(injector.getNetworkManager(), true);
} catch (IllegalAccessException e) {
// Let the user know
reporter.reportWarning(this, "Unable to fully revert old injector. May cause conflicts.", e);
}
}
if (address != null)
addressLookup.remove(address);
// Clean up
if (removeAuxiliary) {
if (input != null)
dataInputLookup.remove(input);
if (address != null)
addressLookup.remove(address);
}
return true;
}
}
}
return false;
}
/**
* Unregisters a player by the given address.
* <p>
* If the server handler has been created before we've gotten a chance to unject the player,
* the method will try a workaround to remove the injected hook in the NetServerHandler.
*
* @param address - address of the player to unregister.
* @param serverHandler - whether or not the net server handler has already been created.
* @return TRUE if a player has been uninjected, FALSE otherwise.
*/
public void uninjectPlayer(InetSocketAddress address) {
public boolean uninjectPlayer(InetSocketAddress address) {
if (!hasClosed && address != null) {
PlayerInjector injector = addressLookup.get(address);
// Clean up
if (injector != null)
uninjectPlayer(injector.getPlayer());
uninjectPlayer(injector.getPlayer(), false, true);
return true;
}
return false;
}
/**
@@ -407,7 +477,7 @@ public class PlayerInjectionHandler {
if (injector != null)
injector.sendServerPacket(packet.getHandle(), filters);
else
logger.log(Level.WARNING, String.format(
reporter.reportWarning(this, String.format(
"Unable to send packet %s (%s): Player %s has logged out.",
packet.getID(), packet, reciever.getName()
));
@@ -428,7 +498,7 @@ public class PlayerInjectionHandler {
if (injector != null)
injector.processPacket(mcPacket);
else
logger.log(Level.WARNING, String.format(
reporter.reportWarning(this, String.format(
"Unable to receieve packet %s. Player %s has logged out.",
mcPacket, player.getName()
));
@@ -443,6 +513,26 @@ public class PlayerInjectionHandler {
return playerInjection.get(player);
}
/**
* Retrieve a player injector by looking for its NetworkManager.
* @param networkManager - current network manager.
* @return Related player injector.
*/
PlayerInjector getInjectorByNetworkHandler(Object networkManager) {
// That's not legal
if (networkManager == null)
return null;
// O(n) is okay in this instance. This is only a backup solution.
for (PlayerInjector injector : playerInjection.values()) {
if (injector.getNetworkManager() == networkManager)
return injector;
}
// None found
return null;
}
/**
* Determine if the given listeners are valid.
* @param listeners - listeners to check.
@@ -451,26 +541,30 @@ public class PlayerInjectionHandler {
// Make sure the current listeners are compatible
if (lastSuccessfulHook != null) {
for (PacketListener listener : listeners) {
try {
checkListener(listener);
} catch (IllegalStateException e) {
logger.log(Level.WARNING, "Unsupported listener.", e);
}
checkListener(listener);
}
}
}
/**
* Determine if a listener is valid or not.
* <p>
* If not, a warning will be printed to the console.
* @param listener - listener to check.
* @throws IllegalStateException If the given listener's whitelist cannot be fulfilled.
*/
public void checkListener(PacketListener listener) {
try {
if (lastSuccessfulHook != null)
lastSuccessfulHook.checkListener(listener);
} catch (Exception e) {
throw new IllegalStateException("Registering listener " + PacketAdapter.getPluginName(listener) + " failed", e);
if (lastSuccessfulHook != null) {
UnsupportedListener result = lastSuccessfulHook.checkListener(listener);
// We won't prevent the listener, as it may still have valid packets
if (result != null) {
reporter.reportWarning(this, "Cannot fully register listener for " +
PacketAdapter.getPluginName(listener) + ": " + result.toString());
// These are illegal
for (int packetID : result.getPackets())
removePacketHandler(packetID);
}
}
}
@@ -482,14 +576,6 @@ public class PlayerInjectionHandler {
return sendingFilters.toSet();
}
/**
* Retrieve the current logger.
* @return Error logger.
*/
public Logger getLogger() {
return logger;
}
public void close() {
// Guard
@@ -23,8 +23,7 @@ import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.Socket;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.net.SocketAddress;
import net.minecraft.server.EntityPlayer;
import net.minecraft.server.NetLoginHandler;
@@ -35,6 +34,7 @@ import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.entity.Player;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
@@ -99,17 +99,20 @@ abstract class PlayerInjector {
protected DataInputStream cachedInput;
// Handle errors
protected Logger logger;
protected ErrorReporter reporter;
// Scheduled action on the next packet event
protected Runnable scheduledAction;
// Whether or not the injector has been cleaned
private boolean clean;
// Whether or not to update the current player on the first Packet1Login
boolean updateOnLogin;
Player updatedPlayer;
public PlayerInjector(Logger logger, Player player, ListenerInvoker invoker) throws IllegalAccessException {
this.logger = logger;
public PlayerInjector(ErrorReporter reporter, Player player, ListenerInvoker invoker) throws IllegalAccessException {
this.reporter = reporter;
this.player = player;
this.invoker = invoker;
}
@@ -256,6 +259,21 @@ abstract class PlayerInjector {
}
}
/**
* Retrieve the associated address of this player.
* @return The associated address.
* @throws IllegalAccessException If we're unable to read the socket field.
*/
public SocketAddress getAddress() throws IllegalAccessException {
Socket socket = getSocket();
// Guard against NULL
if (socket != null)
return socket.getRemoteSocketAddress();
else
return null;
}
/**
* Attempt to disconnect the current client.
* @param message - the message to display.
@@ -284,19 +302,24 @@ abstract class PlayerInjector {
disconnect.invoke(handler, message);
return;
} catch (IllegalArgumentException e) {
logger.log(Level.WARNING, "Invalid argument passed to disconnect method: " + message, e);
reporter.reportDetailed(this, "Invalid argument passed to disconnect method: " + message, e, handler);
} catch (IllegalAccessException e) {
logger.log(Level.SEVERE, "Unable to access disconnect method.", e);
reporter.reportWarning(this, "Unable to access disconnect method.", e);
}
}
// Fuck it
try {
getSocket().close();
} catch (IOException e) {
logger.log(Level.SEVERE, "Unable to close socket.", e);
Socket socket = getSocket();
try {
socket.close();
} catch (IOException e) {
reporter.reportDetailed(this, "Unable to close socket.", e, socket);
}
} catch (IllegalAccessException e) {
logger.log(Level.SEVERE, "Insufficient permissions. Cannot close socket.", e);
reporter.reportWarning(this, "Insufficient permissions. Cannot close socket.", e);
}
}
@@ -313,7 +336,7 @@ abstract class PlayerInjector {
return null;
hasProxyType = true;
logger.log(Level.WARNING, "Detected server handler proxy type by another plugin. Conflict may occur!");
reporter.reportWarning(this, "Detected server handler proxy type by another plugin. Conflict may occur!");
// No? Is it a Proxy type?
try {
@@ -328,7 +351,7 @@ abstract class PlayerInjector {
}
} catch (IllegalAccessException e) {
logger.warning("Unable to load server handler from proxy type.");
reporter.reportWarning(this, "Unable to load server handler from proxy type.");
}
// Nope, just go with it
@@ -427,7 +450,29 @@ abstract class PlayerInjector {
/**
* Remove all hooks and modifications.
*/
public abstract void cleanupAll();
public final void cleanupAll() {
if (!clean)
cleanHook();
clean = true;
}
/**
* Clean up after the player has disconnected.
*/
public abstract void handleDisconnect();
/**
* Override to add custom cleanup behavior.
*/
protected abstract void cleanHook();
/**
* Determine whether or not this hook has already been cleaned.
* @return TRUE if it has, FALSE otherwise.
*/
public boolean isClean() {
return clean;
}
/**
* Determine if this inject method can even be attempted.
@@ -444,10 +489,12 @@ abstract class PlayerInjector {
/**
* Invoked before a new listener is registered.
* <p>
* The player injector should throw an exception if this listener cannot be properly supplied with packet events.
* The player injector should only return a non-null value if some or all of the packet IDs are unsupported.
*
* @param listener - the listener that is about to be registered.
* @return A error message with the unsupported packet IDs, or NULL if this listener is valid.
*/
public abstract void checkListener(PacketListener listener);
public abstract UnsupportedListener checkListener(PacketListener listener);
/**
* Allows a packet to be sent by the listeners.
@@ -455,43 +502,48 @@ abstract class PlayerInjector {
* @return The given packet, or the packet replaced by the listeners.
*/
public Packet handlePacketSending(Packet packet) {
// Get the packet ID too
Integer id = invoker.getPacketID(packet);
Player currentPlayer = player;
// Hack #1: Handle a single scheduled action
if (scheduledAction != null) {
scheduledAction.run();
scheduledAction = null;
}
// Hack #2
if (updateOnLogin) {
if (id == Packets.Server.LOGIN) {
try {
updatedPlayer = getEntityPlayer(getNetHandler()).getBukkitEntity();
} catch (IllegalAccessException e) {
logger.log(Level.WARNING, "Cannot update player in PlayerEvent.", e);
try {
// Get the packet ID too
Integer id = invoker.getPacketID(packet);
Player currentPlayer = player;
// Hack #1: Handle a single scheduled action
if (scheduledAction != null) {
scheduledAction.run();
scheduledAction = null;
}
// Hack #2
if (updateOnLogin) {
if (id == Packets.Server.LOGIN) {
try {
updatedPlayer = getEntityPlayer(getNetHandler()).getBukkitEntity();
} catch (IllegalAccessException e) {
reporter.reportDetailed(this, "Cannot update player in PlayerEvent.", e, packet);
}
}
// This will only occur in the NetLoginHandler injection
if (updatedPlayer != null)
currentPlayer = updatedPlayer;
}
// This will only occur in the NetLoginHandler injection
if (updatedPlayer != null)
currentPlayer = updatedPlayer;
}
// Make sure we're listening
if (id != null && hasListener(id)) {
// A packet has been sent guys!
PacketContainer container = new PacketContainer(id, packet);
PacketEvent event = PacketEvent.fromServer(invoker, container, currentPlayer);
invoker.invokePacketSending(event);
// Make sure we're listening
if (id != null && hasListener(id)) {
// A packet has been sent guys!
PacketContainer container = new PacketContainer(id, packet);
PacketEvent event = PacketEvent.fromServer(invoker, container, currentPlayer);
invoker.invokePacketSending(event);
// Cancelling is pretty simple. Just ignore the packet.
if (event.isCancelled())
return null;
// Right, remember to replace the packet again
return event.getPacket().getHandle();
}
// Cancelling is pretty simple. Just ignore the packet.
if (event.isCancelled())
return null;
// Right, remember to replace the packet again
return event.getPacket().getHandle();
} catch (Throwable e) {
reporter.reportDetailed(this, "Cannot handle server packet.", e, packet);
}
return packet;
@@ -544,6 +596,14 @@ abstract class PlayerInjector {
return player;
}
/**
* Object that can invoke the packet events.
* @return Packet event invoker.
*/
public ListenerInvoker getInvoker() {
return invoker;
}
/**
* Retrieve the hooked player object OR the more up-to-date player instance.
* @return The hooked player, or a more up-to-date instance.
@@ -68,7 +68,7 @@ class ReplacedArrayList<TKey> extends ArrayList<TKey> {
}
/**
* Invoksed when an element is being removed.
* Invoked when an element is being removed.
* @param removing - the element being removed.
*/
protected void onRemoved(TKey removing) {
@@ -264,6 +264,15 @@ class ReplacedArrayList<TKey> extends ArrayList<TKey> {
addMapping(target, replacement, false);
}
/**
* Retrieve the old value, if it exists.
* @param target - the key.
* @return The value that was replaced, or NULL.
*/
public TKey getMapping(TKey target) {
return replaceMap.get(target);
}
/**
* Add a replace rule.
* <p>
@@ -284,8 +293,9 @@ class ReplacedArrayList<TKey> extends ArrayList<TKey> {
/**
* Revert the given mapping.
* @param target - the instance we replaced.
* @return The old mapped value, or NULL if nothing was replaced.
*/
public synchronized void removeMapping(TKey target) {
public synchronized TKey removeMapping(TKey target) {
// Make sure the mapping exist
if (replaceMap.containsKey(target)) {
TKey replacement = replaceMap.get(target);
@@ -293,7 +303,25 @@ class ReplacedArrayList<TKey> extends ArrayList<TKey> {
// Revert existing elements
replaceAll(replacement, target);
return replacement;
}
return null;
}
/**
* Swap the new replaced value with its old value.
* @param target - the instance we replaced.
* @param The old mapped value, or NULL if nothing was swapped.
*/
public synchronized TKey swapMapping(TKey target) {
// Make sure the mapping exist
TKey replacement = removeMapping(target);
// Add the reverse
if (replacement != null) {
replaceMap.put(replacement, target);
}
return replacement;
}
/**
@@ -42,6 +42,9 @@ 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" });
}
@@ -76,13 +79,16 @@ class TemporaryPlayerFactory {
String methodName = method.getName();
PlayerInjector injector = ((InjectContainer) obj).getInjector();
if (injector == null)
throw new IllegalStateException("Unable to find injector.");
// Use the socket to get the address
if (methodName.equalsIgnoreCase("getName"))
return "UNKNOWN[" + injector.getSocket().getRemoteSocketAddress() + "]";
if (methodName.equalsIgnoreCase("getPlayer"))
return injector.getUpdatedPlayer();
if (methodName.equalsIgnoreCase("getAddress"))
return injector.getSocket().getRemoteSocketAddress();
return injector.getAddress();
if (methodName.equalsIgnoreCase("getServer"))
return server;
@@ -117,22 +123,27 @@ class TemporaryPlayerFactory {
}
};
// 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(InjectContainer.class))
return 0;
else
return 1;
}
};
}
// CGLib is amazing
Enhancer ex = new Enhancer();
ex.setSuperclass(InjectContainer.class);
ex.setInterfaces(new Class[] { Player.class });
ex.setCallbacks(new Callback[] { NoOp.INSTANCE, implementation });
ex.setCallbackFilter(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(InjectContainer.class))
return 0;
else
return 1;
}
});
ex.setCallbackFilter(callbackFilter);
return (Player) ex.create();
}
@@ -0,0 +1,47 @@
package com.comphenix.protocol.injector.player;
import java.util.Arrays;
import com.google.common.base.Joiner;
/**
* Represents an error message from a player injector.
*
* @author Kristian
*/
class UnsupportedListener {
private String message;
private int[] packets;
/**
* Create a new error message.
* @param message - the message.
* @param packets - unsupported packets.
*/
public UnsupportedListener(String message, int[] packets) {
super();
this.message = message;
this.packets = packets;
}
/**
* Retrieve the error message.
* @return Error message.
*/
public String getMessage() {
return message;
}
/**
* Retrieve all unsupported packets.
* @return Unsupported packets.
*/
public int[] getPackets() {
return packets;
}
@Override
public String toString() {
return String.format("%s (%s)", message, Joiner.on(", ").join(Arrays.asList(packets)));
}
}
@@ -67,6 +67,16 @@ public abstract class CompiledStructureModifier<TField> extends StructureModifie
else
return (TField) result;
}
/**
* Read the given field index using reflection.
* @param index - index of field.
* @return Resulting value.
* @throws FieldAccessException The field doesn't exist, or it cannot be accessed under the current security contraints.
*/
protected Object readReflected(int index) throws FieldAccessException {
return super.read(index);
}
protected abstract Object readGenerated(int fieldIndex) throws FieldAccessException;
@@ -78,6 +88,17 @@ public abstract class CompiledStructureModifier<TField> extends StructureModifie
return writeGenerated(index, value);
}
/**
* Write the given field using reflection.
* @param index - index of field.
* @param value - new value.
* @throws FieldAccessException The field doesn't exist, or it cannot be accessed under the current security contraints.
*/
@SuppressWarnings("unchecked")
protected void writeReflected(int index, Object value) throws FieldAccessException {
super.write(index, (TField) value);
}
protected abstract StructureModifier<TField> writeGenerated(int index, Object value) throws FieldAccessException;
@Override
@@ -335,8 +335,7 @@ public final class StructureCompiler {
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ILOAD, 1);
mv.visitVarInsn(Opcodes.ALOAD, 2);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, COMPILED_CLASS, "write", "(ILjava/lang/Object;)L" + SUPER_CLASS + ";");
mv.visitInsn(Opcodes.POP);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, COMPILED_CLASS, "writeReflected", "(ILjava/lang/Object;)V;");
}
mv.visitJumpInsn(Opcodes.GOTO, returnLabel);
@@ -408,7 +407,7 @@ public final class StructureCompiler {
// We have to use reflection for private and protected fields.
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ILOAD, 1);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, COMPILED_CLASS, "read", "(I)Ljava/lang/Object;");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, COMPILED_CLASS, "readReflected", "(I)Ljava/lang/Object;");
}
mv.visitInsn(Opcodes.ARETURN);
@@ -206,7 +206,6 @@ public class DefaultInstances {
// Just check if any of them are NULL
for (Class<?> type : types) {
if (getDefaultInternal(type, providers, recursionLevel) == null) {
System.out.println(type.getName() + " is NULL!");
return true;
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
name: ProtocolLib
version: 1.4.0
version: 1.5.0
description: Provides read/write access to the Minecraft protocol.
author: Comphenix
website: http://www.comphenix.net/ProtocolLib