Archiviert
13
0

Merge branch 'master' into gh-pages

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

Datei anzeigen

@ -7,6 +7,12 @@
</attributes>
</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"/>

Datei anzeigen

@ -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>
@ -219,16 +219,16 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -21,6 +21,7 @@ import java.lang.reflect.InvocationTargetException;
import org.bukkit.entity.Player;
import 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.
*/

Datei anzeigen

@ -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;

Datei anzeigen

@ -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.
@ -100,6 +165,17 @@ class ProtocolConfig {
System.out.println("[ProtocolLib] Created default configuration.");
}
}
/**
* Set a particular configuration key value pair.
* @param section - the configuration root.
* @param path - the path to the key.
* @param value - the value to set.
*/
private void setConfig(ConfigurationSection section, String path, Object value) {
configChanged = true;
section.set(path, value);
}
/**
* Retrieve a reference to the configuration file.
@ -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,17 +254,9 @@ class ProtocolConfig {
// Silently fix the delay
if (delaySeconds < DEFAULT_UPDATER_DELAY)
delaySeconds = DEFAULT_UPDATER_DELAY;
updater.set(UPDATER_DELAY, delaySeconds);
setConfig(updater, UPDATER_DELAY, delaySeconds);
}
/**
* Retrieve the last time we updated, in seconds since 1970.01.01 00:00.
* @return Last update time.
*/
public long getAutoLastTime() {
return updater.getLong(UPDATER_LAST_TIME, 0);
}
/**
* The version of Minecraft to ignore the built-in safety feature.
* @return The version to ignore ProtocolLib's satefy.
@ -188,7 +274,7 @@ class ProtocolConfig {
* @param ignoreVersion - the version of Minecraft where the satefy will be disabled.
*/
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() {
plugin.saveConfig();
if (valuesChanged)
saveLastUpdate(lastUpdateTime);
if (configChanged)
plugin.saveConfig();
// And we're done
valuesChanged = false;
configChanged = false;
}
}

Datei anzeigen

@ -33,8 +33,11 @@ import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import 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() {

Datei anzeigen

@ -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.
*/

Datei anzeigen

@ -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.

Datei anzeigen

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

Datei anzeigen

@ -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,21 +59,20 @@ 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) {
// It would be nice if we could use a PriorityBlockingQueue, but it doesn't preseve iterator order,
// which is a essential feature for our purposes.
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();
}
}

Datei anzeigen

@ -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.

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -0,0 +1,66 @@
package com.comphenix.protocol.error;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import com.comphenix.protocol.reflect.FieldAccessException;
/**
* Represents a strongly-typed report. Subclasses should be immutable.
* <p>
* By convention, a report must be declared as a static field publicly accessible from the sender class.
* @author Kristian
*/
public class ReportType {
private final String errorFormat;
/**
* Construct a new report type.
* @param errorFormat - string used to format the underlying report.
*/
public ReportType(String errorFormat) {
this.errorFormat = errorFormat;
}
/**
* Convert the given report to a string, using the provided parameters.
* @param parameters - parameters to insert, or NULL to insert nothing.
* @return The full report in string format.
*/
public String getMessage(Object[] parameters) {
if (parameters == null || parameters.length == 0)
return toString();
else
return String.format(errorFormat, parameters);
}
@Override
public String toString() {
return errorFormat;
}
/**
* Retrieve all publicly associated reports.
* @param clazz - sender class.
* @return All associated reports.
*/
public static ReportType[] getReports(Class<?> clazz) {
if (clazz == null)
throw new IllegalArgumentException("clazz cannot be NULL.");
List<ReportType> result = new ArrayList<ReportType>();
for (Field field : clazz.getFields()) {
if (Modifier.isStatic(field.getModifiers()) &&
ReportType.class.isAssignableFrom(field.getDeclaringClass())) {
try {
result.add((ReportType) field.get(null));
} catch (IllegalAccessException e) {
throw new FieldAccessException("Unable to access field.", e);
}
}
}
return result.toArray(new ReportType[0]);
}
}

