Archiviert
13
0

Merge branch 'master' into gh-pages

Dieser Commit ist enthalten in:
Kristian S. Stangeland 2013-02-05 22:16:42 +01:00
Commit fc44ff9fba
54 geänderte Dateien mit 5010 neuen und 746 gelöschten Zeilen

Datei anzeigen

@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.comphenix.protocol</groupId> <groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId> <artifactId>ProtocolLib</artifactId>
<version>2.1.0</version> <version>2.2.0</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<description>Provides read/write access to the Minecraft protocol.</description> <description>Provides read/write access to the Minecraft protocol.</description>

Datei anzeigen

@ -78,9 +78,9 @@ class CleanupStaticMembers {
"com.comphenix.protocol.injector.player.PlayerInjector", "com.comphenix.protocol.injector.player.PlayerInjector",
"com.comphenix.protocol.injector.player.TemporaryPlayerFactory", "com.comphenix.protocol.injector.player.TemporaryPlayerFactory",
"com.comphenix.protocol.injector.EntityUtilities", "com.comphenix.protocol.injector.EntityUtilities",
"com.comphenix.protocol.injector.MinecraftRegistry", "com.comphenix.protocol.injector.packet.PacketRegistry",
"com.comphenix.protocol.injector.PacketInjector", "com.comphenix.protocol.injector.packet.PacketInjector",
"com.comphenix.protocol.injector.ReadPacketModifier", "com.comphenix.protocol.injector.packet.ReadPacketModifier",
"com.comphenix.protocol.injector.StructureCache", "com.comphenix.protocol.injector.StructureCache",
"com.comphenix.protocol.reflect.compiler.BoxingHelper", "com.comphenix.protocol.reflect.compiler.BoxingHelper",
"com.comphenix.protocol.reflect.compiler.MethodDescriptor" "com.comphenix.protocol.reflect.compiler.MethodDescriptor"

Datei anzeigen

@ -175,7 +175,7 @@ class CommandPacket extends CommandBase {
try { try {
chatter.broadcastMessageSilently(message, permission); chatter.broadcastMessageSilently(message, permission);
} catch (InvocationTargetException e) { } catch (InvocationTargetException e) {
reporter.reportDetailed(this, "Cannot send chat message.", e, message, message); reporter.reportDetailed(this, "Cannot send chat message.", e, message, permission);
} }
} }
@ -415,7 +415,7 @@ class CommandPacket extends CommandBase {
Class<?> clazz = packet.getClass(); Class<?> clazz = packet.getClass();
// Get the first Minecraft super class // Get the first Minecraft super class
while ((!clazz.getName().startsWith("net.minecraft.server") || while ((!MinecraftReflection.isMinecraftClass(clazz) ||
Factory.class.isAssignableFrom(clazz)) && clazz != Object.class) { Factory.class.isAssignableFrom(clazz)) && clazz != Object.class) {
clazz = clazz.getSuperclass(); clazz = clazz.getSuperclass();
} }

Datei anzeigen

@ -30,6 +30,11 @@ import com.comphenix.protocol.reflect.IntEnum;
*/ */
public final class Packets { public final class Packets {
/**
* The highest possible packet ID. It's unlikely that this value will ever change.
*/
public static final int MAXIMUM_PACKET_ID = 255;
/** /**
* List of packets sent only by the server. * List of packets sent only by the server.
* @author Kristian * @author Kristian
@ -60,7 +65,7 @@ public final class Packets {
public static final int ARM_ANIMATION = 18; public static final int ARM_ANIMATION = 18;
public static final int NAMED_ENTITY_SPAWN = 20; public static final int NAMED_ENTITY_SPAWN = 20;
/** /**
* Removed in 1.4.6 and replaced with {@link VEHICLE_SPAWN}. * Removed in 1.4.6 and replaced with VEHICLE_SPAWN.
* @see <a href="http://www.wiki.vg/Protocol_History#2012-12-20">Protocol History - MinecraftCoalition</a> * @see <a href="http://www.wiki.vg/Protocol_History#2012-12-20">Protocol History - MinecraftCoalition</a>
*/ */
@Deprecated() @Deprecated()

Datei anzeigen

@ -297,7 +297,10 @@ public class ProtocolLibrary extends JavaPlugin {
if (match.matches()) { if (match.matches()) {
MinecraftVersion version = new MinecraftVersion(match.group(1)); MinecraftVersion version = new MinecraftVersion(match.group(1));
if (newestVersion == null || newestVersion.compareTo(version) < 0) { if (candidate.length() == 0) {
// Delete and inform the user
logger.info((candidate.delete() ? "Deleted " : "Could not delete ") + candidate);
} else if (newestVersion == null || newestVersion.compareTo(version) < 0) {
newestVersion = version; newestVersion = version;
} }
} }

Datei anzeigen

@ -19,6 +19,7 @@ package com.comphenix.protocol.async;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
import java.util.PriorityQueue;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
@ -67,10 +68,19 @@ class PacketProcessingQueue extends AbstractConcurrentListenerMultimap<AsyncList
public PacketProcessingQueue(PlayerSendingHandler sendingHandler, int initialSize, int maximumSize, int maximumConcurrency) { public PacketProcessingQueue(PlayerSendingHandler sendingHandler, int initialSize, int maximumSize, int maximumConcurrency) {
super(); super();
this.processingQueue = Synchronization.queue(MinMaxPriorityQueue. try {
expectedSize(initialSize). this.processingQueue = Synchronization.queue(MinMaxPriorityQueue.
maximumSize(maximumSize). expectedSize(initialSize).
<PacketEventHolder>create(), null); maximumSize(maximumSize).
<PacketEventHolder>create(), null);
} catch (IncompatibleClassChangeError e) {
System.out.println("[ProtocolLib] Guava is either missing or corrupt. Reverting to PriorityQueue.");
e.printStackTrace();
// It's a Beta class after all
this.processingQueue = Synchronization.queue(
new PriorityQueue<PacketEventHolder>(), null);
}
this.maximumConcurrency = maximumConcurrency; this.maximumConcurrency = maximumConcurrency;
this.concurrentProcessing = new Semaphore(maximumConcurrency); this.concurrentProcessing = new Semaphore(maximumConcurrency);

Datei anzeigen

@ -15,7 +15,7 @@
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol.injector.player; package com.comphenix.protocol.concurrency;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
@ -27,7 +27,7 @@ import java.util.Set;
* This class is intentionally missing a size method. * This class is intentionally missing a size method.
* @author Kristian * @author Kristian
*/ */
class IntegerSet { public class IntegerSet {
private final boolean[] array; private final boolean[] array;
/** /**
@ -44,7 +44,7 @@ class IntegerSet {
/** /**
* Determine whether or not the given element exists in the set. * Determine whether or not the given element exists in the set.
* @param value - the element to check. Must be in the range [0, count). * @param element - the element to check. Must be in the range [0, count).
* @return TRUE if the given element exists, FALSE otherwise. * @return TRUE if the given element exists, FALSE otherwise.
*/ */
public boolean contains(int element) { public boolean contains(int element) {

Datei anzeigen

@ -164,9 +164,9 @@ class EntityUtilities {
BukkitUnwrapper unwrapper = new BukkitUnwrapper(); BukkitUnwrapper unwrapper = new BukkitUnwrapper();
Object worldServer = unwrapper.unwrapItem(world); Object worldServer = unwrapper.unwrapItem(world);
// We have to rely on the class naming here.
if (entityTrackerField == null) if (entityTrackerField == null)
entityTrackerField = FuzzyReflection.fromObject(worldServer).getFieldByType(".*Tracker"); entityTrackerField = FuzzyReflection.fromObject(worldServer).
getFieldByType("tracker", MinecraftReflection.getEntityTrackerClass());
// Get the tracker // Get the tracker
Object tracker = null; Object tracker = null;
@ -191,7 +191,7 @@ class EntityUtilities {
// The Minecraft field that's NOT filled in by the constructor // The Minecraft field that's NOT filled in by the constructor
trackedEntitiesField = FuzzyReflection.fromObject(tracker, true). trackedEntitiesField = FuzzyReflection.fromObject(tracker, true).
getFieldByType(MinecraftReflection.MINECRAFT_OBJECT, ignoredTypes); getFieldByType(MinecraftReflection.getMinecraftObjectRegex(), ignoredTypes);
} }
// Read the entity hashmap // Read the entity hashmap
@ -250,8 +250,16 @@ class EntityUtilities {
// Handle NULL cases // Handle NULL cases
if (trackerEntry != null) { if (trackerEntry != null) {
if (trackerField == null) if (trackerField == null) {
trackerField = trackerEntry.getClass().getField("tracker"); try {
trackerField = trackerEntry.getClass().getField("tracker");
} catch (NoSuchFieldException e) {
// Assume it's the first public entity field then
trackerField = FuzzyReflection.fromObject(trackerEntry).getFieldByType(
"tracker", MinecraftReflection.getEntityClass());
}
}
tracker = FieldUtils.readField(trackerField, trackerEntry, true); tracker = FieldUtils.readField(trackerField, trackerEntry, true);
} }

Datei anzeigen

@ -22,6 +22,7 @@ import java.lang.reflect.InvocationTargetException;
import java.util.List; import java.util.List;
import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FieldAccessException;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
@ -114,7 +115,7 @@ public class PacketConstructor {
} }
} }
Class<?> packetType = MinecraftRegistry.getPacketClassFromID(id, true); Class<?> packetType = PacketRegistry.getPacketClassFromID(id, true);
if (packetType == null) if (packetType == null)
throw new IllegalArgumentException("Could not find a packet by the id " + id); throw new IllegalArgumentException("Could not find a packet by the id " + id);

Datei anzeigen

@ -51,7 +51,12 @@ import com.comphenix.protocol.async.AsyncFilterManager;
import com.comphenix.protocol.async.AsyncMarker; import com.comphenix.protocol.async.AsyncMarker;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.*; import com.comphenix.protocol.events.*;
import com.comphenix.protocol.injector.packet.PacketInjector;
import com.comphenix.protocol.injector.packet.PacketInjectorBuilder;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler; import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
import com.comphenix.protocol.injector.player.PlayerInjectorBuilder;
import com.comphenix.protocol.injector.spigot.SpigotPacketInjector;
import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftReflection;
@ -138,6 +143,9 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
// Whether or not plugins are using the send/receive methods // Whether or not plugins are using the send/receive methods
private AtomicBoolean packetCreation = new AtomicBoolean(); private AtomicBoolean packetCreation = new AtomicBoolean();
// Spigot listener, if in use
private SpigotPacketInjector spigotInjector;
/** /**
* Only create instances of this class if protocol lib is disabled. * Only create instances of this class if protocol lib is disabled.
* @param unhookTask * @param unhookTask
@ -177,15 +185,37 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
}; };
try { try {
// Initialize injection mangers // Spigot
this.playerInjection = new PlayerInjectionHandler(classLoader, reporter, isInjectionNecessary, this, packetListeners, server); if (SpigotPacketInjector.canUseSpigotListener()) {
this.packetInjector = new PacketInjector(classLoader, this, playerInjection, reporter); spigotInjector = new SpigotPacketInjector(classLoader, reporter, this, server);
this.playerInjection = spigotInjector.getPlayerHandler();
this.packetInjector = spigotInjector.getPacketInjector();
} else {
// Initialize standard injection mangers
this.playerInjection = PlayerInjectorBuilder.newBuilder().
invoker(this).
server(server).
reporter(reporter).
classLoader(classLoader).
packetListeners(packetListeners).
injectionFilter(isInjectionNecessary).
buildHandler();
this.packetInjector = PacketInjectorBuilder.newBuilder().
invoker(this).
reporter(reporter).
classLoader(classLoader).
playerInjection(playerInjection).
buildInjector();
}
this.asyncFilterManager = new AsyncFilterManager(reporter, server.getScheduler(), this); this.asyncFilterManager = new AsyncFilterManager(reporter, server.getScheduler(), this);
// Attempt to load the list of server and client packets // Attempt to load the list of server and client packets
try { try {
this.serverPackets = MinecraftRegistry.getServerPackets(); this.serverPackets = PacketRegistry.getServerPackets();
this.clientPackets = MinecraftRegistry.getClientPackets(); this.clientPackets = PacketRegistry.getClientPackets();
} catch (FieldAccessException e) { } catch (FieldAccessException e) {
reporter.reportWarning(this, "Cannot load server and client packet list.", e); reporter.reportWarning(this, "Cannot load server and client packet list.", e);
} }
@ -600,6 +630,8 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
* @param plugin - the parent plugin. * @param plugin - the parent plugin.
*/ */
public void registerEvents(PluginManager manager, final Plugin plugin) { public void registerEvents(PluginManager manager, final Plugin plugin) {
if (spigotInjector != null && !spigotInjector.register(plugin))
throw new IllegalArgumentException("Spigot has already been registered.");
try { try {
manager.registerEvents(new Listener() { manager.registerEvents(new Listener() {
@ -692,22 +724,22 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
if (!MinecraftReflection.isPacketClass(packet)) if (!MinecraftReflection.isPacketClass(packet))
throw new IllegalArgumentException("The given object " + packet + " is not a packet."); throw new IllegalArgumentException("The given object " + packet + " is not a packet.");
return MinecraftRegistry.getPacketToID().get(packet.getClass()); return PacketRegistry.getPacketToID().get(packet.getClass());
} }
@Override @Override
public void registerPacketClass(Class<?> clazz, int packetID) { public void registerPacketClass(Class<?> clazz, int packetID) {
MinecraftRegistry.getPacketToID().put(clazz, packetID); PacketRegistry.getPacketToID().put(clazz, packetID);
} }
@Override @Override
public void unregisterPacketClass(Class<?> clazz) { public void unregisterPacketClass(Class<?> clazz) {
MinecraftRegistry.getPacketToID().remove(clazz); PacketRegistry.getPacketToID().remove(clazz);
} }
@Override @Override
public Class<?> getPacketClassFromID(int packetID, boolean forceVanilla) { public Class<?> getPacketClassFromID(int packetID, boolean forceVanilla) {
return MinecraftRegistry.getPacketClassFromID(packetID, forceVanilla); return PacketRegistry.getPacketClassFromID(packetID, forceVanilla);
} }
// Yes, this is crazy. // Yes, this is crazy.
@ -823,7 +855,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
* @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft. * @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft.
*/ */
public static Set<Integer> getServerPackets() throws FieldAccessException { public static Set<Integer> getServerPackets() throws FieldAccessException {
return MinecraftRegistry.getServerPackets(); return PacketRegistry.getServerPackets();
} }
/** /**
@ -832,7 +864,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
* @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft. * @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft.
*/ */
public static Set<Integer> getClientPackets() throws FieldAccessException { public static Set<Integer> getClientPackets() throws FieldAccessException {
return MinecraftRegistry.getClientPackets(); return PacketRegistry.getClientPackets();
} }
/** /**

Datei anzeigen

@ -22,6 +22,7 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler; import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
import com.comphenix.protocol.reflect.compiler.CompileListener; import com.comphenix.protocol.reflect.compiler.CompileListener;
@ -46,7 +47,7 @@ public class StructureCache {
*/ */
public static Object newPacket(int id) { public static Object newPacket(int id) {
try { try {
return MinecraftRegistry.getPacketClassFromID(id, true).newInstance(); return PacketRegistry.getPacketClassFromID(id, true).newInstance();
} catch (InstantiationException e) { } catch (InstantiationException e) {
return null; return null;
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
@ -82,7 +83,7 @@ public class StructureCache {
*/ */
public static StructureModifier<Object> getStructure(Class<?> packetType, boolean compile) { public static StructureModifier<Object> getStructure(Class<?> packetType, boolean compile) {
// Get the ID from the class // Get the ID from the class
return getStructure(MinecraftRegistry.getPacketID(packetType), compile); return getStructure(PacketRegistry.getPacketID(packetType), compile);
} }
/** /**
@ -99,7 +100,7 @@ public class StructureCache {
if (result == null) { if (result == null) {
// Use the vanilla class definition // Use the vanilla class definition
final StructureModifier<Object> value = new StructureModifier<Object>( final StructureModifier<Object> value = new StructureModifier<Object>(
MinecraftRegistry.getPacketClassFromID(id, true), MinecraftReflection.getPacketClass(), true); PacketRegistry.getPacketClassFromID(id, true), MinecraftReflection.getPacketClass(), true);
result = structureModifiers.putIfAbsent(id, value); result = structureModifiers.putIfAbsent(id, value);

Datei anzeigen

@ -0,0 +1,62 @@
package com.comphenix.protocol.injector.packet;
import java.util.Set;
import org.bukkit.entity.Player;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
/**
* Represents a incoming packet injector.
*
* @author Kristian
*/
public interface PacketInjector {
/**
* Undo a packet cancel.
* @param id - the id of the packet.
* @param packet - packet to uncancel.
*/
public abstract void undoCancel(Integer id, Object packet);
/**
* Start intercepting packets with the given packet ID.
* @param packetID - the ID of the packets to start intercepting.
* @return TRUE if we didn't already intercept these packets, FALSE otherwise.
*/
public abstract boolean addPacketHandler(int packetID);
/**
* Stop intercepting packets with the given packet ID.
* @param packetID - the ID of the packets to stop intercepting.
* @return TRUE if we successfuly stopped intercepting a given packet ID, FALSE otherwise.
*/
public abstract boolean removePacketHandler(int packetID);
/**
* Determine if packets with the given packet ID is being intercepted.
* @param packetID - the packet ID to lookup.
* @return TRUE if we do, FALSE otherwise.
*/
public abstract boolean hasPacketHandler(int packetID);
/**
* Retrieve every intercepted packet ID.
* @return Every intercepted packet ID.
*/
public abstract Set<Integer> getPacketHandlers();
/**
* Let the packet listeners process the given packet.
* @param packet - a packet to process.
* @param client - the client that sent the packet.
* @return The resulting packet event.
*/
public abstract PacketEvent packetRecieved(PacketContainer packet, Player client);
/**
* Perform any necessary cleanup before unloading ProtocolLib.
*/
public abstract void cleanupAll();
}

Datei anzeigen

@ -0,0 +1,109 @@
package com.comphenix.protocol.injector.packet;
import javax.annotation.Nonnull;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
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.google.common.base.Preconditions;
/**
* A builder responsible for creating incoming packet injectors.
*
* @author Kristian
*/
public class PacketInjectorBuilder {
protected PacketInjectorBuilder() {
// No need to construct this
}
/**
* Retrieve a new packet injector builder.
* @return Injector builder.
*/
public static PacketInjectorBuilder newBuilder() {
return new PacketInjectorBuilder();
}
protected ClassLoader classLoader;
protected ListenerInvoker invoker;
protected ErrorReporter reporter;
protected PlayerInjectionHandler playerInjection;
/**
* Set the class loader to use during class generation.
* @param classLoader - new class loader.
* @return This builder, for chaining.
*/
public PacketInjectorBuilder classLoader(@Nonnull ClassLoader classLoader) {
Preconditions.checkNotNull(classLoader, "classLoader cannot be NULL");
this.classLoader = classLoader;
return this;
}
/**
* The error reporter used by the created injector.
* @param reporter - new error reporter.
* @return This builder, for chaining.
*/
public PacketInjectorBuilder reporter(@Nonnull ErrorReporter reporter) {
Preconditions.checkNotNull(reporter, "reporter cannot be NULL");
this.reporter = reporter;
return this;
}
/**
* The packet stream invoker.
* @param invoker - the invoker.
* @return This builder, for chaining.
*/
public PacketInjectorBuilder invoker(@Nonnull ListenerInvoker invoker) {
Preconditions.checkNotNull(invoker, "invoker cannot be NULL");
this.invoker = invoker;
return this;
}
/**
* The packet stream invoker.
* @param invoker - the invoker.
* @return This builder, for chaining.
*/
@Nonnull
public PacketInjectorBuilder playerInjection(@Nonnull PlayerInjectionHandler playerInjection) {
Preconditions.checkNotNull(playerInjection, "playerInjection cannot be NULL");
this.playerInjection = playerInjection;
return this;
}
/**
* Called before an object is created with this builder.
*/
private void initializeDefaults() {
ProtocolManager manager = ProtocolLibrary.getProtocolManager();
// Initialize with default values if we can
if (classLoader == null)
classLoader = this.getClass().getClassLoader();
if (reporter == null)
reporter = ProtocolLibrary.getErrorReporter();
if (invoker == null)
invoker = (PacketFilterManager) manager;
if (playerInjection == null)
throw new IllegalStateException("Player injection parameter must be initialized.");
}
/**
* Create a packet injector using the provided fields or the default values.
* <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.
*/
public PacketInjector buildInjector() throws IllegalAccessException {
initializeDefaults();
return new ProxyPacketInjector(classLoader, invoker, playerInjection, reporter);
}
}

Datei anzeigen

@ -15,7 +15,7 @@
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol.injector; package com.comphenix.protocol.injector.packet;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.HashMap; import java.util.HashMap;
@ -33,12 +33,12 @@ import com.google.common.base.Objects;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
/** /**
* Static registries in Minecraft. * Static packet registry in Minecraft.
* *
* @author Kristian * @author Kristian
*/ */
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
class MinecraftRegistry { public class PacketRegistry {
// Fuzzy reflection // Fuzzy reflection
private static FuzzyReflection packetRegistry; private static FuzzyReflection packetRegistry;
@ -174,7 +174,7 @@ class MinecraftRegistry {
for (Map.Entry<Class, Integer> entry : getPacketToID().entrySet()) { for (Map.Entry<Class, Integer> entry : getPacketToID().entrySet()) {
if (Objects.equal(entry.getValue(), packetID)) { if (Objects.equal(entry.getValue(), packetID)) {
// Attempt to get the vanilla class here too // Attempt to get the vanilla class here too
if (!forceVanilla || entry.getKey().getName().startsWith("net.minecraft.server")) if (!forceVanilla || MinecraftReflection.isMinecraftClass(entry.getKey()))
return removeEnhancer(entry.getKey(), forceVanilla); return removeEnhancer(entry.getKey(), forceVanilla);
} }
} }

Datei anzeigen

@ -15,7 +15,7 @@
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol.injector; package com.comphenix.protocol.injector.packet;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.lang.reflect.Field; import java.lang.reflect.Field;
@ -33,6 +33,7 @@ import net.sf.cglib.proxy.Enhancer;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler; import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.FuzzyReflection;
@ -43,7 +44,7 @@ import com.comphenix.protocol.utility.MinecraftReflection;
* *
* @author Kristian * @author Kristian
*/ */
class PacketInjector { class ProxyPacketInjector implements PacketInjector {
// The "put" method that associates a packet ID with a packet class // The "put" method that associates a packet ID with a packet class
private static Method putMethod; private static Method putMethod;
@ -64,7 +65,7 @@ class PacketInjector {
// Class loader // Class loader
private ClassLoader classLoader; private ClassLoader classLoader;
public PacketInjector(ClassLoader classLoader, ListenerInvoker manager, public ProxyPacketInjector(ClassLoader classLoader, ListenerInvoker manager,
PlayerInjectionHandler playerInjection, ErrorReporter reporter) throws IllegalAccessException { PlayerInjectionHandler playerInjection, ErrorReporter reporter) throws IllegalAccessException {
this.classLoader = classLoader; this.classLoader = classLoader;
@ -80,6 +81,7 @@ class PacketInjector {
* @param id - the id of the packet. * @param id - the id of the packet.
* @param packet - packet to uncancel. * @param packet - packet to uncancel.
*/ */
@Override
public void undoCancel(Integer id, Object packet) { public void undoCancel(Integer id, Object packet) {
ReadPacketModifier modifier = readModifier.get(id); ReadPacketModifier modifier = readModifier.get(id);
@ -93,7 +95,7 @@ class PacketInjector {
if (intHashMap == null) { if (intHashMap == null) {
// We're looking for the first static field with a Minecraft-object. This should be a IntHashMap. // We're looking for the first static field with a Minecraft-object. This should be a IntHashMap.
Field intHashMapField = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true). Field intHashMapField = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true).
getFieldByType(MinecraftReflection.MINECRAFT_OBJECT); getFieldByType(MinecraftReflection.getMinecraftObjectRegex());
try { try {
intHashMap = FieldUtils.readField(intHashMapField, (Object) null, true); intHashMap = FieldUtils.readField(intHashMapField, (Object) null, true);
@ -106,6 +108,7 @@ class PacketInjector {
} }
} }
@Override
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
public boolean addPacketHandler(int packetID) { public boolean addPacketHandler(int packetID) {
if (hasPacketHandler(packetID)) if (hasPacketHandler(packetID))
@ -118,17 +121,17 @@ class PacketInjector {
// * Object removeObject(int par1) // * Object removeObject(int par1)
// So, we'll use the classMapToInt registry instead. // So, we'll use the classMapToInt registry instead.
Map<Integer, Class> overwritten = MinecraftRegistry.getOverwrittenPackets(); Map<Integer, Class> overwritten = PacketRegistry.getOverwrittenPackets();
Map<Integer, Class> previous = MinecraftRegistry.getPreviousPackets(); Map<Integer, Class> previous = PacketRegistry.getPreviousPackets();
Map<Class, Integer> registry = MinecraftRegistry.getPacketToID(); Map<Class, Integer> registry = PacketRegistry.getPacketToID();
Class old = MinecraftRegistry.getPacketClassFromID(packetID); Class old = PacketRegistry.getPacketClassFromID(packetID);
// If this packet is not known // If this packet is not known
if (old == null) { if (old == null) {
throw new IllegalStateException("Packet ID " + packetID + " is not a valid packet ID in this version."); throw new IllegalStateException("Packet ID " + packetID + " is not a valid packet ID in this version.");
} }
// Check for previous injections // Check for previous injections
if (!old.getName().startsWith("net.minecraft.")) { if (!MinecraftReflection.isMinecraftClass(old)) {
throw new IllegalStateException("Packet " + packetID + " has already been injected."); throw new IllegalStateException("Packet " + packetID + " has already been injected.");
} }
@ -162,19 +165,20 @@ class PacketInjector {
} }
} }
@Override
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
public boolean removePacketHandler(int packetID) { public boolean removePacketHandler(int packetID) {
if (!hasPacketHandler(packetID)) if (!hasPacketHandler(packetID))
return false; return false;
Map<Class, Integer> registry = MinecraftRegistry.getPacketToID(); Map<Class, Integer> registry = PacketRegistry.getPacketToID();
Map<Integer, Class> previous = MinecraftRegistry.getPreviousPackets(); Map<Integer, Class> previous = PacketRegistry.getPreviousPackets();
Map<Integer, Class> overwritten = MinecraftRegistry.getOverwrittenPackets(); Map<Integer, Class> overwritten = PacketRegistry.getOverwrittenPackets();
// Use the old class definition // Use the old class definition
try { try {
Class old = previous.get(packetID); Class old = previous.get(packetID);
Class proxy = MinecraftRegistry.getPacketClassFromID(packetID); Class proxy = PacketRegistry.getPacketClassFromID(packetID);
putMethod.invoke(intHashMap, packetID, old); putMethod.invoke(intHashMap, packetID, old);
previous.remove(packetID); previous.remove(packetID);
@ -193,16 +197,18 @@ class PacketInjector {
} }
} }
@Override
public boolean hasPacketHandler(int packetID) { public boolean hasPacketHandler(int packetID) {
return MinecraftRegistry.getPreviousPackets().containsKey(packetID); return PacketRegistry.getPreviousPackets().containsKey(packetID);
} }
@Override
public Set<Integer> getPacketHandlers() { public Set<Integer> getPacketHandlers() {
return MinecraftRegistry.getPreviousPackets().keySet(); return PacketRegistry.getPreviousPackets().keySet();
} }
// Called from the ReadPacketModified monitor // Called from the ReadPacketModified monitor
PacketEvent packetRecieved(PacketContainer packet, DataInputStream input) { public PacketEvent packetRecieved(PacketContainer packet, DataInputStream input) {
try { try {
Player client = playerInjection.getPlayerByConnection(input); Player client = playerInjection.getPlayerByConnection(input);
@ -225,18 +231,19 @@ class PacketInjector {
* @param client - the client that sent the packet. * @param client - the client that sent the packet.
* @return The resulting packet event. * @return The resulting packet event.
*/ */
@Override
public PacketEvent packetRecieved(PacketContainer packet, Player client) { public PacketEvent packetRecieved(PacketContainer packet, Player client) {
PacketEvent event = PacketEvent.fromClient((Object) manager, packet, client); PacketEvent event = PacketEvent.fromClient((Object) manager, packet, client);
manager.invokePacketRecieving(event); manager.invokePacketRecieving(event);
return event; return event;
} }
@Override
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
public synchronized void cleanupAll() { public synchronized void cleanupAll() {
Map<Integer, Class> overwritten = MinecraftRegistry.getOverwrittenPackets(); Map<Integer, Class> overwritten = PacketRegistry.getOverwrittenPackets();
Map<Integer, Class> previous = MinecraftRegistry.getPreviousPackets(); Map<Integer, Class> previous = PacketRegistry.getPreviousPackets();
// Remove every packet handler // Remove every packet handler
for (Integer id : previous.keySet().toArray(new Integer[0])) { for (Integer id : previous.keySet().toArray(new Integer[0])) {

Datei anzeigen

@ -15,7 +15,7 @@
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol.injector; package com.comphenix.protocol.injector.packet;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@ -41,7 +41,7 @@ class ReadPacketModifier implements MethodInterceptor {
private static final Object CANCEL_MARKER = new Object(); private static final Object CANCEL_MARKER = new Object();
// Common for all packets of the same type // Common for all packets of the same type
private PacketInjector packetInjector; private ProxyPacketInjector packetInjector;
private int packetID; private int packetID;
// Report errors // Report errors
@ -50,7 +50,7 @@ class ReadPacketModifier implements MethodInterceptor {
// Whether or not a packet has been cancelled // Whether or not a packet has been cancelled
private static Map<Object, Object> override = Collections.synchronizedMap(new WeakHashMap<Object, Object>()); private static Map<Object, Object> override = Collections.synchronizedMap(new WeakHashMap<Object, Object>());
public ReadPacketModifier(int packetID, PacketInjector packetInjector, ErrorReporter reporter) { public ReadPacketModifier(int packetID, ProxyPacketInjector packetInjector, ErrorReporter reporter) {
this.packetID = packetID; this.packetID = packetID;
this.packetInjector = packetInjector; this.packetInjector = packetInjector;
this.reporter = reporter; this.reporter = reporter;

Datei anzeigen

@ -22,6 +22,8 @@ import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Set; import java.util.Set;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.player.NetworkFieldInjector.FakePacket; import com.comphenix.protocol.injector.player.NetworkFieldInjector.FakePacket;
@ -64,7 +66,8 @@ class InjectedArrayList extends ArrayList<Object> {
if (packet instanceof FakePacket) { if (packet instanceof FakePacket) {
return true; return true;
} else if (ignoredPackets.contains(packet)) { } else if (ignoredPackets.contains(packet)) {
ignoredPackets.remove(packet); // Don't send it to the filters
result = ignoredPackets.remove(packet);
} else { } else {
result = injector.handlePacketSending(packet); result = injector.handlePacketSending(packet);
} }
@ -82,7 +85,18 @@ class InjectedArrayList extends ArrayList<Object> {
return true; return true;
} catch (InvocationTargetException e) { } catch (InvocationTargetException e) {
throw new RuntimeException("Reverting cancelled packet failed.", e.getTargetException()); 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();
}
// Failure
return false;
} }
} }

Datei anzeigen

@ -90,7 +90,8 @@ class InjectedServerConnection {
try { try {
if (serverConnectionMethod == null) if (serverConnectionMethod == null)
serverConnectionMethod = FuzzyReflection.fromClass(minecraftServerField.getType()). serverConnectionMethod = FuzzyReflection.fromClass(minecraftServerField.getType()).
getMethodByParameters("getServerConnection", ".*ServerConnection", new String[] {}); getMethodByParameters("getServerConnection",
MinecraftReflection.getServerConnectionClass(), new Class[] {});
// We're using Minecraft 1.3.1 // We're using Minecraft 1.3.1
injectServerConnection(); injectServerConnection();
@ -106,12 +107,10 @@ class InjectedServerConnection {
} }
private void injectListenerThread() { private void injectListenerThread() {
try { try {
if (listenerThreadField == null)
if (listenerThreadField == null) listenerThreadField = FuzzyReflection.fromObject(minecraftServer).
listenerThreadField = FuzzyReflection.fromObject(minecraftServer). getFieldByType("networkListenThread", MinecraftReflection.getNetworkListenThreadClass());
getFieldByType(".*NetworkListenThread");
} catch (RuntimeException e) { } catch (RuntimeException e) {
reporter.reportDetailed(this, "Cannot find listener thread in MinecraftServer.", e, minecraftServer); reporter.reportDetailed(this, "Cannot find listener thread in MinecraftServer.", e, minecraftServer);
return; return;

Datei anzeigen

@ -38,7 +38,7 @@ class NetLoginInjector {
private ConcurrentMap<Object, PlayerInjector> injectedLogins = Maps.newConcurrentMap(); private ConcurrentMap<Object, PlayerInjector> injectedLogins = Maps.newConcurrentMap();
// Handles every hook // Handles every hook
private PlayerInjectionHandler injectionHandler; private ProxyPlayerInjectionHandler injectionHandler;
private Server server; private Server server;
// The current error rerporter // The current error rerporter
@ -47,7 +47,7 @@ class NetLoginInjector {
// Used to create fake players // Used to create fake players
private TemporaryPlayerFactory tempPlayerFactory = new TemporaryPlayerFactory(); private TemporaryPlayerFactory tempPlayerFactory = new TemporaryPlayerFactory();
public NetLoginInjector(ErrorReporter reporter, PlayerInjectionHandler injectionHandler, Server server) { public NetLoginInjector(ErrorReporter reporter, ProxyPlayerInjectionHandler injectionHandler, Server server) {
this.reporter = reporter; this.reporter = reporter;
this.injectionHandler = injectionHandler; this.injectionHandler = injectionHandler;
this.server = server; this.server = server;

Datei anzeigen

@ -28,6 +28,7 @@ import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import com.comphenix.protocol.Packets; import com.comphenix.protocol.Packets;
import com.comphenix.protocol.concurrency.IntegerSet;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.ListeningWhitelist; import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.events.PacketListener;

Datei anzeigen

@ -28,22 +28,25 @@ import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import org.bukkit.Server;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import com.comphenix.protocol.Packets; import com.comphenix.protocol.Packets;
import com.comphenix.protocol.concurrency.IntegerSet;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.ListeningWhitelist; import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase; import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
import com.comphenix.protocol.injector.player.TemporaryPlayerFactory.InjectContainer;
/** /**
* Injection method that overrides the NetworkHandler itself, and it's sendPacket-method. * Injection method that overrides the NetworkHandler itself, and it's queue-method.
* *
* @author Kristian * @author Kristian
*/ */
class NetworkObjectInjector extends PlayerInjector { public class NetworkObjectInjector extends PlayerInjector {
// Determine if we're listening // Determine if we're listening
private IntegerSet sendingFilters; private IntegerSet sendingFilters;
@ -53,6 +56,9 @@ class NetworkObjectInjector extends PlayerInjector {
// Shared callback filter - avoid creating a new class every time // Shared callback filter - avoid creating a new class every time
private static CallbackFilter callbackFilter; private static CallbackFilter callbackFilter;
// Temporary player factory
private static volatile TemporaryPlayerFactory tempPlayerFactory;
public NetworkObjectInjector(ClassLoader classLoader, ErrorReporter reporter, Player player, public NetworkObjectInjector(ClassLoader classLoader, ErrorReporter reporter, Player player,
ListenerInvoker invoker, IntegerSet sendingFilters) throws IllegalAccessException { ListenerInvoker invoker, IntegerSet sendingFilters) throws IllegalAccessException {
super(reporter, player, invoker); super(reporter, player, invoker);
@ -65,6 +71,21 @@ class NetworkObjectInjector extends PlayerInjector {
return sendingFilters.contains(packetID); return sendingFilters.contains(packetID);
} }
/**
* Create a temporary player for use during login.
* @param server - Bukkit server.
* @return The temporary player.
*/
public Player createTemporaryPlayer(Server server) {
if (tempPlayerFactory == null)
tempPlayerFactory = new TemporaryPlayerFactory();
// Create and associate this fake player with this network injector
Player player = tempPlayerFactory.createTemporaryPlayer(server);
((InjectContainer) player).setInjector(this);
return player;
}
@Override @Override
public void sendServerPacket(Object packet, boolean filtered) throws InvocationTargetException { public void sendServerPacket(Object packet, boolean filtered) throws InvocationTargetException {
Object networkDelegate = filtered ? networkManagerRef.getValue() : networkManagerRef.getOldValue(); Object networkDelegate = filtered ? networkManagerRef.getValue() : networkManagerRef.getOldValue();

Datei anzeigen

@ -20,7 +20,8 @@ package com.comphenix.protocol.injector.player;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import net.sf.cglib.proxy.Callback; import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter; import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.Enhancer;
@ -31,6 +32,7 @@ import net.sf.cglib.proxy.NoOp;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import com.comphenix.protocol.concurrency.IntegerSet;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase; import com.comphenix.protocol.injector.GamePhase;
@ -43,13 +45,14 @@ import com.comphenix.protocol.reflect.VolatileField;
import com.comphenix.protocol.reflect.instances.DefaultInstances; import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.reflect.instances.ExistingGenerator; import com.comphenix.protocol.reflect.instances.ExistingGenerator;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.Maps;
/** /**
* Represents a player hook into the NetServerHandler class. * Represents a player hook into the NetServerHandler class.
* *
* @author Kristian * @author Kristian
*/ */
public class NetworkServerInjector extends PlayerInjector { class NetworkServerInjector extends PlayerInjector {
private volatile static CallbackFilter callbackFilter; private volatile static CallbackFilter callbackFilter;
@ -91,19 +94,69 @@ public class NetworkServerInjector extends PlayerInjector {
// Get the send packet method! // Get the send packet method!
if (hasInitialized) { if (hasInitialized) {
if (sendPacketMethod == null) if (sendPacketMethod == null) {
sendPacketMethod = FuzzyReflection.fromObject(serverHandler).getMethodByName("sendPacket.*"); try {
sendPacketMethod = FuzzyReflection.fromObject(serverHandler).getMethodByName("sendPacket.*");
} catch (IllegalArgumentException e) {
Map<String, Method> netServer = getMethodList(
MinecraftReflection.getNetServerHandlerClass(), MinecraftReflection.getPacketClass());
Map<String, Method> netHandler = getMethodList(
MinecraftReflection.getNetHandlerClass(), MinecraftReflection.getPacketClass());
// Remove every method in net handler from net server
for (String methodName : netHandler.keySet()) {
netServer.remove(methodName);
}
// The remainder is the send packet method
if (netServer.size() == 1) {
Method[] methods = netServer.values().toArray(new Method[0]);
sendPacketMethod = methods[0];
} else {
throw new IllegalArgumentException("Unable to find the sendPacket method in NetServerHandler/PlayerConnection.");
}
}
}
} }
} }
/**
* Retrieve a method mapped list of every method with the given signature.
* @param source - class source.
* @param params - parameters.
* @return Method mapped list.
*/
private Map<String, Method> getMethodList(Class<?> source, Class<?>... params) {
return getMappedMethods(
FuzzyReflection.fromClass(source, true).
getMethodListByParameters(Void.TYPE, params)
);
}
/**
* Retrieve every method as a map over names.
* <p>
* Note that overloaded methods will only occur once in the resulting map.
* @param methods - every method.
* @return A map over every given method.
*/
private Map<String, Method> getMappedMethods(List<Method> methods) {
Map<String, Method> map = Maps.newHashMap();
for (Method method : methods) {
map.put(method.getName(), method);
}
return map;
}
@Override @Override
public void sendServerPacket(Object packet, boolean filtered) throws InvocationTargetException { public void sendServerPacket(Object packet, boolean filtered) throws InvocationTargetException {
Object serverDeleage = filtered ? serverHandlerRef.getValue() : serverHandlerRef.getOldValue(); Object serverDelegate = filtered ? serverHandlerRef.getValue() : serverHandlerRef.getOldValue();
if (serverDeleage != null) { if (serverDelegate != null) {
try { try {
// Note that invocation target exception is a wrapper for a checked exception // Note that invocation target exception is a wrapper for a checked exception
sendPacketMethod.invoke(serverDeleage, packet); sendPacketMethod.invoke(serverDelegate, packet);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
throw e; throw e;
@ -297,7 +350,21 @@ public class NetworkServerInjector extends PlayerInjector {
FieldUtils.writeField(disconnectField, handler, value); FieldUtils.writeField(disconnectField, handler, value);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
reporter.reportDetailed(this, "Unable to find disconnect field. Is ProtocolLib up to date?", e, handler); // 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) { } catch (IllegalAccessException e) {
reporter.reportWarning(this, "Unable to update disconnected field. Player quit event may be sent twice."); reporter.reportWarning(this, "Unable to update disconnected field. Player quit event may be sent twice.");
} }

Datei anzeigen

@ -1,199 +1,56 @@
/*
* 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; package com.comphenix.protocol.injector.player;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.bukkit.Server;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import com.comphenix.protocol.concurrency.BlockingHashMap;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase; import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PlayerLoggedOutException;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
import com.google.common.base.Predicate;
import com.google.common.collect.Maps;
/**
* Responsible for injecting into a player's sendPacket method.
*
* @author Kristian
*/
public class PlayerInjectionHandler {
/**
* The maximum number of milliseconds to wait until a player can be looked up by connection.
*/
private static final long TIMEOUT_PLAYER_LOOKUP = 2000; // ms
/**
* The highest possible packet ID. It's unlikely that this value will ever change.
*/
private static final int MAXIMUM_PACKET_ID = 255;
// Server connection injection
private InjectedServerConnection serverInjection;
// NetLogin injector
private NetLoginInjector netLoginInjector;
// The last successful player hook
private PlayerInjector lastSuccessfulHook;
// Player injection
private Map<SocketAddress, PlayerInjector> addressLookup = Maps.newConcurrentMap();
private Map<Player, PlayerInjector> playerInjection = Maps.newConcurrentMap();
// Lookup player by connection
private BlockingHashMap<DataInputStream, PlayerInjector> dataInputLookup = BlockingHashMap.create();
// Player injection types
private volatile PlayerInjectHooks loginPlayerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
private volatile PlayerInjectHooks playingPlayerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
// Error reporter
private ErrorReporter reporter;
// Whether or not we're closing
private boolean hasClosed;
// Used to invoke events
private ListenerInvoker invoker;
// Enabled packet filters
private IntegerSet sendingFilters = new IntegerSet(MAXIMUM_PACKET_ID + 1);
// List of packet listeners
private Set<PacketListener> packetListeners;
// The class loader we're using
private ClassLoader classLoader;
// Used to filter injection attempts
private Predicate<GamePhase> injectionFilter;
public PlayerInjectionHandler(ClassLoader classLoader, ErrorReporter reporter, Predicate<GamePhase> injectionFilter,
ListenerInvoker invoker, Set<PacketListener> packetListeners, Server server) {
this.classLoader = classLoader;
this.reporter = reporter;
this.invoker = invoker;
this.injectionFilter = injectionFilter;
this.packetListeners = packetListeners;
this.netLoginInjector = new NetLoginInjector(reporter, this, server);
this.serverInjection = new InjectedServerConnection(reporter, server, netLoginInjector);
serverInjection.injectList();
}
public interface PlayerInjectionHandler {
/** /**
* Retrieves how the server packets are read. * Retrieves how the server packets are read.
* @return Injection method for reading server packets. * @return Injection method for reading server packets.
*/ */
public PlayerInjectHooks getPlayerHook() { public abstract PlayerInjectHooks getPlayerHook();
return getPlayerHook(GamePhase.PLAYING);
}
/** /**
* Retrieves how the server packets are read. * Retrieves how the server packets are read.
* @param phase - the current game phase. * @param phase - the current game phase.
* @return Injection method for reading server packets. * @return Injection method for reading server packets.
*/ */
public PlayerInjectHooks getPlayerHook(GamePhase phase) { public abstract PlayerInjectHooks getPlayerHook(GamePhase phase);
switch (phase) {
case LOGIN:
return loginPlayerHook;
case PLAYING:
return playingPlayerHook;
default:
throw new IllegalArgumentException("Cannot retrieve injection hook for both phases at the same time.");
}
}
/** /**
* Sets how the server packets are read. * Sets how the server packets are read.
* @param playerHook - the new injection method for reading server packets. * @param playerHook - the new injection method for reading server packets.
*/ */
public void setPlayerHook(PlayerInjectHooks playerHook) { public abstract void setPlayerHook(PlayerInjectHooks playerHook);
setPlayerHook(GamePhase.PLAYING, playerHook);
}
/** /**
* Sets how the server packets are read. * Sets how the server packets are read.
* @param phase - the current game phase. * @param phase - the current game phase.
* @param playerHook - the new injection method for reading server packets. * @param playerHook - the new injection method for reading server packets.
*/ */
public void setPlayerHook(GamePhase phase, PlayerInjectHooks playerHook) { public abstract void setPlayerHook(GamePhase phase, PlayerInjectHooks playerHook);
if (phase.hasLogin())
loginPlayerHook = playerHook;
if (phase.hasPlaying())
playingPlayerHook = playerHook;
// Make sure the current listeners are compatible
checkListener(packetListeners);
}
/** /**
* Add an underlying packet handler of the given ID. * Add an underlying packet handler of the given ID.
* @param packetID - packet ID to register. * @param packetID - packet ID to register.
*/ */
public void addPacketHandler(int packetID) { public abstract void addPacketHandler(int packetID);
sendingFilters.add(packetID);
}
/** /**
* Remove an underlying packet handler of ths ID. * Remove an underlying packet handler of ths ID.
* @param packetID - packet ID to unregister. * @param packetID - packet ID to unregister.
*/ */
public void removePacketHandler(int packetID) { public abstract void removePacketHandler(int packetID);
sendingFilters.remove(packetID);
}
/**
* Used to construct a player hook.
* @param player - the player to hook.
* @param hook - the hook type.
* @return A new player hoook
* @throws IllegalAccessException Unable to do our reflection magic.
*/
private PlayerInjector getHookInstance(Player player, PlayerInjectHooks hook) throws IllegalAccessException {
// Construct the correct player hook
switch (hook) {
case NETWORK_HANDLER_FIELDS:
return new NetworkFieldInjector(classLoader, reporter, player, invoker, sendingFilters);
case NETWORK_MANAGER_OBJECT:
return new NetworkObjectInjector(classLoader, reporter, player, invoker, sendingFilters);
case NETWORK_SERVER_OBJECT:
return new NetworkServerInjector(classLoader, reporter, player, invoker, sendingFilters, serverInjection);
default:
throw new IllegalArgumentException("Cannot construct a player injector.");
}
}
/** /**
* Retrieve a player by its DataInput connection. * Retrieve a player by its DataInput connection.
@ -201,9 +58,8 @@ public class PlayerInjectionHandler {
* @return The player. * @return The player.
* @throws InterruptedException If the thread was interrupted during the wait. * @throws InterruptedException If the thread was interrupted during the wait.
*/ */
public Player getPlayerByConnection(DataInputStream inputStream) throws InterruptedException { public abstract Player getPlayerByConnection(DataInputStream inputStream)
return getPlayerByConnection(inputStream, TIMEOUT_PLAYER_LOOKUP, TimeUnit.MILLISECONDS); throws InterruptedException;
}
/** /**
* Retrieve a player by its DataInput connection. * Retrieve a player by its DataInput connection.
@ -213,26 +69,7 @@ public class PlayerInjectionHandler {
* @return The player. * @return The player.
* @throws InterruptedException If the thread was interrupted during the wait. * @throws InterruptedException If the thread was interrupted during the wait.
*/ */
public Player getPlayerByConnection(DataInputStream inputStream, long playerTimeout, TimeUnit unit) throws InterruptedException { public abstract Player getPlayerByConnection(DataInputStream inputStream, long playerTimeout, TimeUnit unit) throws InterruptedException;
// Wait until the connection owner has been established
PlayerInjector injector = dataInputLookup.get(inputStream, playerTimeout, unit);
if (injector != null) {
return injector.getPlayer();
} else {
reporter.reportWarning(this, "Unable to find stream: " + inputStream);
return null;
}
}
/**
* Helper function that retrieves the injector type of a given player injector.
* @param injector - injector type.
* @return The injector type.
*/
private PlayerInjectHooks getInjectorType(PlayerInjector injector) {
return injector != null ? injector.getHookType() : PlayerInjectHooks.NONE;
}
/** /**
* Initialize a player hook, allowing us to read server packets. * Initialize a player hook, allowing us to read server packets.
@ -240,215 +77,20 @@ public class PlayerInjectionHandler {
* This call will be ignored if there's no listener that can receive the given events. * This call will be ignored if there's no listener that can receive the given events.
* @param player - player to hook. * @param player - player to hook.
*/ */
public void injectPlayer(Player player) { public abstract void injectPlayer(Player player);
// Inject using the player instance itself
if (isInjectionNecessary(GamePhase.PLAYING)) {
injectPlayer(player, player, GamePhase.PLAYING);
}
}
/**
* Determine if it's truly necessary to perform the given player injection.
* @param phase - current game phase.
* @return TRUE if we should perform the injection, FALSE otherwise.
*/
public boolean isInjectionNecessary(GamePhase phase) {
return injectionFilter.apply(phase);
}
/**
* Initialize a player hook, allowing us to read server packets.
* <p>
* This method will always perform the instructed injection.
*
* @param player - player to hook.
* @param injectionPoint - the object to use during the injection process.
* @param phase - the current game phase.
* @return The resulting player injector, or NULL if the injection failed.
*/
PlayerInjector injectPlayer(Player player, Object injectionPoint, GamePhase phase) {
// Unfortunately, due to NetLoginHandler, multiple threads may potentially call this method.
synchronized (player) {
return injectPlayerInternal(player, injectionPoint, phase);
}
}
// Unsafe variant of the above
private PlayerInjector injectPlayerInternal(Player player, Object injectionPoint, GamePhase phase) {
PlayerInjector injector = playerInjection.get(player);
PlayerInjectHooks tempHook = getPlayerHook(phase);
PlayerInjectHooks permanentHook = tempHook;
// The given player object may be fake, so be careful!
// See if we need to inject something else
boolean invalidInjector = injector != null ? !injector.canInject(phase) : true;
// Don't inject if the class has closed
if (!hasClosed && player != null && (tempHook != getInjectorType(injector) || invalidInjector)) {
while (tempHook != PlayerInjectHooks.NONE) {
// Whether or not the current hook method failed completely
boolean hookFailed = false;
// Remove the previous hook, if any
cleanupHook(injector);
try {
injector = getHookInstance(player, tempHook);
// Make sure this injection method supports the current game phase
if (injector.canInject(phase)) {
injector.initialize(injectionPoint);
DataInputStream inputStream = injector.getInputStream(false);
Socket socket = injector.getSocket();
SocketAddress address = socket != null ? socket.getRemoteSocketAddress() : null;
// Guard against NPE here too
PlayerInjector previous = address != null ? addressLookup.get(address) : null;
// Close any previously associated hooks before we proceed
if (previous != null) {
uninjectPlayer(previous.getPlayer(), false, true);
}
injector.injectManager();
if (inputStream != null)
dataInputLookup.put(inputStream, injector);
if (address != null)
addressLookup.put(address, injector);
break;
}
} catch (PlayerLoggedOutException e) {
throw e;
} catch (Exception e) {
// Mark this injection attempt as a failure
reporter.reportDetailed(this, "Player hook " + tempHook.toString() + " failed.",
e, player, injectionPoint, phase);
hookFailed = true;
}
// Choose the previous player hook type
tempHook = PlayerInjectHooks.values()[tempHook.ordinal() - 1];
if (hookFailed)
reporter.reportWarning(this, "Switching to " + tempHook.toString() + " instead.");
// Check for UTTER FAILURE
if (tempHook == PlayerInjectHooks.NONE) {
cleanupHook(injector);
injector = null;
hookFailed = true;
}
// Should we set the default hook method too?
if (hookFailed) {
permanentHook = tempHook;
}
}
// Update values
if (injector != null)
lastSuccessfulHook = injector;
if (permanentHook != getPlayerHook(phase))
setPlayerHook(phase, tempHook);
// Save injector
if (injector != null) {
playerInjection.put(player, injector);
}
}
return injector;
}
private void cleanupHook(PlayerInjector injector) {
// Clean up as much as possible
try {
if (injector != null)
injector.cleanupAll();
} catch (Exception ex) {
reporter.reportDetailed(this, "Cleaing up after player hook failed.", ex, injector);
}
}
/** /**
* Invoke special routines for handling disconnect before a player is uninjected. * Invoke special routines for handling disconnect before a player is uninjected.
* @param player - player to process. * @param player - player to process.
*/ */
public void handleDisconnect(Player player) { public abstract void handleDisconnect(Player player);
PlayerInjector injector = getInjector(player);
if (injector != null) {
injector.handleDisconnect();
}
}
/** /**
* Unregisters the given player. * Unregisters the given player.
* @param player - player to unregister. * @param player - player to unregister.
* @return TRUE if a player has been uninjected, FALSE otherwise. * @return TRUE if a player has been uninjected, FALSE otherwise.
*/ */
public boolean uninjectPlayer(Player player) { public abstract boolean uninjectPlayer(Player player);
return uninjectPlayer(player, true, false);
}
/**
* Unregisters the given player.
* @param player - player to unregister.
* @param removeAuxiliary - TRUE to remove auxiliary information, such as input stream and address.
* @return TRUE if a player has been uninjected, FALSE otherwise.
*/
public boolean uninjectPlayer(Player player, boolean removeAuxiliary) {
return uninjectPlayer(player, removeAuxiliary, false);
}
/**
* Unregisters the given player.
* @param player - player to unregister.
* @param removeAuxiliary - TRUE to remove auxiliary information, such as input stream and address.
* @param prepareNextHook - whether or not we need to fix any lingering hooks.
* @return TRUE if a player has been uninjected, FALSE otherwise.
*/
private boolean uninjectPlayer(Player player, boolean removeAuxiliary, boolean prepareNextHook) {
if (!hasClosed && player != null) {
PlayerInjector injector = playerInjection.remove(player);
if (injector != null) {
InetSocketAddress address = player.getAddress();
injector.cleanupAll();
// Remove the "hooked" network manager in our instance as well
if (prepareNextHook && injector instanceof NetworkObjectInjector) {
try {
PlayerInjector dummyInjector = getHookInstance(player, PlayerInjectHooks.NETWORK_SERVER_OBJECT);
dummyInjector.initializePlayer(player);
dummyInjector.setNetworkManager(injector.getNetworkManager(), true);
} catch (IllegalAccessException e) {
// Let the user know
reporter.reportWarning(this, "Unable to fully revert old injector. May cause conflicts.", e);
}
}
// Clean up
if (removeAuxiliary) {
// Note that the dataInputLookup will clean itself
if (address != null)
addressLookup.remove(address);
}
return true;
}
}
return false;
}
/** /**
* Unregisters a player by the given address. * Unregisters a player by the given address.
@ -459,18 +101,7 @@ public class PlayerInjectionHandler {
* @param address - address of the player to unregister. * @param address - address of the player to unregister.
* @return TRUE if a player has been uninjected, FALSE otherwise. * @return TRUE if a player has been uninjected, FALSE otherwise.
*/ */
public boolean uninjectPlayer(InetSocketAddress address) { public abstract boolean uninjectPlayer(InetSocketAddress address);
if (!hasClosed && address != null) {
PlayerInjector injector = addressLookup.get(address);
// Clean up
if (injector != null)
uninjectPlayer(injector.getPlayer(), false, true);
return true;
}
return false;
}
/** /**
* Send the given packet to the given reciever. * Send the given packet to the given reciever.
@ -479,18 +110,8 @@ public class PlayerInjectionHandler {
* @param filters - whether or not to invoke the packet filters. * @param filters - whether or not to invoke the packet filters.
* @throws InvocationTargetException If an error occured during sending. * @throws InvocationTargetException If an error occured during sending.
*/ */
public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) throws InvocationTargetException { public abstract void sendServerPacket(Player reciever, PacketContainer packet, boolean filters)
PlayerInjector injector = getInjector(reciever); throws InvocationTargetException;
// Send the packet, or drop it completely
if (injector != null)
injector.sendServerPacket(packet.getHandle(), filters);
else
throw new PlayerLoggedOutException(String.format(
"Unable to send packet %s (%s): Player %s has logged out.",
packet.getID(), packet, reciever.getName()
));
}
/** /**
* Process a packet as if it were sent by the given player. * Process a packet as if it were sent by the given player.
@ -499,61 +120,14 @@ public class PlayerInjectionHandler {
* @throws IllegalAccessException If the reflection machinery failed. * @throws IllegalAccessException If the reflection machinery failed.
* @throws InvocationTargetException If the underlying method caused an error. * @throws InvocationTargetException If the underlying method caused an error.
*/ */
public void processPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException { public abstract void processPacket(Player player, Object mcPacket)
throws IllegalAccessException, InvocationTargetException;
PlayerInjector injector = getInjector(player);
// Process the given packet, or simply give up
if (injector != null)
injector.processPacket(mcPacket);
else
throw new PlayerLoggedOutException(String.format(
"Unable to receieve packet %s. Player %s has logged out.",
mcPacket, player.getName()
));
}
/**
* Retrieve the injector associated with this player.
* @param player - the player to find.
* @return The injector, or NULL if not found.
*/
private PlayerInjector getInjector(Player player) {
return playerInjection.get(player);
}
/**
* Retrieve a player injector by looking for its NetworkManager.
* @param networkManager - current network manager.
* @return Related player injector.
*/
PlayerInjector getInjectorByNetworkHandler(Object networkManager) {
// That's not legal
if (networkManager == null)
return null;
// O(n) is okay in this instance. This is only a backup solution.
for (PlayerInjector injector : playerInjection.values()) {
if (injector.getNetworkManager() == networkManager)
return injector;
}
// None found
return null;
}
/** /**
* Determine if the given listeners are valid. * Determine if the given listeners are valid.
* @param listeners - listeners to check. * @param listeners - listeners to check.
*/ */
public void checkListener(Set<PacketListener> listeners) { public abstract void checkListener(Set<PacketListener> listeners);
// Make sure the current listeners are compatible
if (lastSuccessfulHook != null) {
for (PacketListener listener : listeners) {
checkListener(listener);
}
}
}
/** /**
* Determine if a listener is valid or not. * Determine if a listener is valid or not.
@ -561,71 +135,22 @@ public class PlayerInjectionHandler {
* If not, a warning will be printed to the console. * If not, a warning will be printed to the console.
* @param listener - listener to check. * @param listener - listener to check.
*/ */
public void checkListener(PacketListener listener) { public abstract void checkListener(PacketListener listener);
if (lastSuccessfulHook != null) {
UnsupportedListener result = lastSuccessfulHook.checkListener(listener);
// We won't prevent the listener, as it may still have valid packets
if (result != null) {
reporter.reportWarning(this, "Cannot fully register listener for " +
PacketAdapter.getPluginName(listener) + ": " + result.toString());
// These are illegal
for (int packetID : result.getPackets())
removePacketHandler(packetID);
}
}
}
/** /**
* Retrieve the current list of registered sending listeners. * Retrieve the current list of registered sending listeners.
* @return List of the sending listeners's packet IDs. * @return List of the sending listeners's packet IDs.
*/ */
public Set<Integer> getSendingFilters() { public abstract Set<Integer> getSendingFilters();
return sendingFilters.toSet();
}
public void close() { /**
// Guard * Close any lingering proxy injections.
if (hasClosed || playerInjection == null) */
return; public abstract void close();
// Remove everything
for (PlayerInjector injection : playerInjection.values()) {
if (injection != null) {
injection.cleanupAll();
}
}
// Remove server handler
if (serverInjection != null)
serverInjection.cleanupAll();
if (netLoginInjector != null)
netLoginInjector.cleanupAll();
serverInjection = null;
netLoginInjector = null;
hasClosed = true;
playerInjection.clear();
addressLookup.clear();
invoker = null;
}
/** /**
* Inform the current PlayerInjector that it should update the DataInputStream next. * Inform the current PlayerInjector that it should update the DataInputStream next.
* @param player - the player to update. * @param player - the player to update.
*/ */
public void scheduleDataInputRefresh(Player player) { public abstract void scheduleDataInputRefresh(Player player);
final PlayerInjector injector = getInjector(player);
// Update the DataInputStream
if (injector != null) {
injector.scheduleAction(new Runnable() {
@Override
public void run() {
dataInputLookup.put(injector.getInputStream(false), injector);
}
});
}
}
} }

Datei anzeigen

@ -58,10 +58,10 @@ abstract class PlayerInjector {
protected static Field proxyServerField; protected static Field proxyServerField;
protected static Field networkManagerField; protected static Field networkManagerField;
protected static Field inputField;
protected static Field netHandlerField; protected static Field netHandlerField;
protected static Field socketField; protected static Field socketField;
private static Field inputField;
private static Field entityPlayerField; private static Field entityPlayerField;
// Whether or not we're using a proxy type // Whether or not we're using a proxy type
@ -135,7 +135,7 @@ abstract class PlayerInjector {
//Dispatch to the correct injection method //Dispatch to the correct injection method
if (injectionSource instanceof Player) if (injectionSource instanceof Player)
initializePlayer(injectionSource); initializePlayer((Player) injectionSource);
else if (MinecraftReflection.isLoginHandler(injectionSource)) else if (MinecraftReflection.isLoginHandler(injectionSource))
initializeLogin(injectionSource); initializeLogin(injectionSource);
else else
@ -146,10 +146,12 @@ abstract class PlayerInjector {
* Initialize the player injector using an actual player instance. * Initialize the player injector using an actual player instance.
* @param player - the player to hook. * @param player - the player to hook.
*/ */
public void initializePlayer(Object player) { public void initializePlayer(Player player) {
Object notchEntity = getEntityPlayer((Player) player); Object notchEntity = getEntityPlayer((Player) player);
// Save the player too
this.player = player;
if (!hasInitialized) { if (!hasInitialized) {
// Do this first, in case we encounter an exception // Do this first, in case we encounter an exception
hasInitialized = true; hasInitialized = true;
@ -167,24 +169,29 @@ abstract class PlayerInjector {
// Next, get the network manager // Next, get the network manager
if (networkManagerField == null) if (networkManagerField == null)
networkManagerField = FuzzyReflection.fromObject(serverHandler). networkManagerField = FuzzyReflection.fromObject(serverHandler).getFieldByType(
getFieldByType(".*" + MinecraftReflection.getNetworkManagerName()); "networkManager", MinecraftReflection.getNetworkManagerClass());
initializeNetworkManager(networkManagerField, serverHandler); initializeNetworkManager(networkManagerField, serverHandler);
} }
} }
/** /**
* Initialize the player injector for a NetLoginHandler instead. * Initialize the player injector from a NetLoginHandler.
* @param netLoginHandler - the net login handler to inject. * @param netLoginHandler - the net login handler to inject.
*/ */
public void initializeLogin(Object netLoginHandler) { public void initializeLogin(Object netLoginHandler) {
if (!hasInitialized) { if (!hasInitialized) {
// Just in case
if (!MinecraftReflection.isLoginHandler(netLoginHandler))
throw new IllegalArgumentException("netLoginHandler (" + netLoginHandler + ") is not a " +
MinecraftReflection.getNetLoginHandlerName());
hasInitialized = true; hasInitialized = true;
loginHandler = netLoginHandler; loginHandler = netLoginHandler;
if (netLoginNetworkField == null) if (netLoginNetworkField == null)
netLoginNetworkField = FuzzyReflection.fromObject(netLoginHandler). netLoginNetworkField = FuzzyReflection.fromObject(netLoginHandler).
getFieldByType(".*" + MinecraftReflection.getNetworkManagerName()); getFieldByType("networkManager", MinecraftReflection.getNetworkManagerClass());
initializeNetworkManager(netLoginNetworkField, netLoginHandler); initializeNetworkManager(netLoginNetworkField, netLoginHandler);
} }
} }
@ -206,11 +213,6 @@ abstract class PlayerInjector {
if (queueMethod == null) if (queueMethod == null)
queueMethod = FuzzyReflection.fromClass(reference.getType()). queueMethod = FuzzyReflection.fromClass(reference.getType()).
getMethodByParameters("queue", MinecraftReflection.getPacketClass()); getMethodByParameters("queue", MinecraftReflection.getPacketClass());
// And the data input stream that we'll use to identify a player
if (inputField == null)
inputField = FuzzyReflection.fromObject(networkManager, true).
getFieldByType("java\\.io\\.DataInputStream");
} }
/** /**
@ -250,9 +252,9 @@ abstract class PlayerInjector {
public Socket getSocket() throws IllegalAccessException { public Socket getSocket() throws IllegalAccessException {
try { try {
if (socketField == null) if (socketField == null)
socketField = FuzzyReflection.fromObject(networkManager).getFieldListByType(Socket.class).get(0); socketField = FuzzyReflection.fromObject(networkManager, true).getFieldListByType(Socket.class).get(0);
if (socket == null) if (socket == null)
socket = (Socket) FieldUtils.readField(socketField, networkManager); socket = (Socket) FieldUtils.readField(socketField, networkManager, true);
return socket; return socket;
} catch (IndexOutOfBoundsException e) { } catch (IndexOutOfBoundsException e) {
@ -290,7 +292,13 @@ abstract class PlayerInjector {
// Execute disconnect on it // Execute disconnect on it
if (handler != null) { if (handler != null) {
if (disconnect == null) { if (disconnect == null) {
disconnect = FuzzyReflection.fromObject(handler).getMethodByName("disconnect.*"); try {
disconnect = FuzzyReflection.fromObject(handler).getMethodByName("disconnect.*");
} catch (IllegalArgumentException e) {
// Just assume it's the first String method
disconnect = FuzzyReflection.fromObject(handler).getMethodByParameters("disconnect", String.class);
reporter.reportWarning(this, "Cannot find disconnect method by name. Assuming " + disconnect);
}
// Save the method for later // Save the method for later
if (usingNetServer) if (usingNetServer)
@ -330,7 +338,7 @@ abstract class PlayerInjector {
Object handler = FieldUtils.readField(serverHandlerField, notchEntity, true); Object handler = FieldUtils.readField(serverHandlerField, notchEntity, true);
// Is this a Minecraft hook? // Is this a Minecraft hook?
if (handler != null && !handler.getClass().getName().startsWith("net.minecraft.server")) { if (handler != null && !MinecraftReflection.isMinecraftObject(handler)) {
// This is our proxy object // This is our proxy object
if (handler instanceof Factory) if (handler instanceof Factory)
@ -380,7 +388,7 @@ abstract class PlayerInjector {
try { try {
// Well, that sucks. Try just Minecraft objects then. // Well, that sucks. Try just Minecraft objects then.
netHandlerField = FuzzyReflection.fromClass(networkManager.getClass(), true). netHandlerField = FuzzyReflection.fromClass(networkManager.getClass(), true).
getFieldByType(MinecraftReflection.MINECRAFT_OBJECT); getFieldByType(MinecraftReflection.getMinecraftObjectRegex());
} catch (RuntimeException e2) { } catch (RuntimeException e2) {
throw new IllegalAccessException("Cannot locate net handler. " + e2.getMessage()); throw new IllegalAccessException("Cannot locate net handler. " + e2.getMessage());
@ -564,10 +572,12 @@ abstract class PlayerInjector {
* @return The player's input stream. * @return The player's input stream.
*/ */
public DataInputStream getInputStream(boolean cache) { public DataInputStream getInputStream(boolean cache) {
if (inputField == null) // And the data input stream that we'll use to identify a player
throw new IllegalStateException("Input field is NULL.");
if (networkManager == null) if (networkManager == null)
throw new IllegalStateException("Network manager is NULL."); throw new IllegalStateException("Network manager is NULL.");
if (inputField == null)
inputField = FuzzyReflection.fromObject(networkManager, true).
getFieldByType("java\\.io\\.DataInputStream");
// Get the associated input stream // Get the associated input stream
try { try {
@ -598,6 +608,16 @@ abstract class PlayerInjector {
return player; return player;
} }
/**
* Set the hooked player.
* <p>
* Should only be called during the creation of the injector.
* @param player - the new hooked player.
*/
public void setPlayer(Player player) {
this.player = player;
}
/** /**
* Object that can invoke the packet events. * Object that can invoke the packet events.
* @return Packet event invoker. * @return Packet event invoker.
@ -616,4 +636,12 @@ abstract class PlayerInjector {
else else
return player; return player;
} }
/**
* Set the real Bukkit player that we will use.
* @param updatedPlayer - the real Bukkit player.
*/
public void setUpdatedPlayer(Player updatedPlayer) {
this.updatedPlayer = updatedPlayer;
}
} }

Datei anzeigen

@ -0,0 +1,145 @@
package com.comphenix.protocol.injector.player;
import java.util.Set;
import javax.annotation.Nonnull;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
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;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
/**
* Constructor for different player injectors.
*
* @author Kristian
*/
public class PlayerInjectorBuilder {
public static PlayerInjectorBuilder newBuilder() {
return new PlayerInjectorBuilder();
}
protected PlayerInjectorBuilder() {
// Use the static method.
}
protected ClassLoader classLoader;
protected ErrorReporter reporter;
protected Predicate<GamePhase> injectionFilter;
protected ListenerInvoker invoker;
protected Set<PacketListener> packetListeners;
protected Server server;
/**
* Set the class loader to use during class generation.
* @param classLoader - new class loader.
* @return This builder, for chaining.
*/
public PlayerInjectorBuilder classLoader(@Nonnull ClassLoader classLoader) {
Preconditions.checkNotNull(classLoader, "classLoader cannot be NULL");
this.classLoader = classLoader;
return this;
}
/**
* The error reporter used by the created injector.
* @param reporter - new error reporter.
* @return This builder, for chaining.
*/
public PlayerInjectorBuilder reporter(@Nonnull ErrorReporter reporter) {
Preconditions.checkNotNull(reporter, "reporter cannot be NULL");
this.reporter = reporter;
return this;
}
/**
* The injection filter that is used to determine if it is necessary to perform
* injection during a certain phase.
* @param injectionFilter - filter predicate.
* @return This builder, for chaining.
*/
@Nonnull
public PlayerInjectorBuilder injectionFilter(@Nonnull Predicate<GamePhase> injectionFilter) {
Preconditions.checkNotNull(injectionFilter, "injectionFilter cannot be NULL");
this.injectionFilter = injectionFilter;
return this;
}
/**
* The packet stream invoker.
* @param invoker - the invoker.
* @return This builder, for chaining.
*/
public PlayerInjectorBuilder invoker(@Nonnull ListenerInvoker invoker) {
Preconditions.checkNotNull(invoker, "invoker cannot be NULL");
this.invoker = invoker;
return this;
}
/**
* Set the set of packet listeners.
* @param packetListeners - packet listeners.
* @return This builder, for chaining.
*/
@Nonnull
public PlayerInjectorBuilder packetListeners(@Nonnull Set<PacketListener> packetListeners) {
Preconditions.checkNotNull(packetListeners, "packetListeners cannot be NULL");
this.packetListeners = packetListeners;
return this;
}
/**
* Set the Bukkit server used for scheduling.
* @param server - the Bukkit server.
* @return This builder, for chaining.
*/
public PlayerInjectorBuilder server(@Nonnull Server server) {
Preconditions.checkNotNull(server, "server cannot be NULL");
this.server = server;
return this;
}
/**
* Called before an object is created with this builder.
*/
private void initializeDefaults() {
ProtocolManager manager = ProtocolLibrary.getProtocolManager();
// Initialize with default values if we can
if (classLoader == null)
classLoader = this.getClass().getClassLoader();
if (reporter == null)
reporter = ProtocolLibrary.getErrorReporter();
if (invoker == null)
invoker = (PacketFilterManager) manager;
if (server == null)
server = Bukkit.getServer();
if (injectionFilter == null)
throw new IllegalStateException("injectionFilter must be initialized.");
if (packetListeners == null)
throw new IllegalStateException("packetListeners must be initialized.");
}
/**
* Construct the injection handler.
* <p>
* Any builder parameter marked as NON-NULL is essential and must be initialized.
* @return The constructed injection handler using the current parameters.
*/
public PlayerInjectionHandler buildHandler() {
// Fill any default fields
initializeDefaults();
return new ProxyPlayerInjectionHandler(
classLoader, reporter, injectionFilter,
invoker, packetListeners, server);
}
}

Datei anzeigen

@ -0,0 +1,674 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.player;
import java.io.DataInputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.concurrency.BlockingHashMap;
import com.comphenix.protocol.concurrency.IntegerSet;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PlayerLoggedOutException;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
import com.comphenix.protocol.injector.player.TemporaryPlayerFactory.InjectContainer;
import com.google.common.base.Predicate;
import com.google.common.collect.Maps;
/**
* Responsible for injecting into a player's sendPacket method.
*
* @author Kristian
*/
class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
/**
* The maximum number of milliseconds to wait until a player can be looked up by connection.
*/
private static final long TIMEOUT_PLAYER_LOOKUP = 2000; // ms
// Server connection injection
private InjectedServerConnection serverInjection;
// NetLogin injector
private NetLoginInjector netLoginInjector;
// The last successful player hook
private PlayerInjector lastSuccessfulHook;
// Player injection
private Map<SocketAddress, PlayerInjector> addressLookup = Maps.newConcurrentMap();
private Map<Player, PlayerInjector> playerInjection = Maps.newConcurrentMap();
// Lookup player by connection
private BlockingHashMap<DataInputStream, PlayerInjector> dataInputLookup = BlockingHashMap.create();
// Player injection types
private volatile PlayerInjectHooks loginPlayerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
private volatile PlayerInjectHooks playingPlayerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
// Error reporter
private ErrorReporter reporter;
// Whether or not we're closing
private boolean hasClosed;
// Used to invoke events
private ListenerInvoker invoker;
// Enabled packet filters
private IntegerSet sendingFilters = new IntegerSet(Packets.MAXIMUM_PACKET_ID + 1);
// List of packet listeners
private Set<PacketListener> packetListeners;
// The class loader we're using
private ClassLoader classLoader;
// Used to filter injection attempts
private Predicate<GamePhase> injectionFilter;
public ProxyPlayerInjectionHandler(ClassLoader classLoader, ErrorReporter reporter, Predicate<GamePhase> injectionFilter,
ListenerInvoker invoker, Set<PacketListener> packetListeners, Server server) {
this.classLoader = classLoader;
this.reporter = reporter;
this.invoker = invoker;
this.injectionFilter = injectionFilter;
this.packetListeners = packetListeners;
this.netLoginInjector = new NetLoginInjector(reporter, this, server);
this.serverInjection = new InjectedServerConnection(reporter, server, netLoginInjector);
serverInjection.injectList();
}
/**
* Retrieves how the server packets are read.
* @return Injection method for reading server packets.
*/
@Override
public PlayerInjectHooks getPlayerHook() {
return getPlayerHook(GamePhase.PLAYING);
}
/**
* Retrieves how the server packets are read.
* @param phase - the current game phase.
* @return Injection method for reading server packets.
*/
@Override
public PlayerInjectHooks getPlayerHook(GamePhase phase) {
switch (phase) {
case LOGIN:
return loginPlayerHook;
case PLAYING:
return playingPlayerHook;
default:
throw new IllegalArgumentException("Cannot retrieve injection hook for both phases at the same time.");
}
}
/**
* Sets how the server packets are read.
* @param playerHook - the new injection method for reading server packets.
*/
@Override
public void setPlayerHook(PlayerInjectHooks playerHook) {
setPlayerHook(GamePhase.PLAYING, playerHook);
}
/**
* Sets how the server packets are read.
* @param phase - the current game phase.
* @param playerHook - the new injection method for reading server packets.
*/
@Override
public void setPlayerHook(GamePhase phase, PlayerInjectHooks playerHook) {
if (phase.hasLogin())
loginPlayerHook = playerHook;
if (phase.hasPlaying())
playingPlayerHook = playerHook;
// Make sure the current listeners are compatible
checkListener(packetListeners);
}
/**
* Add an underlying packet handler of the given ID.
* @param packetID - packet ID to register.
*/
@Override
public void addPacketHandler(int packetID) {
sendingFilters.add(packetID);
}
/**
* Remove an underlying packet handler of ths ID.
* @param packetID - packet ID to unregister.
*/
@Override
public void removePacketHandler(int packetID) {
sendingFilters.remove(packetID);
}
/**
* Used to construct a player hook.
* @param player - the player to hook.
* @param hook - the hook type.
* @return A new player hoook
* @throws IllegalAccessException Unable to do our reflection magic.
*/
private PlayerInjector getHookInstance(Player player, PlayerInjectHooks hook) throws IllegalAccessException {
// Construct the correct player hook
switch (hook) {
case NETWORK_HANDLER_FIELDS:
return new NetworkFieldInjector(classLoader, reporter, player, invoker, sendingFilters);
case NETWORK_MANAGER_OBJECT:
return new NetworkObjectInjector(classLoader, reporter, player, invoker, sendingFilters);
case NETWORK_SERVER_OBJECT:
return new NetworkServerInjector(classLoader, reporter, player, invoker, sendingFilters, serverInjection);
default:
throw new IllegalArgumentException("Cannot construct a player injector.");
}
}
/**
* Retrieve a player by its DataInput connection.
* @param inputStream - the associated DataInput connection.
* @return The player.
* @throws InterruptedException If the thread was interrupted during the wait.
*/
@Override
public Player getPlayerByConnection(DataInputStream inputStream) throws InterruptedException {
return getPlayerByConnection(inputStream, TIMEOUT_PLAYER_LOOKUP, TimeUnit.MILLISECONDS);
}
/**
* Retrieve a player by its DataInput connection.
* @param inputStream - the associated DataInput connection.
* @param playerTimeout - the amount of time to wait for a result.
* @param unit - unit of playerTimeout.
* @return The player.
* @throws InterruptedException If the thread was interrupted during the wait.
*/
@Override
public Player getPlayerByConnection(DataInputStream inputStream, long playerTimeout, TimeUnit unit) throws InterruptedException {
// Wait until the connection owner has been established
PlayerInjector injector = dataInputLookup.get(inputStream, playerTimeout, unit);
if (injector != null) {
return injector.getPlayer();
} else {
reporter.reportWarning(this, "Unable to find stream: " + inputStream);
return null;
}
}
/**
* Helper function that retrieves the injector type of a given player injector.
* @param injector - injector type.
* @return The injector type.
*/
private PlayerInjectHooks getInjectorType(PlayerInjector injector) {
return injector != null ? injector.getHookType() : PlayerInjectHooks.NONE;
}
/**
* Initialize a player hook, allowing us to read server packets.
* <p>
* This call will be ignored if there's no listener that can receive the given events.
* @param player - player to hook.
*/
@Override
public void injectPlayer(Player player) {
// Inject using the player instance itself
if (isInjectionNecessary(GamePhase.PLAYING)) {
injectPlayer(player, player, GamePhase.PLAYING);
}
}
/**
* Determine if it's truly necessary to perform the given player injection.
* @param phase - current game phase.
* @return TRUE if we should perform the injection, FALSE otherwise.
*/
public boolean isInjectionNecessary(GamePhase phase) {
return injectionFilter.apply(phase);
}
/**
* Initialize a player hook, allowing us to read server packets.
* <p>
* This method will always perform the instructed injection.
*
* @param player - player to hook.
* @param injectionPoint - the object to use during the injection process.
* @param phase - the current game phase.
* @return The resulting player injector, or NULL if the injection failed.
*/
PlayerInjector injectPlayer(Player player, Object injectionPoint, GamePhase phase) {
// Unfortunately, due to NetLoginHandler, multiple threads may potentially call this method.
synchronized (player) {
return injectPlayerInternal(player, injectionPoint, phase);
}
}
// Unsafe variant of the above
private PlayerInjector injectPlayerInternal(Player player, Object injectionPoint, GamePhase phase) {
PlayerInjector injector = playerInjection.get(player);
PlayerInjectHooks tempHook = getPlayerHook(phase);
PlayerInjectHooks permanentHook = tempHook;
// The given player object may be fake, so be careful!
// See if we need to inject something else
boolean invalidInjector = injector != null ? !injector.canInject(phase) : true;
// Don't inject if the class has closed
if (!hasClosed && player != null && (tempHook != getInjectorType(injector) || invalidInjector)) {
while (tempHook != PlayerInjectHooks.NONE) {
// Whether or not the current hook method failed completely
boolean hookFailed = false;
// Remove the previous hook, if any
cleanupHook(injector);
try {
injector = getHookInstance(player, tempHook);
// Make sure this injection method supports the current game phase
if (injector.canInject(phase)) {
injector.initialize(injectionPoint);
DataInputStream inputStream = injector.getInputStream(false);
Socket socket = injector.getSocket();
SocketAddress address = socket != null ? socket.getRemoteSocketAddress() : null;
// Guard against NPE here too
PlayerInjector previous = address != null ? addressLookup.get(address) : null;
// Close any previously associated hooks before we proceed
if (previous != null) {
uninjectPlayer(previous.getPlayer(), false, true);
}
injector.injectManager();
if (inputStream != null)
dataInputLookup.put(inputStream, injector);
if (address != null)
addressLookup.put(address, injector);
break;
}
} catch (PlayerLoggedOutException e) {
throw e;
} catch (Exception e) {
// Mark this injection attempt as a failure
reporter.reportDetailed(this, "Player hook " + tempHook.toString() + " failed.",
e, player, injectionPoint, phase);
hookFailed = true;
}
// Choose the previous player hook type
tempHook = PlayerInjectHooks.values()[tempHook.ordinal() - 1];
if (hookFailed)
reporter.reportWarning(this, "Switching to " + tempHook.toString() + " instead.");
// Check for UTTER FAILURE
if (tempHook == PlayerInjectHooks.NONE) {
cleanupHook(injector);
injector = null;
hookFailed = true;
}
// Should we set the default hook method too?
if (hookFailed) {
permanentHook = tempHook;
}
}
// Update values
if (injector != null)
lastSuccessfulHook = injector;
if (permanentHook != getPlayerHook(phase))
setPlayerHook(phase, tempHook);
// Save injector
if (injector != null) {
playerInjection.put(player, injector);
}
}
return injector;
}
private void cleanupHook(PlayerInjector injector) {
// Clean up as much as possible
try {
if (injector != null)
injector.cleanupAll();
} catch (Exception ex) {
reporter.reportDetailed(this, "Cleaing up after player hook failed.", ex, injector);
}
}
/**
* Invoke special routines for handling disconnect before a player is uninjected.
* @param player - player to process.
*/
@Override
public void handleDisconnect(Player player) {
PlayerInjector injector = getInjector(player);
if (injector != null) {
injector.handleDisconnect();
}
}
/**
* Unregisters the given player.
* @param player - player to unregister.
* @return TRUE if a player has been uninjected, FALSE otherwise.
*/
@Override
public boolean uninjectPlayer(Player player) {
return uninjectPlayer(player, true, false);
}
/**
* Unregisters the given player.
* @param player - player to unregister.
* @param removeAuxiliary - TRUE to remove auxiliary information, such as input stream and address.
* @return TRUE if a player has been uninjected, FALSE otherwise.
*/
public boolean uninjectPlayer(Player player, boolean removeAuxiliary) {
return uninjectPlayer(player, removeAuxiliary, false);
}
/**
* Unregisters the given player.
* @param player - player to unregister.
* @param removeAuxiliary - TRUE to remove auxiliary information, such as input stream and address.
* @param prepareNextHook - whether or not we need to fix any lingering hooks.
* @return TRUE if a player has been uninjected, FALSE otherwise.
*/
private boolean uninjectPlayer(Player player, boolean removeAuxiliary, boolean prepareNextHook) {
if (!hasClosed && player != null) {
PlayerInjector injector = playerInjection.remove(player);
if (injector != null) {
InetSocketAddress address = player.getAddress();
injector.cleanupAll();
// Remove the "hooked" network manager in our instance as well
if (prepareNextHook && injector instanceof NetworkObjectInjector) {
try {
PlayerInjector dummyInjector = getHookInstance(player, PlayerInjectHooks.NETWORK_SERVER_OBJECT);
dummyInjector.initializePlayer(player);
dummyInjector.setNetworkManager(injector.getNetworkManager(), true);
} catch (IllegalAccessException e) {
// Let the user know
reporter.reportWarning(this, "Unable to fully revert old injector. May cause conflicts.", e);
}
}
// Clean up
if (removeAuxiliary) {
// Note that the dataInputLookup will clean itself
if (address != null)
addressLookup.remove(address);
}
return true;
}
}
return false;
}
/**
* Unregisters a player by the given address.
* <p>
* If the server handler has been created before we've gotten a chance to unject the player,
* the method will try a workaround to remove the injected hook in the NetServerHandler.
*
* @param address - address of the player to unregister.
* @return TRUE if a player has been uninjected, FALSE otherwise.
*/
@Override
public boolean uninjectPlayer(InetSocketAddress address) {
if (!hasClosed && address != null) {
PlayerInjector injector = addressLookup.get(address);
// Clean up
if (injector != null)
uninjectPlayer(injector.getPlayer(), false, true);
return true;
}
return false;
}
/**
* Send the given packet to the given reciever.
* @param reciever - the player receiver.
* @param packet - the packet to send.
* @param filters - whether or not to invoke the packet filters.
* @throws InvocationTargetException If an error occured during sending.
*/
@Override
public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) throws InvocationTargetException {
PlayerInjector injector = getInjector(reciever);
// Send the packet, or drop it completely
if (injector != null) {
injector.sendServerPacket(packet.getHandle(), filters);
} else {
throw new PlayerLoggedOutException(String.format(
"Unable to send packet %s (%s): Player %s has logged out.",
packet.getID(), packet, reciever.getName()
));
}
}
/**
* Process a packet as if it were sent by the given player.
* @param player - the sender.
* @param mcPacket - the packet to process.
* @throws IllegalAccessException If the reflection machinery failed.
* @throws InvocationTargetException If the underlying method caused an error.
*/
@Override
public void processPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException {
PlayerInjector injector = getInjector(player);
// Process the given packet, or simply give up
if (injector != null)
injector.processPacket(mcPacket);
else
throw new PlayerLoggedOutException(String.format(
"Unable to receieve packet %s. Player %s has logged out.",
mcPacket, player.getName()
));
}
/**
* Retrieve the injector associated with this player.
* @param player - the player to find.
* @return The injector, or NULL if not found.
*/
private PlayerInjector getInjector(Player player) {
PlayerInjector injector = playerInjection.get(player);
if (injector == null) {
// Try getting it from the player itself
if (player instanceof InjectContainer)
return ((InjectContainer) player).getInjector();
else
return searchAddressLookup(player);
} else {
return injector;
}
}
/**
* Find an injector by looking through the address map.
* @param player - player to find.
* @return The injector, or NULL if not found.
*/
private PlayerInjector searchAddressLookup(Player player) {
// See if we can find it anywhere
for (PlayerInjector injector : addressLookup.values()) {
if (player.equals(injector.getUpdatedPlayer())) {
return injector;
}
}
return null;
}
/**
* Retrieve a player injector by looking for its NetworkManager.
* @param networkManager - current network manager.
* @return Related player injector.
*/
PlayerInjector getInjectorByNetworkHandler(Object networkManager) {
// That's not legal
if (networkManager == null)
return null;
// O(n) is okay in this instance. This is only a backup solution.
for (PlayerInjector injector : playerInjection.values()) {
if (injector.getNetworkManager() == networkManager)
return injector;
}
// None found
return null;
}
/**
* Determine if the given listeners are valid.
* @param listeners - listeners to check.
*/
@Override
public void checkListener(Set<PacketListener> listeners) {
// Make sure the current listeners are compatible
if (lastSuccessfulHook != null) {
for (PacketListener listener : listeners) {
checkListener(listener);
}
}
}
/**
* Determine if a listener is valid or not.
* <p>
* If not, a warning will be printed to the console.
* @param listener - listener to check.
*/
@Override
public void checkListener(PacketListener listener) {
if (lastSuccessfulHook != null) {
UnsupportedListener result = lastSuccessfulHook.checkListener(listener);
// We won't prevent the listener, as it may still have valid packets
if (result != null) {
reporter.reportWarning(this, "Cannot fully register listener for " +
PacketAdapter.getPluginName(listener) + ": " + result.toString());
// These are illegal
for (int packetID : result.getPackets())
removePacketHandler(packetID);
}
}
}
/**
* Retrieve the current list of registered sending listeners.
* @return List of the sending listeners's packet IDs.
*/
@Override
public Set<Integer> getSendingFilters() {
return sendingFilters.toSet();
}
@Override
public void close() {
// Guard
if (hasClosed || playerInjection == null)
return;
// Remove everything
for (PlayerInjector injection : playerInjection.values()) {
if (injection != null) {
injection.cleanupAll();
}
}
// Remove server handler
if (serverInjection != null)
serverInjection.cleanupAll();
if (netLoginInjector != null)
netLoginInjector.cleanupAll();
serverInjection = null;
netLoginInjector = null;
hasClosed = true;
playerInjection.clear();
addressLookup.clear();
invoker = null;
}
/**
* Inform the current PlayerInjector that it should update the DataInputStream next.
* @param player - the player to update.
*/
@Override
public void scheduleDataInputRefresh(Player player) {
final PlayerInjector injector = getInjector(player);
// Update the DataInputStream
if (injector != null) {
injector.scheduleAction(new Runnable() {
@Override
public void run() {
dataInputLookup.put(injector.getInputStream(false), injector);
}
});
}
}
}

Datei anzeigen

@ -81,7 +81,7 @@ class TemporaryPlayerFactory {
* </ul> * </ul>
* <p> * <p>
* Note that the player a player has not been assigned a name yet, and thus cannot be * Note that the player a player has not been assigned a name yet, and thus cannot be
* uniquely identified. Use the * uniquely identified. Use the address instead.
* @param injector - the player injector used. * @param injector - the player injector used.
* @param server - the current server. * @param server - the current server.
* @return A temporary player instance. * @return A temporary player instance.

Datei anzeigen

@ -0,0 +1,57 @@
package com.comphenix.protocol.injector.spigot;
import java.util.Set;
import org.bukkit.entity.Player;
import com.comphenix.protocol.concurrency.IntegerSet;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.packet.PacketInjector;
public class DummyPacketInjector implements PacketInjector {
private SpigotPacketInjector injector;
private IntegerSet reveivedFilters;
public DummyPacketInjector(SpigotPacketInjector injector, IntegerSet reveivedFilters) {
this.injector = injector;
this.reveivedFilters = reveivedFilters;
}
@Override
public void undoCancel(Integer id, Object packet) {
// Do nothing yet
}
@Override
public boolean addPacketHandler(int packetID) {
reveivedFilters.add(packetID);
return true;
}
@Override
public boolean removePacketHandler(int packetID) {
reveivedFilters.remove(packetID);
return true;
}
@Override
public boolean hasPacketHandler(int packetID) {
return reveivedFilters.contains(packetID);
}
@Override
public Set<Integer> getPacketHandlers() {
return reveivedFilters.toSet();
}
@Override
public PacketEvent packetRecieved(PacketContainer packet, Player client) {
return injector.packetReceived(packet, client);
}
@Override
public void cleanupAll() {
reveivedFilters.clear();
}
}

Datei anzeigen

@ -0,0 +1,123 @@
package com.comphenix.protocol.injector.spigot;
import java.io.DataInputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.bukkit.entity.Player;
import com.comphenix.protocol.concurrency.IntegerSet;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
class DummyPlayerHandler implements PlayerInjectionHandler {
private SpigotPacketInjector injector;
private IntegerSet sendingFilters;
public DummyPlayerHandler(SpigotPacketInjector injector, IntegerSet sendingFilters) {
this.injector = injector;
this.sendingFilters = sendingFilters;
}
@Override
public boolean uninjectPlayer(InetSocketAddress address) {
return true;
}
@Override
public boolean uninjectPlayer(Player player) {
injector.uninjectPlayer(player);
return true;
}
@Override
public void setPlayerHook(GamePhase phase, PlayerInjectHooks playerHook) {
throw new UnsupportedOperationException("This is not needed in Spigot.");
}
@Override
public void setPlayerHook(PlayerInjectHooks playerHook) {
throw new UnsupportedOperationException("This is not needed in Spigot.");
}
@Override
public void scheduleDataInputRefresh(Player player) {
// Fine
}
@Override
public void addPacketHandler(int packetID) {
sendingFilters.add(packetID);
}
@Override
public void removePacketHandler(int packetID) {
sendingFilters.remove(packetID);
}
@Override
public Set<Integer> getSendingFilters() {
return sendingFilters.toSet();
}
@Override
public void close() {
sendingFilters.clear();
}
@Override
public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) throws InvocationTargetException {
injector.sendServerPacket(reciever, packet, filters);
}
@Override
public void processPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException {
injector.processPacket(player, mcPacket);
}
@Override
public void injectPlayer(Player player) {
injector.injectPlayer(player);
}
@Override
public void handleDisconnect(Player player) {
// Just ignore
}
@Override
public PlayerInjectHooks getPlayerHook(GamePhase phase) {
return PlayerInjectHooks.NETWORK_SERVER_OBJECT;
}
@Override
public PlayerInjectHooks getPlayerHook() {
// Pretend that we do
return PlayerInjectHooks.NETWORK_SERVER_OBJECT;
}
@Override
public Player getPlayerByConnection(DataInputStream inputStream, long playerTimeout, TimeUnit unit) throws InterruptedException {
throw new UnsupportedOperationException("This is not needed in Spigot.");
}
@Override
public Player getPlayerByConnection(DataInputStream inputStream) throws InterruptedException {
throw new UnsupportedOperationException("This is not needed in Spigot.");
}
@Override
public void checkListener(PacketListener listener) {
// They're all fine!
}
@Override
public void checkListener(Set<PacketListener> listeners) {
// Yes, really
}
}

Datei anzeigen

@ -0,0 +1,466 @@
package com.comphenix.protocol.injector.spigot;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
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 com.comphenix.protocol.Packets;
import com.comphenix.protocol.concurrency.IntegerSet;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PlayerLoggedOutException;
import com.comphenix.protocol.injector.packet.PacketInjector;
import com.comphenix.protocol.injector.player.NetworkObjectInjector;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.MethodInfo;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.MapMaker;
/**
* Offload all the work to Spigot, if possible.
*
* @author Kristian
*/
public class SpigotPacketInjector implements SpigotPacketListener {
// Lazily retrieve the spigot listener class
private static volatile Class<?> spigotListenerClass;
private static volatile boolean classChecked;
// Retrieve the entity player from a PlayerConnection
private static Field playerConnectionPlayer;
// Packets that are not to be processed by the filters
private Set<Object> ignoredPackets = Collections.newSetFromMap(new MapMaker().weakKeys().<Object, Boolean>makeMap());
/**
* The amount of ticks to wait before removing all traces of a player.
*/
private static final int CLEANUP_DELAY = 100;
/**
* Retrieve the spigot packet listener class.
* @return The listener class.
*/
private static Class<?> getSpigotListenerClass() {
if (!classChecked) {
try {
spigotListenerClass = SpigotPacketInjector.class.getClassLoader().loadClass("org.spigotmc.netty.PacketListener");
} catch (ClassNotFoundException e) {
return null;
} finally {
// We've given it a try now
classChecked = true;
}
}
return spigotListenerClass;
}
/**
* Retrieve the register packet listener method.
* @return The method used to register a packet listener.
*/
private static Method getRegisterMethod() {
Class<?> clazz = getSpigotListenerClass();
if (clazz != null) {
try {
return clazz.getMethod("register", clazz, Plugin.class);
} catch (SecurityException e) {
// If this happens, then ... we're doomed
throw new RuntimeException("Reflection is not allowed.", e);
} catch (NoSuchMethodException e) {
throw new IllegalStateException("Cannot find register() method in " + clazz, e);
}
}
// Also bad
throw new IllegalStateException("Spigot could not be found!");
}
/**
* Determine if there is a Spigot packet listener.
* @return Spigot packet listener.
*/
public static boolean canUseSpigotListener() {
return getSpigotListenerClass() != null;
}
// The listener we will register on Spigot.
// Unfortunately, due to the use of PlayerConnection, INetworkManager and Packet, we're
// unable to reference it directly. But with CGLib, it shouldn't cost us much.
private Object dynamicListener;
// Reference to ProtocolLib
private Plugin plugin;
// Different sending filters
private IntegerSet queuedFilters;
private IntegerSet reveivedFilters;
// NetworkManager to injector and player
private ConcurrentMap<Object, NetworkObjectInjector> networkManagerInjector = new ConcurrentHashMap<Object, NetworkObjectInjector>();
// Player to injector
private ConcurrentMap<Player, NetworkObjectInjector> playerInjector = new ConcurrentHashMap<Player, NetworkObjectInjector>();
// Responsible for informing the PL packet listeners
private ListenerInvoker invoker;
private ErrorReporter reporter;
private Server server;
private ClassLoader classLoader;
/**
* Create a new spigot injector.
*/
public SpigotPacketInjector(ClassLoader classLoader, ErrorReporter reporter, ListenerInvoker invoker, Server server) {
this.classLoader = classLoader;
this.reporter = reporter;
this.invoker = invoker;
this.server = server;
this.queuedFilters = new IntegerSet(Packets.MAXIMUM_PACKET_ID + 1);
this.reveivedFilters = new IntegerSet(Packets.MAXIMUM_PACKET_ID + 1);
}
public boolean register(Plugin plugin) {
if (hasRegistered())
return false;
// Save the plugin too
this.plugin = plugin;
final Callback[] callbacks = new Callback[3];
final boolean[] found = new boolean[3];
// Packets received from the clients
callbacks[0] = new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
return SpigotPacketInjector.this.packetReceived(args[0], args[1], args[2]);
}
};
// Packet sent/queued
callbacks[1] = new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
return SpigotPacketInjector.this.packetQueued(args[0], args[1], args[2]);
}
};
// Don't care for everything else
callbacks[2] = NoOp.INSTANCE;
Enhancer enhancer = new Enhancer();
enhancer.setClassLoader(classLoader);
enhancer.setSuperclass(getSpigotListenerClass());
enhancer.setCallbacks(callbacks);
enhancer.setCallbackFilter(new CallbackFilter() {
@Override
public int accept(Method method) {
// We'll be pretty stringent
if (matchMethod("packetReceived", method)) {
found[0] = true;
return 0;
} else if (matchMethod("packetQueued", method)) {
found[1] = true;
return 1;
} else {
found[2] = true;
return 2;
}
}
});
dynamicListener = enhancer.create();
// Verify methods
if (!found[0])
throw new IllegalStateException("Unable to find a valid packet receiver in Spigot.");
if (!found[1])
throw new IllegalStateException("Unable to find a valid packet queue in Spigot.");
// Lets register it too
try {
getRegisterMethod().invoke(null, dynamicListener, plugin);
} catch (Exception e) {
throw new RuntimeException("Cannot register Spigot packet listener.", e);
}
// If we succeed
return true;
}
/**
* Determine if the given method is a valid packet receiver or queued method.
* @param methodName - the expected name of the method.
* @param method - the method we're testing.
* @return TRUE if this is a correct method, FALSE otherwise.
*/
private boolean matchMethod(String methodName, Method method) {
return FuzzyMethodContract.newBuilder().
nameExact(methodName).
parameterCount(3).
parameterSuperOf(MinecraftReflection.getNetHandlerClass(), 1).
parameterSuperOf(MinecraftReflection.getPacketClass(), 2).
returnTypeExact(MinecraftReflection.getPacketClass()).
build().
isMatch(MethodInfo.fromMethod(method), null);
}
public boolean hasRegistered() {
return dynamicListener != null;
}
public PlayerInjectionHandler getPlayerHandler() {
return new DummyPlayerHandler(this, queuedFilters);
}
public PacketInjector getPacketInjector() {
return new DummyPacketInjector(this, reveivedFilters);
}
/**
* Retrieve the currently registered injector for the given player.
* @param player - injected player.
* @return The injector.
*/
NetworkObjectInjector getInjector(Player player) {
return playerInjector.get(player);
}
/**
* Retrieve or create a registered injector for the given network manager and connection.
* @param networkManager - a INetworkManager object.
* @param connection - a Connection (PlayerConnection, PendingConnection) object.
* @return The created NetworkObjectInjector with a temporary player.
*/
NetworkObjectInjector getInjector(Object networkManager, Object connection) {
NetworkObjectInjector dummyInjector = networkManagerInjector.get(networkManager);
if (dummyInjector == null) {
// Inject the network manager
try {
NetworkObjectInjector created = new NetworkObjectInjector(classLoader, reporter, null, invoker, null);
if (MinecraftReflection.isLoginHandler(connection)) {
created.initialize(connection);
created.setPlayer(created.createTemporaryPlayer(server));
} else if (MinecraftReflection.isServerHandler(connection)) {
// Get the player instead
if (playerConnectionPlayer == null)
playerConnectionPlayer = FuzzyReflection.fromObject(connection).
getFieldByType("player", MinecraftReflection.getEntityPlayerClass());
Object entityPlayer = playerConnectionPlayer.get(connection);
created.initialize(MinecraftReflection.getBukkitEntity(entityPlayer));
} else {
throw new IllegalArgumentException("Unregonized connection in NetworkManager.");
}
dummyInjector = saveInjector(networkManager, created);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot create dummy injector.", e);
}
}
return dummyInjector;
}
/**
* Save a given player injector for later.
* @param networkManager - the associated network manager.
* @param created - the created network object creator.
* @return Any other network injector that came before us.
*/
private NetworkObjectInjector saveInjector(Object networkManager, NetworkObjectInjector created) {
// Concurrency - use the same injector!
NetworkObjectInjector result = networkManagerInjector.putIfAbsent(networkManager, created);
if (result == null) {
result = created;
}
// Save the player as well
playerInjector.put(created.getPlayer(), created);
return result;
}
@Override
public Object packetReceived(Object networkManager, Object connection, Object packet) {
Integer id = invoker.getPacketID(packet);
if (id != null && reveivedFilters.contains(id)) {
// Check for ignored packets
if (ignoredPackets.remove(packet)) {
return packet;
}
Player sender = getInjector(networkManager, connection).getUpdatedPlayer();
PacketContainer container = new PacketContainer(id, packet);
PacketEvent event = packetReceived(container, sender);
if (!event.isCancelled())
return event.getPacket().getHandle();
else
return null; // Cancel
}
// Don't change anything
return packet;
}
@Override
public Object packetQueued(Object networkManager, Object connection, Object packet) {
Integer id = invoker.getPacketID(packet);
if (id != null & queuedFilters.contains(id)) {
// Check for ignored packets
if (ignoredPackets.remove(packet)) {
return packet;
}
Player reciever = getInjector(networkManager, connection).getUpdatedPlayer();
PacketContainer container = new PacketContainer(id, packet);
PacketEvent event = packetQueued(container, reciever);
if (!event.isCancelled())
return event.getPacket().getHandle();
else
return null; // Cancel
}
// Don't change anything
return packet;
}
/**
* Called to inform the event listeners of a queued packet.
* @param packet - the packet that is to be sent.
* @param reciever - the reciever of this packet.
* @return The packet event that was used.
*/
PacketEvent packetQueued(PacketContainer packet, Player reciever) {
PacketEvent event = PacketEvent.fromServer(this, packet, reciever);
invoker.invokePacketSending(event);
return event;
}
/**
* Called to inform the event listeners of a received packet.
* @param packet - the packet that has been receieved.
* @param sender - the client packet.
* @return The packet event that was used.
*/
PacketEvent packetReceived(PacketContainer packet, Player sender) {
PacketEvent event = PacketEvent.fromClient(this, packet, sender);
invoker.invokePacketRecieving(event);
return event;
}
/**
* Called when a player has logged in properly.
* @param player - the player that has logged in.
*/
void injectPlayer(Player player) {
try {
NetworkObjectInjector dummy = new NetworkObjectInjector(classLoader, reporter, player, invoker, null);
dummy.initializePlayer(player);
// Save this player for the network manager
NetworkObjectInjector realInjector = networkManagerInjector.get(dummy.getNetworkManager());
if (realInjector != null) {
// Update all future references
realInjector.setUpdatedPlayer(player);
playerInjector.put(player, realInjector);
} else {
// Ah - in that case, save this injector
saveInjector(dummy.getNetworkManager(), dummy);
}
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot inject " + player);
}
}
/**
* Uninject the given player.
* @param player - the player to uninject.
*/
void uninjectPlayer(Player player) {
final NetworkObjectInjector injector = getInjector(player);
if (player != null) {
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() {
@Override
public void run() {
// Clean up
playerInjector.remove(injector.getPlayer());
playerInjector.remove(injector.getUpdatedPlayer());
networkManagerInjector.remove(injector);
}
}, CLEANUP_DELAY);
}
}
/**
* Invoked when a plugin wants to sent a packet.
* @param reciever - the packet receiver.
* @param packet - the packet to transmit.
* @param filters - whether or not to invoke the packet listeners.
* @throws InvocationTargetException If anything went wrong.
*/
void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) throws InvocationTargetException {
NetworkObjectInjector networkObject = getInjector(reciever);
// If TRUE, process this packet like any other
if (filters)
ignoredPackets.remove(packet.getHandle());
else
ignoredPackets.add(packet.getHandle());
if (networkObject != null)
networkObject.sendServerPacket(packet.getHandle(), filters);
else
throw new PlayerLoggedOutException("Player " + reciever + " has logged out");
}
/**
* Invoked when a plugin wants to simulate receiving a packet.
* @param player - the supposed sender.
* @param mcPacket - the packet to receieve.
* @throws IllegalAccessException Reflection is not permitted.
* @throws InvocationTargetException Minecraft threw an exception.
*/
void processPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException {
NetworkObjectInjector networkObject = getInjector(player);
// We will always ignore this packet
ignoredPackets.add(mcPacket);
if (networkObject != null)
networkObject.processPacket(mcPacket);
else
throw new PlayerLoggedOutException("Player " + player + " has logged out");
}
}

Datei anzeigen

@ -0,0 +1,34 @@
package com.comphenix.protocol.injector.spigot;
/**
* Represents a proxy for a Spigot packet listener.
*
* @author Kristian
*/
interface SpigotPacketListener {
/**
* Called when a packet has been received and is about to be handled by the
* current Connection.
* <p>
* The returned packet will be the packet passed on for handling, or in the case of
* null being returned, not handled at all.
*
* @param networkManager the NetworkManager receiving the packet
* @param connection the connection which will handle the packet
* @param packet the received packet
* @return the packet to be handled, or null to cancel
*/
public Object packetReceived(Object networkManager, Object connection, Object packet);
/**
* Called when a packet is queued to be sent.The returned packet will be
* the packet sent. In the case of null being returned, the packet will not
* be sent.
*
* @param networkManager the NetworkManager which will send the packet
* @param connection the connection which queued the packet
* @param packet the queue packet
* @return the packet to be sent, or null if the packet will not be sent.
*/
public Object packetQueued(Object networkManager, Object connection, Object packet);
}

Datei anzeigen

@ -17,6 +17,7 @@
package com.comphenix.protocol.reflect; package com.comphenix.protocol.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
@ -26,6 +27,9 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import com.comphenix.protocol.reflect.fuzzy.AbstractFuzzyMatcher;
import com.google.common.collect.Lists;
/** /**
* Retrieves fields and methods by signature, not just name. * Retrieves fields and methods by signature, not just name.
* *
@ -89,6 +93,42 @@ public class FuzzyReflection {
return source; return source;
} }
/**
* Retrieve the first method that matches.
* <p>
* ForceAccess must be TRUE in order for this method to access private, protected and package level method.
* @param matcher - the matcher to use.
* @return The first method that satisfies the given matcher.
* @throws IllegalArgumentException If the method cannot be found.
*/
public Method getMethod(AbstractFuzzyMatcher<MethodInfo> matcher) {
List<Method> result = getMethodList(matcher);
if (result.size() > 0)
return result.get(0);
else
throw new IllegalArgumentException("Unable to find a method that matches " + matcher);
}
/**
* Retrieve a list of every method that matches the given matcher.
* <p>
* ForceAccess must be TRUE in order for this method to access private, protected and package level methods.
* @param matcher - the matcher to apply.
* @return List of found methods.
*/
public List<Method> getMethodList(AbstractFuzzyMatcher<MethodInfo> matcher) {
List<Method> methods = Lists.newArrayList();
// Add all matching fields to the list
for (Method method : getMethods()) {
if (matcher.isMatch(MethodInfo.fromMethod(method), source)) {
methods.add(method);
}
}
return methods;
}
/** /**
* Retrieves a method by looking at its name. * Retrieves a method by looking at its name.
* @param nameRegex - regular expression that will match method names. * @param nameRegex - regular expression that will match method names.
@ -96,7 +136,6 @@ public class FuzzyReflection {
* @throws IllegalArgumentException If the method cannot be found. * @throws IllegalArgumentException If the method cannot be found.
*/ */
public Method getMethodByName(String nameRegex) { public Method getMethodByName(String nameRegex) {
Pattern match = Pattern.compile(nameRegex); Pattern match = Pattern.compile(nameRegex);
for (Method method : getMethods()) { for (Method method : getMethods()) {
@ -118,7 +157,6 @@ public class FuzzyReflection {
* @throws IllegalArgumentException If the method cannot be found. * @throws IllegalArgumentException If the method cannot be found.
*/ */
public Method getMethodByParameters(String name, Class<?>... args) { public Method getMethodByParameters(String name, Class<?>... args) {
// Find the correct method to call // Find the correct method to call
for (Method method : getMethods()) { for (Method method : getMethods()) {
if (Arrays.equals(method.getParameterTypes(), args)) { if (Arrays.equals(method.getParameterTypes(), args)) {
@ -159,7 +197,6 @@ public class FuzzyReflection {
* @throws IllegalArgumentException If the method cannot be found. * @throws IllegalArgumentException If the method cannot be found.
*/ */
public Method getMethodByParameters(String name, String returnTypeRegex, String[] argsRegex) { public Method getMethodByParameters(String name, String returnTypeRegex, String[] argsRegex) {
Pattern match = Pattern.compile(returnTypeRegex); Pattern match = Pattern.compile(returnTypeRegex);
Pattern[] argMatch = new Pattern[argsRegex.length]; Pattern[] argMatch = new Pattern[argsRegex.length];
@ -199,7 +236,6 @@ public class FuzzyReflection {
* @return Every method that satisfies the given constraints. * @return Every method that satisfies the given constraints.
*/ */
public List<Method> getMethodListByParameters(Class<?> returnType, Class<?>[] args) { public List<Method> getMethodListByParameters(Class<?> returnType, Class<?>[] args) {
List<Method> methods = new ArrayList<Method>(); List<Method> methods = new ArrayList<Method>();
// Find the correct method to call // Find the correct method to call
@ -219,7 +255,6 @@ public class FuzzyReflection {
* @throws IllegalArgumentException If the field cannot be found. * @throws IllegalArgumentException If the field cannot be found.
*/ */
public Field getFieldByName(String nameRegex) { public Field getFieldByName(String nameRegex) {
Pattern match = Pattern.compile(nameRegex); Pattern match = Pattern.compile(nameRegex);
for (Field field : getFields()) { for (Field field : getFields()) {
@ -241,7 +276,6 @@ public class FuzzyReflection {
* @return The first field with a type that is an instance of the given type. * @return The first field with a type that is an instance of the given type.
*/ */
public Field getFieldByType(String name, Class<?> type) { public Field getFieldByType(String name, Class<?> type) {
List<Field> fields = getFieldListByType(type); List<Field> fields = getFieldListByType(type);
if (fields.size() > 0) { if (fields.size() > 0) {
@ -260,7 +294,6 @@ public class FuzzyReflection {
* @return Every field with a type that is an instance of the given type. * @return Every field with a type that is an instance of the given type.
*/ */
public List<Field> getFieldListByType(Class<?> type) { public List<Field> getFieldListByType(Class<?> type) {
List<Field> fields = new ArrayList<Field>(); List<Field> fields = new ArrayList<Field>();
// Field with a compatible type // Field with a compatible type
@ -274,6 +307,42 @@ public class FuzzyReflection {
return fields; return fields;
} }
/**
* Retrieve the first field that matches.
* <p>
* ForceAccess must be TRUE in order for this method to access private, protected and package level fields.
* @param matcher - the matcher to use.
* @return The first method that satisfies the given matcher.
* @throws IllegalArgumentException If the method cannot be found.
*/
public Field getField(AbstractFuzzyMatcher<Field> matcher) {
List<Field> result = getFieldList(matcher);
if (result.size() > 0)
return result.get(0);
else
throw new IllegalArgumentException("Unable to find a field that matches " + matcher);
}
/**
* Retrieve a list of every field that matches the given matcher.
* <p>
* ForceAccess must be TRUE in order for this method to access private, protected and package level fields.
* @param matcher - the matcher to apply.
* @return List of found fields.
*/
public List<Field> getFieldList(AbstractFuzzyMatcher<Field> matcher) {
List<Field> fields = Lists.newArrayList();
// Add all matching fields to the list
for (Field field : getFields()) {
if (matcher.isMatch(field, source)) {
fields.add(field);
}
}
return fields;
}
/** /**
* Retrieves a field by type. * Retrieves a field by type.
* <p> * <p>
@ -336,8 +405,46 @@ public class FuzzyReflection {
typeRegex + " in " + source.getName()); typeRegex + " in " + source.getName());
} }
/**
* Retrieve the first constructor that matches.
* <p>
* ForceAccess must be TRUE in order for this method to access private, protected and package level constructors.
* @param matcher - the matcher to use.
* @return The first constructor that satisfies the given matcher.
* @throws IllegalArgumentException If the constructor cannot be found.
*/
public Constructor<?> getConstructor(AbstractFuzzyMatcher<MethodInfo> matcher) {
List<Constructor<?>> result = getConstructorList(matcher);
if (result.size() > 0)
return result.get(0);
else
throw new IllegalArgumentException("Unable to find a method that matches " + matcher);
}
/**
* Retrieve a list of every constructor that matches the given matcher.
* <p>
* ForceAccess must be TRUE in order for this method to access private, protected and package level constructors.
* @param matcher - the matcher to apply.
* @return List of found constructors.
*/
public List<Constructor<?>> getConstructorList(AbstractFuzzyMatcher<MethodInfo> matcher) {
List<Constructor<?>> constructors = Lists.newArrayList();
// Add all matching fields to the list
for (Constructor<?> constructor : getConstructors()) {
if (matcher.isMatch(MethodInfo.fromConstructor(constructor), source)) {
constructors.add(constructor);
}
}
return constructors;
}
/** /**
* Retrieves all private and public fields in declared order (after JDK 1.5). * Retrieves all private and public fields in declared order (after JDK 1.5).
* <p>
* Private, protected and package fields are ignored if forceAccess is FALSE.
* @return Every field. * @return Every field.
*/ */
public Set<Field> getFields() { public Set<Field> getFields() {
@ -350,6 +457,8 @@ public class FuzzyReflection {
/** /**
* Retrieves all private and public methods in declared order (after JDK 1.5). * Retrieves all private and public methods in declared order (after JDK 1.5).
* <p>
* Private, protected and package methods are ignored if forceAccess is FALSE.
* @return Every method. * @return Every method.
*/ */
public Set<Method> getMethods() { public Set<Method> getMethods() {
@ -360,6 +469,19 @@ public class FuzzyReflection {
return setUnion(source.getMethods()); return setUnion(source.getMethods());
} }
/**
* Retrieves all private and public constructors in declared order (after JDK 1.5).
* <p>
* Private, protected and package constructors are ignored if forceAccess is FALSE.
* @return Every constructor.
*/
public Set<Constructor<?>> getConstructors() {
if (forceAccess)
return setUnion(source.getDeclaredConstructors());
else
return setUnion(source.getConstructors());
}
// Prevent duplicate fields // Prevent duplicate fields
private static <T> Set<T> setUnion(T[]... array) { private static <T> Set<T> setUnion(T[]... array) {
Set<T> result = new LinkedHashSet<T>(); Set<T> result = new LinkedHashSet<T>();

Datei anzeigen

@ -0,0 +1,230 @@
package com.comphenix.protocol.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.GenericDeclaration;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.TypeVariable;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.apache.commons.lang.NotImplementedException;
import com.google.common.collect.Lists;
/**
* Represents a method or a constructor.
*
* @author Kristian
*/
public abstract class MethodInfo implements GenericDeclaration, Member {
/**
* Wraps a method as a MethodInfo object.
* @param method - the method to wrap.
* @return The wrapped method.
*/
public static MethodInfo fromMethod(final Method method) {
return new MethodInfo() {
@Override
public String getName() {
return method.getName();
}
@Override
public Class<?>[] getParameterTypes() {
return method.getParameterTypes();
}
@Override
public Class<?> getDeclaringClass() {
return method.getDeclaringClass();
}
@Override
public Class<?> getReturnType() {
return method.getReturnType();
}
@Override
public int getModifiers() {
return method.getModifiers();
}
@Override
public Class<?>[] getExceptionTypes() {
return method.getExceptionTypes();
}
@Override
public TypeVariable<?>[] getTypeParameters() {
return method.getTypeParameters();
}
@Override
public String toGenericString() {
return method.toGenericString();
}
@Override
public String toString() {
return method.toString();
}
@Override
public boolean isSynthetic() {
return method.isSynthetic();
}
@Override
public int hashCode() {
return method.hashCode();
}
@Override
public boolean isConstructor() {
return false;
}
};
}
/**
* Construct a list of method infos from a given array of methods.
* @param methods - array of methods.
* @return Method info list.
*/
public static Collection<MethodInfo> fromMethods(Method[] methods) {
return fromMethods(Arrays.asList(methods));
}
/**
* Construct a list of method infos from a given collection of methods.
* @param methods - list of methods.
* @return Method info list.
*/
public static List<MethodInfo> fromMethods(Collection<Method> methods) {
List<MethodInfo> infos = Lists.newArrayList();
for (Method method : methods)
infos.add(fromMethod(method));
return infos;
}
/**
* Wraps a constructor as a method information object.
* @param constructor - the constructor to wrap.
* @return A wrapped constructor.
*/
public static MethodInfo fromConstructor(final Constructor<?> constructor) {
return new MethodInfo() {
@Override
public String getName() {
return constructor.getName();
}
@Override
public Class<?>[] getParameterTypes() {
return constructor.getParameterTypes();
}
@Override
public Class<?> getDeclaringClass() {
return constructor.getDeclaringClass();
}
@Override
public Class<?> getReturnType() {
return Void.class;
}
@Override
public int getModifiers() {
return constructor.getModifiers();
}
@Override
public Class<?>[] getExceptionTypes() {
return constructor.getExceptionTypes();
}
@Override
public TypeVariable<?>[] getTypeParameters() {
return constructor.getTypeParameters();
}
@Override
public String toGenericString() {
return constructor.toGenericString();
}
@Override
public String toString() {
return constructor.toString();
}
@Override
public boolean isSynthetic() {
return constructor.isSynthetic();
}
@Override
public int hashCode() {
return constructor.hashCode();
}
@Override
public boolean isConstructor() {
return true;
}
};
}
/**
* Construct a list of method infos from a given array of constructors.
* @param constructors - array of constructors.
* @return Method info list.
*/
public static Collection<MethodInfo> fromConstructors(Constructor<?>[] constructors) {
return fromConstructors(Arrays.asList(constructors));
}
/**
* Construct a list of method infos from a given collection of constructors.
* @param constructors - list of constructors.
* @return Method info list.
*/
public static List<MethodInfo> fromConstructors(Collection<Constructor<?>> constructors) {
List<MethodInfo> infos = Lists.newArrayList();
for (Constructor<?> constructor : constructors)
infos.add(fromConstructor(constructor));
return infos;
}
/**
* Returns a string describing this method or constructor
* @return A string representation of the object.
* @see {@link Method#toString()} or {@link Constructor#toString()}
*/
@Override
public String toString() {
throw new NotImplementedException();
}
/**
* Returns a string describing this method or constructor, including type parameters.
* @return A string describing this Method, include type parameters
* @see {@link Method#toGenericString()} or {@link Constructor#toGenericString()}
*/
public abstract String toGenericString();
/**
* Returns an array of Class objects that represent the types of the exceptions declared to be thrown by the
* underlying method or constructor represented by this MethodInfo object.
* @return The exception types declared as being thrown by the method or constructor this object represents.
* @see {@link Method#getExceptionTypes()} or {@link Constructor#getExceptionTypes()}
*/
public abstract Class<?>[] getExceptionTypes();
/**
* Returns a Class object that represents the formal return type of the method or constructor
* represented by this MethodInfo object.
* <p>
* This is always {@link Void} for constructors.
* @return The return value, or Void if a constructor.
* @see {@link Method#getReturnType()}
*/
public abstract Class<?> getReturnType();
/**
* Returns an array of Class objects that represent the formal parameter types, in declaration order,
* of the method or constructor represented by this MethodInfo object.
* @return The parameter types for the method or constructor this object represents.
* @see {@link Method#getParameterTypes()} or {@link Constructor#getParameterTypes()}
*/
public abstract Class<?>[] getParameterTypes();
/**
* Determine if this is a constructor or not.
* @return TRUE if this represents a constructor, FALSE otherwise.
*/
public abstract boolean isConstructor();
}

Datei anzeigen

@ -40,7 +40,6 @@ public class PrettyPrinter {
/** /**
* Print the content of an object. * Print the content of an object.
* @param object - the object to serialize. * @param object - the object to serialize.
* @param stop - superclass that will stop the process.
* @return String representation of the class. * @return String representation of the class.
* @throws IllegalAccessException * @throws IllegalAccessException
*/ */

Datei anzeigen

@ -26,9 +26,14 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler; import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
import com.comphenix.protocol.reflect.instances.BannedGenerator;
import com.comphenix.protocol.reflect.instances.DefaultInstances; import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.reflect.instances.InstanceProvider;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
/** /**
* Provides list-oriented access to the fields of a Minecraft packet. * Provides list-oriented access to the fields of a Minecraft packet.
@ -64,6 +69,18 @@ public class StructureModifier<TField> {
// Whether or not to automatically compile the structure modifier // Whether or not to automatically compile the structure modifier
protected boolean useStructureCompiler; protected boolean useStructureCompiler;
// Instance generator we wil use
private static DefaultInstances DEFAULT_GENERATOR = getDefaultGenerator();
private static DefaultInstances getDefaultGenerator() {
List<InstanceProvider> providers = Lists.newArrayList();
// Prevent certain classes from being generated
providers.add(new BannedGenerator(MinecraftReflection.getItemStackClass(), MinecraftReflection.getBlockClass()));
providers.addAll(DefaultInstances.DEFAULT.getRegistered());
return DefaultInstances.fromCollection(providers);
}
/** /**
* Creates a structure modifier. * Creates a structure modifier.
* @param targetType - the structure to modify. * @param targetType - the structure to modify.
@ -394,23 +411,13 @@ public class StructureModifier<TField> {
result = result.withTarget(target); result = result.withTarget(target);
// And the converter, if it's needed // And the converter, if it's needed
if (!sameConverter(result.converter, converter)) { if (!Objects.equal(result.converter, converter)) {
result = result.withConverter(converter); result = result.withConverter(converter);
} }
return result; return result;
} }
private boolean sameConverter(EquivalentConverter<?> a, EquivalentConverter<?> b) {
// Compare the converter types
if (a == null)
return b == null;
else if (b == null)
return false;
else
return a.getSpecificType().equals(b.getSpecificType());
}
/** /**
* Retrieves the common type of each field. * Retrieves the common type of each field.
* @return Common type of each field. * @return Common type of each field.
@ -537,7 +544,7 @@ public class StructureModifier<TField> {
private static Map<Field, Integer> generateDefaultFields(List<Field> fields) { private static Map<Field, Integer> generateDefaultFields(List<Field> fields) {
Map<Field, Integer> requireDefaults = new HashMap<Field, Integer>(); Map<Field, Integer> requireDefaults = new HashMap<Field, Integer>();
DefaultInstances generator = DefaultInstances.DEFAULT; DefaultInstances generator = DEFAULT_GENERATOR;
int index = 0; int index = 0;
for (Field field : fields) { for (Field field : fields) {

Datei anzeigen

@ -20,6 +20,7 @@ package com.comphenix.protocol.reflect.cloning;
import com.comphenix.protocol.reflect.ObjectWriter; import com.comphenix.protocol.reflect.ObjectWriter;
import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.instances.InstanceProvider; import com.comphenix.protocol.reflect.instances.InstanceProvider;
import com.comphenix.protocol.reflect.instances.NotConstructableException;
/** /**
* Represents a class capable of cloning objects by deeply copying its fields. * Represents a class capable of cloning objects by deeply copying its fields.
@ -72,7 +73,11 @@ public class FieldCloner implements Cloner {
return false; return false;
// Attempt to create the type // Attempt to create the type
return instanceProvider.create(source.getClass()) != null; try {
return instanceProvider.create(source.getClass()) != null;
} catch (NotConstructableException e) {
return false;
}
} }
@Override @Override

Datei anzeigen

@ -0,0 +1,135 @@
package com.comphenix.protocol.reflect.fuzzy;
import com.google.common.primitives.Ints;
/**
* Represents a matcher for fields, methods, constructors and classes.
* <p>
* This class should ideally never expose mutable state. Its round number must be immutable.
* @author Kristian
*/
public abstract class AbstractFuzzyMatcher<T> implements Comparable<AbstractFuzzyMatcher<T>> {
private Integer roundNumber;
/**
* Determine if the given value is a match.
* @param value - the value to match.
* @param parent - the parent container, or NULL if this value is the root.
* @return TRUE if it is a match, FALSE otherwise.
*/
public abstract boolean isMatch(T value, Object parent);
/**
* Calculate the round number indicating when this matcher should be applied.
* <p>
* Matchers with a lower round number are applied before matchers with a higher round number.
* <p>
* By convention, this round number should be negative, except for zero in the case of a matcher
* that accepts any value. A good implementation should return the inverted tree depth (class hierachy)
* of the least specified type used in the matching. Thus {@link Integer} will have a lower round number than
* {@link Number}.
*
* @return A number (positive or negative) that is used to order matchers.
*/
protected abstract int calculateRoundNumber();
/**
* Retrieve the cached round number. This should never change once calculated.
* <p>
* Matchers with a lower round number are applied before matchers with a higher round number.
* @return The round number.
* @see {@link #calculateRoundNumber()}
*/
public final int getRoundNumber() {
if (roundNumber == null) {
return roundNumber = calculateRoundNumber();
} else {
return roundNumber;
}
}
/**
* Combine two round numbers by taking the highest non-zero number, or return zero.
* @param roundA - the first round number.
* @param roundB - the second round number.
* @return The combined round number.
*/
protected final int combineRounds(int roundA, int roundB) {
if (roundA == 0)
return roundB;
else if (roundB == 0)
return roundA;
else
return Math.max(roundA, roundB);
}
@Override
public int compareTo(AbstractFuzzyMatcher<T> obj) {
if (obj instanceof AbstractFuzzyMatcher) {
AbstractFuzzyMatcher<?> matcher = (AbstractFuzzyMatcher<?>) obj;
return Ints.compare(getRoundNumber(), matcher.getRoundNumber());
}
// No match
return -1;
}
/**
* Create a fuzzy matcher that returns the opposite result of the current matcher.
* @return An inverted fuzzy matcher.
*/
public AbstractFuzzyMatcher<T> inverted() {
return new AbstractFuzzyMatcher<T>() {
@Override
public boolean isMatch(T value, Object parent) {
return !AbstractFuzzyMatcher.this.isMatch(value, parent);
}
@Override
protected int calculateRoundNumber() {
return -2;
}
};
}
/**
* Require that this and the given matcher be TRUE.
* @param other - the other fuzzy matcher.
* @return A combined fuzzy matcher.
*/
public AbstractFuzzyMatcher<T> and(final AbstractFuzzyMatcher<T> other) {
return new AbstractFuzzyMatcher<T>() {
@Override
public boolean isMatch(T value, Object parent) {
// They both have to be true
return AbstractFuzzyMatcher.this.isMatch(value, parent) &&
other.isMatch(value, parent);
}
@Override
protected int calculateRoundNumber() {
return combineRounds(AbstractFuzzyMatcher.this.getRoundNumber(), other.getRoundNumber());
}
};
}
/**
* Require that either this or the other given matcher be TRUE.
* @param other - the other fuzzy matcher.
* @return A combined fuzzy matcher.
*/
public AbstractFuzzyMatcher<T> or(final AbstractFuzzyMatcher<T> other) {
return new AbstractFuzzyMatcher<T>() {
@Override
public boolean isMatch(T value, Object parent) {
// Either can be true
return AbstractFuzzyMatcher.this.isMatch(value, parent) ||
other.isMatch(value, parent);
}
@Override
protected int calculateRoundNumber() {
return combineRounds(AbstractFuzzyMatcher.this.getRoundNumber(), other.getRoundNumber());
}
};
}
}

Datei anzeigen

@ -0,0 +1,295 @@
package com.comphenix.protocol.reflect.fuzzy;
import java.lang.reflect.Member;
import java.util.Map;
import java.util.regex.Pattern;
import javax.annotation.Nonnull;
import com.google.common.base.Objects;
import com.google.common.collect.Maps;
/**
* Represents a matcher that matches members.
*
* @author Kristian
* @param <T> - type that it matches.
*/
public abstract class AbstractFuzzyMember<T extends Member> extends AbstractFuzzyMatcher<T> {
// Accessibility matchers
protected int modifiersRequired;
protected int modifiersBanned;
protected Pattern nameRegex;
protected AbstractFuzzyMatcher<Class<?>> declaringMatcher = ClassExactMatcher.MATCH_ALL;
/**
* Whether or not this contract can be modified.
*/
protected transient boolean sealed;
/**
* Represents a builder of a fuzzy member contract.
*
* @author Kristian
*/
public static abstract class Builder<T extends AbstractFuzzyMember<?>> {
protected T member = initialMember();
/**
* Add a given bit-field of required modifiers for every matching member.
* @param modifier - bit-field of modifiers that are required.
* @return This builder, for chaining.
*/
public Builder<T> requireModifier(int modifier) {
member.modifiersRequired |= modifier;
return this;
}
/**
* Add a given bit-field of modifers that will skip or ignore members.
* @param modifier - bit-field of modifiers to skip or ignore.
* @return This builder, for chaining.
*/
public Builder<T> banModifier(int modifier) {
member.modifiersBanned |= modifier;
return this;
}
/**
* Set the regular expresson that matches a members name.
* @param regex - new regular expression of valid names.
* @return This builder, for chaining.
*/
public Builder<T> nameRegex(String regex) {
member.nameRegex = Pattern.compile(regex);
return this;
}
/**
* Set the regular expression pattern that matches a members name.
* @param pattern - regular expression pattern for a valid name.
* @return This builder, for chaining.
*/
public Builder<T> nameRegex(Pattern pattern) {
member.nameRegex = pattern;
return this;
}
/**
* Set the exact name of the member we are matching.
* <p<
* This will overwrite the regular expression rule.
* @param name - exact name.
* @return This builder, for chaining.
*/
public Builder<T> nameExact(String name) {
return nameRegex(Pattern.quote(name));
}
/**
* Require that a member is defined by this exact class.
* @param declaringClass - the declaring class of any matching member.
* @return This builder, for chaining.
*/
public Builder<T> declaringClassExactType(Class<?> declaringClass) {
member.declaringMatcher = FuzzyMatchers.matchExact(declaringClass);
return this;
}
/**
* Require that a member is defined by this exact class, or any super class.
* @param declaringClass - the declaring class.
* @return This builder, for chaining.
*/
public Builder<T> declaringClassSuperOf(Class<?> declaringClass) {
member.declaringMatcher = FuzzyMatchers.matchSuper(declaringClass);
return this;
}
/**
* Require that a member is defined by this exact class, or any super class.
* @param declaringClass - the declaring class.
* @return This builder, for chaining.
*/
public Builder<T> declaringClassDerivedOf(Class<?> declaringClass) {
member.declaringMatcher = FuzzyMatchers.matchDerived(declaringClass);
return this;
}
/**
* Require that a member is defined by a class that matches the given matcher.
* @param classMatcher - class matcher.
* @return This builder, for chaining.
*/
public Builder<T> declaringClassMatching(AbstractFuzzyMatcher<Class<?>> classMatcher) {
member.declaringMatcher = classMatcher;
return this;
}
/**
* Construct a new instance of the current type.
* @return New instance.
*/
@Nonnull
protected abstract T initialMember();
/**
* Build a new instance of this type.
* <p>
* Builders should call {@link AbstractFuzzyMember#prepareBuild()} when constructing new objects.
* @return New instance of this type.
*/
public abstract T build();
}
protected AbstractFuzzyMember() {
// Only allow construction through the builder
}
/**
* Called before a builder is building a member and copying its state.
* <p>
* Use this to prepare any special values.
*/
protected void prepareBuild() {
// No need to prepare anything
}
// Clone a given contract
protected AbstractFuzzyMember(AbstractFuzzyMember<T> other) {
this.modifiersRequired = other.modifiersRequired;
this.modifiersBanned = other.modifiersBanned;
this.nameRegex = other.nameRegex;
this.declaringMatcher = other.declaringMatcher;
this.sealed = true;
}
/**
* Retrieve a bit field of every {@link java.lang.reflect.Modifier Modifier} that is required for the member to match.
* @return A required modifier bit field.
*/
public int getModifiersRequired() {
return modifiersRequired;
}
/**
* Retrieve a bit field of every {@link java.lang.reflect.Modifier Modifier} that must not be present for the member to match.
* @return A banned modifier bit field.
*/
public int getModifiersBanned() {
return modifiersBanned;
}
/**
* Retrieve the regular expression pattern that is used to match the name of a member.
* @return The regex matching a name, or NULL if everything matches.
*/
public Pattern getNameRegex() {
return nameRegex;
}
/**
* Retrieve a class matcher for the declaring class of the member.
* @return An object matching the declaring class.
*/
public AbstractFuzzyMatcher<Class<?>> getDeclaringMatcher() {
return declaringMatcher;
}
@Override
public boolean isMatch(T value, Object parent) {
int mods = value.getModifiers();
// Match accessibility and name
return (mods & modifiersRequired) == modifiersRequired &&
(mods & modifiersBanned) == 0 &&
declaringMatcher.isMatch(value.getDeclaringClass(), value) &&
isNameMatch(value.getName());
}
/**
* Determine if a given name matches the current member matcher.
* @param name - the name to match.
* @return TRUE if the name matches, FALSE otherwise.
*/
private boolean isNameMatch(String name) {
if (nameRegex == null)
return true;
else
return nameRegex.matcher(name).matches();
}
@Override
protected int calculateRoundNumber() {
// Sanity check
if (!sealed)
throw new IllegalStateException("Cannot calculate round number during construction.");
// NULL is zero
return declaringMatcher.getRoundNumber();
}
@Override
public String toString() {
return getKeyValueView().toString();
}
/**
* Generate a view of this matcher as a key-value map.
* <p>
* Used by {@link #toString()} to print a representation of this object.
* @return A modifiable key-value view.
*/
protected Map<String, Object> getKeyValueView() {
Map<String, Object> map = Maps.newLinkedHashMap();
// Build our representation
if (modifiersRequired != Integer.MAX_VALUE || modifiersBanned != 0) {
map.put("modifiers", String.format("[required: %s, banned: %s]",
getBitView(modifiersRequired, 16),
getBitView(modifiersBanned, 16))
);
}
if (nameRegex != null) {
map.put("name", nameRegex.pattern());
}
if (declaringMatcher != ClassExactMatcher.MATCH_ALL) {
map.put("declaring", declaringMatcher);
}
return map;
}
private static String getBitView(int value, int bits) {
if (bits < 0 || bits > 31)
throw new IllegalArgumentException("Bits must be a value between 0 and 32");
// Extract our needed bits
int snipped = value & ((1 << bits) - 1);
return Integer.toBinaryString(snipped);
}
@Override
public boolean equals(Object obj) {
// Immutablity is awesome
if (this == obj) {
return true;
} else if (obj instanceof AbstractFuzzyMember) {
@SuppressWarnings("unchecked")
AbstractFuzzyMember<T> other = (AbstractFuzzyMember<T>) obj;
return modifiersBanned == other.modifiersBanned &&
modifiersRequired == other.modifiersRequired &&
FuzzyMatchers.checkPattern(nameRegex, other.nameRegex) &&
Objects.equal(declaringMatcher, other.declaringMatcher);
}
return false;
}
@Override
public int hashCode() {
return Objects.hashCode(modifiersBanned, modifiersRequired,
nameRegex != null ? nameRegex.pattern() : null, declaringMatcher);
}
}

Datei anzeigen

@ -0,0 +1,139 @@
package com.comphenix.protocol.reflect.fuzzy;
import com.google.common.base.Objects;
/**
* Used to check class equality.
*
* @author Kristian
*/
class ClassExactMatcher extends AbstractFuzzyMatcher<Class<?>> {
/**
* Different matching rules.
*/
enum Options {
/**
* Match classes exactly.
*/
MATCH_EXACT,
/**
* A match if the input class is a superclass of the matcher class, or the same class.
*/
MATCH_SUPER,
/**
* A match if the input class is a derived class of the matcher class, or the same class.
*/
MATCH_DERIVED
}
/**
* Match any class.
*/
public static final ClassExactMatcher MATCH_ALL = new ClassExactMatcher(null, Options.MATCH_SUPER);
private final Class<?> matcher;
private final Options option;
/**
* Constructs a new class matcher.
* @param matcher - the matching class, or NULL to represent anything.
* @param option - options specifying the matching rules.
*/
ClassExactMatcher(Class<?> matcher, Options option) {
this.matcher = matcher;
this.option = option;
}
/**
* Determine if a given class is equivalent.
* <p>
* If the matcher is NULL, the result will only be TRUE if we're not matching exactly.
* @param input - the input class defined in the source file.
* @param parent - the container that holds a reference to this class.
* @return TRUE if input matches according to the rules in {@link #getOptions()}, FALSE otherwise.
*/
@Override
public boolean isMatch(Class<?> input, Object parent) {
if (input == null)
throw new IllegalArgumentException("Input class cannot be NULL.");
// Do our checking
if (matcher == null)
return option != Options.MATCH_EXACT;
else if (option == Options.MATCH_SUPER)
return input.isAssignableFrom(matcher); // matcher instanceof input
else if (option == Options.MATCH_DERIVED)
return matcher.isAssignableFrom(input); // input instanceof matcher
else
return input.equals(matcher);
}
@Override
protected int calculateRoundNumber() {
return -getClassNumber(matcher);
}
/**
* Retrieve the number of superclasses of the specific class.
* <p>
* Object is represented as one. All interfaces are one, unless they're derived.
* @param clazz - the class to test.
* @return The number of superclasses.
*/
public static int getClassNumber(Class<?> clazz) {
int count = 0;
// Move up the hierachy
while (clazz != null) {
count++;
clazz = clazz.getSuperclass();
}
return count;
}
/**
* Retrieve the class we're comparing against.
* @return Class to compare against.
*/
public Class<?> getMatcher() {
return matcher;
}
/**
* The matching rules for this class matcher.
* @return The current matching option.
*/
public Options getOptions() {
return option;
}
@Override
public String toString() {
if (option == Options.MATCH_SUPER)
return matcher + " instanceof input";
else if (option == Options.MATCH_DERIVED)
return "input instanceof " + matcher;
else
return "Exact " + matcher;
}
@Override
public int hashCode() {
return Objects.hashCode(matcher, option);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
} else if (obj instanceof ClassExactMatcher) {
ClassExactMatcher other = (ClassExactMatcher) obj;
return Objects.equal(matcher, other.matcher) &&
Objects.equal(option, other.option);
}
return false;
}
}

Datei anzeigen

@ -0,0 +1,58 @@
package com.comphenix.protocol.reflect.fuzzy;
import java.util.regex.Pattern;
import com.google.common.base.Objects;
/**
* Determine if a class matches based on its name using a regular expression.
*
* @author Kristian
*/
class ClassRegexMatcher extends AbstractFuzzyMatcher<Class<?>> {
private final Pattern regex;
private final int priority;
public ClassRegexMatcher(Pattern regex, int priority) {
if (regex == null)
throw new IllegalArgumentException("Regular expression pattern cannot be NULL.");
this.regex = regex;
this.priority = priority;
}
@Override
public boolean isMatch(Class<?> value, Object parent) {
if (value != null)
return regex.matcher(value.getCanonicalName()).matches();
else
return false;
}
@Override
protected int calculateRoundNumber() {
return -priority;
}
@Override
public String toString() {
return "class name of " + regex.toString();
}
@Override
public int hashCode() {
return Objects.hashCode(regex, priority);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
} else if (obj instanceof ClassRegexMatcher) {
ClassRegexMatcher other = (ClassRegexMatcher) obj;
return priority == other.priority &&
FuzzyMatchers.checkPattern(regex, other.regex);
}
return false;
}
}

Datei anzeigen

@ -0,0 +1,57 @@
package com.comphenix.protocol.reflect.fuzzy;
import java.util.Set;
import com.google.common.base.Objects;
/**
* Represents a class matcher that checks for equality using a given set of classes.
*
* @author Kristian
*/
class ClassSetMatcher extends AbstractFuzzyMatcher<Class<?>> {
private final Set<Class<?>> classes;
public ClassSetMatcher(Set<Class<?>> classes) {
if (classes == null)
throw new IllegalArgumentException("Set of classes cannot be NULL.");
this.classes = classes;
}
@Override
public boolean isMatch(Class<?> value, Object parent) {
return classes.contains(value);
}
@Override
protected int calculateRoundNumber() {
int roundNumber = 0;
// The highest round number (except zero).
for (Class<?> clazz : classes) {
roundNumber = combineRounds(roundNumber, -ClassExactMatcher.getClassNumber(clazz));
}
return roundNumber;
}
@Override
public String toString() {
return "match any: " + classes;
}
@Override
public int hashCode() {
return classes.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
} else if (obj instanceof ClassSetMatcher) {
// See if the sets are equal
return Objects.equal(classes, ((ClassSetMatcher) obj).classes);
}
return true;
}
}

Datei anzeigen

@ -0,0 +1,240 @@
package com.comphenix.protocol.reflect.fuzzy;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.MethodInfo;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
/**
* Determine if a given class implements a given fuzzy (duck typed) contract.
*
* @author Kristian
*/
public class FuzzyClassContract extends AbstractFuzzyMatcher<Class<?>> {
private final ImmutableList<AbstractFuzzyMatcher<Field>> fieldContracts;
private final ImmutableList<AbstractFuzzyMatcher<MethodInfo>> methodContracts;
private final ImmutableList<AbstractFuzzyMatcher<MethodInfo>> constructorContracts;
/**
* Represents a class contract builder.
* @author Kristian
*
*/
public static class Builder {
private List<AbstractFuzzyMatcher<Field>> fieldContracts = Lists.newArrayList();
private List<AbstractFuzzyMatcher<MethodInfo>> methodContracts = Lists.newArrayList();
private List<AbstractFuzzyMatcher<MethodInfo>> constructorContracts = Lists.newArrayList();
/**
* Add a new field contract.
* @param matcher - new field contract.
* @return This builder, for chaining.
*/
public Builder field(AbstractFuzzyMatcher<Field> matcher) {
fieldContracts.add(matcher);
return this;
}
/**
* Add a new field contract via a builder.
* @param builder - builder for the new field contract.
* @return This builder, for chaining.
*/
public Builder field(FuzzyFieldContract.Builder builder) {
return field(builder.build());
}
/**
* Add a new method contract.
* @param matcher - new method contract.
* @return This builder, for chaining.
*/
public Builder method(AbstractFuzzyMatcher<MethodInfo> matcher) {
methodContracts.add(matcher);
return this;
}
/**
* Add a new method contract via a builder.
* @param builder - builder for the new method contract.
* @return This builder, for chaining.
*/
public Builder method(FuzzyMethodContract.Builder builder) {
return method(builder.build());
}
/**
* Add a new constructor contract.
* @param matcher - new constructor contract.
* @return This builder, for chaining.
*/
public Builder constructor(AbstractFuzzyMatcher<MethodInfo> matcher) {
constructorContracts.add(matcher);
return this;
}
/**
* Add a new constructor contract via a builder.
* @param builder - builder for the new constructor contract.
* @return This builder, for chaining.
*/
public Builder constructor(FuzzyMethodContract.Builder builder) {
return constructor(builder.build());
}
public FuzzyClassContract build() {
Collections.sort(fieldContracts);
Collections.sort(methodContracts);
Collections.sort(constructorContracts);
// Construct a new class matcher
return new FuzzyClassContract(
ImmutableList.copyOf(fieldContracts),
ImmutableList.copyOf(methodContracts),
ImmutableList.copyOf(constructorContracts)
);
}
}
/**
* Construct a new fuzzy class contract builder.
* @return A new builder.
*/
public static Builder newBuilder() {
return new Builder();
}
/**
* Constructs a new fuzzy class contract with the given contracts.
* @param fieldContracts - field contracts.
* @param methodContracts - method contracts.
* @param constructorContracts - constructor contracts.
*/
private FuzzyClassContract(ImmutableList<AbstractFuzzyMatcher<Field>> fieldContracts,
ImmutableList<AbstractFuzzyMatcher<MethodInfo>> methodContracts,
ImmutableList<AbstractFuzzyMatcher<MethodInfo>> constructorContracts) {
super();
this.fieldContracts = fieldContracts;
this.methodContracts = methodContracts;
this.constructorContracts = constructorContracts;
}
/**
* Retrieve an immutable list of every field contract.
* <p>
* This list is ordered in descending order of priority.
* @return List of every field contract.
*/
public ImmutableList<AbstractFuzzyMatcher<Field>> getFieldContracts() {
return fieldContracts;
}
/**
* Retrieve an immutable list of every method contract.
* <p>
* This list is ordered in descending order of priority.
* @return List of every method contract.
*/
public ImmutableList<AbstractFuzzyMatcher<MethodInfo>> getMethodContracts() {
return methodContracts;
}
/**
* Retrieve an immutable list of every constructor contract.
* <p>
* This list is ordered in descending order of priority.
* @return List of every constructor contract.
*/
public ImmutableList<AbstractFuzzyMatcher<MethodInfo>> getConstructorContracts() {
return constructorContracts;
}
@Override
protected int calculateRoundNumber() {
// Find the highest round number
return combineRounds(findHighestRound(fieldContracts),
combineRounds(findHighestRound(methodContracts),
findHighestRound(constructorContracts)));
}
private <T> int findHighestRound(Collection<AbstractFuzzyMatcher<T>> list) {
int highest = 0;
// Go through all the elements
for (AbstractFuzzyMatcher<T> matcher : list)
highest = combineRounds(highest, matcher.getRoundNumber());
return highest;
}
@Override
public boolean isMatch(Class<?> value, Object parent) {
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);
}
private <T> boolean processContracts(Collection<T> values, Class<?> parent, List<AbstractFuzzyMatcher<T>> matchers) {
boolean[] accepted = new boolean[matchers.size()];
int count = accepted.length;
// Process every value in turn
for (T value : values) {
int index = processValue(value, parent, accepted, matchers);
// See if this worked
if (index >= 0) {
accepted[index] = true;
count--;
}
// Break early
if (count == 0)
return true;
}
return count == 0;
}
private <T> int processValue(T value, Class<?> parent, boolean accepted[], List<AbstractFuzzyMatcher<T>> matchers) {
// The order matters
for (int i = 0; i < matchers.size(); i++) {
if (!accepted[i]) {
AbstractFuzzyMatcher<T> matcher = matchers.get(i);
// Mark this as detected
if (matcher.isMatch(value, parent)) {
return i;
}
}
}
// Failure
return -1;
}
@Override
public String toString() {
Map<String, Object> params = Maps.newLinkedHashMap();
if (fieldContracts.size() > 0) {
params.put("fields", fieldContracts);
}
if (methodContracts.size() > 0) {
params.put("methods", methodContracts);
}
if (constructorContracts.size() > 0) {
params.put("constructors", constructorContracts);
}
return "{\n " + Joiner.on(", \n ").join(params.entrySet()) + "\n}";
}
}

Datei anzeigen

@ -0,0 +1,182 @@
package com.comphenix.protocol.reflect.fuzzy;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.regex.Pattern;
import javax.annotation.Nonnull;
import com.google.common.base.Objects;
/**
* Represents a field matcher.
*
* @author Kristian
*/
public class FuzzyFieldContract extends AbstractFuzzyMember<Field> {
private AbstractFuzzyMatcher<Class<?>> typeMatcher = ClassExactMatcher.MATCH_ALL;
/**
* Represents a builder for a field matcher.
*
* @author Kristian
*/
public static class Builder extends AbstractFuzzyMember.Builder<FuzzyFieldContract> {
@Override
public Builder requireModifier(int modifier) {
super.requireModifier(modifier);
return this;
}
@Override
public Builder banModifier(int modifier) {
super.banModifier(modifier);
return this;
}
@Override
public Builder nameRegex(String regex) {
super.nameRegex(regex);
return this;
}
@Override
public Builder nameRegex(Pattern pattern) {
super.nameRegex(pattern);
return this;
}
@Override
public Builder nameExact(String name) {
super.nameExact(name);
return this;
}
public Builder declaringClassExactType(Class<?> declaringClass) {
super.declaringClassExactType(declaringClass);
return this;
}
@Override
public Builder declaringClassSuperOf(Class<?> declaringClass) {
super.declaringClassSuperOf(declaringClass);
return this;
}
@Override
public Builder declaringClassDerivedOf(Class<?> declaringClass) {
super.declaringClassDerivedOf(declaringClass);
return this;
}
@Override
public Builder declaringClassMatching(AbstractFuzzyMatcher<Class<?>> classMatcher) {
super.declaringClassMatching(classMatcher);
return this;
}
@Override
@Nonnull
protected FuzzyFieldContract initialMember() {
return new FuzzyFieldContract();
}
public Builder typeExact(Class<?> type) {
member.typeMatcher = FuzzyMatchers.matchExact(type);
return this;
}
public Builder typeSuperOf(Class<?> type) {
member.typeMatcher = FuzzyMatchers.matchSuper(type);
return this;
}
public Builder typeDerivedOf(Class<?> type) {
member.typeMatcher = FuzzyMatchers.matchDerived(type);
return this;
}
public Builder typeMatches(AbstractFuzzyMatcher<Class<?>> matcher) {
member.typeMatcher = matcher;
return this;
}
@Override
public FuzzyFieldContract build() {
member.prepareBuild();
return new FuzzyFieldContract(member);
}
}
/**
* Return a new fuzzy field contract builder.
* @return New fuzzy field contract builder.
*/
public static Builder newBuilder() {
return new Builder();
}
private FuzzyFieldContract() {
// Only allow construction through the builder
super();
}
/**
* Retrieve the class matcher that matches the type of a field.
* @return The class matcher.
*/
public AbstractFuzzyMatcher<Class<?>> getTypeMatcher() {
return typeMatcher;
}
/**
* Create a new field contract from the given contract.
* @param other - the contract to clone.
*/
private FuzzyFieldContract(FuzzyFieldContract other) {
super(other);
this.typeMatcher = other.typeMatcher;
}
@Override
public boolean isMatch(Field value, Object parent) {
if (super.isMatch(value, parent)) {
return typeMatcher.isMatch(value.getType(), value);
}
// No match
return false;
}
@Override
protected int calculateRoundNumber() {
// Combine the two
return combineRounds(super.calculateRoundNumber(),
typeMatcher.calculateRoundNumber());
}
@Override
protected Map<String, Object> getKeyValueView() {
Map<String, Object> member = super.getKeyValueView();
if (typeMatcher != ClassExactMatcher.MATCH_ALL) {
member.put("type", typeMatcher);
}
return member;
}
@Override
public int hashCode() {
return Objects.hashCode(typeMatcher, super.hashCode());
}
@Override
public boolean equals(Object obj) {
// Use the member equals method
if (this == obj) {
return true;
} else if (obj instanceof FuzzyFieldContract && super.equals(obj)) {
return Objects.equal(typeMatcher, ((FuzzyFieldContract) obj).typeMatcher);
}
return true;
}
}

Datei anzeigen

@ -0,0 +1,142 @@
package com.comphenix.protocol.reflect.fuzzy;
import java.lang.reflect.Member;
import java.util.Set;
import java.util.regex.Pattern;
import com.google.common.collect.Sets;
/**
* Contains factory methods for matching classes.
*
* @author Kristian
*/
public class FuzzyMatchers {
private FuzzyMatchers() {
// Don't make this constructable
}
/**
* Construct a class matcher that matches types exactly.
* @param matcher - the matching class.
* @return A new class mathcher.
*/
public static AbstractFuzzyMatcher<Class<?>> matchExact(Class<?> matcher) {
return new ClassExactMatcher(matcher, ClassExactMatcher.Options.MATCH_EXACT);
}
/**
* Construct a class matcher that matches any of the given classes exactly.
* @param classes - list of classes to match.
* @return A new class mathcher.
*/
public static AbstractFuzzyMatcher<Class<?>> matchAnyOf(Class<?>... classes) {
return matchAnyOf(Sets.newHashSet(classes));
}
/**
* Construct a class matcher that matches any of the given classes exactly.
* @param classes - set of classes to match.
* @return A new class mathcher.
*/
public static AbstractFuzzyMatcher<Class<?>> matchAnyOf(Set<Class<?>> classes) {
return new ClassSetMatcher(classes);
}
/**
* Construct a class matcher that matches super types of the given class.
* @param matcher - the matching type must be a super class of this type.
* @return A new class mathcher.
*/
public static AbstractFuzzyMatcher<Class<?>> matchSuper(Class<?> matcher) {
return new ClassExactMatcher(matcher, ClassExactMatcher.Options.MATCH_SUPER);
}
/**
* Construct a class matcher that matches derived types of the given class.
* @param matcher - the matching type must be a derived class of this type.
* @return A new class mathcher.
*/
public static AbstractFuzzyMatcher<Class<?>> matchDerived(Class<?> matcher) {
return new ClassExactMatcher(matcher, ClassExactMatcher.Options.MATCH_DERIVED);
}
/**
* Construct a class matcher based on the canonical names of classes.
* @param regex - regular expression pattern matching class names.
* @param priority - the priority this matcher takes - higher is better.
* @return A fuzzy class matcher based on name.
*/
public static AbstractFuzzyMatcher<Class<?>> matchRegex(final Pattern regex, final int priority) {
return new ClassRegexMatcher(regex, priority);
}
/**
* Construct a class matcher based on the canonical names of classes.
* @param regex - regular expression matching class names.
* @param priority - the priority this matcher takes - higher is better.
* @return A fuzzy class matcher based on name.
*/
public static AbstractFuzzyMatcher<Class<?>> matchRegex(String regex, final int priority) {
return FuzzyMatchers.matchRegex(Pattern.compile(regex), priority);
}
/**
* Match the parent class of a method, field or constructor.
* @return Parent matcher.
*/
public static AbstractFuzzyMatcher<Class<?>> matchParent() {
return new AbstractFuzzyMatcher<Class<?>>() {
@Override
public boolean isMatch(Class<?> value, Object parent) {
if (parent instanceof Member) {
return ((Member) parent).getDeclaringClass().equals(value);
} else if (parent instanceof Class) {
return parent.equals(value);
} else {
// Can't be a match
return false;
}
}
@Override
protected int calculateRoundNumber() {
// We match a very specific type
return -100;
}
@Override
public String toString() {
return "match parent class";
}
@Override
public int hashCode() {
return 0;
}
@Override
public boolean equals(Object obj) {
// If they're the same type, then yes
return obj != null && obj.getClass() == this.getClass();
}
};
}
/**
* Determine if two patterns are the same.
* <p>
* Note that two patterns may be functionally the same, but nevertheless be different.
* @param a - the first pattern.
* @param b - the second pattern.
* @return TRUE if they are compiled from the same pattern, FALSE otherwise.
*/
static boolean checkPattern(Pattern a, Pattern b) {
if (a == null)
return b == null;
else if (b == null)
return false;
else
return a.pattern().equals(b.pattern());
}
}

Datei anzeigen

@ -0,0 +1,539 @@
package com.comphenix.protocol.reflect.fuzzy;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import javax.annotation.Nonnull;
import org.apache.commons.lang.NotImplementedException;
import com.comphenix.protocol.reflect.MethodInfo;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
/**
* Represents a contract for matching methods or constructors.
*
* @author Kristian
*/
public class FuzzyMethodContract extends AbstractFuzzyMember<MethodInfo> {
private static class ParameterClassMatcher extends AbstractFuzzyMatcher<Class<?>[]> {
/**
* The expected index.
*/
private final AbstractFuzzyMatcher<Class<?>> typeMatcher;
private final Integer indexMatch;
/**
* Construct a new parameter class matcher.
* @param typeMatcher - class type matcher.
*/
public ParameterClassMatcher(@Nonnull AbstractFuzzyMatcher<Class<?>> typeMatcher) {
this(typeMatcher, null);
}
/**
* Construct a new parameter class matcher.
* @param typeMatcher - class type matcher.
* @param indexMatch - parameter index to match, or NULL for anything.
*/
public ParameterClassMatcher(@Nonnull AbstractFuzzyMatcher<Class<?>> typeMatcher, Integer indexMatch) {
if (typeMatcher == null)
throw new IllegalArgumentException("Type matcher cannot be NULL.");
this.typeMatcher = typeMatcher;
this.indexMatch = indexMatch;
}
/**
* See if there's a match for this matcher.
* @param used - parameters that have been matched before.
* @param parent - the container (member) that holds a reference to this parameter.
* @param params - the type of each parameter.
* @return TRUE if this matcher matches any of the given parameters, FALSE otherwise.
*/
public boolean isParameterMatch(Class<?> param, MethodInfo parent, int index) {
// Make sure the index is valid (or NULL)
if (indexMatch == null || indexMatch == index)
return typeMatcher.isMatch(param, parent);
else
return false;
}
@Override
public boolean isMatch(Class<?>[] value, Object parent) {
throw new NotImplementedException("Use the parameter match instead.");
}
@Override
protected int calculateRoundNumber() {
return typeMatcher.getRoundNumber();
}
@Override
public String toString() {
return String.format("{Type: %s, Index: %s}", typeMatcher, indexMatch);
}
}
// Match return value
private AbstractFuzzyMatcher<Class<?>> returnMatcher = ClassExactMatcher.MATCH_ALL;
// Handle parameters and exceptions
private List<ParameterClassMatcher> paramMatchers;
private List<ParameterClassMatcher> exceptionMatchers;
// Expected parameter count
private Integer paramCount;
/**
* Represents a builder for a fuzzy method contract.
*
* @author Kristian
*/
public static class Builder extends AbstractFuzzyMember.Builder<FuzzyMethodContract> {
public Builder requireModifier(int modifier) {
super.requireModifier(modifier);
return this;
}
@Override
public Builder banModifier(int modifier) {
super.banModifier(modifier);
return this;
}
@Override
public Builder nameRegex(String regex) {
super.nameRegex(regex);
return this;
}
@Override
public Builder nameRegex(Pattern pattern) {
super.nameRegex(pattern);
return this;
}
@Override
public Builder nameExact(String name) {
super.nameExact(name);
return this;
}
@Override
public Builder declaringClassExactType(Class<?> declaringClass) {
super.declaringClassExactType(declaringClass);
return this;
}
@Override
public Builder declaringClassSuperOf(Class<?> declaringClass) {
super.declaringClassSuperOf(declaringClass);
return this;
}
@Override
public Builder declaringClassDerivedOf(Class<?> declaringClass) {
super.declaringClassDerivedOf(declaringClass);
return this;
}
@Override
public Builder declaringClassMatching(AbstractFuzzyMatcher<Class<?>> classMatcher) {
super.declaringClassMatching(classMatcher);
return this;
}
/**
* Add a new required parameter by type for any matching method.
* @param type - the exact type this parameter must match.
* @return This builder, for chaining.
*/
public Builder parameterExactType(Class<?> type) {
member.paramMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchExact(type)));
return this;
}
/**
* Add a new required parameter whose type must be a superclass of the given type.
* <p>
* If a parameter is of type Number, any derived class (Integer, Long, etc.) will match it.
* @param type - a type or derived type of the matching parameter.
* @return This builder, for chaining.
*/
public Builder parameterSuperOf(Class<?> type) {
member.paramMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchSuper(type)));
return this;
}
/**
* Add a new required parameter whose type must match the given class matcher.
* @param classMatcher - the class matcher.
* @return This builder, for chaining.
*/
public Builder parameterMatches(AbstractFuzzyMatcher<Class<?>> classMatcher) {
member.paramMatchers.add(new ParameterClassMatcher(classMatcher));
return this;
}
/**
* Add a new required parameter by type and position for any matching method.
* @param type - the exact type this parameter must match.
* @param index - the expected position in the parameter list.
* @return This builder, for chaining.
*/
public Builder parameterExactType(Class<?> type, int index) {
member.paramMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchExact(type), index));
return this;
}
/**
* Add a new required parameter whose type must be a superclass of the given type.
* <p>
* If a parameter is of type Number, any derived class (Integer, Long, etc.) will match it.
* @param type - a type or derived type of the matching parameter.
* @param index - the expected position in the parameter list.
* @return This builder, for chaining.
*/
public Builder parameterSuperOf(Class<?> type, int index) {
member.paramMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchSuper(type), index));
return this;
}
/**
* Add a new required parameter whose type must match the given class matcher and index.
* @param classMatcher - the class matcher.
* @param index - the expected position in the parameter list.
* @return This builder, for chaining.
*/
public Builder parameterMatches(AbstractFuzzyMatcher<Class<?>> classMatcher, int index) {
member.paramMatchers.add(new ParameterClassMatcher(classMatcher, index));
return this;
}
/**
* Set the expected number of parameters in the matching method.
* @param expectedCount - the number of parameters to expect.
* @return This builder, for chaining.
*/
public Builder parameterCount(int expectedCount) {
member.paramCount = expectedCount;
return this;
}
/**
* Require a void method.
* @return This builder, for chaining.
*/
public Builder returnTypeVoid() {
return returnTypeExact(Void.TYPE);
}
/**
* Set the return type of a matching method exactly.
* @param type - the exact return type.
* @return This builder, for chaining.
*/
public Builder returnTypeExact(Class<?> type) {
member.returnMatcher = FuzzyMatchers.matchExact(type);
return this;
}
/**
* Set the expected super class of the return type for every matching method.
* @param type - the return type, or a super class of it.
* @return This builder, for chaining.
*/
public Builder returnDerivedOf(Class<?> type) {
member.returnMatcher = FuzzyMatchers.matchDerived(type);
return this;
}
/**
* Set a matcher that must match the return type of a matching method.
* @param classMatcher - the exact return type.
* @return This builder, for chaining.
*/
public Builder returnTypeMatches(AbstractFuzzyMatcher<Class<?>> classMatcher) {
member.returnMatcher = classMatcher;
return this;
}
/**
* Add a throwable exception that must match the given type exactly.
* @param type - exception type.
* @return This builder, for chaining.
*/
public Builder exceptionExactType(Class<?> type) {
member.exceptionMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchExact(type)));
return this;
}
/**
* Add a throwable exception that must match the given type or be derived.
* @param type - exception type.
* @return This builder, for chaining.
*/
public Builder exceptionSuperOf(Class<?> type) {
member.exceptionMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchSuper(type)));
return this;
}
/**
* Add a throwable exception that must match the given matcher,
* @param classMatcher - the class matcher that must match.
* @return This builder, for chaining.
*/
public Builder exceptionMatches(AbstractFuzzyMatcher<Class<?>> classMatcher) {
member.exceptionMatchers.add(new ParameterClassMatcher(classMatcher));
return this;
}
/**
* Add a throwable exception that must match the given type exactly and index.
* @param type - exception type.
* @param index - the position in the throwable list.
* @return This builder, for chaining.
*/
public Builder exceptionExactType(Class<?> type, int index) {
member.exceptionMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchExact(type), index));
return this;
}
/**
* Add a throwable exception that must match the given type or be derived and index.
* @param type - exception type.
* @param index - the position in the throwable list.
* @return This builder, for chaining.
*/
public Builder exceptionSuperOf(Class<?> type, int index) {
member.exceptionMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchSuper(type), index));
return this;
}
/**
* Add a throwable exception that must match the given matcher and index.
* @param classMatcher - the class matcher that must match.
* @param index - the position in the throwable list.
* @return This builder, for chaining.
*/
public Builder exceptionMatches(AbstractFuzzyMatcher<Class<?>> classMatcher, int index) {
member.exceptionMatchers.add(new ParameterClassMatcher(classMatcher, index));
return this;
}
@Override
@Nonnull
protected FuzzyMethodContract initialMember() {
// With mutable lists
return new FuzzyMethodContract();
}
@Override
public FuzzyMethodContract build() {
member.prepareBuild();
return immutableCopy(member);
}
}
/**
* Return a method contract builder.
* @return Method contract builder.
*/
public static Builder newBuilder() {
return new Builder();
}
private FuzzyMethodContract() {
// Only allow construction from the builder
paramMatchers = Lists.newArrayList();
exceptionMatchers = Lists.newArrayList();
}
private FuzzyMethodContract(FuzzyMethodContract other) {
super(other);
this.returnMatcher = other.returnMatcher;
this.paramMatchers = other.paramMatchers;
this.exceptionMatchers = other.exceptionMatchers;
this.paramCount = other.paramCount;
}
/**
* Construct a new immutable copy of the given method contract.
* @param other - the contract to clone.
* @return A immutable copy of the given contract.
*/
private static FuzzyMethodContract immutableCopy(FuzzyMethodContract other) {
FuzzyMethodContract copy = new FuzzyMethodContract(other);
// Ensure that the lists are immutable
copy.paramMatchers = ImmutableList.copyOf(copy.paramMatchers);
copy.exceptionMatchers = ImmutableList.copyOf(copy.exceptionMatchers);
return copy;
}
/**
* Retrieve the class matcher for the return type.
* @return Class matcher for the return type.
*/
public AbstractFuzzyMatcher<Class<?>> getReturnMatcher() {
return returnMatcher;
}
/**
* Retrieve an immutable list of every parameter matcher for this method.
* @return Immutable list of every parameter matcher.
*/
public ImmutableList<ParameterClassMatcher> getParamMatchers() {
if (paramMatchers instanceof ImmutableList)
return (ImmutableList<ParameterClassMatcher>) paramMatchers;
else
throw new IllegalStateException("Lists haven't been sealed yet.");
}
/**
* Retrieve an immutable list of every exception matcher for this method.
* @return Immutable list of every exception matcher.
*/
public List<ParameterClassMatcher> getExceptionMatchers() {
if (exceptionMatchers instanceof ImmutableList)
return exceptionMatchers;
else
throw new IllegalStateException("Lists haven't been sealed yet.");
}
/**
* Retrieve the expected parameter count for this method.
* @return Expected parameter count, or NULL if anyting goes.
*/
public Integer getParamCount() {
return paramCount;
}
@Override
protected void prepareBuild() {
super.prepareBuild();
// Sort lists such that more specific tests are up front
Collections.sort(paramMatchers);
Collections.sort(exceptionMatchers);
}
@Override
public boolean isMatch(MethodInfo value, Object parent) {
if (super.isMatch(value, parent)) {
Class<?>[] params = value.getParameterTypes();
Class<?>[] exceptions = value.getExceptionTypes();
if (!returnMatcher.isMatch(value.getReturnType(), value))
return false;
if (paramCount != null && paramCount != value.getParameterTypes().length)
return false;
// Finally, check parameters and exceptions
return matchParameters(params, value, paramMatchers) &&
matchParameters(exceptions, value, exceptionMatchers);
}
// No match
return false;
}
private boolean matchParameters(Class<?>[] types, MethodInfo parent, List<ParameterClassMatcher> matchers) {
boolean[] accepted = new boolean[matchers.size()];
int count = accepted.length;
// Process every parameter in turn
for (int i = 0; i < types.length; i++) {
int matcherIndex = processValue(types[i], parent, i, accepted, matchers);
if (matcherIndex >= 0) {
accepted[matcherIndex] = true;
count--;
}
// Break early
if (count == 0)
return true;
}
return count == 0;
}
private int processValue(Class<?> value, MethodInfo parent, int index, boolean accepted[], List<ParameterClassMatcher> matchers) {
// The order matters
for (int i = 0; i < matchers.size(); i++) {
if (!accepted[i]) {
// See if we got jackpot
if (matchers.get(i).isParameterMatch(value, parent, index)) {
return i;
}
}
}
// Failure
return -1;
}
@Override
protected int calculateRoundNumber() {
int current = 0;
// Consider the return value first
current = returnMatcher.getRoundNumber();
// Handle parameters
for (ParameterClassMatcher matcher : paramMatchers) {
current = combineRounds(current, matcher.calculateRoundNumber());
}
// And exceptions
for (ParameterClassMatcher matcher : exceptionMatchers) {
current = combineRounds(current, matcher.calculateRoundNumber());
}
return combineRounds(super.calculateRoundNumber(), current);
}
@Override
protected Map<String, Object> getKeyValueView() {
Map<String, Object> member = super.getKeyValueView();
// Only add fields that are actual contraints
if (returnMatcher != ClassExactMatcher.MATCH_ALL) {
member.put("return", returnMatcher);
}
if (paramMatchers.size() > 0) {
member.put("params", paramMatchers);
}
if (exceptionMatchers.size() > 0) {
member.put("exceptions", exceptionMatchers);
}
if (paramCount != null) {
member.put("paramCount", paramCount);
}
return member;
}
@Override
public int hashCode() {
return Objects.hashCode(returnMatcher, paramMatchers, exceptionMatchers, paramCount, super.hashCode());
}
@Override
public boolean equals(Object obj) {
// Use the member equals method
if (this == obj) {
return true;
} else if (obj instanceof FuzzyMethodContract && super.equals(obj)) {
FuzzyMethodContract other = (FuzzyMethodContract) obj;
return Objects.equal(paramCount, other.paramCount) &&
Objects.equal(returnMatcher, other.returnMatcher) &&
Objects.equal(paramMatchers, other.paramMatchers) &&
Objects.equal(exceptionMatchers, other.exceptionMatchers);
}
return true;
}
}

Datei anzeigen

@ -0,0 +1,36 @@
package com.comphenix.protocol.reflect.instances;
import javax.annotation.Nullable;
import com.comphenix.protocol.reflect.fuzzy.AbstractFuzzyMatcher;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMatchers;
/**
* Generator that ensures certain types will never be created.
*
* @author Kristian
*/
public class BannedGenerator implements InstanceProvider {
private AbstractFuzzyMatcher<Class<?>> classMatcher;
/**
* Construct a generator that ensures any class that matches the given matcher is never constructed.
* @param classMatcher - a class matcher.
*/
public BannedGenerator(AbstractFuzzyMatcher<Class<?>> classMatcher) {
this.classMatcher = classMatcher;
}
public BannedGenerator(Class<?>... classes) {
this.classMatcher = FuzzyMatchers.matchAnyOf(classes);
}
@Override
public Object create(@Nullable Class<?> type) {
// Prevent these types from being constructed
if (classMatcher.isMatch(type, null)) {
throw new NotConstructableException();
}
return null;
}
}

Datei anzeigen

@ -86,8 +86,17 @@ public class DefaultInstances implements InstanceProvider {
* @param instaceProviders - array of instance providers. * @param instaceProviders - array of instance providers.
* @return An default instance generator. * @return An default instance generator.
*/ */
public static DefaultInstances fromArray(InstanceProvider... instaceProviders) { public static DefaultInstances fromArray(InstanceProvider... instanceProviders) {
return new DefaultInstances(ImmutableList.copyOf(instaceProviders)); return new DefaultInstances(ImmutableList.copyOf(instanceProviders));
}
/**
* Construct a default instance generator using the given instance providers.
* @param instaceProviders - collection of instance providers.
* @return An default instance generator.
*/
public static DefaultInstances fromCollection(Collection<InstanceProvider> instanceProviders) {
return new DefaultInstances(ImmutableList.copyOf(instanceProviders));
} }
/** /**
@ -241,11 +250,15 @@ public class DefaultInstances implements InstanceProvider {
private <T> T getDefaultInternal(Class<T> type, List<InstanceProvider> providers, int recursionLevel) { private <T> T getDefaultInternal(Class<T> type, List<InstanceProvider> providers, int recursionLevel) {
// The instance providiers should protect themselves against recursion // The instance providiers should protect themselves against recursion
for (InstanceProvider generator : providers) { try {
Object value = generator.create(type); for (InstanceProvider generator : providers) {
Object value = generator.create(type);
if (value != null) if (value != null)
return (T) value; return (T) value;
}
} catch (NotConstructableException e) {
return null;
} }
// Guard against recursion // Guard against recursion
@ -276,7 +289,7 @@ public class DefaultInstances implements InstanceProvider {
} }
} catch (Exception e) { } catch (Exception e) {
// Nope, we couldn't create this type // Nope, we couldn't create this type. Might for instance be NotConstructableException.
} }
// No suitable default value could be found // No suitable default value could be found

Datei anzeigen

@ -25,11 +25,11 @@ import javax.annotation.Nullable;
* @author Kristian * @author Kristian
*/ */
public interface InstanceProvider { public interface InstanceProvider {
/** /**
* Create an instance given a type, if possible. * Create an instance given a type, if possible.
* @param type - type to create. * @param type - type to create.
* @return The instance, or NULL if the type cannot be created. * @return The instance, or NULL if the type cannot be created.
* @throws NotConstructableException Thrown to indicate that this type cannot or should never be constructed.
*/ */
public abstract Object create(@Nullable Class<?> type); public abstract Object create(@Nullable Class<?> type);
} }

Datei anzeigen

@ -0,0 +1,42 @@
package com.comphenix.protocol.reflect.instances;
/**
* Invoked when a instance provider indicates that a given type cannot or should not be
* constructed under any circumstances.
*
* @author Kristian
*/
public class NotConstructableException extends IllegalArgumentException {
/**
* Generated by Eclipse.
*/
private static final long serialVersionUID = -1144171604744845463L;
/**
* Construct a new not constructable exception.
*/
public NotConstructableException() {
super("This object should never be constructed.");
}
/**
* Construct a new not constructable exception with a custom message.
*/
public NotConstructableException(String message) {
super(message);
}
/**
* Construct a new not constructable exception with a custom message and cause.
*/
public NotConstructableException(String message, Throwable cause) {
super(message, cause);
}
/**
* Construct a new not constructable exception with a custom cause.
*/
public NotConstructableException(Throwable cause) {
super( cause);
}
}

Datei anzeigen

@ -17,9 +17,19 @@
package com.comphenix.protocol.utility; package com.comphenix.protocol.utility;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.ServerSocket;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
@ -28,6 +38,13 @@ import org.bukkit.Server;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import com.comphenix.protocol.injector.BukkitUnwrapper; import com.comphenix.protocol.injector.BukkitUnwrapper;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.fuzzy.AbstractFuzzyMatcher;
import com.comphenix.protocol.reflect.fuzzy.FuzzyClassContract;
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMatchers;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
/** /**
@ -38,9 +55,17 @@ import com.google.common.base.Joiner;
public class MinecraftReflection { public class MinecraftReflection {
/** /**
* Regular expression that matches a Minecraft object. * Regular expression that matches a Minecraft object.
* <p>
* Replaced by the method {@link #getMinecraftObjectRegex()}.
*/ */
@Deprecated
public static final String MINECRAFT_OBJECT = "net\\.minecraft(\\.\\w+)+"; public static final String MINECRAFT_OBJECT = "net\\.minecraft(\\.\\w+)+";
/**
* Regular expression computed dynamically.
*/
private static String DYNAMIC_PACKAGE_MATCHER = null;
/** /**
* The package name of all the classes that belongs to the native code in Minecraft. * The package name of all the classes that belongs to the native code in Minecraft.
*/ */
@ -64,6 +89,28 @@ public class MinecraftReflection {
// net.minecraft.server // net.minecraft.server
private static Class<?> itemStackArrayClass; private static Class<?> itemStackArrayClass;
private MinecraftReflection() {
// No need to make this constructable.
}
/**
* Retrieve a regular expression that can match Minecraft package objects.
* @return Minecraft package matcher.
*/
public static String getMinecraftObjectRegex() {
if (DYNAMIC_PACKAGE_MATCHER == null)
getMinecraftPackage();
return DYNAMIC_PACKAGE_MATCHER;
}
/**
* Retrieve a abstract fuzzy class matcher for Minecraft objects.
* @return A matcher for Minecraft objects.
*/
public static AbstractFuzzyMatcher<Class<?>> getMinecraftObjectMatcher() {
return FuzzyMatchers.matchRegex(getMinecraftObjectRegex(), 50);
}
/** /**
* Retrieve the name of the Minecraft server package. * Retrieve the name of the Minecraft server package.
* @return Full canonical name of the Minecraft server package. * @return Full canonical name of the Minecraft server package.
@ -87,6 +134,25 @@ public class MinecraftReflection {
// The return type will tell us the full package, regardless of formating // The return type will tell us the full package, regardless of formating
CRAFTBUKKIT_PACKAGE = getPackage(craftClass.getCanonicalName()); CRAFTBUKKIT_PACKAGE = getPackage(craftClass.getCanonicalName());
MINECRAFT_FULL_PACKAGE = getPackage(returnName); MINECRAFT_FULL_PACKAGE = getPackage(returnName);
// Pretty important invariant
if (!MINECRAFT_FULL_PACKAGE.startsWith(MINECRAFT_PREFIX_PACKAGE)) {
// Assume they're the same instead
MINECRAFT_PREFIX_PACKAGE = MINECRAFT_FULL_PACKAGE;
// The package is usualy flat, so go with that assumtion
DYNAMIC_PACKAGE_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 + ")";
} else {
// Use the standard matcher
DYNAMIC_PACKAGE_MATCHER = MINECRAFT_OBJECT;
}
return MINECRAFT_FULL_PACKAGE; return MINECRAFT_FULL_PACKAGE;
} catch (SecurityException e) { } catch (SecurityException e) {
@ -108,6 +174,9 @@ public class MinecraftReflection {
public static void setMinecraftPackage(String minecraftPackage, String craftBukkitPackage) { public static void setMinecraftPackage(String minecraftPackage, String craftBukkitPackage) {
MINECRAFT_FULL_PACKAGE = minecraftPackage; MINECRAFT_FULL_PACKAGE = minecraftPackage;
CRAFTBUKKIT_PACKAGE = craftBukkitPackage; CRAFTBUKKIT_PACKAGE = craftBukkitPackage;
// Standard matcher
DYNAMIC_PACKAGE_MATCHER = MINECRAFT_OBJECT;
} }
/** /**
@ -126,7 +195,12 @@ public class MinecraftReflection {
* @return The package name. * @return The package name.
*/ */
private static String getPackage(String fullName) { private static String getPackage(String fullName) {
return fullName.substring(0, fullName.lastIndexOf(".")); int index = fullName.lastIndexOf(".");
if (index > 0)
return fullName.substring(0, index);
else
return ""; // Default package
} }
/** /**
@ -160,6 +234,19 @@ public class MinecraftReflection {
return obj.getClass().getName().startsWith(MINECRAFT_PREFIX_PACKAGE); return obj.getClass().getName().startsWith(MINECRAFT_PREFIX_PACKAGE);
} }
/**
* Determine if the given class is found within the package net.minecraft.server, or any equivalent package.
* @param clazz - the class to test.
* @return TRUE if it can, FALSE otherwise.
*/
public static boolean isMinecraftClass(@Nonnull Class<?> clazz) {
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);
}
/** /**
* Determine if a given object is found in net.minecraft.server, and has the given name. * Determine if a given object is found in net.minecraft.server, and has the given name.
* @param obj - the object to test. * @param obj - the object to test.
@ -202,7 +289,7 @@ public class MinecraftReflection {
} }
/** /**
* Determine if the given object is a NetLoginHandler. * Determine if the given object is a NetLoginHandler (PendingConnection)
* @param obj - the given object. * @param obj - the given object.
* @return TRUE if it is, FALSE otherwise. * @return TRUE if it is, FALSE otherwise.
*/ */
@ -210,6 +297,15 @@ public class MinecraftReflection {
return getNetLoginHandlerClass().isAssignableFrom(obj.getClass()); return getNetLoginHandlerClass().isAssignableFrom(obj.getClass());
} }
/**
* Determine if the given object is assignable to a NetServerHandler (PlayerConnection)
* @param obj - the given object.
* @return TRUE if it is, FALSE otherwise.
*/
public static boolean isServerHandler(Object obj) {
return getNetServerHandlerClass().isAssignableFrom(obj.getClass());
}
/** /**
* Determine if the given object is actually a Minecraft packet. * Determine if the given object is actually a Minecraft packet.
* @param obj - the given object. * @param obj - the given object.
@ -221,13 +317,22 @@ public class MinecraftReflection {
/** /**
* Determine if the given object is a NMS ItemStack. * Determine if the given object is a NMS ItemStack.
* @param obj - the given object. * @param value - the given object.
* @return TRUE if it is, FALSE otherwise. * @return TRUE if it is, FALSE otherwise.
*/ */
public static boolean isItemStack(Object value) { public static boolean isItemStack(Object value) {
return getItemStackClass().isAssignableFrom(value.getClass()); return getItemStackClass().isAssignableFrom(value.getClass());
} }
/**
* Determine if the given object is a CraftPlayer class.
* @param value - the given object.
* @return TRUE if it is, FALSE otherwise.
*/
public static boolean isCraftPlayer(Object value) {
return getCraftPlayerClass().isAssignableFrom(value.getClass());
}
/** /**
* Determine if the given object is a Minecraft player entity. * Determine if the given object is a Minecraft player entity.
* @param obj - the given object. * @param obj - the given object.
@ -269,7 +374,22 @@ public class MinecraftReflection {
* @return The entity class. * @return The entity class.
*/ */
public static Class<?> getEntityPlayerClass() { public static Class<?> getEntityPlayerClass() {
return getMinecraftClass("EntityPlayer"); try {
return getMinecraftClass("EntityPlayer");
} catch (RuntimeException e) {
try {
// A fairly stable method
Method detect = FuzzyReflection.fromClass(getCraftBukkitClass("CraftServer")).
getMethodByName("detectListNameConflict");
// EntityPlayer is then the first parameter
return detect.getParameterTypes()[0];
} catch (IllegalArgumentException ex) {
// Last resort
return fallbackMethodReturn("EntityPlayer", "entity.CraftPlayer", "getHandle");
}
}
} }
/** /**
@ -277,7 +397,38 @@ public class MinecraftReflection {
* @return The entity class. * @return The entity class.
*/ */
public static Class<?> getEntityClass() { public static Class<?> getEntityClass() {
try {
return getMinecraftClass("Entity"); return getMinecraftClass("Entity");
} catch (RuntimeException e) {
return fallbackMethodReturn("Entity", "entity.CraftEntity", "getHandle");
}
}
/**
* Retrieve the WorldServer (NMS) class.
* @return The WorldServer class.
*/
public static Class<?> getWorldServerClass() {
try {
return getMinecraftClass("WorldServer");
} catch (RuntimeException e) {
return fallbackMethodReturn("WorldServer", "CraftWorld", "getHandle");
}
}
/**
* Fallback on the return value of a named method in order to get a NMS class.
* @param nmsClass - the expected name of the Minecraft class.
* @param craftClass - a CraftBukkit class to look at.
* @param methodName - the method we will use.
* @return The return value of this method, which will be saved to the package cache.
*/
private static Class<?> fallbackMethodReturn(String nmsClass, String craftClass, String methodName) {
Class<?> result = FuzzyReflection.fromClass(getCraftBukkitClass(craftClass)).
getMethodByName(methodName).getReturnType();
// Save the result
return setMinecraftClass(nmsClass, result);
} }
/** /**
@ -285,39 +436,160 @@ public class MinecraftReflection {
* @return The packet class. * @return The packet class.
*/ */
public static Class<?> getPacketClass() { public static Class<?> getPacketClass() {
return getMinecraftClass("Packet"); try {
return getMinecraftClass("Packet");
} catch (RuntimeException e) {
// What kind of class we're looking for (sanity check)
FuzzyClassContract paketContract =
FuzzyClassContract.newBuilder().
field(FuzzyFieldContract.newBuilder().
typeDerivedOf(Map.class).
requireModifier(Modifier.STATIC)).
field(FuzzyFieldContract.newBuilder().
typeDerivedOf(Set.class).
requireModifier(Modifier.STATIC)).
method(FuzzyMethodContract.newBuilder().
parameterSuperOf(DataInputStream.class).
returnTypeVoid()).
build();
// Select a method with one Minecraft object parameter
Method selected = FuzzyReflection.fromClass(getNetHandlerClass()).
getMethod(FuzzyMethodContract.newBuilder().
parameterMatches(paketContract, 0).
parameterCount(1).
build()
);
// Save and return
Class<?> clazz = getTopmostClass(selected.getParameterTypes()[0]);
return setMinecraftClass("Packet", clazz);
}
} }
/** /**
* Retrieve the NetLoginHandler class. * Retrieve the least derived class, except Object.
* @return Least derived super class.
*/
private static Class<?> getTopmostClass(Class<?> clazz) {
while (true) {
Class<?> superClass = clazz.getSuperclass();
if (superClass == Object.class || superClass == null)
return clazz;
else
clazz = superClass;
}
}
/**
* Retrieve the MinecraftServer class.
* @return MinecraftServer class.
*/
public static Class<?> getMinecraftServerClass() {
try {
return getMinecraftClass("MinecraftServer");
} catch (RuntimeException e) {
// Get the first constructor that matches CraftServer(MINECRAFT_OBJECT, ANY)
Constructor<?> selected = FuzzyReflection.fromClass(getCraftBukkitClass("CraftServer")).
getConstructor(FuzzyMethodContract.newBuilder().
parameterMatches(getMinecraftObjectMatcher(), 0).
parameterCount(2).
build()
);
Class<?>[] params = selected.getParameterTypes();
// Jackpot - two classes at the same time!
setMinecraftClass("MinecraftServer", params[0]);
setMinecraftClass("ServerConfigurationManager", params[1]);
return params[0];
}
}
/**
* Retrieve the player list class (or ServerConfigurationManager),
* @return The player list class.
*/
public static Class<?> getPlayerListClass() {
try {
return getMinecraftClass("ServerConfigurationManager", "PlayerList");
} catch (RuntimeException e) {
// Try again
getMinecraftServerClass();
return getMinecraftClass("ServerConfigurationManager");
}
}
/**
* Retrieve the NetLoginHandler class (or PendingConnection)
* @return The NetLoginHandler class. * @return The NetLoginHandler class.
*/ */
public static Class<?> getNetLoginHandlerClass() { public static Class<?> getNetLoginHandlerClass() {
return getMinecraftClass("NetLoginHandler", "PendingConnection"); try {
return getMinecraftClass("NetLoginHandler", "PendingConnection");
} catch (RuntimeException e) {
Method selected = FuzzyReflection.fromClass(getPlayerListClass()).
getMethod(FuzzyMethodContract.newBuilder().
parameterMatches(
FuzzyMatchers.matchExact(getEntityPlayerClass()).inverted(), 0
).
parameterExactType(String.class, 1).
parameterExactType(String.class, 2).
build()
);
// Save the pending connection reference
return setMinecraftClass("NetLoginHandler", selected.getParameterTypes()[0]);
}
} }
/** /**
* Retrieve the NetServerHandler class. * Retrieve the NetServerHandler class (or PlayerConnection)
* @return The NetServerHandler class. * @return The NetServerHandler class.
*/ */
public static Class<?> getNetServerHandlerClass() { public static Class<?> getNetServerHandlerClass() {
return getMinecraftClass("NetServerHandler", "PlayerConnection"); try {
return getMinecraftClass("NetServerHandler", "PlayerConnection");
} catch (RuntimeException e) {
// Use the player connection field
return setMinecraftClass("NetLoginHandler",
FuzzyReflection.fromClass(getEntityPlayerClass()).
getFieldByType("playerConnection", getNetHandlerClass()).getType()
);
}
} }
/** /**
* Retrieve the NetworkManager class. * Retrieve the NetworkManager class or its interface.
* @return The NetworkManager class. * @return The NetworkManager class or its interface.
*/ */
public static Class<?> getNetworkManagerClass() { public static Class<?> getNetworkManagerClass() {
return getMinecraftClass("NetworkManager"); try {
return getMinecraftClass("INetworkManager", "NetworkManager");
} catch (RuntimeException e) {
Constructor<?> selected = FuzzyReflection.fromClass(getNetServerHandlerClass()).
getConstructor(FuzzyMethodContract.newBuilder().
parameterSuperOf(getMinecraftServerClass(), 0).
parameterSuperOf(getEntityPlayerClass(), 2).
build()
);
// And we're done
return setMinecraftClass("INetworkManager", selected.getParameterTypes()[1]);
}
} }
/** /**
* Retrieve the NetHandler class. * Retrieve the NetHandler class (or Connection)
* @return The NetHandler class. * @return The NetHandler class.
*/ */
public static Class<?> getNetHandlerClass() { public static Class<?> getNetHandlerClass() {
return getMinecraftClass("NetHandler", "Connection"); try {
return getMinecraftClass("NetHandler", "Connection");
} catch (RuntimeException e) {
return setMinecraftClass("NetHandler", getNetLoginHandlerClass().getSuperclass());
}
} }
/** /**
@ -325,7 +597,43 @@ public class MinecraftReflection {
* @return The ItemStack class. * @return The ItemStack class.
*/ */
public static Class<?> getItemStackClass() { public static Class<?> getItemStackClass() {
return getMinecraftClass("ItemStack"); try {
return getMinecraftClass("ItemStack");
} catch (RuntimeException e) {
// Use the handle reference
return setMinecraftClass("ItemStack",
FuzzyReflection.fromClass(getCraftItemStackClass(), true).getFieldByName("handle").getType());
}
}
/**
* Retrieve the Block (NMS) class.
* @return Block (NMS) class.
*/
public static Class<?> getBlockClass() {
try {
return getMinecraftClass("Block");
} catch (RuntimeException e) {
FuzzyReflection reflect = FuzzyReflection.fromClass(getItemStackClass());
Set<Class<?>> candidates = new HashSet<Class<?>>();
// Minecraft objects in the constructor
for (Constructor<?> constructor : reflect.getConstructors()) {
for (Class<?> clazz : constructor.getParameterTypes()) {
if (isMinecraftClass(clazz)) {
candidates.add(clazz);
}
}
}
// Useful constructors
Method selected =
reflect.getMethod(FuzzyMethodContract.newBuilder().
parameterMatches(FuzzyMatchers.matchAnyOf(candidates)).
returnTypeExact(float.class).
build());
return setMinecraftClass("Block", selected.getParameterTypes()[0]);
}
} }
/** /**
@ -333,15 +641,21 @@ public class MinecraftReflection {
* @return The WorldType class. * @return The WorldType class.
*/ */
public static Class<?> getWorldTypeClass() { public static Class<?> getWorldTypeClass() {
return getMinecraftClass("WorldType"); try {
} return getMinecraftClass("WorldType");
} catch (RuntimeException e) {
/** // Get the first constructor that matches CraftServer(MINECRAFT_OBJECT, ANY)
* Retrieve the MinecraftServer class. Method selected = FuzzyReflection.fromClass(getMinecraftServerClass(), true).
* @return MinecraftServer class. getMethod(FuzzyMethodContract.newBuilder().
*/ parameterExactType(String.class, 0).
public static Class<?> getMinecraftServerClass() { parameterExactType(String.class, 1).
return getMinecraftClass("MinecraftServer"); parameterMatches(getMinecraftObjectMatcher()).
parameterExactType(String.class, 4).
parameterCount(5).
build()
);
return setMinecraftClass("WorldType", selected.getParameterTypes()[3]);
}
} }
/** /**
@ -349,7 +663,33 @@ public class MinecraftReflection {
* @return The DataWatcher class. * @return The DataWatcher class.
*/ */
public static Class<?> getDataWatcherClass() { public static Class<?> getDataWatcherClass() {
return getMinecraftClass("DataWatcher"); try {
return getMinecraftClass("DataWatcher");
} catch (RuntimeException e) {
// Describe the DataWatcher
FuzzyClassContract dataWatcherContract = FuzzyClassContract.newBuilder().
field(FuzzyFieldContract.newBuilder().
requireModifier(Modifier.STATIC).
typeDerivedOf(Map.class)).
field(FuzzyFieldContract.newBuilder().
banModifier(Modifier.STATIC).
typeDerivedOf(Map.class)).
method(FuzzyMethodContract.newBuilder().
parameterExactType(int.class).
parameterExactType(Object.class).
returnTypeVoid()).
build();
FuzzyFieldContract fieldContract = FuzzyFieldContract.newBuilder().
typeMatches(dataWatcherContract).
build();
// Get such a field and save the result
return setMinecraftClass("DataWatcher",
FuzzyReflection.fromClass(getEntityClass(), true).
getField(fieldContract).
getType()
);
}
} }
/** /**
@ -357,7 +697,25 @@ public class MinecraftReflection {
* @return The ChunkPosition class. * @return The ChunkPosition class.
*/ */
public static Class<?> getChunkPositionClass() { public static Class<?> getChunkPositionClass() {
return getMinecraftClass("ChunkPosition"); try {
return getMinecraftClass("ChunkPosition");
} catch (RuntimeException e) {
Class<?> normalChunkGenerator = getCraftBukkitClass("generator.NormalChunkGenerator");
// ChunkPosition a(net.minecraft.server.World world, String string, int i, int i1, int i2) {
FuzzyMethodContract selected = FuzzyMethodContract.newBuilder().
banModifier(Modifier.STATIC).
parameterMatches(getMinecraftObjectMatcher(), 0).
parameterExactType(String.class, 1).
parameterExactType(int.class, 2).
parameterExactType(int.class, 3).
parameterExactType(int.class, 4).
build();
return setMinecraftClass("ChunkPosition",
FuzzyReflection.fromClass(normalChunkGenerator).
getMethod(selected).getReturnType());
}
} }
/** /**
@ -365,7 +723,11 @@ public class MinecraftReflection {
* @return The ChunkPosition class. * @return The ChunkPosition class.
*/ */
public static Class<?> getChunkCoordinatesClass() { public static Class<?> getChunkCoordinatesClass() {
return getMinecraftClass("ChunkCoordinates"); try {
return getMinecraftClass("ChunkCoordinates");
} catch (RuntimeException e) {
return setMinecraftClass("ChunkCoordinates", WrappedDataWatcher.getTypeClass(6));
}
} }
/** /**
@ -373,7 +735,46 @@ public class MinecraftReflection {
* @return The WatchableObject class. * @return The WatchableObject class.
*/ */
public static Class<?> getWatchableObjectClass() { public static Class<?> getWatchableObjectClass() {
return getMinecraftClass("WatchableObject"); try {
return getMinecraftClass("WatchableObject");
} catch (RuntimeException e) {
Method selected = FuzzyReflection.fromClass(getDataWatcherClass(), true).
getMethod(FuzzyMethodContract.newBuilder().
requireModifier(Modifier.STATIC).
parameterSuperOf(DataOutputStream.class, 0).
parameterMatches(getMinecraftObjectMatcher(), 1).
build());
// Use the second parameter
return setMinecraftClass("WatchableObject", selected.getParameterTypes()[1]);
}
}
/**
* Retrieve the ServerConnection abstract class.
* @return The ServerConnection class.
*/
public static Class<?> getServerConnectionClass() {
try {
return getMinecraftClass("ServerConnection");
} catch (RuntimeException e) {
FuzzyClassContract serverConnectionContract = FuzzyClassContract.newBuilder().
constructor(FuzzyMethodContract.newBuilder().
parameterExactType(getMinecraftServerClass()).
parameterCount(1)).
method(FuzzyMethodContract.newBuilder().
parameterExactType(getNetServerHandlerClass())).
build();
Method selected = FuzzyReflection.fromClass(getMinecraftServerClass()).
getMethod(FuzzyMethodContract.newBuilder().
requireModifier(Modifier.ABSTRACT).
returnTypeMatches(serverConnectionContract).
build());
// Use the return type
return setMinecraftClass("ServerConnection", selected.getReturnType());
}
} }
/** /**
@ -381,7 +782,95 @@ public class MinecraftReflection {
* @return The NBT base class. * @return The NBT base class.
*/ */
public static Class<?> getNBTBaseClass() { public static Class<?> getNBTBaseClass() {
return getMinecraftClass("NBTBase"); try {
return getMinecraftClass("NBTBase");
} catch (RuntimeException e) {
FuzzyClassContract tagCompoundContract = FuzzyClassContract.newBuilder().
constructor(FuzzyMethodContract.newBuilder().
parameterExactType(String.class).
parameterCount(1)).
field(FuzzyFieldContract.newBuilder().
typeDerivedOf(Map.class)).
build();
Method selected = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).
getMethod(FuzzyMethodContract.newBuilder().
requireModifier(Modifier.STATIC).
parameterSuperOf(DataInputStream.class).
parameterCount(1).
returnTypeMatches(tagCompoundContract).
build()
);
// Use the return type here too
return setMinecraftClass("NBTBase", selected.getReturnType());
}
}
/**
* Retrieve the EntityTracker (NMS) class.
* @return EntityTracker class.
*/
public static Class<?> getEntityTrackerClass() {
try {
return getMinecraftClass("EntityTracker");
} catch (RuntimeException e) {
FuzzyClassContract entityTrackerContract = FuzzyClassContract.newBuilder().
field(FuzzyFieldContract.newBuilder().
typeDerivedOf(Set.class)).
method(FuzzyMethodContract.newBuilder().
parameterSuperOf(MinecraftReflection.getEntityClass()).
parameterCount(1).
returnTypeVoid()).
method(FuzzyMethodContract.newBuilder().
parameterSuperOf(MinecraftReflection.getEntityClass(), 0).
parameterSuperOf(int.class, 1).
parameterSuperOf(int.class, 2).
parameterCount(3).
returnTypeVoid()).
build();
Field selected = FuzzyReflection.fromClass(MinecraftReflection.getWorldServerClass(), true).
getField(FuzzyFieldContract.newBuilder().
typeMatches(entityTrackerContract).
build()
);
// Go by the defined type of this field
return setMinecraftClass("EntityTracker", selected.getType());
}
}
/**
* Retrieve the NetworkListenThread class (NMS).
* <p>
* Note that this class was removed after Minecraft 1.3.1.
* @return NetworkListenThread class.
*/
public static Class<?> getNetworkListenThreadClass() {
try {
return getMinecraftClass("NetworkListenThread");
} catch (RuntimeException e) {
FuzzyClassContract networkListenContract = FuzzyClassContract.newBuilder().
field(FuzzyFieldContract.newBuilder().
typeDerivedOf(ServerSocket.class)).
field(FuzzyFieldContract.newBuilder().
typeDerivedOf(Thread.class)).
field(FuzzyFieldContract.newBuilder().
typeDerivedOf(List.class)).
method(FuzzyMethodContract.newBuilder().
parameterExactType(getNetServerHandlerClass())).
build();
Field selected = FuzzyReflection.fromClass(MinecraftReflection.getMinecraftServerClass(), true).
getField(FuzzyFieldContract.newBuilder().
typeMatches(networkListenContract).
build()
);
// Go by the defined type of this field
return setMinecraftClass("NetworkListenThread", selected.getType());
}
} }
/** /**
@ -412,6 +901,14 @@ public class MinecraftReflection {
return getCraftBukkitClass("inventory.CraftItemStack"); return getCraftBukkitClass("inventory.CraftItemStack");
} }
/**
* Retrieve the CraftPlayer class.
* @return CraftPlayer class.
*/
public static Class<?> getCraftPlayerClass() {
return getCraftBukkitClass("entity.CraftPlayer");
}
/** /**
* Retrieve a CraftItemStack from a given ItemStack. * Retrieve a CraftItemStack from a given ItemStack.
* @param bukkitItemStack - the Bukkit ItemStack to convert. * @param bukkitItemStack - the Bukkit ItemStack to convert.
@ -547,9 +1044,23 @@ public class MinecraftReflection {
return minecraftPackage.getPackageClass(className); return minecraftPackage.getPackageClass(className);
} }
/**
* Set the class object for the specific Minecraft class.
* @param className - name of the Minecraft class.
* @param clazz - the new class object.
* @return The provided clazz object.
*/
private static Class<?> setMinecraftClass(String className, Class<?> clazz) {
if (minecraftPackage == null)
minecraftPackage = new CachedPackage(getMinecraftPackage());
minecraftPackage.setPackageClass(className, clazz);
return clazz;
}
/** /**
* Retrieve the first class that matches a specified Minecraft name. * Retrieve the first class that matches a specified Minecraft name.
* @param classes - the specific Minecraft class. * @param className - the specific Minecraft class.
* @param aliases - alternative names for this Minecraft class.
* @return Class object. * @return Class object.
* @throws RuntimeException If we are unable to find any of the given classes. * @throws RuntimeException If we are unable to find any of the given classes.
*/ */
@ -600,4 +1111,6 @@ public class MinecraftReflection {
public static String getNetLoginHandlerName() { public static String getNetLoginHandlerName() {
return getNetLoginHandlerClass().getSimpleName(); return getNetLoginHandlerClass().getSimpleName();
} }
} }

Datei anzeigen

@ -6,6 +6,9 @@ import java.io.DataInputStream;
import java.io.DataOutputStream; import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import javax.annotation.Nonnull;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder; import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
@ -28,10 +31,12 @@ public class StreamSerializer {
* and {@link java.io.DataInputStream DataInputStream}. * and {@link java.io.DataInputStream DataInputStream}.
* *
* @param input - the target input stream. * @param input - the target input stream.
* @return The resulting item stack. * @return The resulting item stack, or NULL if the serialized item stack was NULL.
* @throws IOException If the operation failed due to reflection or corrupt data. * @throws IOException If the operation failed due to reflection or corrupt data.
*/ */
public ItemStack deserializeItemStack(DataInputStream input) throws IOException { public ItemStack deserializeItemStack(@Nonnull DataInputStream input) throws IOException {
if (input == null)
throw new IllegalArgumentException("Input stream cannot be NULL.");
if (readItemMethod == null) if (readItemMethod == null)
readItemMethod = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()). readItemMethod = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).
getMethodByParameters("readPacket", getMethodByParameters("readPacket",
@ -51,10 +56,13 @@ public class StreamSerializer {
/** /**
* Deserialize an item stack from a base-64 encoded string. * Deserialize an item stack from a base-64 encoded string.
* @param input - base-64 encoded string. * @param input - base-64 encoded string.
* @return A deserialized item stack. * @return A deserialized item stack, or NULL if the serialized ItemStack was also NULL.
* @throws IOException If the operation failed due to reflection or corrupt data. * @throws IOException If the operation failed due to reflection or corrupt data.
*/ */
public ItemStack deserializeItemStack(String input) throws IOException { public ItemStack deserializeItemStack(@Nonnull String input) throws IOException {
if (input == null)
throw new IllegalArgumentException("Input text cannot be NULL.");
ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64Coder.decodeLines(input)); ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64Coder.decodeLines(input));
return deserializeItemStack(new DataInputStream(inputStream)); return deserializeItemStack(new DataInputStream(inputStream));
@ -65,12 +73,18 @@ public class StreamSerializer {
* <p> * <p>
* To supply a byte array, wrap it in a {@link java.io.ByteArrayOutputStream ByteArrayOutputStream} * To supply a byte array, wrap it in a {@link java.io.ByteArrayOutputStream ByteArrayOutputStream}
* and {@link java.io.DataOutputStream DataOutputStream}. * and {@link java.io.DataOutputStream DataOutputStream}.
* <p>
* Note: An ItemStack can be written to a stream even if it's NULL.
* *
* @param output - the target output stream. * @param output - the target output stream.
* @param stack - the item stack that will be written. * @param stack - the item stack that will be written, or NULL to represent air/nothing.
* @throws IOException If the operation fails due to reflection problems. * @throws IOException If the operation fails due to reflection problems.
*/ */
public void serializeItemStack(DataOutputStream output, ItemStack stack) throws IOException { public void serializeItemStack(@Nonnull DataOutputStream output, ItemStack stack) throws IOException {
if (output == null)
throw new IllegalArgumentException("Output stream cannot be NULL.");
// Get the NMS version of the ItemStack
Object nmsItem = MinecraftReflection.getMinecraftItemStack(stack); Object nmsItem = MinecraftReflection.getMinecraftItemStack(stack);
if (writeItemMethod == null) if (writeItemMethod == null)
@ -87,7 +101,10 @@ public class StreamSerializer {
/** /**
* Serialize an item stack as a base-64 encoded string. * Serialize an item stack as a base-64 encoded string.
* @param stack - the item stack to serialize. * <p>
* Note: An ItemStack can be written to the serialized text even if it's NULL.
*
* @param stack - the item stack to serialize, or NULL to represent air/nothing.
* @return A base-64 representation of the given item stack. * @return A base-64 representation of the given item stack.
* @throws IOException If the operation fails due to reflection problems. * @throws IOException If the operation fails due to reflection problems.
*/ */

Datei anzeigen

@ -36,6 +36,7 @@ import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.nbt.NbtBase; import com.comphenix.protocol.wrappers.nbt.NbtBase;
import com.comphenix.protocol.wrappers.nbt.NbtFactory; import com.comphenix.protocol.wrappers.nbt.NbtFactory;
import com.google.common.base.Objects;
/** /**
* Contains several useful equivalent converters for normal Bukkit types. * Contains several useful equivalent converters for normal Bukkit types.
@ -58,12 +59,104 @@ public class BukkitConverters {
} }
} }
/**
* Represents a typical equivalence converter.
*
* @author Kristian
* @param <T> - type that can be converted.
*/
private static abstract class IgnoreNullConverter<TType> implements EquivalentConverter<TType> {
public final Object getGeneric(Class<?> genericType, TType specific) {
if (specific != null)
return getGenericValue(genericType, specific);
else
return null;
}
/**
* Retrieve a copy of the actual generic value.
* @param genericType - generic type.
* @param specific - the specific type-
* @return A copy of the specific type.
*/
protected abstract Object getGenericValue(Class<?> genericType, TType specific);
@Override
public final TType getSpecific(Object generic) {
if (generic != null)
return getSpecificValue(generic);
else
return null;
}
/**
* Retrieve a copy of the specific type using an instance of the generic type.
* @param generic - generic type.
* @return A copy of the specific type.
*/
protected abstract TType getSpecificValue(Object generic);
@Override
public boolean equals(Object obj) {
// Very short
if (this == obj)
return true;
if (obj == null)
return false;
// See if they're equivalent
if (obj instanceof EquivalentConverter) {
@SuppressWarnings("rawtypes")
EquivalentConverter other = (EquivalentConverter) obj;
return Objects.equal(this.getSpecificType(), other.getSpecificType());
}
return false;
}
}
/**
* Represents a converter that is only valid in a given world.
*
* @author Kristian
* @param <TType> - instance types it converts.
*/
private static abstract class WorldSpecificConverter<TType> extends IgnoreNullConverter<TType> {
protected World world;
/**
* Initialize a new world-specificn converter.
* @param world - the given world.
*/
public WorldSpecificConverter(World world) {
super();
this.world = world;
}
@Override
public boolean equals(Object obj) {
// More shortcuts
if (obj == this)
return true;
if (obj == null)
return false;
// Add another constraint
if (obj instanceof WorldSpecificConverter && super.equals(obj)) {
@SuppressWarnings("rawtypes")
WorldSpecificConverter other = (WorldSpecificConverter) obj;
return Objects.equal(world, other.world);
}
return false;
}
}
public static <T> EquivalentConverter<List<T>> getListConverter(final Class<?> genericItemType, final EquivalentConverter<T> itemConverter) { public static <T> EquivalentConverter<List<T>> getListConverter(final Class<?> genericItemType, final EquivalentConverter<T> itemConverter) {
// Convert to and from the wrapper // Convert to and from the wrapper
return getIgnoreNull(new EquivalentConverter<List<T>>() { return new IgnoreNullConverter<List<T>>() {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public List<T> getSpecific(Object generic) { protected List<T> getSpecificValue(Object generic) {
if (generic instanceof Collection) { if (generic instanceof Collection) {
List<T> items = new ArrayList<T>(); List<T> items = new ArrayList<T>();
@ -83,7 +176,7 @@ public class BukkitConverters {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public Object getGeneric(Class<?> genericType, List<T> specific) { protected Object getGenericValue(Class<?> genericType, List<T> specific) {
Collection<Object> newContainer = (Collection<Object>) DefaultInstances.DEFAULT.getDefault(genericType); Collection<Object> newContainer = (Collection<Object>) DefaultInstances.DEFAULT.getDefault(genericType);
// Convert each object // Convert each object
@ -105,8 +198,7 @@ public class BukkitConverters {
Class<?> dummy = List.class; Class<?> dummy = List.class;
return (Class<List<T>>) dummy; return (Class<List<T>>) dummy;
} }
} };
);
} }
/** /**
@ -114,13 +206,13 @@ public class BukkitConverters {
* @return A watchable object converter. * @return A watchable object converter.
*/ */
public static EquivalentConverter<WrappedWatchableObject> getWatchableObjectConverter() { public static EquivalentConverter<WrappedWatchableObject> getWatchableObjectConverter() {
return getIgnoreNull(new EquivalentConverter<WrappedWatchableObject>() { return new IgnoreNullConverter<WrappedWatchableObject>() {
@Override @Override
public Object getGeneric(Class<?> genericType, WrappedWatchableObject specific) { protected Object getGenericValue(Class<?> genericType, WrappedWatchableObject specific) {
return specific.getHandle(); return specific.getHandle();
} }
public WrappedWatchableObject getSpecific(Object generic) { protected WrappedWatchableObject getSpecificValue(Object generic) {
if (MinecraftReflection.isWatchableObject(generic)) if (MinecraftReflection.isWatchableObject(generic))
return new WrappedWatchableObject(generic); return new WrappedWatchableObject(generic);
else if (generic instanceof WrappedWatchableObject) else if (generic instanceof WrappedWatchableObject)
@ -133,7 +225,7 @@ public class BukkitConverters {
public Class<WrappedWatchableObject> getSpecificType() { public Class<WrappedWatchableObject> getSpecificType() {
return WrappedWatchableObject.class; return WrappedWatchableObject.class;
} }
}); };
} }
/** /**
@ -141,14 +233,14 @@ public class BukkitConverters {
* @return A DataWatcher converter. * @return A DataWatcher converter.
*/ */
public static EquivalentConverter<WrappedDataWatcher> getDataWatcherConverter() { public static EquivalentConverter<WrappedDataWatcher> getDataWatcherConverter() {
return getIgnoreNull(new EquivalentConverter<WrappedDataWatcher>() { return new IgnoreNullConverter<WrappedDataWatcher>() {
@Override @Override
public Object getGeneric(Class<?> genericType, WrappedDataWatcher specific) { protected Object getGenericValue(Class<?> genericType, WrappedDataWatcher specific) {
return specific.getHandle(); return specific.getHandle();
} }
@Override @Override
public WrappedDataWatcher getSpecific(Object generic) { protected WrappedDataWatcher getSpecificValue(Object generic) {
if (MinecraftReflection.isDataWatcher(generic)) if (MinecraftReflection.isDataWatcher(generic))
return new WrappedDataWatcher(generic); return new WrappedDataWatcher(generic);
else if (generic instanceof WrappedDataWatcher) else if (generic instanceof WrappedDataWatcher)
@ -161,7 +253,7 @@ public class BukkitConverters {
public Class<WrappedDataWatcher> getSpecificType() { public Class<WrappedDataWatcher> getSpecificType() {
return WrappedDataWatcher.class; return WrappedDataWatcher.class;
} }
}); };
} }
/** /**
@ -173,9 +265,9 @@ public class BukkitConverters {
if (!hasWorldType) if (!hasWorldType)
return null; return null;
return getIgnoreNull(new EquivalentConverter<WorldType>() { return new IgnoreNullConverter<WorldType>() {
@Override @Override
public Object getGeneric(Class<?> genericType, WorldType specific) { protected Object getGenericValue(Class<?> genericType, WorldType specific) {
try { try {
if (worldTypeGetType == null) if (worldTypeGetType == null)
worldTypeGetType = MinecraftReflection.getWorldTypeClass().getMethod("getType", String.class); worldTypeGetType = MinecraftReflection.getWorldTypeClass().getMethod("getType", String.class);
@ -189,7 +281,7 @@ public class BukkitConverters {
} }
@Override @Override
public WorldType getSpecific(Object generic) { protected WorldType getSpecificValue(Object generic) {
try { try {
if (worldTypeName == null) if (worldTypeName == null)
worldTypeName = MinecraftReflection.getWorldTypeClass().getMethod("name"); worldTypeName = MinecraftReflection.getWorldTypeClass().getMethod("name");
@ -207,7 +299,7 @@ public class BukkitConverters {
public Class<WorldType> getSpecificType() { public Class<WorldType> getSpecificType() {
return WorldType.class; return WorldType.class;
} }
}); };
} }
/** /**
@ -215,14 +307,14 @@ public class BukkitConverters {
* @return An equivalent converter for NBT. * @return An equivalent converter for NBT.
*/ */
public static EquivalentConverter<NbtBase<?>> getNbtConverter() { public static EquivalentConverter<NbtBase<?>> getNbtConverter() {
return getIgnoreNull(new EquivalentConverter<NbtBase<?>>() { return new IgnoreNullConverter<NbtBase<?>>() {
@Override @Override
public Object getGeneric(Class<?> genericType, NbtBase<?> specific) { protected Object getGenericValue(Class<?> genericType, NbtBase<?> specific) {
return NbtFactory.fromBase(specific).getHandle(); return NbtFactory.fromBase(specific).getHandle();
} }
@Override @Override
public NbtBase<?> getSpecific(Object generic) { protected NbtBase<?> getSpecificValue(Object generic) {
return NbtFactory.fromNMS(generic); return NbtFactory.fromNMS(generic);
} }
@ -233,7 +325,7 @@ public class BukkitConverters {
Class<?> dummy = NbtBase.class; Class<?> dummy = NbtBase.class;
return (Class<NbtBase<?>>) dummy; return (Class<NbtBase<?>>) dummy;
} }
}); };
} }
/** /**
@ -242,27 +334,25 @@ public class BukkitConverters {
* @return A converter between the underlying NMS entity and Bukkit's wrapper. * @return A converter between the underlying NMS entity and Bukkit's wrapper.
*/ */
public static EquivalentConverter<Entity> getEntityConverter(World world) { public static EquivalentConverter<Entity> getEntityConverter(World world) {
final World container = world;
final WeakReference<ProtocolManager> managerRef = final WeakReference<ProtocolManager> managerRef =
new WeakReference<ProtocolManager>(ProtocolLibrary.getProtocolManager()); new WeakReference<ProtocolManager>(ProtocolLibrary.getProtocolManager());
return getIgnoreNull(new EquivalentConverter<Entity>() { return new WorldSpecificConverter<Entity>(world) {
@Override @Override
public Object getGeneric(Class<?> genericType, Entity specific) { public Object getGenericValue(Class<?> genericType, Entity specific) {
// Simple enough // Simple enough
return specific.getEntityId(); return specific.getEntityId();
} }
@Override @Override
public Entity getSpecific(Object generic) { public Entity getSpecificValue(Object generic) {
try { try {
Integer id = (Integer) generic; Integer id = (Integer) generic;
ProtocolManager manager = managerRef.get(); ProtocolManager manager = managerRef.get();
// Use the // Use the entity ID to get a reference to the entity
if (id != null && manager != null) { if (id != null && manager != null) {
return manager.getEntityFromID(container, id); return manager.getEntityFromID(world, id);
} else { } else {
return null; return null;
} }
@ -276,7 +366,7 @@ public class BukkitConverters {
public Class<Entity> getSpecificType() { public Class<Entity> getSpecificType() {
return Entity.class; return Entity.class;
} }
}); };
} }
/** /**
@ -284,13 +374,14 @@ public class BukkitConverters {
* @return Item stack converter. * @return Item stack converter.
*/ */
public static EquivalentConverter<ItemStack> getItemStackConverter() { public static EquivalentConverter<ItemStack> getItemStackConverter() {
return getIgnoreNull(new EquivalentConverter<ItemStack>() { return new IgnoreNullConverter<ItemStack>() {
public Object getGeneric(Class<?> genericType, ItemStack specific) { @Override
protected Object getGenericValue(Class<?> genericType, ItemStack specific) {
return MinecraftReflection.getMinecraftItemStack(specific); return MinecraftReflection.getMinecraftItemStack(specific);
} }
@Override @Override
public ItemStack getSpecific(Object generic) { protected ItemStack getSpecificValue(Object generic) {
return MinecraftReflection.getBukkitItemStack(generic); return MinecraftReflection.getBukkitItemStack(generic);
} }
@ -298,7 +389,7 @@ public class BukkitConverters {
public Class<ItemStack> getSpecificType() { public Class<ItemStack> getSpecificType() {
return ItemStack.class; return ItemStack.class;
} }
}); };
} }
/** /**
@ -308,20 +399,15 @@ public class BukkitConverters {
*/ */
public static <TType> EquivalentConverter<TType> getIgnoreNull(final EquivalentConverter<TType> delegate) { public static <TType> EquivalentConverter<TType> getIgnoreNull(final EquivalentConverter<TType> delegate) {
// Automatically wrap all parameters to the delegate with a NULL check // Automatically wrap all parameters to the delegate with a NULL check
return new EquivalentConverter<TType>() { return new IgnoreNullConverter<TType>() {
public Object getGeneric(Class<?> genericType, TType specific) { @Override
if (specific != null) public Object getGenericValue(Class<?> genericType, TType specific) {
return delegate.getGeneric(genericType, specific); return delegate.getGeneric(genericType, specific);
else
return null;
} }
@Override @Override
public TType getSpecific(Object generic) { public TType getSpecificValue(Object generic) {
if (generic != null) return delegate.getSpecific(generic);
return delegate.getSpecific(generic);
else
return null;
} }
@Override @Override

Datei anzeigen

@ -551,8 +551,17 @@ public class WrappedDataWatcher implements Iterable<WrappedWatchableObject> {
List<Method> candidates = fuzzy.getMethodListByParameters(Void.TYPE, List<Method> candidates = fuzzy.getMethodListByParameters(Void.TYPE,
new Class<?>[] { int.class, Object.class}); new Class<?>[] { int.class, Object.class});
for (Method method : candidates) { // Load the get-method
try {
getKeyValueMethod = fuzzy.getMethodByParameters(
"getWatchableObject", MinecraftReflection.getWatchableObjectClass(), new Class[] { int.class });
getKeyValueMethod.setAccessible(true);
} catch (IllegalArgumentException e) {
// Use the fallback method
}
for (Method method : candidates) {
if (!method.getName().startsWith("watch")) { if (!method.getName().startsWith("watch")) {
createKeyValueMethod = method; createKeyValueMethod = method;
} else { } else {
@ -569,15 +578,21 @@ public class WrappedDataWatcher implements Iterable<WrappedWatchableObject> {
} else { } else {
throw new IllegalStateException("Unable to find create and update watchable object. Update ProtocolLib."); throw new IllegalStateException("Unable to find create and update watchable object. Update ProtocolLib.");
} }
}
// Load the get-method // Be a little scientist - see if this in fact IS the right way around
try { try {
getKeyValueMethod = fuzzy.getMethodByParameters( WrappedDataWatcher watcher = new WrappedDataWatcher();
"getWatchableObject", ".*WatchableObject", new String[] { int.class.getName() }); watcher.setObject(0, 0);
getKeyValueMethod.setAccessible(true); watcher.setObject(0, 1);
} catch (IllegalArgumentException e) {
// Use fallback method if (watcher.getInteger(0) != 1) {
throw new IllegalStateException("This cannot be!");
}
} catch (Exception e) {
// Nope
updateKeyValueMethod = candidates.get(0);
createKeyValueMethod = candidates.get(1);
}
} }
} }

Datei anzeigen

@ -1,5 +1,5 @@
name: ProtocolLib name: ProtocolLib
version: 2.1.0 version: 2.2.0
description: Provides read/write access to the Minecraft protocol. description: Provides read/write access to the Minecraft protocol.
author: Comphenix author: Comphenix
website: http://www.comphenix.net/ProtocolLib website: http://www.comphenix.net/ProtocolLib