diff --git a/ProtocolLib/.project b/ProtocolLib/.project
index d36fcbe8..a1960c9e 100644
--- a/ProtocolLib/.project
+++ b/ProtocolLib/.project
@@ -11,12 +11,12 @@
- net.sourceforge.metrics.builder
+ org.eclipse.m2e.core.maven2Builder
- org.eclipse.m2e.core.maven2Builder
+ net.sourceforge.metrics.builder
diff --git a/ProtocolLib/pom.xml b/ProtocolLib/pom.xml
index 414e2599..695153b4 100644
--- a/ProtocolLib/pom.xml
+++ b/ProtocolLib/pom.xml
@@ -2,7 +2,7 @@
4.0.0com.comphenix.protocolProtocolLib
- 2.4.3
+ 2.4.5jarProvides read/write access to the Minecraft protocol.
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java
index 74bb63c2..dd0655cc 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java
@@ -1,167 +1,168 @@
-/*
- * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
- * Copyright (C) 2012 Kristian S. Stangeland
- *
- * This program is free software; you can redistribute it and/or modify it under the terms of the
- * GNU General Public License as published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
- * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with this program;
- * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
- * 02111-1307 USA
- */
-
-package com.comphenix.protocol;
-
-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.error.Report;
-import com.comphenix.protocol.error.ReportType;
-import com.comphenix.protocol.events.ListeningWhitelist;
-import com.comphenix.protocol.events.PacketContainer;
-import com.comphenix.protocol.injector.BukkitUnwrapper;
-import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
-import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
-import com.comphenix.protocol.injector.spigot.SpigotPacketInjector;
-import com.comphenix.protocol.reflect.FieldUtils;
-import com.comphenix.protocol.reflect.FuzzyReflection;
-import com.comphenix.protocol.reflect.MethodUtils;
-import com.comphenix.protocol.reflect.ObjectWriter;
-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;
-import com.comphenix.protocol.utility.MinecraftReflection;
-import com.comphenix.protocol.wrappers.ChunkPosition;
-import com.comphenix.protocol.wrappers.WrappedDataWatcher;
-import com.comphenix.protocol.wrappers.WrappedWatchableObject;
-import com.comphenix.protocol.wrappers.nbt.io.NbtBinarySerializer;
-
-/**
- * Used to fix ClassLoader leaks that may lead to filling up the permanent generation.
- *
- * @author Kristian
- */
-class CleanupStaticMembers {
- // Reports
- public final static ReportType REPORT_CANNOT_RESET_FIELD = new ReportType("Unable to reset field %s: %s");
- public final static ReportType REPORT_CANNOT_UNLOAD_CLASS = new ReportType("Unable to unload class %s.");
-
- 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,
- ObjectWriter.class, Packets.Server.class, Packets.Client.class,
- ChunkPosition.class, WrappedDataWatcher.class, WrappedWatchableObject.class,
- AbstractInputStreamLookup.class, TemporaryPlayerFactory.class, SpigotPacketInjector.class,
- MinecraftReflection.class, NbtBinarySerializer.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.EntityUtilities",
- "com.comphenix.protocol.injector.packet.PacketRegistry",
- "com.comphenix.protocol.injector.packet.PacketInjector",
- "com.comphenix.protocol.injector.packet.ReadPacketModifier",
- "com.comphenix.protocol.injector.StructureCache",
- "com.comphenix.protocol.reflect.compiler.BoxingHelper",
- "com.comphenix.protocol.reflect.compiler.MethodDescriptor",
- "com.comphenix.protocol.wrappers.nbt.WrappedElement",
- };
-
- 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,
- Report.newBuilder(REPORT_CANNOT_RESET_FIELD).error(e).messageParam(field.getName(), e.getMessage())
- );
- }
- }
- }
- }
-
- // 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> output = new ArrayList>();
-
- for (String name : names) {
- try {
- output.add(loader.loadClass(name));
- } catch (ClassNotFoundException e) {
- // Warn the user
- reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_UNLOAD_CLASS).error(e).messageParam(name));
- }
- }
-
- return output.toArray(new Class>[0]);
- }
-}
+/*
+ * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
+ * Copyright (C) 2012 Kristian S. Stangeland
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program;
+ * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA
+ */
+
+package com.comphenix.protocol;
+
+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.error.Report;
+import com.comphenix.protocol.error.ReportType;
+import com.comphenix.protocol.events.ListeningWhitelist;
+import com.comphenix.protocol.events.PacketContainer;
+import com.comphenix.protocol.injector.BukkitUnwrapper;
+import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
+import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
+import com.comphenix.protocol.injector.spigot.SpigotPacketInjector;
+import com.comphenix.protocol.reflect.FieldUtils;
+import com.comphenix.protocol.reflect.FuzzyReflection;
+import com.comphenix.protocol.reflect.MethodUtils;
+import com.comphenix.protocol.reflect.ObjectWriter;
+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;
+import com.comphenix.protocol.utility.MinecraftReflection;
+import com.comphenix.protocol.wrappers.ChunkPosition;
+import com.comphenix.protocol.wrappers.WrappedDataWatcher;
+import com.comphenix.protocol.wrappers.WrappedWatchableObject;
+import com.comphenix.protocol.wrappers.nbt.io.NbtBinarySerializer;
+
+/**
+ * Used to fix ClassLoader leaks that may lead to filling up the permanent generation.
+ *
+ * @author Kristian
+ */
+class CleanupStaticMembers {
+ // Reports
+ public final static ReportType REPORT_CANNOT_RESET_FIELD = new ReportType("Unable to reset field %s: %s");
+ public final static ReportType REPORT_CANNOT_UNLOAD_CLASS = new ReportType("Unable to unload class %s.");
+
+ 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,
+ ObjectWriter.class, Packets.Server.class, Packets.Client.class,
+ ChunkPosition.class, WrappedDataWatcher.class, WrappedWatchableObject.class,
+ AbstractInputStreamLookup.class, TemporaryPlayerFactory.class, SpigotPacketInjector.class,
+ MinecraftReflection.class, NbtBinarySerializer.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.EntityUtilities",
+ "com.comphenix.protocol.injector.packet.PacketRegistry",
+ "com.comphenix.protocol.injector.packet.PacketInjector",
+ "com.comphenix.protocol.injector.packet.ReadPacketModifier",
+ "com.comphenix.protocol.injector.StructureCache",
+ "com.comphenix.protocol.reflect.compiler.BoxingHelper",
+ "com.comphenix.protocol.reflect.compiler.MethodDescriptor",
+ "com.comphenix.protocol.wrappers.nbt.WrappedElement",
+ };
+
+ 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,
+ Report.newBuilder(REPORT_CANNOT_RESET_FIELD).error(e).messageParam(field.getName(), e.getMessage())
+ );
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ // 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> output = new ArrayList>();
+
+ for (String name : names) {
+ try {
+ output.add(loader.loadClass(name));
+ } catch (ClassNotFoundException e) {
+ // Warn the user
+ reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_UNLOAD_CLASS).error(e).messageParam(name));
+ }
+ }
+
+ return output.toArray(new Class>[0]);
+ }
+}
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandFilter.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandFilter.java
index 392b1efc..09be6900 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandFilter.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandFilter.java
@@ -220,14 +220,15 @@ public class CommandFilter extends CommandBase {
// Script engine
private ScriptEngine engine;
+ private boolean uninitialized;
public CommandFilter(ErrorReporter reporter, Plugin plugin, ProtocolConfig config) {
super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 2);
this.plugin = plugin;
this.config = config;
- // Start the engine
- initalizeScript();
+ // Tell the filter system to initialize the script first chance it gets
+ this.uninitialized = true;
}
private void initalizeScript() {
@@ -238,6 +239,8 @@ public class CommandFilter extends CommandBase {
// Oh for ..
if (!isInitialized()) {
throw new ScriptException("A JavaScript engine could not be found.");
+ } else {
+ plugin.getLogger().info("Loaded command filter engine.");
}
} catch (ScriptException e1) {
// It's not a huge deal
@@ -342,12 +345,25 @@ public class CommandFilter extends CommandBase {
return true;
}
+ /**
+ * Initialize the script engine if necessary.
+ */
+ private void checkScriptStatus() {
+ // Start the engine
+ if (uninitialized) {
+ uninitialized = false;
+ initalizeScript();
+ }
+ }
+
/*
* Description: Adds or removes a simple packet filter.
Usage: / add|remove name [packet IDs]
*/
@Override
protected boolean handleCommand(CommandSender sender, String[] args) {
+ checkScriptStatus();
+
if (!config.isDebug()) {
sender.sendMessage(ChatColor.RED + "Debug mode must be enabled in the configuration first!");
return true;
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/Packets.java b/ProtocolLib/src/main/java/com/comphenix/protocol/Packets.java
index 8b543e32..b4337dca 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/Packets.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/Packets.java
@@ -85,6 +85,12 @@ public final class Packets {
public static final int ENTITY_HEAD_ROTATION = 35;
public static final int ENTITY_STATUS = 38;
public static final int ATTACH_ENTITY = 39;
+
+ /**
+ * Sent when an entities DataWatcher is updated.
+ *
+ * Remember to clone the packet if you are modifying it.
+ */
public static final int ENTITY_METADATA = 40;
public static final int MOB_EFFECT = 41;
public static final int REMOVE_MOB_EFFECT = 42;
@@ -109,6 +115,12 @@ public final class Packets {
public static final int SET_CREATIVE_SLOT = 107;
public static final int UPDATE_SIGN = 130;
public static final int ITEM_DATA = 131;
+
+ /**
+ * Sent the first time a tile entity (chest inventory, etc.) is withing range of the player, or has been updated.
+ *
+ * Remember to clone the packet if you are modifying it.
+ */
public static final int TILE_ENTITY_DATA = 132;
public static final int STATISTIC = 200;
public static final int PLAYER_INFO = 201;
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java
index 269bcb04..dc4c24fc 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java
@@ -19,6 +19,7 @@ package com.comphenix.protocol;
import java.io.File;
import java.io.IOException;
+import java.util.List;
import org.bukkit.configuration.Configuration;
import org.bukkit.configuration.ConfigurationSection;
@@ -26,6 +27,8 @@ import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
import com.google.common.io.Files;
/**
@@ -48,6 +51,7 @@ class ProtocolConfig {
private static final String INJECTION_METHOD = "injection method";
private static final String SCRIPT_ENGINE_NAME = "script engine";
+ private static final String SUPPRESSED_REPORTS = "suppressed reports";
private static final String UPDATER_NOTIFY = "notify";
private static final String UPDATER_DOWNLAD = "download";
@@ -68,6 +72,9 @@ class ProtocolConfig {
private boolean configChanged;
private boolean valuesChanged;
+ // Modifications
+ private int modCount;
+
public ProtocolConfig(Plugin plugin) {
this(plugin, plugin.getConfig());
}
@@ -84,6 +91,7 @@ class ProtocolConfig {
// Reset
configChanged = false;
valuesChanged = false;
+ modCount++;
this.config = plugin.getConfig();
this.lastUpdateTime = loadLastUpdate();
@@ -199,6 +207,7 @@ class ProtocolConfig {
*/
public void setAutoNotify(boolean value) {
setConfig(updater, UPDATER_NOTIFY, value);
+ modCount++;
}
/**
@@ -215,6 +224,7 @@ class ProtocolConfig {
*/
public void setAutoDownload(boolean value) {
setConfig(updater, UPDATER_DOWNLAD, value);
+ modCount++;
}
/**
@@ -233,6 +243,24 @@ class ProtocolConfig {
*/
public void setDebug(boolean value) {
setConfig(global, DEBUG_MODE_ENABLED, value);
+ modCount++;
+ }
+
+ /**
+ * Retrieve an immutable list of every suppressed report type.
+ * @return Every suppressed report type.
+ */
+ public ImmutableList getSuppressedReports() {
+ return ImmutableList.copyOf(global.getStringList(SUPPRESSED_REPORTS));
+ }
+
+ /**
+ * Set the list of suppressed report types,
+ * @param reports - suppressed report types.
+ */
+ public void setSuppressedReports(List reports) {
+ global.set(SUPPRESSED_REPORTS, Lists.newArrayList(reports));
+ modCount++;
}
/**
@@ -255,6 +283,7 @@ class ProtocolConfig {
if (delaySeconds < DEFAULT_UPDATER_DELAY)
delaySeconds = DEFAULT_UPDATER_DELAY;
setConfig(updater, UPDATER_DELAY, delaySeconds);
+ modCount++;
}
/**
@@ -275,6 +304,7 @@ class ProtocolConfig {
*/
public void setIgnoreVersionCheck(String ignoreVersion) {
setConfig(global, IGNORE_VERSION_CHECK, ignoreVersion);
+ modCount++;
}
/**
@@ -294,6 +324,7 @@ class ProtocolConfig {
*/
public void setMetricsEnabled(boolean enabled) {
setConfig(global, METRICS_ENABLED, enabled);
+ modCount++;
}
/**
@@ -313,6 +344,7 @@ class ProtocolConfig {
*/
public void setBackgroundCompilerEnabled(boolean enabled) {
setConfig(global, BACKGROUND_COMPILER_ENABLED, enabled);
+ modCount++;
}
/**
@@ -325,6 +357,9 @@ class ProtocolConfig {
/**
* Set the last time we updated, in seconds since 1970.01.01 00:00.
+ *
+ * Note that this is not considered to modify the configuration, so the modification count
+ * will not be incremented.
* @param lastTimeSeconds - new last update time.
*/
public void setAutoLastTime(long lastTimeSeconds) {
@@ -348,6 +383,7 @@ class ProtocolConfig {
*/
public void setScriptEngineName(String name) {
setConfig(global, SCRIPT_ENGINE_NAME, name);
+ modCount++;
}
/**
@@ -380,6 +416,15 @@ class ProtocolConfig {
*/
public void setInjectionMethod(PlayerInjectHooks hook) {
setConfig(global, INJECTION_METHOD, hook.name());
+ modCount++;
+ }
+
+ /**
+ * Retrieve the number of modifications made to this configuration.
+ * @return The number of modifications.
+ */
+ public int getModificationCount() {
+ return modCount;
}
/**
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java
index b93c662a..a9c4315f 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java
@@ -19,6 +19,7 @@ package com.comphenix.protocol;
import java.io.File;
import java.io.IOException;
+import java.util.Set;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
@@ -34,6 +35,7 @@ import org.bukkit.plugin.java.JavaPlugin;
import com.comphenix.protocol.async.AsyncFilterManager;
import com.comphenix.protocol.error.BasicErrorReporter;
+import com.comphenix.protocol.error.DelegatedErrorReporter;
import com.comphenix.protocol.error.DetailedErrorReporter;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
@@ -47,6 +49,9 @@ import com.comphenix.protocol.metrics.Updater.UpdateResult;
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
import com.comphenix.protocol.utility.ChatExtensions;
import com.comphenix.protocol.utility.MinecraftVersion;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
/**
* The main entry point for ProtocolLib.
@@ -136,12 +141,12 @@ public class ProtocolLibrary extends JavaPlugin {
// Add global parameters
DetailedErrorReporter detailedReporter = new DetailedErrorReporter(this);
- reporter = detailedReporter;
+ reporter = getFilteredReporter(detailedReporter);
try {
config = new ProtocolConfig(this);
} catch (Exception e) {
- detailedReporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_CONFIG).error(e));
+ reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_CONFIG).error(e));
// Load it again
if (deleteConfig()) {
@@ -168,7 +173,7 @@ public class ProtocolLibrary extends JavaPlugin {
unhookTask = new DelayedSingleTask(this);
protocolManager = new PacketFilterManager(
- getClassLoader(), getServer(), this, version, unhookTask, detailedReporter);
+ getClassLoader(), getServer(), this, version, unhookTask, reporter);
// Setup error reporter
detailedReporter.addGlobalParameter("manager", protocolManager);
@@ -183,23 +188,52 @@ public class ProtocolLibrary extends JavaPlugin {
protocolManager.setPlayerHook(hook);
}
} catch (IllegalArgumentException e) {
- detailedReporter.reportWarning(config, Report.newBuilder(REPORT_CANNOT_PARSE_INJECTION_METHOD).error(e));
+ reporter.reportWarning(config, Report.newBuilder(REPORT_CANNOT_PARSE_INJECTION_METHOD).error(e));
}
// Initialize command handlers
- commandProtocol = new CommandProtocol(detailedReporter, this, updater, config);
- commandFilter = new CommandFilter(detailedReporter, this, config);
- commandPacket = new CommandPacket(detailedReporter, this, logger, commandFilter, protocolManager);
+ commandProtocol = new CommandProtocol(reporter, this, updater, config);
+ commandFilter = new CommandFilter(reporter, this, config);
+ commandPacket = new CommandPacket(reporter, this, logger, commandFilter, protocolManager);
// Send logging information to player listeners too
setupBroadcastUsers(PERMISSION_INFO);
} catch (Throwable e) {
- detailedReporter.reportDetailed(this, Report.newBuilder(REPORT_PLUGIN_LOAD_ERROR).error(e).callerParam(protocolManager));
+ reporter.reportDetailed(this, Report.newBuilder(REPORT_PLUGIN_LOAD_ERROR).error(e).callerParam(protocolManager));
disablePlugin();
}
}
+ /**
+ * Retrieve a error reporter that may be filtered by the configuration.
+ * @return The new default error reporter.
+ */
+ private ErrorReporter getFilteredReporter(ErrorReporter reporter) {
+ return new DelegatedErrorReporter(reporter) {
+ private int lastModCount = -1;
+ private Set reports = Sets.newHashSet();
+
+ @Override
+ protected Report filterReport(Object sender, Report report, boolean detailed) {
+ String canonicalName = ReportType.getReportName(sender.getClass(), report.getType());
+ String reportName = Iterables.getLast(Splitter.on("#").split(canonicalName)).toUpperCase();
+
+ if (config != null && config.getModificationCount() != lastModCount) {
+ // Update our cached set again
+ reports = Sets.newHashSet(config.getSuppressedReports());
+ lastModCount = config.getModificationCount();
+ }
+
+ // Cancel reports either on the full canonical name, or just the report name
+ if (reports.contains(canonicalName) || reports.contains(reportName))
+ return null;
+ else
+ return report;
+ }
+ };
+ }
+
private boolean deleteConfig() {
return config.getFile().delete();
}
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/error/ReportType.java b/ProtocolLib/src/main/java/com/comphenix/protocol/error/ReportType.java
index cd4490e0..e795677c 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/error/ReportType.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/error/ReportType.java
@@ -1,66 +1,115 @@
-package com.comphenix.protocol.error;
-
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-import java.util.ArrayList;
-import java.util.List;
-
-import com.comphenix.protocol.reflect.FieldAccessException;
-
-/**
- * Represents a strongly-typed report. Subclasses should be immutable.
- *
- * By convention, a report must be declared as a static field publicly accessible from the sender class.
- * @author Kristian
- */
-public class ReportType {
- private final String errorFormat;
-
- /**
- * Construct a new report type.
- * @param errorFormat - string used to format the underlying report.
- */
- public ReportType(String errorFormat) {
- this.errorFormat = errorFormat;
- }
-
- /**
- * Convert the given report to a string, using the provided parameters.
- * @param parameters - parameters to insert, or NULL to insert nothing.
- * @return The full report in string format.
- */
- public String getMessage(Object[] parameters) {
- if (parameters == null || parameters.length == 0)
- return toString();
- else
- return String.format(errorFormat, parameters);
- }
-
- @Override
- public String toString() {
- return errorFormat;
- }
-
- /**
- * Retrieve all publicly associated reports.
- * @param clazz - sender class.
- * @return All associated reports.
- */
- public static ReportType[] getReports(Class> clazz) {
- if (clazz == null)
- throw new IllegalArgumentException("clazz cannot be NULL.");
- List result = new ArrayList();
-
- for (Field field : clazz.getFields()) {
- if (Modifier.isStatic(field.getModifiers()) &&
- ReportType.class.isAssignableFrom(field.getDeclaringClass())) {
- try {
- result.add((ReportType) field.get(null));
- } catch (IllegalAccessException e) {
- throw new FieldAccessException("Unable to access field.", e);
- }
- }
- }
- return result.toArray(new ReportType[0]);
- }
-}
+package com.comphenix.protocol.error;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.comphenix.protocol.reflect.FieldAccessException;
+import com.comphenix.protocol.reflect.FuzzyReflection;
+import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
+
+/**
+ * Represents a strongly-typed report. Subclasses should be immutable.
+ *
+ * By convention, a report must be declared as a static field publicly accessible from the sender class.
+ * @author Kristian
+ */
+public class ReportType {
+ private final String errorFormat;
+
+ // Used to store the report name
+ protected String reportName;
+
+ /**
+ * Construct a new report type.
+ * @param errorFormat - string used to format the underlying report.
+ */
+ public ReportType(String errorFormat) {
+ this.errorFormat = errorFormat;
+ }
+
+ /**
+ * Convert the given report to a string, using the provided parameters.
+ * @param parameters - parameters to insert, or NULL to insert nothing.
+ * @return The full report in string format.
+ */
+ public String getMessage(Object[] parameters) {
+ if (parameters == null || parameters.length == 0)
+ return toString();
+ else
+ return String.format(errorFormat, parameters);
+ }
+
+ @Override
+ public String toString() {
+ return errorFormat;
+ }
+
+ /**
+ * Retrieve the full canonical name of a given report type.
+ *
+ * This is in the format canonical_name_of_class#report_type
+ * @param clazz - the sender class.
+ * @param type - the report instance.
+ * @return The full canonical name.
+ */
+ public static String getReportName(Class> sender, ReportType type) {
+ if (sender == null)
+ throw new IllegalArgumentException("sender cannot be NUll.");
+
+ // Whether or not we need to retrieve the report name again
+ if (type.reportName == null) {
+ for (Field field : getReportFields(sender)) {
+ try {
+ field.setAccessible(true);
+
+ if (field.get(null) == type) {
+ // We got the right field!
+ return type.reportName = field.getDeclaringClass().getCanonicalName() + "#" + field.getName();
+ }
+ } catch (IllegalAccessException e) {
+ throw new FieldAccessException("Unable to read field " + field, e);
+ }
+ }
+ throw new IllegalArgumentException("Cannot find report name for " + type);
+ }
+ return type.reportName;
+ }
+
+ /**
+ * Retrieve all publicly associated reports.
+ * @param sender - sender class.
+ * @return All associated reports.
+ */
+ public static ReportType[] getReports(Class> sender) {
+ if (sender == null)
+ throw new IllegalArgumentException("sender cannot be NULL.");
+ List result = new ArrayList();
+
+ // Go through all the fields
+ for (Field field : getReportFields(sender)) {
+ try {
+ field.setAccessible(true);
+ result.add((ReportType) field.get(null));
+ } catch (IllegalAccessException e) {
+ throw new FieldAccessException("Unable to read field " + field, e);
+ }
+ }
+ return result.toArray(new ReportType[0]);
+ }
+
+ /**
+ * Retrieve all publicly associated report fields.
+ * @param clazz - sender class.
+ * @return All associated report fields.
+ */
+ private static List getReportFields(Class> clazz) {
+ return FuzzyReflection.fromClass(clazz).getFieldList(
+ FuzzyFieldContract.newBuilder().
+ requireModifier(Modifier.STATIC).
+ typeDerivedOf(ReportType.class).
+ build()
+ );
+ }
+}
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java
index 85cebfe7..46d5e7f2 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java
@@ -285,43 +285,11 @@ public class PacketContainer implements Serializable {
* internal Minecraft ItemStack.
* @return A modifier for ItemStack array fields.
*/
- public StructureModifier getItemArrayModifier() {
-
- final EquivalentConverter stackConverter = BukkitConverters.getItemStackConverter();
-
+ public StructureModifier getItemArrayModifier() {
// Convert to and from the Bukkit wrapper
return structureModifier.withType(
MinecraftReflection.getItemStackArrayClass(),
- BukkitConverters.getIgnoreNull(new EquivalentConverter() {
-
- public Object getGeneric(Class>genericType, ItemStack[] specific) {
- Class> nmsStack = MinecraftReflection.getItemStackClass();
- Object[] result = (Object[]) Array.newInstance(nmsStack, specific.length);
-
- // Unwrap every item
- for (int i = 0; i < result.length; i++) {
- result[i] = stackConverter.getGeneric(nmsStack, specific[i]);
- }
- return result;
- }
-
- @Override
- public ItemStack[] getSpecific(Object generic) {
- Object[] input = (Object[]) generic;
- ItemStack[] result = new ItemStack[input.length];
-
- // Add the wrapper
- for (int i = 0; i < result.length; i++) {
- result[i] = stackConverter.getSpecific(input[i]);
- }
- return result;
- }
-
- @Override
- public Class getSpecificType() {
- return ItemStack[].class;
- }
- }));
+ BukkitConverters.getIgnoreNull(new ItemStackArrayConverter()));
}
/**
@@ -556,4 +524,40 @@ public class PacketContainer implements Serializable {
return method;
}
+
+ /**
+ * Represents an equivalent converter for ItemStack arrays.
+ * @author Kristian
+ */
+ private static class ItemStackArrayConverter implements EquivalentConverter {
+ final EquivalentConverter stackConverter = BukkitConverters.getItemStackConverter();
+
+ public Object getGeneric(Class>genericType, ItemStack[] specific) {
+ Class> nmsStack = MinecraftReflection.getItemStackClass();
+ Object[] result = (Object[]) Array.newInstance(nmsStack, specific.length);
+
+ // Unwrap every item
+ for (int i = 0; i < result.length; i++) {
+ result[i] = stackConverter.getGeneric(nmsStack, specific[i]);
+ }
+ return result;
+ }
+
+ @Override
+ public ItemStack[] getSpecific(Object generic) {
+ Object[] input = (Object[]) generic;
+ ItemStack[] result = new ItemStack[input.length];
+
+ // Add the wrapper
+ for (int i = 0; i < result.length; i++) {
+ result[i] = stackConverter.getSpecific(input[i]);
+ }
+ return result;
+ }
+
+ @Override
+ public Class getSpecificType() {
+ return ItemStack[].class;
+ }
+ }
}
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java
index c9a0b6bb..a3fc2748 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java
@@ -1,352 +1,358 @@
-/*
- * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
- * Copyright (C) 2012 Kristian S. Stangeland
- *
- * This program is free software; you can redistribute it and/or modify it under the terms of the
- * GNU General Public License as published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
- * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with this program;
- * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
- * 02111-1307 USA
- */
-
-package com.comphenix.protocol.injector.player;
-
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.Arrays;
-
-import net.sf.cglib.proxy.*;
-
-import org.bukkit.entity.Player;
-
-import com.comphenix.protocol.concurrency.IntegerSet;
-import com.comphenix.protocol.error.ErrorReporter;
-import com.comphenix.protocol.error.Report;
-import com.comphenix.protocol.error.ReportType;
-import com.comphenix.protocol.events.PacketListener;
-import com.comphenix.protocol.injector.GamePhase;
-import com.comphenix.protocol.injector.ListenerInvoker;
-import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
-import com.comphenix.protocol.reflect.FieldUtils;
-import com.comphenix.protocol.reflect.FuzzyReflection;
-import com.comphenix.protocol.reflect.ObjectWriter;
-import com.comphenix.protocol.reflect.VolatileField;
-import com.comphenix.protocol.reflect.instances.DefaultInstances;
-import com.comphenix.protocol.reflect.instances.ExistingGenerator;
-import com.comphenix.protocol.utility.MinecraftMethods;
-import com.comphenix.protocol.utility.MinecraftReflection;
-import com.comphenix.protocol.utility.MinecraftVersion;
-
-/**
- * Represents a player hook into the NetServerHandler class.
- *
- * @author Kristian
- */
-class NetworkServerInjector extends PlayerInjector {
- // Disconnected field
- public static final ReportType REPORT_ASSUMING_DISCONNECT_FIELD = new ReportType("Unable to find 'disconnected' field. Assuming %s.");
- public static final ReportType REPORT_DISCONNECT_FIELD_MISSING = new ReportType("Cannot find disconnected field. Is ProtocolLib up to date?");
- public static final ReportType REPORT_DISCONNECT_FIELD_FAILURE = new ReportType("Unable to update disconnected field. Player quit event may be sent twice.");
-
- private volatile static CallbackFilter callbackFilter;
- private volatile static boolean foundSendPacket;
-
- private volatile static Field disconnectField;
- private InjectedServerConnection serverInjection;
-
- // Determine if we're listening
- private IntegerSet sendingFilters;
-
- // Used to create proxy objects
- private ClassLoader classLoader;
-
- // Whether or not the player has disconnected
- private boolean hasDisconnected;
-
- // Used to copy fields
- private final ObjectWriter writer = new ObjectWriter();
-
- public NetworkServerInjector(
- ClassLoader classLoader, ErrorReporter reporter, Player player,
- ListenerInvoker invoker, IntegerSet sendingFilters,
- InjectedServerConnection serverInjection) throws IllegalAccessException {
-
- super(reporter, player, invoker);
- this.classLoader = classLoader;
- this.sendingFilters = sendingFilters;
- this.serverInjection = serverInjection;
- }
-
- @Override
- protected boolean hasListener(int packetID) {
- return sendingFilters.contains(packetID);
- }
-
- @Override
- public void sendServerPacket(Object packet, boolean filtered) throws InvocationTargetException {
- Object serverDelegate = filtered ? serverHandlerRef.getValue() : serverHandlerRef.getOldValue();
-
- if (serverDelegate != null) {
- try {
- // Note that invocation target exception is a wrapper for a checked exception
- MinecraftMethods.getSendPacketMethod().invoke(serverDelegate, packet);
-
- } catch (IllegalArgumentException e) {
- throw e;
- } catch (InvocationTargetException e) {
- throw e;
- } catch (IllegalAccessException e) {
- throw new IllegalStateException("Unable to access send packet method.", e);
- }
- } else {
- throw new IllegalStateException("Unable to load server handler. Cannot send packet.");
- }
- }
-
- @Override
- public void injectManager() {
-
- if (serverHandlerRef == null)
- throw new IllegalStateException("Cannot find server handler.");
- // Don't inject twice
- if (serverHandlerRef.getValue() instanceof Factory)
- return;
-
- if (!tryInjectManager()) {
- Class> serverHandlerClass = MinecraftReflection.getNetServerHandlerClass();
-
- // Try to override the proxied object
- if (proxyServerField != null) {
- serverHandlerRef = new VolatileField(proxyServerField, serverHandler, true);
- serverHandler = serverHandlerRef.getValue();
-
- if (serverHandler == null)
- throw new RuntimeException("Cannot hook player: Inner proxy object is NULL.");
- else
- serverHandlerClass = serverHandler.getClass();
-
- // Try again
- if (tryInjectManager()) {
- // It worked - probably
- return;
- }
- }
-
- throw new RuntimeException(
- "Cannot hook player: Unable to find a valid constructor for the "
- + serverHandlerClass.getName() + " object.");
- }
- }
-
- private boolean tryInjectManager() {
- Class> serverClass = serverHandler.getClass();
-
- Enhancer ex = new Enhancer();
- Callback sendPacketCallback = new MethodInterceptor() {
- @Override
- public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
- Object packet = args[0];
-
- if (packet != null) {
- packet = handlePacketSending(packet);
-
- // A NULL packet indicate cancelling
- if (packet != null)
- args[0] = packet;
- else
- return null;
- }
-
- // Call the method directly
- return proxy.invokeSuper(obj, args);
- };
- };
- Callback noOpCallback = NoOp.INSTANCE;
-
- // Share callback filter - that way, we avoid generating a new class for
- // every logged in player.
- if (callbackFilter == null) {
- final Method sendPacket = MinecraftMethods.getSendPacketMethod();
-
- callbackFilter = new CallbackFilter() {
- @Override
- public int accept(Method method) {
- if (isCallableEqual(sendPacket, method)) {
- foundSendPacket = true;
- return 0;
- } else {
- return 1;
- }
- }
- };
- }
-
- ex.setClassLoader(classLoader);
- ex.setSuperclass(serverClass);
- ex.setCallbacks(new Callback[] { sendPacketCallback, noOpCallback });
- ex.setCallbackFilter(callbackFilter);
-
- // Find the Minecraft NetServerHandler superclass
- Class> minecraftSuperClass = getFirstMinecraftSuperClass(serverHandler.getClass());
- ExistingGenerator generator = ExistingGenerator.fromObjectFields(serverHandler, minecraftSuperClass);
- DefaultInstances serverInstances = null;
-
- // Maybe the proxy instance can help?
- Object proxyInstance = getProxyServerHandler();
-
- // Use the existing server proxy when we create one
- if (proxyInstance != null && proxyInstance != serverHandler) {
- serverInstances = DefaultInstances.fromArray(generator,
- ExistingGenerator.fromObjectArray(new Object[] { proxyInstance }));
- } else {
- serverInstances = DefaultInstances.fromArray(generator);
- }
-
- serverInstances.setNonNull(true);
- serverInstances.setMaximumRecursion(1);
-
- Object proxyObject = serverInstances.forEnhancer(ex).getDefault(serverClass);
-
- // Inject it now
- if (proxyObject != null) {
- // Did we override a sendPacket method?
- if (!foundSendPacket) {
- throw new IllegalArgumentException("Unable to find a sendPacket method in " + serverClass);
- }
-
- serverInjection.replaceServerHandler(serverHandler, proxyObject);
- serverHandlerRef.setValue(proxyObject);
- return true;
- } else {
- return false;
- }
- }
-
- /**
- * Determine if the two methods are equal in terms of call semantics.
- *
- * Two methods are equal if they have the same name, parameter types and return type.
- * @param first - first method.
- * @param second - second method.
- * @return TRUE if they are, FALSE otherwise.
- */
- private boolean isCallableEqual(Method first, Method second) {
- return first.getName().equals(second.getName()) &&
- first.getReturnType().equals(second.getReturnType()) &&
- Arrays.equals(first.getParameterTypes(), second.getParameterTypes());
- }
-
- private Object getProxyServerHandler() {
- if (proxyServerField != null && !proxyServerField.equals(serverHandlerRef.getField())) {
- try {
- return FieldUtils.readField(proxyServerField, serverHandler, true);
- } catch (Throwable e) {
- // Oh well
- }
- }
-
- return null;
- }
-
- private Class> getFirstMinecraftSuperClass(Class> clazz) {
- if (MinecraftReflection.isMinecraftClass(clazz))
- return clazz;
- else if (clazz.equals(Object.class))
- return clazz;
- else
- return getFirstMinecraftSuperClass(clazz.getSuperclass());
- }
-
- @Override
- protected void cleanHook() {
- if (serverHandlerRef != null && serverHandlerRef.isCurrentSet()) {
- writer.copyTo(serverHandlerRef.getValue(), serverHandlerRef.getOldValue(), serverHandler.getClass());
- serverHandlerRef.revertValue();
-
- try {
- if (getNetHandler() != null) {
- // Restore packet listener
- try {
- FieldUtils.writeField(netHandlerField, networkManager, serverHandlerRef.getOldValue(), true);
- } catch (IllegalAccessException e) {
- // Oh well
- e.printStackTrace();
- }
- }
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- }
-
- // Prevent the PlayerQuitEvent from being sent twice
- if (hasDisconnected) {
- setDisconnect(serverHandlerRef.getValue(), true);
- }
- }
-
- serverInjection.revertServerHandler(serverHandler);
- }
-
- @Override
- 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) {
- // Assume it's the first ...
- if (disconnectField == null) {
- disconnectField = FuzzyReflection.fromObject(handler).getFieldByType("disconnected", boolean.class);
- reporter.reportWarning(this, Report.newBuilder(REPORT_ASSUMING_DISCONNECT_FIELD).messageParam(disconnectField));
-
- // Try again
- if (disconnectField != null) {
- setDisconnect(handler, value);
- return;
- }
- }
-
- // This is really bad
- reporter.reportDetailed(this, Report.newBuilder(REPORT_DISCONNECT_FIELD_MISSING).error(e));
-
- } catch (IllegalAccessException e) {
- reporter.reportWarning(this, Report.newBuilder(REPORT_DISCONNECT_FIELD_FAILURE).error(e));
- }
- }
-
- @Override
- public UnsupportedListener checkListener(MinecraftVersion version, PacketListener listener) {
- // We support everything
- return null;
- }
-
- @Override
- public boolean canInject(GamePhase phase) {
- // Doesn't work when logging in
- return phase == GamePhase.PLAYING;
- }
-
- @Override
- public PlayerInjectHooks getHookType() {
- return PlayerInjectHooks.NETWORK_SERVER_OBJECT;
- }
-}
+/*
+ * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
+ * Copyright (C) 2012 Kristian S. Stangeland
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program;
+ * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA
+ */
+
+package com.comphenix.protocol.injector.player;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+
+import net.sf.cglib.proxy.*;
+
+import org.bukkit.entity.Player;
+
+import com.comphenix.protocol.concurrency.IntegerSet;
+import com.comphenix.protocol.error.ErrorReporter;
+import com.comphenix.protocol.error.Report;
+import com.comphenix.protocol.error.ReportType;
+import com.comphenix.protocol.events.PacketListener;
+import com.comphenix.protocol.injector.GamePhase;
+import com.comphenix.protocol.injector.ListenerInvoker;
+import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
+import com.comphenix.protocol.reflect.FieldUtils;
+import com.comphenix.protocol.reflect.FuzzyReflection;
+import com.comphenix.protocol.reflect.ObjectWriter;
+import com.comphenix.protocol.reflect.VolatileField;
+import com.comphenix.protocol.reflect.instances.DefaultInstances;
+import com.comphenix.protocol.reflect.instances.ExistingGenerator;
+import com.comphenix.protocol.utility.MinecraftMethods;
+import com.comphenix.protocol.utility.MinecraftReflection;
+import com.comphenix.protocol.utility.MinecraftVersion;
+
+/**
+ * Represents a player hook into the NetServerHandler class.
+ *
+ * @author Kristian
+ */
+class NetworkServerInjector extends PlayerInjector {
+ // Disconnected field
+ public static final ReportType REPORT_ASSUMING_DISCONNECT_FIELD = new ReportType("Unable to find 'disconnected' field. Assuming %s.");
+ public static final ReportType REPORT_DISCONNECT_FIELD_MISSING = new ReportType("Cannot find disconnected field. Is ProtocolLib up to date?");
+ public static final ReportType REPORT_DISCONNECT_FIELD_FAILURE = new ReportType("Unable to update disconnected field. Player quit event may be sent twice.");
+
+ private volatile static CallbackFilter callbackFilter;
+ private volatile static boolean foundSendPacket;
+
+ private volatile static Field disconnectField;
+ private InjectedServerConnection serverInjection;
+
+ // Determine if we're listening
+ private IntegerSet sendingFilters;
+
+ // Used to create proxy objects
+ private ClassLoader classLoader;
+
+ // Whether or not the player has disconnected
+ private boolean hasDisconnected;
+
+ // Used to copy fields
+ private final ObjectWriter writer = new ObjectWriter();
+
+ public NetworkServerInjector(
+ ClassLoader classLoader, ErrorReporter reporter, Player player,
+ ListenerInvoker invoker, IntegerSet sendingFilters,
+ InjectedServerConnection serverInjection) throws IllegalAccessException {
+
+ super(reporter, player, invoker);
+ this.classLoader = classLoader;
+ this.sendingFilters = sendingFilters;
+ this.serverInjection = serverInjection;
+ }
+
+ @Override
+ protected boolean hasListener(int packetID) {
+ return sendingFilters.contains(packetID);
+ }
+
+ @Override
+ public void sendServerPacket(Object packet, boolean filtered) throws InvocationTargetException {
+ Object serverDelegate = filtered ? serverHandlerRef.getValue() : serverHandlerRef.getOldValue();
+
+ if (serverDelegate != null) {
+ try {
+ // Note that invocation target exception is a wrapper for a checked exception
+ MinecraftMethods.getSendPacketMethod().invoke(serverDelegate, packet);
+
+ } catch (IllegalArgumentException e) {
+ throw e;
+ } catch (InvocationTargetException e) {
+ throw e;
+ } catch (IllegalAccessException e) {
+ throw new IllegalStateException("Unable to access send packet method.", e);
+ }
+ } else {
+ throw new IllegalStateException("Unable to load server handler. Cannot send packet.");
+ }
+ }
+
+ @Override
+ public void injectManager() {
+
+ if (serverHandlerRef == null)
+ throw new IllegalStateException("Cannot find server handler.");
+ // Don't inject twice
+ if (serverHandlerRef.getValue() instanceof Factory)
+ return;
+
+ if (!tryInjectManager()) {
+ Class> serverHandlerClass = MinecraftReflection.getNetServerHandlerClass();
+
+ // Try to override the proxied object
+ if (proxyServerField != null) {
+ serverHandlerRef = new VolatileField(proxyServerField, serverHandler, true);
+ serverHandler = serverHandlerRef.getValue();
+
+ if (serverHandler == null)
+ throw new RuntimeException("Cannot hook player: Inner proxy object is NULL.");
+ else
+ serverHandlerClass = serverHandler.getClass();
+
+ // Try again
+ if (tryInjectManager()) {
+ // It worked - probably
+ return;
+ }
+ }
+
+ throw new RuntimeException(
+ "Cannot hook player: Unable to find a valid constructor for the "
+ + serverHandlerClass.getName() + " object.");
+ }
+ }
+
+ private boolean tryInjectManager() {
+ Class> serverClass = serverHandler.getClass();
+
+ Enhancer ex = new Enhancer();
+ Callback sendPacketCallback = new MethodInterceptor() {
+ @Override
+ public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
+ Object packet = args[0];
+
+ if (packet != null) {
+ packet = handlePacketSending(packet);
+
+ // A NULL packet indicate cancelling
+ if (packet != null)
+ args[0] = packet;
+ else
+ return null;
+ }
+
+ // Call the method directly
+ return proxy.invokeSuper(obj, args);
+ };
+ };
+ 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 SendMethodFilter();
+ }
+
+ ex.setClassLoader(classLoader);
+ ex.setSuperclass(serverClass);
+ ex.setCallbacks(new Callback[] { sendPacketCallback, noOpCallback });
+ ex.setCallbackFilter(callbackFilter);
+
+ // Find the Minecraft NetServerHandler superclass
+ Class> minecraftSuperClass = getFirstMinecraftSuperClass(serverHandler.getClass());
+ ExistingGenerator generator = ExistingGenerator.fromObjectFields(serverHandler, minecraftSuperClass);
+ DefaultInstances serverInstances = null;
+
+ // Maybe the proxy instance can help?
+ Object proxyInstance = getProxyServerHandler();
+
+ // Use the existing server proxy when we create one
+ if (proxyInstance != null && proxyInstance != serverHandler) {
+ serverInstances = DefaultInstances.fromArray(generator,
+ ExistingGenerator.fromObjectArray(new Object[] { proxyInstance }));
+ } else {
+ serverInstances = DefaultInstances.fromArray(generator);
+ }
+
+ serverInstances.setNonNull(true);
+ serverInstances.setMaximumRecursion(1);
+
+ Object proxyObject = serverInstances.forEnhancer(ex).getDefault(serverClass);
+
+ // Inject it now
+ if (proxyObject != null) {
+ // Did we override a sendPacket method?
+ if (!foundSendPacket) {
+ throw new IllegalArgumentException("Unable to find a sendPacket method in " + serverClass);
+ }
+
+ serverInjection.replaceServerHandler(serverHandler, proxyObject);
+ serverHandlerRef.setValue(proxyObject);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private Object getProxyServerHandler() {
+ if (proxyServerField != null && !proxyServerField.equals(serverHandlerRef.getField())) {
+ try {
+ return FieldUtils.readField(proxyServerField, serverHandler, true);
+ } catch (Throwable e) {
+ // Oh well
+ }
+ }
+
+ return null;
+ }
+
+ private Class> getFirstMinecraftSuperClass(Class> clazz) {
+ if (MinecraftReflection.isMinecraftClass(clazz))
+ return clazz;
+ else if (clazz.equals(Object.class))
+ return clazz;
+ else
+ return getFirstMinecraftSuperClass(clazz.getSuperclass());
+ }
+
+ @Override
+ protected void cleanHook() {
+ if (serverHandlerRef != null && serverHandlerRef.isCurrentSet()) {
+ writer.copyTo(serverHandlerRef.getValue(), serverHandlerRef.getOldValue(), serverHandler.getClass());
+ serverHandlerRef.revertValue();
+
+ try {
+ if (getNetHandler() != null) {
+ // Restore packet listener
+ try {
+ FieldUtils.writeField(netHandlerField, networkManager, serverHandlerRef.getOldValue(), true);
+ } catch (IllegalAccessException e) {
+ // Oh well
+ e.printStackTrace();
+ }
+ }
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ }
+
+ // Prevent the PlayerQuitEvent from being sent twice
+ if (hasDisconnected) {
+ setDisconnect(serverHandlerRef.getValue(), true);
+ }
+ }
+
+ serverInjection.revertServerHandler(serverHandler);
+ }
+
+ @Override
+ 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) {
+ // Assume it's the first ...
+ if (disconnectField == null) {
+ disconnectField = FuzzyReflection.fromObject(handler).getFieldByType("disconnected", boolean.class);
+ reporter.reportWarning(this, Report.newBuilder(REPORT_ASSUMING_DISCONNECT_FIELD).messageParam(disconnectField));
+
+ // Try again
+ if (disconnectField != null) {
+ setDisconnect(handler, value);
+ return;
+ }
+ }
+
+ // This is really bad
+ reporter.reportDetailed(this, Report.newBuilder(REPORT_DISCONNECT_FIELD_MISSING).error(e));
+
+ } catch (IllegalAccessException e) {
+ reporter.reportWarning(this, Report.newBuilder(REPORT_DISCONNECT_FIELD_FAILURE).error(e));
+ }
+ }
+
+ @Override
+ public UnsupportedListener checkListener(MinecraftVersion version, PacketListener listener) {
+ // We support everything
+ return null;
+ }
+
+ @Override
+ public boolean canInject(GamePhase phase) {
+ // Doesn't work when logging in
+ return phase == GamePhase.PLAYING;
+ }
+
+ @Override
+ public PlayerInjectHooks getHookType() {
+ return PlayerInjectHooks.NETWORK_SERVER_OBJECT;
+ }
+
+ /**
+ * Represents a CallbackFilter that only matches the SendPacket method.
+ * @author Kristian
+ */
+ private static class SendMethodFilter implements CallbackFilter {
+ private Method sendPacket = MinecraftMethods.getSendPacketMethod();
+
+ @Override
+ public int accept(Method method) {
+ if (isCallableEqual(sendPacket, method)) {
+ NetworkServerInjector.foundSendPacket = true;
+ return 0;
+ } else {
+ return 1;
+ }
+ }
+
+ /**
+ * Determine if the two methods are equal in terms of call semantics.
+ *
+ * Two methods are equal if they have the same name, parameter types and return type.
+ * @param first - first method.
+ * @param second - second method.
+ * @return TRUE if they are, FALSE otherwise.
+ */
+ private boolean isCallableEqual(Method first, Method second) {
+ return first.getName().equals(second.getName()) &&
+ first.getReturnType().equals(second.getReturnType()) &&
+ Arrays.equals(first.getParameterTypes(), second.getParameterTypes());
+ }
+ }
+}
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java
index 02036a07..bf5a0cd9 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java
@@ -1,664 +1,686 @@
-/*
- * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
- * Copyright (C) 2012 Kristian S. Stangeland
- *
- * This program is free software; you can redistribute it and/or modify it under the terms of the
- * GNU General Public License as published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
- * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with this program;
- * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
- * 02111-1307 USA
- */
-
-package com.comphenix.protocol.injector.player;
-
-import java.io.DataInputStream;
-import java.io.IOException;
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.net.Socket;
-import java.net.SocketAddress;
-import net.sf.cglib.proxy.Factory;
-
-import org.bukkit.entity.Player;
-
-import com.comphenix.protocol.Packets;
-import com.comphenix.protocol.error.ErrorReporter;
-import com.comphenix.protocol.error.Report;
-import com.comphenix.protocol.error.ReportType;
-import com.comphenix.protocol.events.PacketContainer;
-import com.comphenix.protocol.events.PacketEvent;
-import com.comphenix.protocol.events.PacketListener;
-import com.comphenix.protocol.injector.BukkitUnwrapper;
-import com.comphenix.protocol.injector.GamePhase;
-import com.comphenix.protocol.injector.ListenerInvoker;
-import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
-import com.comphenix.protocol.injector.server.SocketInjector;
-import com.comphenix.protocol.reflect.FieldUtils;
-import com.comphenix.protocol.reflect.FuzzyReflection;
-import com.comphenix.protocol.reflect.StructureModifier;
-import com.comphenix.protocol.reflect.VolatileField;
-import com.comphenix.protocol.utility.MinecraftReflection;
-import com.comphenix.protocol.utility.MinecraftVersion;
-
-public abstract class PlayerInjector implements SocketInjector {
- // Disconnect method related reports
- public static final ReportType REPORT_ASSUME_DISCONNECT_METHOD = new ReportType("Cannot find disconnect method by name. Assuming %s.");
- public static final ReportType REPORT_INVALID_ARGUMENT_DISCONNECT = new ReportType("Invalid argument passed to disconnect method: %s");
- public static final ReportType REPORT_CANNOT_ACCESS_DISCONNECT = new ReportType("Unable to access disconnect method.");
-
- public static final ReportType REPORT_CANNOT_CLOSE_SOCKET = new ReportType("Unable to close socket.");
- public static final ReportType REPORT_ACCESS_DENIED_CLOSE_SOCKET = new ReportType("Insufficient permissions. Cannot close socket.");
-
- public static final ReportType REPORT_DETECTED_CUSTOM_SERVER_HANDLER =
- new ReportType("Detected server handler proxy type by another plugin. Conflict may occur!");
- public static final ReportType REPORT_CANNOT_PROXY_SERVER_HANDLER = new ReportType("Unable to load server handler from proxy type.");
-
- public static final ReportType REPORT_CANNOT_UPDATE_PLAYER = new ReportType("Cannot update player in PlayerEvent.");
- public static final ReportType REPORT_CANNOT_HANDLE_PACKET = new ReportType("Cannot handle server packet.");
-
- // Net login handler stuff
- private static Field netLoginNetworkField;
-
- // Different disconnect methods
- private static Method loginDisconnect;
- private static Method serverDisconnect;
-
- // Cache previously retrieved fields
- protected static Field serverHandlerField;
- protected static Field proxyServerField;
-
- protected static Field networkManagerField;
- protected static Field netHandlerField;
- protected static Field socketField;
- protected static Field socketAddressField;
-
- private static Field inputField;
- private static Field entityPlayerField;
-
- // Whether or not we're using a proxy type
- private static boolean hasProxyType;
-
- // To add our injected array lists
- protected static StructureModifier