Archiviert
13
0

Merge branch 'master' into gh-pages

Conflicts:
	ItemDisguise/.settings/org.eclipse.core.resources.prefs
Dieser Commit ist enthalten in:
Kristian S. Stangeland 2013-05-14 01:24:47 +02:00
Commit 7e20abdd37
63 geänderte Dateien mit 8584 neuen und 6001 gelöschten Zeilen

Datei anzeigen

@ -7,6 +7,12 @@
</attributes> </attributes>
</classpathentry> </classpathentry>
<classpathentry combineaccessrules="false" kind="src" path="/ProtocolLib"/> <classpathentry combineaccessrules="false" kind="src" path="/ProtocolLib"/>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6">
<attributes> <attributes>
<attribute name="maven.pomderived" value="true"/> <attribute name="maven.pomderived" value="true"/>

Datei anzeigen

@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.comphenix.protocol</groupId> <groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId> <artifactId>ProtocolLib</artifactId>
<version>2.3.0</version> <version>2.4.3</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<description>Provides read/write access to the Minecraft protocol.</description> <description>Provides read/write access to the Minecraft protocol.</description>
@ -57,7 +57,7 @@
</goals> </goals>
<configuration> <configuration>
<shadedArtifactAttached>false</shadedArtifactAttached> <shadedArtifactAttached>false</shadedArtifactAttached>
<createDependencyReducedPom>true</createDependencyReducedPom> <createDependencyReducedPom>false</createDependencyReducedPom>
<relocations> <relocations>
<relocation> <relocation>
@ -203,7 +203,7 @@
<dependency> <dependency>
<groupId>org.bukkit</groupId> <groupId>org.bukkit</groupId>
<artifactId>craftbukkit</artifactId> <artifactId>craftbukkit</artifactId>
<version>1.4.7-R0.1</version> <version>1.5.1-R0.2-SNAPSHOT</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
@ -219,16 +219,16 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.powermock</groupId> <groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId> <artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version> <version>${powermock.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.powermock</groupId> <groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId> <artifactId>powermock-api-mockito</artifactId>
<version>${powermock.version}</version> <version>${powermock.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
</project> </project>

Datei anzeigen

@ -1,160 +1,167 @@
/* /*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland * Copyright (C) 2012 Kristian S. Stangeland
* *
* This program is free software; you can redistribute it and/or modify it under the terms of the * 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 * GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version. * 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; * 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. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * 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; * 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 * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol; package com.comphenix.protocol;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.comphenix.protocol.async.AsyncListenerHandler; import com.comphenix.protocol.async.AsyncListenerHandler;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.ListeningWhitelist; import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.BukkitUnwrapper; import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.injector.server.AbstractInputStreamLookup; import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory; import com.comphenix.protocol.injector.BukkitUnwrapper;
import com.comphenix.protocol.injector.spigot.SpigotPacketInjector; import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.injector.spigot.SpigotPacketInjector;
import com.comphenix.protocol.reflect.MethodUtils; import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.ObjectWriter; import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler; import com.comphenix.protocol.reflect.MethodUtils;
import com.comphenix.protocol.reflect.compiler.StructureCompiler; import com.comphenix.protocol.reflect.ObjectWriter;
import com.comphenix.protocol.reflect.instances.CollectionGenerator; import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
import com.comphenix.protocol.reflect.instances.DefaultInstances; import com.comphenix.protocol.reflect.compiler.StructureCompiler;
import com.comphenix.protocol.reflect.instances.PrimitiveGenerator; import com.comphenix.protocol.reflect.instances.CollectionGenerator;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.wrappers.ChunkPosition; import com.comphenix.protocol.reflect.instances.PrimitiveGenerator;
import com.comphenix.protocol.wrappers.WrappedDataWatcher; import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.WrappedWatchableObject; import com.comphenix.protocol.wrappers.ChunkPosition;
import com.comphenix.protocol.wrappers.nbt.io.NbtBinarySerializer; 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 * Used to fix ClassLoader leaks that may lead to filling up the permanent generation.
*/ *
class CleanupStaticMembers { * @author Kristian
*/
private ClassLoader loader; class CleanupStaticMembers {
private ErrorReporter reporter; // Reports
public final static ReportType REPORT_CANNOT_RESET_FIELD = new ReportType("Unable to reset field %s: %s");
public CleanupStaticMembers(ClassLoader loader, ErrorReporter reporter) { public final static ReportType REPORT_CANNOT_UNLOAD_CLASS = new ReportType("Unable to unload class %s.");
this.loader = loader;
this.reporter = reporter; private ClassLoader loader;
} private ErrorReporter reporter;
/** public CleanupStaticMembers(ClassLoader loader, ErrorReporter reporter) {
* Ensure that the previous ClassLoader is not leaking. this.loader = loader;
*/ this.reporter = reporter;
public void resetAll() { }
// This list must always be updated
Class<?>[] publicClasses = { /**
AsyncListenerHandler.class, ListeningWhitelist.class, PacketContainer.class, * Ensure that the previous ClassLoader is not leaking.
BukkitUnwrapper.class, DefaultInstances.class, CollectionGenerator.class, */
PrimitiveGenerator.class, FuzzyReflection.class, MethodUtils.class, public void resetAll() {
BackgroundCompiler.class, StructureCompiler.class, // This list must always be updated
ObjectWriter.class, Packets.Server.class, Packets.Client.class, Class<?>[] publicClasses = {
ChunkPosition.class, WrappedDataWatcher.class, WrappedWatchableObject.class, AsyncListenerHandler.class, ListeningWhitelist.class, PacketContainer.class,
AbstractInputStreamLookup.class, TemporaryPlayerFactory.class, SpigotPacketInjector.class, BukkitUnwrapper.class, DefaultInstances.class, CollectionGenerator.class,
MinecraftReflection.class, NbtBinarySerializer.class PrimitiveGenerator.class, FuzzyReflection.class, MethodUtils.class,
}; BackgroundCompiler.class, StructureCompiler.class,
ObjectWriter.class, Packets.Server.class, Packets.Client.class,
String[] internalClasses = { ChunkPosition.class, WrappedDataWatcher.class, WrappedWatchableObject.class,
"com.comphenix.protocol.events.SerializedOfflinePlayer", AbstractInputStreamLookup.class, TemporaryPlayerFactory.class, SpigotPacketInjector.class,
"com.comphenix.protocol.injector.player.InjectedServerConnection", MinecraftReflection.class, NbtBinarySerializer.class
"com.comphenix.protocol.injector.player.NetworkFieldInjector", };
"com.comphenix.protocol.injector.player.NetworkObjectInjector",
"com.comphenix.protocol.injector.player.NetworkServerInjector", String[] internalClasses = {
"com.comphenix.protocol.injector.player.PlayerInjector", "com.comphenix.protocol.events.SerializedOfflinePlayer",
"com.comphenix.protocol.injector.EntityUtilities", "com.comphenix.protocol.injector.player.InjectedServerConnection",
"com.comphenix.protocol.injector.packet.PacketRegistry", "com.comphenix.protocol.injector.player.NetworkFieldInjector",
"com.comphenix.protocol.injector.packet.PacketInjector", "com.comphenix.protocol.injector.player.NetworkObjectInjector",
"com.comphenix.protocol.injector.packet.ReadPacketModifier", "com.comphenix.protocol.injector.player.NetworkServerInjector",
"com.comphenix.protocol.injector.StructureCache", "com.comphenix.protocol.injector.player.PlayerInjector",
"com.comphenix.protocol.reflect.compiler.BoxingHelper", "com.comphenix.protocol.injector.EntityUtilities",
"com.comphenix.protocol.reflect.compiler.MethodDescriptor", "com.comphenix.protocol.injector.packet.PacketRegistry",
"com.comphenix.protocol.wrappers.nbt.WrappedElement", "com.comphenix.protocol.injector.packet.PacketInjector",
}; "com.comphenix.protocol.injector.packet.ReadPacketModifier",
"com.comphenix.protocol.injector.StructureCache",
resetClasses(publicClasses); "com.comphenix.protocol.reflect.compiler.BoxingHelper",
resetClasses(getClasses(loader, internalClasses)); "com.comphenix.protocol.reflect.compiler.MethodDescriptor",
} "com.comphenix.protocol.wrappers.nbt.WrappedElement",
};
private void resetClasses(Class<?>[] classes) {
// Reset each class one by one resetClasses(publicClasses);
for (Class<?> clazz : classes) { resetClasses(getClasses(loader, internalClasses));
resetClass(clazz); }
}
} private void resetClasses(Class<?>[] classes) {
// Reset each class one by one
private void resetClass(Class<?> clazz) { for (Class<?> clazz : classes) {
for (Field field : clazz.getFields()) { resetClass(clazz);
Class<?> type = field.getType(); }
}
// Only check static non-primitive fields. We also skip strings.
if (Modifier.isStatic(field.getModifiers()) && private void resetClass(Class<?> clazz) {
!type.isPrimitive() && !type.equals(String.class)) { for (Field field : clazz.getFields()) {
Class<?> type = field.getType();
try {
setFinalStatic(field, null); // Only check static non-primitive fields. We also skip strings.
} catch (IllegalAccessException e) { if (Modifier.isStatic(field.getModifiers()) &&
// Just inform the player !type.isPrimitive() && !type.equals(String.class)) {
reporter.reportWarning(this, "Unable to reset field " + field.getName() + ": " + e.getMessage(), e);
} try {
} setFinalStatic(field, null);
} } catch (IllegalAccessException e) {
} // Just inform the player
reporter.reportWarning(this,
// HACK! HAACK! Report.newBuilder(REPORT_CANNOT_RESET_FIELD).error(e).messageParam(field.getName(), e.getMessage())
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 // HACK! HAACK!
if (isFinal) { private static void setFinalStatic(Field field, Object newValue) throws IllegalAccessException {
FieldUtils.writeField(modifiersField, field, modifier & ~Modifier.FINAL, true); int modifier = field.getModifiers();
} boolean isFinal = Modifier.isFinal(modifier);
// Now we can safely modify the field Field modifiersField = isFinal ? FieldUtils.getField(Field.class, "modifiers", true) : null;
FieldUtils.writeStaticField(field, newValue, true);
// We have to remove the final field first
// Revert modifier if (isFinal) {
if (isFinal) { FieldUtils.writeField(modifiersField, field, modifier & ~Modifier.FINAL, true);
FieldUtils.writeField(modifiersField, field, modifier, true); }
}
} // Now we can safely modify the field
FieldUtils.writeStaticField(field, newValue, true);
private Class<?>[] getClasses(ClassLoader loader, String[] names) {
List<Class<?>> output = new ArrayList<Class<?>>(); // Revert modifier
if (isFinal) {
for (String name : names) { FieldUtils.writeField(modifiersField, field, modifier, true);
try { }
output.add(loader.loadClass(name)); }
} catch (ClassNotFoundException e) {
// Warn the user private Class<?>[] getClasses(ClassLoader loader, String[] names) {
reporter.reportWarning(this, "Unable to unload class " + name, e); List<Class<?>> output = new ArrayList<Class<?>>();
}
} for (String name : names) {
try {
return output.toArray(new Class<?>[0]); 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]);
}
}

Datei anzeigen

@ -1,109 +1,117 @@
/* /*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland * Copyright (C) 2012 Kristian S. Stangeland
* *
* This program is free software; you can redistribute it and/or modify it under the terms of the * 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 * GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version. * 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; * 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. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * 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; * 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 * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol; package com.comphenix.protocol;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
/** import com.comphenix.protocol.error.ReportType;
* Base class for all our commands.
* /**
* @author Kristian * Base class for all our commands.
*/ *
abstract class CommandBase implements CommandExecutor { * @author Kristian
*/
public static final String PERMISSION_ADMIN = "protocol.admin"; abstract class CommandBase implements CommandExecutor {
public static final ReportType REPORT_COMMAND_ERROR = new ReportType("Cannot execute command %s.");
private String permission; public static final ReportType REPORT_UNEXPECTED_COMMAND = new ReportType("Incorrect command assigned to %s.");
private String name;
private int minimumArgumentCount; public static final String PERMISSION_ADMIN = "protocol.admin";
protected ErrorReporter reporter; private String permission;
private String name;
public CommandBase(ErrorReporter reporter, String permission, String name) { private int minimumArgumentCount;
this(reporter, permission, name, 0);
} protected ErrorReporter reporter;
public CommandBase(ErrorReporter reporter, String permission, String name, int minimumArgumentCount) { public CommandBase(ErrorReporter reporter, String permission, String name) {
this.reporter = reporter; this(reporter, permission, name, 0);
this.name = name; }
this.permission = permission;
this.minimumArgumentCount = minimumArgumentCount; public CommandBase(ErrorReporter reporter, String permission, String name, int minimumArgumentCount) {
} this.reporter = reporter;
this.name = name;
@Override this.permission = permission;
public final boolean onCommand(CommandSender sender, Command command, String label, String[] args) { this.minimumArgumentCount = minimumArgumentCount;
try { }
// Make sure we're dealing with the correct command
if (!command.getName().equalsIgnoreCase(name)) { @Override
return false; public final boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
} try {
if (permission != null && !sender.hasPermission(permission)) { // Make sure we're dealing with the correct command
sender.sendMessage(ChatColor.RED + "You haven't got permission to run this command."); if (!command.getName().equalsIgnoreCase(name)) {
return true; reporter.reportWarning(this, Report.newBuilder(REPORT_UNEXPECTED_COMMAND).messageParam(this));
} return false;
}
// Check argument length if (permission != null && !sender.hasPermission(permission)) {
if (args != null && args.length >= minimumArgumentCount) { sender.sendMessage(ChatColor.RED + "You haven't got permission to run this command.");
return handleCommand(sender, args); return true;
} else { }
return false;
} // Check argument length
if (args != null && args.length >= minimumArgumentCount) {
} catch (Exception e) { return handleCommand(sender, args);
reporter.reportDetailed(this, "Cannot execute command " + name, e, sender, label, args); } else {
return true; sender.sendMessage(ChatColor.RED + "Insufficient commands. You need at least " + minimumArgumentCount);
} return false;
} }
/** } catch (Exception e) {
* Retrieve the permission necessary to execute this command. reporter.reportDetailed(this,
* @return The permission, or NULL if not needed. Report.newBuilder(REPORT_COMMAND_ERROR).error(e).messageParam(name).callerParam(sender, label, args)
*/ );
public String getPermission() { return true;
return permission; }
} }
/** /**
* Retrieve the primary name of this command. * Retrieve the permission necessary to execute this command.
* @return Primary name. * @return The permission, or NULL if not needed.
*/ */
public String getName() { public String getPermission() {
return name; return permission;
} }
/** /**
* Retrieve the error reporter. * Retrieve the primary name of this command.
* @return Error reporter. * @return Primary name.
*/ */
protected ErrorReporter getReporter() { public String getName() {
return reporter; return name;
} }
/** /**
* Main implementation of this command. * Retrieve the error reporter.
* @param sender - command sender. * @return Error reporter.
* @param args */
* @return protected ErrorReporter getReporter() {
*/ return reporter;
protected abstract boolean handleCommand(CommandSender sender, String[] args); }
}
/**
* Main implementation of this command.
* @param sender - command sender.
* @param args
* @return
*/
protected abstract boolean handleCommand(CommandSender sender, String[] args);
}

Datei anzeigen

@ -0,0 +1,482 @@
package com.comphenix.protocol;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.conversations.Conversable;
import org.bukkit.conversations.Conversation;
import org.bukkit.conversations.ConversationAbandonedEvent;
import org.bukkit.conversations.ConversationAbandonedListener;
import org.bukkit.conversations.ConversationContext;
import org.bukkit.conversations.ConversationFactory;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.MultipleLinesPrompt.MultipleConversationCanceller;
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.PacketEvent;
import com.google.common.collect.DiscreteDomains;
import com.google.common.collect.Range;
import com.google.common.collect.Ranges;
/**
* A command to apply JavaScript filtering to the packet command.
*
* @author Kristian
*/
public class CommandFilter extends CommandBase {
public static final ReportType REPORT_FALLBACK_ENGINE = new ReportType("Falling back to the Rhino engine.");
public static final ReportType REPORT_CANNOT_LOAD_FALLBACK_ENGINE = new ReportType("Could not load Rhino either. Please upgrade your JVM or OS.");
public static final ReportType REPORT_PACKAGES_UNSUPPORTED_IN_ENGINE = new ReportType("Unable to initialize packages for JavaScript engine.");
public static final ReportType REPORT_FILTER_REMOVED_FOR_ERROR = new ReportType("Removing filter %s for causing %s.");
public static final ReportType REPORT_CANNOT_HANDLE_CONVERSATION = new ReportType("Cannot handle conversation.");
public interface FilterFailedHandler{
/**
* Invoked when a given filter has failed.
* @param event - the packet event.
* @param filter - the filter that failed.
* @param ex - the failure.
* @returns TRUE to keep processing this filter, FALSE to remove it.
*/
public boolean handle(PacketEvent event, Filter filter, Exception ex);
}
/**
* Possible sub commands.
*
* @author Kristian
*/
private enum SubCommand {
ADD, REMOVE;
}
/**
* A filter that will be used to process a packet event.
* @author Kristian
*/
public static class Filter {
private final String name;
private final String predicate;
private final IntegerSet ranges;
/**
* Construct a new immutable filter.
* @param name - the unique name of the filter.
* @param predicate - the JavaScript predicate that will be used to filter packet events.
* @param ranges - a list of valid packet ID ranges that this filter applies to.
*/
public Filter(String name, String predicate, Set<Integer> packets) {
this.name = name;
this.predicate = predicate;
this.ranges = new IntegerSet(Packets.MAXIMUM_PACKET_ID + 1);
this.ranges.addAll(packets);
}
/**
* Retrieve the unique name of the filter.
* @return Unique name of the filter.
*/
public String getName() {
return name;
}
/**
* Retrieve the JavaScript predicate that will be used to filter packet events.
* @return Predicate itself.
*/
public String getPredicate() {
return predicate;
}
/**
* Retrieve a copy of the set of packets this filter applies to.
* @return Set of packets this filter applies to.
*/
public Set<Integer> getRanges() {
return ranges.toSet();
}
/**
* Determine whether or not a packet event needs to be passed to this filter.
* @param event - the event to test.
* @return TRUE if it does, FALSE otherwise.
*/
private boolean isApplicable(PacketEvent event) {
return ranges.contains(event.getPacketID());
}
/**
* Evaluate the current filter using the provided ScriptEngine as context.
* <p>
* This context may be modified with additional code.
* @param context - the current script context.
* @param event - the packet event to evaluate.
* @return TRUE to pass this packet event on to the debug listeners, FALSE otherwise.
* @throws ScriptException If the compilation failed or the filter is not valid.
*/
public boolean evaluate(ScriptEngine context, PacketEvent event) throws ScriptException {
if (!isApplicable(event))
return true;
// Ensure that the predicate has been compiled
compile(context);
try {
Object result = ((Invocable) context).invokeFunction(name, event, event.getPacket().getHandle());
if (result instanceof Boolean)
return (Boolean) result;
else
throw new ScriptException("Filter result wasn't a boolean: " + result);
} catch (NoSuchMethodException e) {
// Must be a fault with the script engine itself
throw new IllegalStateException("Unable to compile " + name + " into current script engine.", e);
}
}
/**
* Force the compilation of a specific filter.
* @param context - the current script context.
* @throws ScriptException If the compilation failed.
*/
public void compile(ScriptEngine context) throws ScriptException {
if (context.get(name) == null) {
context.eval("var " + name + " = function(event, packet) {\n" + predicate);
}
}
/**
* Clean up all associated code from this filter in the provided script engine.
* @param context - the current script context.
*/
public void close(ScriptEngine context) {
context.put(name, null);
}
}
private class CompilationSuccessCanceller implements MultipleConversationCanceller {
@Override
public boolean cancelBasedOnInput(ConversationContext context, String in) {
throw new UnsupportedOperationException("Cannot cancel on the last line alone.");
}
@Override
public void setConversation(Conversation conversation) {
// Ignore
}
@Override
public boolean cancelBasedOnInput(ConversationContext context, String currentLine, StringBuilder lines, int lineCount) {
try {
engine.eval("function(event, packet) {\n" + lines.toString());
// It compiles - accept the filter!
return true;
} catch (ScriptException e) {
// We also have the function() line
int realLineCount = lineCount + 1;
// Only possible to recover from an error on the last line.
return e.getLineNumber() < realLineCount;
}
}
@Override
public CompilationSuccessCanceller clone() {
return new CompilationSuccessCanceller();
}
}
/**
* Name of this command.
*/
public static final String NAME = "filter";
// Default error handler
private FilterFailedHandler defaultFailedHandler;
// Currently registered filters
private List<Filter> filters = new ArrayList<Filter>();
// Owner plugin
private final Plugin plugin;
// Whether or not the command is enabled
private ProtocolConfig config;
// Script engine
private ScriptEngine engine;
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();
}
private void initalizeScript() {
try {
// First attempt
initializeEngine();
// Oh for ..
if (!isInitialized()) {
throw new ScriptException("A JavaScript engine could not be found.");
}
} catch (ScriptException e1) {
// It's not a huge deal
printPackageWarning(e1);
if (!config.getScriptEngineName().equals("rhino")) {
reporter.reportWarning(this, Report.newBuilder(REPORT_FALLBACK_ENGINE));
config.setScriptEngineName("rhino");
config.saveAll();
try {
initializeEngine();
if (!isInitialized()) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_FALLBACK_ENGINE));
}
} catch (ScriptException e2) {
// And again ..
printPackageWarning(e2);
}
}
}
}
private void printPackageWarning(ScriptException e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_PACKAGES_UNSUPPORTED_IN_ENGINE).error(e));
}
/**
* Initialize the current configured engine.
* @throws ScriptException If we are unable to import packages.
*/
private void initializeEngine() throws ScriptException {
ScriptEngineManager manager = new ScriptEngineManager();
engine = manager.getEngineByName(config.getScriptEngineName());
// Import useful packages
if (engine != null) {
engine.eval("importPackage(org.bukkit);");
engine.eval("importPackage(com.comphenix.protocol.reflect);");
}
}
/**
* Determine if the filter engine has been successfully initialized.
* @return TRUE if it has, FALSE otherwise.
*/
public boolean isInitialized() {
return engine != null;
}
private FilterFailedHandler getDefaultErrorHandler() {
// No need to create a new object every time
if (defaultFailedHandler == null) {
defaultFailedHandler = new FilterFailedHandler() {
@Override
public boolean handle(PacketEvent event, Filter filter, Exception ex) {
reporter.reportMinimal(plugin, "filterEvent(PacketEvent)", ex, event);
reporter.reportWarning(this,
Report.newBuilder(REPORT_FILTER_REMOVED_FOR_ERROR).messageParam(filter.getName(), ex.getClass().getSimpleName())
);
return false;
}
};
}
return defaultFailedHandler;
}
/**
* Determine whether or not to pass the given packet event to the packet listeners.
* <p>
* Uses a default filter failure handler that simply prints the error message and removes the filter.
* @param event - the event.
* @return TRUE if we should, FALSE otherwise.
*/
public boolean filterEvent(PacketEvent event) {
return filterEvent(event, getDefaultErrorHandler());
}
/**
* Determine whether or not to pass the given packet event to the packet listeners.
* @param event - the event.
* @param handler - failure handler.
* @return TRUE if we should, FALSE otherwise.
* @throws FilterFailedException If one of the filters failed.
*/
public boolean filterEvent(PacketEvent event, FilterFailedHandler handler) {
for (Iterator<Filter> it = filters.iterator(); it.hasNext(); ) {
Filter filter = it.next();
try {
if (!filter.evaluate(engine, event)) {
return false;
}
} catch (Exception ex) {
if (!handler.handle(event, filter, ex)) {
it.remove();
}
}
}
// Pass!
return true;
}
/*
* Description: Adds or removes a simple packet filter.
Usage: /<command> add|remove name [packet IDs]
*/
@Override
protected boolean handleCommand(CommandSender sender, String[] args) {
if (!config.isDebug()) {
sender.sendMessage(ChatColor.RED + "Debug mode must be enabled in the configuration first!");
return true;
}
if (!isInitialized()) {
sender.sendMessage(ChatColor.RED + "JavaScript engine was not present. Filter system is disabled.");
return true;
}
final SubCommand command = parseCommand(args, 0);
final String name = args[1];
switch (command) {
case ADD:
// Never overwrite an existing filter
if (findFilter(name) != null) {
sender.sendMessage(ChatColor.RED + "Filter " + name + " already exists. Remove it first.");
return true;
}
final Set<Integer> packets = parseRanges(args, 2);
sender.sendMessage("Enter filter program ('}' to complete or CANCEL):");
// Make sure we can use the conversable interface
if (sender instanceof Conversable) {
final MultipleLinesPrompt prompt =
new MultipleLinesPrompt(new CompilationSuccessCanceller(), "function(event, packet) {");
new ConversationFactory(plugin).
withFirstPrompt(prompt).
withEscapeSequence("CANCEL").
withLocalEcho(false).
addConversationAbandonedListener(new ConversationAbandonedListener() {
@Override
public void conversationAbandoned(ConversationAbandonedEvent event) {
try {
final Conversable whom = event.getContext().getForWhom();
if (event.gracefulExit()) {
String predicate = prompt.removeAccumulatedInput(event.getContext());
Filter filter = new Filter(name, predicate, packets);
// Print the last line as well
whom.sendRawMessage(prompt.getPromptText(event.getContext()));
try {
// Force early compilation
filter.compile(engine);
filters.add(filter);
whom.sendRawMessage(ChatColor.GOLD + "Added filter " + name);
} catch (ScriptException e) {
e.printStackTrace();
whom.sendRawMessage(ChatColor.GOLD + "Compilation error: " + e.getMessage());
}
} else {
// Too bad
whom.sendRawMessage(ChatColor.RED + "Cancelled filter.");
}
} catch (Exception e) {
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_HANDLE_CONVERSATION).error(e).callerParam(event)
);
}
}
}).
buildConversation((Conversable) sender).
begin();
} else {
sender.sendMessage(ChatColor.RED + "Only console and players are supported!");
}
break;
case REMOVE:
Filter filter = findFilter(name);
// See if it exists before we remove it
if (filter != null) {
filter.close(engine);
filters.remove(filter);
sender.sendMessage(ChatColor.GOLD + "Removed filter " + name);
} else {
sender.sendMessage(ChatColor.RED + "Unable to find a filter by the name " + name);
}
break;
}
return true;
}
private Set<Integer> parseRanges(String[] args, int start) {
List<Range<Integer>> ranges = RangeParser.getRanges(args, 2, args.length - 1, Ranges.closed(0, 255));
Set<Integer> flatten = new HashSet<Integer>();
if (ranges.isEmpty()) {
// Use every packet ID
ranges.add(Ranges.closed(0, 255));
}
// Finally, flatten it all
for (Range<Integer> range : ranges) {
flatten.addAll(range.asSet(DiscreteDomains.integers()));
}
return flatten;
}
/**
* Lookup a filter by its name.
* @param name - the filter name.
* @return The filter, or NULL if not found.
*/
private Filter findFilter(String name) {
// We'll just use a linear scan for now - we don't expect that many filters
for (Filter filter : filters) {
if (filter.getName().equalsIgnoreCase(name)) {
return filter;
}
}
return null;
}
private SubCommand parseCommand(String[] args, int index) {
String text = args[index].toUpperCase();
try {
return SubCommand.valueOf(text);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(text + " is not a valid sub command. Must be add or remove.", e);
}
}
}

Datei anzeigen

