Merge branch '1.6.2'

This commit is contained in:
Kristian S. Stangeland 2013-07-11 00:12:45 +02:00
commit cd0f8a6fa5
39 changed files with 3633 additions and 2449 deletions

View File

@ -11,12 +11,12 @@
</arguments> </arguments>
</buildCommand> </buildCommand>
<buildCommand> <buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name> <name>net.sourceforge.metrics.builder</name>
<arguments> <arguments>
</arguments> </arguments>
</buildCommand> </buildCommand>
<buildCommand> <buildCommand>
<name>net.sourceforge.metrics.builder</name> <name>org.eclipse.m2e.core.maven2Builder</name>
<arguments> <arguments>
</arguments> </arguments>
</buildCommand> </buildCommand>

View File

@ -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.4.5</version> <version>2.4.8-SNAPSHOT</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>
@ -29,6 +29,11 @@
<id>bukkit-rep</id> <id>bukkit-rep</id>
<url>http://repo.bukkit.org/content/groups/public</url> <url>http://repo.bukkit.org/content/groups/public</url>
</repository> </repository>
<repository>
<id>comphenix-releases</id>
<name>Comphenix Maven Releases</name>
<url>http://repo.comphenix.net/content/repositories/releases/</url>
</repository>
</repositories> </repositories>
<build> <build>
@ -200,10 +205,16 @@
<version>2.2.2</version> <version>2.2.2</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>com.comphenix.executors</groupId>
<artifactId>BukkitExecutors</artifactId>
<version>1.0.0</version>
<scope>compile</scope>
</dependency>
<dependency> <dependency>
<groupId>org.bukkit</groupId> <groupId>org.bukkit</groupId>
<artifactId>craftbukkit</artifactId> <artifactId>craftbukkit</artifactId>
<version>1.5.1-R0.2-SNAPSHOT</version> <version>1.6.2-R0.1-SNAPSHOT</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>

View File

