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>

Datei anzeigen

@ -24,6 +24,8 @@ 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.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.ListeningWhitelist; import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.BukkitUnwrapper; import com.comphenix.protocol.injector.BukkitUnwrapper;
@ -51,6 +53,9 @@ import com.comphenix.protocol.wrappers.nbt.io.NbtBinarySerializer;
* @author Kristian * @author Kristian
*/ */
class CleanupStaticMembers { class CleanupStaticMembers {
// Reports
public final static ReportType REPORT_CANNOT_RESET_FIELD = new ReportType("Unable to reset field %s: %s");
public final static ReportType REPORT_CANNOT_UNLOAD_CLASS = new ReportType("Unable to unload class %s.");
private ClassLoader loader; private ClassLoader loader;
private ErrorReporter reporter; private ErrorReporter reporter;
@ -116,7 +121,9 @@ class CleanupStaticMembers {
setFinalStatic(field, null); setFinalStatic(field, null);
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
// Just inform the player // Just inform the player
reporter.reportWarning(this, "Unable to reset field " + field.getName() + ": " + e.getMessage(), e); reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_RESET_FIELD).error(e).messageParam(field.getName(), e.getMessage())
);
} }
} }
} }
@ -151,7 +158,7 @@ class CleanupStaticMembers {
output.add(loader.loadClass(name)); output.add(loader.loadClass(name));
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
// Warn the user // Warn the user
reporter.reportWarning(this, "Unable to unload class " + name, e); reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_UNLOAD_CLASS).error(e).messageParam(name));
} }
} }

Datei anzeigen