@ -1,137 +1,147 @@
/* /*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland * Copyright (C) 2012 Kristian S. Stangeland
* *
* This program is free software; you can redistribute it and/or modify it under the terms of the * 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 * GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version. * 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; * 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. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * 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; * 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 * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol; package com.comphenix.protocol;
import java.io.IOException; import java.io.IOException;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.metrics.Updater; import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.metrics.Updater.UpdateResult; import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.metrics.Updater.UpdateType; import com.comphenix.protocol.metrics.Updater;
import com.comphenix.protocol.utility.WrappedScheduler; import com.comphenix.protocol.metrics.Updater.UpdateResult;
import com.comphenix.protocol.metrics.Updater.UpdateType;
/** import com.comphenix.protocol.utility.WrappedScheduler;
* Handles the "protocol" administration command.
* /**
* @author Kristian * Handles the "protocol" administration command.
*/ *
class CommandProtocol extends CommandBase { * @author Kristian
/** */
* Name of this command. class CommandProtocol extends CommandBase {
*/ /**
public static final String NAME = "protocol"; * Name of this command.
*/
private Plugin plugin; public static final String NAME = "protocol";
private Updater updater;
private ProtocolConfig config; public static final ReportType REPORT_HTTP_ERROR = new ReportType("Http error: %s");
public static final ReportType REPORT_CANNOT_CHECK_FOR_UPDATES = new ReportType("Cannot check updates for ProtocolLib.");
public CommandProtocol(ErrorReporter reporter, Plugin plugin, Updater updater, ProtocolConfig config) { public static final ReportType REPORT_CANNOT_UPDATE_PLUGIN = new ReportType("Cannot update ProtocolLib.");
super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 1);
this.plugin = plugin; private Plugin plugin;
this.updater = updater; private Updater updater;
this.config = config; private ProtocolConfig config;
}
public CommandProtocol(ErrorReporter reporter, Plugin plugin, Updater updater, ProtocolConfig config) {
@Override super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 1);
protected boolean handleCommand(CommandSender sender, String[] args) { this.plugin = plugin;
String subCommand = args[0]; this.updater = updater;
this.config = config;
// Only return TRUE if we executed the correct command }
if (subCommand.equalsIgnoreCase("config") || subCommand.equalsIgnoreCase("reload"))
reloadConfiguration(sender); @Override
else if (subCommand.equalsIgnoreCase("check")) protected boolean handleCommand(CommandSender sender, String[] args) {
checkVersion(sender); String subCommand = args[0];
else if (subCommand.equalsIgnoreCase("update"))
updateVersion(sender); // Only return TRUE if we executed the correct command
else if (subCommand.equalsIgnoreCase("config") || subCommand.equalsIgnoreCase("reload"))
return false; reloadConfiguration(sender);
return true; else if (subCommand.equalsIgnoreCase("check"))
} checkVersion(sender);
else if (subCommand.equalsIgnoreCase("update"))
public void checkVersion(final CommandSender sender) { updateVersion(sender);
// Perform on an async thread else
WrappedScheduler.runAsynchronouslyOnce(plugin, new Runnable() { return false;
@Override return true;
public void run() { }
try {
UpdateResult result = updater.update(UpdateType.NO_DOWNLOAD, true); public void checkVersion(final CommandSender sender) {
sender.sendMessage(ChatColor.BLUE + "[ProtocolLib] " + result.toString()); // Perform on an async thread
} catch (Exception e) { WrappedScheduler.runAsynchronouslyOnce(plugin, new Runnable() {
if (isHttpError(e)) { @Override
getReporter().reportWarning(this, "Http error: " + e.getCause().getMessage()); public void run() {
} else { try {
getReporter().reportDetailed(this, "Cannot check updates for ProtocolLib.", e, sender); UpdateResult result = updater.update(UpdateType.NO_DOWNLOAD, true);
} sender.sendMessage(ChatColor.BLUE + "[ProtocolLib] " + result.toString());
} } catch (Exception e) {
} if (isHttpError(e)) {
}, 0L); getReporter().reportWarning(this,
Report.newBuilder(REPORT_HTTP_ERROR).messageParam(e.getCause().getMessage())
updateFinished(); );
} } else {
getReporter().reportDetailed(this, Report.newBuilder(REPORT_CANNOT_CHECK_FOR_UPDATES).error(e).callerParam(sender));
public void updateVersion(final CommandSender sender) { }
// Perform on an async thread }
WrappedScheduler.runAsynchronouslyOnce(plugin, new Runnable() { }
@Override }, 0L);
public void run() {
try { updateFinished();
UpdateResult result = updater.update(UpdateType.DEFAULT, true); }
sender.sendMessage(ChatColor.BLUE + "[ProtocolLib] " + result.toString());
} catch (Exception e) { public void updateVersion(final CommandSender sender) {
if (isHttpError(e)) { // Perform on an async thread
getReporter().reportWarning(this, "Http error: " + e.getCause().getMessage()); WrappedScheduler.runAsynchronouslyOnce(plugin, new Runnable() {
} else { @Override
getReporter().reportDetailed(this, "Cannot update ProtocolLib.", e, sender); public void run() {
} try {
} UpdateResult result = updater.update(UpdateType.DEFAULT, true);
} sender.sendMessage(ChatColor.BLUE + "[ProtocolLib] " + result.toString());
}, 0L); } catch (Exception e) {
if (isHttpError(e)) {
updateFinished(); getReporter().reportWarning(this,
} Report.newBuilder(REPORT_HTTP_ERROR).messageParam(e.getCause().getMessage())
);
private boolean isHttpError(Exception e) { } else {
Throwable cause = e.getCause(); getReporter().reportDetailed(this,Report.newBuilder(REPORT_CANNOT_UPDATE_PLUGIN).error(e).callerParam(sender));
}
if (cause instanceof IOException) { }
// Thanks for making the message a part of the API ... }
return cause.getMessage().contains("HTTP response"); }, 0L);
} else {
return false; updateFinished();
} }
}
private boolean isHttpError(Exception e) {
/** Throwable cause = e.getCause();
* Prevent further automatic updates until the next delay.
*/ if (cause instanceof IOException) {
public void updateFinished() { // Thanks for making the message a part of the API ...
long currentTime = System.currentTimeMillis() / ProtocolLibrary.MILLI_PER_SECOND; return cause.getMessage().contains("HTTP response");
} else {
config.setAutoLastTime(currentTime); return false;
config.saveAll(); }
} }
public void reloadConfiguration(CommandSender sender) { /**
plugin.reloadConfig(); * Prevent further automatic updates until the next delay.
sender.sendMessage(ChatColor.BLUE + "Reloaded configuration!"); */
} public void updateFinished() {
} long currentTime = System.currentTimeMillis() / ProtocolLibrary.MILLI_PER_SECOND;
config.setAutoLastTime(currentTime);
config.saveAll();
}
public void reloadConfiguration(CommandSender sender) {
plugin.reloadConfig();
sender.sendMessage(ChatColor.BLUE + "Reloaded configuration!");
}
}

Datei anzeigen

@ -0,0 +1,163 @@
package com.comphenix.protocol;
import org.bukkit.conversations.Conversation;
import org.bukkit.conversations.ConversationCanceller;
import org.bukkit.conversations.ConversationContext;
import org.bukkit.conversations.ExactMatchConversationCanceller;
import org.bukkit.conversations.Prompt;
import org.bukkit.conversations.StringPrompt;
/**
* Represents a conversation prompt that accepts a list of lines.
*
* @author Kristian
*/
class MultipleLinesPrompt extends StringPrompt {
/**
* Represents a canceller that determines if the multiple lines prompt is finished.
* @author Kristian
*/
public static interface MultipleConversationCanceller extends ConversationCanceller {
@Override
public boolean cancelBasedOnInput(ConversationContext context, String currentLine);
/**
* Determine if the current prompt is done based on the context, last
* line and collected lines.
*
* @param context - current context.
* @param currentLine - current (last) line.
* @param lines - collected lines.
* @param lineCount - number of lines.
* @return TRUE if we are done, FALSE otherwise.
*/
public boolean cancelBasedOnInput(ConversationContext context, String currentLine,
StringBuilder lines, int lineCount);
}
/**
* A wrapper class for turning a ConversationCanceller into a MultipleConversationCanceller.
* @author Kristian
*/
private static class MultipleWrapper implements MultipleConversationCanceller {
private ConversationCanceller canceller;
public MultipleWrapper(ConversationCanceller canceller) {
this.canceller = canceller;
}
@Override
public boolean cancelBasedOnInput(ConversationContext context, String currentLine) {
return canceller.cancelBasedOnInput(context, currentLine);
}
@Override
public boolean cancelBasedOnInput(ConversationContext context, String currentLine,
StringBuilder lines, int lineCount) {
return cancelBasedOnInput(context, currentLine);
}
@Override
public void setConversation(Conversation conversation) {
canceller.setConversation(conversation);
}
@Override
public MultipleWrapper clone() {
return new MultipleWrapper(canceller.clone());
}
}
// Feels a bit like Android
private static final String KEY = "multiple_lines_prompt";
private static final String KEY_LAST = KEY + ".last_line";
private static final String KEY_LINES = KEY + ".linecount";
private final MultipleConversationCanceller endMarker;
private final String initialPrompt;
/**
* Retrieve and remove the current accumulated input.
*
* @param context
* - conversation context.
* @return The accumulated input, or NULL if not found.
*/
public String removeAccumulatedInput(ConversationContext context) {
Object result = context.getSessionData(KEY);
if (result instanceof StringBuilder) {
context.setSessionData(KEY, null);
context.setSessionData(KEY_LINES, null);
return ((StringBuilder) result).toString();
} else {
return null;
}
}
/**
* Construct a multiple lines input prompt with a specific end marker.
* <p>
* This is usually an empty string.
*
* @param endMarker - the end marker.
*/
public MultipleLinesPrompt(String endMarker, String initialPrompt) {
this(new ExactMatchConversationCanceller(endMarker), initialPrompt);
}
/**
* Construct a multiple lines input prompt with a specific end marker implementation.
* <p>
* Note: Use {@link #MultipleLinesPrompt(MultipleConversationCanceller, String)} if implementing a custom canceller.
* @param endMarker - the end marker.
* @param initialPrompt - the initial prompt text.
*/
public MultipleLinesPrompt(ConversationCanceller endMarker, String initialPrompt) {
this.endMarker = new MultipleWrapper(endMarker);
this.initialPrompt = initialPrompt;
}
/**
* Construct a multiple lines input prompt with a specific end marker implementation.
* @param endMarker - the end marker.
* @param initialPrompt - the initial prompt text.
*/
public MultipleLinesPrompt(MultipleConversationCanceller endMarker, String initialPrompt) {
this.endMarker = endMarker;
this.initialPrompt = initialPrompt;
}
@Override
public Prompt acceptInput(ConversationContext context, String in) {
StringBuilder result = (StringBuilder) context.getSessionData(KEY);
Integer count = (Integer) context.getSessionData(KEY_LINES);
// Handle first run
if (result == null)
context.setSessionData(KEY, result = new StringBuilder());
if (count == null)
count = 0;
// Save the last line as well
context.setSessionData(KEY_LAST, in);
context.setSessionData(KEY_LINES, ++count);
result.append(in + "\n");
// And we're done
if (endMarker.cancelBasedOnInput(context, in, result, count))
return Prompt.END_OF_CONVERSATION;
else
return this;
}
@Override
public String getPromptText(ConversationContext context) {
Object last = context.getSessionData(KEY_LAST);
if (last instanceof String)
return (String) last;
else
return initialPrompt;
}
}

Datei anzeigen

@ -21,6 +21,7 @@ import java.lang.reflect.InvocationTargetException;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketContainer;
/** /**
@ -43,7 +44,7 @@ public interface PacketStream {
* Send a packet to the given player. * Send a packet to the given player.
* @param reciever - the reciever. * @param reciever - the reciever.
* @param packet - packet to send. * @param packet - packet to send.
* @param filters - whether or not to invoke any packet filters. * @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
* @throws InvocationTargetException - if an error occured when sending the packet. * @throws InvocationTargetException - if an error occured when sending the packet.
*/ */
public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters)
@ -63,7 +64,7 @@ public interface PacketStream {
* Simulate recieving a certain packet from a given player. * Simulate recieving a certain packet from a given player.
* @param sender - the sender. * @param sender - the sender.
* @param packet - the packet that was sent. * @param packet - the packet that was sent.
* @param filters - whether or not to invoke any packet filters. * @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
* @throws InvocationTargetException If the reflection machinery failed. * @throws InvocationTargetException If the reflection machinery failed.
* @throws IllegalAccessException If the underlying method caused an error. * @throws IllegalAccessException If the underlying method caused an error.
*/ */

Datei anzeigen

@ -114,6 +114,10 @@ public final class Packets {
public static final int PLAYER_INFO = 201; public static final int PLAYER_INFO = 201;
public static final int ABILITIES = 202; public static final int ABILITIES = 202;
public static final int TAB_COMPLETE = 203; public static final int TAB_COMPLETE = 203;
public static final int SCOREBOARD_OBJECTIVE = 206;
public static final int UPDATE_SCORE = 207;
public static final int DISPLAY_SCOREBOARD = 208;
public static final int TEAMS = 209;
public static final int CUSTOM_PAYLOAD = 250; public static final int CUSTOM_PAYLOAD = 250;
public static final int KEY_RESPONSE = 252; public static final int KEY_RESPONSE = 252;
public static final int KEY_REQUEST = 253; public static final int KEY_REQUEST = 253;

Datei anzeigen

@ -18,12 +18,15 @@
package com.comphenix.protocol; package com.comphenix.protocol;
import java.io.File; import java.io.File;
import java.io.IOException;
import org.bukkit.configuration.Configuration; import org.bukkit.configuration.Configuration;
import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
import com.google.common.base.Charsets;
import com.google.common.io.Files;
/** /**
* Represents the configuration of ProtocolLib. * Represents the configuration of ProtocolLib.
@ -31,6 +34,7 @@ import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
* @author Kristian * @author Kristian
*/ */
class ProtocolConfig { class ProtocolConfig {
private static final String LAST_UPDATE_FILE = "lastupdate";
private static final String SECTION_GLOBAL = "global"; private static final String SECTION_GLOBAL = "global";
private static final String SECTION_AUTOUPDATER = "auto updater"; private static final String SECTION_AUTOUPDATER = "auto updater";
@ -40,12 +44,14 @@ class ProtocolConfig {
private static final String IGNORE_VERSION_CHECK = "ignore version check"; private static final String IGNORE_VERSION_CHECK = "ignore version check";
private static final String BACKGROUND_COMPILER_ENABLED = "background compiler"; private static final String BACKGROUND_COMPILER_ENABLED = "background compiler";
private static final String DEBUG_MODE_ENABLED = "debug";
private static final String INJECTION_METHOD = "injection method"; private static final String INJECTION_METHOD = "injection method";
private static final String SCRIPT_ENGINE_NAME = "script engine";
private static final String UPDATER_NOTIFY = "notify"; private static final String UPDATER_NOTIFY = "notify";
private static final String UPDATER_DOWNLAD = "download"; private static final String UPDATER_DOWNLAD = "download";
private static final String UPDATER_DELAY = "delay"; private static final String UPDATER_DELAY = "delay";
private static final String UPDATER_LAST_TIME = "last";
// Defaults // Defaults
private static final long DEFAULT_UPDATER_DELAY = 43200; private static final long DEFAULT_UPDATER_DELAY = 43200;
@ -57,6 +63,11 @@ class ProtocolConfig {
private ConfigurationSection global; private ConfigurationSection global;
private ConfigurationSection updater; private ConfigurationSection updater;
// Last update time
private long lastUpdateTime;
private boolean configChanged;
private boolean valuesChanged;
public ProtocolConfig(Plugin plugin) { public ProtocolConfig(Plugin plugin) {
this(plugin, plugin.getConfig()); this(plugin, plugin.getConfig());
} }
@ -70,10 +81,64 @@ class ProtocolConfig {
* Reload configuration file. * Reload configuration file.
*/ */
public void reloadConfig() { public void reloadConfig() {
// Reset
configChanged = false;
valuesChanged = false;
this.config = plugin.getConfig(); this.config = plugin.getConfig();
this.lastUpdateTime = loadLastUpdate();
loadSections(!loadingSections); loadSections(!loadingSections);
} }
/**
* Load the last update time stamp from the file system.
* @return Last update time stamp.
*/
private long loadLastUpdate() {
File dataFile = getLastUpdateFile();
if (dataFile.exists()) {
try {
return Long.parseLong(Files.toString(dataFile, Charsets.UTF_8));
} catch (NumberFormatException e) {
throw new RuntimeException("Cannot parse " + dataFile + " as a number.", e);
} catch (IOException e) {
throw new RuntimeException("Cannot read " + dataFile, e);
}
} else {
// Default last update
return 0;
}
}
/**
* Store the given time stamp.
* @param value - time stamp to store.
*/
private void saveLastUpdate(long value) {
File dataFile = getLastUpdateFile();
// The data folder must exist
dataFile.getParentFile().mkdirs();
if (dataFile.exists())
dataFile.delete();
try {
Files.write(Long.toString(value), dataFile, Charsets.UTF_8);
} catch (IOException e) {
throw new RuntimeException("Cannot write " + dataFile, e);
}
}
/**
* Retrieve the file that is used to store the update time stamp.
* @return File storing the update time stamp.
*/
private File getLastUpdateFile() {
return new File(plugin.getDataFolder(), LAST_UPDATE_FILE);
}
/** /**
* Load data sections. * Load data sections.
* @param copyDefaults - whether or not to copy configuration defaults. * @param copyDefaults - whether or not to copy configuration defaults.
@ -100,6 +165,17 @@ class ProtocolConfig {
System.out.println("[ProtocolLib] Created default configuration."); System.out.println("[ProtocolLib] Created default configuration.");
} }
} }
/**
* Set a particular configuration key value pair.
* @param section - the configuration root.
* @param path - the path to the key.
* @param value - the value to set.
*/
private void setConfig(ConfigurationSection section, String path, Object value) {
configChanged = true;
section.set(path, value);
}
/** /**
* Retrieve a reference to the configuration file. * Retrieve a reference to the configuration file.
@ -122,7 +198,7 @@ class ProtocolConfig {
* @param value - TRUE to do this automatically, FALSE otherwise. * @param value - TRUE to do this automatically, FALSE otherwise.
*/ */
public void setAutoNotify(boolean value) { public void setAutoNotify(boolean value) {
updater.set(UPDATER_NOTIFY, value); setConfig(updater, UPDATER_NOTIFY, value);
} }
/** /**
@ -138,7 +214,25 @@ class ProtocolConfig {
* @param value - TRUE if it should. FALSE otherwise. * @param value - TRUE if it should. FALSE otherwise.
*/ */
public void setAutoDownload(boolean value) { public void setAutoDownload(boolean value) {
updater.set(UPDATER_DOWNLAD, value); setConfig(updater, UPDATER_DOWNLAD, value);
}
/**
* Determine whether or not debug mode is enabled.
* <p>
* This grants access to the filter command.
* @return TRUE if it is, FALSE otherwise.
*/
public boolean isDebug() {
return global.getBoolean(DEBUG_MODE_ENABLED, false);
}
/**
* Set whether or not debug mode is enabled.
* @param value - TRUE if it is enabled, FALSE otherwise.
*/
public void setDebug(boolean value) {
setConfig(global, DEBUG_MODE_ENABLED, value);
} }
/** /**
@ -160,17 +254,9 @@ class ProtocolConfig {
// Silently fix the delay // Silently fix the delay
if (delaySeconds < DEFAULT_UPDATER_DELAY) if (delaySeconds < DEFAULT_UPDATER_DELAY)
delaySeconds = DEFAULT_UPDATER_DELAY; delaySeconds = DEFAULT_UPDATER_DELAY;
updater.set(UPDATER_DELAY, delaySeconds); setConfig(updater, UPDATER_DELAY, delaySeconds);
} }
/**
* Retrieve the last time we updated, in seconds since 1970.01.01 00:00.
* @return Last update time.
*/
public long getAutoLastTime() {
return updater.getLong(UPDATER_LAST_TIME, 0);
}
/** /**
* The version of Minecraft to ignore the built-in safety feature. * The version of Minecraft to ignore the built-in safety feature.
* @return The version to ignore ProtocolLib's satefy. * @return The version to ignore ProtocolLib's satefy.
@ -188,7 +274,7 @@ class ProtocolConfig {
* @param ignoreVersion - the version of Minecraft where the satefy will be disabled. * @param ignoreVersion - the version of Minecraft where the satefy will be disabled.
*/ */
public void setIgnoreVersionCheck(String ignoreVersion) { public void setIgnoreVersionCheck(String ignoreVersion) {
global.set(IGNORE_VERSION_CHECK, ignoreVersion); setConfig(global, IGNORE_VERSION_CHECK, ignoreVersion);
} }
/** /**
@ -207,7 +293,7 @@ class ProtocolConfig {
* @param enabled - whether or not metrics is enabled. * @param enabled - whether or not metrics is enabled.
*/ */
public void setMetricsEnabled(boolean enabled) { public void setMetricsEnabled(boolean enabled) {
global.set(METRICS_ENABLED, enabled); setConfig(global, METRICS_ENABLED, enabled);
} }
/** /**
@ -226,7 +312,15 @@ class ProtocolConfig {
* @param enabled - TRUE if is enabled/running, FALSE otherwise. * @param enabled - TRUE if is enabled/running, FALSE otherwise.
*/ */
public void setBackgroundCompilerEnabled(boolean enabled) { public void setBackgroundCompilerEnabled(boolean enabled) {
global.set(BACKGROUND_COMPILER_ENABLED, enabled); setConfig(global, BACKGROUND_COMPILER_ENABLED, enabled);
}
/**
* Retrieve the last time we updated, in seconds since 1970.01.01 00:00.
* @return Last update time.
*/
public long getAutoLastTime() {
return lastUpdateTime;
} }
/** /**
@ -234,7 +328,26 @@ class ProtocolConfig {
* @param lastTimeSeconds - new last update time. * @param lastTimeSeconds - new last update time.
*/ */
public void setAutoLastTime(long lastTimeSeconds) { public void setAutoLastTime(long lastTimeSeconds) {
updater.set(UPDATER_LAST_TIME, lastTimeSeconds); this.valuesChanged = true;
this.lastUpdateTime = lastTimeSeconds;
}
/**
* Retrieve the unique name of the script engine to use for filtering.
* @return Script engine to use.
*/
public String getScriptEngineName() {
return global.getString(SCRIPT_ENGINE_NAME, "JavaScript");
}
/**
* Set the unique name of the script engine to use for filtering.
* <p>
* This setting will take effect next time ProtocolLib is started.
* @param name - name of the script engine to use.
*/
public void setScriptEngineName(String name) {
setConfig(global, SCRIPT_ENGINE_NAME, name);
} }
/** /**
@ -266,13 +379,20 @@ class ProtocolConfig {
* @return Injection method. * @return Injection method.
*/ */
public void setInjectionMethod(PlayerInjectHooks hook) { public void setInjectionMethod(PlayerInjectHooks hook) {
global.set(INJECTION_METHOD, hook.name()); setConfig(global, INJECTION_METHOD, hook.name());
} }
/** /**
* Save the current configuration file. * Save the current configuration file.
*/ */
public void saveAll() { public void saveAll() {
plugin.saveConfig(); if (valuesChanged)
saveLastUpdate(lastUpdateTime);
if (configChanged)
plugin.saveConfig();
// And we're done
valuesChanged = false;
configChanged = false;
} }
} }

Datei anzeigen

@ -33,8 +33,11 @@ import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import com.comphenix.protocol.async.AsyncFilterManager; import com.comphenix.protocol.async.AsyncFilterManager;
import com.comphenix.protocol.error.BasicErrorReporter;
import com.comphenix.protocol.error.DetailedErrorReporter; import com.comphenix.protocol.error.DetailedErrorReporter;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.DelayedSingleTask; import com.comphenix.protocol.injector.DelayedSingleTask;
import com.comphenix.protocol.injector.PacketFilterManager; import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
@ -43,6 +46,7 @@ import com.comphenix.protocol.metrics.Updater;
import com.comphenix.protocol.metrics.Updater.UpdateResult; import com.comphenix.protocol.metrics.Updater.UpdateResult;
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler; import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
import com.comphenix.protocol.utility.ChatExtensions; import com.comphenix.protocol.utility.ChatExtensions;
import com.comphenix.protocol.utility.MinecraftVersion;
/** /**
* The main entry point for ProtocolLib. * The main entry point for ProtocolLib.
@ -50,6 +54,24 @@ import com.comphenix.protocol.utility.ChatExtensions;
* @author Kristian * @author Kristian
*/ */
public class ProtocolLibrary extends JavaPlugin { public class ProtocolLibrary extends JavaPlugin {
// Every possible error or warning report type
public static final ReportType REPORT_CANNOT_LOAD_CONFIG = new ReportType("Cannot load configuration");
public static final ReportType REPORT_CANNOT_DELETE_CONFIG = new ReportType("Cannot delete old ProtocolLib configuration.");
public static final ReportType REPORT_CANNOT_PARSE_INJECTION_METHOD = new ReportType("Cannot parse injection method. Using default.");
public static final ReportType REPORT_PLUGIN_LOAD_ERROR = new ReportType("Cannot load ProtocolLib.");
public static final ReportType REPORT_PLUGIN_ENABLE_ERROR = new ReportType("Cannot enable ProtocolLib.");
public static final ReportType REPORT_METRICS_IO_ERROR = new ReportType("Unable to enable metrics due to network problems.");
public static final ReportType REPORT_METRICS_GENERIC_ERROR = new ReportType("Unable to enable metrics due to network problems.");
public static final ReportType REPORT_CANNOT_PARSE_MINECRAFT_VERSION = new ReportType("Unable to retrieve current Minecraft version.");
public static final ReportType REPORT_CANNOT_DETECT_CONFLICTING_PLUGINS = new ReportType("Unable to detect conflicting plugin versions.");
public static final ReportType REPORT_CANNOT_REGISTER_COMMAND = new ReportType("Cannot register command %s: %s");
public static final ReportType REPORT_CANNOT_CREATE_TIMEOUT_TASK = new ReportType("Unable to create packet timeout task.");
public static final ReportType REPORT_CANNOT_UPDATE_PLUGIN = new ReportType("Cannot perform automatic updates.");
/** /**
* The minimum version ProtocolLib has been tested with. * The minimum version ProtocolLib has been tested with.
*/ */
@ -58,7 +80,7 @@ public class ProtocolLibrary extends JavaPlugin {
/** /**
* The maximum version ProtocolLib has been tested with, * The maximum version ProtocolLib has been tested with,
*/ */
private static final String MAXIMUM_MINECRAFT_VERSION = "1.4.7"; private static final String MAXIMUM_MINECRAFT_VERSION = "1.5.2";
/** /**
* The number of milliseconds per second. * The number of milliseconds per second.
@ -71,7 +93,7 @@ public class ProtocolLibrary extends JavaPlugin {
private static PacketFilterManager protocolManager; private static PacketFilterManager protocolManager;
// Error reporter // Error reporter
private static ErrorReporter reporter; private static ErrorReporter reporter = new BasicErrorReporter();
// Metrics and statistisc // Metrics and statistisc
private Statistics statistisc; private Statistics statistisc;
@ -102,6 +124,7 @@ public class ProtocolLibrary extends JavaPlugin {
// Commands // Commands
private CommandProtocol commandProtocol; private CommandProtocol commandProtocol;
private CommandPacket commandPacket; private CommandPacket commandPacket;
private CommandFilter commandFilter;
// Whether or not disable is not needed // Whether or not disable is not needed
private boolean skipDisable; private boolean skipDisable;
@ -118,26 +141,34 @@ public class ProtocolLibrary extends JavaPlugin {
try { try {
config = new ProtocolConfig(this); config = new ProtocolConfig(this);
} catch (Exception e) { } catch (Exception e) {
detailedReporter.reportWarning(this, "Cannot load configuration", e); detailedReporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_CONFIG).error(e));
// Load it again // Load it again
if (deleteConfig()) { if (deleteConfig()) {
config = new ProtocolConfig(this); config = new ProtocolConfig(this);
} else { } else {
reporter.reportWarning(this, "Cannot delete old ProtocolLib configuration."); reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_DELETE_CONFIG));
} }
} }
// Print the state of the debug mode
if (config.isDebug()) {
logger.warning("Debug mode is enabled!");
}
try { try {
// Check for other versions // Check for other versions
checkConflictingVersions(); checkConflictingVersions();
// Handle unexpected Minecraft versions
MinecraftVersion version = verifyMinecraftVersion();
// Set updater // Set updater
updater = new Updater(this, logger, "protocollib", getFile(), "protocol.info"); updater = new Updater(this, logger, "protocollib", getFile(), "protocol.info");
unhookTask = new DelayedSingleTask(this); unhookTask = new DelayedSingleTask(this);
protocolManager = new PacketFilterManager( protocolManager = new PacketFilterManager(
getClassLoader(), getServer(), unhookTask, detailedReporter); getClassLoader(), getServer(), this, version, unhookTask, detailedReporter);
// Setup error reporter // Setup error reporter
detailedReporter.addGlobalParameter("manager", protocolManager); detailedReporter.addGlobalParameter("manager", protocolManager);
@ -152,18 +183,19 @@ public class ProtocolLibrary extends JavaPlugin {
protocolManager.setPlayerHook(hook); protocolManager.setPlayerHook(hook);
} }
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
detailedReporter.reportWarning(config, "Cannot parse injection method. Using default.", e); detailedReporter.reportWarning(config, Report.newBuilder(REPORT_CANNOT_PARSE_INJECTION_METHOD).error(e));
} }
// Initialize command handlers // Initialize command handlers
commandProtocol = new CommandProtocol(detailedReporter, this, updater, config); commandProtocol = new CommandProtocol(detailedReporter, this, updater, config);
commandPacket = new CommandPacket(detailedReporter, this, logger, protocolManager); commandFilter = new CommandFilter(detailedReporter, this, config);
commandPacket = new CommandPacket(detailedReporter, this, logger, commandFilter, protocolManager);
// Send logging information to player listeners too // Send logging information to player listeners too
setupBroadcastUsers(PERMISSION_INFO); setupBroadcastUsers(PERMISSION_INFO);
} catch (Throwable e) { } catch (Throwable e) {
detailedReporter.reportDetailed(this, "Cannot load ProtocolLib.", e, protocolManager); detailedReporter.reportDetailed(this, Report.newBuilder(REPORT_PLUGIN_LOAD_ERROR).error(e).callerParam(protocolManager));
disablePlugin(); disablePlugin();
} }
} }
@ -249,12 +281,10 @@ public class ProtocolLibrary extends JavaPlugin {
logger.info("Structure compiler thread has been disabled."); logger.info("Structure compiler thread has been disabled.");
} }
// Handle unexpected Minecraft versions
verifyMinecraftVersion();
// Set up command handlers // Set up command handlers
registerCommand(CommandProtocol.NAME, commandProtocol); registerCommand(CommandProtocol.NAME, commandProtocol);
registerCommand(CommandPacket.NAME, commandPacket); registerCommand(CommandPacket.NAME, commandPacket);
registerCommand(CommandFilter.NAME, commandFilter);
// Player login and logout events // Player login and logout events
protocolManager.registerEvents(manager, this); protocolManager.registerEvents(manager, this);
@ -264,7 +294,7 @@ public class ProtocolLibrary extends JavaPlugin {
createAsyncTask(server); createAsyncTask(server);
} catch (Throwable e) { } catch (Throwable e) {
reporter.reportDetailed(this, "Cannot enable ProtocolLib.", e); reporter.reportDetailed(this, Report.newBuilder(REPORT_PLUGIN_ENABLE_ERROR).error(e));
disablePlugin(); disablePlugin();
return; return;
} }
@ -275,14 +305,14 @@ public class ProtocolLibrary extends JavaPlugin {
statistisc = new Statistics(this); statistisc = new Statistics(this);
} }
} catch (IOException e) { } catch (IOException e) {
reporter.reportDetailed(this, "Unable to enable metrics.", e, statistisc); reporter.reportDetailed(this, Report.newBuilder(REPORT_METRICS_IO_ERROR).error(e).callerParam(statistisc));
} catch (Throwable e) { } catch (Throwable e) {
reporter.reportDetailed(this, "Metrics cannot be enabled. Incompatible Bukkit version.", e, statistisc); reporter.reportDetailed(this, Report.newBuilder(REPORT_METRICS_GENERIC_ERROR).error(e).callerParam(statistisc));
} }
} }
// Used to check Minecraft version // Used to check Minecraft version
private void verifyMinecraftVersion() { private MinecraftVersion verifyMinecraftVersion() {
try { try {
MinecraftVersion minimum = new MinecraftVersion(MINIMUM_MINECRAFT_VERSION); MinecraftVersion minimum = new MinecraftVersion(MINIMUM_MINECRAFT_VERSION);
MinecraftVersion maximum = new MinecraftVersion(MAXIMUM_MINECRAFT_VERSION); MinecraftVersion maximum = new MinecraftVersion(MAXIMUM_MINECRAFT_VERSION);
@ -296,9 +326,14 @@ public class ProtocolLibrary extends JavaPlugin {
if (current.compareTo(maximum) > 0) if (current.compareTo(maximum) > 0)
logger.warning("Version " + current + " has not yet been tested! Proceed with caution."); logger.warning("Version " + current + " has not yet been tested! Proceed with caution.");
} }
return current;
} catch (Exception e) { } catch (Exception e) {
reporter.reportWarning(this, "Unable to retrieve current Minecraft version.", e); reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_PARSE_MINECRAFT_VERSION).error(e));
} }
// Unknown version
return null;
} }
private void checkConflictingVersions() { private void checkConflictingVersions() {
@ -331,7 +366,7 @@ public class ProtocolLibrary extends JavaPlugin {
} }
} catch (Exception e) { } catch (Exception e) {
reporter.reportWarning(this, "Unable to detect conflicting plugin versions.", e); reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_DETECT_CONFLICTING_PLUGINS).error(e));
} }
// See if the newest version is actually higher // See if the newest version is actually higher
@ -360,7 +395,9 @@ public class ProtocolLibrary extends JavaPlugin {
throw new RuntimeException("plugin.yml might be corrupt."); throw new RuntimeException("plugin.yml might be corrupt.");
} catch (RuntimeException e) { } catch (RuntimeException e) {
reporter.reportWarning(this, "Cannot register command " + name + ": " + e.getMessage()); reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_REGISTER_COMMAND).messageParam(name, e.getMessage()).error(e)
);
} }
} }
@ -394,7 +431,7 @@ public class ProtocolLibrary extends JavaPlugin {
} catch (Throwable e) { } catch (Throwable e) {
if (asyncPacketTask == -1) { if (asyncPacketTask == -1) {
reporter.reportDetailed(this, "Unable to create packet timeout task.", e); reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_CREATE_TIMEOUT_TASK).error(e));
} }
} }
} }
@ -417,7 +454,7 @@ public class ProtocolLibrary extends JavaPlugin {
commandProtocol.updateFinished(); commandProtocol.updateFinished();
} }
} catch (Exception e) { } catch (Exception e) {
reporter.reportDetailed(this, "Cannot perform automatic updates.", e); reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_UPDATE_PLUGIN).error(e));
updateDisabled = true; updateDisabled = true;
} }
} }
@ -454,7 +491,9 @@ public class ProtocolLibrary extends JavaPlugin {
unhookTask.close(); unhookTask.close();
protocolManager = null; protocolManager = null;
statistisc = null; statistisc = null;
reporter = null;
// To clean up global parameters
reporter = new BasicErrorReporter();
// Leaky ClassLoader begone! // Leaky ClassLoader begone!
if (updater == null || updater.getResult() != UpdateResult.SUCCESS) { if (updater == null || updater.getResult() != UpdateResult.SUCCESS) {
@ -479,6 +518,8 @@ public class ProtocolLibrary extends JavaPlugin {
/** /**
* Retrieve the current error reporter. * Retrieve the current error reporter.
* <p>
* This is guaranteed to not be NULL in 2.5.0 and later.
* @return Current error reporter. * @return Current error reporter.
*/ */
public static ErrorReporter getErrorReporter() { public static ErrorReporter getErrorReporter() {

Datei anzeigen

@ -27,6 +27,7 @@ import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.async.AsyncMarker; import com.comphenix.protocol.async.AsyncMarker;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.PacketConstructor; import com.comphenix.protocol.injector.PacketConstructor;
@ -47,7 +48,7 @@ public interface ProtocolManager extends PacketStream {
* *
* @param reciever - the reciever. * @param reciever - the reciever.
* @param packet - packet to send. * @param packet - packet to send.
* @param filters - whether or not to invoke any packet filters. * @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
* @throws InvocationTargetException - if an error occured when sending the packet. * @throws InvocationTargetException - if an error occured when sending the packet.
*/ */
@Override @Override
@ -62,7 +63,7 @@ public interface ProtocolManager extends PacketStream {
* *
* @param sender - the sender. * @param sender - the sender.
* @param packet - the packet that was sent. * @param packet - the packet that was sent.
* @param filters - whether or not to invoke any packet filters. * @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
* @throws InvocationTargetException If the reflection machinery failed. * @throws InvocationTargetException If the reflection machinery failed.
* @throws IllegalAccessException If the underlying method caused an error. * @throws IllegalAccessException If the underlying method caused an error.
*/ */

Datei anzeigen

@ -23,6 +23,7 @@ import java.util.PriorityQueue;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.concurrency.AbstractConcurrentListenerMultimap; import com.comphenix.protocol.concurrency.AbstractConcurrentListenerMultimap;
import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.PrioritizedListener; import com.comphenix.protocol.injector.PrioritizedListener;
@ -66,7 +67,7 @@ class PacketProcessingQueue extends AbstractConcurrentListenerMultimap<AsyncList
} }
public PacketProcessingQueue(PlayerSendingHandler sendingHandler, int initialSize, int maximumSize, int maximumConcurrency) { public PacketProcessingQueue(PlayerSendingHandler sendingHandler, int initialSize, int maximumSize, int maximumConcurrency) {
super(); super(Packets.MAXIMUM_PACKET_ID);
try { try {
this.processingQueue = Synchronization.queue(MinMaxPriorityQueue. this.processingQueue = Synchronization.queue(MinMaxPriorityQueue.

Datei anzeigen

@ -1,304 +1,305 @@
/* /*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland * Copyright (C) 2012 Kristian S. Stangeland
* *
* This program is free software; you can redistribute it and/or modify it under the terms of the * 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 * GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version. * 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; * 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. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * 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; * 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 * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol.async; package com.comphenix.protocol.async;
import java.io.IOException; import java.io.IOException;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.PriorityBlockingQueue;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.PlayerLoggedOutException; import com.comphenix.protocol.injector.PlayerLoggedOutException;
import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FieldAccessException;
/** /**
* Represents packets ready to be transmitted to a client. * Represents packets ready to be transmitted to a client.
* @author Kristian * @author Kristian
*/ */
abstract class PacketSendingQueue { abstract class PacketSendingQueue {
public static final int INITIAL_CAPACITY = 10; public static final int INITIAL_CAPACITY = 10;
private PriorityBlockingQueue<PacketEventHolder> sendingQueue; private PriorityBlockingQueue<PacketEventHolder> sendingQueue;
// Asynchronous packet sending // Asynchronous packet sending
private Executor asynchronousSender; private Executor asynchronousSender;
// Whether or not packet transmission must occur on a specific thread // Whether or not packet transmission must occur on a specific thread
private final boolean notThreadSafe; private final boolean notThreadSafe;
// Whether or not we've run the cleanup procedure // Whether or not we've run the cleanup procedure
private boolean cleanedUp = false; private boolean cleanedUp = false;
/** /**
* Create a packet sending queue. * Create a packet sending queue.
* @param notThreadSafe - whether or not to synchronize with the main thread or a background thread. * @param notThreadSafe - whether or not to synchronize with the main thread or a background thread.
*/ */
public PacketSendingQueue(boolean notThreadSafe, Executor asynchronousSender) { public PacketSendingQueue(boolean notThreadSafe, Executor asynchronousSender) {
this.sendingQueue = new PriorityBlockingQueue<PacketEventHolder>(INITIAL_CAPACITY); this.sendingQueue = new PriorityBlockingQueue<PacketEventHolder>(INITIAL_CAPACITY);
this.notThreadSafe = notThreadSafe; this.notThreadSafe = notThreadSafe;
this.asynchronousSender = asynchronousSender; this.asynchronousSender = asynchronousSender;
} }
/** /**
* Number of packet events in the queue. * Number of packet events in the queue.
* @return The number of packet events in the queue. * @return The number of packet events in the queue.
*/ */
public int size() { public int size() {
return sendingQueue.size(); return sendingQueue.size();
} }
/** /**
* Enqueue a packet for sending. * Enqueue a packet for sending.
* @param packet - packet to queue. * @param packet - packet to queue.
*/ */
public void enqueue(PacketEvent packet) { public void enqueue(PacketEvent packet) {
sendingQueue.add(new PacketEventHolder(packet)); sendingQueue.add(new PacketEventHolder(packet));
} }
/** /**
* Invoked when one of the packets have finished processing. * Invoked when one of the packets have finished processing.
* @param packetUpdated - the packet that has now been updated. * @param packetUpdated - the packet that has now been updated.
* @param onMainThread - whether or not this is occuring on the main thread. * @param onMainThread - whether or not this is occuring on the main thread.
*/ */
public synchronized void signalPacketUpdate(PacketEvent packetUpdated, boolean onMainThread) { public synchronized void signalPacketUpdate(PacketEvent packetUpdated, boolean onMainThread) {
AsyncMarker marker = packetUpdated.getAsyncMarker(); AsyncMarker marker = packetUpdated.getAsyncMarker();
// Should we reorder the event? // Should we reorder the event?
if (marker.getQueuedSendingIndex() != marker.getNewSendingIndex() && !marker.hasExpired()) { if (marker.getQueuedSendingIndex() != marker.getNewSendingIndex() && !marker.hasExpired()) {
PacketEvent copy = PacketEvent.fromSynchronous(packetUpdated, marker); PacketEvent copy = PacketEvent.fromSynchronous(packetUpdated, marker);
// "Cancel" the original event // "Cancel" the original event
packetUpdated.setCancelled(true); packetUpdated.setReadOnly(false);
packetUpdated.setCancelled(true);
// Enqueue the copy with the new sending index
enqueue(copy); // Enqueue the copy with the new sending index
} enqueue(copy);
}
// Mark this packet as finished
marker.setProcessed(true); // Mark this packet as finished
trySendPackets(onMainThread); marker.setProcessed(true);
} trySendPackets(onMainThread);
}
/***
* Invoked when a list of packet IDs are no longer associated with any listeners. /***
* @param packetsRemoved - packets that no longer have any listeners. * Invoked when a list of packet IDs are no longer associated with any listeners.
* @param onMainThread - whether or not this is occuring on the main thread. * @param packetsRemoved - packets that no longer have any listeners.
*/ * @param onMainThread - whether or not this is occuring on the main thread.
public synchronized void signalPacketUpdate(List<Integer> packetsRemoved, boolean onMainThread) { */
public synchronized void signalPacketUpdate(List<Integer> packetsRemoved, boolean onMainThread) {
Set<Integer> lookup = new HashSet<Integer>(packetsRemoved);
Set<Integer> lookup = new HashSet<Integer>(packetsRemoved);
// Note that this is O(n), so it might be expensive
for (PacketEventHolder holder : sendingQueue) { // Note that this is O(n), so it might be expensive
PacketEvent event = holder.getEvent(); for (PacketEventHolder holder : sendingQueue) {
PacketEvent event = holder.getEvent();
if (lookup.contains(event.getPacketID())) {
event.getAsyncMarker().setProcessed(true); if (lookup.contains(event.getPacketID())) {
} event.getAsyncMarker().setProcessed(true);
} }
}
// This is likely to have changed the situation a bit
trySendPackets(onMainThread); // This is likely to have changed the situation a bit
} trySendPackets(onMainThread);
}
/**
* Attempt to send any remaining packets. /**
* @param onMainThread - whether or not this is occuring on the main thread. * Attempt to send any remaining packets.
*/ * @param onMainThread - whether or not this is occuring on the main thread.
public void trySendPackets(boolean onMainThread) { */
// Whether or not to continue sending packets public void trySendPackets(boolean onMainThread) {
boolean sending = true; // Whether or not to continue sending packets
boolean sending = true;
// Transmit as many packets as we can
while (sending) { // Transmit as many packets as we can
PacketEventHolder holder = sendingQueue.poll(); while (sending) {
PacketEventHolder holder = sendingQueue.poll();
if (holder != null) {
sending = processPacketHolder(onMainThread, holder); if (holder != null) {
sending = processPacketHolder(onMainThread, holder);
if (!sending) {
// Add it back again if (!sending) {
sendingQueue.add(holder); // Add it back again
} sendingQueue.add(holder);
}
} else {
// No more packets to send } else {
sending = false; // No more packets to send
} sending = false;
} }
} }
}
/**
* Invoked when a packet might be ready for transmission. /**
* @param onMainThread - TRUE if we're on the main thread, FALSE otherwise. * Invoked when a packet might be ready for transmission.
* @param holder - packet container. * @param onMainThread - TRUE if we're on the main thread, FALSE otherwise.
* @return TRUE to continue sending packets, FALSE otherwise. * @param holder - packet container.
*/ * @return TRUE to continue sending packets, FALSE otherwise.
private boolean processPacketHolder(boolean onMainThread, final PacketEventHolder holder) { */
PacketEvent current = holder.getEvent(); private boolean processPacketHolder(boolean onMainThread, final PacketEventHolder holder) {
AsyncMarker marker = current.getAsyncMarker(); PacketEvent current = holder.getEvent();
boolean hasExpired = marker.hasExpired(); AsyncMarker marker = current.getAsyncMarker();
boolean hasExpired = marker.hasExpired();
// Guard in cause the queue is closed
if (cleanedUp) { // Guard in cause the queue is closed
return true; if (cleanedUp) {
} return true;
}
// End condition?
if (marker.isProcessed() || hasExpired) { // End condition?
if (hasExpired) { if (marker.isProcessed() || hasExpired) {
// Notify timeout listeners if (hasExpired) {
onPacketTimeout(current); // Notify timeout listeners
onPacketTimeout(current);
// Recompute
marker = current.getAsyncMarker(); // Recompute
hasExpired = marker.hasExpired(); marker = current.getAsyncMarker();
hasExpired = marker.hasExpired();
// Could happen due to the timeout listeners
if (!marker.isProcessed() && !hasExpired) { // Could happen due to the timeout listeners
return false; if (!marker.isProcessed() && !hasExpired) {
} return false;
} }
}
// Is it okay to send the packet?
if (!current.isCancelled() && !hasExpired) { // Is it okay to send the packet?
// Make sure we're on the main thread if (!current.isCancelled() && !hasExpired) {
if (notThreadSafe) { // Make sure we're on the main thread
try { if (notThreadSafe) {
boolean wantAsync = marker.isMinecraftAsync(current); try {
boolean wantSync = !wantAsync; boolean wantAsync = marker.isMinecraftAsync(current);
boolean wantSync = !wantAsync;
// Wait for the next main thread heartbeat if we haven't fulfilled our promise
if (!onMainThread && wantSync) { // Wait for the next main thread heartbeat if we haven't fulfilled our promise
return false; if (!onMainThread && wantSync) {
} return false;
}
// Let's give it what it wants
if (onMainThread && wantAsync) { // Let's give it what it wants
asynchronousSender.execute(new Runnable() { if (onMainThread && wantAsync) {
@Override asynchronousSender.execute(new Runnable() {
public void run() { @Override
// We know this isn't on the main thread public void run() {
processPacketHolder(false, holder); // We know this isn't on the main thread
} processPacketHolder(false, holder);
}); }
});
// Scheduler will do the rest
return true; // Scheduler will do the rest
} return true;
}
} catch (FieldAccessException e) {
e.printStackTrace(); } catch (FieldAccessException e) {
e.printStackTrace();
// Just drop the packet
return true; // Just drop the packet
} return true;
} }
}
// Silently skip players that have logged out
if (isOnline(current.getPlayer())) { // Silently skip players that have logged out
sendPacket(current); if (isOnline(current.getPlayer())) {
} sendPacket(current);
} }
}
// Drop the packet
return true; // Drop the packet
} return true;
}
// Add it back and stop sending
return false; // Add it back and stop sending
} return false;
}
/**
* Invoked when a packet has timed out. /**
* @param event - the timed out packet. * Invoked when a packet has timed out.
*/ * @param event - the timed out packet.
protected abstract void onPacketTimeout(PacketEvent event); */
protected abstract void onPacketTimeout(PacketEvent event);
private boolean isOnline(Player player) {
return player != null && player.isOnline(); private boolean isOnline(Player player) {
} return player != null && player.isOnline();
}
/**
* Send every packet, regardless of the processing state. /**
*/ * Send every packet, regardless of the processing state.
private void forceSend() { */
while (true) { private void forceSend() {
PacketEventHolder holder = sendingQueue.poll(); while (true) {
PacketEventHolder holder = sendingQueue.poll();
if (holder != null) {
sendPacket(holder.getEvent()); if (holder != null) {
} else { sendPacket(holder.getEvent());
break; } else {
} break;
} }
} }
}
/**
* Whether or not the packet transmission must synchronize with the main thread. /**
* @return TRUE if it must, FALSE otherwise. * Whether or not the packet transmission must synchronize with the main thread.
*/ * @return TRUE if it must, FALSE otherwise.
public boolean isSynchronizeMain() { */
return notThreadSafe; public boolean isSynchronizeMain() {
} return notThreadSafe;
}
/**
* Transmit a packet, if it hasn't already. /**
* @param event - the packet to transmit. * Transmit a packet, if it hasn't already.
*/ * @param event - the packet to transmit.
private void sendPacket(PacketEvent event) { */
private void sendPacket(PacketEvent event) {
AsyncMarker marker = event.getAsyncMarker();
AsyncMarker marker = event.getAsyncMarker();
try {
// Don't send a packet twice try {
if (marker != null && !marker.isTransmitted()) { // Don't send a packet twice
marker.sendPacket(event); if (marker != null && !marker.isTransmitted()) {
} marker.sendPacket(event);
}
} catch (PlayerLoggedOutException e) {
System.out.println(String.format( } catch (PlayerLoggedOutException e) {
"[ProtocolLib] Warning: Dropped packet index %s of ID %s", System.out.println(String.format(
marker.getOriginalSendingIndex(), event.getPacketID() "[ProtocolLib] Warning: Dropped packet index %s of ID %s",
)); marker.getOriginalSendingIndex(), event.getPacketID()
));
} catch (IOException e) {
// Just print the error } catch (IOException e) {
e.printStackTrace(); // Just print the error
} e.printStackTrace();
} }
}
/**
* Automatically transmits every delayed packet. /**
*/ * Automatically transmits every delayed packet.
public void cleanupAll() { */
if (!cleanedUp) { public void cleanupAll() {
// Note that the cleanup itself will always occur on the main thread if (!cleanedUp) {
forceSend(); // Note that the cleanup itself will always occur on the main thread
forceSend();
// And we're done
cleanedUp = true; // And we're done
} cleanedUp = true;
} }
} }
}

Datei anzeigen

@ -23,6 +23,7 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicReferenceArray;
import com.comphenix.protocol.events.ListeningWhitelist; import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.injector.PrioritizedListener; import com.comphenix.protocol.injector.PrioritizedListener;
@ -34,10 +35,14 @@ import com.google.common.collect.Iterables;
* @author Kristian * @author Kristian
*/ */
public abstract class AbstractConcurrentListenerMultimap<TListener> { public abstract class AbstractConcurrentListenerMultimap<TListener> {
// The core of our map // The core of our map
private ConcurrentMap<Integer, SortedCopyOnWriteArray<PrioritizedListener<TListener>>> listeners = private AtomicReferenceArray<SortedCopyOnWriteArray<PrioritizedListener<TListener>>> arrayListeners;
new ConcurrentHashMap<Integer, SortedCopyOnWriteArray<PrioritizedListener<TListener>>>(); private ConcurrentMap<Integer, SortedCopyOnWriteArray<PrioritizedListener<TListener>>> mapListeners;
public AbstractConcurrentListenerMultimap(int maximumPacketID) {
arrayListeners = new AtomicReferenceArray<SortedCopyOnWriteArray<PrioritizedListener<TListener>>>(maximumPacketID + 1);
mapListeners = new ConcurrentHashMap<Integer, SortedCopyOnWriteArray<PrioritizedListener<TListener>>>();
}
/** /**
* Adds a listener to its requested list of packet recievers. * Adds a listener to its requested list of packet recievers.
@ -45,7 +50,6 @@ public abstract class AbstractConcurrentListenerMultimap<TListener> {
* @param whitelist - the packet whitelist to use. * @param whitelist - the packet whitelist to use.
*/ */
public void addListener(TListener listener, ListeningWhitelist whitelist) { public void addListener(TListener listener, ListeningWhitelist whitelist) {
PrioritizedListener<TListener> prioritized = new PrioritizedListener<TListener>(listener, whitelist.getPriority()); PrioritizedListener<TListener> prioritized = new PrioritizedListener<TListener>(listener, whitelist.getPriority());
for (Integer packetID : whitelist.getWhitelist()) { for (Integer packetID : whitelist.getWhitelist()) {
@ -55,21 +59,20 @@ public abstract class AbstractConcurrentListenerMultimap<TListener> {
// Add the listener to a specific packet notifcation list // Add the listener to a specific packet notifcation list
private void addListener(Integer packetID, PrioritizedListener<TListener> listener) { private void addListener(Integer packetID, PrioritizedListener<TListener> listener) {
SortedCopyOnWriteArray<PrioritizedListener<TListener>> list = arrayListeners.get(packetID);
SortedCopyOnWriteArray<PrioritizedListener<TListener>> list = listeners.get(packetID);
// We don't want to create this for every lookup // We don't want to create this for every lookup
if (list == null) { if (list == null) {
// It would be nice if we could use a PriorityBlockingQueue, but it doesn't preseve iterator order, // It would be nice if we could use a PriorityBlockingQueue, but it doesn't preseve iterator order,
// which is a essential feature for our purposes. // which is a essential feature for our purposes.
final SortedCopyOnWriteArray<PrioritizedListener<TListener>> value = new SortedCopyOnWriteArray<PrioritizedListener<TListener>>(); final SortedCopyOnWriteArray<PrioritizedListener<TListener>> value = new SortedCopyOnWriteArray<PrioritizedListener<TListener>>();
list = listeners.putIfAbsent(packetID, value); // We may end up creating multiple multisets, but we'll agree on which to use
if (arrayListeners.compareAndSet(packetID, null, value)) {
// We may end up creating multiple multisets, but we'll agree mapListeners.put(packetID, value);
// on the one to use.
if (list == null) {
list = value; list = value;
} else {
list = arrayListeners.get(packetID);
} }
} }
@ -84,13 +87,11 @@ public abstract class AbstractConcurrentListenerMultimap<TListener> {
* @return Every packet ID that was removed due to no listeners. * @return Every packet ID that was removed due to no listeners.
*/ */
public List<Integer> removeListener(TListener listener, ListeningWhitelist whitelist) { public List<Integer> removeListener(TListener listener, ListeningWhitelist whitelist) {
List<Integer> removedPackets = new ArrayList<Integer>(); List<Integer> removedPackets = new ArrayList<Integer>();
// Again, not terribly efficient. But adding or removing listeners should be a rare event. // Again, not terribly efficient. But adding or removing listeners should be a rare event.
for (Integer packetID : whitelist.getWhitelist()) { for (Integer packetID : whitelist.getWhitelist()) {
SortedCopyOnWriteArray<PrioritizedListener<TListener>> list = arrayListeners.get(packetID);
SortedCopyOnWriteArray<PrioritizedListener<TListener>> list = listeners.get(packetID);
// Remove any listeners // Remove any listeners
if (list != null) { if (list != null) {
@ -100,7 +101,8 @@ public abstract class AbstractConcurrentListenerMultimap<TListener> {
list.remove(new PrioritizedListener<TListener>(listener, whitelist.getPriority())); list.remove(new PrioritizedListener<TListener>(listener, whitelist.getPriority()));
if (list.size() == 0) { if (list.size() == 0) {
listeners.remove(packetID); arrayListeners.set(packetID, null);
mapListeners.remove(packetID);
removedPackets.add(packetID); removedPackets.add(packetID);
} }
} }
@ -120,7 +122,7 @@ public abstract class AbstractConcurrentListenerMultimap<TListener> {
* @return Registered listeners. * @return Registered listeners.
*/ */
public Collection<PrioritizedListener<TListener>> getListener(int packetID) { public Collection<PrioritizedListener<TListener>> getListener(int packetID) {
return listeners.get(packetID); return arrayListeners.get(packetID);
} }
/** /**
@ -128,7 +130,7 @@ public abstract class AbstractConcurrentListenerMultimap<TListener> {
* @return Every listener. * @return Every listener.
*/ */
public Iterable<PrioritizedListener<TListener>> values() { public Iterable<PrioritizedListener<TListener>> values() {
return Iterables.concat(listeners.values()); return Iterables.concat(mapListeners.values());
} }
/** /**
@ -136,13 +138,15 @@ public abstract class AbstractConcurrentListenerMultimap<TListener> {
* @return Registered packet ID. * @return Registered packet ID.
*/ */
public Set<Integer> keySet() { public Set<Integer> keySet() {
return listeners.keySet(); return mapListeners.keySet();
} }
/** /**
* Remove all packet listeners. * Remove all packet listeners.
*/ */
protected void clearListeners() { protected void clearListeners() {
listeners.clear(); arrayListeners = new AtomicReferenceArray<
SortedCopyOnWriteArray<PrioritizedListener<TListener>>>(arrayListeners.length());
mapListeners.clear();
} }
} }

Datei anzeigen

@ -18,6 +18,7 @@
package com.comphenix.protocol.concurrency; package com.comphenix.protocol.concurrency;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@ -60,6 +61,16 @@ public class IntegerSet {
array[element] = true; array[element] = true;
} }
/**
* Add the given collection of elements to the set.
* @param packets - elements to add.
*/
public void addAll(Collection<Integer> packets) {
for (Integer id : packets) {
add(id);
}
}
/** /**
* Remove the given element from the set, or do nothing if it's already removed. * Remove the given element from the set, or do nothing if it's already removed.
* @param element - element to remove. * @param element - element to remove.

Datei anzeigen

@ -0,0 +1,96 @@
package com.comphenix.protocol.error;
import java.io.PrintStream;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.error.Report.ReportBuilder;
import com.comphenix.protocol.reflect.PrettyPrinter;
/**
* Represents a basic error reporter that prints error reports to the standard error stream.
* <p>
* Note that this implementation doesn't distinguish between {@link #reportWarning(Object, Report)}
* and {@link #reportDetailed(Object, Report)} - they both have the exact same behavior.
* @author Kristian
*/
public class BasicErrorReporter implements ErrorReporter {
private final PrintStream output;
/**
* Construct a new basic error reporter that prints directly the standard error stream.
*/
public BasicErrorReporter() {
this(System.err);
}
/**
* Construct a error reporter that prints to the given output stream.
* @param output - the output stream.
*/
public BasicErrorReporter(PrintStream output) {
this.output = output;
}
@Override
public void reportMinimal(Plugin sender, String methodName, Throwable error) {
output.println("Unhandled exception occured in " + methodName + " for " + sender.getName());
error.printStackTrace(output);
}
@Override
public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) {
reportMinimal(sender, methodName, error);
// Also print parameters
printParameters(parameters);
}
@Override
public void reportWarning(Object sender, Report report) {
// Basic warning
output.println("[" + sender.getClass().getSimpleName() + "] " + report.getReportMessage());
if (report.getException() != null) {
report.getException().printStackTrace(output);
}
printParameters(report.getCallerParameters());
}
@Override
public void reportWarning(Object sender, ReportBuilder reportBuilder) {
reportWarning(sender, reportBuilder.build());
}
@Override
public void reportDetailed(Object sender, Report report) {
// No difference from warning
reportWarning(sender, report);
}
@Override
public void reportDetailed(Object sender, ReportBuilder reportBuilder) {
reportWarning(sender, reportBuilder);
}
/**
* Print the given parameters to the standard error stream.
* @param parameters - the output parameters.
*/
private void printParameters(Object[] parameters) {
if (parameters != null && parameters.length > 0) {
output.println("Parameters: ");
try {
for (Object parameter : parameters) {
if (parameter == null)
output.println("[NULL]");
else
output.println(PrettyPrinter.printObject(parameter));
}
} catch (IllegalAccessException e) {
// Damn it
e.printStackTrace();
}
}
}
}

Datei anzeigen

@ -0,0 +1,80 @@
package com.comphenix.protocol.error;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.error.Report.ReportBuilder;
/**
* Construct an error reporter that delegates to another error reporter.
* @author Kristian
*/
public class DelegatedErrorReporter implements ErrorReporter {
private final ErrorReporter delegated;
/**
* Construct a new error reporter that forwards all reports to a given reporter.
* @param delegated - the delegated reporter.
*/
public DelegatedErrorReporter(ErrorReporter delegated) {
this.delegated = delegated;
}
/**
* Retrieve the underlying error reporter.
* @return Underlying error reporter.
*/
public ErrorReporter getDelegated() {
return delegated;
}
@Override
public void reportMinimal(Plugin sender, String methodName, Throwable error) {
delegated.reportMinimal(sender, methodName, error);
}
@Override
public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) {
delegated.reportMinimal(sender, methodName, error, parameters);
}
@Override
public void reportWarning(Object sender, Report report) {
Report transformed = filterReport(sender, report, false);
if (transformed != null) {
delegated.reportWarning(sender, transformed);
}
}
@Override
public void reportDetailed(Object sender, Report report) {
Report transformed = filterReport(sender, report, true);
if (transformed != null) {
delegated.reportDetailed(sender, transformed);
}
}
/**
* Invoked before an error report is passed on to the underlying error reporter.
* <p>
* To cancel a report, return NULL.
* @param sender - the sender component.
* @param report - the error report.
* @param detailed - whether or not the report will be displayed in detail.
* @return The report to pass on, or NULL to cancel it.
*/
protected Report filterReport(Object sender, Report report, boolean detailed) {
return report;
}
@Override
public void reportWarning(Object sender, ReportBuilder reportBuilder) {
reportWarning(sender, reportBuilder.build());
}
@Override
public void reportDetailed(Object sender, ReportBuilder reportBuilder) {
reportDetailed(sender, reportBuilder.build());
}
}

Datei anzeigen

@ -1,387 +1,499 @@
/* /*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland * Copyright (C) 2012 Kristian S. Stangeland
* *
* This program is free software; you can redistribute it and/or modify it under the terms of the * 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 * GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version. * 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; * 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. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * 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; * 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 * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol.error; package com.comphenix.protocol.error;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle; import org.apache.commons.lang.builder.ToStringStyle;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.events.PacketAdapter; import com.comphenix.protocol.error.Report.ReportBuilder;
import com.comphenix.protocol.reflect.PrettyPrinter; import com.comphenix.protocol.events.PacketAdapter;
import com.google.common.primitives.Primitives; import com.comphenix.protocol.reflect.PrettyPrinter;
import com.google.common.primitives.Primitives;
/**
* Internal class used to handle exceptions. /**
* * Internal class used to handle exceptions.
* @author Kristian *
*/ * @author Kristian
public class DetailedErrorReporter implements ErrorReporter { */
public class DetailedErrorReporter implements ErrorReporter {
public static final String SECOND_LEVEL_PREFIX = " "; /**
public static final String DEFAULT_PREFIX = " "; * Report format for printing the current exception count.
public static final String DEFAULT_SUPPORT_URL = "http://dev.bukkit.org/server-mods/protocollib/"; */
public static final String PLUGIN_NAME = "ProtocolLib"; public static final ReportType REPORT_EXCEPTION_COUNT = new ReportType("Internal exception count: %s!");
// Users that are informed about errors in the chat public static final String SECOND_LEVEL_PREFIX = " ";
public static final String ERROR_PERMISSION = "protocol.info"; public static final String DEFAULT_PREFIX = " ";
public static final String DEFAULT_SUPPORT_URL = "http://dev.bukkit.org/server-mods/protocollib/";
// We don't want to spam the server
public static final int DEFAULT_MAX_ERROR_COUNT = 20; // Users that are informed about errors in the chat
public static final String ERROR_PERMISSION = "protocol.info";
// Prevent spam per plugin too
private ConcurrentMap<String, AtomicInteger> warningCount = new ConcurrentHashMap<String, AtomicInteger>(); // We don't want to spam the server
public static final int DEFAULT_MAX_ERROR_COUNT = 20;
protected String prefix;
protected String supportURL; // Prevent spam per plugin too
private ConcurrentMap<String, AtomicInteger> warningCount = new ConcurrentHashMap<String, AtomicInteger>();
protected AtomicInteger internalErrorCount = new AtomicInteger();
protected String prefix;
protected int maxErrorCount; protected String supportURL;
protected Logger logger;
protected AtomicInteger internalErrorCount = new AtomicInteger();
protected WeakReference<Plugin> pluginReference;
protected int maxErrorCount;
// Whether or not Apache Commons is not present protected Logger logger;
protected boolean apacheCommonsMissing;
protected WeakReference<Plugin> pluginReference;
// Map of global objects protected String pluginName;
protected Map<String, Object> globalParameters = new HashMap<String, Object>();
// Whether or not Apache Commons is not present
/** protected boolean apacheCommonsMissing;
* Create a default error reporting system.
*/ // Map of global objects
public DetailedErrorReporter(Plugin plugin) { protected Map<String, Object> globalParameters = new HashMap<String, Object>();
this(plugin, DEFAULT_PREFIX, DEFAULT_SUPPORT_URL);
} /**
* Create a default error reporting system.
/** */
* Create a central error reporting system. public DetailedErrorReporter(Plugin plugin) {
* @param plugin - the plugin owner. this(plugin, DEFAULT_PREFIX, DEFAULT_SUPPORT_URL);
* @param prefix - default line prefix. }
* @param supportURL - URL to report the error.
*/ /**
public DetailedErrorReporter(Plugin plugin, String prefix, String supportURL) { * Create a central error reporting system.
this(plugin, prefix, supportURL, DEFAULT_MAX_ERROR_COUNT, getBukkitLogger()); * @param plugin - the plugin owner.
} * @param prefix - default line prefix.
* @param supportURL - URL to report the error.
// Attempt to get the logger. */
private static Logger getBukkitLogger() { public DetailedErrorReporter(Plugin plugin, String prefix, String supportURL) {
try { this(plugin, prefix, supportURL, DEFAULT_MAX_ERROR_COUNT, getBukkitLogger());
return Bukkit.getLogger(); }
} catch (Throwable e) {
return Logger.getLogger("Minecraft"); /**
} * Create a central error reporting system.
} * @param plugin - the plugin owner.
* @param prefix - default line prefix.
/** * @param supportURL - URL to report the error.
* Create a central error reporting system. * @param maxErrorCount - number of errors to print before giving up.
* @param plugin - the plugin owner. * @param logger - current logger.
* @param prefix - default line prefix. */
* @param supportURL - URL to report the error. public DetailedErrorReporter(Plugin plugin, String prefix, String supportURL, int maxErrorCount, Logger logger) {
* @param maxErrorCount - number of errors to print before giving up. if (plugin == null)
* @param logger - current logger. throw new IllegalArgumentException("Plugin cannot be NULL.");
*/
public DetailedErrorReporter(Plugin plugin, String prefix, String supportURL, int maxErrorCount, Logger logger) { this.pluginReference = new WeakReference<Plugin>(plugin);
if (plugin == null) this.pluginName = plugin.getName();
throw new IllegalArgumentException("Plugin cannot be NULL."); this.prefix = prefix;
this.supportURL = supportURL;
this.pluginReference = new WeakReference<Plugin>(plugin); this.maxErrorCount = maxErrorCount;
this.prefix = prefix; this.logger = logger;
this.supportURL = supportURL; }
this.maxErrorCount = maxErrorCount;
this.logger = logger; // Attempt to get the logger.
} private static Logger getBukkitLogger() {
try {
@Override return Bukkit.getLogger();
public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) { } catch (Throwable e) {
if (reportMinimalNoSpam(sender, methodName, error)) { return Logger.getLogger("Minecraft");
// Print parameters, if they are given }
if (parameters != null && parameters.length > 0) { }
logger.log(Level.SEVERE, " Parameters:");
@Override
// Print each parameter public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) {
for (Object parameter : parameters) { if (reportMinimalNoSpam(sender, methodName, error)) {
logger.log(Level.SEVERE, " " + getStringDescription(parameter)); // Print parameters, if they are given
} if (parameters != null && parameters.length > 0) {
} logger.log(Level.SEVERE, printParameters(parameters));
} }
} }
}
@Override
public void reportMinimal(Plugin sender, String methodName, Throwable error) { @Override
reportMinimalNoSpam(sender, methodName, error); public void reportMinimal(Plugin sender, String methodName, Throwable error) {
} reportMinimalNoSpam(sender, methodName, error);
}
public boolean reportMinimalNoSpam(Plugin sender, String methodName, Throwable error) {
String pluginName = PacketAdapter.getPluginName(sender); /**
AtomicInteger counter = warningCount.get(pluginName); * Report a problem with a given method and plugin, ensuring that we don't exceed the maximum number of error reports.
* @param sender - the component that observed this exception.
// Thread safe pattern * @param methodName - the method name.
if (counter == null) { * @param error - the error itself.
AtomicInteger created = new AtomicInteger(); * @return TRUE if the error was printed, FALSE if it was suppressed.
counter = warningCount.putIfAbsent(pluginName, created); */
public boolean reportMinimalNoSpam(Plugin sender, String methodName, Throwable error) {
if (counter == null) { String pluginName = PacketAdapter.getPluginName(sender);
counter = created; AtomicInteger counter = warningCount.get(pluginName);
}
} // Thread safe pattern
if (counter == null) {
final int errorCount = counter.incrementAndGet(); AtomicInteger created = new AtomicInteger();
counter = warningCount.putIfAbsent(pluginName, created);
// See if we should print the full error
if (errorCount < getMaxErrorCount()) { if (counter == null) {
logger.log(Level.SEVERE, "[" + PLUGIN_NAME + "] Unhandled exception occured in " + counter = created;
methodName + " for " + pluginName, error); }
return true; }
} else { final int errorCount = counter.incrementAndGet();
// Nope - only print the error count occationally
if (isPowerOfTwo(errorCount)) { // See if we should print the full error
logger.log(Level.SEVERE, "[" + PLUGIN_NAME + "] Unhandled exception number " + errorCount + " occured in " + if (errorCount < getMaxErrorCount()) {
methodName + " for " + pluginName, error); logger.log(Level.SEVERE, "[" + pluginName + "] Unhandled exception occured in " +
} methodName + " for " + pluginName, error);
return false; return true;
}
} } else {
// Nope - only print the error count occationally
/** if (isPowerOfTwo(errorCount)) {
* Determine if a given number is a power of two. logger.log(Level.SEVERE, "[" + pluginName + "] Unhandled exception number " + errorCount + " occured in " +
* <p> methodName + " for " + pluginName, error);
* That is, if there exists an N such that 2^N = number. }
* @param number - the number to check. return false;
* @return TRUE if the given number is a power of two, FALSE otherwise. }
*/ }
private boolean isPowerOfTwo(int number) {
return (number & (number - 1)) == 0; /**
} * Determine if a given number is a power of two.
* <p>
@Override * That is, if there exists an N such that 2^N = number.
public void reportWarning(Object sender, String message) { * @param number - the number to check.
logger.log(Level.WARNING, "[" + PLUGIN_NAME + "] [" + getSenderName(sender) + "] " + message); * @return TRUE if the given number is a power of two, FALSE otherwise.
} */
private boolean isPowerOfTwo(int number) {
@Override return (number & (number - 1)) == 0;
public void reportWarning(Object sender, String message, Throwable error) { }
logger.log(Level.WARNING, "[" + PLUGIN_NAME + "] [" + getSenderName(sender) + "] " + message, error);
} @Override
public void reportWarning(Object sender, ReportBuilder reportBuilder) {
private String getSenderName(Object sender) { if (reportBuilder == null)
if (sender != null) throw new IllegalArgumentException("reportBuilder cannot be NULL.");
return sender.getClass().getSimpleName();
else reportWarning(sender, reportBuilder.build());
return "NULL"; }
}
@Override
@Override public void reportWarning(Object sender, Report report) {
public void reportDetailed(Object sender, String message, Throwable error, Object... parameters) { String message = "[" + pluginName + "] [" + getSenderName(sender) + "] " + report.getReportMessage();
final Plugin plugin = pluginReference.get(); // Print the main warning
final int errorCount = internalErrorCount.incrementAndGet(); if (report.getException() != null) {
logger.log(Level.WARNING, message, report.getException());
// Do not overtly spam the server! } else {
if (errorCount > getMaxErrorCount()) { logger.log(Level.WARNING, message);
// Only allow the error count at rare occations }
if (isPowerOfTwo(errorCount)) {
// Permit it - but print the number of exceptions first // Parameters?
reportWarning(this, "Internal exception count: " + errorCount + "!"); if (report.hasCallerParameters()) {
} else { // Write it
// NEVER SPAM THE CONSOLE logger.log(Level.WARNING, printParameters(report.getCallerParameters()));
return; }
} }
}
/**
StringWriter text = new StringWriter(); * Retrieve the name of a sender class.
PrintWriter writer = new PrintWriter(text); * @param sender - sender object.
* @return The name of the sender's class.
// Helpful message */
writer.println("[ProtocolLib] INTERNAL ERROR: " + message); private String getSenderName(Object sender) {
writer.println("If this problem hasn't already been reported, please open a ticket"); if (sender != null)
writer.println("at " + supportURL + " with the following data:"); return sender.getClass().getSimpleName();
else
// Now, let us print important exception information return "NULL";
writer.println(" ===== STACK TRACE ====="); }
if (error != null) @Override
error.printStackTrace(writer); public void reportDetailed(Object sender, ReportBuilder reportBuilder) {
reportDetailed(sender, reportBuilder.build());
// Data dump! }
writer.println(" ===== DUMP =====");
@Override
// Relevant parameters public void reportDetailed(Object sender, Report report) {
if (parameters != null && parameters.length > 0) { final Plugin plugin = pluginReference.get();
writer.println("Parameters:"); final int errorCount = internalErrorCount.incrementAndGet();
// We *really* want to get as much information as possible // Do not overtly spam the server!
for (Object param : parameters) { if (errorCount > getMaxErrorCount()) {
writer.println(addPrefix(getStringDescription(param), SECOND_LEVEL_PREFIX)); // Only allow the error count at rare occations
} if (isPowerOfTwo(errorCount)) {
} // Permit it - but print the number of exceptions first
reportWarning(this, Report.newBuilder(REPORT_EXCEPTION_COUNT).messageParam(errorCount).build());
// Global parameters } else {
for (String param : globalParameters()) { // NEVER SPAM THE CONSOLE
writer.println(SECOND_LEVEL_PREFIX + param + ":"); return;
writer.println(addPrefix(getStringDescription(getGlobalParameter(param)), }
SECOND_LEVEL_PREFIX + SECOND_LEVEL_PREFIX)); }
}
StringWriter text = new StringWriter();
// Now, for the sender itself PrintWriter writer = new PrintWriter(text);
writer.println("Sender:");
writer.println(addPrefix(getStringDescription(sender), SECOND_LEVEL_PREFIX)); // Helpful message
writer.println("[" + pluginName + "] INTERNAL ERROR: " + report.getReportMessage());
// And plugin writer.println("If this problem hasn't already been reported, please open a ticket");
if (plugin != null) { writer.println("at " + supportURL + " with the following data:");
writer.println("Version:");
writer.println(addPrefix(plugin.toString(), SECOND_LEVEL_PREFIX)); // Now, let us print important exception information
} writer.println(" ===== STACK TRACE =====");
// Add the server version too if (report.getException() != null) {
if (Bukkit.getServer() != null) { report.getException().printStackTrace(writer);
writer.println("Server:"); }
writer.println(addPrefix(Bukkit.getServer().getVersion(), SECOND_LEVEL_PREFIX));
// Data dump!
// Inform of this occurrence writer.println(" ===== DUMP =====");
if (ERROR_PERMISSION != null) {
Bukkit.getServer().broadcast( // Relevant parameters
String.format("Error %s (%s) occured in %s.", message, error, sender), if (report.hasCallerParameters()) {
ERROR_PERMISSION printParameters(writer, report.getCallerParameters());
); }
}
} // Global parameters
for (String param : globalParameters()) {
// Make sure it is reported writer.println(SECOND_LEVEL_PREFIX + param + ":");
logger.severe(addPrefix(text.toString(), prefix)); writer.println(addPrefix(getStringDescription(getGlobalParameter(param)),
} SECOND_LEVEL_PREFIX + SECOND_LEVEL_PREFIX));
}
/**
* Adds the given prefix to every line in the text. // Now, for the sender itself
* @param text - text to modify. writer.println("Sender:");
* @param prefix - prefix added to every line in the text. writer.println(addPrefix(getStringDescription(sender), SECOND_LEVEL_PREFIX));
* @return The modified text.
*/ // And plugin
protected String addPrefix(String text, String prefix) { if (plugin != null) {
return text.replaceAll("(?m)^", prefix); writer.println("Version:");
} writer.println(addPrefix(plugin.toString(), SECOND_LEVEL_PREFIX));
}
protected String getStringDescription(Object value) {
// Add the server version too
// We can't only rely on toString. if (Bukkit.getServer() != null) {
if (value == null) { writer.println("Server:");
return "[NULL]"; writer.println(addPrefix(Bukkit.getServer().getVersion(), SECOND_LEVEL_PREFIX));
} if (isSimpleType(value)) {
return value.toString(); // Inform of this occurrence
} else { if (ERROR_PERMISSION != null) {
try { Bukkit.getServer().broadcast(
if (!apacheCommonsMissing) String.format("Error %s (%s) occured in %s.", report.getReportMessage(), report.getException(), sender),
return (ToStringBuilder.reflectionToString(value, ToStringStyle.MULTI_LINE_STYLE, false, null)); ERROR_PERMISSION
} catch (Throwable ex) { );
// Apache is probably missing }
apacheCommonsMissing = true; }
}
// Make sure it is reported
// Use our custom object printer instead logger.severe(addPrefix(text.toString(), prefix));
try { }
return PrettyPrinter.printObject(value, value.getClass(), Object.class);
} catch (IllegalAccessException e) { private String printParameters(Object... parameters) {
return "[Error: " + e.getMessage() + "]"; StringWriter writer = new StringWriter();
}
} // Print and retrieve the string buffer
} printParameters(new PrintWriter(writer), parameters);
return writer.toString();
/** }
* Determine if the given object is a wrapper for a primitive/simple type or not.
* @param test - the object to test. private void printParameters(PrintWriter writer, Object[] parameters) {
* @return TRUE if this object is simple enough to simply be printed, FALSE othewise. writer.println("Parameters: ");
*/
protected boolean isSimpleType(Object test) { // We *really* want to get as much information as possible
return test instanceof String || Primitives.isWrapperType(test.getClass()); for (Object param : parameters) {
} writer.println(addPrefix(getStringDescription(param), SECOND_LEVEL_PREFIX));
}
public int getErrorCount() { }
return internalErrorCount.get();
} /**
* Adds the given prefix to every line in the text.
public void setErrorCount(int errorCount) { * @param text - text to modify.
internalErrorCount.set(errorCount); * @param prefix - prefix added to every line in the text.
} * @return The modified text.
*/
public int getMaxErrorCount() { protected String addPrefix(String text, String prefix) {
return maxErrorCount; return text.replaceAll("(?m)^", prefix);
} }
public void setMaxErrorCount(int maxErrorCount) { /**
this.maxErrorCount = maxErrorCount; * Retrieve a string representation of the given object.
} * @param value - object to convert.
* @return String representation.
/** */
* Adds the given global parameter. It will be included in every error report. protected String getStringDescription(Object value) {
* @param key - name of parameter. // We can't only rely on toString.
* @param value - the global parameter itself. if (value == null) {
*/ return "[NULL]";
public void addGlobalParameter(String key, Object value) { } if (isSimpleType(value)) {
globalParameters.put(key, value); return value.toString();
} } else {
try {
public Object getGlobalParameter(String key) { if (!apacheCommonsMissing)
return globalParameters.get(key); return (ToStringBuilder.reflectionToString(value, ToStringStyle.MULTI_LINE_STYLE, false, null));
} } catch (Throwable ex) {
// Apache is probably missing
public void clearGlobalParameters() { apacheCommonsMissing = true;
globalParameters.clear(); }
}
// Use our custom object printer instead
public Set<String> globalParameters() { try {
return globalParameters.keySet(); return PrettyPrinter.printObject(value, value.getClass(), Object.class);
} } catch (IllegalAccessException e) {
return "[Error: " + e.getMessage() + "]";
public String getSupportURL() { }
return supportURL; }
} }
public void setSupportURL(String supportURL) { /**
this.supportURL = supportURL; * 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.
public String getPrefix() { */
return prefix; protected boolean isSimpleType(Object test) {
} return test instanceof String || Primitives.isWrapperType(test.getClass());
}
public void setPrefix(String prefix) {
this.prefix = prefix; /**
} * Retrieve the current number of errors printed through {@link #reportDetailed(Object, Report)}.
* @return Number of errors printed.
public Logger getLogger() { */
return logger; public int getErrorCount() {
} return internalErrorCount.get();
}
public void setLogger(Logger logger) {
this.logger = logger; /**
} * Set the number of errors printed.
} * @param errorCount - new number of errors printed.
*/
public void setErrorCount(int errorCount) {
internalErrorCount.set(errorCount);
}
/**
* Retrieve the maximum number of errors we can print before we begin suppressing errors.
* @return Maximum number of errors.
*/
public int getMaxErrorCount() {
return maxErrorCount;
}
/**
* Set the maximum number of errors we can print before we begin suppressing errors.
* @param maxErrorCount - new max count.
*/
public void setMaxErrorCount(int maxErrorCount) {
this.maxErrorCount = maxErrorCount;
}
/**
* Adds the given global parameter. It will be included in every error report.
* <p>
* Both key and value must be non-null.
* @param key - name of parameter.
* @param value - the global parameter itself.
*/
public void addGlobalParameter(String key, Object value) {
if (key == null)
throw new IllegalArgumentException("key cannot be NULL.");
if (value == null)
throw new IllegalArgumentException("value cannot be NULL.");
globalParameters.put(key, value);
}
/**
* Retrieve a global parameter by its key.
* @param key - key of the parameter to retrieve.
* @return The value of the global parameter, or NULL if not found.
*/
public Object getGlobalParameter(String key) {
if (key == null)
throw new IllegalArgumentException("key cannot be NULL.");
return globalParameters.get(key);
}
/**
* Reset all global parameters.
*/
public void clearGlobalParameters() {
globalParameters.clear();
}
/**
* Retrieve a set of every registered global parameter.
* @return Set of all registered global parameters.
*/
public Set<String> globalParameters() {
return globalParameters.keySet();
}
/**
* Retrieve the support URL that will be added to all detailed reports.
* @return Support URL.
*/
public String getSupportURL() {
return supportURL;
}
/**
* Set the support URL that will be added to all detailed reports.
* @param supportURL - the new support URL.
*/
public void setSupportURL(String supportURL) {
this.supportURL = supportURL;
}
/**
* Retrieve the prefix to apply to every line in the error reports.
* @return Error report prefix.
*/
public String getPrefix() {
return prefix;
}
/**
* Set the prefix to apply to every line in the error reports.
* @param prefix - new prefix.
*/
public void setPrefix(String prefix) {
this.prefix = prefix;
}
/**
* Retrieve the current logger that is used to print all reports.
* @return The current logger.
*/
public Logger getLogger() {
return logger;
}
/**
* Set the current logger that is used to print all reports.
* @param logger - new logger.
*/
public void setLogger(Logger logger) {
this.logger = logger;
}
}

Datei anzeigen

@ -1,65 +1,74 @@
/* /*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland * Copyright (C) 2012 Kristian S. Stangeland
* *
* This program is free software; you can redistribute it and/or modify it under the terms of the * 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 * GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version. * 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; * 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. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * 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; * 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 * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol.error; package com.comphenix.protocol.error;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
public interface ErrorReporter { import com.comphenix.protocol.error.Report.ReportBuilder;
/** /**
* Prints a small minimal error report about an exception from another plugin. * Represents an object that can forward an error {@link Report} to the display and permanent storage.
* @param sender - the other plugin. *
* @param methodName - name of the caller method. * @author Kristian
* @param error - the exception itself. */
*/ public interface ErrorReporter {
public abstract void reportMinimal(Plugin sender, String methodName, Throwable error); /**
* Prints a small minimal error report regarding an exception from another plugin.
/** * @param sender - the other plugin.
* Prints a small minimal error report about an exception from another plugin. * @param methodName - name of the caller method.
* @param sender - the other plugin. * @param error - the exception itself.
* @param methodName - name of the caller method. */
* @param error - the exception itself. public abstract void reportMinimal(Plugin sender, String methodName, Throwable error);
* @param parameters - any relevant parameters to print.
*/ /**
public abstract void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters); * Prints a small minimal error report regarding an exception from another plugin.
* @param sender - the other plugin.
/** * @param methodName - name of the caller method.
* Prints a warning message from the current plugin. * @param error - the exception itself.
* @param sender - the object containing the caller method. * @param parameters - any relevant parameters to print.
* @param message - error message. */
*/ public abstract void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters);
public abstract void reportWarning(Object sender, String message);
/**
/** * Prints a warning message from the current plugin.
* Prints a warning message from the current plugin. * @param sender - the object containing the caller method.
* @param sender - the object containing the caller method. * @param report - an error report to include.
* @param message - error message. */
* @param error - the exception that was thrown. public abstract void reportWarning(Object sender, Report report);
*/
public abstract void reportWarning(Object sender, String message, Throwable error); /**
* Prints a warning message from the current plugin.
/** * @param sender - the object containing the caller method.
* Prints a detailed error report about an unhandled exception. * @param reportBuilder - an error report builder that will be used to get the report.
* @param sender - the object containing the caller method. */
* @param message - an error message to include. public abstract void reportWarning(Object sender, ReportBuilder reportBuilder);
* @param error - the exception that was thrown in the caller method.
* @param parameters - parameters from the caller method. /**
*/ * Prints a detailed error report about an unhandled exception.
public abstract void reportDetailed(Object sender, String message, Throwable error, Object... parameters); * @param sender - the object containing the caller method.
* @param report - an error report to include.
*/
public abstract void reportDetailed(Object sender, Report report);
/**
* Prints a detailed error report about an unhandled exception.
* @param sender - the object containing the caller method.
* @param reportBuilder - an error report builder that will be used to get the report.
*/
public abstract void reportDetailed(Object sender, ReportBuilder reportBuilder);
} }

Datei anzeigen

@ -0,0 +1,162 @@
package com.comphenix.protocol.error;
import javax.annotation.Nullable;
/**
* Represents a error or warning report.
*
* @author Kristian
*/
public class Report {
private final ReportType type;
private final Throwable exception;
private final Object[] messageParameters;
private final Object[] callerParameters;
/**
* Must be constructed through the factory method in Report.
*/
public static class ReportBuilder {
private ReportType type;
private Throwable exception;
private Object[] messageParameters;
private Object[] callerParameters;
private ReportBuilder() {
// Don't allow
}
/**
* Set the current report type. Cannot be NULL.
* @param type - report type.
* @return This builder, for chaining.
*/
public ReportBuilder type(ReportType type) {
if (type == null)
throw new IllegalArgumentException("Report type cannot be set to NULL.");
this.type = type;
return this;
}
/**
* Set the current exception that occured.
* @param exception - exception that occured.
* @return This builder, for chaining.
*/
public ReportBuilder error(@Nullable Throwable exception) {
this.exception = exception;
return this;
}
/**
* Set the message parameters that are used to construct a message text.
* @param messageParameters - parameters for the report type.
* @return This builder, for chaining.
*/
public ReportBuilder messageParam(@Nullable Object... messageParameters) {
this.messageParameters = messageParameters;
return this;
}
/**
* Set the parameters in the caller method. This is optional.
* @param callerParameters - parameters of the caller method.
* @return This builder, for chaining.
*/
public ReportBuilder callerParam(@Nullable Object... callerParameters) {
this.callerParameters = callerParameters;
return this;
}
/**
* Construct a new report with the provided input.
* @return A new report.
*/
public Report build() {
return new Report(type, exception, messageParameters, callerParameters);
}
}
/**
* Construct a new report builder.
* @param type - the initial report type.
* @return Report builder.
*/
public static ReportBuilder newBuilder(ReportType type) {
return new ReportBuilder().type(type);
}
/**
* Construct a new report with the given type and parameters.
* @param exception - exception that occured in the caller method.
* @param type - the report type that will be used to construct the message.
* @param messageParameters - parameters used to construct the report message.
* @param callerParameters - parameters from the caller method.
*/
protected Report(ReportType type, @Nullable Throwable exception, @Nullable Object[] messageParameters, @Nullable Object[] callerParameters) {
if (type == null)
throw new IllegalArgumentException("type cannot be NULL.");
this.type = type;
this.exception = exception;
this.messageParameters = messageParameters;
this.callerParameters = callerParameters;
}
/**
* Format the current report type with the provided message parameters.
* @return The formated report message.
*/
public String getReportMessage() {
return type.getMessage(messageParameters);
}
/**
* Retrieve the message parameters that will be used to construc the report message.
* <p<
* This should not be confused with the method parameters of the caller method.
* @return Message parameters.
*/
public Object[] getMessageParameters() {
return messageParameters;
}
/**
* Retrieve the parameters of the caller method. Optional - may be NULL.
* @return Parameters or the caller method.
*/
public Object[] getCallerParameters() {
return callerParameters;
}
/**
* Retrieve the report type.
* @return Report type.
*/
public ReportType getType() {
return type;
}
/**
* Retrieve the associated exception, or NULL if not found.
* @return Associated exception, or NULL.
*/
public Throwable getException() {
return exception;
}
/**
* Determine if we have any message parameters.
* @return TRUE if there are any message parameters, FALSE otherwise.
*/
public boolean hasMessageParameters() {
return messageParameters != null && messageParameters.length > 0;
}
/**
* Determine if we have any caller parameters.
* @return TRUE if there are any caller parameters, FALSE otherwise.
*/
public boolean hasCallerParameters() {
return callerParameters != null && callerParameters.length > 0;
}
}

Datei anzeigen

@ -0,0 +1,66 @@
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.
* <p>
* 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<ReportType> result = new ArrayList<ReportType>();
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]);
}
}

Datei anzeigen

@ -44,6 +44,9 @@ public class PacketEvent extends EventObject implements Cancellable {
private AsyncMarker asyncMarker; private AsyncMarker asyncMarker;
private boolean asynchronous; private boolean asynchronous;
// Whether or not a packet event is read only
private boolean readOnly;
/** /**
* Use the static constructors to create instances of this event. * Use the static constructors to create instances of this event.
* @param source - the event source. * @param source - the event source.
@ -114,6 +117,8 @@ public class PacketEvent extends EventObject implements Cancellable {
* @param packet - the packet that will be sent instead. * @param packet - the packet that will be sent instead.
*/ */
public void setPacket(PacketContainer packet) { public void setPacket(PacketContainer packet) {
if (readOnly)
throw new IllegalStateException("The packet event is read-only.");
this.packet = packet; this.packet = packet;
} }
@ -147,6 +152,8 @@ public class PacketEvent extends EventObject implements Cancellable {
* @param cancel - TRUE if it should be cancelled, FALSE otherwise. * @param cancel - TRUE if it should be cancelled, FALSE otherwise.
*/ */
public void setCancelled(boolean cancel) { public void setCancelled(boolean cancel) {
if (readOnly)
throw new IllegalStateException("The packet event is read-only.");
this.cancel = cancel; this.cancel = cancel;
} }
@ -193,9 +200,34 @@ public class PacketEvent extends EventObject implements Cancellable {
public void setAsyncMarker(AsyncMarker asyncMarker) { public void setAsyncMarker(AsyncMarker asyncMarker) {
if (isAsynchronous()) if (isAsynchronous())
throw new IllegalStateException("The marker is immutable for asynchronous events"); throw new IllegalStateException("The marker is immutable for asynchronous events");
if (readOnly)
throw new IllegalStateException("The packet event is read-only.");
this.asyncMarker = asyncMarker; this.asyncMarker = asyncMarker;
} }
/**
* Determine if the current packet event is read only.
* <p>
* This is used to ensure that a monitor listener doesn't accidentally alter the state of the event. However,
* it is still possible to modify the packet itself, as it would require too many resources to verify its integrity.
* <p>
* Thus, the packet is considered immutable if the packet event is read only.
* @return TRUE if it is, FALSE otherwise.
*/
public boolean isReadOnly() {
return readOnly;
}
/**
* Set the read-only state of this packet event.
* <p>
* This will be reset for every packet listener.
* @param readOnly - TRUE if it is read-only, FALSE otherwise.
*/
public void setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
}
/** /**
* Determine if the packet event has been executed asynchronously or not. * Determine if the packet event has been executed asynchronously or not.
* @return TRUE if this packet event is asynchronous, FALSE otherwise. * @return TRUE if this packet event is asynchronous, FALSE otherwise.
@ -203,7 +235,7 @@ public class PacketEvent extends EventObject implements Cancellable {
public boolean isAsynchronous() { public boolean isAsynchronous() {
return asynchronous; return asynchronous;
} }
private void writeObject(ObjectOutputStream output) throws IOException { private void writeObject(ObjectOutputStream output) throws IOException {
// Default serialization // Default serialization
output.defaultWriteObject(); output.defaultWriteObject();

Datei anzeigen

@ -1,181 +1,213 @@
/* /*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland * Copyright (C) 2012 Kristian S. Stangeland
* *
* This program is free software; you can redistribute it and/or modify it under the terms of the * 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 * GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version. * 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; * 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. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * 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; * 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 * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol.injector; package com.comphenix.protocol.injector;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.injector.PacketConstructor.Unwrapper; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.reflect.instances.DefaultInstances; import com.comphenix.protocol.error.ReportType;
import com.google.common.primitives.Primitives; import com.comphenix.protocol.injector.PacketConstructor.Unwrapper;
import com.comphenix.protocol.reflect.FieldUtils;
/** import com.comphenix.protocol.reflect.instances.DefaultInstances;
* Represents an object capable of converting wrapped Bukkit objects into NMS objects. import com.google.common.primitives.Primitives;
* <p>
* Typical conversions include: /**
* <ul> * Represents an object capable of converting wrapped Bukkit objects into NMS objects.
* <li>org.bukkit.entity.Player -> net.minecraft.server.EntityPlayer</li> * <p>
* <li>org.bukkit.World -> net.minecraft.server.WorldServer</li> * Typical conversions include:
* </ul> * <ul>
* * <li>org.bukkit.entity.Player -> net.minecraft.server.EntityPlayer</li>
* @author Kristian * <li>org.bukkit.World -> net.minecraft.server.WorldServer</li>
*/ * </ul>
public class BukkitUnwrapper implements Unwrapper { *
private static Map<Class<?>, Unwrapper> unwrapperCache = new ConcurrentHashMap<Class<?>, Unwrapper>(); * @author Kristian
*/
@SuppressWarnings("unchecked") public class BukkitUnwrapper implements Unwrapper {
@Override public static final ReportType REPORT_ILLEGAL_ARGUMENT = new ReportType("Illegal argument.");
public Object unwrapItem(Object wrappedObject) { public static final ReportType REPORT_SECURITY_LIMITATION = new ReportType("Security limitation.");
// Special case public static final ReportType REPORT_CANNOT_FIND_UNWRAP_METHOD = new ReportType("Cannot find method.");
if (wrappedObject == null)
return null; public static final ReportType REPORT_CANNOT_READ_FIELD_HANDLE = new ReportType("Cannot read field 'handle'.");
Class<?> currentClass = wrappedObject.getClass();
private static Map<Class<?>, Unwrapper> unwrapperCache = new ConcurrentHashMap<Class<?>, Unwrapper>();
// Next, check for types that doesn't have a getHandle()
if (wrappedObject instanceof Collection) { // The current error reporter
return handleCollection((Collection<Object>) wrappedObject); private final ErrorReporter reporter;
} else if (Primitives.isWrapperType(currentClass) || wrappedObject instanceof String) {
return null; /**
} * Construct a new Bukkit unwrapper with ProtocolLib's default error reporter.
*/
Unwrapper specificUnwrapper = getSpecificUnwrapper(currentClass); public BukkitUnwrapper() {
this(ProtocolLibrary.getErrorReporter());
// Retrieve the handle }
if (specificUnwrapper != null)
return specificUnwrapper.unwrapItem(wrappedObject); /**
else * Construct a new Bukkit unwrapper with the given error reporter.
return null; * @param reporter - the error reporter to use.
} */
public BukkitUnwrapper(ErrorReporter reporter) {
// Handle a collection of items this.reporter = reporter;
private Object handleCollection(Collection<Object> wrappedObject) { }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Collection<Object> copy = DefaultInstances.DEFAULT.getDefault(wrappedObject.getClass()); @Override
public Object unwrapItem(Object wrappedObject) {
if (copy != null) { // Special case
// Unwrap every element if (wrappedObject == null)
for (Object element : wrappedObject) { return null;
copy.add(unwrapItem(element)); Class<?> currentClass = wrappedObject.getClass();
}
return copy; // Next, check for types that doesn't have a getHandle()
if (wrappedObject instanceof Collection) {
} else { return handleCollection((Collection<Object>) wrappedObject);
// Impossible } else if (Primitives.isWrapperType(currentClass) || wrappedObject instanceof String) {
return null; return null;
} }
}
Unwrapper specificUnwrapper = getSpecificUnwrapper(currentClass);
/**
* Retrieve a cached class unwrapper for the given class. // Retrieve the handle
* @param type - the type of the class. if (specificUnwrapper != null)
* @return An unwrapper for the given class. return specificUnwrapper.unwrapItem(wrappedObject);
*/ else
private Unwrapper getSpecificUnwrapper(Class<?> type) { return null;
// See if we're already determined this }
if (unwrapperCache.containsKey(type)) {
// We will never remove from the cache, so this ought to be thread safe // Handle a collection of items
return unwrapperCache.get(type); private Object handleCollection(Collection<Object> wrappedObject) {
}
@SuppressWarnings("unchecked")
try { Collection<Object> copy = DefaultInstances.DEFAULT.getDefault(wrappedObject.getClass());
final Method find = type.getMethod("getHandle");
if (copy != null) {
// It's thread safe, as getMethod should return the same handle // Unwrap every element
Unwrapper methodUnwrapper = new Unwrapper() { for (Object element : wrappedObject) {
@Override copy.add(unwrapItem(element));
public Object unwrapItem(Object wrappedObject) { }
return copy;
try {
return find.invoke(wrappedObject); } else {
// Impossible
} catch (IllegalArgumentException e) { return null;
ProtocolLibrary.getErrorReporter().reportDetailed( }
this, "Illegal argument.", e, wrappedObject, find); }
} catch (IllegalAccessException e) {
// Should not occur either /**
return null; * Retrieve a cached class unwrapper for the given class.
} catch (InvocationTargetException e) { * @param type - the type of the class.
// This is really bad * @return An unwrapper for the given class.
throw new RuntimeException("Minecraft error.", e); */
} private Unwrapper getSpecificUnwrapper(Class<?> type) {
// See if we're already determined this
return null; if (unwrapperCache.containsKey(type)) {
} // We will never remove from the cache, so this ought to be thread safe
}; return unwrapperCache.get(type);
}
unwrapperCache.put(type, methodUnwrapper);
return methodUnwrapper; try {
final Method find = type.getMethod("getHandle");
} catch (SecurityException e) {
ProtocolLibrary.getErrorReporter().reportDetailed(this, "Security limitation.", e, type.getName()); // It's thread safe, as getMethod should return the same handle
} catch (NoSuchMethodException e) { Unwrapper methodUnwrapper = new Unwrapper() {
// Try getting the field unwrapper too @Override
Unwrapper fieldUnwrapper = getFieldUnwrapper(type); public Object unwrapItem(Object wrappedObject) {
if (fieldUnwrapper != null) try {
return fieldUnwrapper; return find.invoke(wrappedObject);
else
ProtocolLibrary.getErrorReporter().reportDetailed(this, "Cannot find method.", e, type.getName()); } catch (IllegalArgumentException e) {
} reporter.reportDetailed(this,
Report.newBuilder(REPORT_ILLEGAL_ARGUMENT).error(e).callerParam(wrappedObject, find)
// Default method );
return null; } catch (IllegalAccessException e) {
} // Should not occur either
return null;
/** } catch (InvocationTargetException e) {
* Retrieve a cached unwrapper using the handle field. // This is really bad
* @param type - a cached field unwrapper. throw new RuntimeException("Minecraft error.", e);
* @return The cached field unwrapper. }
*/
private Unwrapper getFieldUnwrapper(Class<?> type) { return null;
final Field find = FieldUtils.getField(type, "handle", true); }
};
// See if we succeeded
if (find != null) { unwrapperCache.put(type, methodUnwrapper);
Unwrapper fieldUnwrapper = new Unwrapper() { return methodUnwrapper;
@Override
public Object unwrapItem(Object wrappedObject) { } catch (SecurityException e) {
try { reporter.reportDetailed(this,
return FieldUtils.readField(find, wrappedObject, true); Report.newBuilder(REPORT_SECURITY_LIMITATION).error(e).callerParam(type)
} catch (IllegalAccessException e) { );
ProtocolLibrary.getErrorReporter().reportDetailed( } catch (NoSuchMethodException e) {
this, "Cannot read field 'handle'.", e, wrappedObject, find.getName()); // Try getting the field unwrapper too
return null; Unwrapper fieldUnwrapper = getFieldUnwrapper(type);
}
} if (fieldUnwrapper != null)
}; return fieldUnwrapper;
else
unwrapperCache.put(type, fieldUnwrapper); reporter.reportDetailed(this,
return fieldUnwrapper; Report.newBuilder(REPORT_CANNOT_FIND_UNWRAP_METHOD).error(e).callerParam(type));
}
} else {
// Inform about this too // Default method
ProtocolLibrary.getErrorReporter().reportDetailed( return null;
this, "Could not find field 'handle'.", }
new Exception("Unable to find 'handle'"), type.getName());
return null; /**
} * Retrieve a cached unwrapper using the handle field.
} * @param type - a cached field unwrapper.
} * @return The cached field unwrapper.
*/
private Unwrapper getFieldUnwrapper(Class<?> type) {
final Field find = FieldUtils.getField(type, "handle", true);
// See if we succeeded
if (find != null) {
Unwrapper fieldUnwrapper = new Unwrapper() {
@Override
public Object unwrapItem(Object wrappedObject) {
try {
return FieldUtils.readField(find, wrappedObject, true);
} catch (IllegalAccessException e) {
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).error(e).callerParam(wrappedObject, find)
);
return null;
}
}
};
unwrapperCache.put(type, fieldUnwrapper);
return fieldUnwrapper;
} else {
// Inform about this too
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).callerParam(find)
);
return null;
}
}
}

Datei anzeigen

@ -1,67 +1,68 @@
/* /*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland * Copyright (C) 2012 Kristian S. Stangeland
* *
* This program is free software; you can redistribute it and/or modify it under the terms of the * 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 * GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version. * 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; * 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. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * 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; * 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 * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol.injector; package com.comphenix.protocol.injector;
import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketEvent;
/** /**
* Represents an object that initiate the packet listeners. * Represents an object that initiate the packet listeners.
* *
* @author Kristian * @author Kristian
*/ */
public interface ListenerInvoker { public interface ListenerInvoker {
/** /**
* Invokes the given packet event for every registered listener. * Invokes the given packet event for every registered listener.
* @param event - the packet event to invoke. * @param event - the packet event to invoke.
*/ */
public abstract void invokePacketRecieving(PacketEvent event); public abstract void invokePacketRecieving(PacketEvent event);
/** /**
* Invokes the given packet event for every registered listener. * Invokes the given packet event for every registered listener.
* @param event - the packet event to invoke. * @param event - the packet event to invoke.
*/ */
public abstract void invokePacketSending(PacketEvent event); public abstract void invokePacketSending(PacketEvent event);
/** /**
* Retrieve the associated ID of a packet. * Retrieve the associated ID of a packet.
* @param packet - the packet. * @param packet - the packet.
* @return The packet ID. * @return The packet ID.
*/ */
public abstract int getPacketID(Object packet); public abstract int getPacketID(Object packet);
/** /**
* Associate a given class with the given packet ID. Internal method. * Associate a given class with the given packet ID. Internal method.
* @param clazz - class to associate. * @param clazz - class to associate.
*/ */
public abstract void unregisterPacketClass(Class<?> clazz); public abstract void unregisterPacketClass(Class<?> clazz);
/** /**
* Remove a given class from the packet registry. Internal method. * Register a given class in the packet registry. Internal method.
* @param clazz - class to remove. * @param clazz - class to register.
*/ * @param packetID - the the new associated packet ID.
public abstract void registerPacketClass(Class<?> clazz, int packetID); */
public abstract void registerPacketClass(Class<?> clazz, int packetID);
/**
* Retrieves the correct packet class from a given packet ID. /**
* @param packetID - the packet ID. * Retrieves the correct packet class from a given packet ID.
* @param forceVanilla - whether or not to look for vanilla classes, not injected classes. * @param packetID - the packet ID.
* @return The associated class. * @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); */
public abstract Class<?> getPacketClassFromID(int packetID, boolean forceVanilla);
} }

Datei anzeigen

@ -50,6 +50,8 @@ import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.async.AsyncFilterManager; import com.comphenix.protocol.async.AsyncFilterManager;
import com.comphenix.protocol.async.AsyncMarker; import com.comphenix.protocol.async.AsyncMarker;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.*; import com.comphenix.protocol.events.*;
import com.comphenix.protocol.injector.packet.PacketInjector; import com.comphenix.protocol.injector.packet.PacketInjector;
import com.comphenix.protocol.injector.packet.PacketInjectorBuilder; import com.comphenix.protocol.injector.packet.PacketInjectorBuilder;
@ -61,12 +63,29 @@ import com.comphenix.protocol.injector.spigot.SpigotPacketInjector;
import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
public final class PacketFilterManager implements ProtocolManager, ListenerInvoker { public final class PacketFilterManager implements ProtocolManager, ListenerInvoker {
public static final ReportType REPORT_CANNOT_LOAD_PACKET_LIST = new ReportType("Cannot load server and client packet list.");
public static final ReportType REPORT_CANNOT_INITIALIZE_PACKET_INJECTOR = new ReportType("Unable to initialize packet injector");
public static final ReportType REPORT_PLUGIN_DEPEND_MISSING =
new ReportType("%s doesn't depend on ProtocolLib. Check that its plugin.yml has a 'depend' directive.");
// Registering packet IDs that are not supported
public static final ReportType REPORT_UNSUPPORTED_SERVER_PACKET_ID = new ReportType("[%s] Unsupported server packet ID in current Minecraft version: %s");
public static final ReportType REPORT_UNSUPPORTED_CLIENT_PACKET_ID = new ReportType("[%s] Unsupported client packet ID in current Minecraft version: %s");
// Problems injecting and uninjecting players
public static final ReportType REPORT_CANNOT_UNINJECT_PLAYER = new ReportType("Unable to uninject net handler for player.");
public static final ReportType REPORT_CANNOT_UNINJECT_OFFLINE_PLAYER = new ReportType("Unable to uninject logged off player.");
public static final ReportType REPORT_CANNOT_INJECT_PLAYER = new ReportType("Unable to inject player.");
public static final ReportType REPORT_CANNOT_UNREGISTER_PLUGIN = new ReportType("Unable to handle disabled plugin.");
/** /**
* Sets the inject hook type. Different types allow for maximum compatibility. * Sets the inject hook type. Different types allow for maximum compatibility.
* @author Kristian * @author Kristian
@ -147,11 +166,22 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
// Spigot listener, if in use // Spigot listener, if in use
private SpigotPacketInjector spigotInjector; private SpigotPacketInjector spigotInjector;
// Plugin verifier
private PluginVerifier pluginVerifier;
/** /**
* Only create instances of this class if protocol lib is disabled. * Only create instances of this class if protocol lib is disabled.
* @param unhookTask
*/ */
public PacketFilterManager(ClassLoader classLoader, Server server, DelayedSingleTask unhookTask, ErrorReporter reporter) { public PacketFilterManager(ClassLoader classLoader, Server server, Plugin library, DelayedSingleTask unhookTask, ErrorReporter reporter) {
this(classLoader, server, library, new MinecraftVersion(server), unhookTask, reporter);
}
/**
* Only create instances of this class if protocol lib is disabled.
*/
public PacketFilterManager(ClassLoader classLoader, Server server, Plugin library,
MinecraftVersion mcVersion, DelayedSingleTask unhookTask, ErrorReporter reporter) {
if (reporter == null) if (reporter == null)
throw new IllegalArgumentException("reporter cannot be NULL."); throw new IllegalArgumentException("reporter cannot be NULL.");
if (classLoader == null) if (classLoader == null)
@ -170,6 +200,9 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
this.classLoader = classLoader; this.classLoader = classLoader;
this.reporter = reporter; this.reporter = reporter;
// The plugin verifier
this.pluginVerifier = new PluginVerifier(library);
// Used to determine if injection is needed // Used to determine if injection is needed
Predicate<GamePhase> isInjectionNecessary = new Predicate<GamePhase>() { Predicate<GamePhase> isInjectionNecessary = new Predicate<GamePhase>() {
@Override @Override
@ -201,6 +234,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
classLoader(classLoader). classLoader(classLoader).
packetListeners(packetListeners). packetListeners(packetListeners).
injectionFilter(isInjectionNecessary). injectionFilter(isInjectionNecessary).
version(mcVersion).
buildHandler(); buildHandler();
this.packetInjector = PacketInjectorBuilder.newBuilder(). this.packetInjector = PacketInjectorBuilder.newBuilder().
@ -218,11 +252,11 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
knowsServerPackets = PacketRegistry.getServerPackets() != null; knowsServerPackets = PacketRegistry.getServerPackets() != null;
knowsClientPackets = PacketRegistry.getClientPackets() != null; knowsClientPackets = PacketRegistry.getClientPackets() != null;
} catch (FieldAccessException e) { } catch (FieldAccessException e) {
reporter.reportWarning(this, "Cannot load server and client packet list.", e); reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_PACKET_LIST).error(e));
} }
} catch (IllegalAccessException e) { } catch (FieldAccessException e) {
reporter.reportWarning(this, "Unable to initialize packet injector.", e); reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_INITIALIZE_PACKET_INJECTOR).error(e));
} }
} }
@ -259,6 +293,20 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
return ImmutableSet.copyOf(packetListeners); return ImmutableSet.copyOf(packetListeners);
} }
/**
* Warn of common programming mistakes.
* @param plugin - plugin to check.
*/
private void printPluginWarnings(Plugin plugin) {
switch (pluginVerifier.verify(plugin)) {
case NO_DEPEND:
reporter.reportWarning(this, Report.newBuilder(REPORT_PLUGIN_DEPEND_MISSING).messageParam(plugin.getName()));
case VALID:
// Do nothing
break;
}
}
@Override @Override
public void addPacketListener(PacketListener listener) { public void addPacketListener(PacketListener listener) {
if (listener == null) if (listener == null)
@ -267,6 +315,8 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
// A listener can only be added once // A listener can only be added once
if (packetListeners.contains(listener)) if (packetListeners.contains(listener))
return; return;
// Check plugin
printPluginWarnings(listener.getPlugin());
ListeningWhitelist sending = listener.getSendingWhitelist(); ListeningWhitelist sending = listener.getSendingWhitelist();
ListeningWhitelist receiving = listener.getReceivingWhitelist(); ListeningWhitelist receiving = listener.getReceivingWhitelist();
@ -446,6 +496,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
asyncFilterManager.enqueueSyncPacket(event, event.getAsyncMarker()); asyncFilterManager.enqueueSyncPacket(event, event.getAsyncMarker());
// The above makes a copy of the event, so it's safe to cancel it // The above makes a copy of the event, so it's safe to cancel it
event.setReadOnly(false);
event.setCancelled(true); event.setCancelled(true);
} }
} }
@ -478,10 +529,9 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
if (!knowsServerPackets || PacketRegistry.getServerPackets().contains(packetID)) if (!knowsServerPackets || PacketRegistry.getServerPackets().contains(packetID))
playerInjection.addPacketHandler(packetID); playerInjection.addPacketHandler(packetID);
else else
reporter.reportWarning(this, String.format( reporter.reportWarning(this,
"[%s] Unsupported server packet ID in current Minecraft version: %s", Report.newBuilder(REPORT_UNSUPPORTED_SERVER_PACKET_ID).messageParam(PacketAdapter.getPluginName(listener), packetID)
PacketAdapter.getPluginName(listener), packetID );
));
} }
// As above, only for client packets // As above, only for client packets
@ -489,10 +539,9 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
if (!knowsClientPackets || PacketRegistry.getClientPackets().contains(packetID)) if (!knowsClientPackets || PacketRegistry.getClientPackets().contains(packetID))
packetInjector.addPacketHandler(packetID); packetInjector.addPacketHandler(packetID);
else else
reporter.reportWarning(this, String.format( reporter.reportWarning(this,
"[%s] Unsupported client packet ID in current Minecraft version: %s", Report.newBuilder(REPORT_UNSUPPORTED_CLIENT_PACKET_ID).messageParam(PacketAdapter.getPluginName(listener), packetID)
PacketAdapter.getPluginName(listener), packetID );
));
} }
} }
} }
@ -529,6 +578,14 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
if (packetCreation.compareAndSet(false, true)) if (packetCreation.compareAndSet(false, true))
incrementPhases(GamePhase.PLAYING); incrementPhases(GamePhase.PLAYING);
// Inform the MONITOR packets
if (!filters) {
sendingListeners.invokePacketSending(
reporter,
PacketEvent.fromServer(this, packet, reciever),
ListenerPriority.MONITOR);
}
playerInjection.sendServerPacket(reciever, packet, filters); playerInjection.sendServerPacket(reciever, packet, filters);
} }
@ -559,9 +616,16 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
mcPacket = event.getPacket().getHandle(); mcPacket = event.getPacket().getHandle();
else else
return; return;
} else {
// Let the monitors know though
recievedListeners.invokePacketSending(
reporter,
PacketEvent.fromClient(this, packet, sender),
ListenerPriority.MONITOR);
} }
playerInjection.processPacket(sender, mcPacket); playerInjection.recieveClientPacket(sender, mcPacket);
} }
@Override @Override
@ -673,8 +737,11 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
try { try {
// Let's clean up the other injection first. // Let's clean up the other injection first.
playerInjection.uninjectPlayer(event.getPlayer().getAddress()); playerInjection.uninjectPlayer(event.getPlayer().getAddress());
playerInjection.updatePlayer(event.getPlayer());
} catch (Exception e) { } catch (Exception e) {
reporter.reportDetailed(PacketFilterManager.this, "Unable to uninject net handler for player.", e, event); reporter.reportDetailed(PacketFilterManager.this,
Report.newBuilder(REPORT_CANNOT_UNINJECT_PLAYER).callerParam(event).error(e)
);
} }
} }
@ -683,7 +750,9 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
// This call will be ignored if no listeners are registered // This call will be ignored if no listeners are registered
playerInjection.injectPlayer(event.getPlayer(), ConflictStrategy.OVERRIDE); playerInjection.injectPlayer(event.getPlayer(), ConflictStrategy.OVERRIDE);
} catch (Exception e) { } catch (Exception e) {
reporter.reportDetailed(PacketFilterManager.this, "Unable to inject player.", e, event); reporter.reportDetailed(PacketFilterManager.this,
Report.newBuilder(REPORT_CANNOT_INJECT_PLAYER).callerParam(event).error(e)
);
} }
} }
@ -695,7 +764,9 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
playerInjection.handleDisconnect(player); playerInjection.handleDisconnect(player);
playerInjection.uninjectPlayer(player); playerInjection.uninjectPlayer(player);
} catch (Exception e) { } catch (Exception e) {
reporter.reportDetailed(PacketFilterManager.this, "Unable to uninject logged off player.", e, event); reporter.reportDetailed(PacketFilterManager.this,
Report.newBuilder(REPORT_CANNOT_UNINJECT_OFFLINE_PLAYER).callerParam(event).error(e)
);
} }
} }
@ -706,7 +777,9 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
removePacketListeners(event.getPlugin()); removePacketListeners(event.getPlugin());
} }
} catch (Exception e) { } catch (Exception e) {
reporter.reportDetailed(PacketFilterManager.this, "Unable handle disabled plugin.", e, event); reporter.reportDetailed(PacketFilterManager.this,
Report.newBuilder(REPORT_CANNOT_UNREGISTER_PLUGIN).callerParam(event).error(e)
);
} }
} }
@ -733,7 +806,14 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
if (!MinecraftReflection.isPacketClass(packet)) if (!MinecraftReflection.isPacketClass(packet))
throw new IllegalArgumentException("The given object " + packet + " is not a packet."); throw new IllegalArgumentException("The given object " + packet + " is not a packet.");
return PacketRegistry.getPacketToID().get(packet.getClass()); Integer id = PacketRegistry.getPacketToID().get(packet.getClass());
if (id != null) {
return id;
} else {
throw new IllegalArgumentException(
"Unable to find associated packet of " + packet + ": Lookup returned NULL.");
}
} }
@Override @Override