Datei anzeigen

@ -44,6 +44,9 @@ public class PacketEvent extends EventObject implements Cancellable {
private AsyncMarker asyncMarker;
private 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.
@ -203,7 +235,7 @@ public class PacketEvent extends EventObject implements Cancellable {
public boolean isAsynchronous() {
return asynchronous;
}
private void writeObject(ObjectOutputStream output) throws IOException {
// Default serialization
output.defaultWriteObject();

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -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,12 +63,29 @@ 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.
* @author Kristian
@ -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

Datei anzeigen

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

Datei anzeigen

@ -19,8 +19,10 @@ package com.comphenix.protocol.injector;
import java.util.Collection;
import 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());
}
}
}
}

Datei anzeigen

@ -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);
}

Datei anzeigen

@ -1,228 +1,303 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.packet;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.sf.cglib.proxy.Factory;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableSet;
/**
* Static packet registry in Minecraft.
*
* @author Kristian
*/
@SuppressWarnings("rawtypes")
public class PacketRegistry {
// Fuzzy reflection
private static FuzzyReflection packetRegistry;
// The packet class to packet ID translator
private static Map<Class, Integer> packetToID;
// Whether or not certain packets are sent by the client or the server
private static ImmutableSet<Integer> serverPackets;
private static ImmutableSet<Integer> clientPackets;
// The underlying sets
private static Set<Integer> serverPacketsRef;
private static Set<Integer> clientPacketsRef;
// New proxy values
private static Map<Integer, Class> overwrittenPackets = new HashMap<Integer, Class>();
// Vanilla packets
private static Map<Integer, Class> previousValues = new HashMap<Integer, Class>();
@SuppressWarnings({ "unchecked" })
public static Map<Class, Integer> getPacketToID() {
// Initialize it, if we haven't already
if (packetToID == null) {
try {
Field packetsField = getPacketRegistry().getFieldByType("packetsField", Map.class);
packetToID = (Map<Class, Integer>) FieldUtils.readStaticField(packetsField, true);
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to retrieve the packetClassToIdMap", e);
}
}
return packetToID;
}
/**
* Retrieve the cached fuzzy reflection instance allowing access to the packet registry.
* @return Reflected packet registry.
*/
private static FuzzyReflection getPacketRegistry() {
if (packetRegistry == null)
packetRegistry = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true);
return packetRegistry;
}
/**
* Retrieve the injected proxy classes handlig each packet ID.
* @return Injected classes.
*/
public static Map<Integer, Class> getOverwrittenPackets() {
return overwrittenPackets;
}
/**
* Retrieve the vanilla classes handling each packet ID.
* @return Vanilla classes.
*/
public static Map<Integer, Class> getPreviousPackets() {
return previousValues;
}
/**
* Retrieve every known and supported server packet.
* @return An immutable set of every known server packet.
* @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft.
*/
public static Set<Integer> getServerPackets() throws FieldAccessException {
initializeSets();
return serverPackets;
}
/**
* Retrieve every known and supported client packet.
* @return An immutable set of every known client packet.
* @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft.
*/
public static Set<Integer> getClientPackets() throws FieldAccessException {
initializeSets();
return clientPackets;
}
@SuppressWarnings("unchecked")
private static void initializeSets() throws FieldAccessException {
if (serverPacketsRef == null || clientPacketsRef == null) {
List<Field> sets = getPacketRegistry().getFieldListByType(Set.class);
try {
if (sets.size() > 1) {
serverPacketsRef = (Set<Integer>) FieldUtils.readStaticField(sets.get(0), true);
clientPacketsRef = (Set<Integer>) FieldUtils.readStaticField(sets.get(1), true);
// Impossible
if (serverPacketsRef == null || clientPacketsRef == null)
throw new FieldAccessException("Packet sets are in an illegal state.");
// NEVER allow callers to modify the underlying sets
serverPackets = ImmutableSet.copyOf(serverPacketsRef);
clientPackets = ImmutableSet.copyOf(clientPacketsRef);
} else {
throw new FieldAccessException("Cannot retrieve packet client/server sets.");
}
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot access field.", e);
}
} else {
// Copy over again if it has changed
if (serverPacketsRef != null && serverPacketsRef.size() != serverPackets.size())
serverPackets = ImmutableSet.copyOf(serverPacketsRef);
if (clientPacketsRef != null && clientPacketsRef.size() != clientPackets.size())
clientPackets = ImmutableSet.copyOf(clientPacketsRef);
}
}
/**
* Retrieves the correct packet class from a given packet ID.
* @param packetID - the packet ID.
* @return The associated class.
*/
public static Class getPacketClassFromID(int packetID) {
return getPacketClassFromID(packetID, false);
}
/**
* Retrieves the correct packet class from a given packet ID.
* @param packetID - the packet ID.
* @param forceVanilla - whether or not to look for vanilla classes, not injected classes.
* @return The associated class.
*/
public static Class getPacketClassFromID(int packetID, boolean forceVanilla) {
Map<Integer, Class> lookup = forceVanilla ? previousValues : overwrittenPackets;
// Optimized lookup
if (lookup.containsKey(packetID)) {
return removeEnhancer(lookup.get(packetID), forceVanilla);
}
// Will most likely not be used
for (Map.Entry<Class, Integer> entry : getPacketToID().entrySet()) {
if (Objects.equal(entry.getValue(), packetID)) {
// Attempt to get the vanilla class here too
if (!forceVanilla || MinecraftReflection.isMinecraftClass(entry.getKey()))
return removeEnhancer(entry.getKey(), forceVanilla);
}
}
throw new IllegalArgumentException("The packet ID " + packetID + " is not registered.");
}
/**
* Retrieve the packet ID of a given packet.
* @param packet - the type of packet to check.
* @return The ID of the given packet.
* @throws IllegalArgumentException If this is not a valid packet.
*/
public static int getPacketID(Class<?> packet) {
if (packet == null)
throw new IllegalArgumentException("Packet type class cannot be NULL.");
if (!MinecraftReflection.getPacketClass().isAssignableFrom(packet))
throw new IllegalArgumentException("Type must be a packet.");
// The registry contains both the overridden and original packets
return getPacketToID().get(packet);
}
/**
* Find the first superclass that is not a CBLib proxy object.
* @param clazz - the class whose hierachy we're going to search through.
* @param remove - whether or not to skip enhanced (proxy) classes.
* @return If remove is TRUE, the first superclass that is not a proxy.
*/
private static Class removeEnhancer(Class clazz, boolean remove) {
if (remove) {
// Get the underlying vanilla class
while (Factory.class.isAssignableFrom(clazz) && !clazz.equals(Object.class)) {
clazz = clazz.getSuperclass();
}
}
return clazz;
}
}
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.packet;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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;
/**
* Static packet registry in Minecraft.
*
* @author Kristian
*/
@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;
// The packet class to packet ID translator
private static Map<Class, Integer> packetToID;
// Whether or not certain packets are sent by the client or the server
private static ImmutableSet<Integer> serverPackets;
private static ImmutableSet<Integer> clientPackets;
// The underlying sets
private static Set<Integer> serverPacketsRef;
private static Set<Integer> clientPacketsRef;
// New proxy values
private static Map<Integer, Class> overwrittenPackets = new HashMap<Integer, Class>();
// Vanilla packets
private static Map<Integer, Class> previousValues = new HashMap<Integer, Class>();
@SuppressWarnings({ "unchecked" })
public static Map<Class, Integer> getPacketToID() {
// Initialize it, if we haven't already
if (packetToID == null) {
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);
}
}
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.
*/
private static FuzzyReflection getPacketRegistry() {
if (packetRegistry == null)
packetRegistry = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true);
return packetRegistry;
}
/**
* Retrieve the injected proxy classes handlig each packet ID.
* @return Injected classes.
*/
public static Map<Integer, Class> getOverwrittenPackets() {
return overwrittenPackets;
}
/**
* Retrieve the vanilla classes handling each packet ID.
* @return Vanilla classes.
*/
public static Map<Integer, Class> getPreviousPackets() {
return previousValues;
}
/**
* Retrieve every known and supported server packet.
* @return An immutable set of every known server packet.
* @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft.
*/
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;
}
/**
* Retrieve every known and supported client packet.
* @return An immutable set of every known client packet.
* @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft.
*/
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;
}
@SuppressWarnings("unchecked")
private static void initializeSets() throws FieldAccessException {
if (serverPacketsRef == null || clientPacketsRef == null) {
List<Field> sets = getPacketRegistry().getFieldListByType(Set.class);
try {
if (sets.size() > 1) {
serverPacketsRef = (Set<Integer>) FieldUtils.readStaticField(sets.get(0), true);
clientPacketsRef = (Set<Integer>) FieldUtils.readStaticField(sets.get(1), true);
// Impossible
if (serverPacketsRef == null || clientPacketsRef == null)
throw new FieldAccessException("Packet sets are in an illegal state.");
// NEVER allow callers to modify the underlying sets
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.");
}
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot access field.", e);
}
} else {
// Copy over again if it has changed
if (serverPacketsRef != null && serverPacketsRef.size() != serverPackets.size())
serverPackets = ImmutableSet.copyOf(serverPacketsRef);
if (clientPacketsRef != null && clientPacketsRef.size() != clientPackets.size())
clientPackets = ImmutableSet.copyOf(clientPacketsRef);
}
}
/**
* Retrieves the correct packet class from a given packet ID.
* @param packetID - the packet ID.
* @return The associated class.
*/
public static Class getPacketClassFromID(int packetID) {
return getPacketClassFromID(packetID, false);
}
/**
* Retrieves the correct packet class from a given packet ID.
* @param packetID - the packet ID.
* @param forceVanilla - whether or not to look for vanilla classes, not injected classes.
* @return The associated class.
*/
public static Class getPacketClassFromID(int packetID, boolean forceVanilla) {
Map<Integer, Class> lookup = forceVanilla ? previousValues : overwrittenPackets;
// Optimized lookup
if (lookup.containsKey(packetID)) {
return removeEnhancer(lookup.get(packetID), forceVanilla);
}
// Will most likely not be used
for (Map.Entry<Class, Integer> entry : getPacketToID().entrySet()) {
if (Objects.equal(entry.getValue(), packetID)) {
// Attempt to get the vanilla class here too
if (!forceVanilla || MinecraftReflection.isMinecraftClass(entry.getKey()))
return removeEnhancer(entry.getKey(), forceVanilla);
}
}
throw new IllegalArgumentException("The packet ID " + packetID + " is not registered.");
}
/**
* Retrieve the packet ID of a given packet.
* @param packet - the type of packet to check.
* @return The ID of the given packet.
* @throws IllegalArgumentException If this is not a valid packet.
*/
public static int getPacketID(Class<?> packet) {
if (packet == null)
throw new IllegalArgumentException("Packet type class cannot be NULL.");
if (!MinecraftReflection.getPacketClass().isAssignableFrom(packet))
throw new IllegalArgumentException("Type must be a packet.");
// The registry contains both the overridden and original packets
return getPacketToID().get(packet);
}
/**
* Find the first superclass that is not a CBLib proxy object.
* @param clazz - the class whose hierachy we're going to search through.
* @param remove - whether or not to skip enhanced (proxy) classes.
* @return If remove is TRUE, the first superclass that is not a proxy.
*/
private static Class removeEnhancer(Class clazz, boolean remove) {
if (remove) {
// Get the underlying vanilla class
while (Factory.class.isAssignableFrom(clazz) && !clazz.equals(Object.class)) {
clazz = clazz.getSuperclass();
}
}
return clazz;
}
}

