Merge branch 'master' into gh-pages
Conflicts: ItemDisguise/.settings/org.eclipse.core.resources.prefs
Dieser Commit ist enthalten in:
Commit
7e20abdd37
@ -7,6 +7,12 @@
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<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">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.comphenix.protocol</groupId>
|
||||
<artifactId>ProtocolLib</artifactId>
|
||||
<version>2.3.0</version>
|
||||
<version>2.4.3</version>
|
||||
<packaging>jar</packaging>
|
||||
<description>Provides read/write access to the Minecraft protocol.</description>
|
||||
|
||||
@ -57,7 +57,7 @@
|
||||
</goals>
|
||||
<configuration>
|
||||
<shadedArtifactAttached>false</shadedArtifactAttached>
|
||||
<createDependencyReducedPom>true</createDependencyReducedPom>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
|
||||
<relocations>
|
||||
<relocation>
|
||||
@ -203,7 +203,7 @@
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>1.4.7-R0.1</version>
|
||||
<version>1.5.1-R0.2-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
@ -24,6 +24,8 @@ import java.util.List;
|
||||
|
||||
import com.comphenix.protocol.async.AsyncListenerHandler;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.injector.BukkitUnwrapper;
|
||||
@ -51,6 +53,9 @@ import com.comphenix.protocol.wrappers.nbt.io.NbtBinarySerializer;
|
||||
* @author Kristian
|
||||
*/
|
||||
class CleanupStaticMembers {
|
||||
// Reports
|
||||
public final static ReportType REPORT_CANNOT_RESET_FIELD = new ReportType("Unable to reset field %s: %s");
|
||||
public final static ReportType REPORT_CANNOT_UNLOAD_CLASS = new ReportType("Unable to unload class %s.");
|
||||
|
||||
private ClassLoader loader;
|
||||
private ErrorReporter reporter;
|
||||
@ -116,7 +121,9 @@ class CleanupStaticMembers {
|
||||
setFinalStatic(field, null);
|
||||
} catch (IllegalAccessException e) {
|
||||
// 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));
|
||||
} catch (ClassNotFoundException e) {
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,8 @@ import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
|
||||
/**
|
||||
* Base class for all our commands.
|
||||
@ -30,6 +32,8 @@ import com.comphenix.protocol.error.ErrorReporter;
|
||||
* @author Kristian
|
||||
*/
|
||||
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";
|
||||
|
||||
@ -55,6 +59,7 @@ abstract class CommandBase implements CommandExecutor {
|
||||
try {
|
||||
// Make sure we're dealing with the correct command
|
||||
if (!command.getName().equalsIgnoreCase(name)) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_UNEXPECTED_COMMAND).messageParam(this));
|
||||
return false;
|
||||
}
|
||||
if (permission != null && !sender.hasPermission(permission)) {
|
||||
@ -66,11 +71,14 @@ abstract class CommandBase implements CommandExecutor {
|
||||
if (args != null && args.length >= minimumArgumentCount) {
|
||||
return handleCommand(sender, args);
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "Insufficient commands. You need at least " + minimumArgumentCount);
|
||||
return false;
|
||||
}
|
||||
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
482
ProtocolLib/src/main/java/com/comphenix/protocol/CommandFilter.java
Normale Datei
482
ProtocolLib/src/main/java/com/comphenix/protocol/CommandFilter.java
Normale Datei
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -36,6 +36,8 @@ import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.concurrency.AbstractIntervalTree;
|
||||
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.ListenerPriority;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
@ -57,6 +59,7 @@ import com.google.common.collect.Sets;
|
||||
* @author Kristian
|
||||
*/
|
||||
class CommandPacket extends CommandBase {
|
||||
public static final ReportType REPORT_CANNOT_SEND_MESSAGE = new ReportType("Cannot send chat message.");
|
||||
|
||||
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> 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);
|
||||
this.plugin = plugin;
|
||||
this.logger = logger;
|
||||
this.manager = manager;
|
||||
this.filter = filter;
|
||||
this.chatter = new ChatExtensions(manager);
|
||||
}
|
||||
|
||||
@ -162,7 +169,9 @@ class CommandPacket extends CommandBase {
|
||||
try {
|
||||
chatter.sendMessageSilently(receiver, message);
|
||||
} 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 {
|
||||
chatter.broadcastMessageSilently(message, permission);
|
||||
} 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());
|
||||
else if (side.isForServer())
|
||||
supported.addAll(Packets.Server.getSupported());
|
||||
|
||||
return supported;
|
||||
}
|
||||
|
||||
@ -362,7 +374,6 @@ class CommandPacket extends CommandBase {
|
||||
}
|
||||
|
||||
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> packets;
|
||||
|
||||
@ -386,14 +397,14 @@ class CommandPacket extends CommandBase {
|
||||
return new DetailedPacketListener() {
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
if (side.isForServer()) {
|
||||
if (side.isForServer() && filter.filterEvent(event)) {
|
||||
printInformation(event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
if (side.isForClient()) {
|
||||
if (side.isForClient() && filter.filterEvent(event)) {
|
||||
printInformation(event);
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,8 @@ import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
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.UpdateResult;
|
||||
import com.comphenix.protocol.metrics.Updater.UpdateType;
|
||||
@ -40,6 +42,10 @@ class CommandProtocol extends CommandBase {
|
||||
*/
|
||||
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 Updater updater;
|
||||
private ProtocolConfig config;
|
||||
@ -77,9 +83,11 @@ class CommandProtocol extends CommandBase {
|
||||
sender.sendMessage(ChatColor.BLUE + "[ProtocolLib] " + result.toString());
|
||||
} catch (Exception 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 {
|
||||
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());
|
||||
} catch (Exception 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 {
|
||||
getReporter().reportDetailed(this, "Cannot update ProtocolLib.", e, sender);
|
||||
getReporter().reportDetailed(this,Report.newBuilder(REPORT_CANNOT_UPDATE_PLUGIN).error(e).callerParam(sender));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -21,6 +21,7 @@ import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.events.ListenerPriority;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
|
||||
/**
|
||||
@ -43,7 +44,7 @@ public interface PacketStream {
|
||||
* Send a packet to the given player.
|
||||
* @param reciever - the reciever.
|
||||
* @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.
|
||||
*/
|
||||
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.
|
||||
* @param sender - the sender.
|
||||
* @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 IllegalAccessException If the underlying method caused an error.
|
||||
*/
|
||||
|
@ -114,6 +114,10 @@ public final class Packets {
|
||||
public static final int PLAYER_INFO = 201;
|
||||
public static final int ABILITIES = 202;
|
||||
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 KEY_RESPONSE = 252;
|
||||
public static final int KEY_REQUEST = 253;
|
||||
|
@ -18,12 +18,15 @@
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.bukkit.configuration.Configuration;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.io.Files;
|
||||
|
||||
/**
|
||||
* Represents the configuration of ProtocolLib.
|
||||
@ -31,6 +34,7 @@ import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
|
||||
* @author Kristian
|
||||
*/
|
||||
class ProtocolConfig {
|
||||
private static final String LAST_UPDATE_FILE = "lastupdate";
|
||||
|
||||
private static final String SECTION_GLOBAL = "global";
|
||||
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 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 SCRIPT_ENGINE_NAME = "script engine";
|
||||
|
||||
private static final String UPDATER_NOTIFY = "notify";
|
||||
private static final String UPDATER_DOWNLAD = "download";
|
||||
private static final String UPDATER_DELAY = "delay";
|
||||
private static final String UPDATER_LAST_TIME = "last";
|
||||
|
||||
// Defaults
|
||||
private static final long DEFAULT_UPDATER_DELAY = 43200;
|
||||
@ -57,6 +63,11 @@ class ProtocolConfig {
|
||||
private ConfigurationSection global;
|
||||
private ConfigurationSection updater;
|
||||
|
||||
// Last update time
|
||||
private long lastUpdateTime;
|
||||
private boolean configChanged;
|
||||
private boolean valuesChanged;
|
||||
|
||||
public ProtocolConfig(Plugin plugin) {
|
||||
this(plugin, plugin.getConfig());
|
||||
}
|
||||
@ -70,10 +81,64 @@ class ProtocolConfig {
|
||||
* Reload configuration file.
|
||||
*/
|
||||
public void reloadConfig() {
|
||||
// Reset
|
||||
configChanged = false;
|
||||
valuesChanged = false;
|
||||
|
||||
this.config = plugin.getConfig();
|
||||
this.lastUpdateTime = loadLastUpdate();
|
||||
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.
|
||||
* @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.
|
||||
* @return Configuration file on disk.
|
||||
@ -122,7 +198,7 @@ class ProtocolConfig {
|
||||
* @param value - TRUE to do this automatically, FALSE otherwise.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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
|
||||
if (delaySeconds < DEFAULT_UPDATER_DELAY)
|
||||
delaySeconds = DEFAULT_UPDATER_DELAY;
|
||||
updater.set(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);
|
||||
setConfig(updater, UPDATER_DELAY, delaySeconds);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -188,7 +274,7 @@ class ProtocolConfig {
|
||||
* @param ignoreVersion - the version of Minecraft where the satefy will be disabled.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public void setInjectionMethod(PlayerInjectHooks hook) {
|
||||
global.set(INJECTION_METHOD, hook.name());
|
||||
setConfig(global, INJECTION_METHOD, hook.name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the current configuration file.
|
||||
*/
|
||||
public void saveAll() {
|
||||
if (valuesChanged)
|
||||
saveLastUpdate(lastUpdateTime);
|
||||
if (configChanged)
|
||||
plugin.saveConfig();
|
||||
|
||||
// And we're done
|
||||
valuesChanged = false;
|
||||
configChanged = false;
|
||||
}
|
||||
}
|
||||
|
@ -33,8 +33,11 @@ import org.bukkit.plugin.PluginManager;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import com.comphenix.protocol.async.AsyncFilterManager;
|
||||
import com.comphenix.protocol.error.BasicErrorReporter;
|
||||
import com.comphenix.protocol.error.DetailedErrorReporter;
|
||||
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.PacketFilterManager;
|
||||
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.reflect.compiler.BackgroundCompiler;
|
||||
import com.comphenix.protocol.utility.ChatExtensions;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
|
||||
/**
|
||||
* The main entry point for ProtocolLib.
|
||||
@ -50,6 +54,24 @@ import com.comphenix.protocol.utility.ChatExtensions;
|
||||
* @author Kristian
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@ -58,7 +80,7 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
/**
|
||||
* 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.
|
||||
@ -71,7 +93,7 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
private static PacketFilterManager protocolManager;
|
||||
|
||||
// Error reporter
|
||||
private static ErrorReporter reporter;
|
||||
private static ErrorReporter reporter = new BasicErrorReporter();
|
||||
|
||||
// Metrics and statistisc
|
||||
private Statistics statistisc;
|
||||
@ -102,6 +124,7 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
// Commands
|
||||
private CommandProtocol commandProtocol;
|
||||
private CommandPacket commandPacket;
|
||||
private CommandFilter commandFilter;
|
||||
|
||||
// Whether or not disable is not needed
|
||||
private boolean skipDisable;
|
||||
@ -118,26 +141,34 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
try {
|
||||
config = new ProtocolConfig(this);
|
||||
} catch (Exception e) {
|
||||
detailedReporter.reportWarning(this, "Cannot load configuration", e);
|
||||
detailedReporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_CONFIG).error(e));
|
||||
|
||||
// Load it again
|
||||
if (deleteConfig()) {
|
||||
config = new ProtocolConfig(this);
|
||||
} 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 {
|
||||
// Check for other versions
|
||||
checkConflictingVersions();
|
||||
|
||||
// Handle unexpected Minecraft versions
|
||||
MinecraftVersion version = verifyMinecraftVersion();
|
||||
|
||||
// Set updater
|
||||
updater = new Updater(this, logger, "protocollib", getFile(), "protocol.info");
|
||||
|
||||
unhookTask = new DelayedSingleTask(this);
|
||||
protocolManager = new PacketFilterManager(
|
||||
getClassLoader(), getServer(), unhookTask, detailedReporter);
|
||||
getClassLoader(), getServer(), this, version, unhookTask, detailedReporter);
|
||||
|
||||
// Setup error reporter
|
||||
detailedReporter.addGlobalParameter("manager", protocolManager);
|
||||
@ -152,18 +183,19 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
protocolManager.setPlayerHook(hook);
|
||||
}
|
||||
} 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
|
||||
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
|
||||
setupBroadcastUsers(PERMISSION_INFO);
|
||||
|
||||
} 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();
|
||||
}
|
||||
}
|
||||
@ -249,12 +281,10 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
logger.info("Structure compiler thread has been disabled.");
|
||||
}
|
||||
|
||||
// Handle unexpected Minecraft versions
|
||||
verifyMinecraftVersion();
|
||||
|
||||
// Set up command handlers
|
||||
registerCommand(CommandProtocol.NAME, commandProtocol);
|
||||
registerCommand(CommandPacket.NAME, commandPacket);
|
||||
registerCommand(CommandFilter.NAME, commandFilter);
|
||||
|
||||
// Player login and logout events
|
||||
protocolManager.registerEvents(manager, this);
|
||||
@ -264,7 +294,7 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
createAsyncTask(server);
|
||||
|
||||
} catch (Throwable e) {
|
||||
reporter.reportDetailed(this, "Cannot enable ProtocolLib.", e);
|
||||
reporter.reportDetailed(this, Report.newBuilder(REPORT_PLUGIN_ENABLE_ERROR).error(e));
|
||||
disablePlugin();
|
||||
return;
|
||||
}
|
||||
@ -275,14 +305,14 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
statistisc = new Statistics(this);
|
||||
}
|
||||
} 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) {
|
||||
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
|
||||
private void verifyMinecraftVersion() {
|
||||
private MinecraftVersion verifyMinecraftVersion() {
|
||||
try {
|
||||
MinecraftVersion minimum = new MinecraftVersion(MINIMUM_MINECRAFT_VERSION);
|
||||
MinecraftVersion maximum = new MinecraftVersion(MAXIMUM_MINECRAFT_VERSION);
|
||||
@ -296,9 +326,14 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
if (current.compareTo(maximum) > 0)
|
||||
logger.warning("Version " + current + " has not yet been tested! Proceed with caution.");
|
||||
}
|
||||
return current;
|
||||
|
||||
} 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() {
|
||||
@ -331,7 +366,7 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
}
|
||||
|
||||
} 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
|
||||
@ -360,7 +395,9 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
throw new RuntimeException("plugin.yml might be corrupt.");
|
||||
|
||||
} 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) {
|
||||
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();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
reporter.reportDetailed(this, "Cannot perform automatic updates.", e);
|
||||
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_UPDATE_PLUGIN).error(e));
|
||||
updateDisabled = true;
|
||||
}
|
||||
}
|
||||
@ -454,7 +491,9 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
unhookTask.close();
|
||||
protocolManager = null;
|
||||
statistisc = null;
|
||||
reporter = null;
|
||||
|
||||
// To clean up global parameters
|
||||
reporter = new BasicErrorReporter();
|
||||
|
||||
// Leaky ClassLoader begone!
|
||||
if (updater == null || updater.getResult() != UpdateResult.SUCCESS) {
|
||||
@ -479,6 +518,8 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
|
||||
/**
|
||||
* Retrieve the current error reporter.
|
||||
* <p>
|
||||
* This is guaranteed to not be NULL in 2.5.0 and later.
|
||||
* @return Current error reporter.
|
||||
*/
|
||||
public static ErrorReporter getErrorReporter() {
|
||||
|
@ -27,6 +27,7 @@ import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.async.AsyncMarker;
|
||||
import com.comphenix.protocol.events.ListenerPriority;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.injector.PacketConstructor;
|
||||
@ -47,7 +48,7 @@ public interface ProtocolManager extends PacketStream {
|
||||
*
|
||||
* @param reciever - the reciever.
|
||||
* @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.
|
||||
*/
|
||||
@Override
|
||||
@ -62,7 +63,7 @@ public interface ProtocolManager extends PacketStream {
|
||||
*
|
||||
* @param sender - the sender.
|
||||
* @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 IllegalAccessException If the underlying method caused an error.
|
||||
*/
|
||||
|
@ -23,6 +23,7 @@ import java.util.PriorityQueue;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
import com.comphenix.protocol.Packets;
|
||||
import com.comphenix.protocol.concurrency.AbstractConcurrentListenerMultimap;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
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) {
|
||||
super();
|
||||
super(Packets.MAXIMUM_PACKET_ID);
|
||||
|
||||
try {
|
||||
this.processingQueue = Synchronization.queue(MinMaxPriorityQueue.
|
||||
|
@ -89,6 +89,7 @@ abstract class PacketSendingQueue {
|
||||
PacketEvent copy = PacketEvent.fromSynchronous(packetUpdated, marker);
|
||||
|
||||
// "Cancel" the original event
|
||||
packetUpdated.setReadOnly(false);
|
||||
packetUpdated.setCancelled(true);
|
||||
|
||||
// Enqueue the copy with the new sending index
|
||||
|
@ -23,6 +23,7 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.atomic.AtomicReferenceArray;
|
||||
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.injector.PrioritizedListener;
|
||||
@ -34,10 +35,14 @@ import com.google.common.collect.Iterables;
|
||||
* @author Kristian
|
||||
*/
|
||||
public abstract class AbstractConcurrentListenerMultimap<TListener> {
|
||||
|
||||
// The core of our map
|
||||
private ConcurrentMap<Integer, SortedCopyOnWriteArray<PrioritizedListener<TListener>>> listeners =
|
||||
new ConcurrentHashMap<Integer, SortedCopyOnWriteArray<PrioritizedListener<TListener>>>();
|
||||
private AtomicReferenceArray<SortedCopyOnWriteArray<PrioritizedListener<TListener>>> arrayListeners;
|
||||
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.
|
||||
@ -45,7 +50,6 @@ public abstract class AbstractConcurrentListenerMultimap<TListener> {
|
||||
* @param whitelist - the packet whitelist to use.
|
||||
*/
|
||||
public void addListener(TListener listener, ListeningWhitelist whitelist) {
|
||||
|
||||
PrioritizedListener<TListener> prioritized = new PrioritizedListener<TListener>(listener, whitelist.getPriority());
|
||||
|
||||
for (Integer packetID : whitelist.getWhitelist()) {
|
||||
@ -55,8 +59,7 @@ public abstract class AbstractConcurrentListenerMultimap<TListener> {
|
||||
|
||||
// Add the listener to a specific packet notifcation list
|
||||
private void addListener(Integer packetID, PrioritizedListener<TListener> listener) {
|
||||
|
||||
SortedCopyOnWriteArray<PrioritizedListener<TListener>> list = listeners.get(packetID);
|
||||
SortedCopyOnWriteArray<PrioritizedListener<TListener>> list = arrayListeners.get(packetID);
|
||||
|
||||
// We don't want to create this for every lookup
|
||||
if (list == null) {
|
||||
@ -64,12 +67,12 @@ public abstract class AbstractConcurrentListenerMultimap<TListener> {
|
||||
// which is a essential feature for our purposes.
|
||||
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 the one to use.
|
||||
if (list == null) {
|
||||
// We may end up creating multiple multisets, but we'll agree on which to use
|
||||
if (arrayListeners.compareAndSet(packetID, null, value)) {
|
||||
mapListeners.put(packetID, 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.
|
||||
*/
|
||||
public List<Integer> removeListener(TListener listener, ListeningWhitelist whitelist) {
|
||||
|
||||
List<Integer> removedPackets = new ArrayList<Integer>();
|
||||
|
||||
// Again, not terribly efficient. But adding or removing listeners should be a rare event.
|
||||
for (Integer packetID : whitelist.getWhitelist()) {
|
||||
|
||||
SortedCopyOnWriteArray<PrioritizedListener<TListener>> list = listeners.get(packetID);
|
||||
SortedCopyOnWriteArray<PrioritizedListener<TListener>> list = arrayListeners.get(packetID);
|
||||
|
||||
// Remove any listeners
|
||||
if (list != null) {
|
||||
@ -100,7 +101,8 @@ public abstract class AbstractConcurrentListenerMultimap<TListener> {
|
||||
list.remove(new PrioritizedListener<TListener>(listener, whitelist.getPriority()));
|
||||
|
||||
if (list.size() == 0) {
|
||||
listeners.remove(packetID);
|
||||
arrayListeners.set(packetID, null);
|
||||
mapListeners.remove(packetID);
|
||||
removedPackets.add(packetID);
|
||||
}
|
||||
}
|
||||
@ -120,7 +122,7 @@ public abstract class AbstractConcurrentListenerMultimap<TListener> {
|
||||
* @return Registered listeners.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public Set<Integer> keySet() {
|
||||
return listeners.keySet();
|
||||
return mapListeners.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all packet listeners.
|
||||
*/
|
||||
protected void clearListeners() {
|
||||
listeners.clear();
|
||||
arrayListeners = new AtomicReferenceArray<
|
||||
SortedCopyOnWriteArray<PrioritizedListener<TListener>>>(arrayListeners.length());
|
||||
mapListeners.clear();
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
package com.comphenix.protocol.concurrency;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@ -60,6 +61,16 @@ public class IntegerSet {
|
||||
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.
|
||||
* @param element - element to remove.
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -34,6 +34,7 @@ import org.apache.commons.lang.builder.ToStringStyle;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.error.Report.ReportBuilder;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.reflect.PrettyPrinter;
|
||||
import com.google.common.primitives.Primitives;
|
||||
@ -44,11 +45,14 @@ import com.google.common.primitives.Primitives;
|
||||
* @author Kristian
|
||||
*/
|
||||
public class DetailedErrorReporter implements ErrorReporter {
|
||||
/**
|
||||
* Report format for printing the current exception count.
|
||||
*/
|
||||
public static final ReportType REPORT_EXCEPTION_COUNT = new ReportType("Internal exception count: %s!");
|
||||
|
||||
public static final String SECOND_LEVEL_PREFIX = " ";
|
||||
public static final String DEFAULT_PREFIX = " ";
|
||||
public static final String DEFAULT_SUPPORT_URL = "http://dev.bukkit.org/server-mods/protocollib/";
|
||||
public static final String PLUGIN_NAME = "ProtocolLib";
|
||||
|
||||
// Users that are informed about errors in the chat
|
||||
public static final String ERROR_PERMISSION = "protocol.info";
|
||||
@ -68,6 +72,7 @@ public class DetailedErrorReporter implements ErrorReporter {
|
||||
protected Logger logger;
|
||||
|
||||
protected WeakReference<Plugin> pluginReference;
|
||||
protected String pluginName;
|
||||
|
||||
// Whether or not Apache Commons is not present
|
||||
protected boolean apacheCommonsMissing;
|
||||
@ -92,15 +97,6 @@ public class DetailedErrorReporter implements ErrorReporter {
|
||||
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.
|
||||
* @param plugin - the plugin owner.
|
||||
@ -114,23 +110,28 @@ public class DetailedErrorReporter implements ErrorReporter {
|
||||
throw new IllegalArgumentException("Plugin cannot be NULL.");
|
||||
|
||||
this.pluginReference = new WeakReference<Plugin>(plugin);
|
||||
this.pluginName = plugin.getName();
|
||||
this.prefix = prefix;
|
||||
this.supportURL = supportURL;
|
||||
this.maxErrorCount = maxErrorCount;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
// Attempt to get the logger.
|
||||
private static Logger getBukkitLogger() {
|
||||
try {
|
||||
return Bukkit.getLogger();
|
||||
} catch (Throwable e) {
|
||||
return Logger.getLogger("Minecraft");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) {
|
||||
if (reportMinimalNoSpam(sender, methodName, error)) {
|
||||
// Print parameters, if they are given
|
||||
if (parameters != null && parameters.length > 0) {
|
||||
logger.log(Level.SEVERE, " Parameters:");
|
||||
|
||||
// Print each parameter
|
||||
for (Object parameter : parameters) {
|
||||
logger.log(Level.SEVERE, " " + getStringDescription(parameter));
|
||||
}
|
||||
logger.log(Level.SEVERE, printParameters(parameters));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -140,6 +141,13 @@ public class DetailedErrorReporter implements ErrorReporter {
|
||||
reportMinimalNoSpam(sender, methodName, error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Report a problem with a given method and plugin, ensuring that we don't exceed the maximum number of error reports.
|
||||
* @param sender - the component that observed this exception.
|
||||
* @param methodName - the method name.
|
||||
* @param error - the error itself.
|
||||
* @return TRUE if the error was printed, FALSE if it was suppressed.
|
||||
*/
|
||||
public boolean reportMinimalNoSpam(Plugin sender, String methodName, Throwable error) {
|
||||
String pluginName = PacketAdapter.getPluginName(sender);
|
||||
AtomicInteger counter = warningCount.get(pluginName);
|
||||
@ -158,14 +166,14 @@ public class DetailedErrorReporter implements ErrorReporter {
|
||||
|
||||
// See if we should print the full error
|
||||
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);
|
||||
return true;
|
||||
|
||||
} else {
|
||||
// Nope - only print the error count occationally
|
||||
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);
|
||||
}
|
||||
return false;
|
||||
@ -184,15 +192,36 @@ public class DetailedErrorReporter implements ErrorReporter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportWarning(Object sender, String message) {
|
||||
logger.log(Level.WARNING, "[" + PLUGIN_NAME + "] [" + getSenderName(sender) + "] " + message);
|
||||
public void reportWarning(Object sender, ReportBuilder reportBuilder) {
|
||||
if (reportBuilder == null)
|
||||
throw new IllegalArgumentException("reportBuilder cannot be NULL.");
|
||||
|
||||
reportWarning(sender, reportBuilder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportWarning(Object sender, String message, Throwable error) {
|
||||
logger.log(Level.WARNING, "[" + PLUGIN_NAME + "] [" + getSenderName(sender) + "] " + message, error);
|
||||
public void reportWarning(Object sender, Report report) {
|
||||
String message = "[" + pluginName + "] [" + getSenderName(sender) + "] " + report.getReportMessage();
|
||||
|
||||
// Print the main warning
|
||||
if (report.getException() != null) {
|
||||
logger.log(Level.WARNING, message, report.getException());
|
||||
} else {
|
||||
logger.log(Level.WARNING, message);
|
||||
}
|
||||
|
||||
// Parameters?
|
||||
if (report.hasCallerParameters()) {
|
||||
// Write it
|
||||
logger.log(Level.WARNING, printParameters(report.getCallerParameters()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the name of a sender class.
|
||||
* @param sender - sender object.
|
||||
* @return The name of the sender's class.
|
||||
*/
|
||||
private String getSenderName(Object sender) {
|
||||
if (sender != null)
|
||||
return sender.getClass().getSimpleName();
|
||||
@ -201,8 +230,12 @@ public class DetailedErrorReporter implements ErrorReporter {
|
||||
}
|
||||
|
||||
@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 int errorCount = internalErrorCount.incrementAndGet();
|
||||
|
||||
@ -211,7 +244,7 @@ public class DetailedErrorReporter implements ErrorReporter {
|
||||
// Only allow the error count at rare occations
|
||||
if (isPowerOfTwo(errorCount)) {
|
||||
// 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 {
|
||||
// NEVER SPAM THE CONSOLE
|
||||
return;
|
||||
@ -222,27 +255,23 @@ public class DetailedErrorReporter implements ErrorReporter {
|
||||
PrintWriter writer = new PrintWriter(text);
|
||||
|
||||
// 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("at " + supportURL + " with the following data:");
|
||||
|
||||
// Now, let us print important exception information
|
||||
writer.println(" ===== STACK TRACE =====");
|
||||
|
||||
if (error != null)
|
||||
error.printStackTrace(writer);
|
||||
if (report.getException() != null) {
|
||||
report.getException().printStackTrace(writer);
|
||||
}
|
||||
|
||||
// Data dump!
|
||||
writer.println(" ===== DUMP =====");
|
||||
|
||||
// Relevant parameters
|
||||
if (parameters != null && parameters.length > 0) {
|
||||
writer.println("Parameters:");
|
||||
|
||||
// We *really* want to get as much information as possible
|
||||
for (Object param : parameters) {
|
||||
writer.println(addPrefix(getStringDescription(param), SECOND_LEVEL_PREFIX));
|
||||
}
|
||||
if (report.hasCallerParameters()) {
|
||||
printParameters(writer, report.getCallerParameters());
|
||||
}
|
||||
|
||||
// Global parameters
|
||||
@ -270,7 +299,7 @@ public class DetailedErrorReporter implements ErrorReporter {
|
||||
// Inform of this occurrence
|
||||
if (ERROR_PERMISSION != null) {
|
||||
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
|
||||
);
|
||||
}
|
||||
@ -280,6 +309,23 @@ public class DetailedErrorReporter implements ErrorReporter {
|
||||
logger.severe(addPrefix(text.toString(), prefix));
|
||||
}
|
||||
|
||||
private String printParameters(Object... parameters) {
|
||||
StringWriter writer = new StringWriter();
|
||||
|
||||
// Print and retrieve the string buffer
|
||||
printParameters(new PrintWriter(writer), parameters);
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
private void printParameters(PrintWriter writer, Object[] parameters) {
|
||||
writer.println("Parameters: ");
|
||||
|
||||
// We *really* want to get as much information as possible
|
||||
for (Object param : parameters) {
|
||||
writer.println(addPrefix(getStringDescription(param), SECOND_LEVEL_PREFIX));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given prefix to every line in the text.
|
||||
* @param text - text to modify.
|
||||
@ -290,8 +336,12 @@ public class DetailedErrorReporter implements ErrorReporter {
|
||||
return text.replaceAll("(?m)^", prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a string representation of the given object.
|
||||
* @param value - object to convert.
|
||||
* @return String representation.
|
||||
*/
|
||||
protected String getStringDescription(Object value) {
|
||||
|
||||
// We can't only rely on toString.
|
||||
if (value == null) {
|
||||
return "[NULL]";
|
||||
@ -324,63 +374,125 @@ public class DetailedErrorReporter implements ErrorReporter {
|
||||
return test instanceof String || Primitives.isWrapperType(test.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current number of errors printed through {@link #reportDetailed(Object, Report)}.
|
||||
* @return Number of errors printed.
|
||||
*/
|
||||
public int getErrorCount() {
|
||||
return internalErrorCount.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the number of errors printed.
|
||||
* @param errorCount - new number of errors printed.
|
||||
*/
|
||||
public void setErrorCount(int errorCount) {
|
||||
internalErrorCount.set(errorCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the maximum number of errors we can print before we begin suppressing errors.
|
||||
* @return Maximum number of errors.
|
||||
*/
|
||||
public int getMaxErrorCount() {
|
||||
return maxErrorCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum number of errors we can print before we begin suppressing errors.
|
||||
* @param maxErrorCount - new max count.
|
||||
*/
|
||||
public void setMaxErrorCount(int maxErrorCount) {
|
||||
this.maxErrorCount = maxErrorCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given global parameter. It will be included in every error report.
|
||||
* <p>
|
||||
* Both key and value must be non-null.
|
||||
* @param key - name of parameter.
|
||||
* @param value - the global parameter itself.
|
||||
*/
|
||||
public void addGlobalParameter(String key, Object value) {
|
||||
if (key == null)
|
||||
throw new IllegalArgumentException("key cannot be NULL.");
|
||||
if (value == null)
|
||||
throw new IllegalArgumentException("value cannot be NULL.");
|
||||
|
||||
globalParameters.put(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a global parameter by its key.
|
||||
* @param key - key of the parameter to retrieve.
|
||||
* @return The value of the global parameter, or NULL if not found.
|
||||
*/
|
||||
public Object getGlobalParameter(String key) {
|
||||
if (key == null)
|
||||
throw new IllegalArgumentException("key cannot be NULL.");
|
||||
|
||||
return globalParameters.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all global parameters.
|
||||
*/
|
||||
public void clearGlobalParameters() {
|
||||
globalParameters.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a set of every registered global parameter.
|
||||
* @return Set of all registered global parameters.
|
||||
*/
|
||||
public Set<String> globalParameters() {
|
||||
return globalParameters.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the support URL that will be added to all detailed reports.
|
||||
* @return Support URL.
|
||||
*/
|
||||
public String getSupportURL() {
|
||||
return supportURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the support URL that will be added to all detailed reports.
|
||||
* @param supportURL - the new support URL.
|
||||
*/
|
||||
public void setSupportURL(String supportURL) {
|
||||
this.supportURL = supportURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the prefix to apply to every line in the error reports.
|
||||
* @return Error report prefix.
|
||||
*/
|
||||
public String getPrefix() {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the prefix to apply to every line in the error reports.
|
||||
* @param prefix - new prefix.
|
||||
*/
|
||||
public void setPrefix(String prefix) {
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current logger that is used to print all reports.
|
||||
* @return The current logger.
|
||||
*/
|
||||
public Logger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current logger that is used to print all reports.
|
||||
* @param logger - new logger.
|
||||
*/
|
||||
public void setLogger(Logger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
@ -19,10 +19,16 @@ package com.comphenix.protocol.error;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
public interface ErrorReporter {
|
||||
import com.comphenix.protocol.error.Report.ReportBuilder;
|
||||
|
||||
/**
|
||||
* 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 about an exception from another plugin.
|
||||
* Prints a small minimal error report regarding an exception from another plugin.
|
||||
* @param sender - the other plugin.
|
||||
* @param methodName - name of the caller method.
|
||||
* @param error - the exception itself.
|
||||
@ -30,7 +36,7 @@ public interface ErrorReporter {
|
||||
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 methodName - name of the caller method.
|
||||
* @param error - the exception itself.
|
||||
@ -41,25 +47,28 @@ public interface ErrorReporter {
|
||||
/**
|
||||
* Prints a warning message from the current plugin.
|
||||
* @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.
|
||||
* @param sender - the object containing the caller method.
|
||||
* @param message - error message.
|
||||
* @param error - the exception that was thrown.
|
||||
* @param reportBuilder - an error report builder that will be used to get the report.
|
||||
*/
|
||||
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.
|
||||
* @param sender - the object containing the caller method.
|
||||
* @param message - an error message to include.
|
||||
* @param error - the exception that was thrown in the caller method.
|
||||
* @param parameters - parameters from the caller method.
|
||||
* @param report - an error report to include.
|
||||
*/
|
||||
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);
|
||||
}
|
162
ProtocolLib/src/main/java/com/comphenix/protocol/error/Report.java
Normale Datei
162
ProtocolLib/src/main/java/com/comphenix/protocol/error/Report.java
Normale Datei
@ -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;
|
||||
}
|
||||
}
|
@ -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]);
|
||||
}
|
||||
}
|
@ -44,6 +44,9 @@ public class PacketEvent extends EventObject implements Cancellable {
|
||||
private AsyncMarker asyncMarker;
|
||||
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.
|
||||
* @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.
|
||||
*/
|
||||
public void setPacket(PacketContainer packet) {
|
||||
if (readOnly)
|
||||
throw new IllegalStateException("The packet event is read-only.");
|
||||
this.packet = packet;
|
||||
}
|
||||
|
||||
@ -147,6 +152,8 @@ public class PacketEvent extends EventObject implements Cancellable {
|
||||
* @param cancel - TRUE if it should be cancelled, FALSE otherwise.
|
||||
*/
|
||||
public void setCancelled(boolean cancel) {
|
||||
if (readOnly)
|
||||
throw new IllegalStateException("The packet event is read-only.");
|
||||
this.cancel = cancel;
|
||||
}
|
||||
|
||||
@ -193,9 +200,34 @@ public class PacketEvent extends EventObject implements Cancellable {
|
||||
public void setAsyncMarker(AsyncMarker asyncMarker) {
|
||||
if (isAsynchronous())
|
||||
throw new IllegalStateException("The marker is immutable for asynchronous events");
|
||||
if (readOnly)
|
||||
throw new IllegalStateException("The packet event is read-only.");
|
||||
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.
|
||||
* @return TRUE if this packet event is asynchronous, FALSE otherwise.
|
||||
|
@ -25,6 +25,9 @@ import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.injector.PacketConstructor.Unwrapper;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||
@ -42,8 +45,32 @@ import com.google.common.primitives.Primitives;
|
||||
* @author Kristian
|
||||
*/
|
||||
public class BukkitUnwrapper implements Unwrapper {
|
||||
public static final ReportType REPORT_ILLEGAL_ARGUMENT = new ReportType("Illegal argument.");
|
||||
public static final ReportType REPORT_SECURITY_LIMITATION = new ReportType("Security limitation.");
|
||||
public static final ReportType REPORT_CANNOT_FIND_UNWRAP_METHOD = new ReportType("Cannot find method.");
|
||||
|
||||
public static final ReportType REPORT_CANNOT_READ_FIELD_HANDLE = new ReportType("Cannot read field 'handle'.");
|
||||
|
||||
private static Map<Class<?>, Unwrapper> unwrapperCache = new ConcurrentHashMap<Class<?>, Unwrapper>();
|
||||
|
||||
// The current error reporter
|
||||
private final ErrorReporter reporter;
|
||||
|
||||
/**
|
||||
* Construct a new Bukkit unwrapper with ProtocolLib's default error reporter.
|
||||
*/
|
||||
public BukkitUnwrapper() {
|
||||
this(ProtocolLibrary.getErrorReporter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new Bukkit unwrapper with the given error reporter.
|
||||
* @param reporter - the error reporter to use.
|
||||
*/
|
||||
public BukkitUnwrapper(ErrorReporter reporter) {
|
||||
this.reporter = reporter;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object unwrapItem(Object wrappedObject) {
|
||||
@ -111,8 +138,9 @@ public class BukkitUnwrapper implements Unwrapper {
|
||||
return find.invoke(wrappedObject);
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
ProtocolLibrary.getErrorReporter().reportDetailed(
|
||||
this, "Illegal argument.", e, wrappedObject, find);
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_ILLEGAL_ARGUMENT).error(e).callerParam(wrappedObject, find)
|
||||
);
|
||||
} catch (IllegalAccessException e) {
|
||||
// Should not occur either
|
||||
return null;
|
||||
@ -129,7 +157,9 @@ public class BukkitUnwrapper implements Unwrapper {
|
||||
return methodUnwrapper;
|
||||
|
||||
} 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) {
|
||||
// Try getting the field unwrapper too
|
||||
Unwrapper fieldUnwrapper = getFieldUnwrapper(type);
|
||||
@ -137,7 +167,8 @@ public class BukkitUnwrapper implements Unwrapper {
|
||||
if (fieldUnwrapper != null)
|
||||
return fieldUnwrapper;
|
||||
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
|
||||
@ -160,8 +191,9 @@ public class BukkitUnwrapper implements Unwrapper {
|
||||
try {
|
||||
return FieldUtils.readField(find, wrappedObject, true);
|
||||
} catch (IllegalAccessException e) {
|
||||
ProtocolLibrary.getErrorReporter().reportDetailed(
|
||||
this, "Cannot read field 'handle'.", e, wrappedObject, find.getName());
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).error(e).callerParam(wrappedObject, find)
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -172,9 +204,9 @@ public class BukkitUnwrapper implements Unwrapper {
|
||||
|
||||
} else {
|
||||
// Inform about this too
|
||||
ProtocolLibrary.getErrorReporter().reportDetailed(
|
||||
this, "Could not find field 'handle'.",
|
||||
new Exception("Unable to find 'handle'"), type.getName());
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).callerParam(find)
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -52,8 +52,9 @@ public interface ListenerInvoker {
|
||||
public abstract void unregisterPacketClass(Class<?> clazz);
|
||||
|
||||
/**
|
||||
* Remove a given class from the packet registry. Internal method.
|
||||
* @param clazz - class to remove.
|
||||
* Register a given class in the packet registry. Internal method.
|
||||
* @param clazz - class to register.
|
||||
* @param packetID - the the new associated packet ID.
|
||||
*/
|
||||
public abstract void registerPacketClass(Class<?> clazz, int packetID);
|
||||
|
||||
|
@ -50,6 +50,8 @@ import com.comphenix.protocol.ProtocolManager;
|
||||
import com.comphenix.protocol.async.AsyncFilterManager;
|
||||
import com.comphenix.protocol.async.AsyncMarker;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.*;
|
||||
import com.comphenix.protocol.injector.packet.PacketInjector;
|
||||
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.FuzzyReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
public final class PacketFilterManager implements ProtocolManager, ListenerInvoker {
|
||||
public 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.
|
||||
@ -147,11 +166,22 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
||||
// Spigot listener, if in use
|
||||
private SpigotPacketInjector spigotInjector;
|
||||
|
||||
// Plugin verifier
|
||||
private PluginVerifier pluginVerifier;
|
||||
|
||||
/**
|
||||
* 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)
|
||||
throw new IllegalArgumentException("reporter cannot be NULL.");
|
||||
if (classLoader == null)
|
||||
@ -170,6 +200,9 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
||||
this.classLoader = classLoader;
|
||||
this.reporter = reporter;
|
||||
|
||||
// The plugin verifier
|
||||
this.pluginVerifier = new PluginVerifier(library);
|
||||
|
||||
// Used to determine if injection is needed
|
||||
Predicate<GamePhase> isInjectionNecessary = new Predicate<GamePhase>() {
|
||||
@Override
|
||||
@ -201,6 +234,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
||||
classLoader(classLoader).
|
||||
packetListeners(packetListeners).
|
||||
injectionFilter(isInjectionNecessary).
|
||||
version(mcVersion).
|
||||
buildHandler();
|
||||
|
||||
this.packetInjector = PacketInjectorBuilder.newBuilder().
|
||||
@ -218,11 +252,11 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
||||
knowsServerPackets = PacketRegistry.getServerPackets() != null;
|
||||
knowsClientPackets = PacketRegistry.getClientPackets() != null;
|
||||
} 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) {
|
||||
reporter.reportWarning(this, "Unable to initialize packet injector.", e);
|
||||
} catch (FieldAccessException 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
public void addPacketListener(PacketListener listener) {
|
||||
if (listener == null)
|
||||
@ -267,6 +315,8 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
||||
// A listener can only be added once
|
||||
if (packetListeners.contains(listener))
|
||||
return;
|
||||
// Check plugin
|
||||
printPluginWarnings(listener.getPlugin());
|
||||
|
||||
ListeningWhitelist sending = listener.getSendingWhitelist();
|
||||
ListeningWhitelist receiving = listener.getReceivingWhitelist();
|
||||
@ -446,6 +496,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
||||
asyncFilterManager.enqueueSyncPacket(event, event.getAsyncMarker());
|
||||
|
||||
// The above makes a copy of the event, so it's safe to cancel it
|
||||
event.setReadOnly(false);
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
@ -478,10 +529,9 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
||||
if (!knowsServerPackets || PacketRegistry.getServerPackets().contains(packetID))
|
||||
playerInjection.addPacketHandler(packetID);
|
||||
else
|
||||
reporter.reportWarning(this, String.format(
|
||||
"[%s] Unsupported server packet ID in current Minecraft version: %s",
|
||||
PacketAdapter.getPluginName(listener), packetID
|
||||
));
|
||||
reporter.reportWarning(this,
|
||||
Report.newBuilder(REPORT_UNSUPPORTED_SERVER_PACKET_ID).messageParam(PacketAdapter.getPluginName(listener), packetID)
|
||||
);
|
||||
}
|
||||
|
||||
// As above, only for client packets
|
||||
@ -489,10 +539,9 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
||||
if (!knowsClientPackets || PacketRegistry.getClientPackets().contains(packetID))
|
||||
packetInjector.addPacketHandler(packetID);
|
||||
else
|
||||
reporter.reportWarning(this, String.format(
|
||||
"[%s] Unsupported client packet ID in current Minecraft version: %s",
|
||||
PacketAdapter.getPluginName(listener), packetID
|
||||
));
|
||||
reporter.reportWarning(this,
|
||||
Report.newBuilder(REPORT_UNSUPPORTED_CLIENT_PACKET_ID).messageParam(PacketAdapter.getPluginName(listener), packetID)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -529,6 +578,14 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
||||
if (packetCreation.compareAndSet(false, true))
|
||||
incrementPhases(GamePhase.PLAYING);
|
||||
|
||||
// Inform the MONITOR packets
|
||||
if (!filters) {
|
||||
sendingListeners.invokePacketSending(
|
||||
reporter,
|
||||
PacketEvent.fromServer(this, packet, reciever),
|
||||
ListenerPriority.MONITOR);
|
||||
}
|
||||
|
||||
playerInjection.sendServerPacket(reciever, packet, filters);
|
||||
}
|
||||
|
||||
@ -559,9 +616,16 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
||||
mcPacket = event.getPacket().getHandle();
|
||||
else
|
||||
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
|
||||
@ -673,8 +737,11 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
||||
try {
|
||||
// Let's clean up the other injection first.
|
||||
playerInjection.uninjectPlayer(event.getPlayer().getAddress());
|
||||
playerInjection.updatePlayer(event.getPlayer());
|
||||
} 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
|
||||
playerInjection.injectPlayer(event.getPlayer(), ConflictStrategy.OVERRIDE);
|
||||
} 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.uninjectPlayer(player);
|
||||
} 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());
|
||||
}
|
||||
} 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))
|
||||
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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -19,8 +19,10 @@ package com.comphenix.protocol.injector;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import com.comphenix.protocol.Packets;
|
||||
import com.comphenix.protocol.concurrency.AbstractConcurrentListenerMultimap;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.ListenerPriority;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
|
||||
@ -30,6 +32,9 @@ import com.comphenix.protocol.events.PacketListener;
|
||||
* @author Kristian
|
||||
*/
|
||||
public final class SortedPacketListenerList extends AbstractConcurrentListenerMultimap<PacketListener> {
|
||||
public SortedPacketListenerList() {
|
||||
super(Packets.MAXIMUM_PACKET_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
for (PrioritizedListener<PacketListener> element : list) {
|
||||
try {
|
||||
event.setReadOnly(element.getPriority() == ListenerPriority.MONITOR);
|
||||
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) {
|
||||
// Minecraft doesn't want your Exception.
|
||||
reporter.reportMinimal(element.getListener().getPlugin(), "onPacketReceiving(PacketEvent)", e,
|
||||
@ -67,7 +109,13 @@ public final class SortedPacketListenerList extends AbstractConcurrentListenerMu
|
||||
|
||||
for (PrioritizedListener<PacketListener> element : list) {
|
||||
try {
|
||||
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,
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||
import com.comphenix.protocol.injector.PacketFilterManager;
|
||||
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
/**
|
||||
@ -100,9 +101,9 @@ public class PacketInjectorBuilder {
|
||||
* <p>
|
||||
* Note that any non-null builder parameters must be set.
|
||||
* @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();
|
||||
return new ProxyPacketInjector(classLoader, invoker, playerInjection, reporter);
|
||||
}
|
||||
|
@ -25,10 +25,17 @@ import java.util.Set;
|
||||
|
||||
import net.sf.cglib.proxy.Factory;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyClassContract;
|
||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
|
||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.wrappers.TroveWrapper;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
@ -39,6 +46,13 @@ import com.google.common.collect.ImmutableSet;
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class PacketRegistry {
|
||||
public static final ReportType REPORT_CANNOT_CORRECT_TROVE_MAP = new ReportType("Unable to correct no entry value.");
|
||||
|
||||
public static final ReportType REPORT_INSUFFICIENT_SERVER_PACKETS = new ReportType("Too few server packets detected: %s");
|
||||
public static final ReportType REPORT_INSUFFICIENT_CLIENT_PACKETS = new ReportType("Too few client packets detected: %s");
|
||||
|
||||
private static final int MIN_SERVER_PACKETS = 5;
|
||||
private static final int MIN_CLIENT_PACKETS = 5;
|
||||
|
||||
// Fuzzy reflection
|
||||
private static FuzzyReflection packetRegistry;
|
||||
@ -67,6 +81,14 @@ public class PacketRegistry {
|
||||
try {
|
||||
Field packetsField = getPacketRegistry().getFieldByType("packetsField", Map.class);
|
||||
packetToID = (Map<Class, Integer>) FieldUtils.readStaticField(packetsField, true);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Spigot 1.2.5 MCPC workaround
|
||||
try {
|
||||
packetToID = getSpigotWrapper();
|
||||
} catch (Exception e2) {
|
||||
// Very bad indeed
|
||||
throw new IllegalArgumentException(e.getMessage() + "; Spigot workaround failed.", e2);
|
||||
}
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Unable to retrieve the packetClassToIdMap", e);
|
||||
@ -76,6 +98,41 @@ public class PacketRegistry {
|
||||
return packetToID;
|
||||
}
|
||||
|
||||
private static Map<Class, Integer> getSpigotWrapper() throws IllegalAccessException {
|
||||
// If it talks like a duck, etc.
|
||||
// Perhaps it would be nice to have a proper duck typing library as well
|
||||
FuzzyClassContract mapLike = FuzzyClassContract.newBuilder().
|
||||
method(FuzzyMethodContract.newBuilder().
|
||||
nameExact("size").returnTypeExact(int.class)).
|
||||
method(FuzzyMethodContract.newBuilder().
|
||||
nameExact("put").parameterCount(2)).
|
||||
method(FuzzyMethodContract.newBuilder().
|
||||
nameExact("get").parameterCount(1)).
|
||||
build();
|
||||
|
||||
Field packetsField = getPacketRegistry().getField(
|
||||
FuzzyFieldContract.newBuilder().typeMatches(mapLike).build());
|
||||
Object troveMap = FieldUtils.readStaticField(packetsField, true);
|
||||
|
||||
// Check for stupid no_entry_values
|
||||
try {
|
||||
Field field = FieldUtils.getField(troveMap.getClass(), "no_entry_value", true);
|
||||
Integer value = (Integer) FieldUtils.readField(field, troveMap, true);
|
||||
|
||||
if (value >= 0 && value < 256) {
|
||||
// Someone forgot to set the no entry value. Let's help them.
|
||||
FieldUtils.writeField(field, troveMap, -1);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Whatever
|
||||
ProtocolLibrary.getErrorReporter().reportWarning(PacketRegistry.class,
|
||||
Report.newBuilder(REPORT_CANNOT_CORRECT_TROVE_MAP).error(e));
|
||||
}
|
||||
|
||||
// We'll assume this a Trove map
|
||||
return TroveWrapper.getDecoratedMap(troveMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the cached fuzzy reflection instance allowing access to the packet registry.
|
||||
* @return Reflected packet registry.
|
||||
@ -109,6 +166,10 @@ public class PacketRegistry {
|
||||
*/
|
||||
public static Set<Integer> getServerPackets() throws FieldAccessException {
|
||||
initializeSets();
|
||||
|
||||
// Sanity check. This is impossible!
|
||||
if (serverPackets != null && serverPackets.size() < MIN_SERVER_PACKETS)
|
||||
throw new FieldAccessException("Server packet list is empty. Seems to be unsupported");
|
||||
return serverPackets;
|
||||
}
|
||||
|
||||
@ -119,6 +180,10 @@ public class PacketRegistry {
|
||||
*/
|
||||
public static Set<Integer> getClientPackets() throws FieldAccessException {
|
||||
initializeSets();
|
||||
|
||||
// As above
|
||||
if (clientPackets != null && clientPackets.size() < MIN_CLIENT_PACKETS)
|
||||
throw new FieldAccessException("Client packet list is empty. Seems to be unsupported");
|
||||
return clientPackets;
|
||||
}
|
||||
|
||||
@ -140,6 +205,16 @@ public class PacketRegistry {
|
||||
serverPackets = ImmutableSet.copyOf(serverPacketsRef);
|
||||
clientPackets = ImmutableSet.copyOf(clientPacketsRef);
|
||||
|
||||
// Check sizes
|
||||
if (serverPackets.size() < MIN_SERVER_PACKETS)
|
||||
ProtocolLibrary.getErrorReporter().reportWarning(
|
||||
PacketRegistry.class, Report.newBuilder(REPORT_INSUFFICIENT_SERVER_PACKETS).messageParam(serverPackets.size())
|
||||
);
|
||||
if (clientPackets.size() < MIN_CLIENT_PACKETS)
|
||||
ProtocolLibrary.getErrorReporter().reportWarning(
|
||||
PacketRegistry.class, Report.newBuilder(REPORT_INSUFFICIENT_CLIENT_PACKETS).messageParam(clientPackets.size())
|
||||
);
|
||||
|
||||
} else {
|
||||
throw new FieldAccessException("Cannot retrieve packet client/server sets.");
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.MethodInfo;
|
||||
@ -49,6 +50,85 @@ import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
* @author Kristian
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@ -58,9 +138,7 @@ class ProxyPacketInjector implements PacketInjector {
|
||||
parameterCount(1).
|
||||
build();
|
||||
|
||||
// The "put" method that associates a packet ID with a packet class
|
||||
private static Method putMethod;
|
||||
private static Object intHashMap;
|
||||
private static PacketClassLookup lookup;
|
||||
|
||||
// The packet filter manager
|
||||
private ListenerInvoker manager;
|
||||
@ -78,7 +156,7 @@ class ProxyPacketInjector implements PacketInjector {
|
||||
private CallbackFilter filter;
|
||||
|
||||
public ProxyPacketInjector(ClassLoader classLoader, ListenerInvoker manager,
|
||||
PlayerInjectionHandler playerInjection, ErrorReporter reporter) throws IllegalAccessException {
|
||||
PlayerInjectionHandler playerInjection, ErrorReporter reporter) throws FieldAccessException {
|
||||
|
||||
this.classLoader = classLoader;
|
||||
this.manager = manager;
|
||||
@ -100,20 +178,21 @@ class ProxyPacketInjector implements PacketInjector {
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
private void initialize() throws FieldAccessException {
|
||||
if (lookup == null) {
|
||||
try {
|
||||
lookup = new IntHashMapLookup();
|
||||
} catch (Exception e1) {
|
||||
|
||||
try {
|
||||
intHashMap = FieldUtils.readField(intHashMapField, (Object) null, true);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new RuntimeException("Minecraft is incompatible.", e);
|
||||
lookup = new ArrayLookup();
|
||||
} catch (Exception e2) {
|
||||
// Wow
|
||||
throw new FieldAccessException(e1.getMessage() + ". Workaround failed too.", e2);
|
||||
}
|
||||
}
|
||||
|
||||
// Now, get the "put" method.
|
||||
putMethod = FuzzyReflection.fromObject(intHashMap).getMethodByParameters("put", int.class, Object.class);
|
||||
// Should work fine now
|
||||
}
|
||||
}
|
||||
|
||||
@ -173,21 +252,12 @@ class ProxyPacketInjector implements PacketInjector {
|
||||
// Add a static reference
|
||||
Enhancer.registerStaticCallbacks(proxy, new Callback[] { NoOp.INSTANCE, modifierReadPacket, modifierRest });
|
||||
|
||||
try {
|
||||
// Override values
|
||||
previous.put(packetID, old);
|
||||
registry.put(proxy, packetID);
|
||||
overwritten.put(packetID, proxy);
|
||||
putMethod.invoke(intHashMap, packetID, proxy);
|
||||
lookup.setLookup(packetID, proxy);
|
||||
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
|
||||
@ -200,25 +270,14 @@ class ProxyPacketInjector implements PacketInjector {
|
||||
Map<Integer, Class> previous = PacketRegistry.getPreviousPackets();
|
||||
Map<Integer, Class> overwritten = PacketRegistry.getOverwrittenPackets();
|
||||
|
||||
// Use the old class definition
|
||||
try {
|
||||
Class old = previous.get(packetID);
|
||||
Class proxy = PacketRegistry.getPacketClassFromID(packetID);
|
||||
|
||||
putMethod.invoke(intHashMap, packetID, old);
|
||||
lookup.setLookup(packetID, old);
|
||||
previous.remove(packetID);
|
||||
registry.remove(proxy);
|
||||
overwritten.remove(packetID);
|
||||
return true;
|
||||
|
||||
// Handle some problems
|
||||
} catch (IllegalArgumentException e) {
|
||||
return false;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Cannot access method.", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException("Exception occured in IntHashMap.put.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -22,6 +22,8 @@ import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.google.common.collect.MapMaker;
|
||||
@ -30,6 +32,8 @@ import net.sf.cglib.proxy.MethodInterceptor;
|
||||
import net.sf.cglib.proxy.MethodProxy;
|
||||
|
||||
class ReadPacketModifier implements MethodInterceptor {
|
||||
public static final ReportType REPORT_CANNOT_HANDLE_CLIENT_PACKET = new ReportType("Cannot handle client packet.");
|
||||
|
||||
// A cancel marker
|
||||
private static final Object CANCEL_MARKER = new Object();
|
||||
|
||||
@ -122,7 +126,9 @@ class ReadPacketModifier implements MethodInterceptor {
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
// 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;
|
||||
|
@ -21,11 +21,15 @@ import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
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.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.Enhancer;
|
||||
@ -38,12 +42,16 @@ import net.sf.cglib.proxy.MethodProxy;
|
||||
* @author Kristian
|
||||
*/
|
||||
class InjectedArrayList extends ArrayList<Object> {
|
||||
public static final ReportType REPORT_CANNOT_REVERT_CANCELLED_PACKET = new ReportType("Reverting cancelled packet failed.");
|
||||
|
||||
/**
|
||||
* Silly Eclipse.
|
||||
*/
|
||||
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 Set<Object> ignoredPackets;
|
||||
private transient ClassLoader classLoader;
|
||||
@ -85,15 +93,10 @@ class InjectedArrayList extends ArrayList<Object> {
|
||||
return true;
|
||||
|
||||
} catch (InvocationTargetException e) {
|
||||
ErrorReporter reporter = ProtocolLibrary.getErrorReporter();
|
||||
|
||||
// Prefer to report this to the user, instead of risking sending it to Minecraft
|
||||
if (reporter != null) {
|
||||
reporter.reportDetailed(this, "Reverting cancelled packet failed.", e, packet);
|
||||
} else {
|
||||
System.out.println("[ProtocolLib] Reverting cancelled packet failed.");
|
||||
e.printStackTrace();
|
||||
}
|
||||
ProtocolLibrary.getErrorReporter().reportDetailed(this,
|
||||
Report.newBuilder(REPORT_CANNOT_REVERT_CANCELLED_PACKET).error(e).callerParam(packet)
|
||||
);
|
||||
|
||||
// Failure
|
||||
return false;
|
||||
@ -111,9 +114,6 @@ class InjectedArrayList extends ArrayList<Object> {
|
||||
ListenerInvoker invoker = injector.getInvoker();
|
||||
|
||||
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
|
||||
// total of outstanding packets. Otherwise, cancelling too many packets
|
||||
@ -134,7 +134,7 @@ class InjectedArrayList extends ArrayList<Object> {
|
||||
// ect.
|
||||
// }
|
||||
Enhancer ex = new Enhancer();
|
||||
ex.setSuperclass(type);
|
||||
ex.setSuperclass(MinecraftReflection.getPacketClass());
|
||||
ex.setInterfaces(new Class[] { FakePacket.class } );
|
||||
ex.setUseCache(true);
|
||||
ex.setClassLoader(classLoader);
|
||||
@ -146,7 +146,10 @@ class InjectedArrayList extends ArrayList<Object> {
|
||||
try {
|
||||
// Temporarily associate the fake packet class
|
||||
invoker.registerPacketClass(proxyClass, packetID);
|
||||
return proxyClass.newInstance();
|
||||
Object proxy = proxyClass.newInstance();
|
||||
|
||||
InjectedArrayList.registerDelegate(proxy, source);
|
||||
return proxy;
|
||||
|
||||
} catch (Exception e) {
|
||||
// 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.
|
||||
* @author Kristian
|
||||
@ -164,11 +176,17 @@ class InjectedArrayList extends ArrayList<Object> {
|
||||
private class InvertedIntegerCallback implements MethodInterceptor {
|
||||
@Override
|
||||
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) {
|
||||
Integer result = (Integer) proxy.invokeSuper(obj, args);
|
||||
Integer result = (Integer) proxy.invoke(delegate, args);
|
||||
return -result;
|
||||
} else {
|
||||
return proxy.invokeSuper(obj, args);
|
||||
return proxy.invoke(delegate, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,8 @@ import net.sf.cglib.proxy.Factory;
|
||||
import org.bukkit.Server;
|
||||
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
@ -40,6 +42,19 @@ import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
* @author Kristian
|
||||
*/
|
||||
class InjectedServerConnection {
|
||||
// A number of things can go wrong ...
|
||||
public static final ReportType REPORT_CANNOT_FIND_MINECRAFT_SERVER = new ReportType("Cannot extract minecraft server from Bukkit.");
|
||||
public static final ReportType REPORT_CANNOT_INJECT_SERVER_CONNECTION = new ReportType("Cannot inject into server connection. Bad things will happen.");
|
||||
|
||||
public static final ReportType REPORT_CANNOT_FIND_LISTENER_THREAD = new ReportType("Cannot find listener thread in MinecraftServer.");
|
||||
public static final ReportType REPORT_CANNOT_READ_LISTENER_THREAD = new ReportType("Unable to read the listener thread.");
|
||||
|
||||
public static final ReportType REPORT_CANNOT_FIND_SERVER_CONNECTION = new ReportType("Unable to retrieve server connection");
|
||||
public static final ReportType REPORT_UNEXPECTED_THREAD_COUNT = new ReportType("Unexpected number of threads in %s: %s");
|
||||
public static final ReportType REPORT_CANNOT_FIND_NET_HANDLER_THREAD = new ReportType("Unable to retrieve net handler thread.");
|
||||
public static final ReportType REPORT_INSUFFICENT_THREAD_COUNT = new ReportType("Unable to inject %s lists in %s.");
|
||||
|
||||
public static final ReportType REPORT_CANNOT_COPY_OLD_TO_NEW = new ReportType("Cannot copy old %s to new.");
|
||||
|
||||
private static Field listenerThreadField;
|
||||
private static Field minecraftServerField;
|
||||
@ -74,7 +89,6 @@ class InjectedServerConnection {
|
||||
}
|
||||
|
||||
public void injectList() {
|
||||
|
||||
// Only execute this method once
|
||||
if (!hasAttempted)
|
||||
hasAttempted = true;
|
||||
@ -88,7 +102,7 @@ class InjectedServerConnection {
|
||||
try {
|
||||
minecraftServer = FieldUtils.readField(minecraftServerField, server, true);
|
||||
} catch (IllegalAccessException e1) {
|
||||
reporter.reportWarning(this, "Cannot extract minecraft server from Bukkit.");
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_FIND_MINECRAFT_SERVER));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -107,7 +121,7 @@ class InjectedServerConnection {
|
||||
|
||||
} catch (Exception e) {
|
||||
// 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).
|
||||
getFieldByType("networkListenThread", MinecraftReflection.getNetworkListenThreadClass());
|
||||
} 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;
|
||||
}
|
||||
|
||||
@ -127,7 +143,7 @@ class InjectedServerConnection {
|
||||
try {
|
||||
listenerThread = listenerThreadField.get(minecraftServer);
|
||||
} 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;
|
||||
}
|
||||
|
||||
@ -140,14 +156,15 @@ class InjectedServerConnection {
|
||||
}
|
||||
|
||||
private void injectServerConnection() {
|
||||
|
||||
Object serverConnection = null;
|
||||
|
||||
// Careful - we might fail
|
||||
try {
|
||||
serverConnection = serverConnectionMethod.invoke(minecraftServer);
|
||||
} catch (Exception ex) {
|
||||
reporter.reportDetailed(this, "Unable to retrieve server connection", ex, minecraftServer);
|
||||
} catch (Exception e) {
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_CANNOT_FIND_SERVER_CONNECTION).callerParam(minecraftServer).error(e)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -160,7 +177,9 @@ class InjectedServerConnection {
|
||||
|
||||
// Verify the field count
|
||||
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
|
||||
dedicatedThreadField = matches.get(0);
|
||||
}
|
||||
@ -175,7 +194,7 @@ class InjectedServerConnection {
|
||||
injectEveryListField(dedicatedThread, 1);
|
||||
}
|
||||
} 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);
|
||||
@ -201,7 +220,7 @@ class InjectedServerConnection {
|
||||
|
||||
// Warn about unexpected errors
|
||||
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 {
|
||||
writer.copyTo(inserting, replacement, inserting.getClass());
|
||||
} catch (Throwable e) {
|
||||
reporter.reportDetailed(InjectedServerConnection.this, "Cannot copy old " + inserting +
|
||||
" to new.", e, inserting, replacement);
|
||||
reporter.reportDetailed(InjectedServerConnection.this,
|
||||
Report.newBuilder(REPORT_CANNOT_COPY_OLD_TO_NEW).messageParam(inserting).callerParam(inserting, replacement).error(e)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,8 @@ import org.bukkit.Server;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.injector.GamePhase;
|
||||
import com.comphenix.protocol.injector.player.PlayerInjectionHandler.ConflictStrategy;
|
||||
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
|
||||
@ -35,6 +37,9 @@ import com.google.common.collect.Maps;
|
||||
* @author Kristian
|
||||
*/
|
||||
class NetLoginInjector {
|
||||
public static final ReportType REPORT_CANNOT_HOOK_LOGIN_HANDLER = new ReportType("Unable to hook %s.");
|
||||
public static final ReportType REPORT_CANNOT_CLEANUP_LOGIN_HANDLER = new ReportType("Cannot cleanup %s.");
|
||||
|
||||
private ConcurrentMap<Object, PlayerInjector> injectedLogins = Maps.newConcurrentMap();
|
||||
|
||||
// Handles every hook
|
||||
@ -83,8 +88,12 @@ class NetLoginInjector {
|
||||
|
||||
} catch (Throwable e) {
|
||||
// Minecraft can't handle this, so we'll deal with it here
|
||||
reporter.reportDetailed(this, "Unable to hook " +
|
||||
MinecraftReflection.getNetLoginHandlerName() + ".", e, inserting, injectionHandler);
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_CANNOT_HOOK_LOGIN_HANDLER).
|
||||
messageParam(MinecraftReflection.getNetLoginHandlerName()).
|
||||
callerParam(inserting, injectionHandler).
|
||||
error(e)
|
||||
);
|
||||
return inserting;
|
||||
}
|
||||
}
|
||||
@ -122,8 +131,12 @@ class NetLoginInjector {
|
||||
|
||||
} catch (Throwable e) {
|
||||
// Don't leak this to Minecraft
|
||||
reporter.reportDetailed(this, "Cannot cleanup " +
|
||||
MinecraftReflection.getNetLoginHandlerName() + ".", e, removing);
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_CANNOT_CLEANUP_LOGIN_HANDLER).
|
||||
messageParam(MinecraftReflection.getNetLoginHandlerName()).
|
||||
callerParam(removing).
|
||||
error(e)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.reflect.VolatileField;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
/**
|
||||
@ -56,6 +57,12 @@ class NetworkFieldInjector extends PlayerInjector {
|
||||
// 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
|
||||
private Set<Object> ignoredPackets = Sets.newSetFromMap(new ConcurrentHashMap<Object, Boolean>());
|
||||
|
||||
@ -99,7 +106,6 @@ class NetworkFieldInjector extends PlayerInjector {
|
||||
|
||||
@Override
|
||||
public void sendServerPacket(Object packet, boolean filtered) throws InvocationTargetException {
|
||||
|
||||
if (networkManager != null) {
|
||||
try {
|
||||
if (!filtered) {
|
||||
@ -122,7 +128,11 @@ class NetworkFieldInjector extends PlayerInjector {
|
||||
}
|
||||
|
||||
@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 };
|
||||
|
||||
// Unfortunately, we don't support chunk packets
|
||||
@ -132,6 +142,7 @@ class NetworkFieldInjector extends PlayerInjector {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectManager() {
|
||||
|
@ -40,6 +40,7 @@ import com.comphenix.protocol.injector.GamePhase;
|
||||
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
|
||||
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
|
||||
/**
|
||||
* 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
|
||||
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
|
||||
private volatile static CallbackFilter callbackFilter;
|
||||
|
||||
@ -117,7 +124,11 @@ public class NetworkObjectInjector extends PlayerInjector {
|
||||
}
|
||||
|
||||
@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 };
|
||||
|
||||
// Unfortunately, we don't support chunk packets
|
||||
@ -127,6 +138,7 @@ public class NetworkObjectInjector extends PlayerInjector {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectManager() {
|
||||
|
@ -20,12 +20,16 @@ package com.comphenix.protocol.injector.player;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
|
||||
import net.sf.cglib.proxy.*;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.concurrency.IntegerSet;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.injector.GamePhase;
|
||||
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||
@ -38,6 +42,7 @@ import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||
import com.comphenix.protocol.reflect.instances.ExistingGenerator;
|
||||
import com.comphenix.protocol.utility.MinecraftMethods;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
|
||||
/**
|
||||
* Represents a player hook into the NetServerHandler class.
|
||||
@ -45,8 +50,13 @@ import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
* @author Kristian
|
||||
*/
|
||||
class NetworkServerInjector extends PlayerInjector {
|
||||
// Disconnected field
|
||||
public static final ReportType REPORT_ASSUMING_DISCONNECT_FIELD = new ReportType("Unable to find 'disconnected' field. Assuming %s.");
|
||||
public static final ReportType REPORT_DISCONNECT_FIELD_MISSING = new ReportType("Cannot find disconnected field. Is ProtocolLib up to date?");
|
||||
public static final ReportType REPORT_DISCONNECT_FIELD_FAILURE = new ReportType("Unable to update disconnected field. Player quit event may be sent twice.");
|
||||
|
||||
private volatile static CallbackFilter callbackFilter;
|
||||
private volatile static boolean foundSendPacket;
|
||||
|
||||
private volatile static Field disconnectField;
|
||||
private InjectedServerConnection serverInjection;
|
||||
@ -168,11 +178,13 @@ class NetworkServerInjector extends PlayerInjector {
|
||||
callbackFilter = new CallbackFilter() {
|
||||
@Override
|
||||
public int accept(Method method) {
|
||||
if (method.equals(sendPacket))
|
||||
if (isCallableEqual(sendPacket, method)) {
|
||||
foundSendPacket = true;
|
||||
return 0;
|
||||
else
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -204,8 +216,10 @@ class NetworkServerInjector extends PlayerInjector {
|
||||
|
||||
// Inject it now
|
||||
if (proxyObject != null) {
|
||||
// This will be done by InjectedServerConnection instead
|
||||
//copyTo(serverHandler, proxyObject);
|
||||
// Did we override a sendPacket method?
|
||||
if (!foundSendPacket) {
|
||||
throw new IllegalArgumentException("Unable to find a sendPacket method in " + serverClass);
|
||||
}
|
||||
|
||||
serverInjection.replaceServerHandler(serverHandler, proxyObject);
|
||||
serverHandlerRef.setValue(proxyObject);
|
||||
@ -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() {
|
||||
if (proxyServerField != null && !proxyServerField.equals(serverHandlerRef.getField())) {
|
||||
try {
|
||||
@ -228,7 +256,7 @@ class NetworkServerInjector extends PlayerInjector {
|
||||
}
|
||||
|
||||
private Class<?> getFirstMinecraftSuperClass(Class<?> clazz) {
|
||||
if (clazz.getName().startsWith(MinecraftReflection.getMinecraftPackage()))
|
||||
if (MinecraftReflection.isMinecraftClass(clazz))
|
||||
return clazz;
|
||||
else if (clazz.equals(Object.class))
|
||||
return clazz;
|
||||
@ -288,7 +316,7 @@ class NetworkServerInjector extends PlayerInjector {
|
||||
// Assume it's the first ...
|
||||
if (disconnectField == null) {
|
||||
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
|
||||
if (disconnectField != null) {
|
||||
@ -298,15 +326,15 @@ class NetworkServerInjector extends PlayerInjector {
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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
|
||||
public UnsupportedListener checkListener(PacketListener listener) {
|
||||
public UnsupportedListener checkListener(MinecraftVersion version, PacketListener listener) {
|
||||
// We support everything
|
||||
return null;
|
||||
}
|
||||
|
@ -126,11 +126,18 @@ public interface PlayerInjectionHandler {
|
||||
* @throws IllegalAccessException If the reflection machinery failed.
|
||||
* @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;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @param version - the current Minecraft version, or NULL if unknown.
|
||||
* @param listeners - listeners to check.
|
||||
*/
|
||||
public abstract void checkListener(Set<PacketListener> listeners);
|
||||
@ -139,6 +146,7 @@ public interface PlayerInjectionHandler {
|
||||
* Determine if a listener is valid or not.
|
||||
* <p>
|
||||
* 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.
|
||||
*/
|
||||
public abstract void checkListener(PacketListener listener);
|
||||
|
@ -30,6 +30,8 @@ import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.Packets;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
@ -43,8 +45,23 @@ import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.reflect.VolatileField;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
|
||||
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
|
||||
private static Field netLoginNetworkField;
|
||||
@ -304,7 +321,7 @@ abstract class PlayerInjector implements SocketInjector {
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Just assume it's the first String method
|
||||
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
|
||||
@ -318,9 +335,9 @@ abstract class PlayerInjector implements SocketInjector {
|
||||
disconnect.invoke(handler, message);
|
||||
return;
|
||||
} 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) {
|
||||
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 {
|
||||
socket.close();
|
||||
} 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) {
|
||||
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) {
|
||||
|
||||
try {
|
||||
Object handler = FieldUtils.readField(serverHandlerField, notchEntity, true);
|
||||
|
||||
@ -352,7 +368,7 @@ abstract class PlayerInjector implements SocketInjector {
|
||||
return null;
|
||||
|
||||
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?
|
||||
try {
|
||||
@ -367,7 +383,7 @@ abstract class PlayerInjector implements SocketInjector {
|
||||
}
|
||||
|
||||
} 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
|
||||
@ -508,11 +524,13 @@ abstract class PlayerInjector implements SocketInjector {
|
||||
* Invoked before a new listener is registered.
|
||||
* <p>
|
||||
* 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.
|
||||
* @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.
|
||||
@ -531,7 +549,7 @@ abstract class PlayerInjector implements SocketInjector {
|
||||
try {
|
||||
updatedPlayer = (Player) MinecraftReflection.getBukkitEntity(getEntityPlayer(getNetHandler()));
|
||||
} 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) {
|
||||
reporter.reportDetailed(this, "Cannot handle server packet.", e, packet);
|
||||
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_HANDLE_PACKET).error(e).callerParam(packet));
|
||||
}
|
||||
|
||||
return packet;
|
||||
@ -639,10 +657,7 @@ abstract class PlayerInjector implements SocketInjector {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the real Bukkit player that we will use.
|
||||
* @param updatedPlayer - the real Bukkit player.
|
||||
*/
|
||||
@Override
|
||||
public void setUpdatedPlayer(Player updatedPlayer) {
|
||||
this.updatedPlayer = updatedPlayer;
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.injector.GamePhase;
|
||||
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||
import com.comphenix.protocol.injector.PacketFilterManager;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
@ -37,6 +38,7 @@ public class PlayerInjectorBuilder {
|
||||
protected ListenerInvoker invoker;
|
||||
protected Set<PacketListener> packetListeners;
|
||||
protected Server server;
|
||||
protected MinecraftVersion version;
|
||||
|
||||
/**
|
||||
* Set the class loader to use during class generation.
|
||||
@ -107,6 +109,16 @@ public class PlayerInjectorBuilder {
|
||||
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.
|
||||
*/
|
||||
@ -140,6 +152,6 @@ public class PlayerInjectorBuilder {
|
||||
|
||||
return new ProxyPlayerInjectionHandler(
|
||||
classLoader, reporter, injectionFilter,
|
||||
invoker, packetListeners, server);
|
||||
invoker, packetListeners, server, version);
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,8 @@ import com.comphenix.protocol.Packets;
|
||||
import com.comphenix.protocol.concurrency.BlockingHashMap;
|
||||
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.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
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.PacketFilterManager.PlayerInjectHooks;
|
||||
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.SocketInjector;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.cache.Cache;
|
||||
@ -57,6 +61,16 @@ import com.google.common.collect.Maps;
|
||||
* @author Kristian
|
||||
*/
|
||||
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
|
||||
private InjectedServerConnection serverInjection;
|
||||
|
||||
@ -91,6 +105,9 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
||||
// Used to invoke events
|
||||
private ListenerInvoker invoker;
|
||||
|
||||
// Current Minecraft version
|
||||
private MinecraftVersion version;
|
||||
|
||||
// Enabled packet filters
|
||||
private IntegerSet sendingFilters = new IntegerSet(Packets.MAXIMUM_PACKET_ID + 1);
|
||||
|
||||
@ -105,13 +122,14 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
||||
|
||||
public ProxyPlayerInjectionHandler(
|
||||
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.reporter = reporter;
|
||||
this.invoker = invoker;
|
||||
this.injectionFilter = injectionFilter;
|
||||
this.packetListeners = packetListeners;
|
||||
this.version = version;
|
||||
|
||||
this.inputStreamLookup = InputStreamLookupBuilder.newBuilder().
|
||||
server(server).
|
||||
@ -349,8 +367,9 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
||||
|
||||
} catch (Exception e) {
|
||||
// Mark this injection attempt as a failure
|
||||
reporter.reportDetailed(this, "Player hook " + tempHook.toString() + " failed.",
|
||||
e, player, injectionPoint, phase);
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_PLAYER_HOOK_FAILED).messageParam(tempHook).callerParam(player, injectionPoint, phase).error(e)
|
||||
);
|
||||
hookFailed = true;
|
||||
}
|
||||
|
||||
@ -358,7 +377,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
||||
tempHook = PlayerInjectHooks.values()[tempHook.ordinal() - 1];
|
||||
|
||||
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
|
||||
if (tempHook == PlayerInjectHooks.NONE) {
|
||||
@ -394,7 +413,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
||||
if (injector != null)
|
||||
injector.cleanupAll();
|
||||
} 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.
|
||||
* @param player - player to unregister.
|
||||
@ -444,7 +475,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
// 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 mcPacket - the packet to process.
|
||||
* @throws IllegalAccessException If the reflection machinery failed.
|
||||
* @throws InvocationTargetException If the underlying method caused an error.
|
||||
*/
|
||||
@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);
|
||||
|
||||
// Process the given packet, or simply give up
|
||||
@ -532,6 +563,11 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
||||
if (injector == null) {
|
||||
// Try getting it from the player itself
|
||||
SocketAddress address = player.getAddress();
|
||||
|
||||
// Must have logged out - there's nothing we can do
|
||||
if (address == null)
|
||||
return null;
|
||||
|
||||
// Look that up without blocking
|
||||
SocketInjector result = inputStreamLookup.peekSocketInjector(address);
|
||||
|
||||
@ -619,12 +655,13 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
||||
@Override
|
||||
public void checkListener(PacketListener listener) {
|
||||
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
|
||||
if (result != null) {
|
||||
reporter.reportWarning(this, "Cannot fully register listener for " +
|
||||
PacketAdapter.getPluginName(listener) + ": " + result.toString());
|
||||
reporter.reportWarning(this,
|
||||
Report.newBuilder(REPORT_UNSUPPPORTED_LISTENER).messageParam(PacketAdapter.getPluginName(listener), result)
|
||||
);
|
||||
|
||||
// These are illegal
|
||||
for (int packetID : result.getPackets())
|
||||
|
@ -1,22 +1,14 @@
|
||||
package com.comphenix.protocol.injector.server;
|
||||
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketAddress;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
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 {
|
||||
// Used to access the inner input stream of a filtered input stream
|
||||
private static Field filteredInputField;
|
||||
|
||||
// Error reporter
|
||||
protected final ErrorReporter reporter;
|
||||
|
||||
@ -28,30 +20,6 @@ public abstract class AbstractInputStreamLookup {
|
||||
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.
|
||||
* @param container - class that contains a ServerSocket field.
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -18,6 +18,9 @@ import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.google.common.collect.MapMaker;
|
||||
|
||||
class InputStreamReflectLookup extends AbstractInputStreamLookup {
|
||||
// Used to access the inner input stream of a filtered input stream
|
||||
private static Field filteredInputField;
|
||||
|
||||
// The default lookup timeout
|
||||
private static final long DEFAULT_TIMEOUT = 2000; // ms
|
||||
|
||||
@ -123,6 +126,30 @@ class InputStreamReflectLookup extends AbstractInputStreamLookup {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the underlying input stream that is associated with a given filter input stream.
|
||||
* @param filtered - the filter input stream.
|
||||
* @return The underlying input stream that is being filtered.
|
||||
* @throws FieldAccessException Unable to access input stream.
|
||||
*/
|
||||
protected static InputStream getInputStream(FilterInputStream filtered) {
|
||||
if (filteredInputField == null)
|
||||
filteredInputField = FuzzyReflection.fromClass(FilterInputStream.class, true).
|
||||
getFieldByType("in", InputStream.class);
|
||||
|
||||
InputStream current = filtered;
|
||||
|
||||
try {
|
||||
// Iterate until we find the real input stream
|
||||
while (current instanceof FilterInputStream) {
|
||||
current = (InputStream) FieldUtils.readField(filteredInputField, current, true);
|
||||
}
|
||||
return current;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Cannot access filtered input field.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSocketInjector(SocketAddress address, SocketInjector injector) {
|
||||
if (address == null)
|
||||
|
@ -58,4 +58,10 @@ public interface SocketInjector {
|
||||
* @param delegate - the new injector.
|
||||
*/
|
||||
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);
|
||||
}
|
@ -102,6 +102,8 @@ public class TemporaryPlayerFactory {
|
||||
throw new IllegalStateException("Unable to find injector.");
|
||||
|
||||
// Use the socket to get the address
|
||||
if (methodName.equalsIgnoreCase("isOnline"))
|
||||
return injector.getSocket() != null && injector.getSocket().isConnected();
|
||||
if (methodName.equalsIgnoreCase("getName"))
|
||||
return "UNKNOWN[" + injector.getSocket().getRemoteSocketAddress() + "]";
|
||||
if (methodName.equalsIgnoreCase("getPlayer"))
|
||||
|
@ -74,7 +74,7 @@ class DummyPlayerHandler implements PlayerInjectionHandler {
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@ -119,4 +119,9 @@ class DummyPlayerHandler implements PlayerInjectionHandler {
|
||||
public void postWorldLoaded() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePlayer(Player player) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,9 @@ import net.sf.cglib.proxy.NoOp;
|
||||
|
||||
import com.comphenix.protocol.Packets;
|
||||
import com.comphenix.protocol.concurrency.IntegerSet;
|
||||
import com.comphenix.protocol.error.DelegatedErrorReporter;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||
@ -47,7 +49,7 @@ public class SpigotPacketInjector implements SpigotPacketListener {
|
||||
private static volatile boolean classChecked;
|
||||
|
||||
// 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
|
||||
private Set<Object> ignoredPackets = Collections.newSetFromMap(new MapMaker().weakKeys().<Object, Boolean>makeMap());
|
||||
@ -275,7 +277,8 @@ public class SpigotPacketInjector implements SpigotPacketListener {
|
||||
if (dummyInjector == null) {
|
||||
// Inject the network manager
|
||||
try {
|
||||
NetworkObjectInjector created = new NetworkObjectInjector(classLoader, reporter, null, invoker, null);
|
||||
NetworkObjectInjector created = new NetworkObjectInjector(
|
||||
classLoader, filterImpossibleWarnings(reporter), null, invoker, null);
|
||||
|
||||
if (MinecraftReflection.isLoginHandler(connection)) {
|
||||
created.initialize(connection);
|
||||
@ -303,6 +306,23 @@ public class SpigotPacketInjector implements SpigotPacketListener {
|
||||
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.
|
||||
* @param networkManager - the associated network manager.
|
||||
@ -400,7 +420,8 @@ public class SpigotPacketInjector implements SpigotPacketListener {
|
||||
*/
|
||||
void injectPlayer(Player player) {
|
||||
try {
|
||||
NetworkObjectInjector dummy = new NetworkObjectInjector(classLoader, reporter, player, invoker, null);
|
||||
NetworkObjectInjector dummy = new NetworkObjectInjector(
|
||||
classLoader, filterImpossibleWarnings(reporter), player, invoker, null);
|
||||
dummy.initializePlayer(player);
|
||||
|
||||
// Save this player for the network manager
|
||||
|
@ -29,9 +29,9 @@ import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.reflect.compiler.StructureCompiler.StructureKey;
|
||||
import com.google.common.collect.Lists;
|
||||
@ -46,6 +46,8 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
* @author Kristian
|
||||
*/
|
||||
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.
|
||||
@ -118,11 +120,13 @@ public class BackgroundCompiler {
|
||||
}
|
||||
|
||||
// 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)
|
||||
throw new IllegalArgumentException("loader cannot be NULL");
|
||||
if (executor == null)
|
||||
throw new IllegalArgumentException("executor cannot be NULL");
|
||||
if (reporter == null)
|
||||
throw new IllegalArgumentException("reporter cannot be NULL.");
|
||||
|
||||
this.compiler = new StructureCompiler(loader);
|
||||
this.reporter = reporter;
|
||||
@ -198,6 +202,11 @@ public class BackgroundCompiler {
|
||||
|
||||
synchronized (listenerLock) {
|
||||
list = listeners.get(key);
|
||||
|
||||
// Prevent ConcurrentModificationExceptions
|
||||
if (list != null) {
|
||||
list = Lists.newArrayList(list);
|
||||
}
|
||||
}
|
||||
|
||||
// Only execute the listeners if there is a list
|
||||
@ -217,13 +226,9 @@ public class BackgroundCompiler {
|
||||
setEnabled(false);
|
||||
|
||||
// Inform about this error as best as we can
|
||||
if (reporter != null) {
|
||||
reporter.reportDetailed(BackgroundCompiler.this,
|
||||
"Cannot compile structure. Disabing compiler.", e, uncompiled);
|
||||
} else {
|
||||
System.err.println("Exception occured in structure compiler: ");
|
||||
e.printStackTrace();
|
||||
}
|
||||
Report.newBuilder(REPORT_CANNOT_COMPILE_STRUCTURE_MODIFIER).callerParam(uncompiled).error(e)
|
||||
);
|
||||
}
|
||||
|
||||
// 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
|
||||
// is only an optmization and not really essential we'll just log this failure
|
||||
// and move on.
|
||||
reporter.reportWarning(this, "Unable to schedule compilation task.", e);
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_SCHEDULE_COMPILATION).error(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,8 @@ import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
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.google.common.base.Objects;
|
||||
import com.google.common.primitives.Primitives;
|
||||
@ -92,6 +94,7 @@ import net.sf.cglib.asm.*;
|
||||
* @author Kristian
|
||||
*/
|
||||
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
|
||||
@SuppressWarnings("rawtypes")
|
||||
@ -210,7 +213,8 @@ public final class StructureCompiler {
|
||||
} catch (OutOfMemoryError e) {
|
||||
// Print the number of generated classes by the current instances
|
||||
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;
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IllegalStateException("Used invalid parameters in instance creation", e);
|
||||
|
@ -63,6 +63,25 @@ public abstract class AbstractFuzzyMatcher<T> implements Comparable<AbstractFuzz
|
||||
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
|
||||
public int compareTo(AbstractFuzzyMatcher<T> obj) {
|
||||
if (obj instanceof AbstractFuzzyMatcher) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.comphenix.protocol.reflect.fuzzy;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
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>> constructorContracts;
|
||||
|
||||
private final ImmutableList<AbstractFuzzyMatcher<Class<?>>> baseclassContracts;
|
||||
private final ImmutableList<AbstractFuzzyMatcher<Class<?>>> interfaceContracts;
|
||||
|
||||
/**
|
||||
* Represents a class contract builder.
|
||||
* @author Kristian
|
||||
@ -33,6 +37,9 @@ public class FuzzyClassContract extends AbstractFuzzyMatcher<Class<?>> {
|
||||
private List<AbstractFuzzyMatcher<MethodInfo>> methodContracts = 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.
|
||||
* @param matcher - new field contract.
|
||||
@ -90,17 +97,53 @@ public class FuzzyClassContract extends AbstractFuzzyMatcher<Class<?>> {
|
||||
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() {
|
||||
Collections.sort(fieldContracts);
|
||||
Collections.sort(methodContracts);
|
||||
Collections.sort(constructorContracts);
|
||||
Collections.sort(baseclassContracts);
|
||||
Collections.sort(interfaceContracts);
|
||||
|
||||
// Construct a new class matcher
|
||||
return new FuzzyClassContract(
|
||||
ImmutableList.copyOf(fieldContracts),
|
||||
ImmutableList.copyOf(methodContracts),
|
||||
ImmutableList.copyOf(constructorContracts)
|
||||
);
|
||||
return new FuzzyClassContract(this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,17 +157,15 @@ public class FuzzyClassContract extends AbstractFuzzyMatcher<Class<?>> {
|
||||
|
||||
/**
|
||||
* Constructs a new fuzzy class contract with the given contracts.
|
||||
* @param fieldContracts - field contracts.
|
||||
* @param methodContracts - method contracts.
|
||||
* @param constructorContracts - constructor contracts.
|
||||
* @param builder - the builder that is constructing us.
|
||||
*/
|
||||
private FuzzyClassContract(ImmutableList<AbstractFuzzyMatcher<Field>> fieldContracts,
|
||||
ImmutableList<AbstractFuzzyMatcher<MethodInfo>> methodContracts,
|
||||
ImmutableList<AbstractFuzzyMatcher<MethodInfo>> constructorContracts) {
|
||||
private FuzzyClassContract(Builder builder) {
|
||||
super();
|
||||
this.fieldContracts = fieldContracts;
|
||||
this.methodContracts = methodContracts;
|
||||
this.constructorContracts = constructorContracts;
|
||||
this.fieldContracts = ImmutableList.copyOf(builder.fieldContracts);
|
||||
this.methodContracts = ImmutableList.copyOf(builder.methodContracts);
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
protected int calculateRoundNumber() {
|
||||
// Find the highest round number
|
||||
return combineRounds(findHighestRound(fieldContracts),
|
||||
combineRounds(findHighestRound(methodContracts),
|
||||
findHighestRound(constructorContracts)));
|
||||
findHighestRound(methodContracts),
|
||||
findHighestRound(constructorContracts),
|
||||
findHighestRound(interfaceContracts),
|
||||
findHighestRound(baseclassContracts));
|
||||
}
|
||||
|
||||
private <T> int findHighestRound(Collection<AbstractFuzzyMatcher<T>> list) {
|
||||
@ -179,12 +242,19 @@ public class FuzzyClassContract extends AbstractFuzzyMatcher<Class<?>> {
|
||||
FuzzyReflection reflection = FuzzyReflection.fromClass(value, true);
|
||||
|
||||
// Make sure all the contracts are valid
|
||||
return processContracts(reflection.getFields(), value, fieldContracts) &&
|
||||
processContracts(MethodInfo.fromMethods(reflection.getMethods()), value, methodContracts) &&
|
||||
processContracts(MethodInfo.fromConstructors(value.getDeclaredConstructors()), value, constructorContracts);
|
||||
return (fieldContracts.size() == 0 ||
|
||||
processContracts(reflection.getFields(), value, fieldContracts)) &&
|
||||
(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()];
|
||||
int count = accepted.length;
|
||||
|
||||
@ -205,7 +275,18 @@ public class FuzzyClassContract extends AbstractFuzzyMatcher<Class<?>> {
|
||||
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
|
||||
for (int i = 0; i < matchers.size(); i++) {
|
||||
if (!accepted[i]) {
|
||||
@ -235,6 +316,12 @@ public class FuzzyClassContract extends AbstractFuzzyMatcher<Class<?>> {
|
||||
if (constructorContracts.size() > 0) {
|
||||
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}";
|
||||
}
|
||||
}
|
||||
|
@ -327,6 +327,7 @@ public class DefaultInstances implements InstanceProvider {
|
||||
try {
|
||||
return (T) constructor.newInstance(params);
|
||||
} catch (Exception e) {
|
||||
//e.printStackTrace();
|
||||
// Cannot create it
|
||||
return null;
|
||||
}
|
||||
|
@ -81,6 +81,9 @@ public class MinecraftReflection {
|
||||
private static Constructor<?> craftNMSConstructor;
|
||||
private static Constructor<?> craftBukkitConstructor;
|
||||
|
||||
// Matches classes
|
||||
private static AbstractFuzzyMatcher<Class<?>> fuzzyMatcher;
|
||||
|
||||
// New in 1.4.5
|
||||
private static Method craftNMSMethod;
|
||||
private static Method craftBukkitMethod;
|
||||
@ -89,6 +92,11 @@ public class MinecraftReflection {
|
||||
// net.minecraft.server
|
||||
private static Class<?> itemStackArrayClass;
|
||||
|
||||
/**
|
||||
* Whether or not we're currently initializing the reflection handler.
|
||||
*/
|
||||
private static boolean initializing;
|
||||
|
||||
private MinecraftReflection() {
|
||||
// No need to make this constructable.
|
||||
}
|
||||
@ -108,7 +116,9 @@ public class MinecraftReflection {
|
||||
* @return A matcher for Minecraft objects.
|
||||
*/
|
||||
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
|
||||
if (MINECRAFT_FULL_PACKAGE != null)
|
||||
return MINECRAFT_FULL_PACKAGE;
|
||||
if (initializing)
|
||||
throw new IllegalStateException("Already initializing minecraft package!");
|
||||
initializing = true;
|
||||
|
||||
Server craftServer = Bukkit.getServer();
|
||||
|
||||
@ -129,6 +142,9 @@ public class MinecraftReflection {
|
||||
Class<?> craftClass = craftServer.getClass();
|
||||
CRAFTBUKKIT_PACKAGE = getPackage(craftClass.getCanonicalName());
|
||||
|
||||
// Libigot patch
|
||||
handleLibigot();
|
||||
|
||||
// Next, do the same for CraftEntity.getHandle() in order to get the correct Minecraft package
|
||||
Class<?> craftEntity = getCraftEntityClass();
|
||||
Method getHandle = craftEntity.getMethod("getHandle");
|
||||
@ -141,16 +157,16 @@ public class MinecraftReflection {
|
||||
MINECRAFT_PREFIX_PACKAGE = MINECRAFT_FULL_PACKAGE;
|
||||
|
||||
// The package is usualy flat, so go with that assumtion
|
||||
DYNAMIC_PACKAGE_MATCHER =
|
||||
String matcher =
|
||||
(MINECRAFT_PREFIX_PACKAGE.length() > 0 ?
|
||||
Pattern.quote(MINECRAFT_PREFIX_PACKAGE + ".") : "") + "\\w+";
|
||||
|
||||
// We'll still accept the default location, however
|
||||
DYNAMIC_PACKAGE_MATCHER = "(" + DYNAMIC_PACKAGE_MATCHER + ")|(" + MINECRAFT_OBJECT + ")";
|
||||
setDynamicPackageMatcher("(" + matcher + ")|(" + MINECRAFT_OBJECT + ")");
|
||||
|
||||
} else {
|
||||
// Use the standard matcher
|
||||
DYNAMIC_PACKAGE_MATCHER = MINECRAFT_OBJECT;
|
||||
setDynamicPackageMatcher(MINECRAFT_OBJECT);
|
||||
}
|
||||
|
||||
return MINECRAFT_FULL_PACKAGE;
|
||||
@ -159,13 +175,41 @@ public class MinecraftReflection {
|
||||
throw new RuntimeException("Security violation. Cannot get handle method.", e);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new IllegalStateException("Cannot find getHandle() method on server. Is this a modified CraftBukkit version?", e);
|
||||
} finally {
|
||||
initializing = false;
|
||||
}
|
||||
|
||||
} else {
|
||||
initializing = false;
|
||||
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.
|
||||
* @param minecraftPackage - the current Minecraft package.
|
||||
@ -176,7 +220,7 @@ public class MinecraftReflection {
|
||||
CRAFTBUKKIT_PACKAGE = craftBukkitPackage;
|
||||
|
||||
// Standard matcher
|
||||
DYNAMIC_PACKAGE_MATCHER = MINECRAFT_OBJECT;
|
||||
setDynamicPackageMatcher(MINECRAFT_OBJECT);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -244,8 +288,7 @@ public class MinecraftReflection {
|
||||
if (clazz == null)
|
||||
throw new IllegalArgumentException("Class cannot be NULL.");
|
||||
|
||||
// Doesn't matter if we don't check for the version here
|
||||
return clazz.getName().startsWith(MINECRAFT_PREFIX_PACKAGE);
|
||||
return getMinecraftObjectMatcher().isMatch(clazz, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -809,9 +852,15 @@ public class MinecraftReflection {
|
||||
returnTypeMatches(tagCompoundContract).
|
||||
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
|
||||
return setMinecraftClass("NBTBase", selected.getReturnType());
|
||||
return setMinecraftClass("NBTBase", nbtBase);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,7 @@
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol;
|
||||
package com.comphenix.protocol.utility;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@ -31,7 +31,7 @@ import com.google.common.collect.Ordering;
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class MinecraftVersion implements Comparable<MinecraftVersion> {
|
||||
public class MinecraftVersion implements Comparable<MinecraftVersion> {
|
||||
/**
|
||||
* Regular expression used to parse version strings.
|
||||
*/
|
@ -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() + ")");
|
||||
}
|
||||
}
|
@ -6,8 +6,6 @@ global:
|
||||
|
||||
# Number of seconds to wait until a new update is downloaded
|
||||
delay: 43200 # 12 hours
|
||||
# Last update time
|
||||
last: 0
|
||||
|
||||
metrics: true
|
||||
|
||||
@ -19,3 +17,9 @@ global:
|
||||
|
||||
# Override the starting injecting method
|
||||
injection method:
|
||||
|
||||
# Whether or not to enable the filter command
|
||||
debug: false
|
||||
|
||||
# The engine used by the filter command
|
||||
script engine: JavaScript
|
@ -1,9 +1,11 @@
|
||||
name: ProtocolLib
|
||||
version: 2.3.0
|
||||
version: 2.4.3
|
||||
description: Provides read/write access to the Minecraft protocol.
|
||||
author: Comphenix
|
||||
website: http://www.comphenix.net/ProtocolLib
|
||||
|
||||
main: com.comphenix.protocol.ProtocolLibrary
|
||||
load: startup
|
||||
database: false
|
||||
|
||||
commands:
|
||||
@ -17,6 +19,12 @@ commands:
|
||||
usage: /<command> add|remove|names client|server [ID start]-[ID stop] [detailed]
|
||||
permission: protocol.admin
|
||||
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:
|
||||
protocol.*:
|
||||
|
@ -4,8 +4,10 @@ import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import net.minecraft.server.v1_5_R2.StatisticList;
|
||||
|
||||
// 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.Material;
|
||||
@ -34,6 +36,12 @@ public class BukkitInitialization {
|
||||
|
||||
initializePackage();
|
||||
|
||||
try {
|
||||
StatisticList.b();
|
||||
} catch (Exception e) {
|
||||
// Swallow
|
||||
}
|
||||
|
||||
// Mock the server object
|
||||
Server mockedServer = mock(Server.class);
|
||||
ItemFactory mockedFactory = mock(CraftItemFactory.class);
|
||||
@ -55,6 +63,6 @@ public class BukkitInitialization {
|
||||
*/
|
||||
public static void initializePackage() {
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,8 @@ import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
|
||||
public class MinecraftVersionTest {
|
||||
|
||||
@Test
|
||||
|
@ -22,7 +22,7 @@ import java.lang.reflect.Array;
|
||||
import java.util.List;
|
||||
|
||||
// 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.WorldType;
|
||||
|
In neuem Issue referenzieren
Einen Benutzer sperren