Datei anzeigen

@ -0,0 +1,226 @@
package com.comphenix.protocol.injector;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginLoadOrder;
import com.google.common.collect.Sets;
/**
* Determine if a plugin using ProtocolLib is correct.
*
* @author Kristian
*/
class PluginVerifier {
/**
* A named plugin cannot be found.
* @author Kristian
*/
public static class PluginNotFoundException extends RuntimeException {
/**
* Generated by Eclipse.
*/
private static final long serialVersionUID = 8956699101336877611L;
public PluginNotFoundException() {
super();
}
public PluginNotFoundException(String message) {
super(message);
}
}
public enum VerificationResult {
VALID,
/**
* The plugin doesn't depend on ProtocolLib directly or indirectly.
*/
NO_DEPEND;
/**
* Determine if the verification was valid.
*/
public boolean isValid() {
return this == VALID;
}
}
/**
* Set of plugins that have been loaded after ProtocolLib.
*/
private final Set<String> loadedAfter = new HashSet<String>();
/**
* Reference to ProtocolLib.
*/
private final Plugin dependency;
/**
* Construct a new plugin verifier.
* @param dependency - reference to ProtocolLib, a dependency we require of plugins.
*/
public PluginVerifier(Plugin dependency) {
if (dependency == null)
throw new IllegalArgumentException("dependency cannot be NULL.");
// This would screw with the assumption in hasDependency(Plugin, Plugin)
if (safeConversion(dependency.getDescription().getLoadBefore()).size() > 0)
throw new IllegalArgumentException("dependency cannot have a load directives.");
this.dependency = dependency;
}
/**
* Retrieve a plugin by name.
* @param pluginName - the non-null name of the plugin to retrieve.
* @return The retrieved plugin.
* @throws PluginNotFoundException If a plugin with the given name cannot be found.
*/
private Plugin getPlugin(String pluginName) {
Plugin plugin = getPluginOrDefault(pluginName);
// Ensure that the plugin exists
if (plugin != null)
return plugin;
else
throw new PluginNotFoundException("Cannot find plugin " + pluginName);
}
/**
* Retrieve a plugin by name.
* @param pluginName - the non-null name of the plugin to retrieve.
* @return The retrieved plugin, or NULL if not found.
*/
private Plugin getPluginOrDefault(String pluginName) {
return Bukkit.getPluginManager().getPlugin(pluginName);
}
/**
* Performs simple verifications on the given plugin.
* <p>
* Results may be cached.
* @param pluginName - the plugin to verify.
* @return A verification result.
* @throws IllegalArgumentException If plugin name is NULL.
* @throws PluginNotFoundException If a plugin with the given name cannot be found.
*/
public VerificationResult verify(String pluginName) {
if (pluginName == null)
throw new IllegalArgumentException("pluginName cannot be NULL.");
return verify(getPlugin(pluginName));
}
/**
* Performs simple verifications on the given plugin.
* <p>
* Results may be cached.
* @param plugin - the plugin to verify.
* @return A verification result.
* @throws IllegalArgumentException If plugin name is NULL.
* @throws PluginNotFoundException If a plugin with the given name cannot be found.
*/
public VerificationResult verify(Plugin plugin) {
if (plugin == null)
throw new IllegalArgumentException("plugin cannot be NULL.");
// Skip the load order check for ProtocolLib itself
if (!dependency.equals(plugin)) {
if (!loadedAfter.contains(plugin.getName())) {
if (verifyLoadOrder(dependency, plugin)) {
// Memorize
loadedAfter.add(plugin.getName());
} else {
return VerificationResult.NO_DEPEND;
}
}
}
// Everything seems to be in order
return VerificationResult.VALID;
}
/**
* Determine if a given plugin is guarenteed to be loaded before the other.
* <p>
* Note that the before plugin is assumed to have no "load" directives - that is, plugins to be
* loaded after itself. The after plugin may have "load" directives, but it is irrelevant for our purposes.
* @param beforePlugin - the plugin that is loaded first.
* @param afterPlugin - the plugin that is loaded last.
* @return TRUE if it will, FALSE if it may or must load in the opposite other.
*/
private boolean verifyLoadOrder(Plugin beforePlugin, Plugin afterPlugin) {
// If a plugin has a dependency, it will be loaded after its dependency
if (hasDependency(afterPlugin, beforePlugin)) {
return true;
}
// No dependency - check the load order
if (beforePlugin.getDescription().getLoad() == PluginLoadOrder.STARTUP &&
afterPlugin.getDescription().getLoad() == PluginLoadOrder.POSTWORLD) {
return true;
}
return false;
}
/**
* Determine if a plugin has a given dependency, either directly or indirectly.
* @param plugin - the plugin to check.
* @param dependency - the dependency to find.
* @return TRUE if the plugin has the given dependency, FALSE otherwise.
*/
private boolean hasDependency(Plugin plugin, Plugin dependency) {
return hasDependency(plugin, dependency, Sets.<String>newHashSet());
}
/**
* Convert a list to a set.
* <p>
* A null list will be converted to an empty set.
* @param list - the list to convert.
* @return The converted list.
*/
private Set<String> safeConversion(List<String> list) {
if (list == null)
return Collections.emptySet();
else
return Sets.newHashSet(list);
}
// Avoid cycles. DFS.
private boolean hasDependency(Plugin plugin, Plugin dependency, Set<String> checking) {
Set<String> childNames = Sets.union(
safeConversion(plugin.getDescription().getDepend()),
safeConversion(plugin.getDescription().getSoftDepend())
);
// Ensure that the same plugin isn't processed twice
if (!checking.add(plugin.getName())) {
throw new IllegalStateException("Cycle detected in dependency graph: " + plugin);
}
// Look for the dependency in the immediate children
if (childNames.contains(dependency.getName())) {
return true;
}
// Recurse through their dependencies
for (String childName : childNames) {
Plugin childPlugin = getPluginOrDefault(childName);
if (childPlugin != null && hasDependency(childPlugin, dependency, checking)) {
return true;
}
}
// Cross edges are permitted
checking.remove(plugin.getName());
// No dependency found!
return false;
}
}