Datei anzeigen

@ -37,6 +37,7 @@ import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.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 {
intHashMap = FieldUtils.readField(intHashMapField, (Object) null, true);
} catch (IllegalArgumentException e) {
throw new RuntimeException("Minecraft is incompatible.", e);
lookup = new IntHashMapLookup();
} catch (Exception e1) {
try {
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);
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 values
previous.put(packetID, old);
registry.put(proxy, packetID);
overwritten.put(packetID, proxy);
lookup.setLookup(packetID, proxy);
return true;
}
@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);
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);
}
Class old = previous.get(packetID);
Class proxy = PacketRegistry.getPacketClassFromID(packetID);
lookup.setLookup(packetID, old);
previous.remove(packetID);
registry.remove(proxy);
overwritten.remove(packetID);
return true;
}
@Override

Datei anzeigen

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

Datei anzeigen

@ -21,11 +21,15 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.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,10 +114,7 @@ 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
// might cause a "disconnect.overflow" error.
@ -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,18 +160,33 @@ class InjectedArrayList extends ArrayList<Object> {
}
}
/**
* Ensure that the inverted integer proxy uses the given object as source.
* @param proxy - inverted integer proxy.
* @param source - source object.
*/
private static void registerDelegate(Object proxy, Object source) {
delegateLookup.put(proxy, source);
}
/**
* Inverts the integer result of every integer method.
* @author Kristian
*/
private class InvertedIntegerCallback implements MethodInterceptor {
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);
}
}
}

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -39,6 +39,7 @@ import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.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,14 +128,19 @@ class NetworkFieldInjector extends PlayerInjector {
}
@Override
public UnsupportedListener checkListener(PacketListener listener) {
int[] unsupported = { Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK };
// Unfortunately, we don't support chunk packets
if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(), unsupported)) {
return new UnsupportedListener("The NETWORK_FIELD_INJECTOR hook doesn't support map chunk listeners.", unsupported);
} else {
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
if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(), unsupported)) {
return new UnsupportedListener("The NETWORK_FIELD_INJECTOR hook doesn't support map chunk listeners.", unsupported);
} else {
return null;
}
}
}