@ -23,6 +23,8 @@ 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. * Base class for all our commands.
@ -30,6 +32,8 @@ import com.comphenix.protocol.error.ErrorReporter;
* @author Kristian * @author Kristian
*/ */
abstract class CommandBase implements CommandExecutor { abstract class CommandBase implements CommandExecutor {
public static final ReportType REPORT_COMMAND_ERROR = new ReportType("Cannot execute command %s.");
public static final ReportType REPORT_UNEXPECTED_COMMAND = new ReportType("Incorrect command assigned to %s.");
public static final String PERMISSION_ADMIN = "protocol.admin"; public static final String PERMISSION_ADMIN = "protocol.admin";
@ -55,6 +59,7 @@ abstract class CommandBase implements CommandExecutor {
try { try {
// Make sure we're dealing with the correct command // Make sure we're dealing with the correct command
if (!command.getName().equalsIgnoreCase(name)) { if (!command.getName().equalsIgnoreCase(name)) {
reporter.reportWarning(this, Report.newBuilder(REPORT_UNEXPECTED_COMMAND).messageParam(this));
return false; return false;
} }
if (permission != null && !sender.hasPermission(permission)) { if (permission != null && !sender.hasPermission(permission)) {
@ -66,11 +71,14 @@ abstract class CommandBase implements CommandExecutor {
if (args != null && args.length >= minimumArgumentCount) { if (args != null && args.length >= minimumArgumentCount) {
return handleCommand(sender, args); return handleCommand(sender, args);
} else { } else {
sender.sendMessage(ChatColor.RED + "Insufficient commands. You need at least " + minimumArgumentCount);
return false; return false;
} }
} catch (Exception e) { } catch (Exception e) {
reporter.reportDetailed(this, "Cannot execute command " + name, e, sender, label, args); reporter.reportDetailed(this,
Report.newBuilder(REPORT_COMMAND_ERROR).error(e).messageParam(name).callerParam(sender, label, args)
);
return true; return true;
} }
} }

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

@ -36,6 +36,8 @@ import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.concurrency.AbstractIntervalTree; import com.comphenix.protocol.concurrency.AbstractIntervalTree;
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.ConnectionSide; import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.ListenerPriority; import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.ListeningWhitelist; import com.comphenix.protocol.events.ListeningWhitelist;
@ -57,6 +59,7 @@ import com.google.common.collect.Sets;
* @author Kristian * @author Kristian
*/ */
class CommandPacket extends CommandBase { class CommandPacket extends CommandBase {
public static final ReportType REPORT_CANNOT_SEND_MESSAGE = new ReportType("Cannot send chat message.");
private interface DetailedPacketListener extends PacketListener { private interface DetailedPacketListener extends PacketListener {
/** /**
@ -93,11 +96,15 @@ class CommandPacket extends CommandBase {
private AbstractIntervalTree<Integer, DetailedPacketListener> clientListeners = createTree(ConnectionSide.CLIENT_SIDE); private AbstractIntervalTree<Integer, DetailedPacketListener> clientListeners = createTree(ConnectionSide.CLIENT_SIDE);
private AbstractIntervalTree<Integer, DetailedPacketListener> serverListeners = createTree(ConnectionSide.SERVER_SIDE); private AbstractIntervalTree<Integer, DetailedPacketListener> serverListeners = createTree(ConnectionSide.SERVER_SIDE);
public CommandPacket(ErrorReporter reporter, Plugin plugin, Logger logger, ProtocolManager manager) { // Filter packet events
private CommandFilter filter;
public CommandPacket(ErrorReporter reporter, Plugin plugin, Logger logger, CommandFilter filter, ProtocolManager manager) {
super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 1); super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 1);
this.plugin = plugin; this.plugin = plugin;
this.logger = logger; this.logger = logger;
this.manager = manager; this.manager = manager;
this.filter = filter;
this.chatter = new ChatExtensions(manager); this.chatter = new ChatExtensions(manager);
} }
@ -162,7 +169,9 @@ class CommandPacket extends CommandBase {
try { try {
chatter.sendMessageSilently(receiver, message); chatter.sendMessageSilently(receiver, message);
} catch (InvocationTargetException e) { } catch (InvocationTargetException e) {
reporter.reportDetailed(this, "Cannot send chat message.", e, receiver, message); reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_SEND_MESSAGE).error(e).callerParam(receiver, message)
);
} }
} }
@ -175,7 +184,9 @@ class CommandPacket extends CommandBase {
try { try {
chatter.broadcastMessageSilently(message, permission); chatter.broadcastMessageSilently(message, permission);
} catch (InvocationTargetException e) { } catch (InvocationTargetException e) {
reporter.reportDetailed(this, "Cannot send chat message.", e, message, permission); reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_SEND_MESSAGE).error(e).callerParam(message, permission)
);
} }
} }
@ -336,6 +347,7 @@ class CommandPacket extends CommandBase {
supported.addAll(Packets.Client.getSupported()); supported.addAll(Packets.Client.getSupported());
else if (side.isForServer()) else if (side.isForServer())
supported.addAll(Packets.Server.getSupported()); supported.addAll(Packets.Server.getSupported());
return supported; return supported;
} }
@ -362,7 +374,6 @@ class CommandPacket extends CommandBase {
} }
public DetailedPacketListener createPacketListener(final ConnectionSide side, int idStart, int idStop, final boolean detailed) { public DetailedPacketListener createPacketListener(final ConnectionSide side, int idStart, int idStop, final boolean detailed) {
Set<Integer> range = Ranges.closed(idStart, idStop).asSet(DiscreteDomains.integers()); Set<Integer> range = Ranges.closed(idStart, idStop).asSet(DiscreteDomains.integers());
Set<Integer> packets; Set<Integer> packets;
@ -386,14 +397,14 @@ class CommandPacket extends CommandBase {
return new DetailedPacketListener() { return new DetailedPacketListener() {
@Override @Override
public void onPacketSending(PacketEvent event) { public void onPacketSending(PacketEvent event) {
if (side.isForServer()) { if (side.isForServer() && filter.filterEvent(event)) {
printInformation(event); printInformation(event);
} }
} }
@Override @Override
public void onPacketReceiving(PacketEvent event) { public void onPacketReceiving(PacketEvent event) {
if (side.isForClient()) { if (side.isForClient() && filter.filterEvent(event)) {
printInformation(event); printInformation(event);
} }
} }

Datei anzeigen

@ -24,6 +24,8 @@ 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.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.metrics.Updater; import com.comphenix.protocol.metrics.Updater;
import com.comphenix.protocol.metrics.Updater.UpdateResult; import com.comphenix.protocol.metrics.Updater.UpdateResult;
import com.comphenix.protocol.metrics.Updater.UpdateType; import com.comphenix.protocol.metrics.Updater.UpdateType;
@ -40,6 +42,10 @@ class CommandProtocol extends CommandBase {
*/ */
public static final String NAME = "protocol"; public static final String NAME = "protocol";
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 static final ReportType REPORT_CANNOT_UPDATE_PLUGIN = new ReportType("Cannot update ProtocolLib.");
private Plugin plugin; private Plugin plugin;
private Updater updater; private Updater updater;
private ProtocolConfig config; private ProtocolConfig config;
@ -77,9 +83,11 @@ class CommandProtocol extends CommandBase {
sender.sendMessage(ChatColor.BLUE + "[ProtocolLib] " + result.toString()); sender.sendMessage(ChatColor.BLUE + "[ProtocolLib] " + result.toString());
} catch (Exception e) { } catch (Exception e) {
if (isHttpError(e)) { if (isHttpError(e)) {
getReporter().reportWarning(this, "Http error: " + e.getCause().getMessage()); getReporter().reportWarning(this,
Report.newBuilder(REPORT_HTTP_ERROR).messageParam(e.getCause().getMessage())
);
} else { } else {
getReporter().reportDetailed(this, "Cannot check updates for ProtocolLib.", e, sender); getReporter().reportDetailed(this, Report.newBuilder(REPORT_CANNOT_CHECK_FOR_UPDATES).error(e).callerParam(sender));
} }
} }
} }
@ -98,9 +106,11 @@ class CommandProtocol extends CommandBase {
sender.sendMessage(ChatColor.BLUE + "[ProtocolLib] " + result.toString()); sender.sendMessage(ChatColor.BLUE + "[ProtocolLib] " + result.toString());
} catch (Exception e) { } catch (Exception e) {
if (isHttpError(e)) { if (isHttpError(e)) {
getReporter().reportWarning(this, "Http error: " + e.getCause().getMessage()); getReporter().reportWarning(this,
Report.newBuilder(REPORT_HTTP_ERROR).messageParam(e.getCause().getMessage())
);
} else { } else {
getReporter().reportDetailed(this, "Cannot update ProtocolLib.", e, sender); getReporter().reportDetailed(this,Report.newBuilder(REPORT_CANNOT_UPDATE_PLUGIN).error(e).callerParam(sender));
} }
} }
} }

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.
@ -101,6 +166,17 @@ class ProtocolConfig {
} }
} }
/**
* 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.
* @return Configuration file on disk. * @return Configuration file on disk.
@ -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,15 +254,7 @@ 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);
} }
/** /**
@ -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() {
if (valuesChanged)
saveLastUpdate(lastUpdateTime);
if (configChanged)
plugin.saveConfig(); 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

@ -89,6 +89,7 @@ abstract class PacketSendingQueue {
PacketEvent copy = PacketEvent.fromSynchronous(packetUpdated, marker); PacketEvent copy = PacketEvent.fromSynchronous(packetUpdated, marker);
// "Cancel" the original event // "Cancel" the original event
packetUpdated.setReadOnly(false);
packetUpdated.setCancelled(true); packetUpdated.setCancelled(true);
// Enqueue the copy with the new sending index // Enqueue the copy with the new sending index

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,8 +59,7 @@ 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) {
@ -64,12 +67,12 @@ public abstract class AbstractConcurrentListenerMultimap<TListener> {
// 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

@ -34,6 +34,7 @@ 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.error.Report.ReportBuilder;
import com.comphenix.protocol.events.PacketAdapter; import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.reflect.PrettyPrinter; import com.comphenix.protocol.reflect.PrettyPrinter;
import com.google.common.primitives.Primitives; import com.google.common.primitives.Primitives;
@ -44,11 +45,14 @@ import com.google.common.primitives.Primitives;
* @author Kristian * @author Kristian
*/ */
public class DetailedErrorReporter implements ErrorReporter { public class DetailedErrorReporter implements ErrorReporter {
/**
* Report format for printing the current exception count.
*/
public static final ReportType REPORT_EXCEPTION_COUNT = new ReportType("Internal exception count: %s!");
public static final String SECOND_LEVEL_PREFIX = " "; public static final String SECOND_LEVEL_PREFIX = " ";
public static final String DEFAULT_PREFIX = " "; public static final String DEFAULT_PREFIX = " ";
public static final String DEFAULT_SUPPORT_URL = "http://dev.bukkit.org/server-mods/protocollib/"; public static final String DEFAULT_SUPPORT_URL = "http://dev.bukkit.org/server-mods/protocollib/";
public static final String PLUGIN_NAME = "ProtocolLib";
// Users that are informed about errors in the chat // Users that are informed about errors in the chat
public static final String ERROR_PERMISSION = "protocol.info"; public static final String ERROR_PERMISSION = "protocol.info";
@ -68,6 +72,7 @@ public class DetailedErrorReporter implements ErrorReporter {
protected Logger logger; protected Logger logger;
protected WeakReference<Plugin> pluginReference; protected WeakReference<Plugin> pluginReference;
protected String pluginName;
// Whether or not Apache Commons is not present // Whether or not Apache Commons is not present
protected boolean apacheCommonsMissing; protected boolean apacheCommonsMissing;
@ -92,15 +97,6 @@ public class DetailedErrorReporter implements ErrorReporter {
this(plugin, prefix, supportURL, DEFAULT_MAX_ERROR_COUNT, getBukkitLogger()); this(plugin, prefix, supportURL, DEFAULT_MAX_ERROR_COUNT, getBukkitLogger());
} }
// Attempt to get the logger.
private static Logger getBukkitLogger() {
try {
return Bukkit.getLogger();
} catch (Throwable e) {
return Logger.getLogger("Minecraft");
}
}
/** /**
* Create a central error reporting system. * Create a central error reporting system.
* @param plugin - the plugin owner. * @param plugin - the plugin owner.
@ -114,23 +110,28 @@ public class DetailedErrorReporter implements ErrorReporter {
throw new IllegalArgumentException("Plugin cannot be NULL."); throw new IllegalArgumentException("Plugin cannot be NULL.");
this.pluginReference = new WeakReference<Plugin>(plugin); this.pluginReference = new WeakReference<Plugin>(plugin);
this.pluginName = plugin.getName();
this.prefix = prefix; this.prefix = prefix;
this.supportURL = supportURL; this.supportURL = supportURL;
this.maxErrorCount = maxErrorCount; this.maxErrorCount = maxErrorCount;
this.logger = logger; this.logger = logger;
} }
// Attempt to get the logger.
private static Logger getBukkitLogger() {
try {
return Bukkit.getLogger();
} catch (Throwable e) {
return Logger.getLogger("Minecraft");
}
}
@Override @Override
public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) { public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) {
if (reportMinimalNoSpam(sender, methodName, error)) { if (reportMinimalNoSpam(sender, methodName, error)) {
// Print parameters, if they are given // Print parameters, if they are given
if (parameters != null && parameters.length > 0) { if (parameters != null && parameters.length > 0) {
logger.log(Level.SEVERE, " Parameters:"); logger.log(Level.SEVERE, printParameters(parameters));
// Print each parameter
for (Object parameter : parameters) {
logger.log(Level.SEVERE, " " + getStringDescription(parameter));
}
} }
} }
} }
@ -140,6 +141,13 @@ public class DetailedErrorReporter implements ErrorReporter {
reportMinimalNoSpam(sender, methodName, error); reportMinimalNoSpam(sender, methodName, error);
} }
/**
* Report a problem with a given method and plugin, ensuring that we don't exceed the maximum number of error reports.
* @param sender - the component that observed this exception.
* @param methodName - the method name.
* @param error - the error itself.
* @return TRUE if the error was printed, FALSE if it was suppressed.
*/
public boolean reportMinimalNoSpam(Plugin sender, String methodName, Throwable error) { public boolean reportMinimalNoSpam(Plugin sender, String methodName, Throwable error) {
String pluginName = PacketAdapter.getPluginName(sender); String pluginName = PacketAdapter.getPluginName(sender);
AtomicInteger counter = warningCount.get(pluginName); AtomicInteger counter = warningCount.get(pluginName);
@ -158,14 +166,14 @@ public class DetailedErrorReporter implements ErrorReporter {
// See if we should print the full error // See if we should print the full error
if (errorCount < getMaxErrorCount()) { if (errorCount < getMaxErrorCount()) {
logger.log(Level.SEVERE, "[" + PLUGIN_NAME + "] Unhandled exception occured in " + logger.log(Level.SEVERE, "[" + pluginName + "] Unhandled exception occured in " +
methodName + " for " + pluginName, error); methodName + " for " + pluginName, error);
return true; return true;
} else { } else {
// Nope - only print the error count occationally // Nope - only print the error count occationally
if (isPowerOfTwo(errorCount)) { if (isPowerOfTwo(errorCount)) {
logger.log(Level.SEVERE, "[" + PLUGIN_NAME + "] Unhandled exception number " + errorCount + " occured in " + logger.log(Level.SEVERE, "[" + pluginName + "] Unhandled exception number " + errorCount + " occured in " +
methodName + " for " + pluginName, error); methodName + " for " + pluginName, error);
} }
return false; return false;
@ -184,15 +192,36 @@ public class DetailedErrorReporter implements ErrorReporter {
} }
@Override @Override
public void reportWarning(Object sender, String message) { public void reportWarning(Object sender, ReportBuilder reportBuilder) {
logger.log(Level.WARNING, "[" + PLUGIN_NAME + "] [" + getSenderName(sender) + "] " + message); if (reportBuilder == null)
throw new IllegalArgumentException("reportBuilder cannot be NULL.");
reportWarning(sender, reportBuilder.build());
} }
@Override @Override
public void reportWarning(Object sender, String message, Throwable error) { public void reportWarning(Object sender, Report report) {
logger.log(Level.WARNING, "[" + PLUGIN_NAME + "] [" + getSenderName(sender) + "] " + message, error); String message = "[" + pluginName + "] [" + getSenderName(sender) + "] " + report.getReportMessage();
// Print the main warning
if (report.getException() != null) {
logger.log(Level.WARNING, message, report.getException());
} else {
logger.log(Level.WARNING, message);
} }
// Parameters?
if (report.hasCallerParameters()) {
// Write it
logger.log(Level.WARNING, printParameters(report.getCallerParameters()));
}
}
/**
* Retrieve the name of a sender class.
* @param sender - sender object.
* @return The name of the sender's class.
*/
private String getSenderName(Object sender) { private String getSenderName(Object sender) {
if (sender != null) if (sender != null)
return sender.getClass().getSimpleName(); return sender.getClass().getSimpleName();
@ -201,8 +230,12 @@ public class DetailedErrorReporter implements ErrorReporter {
} }
@Override @Override
public void reportDetailed(Object sender, String message, Throwable error, Object... parameters) { public void reportDetailed(Object sender, ReportBuilder reportBuilder) {
reportDetailed(sender, reportBuilder.build());
}
@Override
public void reportDetailed(Object sender, Report report) {
final Plugin plugin = pluginReference.get(); final Plugin plugin = pluginReference.get();
final int errorCount = internalErrorCount.incrementAndGet(); final int errorCount = internalErrorCount.incrementAndGet();
@ -211,7 +244,7 @@ public class DetailedErrorReporter implements ErrorReporter {
// Only allow the error count at rare occations // Only allow the error count at rare occations
if (isPowerOfTwo(errorCount)) { if (isPowerOfTwo(errorCount)) {
// Permit it - but print the number of exceptions first // Permit it - but print the number of exceptions first
reportWarning(this, "Internal exception count: " + errorCount + "!"); reportWarning(this, Report.newBuilder(REPORT_EXCEPTION_COUNT).messageParam(errorCount).build());
} else { } else {
// NEVER SPAM THE CONSOLE // NEVER SPAM THE CONSOLE
return; return;
@ -222,27 +255,23 @@ public class DetailedErrorReporter implements ErrorReporter {
PrintWriter writer = new PrintWriter(text); PrintWriter writer = new PrintWriter(text);
// Helpful message // Helpful message
writer.println("[ProtocolLib] INTERNAL ERROR: " + message); writer.println("[" + pluginName + "] INTERNAL ERROR: " + report.getReportMessage());
writer.println("If this problem hasn't already been reported, please open a ticket"); writer.println("If this problem hasn't already been reported, please open a ticket");
writer.println("at " + supportURL + " with the following data:"); writer.println("at " + supportURL + " with the following data:");
// Now, let us print important exception information // Now, let us print important exception information
writer.println(" ===== STACK TRACE ====="); writer.println(" ===== STACK TRACE =====");
if (error != null) if (report.getException() != null) {
error.printStackTrace(writer); report.getException().printStackTrace(writer);
}
// Data dump! // Data dump!
writer.println(" ===== DUMP ====="); writer.println(" ===== DUMP =====");
// Relevant parameters // Relevant parameters
if (parameters != null && parameters.length > 0) { if (report.hasCallerParameters()) {
writer.println("Parameters:"); printParameters(writer, report.getCallerParameters());
// We *really* want to get as much information as possible
for (Object param : parameters) {
writer.println(addPrefix(getStringDescription(param), SECOND_LEVEL_PREFIX));
}
} }
// Global parameters // Global parameters
@ -270,7 +299,7 @@ public class DetailedErrorReporter implements ErrorReporter {
// Inform of this occurrence // Inform of this occurrence
if (ERROR_PERMISSION != null) { if (ERROR_PERMISSION != null) {
Bukkit.getServer().broadcast( Bukkit.getServer().broadcast(
String.format("Error %s (%s) occured in %s.", message, error, sender), String.format("Error %s (%s) occured in %s.", report.getReportMessage(), report.getException(), sender),
ERROR_PERMISSION ERROR_PERMISSION
); );
} }
@ -280,6 +309,23 @@ public class DetailedErrorReporter implements ErrorReporter {
logger.severe(addPrefix(text.toString(), prefix)); logger.severe(addPrefix(text.toString(), prefix));
} }
private String printParameters(Object... parameters) {
StringWriter writer = new StringWriter();
// Print and retrieve the string buffer
printParameters(new PrintWriter(writer), parameters);
return writer.toString();
}
private void printParameters(PrintWriter writer, Object[] parameters) {
writer.println("Parameters: ");
// We *really* want to get as much information as possible
for (Object param : parameters) {
writer.println(addPrefix(getStringDescription(param), SECOND_LEVEL_PREFIX));
}
}
/** /**
* Adds the given prefix to every line in the text. * Adds the given prefix to every line in the text.
* @param text - text to modify. * @param text - text to modify.
@ -290,8 +336,12 @@ public class DetailedErrorReporter implements ErrorReporter {
return text.replaceAll("(?m)^", prefix); return text.replaceAll("(?m)^", prefix);
} }
/**
* Retrieve a string representation of the given object.
* @param value - object to convert.
* @return String representation.
*/
protected String getStringDescription(Object value) { protected String getStringDescription(Object value) {
// We can't only rely on toString. // We can't only rely on toString.
if (value == null) { if (value == null) {
return "[NULL]"; return "[NULL]";
@ -324,63 +374,125 @@ public class DetailedErrorReporter implements ErrorReporter {
return test instanceof String || Primitives.isWrapperType(test.getClass()); return test instanceof String || Primitives.isWrapperType(test.getClass());
} }
/**
* Retrieve the current number of errors printed through {@link #reportDetailed(Object, Report)}.
* @return Number of errors printed.
*/
public int getErrorCount() { public int getErrorCount() {
return internalErrorCount.get(); return internalErrorCount.get();
} }
/**
* Set the number of errors printed.
* @param errorCount - new number of errors printed.
*/
public void setErrorCount(int errorCount) { public void setErrorCount(int errorCount) {
internalErrorCount.set(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() { public int getMaxErrorCount() {
return maxErrorCount; 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) { public void setMaxErrorCount(int maxErrorCount) {
this.maxErrorCount = maxErrorCount; this.maxErrorCount = maxErrorCount;
} }
/** /**
* Adds the given global parameter. It will be included in every error report. * 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 key - name of parameter.
* @param value - the global parameter itself. * @param value - the global parameter itself.
*/ */
public void addGlobalParameter(String key, Object value) { 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); 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) { public Object getGlobalParameter(String key) {
if (key == null)
throw new IllegalArgumentException("key cannot be NULL.");
return globalParameters.get(key); return globalParameters.get(key);
} }
/**
* Reset all global parameters.
*/
public void clearGlobalParameters() { public void clearGlobalParameters() {
globalParameters.clear(); globalParameters.clear();
} }
/**
* Retrieve a set of every registered global parameter.
* @return Set of all registered global parameters.
*/
public Set<String> globalParameters() { public Set<String> globalParameters() {
return globalParameters.keySet(); return globalParameters.keySet();
} }
/**
* Retrieve the support URL that will be added to all detailed reports.
* @return Support URL.
*/
public String getSupportURL() { public String getSupportURL() {
return supportURL; 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) { public void setSupportURL(String supportURL) {
this.supportURL = supportURL; this.supportURL = supportURL;
} }
/**
* Retrieve the prefix to apply to every line in the error reports.
* @return Error report prefix.
*/
public String getPrefix() { public String getPrefix() {
return prefix; return prefix;
} }
/**
* Set the prefix to apply to every line in the error reports.
* @param prefix - new prefix.
*/
public void setPrefix(String prefix) { public void setPrefix(String prefix) {
this.prefix = prefix; this.prefix = prefix;
} }
/**
* Retrieve the current logger that is used to print all reports.
* @return The current logger.
*/
public Logger getLogger() { public Logger getLogger() {
return logger; return logger;
} }
/**
* Set the current logger that is used to print all reports.
* @param logger - new logger.
*/
public void setLogger(Logger logger) { public void setLogger(Logger logger) {
this.logger = logger; this.logger = logger;
} }

Datei anzeigen

@ -19,10 +19,16 @@ 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.
*
* @author Kristian
*/
public interface ErrorReporter {
/**
* Prints a small minimal error report regarding an exception from another plugin.
* @param sender - the other plugin. * @param sender - the other plugin.
* @param methodName - name of the caller method. * @param methodName - name of the caller method.
* @param error - the exception itself. * @param error - the exception itself.
@ -30,7 +36,7 @@ public interface ErrorReporter {
public abstract void reportMinimal(Plugin sender, String methodName, Throwable error); public abstract void reportMinimal(Plugin sender, String methodName, Throwable error);
/** /**
* Prints a small minimal error report about an exception from another plugin. * Prints a small minimal error report regarding an exception from another plugin.
* @param sender - the other plugin. * @param sender - the other plugin.
* @param methodName - name of the caller method. * @param methodName - name of the caller method.
* @param error - the exception itself. * @param error - the exception itself.
@ -41,25 +47,28 @@ public interface ErrorReporter {
/** /**
* 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 message - error message. * @param report - an error report to include.
*/ */
public abstract void reportWarning(Object sender, String message); public abstract void reportWarning(Object sender, Report report);
/** /**
* 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 message - error message. * @param reportBuilder - an error report builder that will be used to get the report.
* @param error - the exception that was thrown.
*/ */
public abstract void reportWarning(Object sender, String message, Throwable error); public abstract void reportWarning(Object sender, ReportBuilder reportBuilder);
/** /**
* Prints a detailed error report about an unhandled exception. * Prints a detailed error report about an unhandled exception.
* @param sender - the object containing the caller method. * @param sender - the object containing the caller method.
* @param message - an error message to include. * @param report - an error report to include.
* @param error - the exception that was thrown in the caller method.
* @param parameters - parameters from the caller method.
*/ */
public abstract void reportDetailed(Object sender, String message, Throwable error, Object... parameters); 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.

Datei anzeigen

@ -25,6 +25,9 @@ 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.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.PacketConstructor.Unwrapper; import com.comphenix.protocol.injector.PacketConstructor.Unwrapper;
import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.instances.DefaultInstances; import com.comphenix.protocol.reflect.instances.DefaultInstances;
@ -42,8 +45,32 @@ import com.google.common.primitives.Primitives;
* @author Kristian * @author Kristian
*/ */
public class BukkitUnwrapper implements Unwrapper { public class BukkitUnwrapper implements Unwrapper {
public static final ReportType REPORT_ILLEGAL_ARGUMENT = new ReportType("Illegal argument.");
public static final ReportType REPORT_SECURITY_LIMITATION = new ReportType("Security limitation.");
public static final ReportType REPORT_CANNOT_FIND_UNWRAP_METHOD = new ReportType("Cannot find method.");
public static final ReportType REPORT_CANNOT_READ_FIELD_HANDLE = new ReportType("Cannot read field 'handle'.");
private static Map<Class<?>, Unwrapper> unwrapperCache = new ConcurrentHashMap<Class<?>, Unwrapper>(); private static Map<Class<?>, Unwrapper> unwrapperCache = new ConcurrentHashMap<Class<?>, Unwrapper>();
// The current error reporter
private final ErrorReporter reporter;
/**
* Construct a new Bukkit unwrapper with ProtocolLib's default error reporter.
*/
public BukkitUnwrapper() {
this(ProtocolLibrary.getErrorReporter());
}
/**
* Construct a new Bukkit unwrapper with the given error reporter.
* @param reporter - the error reporter to use.
*/
public BukkitUnwrapper(ErrorReporter reporter) {
this.reporter = reporter;
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public Object unwrapItem(Object wrappedObject) { public Object unwrapItem(Object wrappedObject) {
@ -111,8 +138,9 @@ public class BukkitUnwrapper implements Unwrapper {
return find.invoke(wrappedObject); return find.invoke(wrappedObject);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
ProtocolLibrary.getErrorReporter().reportDetailed( reporter.reportDetailed(this,
this, "Illegal argument.", e, wrappedObject, find); Report.newBuilder(REPORT_ILLEGAL_ARGUMENT).error(e).callerParam(wrappedObject, find)
);
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
// Should not occur either // Should not occur either
return null; return null;
@ -129,7 +157,9 @@ public class BukkitUnwrapper implements Unwrapper {
return methodUnwrapper; return methodUnwrapper;
} catch (SecurityException e) { } catch (SecurityException e) {
ProtocolLibrary.getErrorReporter().reportDetailed(this, "Security limitation.", e, type.getName()); reporter.reportDetailed(this,
Report.newBuilder(REPORT_SECURITY_LIMITATION).error(e).callerParam(type)
);
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException e) {
// Try getting the field unwrapper too // Try getting the field unwrapper too
Unwrapper fieldUnwrapper = getFieldUnwrapper(type); Unwrapper fieldUnwrapper = getFieldUnwrapper(type);
@ -137,7 +167,8 @@ public class BukkitUnwrapper implements Unwrapper {
if (fieldUnwrapper != null) if (fieldUnwrapper != null)
return fieldUnwrapper; return fieldUnwrapper;
else else
ProtocolLibrary.getErrorReporter().reportDetailed(this, "Cannot find method.", e, type.getName()); reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_FIND_UNWRAP_METHOD).error(e).callerParam(type));
} }
// Default method // Default method
@ -160,8 +191,9 @@ public class BukkitUnwrapper implements Unwrapper {
try { try {
return FieldUtils.readField(find, wrappedObject, true); return FieldUtils.readField(find, wrappedObject, true);
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
ProtocolLibrary.getErrorReporter().reportDetailed( reporter.reportDetailed(this,
this, "Cannot read field 'handle'.", e, wrappedObject, find.getName()); Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).error(e).callerParam(wrappedObject, find)
);
return null; return null;
} }
} }
@ -172,9 +204,9 @@ public class BukkitUnwrapper implements Unwrapper {
} else { } else {
// Inform about this too // Inform about this too
ProtocolLibrary.getErrorReporter().reportDetailed( reporter.reportDetailed(this,
this, "Could not find field 'handle'.", Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).callerParam(find)
new Exception("Unable to find 'handle'"), type.getName()); );
return null; return null;
} }
} }

Datei anzeigen

@ -52,8 +52,9 @@ public interface ListenerInvoker {
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);

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,11 +63,28 @@ 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.
@ -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

@ -25,10 +25,17 @@ import java.util.Set;
import net.sf.cglib.proxy.Factory; import net.sf.cglib.proxy.Factory;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.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.fuzzy.FuzzyClassContract;
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.TroveWrapper;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
@ -39,6 +46,13 @@ import com.google.common.collect.ImmutableSet;
*/ */
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
public class PacketRegistry { public class PacketRegistry {
public static final ReportType REPORT_CANNOT_CORRECT_TROVE_MAP = new ReportType("Unable to correct no entry value.");
public static final ReportType REPORT_INSUFFICIENT_SERVER_PACKETS = new ReportType("Too few server packets detected: %s");
public static final ReportType REPORT_INSUFFICIENT_CLIENT_PACKETS = new ReportType("Too few client packets detected: %s");
private static final int MIN_SERVER_PACKETS = 5;
private static final int MIN_CLIENT_PACKETS = 5;
// Fuzzy reflection // Fuzzy reflection
private static FuzzyReflection packetRegistry; private static FuzzyReflection packetRegistry;
@ -67,6 +81,14 @@ public class PacketRegistry {
try { try {
Field packetsField = getPacketRegistry().getFieldByType("packetsField", Map.class); Field packetsField = getPacketRegistry().getFieldByType("packetsField", Map.class);
packetToID = (Map<Class, Integer>) FieldUtils.readStaticField(packetsField, true); packetToID = (Map<Class, Integer>) FieldUtils.readStaticField(packetsField, true);
} catch (IllegalArgumentException e) {
// Spigot 1.2.5 MCPC workaround
try {
packetToID = getSpigotWrapper();
} catch (Exception e2) {
// Very bad indeed
throw new IllegalArgumentException(e.getMessage() + "; Spigot workaround failed.", e2);
}
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
throw new RuntimeException("Unable to retrieve the packetClassToIdMap", e); throw new RuntimeException("Unable to retrieve the packetClassToIdMap", e);
@ -76,6 +98,41 @@ public class PacketRegistry {
return packetToID; return packetToID;
} }
private static Map<Class, Integer> getSpigotWrapper() throws IllegalAccessException {
// If it talks like a duck, etc.
// Perhaps it would be nice to have a proper duck typing library as well
FuzzyClassContract mapLike = FuzzyClassContract.newBuilder().
method(FuzzyMethodContract.newBuilder().
nameExact("size").returnTypeExact(int.class)).
method(FuzzyMethodContract.newBuilder().
nameExact("put").parameterCount(2)).
method(FuzzyMethodContract.newBuilder().
nameExact("get").parameterCount(1)).
build();
Field packetsField = getPacketRegistry().getField(
FuzzyFieldContract.newBuilder().typeMatches(mapLike).build());
Object troveMap = FieldUtils.readStaticField(packetsField, true);
// Check for stupid no_entry_values
try {
Field field = FieldUtils.getField(troveMap.getClass(), "no_entry_value", true);
Integer value = (Integer) FieldUtils.readField(field, troveMap, true);
if (value >= 0 && value < 256) {
// Someone forgot to set the no entry value. Let's help them.
FieldUtils.writeField(field, troveMap, -1);
}
} catch (IllegalArgumentException e) {
// Whatever
ProtocolLibrary.getErrorReporter().reportWarning(PacketRegistry.class,
Report.newBuilder(REPORT_CANNOT_CORRECT_TROVE_MAP).error(e));
}
// We'll assume this a Trove map
return TroveWrapper.getDecoratedMap(troveMap);
}
/** /**
* Retrieve the cached fuzzy reflection instance allowing access to the packet registry. * Retrieve the cached fuzzy reflection instance allowing access to the packet registry.
* @return Reflected packet registry. * @return Reflected packet registry.
@ -109,6 +166,10 @@ public class PacketRegistry {
*/ */
public static Set<Integer> getServerPackets() throws FieldAccessException { public static Set<Integer> getServerPackets() throws FieldAccessException {
initializeSets(); initializeSets();
// Sanity check. This is impossible!
if (serverPackets != null && serverPackets.size() < MIN_SERVER_PACKETS)
throw new FieldAccessException("Server packet list is empty. Seems to be unsupported");
return serverPackets; return serverPackets;
} }
@ -119,6 +180,10 @@ public class PacketRegistry {
*/ */
public static Set<Integer> getClientPackets() throws FieldAccessException { public static Set<Integer> getClientPackets() throws FieldAccessException {
initializeSets(); initializeSets();
// As above
if (clientPackets != null && clientPackets.size() < MIN_CLIENT_PACKETS)
throw new FieldAccessException("Client packet list is empty. Seems to be unsupported");
return clientPackets; return clientPackets;
} }
@ -140,6 +205,16 @@ public class PacketRegistry {
serverPackets = ImmutableSet.copyOf(serverPacketsRef); serverPackets = ImmutableSet.copyOf(serverPacketsRef);
clientPackets = ImmutableSet.copyOf(clientPacketsRef); clientPackets = ImmutableSet.copyOf(clientPacketsRef);
// Check sizes
if (serverPackets.size() < MIN_SERVER_PACKETS)
ProtocolLibrary.getErrorReporter().reportWarning(
PacketRegistry.class, Report.newBuilder(REPORT_INSUFFICIENT_SERVER_PACKETS).messageParam(serverPackets.size())
);
if (clientPackets.size() < MIN_CLIENT_PACKETS)
ProtocolLibrary.getErrorReporter().reportWarning(
PacketRegistry.class, Report.newBuilder(REPORT_INSUFFICIENT_CLIENT_PACKETS).messageParam(clientPackets.size())
);
} else { } else {
throw new FieldAccessException("Cannot retrieve packet client/server sets."); throw new FieldAccessException("Cannot retrieve packet client/server sets.");
} }

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. try {
Field intHashMapField = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true). lookup = new IntHashMapLookup();
getFieldByType(MinecraftReflection.getMinecraftObjectRegex()); } catch (Exception e1) {
try { try {
intHashMap = FieldUtils.readField(intHashMapField, (Object) null, true); lookup = new ArrayLookup();
} catch (IllegalArgumentException e) { } catch (Exception e2) {
throw new RuntimeException("Minecraft is incompatible.", e); // 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);
putMethod.invoke(intHashMap, packetID, proxy); lookup.setLookup(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
try {
Class old = previous.get(packetID); Class old = previous.get(packetID);
Class proxy = PacketRegistry.getPacketClassFromID(packetID); Class proxy = PacketRegistry.getPacketClassFromID(packetID);
putMethod.invoke(intHashMap, packetID, old); lookup.setLookup(packetID, old);
previous.remove(packetID); previous.remove(packetID);
registry.remove(proxy); registry.remove(proxy);
overwritten.remove(packetID); overwritten.remove(packetID);
return true; 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

@ -22,6 +22,8 @@ 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.error.Report;
import com.comphenix.protocol.error.ReportType;
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.google.common.collect.MapMaker; import com.google.common.collect.MapMaker;
@ -30,6 +32,8 @@ import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy; import net.sf.cglib.proxy.MethodProxy;
class ReadPacketModifier implements MethodInterceptor { class ReadPacketModifier implements MethodInterceptor {
public static final ReportType REPORT_CANNOT_HANDLE_CLIENT_PACKET = new ReportType("Cannot handle client packet.");
// A cancel marker // A cancel marker
private static final Object CANCEL_MARKER = new Object(); private static final Object CANCEL_MARKER = new Object();
@ -122,7 +126,9 @@ class ReadPacketModifier implements MethodInterceptor {
} }
} catch (Throwable e) { } catch (Throwable e) {
// Minecraft cannot handle this error // Minecraft cannot handle this error
reporter.reportDetailed(this, "Cannot handle client packet.", e, args[0]); reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_HANDLE_CLIENT_PACKET).callerParam(args[0]).error(e)
);
} }
} }
return returnValue; return returnValue;

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,9 +114,6 @@ 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
@ -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,6 +160,15 @@ 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
@ -164,11 +176,17 @@ class InjectedArrayList extends ArrayList<Object> {
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

@ -27,6 +27,8 @@ 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.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.server.AbstractInputStreamLookup; import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.FuzzyReflection;
@ -40,6 +42,19 @@ import com.comphenix.protocol.utility.MinecraftReflection;
* @author Kristian * @author Kristian
*/ */
class InjectedServerConnection { class InjectedServerConnection {
// A number of things can go wrong ...
public static final ReportType REPORT_CANNOT_FIND_MINECRAFT_SERVER = new ReportType("Cannot extract minecraft server from Bukkit.");
public static final ReportType REPORT_CANNOT_INJECT_SERVER_CONNECTION = new ReportType("Cannot inject into server connection. Bad things will happen.");
public static final ReportType REPORT_CANNOT_FIND_LISTENER_THREAD = new ReportType("Cannot find listener thread in MinecraftServer.");
public static final ReportType REPORT_CANNOT_READ_LISTENER_THREAD = new ReportType("Unable to read the listener thread.");
public static final ReportType REPORT_CANNOT_FIND_SERVER_CONNECTION = new ReportType("Unable to retrieve server connection");
public static final ReportType REPORT_UNEXPECTED_THREAD_COUNT = new ReportType("Unexpected number of threads in %s: %s");
public static final ReportType REPORT_CANNOT_FIND_NET_HANDLER_THREAD = new ReportType("Unable to retrieve net handler thread.");
public static final ReportType REPORT_INSUFFICENT_THREAD_COUNT = new ReportType("Unable to inject %s lists in %s.");
public static final ReportType REPORT_CANNOT_COPY_OLD_TO_NEW = new ReportType("Cannot copy old %s to new.");
private static Field listenerThreadField; private static Field listenerThreadField;
private static Field minecraftServerField; private static Field minecraftServerField;
@ -74,7 +89,6 @@ class InjectedServerConnection {
} }
public void injectList() { public void injectList() {
// Only execute this method once // Only execute this method once
if (!hasAttempted) if (!hasAttempted)
hasAttempted = true; hasAttempted = true;
@ -88,7 +102,7 @@ class InjectedServerConnection {
try { try {
minecraftServer = FieldUtils.readField(minecraftServerField, server, true); minecraftServer = FieldUtils.readField(minecraftServerField, server, true);
} catch (IllegalAccessException e1) { } catch (IllegalAccessException e1) {
reporter.reportWarning(this, "Cannot extract minecraft server from Bukkit."); reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_FIND_MINECRAFT_SERVER));
return; return;
} }
@ -107,7 +121,7 @@ class InjectedServerConnection {
} catch (Exception e) { } catch (Exception e) {
// Oh damn - inform the player // Oh damn - inform the player
reporter.reportDetailed(this, "Cannot inject into server connection. Bad things will happen.", e); reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_INJECT_SERVER_CONNECTION).error(e));
} }
} }
@ -117,7 +131,9 @@ class InjectedServerConnection {
listenerThreadField = FuzzyReflection.fromObject(minecraftServer). listenerThreadField = FuzzyReflection.fromObject(minecraftServer).
getFieldByType("networkListenThread", MinecraftReflection.getNetworkListenThreadClass()); getFieldByType("networkListenThread", MinecraftReflection.getNetworkListenThreadClass());
} catch (RuntimeException e) { } catch (RuntimeException e) {
reporter.reportDetailed(this, "Cannot find listener thread in MinecraftServer.", e, minecraftServer); reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_FIND_LISTENER_THREAD).callerParam(minecraftServer).error(e)
);
return; return;
} }
@ -127,7 +143,7 @@ class InjectedServerConnection {
try { try {
listenerThread = listenerThreadField.get(minecraftServer); listenerThread = listenerThreadField.get(minecraftServer);
} catch (Exception e) { } catch (Exception e) {
reporter.reportWarning(this, "Unable to read the listener thread.", e); reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_READ_LISTENER_THREAD).error(e));
return; return;
} }
@ -140,14 +156,15 @@ class InjectedServerConnection {
} }
private void injectServerConnection() { private void injectServerConnection() {
Object serverConnection = null; Object serverConnection = null;
// Careful - we might fail // Careful - we might fail
try { try {
serverConnection = serverConnectionMethod.invoke(minecraftServer); serverConnection = serverConnectionMethod.invoke(minecraftServer);
} catch (Exception ex) { } catch (Exception e) {
reporter.reportDetailed(this, "Unable to retrieve server connection", ex, minecraftServer); reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_FIND_SERVER_CONNECTION).callerParam(minecraftServer).error(e)
);
return; return;
} }
@ -160,7 +177,9 @@ class InjectedServerConnection {
// Verify the field count // Verify the field count
if (matches.size() != 1) if (matches.size() != 1)
reporter.reportWarning(this, "Unexpected number of threads in " + serverConnection.getClass().getName()); reporter.reportWarning(this,
Report.newBuilder(REPORT_UNEXPECTED_THREAD_COUNT).messageParam(serverConnection.getClass(), matches.size())
);
else else
dedicatedThreadField = matches.get(0); dedicatedThreadField = matches.get(0);
} }
@ -175,7 +194,7 @@ class InjectedServerConnection {
injectEveryListField(dedicatedThread, 1); injectEveryListField(dedicatedThread, 1);
} }
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
reporter.reportWarning(this, "Unable to retrieve net handler thread.", e); reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_FIND_NET_HANDLER_THREAD).error(e));
} }
injectIntoList(serverConnection, listField); injectIntoList(serverConnection, listField);
@ -201,7 +220,7 @@ class InjectedServerConnection {
// Warn about unexpected errors // Warn about unexpected errors
if (lists.size() < minimum) { if (lists.size() < minimum) {
reporter.reportWarning(this, "Unable to inject " + minimum + " lists in " + container.getClass().getName()); reporter.reportWarning(this, Report.newBuilder(REPORT_INSUFFICENT_THREAD_COUNT).messageParam(minimum, container.getClass()));
} }
} }
@ -241,8 +260,9 @@ class InjectedServerConnection {
try { try {
writer.copyTo(inserting, replacement, inserting.getClass()); writer.copyTo(inserting, replacement, inserting.getClass());
} catch (Throwable e) { } catch (Throwable e) {
reporter.reportDetailed(InjectedServerConnection.this, "Cannot copy old " + inserting + reporter.reportDetailed(InjectedServerConnection.this,
" to new.", e, inserting, replacement); Report.newBuilder(REPORT_CANNOT_COPY_OLD_TO_NEW).messageParam(inserting).callerParam(inserting, replacement).error(e)
);
} }
} }
} }

Datei anzeigen

@ -23,6 +23,8 @@ 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.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.GamePhase; import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler.ConflictStrategy; import com.comphenix.protocol.injector.player.PlayerInjectionHandler.ConflictStrategy;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory; import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
@ -35,6 +37,9 @@ import com.google.common.collect.Maps;
* @author Kristian * @author Kristian
*/ */
class NetLoginInjector { class NetLoginInjector {
public static final ReportType REPORT_CANNOT_HOOK_LOGIN_HANDLER = new ReportType("Unable to hook %s.");
public static final ReportType REPORT_CANNOT_CLEANUP_LOGIN_HANDLER = new ReportType("Cannot cleanup %s.");
private ConcurrentMap<Object, PlayerInjector> injectedLogins = Maps.newConcurrentMap(); private ConcurrentMap<Object, PlayerInjector> injectedLogins = Maps.newConcurrentMap();
// Handles every hook // Handles every hook
@ -83,8 +88,12 @@ class NetLoginInjector {
} catch (Throwable e) { } catch (Throwable e) {
// Minecraft can't handle this, so we'll deal with it here // Minecraft can't handle this, so we'll deal with it here
reporter.reportDetailed(this, "Unable to hook " + reporter.reportDetailed(this,
MinecraftReflection.getNetLoginHandlerName() + ".", e, inserting, injectionHandler); Report.newBuilder(REPORT_CANNOT_HOOK_LOGIN_HANDLER).
messageParam(MinecraftReflection.getNetLoginHandlerName()).
callerParam(inserting, injectionHandler).
error(e)
);
return inserting; return inserting;
} }
} }
@ -122,8 +131,12 @@ class NetLoginInjector {
} catch (Throwable e) { } catch (Throwable e) {
// Don't leak this to Minecraft // Don't leak this to Minecraft
reporter.reportDetailed(this, "Cannot cleanup " + reporter.reportDetailed(this,
MinecraftReflection.getNetLoginHandlerName() + ".", e, removing); Report.newBuilder(REPORT_CANNOT_CLEANUP_LOGIN_HANDLER).
messageParam(MinecraftReflection.getNetLoginHandlerName()).
callerParam(removing).
error(e)
);
} }
} }
} }

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,7 +128,11 @@ class NetworkFieldInjector extends PlayerInjector {
} }
@Override @Override
public UnsupportedListener checkListener(PacketListener listener) { public UnsupportedListener checkListener(MinecraftVersion version, PacketListener listener) {
if (version != null && version.compareTo(safeVersion) > 0) {
return null;
} else {
int[] unsupported = { Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK }; int[] unsupported = { Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK };
// Unfortunately, we don't support chunk packets // Unfortunately, we don't support chunk packets
@ -132,6 +142,7 @@ class NetworkFieldInjector extends PlayerInjector {
return null; return null;
} }
} }
}
@Override @Override
public void injectManager() { public void injectManager() {

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,7 +124,11 @@ public class NetworkObjectInjector extends PlayerInjector {
} }
@Override @Override
public UnsupportedListener checkListener(PacketListener listener) { public UnsupportedListener checkListener(MinecraftVersion version, PacketListener listener) {
if (version != null && version.compareTo(safeVersion) > 0) {
return null;
} else {
int[] unsupported = { Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK }; int[] unsupported = { Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK };
// Unfortunately, we don't support chunk packets // Unfortunately, we don't support chunk packets
@ -127,6 +138,7 @@ public class NetworkObjectInjector extends PlayerInjector {
return null; return null;
} }
} }
}
@Override @Override
public void injectManager() { public void injectManager() {

Datei anzeigen

@ -20,12 +20,16 @@ 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 java.util.Arrays;
import net.sf.cglib.proxy.*; import net.sf.cglib.proxy.*;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import com.comphenix.protocol.concurrency.IntegerSet; import com.comphenix.protocol.concurrency.IntegerSet;
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.PacketListener; 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;
@ -38,6 +42,7 @@ import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.reflect.instances.ExistingGenerator; import com.comphenix.protocol.reflect.instances.ExistingGenerator;
import com.comphenix.protocol.utility.MinecraftMethods; import com.comphenix.protocol.utility.MinecraftMethods;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
/** /**
* Represents a player hook into the NetServerHandler class. * Represents a player hook into the NetServerHandler class.
@ -45,8 +50,13 @@ import com.comphenix.protocol.utility.MinecraftReflection;
* @author Kristian * @author Kristian
*/ */
class NetworkServerInjector extends PlayerInjector { class NetworkServerInjector extends PlayerInjector {
// Disconnected field
public static final ReportType REPORT_ASSUMING_DISCONNECT_FIELD = new ReportType("Unable to find 'disconnected' field. Assuming %s.");
public static final ReportType REPORT_DISCONNECT_FIELD_MISSING = new ReportType("Cannot find disconnected field. Is ProtocolLib up to date?");
public static final ReportType REPORT_DISCONNECT_FIELD_FAILURE = new ReportType("Unable to update disconnected field. Player quit event may be sent twice.");
private volatile static CallbackFilter callbackFilter; private volatile static CallbackFilter callbackFilter;
private volatile static boolean foundSendPacket;
private volatile static Field disconnectField; private volatile static Field disconnectField;
private InjectedServerConnection serverInjection; private InjectedServerConnection serverInjection;
@ -168,11 +178,13 @@ class NetworkServerInjector extends PlayerInjector {
callbackFilter = new CallbackFilter() { callbackFilter = new CallbackFilter() {
@Override @Override
public int accept(Method method) { public int accept(Method method) {
if (method.equals(sendPacket)) if (isCallableEqual(sendPacket, method)) {
foundSendPacket = true;
return 0; return 0;
else } else {
return 1; return 1;
} }
}
}; };
} }
@ -204,8 +216,10 @@ class NetworkServerInjector extends PlayerInjector {
// Inject it now // Inject it now
if (proxyObject != null) { if (proxyObject != null) {
// This will be done by InjectedServerConnection instead // Did we override a sendPacket method?
//copyTo(serverHandler, proxyObject); if (!foundSendPacket) {
throw new IllegalArgumentException("Unable to find a sendPacket method in " + serverClass);
}
serverInjection.replaceServerHandler(serverHandler, proxyObject); serverInjection.replaceServerHandler(serverHandler, proxyObject);
serverHandlerRef.setValue(proxyObject); serverHandlerRef.setValue(proxyObject);
@ -215,6 +229,20 @@ class NetworkServerInjector extends PlayerInjector {
} }
} }
/**
* Determine if the two methods are equal in terms of call semantics.
* <p>
* Two methods are equal if they have the same name, parameter types and return type.
* @param first - first method.
* @param second - second method.
* @return TRUE if they are, FALSE otherwise.
*/
private boolean isCallableEqual(Method first, Method second) {
return first.getName().equals(second.getName()) &&
first.getReturnType().equals(second.getReturnType()) &&
Arrays.equals(first.getParameterTypes(), second.getParameterTypes());
}
private Object getProxyServerHandler() { private Object getProxyServerHandler() {
if (proxyServerField != null && !proxyServerField.equals(serverHandlerRef.getField())) { if (proxyServerField != null && !proxyServerField.equals(serverHandlerRef.getField())) {
try { try {
@ -228,7 +256,7 @@ class NetworkServerInjector extends PlayerInjector {
} }
private Class<?> getFirstMinecraftSuperClass(Class<?> clazz) { private Class<?> getFirstMinecraftSuperClass(Class<?> clazz) {
if (clazz.getName().startsWith(MinecraftReflection.getMinecraftPackage())) if (MinecraftReflection.isMinecraftClass(clazz))
return clazz; return clazz;
else if (clazz.equals(Object.class)) else if (clazz.equals(Object.class))
return clazz; return clazz;
@ -288,7 +316,7 @@ class NetworkServerInjector extends PlayerInjector {
// Assume it's the first ... // Assume it's the first ...
if (disconnectField == null) { if (disconnectField == null) {
disconnectField = FuzzyReflection.fromObject(handler).getFieldByType("disconnected", boolean.class); disconnectField = FuzzyReflection.fromObject(handler).getFieldByType("disconnected", boolean.class);
reporter.reportWarning(this, "Unable to find 'disconnected' field. Assuming " + disconnectField); reporter.reportWarning(this, Report.newBuilder(REPORT_ASSUMING_DISCONNECT_FIELD).messageParam(disconnectField));
// Try again // Try again
if (disconnectField != null) { if (disconnectField != null) {
@ -298,15 +326,15 @@ class NetworkServerInjector extends PlayerInjector {
} }
// This is really bad // This is really bad
reporter.reportDetailed(this, "Cannot find disconnected field. Is ProtocolLib up to date?", e); reporter.reportDetailed(this, Report.newBuilder(REPORT_DISCONNECT_FIELD_MISSING).error(e));
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
reporter.reportWarning(this, "Unable to update disconnected field. Player quit event may be sent twice."); reporter.reportWarning(this, Report.newBuilder(REPORT_DISCONNECT_FIELD_FAILURE).error(e));
} }
} }
@Override @Override
public UnsupportedListener checkListener(PacketListener listener) { public UnsupportedListener checkListener(MinecraftVersion version, PacketListener listener) {
// We support everything // We support everything
return null; return null;
} }

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

@ -30,6 +30,8 @@ import org.bukkit.entity.Player;
import com.comphenix.protocol.Packets; import com.comphenix.protocol.Packets;
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.PacketContainer; import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.events.PacketListener;
@ -43,8 +45,23 @@ 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.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
abstract class PlayerInjector implements SocketInjector { public abstract class PlayerInjector implements SocketInjector {
// Disconnect method related reports
public static final ReportType REPORT_ASSUME_DISCONNECT_METHOD = new ReportType("Cannot find disconnect method by name. Assuming %s.");
public static final ReportType REPORT_INVALID_ARGUMENT_DISCONNECT = new ReportType("Invalid argument passed to disconnect method: %s");
public static final ReportType REPORT_CANNOT_ACCESS_DISCONNECT = new ReportType("Unable to access disconnect method.");
public static final ReportType REPORT_CANNOT_CLOSE_SOCKET = new ReportType("Unable to close socket.");
public static final ReportType REPORT_ACCESS_DENIED_CLOSE_SOCKET = new ReportType("Insufficient permissions. Cannot close socket.");
public static final ReportType REPORT_DETECTED_CUSTOM_SERVER_HANDLER =
new ReportType("Detected server handler proxy type by another plugin. Conflict may occur!");
public static final ReportType REPORT_CANNOT_PROXY_SERVER_HANDLER = new ReportType("Unable to load server handler from proxy type.");
public static final ReportType REPORT_CANNOT_UPDATE_PLAYER = new ReportType("Cannot update player in PlayerEvent.");
public static final ReportType REPORT_CANNOT_HANDLE_PACKET = new ReportType("Cannot handle server packet.");
// Net login handler stuff // Net login handler stuff
private static Field netLoginNetworkField; private static Field netLoginNetworkField;
@ -304,7 +321,7 @@ abstract class PlayerInjector implements SocketInjector {
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
// Just assume it's the first String method // Just assume it's the first String method
disconnect = FuzzyReflection.fromObject(handler).getMethodByParameters("disconnect", String.class); disconnect = FuzzyReflection.fromObject(handler).getMethodByParameters("disconnect", String.class);
reporter.reportWarning(this, "Cannot find disconnect method by name. Assuming " + disconnect); reporter.reportWarning(this, Report.newBuilder(REPORT_ASSUME_DISCONNECT_METHOD).messageParam(disconnect));
} }
// Save the method for later // Save the method for later
@ -318,9 +335,9 @@ abstract class PlayerInjector implements SocketInjector {
disconnect.invoke(handler, message); disconnect.invoke(handler, message);
return; return;
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
reporter.reportDetailed(this, "Invalid argument passed to disconnect method: " + message, e, handler); reporter.reportDetailed(this, Report.newBuilder(REPORT_INVALID_ARGUMENT_DISCONNECT).error(e).messageParam(message).callerParam(handler));
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
reporter.reportWarning(this, "Unable to access disconnect method.", e); reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_ACCESS_DISCONNECT).error(e));
} }
} }
@ -331,16 +348,15 @@ abstract class PlayerInjector implements SocketInjector {
try { try {
socket.close(); socket.close();
} catch (IOException e) { } catch (IOException e) {
reporter.reportDetailed(this, "Unable to close socket.", e, socket); reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_CLOSE_SOCKET).error(e).callerParam(socket));
} }
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
reporter.reportWarning(this, "Insufficient permissions. Cannot close socket.", e); reporter.reportWarning(this, Report.newBuilder(REPORT_ACCESS_DENIED_CLOSE_SOCKET).error(e));
} }
} }
private Field getProxyField(Object notchEntity, Field serverField) { private Field getProxyField(Object notchEntity, Field serverField) {
try { try {
Object handler = FieldUtils.readField(serverHandlerField, notchEntity, true); Object handler = FieldUtils.readField(serverHandlerField, notchEntity, true);
@ -352,7 +368,7 @@ abstract class PlayerInjector implements SocketInjector {
return null; return null;
hasProxyType = true; hasProxyType = true;
reporter.reportWarning(this, "Detected server handler proxy type by another plugin. Conflict may occur!"); reporter.reportWarning(this, Report.newBuilder(REPORT_DETECTED_CUSTOM_SERVER_HANDLER).callerParam(serverField));
// No? Is it a Proxy type? // No? Is it a Proxy type?
try { try {
@ -367,7 +383,7 @@ abstract class PlayerInjector implements SocketInjector {
} }
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
reporter.reportWarning(this, "Unable to load server handler from proxy type."); reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_PROXY_SERVER_HANDLER).error(e).callerParam(notchEntity, serverField));
} }
// Nope, just go with it // Nope, just go with it
@ -508,11 +524,13 @@ abstract class PlayerInjector implements SocketInjector {
* Invoked before a new listener is registered. * Invoked before a new listener is registered.
* <p> * <p>
* The player injector should only return a non-null value if some or all of the packet IDs are unsupported. * The player injector should only return a non-null value if some or all of the packet IDs are unsupported.
* @param version
* *
* @param version - the current Minecraft version, or NULL if unknown.
* @param listener - the listener that is about to be registered. * @param listener - the listener that is about to be registered.
* @return A error message with the unsupported packet IDs, or NULL if this listener is valid. * @return A error message with the unsupported packet IDs, or NULL if this listener is valid.
*/ */
public abstract UnsupportedListener checkListener(PacketListener listener); public abstract UnsupportedListener checkListener(MinecraftVersion version, PacketListener listener);
/** /**
* Allows a packet to be sent by the listeners. * Allows a packet to be sent by the listeners.
@ -531,7 +549,7 @@ abstract class PlayerInjector implements SocketInjector {
try { try {
updatedPlayer = (Player) MinecraftReflection.getBukkitEntity(getEntityPlayer(getNetHandler())); updatedPlayer = (Player) MinecraftReflection.getBukkitEntity(getEntityPlayer(getNetHandler()));
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
reporter.reportDetailed(this, "Cannot update player in PlayerEvent.", e, packet); reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_UPDATE_PLAYER).error(e).callerParam(packet));
} }
} }
@ -556,7 +574,7 @@ abstract class PlayerInjector implements SocketInjector {
} }
} catch (Throwable e) { } catch (Throwable e) {
reporter.reportDetailed(this, "Cannot handle server packet.", e, packet); reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_HANDLE_PACKET).error(e).callerParam(packet));
} }
return packet; return packet;
@ -639,10 +657,7 @@ abstract class PlayerInjector implements SocketInjector {
// Do nothing // Do nothing
} }
/** @Override
* Set the real Bukkit player that we will use.
* @param updatedPlayer - the real Bukkit player.
*/
public void setUpdatedPlayer(Player updatedPlayer) { public void setUpdatedPlayer(Player updatedPlayer) {
this.updatedPlayer = updatedPlayer; this.updatedPlayer = updatedPlayer;
} }

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

@ -34,6 +34,8 @@ import com.comphenix.protocol.Packets;
import com.comphenix.protocol.concurrency.BlockingHashMap; import com.comphenix.protocol.concurrency.BlockingHashMap;
import com.comphenix.protocol.concurrency.IntegerSet; import com.comphenix.protocol.concurrency.IntegerSet;
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.PacketAdapter; import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.events.PacketListener;
@ -42,9 +44,11 @@ import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PlayerLoggedOutException; import com.comphenix.protocol.injector.PlayerLoggedOutException;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
import com.comphenix.protocol.injector.server.AbstractInputStreamLookup; import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
import com.comphenix.protocol.injector.server.BukkitSocketInjector;
import com.comphenix.protocol.injector.server.InputStreamLookupBuilder; import com.comphenix.protocol.injector.server.InputStreamLookupBuilder;
import com.comphenix.protocol.injector.server.SocketInjector; import com.comphenix.protocol.injector.server.SocketInjector;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.cache.Cache; import com.google.common.cache.Cache;
@ -57,6 +61,16 @@ import com.google.common.collect.Maps;
* @author Kristian * @author Kristian
*/ */
class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
// Warnings and errors
public static final ReportType REPORT_UNSUPPPORTED_LISTENER = new ReportType("Cannot fully register listener for %s: %s");
// Fallback to older player hook types
public static final ReportType REPORT_PLAYER_HOOK_FAILED = new ReportType("Player hook %s failed.");
public static final ReportType REPORT_SWITCHED_PLAYER_HOOK = new ReportType("Switching to %s instead.");
public static final ReportType REPORT_HOOK_CLEANUP_FAILED = new ReportType("Cleaing up after player hook failed.");
public static final ReportType REPORT_CANNOT_REVERT_HOOK = new ReportType("Unable to fully revert old injector. May cause conflicts.");
// Server connection injection // Server connection injection
private InjectedServerConnection serverInjection; private InjectedServerConnection serverInjection;
@ -91,6 +105,9 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
// Used to invoke events // Used to invoke events
private ListenerInvoker invoker; private ListenerInvoker invoker;
// Current Minecraft version
private MinecraftVersion version;
// Enabled packet filters // Enabled packet filters
private IntegerSet sendingFilters = new IntegerSet(Packets.MAXIMUM_PACKET_ID + 1); private IntegerSet sendingFilters = new IntegerSet(Packets.MAXIMUM_PACKET_ID + 1);
@ -105,13 +122,14 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
public ProxyPlayerInjectionHandler( public ProxyPlayerInjectionHandler(
ClassLoader classLoader, ErrorReporter reporter, Predicate<GamePhase> injectionFilter, ClassLoader classLoader, ErrorReporter reporter, Predicate<GamePhase> injectionFilter,
ListenerInvoker invoker, Set<PacketListener> packetListeners, Server server) { ListenerInvoker invoker, Set<PacketListener> packetListeners, Server server, MinecraftVersion version) {
this.classLoader = classLoader; this.classLoader = classLoader;
this.reporter = reporter; this.reporter = reporter;
this.invoker = invoker; this.invoker = invoker;
this.injectionFilter = injectionFilter; this.injectionFilter = injectionFilter;
this.packetListeners = packetListeners; this.packetListeners = packetListeners;
this.version = version;
this.inputStreamLookup = InputStreamLookupBuilder.newBuilder(). this.inputStreamLookup = InputStreamLookupBuilder.newBuilder().
server(server). server(server).
@ -349,8 +367,9 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
} catch (Exception e) { } catch (Exception e) {
// Mark this injection attempt as a failure // Mark this injection attempt as a failure
reporter.reportDetailed(this, "Player hook " + tempHook.toString() + " failed.", reporter.reportDetailed(this,
e, player, injectionPoint, phase); Report.newBuilder(REPORT_PLAYER_HOOK_FAILED).messageParam(tempHook).callerParam(player, injectionPoint, phase).error(e)
);
hookFailed = true; hookFailed = true;
} }
@ -358,7 +377,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
tempHook = PlayerInjectHooks.values()[tempHook.ordinal() - 1]; tempHook = PlayerInjectHooks.values()[tempHook.ordinal() - 1];
if (hookFailed) if (hookFailed)
reporter.reportWarning(this, "Switching to " + tempHook.toString() + " instead."); reporter.reportWarning(this, Report.newBuilder(REPORT_SWITCHED_PLAYER_HOOK).messageParam(tempHook));
// Check for UTTER FAILURE // Check for UTTER FAILURE
if (tempHook == PlayerInjectHooks.NONE) { if (tempHook == PlayerInjectHooks.NONE) {
@ -394,7 +413,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
if (injector != null) if (injector != null)
injector.cleanupAll(); injector.cleanupAll();
} catch (Exception ex) { } catch (Exception ex) {
reporter.reportDetailed(this, "Cleaing up after player hook failed.", ex, injector); reporter.reportDetailed(this, Report.newBuilder(REPORT_HOOK_CLEANUP_FAILED).callerParam(injector).error(ex));
} }
} }
@ -411,6 +430,18 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
} }
} }
@Override
public void updatePlayer(Player player) {
SocketInjector injector = inputStreamLookup.peekSocketInjector(player.getAddress());
if (injector != null) {
injector.setUpdatedPlayer(player);
} else {
inputStreamLookup.setSocketInjector(player.getAddress(),
new BukkitSocketInjector(player));
}
}
/** /**
* Unregisters the given player. * Unregisters the given player.
* @param player - player to unregister. * @param player - player to unregister.
@ -444,7 +475,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
// Let the user know // Let the user know
reporter.reportWarning(this, "Unable to fully revert old injector. May cause conflicts.", e); reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_REVERT_HOOK).error(e));
} }
} }
@ -501,14 +532,14 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
} }
/** /**
* Process a packet as if it were sent by the given player. * Recieve a packet as if it were sent by the given player.
* @param player - the sender. * @param player - the sender.
* @param mcPacket - the packet to process. * @param mcPacket - the packet to process.
* @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.
*/ */
@Override @Override
public void processPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException { public void recieveClientPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException {
PlayerInjector injector = getInjector(player); PlayerInjector injector = getInjector(player);
// Process the given packet, or simply give up // Process the given packet, or simply give up
@ -532,6 +563,11 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
if (injector == null) { if (injector == null) {
// Try getting it from the player itself // Try getting it from the player itself
SocketAddress address = player.getAddress(); SocketAddress address = player.getAddress();
// Must have logged out - there's nothing we can do
if (address == null)
return null;
// Look that up without blocking // Look that up without blocking
SocketInjector result = inputStreamLookup.peekSocketInjector(address); SocketInjector result = inputStreamLookup.peekSocketInjector(address);
@ -619,12 +655,13 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
@Override @Override
public void checkListener(PacketListener listener) { public void checkListener(PacketListener listener) {
if (lastSuccessfulHook != null) { if (lastSuccessfulHook != null) {
UnsupportedListener result = lastSuccessfulHook.checkListener(listener); UnsupportedListener result = lastSuccessfulHook.checkListener(version, listener);
// We won't prevent the listener, as it may still have valid packets // We won't prevent the listener, as it may still have valid packets
if (result != null) { if (result != null) {
reporter.reportWarning(this, "Cannot fully register listener for " + reporter.reportWarning(this,
PacketAdapter.getPluginName(listener) + ": " + result.toString()); Report.newBuilder(REPORT_UNSUPPPORTED_LISTENER).messageParam(PacketAdapter.getPluginName(listener), result)
);
// These are illegal // These are illegal
for (int packetID : result.getPackets()) for (int packetID : result.getPackets())

Datei anzeigen

@ -1,22 +1,14 @@
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.lang.reflect.Field;
import java.net.Socket; import java.net.Socket;
import java.net.SocketAddress; import java.net.SocketAddress;
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.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
public abstract class AbstractInputStreamLookup { public abstract class AbstractInputStreamLookup {
// Used to access the inner input stream of a filtered input stream
private static Field filteredInputField;
// Error reporter // Error reporter
protected final ErrorReporter reporter; protected final ErrorReporter reporter;
@ -28,30 +20,6 @@ public abstract class AbstractInputStreamLookup {
this.server = server; this.server = server;
} }
/**
* Retrieve the underlying input stream that is associated with a given filter input stream.
* @param filtered - the filter input stream.
* @return The underlying input stream that is being filtered.
* @throws FieldAccessException Unable to access input stream.
*/
protected static InputStream getInputStream(FilterInputStream filtered) {
if (filteredInputField == null)
filteredInputField = FuzzyReflection.fromClass(FilterInputStream.class, true).
getFieldByType("in", InputStream.class);
InputStream current = filtered;
try {
// Iterate until we find the real input stream
while (current instanceof FilterInputStream) {
current = (InputStream) FieldUtils.readField(filteredInputField, current, true);
}
return current;
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot access filtered input field.", e);
}
}
/** /**
* Inject the given server thread or dedicated connection. * Inject the given server thread or dedicated connection.
* @param container - class that contains a ServerSocket field. * @param container - class that contains a ServerSocket field.

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

@ -18,6 +18,9 @@ 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 {
// Used to access the inner input stream of a filtered input stream
private static Field filteredInputField;
// The default lookup timeout // The default lookup timeout
private static final long DEFAULT_TIMEOUT = 2000; // ms private static final long DEFAULT_TIMEOUT = 2000; // ms
@ -123,6 +126,30 @@ class InputStreamReflectLookup extends AbstractInputStreamLookup {
return result; return result;
} }
/**
* Retrieve the underlying input stream that is associated with a given filter input stream.
* @param filtered - the filter input stream.
* @return The underlying input stream that is being filtered.
* @throws FieldAccessException Unable to access input stream.
*/
protected static InputStream getInputStream(FilterInputStream filtered) {
if (filteredInputField == null)
filteredInputField = FuzzyReflection.fromClass(FilterInputStream.class, true).
getFieldByType("in", InputStream.class);
InputStream current = filtered;
try {
// Iterate until we find the real input stream
while (current instanceof FilterInputStream) {
current = (InputStream) FieldUtils.readField(filteredInputField, current, true);
}
return current;
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot access filtered input field.", e);
}
}
@Override @Override
public void setSocketInjector(SocketAddress address, SocketInjector injector) { public void setSocketInjector(SocketAddress address, SocketInjector injector) {
if (address == null) if (address == null)

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

@ -102,6 +102,8 @@ public class TemporaryPlayerFactory {
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("isOnline"))
return injector.getSocket() != null && injector.getSocket().isConnected();
if (methodName.equalsIgnoreCase("getName")) if (methodName.equalsIgnoreCase("getName"))
return "UNKNOWN[" + injector.getSocket().getRemoteSocketAddress() + "]"; return "UNKNOWN[" + injector.getSocket().getRemoteSocketAddress() + "]";
if (methodName.equalsIgnoreCase("getPlayer")) if (methodName.equalsIgnoreCase("getPlayer"))

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

@ -29,9 +29,9 @@ 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.ErrorReporter;
import com.comphenix.protocol.error.Report;
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;
@ -46,6 +46,8 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder;
* @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. * The default format for the name of new worker threads.
@ -118,11 +120,13 @@ public class BackgroundCompiler {
} }
// Avoid "Constructor call must be the first statement". // Avoid "Constructor call must be the first statement".
private void initializeCompiler(ClassLoader loader, @Nullable ErrorReporter reporter, ExecutorService executor) { private void initializeCompiler(ClassLoader loader, ErrorReporter reporter, ExecutorService executor) {
if (loader == null) if (loader == null)
throw new IllegalArgumentException("loader cannot be NULL"); throw new IllegalArgumentException("loader cannot be NULL");
if (executor == null) if (executor == null)
throw new IllegalArgumentException("executor cannot be NULL"); throw new IllegalArgumentException("executor cannot be NULL");
if (reporter == null)
throw new IllegalArgumentException("reporter cannot be NULL.");
this.compiler = new StructureCompiler(loader); this.compiler = new StructureCompiler(loader);
this.reporter = reporter; this.reporter = reporter;
@ -198,6 +202,11 @@ public class BackgroundCompiler {
synchronized (listenerLock) { synchronized (listenerLock) {
list = listeners.get(key); list = listeners.get(key);
// Prevent ConcurrentModificationExceptions
if (list != null) {
list = Lists.newArrayList(list);
}
} }
// Only execute the listeners if there is a list // Only execute the listeners if there is a list
@ -217,13 +226,9 @@ public class BackgroundCompiler {
setEnabled(false); setEnabled(false);
// Inform about this error as best as we can // Inform about this error as best as we can
if (reporter != null) {
reporter.reportDetailed(BackgroundCompiler.this, reporter.reportDetailed(BackgroundCompiler.this,
"Cannot compile structure. Disabing compiler.", e, uncompiled); Report.newBuilder(REPORT_CANNOT_COMPILE_STRUCTURE_MODIFIER).callerParam(uncompiled).error(e)
} else { );
System.err.println("Exception occured in structure compiler: ");
e.printStackTrace();
}
} }
// We'll also return the new structure modifier // We'll also return the new structure modifier
@ -253,7 +258,7 @@ public class BackgroundCompiler {
// Occures when the underlying queue is overflowing. Since the compilation // Occures when the underlying queue is overflowing. Since the compilation
// is only an optmization and not really essential we'll just log this failure // is only an optmization and not really essential we'll just log this failure
// and move on. // and move on.
reporter.reportWarning(this, "Unable to schedule compilation task.", e); reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_SCHEDULE_COMPILATION).error(e));
} }
} }
} }

Datei anzeigen

@ -26,6 +26,8 @@ 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.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.StructureModifier;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.common.primitives.Primitives; import com.google.common.primitives.Primitives;
@ -92,6 +94,7 @@ import net.sf.cglib.asm.*;
* @author Kristian * @author Kristian
*/ */
public final class StructureCompiler { public final class StructureCompiler {
public static final ReportType REPORT_TOO_MANY_GENERATED_CLASSES = new ReportType("Generated too many classes (count: %s)");
// Used to store generated classes of different types // Used to store generated classes of different types
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
@ -210,7 +213,8 @@ public final class StructureCompiler {
} catch (OutOfMemoryError e) { } catch (OutOfMemoryError e) {
// Print the number of generated classes by the current instances // Print the number of generated classes by the current instances
ProtocolLibrary.getErrorReporter().reportWarning( ProtocolLibrary.getErrorReporter().reportWarning(
this, "May have generated too many classes (count: " + compiledCache.size() + ")"); this, Report.newBuilder(REPORT_TOO_MANY_GENERATED_CLASSES).messageParam(compiledCache.size())
);
throw e; throw e;
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
throw new IllegalStateException("Used invalid parameters in instance creation", e); throw new IllegalStateException("Used invalid parameters in instance creation", e);

Datei anzeigen

@ -63,6 +63,25 @@ public abstract class AbstractFuzzyMatcher<T> implements Comparable<AbstractFuzz
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) {
if (obj instanceof AbstractFuzzyMatcher) { if (obj instanceof AbstractFuzzyMatcher) {

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.
@ -90,17 +97,53 @@ public class FuzzyClassContract extends AbstractFuzzyMatcher<Class<?>> {
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;
@ -89,6 +92,11 @@ 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,13 +175,41 @@ 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.
* @param minecraftPackage - the current Minecraft package. * @param minecraftPackage - the current Minecraft package.
@ -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
@ -19,3 +17,9 @@ global:
# 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;