Datei anzeigen

@ -19,8 +19,10 @@ package com.comphenix.protocol.injector;
import java.util.Collection; import java.util.Collection;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.concurrency.AbstractConcurrentListenerMultimap; import com.comphenix.protocol.concurrency.AbstractConcurrentListenerMultimap;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.events.PacketListener;
@ -30,6 +32,9 @@ import com.comphenix.protocol.events.PacketListener;
* @author Kristian * @author Kristian
*/ */
public final class SortedPacketListenerList extends AbstractConcurrentListenerMultimap<PacketListener> { public final class SortedPacketListenerList extends AbstractConcurrentListenerMultimap<PacketListener> {
public SortedPacketListenerList() {
super(Packets.MAXIMUM_PACKET_ID);
}
/** /**
* Invokes the given packet event for every registered listener. * Invokes the given packet event for every registered listener.
@ -45,7 +50,44 @@ public final class SortedPacketListenerList extends AbstractConcurrentListenerMu
// The returned list is thread-safe // The returned list is thread-safe
for (PrioritizedListener<PacketListener> element : list) { for (PrioritizedListener<PacketListener> element : list) {
try { try {
event.setReadOnly(element.getPriority() == ListenerPriority.MONITOR);
element.getListener().onPacketReceiving(event); element.getListener().onPacketReceiving(event);
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
// Minecraft doesn't want your Exception.
reporter.reportMinimal(element.getListener().getPlugin(), "onPacketReceiving(PacketEvent)", e,
event.getPacket().getHandle());
}
}
}
/**
* Invokes the given packet event for every registered listener of the given priority.
* @param reporter - the error reporter that will be used to inform about listener exceptions.
* @param event - the packet event to invoke.
* @param priorityFilter - the required priority for a listener to be invoked.
*/
public void invokePacketRecieving(ErrorReporter reporter, PacketEvent event, ListenerPriority priorityFilter) {
Collection<PrioritizedListener<PacketListener>> list = getListener(event.getPacketID());
if (list == null)
return;
for (PrioritizedListener<PacketListener> element : list) {
try {
if (element.getPriority() == priorityFilter) {
event.setReadOnly(element.getPriority() == ListenerPriority.MONITOR);
element.getListener().onPacketReceiving(event);
}
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) { } catch (Throwable e) {
// Minecraft doesn't want your Exception. // Minecraft doesn't want your Exception.
reporter.reportMinimal(element.getListener().getPlugin(), "onPacketReceiving(PacketEvent)", e, reporter.reportMinimal(element.getListener().getPlugin(), "onPacketReceiving(PacketEvent)", e,
@ -67,7 +109,13 @@ public final class SortedPacketListenerList extends AbstractConcurrentListenerMu
for (PrioritizedListener<PacketListener> element : list) { for (PrioritizedListener<PacketListener> element : list) {
try { try {
event.setReadOnly(element.getPriority() == ListenerPriority.MONITOR);
element.getListener().onPacketSending(event); element.getListener().onPacketSending(event);
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) { } catch (Throwable e) {
// Minecraft doesn't want your Exception. // Minecraft doesn't want your Exception.
reporter.reportMinimal(element.getListener().getPlugin(), "onPacketSending(PacketEvent)", e, reporter.reportMinimal(element.getListener().getPlugin(), "onPacketSending(PacketEvent)", e,
@ -76,4 +124,34 @@ public final class SortedPacketListenerList extends AbstractConcurrentListenerMu
} }
} }
/**
* Invokes the given packet event for every registered listener of the given priority.
* @param reporter - the error reporter that will be used to inform about listener exceptions.
* @param event - the packet event to invoke.
* @param priorityFilter - the required priority for a listener to be invoked.
*/
public void invokePacketSending(ErrorReporter reporter, PacketEvent event, ListenerPriority priorityFilter) {
Collection<PrioritizedListener<PacketListener>> list = getListener(event.getPacketID());
if (list == null)
return;
for (PrioritizedListener<PacketListener> element : list) {
try {
if (element.getPriority() == priorityFilter) {
event.setReadOnly(element.getPriority() == ListenerPriority.MONITOR);
element.getListener().onPacketSending(event);
}
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
// Minecraft doesn't want your Exception.
reporter.reportMinimal(element.getListener().getPlugin(), "onPacketSending(PacketEvent)", e,
event.getPacket().getHandle());
}
}
}
} }

Datei anzeigen

@ -8,6 +8,7 @@ import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PacketFilterManager; import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler; import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
/** /**
@ -100,9 +101,9 @@ public class PacketInjectorBuilder {
* <p> * <p>
* Note that any non-null builder parameters must be set. * Note that any non-null builder parameters must be set.
* @return The created injector. * @return The created injector.
* @throws IllegalAccessException If anything goes wrong in terms of reflection. * @throws FieldAccessException If anything goes wrong in terms of reflection.
*/ */
public PacketInjector buildInjector() throws IllegalAccessException { public PacketInjector buildInjector() throws FieldAccessException {
initializeDefaults(); initializeDefaults();
return new ProxyPacketInjector(classLoader, invoker, playerInjection, reporter); return new ProxyPacketInjector(classLoader, invoker, playerInjection, reporter);
} }

Datei anzeigen

@ -1,228 +1,303 @@
/* /*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland * Copyright (C) 2012 Kristian S. Stangeland
* *
* This program is free software; you can redistribute it and/or modify it under the terms of the * 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 * GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version. * 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; * 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. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * 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; * 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 * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol.injector.packet; package com.comphenix.protocol.injector.packet;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import net.sf.cglib.proxy.Factory; import net.sf.cglib.proxy.Factory;
import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.reflect.FieldAccessException;
import com.google.common.base.Objects; import com.comphenix.protocol.reflect.FieldUtils;
import com.google.common.collect.ImmutableSet; import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.fuzzy.FuzzyClassContract;
/** import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
* Static packet registry in Minecraft. import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
* import com.comphenix.protocol.utility.MinecraftReflection;
* @author Kristian import com.comphenix.protocol.wrappers.TroveWrapper;
*/ import com.google.common.base.Objects;
@SuppressWarnings("rawtypes") import com.google.common.collect.ImmutableSet;
public class PacketRegistry {
/**
// Fuzzy reflection * Static packet registry in Minecraft.
private static FuzzyReflection packetRegistry; *
* @author Kristian
// The packet class to packet ID translator */
private static Map<Class, Integer> packetToID; @SuppressWarnings("rawtypes")
public class PacketRegistry {
// Whether or not certain packets are sent by the client or the server public static final ReportType REPORT_CANNOT_CORRECT_TROVE_MAP = new ReportType("Unable to correct no entry value.");
private static ImmutableSet<Integer> serverPackets;
private static ImmutableSet<Integer> clientPackets; public static final ReportType REPORT_INSUFFICIENT_SERVER_PACKETS = new ReportType("Too few server packets detected: %s");
public static final ReportType REPORT_INSUFFICIENT_CLIENT_PACKETS = new ReportType("Too few client packets detected: %s");
// The underlying sets
private static Set<Integer> serverPacketsRef; private static final int MIN_SERVER_PACKETS = 5;
private static Set<Integer> clientPacketsRef; private static final int MIN_CLIENT_PACKETS = 5;
// New proxy values // Fuzzy reflection
private static Map<Integer, Class> overwrittenPackets = new HashMap<Integer, Class>(); private static FuzzyReflection packetRegistry;
// Vanilla packets // The packet class to packet ID translator
private static Map<Integer, Class> previousValues = new HashMap<Integer, Class>(); private static Map<Class, Integer> packetToID;
@SuppressWarnings({ "unchecked" }) // Whether or not certain packets are sent by the client or the server
public static Map<Class, Integer> getPacketToID() { private static ImmutableSet<Integer> serverPackets;
// Initialize it, if we haven't already private static ImmutableSet<Integer> clientPackets;
if (packetToID == null) {
try { // The underlying sets
Field packetsField = getPacketRegistry().getFieldByType("packetsField", Map.class); private static Set<Integer> serverPacketsRef;
packetToID = (Map<Class, Integer>) FieldUtils.readStaticField(packetsField, true); private static Set<Integer> clientPacketsRef;
} catch (IllegalAccessException e) { // New proxy values
throw new RuntimeException("Unable to retrieve the packetClassToIdMap", e); private static Map<Integer, Class> overwrittenPackets = new HashMap<Integer, Class>();
}
} // Vanilla packets
private static Map<Integer, Class> previousValues = new HashMap<Integer, Class>();
return packetToID;
} @SuppressWarnings({ "unchecked" })
public static Map<Class, Integer> getPacketToID() {
/** // Initialize it, if we haven't already
* Retrieve the cached fuzzy reflection instance allowing access to the packet registry. if (packetToID == null) {
* @return Reflected packet registry. try {
*/ Field packetsField = getPacketRegistry().getFieldByType("packetsField", Map.class);
private static FuzzyReflection getPacketRegistry() { packetToID = (Map<Class, Integer>) FieldUtils.readStaticField(packetsField, true);
if (packetRegistry == null) } catch (IllegalArgumentException e) {
packetRegistry = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true); // Spigot 1.2.5 MCPC workaround
return packetRegistry; try {
} packetToID = getSpigotWrapper();
} catch (Exception e2) {
/** // Very bad indeed
* Retrieve the injected proxy classes handlig each packet ID. throw new IllegalArgumentException(e.getMessage() + "; Spigot workaround failed.", e2);
* @return Injected classes. }
*/
public static Map<Integer, Class> getOverwrittenPackets() { } catch (IllegalAccessException e) {
return overwrittenPackets; throw new RuntimeException("Unable to retrieve the packetClassToIdMap", e);
} }
}
/**
* Retrieve the vanilla classes handling each packet ID. return packetToID;
* @return Vanilla classes. }
*/
public static Map<Integer, Class> getPreviousPackets() { private static Map<Class, Integer> getSpigotWrapper() throws IllegalAccessException {
return previousValues; // If it talks like a duck, etc.
} // Perhaps it would be nice to have a proper duck typing library as well
FuzzyClassContract mapLike = FuzzyClassContract.newBuilder().
/** method(FuzzyMethodContract.newBuilder().
* Retrieve every known and supported server packet. nameExact("size").returnTypeExact(int.class)).
* @return An immutable set of every known server packet. method(FuzzyMethodContract.newBuilder().
* @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft. nameExact("put").parameterCount(2)).
*/ method(FuzzyMethodContract.newBuilder().
public static Set<Integer> getServerPackets() throws FieldAccessException { nameExact("get").parameterCount(1)).
initializeSets(); build();
return serverPackets;
} Field packetsField = getPacketRegistry().getField(
FuzzyFieldContract.newBuilder().typeMatches(mapLike).build());
/** Object troveMap = FieldUtils.readStaticField(packetsField, true);
* Retrieve every known and supported client packet.
* @return An immutable set of every known client packet. // Check for stupid no_entry_values
* @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft. try {
*/ Field field = FieldUtils.getField(troveMap.getClass(), "no_entry_value", true);
public static Set<Integer> getClientPackets() throws FieldAccessException { Integer value = (Integer) FieldUtils.readField(field, troveMap, true);
initializeSets();
return clientPackets; if (value >= 0 && value < 256) {
} // Someone forgot to set the no entry value. Let's help them.
FieldUtils.writeField(field, troveMap, -1);
@SuppressWarnings("unchecked") }
private static void initializeSets() throws FieldAccessException { } catch (IllegalArgumentException e) {
if (serverPacketsRef == null || clientPacketsRef == null) { // Whatever
List<Field> sets = getPacketRegistry().getFieldListByType(Set.class); ProtocolLibrary.getErrorReporter().reportWarning(PacketRegistry.class,
Report.newBuilder(REPORT_CANNOT_CORRECT_TROVE_MAP).error(e));
try { }
if (sets.size() > 1) {
serverPacketsRef = (Set<Integer>) FieldUtils.readStaticField(sets.get(0), true); // We'll assume this a Trove map
clientPacketsRef = (Set<Integer>) FieldUtils.readStaticField(sets.get(1), true); return TroveWrapper.getDecoratedMap(troveMap);
}
// Impossible
if (serverPacketsRef == null || clientPacketsRef == null) /**
throw new FieldAccessException("Packet sets are in an illegal state."); * Retrieve the cached fuzzy reflection instance allowing access to the packet registry.
* @return Reflected packet registry.
// NEVER allow callers to modify the underlying sets */
serverPackets = ImmutableSet.copyOf(serverPacketsRef); private static FuzzyReflection getPacketRegistry() {
clientPackets = ImmutableSet.copyOf(clientPacketsRef); if (packetRegistry == null)
packetRegistry = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true);
} else { return packetRegistry;
throw new FieldAccessException("Cannot retrieve packet client/server sets."); }
}
/**
} catch (IllegalAccessException e) { * Retrieve the injected proxy classes handlig each packet ID.
throw new FieldAccessException("Cannot access field.", e); * @return Injected classes.
} */
public static Map<Integer, Class> getOverwrittenPackets() {
} else { return overwrittenPackets;
// Copy over again if it has changed }
if (serverPacketsRef != null && serverPacketsRef.size() != serverPackets.size())
serverPackets = ImmutableSet.copyOf(serverPacketsRef); /**
if (clientPacketsRef != null && clientPacketsRef.size() != clientPackets.size()) * Retrieve the vanilla classes handling each packet ID.
clientPackets = ImmutableSet.copyOf(clientPacketsRef); * @return Vanilla classes.
} */
} public static Map<Integer, Class> getPreviousPackets() {
return previousValues;
/** }
* Retrieves the correct packet class from a given packet ID.
* @param packetID - the packet ID. /**
* @return The associated class. * Retrieve every known and supported server packet.
*/ * @return An immutable set of every known server packet.
public static Class getPacketClassFromID(int packetID) { * @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft.
return getPacketClassFromID(packetID, false); */
} public static Set<Integer> getServerPackets() throws FieldAccessException {
initializeSets();
/**
* Retrieves the correct packet class from a given packet ID. // Sanity check. This is impossible!
* @param packetID - the packet ID. if (serverPackets != null && serverPackets.size() < MIN_SERVER_PACKETS)
* @param forceVanilla - whether or not to look for vanilla classes, not injected classes. throw new FieldAccessException("Server packet list is empty. Seems to be unsupported");
* @return The associated class. return serverPackets;
*/ }
public static Class getPacketClassFromID(int packetID, boolean forceVanilla) {
/**
Map<Integer, Class> lookup = forceVanilla ? previousValues : overwrittenPackets; * Retrieve every known and supported client packet.
* @return An immutable set of every known client packet.
// Optimized lookup * @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft.
if (lookup.containsKey(packetID)) { */
return removeEnhancer(lookup.get(packetID), forceVanilla); public static Set<Integer> getClientPackets() throws FieldAccessException {
} initializeSets();
// Will most likely not be used // As above
for (Map.Entry<Class, Integer> entry : getPacketToID().entrySet()) { if (clientPackets != null && clientPackets.size() < MIN_CLIENT_PACKETS)
if (Objects.equal(entry.getValue(), packetID)) { throw new FieldAccessException("Client packet list is empty. Seems to be unsupported");
// Attempt to get the vanilla class here too return clientPackets;
if (!forceVanilla || MinecraftReflection.isMinecraftClass(entry.getKey())) }
return removeEnhancer(entry.getKey(), forceVanilla);
} @SuppressWarnings("unchecked")
} private static void initializeSets() throws FieldAccessException {
if (serverPacketsRef == null || clientPacketsRef == null) {
throw new IllegalArgumentException("The packet ID " + packetID + " is not registered."); List<Field> sets = getPacketRegistry().getFieldListByType(Set.class);
}
try {
/** if (sets.size() > 1) {
* Retrieve the packet ID of a given packet. serverPacketsRef = (Set<Integer>) FieldUtils.readStaticField(sets.get(0), true);
* @param packet - the type of packet to check. clientPacketsRef = (Set<Integer>) FieldUtils.readStaticField(sets.get(1), true);
* @return The ID of the given packet.
* @throws IllegalArgumentException If this is not a valid packet. // Impossible
*/ if (serverPacketsRef == null || clientPacketsRef == null)
public static int getPacketID(Class<?> packet) { throw new FieldAccessException("Packet sets are in an illegal state.");
if (packet == null)
throw new IllegalArgumentException("Packet type class cannot be NULL."); // NEVER allow callers to modify the underlying sets
if (!MinecraftReflection.getPacketClass().isAssignableFrom(packet)) serverPackets = ImmutableSet.copyOf(serverPacketsRef);
throw new IllegalArgumentException("Type must be a packet."); clientPackets = ImmutableSet.copyOf(clientPacketsRef);
// The registry contains both the overridden and original packets // Check sizes
return getPacketToID().get(packet); if (serverPackets.size() < MIN_SERVER_PACKETS)
} ProtocolLibrary.getErrorReporter().reportWarning(
PacketRegistry.class, Report.newBuilder(REPORT_INSUFFICIENT_SERVER_PACKETS).messageParam(serverPackets.size())
/** );
* Find the first superclass that is not a CBLib proxy object. if (clientPackets.size() < MIN_CLIENT_PACKETS)
* @param clazz - the class whose hierachy we're going to search through. ProtocolLibrary.getErrorReporter().reportWarning(
* @param remove - whether or not to skip enhanced (proxy) classes. PacketRegistry.class, Report.newBuilder(REPORT_INSUFFICIENT_CLIENT_PACKETS).messageParam(clientPackets.size())
* @return If remove is TRUE, the first superclass that is not a proxy. );
*/
private static Class removeEnhancer(Class clazz, boolean remove) { } else {
if (remove) { throw new FieldAccessException("Cannot retrieve packet client/server sets.");
// Get the underlying vanilla class }
while (Factory.class.isAssignableFrom(clazz) && !clazz.equals(Object.class)) {
clazz = clazz.getSuperclass(); } catch (IllegalAccessException e) {
} throw new FieldAccessException("Cannot access field.", e);
} }
return clazz; } else {
} // Copy over again if it has changed
} if (serverPacketsRef != null && serverPacketsRef.size() != serverPackets.size())
serverPackets = ImmutableSet.copyOf(serverPacketsRef);
if (clientPacketsRef != null && clientPacketsRef.size() != clientPackets.size())
clientPackets = ImmutableSet.copyOf(clientPacketsRef);
}
}
/**
* Retrieves the correct packet class from a given packet ID.
* @param packetID - the packet ID.
* @return The associated class.
*/
public static Class getPacketClassFromID(int packetID) {
return getPacketClassFromID(packetID, false);
}
/**
* Retrieves the correct packet class from a given packet ID.
* @param packetID - the packet ID.
* @param forceVanilla - whether or not to look for vanilla classes, not injected classes.
* @return The associated class.
*/
public static Class getPacketClassFromID(int packetID, boolean forceVanilla) {
Map<Integer, Class> lookup = forceVanilla ? previousValues : overwrittenPackets;
// Optimized lookup
if (lookup.containsKey(packetID)) {
return removeEnhancer(lookup.get(packetID), forceVanilla);
}
// Will most likely not be used
for (Map.Entry<Class, Integer> entry : getPacketToID().entrySet()) {
if (Objects.equal(entry.getValue(), packetID)) {
// Attempt to get the vanilla class here too
if (!forceVanilla || MinecraftReflection.isMinecraftClass(entry.getKey()))
return removeEnhancer(entry.getKey(), forceVanilla);
}
}
throw new IllegalArgumentException("The packet ID " + packetID + " is not registered.");
}
/**
* Retrieve the packet ID of a given packet.
* @param packet - the type of packet to check.
* @return The ID of the given packet.
* @throws IllegalArgumentException If this is not a valid packet.
*/
public static int getPacketID(Class<?> packet) {
if (packet == null)
throw new IllegalArgumentException("Packet type class cannot be NULL.");
if (!MinecraftReflection.getPacketClass().isAssignableFrom(packet))
throw new IllegalArgumentException("Type must be a packet.");
// The registry contains both the overridden and original packets
return getPacketToID().get(packet);
}
/**
* Find the first superclass that is not a CBLib proxy object.
* @param clazz - the class whose hierachy we're going to search through.
* @param remove - whether or not to skip enhanced (proxy) classes.
* @return If remove is TRUE, the first superclass that is not a proxy.
*/
private static Class removeEnhancer(Class clazz, boolean remove) {
if (remove) {
// Get the underlying vanilla class
while (Factory.class.isAssignableFrom(clazz) && !clazz.equals(Object.class)) {
clazz = clazz.getSuperclass();
}
}
return clazz;
}
}

Datei anzeigen

@ -37,6 +37,7 @@ import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler; import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.MethodInfo; import com.comphenix.protocol.reflect.MethodInfo;
@ -49,6 +50,85 @@ import com.comphenix.protocol.utility.MinecraftReflection;
* @author Kristian * @author Kristian
*/ */
class ProxyPacketInjector implements PacketInjector { class ProxyPacketInjector implements PacketInjector {
/**
* Represents a way to update the packet ID to class lookup table.
* @author Kristian
*/
private static interface PacketClassLookup {
public void setLookup(int packetID, Class<?> clazz);
}
private static class IntHashMapLookup implements PacketClassLookup {
// The "put" method that associates a packet ID with a packet class
private Method putMethod;
private Object intHashMap;
public IntHashMapLookup() throws IllegalAccessException {
initialize();
}
@Override
public void setLookup(int packetID, Class<?> clazz) {
try {
putMethod.invoke(intHashMap, packetID, clazz);
} catch (IllegalArgumentException e) {
throw new RuntimeException("Illegal argument.", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access method.", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Exception occured in IntHashMap.put.", e);
}
}
private void initialize() throws IllegalAccessException {
if (intHashMap == null) {
// We're looking for the first static field with a Minecraft-object. This should be a IntHashMap.
Field intHashMapField = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true).
getFieldByType(MinecraftReflection.getMinecraftObjectRegex());
try {
intHashMap = FieldUtils.readField(intHashMapField, (Object) null, true);
} catch (IllegalArgumentException e) {
throw new RuntimeException("Minecraft is incompatible.", e);
}
// Now, get the "put" method.
putMethod = FuzzyReflection.fromObject(intHashMap).
getMethodByParameters("put", int.class, Object.class);
}
}
}
private static class ArrayLookup implements PacketClassLookup {
private Class<?>[] array;
public ArrayLookup() throws IllegalAccessException {
initialize();
}
@Override
public void setLookup(int packetID, Class<?> clazz) {
array[packetID] = clazz;
}
private void initialize() throws IllegalAccessException {
FuzzyReflection reflection = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass());
// Is there a Class array with 256 elements instead?
for (Field field : reflection.getFieldListByType(Class[].class)) {
Class<?>[] test = (Class<?>[]) FieldUtils.readField(field, (Object)null);
if (test.length == 256) {
array = test;
return;
}
}
throw new IllegalArgumentException(
"Unable to find an array with the type " + Class[].class +
" in " + MinecraftReflection.getPacketClass());
}
}
/** /**
* Matches the readPacketData(DataInputStream) method in Packet. * Matches the readPacketData(DataInputStream) method in Packet.
*/ */
@ -58,9 +138,7 @@ class ProxyPacketInjector implements PacketInjector {
parameterCount(1). parameterCount(1).
build(); build();
// The "put" method that associates a packet ID with a packet class private static PacketClassLookup lookup;
private static Method putMethod;
private static Object intHashMap;
// The packet filter manager // The packet filter manager
private ListenerInvoker manager; private ListenerInvoker manager;
@ -78,7 +156,7 @@ class ProxyPacketInjector implements PacketInjector {
private CallbackFilter filter; private CallbackFilter filter;
public ProxyPacketInjector(ClassLoader classLoader, ListenerInvoker manager, public ProxyPacketInjector(ClassLoader classLoader, ListenerInvoker manager,
PlayerInjectionHandler playerInjection, ErrorReporter reporter) throws IllegalAccessException { PlayerInjectionHandler playerInjection, ErrorReporter reporter) throws FieldAccessException {
this.classLoader = classLoader; this.classLoader = classLoader;
this.manager = manager; this.manager = manager;
@ -100,20 +178,21 @@ class ProxyPacketInjector implements PacketInjector {
} }
} }
private void initialize() throws IllegalAccessException { private void initialize() throws FieldAccessException {
if (intHashMap == null) { if (lookup == null) {
// We're looking for the first static field with a Minecraft-object. This should be a IntHashMap.
Field intHashMapField = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true).
getFieldByType(MinecraftReflection.getMinecraftObjectRegex());
try { try {
intHashMap = FieldUtils.readField(intHashMapField, (Object) null, true); lookup = new IntHashMapLookup();
} catch (IllegalArgumentException e) { } catch (Exception e1) {
throw new RuntimeException("Minecraft is incompatible.", e);
try {
lookup = new ArrayLookup();
} catch (Exception e2) {
// Wow
throw new FieldAccessException(e1.getMessage() + ". Workaround failed too.", e2);
}
} }
// Now, get the "put" method. // Should work fine now
putMethod = FuzzyReflection.fromObject(intHashMap).getMethodByParameters("put", int.class, Object.class);
} }
} }
@ -173,21 +252,12 @@ class ProxyPacketInjector implements PacketInjector {
// Add a static reference // Add a static reference
Enhancer.registerStaticCallbacks(proxy, new Callback[] { NoOp.INSTANCE, modifierReadPacket, modifierRest }); Enhancer.registerStaticCallbacks(proxy, new Callback[] { NoOp.INSTANCE, modifierReadPacket, modifierRest });
try { // Override values
// Override values previous.put(packetID, old);
previous.put(packetID, old); registry.put(proxy, packetID);
registry.put(proxy, packetID); overwritten.put(packetID, proxy);
overwritten.put(packetID, proxy); lookup.setLookup(packetID, proxy);
putMethod.invoke(intHashMap, packetID, proxy); return true;
return true;
} catch (IllegalArgumentException e) {
throw new RuntimeException("Illegal argument.", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access method.", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Exception occured in IntHashMap.put.", e);
}
} }
@Override @Override
@ -200,25 +270,14 @@ class ProxyPacketInjector implements PacketInjector {
Map<Integer, Class> previous = PacketRegistry.getPreviousPackets(); Map<Integer, Class> previous = PacketRegistry.getPreviousPackets();
Map<Integer, Class> overwritten = PacketRegistry.getOverwrittenPackets(); Map<Integer, Class> overwritten = PacketRegistry.getOverwrittenPackets();
// Use the old class definition Class old = previous.get(packetID);
try { Class proxy = PacketRegistry.getPacketClassFromID(packetID);
Class old = previous.get(packetID);
Class proxy = PacketRegistry.getPacketClassFromID(packetID); lookup.setLookup(packetID, old);
previous.remove(packetID);
putMethod.invoke(intHashMap, packetID, old); registry.remove(proxy);
previous.remove(packetID); overwritten.remove(packetID);
registry.remove(proxy); return true;
overwritten.remove(packetID);
return true;
// Handle some problems
} catch (IllegalArgumentException e) {
return false;
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access method.", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Exception occured in IntHashMap.put.", e);
}
} }
@Override @Override

Datei anzeigen

@ -1,134 +1,140 @@
/* /*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland * Copyright (C) 2012 Kristian S. Stangeland
* *
* This program is free software; you can redistribute it and/or modify it under the terms of the * 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 * GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version. * 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; * 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. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * 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; * 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 * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol.injector.packet; package com.comphenix.protocol.injector.packet;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Map; import java.util.Map;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.error.ReportType;
import com.google.common.collect.MapMaker; import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import net.sf.cglib.proxy.MethodInterceptor; import com.google.common.collect.MapMaker;
import net.sf.cglib.proxy.MethodProxy;
import net.sf.cglib.proxy.MethodInterceptor;
class ReadPacketModifier implements MethodInterceptor { import net.sf.cglib.proxy.MethodProxy;
// A cancel marker
private static final Object CANCEL_MARKER = new Object(); class ReadPacketModifier implements MethodInterceptor {
public static final ReportType REPORT_CANNOT_HANDLE_CLIENT_PACKET = new ReportType("Cannot handle client packet.");
// Common for all packets of the same type
private ProxyPacketInjector packetInjector; // A cancel marker
private int packetID; private static final Object CANCEL_MARKER = new Object();
// Report errors // Common for all packets of the same type
private ErrorReporter reporter; private ProxyPacketInjector packetInjector;
private int packetID;
// If this is a read packet data method
private boolean isReadPacketDataMethod; // Report errors
private ErrorReporter reporter;
// Whether or not a packet has been cancelled
private static Map<Object, Object> override = new MapMaker().weakKeys().makeMap(); // If this is a read packet data method
private boolean isReadPacketDataMethod;
public ReadPacketModifier(int packetID, ProxyPacketInjector packetInjector, ErrorReporter reporter, boolean isReadPacketDataMethod) {
this.packetID = packetID; // Whether or not a packet has been cancelled
this.packetInjector = packetInjector; private static Map<Object, Object> override = new MapMaker().weakKeys().makeMap();
this.reporter = reporter;
this.isReadPacketDataMethod = isReadPacketDataMethod; public ReadPacketModifier(int packetID, ProxyPacketInjector packetInjector, ErrorReporter reporter, boolean isReadPacketDataMethod) {
} this.packetID = packetID;
this.packetInjector = packetInjector;
/** this.reporter = reporter;
* Remove any packet overrides. this.isReadPacketDataMethod = isReadPacketDataMethod;
* @param packet - the packet to rever }
*/
public static void removeOverride(Object packet) { /**
override.remove(packet); * Remove any packet overrides.
} * @param packet - the packet to rever
*/
/** public static void removeOverride(Object packet) {
* Retrieve the packet that overrides the methods of the given packet. override.remove(packet);
* @param packet - the given packet. }
* @return Overriden object.
*/ /**
public static Object getOverride(Object packet) { * Retrieve the packet that overrides the methods of the given packet.
return override.get(packet); * @param packet - the given packet.
} * @return Overriden object.
*/
/** public static Object getOverride(Object packet) {
* Determine if the given packet has been cancelled before. return override.get(packet);
* @param packet - the packet to check. }
* @return TRUE if it has been cancelled, FALSE otherwise.
*/ /**
public static boolean hasCancelled(Object packet) { * Determine if the given packet has been cancelled before.
return getOverride(packet) == CANCEL_MARKER; * @param packet - the packet to check.
} * @return TRUE if it has been cancelled, FALSE otherwise.
*/
@Override public static boolean hasCancelled(Object packet) {
public Object intercept(Object thisObj, Method method, Object[] args, MethodProxy proxy) throws Throwable { return getOverride(packet) == CANCEL_MARKER;
// Atomic retrieval }
Object overridenObject = override.get(thisObj);
Object returnValue = null; @Override
public Object intercept(Object thisObj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
if (overridenObject != null) { // Atomic retrieval
// This packet has been cancelled Object overridenObject = override.get(thisObj);
if (overridenObject == CANCEL_MARKER) { Object returnValue = null;
// So, cancel all void methods
if (method.getReturnType().equals(Void.TYPE)) if (overridenObject != null) {
return null; // This packet has been cancelled
else // Revert to normal for everything else if (overridenObject == CANCEL_MARKER) {
overridenObject = thisObj; // So, cancel all void methods
} if (method.getReturnType().equals(Void.TYPE))
return null;
returnValue = proxy.invokeSuper(overridenObject, args); else // Revert to normal for everything else
} else { overridenObject = thisObj;
returnValue = proxy.invokeSuper(thisObj, args); }
}
returnValue = proxy.invokeSuper(overridenObject, args);
// Is this a readPacketData method? } else {
if (isReadPacketDataMethod) { returnValue = proxy.invokeSuper(thisObj, args);
try { }
// We need this in order to get the correct player
DataInputStream input = (DataInputStream) args[0]; // Is this a readPacketData method?
if (isReadPacketDataMethod) {
// Let the people know try {
PacketContainer container = new PacketContainer(packetID, thisObj); // We need this in order to get the correct player
PacketEvent event = packetInjector.packetRecieved(container, input); DataInputStream input = (DataInputStream) args[0];
// Handle override // Let the people know
if (event != null) { PacketContainer container = new PacketContainer(packetID, thisObj);
Object result = event.getPacket().getHandle(); PacketEvent event = packetInjector.packetRecieved(container, input);
if (event.isCancelled()) { // Handle override
override.put(thisObj, CANCEL_MARKER); if (event != null) {
} else if (!objectEquals(thisObj, result)) { Object result = event.getPacket().getHandle();
override.put(thisObj, result);
} if (event.isCancelled()) {
} override.put(thisObj, CANCEL_MARKER);
} catch (Throwable e) { } else if (!objectEquals(thisObj, result)) {
// Minecraft cannot handle this error override.put(thisObj, result);
reporter.reportDetailed(this, "Cannot handle client packet.", e, args[0]); }
} }
} } catch (Throwable e) {
return returnValue; // Minecraft cannot handle this error
} reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_HANDLE_CLIENT_PACKET).callerParam(args[0]).error(e)
private boolean objectEquals(Object a, Object b) { );
return System.identityHashCode(a) != System.identityHashCode(b); }
} }
} return returnValue;
}
private boolean objectEquals(Object a, Object b) {
return System.identityHashCode(a) != System.identityHashCode(b);
}
}

Datei anzeigen

@ -21,11 +21,15 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.player.NetworkFieldInjector.FakePacket; import com.comphenix.protocol.injector.player.NetworkFieldInjector.FakePacket;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.MapMaker;
import net.sf.cglib.proxy.Callback; import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.Enhancer;
@ -38,12 +42,16 @@ import net.sf.cglib.proxy.MethodProxy;
* @author Kristian * @author Kristian
*/ */
class InjectedArrayList extends ArrayList<Object> { class InjectedArrayList extends ArrayList<Object> {
public static final ReportType REPORT_CANNOT_REVERT_CANCELLED_PACKET = new ReportType("Reverting cancelled packet failed.");
/** /**
* Silly Eclipse. * Silly Eclipse.
*/ */
private static final long serialVersionUID = -1173865905404280990L; private static final long serialVersionUID = -1173865905404280990L;
// Fake inverted proxy objects
private static ConcurrentMap<Object, Object> delegateLookup = new MapMaker().weakKeys().makeMap();
private transient PlayerInjector injector; private transient PlayerInjector injector;
private transient Set<Object> ignoredPackets; private transient Set<Object> ignoredPackets;
private transient ClassLoader classLoader; private transient ClassLoader classLoader;
@ -85,15 +93,10 @@ class InjectedArrayList extends ArrayList<Object> {
return true; return true;
} catch (InvocationTargetException e) { } catch (InvocationTargetException e) {
ErrorReporter reporter = ProtocolLibrary.getErrorReporter();
// Prefer to report this to the user, instead of risking sending it to Minecraft // Prefer to report this to the user, instead of risking sending it to Minecraft
if (reporter != null) { ProtocolLibrary.getErrorReporter().reportDetailed(this,
reporter.reportDetailed(this, "Reverting cancelled packet failed.", e, packet); Report.newBuilder(REPORT_CANNOT_REVERT_CANCELLED_PACKET).error(e).callerParam(packet)
} else { );
System.out.println("[ProtocolLib] Reverting cancelled packet failed.");
e.printStackTrace();
}
// Failure // Failure
return false; return false;
@ -111,10 +114,7 @@ class InjectedArrayList extends ArrayList<Object> {
ListenerInvoker invoker = injector.getInvoker(); ListenerInvoker invoker = injector.getInvoker();
int packetID = invoker.getPacketID(source); int packetID = invoker.getPacketID(source);
Class<?> type = invoker.getPacketClassFromID(packetID, true);
System.out.println(type.getName());
// We want to subtract the byte amount that were added to the running // We want to subtract the byte amount that were added to the running
// total of outstanding packets. Otherwise, cancelling too many packets // total of outstanding packets. Otherwise, cancelling too many packets
// might cause a "disconnect.overflow" error. // might cause a "disconnect.overflow" error.
@ -134,7 +134,7 @@ class InjectedArrayList extends ArrayList<Object> {
// ect. // ect.
// } // }
Enhancer ex = new Enhancer(); Enhancer ex = new Enhancer();
ex.setSuperclass(type); ex.setSuperclass(MinecraftReflection.getPacketClass());
ex.setInterfaces(new Class[] { FakePacket.class } ); ex.setInterfaces(new Class[] { FakePacket.class } );
ex.setUseCache(true); ex.setUseCache(true);
ex.setClassLoader(classLoader); ex.setClassLoader(classLoader);
@ -146,7 +146,10 @@ class InjectedArrayList extends ArrayList<Object> {
try { try {
// Temporarily associate the fake packet class // Temporarily associate the fake packet class
invoker.registerPacketClass(proxyClass, packetID); invoker.registerPacketClass(proxyClass, packetID);
return proxyClass.newInstance(); Object proxy = proxyClass.newInstance();
InjectedArrayList.registerDelegate(proxy, source);
return proxy;
} catch (Exception e) { } catch (Exception e) {
// Don't pollute the throws tree // Don't pollute the throws tree
@ -157,18 +160,33 @@ class InjectedArrayList extends ArrayList<Object> {
} }
} }
/**
* Ensure that the inverted integer proxy uses the given object as source.
* @param proxy - inverted integer proxy.
* @param source - source object.
*/
private static void registerDelegate(Object proxy, Object source) {
delegateLookup.put(proxy, source);
}
/** /**
* Inverts the integer result of every integer method. * Inverts the integer result of every integer method.
* @author Kristian * @author Kristian
*/ */
private class InvertedIntegerCallback implements MethodInterceptor { private class InvertedIntegerCallback implements MethodInterceptor {
@Override @Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
final Object delegate = delegateLookup.get(obj);
if (delegate == null) {
throw new IllegalStateException("Unable to find delegate source for " + obj);
}
if (method.getReturnType().equals(int.class) && args.length == 0) { if (method.getReturnType().equals(int.class) && args.length == 0) {
Integer result = (Integer) proxy.invokeSuper(obj, args); Integer result = (Integer) proxy.invoke(delegate, args);
return -result; return -result;
} else { } else {
return proxy.invokeSuper(obj, args); return proxy.invoke(delegate, args);
} }
} }
} }

Datei anzeigen

@ -1,318 +1,338 @@
/* /*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland * Copyright (C) 2012 Kristian S. Stangeland
* *
* This program is free software; you can redistribute it and/or modify it under the terms of the * 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 * GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version. * 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; * 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. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * 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; * 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 * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol.injector.player; package com.comphenix.protocol.injector.player;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import net.sf.cglib.proxy.Factory; import net.sf.cglib.proxy.Factory;
import org.bukkit.Server; import org.bukkit.Server;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.injector.server.AbstractInputStreamLookup; import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
import com.comphenix.protocol.reflect.ObjectWriter; import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.VolatileField; import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.reflect.ObjectWriter;
import com.comphenix.protocol.reflect.VolatileField;
/** import com.comphenix.protocol.utility.MinecraftReflection;
* Used to ensure that the 1.3 server is referencing the correct server handler.
* /**
* @author Kristian * Used to ensure that the 1.3 server is referencing the correct server handler.
*/ *
class InjectedServerConnection { * @author Kristian
*/
private static Field listenerThreadField; class InjectedServerConnection {
private static Field minecraftServerField; // A number of things can go wrong ...
private static Field listField; public static final ReportType REPORT_CANNOT_FIND_MINECRAFT_SERVER = new ReportType("Cannot extract minecraft server from Bukkit.");
private static Field dedicatedThreadField; public static final ReportType REPORT_CANNOT_INJECT_SERVER_CONNECTION = new ReportType("Cannot inject into server connection. Bad things will happen.");
private static Method serverConnectionMethod; public static final ReportType REPORT_CANNOT_FIND_LISTENER_THREAD = new ReportType("Cannot find listener thread in MinecraftServer.");
public static final ReportType REPORT_CANNOT_READ_LISTENER_THREAD = new ReportType("Unable to read the listener thread.");
private List<VolatileField> listFields;
private List<ReplacedArrayList<Object>> replacedLists; public static final ReportType REPORT_CANNOT_FIND_SERVER_CONNECTION = new ReportType("Unable to retrieve server connection");
public static final ReportType REPORT_UNEXPECTED_THREAD_COUNT = new ReportType("Unexpected number of threads in %s: %s");
// Used to inject net handlers public static final ReportType REPORT_CANNOT_FIND_NET_HANDLER_THREAD = new ReportType("Unable to retrieve net handler thread.");
private NetLoginInjector netLoginInjector; public static final ReportType REPORT_INSUFFICENT_THREAD_COUNT = new ReportType("Unable to inject %s lists in %s.");
// Inject server connections public static final ReportType REPORT_CANNOT_COPY_OLD_TO_NEW = new ReportType("Cannot copy old %s to new.");
private AbstractInputStreamLookup socketInjector;
private static Field listenerThreadField;
private Server server; private static Field minecraftServerField;
private ErrorReporter reporter; private static Field listField;
private boolean hasAttempted; private static Field dedicatedThreadField;
private boolean hasSuccess;
private static Method serverConnectionMethod;
private Object minecraftServer = null;
private List<VolatileField> listFields;
public InjectedServerConnection(ErrorReporter reporter, AbstractInputStreamLookup socketInjector, Server server, NetLoginInjector netLoginInjector) { private List<ReplacedArrayList<Object>> replacedLists;
this.listFields = new ArrayList<VolatileField>();
this.replacedLists = new ArrayList<ReplacedArrayList<Object>>(); // Used to inject net handlers
this.reporter = reporter; private NetLoginInjector netLoginInjector;
this.server = server;
this.socketInjector = socketInjector; // Inject server connections
this.netLoginInjector = netLoginInjector; private AbstractInputStreamLookup socketInjector;
}
private Server server;
public void injectList() { private ErrorReporter reporter;
private boolean hasAttempted;
// Only execute this method once private boolean hasSuccess;
if (!hasAttempted)
hasAttempted = true; private Object minecraftServer = null;
else
return; public InjectedServerConnection(ErrorReporter reporter, AbstractInputStreamLookup socketInjector, Server server, NetLoginInjector netLoginInjector) {
this.listFields = new ArrayList<VolatileField>();
if (minecraftServerField == null) this.replacedLists = new ArrayList<ReplacedArrayList<Object>>();
minecraftServerField = FuzzyReflection.fromObject(server, true). this.reporter = reporter;
getFieldByType("MinecraftServer", MinecraftReflection.getMinecraftServerClass()); this.server = server;
this.socketInjector = socketInjector;
try { this.netLoginInjector = netLoginInjector;
minecraftServer = FieldUtils.readField(minecraftServerField, server, true); }
} catch (IllegalAccessException e1) {
reporter.reportWarning(this, "Cannot extract minecraft server from Bukkit."); public void injectList() {
return; // Only execute this method once
} if (!hasAttempted)
hasAttempted = true;
try { else
if (serverConnectionMethod == null) return;
serverConnectionMethod = FuzzyReflection.fromClass(minecraftServerField.getType()).
getMethodByParameters("getServerConnection", if (minecraftServerField == null)
MinecraftReflection.getServerConnectionClass(), new Class[] {}); minecraftServerField = FuzzyReflection.fromObject(server, true).
// We're using Minecraft 1.3.1 getFieldByType("MinecraftServer", MinecraftReflection.getMinecraftServerClass());
injectServerConnection();
try {
} catch (IllegalArgumentException e) { minecraftServer = FieldUtils.readField(minecraftServerField, server, true);
} catch (IllegalAccessException e1) {
// Minecraft 1.2.5 or lower reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_FIND_MINECRAFT_SERVER));
injectListenerThread(); return;
}
} catch (Exception e) {
// Oh damn - inform the player try {
reporter.reportDetailed(this, "Cannot inject into server connection. Bad things will happen.", e); if (serverConnectionMethod == null)
} serverConnectionMethod = FuzzyReflection.fromClass(minecraftServerField.getType()).
} getMethodByParameters("getServerConnection",
MinecraftReflection.getServerConnectionClass(), new Class[] {});
private void injectListenerThread() { // We're using Minecraft 1.3.1
try { injectServerConnection();
if (listenerThreadField == null)
listenerThreadField = FuzzyReflection.fromObject(minecraftServer). } catch (IllegalArgumentException e) {
getFieldByType("networkListenThread", MinecraftReflection.getNetworkListenThreadClass());
} catch (RuntimeException e) { // Minecraft 1.2.5 or lower
reporter.reportDetailed(this, "Cannot find listener thread in MinecraftServer.", e, minecraftServer); injectListenerThread();
return;
} } catch (Exception e) {
// Oh damn - inform the player
Object listenerThread = null; reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_INJECT_SERVER_CONNECTION).error(e));
}
// Attempt to get the thread }
try {
listenerThread = listenerThreadField.get(minecraftServer); private void injectListenerThread() {
} catch (Exception e) { try {
reporter.reportWarning(this, "Unable to read the listener thread.", e); if (listenerThreadField == null)
return; listenerThreadField = FuzzyReflection.fromObject(minecraftServer).
} getFieldByType("networkListenThread", MinecraftReflection.getNetworkListenThreadClass());
} catch (RuntimeException e) {
// Inject the server socket too reporter.reportDetailed(this,
injectServerSocket(listenerThread); Report.newBuilder(REPORT_CANNOT_FIND_LISTENER_THREAD).callerParam(minecraftServer).error(e)
);
// Just inject every list field we can get return;
injectEveryListField(listenerThread, 1); }
hasSuccess = true;
} Object listenerThread = null;
private void injectServerConnection() { // Attempt to get the thread
try {
Object serverConnection = null; listenerThread = listenerThreadField.get(minecraftServer);
} catch (Exception e) {
// Careful - we might fail reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_READ_LISTENER_THREAD).error(e));
try { return;
serverConnection = serverConnectionMethod.invoke(minecraftServer); }
} catch (Exception ex) {
reporter.reportDetailed(this, "Unable to retrieve server connection", ex, minecraftServer); // Inject the server socket too
return; injectServerSocket(listenerThread);
}
// Just inject every list field we can get
if (listField == null) injectEveryListField(listenerThread, 1);
listField = FuzzyReflection.fromClass(serverConnectionMethod.getReturnType(), true). hasSuccess = true;
getFieldByType("netServerHandlerList", List.class); }
if (dedicatedThreadField == null) {
List<Field> matches = FuzzyReflection.fromObject(serverConnection, true). private void injectServerConnection() {
getFieldListByType(Thread.class); Object serverConnection = null;
// Verify the field count // Careful - we might fail
if (matches.size() != 1) try {
reporter.reportWarning(this, "Unexpected number of threads in " + serverConnection.getClass().getName()); serverConnection = serverConnectionMethod.invoke(minecraftServer);
else } catch (Exception e) {
dedicatedThreadField = matches.get(0); reporter.reportDetailed(this,
} Report.newBuilder(REPORT_CANNOT_FIND_SERVER_CONNECTION).callerParam(minecraftServer).error(e)
);
// Next, try to get the dedicated thread return;
try { }
if (dedicatedThreadField != null) {
Object dedicatedThread = FieldUtils.readField(dedicatedThreadField, serverConnection, true); if (listField == null)
listField = FuzzyReflection.fromClass(serverConnectionMethod.getReturnType(), true).
// Inject server socket and NetServerHandlers. getFieldByType("netServerHandlerList", List.class);
injectServerSocket(dedicatedThread); if (dedicatedThreadField == null) {
injectEveryListField(dedicatedThread, 1); List<Field> matches = FuzzyReflection.fromObject(serverConnection, true).
} getFieldListByType(Thread.class);
} catch (IllegalAccessException e) {
reporter.reportWarning(this, "Unable to retrieve net handler thread.", e); // Verify the field count
} if (matches.size() != 1)
reporter.reportWarning(this,
injectIntoList(serverConnection, listField); Report.newBuilder(REPORT_UNEXPECTED_THREAD_COUNT).messageParam(serverConnection.getClass(), matches.size())
hasSuccess = true; );
} else
dedicatedThreadField = matches.get(0);
private void injectServerSocket(Object container) { }
socketInjector.inject(container);
} // Next, try to get the dedicated thread
try {
/** if (dedicatedThreadField != null) {
* Automatically inject into every List-compatible public or private field of the given object. Object dedicatedThread = FieldUtils.readField(dedicatedThreadField, serverConnection, true);
* @param container - container object with the fields to inject.
* @param minimum - the minimum number of fields we expect exists. // Inject server socket and NetServerHandlers.
*/ injectServerSocket(dedicatedThread);
private void injectEveryListField(Object container, int minimum) { injectEveryListField(dedicatedThread, 1);
// Ok, great. Get every list field }
List<Field> lists = FuzzyReflection.fromObject(container, true).getFieldListByType(List.class); } catch (IllegalAccessException e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_FIND_NET_HANDLER_THREAD).error(e));
for (Field list : lists) { }
injectIntoList(container, list);
} injectIntoList(serverConnection, listField);
hasSuccess = true;
// Warn about unexpected errors }
if (lists.size() < minimum) {
reporter.reportWarning(this, "Unable to inject " + minimum + " lists in " + container.getClass().getName()); private void injectServerSocket(Object container) {
} socketInjector.inject(container);
} }
@SuppressWarnings("unchecked") /**
private void injectIntoList(Object instance, Field field) { * Automatically inject into every List-compatible public or private field of the given object.
VolatileField listFieldRef = new VolatileField(field, instance, true); * @param container - container object with the fields to inject.
List<Object> list = (List<Object>) listFieldRef.getValue(); * @param minimum - the minimum number of fields we expect exists.
*/
// Careful not to inject twice private void injectEveryListField(Object container, int minimum) {
if (list instanceof ReplacedArrayList) { // Ok, great. Get every list field
replacedLists.add((ReplacedArrayList<Object>) list); List<Field> lists = FuzzyReflection.fromObject(container, true).getFieldListByType(List.class);
} else {
ReplacedArrayList<Object> injectedList = createReplacement(list); for (Field list : lists) {
injectIntoList(container, list);
replacedLists.add(injectedList); }
listFieldRef.setValue(injectedList);
listFields.add(listFieldRef); // Warn about unexpected errors
} if (lists.size() < minimum) {
} reporter.reportWarning(this, Report.newBuilder(REPORT_INSUFFICENT_THREAD_COUNT).messageParam(minimum, container.getClass()));
}
// Hack to avoid the "moved to quickly" error }
private ReplacedArrayList<Object> createReplacement(List<Object> list) {
return new ReplacedArrayList<Object>(list) { @SuppressWarnings("unchecked")
/** private void injectIntoList(Object instance, Field field) {
* Shut up Eclipse! VolatileField listFieldRef = new VolatileField(field, instance, true);
*/ List<Object> list = (List<Object>) listFieldRef.getValue();
private static final long serialVersionUID = 2070481080950500367L;
// Careful not to inject twice
// Object writer we'll use if (list instanceof ReplacedArrayList) {
private final ObjectWriter writer = new ObjectWriter(); replacedLists.add((ReplacedArrayList<Object>) list);
} else {
@Override ReplacedArrayList<Object> injectedList = createReplacement(list);
protected void onReplacing(Object inserting, Object replacement) {
// Is this a normal Minecraft object? replacedLists.add(injectedList);
if (!(inserting instanceof Factory)) { listFieldRef.setValue(injectedList);
// If so, copy the content of the old element to the new listFields.add(listFieldRef);
try { }
writer.copyTo(inserting, replacement, inserting.getClass()); }
} catch (Throwable e) {
reporter.reportDetailed(InjectedServerConnection.this, "Cannot copy old " + inserting + // Hack to avoid the "moved to quickly" error
" to new.", e, inserting, replacement); private ReplacedArrayList<Object> createReplacement(List<Object> list) {
} return new ReplacedArrayList<Object>(list) {
} /**
} * Shut up Eclipse!
*/
@Override private static final long serialVersionUID = 2070481080950500367L;
protected void onInserting(Object inserting) {
// Ready for some login handler injection? // Object writer we'll use
if (MinecraftReflection.isLoginHandler(inserting)) { private final ObjectWriter writer = new ObjectWriter();
Object replaced = netLoginInjector.onNetLoginCreated(inserting);
@Override
// Only replace if it has changed protected void onReplacing(Object inserting, Object replacement) {
if (inserting != replaced) // Is this a normal Minecraft object?
addMapping(inserting, replaced, true); if (!(inserting instanceof Factory)) {
} // If so, copy the content of the old element to the new
} try {
writer.copyTo(inserting, replacement, inserting.getClass());
@Override } catch (Throwable e) {
protected void onRemoved(Object removing) { reporter.reportDetailed(InjectedServerConnection.this,
// Clean up? Report.newBuilder(REPORT_CANNOT_COPY_OLD_TO_NEW).messageParam(inserting).callerParam(inserting, replacement).error(e)
if (MinecraftReflection.isLoginHandler(removing)) { );
netLoginInjector.cleanup(removing); }
} }
} }
};
} @Override
protected void onInserting(Object inserting) {
/** // Ready for some login handler injection?
* Replace the server handler instance kept by the "keep alive" object. if (MinecraftReflection.isLoginHandler(inserting)) {
* @param oldHandler - old server handler. Object replaced = netLoginInjector.onNetLoginCreated(inserting);
* @param newHandler - new, proxied server handler.
*/ // Only replace if it has changed
public void replaceServerHandler(Object oldHandler, Object newHandler) { if (inserting != replaced)
if (!hasAttempted) { addMapping(inserting, replaced, true);
injectList(); }
} }
if (hasSuccess) { @Override
for (ReplacedArrayList<Object> replacedList : replacedLists) { protected void onRemoved(Object removing) {
replacedList.addMapping(oldHandler, newHandler); // Clean up?
} if (MinecraftReflection.isLoginHandler(removing)) {
} netLoginInjector.cleanup(removing);
} }
}
/** };
* Revert to the old vanilla server handler, if it has been replaced. }
* @param oldHandler - old vanilla server handler.
*/ /**
public void revertServerHandler(Object oldHandler) { * Replace the server handler instance kept by the "keep alive" object.
if (hasSuccess) { * @param oldHandler - old server handler.
for (ReplacedArrayList<Object> replacedList : replacedLists) { * @param newHandler - new, proxied server handler.
replacedList.removeMapping(oldHandler); */
} public void replaceServerHandler(Object oldHandler, Object newHandler) {
} if (!hasAttempted) {
} injectList();
}
/**
* Undoes everything. if (hasSuccess) {
*/ for (ReplacedArrayList<Object> replacedList : replacedLists) {
public void cleanupAll() { replacedList.addMapping(oldHandler, newHandler);
if (replacedLists.size() > 0) { }
// Repair the underlying lists }
for (ReplacedArrayList<Object> replacedList : replacedLists) { }
replacedList.revertAll();
} /**
for (VolatileField field : listFields) { * Revert to the old vanilla server handler, if it has been replaced.
field.revertValue(); * @param oldHandler - old vanilla server handler.
} */
public void revertServerHandler(Object oldHandler) {
listFields.clear(); if (hasSuccess) {
replacedLists.clear(); for (ReplacedArrayList<Object> replacedList : replacedLists) {
} replacedList.removeMapping(oldHandler);
} }
} }
}
/**
* Undoes everything.
*/
public void cleanupAll() {
if (replacedLists.size() > 0) {
// Repair the underlying lists
for (ReplacedArrayList<Object> replacedList : replacedLists) {
replacedList.revertAll();
}
for (VolatileField field : listFields) {
field.revertValue();
}
listFields.clear();
replacedLists.clear();
}
}
}

