Added support for Spigot with Netty disabled.

I've also added BukkitExecutors to ProtocolLib, which any plugin that
depend on ProtocolLib may now use.
This commit is contained in:
Kristian S. Stangeland 2013-07-06 00:24:11 +02:00
parent 81e158d74a
commit 5e35f46b96
18 changed files with 1921 additions and 1375 deletions

View File

@ -200,6 +200,12 @@
<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>

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;
@ -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,7 +173,7 @@ 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.createManager(
getClassLoader(), getServer(), this, version, unhookTask, reporter); getClassLoader(), getServer(), this, version, unhookTask, reporter);
// Setup error reporter // Setup error reporter
@ -181,7 +182,7 @@ public class ProtocolLibrary extends JavaPlugin {
// Update injection hook // Update injection hook
try { try {
PlayerInjectHooks hook = config.getInjectionMethod(); PlayerInjectHooks hook = config.getInjectionMethod();
// Only update the hook if it's different // Only update the hook if it's different
if (!protocolManager.getPlayerHook().equals(hook)) { if (!protocolManager.getPlayerHook().equals(hook)) {
logger.info("Changing player hook from " + protocolManager.getPlayerHook() + " to " + hook); logger.info("Changing player hook from " + protocolManager.getPlayerHook() + " to " + hook);
@ -302,9 +303,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

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

View File

@ -0,0 +1,385 @@
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;
}
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

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

View File

@ -42,9 +42,11 @@ import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.server.PluginDisableEvent; import org.bukkit.event.server.PluginDisableEvent;
import org.bukkit.event.world.WorldInitEvent;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.PluginManager;
import com.comphenix.executors.BukkitFutures;
import com.comphenix.protocol.AsynchronousManager; import com.comphenix.protocol.AsynchronousManager;
import com.comphenix.protocol.ProtocolManager; import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.async.AsyncFilterManager; import com.comphenix.protocol.async.AsyncFilterManager;
@ -56,6 +58,7 @@ import com.comphenix.protocol.events.*;
import com.comphenix.protocol.injector.packet.PacketInjector; import com.comphenix.protocol.injector.packet.PacketInjector;
import com.comphenix.protocol.injector.packet.PacketInjectorBuilder; import com.comphenix.protocol.injector.packet.PacketInjectorBuilder;
import com.comphenix.protocol.injector.packet.PacketRegistry; import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.injector.player.InjectedServerConnection;
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.player.PlayerInjectorBuilder;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler.ConflictStrategy; import com.comphenix.protocol.injector.player.PlayerInjectionHandler.ConflictStrategy;
@ -67,8 +70,11 @@ 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;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
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");
@ -87,6 +93,8 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
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"); public static final ReportType REPORT_PLUGIN_VERIFIER_ERROR = new ReportType("Verifier error: %s");
public static final ReportType REPORT_TEMPORARY_EVENT_ERROR = new ReportType("Unable to register or handle temporary event.");
/** /**
* Sets the inject hook type. Different types allow for maximum compatibility. * Sets the inject hook type. Different types allow for maximum compatibility.
* @author Kristian * @author Kristian
@ -173,23 +181,31 @@ 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(
this(classLoader, server, library, new MinecraftVersion(server), unhookTask, reporter); ClassLoader classLoader, Server server, Plugin library,
} AsyncFilterManager asyncManager, MinecraftVersion mcVersion,
final DelayedSingleTask unhookTask,
/** ErrorReporter reporter, boolean nettyEnabled) {
* Only create instances of this class if protocol lib is disabled.
*/
public PacketFilterManager(ClassLoader classLoader, Server server, Plugin library,
MinecraftVersion mcVersion, DelayedSingleTask unhookTask, ErrorReporter reporter) {
if (reporter == null) if (reporter == null)
throw new IllegalArgumentException("reporter cannot be NULL."); throw new IllegalArgumentException("reporter cannot be NULL.");
if (classLoader == null) if (classLoader == null)
throw new IllegalArgumentException("classLoader cannot be NULL."); throw new IllegalArgumentException("classLoader cannot be NULL.");
// Just boilerplate // Used to determine if injection is needed
final DelayedSingleTask finalUnhookTask = unhookTask; Predicate<GamePhase> isInjectionNecessary = new Predicate<GamePhase>() {
@Override
public boolean apply(@Nullable GamePhase phase) {
boolean result = true;
if (phase.hasLogin())
result &= getPhaseLoginCount() > 0;
// Note that we will still hook players if the unhooking has been delayed
if (phase.hasPlaying())
result &= getPhasePlayingCount() > 0 || unhookTask.isRunning();
return result;
}
};
// Listener containers // Listener containers
this.recievedListeners = new SortedPacketListenerList(); this.recievedListeners = new SortedPacketListenerList();
@ -204,70 +220,99 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
// The plugin verifier // The plugin verifier
this.pluginVerifier = new PluginVerifier(library); this.pluginVerifier = new PluginVerifier(library);
// Used to determine if injection is needed // Use the correct injection type
Predicate<GamePhase> isInjectionNecessary = new Predicate<GamePhase>() { if (nettyEnabled) {
@Override spigotInjector = new SpigotPacketInjector(classLoader, reporter, this, server);
public boolean apply(@Nullable GamePhase phase) { this.playerInjection = spigotInjector.getPlayerHandler();
boolean result = true; this.packetInjector = spigotInjector.getPacketInjector();
if (phase.hasLogin()) } else {
result &= getPhaseLoginCount() > 0; // Initialize standard injection mangers
// Note that we will still hook players if the unhooking has been delayed this.playerInjection = PlayerInjectorBuilder.newBuilder().
if (phase.hasPlaying()) invoker(this).
result &= getPhasePlayingCount() > 0 || finalUnhookTask.isRunning(); server(server).
return result; reporter(reporter).
} classLoader(classLoader).
}; packetListeners(packetListeners).
injectionFilter(isInjectionNecessary).
version(mcVersion).
buildHandler();
this.packetInjector = PacketInjectorBuilder.newBuilder().
invoker(this).
reporter(reporter).
classLoader(classLoader).
playerInjection(playerInjection).
buildInjector();
}
this.asyncFilterManager = asyncManager;
// Attempt to load the list of server and client packets
try { try {
// Spigot knowsServerPackets = PacketRegistry.getServerPackets() != null;
if (SpigotPacketInjector.canUseSpigotListener()) { knowsClientPackets = PacketRegistry.getClientPackets() != null;
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).
version(mcVersion).
buildHandler();
this.packetInjector = PacketInjectorBuilder.newBuilder().
invoker(this).
reporter(reporter).
classLoader(classLoader).
playerInjection(playerInjection).
buildInjector();
}
this.asyncFilterManager = new AsyncFilterManager(reporter, server.getScheduler(), this);
// Attempt to load the list of server and client packets
try {
knowsServerPackets = PacketRegistry.getServerPackets() != null;
knowsClientPackets = PacketRegistry.getClientPackets() != null;
} catch (FieldAccessException e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_PACKET_LIST).error(e));
}
} catch (FieldAccessException e) { } catch (FieldAccessException e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_INITIALIZE_PACKET_INJECTOR).error(e)); reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_PACKET_LIST).error(e));
} }
} }
/** public static InternalManager createManager(
* Initiate logic that is performed after the world has loaded. final ClassLoader classLoader, final Server server, final Plugin library,
*/ final MinecraftVersion mcVersion, final DelayedSingleTask unhookTask,
public void postWorldLoaded() { final ErrorReporter reporter) {
playerInjection.postWorldLoaded();
final AsyncFilterManager asyncManager = new AsyncFilterManager(reporter, server.getScheduler());
// Spigot
if (SpigotPacketInjector.canUseSpigotListener()) {
// We need to delay this until we know if Netty is enabled
final DelayedPacketManager delayed = new DelayedPacketManager(reporter);
Futures.addCallback(BukkitFutures.nextEvent(library, WorldInitEvent.class), new FutureCallback<WorldInitEvent>() {
@Override
public void onSuccess(WorldInitEvent event) {
// Nevermind
if (delayed.isClosed())
return;
try {
// Now we are probably able to check for Netty
InjectedServerConnection inspector = new InjectedServerConnection(reporter, null, server, null);
Object connection = inspector.getServerConnection();
// Use netty if we have a non-standard ServerConnection class
boolean useNetty = !MinecraftReflection.isMinecraftObject(connection);
// Switch to the standard manager
delayed.setDelegate(new PacketFilterManager(
classLoader, server, library, asyncManager, mcVersion, unhookTask, reporter, useNetty)
);
// Reference this manager directly
asyncManager.setManager(delayed.getDelegate());
} catch (Exception e) {
onFailure(e);
}
}
@Override
public void onFailure(Throwable error) {
reporter.reportWarning(this, Report.newBuilder(REPORT_TEMPORARY_EVENT_ERROR).error(error));
}
});
// Let plugins use this version instead
return delayed;
} else {
// The standard manager
PacketFilterManager manager = new PacketFilterManager(
classLoader, server, library, asyncManager, mcVersion, unhookTask, reporter, false);
asyncManager.setManager(manager);
return manager;
}
} }
@Override @Override
public AsynchronousManager getAsynchronousManager() { public AsynchronousManager getAsynchronousManager() {
return asyncFilterManager; return asyncFilterManager;
@ -277,6 +322,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();
} }
@ -285,6 +331,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);
} }
@ -707,6 +754,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.");
@ -974,9 +1022,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

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

View File

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

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

@ -144,14 +144,6 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
this.serverInjection = new InjectedServerConnection(reporter, inputStreamLookup, server, netLoginInjector); this.serverInjection = new InjectedServerConnection(reporter, inputStreamLookup, server, netLoginInjector);
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.

View File

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

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

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

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

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

View File

@ -114,12 +114,7 @@ class DummyPlayerHandler implements PlayerInjectionHandler {
public void checkListener(Set<PacketListener> listeners) { public void checkListener(Set<PacketListener> listeners) {
// 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