Datei anzeigen

@ -40,6 +40,7 @@ import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.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,14 +124,19 @@ public class NetworkObjectInjector extends PlayerInjector {
}
@Override
public UnsupportedListener checkListener(PacketListener listener) {
int[] unsupported = { Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK };
// Unfortunately, we don't support chunk packets
if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(), unsupported)) {
return new UnsupportedListener("The NETWORK_OBJECT_INJECTOR hook doesn't support map chunk listeners.", unsupported);
} else {
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
if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(), unsupported)) {
return new UnsupportedListener("The NETWORK_OBJECT_INJECTOR hook doesn't support map chunk listeners.", unsupported);
} else {
return null;
}
}
}

Datei anzeigen

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

Datei anzeigen

@ -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);

Datei anzeigen

@ -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);
}
}

Datei anzeigen

@ -1,119 +1,87 @@
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;
// Reference to the server itself
protected final Server server;
protected AbstractInputStreamLookup(ErrorReporter reporter, Server server) {
this.reporter = reporter;
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.
*/
public abstract void inject(Object container);
/**
* Invoked when the world has loaded.
*/
public abstract void postWorldLoaded();
/**
* Retrieve the associated socket injector for a player.
* @param input - the indentifying filtered input stream.
* @return The socket injector we have associated with this player.
*/
public abstract SocketInjector waitSocketInjector(InputStream input);
/**
* Retrieve an injector by its socket.
* @param socket - the socket.
* @return The socket injector.
*/
public abstract SocketInjector waitSocketInjector(Socket socket);
/**
* Retrieve a injector by its address.
* @param address - the address of the socket.
* @return The socket injector, or NULL if not found.
*/
public abstract SocketInjector waitSocketInjector(SocketAddress address);
/**
* Attempt to get a socket injector without blocking the thread.
* @param address - the address to lookup.
* @return The socket injector, or NULL if not found.
*/
public abstract SocketInjector peekSocketInjector(SocketAddress address);
/**
* Associate a given socket address to the provided socket injector.
* @param address - the socket address to associate.
* @param injector - the injector.
*/
public abstract void setSocketInjector(SocketAddress address, SocketInjector injector);
/**
* If a player can hold a reference to its parent injector, this method will update that reference.
* @param previous - the previous injector.
* @param current - the new injector.
*/
protected void onPreviousSocketOverwritten(SocketInjector previous, SocketInjector current) {
Player player = previous.getPlayer();
// Default implementation
if (player instanceof InjectorContainer) {
TemporaryPlayerFactory.setInjectorInPlayer(player, current);
}
}
/**
* Invoked when the injection should be undone.
*/
public abstract void cleanupAll();
package com.comphenix.protocol.injector.server;
import java.io.InputStream;
import java.net.Socket;
import java.net.SocketAddress;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import com.comphenix.protocol.error.ErrorReporter;
public abstract class AbstractInputStreamLookup {
// Error reporter
protected final ErrorReporter reporter;
// Reference to the server itself
protected final Server server;
protected AbstractInputStreamLookup(ErrorReporter reporter, Server server) {
this.reporter = reporter;
this.server = server;
}
/**
* Inject the given server thread or dedicated connection.
* @param container - class that contains a ServerSocket field.
*/
public abstract void inject(Object container);
/**
* Invoked when the world has loaded.
*/
public abstract void postWorldLoaded();
/**
* Retrieve the associated socket injector for a player.
* @param input - the indentifying filtered input stream.
* @return The socket injector we have associated with this player.
*/
public abstract SocketInjector waitSocketInjector(InputStream input);
/**
* Retrieve an injector by its socket.
* @param socket - the socket.
* @return The socket injector.
*/
public abstract SocketInjector waitSocketInjector(Socket socket);
/**
* Retrieve a injector by its address.
* @param address - the address of the socket.
* @return The socket injector, or NULL if not found.
*/
public abstract SocketInjector waitSocketInjector(SocketAddress address);
/**
* Attempt to get a socket injector without blocking the thread.
* @param address - the address to lookup.
* @return The socket injector, or NULL if not found.
*/
public abstract SocketInjector peekSocketInjector(SocketAddress address);
/**
* Associate a given socket address to the provided socket injector.
* @param address - the socket address to associate.
* @param injector - the injector.
*/
public abstract void setSocketInjector(SocketAddress address, SocketInjector injector);
/**
* If a player can hold a reference to its parent injector, this method will update that reference.
* @param previous - the previous injector.
* @param current - the new injector.
*/
protected void onPreviousSocketOverwritten(SocketInjector previous, SocketInjector current) {
Player player = previous.getPlayer();
// Default implementation
if (player instanceof InjectorContainer) {
TemporaryPlayerFactory.setInjectorInPlayer(player, current);
}
}
/**
* Invoked when the injection should be undone.
*/
public abstract void cleanupAll();
}

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -74,7 +74,7 @@ class DummyPlayerHandler implements PlayerInjectionHandler {
}
@Override
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
}
}

Datei anzeigen

@ -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

Datei anzeigen

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

Datei anzeigen

@ -62,6 +62,25 @@ public abstract class AbstractFuzzyMatcher<T> implements Comparable<AbstractFuzz
else
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) {

Datei anzeigen

@ -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.
@ -89,18 +96,54 @@ public class FuzzyClassContract extends AbstractFuzzyMatcher<Class<?>> {
public Builder constructor(FuzzyMethodContract.Builder builder) {
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}";
}
}

Datei anzeigen

@ -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;
}

Datei anzeigen

@ -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;
@ -88,7 +91,12 @@ 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,12 +175,40 @@ 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.
@ -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);
}
}

Datei anzeigen

@ -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.
*/

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -1,9 +1,11 @@
name: ProtocolLib
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.*:

Datei anzeigen

@ -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");
}
}

Datei anzeigen

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

Datei anzeigen

@ -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;