Datei anzeigen

@ -1,141 +1,154 @@
/* /*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland * Copyright (C) 2012 Kristian S. Stangeland
* *
* This program is free software; you can redistribute it and/or modify it under the terms of the * 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 * GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version. * 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; * 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. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * 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; * 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 * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol.injector.player; package com.comphenix.protocol.injector.player;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import org.bukkit.Server; import org.bukkit.Server;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.injector.GamePhase; import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler.ConflictStrategy; import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory; import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.injector.player.PlayerInjectionHandler.ConflictStrategy;
import com.google.common.collect.Maps; import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import com.comphenix.protocol.utility.MinecraftReflection;
/** import com.google.common.collect.Maps;
* Injects every NetLoginHandler created by the server.
* /**
* @author Kristian * Injects every NetLoginHandler created by the server.
*/ *
class NetLoginInjector { * @author Kristian
private ConcurrentMap<Object, PlayerInjector> injectedLogins = Maps.newConcurrentMap(); */
class NetLoginInjector {
// Handles every hook public static final ReportType REPORT_CANNOT_HOOK_LOGIN_HANDLER = new ReportType("Unable to hook %s.");
private ProxyPlayerInjectionHandler injectionHandler; public static final ReportType REPORT_CANNOT_CLEANUP_LOGIN_HANDLER = new ReportType("Cannot cleanup %s.");
// Create temporary players private ConcurrentMap<Object, PlayerInjector> injectedLogins = Maps.newConcurrentMap();
private TemporaryPlayerFactory playerFactory = new TemporaryPlayerFactory();
// Handles every hook
// The current error reporter private ProxyPlayerInjectionHandler injectionHandler;
private ErrorReporter reporter;
private Server server; // Create temporary players
private TemporaryPlayerFactory playerFactory = new TemporaryPlayerFactory();
public NetLoginInjector(ErrorReporter reporter, Server server, ProxyPlayerInjectionHandler injectionHandler) {
this.reporter = reporter; // The current error reporter
this.server = server; private ErrorReporter reporter;
this.injectionHandler = injectionHandler; private Server server;
}
public NetLoginInjector(ErrorReporter reporter, Server server, ProxyPlayerInjectionHandler injectionHandler) {
/** this.reporter = reporter;
* Invoked when a NetLoginHandler has been created. this.server = server;
* @param inserting - the new NetLoginHandler. this.injectionHandler = injectionHandler;
* @return An injected NetLoginHandler, or the original object. }
*/
public Object onNetLoginCreated(Object inserting) { /**
try { * Invoked when a NetLoginHandler has been created.
// Make sure we actually need to inject during this phase * @param inserting - the new NetLoginHandler.
if (!injectionHandler.isInjectionNecessary(GamePhase.LOGIN)) * @return An injected NetLoginHandler, or the original object.
return inserting; */
public Object onNetLoginCreated(Object inserting) {
Player temporary = playerFactory.createTemporaryPlayer(server); try {
// Note that we bail out if there's an existing player injector // Make sure we actually need to inject during this phase
PlayerInjector injector = injectionHandler.injectPlayer( if (!injectionHandler.isInjectionNecessary(GamePhase.LOGIN))
temporary, inserting, ConflictStrategy.BAIL_OUT, GamePhase.LOGIN); return inserting;
if (injector != null) { Player temporary = playerFactory.createTemporaryPlayer(server);
// Update injector as well // Note that we bail out if there's an existing player injector
TemporaryPlayerFactory.setInjectorInPlayer(temporary, injector); PlayerInjector injector = injectionHandler.injectPlayer(
injector.updateOnLogin = true; temporary, inserting, ConflictStrategy.BAIL_OUT, GamePhase.LOGIN);
// Save the login if (injector != null) {
injectedLogins.putIfAbsent(inserting, injector); // Update injector as well
} TemporaryPlayerFactory.setInjectorInPlayer(temporary, injector);
injector.updateOnLogin = true;
// NetServerInjector can never work (currently), so we don't need to replace the NetLoginHandler
return inserting; // Save the login
injectedLogins.putIfAbsent(inserting, injector);
} catch (Throwable e) { }
// Minecraft can't handle this, so we'll deal with it here
reporter.reportDetailed(this, "Unable to hook " + // NetServerInjector can never work (currently), so we don't need to replace the NetLoginHandler
MinecraftReflection.getNetLoginHandlerName() + ".", e, inserting, injectionHandler); return inserting;
return inserting;
} } catch (Throwable e) {
} // Minecraft can't handle this, so we'll deal with it here
reporter.reportDetailed(this,
/** Report.newBuilder(REPORT_CANNOT_HOOK_LOGIN_HANDLER).
* Invoked when a NetLoginHandler should be reverted. messageParam(MinecraftReflection.getNetLoginHandlerName()).
* @param inserting - the original NetLoginHandler. callerParam(inserting, injectionHandler).
* @return An injected NetLoginHandler, or the original object. error(e)
*/ );
public synchronized void cleanup(Object removing) { return inserting;
PlayerInjector injected = injectedLogins.get(removing); }
}
if (injected != null) {
try { /**
PlayerInjector newInjector = null; * Invoked when a NetLoginHandler should be reverted.
Player player = injected.getPlayer(); * @param inserting - the original NetLoginHandler.
* @return An injected NetLoginHandler, or the original object.
// Clean up list */
injectedLogins.remove(removing); public synchronized void cleanup(Object removing) {
PlayerInjector injected = injectedLogins.get(removing);
// No need to clean up twice
if (injected.isClean()) if (injected != null) {
return; try {
PlayerInjector newInjector = null;
// Hack to clean up other references Player player = injected.getPlayer();
newInjector = injectionHandler.getInjectorByNetworkHandler(injected.getNetworkManager());
injectionHandler.uninjectPlayer(player); // Clean up list
injectedLogins.remove(removing);
// Update NetworkManager
if (newInjector != null) { // No need to clean up twice
if (injected instanceof NetworkObjectInjector) { if (injected.isClean())
newInjector.setNetworkManager(injected.getNetworkManager(), true); return;
}
} // Hack to clean up other references
newInjector = injectionHandler.getInjectorByNetworkHandler(injected.getNetworkManager());
} catch (Throwable e) { injectionHandler.uninjectPlayer(player);
// Don't leak this to Minecraft
reporter.reportDetailed(this, "Cannot cleanup " + // Update NetworkManager
MinecraftReflection.getNetLoginHandlerName() + ".", e, removing); if (newInjector != null) {
} if (injected instanceof NetworkObjectInjector) {
} newInjector.setNetworkManager(injected.getNetworkManager(), true);
} }
}
/**
* Remove all injected hooks. } catch (Throwable e) {
*/ // Don't leak this to Minecraft
public void cleanupAll() { reporter.reportDetailed(this,
for (PlayerInjector injector : injectedLogins.values()) { Report.newBuilder(REPORT_CANNOT_CLEANUP_LOGIN_HANDLER).
injector.cleanupAll(); messageParam(MinecraftReflection.getNetLoginHandlerName()).
} callerParam(removing).
error(e)
injectedLogins.clear(); );
} }
} }
}
/**
* Remove all injected hooks.
*/
public void cleanupAll() {
for (PlayerInjector injector : injectedLogins.values()) {
injector.cleanupAll();
}
injectedLogins.clear();
}
}