@ -95,6 +95,7 @@ public final class Packets {
public static final int MOB_EFFECT = 41; public static final int MOB_EFFECT = 41;
public static final int REMOVE_MOB_EFFECT = 42; public static final int REMOVE_MOB_EFFECT = 42;
public static final int SET_EXPERIENCE = 43; public static final int SET_EXPERIENCE = 43;
public static final int UPDATE_ATTRIBUTES = 44;
public static final int MAP_CHUNK = 51; public static final int MAP_CHUNK = 51;
public static final int MULTI_BLOCK_CHANGE = 52; public static final int MULTI_BLOCK_CHANGE = 52;
public static final int BLOCK_CHANGE = 53; public static final int BLOCK_CHANGE = 53;
@ -199,6 +200,7 @@ public final class Packets {
public static final int BLOCK_ITEM_SWITCH = 16; public static final int BLOCK_ITEM_SWITCH = 16;
public static final int ARM_ANIMATION = 18; public static final int ARM_ANIMATION = 18;
public static final int ENTITY_ACTION = 19; public static final int ENTITY_ACTION = 19;
public static final int PLAYER_INPUT = 27;
public static final int CLOSE_WINDOW = 101; public static final int CLOSE_WINDOW = 101;
public static final int WINDOW_CLICK = 102; public static final int WINDOW_CLICK = 102;
public static final int TRANSACTION = 106; public static final int TRANSACTION = 106;

View File

@ -41,6 +41,7 @@ import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report; import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType; import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.DelayedSingleTask; import com.comphenix.protocol.injector.DelayedSingleTask;
import com.comphenix.protocol.injector.InternalManager;
import com.comphenix.protocol.injector.PacketFilterManager; import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
import com.comphenix.protocol.metrics.Statistics; import com.comphenix.protocol.metrics.Statistics;
@ -85,7 +86,7 @@ public class ProtocolLibrary extends JavaPlugin {
/** /**
* The maximum version ProtocolLib has been tested with, * The maximum version ProtocolLib has been tested with,
*/ */
private static final String MAXIMUM_MINECRAFT_VERSION = "1.5.2"; private static final String MAXIMUM_MINECRAFT_VERSION = "1.6.1";
/** /**
* The number of milliseconds per second. * The number of milliseconds per second.
@ -95,7 +96,7 @@ public class ProtocolLibrary extends JavaPlugin {
private static final String PERMISSION_INFO = "protocol.info"; private static final String PERMISSION_INFO = "protocol.info";
// There should only be one protocol manager, so we'll make it static // There should only be one protocol manager, so we'll make it static
private static PacketFilterManager protocolManager; private static InternalManager protocolManager;
// Error reporter // Error reporter
private static ErrorReporter reporter = new BasicErrorReporter(); private static ErrorReporter reporter = new BasicErrorReporter();
@ -172,8 +173,14 @@ public class ProtocolLibrary extends JavaPlugin {
updater = new Updater(this, logger, "protocollib", getFile(), "protocol.info"); updater = new Updater(this, logger, "protocollib", getFile(), "protocol.info");
unhookTask = new DelayedSingleTask(this); unhookTask = new DelayedSingleTask(this);
protocolManager = new PacketFilterManager( protocolManager = PacketFilterManager.newBuilder().
getClassLoader(), getServer(), this, version, unhookTask, reporter); classLoader(getClassLoader()).
server(getServer()).
library(this).
minecraftVersion(version).
unhookTask(unhookTask).
reporter(reporter).
build();
// Setup error reporter // Setup error reporter
detailedReporter.addGlobalParameter("manager", protocolManager); detailedReporter.addGlobalParameter("manager", protocolManager);
@ -216,7 +223,7 @@ public class ProtocolLibrary extends JavaPlugin {
@Override @Override
protected Report filterReport(Object sender, Report report, boolean detailed) { protected Report filterReport(Object sender, Report report, boolean detailed) {
String canonicalName = ReportType.getReportName(sender.getClass(), report.getType()); String canonicalName = ReportType.getReportName(sender, report.getType());
String reportName = Iterables.getLast(Splitter.on("#").split(canonicalName)).toUpperCase(); String reportName = Iterables.getLast(Splitter.on("#").split(canonicalName)).toUpperCase();
if (config != null && config.getModificationCount() != lastModCount) { if (config != null && config.getModificationCount() != lastModCount) {
@ -302,9 +309,6 @@ public class ProtocolLibrary extends JavaPlugin {
return; return;
} }
// Perform logic when the world has loaded
protocolManager.postWorldLoaded();
// Initialize background compiler // Initialize background compiler
if (backgroundCompiler == null && config.isBackgroundCompilerEnabled()) { if (backgroundCompiler == null && config.isBackgroundCompilerEnabled()) {
backgroundCompiler = new BackgroundCompiler(getClassLoader(), reporter); backgroundCompiler = new BackgroundCompiler(getClassLoader(), reporter);

View File

@ -69,12 +69,12 @@ public class AsyncFilterManager implements AsynchronousManager {
// Default scheduler // Default scheduler
private final BukkitScheduler scheduler; private final BukkitScheduler scheduler;
// Our protocol manager
private final ProtocolManager manager;
// Current packet index // Current packet index
private final AtomicInteger currentSendingIndex = new AtomicInteger(); private final AtomicInteger currentSendingIndex = new AtomicInteger();
// Our protocol manager
private ProtocolManager manager;
/** /**
* Initialize a asynchronous filter manager. * Initialize a asynchronous filter manager.
* <p> * <p>
@ -83,7 +83,7 @@ public class AsyncFilterManager implements AsynchronousManager {
* @param scheduler - task scheduler. * @param scheduler - task scheduler.
* @param manager - protocol manager. * @param manager - protocol manager.
*/ */
public AsyncFilterManager(ErrorReporter reporter, BukkitScheduler scheduler, ProtocolManager manager) { public AsyncFilterManager(ErrorReporter reporter, BukkitScheduler scheduler) {
// Initialize timeout listeners // Initialize timeout listeners
this.serverTimeoutListeners = new SortedPacketListenerList(); this.serverTimeoutListeners = new SortedPacketListenerList();
this.clientTimeoutListeners = new SortedPacketListenerList(); this.clientTimeoutListeners = new SortedPacketListenerList();
@ -95,12 +95,26 @@ public class AsyncFilterManager implements AsynchronousManager {
this.playerSendingHandler.initializeScheduler(); this.playerSendingHandler.initializeScheduler();
this.scheduler = scheduler; this.scheduler = scheduler;
this.manager = manager;
this.reporter = reporter; this.reporter = reporter;
this.mainThread = Thread.currentThread(); this.mainThread = Thread.currentThread();
} }
/**
* Retrieve the protocol manager.
* @return The protocol manager.
*/
public ProtocolManager getManager() {
return manager;
}
/**
* Set the associated protocol manager.
* @param manager - the new manager.
*/
public void setManager(ProtocolManager manager) {
this.manager = manager;
}
@Override @Override
public AsyncListenerHandler registerAsyncHandler(PacketListener listener) { public AsyncListenerHandler registerAsyncHandler(PacketListener listener) {
return registerAsyncHandler(listener, true); return registerAsyncHandler(listener, true);

View File

@ -59,7 +59,7 @@ public class DelegatedErrorReporter implements ErrorReporter {
* Invoked before an error report is passed on to the underlying error reporter. * Invoked before an error report is passed on to the underlying error reporter.
* <p> * <p>
* To cancel a report, return NULL. * To cancel a report, return NULL.
* @param sender - the sender component. * @param sender - the sender instance or class.
* @param report - the error report. * @param report - the error report.
* @param detailed - whether or not the report will be displayed in detail. * @param detailed - whether or not the report will be displayed in detail.
* @return The report to pass on, or NULL to cancel it. * @return The report to pass on, or NULL to cancel it.

View File

@ -224,7 +224,7 @@ public class DetailedErrorReporter implements ErrorReporter {
*/ */
private String getSenderName(Object sender) { private String getSenderName(Object sender) {
if (sender != null) if (sender != null)
return sender.getClass().getSimpleName(); return ReportType.getSenderClass(sender).getSimpleName();
else else
return "NULL"; return "NULL";
} }
@ -345,7 +345,7 @@ public class DetailedErrorReporter implements ErrorReporter {
// We can't only rely on toString. // We can't only rely on toString.
if (value == null) { if (value == null) {
return "[NULL]"; return "[NULL]";
} if (isSimpleType(value)) { } if (isSimpleType(value) || value instanceof Class<?>) {
return value.toString(); return value.toString();
} else { } else {
try { try {

View File

@ -46,6 +46,39 @@ public class ReportType {
return errorFormat; return errorFormat;
} }
/**
* Retrieve the class of the given sender.
* <p>
* If the sender is already a Class, we return it.
* @param sender - the sender to look up.
* @return The class of the sender.
*/
public static Class<?> getSenderClass(Object sender) {
if (sender == null)
throw new IllegalArgumentException("sender cannot be NUll.");
else if (sender instanceof Class<?>)
return (Class<?>) sender;
else
return sender.getClass();
}
/**
* Retrieve the full canonical name of a given report type.
* <p>
* Note that the sender may be a class (for static callers), in which
* case it will be used directly instead of its getClass() method.
* <p>
* It is thus not advisable for class classes to report reports.
* @param sender - the sender, or its class.
* @param type - the report type.
* @return The full canonical name.
*/
public static String getReportName(Object sender, ReportType type) {
if (sender == null)
throw new IllegalArgumentException("sender cannot be NUll.");
return getReportName(getSenderClass(sender), type);
}
/** /**
* Retrieve the full canonical name of a given report type. * Retrieve the full canonical name of a given report type.
* <p> * <p>
@ -54,7 +87,7 @@ public class ReportType {
* @param type - the report instance. * @param type - the report instance.
* @return The full canonical name. * @return The full canonical name.
*/ */
public static String getReportName(Class<?> sender, ReportType type) { private static String getReportName(Class<?> sender, ReportType type) {
if (sender == null) if (sender == null)
throw new IllegalArgumentException("sender cannot be NUll."); throw new IllegalArgumentException("sender cannot be NUll.");

View File

@ -17,7 +17,9 @@
package com.comphenix.protocol.events; package com.comphenix.protocol.events;
import java.io.DataInput;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream; import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
@ -49,6 +51,7 @@ import com.comphenix.protocol.reflect.cloning.CollectionCloner;
import com.comphenix.protocol.reflect.cloning.FieldCloner; import com.comphenix.protocol.reflect.cloning.FieldCloner;
import com.comphenix.protocol.reflect.cloning.ImmutableDetector; import com.comphenix.protocol.reflect.cloning.ImmutableDetector;
import com.comphenix.protocol.reflect.cloning.AggregateCloner.BuilderParameters; import com.comphenix.protocol.reflect.cloning.AggregateCloner.BuilderParameters;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.reflect.instances.DefaultInstances; import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.StreamSerializer; import com.comphenix.protocol.utility.StreamSerializer;
@ -456,7 +459,7 @@ public class PacketContainer implements Serializable {
try { try {
// Call the write-method // Call the write-method
getMethodLazily(writeMethods, handle.getClass(), "write", DataOutputStream.class). getMethodLazily(writeMethods, handle.getClass(), "write", DataOutput.class).
invoke(handle, new DataOutputStream(output)); invoke(handle, new DataOutputStream(output));
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
@ -483,7 +486,7 @@ public class PacketContainer implements Serializable {
// Call the read method // Call the read method
try { try {
getMethodLazily(readMethods, handle.getClass(), "read", DataInputStream.class). getMethodLazily(readMethods, handle.getClass(), "read", DataInput.class).
invoke(handle, new DataInputStream(input)); invoke(handle, new DataInputStream(input));
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
@ -513,7 +516,12 @@ public class PacketContainer implements Serializable {
// Atomic operation // Atomic operation
if (method == null) { if (method == null) {
Method initialized = FuzzyReflection.fromClass(handleClass).getMethodByParameters(methodName, parameterClass); Method initialized = FuzzyReflection.fromClass(handleClass).getMethod(
FuzzyMethodContract.newBuilder().
parameterCount(1).
parameterDerivedOf(parameterClass).
returnTypeVoid().
build());
method = lookup.putIfAbsent(handleClass, initialized); method = lookup.putIfAbsent(handleClass, initialized);
// Use our version if we succeeded // Use our version if we succeeded

View File

@ -0,0 +1,389 @@
package com.comphenix.protocol.injector;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.annotation.Nonnull;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import com.comphenix.protocol.AsynchronousManager;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
/**
* A protocol manager that delays all packet listener registrations and unregistrations until
* an underlying protocol manager can be constructed.
*
* @author Kristian
*/
public class DelayedPacketManager implements ProtocolManager, InternalManager {
// Registering packet IDs that are not supported
public static final ReportType REPORT_CANNOT_SEND_QUEUED_PACKET = new ReportType("Cannot send queued packet %s.");
public static final ReportType REPORT_CANNOT_REGISTER_QUEUED_LISTENER = new ReportType("Cannot register queued listener %s.");
/**
* Represents a packet that will be transmitted later.
* @author Kristian
*
*/
private static class QueuedPacket {
private final Player player;
private final PacketContainer packet;
private final boolean filtered;
private final ConnectionSide side;
public QueuedPacket(Player player, PacketContainer packet, boolean filtered, ConnectionSide side) {
this.player = player;
this.packet = packet;
this.filtered = filtered;
this.side = side;
}
/**
* Retrieve the packet that will be transmitted or receieved.
* @return The packet.
*/
public PacketContainer getPacket() {
return packet;
}
/**
* Retrieve the player that will send or recieve the packet.
* @return The source.
*/
public Player getPlayer() {
return player;
}
/**
* Retrieve whether or not the packet will the sent or received.
* @return The connection side.
*/
public ConnectionSide getSide() {
return side;
}
/**
* Determine if the packet should be intercepted by packet listeners.
* @return TRUE if it should, FALSE otherwise.
*/
public boolean isFiltered() {
return filtered;
}
}
private volatile InternalManager delegate;
// Packet listeners that will be registered
private final Set<PacketListener> queuedListeners = Sets.newSetFromMap(Maps.<PacketListener, Boolean>newConcurrentMap());
private final List<QueuedPacket> queuedPackets = Collections.synchronizedList(Lists.<QueuedPacket>newArrayList());
private AsynchronousManager asyncManager;
private ErrorReporter reporter;
// The current hook
private PlayerInjectHooks hook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
// If we have been closed
private boolean closed;
// Queued registration
private PluginManager queuedManager;
private Plugin queuedPlugin;
public DelayedPacketManager(@Nonnull ErrorReporter reporter) {
Preconditions.checkNotNull(reporter, "reporter cannot be NULL.");
this.reporter = reporter;
}
/**
* Retrieve the underlying protocol manager.
* @return The underlying manager.
*/
public InternalManager getDelegate() {
return delegate;
}
/**
* Update the delegate to the underlying manager.
* <p>
* This will prompt this packet manager to immediately transmit and
* register all queued packets an listeners.
* @param delegate - delegate to the new manager.
*/
protected void setDelegate(InternalManager delegate) {
this.delegate = delegate;
if (delegate != null) {
// Update the hook if needed
if (!Objects.equal(delegate.getPlayerHook(), hook)) {
delegate.setPlayerHook(hook);
}
// Register events as well
if (queuedManager != null && queuedPlugin != null) {
delegate.registerEvents(queuedManager, queuedPlugin);
}
for (PacketListener listener : queuedListeners) {
try {
delegate.addPacketListener(listener);
} catch (IllegalArgumentException e) {
// Inform about this plugin error
reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_REGISTER_QUEUED_LISTENER).
callerParam(delegate).messageParam(listener).error(e));
}
}
synchronized (queuedPackets) {
for (QueuedPacket packet : queuedPackets) {
try {
// Attempt to send it now
switch (packet.getSide()) {
case CLIENT_SIDE:
delegate.recieveClientPacket(packet.getPlayer(), packet.getPacket(), packet.isFiltered());
break;
case SERVER_SIDE:
delegate.sendServerPacket(packet.getPlayer(), packet.getPacket(), packet.isFiltered());
break;
default:
}
} catch (Exception e) {
// Inform about this plugin error
reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_SEND_QUEUED_PACKET).
callerParam(delegate).messageParam(packet).error(e));
}
}
}
// Don't keep this around anymore
queuedListeners.clear();
queuedPackets.clear();
}
}
@Override
public void setPlayerHook(PlayerInjectHooks playerHook) {
this.hook = playerHook;
}
@Override
public PlayerInjectHooks getPlayerHook() {
return hook;
}
@Override
public void sendServerPacket(Player reciever, PacketContainer packet) throws InvocationTargetException {
sendServerPacket(reciever, packet, true);
}
@Override
public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) throws InvocationTargetException {
if (delegate != null) {
delegate.sendServerPacket(reciever, packet, filters);
} else {
queuedPackets.add(new QueuedPacket(reciever, packet, filters, ConnectionSide.SERVER_SIDE));
}
}
@Override
public void recieveClientPacket(Player sender, PacketContainer packet) throws IllegalAccessException, InvocationTargetException {
recieveClientPacket(sender, packet, true);
}
@Override
public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters) throws IllegalAccessException, InvocationTargetException {
if (delegate != null) {
delegate.recieveClientPacket(sender, packet, filters);
} else {
queuedPackets.add(new QueuedPacket(sender, packet, filters, ConnectionSide.CLIENT_SIDE));
}
}
@Override
public ImmutableSet<PacketListener> getPacketListeners() {
if (delegate != null)
return delegate.getPacketListeners();
else
return ImmutableSet.copyOf(queuedListeners);
}
@Override
public void addPacketListener(PacketListener listener) {
if (delegate != null)
delegate.addPacketListener(listener);
else
queuedListeners.add(listener);
}
@Override
public void removePacketListener(PacketListener listener) {
if (delegate != null)
delegate.removePacketListener(listener);
else
queuedListeners.remove(listener);
}
@Override
public void removePacketListeners(Plugin plugin) {
if (delegate != null) {
delegate.removePacketListeners(plugin);
} else {
for (Iterator<PacketListener> it = queuedListeners.iterator(); it.hasNext(); ) {
// Remove listeners of the same plugin
if (Objects.equal(it.next().getPlugin(), plugin)) {
it.remove();
}
}
}
}
@Override
public PacketContainer createPacket(int id) {
if (delegate != null)
return delegate.createPacket(id);
return createPacket(id, true);
}
@Override
public PacketContainer createPacket(int id, boolean forceDefaults) {
if (delegate != null) {
return delegate.createPacket(id);
} else {
// Fallback implementation
PacketContainer packet = new PacketContainer(id);
// Use any default values if possible
if (forceDefaults) {
try {
packet.getModifier().writeDefaults();
} catch (FieldAccessException e) {
throw new RuntimeException("Security exception.", e);
}
}
return packet;
}
}
@Override
public PacketConstructor createPacketConstructor(int id, Object... arguments) {
if (delegate != null)
return delegate.createPacketConstructor(id, arguments);
else
return PacketConstructor.DEFAULT.withPacket(id, arguments);
}
@Override
public Set<Integer> getSendingFilters() {
if (delegate != null) {
return delegate.getSendingFilters();
} else {
// Linear scan is fast enough here
Set<Integer> sending = Sets.newHashSet();
for (PacketListener listener : queuedListeners) {
sending.addAll(listener.getSendingWhitelist().getWhitelist());
}
return sending;
}
}
@Override
public Set<Integer> getReceivingFilters() {
if (delegate != null) {
return delegate.getReceivingFilters();
} else {
Set<Integer> recieving = Sets.newHashSet();
for (PacketListener listener : queuedListeners) {
recieving.addAll(listener.getReceivingWhitelist().getWhitelist());
}
return recieving;
}
}
@Override
public void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException {
if (delegate != null)
delegate.updateEntity(entity, observers);
else
EntityUtilities.updateEntity(entity, observers);
}
@Override
public Entity getEntityFromID(World container, int id) throws FieldAccessException {
if (delegate != null)
return delegate.getEntityFromID(container, id);
else
return EntityUtilities.getEntityFromID(container, id);
}
@Override
public List<Player> getEntityTrackers(Entity entity) throws FieldAccessException {
if (delegate != null)
return delegate.getEntityTrackers(entity);
else
return EntityUtilities.getEntityTrackers(entity);
}
@Override
public boolean isClosed() {
return closed || (delegate != null && delegate.isClosed());
}
@Override
public AsynchronousManager getAsynchronousManager() {
if (delegate != null)
return delegate.getAsynchronousManager();
else
return asyncManager;
}
/**
* Update the asynchronous manager. This must be set.
* @param asyncManager - the asynchronous manager.
*/
public void setAsynchronousManager(AsynchronousManager asyncManager) {
this.asyncManager = asyncManager;
}
@Override
public void registerEvents(PluginManager manager, Plugin plugin) {
if (delegate != null) {
delegate.registerEvents(manager, plugin);
} else {
queuedManager = manager;
queuedPlugin = plugin;
}
}
@Override
public void close() {
if (delegate != null)
delegate.close();
closed = true;
}
}

View File

@ -0,0 +1,38 @@
package com.comphenix.protocol.injector;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
/**
* Yields access to the internal hook configuration.
*
* @author Kristian
*/
public interface InternalManager extends ProtocolManager {
/**
* Retrieves how the server packets are read.
* @return Injection method for reading server packets.
*/
public PlayerInjectHooks getPlayerHook();
/**
* Sets how the server packets are read.
* @param playerHook - the new injection method for reading server packets.
*/
public void setPlayerHook(PlayerInjectHooks playerHook);
/**
* Register this protocol manager on Bukkit.
* @param manager - Bukkit plugin manager that provides player join/leave events.
* @param plugin - the parent plugin.
*/
public void registerEvents(PluginManager manager, final Plugin plugin);
/**
* Called when ProtocolLib is closing.
*/
public void close();
}

View File

@ -0,0 +1,255 @@
package com.comphenix.protocol.injector;
import javax.annotation.Nonnull;
import org.bukkit.Server;
import org.bukkit.event.world.WorldInitEvent;
import org.bukkit.plugin.Plugin;
import com.comphenix.executors.BukkitFutures;
import com.comphenix.protocol.async.AsyncFilterManager;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.player.InjectedServerConnection;
import com.comphenix.protocol.injector.spigot.SpigotPacketInjector;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
public class PacketFilterBuilder {
public static final ReportType REPORT_TEMPORARY_EVENT_ERROR = new ReportType("Unable to register or handle temporary event.");
private ClassLoader classLoader;
private Server server;
private Plugin library;
private MinecraftVersion mcVersion;
private DelayedSingleTask unhookTask;
private ErrorReporter reporter;
// Whether or not we need to enable Netty
private AsyncFilterManager asyncManager;
private boolean nettyEnabled;
/**
* Update the current class loader.
* @param classLoader - current class loader.
* @return This builder, for chaining.
*/
public PacketFilterBuilder classLoader(@Nonnull ClassLoader classLoader) {
if (classLoader == null)
throw new IllegalArgumentException("classLoader cannot be NULL.");
this.classLoader = classLoader;
return this;
}
/**
* Set the current server.
* @param server - current server.
* @return This builder, for chaining.
*/
public PacketFilterBuilder server(@Nonnull Server server) {
if (server == null)
throw new IllegalArgumentException("server cannot be NULL.");
this.server = server;
return this;
}
/**
* Set a reference to the plugin instance of ProtocolLib.
* @param library - plugin instance.
* @return This builder, for chaining.
*/
public PacketFilterBuilder library(@Nonnull Plugin library) {
if (library == null)
throw new IllegalArgumentException("library cannot be NULL.");
this.library = library;
return this;
}
/**
* Set the current Minecraft version.
* @param mcVersion - Minecraft version.
* @return This builder, for chaining.
*/
public PacketFilterBuilder minecraftVersion(@Nonnull MinecraftVersion mcVersion) {
if (mcVersion == null)
throw new IllegalArgumentException("minecraftVersion cannot be NULL.");
this.mcVersion = mcVersion;
return this;
}
/**
* Set the task used to delay unhooking when ProtocolLib is no in use.
* @param unhookTask - the unhook task.
* @return This builder, for chaining.
*/
public PacketFilterBuilder unhookTask(@Nonnull DelayedSingleTask unhookTask) {
if (unhookTask == null)
throw new IllegalArgumentException("unhookTask cannot be NULL.");
this.unhookTask = unhookTask;
return this;
}
/**
* Set the error reporter.
* @param reporter - new error reporter.
* @return This builder, for chaining.
*/
public PacketFilterBuilder reporter(@Nonnull ErrorReporter reporter) {
if (reporter == null)
throw new IllegalArgumentException("reporter cannot be NULL.");
this.reporter = reporter;
return this;
}
/**
* Determine if we should prepare to hook Netty in Spigot.
* <p>
* This is calculated in the {@link #build()} method.
* @return TRUE if we should, FALSE otherwise.
*/
public boolean isNettyEnabled() {
return nettyEnabled;
}
/**
* Retrieve the class loader set in this builder.
* @return The class loader.
*/
public ClassLoader getClassLoader() {
return classLoader;
}
/**
* Retrieve the current CraftBukkit server.
* @return Current server.
*/
public Server getServer() {
return server;
}
/**
* Retrieve a reference to the current ProtocolLib instance.
* @return ProtocolLib.
*/
public Plugin getLibrary() {
return library;
}
/**
* Retrieve the current Minecraft version.
* @return Current version.
*/
public MinecraftVersion getMinecraftVersion() {
return mcVersion;
}
/**
* Retrieve the task that is used to delay unhooking when ProtocolLib is no in use.
* @return The unhook task.
*/
public DelayedSingleTask getUnhookTask() {
return unhookTask;
}
/**
* Retrieve the error reporter.
* @return Error reporter.
*/
public ErrorReporter getReporter() {
return reporter;
}
/**
* Retrieve the asynchronous manager.
* <p>
* This is first constructed the {@link #build()} method.
* @return The asynchronous manager.
*/
public AsyncFilterManager getAsyncManager() {
return asyncManager;
}
/**
* Create a new packet filter manager.
* @return A new packet filter manager.
*/
public InternalManager build() {
if (reporter == null)
throw new IllegalArgumentException("reporter cannot be NULL.");
if (classLoader == null)
throw new IllegalArgumentException("classLoader cannot be NULL.");
asyncManager = new AsyncFilterManager(reporter, server.getScheduler());
nettyEnabled = false;
// Spigot
if (SpigotPacketInjector.canUseSpigotListener()) {
// If the server hasn't loaded yet - wait
if (InjectedServerConnection.getServerConnection(reporter, server) == null) {
// We need to delay this until we know if Netty is enabled
final DelayedPacketManager delayed = new DelayedPacketManager(reporter);
// They must reference each other
delayed.setAsynchronousManager(asyncManager);
asyncManager.setManager(delayed);
Futures.addCallback(BukkitFutures.nextEvent(library, WorldInitEvent.class),
new FutureCallback<WorldInitEvent>() {
@Override
public void onSuccess(WorldInitEvent event) {
// Nevermind
if (delayed.isClosed())
return;
try {
registerSpigot(delayed);
} catch (Exception e) {
onFailure(e);
}
}
@Override
public void onFailure(Throwable error) {
reporter.reportWarning(PacketFilterBuilder.this, Report
.newBuilder(REPORT_TEMPORARY_EVENT_ERROR).error(error));
}
});
System.out.println("Delaying due to Spigot");
// Let plugins use this version instead
return delayed;
} else {
nettyEnabled = !MinecraftReflection.isMinecraftObject(
InjectedServerConnection.getServerConnection(reporter, server));
}
}
// Otherwise - construct the packet filter manager right away
return buildInternal();
}
private void registerSpigot(DelayedPacketManager delayed) {
// Use netty if we have a non-standard ServerConnection class
nettyEnabled = !MinecraftReflection.isMinecraftObject(
InjectedServerConnection.getServerConnection(reporter, server));
// Switch to the standard manager
delayed.setDelegate(buildInternal());
}
/**
* Construct a new packet filter manager without checking for Netty.
* @return A new packet filter manager.
*/
private PacketFilterManager buildInternal() {
PacketFilterManager manager = new PacketFilterManager(this);
// It's a cyclic reference, but it's too late to fix now
asyncManager.setManager(manager);
return manager;
}
}

View File

@ -63,12 +63,12 @@ import com.comphenix.protocol.injector.spigot.SpigotPacketInjector;
import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
public final class PacketFilterManager implements ProtocolManager, ListenerInvoker { public final class PacketFilterManager implements ProtocolManager, ListenerInvoker, InternalManager {
public static final ReportType REPORT_CANNOT_LOAD_PACKET_LIST = new ReportType("Cannot load server and client packet list."); public static final ReportType REPORT_CANNOT_LOAD_PACKET_LIST = new ReportType("Cannot load server and client packet list.");
public static final ReportType REPORT_CANNOT_INITIALIZE_PACKET_INJECTOR = new ReportType("Unable to initialize packet injector"); public static final ReportType REPORT_CANNOT_INITIALIZE_PACKET_INJECTOR = new ReportType("Unable to initialize packet injector");
@ -85,6 +85,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
public static final ReportType REPORT_CANNOT_INJECT_PLAYER = new ReportType("Unable to inject player."); public static final ReportType REPORT_CANNOT_INJECT_PLAYER = new ReportType("Unable to inject player.");
public static final ReportType REPORT_CANNOT_UNREGISTER_PLUGIN = new ReportType("Unable to handle disabled plugin."); public static final ReportType REPORT_CANNOT_UNREGISTER_PLUGIN = new ReportType("Unable to handle disabled plugin.");
public static final ReportType REPORT_PLUGIN_VERIFIER_ERROR = new ReportType("Verifier error: %s");
/** /**
* Sets the inject hook type. Different types allow for maximum compatibility. * Sets the inject hook type. Different types allow for maximum compatibility.
@ -172,37 +173,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
/** /**
* Only create instances of this class if protocol lib is disabled. * Only create instances of this class if protocol lib is disabled.
*/ */
public PacketFilterManager(ClassLoader classLoader, Server server, Plugin library, DelayedSingleTask unhookTask, ErrorReporter reporter) { public PacketFilterManager(PacketFilterBuilder builder) {
this(classLoader, server, library, new MinecraftVersion(server), unhookTask, reporter);
}
/**
* Only create instances of this class if protocol lib is disabled.
*/
public PacketFilterManager(ClassLoader classLoader, Server server, Plugin library,
MinecraftVersion mcVersion, DelayedSingleTask unhookTask, ErrorReporter reporter) {
if (reporter == null)
throw new IllegalArgumentException("reporter cannot be NULL.");
if (classLoader == null)
throw new IllegalArgumentException("classLoader cannot be NULL.");
// Just boilerplate
final DelayedSingleTask finalUnhookTask = unhookTask;
// Listener containers
this.recievedListeners = new SortedPacketListenerList();
this.sendingListeners = new SortedPacketListenerList();
// References
this.unhookTask = unhookTask;
this.server = server;
this.classLoader = classLoader;
this.reporter = reporter;
// The plugin verifier
this.pluginVerifier = new PluginVerifier(library);
// Used to determine if injection is needed // Used to determine if injection is needed
Predicate<GamePhase> isInjectionNecessary = new Predicate<GamePhase>() { Predicate<GamePhase> isInjectionNecessary = new Predicate<GamePhase>() {
@Override @Override
@ -213,14 +184,26 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
result &= getPhaseLoginCount() > 0; result &= getPhaseLoginCount() > 0;
// Note that we will still hook players if the unhooking has been delayed // Note that we will still hook players if the unhooking has been delayed
if (phase.hasPlaying()) if (phase.hasPlaying())
result &= getPhasePlayingCount() > 0 || finalUnhookTask.isRunning(); result &= getPhasePlayingCount() > 0 || unhookTask.isRunning();
return result; return result;
} }
}; };
try { // Listener containers
// Spigot this.recievedListeners = new SortedPacketListenerList();
if (SpigotPacketInjector.canUseSpigotListener()) { this.sendingListeners = new SortedPacketListenerList();
// References
this.unhookTask = builder.getUnhookTask();
this.server = builder.getServer();
this.classLoader = builder.getClassLoader();
this.reporter = builder.getReporter();
// The plugin verifier
this.pluginVerifier = new PluginVerifier(builder.getLibrary());
// Use the correct injection type
if (builder.isNettyEnabled()) {
spigotInjector = new SpigotPacketInjector(classLoader, reporter, this, server); spigotInjector = new SpigotPacketInjector(classLoader, reporter, this, server);
this.playerInjection = spigotInjector.getPlayerHandler(); this.playerInjection = spigotInjector.getPlayerHandler();
this.packetInjector = spigotInjector.getPacketInjector(); this.packetInjector = spigotInjector.getPacketInjector();
@ -234,7 +217,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
classLoader(classLoader). classLoader(classLoader).
packetListeners(packetListeners). packetListeners(packetListeners).
injectionFilter(isInjectionNecessary). injectionFilter(isInjectionNecessary).
version(mcVersion). version(builder.getMinecraftVersion()).
buildHandler(); buildHandler();
this.packetInjector = PacketInjectorBuilder.newBuilder(). this.packetInjector = PacketInjectorBuilder.newBuilder().
@ -244,8 +227,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
playerInjection(playerInjection). playerInjection(playerInjection).
buildInjector(); buildInjector();
} }
this.asyncFilterManager = builder.getAsyncManager();
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 {
@ -254,17 +236,14 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
} catch (FieldAccessException e) { } catch (FieldAccessException e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_PACKET_LIST).error(e)); reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_PACKET_LIST).error(e));
} }
} catch (FieldAccessException e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_INITIALIZE_PACKET_INJECTOR).error(e));
}
} }
/** /**
* Initiate logic that is performed after the world has loaded. * Construct a new packet filter builder.
* @return New builder.
*/ */
public void postWorldLoaded() { public static PacketFilterBuilder newBuilder() {
playerInjection.postWorldLoaded(); return new PacketFilterBuilder();
} }
@Override @Override
@ -276,6 +255,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
* 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.
*/ */
@Override
public PlayerInjectHooks getPlayerHook() { public PlayerInjectHooks getPlayerHook() {
return playerInjection.getPlayerHook(); return playerInjection.getPlayerHook();
} }
@ -284,6 +264,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
* 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.
*/ */
@Override
public void setPlayerHook(PlayerInjectHooks playerHook) { public void setPlayerHook(PlayerInjectHooks playerHook) {
playerInjection.setPlayerHook(playerHook); playerInjection.setPlayerHook(playerHook);
} }
@ -298,6 +279,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
* @param plugin - plugin to check. * @param plugin - plugin to check.
*/ */
private void printPluginWarnings(Plugin plugin) { private void printPluginWarnings(Plugin plugin) {
try {
switch (pluginVerifier.verify(plugin)) { switch (pluginVerifier.verify(plugin)) {
case NO_DEPEND: case NO_DEPEND:
reporter.reportWarning(this, Report.newBuilder(REPORT_PLUGIN_DEPEND_MISSING).messageParam(plugin.getName())); reporter.reportWarning(this, Report.newBuilder(REPORT_PLUGIN_DEPEND_MISSING).messageParam(plugin.getName()));
@ -305,6 +287,9 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
// Do nothing // Do nothing
break; break;
} }
} catch (IllegalStateException e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_PLUGIN_VERIFIER_ERROR).messageParam(e.getMessage()));
}
} }
@Override @Override
@ -702,6 +687,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
* @param manager - Bukkit plugin manager that provides player join/leave events. * @param manager - Bukkit plugin manager that provides player join/leave events.
* @param plugin - the parent plugin. * @param plugin - the parent plugin.
*/ */
@Override
public void registerEvents(PluginManager manager, final Plugin plugin) { public void registerEvents(PluginManager manager, final Plugin plugin) {
if (spigotInjector != null && !spigotInjector.register(plugin)) if (spigotInjector != null && !spigotInjector.register(plugin))
throw new IllegalArgumentException("Spigot has already been registered."); throw new IllegalArgumentException("Spigot has already been registered.");
@ -969,9 +955,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
return hasClosed; return hasClosed;
} }
/** @Override
* Called when ProtocolLib is closing.
*/
public void close() { public void close() {
// Guard // Guard
if (hasClosed) if (hasClosed)

View File

@ -5,7 +5,6 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginLoadOrder; import org.bukkit.plugin.PluginLoadOrder;
@ -98,7 +97,7 @@ class PluginVerifier {
* @return The retrieved plugin, or NULL if not found. * @return The retrieved plugin, or NULL if not found.
*/ */
private Plugin getPluginOrDefault(String pluginName) { private Plugin getPluginOrDefault(String pluginName) {
return Bukkit.getPluginManager().getPlugin(pluginName); return dependency.getServer().getPluginManager().getPlugin(pluginName);
} }
/** /**

View File

@ -0,0 +1,130 @@
package com.comphenix.protocol.injector.packet;
import java.lang.reflect.Field;
import java.util.Map;
import com.comphenix.protocol.reflect.FieldUtils;
import com.google.common.base.Predicate;
import com.google.common.collect.ForwardingMap;
import com.google.common.collect.ForwardingMultimap;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
public class InverseMaps {
private InverseMaps() {
// Not constructable
}
public static <K, V> Multimap<K, V> inverseMultimap(final Map<V, K> map, final Predicate<Map.Entry<V, K>> filter) {
final MapContainer container = new MapContainer(map);
return new ForwardingMultimap<K, V>() {
// The cached multimap
private Multimap<K, V> inverseMultimap;
@Override
protected Multimap<K, V> delegate() {
if (container.hasChanged()) {
inverseMultimap = HashMultimap.create();
// Construct the inverse map
for (Map.Entry<V, K> entry : map.entrySet()) {
if (filter.apply(entry)) {
inverseMultimap.put(entry.getValue(), entry.getKey());
}
}
container.setChanged(false);
}
return inverseMultimap;
}
};
}
public static <K, V> Map<K, V> inverseMap(final Map<V, K> map, final Predicate<Map.Entry<V, K>> filter) {
final MapContainer container = new MapContainer(map);
return new ForwardingMap<K, V>() {
// The cached map
private Map<K, V> inverseMap;
@Override
protected Map<K, V> delegate() {
if (container.hasChanged()) {
inverseMap = Maps.newHashMap();
// Construct the inverse map
for (Map.Entry<V, K> entry : map.entrySet()) {
if (filter.apply(entry)) {
inverseMap.put(entry.getValue(), entry.getKey());
}
}
container.setChanged(false);
}
return inverseMap;
}
};
}
/**
* Represents a class that can detect if a map has changed.
* @author Kristian
*/
private static class MapContainer {
// For detecting changes
private Field modCountField;
private int lastModCount;
// The object along with whether or not this is the initial run
private Object source;
private boolean changed;
public MapContainer(Object source) {
this.source = source;
this.changed = true;
this.modCountField = FieldUtils.getField(source.getClass(), "modCount", true);
}
/**
* Determine if the map has changed.
* @return TRUE if it has, FALSE otherwise.
*/
public boolean hasChanged() {
// Check if unchanged
checkChanged();
return changed;
}
/**
* Mark the map as changed or unchanged.
* @param changed - TRUE if the map has changed, FALSE otherwise.
*/
public void setChanged(boolean changed) {
this.changed = changed;
}
/**
* Check for modifications to the current map.
*/
protected void checkChanged() {
if (!changed) {
if (getModificationCount() != lastModCount) {
lastModCount = getModificationCount();
changed = true;
}
}
}
/**
* Retrieve the current modification count.
* @return The current count, or something different than lastModCount if not accessible.
*/
private int getModificationCount() {
try {
return modCountField != null ? modCountField.getInt(source) : lastModCount + 1;
} catch (Exception e) {
throw new RuntimeException("Unable to retrieve modCount.", e);
}
}
}
}

View File

@ -22,6 +22,9 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.Map.Entry;
import javax.annotation.Nullable;
import net.sf.cglib.proxy.Factory; import net.sf.cglib.proxy.Factory;
@ -36,8 +39,10 @@ import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.TroveWrapper; import com.comphenix.protocol.wrappers.TroveWrapper;
import com.google.common.base.Objects; import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
/** /**
* Static packet registry in Minecraft. * Static packet registry in Minecraft.
@ -60,6 +65,10 @@ public class PacketRegistry {
// The packet class to packet ID translator // The packet class to packet ID translator
private static Map<Class, Integer> packetToID; private static Map<Class, Integer> packetToID;
// Packet IDs to classes, grouped by whether or not they're vanilla or custom defined
private static Multimap<Integer, Class> customIdToPacket;
private static Map<Integer, Class> vanillaIdToPacket;
// Whether or not certain packets are sent by the client or the server // Whether or not certain packets are sent by the client or the server
private static ImmutableSet<Integer> serverPackets; private static ImmutableSet<Integer> serverPackets;
private static ImmutableSet<Integer> clientPackets; private static ImmutableSet<Integer> clientPackets;
@ -93,8 +102,23 @@ public class PacketRegistry {
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
throw new RuntimeException("Unable to retrieve the packetClassToIdMap", e); throw new RuntimeException("Unable to retrieve the packetClassToIdMap", e);
} }
}
// Create the inverse maps
customIdToPacket = InverseMaps.inverseMultimap(packetToID, new Predicate<Map.Entry<Class, Integer>>() {
@Override
public boolean apply(@Nullable Entry<Class, Integer> entry) {
return !MinecraftReflection.isMinecraftClass(entry.getKey());
}
});
// And the vanilla pack - here we assume a unique ID to class mapping
vanillaIdToPacket = InverseMaps.inverseMap(packetToID, new Predicate<Map.Entry<Class, Integer>>() {
@Override
public boolean apply(@Nullable Entry<Class, Integer> entry) {
return MinecraftReflection.isMinecraftClass(entry.getKey());
}
});
}
return packetToID; return packetToID;
} }
@ -248,23 +272,29 @@ public class PacketRegistry {
* @return The associated class. * @return The associated class.
*/ */
public static Class getPacketClassFromID(int packetID, boolean forceVanilla) { public static Class getPacketClassFromID(int packetID, boolean forceVanilla) {
Map<Integer, Class> lookup = forceVanilla ? previousValues : overwrittenPackets; Map<Integer, Class> lookup = forceVanilla ? previousValues : overwrittenPackets;
Class<?> result = null;
// Optimized lookup // Optimized lookup
if (lookup.containsKey(packetID)) { if (lookup.containsKey(packetID)) {
return removeEnhancer(lookup.get(packetID), forceVanilla); return removeEnhancer(lookup.get(packetID), forceVanilla);
} }
// Will most likely not be used // Refresh lookup tables
for (Map.Entry<Class, Integer> entry : getPacketToID().entrySet()) { getPacketToID();
if (Objects.equal(entry.getValue(), packetID)) {
// Attempt to get the vanilla class here too // See if we can look for non-vanilla classes
if (!forceVanilla || MinecraftReflection.isMinecraftClass(entry.getKey())) if (!forceVanilla) {
return removeEnhancer(entry.getKey(), forceVanilla); result = Iterables.getFirst(customIdToPacket.get(packetID), null);
} }
if (result == null) {
result = vanillaIdToPacket.get(packetID);
} }
// See if we got it
if (result != null)
return result;
else
throw new IllegalArgumentException("The packet ID " + packetID + " is not registered."); throw new IllegalArgumentException("The packet ID " + packetID + " is not registered.");
} }

View File

@ -17,6 +17,7 @@
package com.comphenix.protocol.injector.packet; package com.comphenix.protocol.injector.packet;
import java.io.DataInput;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
@ -33,6 +34,8 @@ import net.sf.cglib.proxy.NoOp;
import com.comphenix.protocol.Packets; import com.comphenix.protocol.Packets;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.injector.ListenerInvoker;
@ -50,6 +53,8 @@ import com.comphenix.protocol.utility.MinecraftReflection;
* @author Kristian * @author Kristian
*/ */
class ProxyPacketInjector implements PacketInjector { class ProxyPacketInjector implements PacketInjector {
public static final ReportType REPORT_CANNOT_FIND_READ_PACKET_METHOD = new ReportType("Cannot find read packet method for ID %s.");
/** /**
* Represents a way to update the packet ID to class lookup table. * Represents a way to update the packet ID to class lookup table.
* @author Kristian * @author Kristian
@ -134,7 +139,7 @@ class ProxyPacketInjector implements PacketInjector {
*/ */
private static FuzzyMethodContract readPacket = FuzzyMethodContract.newBuilder(). private static FuzzyMethodContract readPacket = FuzzyMethodContract.newBuilder().
returnTypeVoid(). returnTypeVoid().
parameterExactType(DataInputStream.class). parameterDerivedOf(DataInput.class).
parameterCount(1). parameterCount(1).
build(); build();
@ -155,6 +160,9 @@ class ProxyPacketInjector implements PacketInjector {
// Share callback filter // Share callback filter
private CallbackFilter filter; private CallbackFilter filter;
// Determine if the read packet method was found
private boolean readPacketIntercepted = false;
public ProxyPacketInjector(ClassLoader classLoader, ListenerInvoker manager, public ProxyPacketInjector(ClassLoader classLoader, ListenerInvoker manager,
PlayerInjectionHandler playerInjection, ErrorReporter reporter) throws FieldAccessException { PlayerInjectionHandler playerInjection, ErrorReporter reporter) throws FieldAccessException {
@ -224,17 +232,21 @@ class ProxyPacketInjector implements PacketInjector {
} }
if (filter == null) { if (filter == null) {
readPacketIntercepted = false;
filter = new CallbackFilter() { filter = new CallbackFilter() {
@Override @Override
public int accept(Method method) { public int accept(Method method) {
// Skip methods defined in Object // Skip methods defined in Object
if (method.getDeclaringClass().equals(Object.class)) if (method.getDeclaringClass().equals(Object.class)) {
return 0; return 0;
else if (readPacket.isMatch(MethodInfo.fromMethod(method), null)) } else if (readPacket.isMatch(MethodInfo.fromMethod(method), null)) {
readPacketIntercepted = true;
return 1; return 1;
else } else {
return 2; return 2;
} }
}
}; };
} }
@ -252,6 +264,12 @@ class ProxyPacketInjector implements PacketInjector {
// Add a static reference // Add a static reference
Enhancer.registerStaticCallbacks(proxy, new Callback[] { NoOp.INSTANCE, modifierReadPacket, modifierRest }); Enhancer.registerStaticCallbacks(proxy, new Callback[] { NoOp.INSTANCE, modifierReadPacket, modifierRest });
// Check that we found the read method
if (!readPacketIntercepted) {
reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_FIND_READ_PACKET_METHOD).messageParam(packetID));
}
// Override values // Override values
previous.put(packetID, old); previous.put(packetID, old);
registry.put(proxy, packetID); registry.put(proxy, packetID);

View File

@ -18,6 +18,7 @@
package com.comphenix.protocol.injector.player; package com.comphenix.protocol.injector.player;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -30,6 +31,7 @@ import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report; import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType; import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.server.AbstractInputStreamLookup; import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.ObjectWriter; import com.comphenix.protocol.reflect.ObjectWriter;
@ -41,7 +43,7 @@ import com.comphenix.protocol.utility.MinecraftReflection;
* *
* @author Kristian * @author Kristian
*/ */
class InjectedServerConnection { public class InjectedServerConnection {
// A number of things can go wrong ... // A number of things can go wrong ...
public static final ReportType REPORT_CANNOT_FIND_MINECRAFT_SERVER = new ReportType("Cannot extract minecraft server from Bukkit."); public static final ReportType REPORT_CANNOT_FIND_MINECRAFT_SERVER = new ReportType("Cannot extract minecraft server from Bukkit.");
public static final ReportType REPORT_CANNOT_INJECT_SERVER_CONNECTION = new ReportType("Cannot inject into server connection. Bad things will happen."); public static final ReportType REPORT_CANNOT_INJECT_SERVER_CONNECTION = new ReportType("Cannot inject into server connection. Bad things will happen.");
@ -66,12 +68,21 @@ class InjectedServerConnection {
private List<VolatileField> listFields; private List<VolatileField> listFields;
private List<ReplacedArrayList<Object>> replacedLists; private List<ReplacedArrayList<Object>> replacedLists;
// The current detected server socket
public enum ServerSocketType {
SERVER_CONNECTION,
LISTENER_THREAD,
}
// Used to inject net handlers // Used to inject net handlers
private NetLoginInjector netLoginInjector; private NetLoginInjector netLoginInjector;
// Inject server connections // Inject server connections
private AbstractInputStreamLookup socketInjector; private AbstractInputStreamLookup socketInjector;
// Detected by the initializer
private ServerSocketType socketType;
private Server server; private Server server;
private ErrorReporter reporter; private ErrorReporter reporter;
private boolean hasAttempted; private boolean hasAttempted;
@ -88,7 +99,31 @@ class InjectedServerConnection {
this.netLoginInjector = netLoginInjector; this.netLoginInjector = netLoginInjector;
} }
public void injectList() { /**
* Retrieve the current server connection.
* @param reporter - error reproter.
* @param server - the current server.
* @return The current server connection, or NULL if it hasn't been initialized yet.
* @throws FieldAccessException Reflection error.
*/
public static Object getServerConnection(ErrorReporter reporter, Server server) {
try {
// Now we are probably able to check for Netty
InjectedServerConnection inspector = new InjectedServerConnection(reporter, null, server, null);
return inspector.getServerConnection();
} catch (IllegalAccessException e) {
throw new FieldAccessException("Reflection error.", e);
} catch (IllegalArgumentException e) {
throw new FieldAccessException("Corrupt data.", e);
} catch (InvocationTargetException e) {
throw new FieldAccessException("Minecraft error.", e);
}
}
/**
* Initial reflective detective work. Will be automatically called by most methods in this class.
*/
public void initialize() {
// Only execute this method once // Only execute this method once
if (!hasAttempted) if (!hasAttempted)
hasAttempted = true; hasAttempted = true;
@ -112,12 +147,11 @@ class InjectedServerConnection {
getMethodByParameters("getServerConnection", getMethodByParameters("getServerConnection",
MinecraftReflection.getServerConnectionClass(), new Class[] {}); MinecraftReflection.getServerConnectionClass(), new Class[] {});
// We're using Minecraft 1.3.1 // We're using Minecraft 1.3.1
injectServerConnection(); socketType = ServerSocketType.SERVER_CONNECTION;
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
// Minecraft 1.2.5 or lower // Minecraft 1.2.5 or lower
injectListenerThread(); socketType = ServerSocketType.LISTENER_THREAD;
} catch (Exception e) { } catch (Exception e) {
// Oh damn - inform the player // Oh damn - inform the player
@ -125,11 +159,77 @@ class InjectedServerConnection {
} }
} }
private void injectListenerThread() { /**
try { * Retrieve the known server socket type.
* <p>
* This depends on the version of CraftBukkit we are using.
* @return The server socket type.
*/
public ServerSocketType getServerSocketType() {
return socketType;
}
/**
* Inject the connection interceptor into the correct server socket implementation.
*/
public void injectList() {
initialize();
if (socketType == ServerSocketType.SERVER_CONNECTION) {
injectServerConnection();
} else if (socketType == ServerSocketType.LISTENER_THREAD) {
injectListenerThread();
} else {
// Damn it
throw new IllegalStateException("Unable to detected server connection.");
}
}
/**
* Retrieve the listener thread field.
*/
private void initializeListenerField() {
if (listenerThreadField == null) if (listenerThreadField == null)
listenerThreadField = FuzzyReflection.fromObject(minecraftServer). listenerThreadField = FuzzyReflection.fromObject(minecraftServer).
getFieldByType("networkListenThread", MinecraftReflection.getNetworkListenThreadClass()); getFieldByType("networkListenThread", MinecraftReflection.getNetworkListenThreadClass());
}
/**
* Retrieve the listener thread object, or NULL the server isn't using this socket implementation.
* @return The listener thread, or NULL.
* @throws IllegalAccessException Cannot access field.
* @hrows RuntimeException Unexpected class structure - the field doesn't exist.
*/
public Object getListenerThread() throws RuntimeException, IllegalAccessException {
initialize();
if (socketType == ServerSocketType.LISTENER_THREAD) {
initializeListenerField();
return listenerThreadField.get(minecraftServer);
} else {
return null;
}
}
/**
* Retrieve the server connection object, or NULL if the server isn't using it as the socket implementation.
* @return The socket connection, or NULL.
* @throws IllegalAccessException If the reflective operation failed.
* @throws IllegalArgumentException If the reflective operation failed.
* @throws InvocationTargetException If the reflective operation failed.
*/
public Object getServerConnection() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
initialize();
if (socketType == ServerSocketType.SERVER_CONNECTION)
return serverConnectionMethod.invoke(minecraftServer);
else
return null;
}
private void injectListenerThread() {
try {
initializeListenerField();
} catch (RuntimeException e) { } catch (RuntimeException e) {
reporter.reportDetailed(this, reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_FIND_LISTENER_THREAD).callerParam(minecraftServer).error(e) Report.newBuilder(REPORT_CANNOT_FIND_LISTENER_THREAD).callerParam(minecraftServer).error(e)
@ -141,7 +241,7 @@ class InjectedServerConnection {
// Attempt to get the thread // Attempt to get the thread
try { try {
listenerThread = listenerThreadField.get(minecraftServer); listenerThread = getListenerThread();
} catch (Exception e) { } catch (Exception e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_READ_LISTENER_THREAD).error(e)); reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_READ_LISTENER_THREAD).error(e));
return; return;
@ -160,7 +260,7 @@ class InjectedServerConnection {
// Careful - we might fail // Careful - we might fail
try { try {
serverConnection = serverConnectionMethod.invoke(minecraftServer); serverConnection = getServerConnection();
} catch (Exception e) { } catch (Exception e) {
reporter.reportDetailed(this, reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_FIND_SERVER_CONNECTION).callerParam(minecraftServer).error(e) Report.newBuilder(REPORT_CANNOT_FIND_SERVER_CONNECTION).callerParam(minecraftServer).error(e)

View File

@ -161,9 +161,4 @@ public interface PlayerInjectionHandler {
* Close any lingering proxy injections. * Close any lingering proxy injections.
*/ */
public abstract void close(); public abstract void close();
/**
* Perform any action that must be delayed until the world(s) has loaded.
*/
public abstract void postWorldLoaded();
} }

View File

@ -284,7 +284,7 @@ public abstract class PlayerInjector implements SocketInjector {
/** /**
* Retrieve the associated remote address of a player. * Retrieve the associated remote address of a player.
* @return The associated remote address.. * @return The associated remote address.
* @throws IllegalAccessException If we're unable to read the socket address field. * @throws IllegalAccessException If we're unable to read the socket address field.
*/ */
@Override @Override

View File

@ -21,6 +21,7 @@ import java.io.DataInputStream;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
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.net.SocketAddress;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -51,6 +52,7 @@ import com.comphenix.protocol.injector.server.SocketInjector;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion; import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.base.Objects;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.cache.Cache; import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
@ -143,14 +145,6 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
serverInjection.injectList(); serverInjection.injectList();
} }
@Override
public void postWorldLoaded() {
// This will actually create a socket and a seperate thread ...
if (inputStreamLookup != null) {
inputStreamLookup.postWorldLoaded();
}
}
/** /**
* 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.
@ -350,6 +344,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
return null; return null;
SocketInjector previous = inputStreamLookup.peekSocketInjector(address); SocketInjector previous = inputStreamLookup.peekSocketInjector(address);
Socket socket = injector.getSocket();
// Close any previously associated hooks before we proceed // Close any previously associated hooks before we proceed
if (previous != null && !(player instanceof Factory)) { if (previous != null && !(player instanceof Factory)) {
@ -363,8 +358,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
} }
injector.injectManager(); injector.injectManager();
// Save injector saveAddressLookup(address, socket, injector);
inputStreamLookup.setSocketInjector(address, injector);
break; break;
} }
@ -413,6 +407,17 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
return injector; return injector;
} }
private void saveAddressLookup(SocketAddress address, Socket socket, SocketInjector injector) {
SocketAddress socketAddress = socket != null ? socket.getRemoteSocketAddress() : null;
if (socketAddress != null && !Objects.equal(socketAddress, address)) {
// Save this version as well
inputStreamLookup.setSocketInjector(socketAddress, injector);
}
// Save injector
inputStreamLookup.setSocketInjector(address, injector);
}
private void cleanupHook(PlayerInjector injector) { private void cleanupHook(PlayerInjector injector) {
// Clean up as much as possible // Clean up as much as possible
try { try {

View File

@ -26,11 +26,6 @@ public abstract class AbstractInputStreamLookup {
*/ */
public abstract void inject(Object container); public abstract void inject(Object container);
/**
* Invoked when the world has loaded.
*/
public abstract void postWorldLoaded();
/** /**
* Retrieve the associated socket injector for a player. * Retrieve the associated socket injector for a player.
* @param input - the indentifying filtered input stream. * @param input - the indentifying filtered input stream.

View File

@ -10,36 +10,14 @@ import java.util.List;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
public class BukkitSocketInjector implements SocketInjector { public class BukkitSocketInjector implements SocketInjector {
/**
* Represents a single send packet command.
* @author Kristian
*/
static class SendPacketCommand {
private final Object packet;
private final boolean filtered;
public SendPacketCommand(Object packet, boolean filtered) {
this.packet = packet;
this.filtered = filtered;
}
public Object getPacket() {
return packet;
}
public boolean isFiltered() {
return filtered;
}
}
private Player player; private Player player;
// Queue of server packets // Queue of server packets
private List<SendPacketCommand> syncronizedQueue = Collections.synchronizedList(new ArrayList<SendPacketCommand>()); private List<QueuedSendPacket> syncronizedQueue = Collections.synchronizedList(new ArrayList<QueuedSendPacket>());
/** /**
* Represents a temporary socket injector. * Represents a temporary socket injector.
* @param temporaryPlayer - * @param temporaryPlayer - a temporary player.
*/ */
public BukkitSocketInjector(Player player) { public BukkitSocketInjector(Player player) {
if (player == null) if (player == null)
@ -65,7 +43,7 @@ public class BukkitSocketInjector implements SocketInjector {
@Override @Override
public void sendServerPacket(Object packet, boolean filtered) public void sendServerPacket(Object packet, boolean filtered)
throws InvocationTargetException { throws InvocationTargetException {
SendPacketCommand command = new SendPacketCommand(packet, filtered); QueuedSendPacket command = new QueuedSendPacket(packet, filtered);
// Queue until we can find something better // Queue until we can find something better
syncronizedQueue.add(command); syncronizedQueue.add(command);
@ -86,7 +64,7 @@ public class BukkitSocketInjector implements SocketInjector {
// Transmit all queued packets to a different injector. // Transmit all queued packets to a different injector.
try { try {
synchronized(syncronizedQueue) { synchronized(syncronizedQueue) {
for (SendPacketCommand command : syncronizedQueue) { for (QueuedSendPacket command : syncronizedQueue) {
delegate.sendServerPacket(command.getPacket(), command.isFiltered()); delegate.sendServerPacket(command.getPacket(), command.isFiltered());
} }
syncronizedQueue.clear(); syncronizedQueue.clear();

View File

@ -53,11 +53,6 @@ class InputStreamReflectLookup extends AbstractInputStreamLookup {
// Do nothing // Do nothing
} }
@Override
public void postWorldLoaded() {
// Nothing again
}
@Override @Override
public SocketInjector peekSocketInjector(SocketAddress address) { public SocketInjector peekSocketInjector(SocketAddress address) {
try { try {

View File

@ -0,0 +1,31 @@
package com.comphenix.protocol.injector.server;
/**
* Represents a single send packet command.
* @author Kristian
*/
class QueuedSendPacket {
private final Object packet;
private final boolean filtered;
public QueuedSendPacket(Object packet, boolean filtered) {
this.packet = packet;
this.filtered = filtered;
}
/**
* Retrieve the underlying packet that will be sent.
* @return The underlying packet.
*/
public Object getPacket() {
return packet;
}
/**
* Determine if the packet should be intercepted by packet listeners.
* @return TRUE if it should, FALSE otherwise.
*/
public boolean isFiltered() {
return filtered;
}
}

View File

@ -115,11 +115,6 @@ class DummyPlayerHandler implements PlayerInjectionHandler {
// Yes, really // Yes, really
} }
@Override
public void postWorldLoaded() {
// Do nothing
}
@Override @Override
public void updatePlayer(Player player) { public void updatePlayer(Player player) {
// Do nothing // Do nothing

View File

@ -161,8 +161,8 @@ public class FuzzyMethodContract extends AbstractFuzzyMember<MethodInfo> {
/** /**
* Add a new required parameter whose type must be a superclass of the given type. * Add a new required parameter whose type must be a superclass of the given type.
* <p> * <p>
* If a parameter is of type Number, any derived class (Integer, Long, etc.) will match it. * If a method parameter is of type Number, then any derived class here (Integer, Long, etc.) will match it.
* @param type - a type or derived type of the matching parameter. * @param type - a type or less derived type of the matching parameter.
* @return This builder, for chaining. * @return This builder, for chaining.
*/ */
public Builder parameterSuperOf(Class<?> type) { public Builder parameterSuperOf(Class<?> type) {
@ -170,6 +170,18 @@ public class FuzzyMethodContract extends AbstractFuzzyMember<MethodInfo> {
return this; return this;
} }
/**
* Add a new required parameter whose type must be a derived class of the given class.
* <p>
* If the method parameter has the type Integer, then the class Number here will match it.
* @param type - a type or more derived type of the matching parameter.
* @return This builder, for chaining.
*/
public Builder parameterDerivedOf(Class<?> type) {
member.paramMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchDerived(type)));
return this;
}
/** /**
* Add a new required parameter whose type must match the given class matcher. * Add a new required parameter whose type must match the given class matcher.
* @param classMatcher - the class matcher. * @param classMatcher - the class matcher.
@ -204,6 +216,19 @@ public class FuzzyMethodContract extends AbstractFuzzyMember<MethodInfo> {
return this; return this;
} }
/**
* Add a new required parameter whose type must be a derived class of the given class.
* <p>
* If the method parameter has the type Integer, then the class Number here will match it.
* @param type - a type or more derived type of the matching parameter.
* @param index - the expected position in the parameter list.
* @return This builder, for chaining.
*/
public Builder parameterDerivedOf(Class<?> type, int index) {
member.paramMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchDerived(type), index));
return this;
}
/** /**
* Add a new required parameter whose type must match the given class matcher and index. * Add a new required parameter whose type must match the given class matcher and index.
* @param classMatcher - the class matcher. * @param classMatcher - the class matcher.

View File

@ -18,7 +18,7 @@
package com.comphenix.protocol.utility; package com.comphenix.protocol.utility;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.DataOutputStream; import java.io.DataOutput;
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.Field;
@ -219,6 +219,11 @@ public class MinecraftReflection {
MINECRAFT_FULL_PACKAGE = minecraftPackage; MINECRAFT_FULL_PACKAGE = minecraftPackage;
CRAFTBUKKIT_PACKAGE = craftBukkitPackage; CRAFTBUKKIT_PACKAGE = craftBukkitPackage;
// Make sure it exists
if (getMinecraftServerClass() == null) {
throw new IllegalArgumentException("Cannot find MinecraftServer for package " + minecraftPackage);
}
// Standard matcher // Standard matcher
setDynamicPackageMatcher(MINECRAFT_OBJECT); setDynamicPackageMatcher(MINECRAFT_OBJECT);
} }
@ -792,7 +797,7 @@ public class MinecraftReflection {
Method selected = FuzzyReflection.fromClass(getDataWatcherClass(), true). Method selected = FuzzyReflection.fromClass(getDataWatcherClass(), true).
getMethod(FuzzyMethodContract.newBuilder(). getMethod(FuzzyMethodContract.newBuilder().
requireModifier(Modifier.STATIC). requireModifier(Modifier.STATIC).
parameterSuperOf(DataOutputStream.class, 0). parameterDerivedOf(DataOutput.class, 0).
parameterMatches(getMinecraftObjectMatcher(), 1). parameterMatches(getMinecraftObjectMatcher(), 1).
build()); build());

View File

@ -2,7 +2,9 @@ package com.comphenix.protocol.utility;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.DataOutput;
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;
@ -13,6 +15,7 @@ import org.bukkit.inventory.ItemStack;
import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder; import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
/** /**
* Utility methods for reading and writing Minecraft objects to streams. * Utility methods for reading and writing Minecraft objects to streams.
@ -37,11 +40,14 @@ public class StreamSerializer {
public ItemStack deserializeItemStack(@Nonnull DataInputStream input) throws IOException { public ItemStack deserializeItemStack(@Nonnull DataInputStream input) throws IOException {
if (input == null) if (input == null)
throw new IllegalArgumentException("Input stream cannot be 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()).getMethod(
getMethodByParameters("readPacket", FuzzyMethodContract.newBuilder().
MinecraftReflection.getItemStackClass(), parameterCount(1).
new Class<?>[] {DataInputStream.class}); parameterDerivedOf(DataInput.class).
returnDerivedOf(MinecraftReflection.getItemStackClass()).
build());
}
try { try {
Object nmsItem = readItemMethod.invoke(null, input); Object nmsItem = readItemMethod.invoke(null, input);
@ -88,10 +94,12 @@ public class StreamSerializer {
Object nmsItem = MinecraftReflection.getMinecraftItemStack(stack); Object nmsItem = MinecraftReflection.getMinecraftItemStack(stack);
if (writeItemMethod == null) if (writeItemMethod == null)
writeItemMethod = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()). writeItemMethod = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).getMethod(
getMethodByParameters("writePacket", new Class<?>[] { FuzzyMethodContract.newBuilder().
MinecraftReflection.getItemStackClass(), parameterCount(2).
DataOutputStream.class }); parameterDerivedOf(MinecraftReflection.getItemStackClass(), 0).
parameterDerivedOf(DataOutput.class, 1).
build());
try { try {
writeItemMethod.invoke(null, nmsItem, output); writeItemMethod.invoke(null, nmsItem, output);
} catch (Exception e) { } catch (Exception e) {

View File

@ -1,5 +1,5 @@
name: ProtocolLib name: ProtocolLib
version: 2.4.5 version: 2.4.8-SNAPSHOT
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

View File

@ -4,10 +4,10 @@ import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import net.minecraft.server.v1_5_R2.StatisticList; import net.minecraft.server.v1_6_R2.StatisticList;
// Will have to be updated for every version though // Will have to be updated for every version though
import org.bukkit.craftbukkit.v1_5_R2.inventory.CraftItemFactory; import org.bukkit.craftbukkit.v1_6_R2.inventory.CraftItemFactory;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Material; import org.bukkit.Material;
@ -63,6 +63,6 @@ public class BukkitInitialization {
*/ */
public static void initializePackage() { public static void initializePackage() {
// Initialize reflection // Initialize reflection
MinecraftReflection.setMinecraftPackage("net.minecraft.server.v1_5_R2", "org.bukkit.craftbukkit.v1_5_R2"); MinecraftReflection.setMinecraftPackage("net.minecraft.server.v1_6_R2", "org.bukkit.craftbukkit.v1_6_R2");
} }
} }

View File

@ -21,8 +21,9 @@ import static org.junit.Assert.*;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.util.List; import java.util.List;
import org.apache.commons.lang.SerializationUtils;
// Will have to be updated for every version though // Will have to be updated for every version though
import org.bukkit.craftbukkit.v1_5_R2.inventory.CraftItemFactory; import org.bukkit.craftbukkit.v1_6_R2.inventory.CraftItemFactory;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.WorldType; import org.bukkit.WorldType;
@ -306,6 +307,17 @@ public class PacketContainerTest {
assertEquals(list, watchableAccessor.read(0)); assertEquals(list, watchableAccessor.read(0));
} }
@Test
public void testSerialization() {
PacketContainer chat = new PacketContainer(3);
chat.getStrings().write(0, "Test");
PacketContainer copy = (PacketContainer) SerializationUtils.clone(chat);
assertEquals(3, copy.getID());
assertEquals("Test", copy.getStrings().read(0));
}
@Test @Test
public void testDeepClone() { public void testDeepClone() {
// Try constructing all the packets // Try constructing all the packets

View File

@ -0,0 +1,89 @@
package com.comphenix.protocol.injector;
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.List;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.bukkit.Server;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.PluginLoadOrder;
import org.bukkit.plugin.PluginManager;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.powermock.core.classloader.annotations.PrepareForTest;
import com.comphenix.protocol.injector.PluginVerifier.VerificationResult;
import com.google.common.base.Objects;
import com.google.common.collect.Lists;
// Damn final classes
@RunWith(org.powermock.modules.junit4.PowerMockRunner.class)
@PrepareForTest(PluginDescriptionFile.class)
public class PluginVerifierTest {
@Test
public void testDependecies() {
List<Plugin> plugins = Lists.newArrayList();
Server server = mockServer(plugins);
Plugin library = mockPlugin(server, "ProtocolLib", PluginLoadOrder.POSTWORLD);
Plugin skillPlugin = mockPlugin(server, "SkillPlugin", "RaidCraft-API", "RCPermissions", "RCConversations");
Plugin raidCraftAPI = mockPlugin(server, "RaidCraft-API", "WorldGuard", "WorldEdit");
Plugin conversations = mockPlugin(server, "RCConversations", "RaidCraft-API");
Plugin permissions = mockPlugin(server, "RCPermissions", "RaidCraft-API");
// Add the plugins
plugins.addAll(Arrays.asList(library, skillPlugin, raidCraftAPI, conversations, permissions));
PluginVerifier verifier = new PluginVerifier(library);
// Verify the root - it should have no dependencies on ProtocolLib
assertEquals(VerificationResult.NO_DEPEND, verifier.verify(skillPlugin));
}
private Server mockServer(final List<Plugin> plugins) {
Server mockServer = mock(Server.class);
PluginManager manager = mock(PluginManager.class);
when(mockServer.getPluginManager()).thenReturn(manager);
when(manager.getPlugin(anyString())).thenAnswer(new Answer<Plugin>() {
@Override
public Plugin answer(InvocationOnMock invocation) throws Throwable {
String name = (String) invocation.getArguments()[0];
for (Plugin plugin : plugins) {
if (Objects.equal(name, plugin.getName())) {
return plugin;
}
}
return null;
}
});
return mockServer;
}
private Plugin mockPlugin(Server server, String name,String... depend) {
return mockPlugin(server, name, PluginLoadOrder.POSTWORLD, depend);
}
private Plugin mockPlugin(Server server, String name, PluginLoadOrder order, String... depend) {
Plugin plugin = mock(Plugin.class);
PluginDescriptionFile file = mock(PluginDescriptionFile.class);
when(plugin.getServer()).thenReturn(server);
when(plugin.getName()).thenReturn(name);
when(plugin.toString()).thenReturn(name);
when(plugin.getDescription()).thenReturn(file);
// This is the difficult part
when(file.getLoad()).thenReturn(order);
when(file.getDepend()).thenReturn(Arrays.asList(depend));
when(file.getSoftDepend()).thenReturn(null);
return plugin;
}
}

View File

@ -0,0 +1,36 @@
package com.comphenix.protocol.utility;
import static org.junit.Assert.*;
import java.io.IOException;
import org.bukkit.Material;
import org.bukkit.craftbukkit.v1_6_R2.inventory.CraftItemFactory;
import org.bukkit.inventory.ItemStack;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import com.comphenix.protocol.BukkitInitialization;
@RunWith(org.powermock.modules.junit4.PowerMockRunner.class)
@PrepareForTest(CraftItemFactory.class)
public class StreamSerializerTest {
@BeforeClass
public static void initializeBukkit() throws IllegalAccessException {
BukkitInitialization.initializeItemMeta();
}
@Test
public void testSerializer() throws IOException {
ItemStack before = new ItemStack(Material.GOLD_AXE);
StreamSerializer serializer = new StreamSerializer();
String data = serializer.serializeItemStack(before);
ItemStack after = serializer.deserializeItemStack(data);
assertEquals(before.getType(), after.getType());
assertEquals(before.getAmount(), after.getAmount());
}
}