Datei anzeigen

@ -39,6 +39,7 @@ import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.VolatileField; import com.comphenix.protocol.reflect.VolatileField;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
/** /**
@ -56,6 +57,12 @@ class NetworkFieldInjector extends PlayerInjector {
// Nothing // Nothing
} }
// After commit 336a4e00668fd2518c41242755ed6b3bdc3b0e6c (Update CraftBukkit to Minecraft 1.4.4.),
// CraftBukkit stopped redirecting map chunk and map chunk bulk packets to a separate queue.
// Thus, NetworkFieldInjector can safely handle every packet (though not perfectly - some packets
// will be slightly processed).
private MinecraftVersion safeVersion = new MinecraftVersion("1.4.4");
// Packets to ignore // Packets to ignore
private Set<Object> ignoredPackets = Sets.newSetFromMap(new ConcurrentHashMap<Object, Boolean>()); private Set<Object> ignoredPackets = Sets.newSetFromMap(new ConcurrentHashMap<Object, Boolean>());
@ -99,7 +106,6 @@ class NetworkFieldInjector extends PlayerInjector {
@Override @Override
public void sendServerPacket(Object packet, boolean filtered) throws InvocationTargetException { public void sendServerPacket(Object packet, boolean filtered) throws InvocationTargetException {
if (networkManager != null) { if (networkManager != null) {
try { try {
if (!filtered) { if (!filtered) {
@ -122,14 +128,19 @@ class NetworkFieldInjector extends PlayerInjector {
} }
@Override @Override
public UnsupportedListener checkListener(PacketListener listener) { public UnsupportedListener checkListener(MinecraftVersion version, PacketListener listener) {
int[] unsupported = { Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK }; if (version != null && version.compareTo(safeVersion) > 0) {
// Unfortunately, we don't support chunk packets
if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(), unsupported)) {
return new UnsupportedListener("The NETWORK_FIELD_INJECTOR hook doesn't support map chunk listeners.", unsupported);
} else {
return null; return null;
} else {
int[] unsupported = { Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK };
// Unfortunately, we don't support chunk packets
if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(), unsupported)) {
return new UnsupportedListener("The NETWORK_FIELD_INJECTOR hook doesn't support map chunk listeners.", unsupported);
} else {
return null;
}
} }
} }

Datei anzeigen

@ -40,6 +40,7 @@ import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory; import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import com.comphenix.protocol.utility.MinecraftVersion;
/** /**
* Injection method that overrides the NetworkHandler itself, and its queue-method. * Injection method that overrides the NetworkHandler itself, and its queue-method.
@ -53,6 +54,12 @@ public class NetworkObjectInjector extends PlayerInjector {
// Used to construct proxy objects // Used to construct proxy objects
private ClassLoader classLoader; private ClassLoader classLoader;
// After commit 336a4e00668fd2518c41242755ed6b3bdc3b0e6c (Update CraftBukkit to Minecraft 1.4.4.),
// CraftBukkit stopped redirecting map chunk and map chunk bulk packets to a separate queue.
// Thus, NetworkFieldInjector can safely handle every packet (though not perfectly - some packets
// will be slightly processed).
private MinecraftVersion safeVersion = new MinecraftVersion("1.4.4");
// Shared callback filter - avoid creating a new class every time // Shared callback filter - avoid creating a new class every time
private volatile static CallbackFilter callbackFilter; private volatile static CallbackFilter callbackFilter;
@ -117,14 +124,19 @@ public class NetworkObjectInjector extends PlayerInjector {
} }
@Override @Override
public UnsupportedListener checkListener(PacketListener listener) { public UnsupportedListener checkListener(MinecraftVersion version, PacketListener listener) {
int[] unsupported = { Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK }; if (version != null && version.compareTo(safeVersion) > 0) {
// Unfortunately, we don't support chunk packets
if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(), unsupported)) {
return new UnsupportedListener("The NETWORK_OBJECT_INJECTOR hook doesn't support map chunk listeners.", unsupported);
} else {
return null; return null;
} else {
int[] unsupported = { Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK };
// Unfortunately, we don't support chunk packets
if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(), unsupported)) {
return new UnsupportedListener("The NETWORK_OBJECT_INJECTOR hook doesn't support map chunk listeners.", unsupported);
} else {
return null;
}
} }
} }

Datei anzeigen

@ -1,324 +1,352 @@
/* /*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland * Copyright (C) 2012 Kristian S. Stangeland
* *
* This program is free software; you can redistribute it and/or modify it under the terms of the * 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 * GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version. * 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; * 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. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * 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; * 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 * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol.injector.player; package com.comphenix.protocol.injector.player;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import net.sf.cglib.proxy.*; import java.util.Arrays;
import org.bukkit.entity.Player; import net.sf.cglib.proxy.*;
import com.comphenix.protocol.concurrency.IntegerSet; import org.bukkit.entity.Player;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.concurrency.IntegerSet;
import com.comphenix.protocol.injector.GamePhase; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.reflect.ObjectWriter; import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.reflect.VolatileField; import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
import com.comphenix.protocol.reflect.instances.DefaultInstances; import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.instances.ExistingGenerator; import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.utility.MinecraftMethods; import com.comphenix.protocol.reflect.ObjectWriter;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.reflect.VolatileField;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
/** import com.comphenix.protocol.reflect.instances.ExistingGenerator;
* Represents a player hook into the NetServerHandler class. import com.comphenix.protocol.utility.MinecraftMethods;
* import com.comphenix.protocol.utility.MinecraftReflection;
* @author Kristian import com.comphenix.protocol.utility.MinecraftVersion;
*/
class NetworkServerInjector extends PlayerInjector { /**
* Represents a player hook into the NetServerHandler class.
private volatile static CallbackFilter callbackFilter; *
* @author Kristian
private volatile static Field disconnectField; */
private InjectedServerConnection serverInjection; class NetworkServerInjector extends PlayerInjector {
// Disconnected field
// Determine if we're listening public static final ReportType REPORT_ASSUMING_DISCONNECT_FIELD = new ReportType("Unable to find 'disconnected' field. Assuming %s.");
private IntegerSet sendingFilters; 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.");
// Used to create proxy objects
private ClassLoader classLoader; private volatile static CallbackFilter callbackFilter;
private volatile static boolean foundSendPacket;
// Whether or not the player has disconnected
private boolean hasDisconnected; private volatile static Field disconnectField;
private InjectedServerConnection serverInjection;
// Used to copy fields
private final ObjectWriter writer = new ObjectWriter(); // Determine if we're listening
private IntegerSet sendingFilters;
public NetworkServerInjector(
ClassLoader classLoader, ErrorReporter reporter, Player player, // Used to create proxy objects
ListenerInvoker invoker, IntegerSet sendingFilters, private ClassLoader classLoader;
InjectedServerConnection serverInjection) throws IllegalAccessException {
// Whether or not the player has disconnected
super(reporter, player, invoker); private boolean hasDisconnected;
this.classLoader = classLoader;
this.sendingFilters = sendingFilters; // Used to copy fields
this.serverInjection = serverInjection; private final ObjectWriter writer = new ObjectWriter();
}
public NetworkServerInjector(
@Override ClassLoader classLoader, ErrorReporter reporter, Player player,
protected boolean hasListener(int packetID) { ListenerInvoker invoker, IntegerSet sendingFilters,
return sendingFilters.contains(packetID); InjectedServerConnection serverInjection) throws IllegalAccessException {
}
super(reporter, player, invoker);
@Override this.classLoader = classLoader;
public void sendServerPacket(Object packet, boolean filtered) throws InvocationTargetException { this.sendingFilters = sendingFilters;
Object serverDelegate = filtered ? serverHandlerRef.getValue() : serverHandlerRef.getOldValue(); this.serverInjection = serverInjection;
}
if (serverDelegate != null) {
try { @Override
// Note that invocation target exception is a wrapper for a checked exception protected boolean hasListener(int packetID) {
MinecraftMethods.getSendPacketMethod().invoke(serverDelegate, packet); return sendingFilters.contains(packetID);
}
} catch (IllegalArgumentException e) {
throw e; @Override
} catch (InvocationTargetException e) { public void sendServerPacket(Object packet, boolean filtered) throws InvocationTargetException {
throw e; Object serverDelegate = filtered ? serverHandlerRef.getValue() : serverHandlerRef.getOldValue();
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unable to access send packet method.", e); if (serverDelegate != null) {
} try {
} else { // Note that invocation target exception is a wrapper for a checked exception
throw new IllegalStateException("Unable to load server handler. Cannot send packet."); MinecraftMethods.getSendPacketMethod().invoke(serverDelegate, packet);
}
} } catch (IllegalArgumentException e) {
throw e;
@Override } catch (InvocationTargetException e) {
public void injectManager() { throw e;
} catch (IllegalAccessException e) {
if (serverHandlerRef == null) throw new IllegalStateException("Unable to access send packet method.", e);
throw new IllegalStateException("Cannot find server handler."); }
// Don't inject twice } else {
if (serverHandlerRef.getValue() instanceof Factory) throw new IllegalStateException("Unable to load server handler. Cannot send packet.");
return; }
}
if (!tryInjectManager()) {
Class<?> serverHandlerClass = MinecraftReflection.getNetServerHandlerClass(); @Override
public void injectManager() {
// Try to override the proxied object
if (proxyServerField != null) { if (serverHandlerRef == null)
serverHandlerRef = new VolatileField(proxyServerField, serverHandler, true); throw new IllegalStateException("Cannot find server handler.");
serverHandler = serverHandlerRef.getValue(); // Don't inject twice
if (serverHandlerRef.getValue() instanceof Factory)
if (serverHandler == null) return;
throw new RuntimeException("Cannot hook player: Inner proxy object is NULL.");
else if (!tryInjectManager()) {
serverHandlerClass = serverHandler.getClass(); Class<?> serverHandlerClass = MinecraftReflection.getNetServerHandlerClass();
// Try again // Try to override the proxied object
if (tryInjectManager()) { if (proxyServerField != null) {
// It worked - probably serverHandlerRef = new VolatileField(proxyServerField, serverHandler, true);
return; serverHandler = serverHandlerRef.getValue();
}
} if (serverHandler == null)
throw new RuntimeException("Cannot hook player: Inner proxy object is NULL.");
throw new RuntimeException( else
"Cannot hook player: Unable to find a valid constructor for the " serverHandlerClass = serverHandler.getClass();
+ serverHandlerClass.getName() + " object.");
} // Try again
} if (tryInjectManager()) {
// It worked - probably
private boolean tryInjectManager() { return;
Class<?> serverClass = serverHandler.getClass(); }
}
Enhancer ex = new Enhancer();
Callback sendPacketCallback = new MethodInterceptor() { throw new RuntimeException(
@Override "Cannot hook player: Unable to find a valid constructor for the "
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { + serverHandlerClass.getName() + " object.");
Object packet = args[0]; }
}
if (packet != null) {
packet = handlePacketSending(packet); private boolean tryInjectManager() {
Class<?> serverClass = serverHandler.getClass();
// A NULL packet indicate cancelling
if (packet != null) Enhancer ex = new Enhancer();
args[0] = packet; Callback sendPacketCallback = new MethodInterceptor() {
else @Override
return null; public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
} Object packet = args[0];
// Call the method directly if (packet != null) {
return proxy.invokeSuper(obj, args); packet = handlePacketSending(packet);
};
}; // A NULL packet indicate cancelling
Callback noOpCallback = NoOp.INSTANCE; if (packet != null)
args[0] = packet;
// Share callback filter - that way, we avoid generating a new class for else
// every logged in player. return null;
if (callbackFilter == null) { }
final Method sendPacket = MinecraftMethods.getSendPacketMethod();
// Call the method directly
callbackFilter = new CallbackFilter() { return proxy.invokeSuper(obj, args);
@Override };
public int accept(Method method) { };
if (method.equals(sendPacket)) Callback noOpCallback = NoOp.INSTANCE;
return 0;
else // Share callback filter - that way, we avoid generating a new class for
return 1; // every logged in player.
} if (callbackFilter == null) {
}; final Method sendPacket = MinecraftMethods.getSendPacketMethod();
}
callbackFilter = new CallbackFilter() {
ex.setClassLoader(classLoader); @Override
ex.setSuperclass(serverClass); public int accept(Method method) {
ex.setCallbacks(new Callback[] { sendPacketCallback, noOpCallback }); if (isCallableEqual(sendPacket, method)) {
ex.setCallbackFilter(callbackFilter); foundSendPacket = true;
return 0;
// Find the Minecraft NetServerHandler superclass } else {
Class<?> minecraftSuperClass = getFirstMinecraftSuperClass(serverHandler.getClass()); return 1;
ExistingGenerator generator = ExistingGenerator.fromObjectFields(serverHandler, minecraftSuperClass); }
DefaultInstances serverInstances = null; }
};
// Maybe the proxy instance can help? }
Object proxyInstance = getProxyServerHandler();
ex.setClassLoader(classLoader);
// Use the existing server proxy when we create one ex.setSuperclass(serverClass);
if (proxyInstance != null && proxyInstance != serverHandler) { ex.setCallbacks(new Callback[] { sendPacketCallback, noOpCallback });
serverInstances = DefaultInstances.fromArray(generator, ex.setCallbackFilter(callbackFilter);
ExistingGenerator.fromObjectArray(new Object[] { proxyInstance }));
} else { // Find the Minecraft NetServerHandler superclass
serverInstances = DefaultInstances.fromArray(generator); Class<?> minecraftSuperClass = getFirstMinecraftSuperClass(serverHandler.getClass());
} ExistingGenerator generator = ExistingGenerator.fromObjectFields(serverHandler, minecraftSuperClass);
DefaultInstances serverInstances = null;
serverInstances.setNonNull(true);
serverInstances.setMaximumRecursion(1); // Maybe the proxy instance can help?
Object proxyInstance = getProxyServerHandler();
Object proxyObject = serverInstances.forEnhancer(ex).getDefault(serverClass);
// Use the existing server proxy when we create one
// Inject it now if (proxyInstance != null && proxyInstance != serverHandler) {
if (proxyObject != null) { serverInstances = DefaultInstances.fromArray(generator,
// This will be done by InjectedServerConnection instead ExistingGenerator.fromObjectArray(new Object[] { proxyInstance }));
//copyTo(serverHandler, proxyObject); } else {
serverInstances = DefaultInstances.fromArray(generator);
serverInjection.replaceServerHandler(serverHandler, proxyObject); }
serverHandlerRef.setValue(proxyObject);
return true; serverInstances.setNonNull(true);
} else { serverInstances.setMaximumRecursion(1);
return false;
} Object proxyObject = serverInstances.forEnhancer(ex).getDefault(serverClass);
}
// Inject it now
private Object getProxyServerHandler() { if (proxyObject != null) {
if (proxyServerField != null && !proxyServerField.equals(serverHandlerRef.getField())) { // Did we override a sendPacket method?
try { if (!foundSendPacket) {
return FieldUtils.readField(proxyServerField, serverHandler, true); throw new IllegalArgumentException("Unable to find a sendPacket method in " + serverClass);
} catch (Throwable e) { }
// Oh well
} serverInjection.replaceServerHandler(serverHandler, proxyObject);
} serverHandlerRef.setValue(proxyObject);
return true;
return null; } else {
} return false;
}
private Class<?> getFirstMinecraftSuperClass(Class<?> clazz) { }
if (clazz.getName().startsWith(MinecraftReflection.getMinecraftPackage()))
return clazz; /**
else if (clazz.equals(Object.class)) * Determine if the two methods are equal in terms of call semantics.
return clazz; * <p>
else * Two methods are equal if they have the same name, parameter types and return type.
return getFirstMinecraftSuperClass(clazz.getSuperclass()); * @param first - first method.
} * @param second - second method.
* @return TRUE if they are, FALSE otherwise.
@Override */
protected void cleanHook() { private boolean isCallableEqual(Method first, Method second) {
if (serverHandlerRef != null && serverHandlerRef.isCurrentSet()) { return first.getName().equals(second.getName()) &&
writer.copyTo(serverHandlerRef.getValue(), serverHandlerRef.getOldValue(), serverHandler.getClass()); first.getReturnType().equals(second.getReturnType()) &&
serverHandlerRef.revertValue(); Arrays.equals(first.getParameterTypes(), second.getParameterTypes());
}
try {
if (getNetHandler() != null) { private Object getProxyServerHandler() {
// Restore packet listener if (proxyServerField != null && !proxyServerField.equals(serverHandlerRef.getField())) {
try { try {
FieldUtils.writeField(netHandlerField, networkManager, serverHandlerRef.getOldValue(), true); return FieldUtils.readField(proxyServerField, serverHandler, true);
} catch (IllegalAccessException e) { } catch (Throwable e) {
// Oh well // Oh well
e.printStackTrace(); }
} }
}
} catch (IllegalAccessException e) { return null;
e.printStackTrace(); }
}
private Class<?> getFirstMinecraftSuperClass(Class<?> clazz) {
// Prevent the PlayerQuitEvent from being sent twice if (MinecraftReflection.isMinecraftClass(clazz))
if (hasDisconnected) { return clazz;
setDisconnect(serverHandlerRef.getValue(), true); else if (clazz.equals(Object.class))
} return clazz;
} else
return getFirstMinecraftSuperClass(clazz.getSuperclass());
serverInjection.revertServerHandler(serverHandler); }
}
@Override
@Override protected void cleanHook() {
public void handleDisconnect() { if (serverHandlerRef != null && serverHandlerRef.isCurrentSet()) {
hasDisconnected = true; writer.copyTo(serverHandlerRef.getValue(), serverHandlerRef.getOldValue(), serverHandler.getClass());
} serverHandlerRef.revertValue();
/** try {
* Set the disconnected field in a NetServerHandler. if (getNetHandler() != null) {
* @param handler - the NetServerHandler. // Restore packet listener
* @param value - the new value. try {
*/ FieldUtils.writeField(netHandlerField, networkManager, serverHandlerRef.getOldValue(), true);
private void setDisconnect(Object handler, boolean value) { } catch (IllegalAccessException e) {
// Set it // Oh well
try { e.printStackTrace();
// Load the field }
if (disconnectField == null) { }
disconnectField = FuzzyReflection.fromObject(handler).getFieldByName("disconnected.*"); } catch (IllegalAccessException e) {
} e.printStackTrace();
FieldUtils.writeField(disconnectField, handler, value); }
} catch (IllegalArgumentException e) { // Prevent the PlayerQuitEvent from being sent twice
// Assume it's the first ... if (hasDisconnected) {
if (disconnectField == null) { setDisconnect(serverHandlerRef.getValue(), true);
disconnectField = FuzzyReflection.fromObject(handler).getFieldByType("disconnected", boolean.class); }
reporter.reportWarning(this, "Unable to find 'disconnected' field. Assuming " + disconnectField); }
// Try again serverInjection.revertServerHandler(serverHandler);
if (disconnectField != null) { }
setDisconnect(handler, value);
return; @Override
} public void handleDisconnect() {
} hasDisconnected = true;
}
// This is really bad
reporter.reportDetailed(this, "Cannot find disconnected field. Is ProtocolLib up to date?", e); /**
* Set the disconnected field in a NetServerHandler.
} catch (IllegalAccessException e) { * @param handler - the NetServerHandler.
reporter.reportWarning(this, "Unable to update disconnected field. Player quit event may be sent twice."); * @param value - the new value.
} */
} private void setDisconnect(Object handler, boolean value) {
// Set it
@Override try {
public UnsupportedListener checkListener(PacketListener listener) { // Load the field
// We support everything if (disconnectField == null) {
return null; disconnectField = FuzzyReflection.fromObject(handler).getFieldByName("disconnected.*");
} }
FieldUtils.writeField(disconnectField, handler, value);
@Override
public boolean canInject(GamePhase phase) { } catch (IllegalArgumentException e) {
// Doesn't work when logging in // Assume it's the first ...
return phase == GamePhase.PLAYING; if (disconnectField == null) {
} disconnectField = FuzzyReflection.fromObject(handler).getFieldByType("disconnected", boolean.class);
reporter.reportWarning(this, Report.newBuilder(REPORT_ASSUMING_DISCONNECT_FIELD).messageParam(disconnectField));
@Override
public PlayerInjectHooks getHookType() { // Try again
return PlayerInjectHooks.NETWORK_SERVER_OBJECT; 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;
}
}

Datei anzeigen

@ -126,11 +126,18 @@ public interface PlayerInjectionHandler {
* @throws IllegalAccessException If the reflection machinery failed. * @throws IllegalAccessException If the reflection machinery failed.
* @throws InvocationTargetException If the underlying method caused an error. * @throws InvocationTargetException If the underlying method caused an error.
*/ */
public abstract void processPacket(Player player, Object mcPacket) public abstract void recieveClientPacket(Player player, Object mcPacket)
throws IllegalAccessException, InvocationTargetException; throws IllegalAccessException, InvocationTargetException;
/**
* Ensure that packet readers are informed of this player reference.
* @param player - the player to update.
*/
public abstract void updatePlayer(Player player);
/** /**
* Determine if the given listeners are valid. * Determine if the given listeners are valid.
* @param version - the current Minecraft version, or NULL if unknown.
* @param listeners - listeners to check. * @param listeners - listeners to check.
*/ */
public abstract void checkListener(Set<PacketListener> listeners); public abstract void checkListener(Set<PacketListener> listeners);
@ -139,6 +146,7 @@ public interface PlayerInjectionHandler {
* Determine if a listener is valid or not. * Determine if a listener is valid or not.
* <p> * <p>
* If not, a warning will be printed to the console. * If not, a warning will be printed to the console.
* @param version - the current Minecraft version, or NULL if unknown.
* @param listener - listener to check. * @param listener - listener to check.
*/ */
public abstract void checkListener(PacketListener listener); public abstract void checkListener(PacketListener listener);

Datei anzeigen

@ -14,6 +14,7 @@ import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase; import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PacketFilterManager; import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
@ -37,6 +38,7 @@ public class PlayerInjectorBuilder {
protected ListenerInvoker invoker; protected ListenerInvoker invoker;
protected Set<PacketListener> packetListeners; protected Set<PacketListener> packetListeners;
protected Server server; protected Server server;
protected MinecraftVersion version;
/** /**
* Set the class loader to use during class generation. * Set the class loader to use during class generation.
@ -107,6 +109,16 @@ public class PlayerInjectorBuilder {
return this; return this;
} }
/**
* Set the current Minecraft version.
* @param server - the current Minecraft version, or NULL if unknown.
* @return This builder, for chaining.
*/
public PlayerInjectorBuilder version(MinecraftVersion version) {
this.version = version;
return this;
}
/** /**
* Called before an object is created with this builder. * Called before an object is created with this builder.
*/ */
@ -140,6 +152,6 @@ public class PlayerInjectorBuilder {
return new ProxyPlayerInjectionHandler( return new ProxyPlayerInjectionHandler(
classLoader, reporter, injectionFilter, classLoader, reporter, injectionFilter,
invoker, packetListeners, server); invoker, packetListeners, server, version);
} }
} }

Datei anzeigen

@ -1,119 +1,87 @@
package com.comphenix.protocol.injector.server; package com.comphenix.protocol.injector.server;
import java.io.FilterInputStream; import java.io.InputStream;
import java.io.InputStream; import java.net.Socket;
import java.lang.reflect.Field; import java.net.SocketAddress;
import java.net.Socket; import org.bukkit.Server;
import java.net.SocketAddress; import org.bukkit.entity.Player;
import org.bukkit.Server;
import org.bukkit.entity.Player; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.ErrorReporter; public abstract class AbstractInputStreamLookup {
import com.comphenix.protocol.reflect.FieldAccessException; // Error reporter
import com.comphenix.protocol.reflect.FieldUtils; protected final ErrorReporter reporter;
import com.comphenix.protocol.reflect.FuzzyReflection;
// Reference to the server itself
public abstract class AbstractInputStreamLookup { protected final Server server;
// Used to access the inner input stream of a filtered input stream
private static Field filteredInputField; protected AbstractInputStreamLookup(ErrorReporter reporter, Server server) {
this.reporter = reporter;
// Error reporter this.server = server;
protected final ErrorReporter reporter; }
// Reference to the server itself /**
protected final Server server; * Inject the given server thread or dedicated connection.
* @param container - class that contains a ServerSocket field.
protected AbstractInputStreamLookup(ErrorReporter reporter, Server server) { */
this.reporter = reporter; public abstract void inject(Object container);
this.server = server;
} /**
* Invoked when the world has loaded.
/** */
* Retrieve the underlying input stream that is associated with a given filter input stream. public abstract void postWorldLoaded();
* @param filtered - the filter input stream.
* @return The underlying input stream that is being filtered. /**
* @throws FieldAccessException Unable to access input stream. * Retrieve the associated socket injector for a player.
*/ * @param input - the indentifying filtered input stream.
protected static InputStream getInputStream(FilterInputStream filtered) { * @return The socket injector we have associated with this player.
if (filteredInputField == null) */
filteredInputField = FuzzyReflection.fromClass(FilterInputStream.class, true). public abstract SocketInjector waitSocketInjector(InputStream input);
getFieldByType("in", InputStream.class);
/**
InputStream current = filtered; * Retrieve an injector by its socket.
* @param socket - the socket.
try { * @return The socket injector.
// Iterate until we find the real input stream */
while (current instanceof FilterInputStream) { public abstract SocketInjector waitSocketInjector(Socket socket);
current = (InputStream) FieldUtils.readField(filteredInputField, current, true);
} /**
return current; * Retrieve a injector by its address.
} catch (IllegalAccessException e) { * @param address - the address of the socket.
throw new FieldAccessException("Cannot access filtered input field.", e); * @return The socket injector, or NULL if not found.
} */
} public abstract SocketInjector waitSocketInjector(SocketAddress address);
/** /**
* Inject the given server thread or dedicated connection. * Attempt to get a socket injector without blocking the thread.
* @param container - class that contains a ServerSocket field. * @param address - the address to lookup.
*/ * @return The socket injector, or NULL if not found.
public abstract void inject(Object container); */
public abstract SocketInjector peekSocketInjector(SocketAddress address);
/**
* Invoked when the world has loaded. /**
*/ * Associate a given socket address to the provided socket injector.
public abstract void postWorldLoaded(); * @param address - the socket address to associate.
* @param injector - the injector.
/** */
* Retrieve the associated socket injector for a player. public abstract void setSocketInjector(SocketAddress address, SocketInjector injector);
* @param input - the indentifying filtered input stream.
* @return The socket injector we have associated with this player. /**
*/ * If a player can hold a reference to its parent injector, this method will update that reference.
public abstract SocketInjector waitSocketInjector(InputStream input); * @param previous - the previous injector.
* @param current - the new injector.
/** */
* Retrieve an injector by its socket. protected void onPreviousSocketOverwritten(SocketInjector previous, SocketInjector current) {
* @param socket - the socket. Player player = previous.getPlayer();
* @return The socket injector.
*/ // Default implementation
public abstract SocketInjector waitSocketInjector(Socket socket); if (player instanceof InjectorContainer) {
TemporaryPlayerFactory.setInjectorInPlayer(player, current);
/** }
* Retrieve a injector by its address. }
* @param address - the address of the socket.
* @return The socket injector, or NULL if not found. /**
*/ * Invoked when the injection should be undone.
public abstract SocketInjector waitSocketInjector(SocketAddress address); */
public abstract void cleanupAll();
/**
* Attempt to get a socket injector without blocking the thread.
* @param address - the address to lookup.
* @return The socket injector, or NULL if not found.
*/
public abstract SocketInjector peekSocketInjector(SocketAddress address);
/**
* Associate a given socket address to the provided socket injector.
* @param address - the socket address to associate.
* @param injector - the injector.
*/
public abstract void setSocketInjector(SocketAddress address, SocketInjector injector);
/**
* If a player can hold a reference to its parent injector, this method will update that reference.
* @param previous - the previous injector.
* @param current - the new injector.
*/
protected void onPreviousSocketOverwritten(SocketInjector previous, SocketInjector current) {
Player player = previous.getPlayer();
// Default implementation
if (player instanceof InjectorContainer) {
TemporaryPlayerFactory.setInjectorInPlayer(player, current);
}
}
/**
* Invoked when the injection should be undone.
*/
public abstract void cleanupAll();
} }

Datei anzeigen

@ -0,0 +1,103 @@
package com.comphenix.protocol.injector.server;
import java.lang.reflect.InvocationTargetException;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.bukkit.entity.Player;
public class BukkitSocketInjector implements SocketInjector {
/**
* Represents a single send packet command.
* @author Kristian
*/
static class SendPacketCommand {
private final Object packet;
private final boolean filtered;
public SendPacketCommand(Object packet, boolean filtered) {
this.packet = packet;
this.filtered = filtered;
}
public Object getPacket() {
return packet;
}
public boolean isFiltered() {
return filtered;
}
}
private Player player;
// Queue of server packets
private List<SendPacketCommand> syncronizedQueue = Collections.synchronizedList(new ArrayList<SendPacketCommand>());
/**
* Represents a temporary socket injector.
* @param temporaryPlayer -
*/
public BukkitSocketInjector(Player player) {
if (player == null)
throw new IllegalArgumentException("Player cannot be NULL.");
this.player = player;
}
@Override
public Socket getSocket() throws IllegalAccessException {
throw new UnsupportedOperationException("Cannot get socket from Bukkit player.");
}
@Override
public SocketAddress getAddress() throws IllegalAccessException {
return player.getAddress();
}
@Override
public void disconnect(String message) throws InvocationTargetException {
player.kickPlayer(message);
}
@Override
public void sendServerPacket(Object packet, boolean filtered)
throws InvocationTargetException {
SendPacketCommand command = new SendPacketCommand(packet, filtered);
// Queue until we can find something better
syncronizedQueue.add(command);
}
@Override
public Player getPlayer() {
return player;
}
@Override
public Player getUpdatedPlayer() {
return player;
}
@Override
public void transferState(SocketInjector delegate) {
// Transmit all queued packets to a different injector.
try {
synchronized(syncronizedQueue) {
for (SendPacketCommand command : syncronizedQueue) {
delegate.sendServerPacket(command.getPacket(), command.isFiltered());
}
syncronizedQueue.clear();
}
} catch (InvocationTargetException e) {
throw new RuntimeException("Unable to transmit packets to " + delegate + " from old injector.", e);
}
}
@Override
public void setUpdatedPlayer(Player updatedPlayer) {
this.player = updatedPlayer;
}
}

Datei anzeigen

@ -1,164 +1,191 @@
package com.comphenix.protocol.injector.server; package com.comphenix.protocol.injector.server;
import java.io.FilterInputStream; import java.io.FilterInputStream;
import java.io.InputStream; import java.io.InputStream;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.net.Socket; import java.net.Socket;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.bukkit.Server; import org.bukkit.Server;
import com.comphenix.protocol.concurrency.BlockingHashMap; import com.comphenix.protocol.concurrency.BlockingHashMap;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.FuzzyReflection;
import com.google.common.collect.MapMaker; import com.google.common.collect.MapMaker;
class InputStreamReflectLookup extends AbstractInputStreamLookup { class InputStreamReflectLookup extends AbstractInputStreamLookup {
// The default lookup timeout // Used to access the inner input stream of a filtered input stream
private static final long DEFAULT_TIMEOUT = 2000; // ms private static Field filteredInputField;
// Using weak keys and values ensures that we will not hold up garbage collection // The default lookup timeout
protected BlockingHashMap<SocketAddress, SocketInjector> addressLookup = new BlockingHashMap<SocketAddress, SocketInjector>(); private static final long DEFAULT_TIMEOUT = 2000; // ms
protected ConcurrentMap<InputStream, SocketAddress> inputLookup = new MapMaker().weakValues().makeMap();
// Using weak keys and values ensures that we will not hold up garbage collection
// The timeout protected BlockingHashMap<SocketAddress, SocketInjector> addressLookup = new BlockingHashMap<SocketAddress, SocketInjector>();
private final long injectorTimeout; protected ConcurrentMap<InputStream, SocketAddress> inputLookup = new MapMaker().weakValues().makeMap();
public InputStreamReflectLookup(ErrorReporter reporter, Server server) { // The timeout
this(reporter, server, DEFAULT_TIMEOUT); private final long injectorTimeout;
}
public InputStreamReflectLookup(ErrorReporter reporter, Server server) {
/** this(reporter, server, DEFAULT_TIMEOUT);
* Initialize a reflect lookup with a given default injector timeout. }
* <p>
* This timeout defines the maximum amount of time to wait until an injector has been discovered. /**
* @param reporter - the error reporter. * Initialize a reflect lookup with a given default injector timeout.
* @param server - the current Bukkit server. * <p>
* @param injectorTimeout - the injector timeout. * This timeout defines the maximum amount of time to wait until an injector has been discovered.
*/ * @param reporter - the error reporter.
public InputStreamReflectLookup(ErrorReporter reporter, Server server, long injectorTimeout) { * @param server - the current Bukkit server.
super(reporter, server); * @param injectorTimeout - the injector timeout.
this.injectorTimeout = injectorTimeout; */
} public InputStreamReflectLookup(ErrorReporter reporter, Server server, long injectorTimeout) {
super(reporter, server);
@Override this.injectorTimeout = injectorTimeout;
public void inject(Object container) { }
// Do nothing
} @Override
public void inject(Object container) {
@Override // Do nothing
public void postWorldLoaded() { }
// Nothing again
} @Override
public void postWorldLoaded() {
@Override // Nothing again
public SocketInjector peekSocketInjector(SocketAddress address) { }
try {
return addressLookup.get(address, 0, TimeUnit.MILLISECONDS); @Override
} catch (InterruptedException e) { public SocketInjector peekSocketInjector(SocketAddress address) {
// Whatever try {
return null; return addressLookup.get(address, 0, TimeUnit.MILLISECONDS);
} } catch (InterruptedException e) {
} // Whatever
return null;
@Override }
public SocketInjector waitSocketInjector(SocketAddress address) { }
try {
// Note that we actually SWALLOW interrupts here - this is because Minecraft uses interrupts to @Override
// periodically wake up waiting readers and writers. We have to wait for the dedicated server thread public SocketInjector waitSocketInjector(SocketAddress address) {
// to catch up, so we'll swallow these interrupts. try {
// // Note that we actually SWALLOW interrupts here - this is because Minecraft uses interrupts to
// TODO: Consider if we should raise the thread priority of the dedicated server listener thread. // periodically wake up waiting readers and writers. We have to wait for the dedicated server thread
return addressLookup.get(address, injectorTimeout, TimeUnit.MILLISECONDS, true); // to catch up, so we'll swallow these interrupts.
} catch (InterruptedException e) { //
// This cannot be! // TODO: Consider if we should raise the thread priority of the dedicated server listener thread.
throw new IllegalStateException("Impossible exception occured!", e); return addressLookup.get(address, injectorTimeout, TimeUnit.MILLISECONDS, true);
} } catch (InterruptedException e) {
} // This cannot be!
throw new IllegalStateException("Impossible exception occured!", e);
@Override }
public SocketInjector waitSocketInjector(Socket socket) { }
return waitSocketInjector(socket.getRemoteSocketAddress());
} @Override
public SocketInjector waitSocketInjector(Socket socket) {
@Override return waitSocketInjector(socket.getRemoteSocketAddress());
public SocketInjector waitSocketInjector(InputStream input) { }
try {
SocketAddress address = waitSocketAddress(input); @Override
public SocketInjector waitSocketInjector(InputStream input) {
// Guard against NPE try {
if (address != null) SocketAddress address = waitSocketAddress(input);
return waitSocketInjector(address);
else // Guard against NPE
return null; if (address != null)
} catch (IllegalAccessException e) { return waitSocketInjector(address);
throw new FieldAccessException("Cannot find or access socket field for " + input, e); else
} return null;
} } catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot find or access socket field for " + input, e);
/** }
* Use reflection to get the underlying socket address from an input stream. }
* @param stream - the socket stream to lookup.
* @return The underlying socket address, or NULL if not found. /**
* @throws IllegalAccessException Unable to access socket field. * Use reflection to get the underlying socket address from an input stream.
*/ * @param stream - the socket stream to lookup.
private SocketAddress waitSocketAddress(InputStream stream) throws IllegalAccessException { * @return The underlying socket address, or NULL if not found.
// Extra check, just in case * @throws IllegalAccessException Unable to access socket field.
if (stream instanceof FilterInputStream) */
return waitSocketAddress(getInputStream((FilterInputStream) stream)); private SocketAddress waitSocketAddress(InputStream stream) throws IllegalAccessException {
// Extra check, just in case
SocketAddress result = inputLookup.get(stream); if (stream instanceof FilterInputStream)
return waitSocketAddress(getInputStream((FilterInputStream) stream));
if (result == null) {
Socket socket = lookupSocket(stream); SocketAddress result = inputLookup.get(stream);
// Save it if (result == null) {
result = socket.getRemoteSocketAddress(); Socket socket = lookupSocket(stream);
inputLookup.put(stream, result);
} // Save it
return result; result = socket.getRemoteSocketAddress();
} inputLookup.put(stream, result);
}
@Override return result;
public void setSocketInjector(SocketAddress address, SocketInjector injector) { }
if (address == null)
throw new IllegalArgumentException("address cannot be NULL"); /**
if (injector == null) * Retrieve the underlying input stream that is associated with a given filter input stream.
throw new IllegalArgumentException("injector cannot be NULL."); * @param filtered - the filter input stream.
* @return The underlying input stream that is being filtered.
SocketInjector previous = addressLookup.put(address, injector); * @throws FieldAccessException Unable to access input stream.
*/
// Any previous temporary players will also be associated protected static InputStream getInputStream(FilterInputStream filtered) {
if (previous != null) { if (filteredInputField == null)
// Update the reference to any previous injector filteredInputField = FuzzyReflection.fromClass(FilterInputStream.class, true).
onPreviousSocketOverwritten(previous, injector); getFieldByType("in", InputStream.class);
}
} InputStream current = filtered;
@Override try {
public void cleanupAll() { // Iterate until we find the real input stream
// Do nothing while (current instanceof FilterInputStream) {
} current = (InputStream) FieldUtils.readField(filteredInputField, current, true);
}
/** return current;
* Lookup the underlying socket of a stream through reflection. } catch (IllegalAccessException e) {
* @param stream - the socket stream. throw new FieldAccessException("Cannot access filtered input field.", e);
* @return The underlying socket. }
* @throws IllegalAccessException If reflection failed. }
*/
private static Socket lookupSocket(InputStream stream) throws IllegalAccessException { @Override
if (stream instanceof FilterInputStream) { public void setSocketInjector(SocketAddress address, SocketInjector injector) {
return lookupSocket(getInputStream((FilterInputStream) stream)); if (address == null)
} else { throw new IllegalArgumentException("address cannot be NULL");
// Just do it if (injector == null)
Field socketField = FuzzyReflection.fromObject(stream, true). throw new IllegalArgumentException("injector cannot be NULL.");
getFieldByType("socket", Socket.class);
SocketInjector previous = addressLookup.put(address, injector);
return (Socket) FieldUtils.readField(socketField, stream, true);
} // Any previous temporary players will also be associated
} if (previous != null) {
} // Update the reference to any previous injector
onPreviousSocketOverwritten(previous, injector);
}
}
@Override
public void cleanupAll() {
// Do nothing
}
/**
* Lookup the underlying socket of a stream through reflection.
* @param stream - the socket stream.
* @return The underlying socket.
* @throws IllegalAccessException If reflection failed.
*/
private static Socket lookupSocket(InputStream stream) throws IllegalAccessException {
if (stream instanceof FilterInputStream) {
return lookupSocket(getInputStream((FilterInputStream) stream));
} else {
// Just do it
Field socketField = FuzzyReflection.fromObject(stream, true).
getFieldByType("socket", Socket.class);
return (Socket) FieldUtils.readField(socketField, stream, true);
}
}
}

Datei anzeigen

@ -58,4 +58,10 @@ public interface SocketInjector {
* @param delegate - the new injector. * @param delegate - the new injector.
*/ */
public abstract void transferState(SocketInjector delegate); public abstract void transferState(SocketInjector delegate);
/**
* Set the real Bukkit player that we will use.
* @param updatedPlayer - the real Bukkit player.
*/
public abstract void setUpdatedPlayer(Player updatedPlayer);
} }

Datei anzeigen

@ -1,195 +1,197 @@
/* /*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland * Copyright (C) 2012 Kristian S. Stangeland
* *
* This program is free software; you can redistribute it and/or modify it under the terms of the * 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 * GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version. * 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; * 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. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * 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; * 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 * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol.injector.server; package com.comphenix.protocol.injector.server;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import net.sf.cglib.proxy.Callback; import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter; import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy; import net.sf.cglib.proxy.MethodProxy;
import net.sf.cglib.proxy.NoOp; import net.sf.cglib.proxy.NoOp;
import org.bukkit.Server; import org.bukkit.Server;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import com.comphenix.protocol.injector.PacketConstructor; import com.comphenix.protocol.injector.PacketConstructor;
import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FieldAccessException;
/** /**
* Create fake player instances that represents pre-authenticated clients. * Create fake player instances that represents pre-authenticated clients.
*/ */
public class TemporaryPlayerFactory { public class TemporaryPlayerFactory {
// Helpful constructors // Helpful constructors
private final PacketConstructor chatPacket; private final PacketConstructor chatPacket;
// Prevent too many class creations // Prevent too many class creations
private static CallbackFilter callbackFilter; private static CallbackFilter callbackFilter;
public TemporaryPlayerFactory() { public TemporaryPlayerFactory() {
chatPacket = PacketConstructor.DEFAULT.withPacket(3, new Object[] { "DEMO" }); chatPacket = PacketConstructor.DEFAULT.withPacket(3, new Object[] { "DEMO" });
} }
/** /**
* Retrieve the injector from a given player if it contains one. * Retrieve the injector from a given player if it contains one.
* @param player - the player that may contain a reference to a player injector. * @param player - the player that may contain a reference to a player injector.
* @return The referenced player injector, or NULL if none can be found. * @return The referenced player injector, or NULL if none can be found.
*/ */
public static SocketInjector getInjectorFromPlayer(Player player) { public static SocketInjector getInjectorFromPlayer(Player player) {
if (player instanceof InjectorContainer) { if (player instanceof InjectorContainer) {
return ((InjectorContainer) player).getInjector(); return ((InjectorContainer) player).getInjector();
} }
return null; return null;
} }
/** /**
* Set the player injector, if possible. * Set the player injector, if possible.
* @param player - the player to update. * @param player - the player to update.
* @param injector - the injector to store. * @param injector - the injector to store.
*/ */
public static void setInjectorInPlayer(Player player, SocketInjector injector) { public static void setInjectorInPlayer(Player player, SocketInjector injector) {
((InjectorContainer) player).setInjector(injector); ((InjectorContainer) player).setInjector(injector);
} }
/** /**
* Construct a temporary player that supports a subset of every player command. * Construct a temporary player that supports a subset of every player command.
* <p> * <p>
* Supported methods include: * Supported methods include:
* <ul> * <ul>
* <li>getPlayer()</li> * <li>getPlayer()</li>
* <li>getAddress()</li> * <li>getAddress()</li>
* <li>getServer()</li> * <li>getServer()</li>
* <li>chat(String)</li> * <li>chat(String)</li>
* <li>sendMessage(String)</li> * <li>sendMessage(String)</li>
* <li>sendMessage(String[])</li> * <li>sendMessage(String[])</li>
* <li>kickPlayer(String)</li> * <li>kickPlayer(String)</li>
* </ul> * </ul>
* <p> * <p>
* Note that a temporary player has not yet been assigned a name, and thus cannot be * Note that a temporary player has not yet been assigned a name, and thus cannot be
* uniquely identified. Use the address instead. * uniquely identified. Use the address instead.
* @param injector - the player injector used. * @param injector - the player injector used.
* @param server - the current server. * @param server - the current server.
* @return A temporary player instance. * @return A temporary player instance.
*/ */
public Player createTemporaryPlayer(final Server server) { public Player createTemporaryPlayer(final Server server) {
// Default implementation // Default implementation
Callback implementation = new MethodInterceptor() { Callback implementation = new MethodInterceptor() {
@Override @Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
String methodName = method.getName(); String methodName = method.getName();
SocketInjector injector = ((InjectorContainer) obj).getInjector(); SocketInjector injector = ((InjectorContainer) obj).getInjector();
if (injector == null) if (injector == null)
throw new IllegalStateException("Unable to find injector."); throw new IllegalStateException("Unable to find injector.");
// Use the socket to get the address // Use the socket to get the address
if (methodName.equalsIgnoreCase("getName")) if (methodName.equalsIgnoreCase("isOnline"))
return "UNKNOWN[" + injector.getSocket().getRemoteSocketAddress() + "]"; return injector.getSocket() != null && injector.getSocket().isConnected();
if (methodName.equalsIgnoreCase("getPlayer")) if (methodName.equalsIgnoreCase("getName"))
return injector.getUpdatedPlayer(); return "UNKNOWN[" + injector.getSocket().getRemoteSocketAddress() + "]";
if (methodName.equalsIgnoreCase("getAddress")) if (methodName.equalsIgnoreCase("getPlayer"))
return injector.getAddress(); return injector.getUpdatedPlayer();
if (methodName.equalsIgnoreCase("getServer")) if (methodName.equalsIgnoreCase("getAddress"))
return server; return injector.getAddress();
if (methodName.equalsIgnoreCase("getServer"))
try { return server;
// Handle send message methods
if (methodName.equalsIgnoreCase("chat") || methodName.equalsIgnoreCase("sendMessage")) { try {
Object argument = args[0]; // Handle send message methods
if (methodName.equalsIgnoreCase("chat") || methodName.equalsIgnoreCase("sendMessage")) {
// Dynamic overloading Object argument = args[0];
if (argument instanceof String) {
return sendMessage(injector, (String) argument); // Dynamic overloading
} else if (argument instanceof String[]) { if (argument instanceof String) {
for (String message : (String[]) argument) { return sendMessage(injector, (String) argument);
sendMessage(injector, message); } else if (argument instanceof String[]) {
} for (String message : (String[]) argument) {
return null; sendMessage(injector, message);
} }
} return null;
} catch (InvocationTargetException e) { }
throw e.getCause(); }
} } catch (InvocationTargetException e) {
throw e.getCause();
// Also, handle kicking }
if (methodName.equalsIgnoreCase("kickPlayer")) {
injector.disconnect((String) args[0]); // Also, handle kicking
return null; if (methodName.equalsIgnoreCase("kickPlayer")) {
} injector.disconnect((String) args[0]);
return null;
// Ignore all other methods }
throw new UnsupportedOperationException(
"The method " + method.getName() + " is not supported for temporary players."); // Ignore all other methods
} throw new UnsupportedOperationException(
}; "The method " + method.getName() + " is not supported for temporary players.");
}
// Shared callback filter };
if (callbackFilter == null) {
callbackFilter = new CallbackFilter() { // Shared callback filter
@Override if (callbackFilter == null) {
public int accept(Method method) { callbackFilter = new CallbackFilter() {
// Do not override the object method or the superclass methods @Override
if (method.getDeclaringClass().equals(Object.class) || public int accept(Method method) {
method.getDeclaringClass().equals(InjectorContainer.class)) // Do not override the object method or the superclass methods
return 0; if (method.getDeclaringClass().equals(Object.class) ||
else method.getDeclaringClass().equals(InjectorContainer.class))
return 1; return 0;
} else
}; return 1;
} }
};
// CGLib is amazing }
Enhancer ex = new Enhancer();
ex.setSuperclass(InjectorContainer.class); // CGLib is amazing
ex.setInterfaces(new Class[] { Player.class }); Enhancer ex = new Enhancer();
ex.setCallbacks(new Callback[] { NoOp.INSTANCE, implementation }); ex.setSuperclass(InjectorContainer.class);
ex.setCallbackFilter(callbackFilter); ex.setInterfaces(new Class[] { Player.class });
ex.setCallbacks(new Callback[] { NoOp.INSTANCE, implementation });
return (Player) ex.create(); ex.setCallbackFilter(callbackFilter);
}
return (Player) ex.create();
/** }
* Construct a temporary player with the given associated socket injector.
* @param server - the parent server. /**
* @param injector - the referenced socket injector. * Construct a temporary player with the given associated socket injector.
* @return The temporary player. * @param server - the parent server.
*/ * @param injector - the referenced socket injector.
public Player createTemporaryPlayer(Server server, SocketInjector injector) { * @return The temporary player.
Player temporary = createTemporaryPlayer(server); */
public Player createTemporaryPlayer(Server server, SocketInjector injector) {
((InjectorContainer) temporary).setInjector(injector); Player temporary = createTemporaryPlayer(server);
return temporary;
} ((InjectorContainer) temporary).setInjector(injector);
return temporary;
/** }
* Send a message to the given client.
* @param injector - the injector representing the client. /**
* @param message - a message. * Send a message to the given client.
* @return Always NULL. * @param injector - the injector representing the client.
* @throws InvocationTargetException If the message couldn't be sent. * @param message - a message.
* @throws FieldAccessException If we were unable to construct the message packet. * @return Always NULL.
*/ * @throws InvocationTargetException If the message couldn't be sent.
private Object sendMessage(SocketInjector injector, String message) throws InvocationTargetException, FieldAccessException { * @throws FieldAccessException If we were unable to construct the message packet.
injector.sendServerPacket(chatPacket.createPacket(message).getHandle(), false); */
return null; private Object sendMessage(SocketInjector injector, String message) throws InvocationTargetException, FieldAccessException {
} injector.sendServerPacket(chatPacket.createPacket(message).getHandle(), false);
} return null;
}
}

Datei anzeigen

@ -74,7 +74,7 @@ class DummyPlayerHandler implements PlayerInjectionHandler {
} }
@Override @Override
public void processPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException { public void recieveClientPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException {
injector.processPacket(player, mcPacket); injector.processPacket(player, mcPacket);
} }
@ -119,4 +119,9 @@ class DummyPlayerHandler implements PlayerInjectionHandler {
public void postWorldLoaded() { public void postWorldLoaded() {
// Do nothing // Do nothing
} }
@Override
public void updatePlayer(Player player) {
// Do nothing
}
} }

Datei anzeigen

@ -22,7 +22,9 @@ import net.sf.cglib.proxy.NoOp;
import com.comphenix.protocol.Packets; import com.comphenix.protocol.Packets;
import com.comphenix.protocol.concurrency.IntegerSet; import com.comphenix.protocol.concurrency.IntegerSet;
import com.comphenix.protocol.error.DelegatedErrorReporter;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.injector.ListenerInvoker;
@ -47,7 +49,7 @@ public class SpigotPacketInjector implements SpigotPacketListener {
private static volatile boolean classChecked; private static volatile boolean classChecked;
// Retrieve the entity player from a PlayerConnection // Retrieve the entity player from a PlayerConnection
private static Field playerConnectionPlayer; private static volatile Field playerConnectionPlayer;
// Packets that are not to be processed by the filters // Packets that are not to be processed by the filters
private Set<Object> ignoredPackets = Collections.newSetFromMap(new MapMaker().weakKeys().<Object, Boolean>makeMap()); private Set<Object> ignoredPackets = Collections.newSetFromMap(new MapMaker().weakKeys().<Object, Boolean>makeMap());
@ -275,7 +277,8 @@ public class SpigotPacketInjector implements SpigotPacketListener {
if (dummyInjector == null) { if (dummyInjector == null) {
// Inject the network manager // Inject the network manager
try { try {
NetworkObjectInjector created = new NetworkObjectInjector(classLoader, reporter, null, invoker, null); NetworkObjectInjector created = new NetworkObjectInjector(
classLoader, filterImpossibleWarnings(reporter), null, invoker, null);
if (MinecraftReflection.isLoginHandler(connection)) { if (MinecraftReflection.isLoginHandler(connection)) {
created.initialize(connection); created.initialize(connection);
@ -303,6 +306,23 @@ public class SpigotPacketInjector implements SpigotPacketListener {
return dummyInjector; return dummyInjector;
} }
/**
* Return a delegated error reporter that ignores certain warnings that are irrelevant on Spigot.
* @param reporter - error reporter to delegate.
* @return The filtered error reporter.
*/
private ErrorReporter filterImpossibleWarnings(ErrorReporter reporter) {
return new DelegatedErrorReporter(reporter) {
@Override
protected Report filterReport(Object sender, Report report, boolean detailed) {
// This doesn't matter - ignore it
if (report.getType() == NetworkObjectInjector.REPORT_DETECTED_CUSTOM_SERVER_HANDLER)
return null;
return report;
}
};
}
/** /**
* Save a given player injector for later. * Save a given player injector for later.
* @param networkManager - the associated network manager. * @param networkManager - the associated network manager.
@ -400,7 +420,8 @@ public class SpigotPacketInjector implements SpigotPacketListener {
*/ */
void injectPlayer(Player player) { void injectPlayer(Player player) {
try { try {
NetworkObjectInjector dummy = new NetworkObjectInjector(classLoader, reporter, player, invoker, null); NetworkObjectInjector dummy = new NetworkObjectInjector(
classLoader, filterImpossibleWarnings(reporter), player, invoker, null);
dummy.initializePlayer(player); dummy.initializePlayer(player);
// Save this player for the network manager // Save this player for the network manager

Datei anzeigen

@ -1,360 +1,365 @@
/* /*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland * Copyright (C) 2012 Kristian S. Stangeland
* *
* This program is free software; you can redistribute it and/or modify it under the terms of the * 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 * GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version. * 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; * 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. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * 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; * 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 * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol.reflect.compiler; package com.comphenix.protocol.reflect.compiler;
import java.lang.management.ManagementFactory; import java.lang.management.ManagementFactory;
import java.lang.management.MemoryPoolMXBean; import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryUsage; import java.lang.management.MemoryUsage;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.compiler.StructureCompiler.StructureKey; import com.comphenix.protocol.reflect.compiler.StructureCompiler.StructureKey;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.common.util.concurrent.ThreadFactoryBuilder;
/** /**
* Compiles structure modifiers on a background thread. * Compiles structure modifiers on a background thread.
* <p> * <p>
* This is necessary as we cannot block the main thread. * This is necessary as we cannot block the main thread.
* *
* @author Kristian * @author Kristian
*/ */
public class BackgroundCompiler { public class BackgroundCompiler {
public static final ReportType REPORT_CANNOT_COMPILE_STRUCTURE_MODIFIER = new ReportType("Cannot compile structure. Disabing compiler.");
/** public static final ReportType REPORT_CANNOT_SCHEDULE_COMPILATION = new ReportType("Unable to schedule compilation task.");
* The default format for the name of new worker threads.
*/ /**
public static final String THREAD_FORMAT = "ProtocolLib-StructureCompiler %s"; * The default format for the name of new worker threads.
*/
// How long to wait for a shutdown public static final String THREAD_FORMAT = "ProtocolLib-StructureCompiler %s";
public static final int SHUTDOWN_DELAY_MS = 2000;
// How long to wait for a shutdown
/** public static final int SHUTDOWN_DELAY_MS = 2000;
* The default fraction of perm gen space after which the background compiler will be disabled.
*/ /**
public static final double DEFAULT_DISABLE_AT_PERM_GEN = 0.65; * The default fraction of perm gen space after which the background compiler will be disabled.
*/
// The single background compiler we're using public static final double DEFAULT_DISABLE_AT_PERM_GEN = 0.65;
private static BackgroundCompiler backgroundCompiler;
// The single background compiler we're using
// Classes we're currently compiling private static BackgroundCompiler backgroundCompiler;
private Map<StructureKey, List<CompileListener<?>>> listeners = Maps.newHashMap();
private Object listenerLock = new Object(); // Classes we're currently compiling
private Map<StructureKey, List<CompileListener<?>>> listeners = Maps.newHashMap();
private StructureCompiler compiler; private Object listenerLock = new Object();
private boolean enabled;
private boolean shuttingDown; private StructureCompiler compiler;
private boolean enabled;
private ExecutorService executor; private boolean shuttingDown;
private ErrorReporter reporter;
private ExecutorService executor;
private double disablePermGenFraction = DEFAULT_DISABLE_AT_PERM_GEN; private ErrorReporter reporter;
/** private double disablePermGenFraction = DEFAULT_DISABLE_AT_PERM_GEN;
* Retrieves the current background compiler.
* @return Current background compiler. /**
*/ * Retrieves the current background compiler.
public static BackgroundCompiler getInstance() { * @return Current background compiler.
return backgroundCompiler; */
} public static BackgroundCompiler getInstance() {
return backgroundCompiler;
/** }
* Sets the single background compiler we're using.
* @param backgroundCompiler - current background compiler, or NULL if the library is not loaded. /**
*/ * Sets the single background compiler we're using.
public static void setInstance(BackgroundCompiler backgroundCompiler) { * @param backgroundCompiler - current background compiler, or NULL if the library is not loaded.
BackgroundCompiler.backgroundCompiler = backgroundCompiler; */
} public static void setInstance(BackgroundCompiler backgroundCompiler) {
BackgroundCompiler.backgroundCompiler = backgroundCompiler;
/** }
* Initialize a background compiler.
* <p> /**
* Uses the default {@link #THREAD_FORMAT} to name worker threads. * Initialize a background compiler.
* @param loader - class loader from Bukkit. * <p>
* @param reporter - current error reporter. * Uses the default {@link #THREAD_FORMAT} to name worker threads.
*/ * @param loader - class loader from Bukkit.
public BackgroundCompiler(ClassLoader loader, ErrorReporter reporter) { * @param reporter - current error reporter.
ThreadFactory factory = new ThreadFactoryBuilder(). */
setDaemon(true). public BackgroundCompiler(ClassLoader loader, ErrorReporter reporter) {
setNameFormat(THREAD_FORMAT). ThreadFactory factory = new ThreadFactoryBuilder().
build(); setDaemon(true).
initializeCompiler(loader, reporter, Executors.newSingleThreadExecutor(factory)); setNameFormat(THREAD_FORMAT).
} build();
initializeCompiler(loader, reporter, Executors.newSingleThreadExecutor(factory));
/** }
* Initialize a background compiler utilizing the given thread pool.
* @param loader - class loader from Bukkit. /**
* @param reporter - current error reporter. * Initialize a background compiler utilizing the given thread pool.
* @param executor - thread pool we'll use. * @param loader - class loader from Bukkit.
*/ * @param reporter - current error reporter.
public BackgroundCompiler(ClassLoader loader, ErrorReporter reporter, ExecutorService executor) { * @param executor - thread pool we'll use.
initializeCompiler(loader, reporter, executor); */
} public BackgroundCompiler(ClassLoader loader, ErrorReporter reporter, ExecutorService executor) {
initializeCompiler(loader, reporter, executor);
// Avoid "Constructor call must be the first statement". }
private void initializeCompiler(ClassLoader loader, @Nullable ErrorReporter reporter, ExecutorService executor) {
if (loader == null) // Avoid "Constructor call must be the first statement".
throw new IllegalArgumentException("loader cannot be NULL"); private void initializeCompiler(ClassLoader loader, ErrorReporter reporter, ExecutorService executor) {
if (executor == null) if (loader == null)
throw new IllegalArgumentException("executor cannot be NULL"); throw new IllegalArgumentException("loader cannot be NULL");
if (executor == null)
this.compiler = new StructureCompiler(loader); throw new IllegalArgumentException("executor cannot be NULL");
this.reporter = reporter; if (reporter == null)
this.executor = executor; throw new IllegalArgumentException("reporter cannot be NULL.");
this.enabled = true;
} this.compiler = new StructureCompiler(loader);
this.reporter = reporter;
/** this.executor = executor;
* Ensure that the indirectly given structure modifier is eventually compiled. this.enabled = true;
* @param cache - store of structure modifiers. }
* @param key - key of the structure modifier to compile.
*/ /**
@SuppressWarnings("rawtypes") * Ensure that the indirectly given structure modifier is eventually compiled.
public void scheduleCompilation(final Map<Class, StructureModifier> cache, final Class key) { * @param cache - store of structure modifiers.
* @param key - key of the structure modifier to compile.
@SuppressWarnings("unchecked") */
final StructureModifier<Object> uncompiled = cache.get(key); @SuppressWarnings("rawtypes")
public void scheduleCompilation(final Map<Class, StructureModifier> cache, final Class key) {
if (uncompiled != null) {
scheduleCompilation(uncompiled, new CompileListener<Object>() { @SuppressWarnings("unchecked")
@Override final StructureModifier<Object> uncompiled = cache.get(key);
public void onCompiled(StructureModifier<Object> compiledModifier) {
// Update cache if (uncompiled != null) {
cache.put(key, compiledModifier); scheduleCompilation(uncompiled, new CompileListener<Object>() {
} @Override
}); public void onCompiled(StructureModifier<Object> compiledModifier) {
} // Update cache
} cache.put(key, compiledModifier);
}
/** });
* Ensure that the given structure modifier is eventually compiled. }
* @param uncompiled - structure modifier to compile. }
* @param listener - listener responsible for responding to the compilation.
*/ /**
@SuppressWarnings({"rawtypes", "unchecked"}) * Ensure that the given structure modifier is eventually compiled.
public <TKey> void scheduleCompilation(final StructureModifier<TKey> uncompiled, final CompileListener<TKey> listener) { * @param uncompiled - structure modifier to compile.
// Only schedule if we're enabled * @param listener - listener responsible for responding to the compilation.
if (enabled && !shuttingDown) { */
// Check perm gen @SuppressWarnings({"rawtypes", "unchecked"})
if (getPermGenUsage() > disablePermGenFraction) public <TKey> void scheduleCompilation(final StructureModifier<TKey> uncompiled, final CompileListener<TKey> listener) {
return; // Only schedule if we're enabled
if (enabled && !shuttingDown) {
// Don't try to schedule anything // Check perm gen
if (executor == null || executor.isShutdown()) if (getPermGenUsage() > disablePermGenFraction)
return; return;
// Use to look up structure modifiers // Don't try to schedule anything
final StructureKey key = new StructureKey(uncompiled); if (executor == null || executor.isShutdown())
return;
// Allow others to listen in too
synchronized (listenerLock) { // Use to look up structure modifiers
List list = listeners.get(key); final StructureKey key = new StructureKey(uncompiled);
if (!listeners.containsKey(key)) { // Allow others to listen in too
listeners.put(key, (List) Lists.newArrayList(listener)); synchronized (listenerLock) {
} else { List list = listeners.get(key);
// We're currently compiling
list.add(listener); if (!listeners.containsKey(key)) {
return; listeners.put(key, (List) Lists.newArrayList(listener));
} } else {
} // We're currently compiling
list.add(listener);
// Create the worker that will compile our modifier return;
Callable<?> worker = new Callable<Object>() { }
@Override }
public Object call() throws Exception {
StructureModifier<TKey> modifier = uncompiled; // Create the worker that will compile our modifier
List list = null; Callable<?> worker = new Callable<Object>() {
@Override
// Do our compilation public Object call() throws Exception {
try { StructureModifier<TKey> modifier = uncompiled;
modifier = compiler.compile(modifier); List list = null;
synchronized (listenerLock) { // Do our compilation
list = listeners.get(key); try {
} modifier = compiler.compile(modifier);
// Only execute the listeners if there is a list synchronized (listenerLock) {
if (list != null) { list = listeners.get(key);
for (Object compileListener : list) {
((CompileListener<TKey>) compileListener).onCompiled(modifier); // Prevent ConcurrentModificationExceptions
} if (list != null) {
list = Lists.newArrayList(list);
// Remove it when we're done }
synchronized (listenerLock) { }
list = listeners.remove(key);
} // Only execute the listeners if there is a list
} if (list != null) {
for (Object compileListener : list) {
} catch (Throwable e) { ((CompileListener<TKey>) compileListener).onCompiled(modifier);
// Disable future compilations! }
setEnabled(false);
// Remove it when we're done
// Inform about this error as best as we can synchronized (listenerLock) {
if (reporter != null) { list = listeners.remove(key);
reporter.reportDetailed(BackgroundCompiler.this, }
"Cannot compile structure. Disabing compiler.", e, uncompiled); }
} else {
System.err.println("Exception occured in structure compiler: "); } catch (Throwable e) {
e.printStackTrace(); // Disable future compilations!
} setEnabled(false);
}
// Inform about this error as best as we can
// We'll also return the new structure modifier reporter.reportDetailed(BackgroundCompiler.this,
return modifier; Report.newBuilder(REPORT_CANNOT_COMPILE_STRUCTURE_MODIFIER).callerParam(uncompiled).error(e)
);
} }
};
// We'll also return the new structure modifier
try { return modifier;
// Lookup the previous class name on the main thread.
// This is necessary as the Bukkit class loaders are not thread safe }
if (compiler.lookupClassLoader(uncompiled)) { };
try {
worker.call(); try {
} catch (Exception e) { // Lookup the previous class name on the main thread.
// Impossible! // This is necessary as the Bukkit class loaders are not thread safe
e.printStackTrace(); if (compiler.lookupClassLoader(uncompiled)) {
} try {
worker.call();
} else { } catch (Exception e) {
// Impossible!
// Perform the compilation on a seperate thread e.printStackTrace();
executor.submit(worker); }
}
} else {
} catch (RejectedExecutionException e) {
// Occures when the underlying queue is overflowing. Since the compilation // Perform the compilation on a seperate thread
// is only an optmization and not really essential we'll just log this failure executor.submit(worker);
// and move on. }
reporter.reportWarning(this, "Unable to schedule compilation task.", e);
} } catch (RejectedExecutionException e) {
} // Occures when the underlying queue is overflowing. Since the compilation
} // is only an optmization and not really essential we'll just log this failure
// and move on.
/** reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_SCHEDULE_COMPILATION).error(e));
* Add a compile listener if we are still waiting for the structure modifier to be compiled. }
* @param uncompiled - the structure modifier that may get compiled. }
* @param listener - the listener to invoke in that case. }
*/
@SuppressWarnings("unchecked") /**
public <TKey> void addListener(final StructureModifier<TKey> uncompiled, final CompileListener<TKey> listener) { * Add a compile listener if we are still waiting for the structure modifier to be compiled.
synchronized (listenerLock) { * @param uncompiled - the structure modifier that may get compiled.
StructureKey key = new StructureKey(uncompiled); * @param listener - the listener to invoke in that case.
*/
@SuppressWarnings("rawtypes") @SuppressWarnings("unchecked")
List list = listeners.get(key); public <TKey> void addListener(final StructureModifier<TKey> uncompiled, final CompileListener<TKey> listener) {
synchronized (listenerLock) {
if (list != null) { StructureKey key = new StructureKey(uncompiled);
list.add(listener);
} @SuppressWarnings("rawtypes")
} List list = listeners.get(key);
}
if (list != null) {
/** list.add(listener);
* Retrieve the current usage of the Perm Gen space in percentage. }
* @return Usage of the perm gen space. }
*/ }
private double getPermGenUsage() {
for (MemoryPoolMXBean item : ManagementFactory.getMemoryPoolMXBeans()) { /**
if (item.getName().contains("Perm Gen")) { * Retrieve the current usage of the Perm Gen space in percentage.
MemoryUsage usage = item.getUsage(); * @return Usage of the perm gen space.
return usage.getUsed() / (double) usage.getCommitted(); */
} private double getPermGenUsage() {
} for (MemoryPoolMXBean item : ManagementFactory.getMemoryPoolMXBeans()) {
if (item.getName().contains("Perm Gen")) {
// Unknown MemoryUsage usage = item.getUsage();
return 0; return usage.getUsed() / (double) usage.getCommitted();
} }
}
/**
* Clean up after ourselves using the default timeout. // Unknown
*/ return 0;
public void shutdownAll() { }
shutdownAll(SHUTDOWN_DELAY_MS, TimeUnit.MILLISECONDS);
} /**
* Clean up after ourselves using the default timeout.
/** */
* Clean up after ourselves. public void shutdownAll() {
* @param timeout - the maximum time to wait. shutdownAll(SHUTDOWN_DELAY_MS, TimeUnit.MILLISECONDS);
* @param unit - the time unit of the timeout argument. }
*/
public void shutdownAll(long timeout, TimeUnit unit) { /**
setEnabled(false); * Clean up after ourselves.
shuttingDown = true; * @param timeout - the maximum time to wait.
executor.shutdown(); * @param unit - the time unit of the timeout argument.
*/
try { public void shutdownAll(long timeout, TimeUnit unit) {
executor.awaitTermination(timeout, unit); setEnabled(false);
} catch (InterruptedException e) { shuttingDown = true;
// Unlikely to ever occur - it's the main thread executor.shutdown();
e.printStackTrace();
} try {
} executor.awaitTermination(timeout, unit);
} catch (InterruptedException e) {
/** // Unlikely to ever occur - it's the main thread
* Retrieve whether or not the background compiler is enabled. e.printStackTrace();
* @return TRUE if it is enabled, FALSE otherwise. }
*/ }
public boolean isEnabled() {
return enabled; /**
} * Retrieve whether or not the background compiler is enabled.
* @return TRUE if it is enabled, FALSE otherwise.
/** */
* Sets whether or not the background compiler is enabled. public boolean isEnabled() {
* @param enabled - TRUE to enable it, FALSE otherwise. return enabled;
*/ }
public void setEnabled(boolean enabled) {
this.enabled = enabled; /**
} * Sets whether or not the background compiler is enabled.
* @param enabled - TRUE to enable it, FALSE otherwise.
/** */
* Retrieve the fraction of perm gen space used after which the background compiler will be disabled. public void setEnabled(boolean enabled) {
* @return The fraction after which the background compiler is disabled. this.enabled = enabled;
*/ }
public double getDisablePermGenFraction() {
return disablePermGenFraction; /**
} * Retrieve the fraction of perm gen space used after which the background compiler will be disabled.
* @return The fraction after which the background compiler is disabled.
/** */
* Set the fraction of perm gen space used after which the background compiler will be disabled. public double getDisablePermGenFraction() {
* @param fraction - the maximum use of perm gen space. return disablePermGenFraction;
*/ }
public void setDisablePermGenFraction(double fraction) {
this.disablePermGenFraction = fraction; /**
} * Set the fraction of perm gen space used after which the background compiler will be disabled.
* @param fraction - the maximum use of perm gen space.
/** */
* Retrieve the current structure compiler. public void setDisablePermGenFraction(double fraction) {
* @return Current structure compiler. this.disablePermGenFraction = fraction;
*/ }
public StructureCompiler getCompiler() {
return compiler; /**
} * Retrieve the current structure compiler.
} * @return Current structure compiler.
*/
public StructureCompiler getCompiler() {
return compiler;
}
}

Datei anzeigen

@ -62,6 +62,25 @@ public abstract class AbstractFuzzyMatcher<T> implements Comparable<AbstractFuzz
else else
return Math.max(roundA, roundB); return Math.max(roundA, roundB);
} }
/**
* Combine n round numbers by taking the highest non-zero number, or return zero.
* @param rounds - the round numbers.
* @return The combined round number.
*/
protected final int combineRounds(Integer... rounds) {
if (rounds.length < 2)
throw new IllegalArgumentException("Must supply at least two arguments.");
// Get the seed
int reduced = combineRounds(rounds[0], rounds[1]);
// Aggregate it all
for (int i = 2; i < rounds.length; i++) {
reduced = combineRounds(reduced, rounds[i]);
}
return reduced;
}
@Override @Override
public int compareTo(AbstractFuzzyMatcher<T> obj) { public int compareTo(AbstractFuzzyMatcher<T> obj) {

Datei anzeigen

@ -1,6 +1,7 @@
package com.comphenix.protocol.reflect.fuzzy; package com.comphenix.protocol.reflect.fuzzy;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -23,6 +24,9 @@ public class FuzzyClassContract extends AbstractFuzzyMatcher<Class<?>> {
private final ImmutableList<AbstractFuzzyMatcher<MethodInfo>> methodContracts; private final ImmutableList<AbstractFuzzyMatcher<MethodInfo>> methodContracts;
private final ImmutableList<AbstractFuzzyMatcher<MethodInfo>> constructorContracts; private final ImmutableList<AbstractFuzzyMatcher<MethodInfo>> constructorContracts;
private final ImmutableList<AbstractFuzzyMatcher<Class<?>>> baseclassContracts;
private final ImmutableList<AbstractFuzzyMatcher<Class<?>>> interfaceContracts;
/** /**
* Represents a class contract builder. * Represents a class contract builder.
* @author Kristian * @author Kristian
@ -33,6 +37,9 @@ public class FuzzyClassContract extends AbstractFuzzyMatcher<Class<?>> {
private List<AbstractFuzzyMatcher<MethodInfo>> methodContracts = Lists.newArrayList(); private List<AbstractFuzzyMatcher<MethodInfo>> methodContracts = Lists.newArrayList();
private List<AbstractFuzzyMatcher<MethodInfo>> constructorContracts = Lists.newArrayList(); private List<AbstractFuzzyMatcher<MethodInfo>> constructorContracts = Lists.newArrayList();
private List<AbstractFuzzyMatcher<Class<?>>> baseclassContracts = Lists.newArrayList();
private List<AbstractFuzzyMatcher<Class<?>>> interfaceContracts = Lists.newArrayList();
/** /**
* Add a new field contract. * Add a new field contract.
* @param matcher - new field contract. * @param matcher - new field contract.
@ -89,18 +96,54 @@ public class FuzzyClassContract extends AbstractFuzzyMatcher<Class<?>> {
public Builder constructor(FuzzyMethodContract.Builder builder) { public Builder constructor(FuzzyMethodContract.Builder builder) {
return constructor(builder.build()); return constructor(builder.build());
} }
/**
* Add a new base class contract.
* @param matcher - new base class contract.
* @return This builder, for chaining.
*/
public Builder baseclass(AbstractFuzzyMatcher<Class<?>> matcher) {
baseclassContracts.add(matcher);
return this;
}
/**
* Add a new base class contract.
* @param matcher - builder for the new base class contract.
* @return This builder, for chaining.
*/
public Builder baseclass(FuzzyClassContract.Builder builder) {
return baseclass(builder.build());
}
/**
* Add a new interface contract.
* @param matcher - new interface contract.
* @return This builder, for chaining.
*/
public Builder interfaces(AbstractFuzzyMatcher<Class<?>> matcher) {
interfaceContracts.add(matcher);
return this;
}
/**
* Add a new interface contract.
* @param matcher - builder for the new interface contract.
* @return This builder, for chaining.
*/
public Builder interfaces(FuzzyClassContract.Builder builder) {
return interfaces(builder.build());
}
public FuzzyClassContract build() { public FuzzyClassContract build() {
Collections.sort(fieldContracts); Collections.sort(fieldContracts);
Collections.sort(methodContracts); Collections.sort(methodContracts);
Collections.sort(constructorContracts); Collections.sort(constructorContracts);
Collections.sort(baseclassContracts);
Collections.sort(interfaceContracts);
// Construct a new class matcher // Construct a new class matcher
return new FuzzyClassContract( return new FuzzyClassContract(this);
ImmutableList.copyOf(fieldContracts),
ImmutableList.copyOf(methodContracts),
ImmutableList.copyOf(constructorContracts)
);
} }
} }
@ -114,17 +157,15 @@ public class FuzzyClassContract extends AbstractFuzzyMatcher<Class<?>> {
/** /**
* Constructs a new fuzzy class contract with the given contracts. * Constructs a new fuzzy class contract with the given contracts.
* @param fieldContracts - field contracts. * @param builder - the builder that is constructing us.
* @param methodContracts - method contracts.
* @param constructorContracts - constructor contracts.
*/ */
private FuzzyClassContract(ImmutableList<AbstractFuzzyMatcher<Field>> fieldContracts, private FuzzyClassContract(Builder builder) {
ImmutableList<AbstractFuzzyMatcher<MethodInfo>> methodContracts,
ImmutableList<AbstractFuzzyMatcher<MethodInfo>> constructorContracts) {
super(); super();
this.fieldContracts = fieldContracts; this.fieldContracts = ImmutableList.copyOf(builder.fieldContracts);
this.methodContracts = methodContracts; this.methodContracts = ImmutableList.copyOf(builder.methodContracts);
this.constructorContracts = constructorContracts; this.constructorContracts = ImmutableList.copyOf(builder.constructorContracts);
this.baseclassContracts = ImmutableList.copyOf(builder.baseclassContracts);
this.interfaceContracts = ImmutableList.copyOf(builder.interfaceContracts);
} }
/** /**
@ -157,12 +198,34 @@ public class FuzzyClassContract extends AbstractFuzzyMatcher<Class<?>> {
return constructorContracts; return constructorContracts;
} }
/**
* Retrieve an immutable list of every baseclass contract.
* <p>
* This list is ordered in descending order of priority.
* @return List of every baseclass contract.
*/
public ImmutableList<AbstractFuzzyMatcher<Class<?>>> getBaseclassContracts() {
return baseclassContracts;
}
/**
* Retrieve an immutable list of every interface contract.
* <p>
* This list is ordered in descending order of priority.
* @return List of every interface contract.
*/
public ImmutableList<AbstractFuzzyMatcher<Class<?>>> getInterfaceContracts() {
return interfaceContracts;
}
@Override @Override
protected int calculateRoundNumber() { protected int calculateRoundNumber() {
// Find the highest round number // Find the highest round number
return combineRounds(findHighestRound(fieldContracts), return combineRounds(findHighestRound(fieldContracts),
combineRounds(findHighestRound(methodContracts), findHighestRound(methodContracts),
findHighestRound(constructorContracts))); findHighestRound(constructorContracts),
findHighestRound(interfaceContracts),
findHighestRound(baseclassContracts));
} }
private <T> int findHighestRound(Collection<AbstractFuzzyMatcher<T>> list) { private <T> int findHighestRound(Collection<AbstractFuzzyMatcher<T>> list) {
@ -179,12 +242,19 @@ public class FuzzyClassContract extends AbstractFuzzyMatcher<Class<?>> {
FuzzyReflection reflection = FuzzyReflection.fromClass(value, true); FuzzyReflection reflection = FuzzyReflection.fromClass(value, true);
// Make sure all the contracts are valid // Make sure all the contracts are valid
return processContracts(reflection.getFields(), value, fieldContracts) && return (fieldContracts.size() == 0 ||
processContracts(MethodInfo.fromMethods(reflection.getMethods()), value, methodContracts) && processContracts(reflection.getFields(), value, fieldContracts)) &&
processContracts(MethodInfo.fromConstructors(value.getDeclaredConstructors()), value, constructorContracts); (methodContracts.size() == 0 ||
processContracts(MethodInfo.fromMethods(reflection.getMethods()), value, methodContracts)) &&
(constructorContracts.size() == 0 ||
processContracts(MethodInfo.fromConstructors(value.getDeclaredConstructors()), value, constructorContracts)) &&
(baseclassContracts.size() == 0 ||
processValue(value.getSuperclass(), parent, baseclassContracts)) &&
(interfaceContracts.size() == 0 ||
processContracts(Arrays.asList(value.getInterfaces()), (Class<?>) parent, interfaceContracts));
} }
private <T> boolean processContracts(Collection<T> values, Class<?> parent, List<AbstractFuzzyMatcher<T>> matchers) { private <T> boolean processContracts(Collection<T> values, Object parent, List<AbstractFuzzyMatcher<T>> matchers) {
boolean[] accepted = new boolean[matchers.size()]; boolean[] accepted = new boolean[matchers.size()];
int count = accepted.length; int count = accepted.length;
@ -205,7 +275,18 @@ public class FuzzyClassContract extends AbstractFuzzyMatcher<Class<?>> {
return count == 0; return count == 0;
} }
private <T> int processValue(T value, Class<?> parent, boolean accepted[], List<AbstractFuzzyMatcher<T>> matchers) { private <T> boolean processValue(T value, Object parent, List<AbstractFuzzyMatcher<T>> matchers) {
for (int i = 0; i < matchers.size(); i++) {
if (matchers.get(i).isMatch(value, parent)) {
return true;
}
}
// No match
return false;
}
private <T> int processValue(T value, Object parent, boolean accepted[], List<AbstractFuzzyMatcher<T>> matchers) {
// The order matters // The order matters
for (int i = 0; i < matchers.size(); i++) { for (int i = 0; i < matchers.size(); i++) {
if (!accepted[i]) { if (!accepted[i]) {
@ -235,6 +316,12 @@ public class FuzzyClassContract extends AbstractFuzzyMatcher<Class<?>> {
if (constructorContracts.size() > 0) { if (constructorContracts.size() > 0) {
params.put("constructors", constructorContracts); params.put("constructors", constructorContracts);
} }
if (baseclassContracts.size() > 0) {
params.put("baseclasses", baseclassContracts);
}
if (interfaceContracts.size() > 0) {
params.put("interfaces", interfaceContracts);
}
return "{\n " + Joiner.on(", \n ").join(params.entrySet()) + "\n}"; return "{\n " + Joiner.on(", \n ").join(params.entrySet()) + "\n}";
} }
} }

Datei anzeigen

@ -327,6 +327,7 @@ public class DefaultInstances implements InstanceProvider {
try { try {
return (T) constructor.newInstance(params); return (T) constructor.newInstance(params);
} catch (Exception e) { } catch (Exception e) {
//e.printStackTrace();
// Cannot create it // Cannot create it
return null; return null;
} }

Datei anzeigen

@ -81,6 +81,9 @@ public class MinecraftReflection {
private static Constructor<?> craftNMSConstructor; private static Constructor<?> craftNMSConstructor;
private static Constructor<?> craftBukkitConstructor; private static Constructor<?> craftBukkitConstructor;
// Matches classes
private static AbstractFuzzyMatcher<Class<?>> fuzzyMatcher;
// New in 1.4.5 // New in 1.4.5
private static Method craftNMSMethod; private static Method craftNMSMethod;
private static Method craftBukkitMethod; private static Method craftBukkitMethod;
@ -88,7 +91,12 @@ public class MinecraftReflection {
// net.minecraft.server // net.minecraft.server
private static Class<?> itemStackArrayClass; private static Class<?> itemStackArrayClass;
/**
* Whether or not we're currently initializing the reflection handler.
*/
private static boolean initializing;
private MinecraftReflection() { private MinecraftReflection() {
// No need to make this constructable. // No need to make this constructable.
} }
@ -108,7 +116,9 @@ public class MinecraftReflection {
* @return A matcher for Minecraft objects. * @return A matcher for Minecraft objects.
*/ */
public static AbstractFuzzyMatcher<Class<?>> getMinecraftObjectMatcher() { public static AbstractFuzzyMatcher<Class<?>> getMinecraftObjectMatcher() {
return FuzzyMatchers.matchRegex(getMinecraftObjectRegex(), 50); if (fuzzyMatcher == null)
fuzzyMatcher = FuzzyMatchers.matchRegex(getMinecraftObjectRegex(), 50);
return fuzzyMatcher;
} }
/** /**
@ -119,6 +129,9 @@ public class MinecraftReflection {
// Speed things up // Speed things up
if (MINECRAFT_FULL_PACKAGE != null) if (MINECRAFT_FULL_PACKAGE != null)
return MINECRAFT_FULL_PACKAGE; return MINECRAFT_FULL_PACKAGE;
if (initializing)
throw new IllegalStateException("Already initializing minecraft package!");
initializing = true;
Server craftServer = Bukkit.getServer(); Server craftServer = Bukkit.getServer();
@ -129,6 +142,9 @@ public class MinecraftReflection {
Class<?> craftClass = craftServer.getClass(); Class<?> craftClass = craftServer.getClass();
CRAFTBUKKIT_PACKAGE = getPackage(craftClass.getCanonicalName()); CRAFTBUKKIT_PACKAGE = getPackage(craftClass.getCanonicalName());
// Libigot patch
handleLibigot();
// Next, do the same for CraftEntity.getHandle() in order to get the correct Minecraft package // Next, do the same for CraftEntity.getHandle() in order to get the correct Minecraft package
Class<?> craftEntity = getCraftEntityClass(); Class<?> craftEntity = getCraftEntityClass();
Method getHandle = craftEntity.getMethod("getHandle"); Method getHandle = craftEntity.getMethod("getHandle");
@ -141,16 +157,16 @@ public class MinecraftReflection {
MINECRAFT_PREFIX_PACKAGE = MINECRAFT_FULL_PACKAGE; MINECRAFT_PREFIX_PACKAGE = MINECRAFT_FULL_PACKAGE;
// The package is usualy flat, so go with that assumtion // The package is usualy flat, so go with that assumtion
DYNAMIC_PACKAGE_MATCHER = String matcher =
(MINECRAFT_PREFIX_PACKAGE.length() > 0 ? (MINECRAFT_PREFIX_PACKAGE.length() > 0 ?
Pattern.quote(MINECRAFT_PREFIX_PACKAGE + ".") : "") + "\\w+"; Pattern.quote(MINECRAFT_PREFIX_PACKAGE + ".") : "") + "\\w+";
// We'll still accept the default location, however // We'll still accept the default location, however
DYNAMIC_PACKAGE_MATCHER = "(" + DYNAMIC_PACKAGE_MATCHER + ")|(" + MINECRAFT_OBJECT + ")"; setDynamicPackageMatcher("(" + matcher + ")|(" + MINECRAFT_OBJECT + ")");
} else { } else {
// Use the standard matcher // Use the standard matcher
DYNAMIC_PACKAGE_MATCHER = MINECRAFT_OBJECT; setDynamicPackageMatcher(MINECRAFT_OBJECT);
} }
return MINECRAFT_FULL_PACKAGE; return MINECRAFT_FULL_PACKAGE;
@ -159,12 +175,40 @@ public class MinecraftReflection {
throw new RuntimeException("Security violation. Cannot get handle method.", e); throw new RuntimeException("Security violation. Cannot get handle method.", e);
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException e) {
throw new IllegalStateException("Cannot find getHandle() method on server. Is this a modified CraftBukkit version?", e); throw new IllegalStateException("Cannot find getHandle() method on server. Is this a modified CraftBukkit version?", e);
} finally {
initializing = false;
} }
} else { } else {
initializing = false;
throw new IllegalStateException("Could not find Bukkit. Is it running?"); throw new IllegalStateException("Could not find Bukkit. Is it running?");
} }
} }
/**
* Update the dynamic package matcher.
* @param regex - the Minecraft package regex.
*/
private static void setDynamicPackageMatcher(String regex) {
DYNAMIC_PACKAGE_MATCHER = regex;
// Ensure that the matcher is regenerated
fuzzyMatcher = null;
}
// Patch for Libigot
private static void handleLibigot() {
try {
getCraftEntityClass();
} catch (RuntimeException e) {
// Try reverting the package to the old format
craftbukkitPackage = null;
CRAFTBUKKIT_PACKAGE = "org.bukkit.craftbukkit";
// This might fail too
getCraftEntityClass();
}
}
/** /**
* Used during debugging and testing. * Used during debugging and testing.
@ -176,7 +220,7 @@ public class MinecraftReflection {
CRAFTBUKKIT_PACKAGE = craftBukkitPackage; CRAFTBUKKIT_PACKAGE = craftBukkitPackage;
// Standard matcher // Standard matcher
DYNAMIC_PACKAGE_MATCHER = MINECRAFT_OBJECT; setDynamicPackageMatcher(MINECRAFT_OBJECT);
} }
/** /**
@ -244,8 +288,7 @@ public class MinecraftReflection {
if (clazz == null) if (clazz == null)
throw new IllegalArgumentException("Class cannot be NULL."); throw new IllegalArgumentException("Class cannot be NULL.");
// Doesn't matter if we don't check for the version here return getMinecraftObjectMatcher().isMatch(clazz, null);
return clazz.getName().startsWith(MINECRAFT_PREFIX_PACKAGE);
} }
/** /**
@ -809,9 +852,15 @@ public class MinecraftReflection {
returnTypeMatches(tagCompoundContract). returnTypeMatches(tagCompoundContract).
build() build()
); );
Class<?> nbtBase = selected.getReturnType().getSuperclass();
// That can't be correct
if (nbtBase == null || nbtBase.equals(Object.class)) {
throw new IllegalStateException("Unable to find NBT base class: " + nbtBase);
}
// Use the return type here too // Use the return type here too
return setMinecraftClass("NBTBase", selected.getReturnType()); return setMinecraftClass("NBTBase", nbtBase);
} }
} }

Datei anzeigen

@ -15,7 +15,7 @@
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol; package com.comphenix.protocol.utility;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -31,7 +31,7 @@ import com.google.common.collect.Ordering;
* *
* @author Kristian * @author Kristian
*/ */
class MinecraftVersion implements Comparable<MinecraftVersion> { public class MinecraftVersion implements Comparable<MinecraftVersion> {
/** /**
* Regular expression used to parse version strings. * Regular expression used to parse version strings.
*/ */

Datei anzeigen

@ -0,0 +1,104 @@
package com.comphenix.protocol.wrappers;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nonnull;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.fuzzy.AbstractFuzzyMatcher;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMatchers;
/**
* Wrap a GNU Trove Collection class with an equivalent Java Collection class.
* @author Kristian
*/
public class TroveWrapper {
private volatile static Class<?> decorators;
/**
* Retrieve a Java wrapper for the corresponding Trove map.
* @param troveMap - the trove map to wrap.
* @return The wrapped GNU Trove map.
* @throws IllegalStateException If GNU Trove cannot be found in the class map.
* @throws IllegalArgumentException If troveMap is NULL.
* @throws FieldAccessException Error in wrapper method or lack of reflection permissions.
*/
public static <TKey, TValue> Map<TKey, TValue> getDecoratedMap(@Nonnull Object troveMap) {
@SuppressWarnings("unchecked")
Map<TKey, TValue> result = (Map<TKey, TValue>) getDecorated(troveMap);
return result;
}
/**
* Retrieve a Java wrapper for the corresponding Trove set.
* @param troveSet - the trove set to wrap.
* @return The wrapped GNU Trove set.
* @throws IllegalStateException If GNU Trove cannot be found in the class map.
* @throws IllegalArgumentException If troveSet is NULL.
* @throws FieldAccessException Error in wrapper method or lack of reflection permissions.
*/
public static <TValue> Set<TValue> getDecoratedSet(@Nonnull Object troveSet) {
@SuppressWarnings("unchecked")
Set<TValue> result = (Set<TValue>) getDecorated(troveSet);
return result;
}
/**
* Retrieve a Java wrapper for the corresponding Trove list.
* @param troveList - the trove list to wrap.
* @return The wrapped GNU Trove list.
* @throws IllegalStateException If GNU Trove cannot be found in the class map.
* @throws IllegalArgumentException If troveList is NULL.
* @throws FieldAccessException Error in wrapper method or lack of reflection permissions.
*/
public static <TValue> List<TValue> getDecoratedList(@Nonnull Object troveList) {
@SuppressWarnings("unchecked")
List<TValue> result = (List<TValue>) getDecorated(troveList);
return result;
}
private static Object getDecorated(@Nonnull Object trove) {
if (trove == null)
throw new IllegalArgumentException("trove instance cannot be non-null.");
AbstractFuzzyMatcher<Class<?>> match = FuzzyMatchers.matchSuper(trove.getClass());
if (decorators == null) {
try {
// Attempt to get decorator class
decorators = TroveWrapper.class.getClassLoader().loadClass("gnu.trove.TDecorators");
} catch (ClassNotFoundException e) {
throw new IllegalStateException("Cannot find TDecorators in Gnu Trove.", e);
}
}
// Find an appropriate wrapper method in TDecorators
for (Method method : decorators.getMethods()) {
Class<?>[] types = method.getParameterTypes();
if (types.length == 1 && match.isMatch(types[0], null)) {
try {
Object result = method.invoke(null, trove);
if (result == null)
throw new FieldAccessException("Wrapper returned NULL.");
else
return result;
} catch (IllegalArgumentException e) {
throw new FieldAccessException("Cannot invoke wrapper method.", e);
} catch (IllegalAccessException e) {
throw new FieldAccessException("Illegal access.", e);
} catch (InvocationTargetException e) {
throw new FieldAccessException("Error in invocation.", e);
}
}
}
throw new IllegalArgumentException("Cannot find decorator for " + trove + " (" + trove.getClass() + ")");
}
}

Datei anzeigen

@ -6,8 +6,6 @@ global:
# Number of seconds to wait until a new update is downloaded # Number of seconds to wait until a new update is downloaded
delay: 43200 # 12 hours delay: 43200 # 12 hours
# Last update time
last: 0
metrics: true metrics: true
@ -18,4 +16,10 @@ global:
ignore version check: ignore version check:
# Override the starting injecting method # Override the starting injecting method
injection method: injection method:
# Whether or not to enable the filter command
debug: false
# The engine used by the filter command
script engine: JavaScript

Datei anzeigen

@ -1,9 +1,11 @@
name: ProtocolLib name: ProtocolLib
version: 2.3.0 version: 2.4.3
description: Provides read/write access to the Minecraft protocol. description: Provides read/write access to the Minecraft protocol.
author: Comphenix author: Comphenix
website: http://www.comphenix.net/ProtocolLib website: http://www.comphenix.net/ProtocolLib
main: com.comphenix.protocol.ProtocolLibrary main: com.comphenix.protocol.ProtocolLibrary
load: startup
database: false database: false
commands: commands:
@ -17,6 +19,12 @@ commands:
usage: /<command> add|remove|names client|server [ID start]-[ID stop] [detailed] usage: /<command> add|remove|names client|server [ID start]-[ID stop] [detailed]
permission: protocol.admin permission: protocol.admin
permission-message: You don't have <permission> permission-message: You don't have <permission>
filter:
description: Add or remove programmable filters to the packet listeners.
usage: /<command> add|remove name [ID start]-[ID stop]
aliases: [packet_filter]
permission: protocol.admin
permission-message: You don't have <permission>
permissions: permissions:
protocol.*: protocol.*:

Datei anzeigen

@ -4,8 +4,10 @@ import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import net.minecraft.server.v1_5_R2.StatisticList;
// Will have to be updated for every version though // Will have to be updated for every version though
import org.bukkit.craftbukkit.v1_4_R1.inventory.CraftItemFactory; import org.bukkit.craftbukkit.v1_5_R2.inventory.CraftItemFactory;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Material; import org.bukkit.Material;
@ -34,6 +36,12 @@ public class BukkitInitialization {
initializePackage(); initializePackage();
try {
StatisticList.b();
} catch (Exception e) {
// Swallow
}
// Mock the server object // Mock the server object
Server mockedServer = mock(Server.class); Server mockedServer = mock(Server.class);
ItemFactory mockedFactory = mock(CraftItemFactory.class); ItemFactory mockedFactory = mock(CraftItemFactory.class);
@ -55,6 +63,6 @@ public class BukkitInitialization {
*/ */
public static void initializePackage() { public static void initializePackage() {
// Initialize reflection // Initialize reflection
MinecraftReflection.setMinecraftPackage("net.minecraft.server.v1_4_R1", "org.bukkit.craftbukkit.v1_4_R1"); MinecraftReflection.setMinecraftPackage("net.minecraft.server.v1_5_R2", "org.bukkit.craftbukkit.v1_5_R2");
} }
} }

Datei anzeigen

@ -21,6 +21,8 @@ import static org.junit.Assert.*;
import org.junit.Test; import org.junit.Test;
import com.comphenix.protocol.utility.MinecraftVersion;
public class MinecraftVersionTest { public class MinecraftVersionTest {
@Test @Test

Datei anzeigen

@ -22,7 +22,7 @@ import java.lang.reflect.Array;
import java.util.List; import java.util.List;
// Will have to be updated for every version though // Will have to be updated for every version though
import org.bukkit.craftbukkit.v1_4_R1.inventory.CraftItemFactory; import org.bukkit.craftbukkit.v1_5_R2.inventory.CraftItemFactory;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.WorldType; import org.bukkit.WorldType;