diff --git a/ProtocolLib/pom.xml b/ProtocolLib/pom.xml index 4ef25e3f..ddbd5aca 100644 --- a/ProtocolLib/pom.xml +++ b/ProtocolLib/pom.xml @@ -200,6 +200,12 @@ 2.2.2 compile + + com.comphenix.executors + BukkitExecutors + 1.0.0 + compile + org.bukkit craftbukkit diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java index 962532c9..4677a5f8 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java @@ -41,6 +41,7 @@ import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.Report; import com.comphenix.protocol.error.ReportType; import com.comphenix.protocol.injector.DelayedSingleTask; +import com.comphenix.protocol.injector.InternalManager; import com.comphenix.protocol.injector.PacketFilterManager; import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; import com.comphenix.protocol.metrics.Statistics; @@ -95,7 +96,7 @@ public class ProtocolLibrary extends JavaPlugin { private static final String PERMISSION_INFO = "protocol.info"; // There should only be one protocol manager, so we'll make it static - private static PacketFilterManager protocolManager; + private static InternalManager protocolManager; // Error reporter private static ErrorReporter reporter = new BasicErrorReporter(); @@ -172,7 +173,7 @@ public class ProtocolLibrary extends JavaPlugin { updater = new Updater(this, logger, "protocollib", getFile(), "protocol.info"); unhookTask = new DelayedSingleTask(this); - protocolManager = new PacketFilterManager( + protocolManager = PacketFilterManager.createManager( getClassLoader(), getServer(), this, version, unhookTask, reporter); // Setup error reporter @@ -181,7 +182,7 @@ public class ProtocolLibrary extends JavaPlugin { // Update injection hook try { PlayerInjectHooks hook = config.getInjectionMethod(); - + // Only update the hook if it's different if (!protocolManager.getPlayerHook().equals(hook)) { logger.info("Changing player hook from " + protocolManager.getPlayerHook() + " to " + hook); @@ -302,9 +303,6 @@ public class ProtocolLibrary extends JavaPlugin { return; } - // Perform logic when the world has loaded - protocolManager.postWorldLoaded(); - // Initialize background compiler if (backgroundCompiler == null && config.isBackgroundCompilerEnabled()) { backgroundCompiler = new BackgroundCompiler(getClassLoader(), reporter); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncFilterManager.java b/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncFilterManager.java index 1b3bd8cc..6d8c1345 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncFilterManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncFilterManager.java @@ -69,12 +69,12 @@ public class AsyncFilterManager implements AsynchronousManager { // Default scheduler private final BukkitScheduler scheduler; - // Our protocol manager - private final ProtocolManager manager; - // Current packet index private final AtomicInteger currentSendingIndex = new AtomicInteger(); + // Our protocol manager + private ProtocolManager manager; + /** * Initialize a asynchronous filter manager. *

@@ -83,7 +83,7 @@ public class AsyncFilterManager implements AsynchronousManager { * @param scheduler - task scheduler. * @param manager - protocol manager. */ - public AsyncFilterManager(ErrorReporter reporter, BukkitScheduler scheduler, ProtocolManager manager) { + public AsyncFilterManager(ErrorReporter reporter, BukkitScheduler scheduler) { // Initialize timeout listeners this.serverTimeoutListeners = new SortedPacketListenerList(); this.clientTimeoutListeners = new SortedPacketListenerList(); @@ -95,12 +95,26 @@ public class AsyncFilterManager implements AsynchronousManager { this.playerSendingHandler.initializeScheduler(); this.scheduler = scheduler; - this.manager = manager; - this.reporter = reporter; 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 public AsyncListenerHandler registerAsyncHandler(PacketListener listener) { return registerAsyncHandler(listener, true); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/BukkitUnwrapper.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/BukkitUnwrapper.java index 7a1a724d..b7db2d7f 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/BukkitUnwrapper.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/BukkitUnwrapper.java @@ -1,213 +1,213 @@ -/* - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2012 Kristian S. Stangeland - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program; - * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA - * 02111-1307 USA - */ - -package com.comphenix.protocol.injector; - -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Collection; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import com.comphenix.protocol.ProtocolLibrary; -import com.comphenix.protocol.error.ErrorReporter; -import com.comphenix.protocol.error.Report; -import com.comphenix.protocol.error.ReportType; -import com.comphenix.protocol.injector.PacketConstructor.Unwrapper; -import com.comphenix.protocol.reflect.FieldUtils; -import com.comphenix.protocol.reflect.instances.DefaultInstances; -import com.google.common.primitives.Primitives; - -/** - * Represents an object capable of converting wrapped Bukkit objects into NMS objects. - *

- * Typical conversions include: - *

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

+ * Typical conversions include: + *

    + *
  • org.bukkit.entity.Player -> net.minecraft.server.EntityPlayer
  • + *
  • org.bukkit.World -> net.minecraft.server.WorldServer
  • + *
+ * + * @author Kristian + */ +public class BukkitUnwrapper implements Unwrapper { + public static final ReportType REPORT_ILLEGAL_ARGUMENT = new ReportType("Illegal argument."); + public static final ReportType REPORT_SECURITY_LIMITATION = new ReportType("Security limitation."); + public static final ReportType REPORT_CANNOT_FIND_UNWRAP_METHOD = new ReportType("Cannot find method."); + + public static final ReportType REPORT_CANNOT_READ_FIELD_HANDLE = new ReportType("Cannot read field 'handle'."); + + private static Map, Unwrapper> unwrapperCache = new ConcurrentHashMap, Unwrapper>(); + + // The current error reporter + private final ErrorReporter reporter; + + /** + * Construct a new Bukkit unwrapper with ProtocolLib's default error reporter. + */ + public BukkitUnwrapper() { + this(ProtocolLibrary.getErrorReporter()); + } + + /** + * Construct a new Bukkit unwrapper with the given error reporter. + * @param reporter - the error reporter to use. + */ + public BukkitUnwrapper(ErrorReporter reporter) { + this.reporter = reporter; + } + + @SuppressWarnings("unchecked") + @Override + public Object unwrapItem(Object wrappedObject) { + // Special case + if (wrappedObject == null) + return null; + Class currentClass = wrappedObject.getClass(); + + // Next, check for types that doesn't have a getHandle() + if (wrappedObject instanceof Collection) { + return handleCollection((Collection) wrappedObject); + } else if (Primitives.isWrapperType(currentClass) || wrappedObject instanceof String) { + return null; + } + + Unwrapper specificUnwrapper = getSpecificUnwrapper(currentClass); + + // Retrieve the handle + if (specificUnwrapper != null) + return specificUnwrapper.unwrapItem(wrappedObject); + else + return null; + } + + // Handle a collection of items + private Object handleCollection(Collection wrappedObject) { + + @SuppressWarnings("unchecked") + Collection copy = DefaultInstances.DEFAULT.getDefault(wrappedObject.getClass()); + + if (copy != null) { + // Unwrap every element + for (Object element : wrappedObject) { + copy.add(unwrapItem(element)); + } + return copy; + + } else { + // Impossible + return null; + } + } + + /** + * Retrieve a cached class unwrapper for the given class. + * @param type - the type of the class. + * @return An unwrapper for the given class. + */ + private Unwrapper getSpecificUnwrapper(Class type) { + // See if we're already determined this + if (unwrapperCache.containsKey(type)) { + // We will never remove from the cache, so this ought to be thread safe + return unwrapperCache.get(type); + } + + try { + final Method find = type.getMethod("getHandle"); + + // It's thread safe, as getMethod should return the same handle + Unwrapper methodUnwrapper = new Unwrapper() { + @Override + public Object unwrapItem(Object wrappedObject) { + + try { + return find.invoke(wrappedObject); + + } catch (IllegalArgumentException e) { + reporter.reportDetailed(this, + Report.newBuilder(REPORT_ILLEGAL_ARGUMENT).error(e).callerParam(wrappedObject, find) + ); + } catch (IllegalAccessException e) { + // Should not occur either + return null; + } catch (InvocationTargetException e) { + // This is really bad + throw new RuntimeException("Minecraft error.", e); + } + + return null; + } + }; + + unwrapperCache.put(type, methodUnwrapper); + return methodUnwrapper; + + } catch (SecurityException e) { + reporter.reportDetailed(this, + Report.newBuilder(REPORT_SECURITY_LIMITATION).error(e).callerParam(type) + ); + } catch (NoSuchMethodException e) { + // Try getting the field unwrapper too + Unwrapper fieldUnwrapper = getFieldUnwrapper(type); + + if (fieldUnwrapper != null) + return fieldUnwrapper; + else + reporter.reportDetailed(this, + Report.newBuilder(REPORT_CANNOT_FIND_UNWRAP_METHOD).error(e).callerParam(type)); + } + + // Default method + return null; + } + + /** + * Retrieve a cached unwrapper using the handle field. + * @param type - a cached field unwrapper. + * @return The cached field unwrapper. + */ + private Unwrapper getFieldUnwrapper(Class type) { + final Field find = FieldUtils.getField(type, "handle", true); + + // See if we succeeded + if (find != null) { + Unwrapper fieldUnwrapper = new Unwrapper() { + @Override + public Object unwrapItem(Object wrappedObject) { + try { + return FieldUtils.readField(find, wrappedObject, true); + } catch (IllegalAccessException e) { + reporter.reportDetailed(this, + Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).error(e).callerParam(wrappedObject, find) + ); + return null; + } + } + }; + + unwrapperCache.put(type, fieldUnwrapper); + return fieldUnwrapper; + + } else { + // Inform about this too + reporter.reportDetailed(this, + Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).callerParam(find) + ); + return null; + } + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/DelayedPacketManager.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/DelayedPacketManager.java new file mode 100644 index 00000000..eaef213f --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/DelayedPacketManager.java @@ -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 queuedListeners = Sets.newSetFromMap(Maps.newConcurrentMap()); + private final List queuedPackets = Collections.synchronizedList(Lists.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. + *

+ * 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 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 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 getSendingFilters() { + if (delegate != null) { + return delegate.getSendingFilters(); + } else { + // Linear scan is fast enough here + Set sending = Sets.newHashSet(); + + for (PacketListener listener : queuedListeners) { + sending.addAll(listener.getSendingWhitelist().getWhitelist()); + } + return sending; + } + } + + @Override + public Set getReceivingFilters() { + if (delegate != null) { + return delegate.getReceivingFilters(); + } else { + Set recieving = Sets.newHashSet(); + + for (PacketListener listener : queuedListeners) { + recieving.addAll(listener.getReceivingWhitelist().getWhitelist()); + } + return recieving; + } + } + + @Override + public void updateEntity(Entity entity, List 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 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; + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/InternalManager.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/InternalManager.java new file mode 100644 index 00000000..fe37d341 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/InternalManager.java @@ -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(); +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/ListenerInvoker.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/ListenerInvoker.java index 52b702fc..e0da7b83 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/ListenerInvoker.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/ListenerInvoker.java @@ -1,68 +1,68 @@ -/* - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2012 Kristian S. Stangeland - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program; - * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA - * 02111-1307 USA - */ - -package com.comphenix.protocol.injector; - -import com.comphenix.protocol.events.PacketEvent; - -/** - * Represents an object that initiate the packet listeners. - * - * @author Kristian - */ -public interface ListenerInvoker { - - /** - * Invokes the given packet event for every registered listener. - * @param event - the packet event to invoke. - */ - public abstract void invokePacketRecieving(PacketEvent event); - - /** - * Invokes the given packet event for every registered listener. - * @param event - the packet event to invoke. - */ - public abstract void invokePacketSending(PacketEvent event); - - /** - * Retrieve the associated ID of a packet. - * @param packet - the packet. - * @return The packet ID. - */ - public abstract int getPacketID(Object packet); - - /** - * Associate a given class with the given packet ID. Internal method. - * @param clazz - class to associate. - */ - public abstract void unregisterPacketClass(Class clazz); - - /** - * Register a given class in the packet registry. Internal method. - * @param clazz - class to register. - * @param packetID - the the new associated packet ID. - */ - public abstract void registerPacketClass(Class clazz, int packetID); - - /** - * Retrieves the correct packet class from a given packet ID. - * @param packetID - the packet ID. - * @param forceVanilla - whether or not to look for vanilla classes, not injected classes. - * @return The associated class. - */ - public abstract Class getPacketClassFromID(int packetID, boolean forceVanilla); +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +package com.comphenix.protocol.injector; + +import com.comphenix.protocol.events.PacketEvent; + +/** + * Represents an object that initiate the packet listeners. + * + * @author Kristian + */ +public interface ListenerInvoker { + + /** + * Invokes the given packet event for every registered listener. + * @param event - the packet event to invoke. + */ + public abstract void invokePacketRecieving(PacketEvent event); + + /** + * Invokes the given packet event for every registered listener. + * @param event - the packet event to invoke. + */ + public abstract void invokePacketSending(PacketEvent event); + + /** + * Retrieve the associated ID of a packet. + * @param packet - the packet. + * @return The packet ID. + */ + public abstract int getPacketID(Object packet); + + /** + * Associate a given class with the given packet ID. Internal method. + * @param clazz - class to associate. + */ + public abstract void unregisterPacketClass(Class clazz); + + /** + * Register a given class in the packet registry. Internal method. + * @param clazz - class to register. + * @param packetID - the the new associated packet ID. + */ + public abstract void registerPacketClass(Class clazz, int packetID); + + /** + * Retrieves the correct packet class from a given packet ID. + * @param packetID - the packet ID. + * @param forceVanilla - whether or not to look for vanilla classes, not injected classes. + * @return The associated class. + */ + public abstract Class getPacketClassFromID(int packetID, boolean forceVanilla); } \ No newline at end of file diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java index b8ff35f5..c40fd81d 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java @@ -42,9 +42,11 @@ import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.server.PluginDisableEvent; +import org.bukkit.event.world.WorldInitEvent; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginManager; +import com.comphenix.executors.BukkitFutures; import com.comphenix.protocol.AsynchronousManager; import com.comphenix.protocol.ProtocolManager; 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.PacketInjectorBuilder; 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.PlayerInjectorBuilder; 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.Predicate; 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_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_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. * @author Kristian @@ -173,23 +181,31 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok /** * Only create instances of this class if protocol lib is disabled. */ - public PacketFilterManager(ClassLoader classLoader, Server server, Plugin library, DelayedSingleTask unhookTask, ErrorReporter reporter) { - this(classLoader, server, library, new MinecraftVersion(server), unhookTask, reporter); - } - - /** - * Only create instances of this class if protocol lib is disabled. - */ - public PacketFilterManager(ClassLoader classLoader, Server server, Plugin library, - MinecraftVersion mcVersion, DelayedSingleTask unhookTask, ErrorReporter reporter) { + public PacketFilterManager( + ClassLoader classLoader, Server server, Plugin library, + AsyncFilterManager asyncManager, MinecraftVersion mcVersion, + final DelayedSingleTask unhookTask, + ErrorReporter reporter, boolean nettyEnabled) { if (reporter == null) throw new IllegalArgumentException("reporter cannot be NULL."); if (classLoader == null) throw new IllegalArgumentException("classLoader cannot be NULL."); - // Just boilerplate - final DelayedSingleTask finalUnhookTask = unhookTask; + // Used to determine if injection is needed + Predicate isInjectionNecessary = new Predicate() { + @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 this.recievedListeners = new SortedPacketListenerList(); @@ -204,70 +220,99 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok // The plugin verifier this.pluginVerifier = new PluginVerifier(library); - // Used to determine if injection is needed - Predicate isInjectionNecessary = new Predicate() { - @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 || finalUnhookTask.isRunning(); - return result; - } - }; + // Use the correct injection type + if (nettyEnabled) { + 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 = asyncManager; + + // Attempt to load the list of server and client packets try { - // Spigot - if (SpigotPacketInjector.canUseSpigotListener()) { - 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)); - } - + knowsServerPackets = PacketRegistry.getServerPackets() != null; + knowsClientPackets = PacketRegistry.getClientPackets() != null; } 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)); } } - /** - * Initiate logic that is performed after the world has loaded. - */ - public void postWorldLoaded() { - playerInjection.postWorldLoaded(); + public static InternalManager createManager( + final ClassLoader classLoader, final Server server, final Plugin library, + final MinecraftVersion mcVersion, final DelayedSingleTask unhookTask, + final ErrorReporter reporter) { + + 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() { + @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 public AsynchronousManager getAsynchronousManager() { return asyncFilterManager; @@ -277,6 +322,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok * Retrieves how the server packets are read. * @return Injection method for reading server packets. */ + @Override public PlayerInjectHooks getPlayerHook() { return playerInjection.getPlayerHook(); } @@ -285,6 +331,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok * Sets how the server packets are read. * @param playerHook - the new injection method for reading server packets. */ + @Override public void setPlayerHook(PlayerInjectHooks 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 plugin - the parent plugin. */ + @Override public void registerEvents(PluginManager manager, final Plugin plugin) { if (spigotInjector != null && !spigotInjector.register(plugin)) throw new IllegalArgumentException("Spigot has already been registered."); @@ -974,9 +1022,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok return hasClosed; } - /** - * Called when ProtocolLib is closing. - */ + @Override public void close() { // Guard if (hasClosed) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerConnection.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerConnection.java index 3f09fc5a..b114feaf 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerConnection.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerConnection.java @@ -1,338 +1,416 @@ -/* - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2012 Kristian S. Stangeland - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program; - * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA - * 02111-1307 USA - */ - -package com.comphenix.protocol.injector.player; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; - -import net.sf.cglib.proxy.Factory; - -import org.bukkit.Server; - -import com.comphenix.protocol.error.ErrorReporter; -import com.comphenix.protocol.error.Report; -import com.comphenix.protocol.error.ReportType; -import com.comphenix.protocol.injector.server.AbstractInputStreamLookup; -import com.comphenix.protocol.reflect.FieldUtils; -import com.comphenix.protocol.reflect.FuzzyReflection; -import com.comphenix.protocol.reflect.ObjectWriter; -import com.comphenix.protocol.reflect.VolatileField; -import com.comphenix.protocol.utility.MinecraftReflection; - -/** - * Used to ensure that the 1.3 server is referencing the correct server handler. - * - * @author Kristian - */ -class InjectedServerConnection { - // A number of things can go wrong ... - public static final ReportType REPORT_CANNOT_FIND_MINECRAFT_SERVER = new ReportType("Cannot extract minecraft server from Bukkit."); - public static final ReportType REPORT_CANNOT_INJECT_SERVER_CONNECTION = new ReportType("Cannot inject into server connection. Bad things will happen."); - - public static final ReportType REPORT_CANNOT_FIND_LISTENER_THREAD = new ReportType("Cannot find listener thread in MinecraftServer."); - public static final ReportType REPORT_CANNOT_READ_LISTENER_THREAD = new ReportType("Unable to read the listener thread."); - - public static final ReportType REPORT_CANNOT_FIND_SERVER_CONNECTION = new ReportType("Unable to retrieve server connection"); - public static final ReportType REPORT_UNEXPECTED_THREAD_COUNT = new ReportType("Unexpected number of threads in %s: %s"); - public static final ReportType REPORT_CANNOT_FIND_NET_HANDLER_THREAD = new ReportType("Unable to retrieve net handler thread."); - public static final ReportType REPORT_INSUFFICENT_THREAD_COUNT = new ReportType("Unable to inject %s lists in %s."); - - public static final ReportType REPORT_CANNOT_COPY_OLD_TO_NEW = new ReportType("Cannot copy old %s to new."); - - private static Field listenerThreadField; - private static Field minecraftServerField; - private static Field listField; - private static Field dedicatedThreadField; - - private static Method serverConnectionMethod; - - private List listFields; - private List> replacedLists; - - // Used to inject net handlers - private NetLoginInjector netLoginInjector; - - // Inject server connections - private AbstractInputStreamLookup socketInjector; - - private Server server; - private ErrorReporter reporter; - private boolean hasAttempted; - private boolean hasSuccess; - - private Object minecraftServer = null; - - public InjectedServerConnection(ErrorReporter reporter, AbstractInputStreamLookup socketInjector, Server server, NetLoginInjector netLoginInjector) { - this.listFields = new ArrayList(); - this.replacedLists = new ArrayList>(); - this.reporter = reporter; - this.server = server; - this.socketInjector = socketInjector; - this.netLoginInjector = netLoginInjector; - } - - public void injectList() { - // Only execute this method once - if (!hasAttempted) - hasAttempted = true; - else - return; - - if (minecraftServerField == null) - minecraftServerField = FuzzyReflection.fromObject(server, true). - getFieldByType("MinecraftServer", MinecraftReflection.getMinecraftServerClass()); - - try { - minecraftServer = FieldUtils.readField(minecraftServerField, server, true); - } catch (IllegalAccessException e1) { - reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_FIND_MINECRAFT_SERVER)); - return; - } - - try { - if (serverConnectionMethod == null) - serverConnectionMethod = FuzzyReflection.fromClass(minecraftServerField.getType()). - getMethodByParameters("getServerConnection", - MinecraftReflection.getServerConnectionClass(), new Class[] {}); - // We're using Minecraft 1.3.1 - injectServerConnection(); - - } catch (IllegalArgumentException e) { - - // Minecraft 1.2.5 or lower - injectListenerThread(); - - } catch (Exception e) { - // Oh damn - inform the player - reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_INJECT_SERVER_CONNECTION).error(e)); - } - } - - private void injectListenerThread() { - try { - if (listenerThreadField == null) - listenerThreadField = FuzzyReflection.fromObject(minecraftServer). - getFieldByType("networkListenThread", MinecraftReflection.getNetworkListenThreadClass()); - } catch (RuntimeException e) { - reporter.reportDetailed(this, - Report.newBuilder(REPORT_CANNOT_FIND_LISTENER_THREAD).callerParam(minecraftServer).error(e) - ); - return; - } - - Object listenerThread = null; - - // Attempt to get the thread - try { - listenerThread = listenerThreadField.get(minecraftServer); - } catch (Exception e) { - reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_READ_LISTENER_THREAD).error(e)); - return; - } - - // Inject the server socket too - injectServerSocket(listenerThread); - - // Just inject every list field we can get - injectEveryListField(listenerThread, 1); - hasSuccess = true; - } - - private void injectServerConnection() { - Object serverConnection = null; - - // Careful - we might fail - try { - serverConnection = serverConnectionMethod.invoke(minecraftServer); - } catch (Exception e) { - reporter.reportDetailed(this, - Report.newBuilder(REPORT_CANNOT_FIND_SERVER_CONNECTION).callerParam(minecraftServer).error(e) - ); - return; - } - - if (listField == null) - listField = FuzzyReflection.fromClass(serverConnectionMethod.getReturnType(), true). - getFieldByType("netServerHandlerList", List.class); - if (dedicatedThreadField == null) { - List matches = FuzzyReflection.fromObject(serverConnection, true). - getFieldListByType(Thread.class); - - // Verify the field count - if (matches.size() != 1) - reporter.reportWarning(this, - Report.newBuilder(REPORT_UNEXPECTED_THREAD_COUNT).messageParam(serverConnection.getClass(), matches.size()) - ); - else - dedicatedThreadField = matches.get(0); - } - - // Next, try to get the dedicated thread - try { - if (dedicatedThreadField != null) { - Object dedicatedThread = FieldUtils.readField(dedicatedThreadField, serverConnection, true); - - // Inject server socket and NetServerHandlers. - injectServerSocket(dedicatedThread); - injectEveryListField(dedicatedThread, 1); - } - } catch (IllegalAccessException e) { - reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_FIND_NET_HANDLER_THREAD).error(e)); - } - - injectIntoList(serverConnection, listField); - hasSuccess = true; - } - - private void injectServerSocket(Object container) { - socketInjector.inject(container); - } - - /** - * Automatically inject into every List-compatible public or private field of the given object. - * @param container - container object with the fields to inject. - * @param minimum - the minimum number of fields we expect exists. - */ - private void injectEveryListField(Object container, int minimum) { - // Ok, great. Get every list field - List lists = FuzzyReflection.fromObject(container, true).getFieldListByType(List.class); - - for (Field list : lists) { - injectIntoList(container, list); - } - - // Warn about unexpected errors - if (lists.size() < minimum) { - reporter.reportWarning(this, Report.newBuilder(REPORT_INSUFFICENT_THREAD_COUNT).messageParam(minimum, container.getClass())); - } - } - - @SuppressWarnings("unchecked") - private void injectIntoList(Object instance, Field field) { - VolatileField listFieldRef = new VolatileField(field, instance, true); - List list = (List) listFieldRef.getValue(); - - // Careful not to inject twice - if (list instanceof ReplacedArrayList) { - replacedLists.add((ReplacedArrayList) list); - } else { - ReplacedArrayList injectedList = createReplacement(list); - - replacedLists.add(injectedList); - listFieldRef.setValue(injectedList); - listFields.add(listFieldRef); - } - } - - // Hack to avoid the "moved to quickly" error - private ReplacedArrayList createReplacement(List list) { - return new ReplacedArrayList(list) { - /** - * Shut up Eclipse! - */ - private static final long serialVersionUID = 2070481080950500367L; - - // Object writer we'll use - private final ObjectWriter writer = new ObjectWriter(); - - @Override - protected void onReplacing(Object inserting, Object replacement) { - // Is this a normal Minecraft object? - if (!(inserting instanceof Factory)) { - // If so, copy the content of the old element to the new - try { - writer.copyTo(inserting, replacement, inserting.getClass()); - } catch (Throwable e) { - reporter.reportDetailed(InjectedServerConnection.this, - Report.newBuilder(REPORT_CANNOT_COPY_OLD_TO_NEW).messageParam(inserting).callerParam(inserting, replacement).error(e) - ); - } - } - } - - @Override - protected void onInserting(Object inserting) { - // Ready for some login handler injection? - if (MinecraftReflection.isLoginHandler(inserting)) { - Object replaced = netLoginInjector.onNetLoginCreated(inserting); - - // Only replace if it has changed - if (inserting != replaced) - addMapping(inserting, replaced, true); - } - } - - @Override - protected void onRemoved(Object removing) { - // Clean up? - if (MinecraftReflection.isLoginHandler(removing)) { - netLoginInjector.cleanup(removing); - } - } - }; - } - - /** - * Replace the server handler instance kept by the "keep alive" object. - * @param oldHandler - old server handler. - * @param newHandler - new, proxied server handler. - */ - public void replaceServerHandler(Object oldHandler, Object newHandler) { - if (!hasAttempted) { - injectList(); - } - - if (hasSuccess) { - for (ReplacedArrayList 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 replacedList : replacedLists) { - replacedList.removeMapping(oldHandler); - } - } - } - - /** - * Undoes everything. - */ - public void cleanupAll() { - if (replacedLists.size() > 0) { - // Repair the underlying lists - for (ReplacedArrayList replacedList : replacedLists) { - replacedList.revertAll(); - } - for (VolatileField field : listFields) { - field.revertValue(); - } - - listFields.clear(); - replacedLists.clear(); - } - } -} +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +package com.comphenix.protocol.injector.player; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import net.sf.cglib.proxy.Factory; + +import org.bukkit.Server; + +import com.comphenix.protocol.error.ErrorReporter; +import com.comphenix.protocol.error.Report; +import com.comphenix.protocol.error.ReportType; +import com.comphenix.protocol.injector.server.AbstractInputStreamLookup; +import com.comphenix.protocol.reflect.FieldUtils; +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.ObjectWriter; +import com.comphenix.protocol.reflect.VolatileField; +import com.comphenix.protocol.utility.MinecraftReflection; + +/** + * Used to ensure that the 1.3 server is referencing the correct server handler. + * + * @author Kristian + */ +public class InjectedServerConnection { + // A number of things can go wrong ... + public static final ReportType REPORT_CANNOT_FIND_MINECRAFT_SERVER = new ReportType("Cannot extract minecraft server from Bukkit."); + public static final ReportType REPORT_CANNOT_INJECT_SERVER_CONNECTION = new ReportType("Cannot inject into server connection. Bad things will happen."); + + public static final ReportType REPORT_CANNOT_FIND_LISTENER_THREAD = new ReportType("Cannot find listener thread in MinecraftServer."); + public static final ReportType REPORT_CANNOT_READ_LISTENER_THREAD = new ReportType("Unable to read the listener thread."); + + public static final ReportType REPORT_CANNOT_FIND_SERVER_CONNECTION = new ReportType("Unable to retrieve server connection"); + public static final ReportType REPORT_UNEXPECTED_THREAD_COUNT = new ReportType("Unexpected number of threads in %s: %s"); + public static final ReportType REPORT_CANNOT_FIND_NET_HANDLER_THREAD = new ReportType("Unable to retrieve net handler thread."); + public static final ReportType REPORT_INSUFFICENT_THREAD_COUNT = new ReportType("Unable to inject %s lists in %s."); + + public static final ReportType REPORT_CANNOT_COPY_OLD_TO_NEW = new ReportType("Cannot copy old %s to new."); + + private static Field listenerThreadField; + private static Field minecraftServerField; + private static Field listField; + private static Field dedicatedThreadField; + + private static Method serverConnectionMethod; + + private List listFields; + private List> replacedLists; + + // The current detected server socket + public enum ServerSocketType { + SERVER_CONNECTION, + LISTENER_THREAD, + } + + // Used to inject net handlers + private NetLoginInjector netLoginInjector; + + // Inject server connections + private AbstractInputStreamLookup socketInjector; + + // Detected by the initializer + private ServerSocketType socketType; + + private Server server; + private ErrorReporter reporter; + private boolean hasAttempted; + private boolean hasSuccess; + + private Object minecraftServer = null; + + public InjectedServerConnection(ErrorReporter reporter, AbstractInputStreamLookup socketInjector, Server server, NetLoginInjector netLoginInjector) { + this.listFields = new ArrayList(); + this.replacedLists = new ArrayList>(); + this.reporter = reporter; + this.server = server; + this.socketInjector = socketInjector; + this.netLoginInjector = netLoginInjector; + } + + /** + * Initial reflective detective work. Will be automatically called by most methods in this class. + */ + public void initialize() { + // Only execute this method once + if (!hasAttempted) + hasAttempted = true; + else + return; + + if (minecraftServerField == null) + minecraftServerField = FuzzyReflection.fromObject(server, true). + getFieldByType("MinecraftServer", MinecraftReflection.getMinecraftServerClass()); + + try { + minecraftServer = FieldUtils.readField(minecraftServerField, server, true); + } catch (IllegalAccessException e1) { + reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_FIND_MINECRAFT_SERVER)); + return; + } + + try { + if (serverConnectionMethod == null) + serverConnectionMethod = FuzzyReflection.fromClass(minecraftServerField.getType()). + getMethodByParameters("getServerConnection", + MinecraftReflection.getServerConnectionClass(), new Class[] {}); + // We're using Minecraft 1.3.1 + socketType = ServerSocketType.SERVER_CONNECTION; + + } catch (IllegalArgumentException e) { + // Minecraft 1.2.5 or lower + socketType = ServerSocketType.LISTENER_THREAD; + + } catch (Exception e) { + // Oh damn - inform the player + reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_INJECT_SERVER_CONNECTION).error(e)); + } + } + + /** + * Retrieve the known server socket type. + *

+ * This depends on the version of CraftBukkit we are using. + * @return The server socket type. + */ + public ServerSocketType getServerSocketType() { + return socketType; + } + + /** + * Inject the connection interceptor into the correct server socket implementation. + */ + public void injectList() { + initialize(); + + if (socketType == ServerSocketType.SERVER_CONNECTION) { + injectServerConnection(); + } else if (socketType == ServerSocketType.LISTENER_THREAD) { + injectListenerThread(); + } else { + // Damn it + throw new IllegalStateException("Unable to detected server connection."); + } + } + + /** + * Retrieve the listener thread field. + */ + private void initializeListenerField() { + if (listenerThreadField == null) + listenerThreadField = FuzzyReflection.fromObject(minecraftServer). + getFieldByType("networkListenThread", MinecraftReflection.getNetworkListenThreadClass()); + } + + /** + * Retrieve the listener thread object, or NULL the server isn't using this socket implementation. + * @return The listener thread, or NULL. + * @throws IllegalAccessException Cannot access field. + * @hrows RuntimeException Unexpected class structure - the field doesn't exist. + */ + public Object getListenerThread() throws RuntimeException, IllegalAccessException { + initialize(); + + if (socketType == ServerSocketType.LISTENER_THREAD) { + initializeListenerField(); + return listenerThreadField.get(minecraftServer); + } else { + return null; + } + } + + /** + * Retrieve the server connection object, or NULL if the server isn't using it as the socket implementation. + * @return The socket connection, or NULL. + * @throws IllegalAccessException If the reflective operation failed. + * @throws IllegalArgumentException If the reflective operation failed. + * @throws InvocationTargetException If the reflective operation failed. + */ + public Object getServerConnection() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + initialize(); + + if (socketType == ServerSocketType.SERVER_CONNECTION) + return serverConnectionMethod.invoke(minecraftServer); + else + return null; + } + + private void injectListenerThread() { + try { + initializeListenerField(); + } catch (RuntimeException e) { + reporter.reportDetailed(this, + Report.newBuilder(REPORT_CANNOT_FIND_LISTENER_THREAD).callerParam(minecraftServer).error(e) + ); + return; + } + + Object listenerThread = null; + + // Attempt to get the thread + try { + listenerThread = getListenerThread(); + } catch (Exception e) { + reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_READ_LISTENER_THREAD).error(e)); + return; + } + + // Inject the server socket too + injectServerSocket(listenerThread); + + // Just inject every list field we can get + injectEveryListField(listenerThread, 1); + hasSuccess = true; + } + + private void injectServerConnection() { + Object serverConnection = null; + + // Careful - we might fail + try { + serverConnection = getServerConnection(); + } catch (Exception e) { + reporter.reportDetailed(this, + Report.newBuilder(REPORT_CANNOT_FIND_SERVER_CONNECTION).callerParam(minecraftServer).error(e) + ); + return; + } + + if (listField == null) + listField = FuzzyReflection.fromClass(serverConnectionMethod.getReturnType(), true). + getFieldByType("netServerHandlerList", List.class); + if (dedicatedThreadField == null) { + List matches = FuzzyReflection.fromObject(serverConnection, true). + getFieldListByType(Thread.class); + + // Verify the field count + if (matches.size() != 1) + reporter.reportWarning(this, + Report.newBuilder(REPORT_UNEXPECTED_THREAD_COUNT).messageParam(serverConnection.getClass(), matches.size()) + ); + else + dedicatedThreadField = matches.get(0); + } + + // Next, try to get the dedicated thread + try { + if (dedicatedThreadField != null) { + Object dedicatedThread = FieldUtils.readField(dedicatedThreadField, serverConnection, true); + + // Inject server socket and NetServerHandlers. + injectServerSocket(dedicatedThread); + injectEveryListField(dedicatedThread, 1); + } + } catch (IllegalAccessException e) { + reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_FIND_NET_HANDLER_THREAD).error(e)); + } + + injectIntoList(serverConnection, listField); + hasSuccess = true; + } + + private void injectServerSocket(Object container) { + socketInjector.inject(container); + } + + /** + * Automatically inject into every List-compatible public or private field of the given object. + * @param container - container object with the fields to inject. + * @param minimum - the minimum number of fields we expect exists. + */ + private void injectEveryListField(Object container, int minimum) { + // Ok, great. Get every list field + List lists = FuzzyReflection.fromObject(container, true).getFieldListByType(List.class); + + for (Field list : lists) { + injectIntoList(container, list); + } + + // Warn about unexpected errors + if (lists.size() < minimum) { + reporter.reportWarning(this, Report.newBuilder(REPORT_INSUFFICENT_THREAD_COUNT).messageParam(minimum, container.getClass())); + } + } + + @SuppressWarnings("unchecked") + private void injectIntoList(Object instance, Field field) { + VolatileField listFieldRef = new VolatileField(field, instance, true); + List list = (List) listFieldRef.getValue(); + + // Careful not to inject twice + if (list instanceof ReplacedArrayList) { + replacedLists.add((ReplacedArrayList) list); + } else { + ReplacedArrayList injectedList = createReplacement(list); + + replacedLists.add(injectedList); + listFieldRef.setValue(injectedList); + listFields.add(listFieldRef); + } + } + + // Hack to avoid the "moved to quickly" error + private ReplacedArrayList createReplacement(List list) { + return new ReplacedArrayList(list) { + /** + * Shut up Eclipse! + */ + private static final long serialVersionUID = 2070481080950500367L; + + // Object writer we'll use + private final ObjectWriter writer = new ObjectWriter(); + + @Override + protected void onReplacing(Object inserting, Object replacement) { + // Is this a normal Minecraft object? + if (!(inserting instanceof Factory)) { + // If so, copy the content of the old element to the new + try { + writer.copyTo(inserting, replacement, inserting.getClass()); + } catch (Throwable e) { + reporter.reportDetailed(InjectedServerConnection.this, + Report.newBuilder(REPORT_CANNOT_COPY_OLD_TO_NEW).messageParam(inserting).callerParam(inserting, replacement).error(e) + ); + } + } + } + + @Override + protected void onInserting(Object inserting) { + // Ready for some login handler injection? + if (MinecraftReflection.isLoginHandler(inserting)) { + Object replaced = netLoginInjector.onNetLoginCreated(inserting); + + // Only replace if it has changed + if (inserting != replaced) + addMapping(inserting, replaced, true); + } + } + + @Override + protected void onRemoved(Object removing) { + // Clean up? + if (MinecraftReflection.isLoginHandler(removing)) { + netLoginInjector.cleanup(removing); + } + } + }; + } + + /** + * Replace the server handler instance kept by the "keep alive" object. + * @param oldHandler - old server handler. + * @param newHandler - new, proxied server handler. + */ + public void replaceServerHandler(Object oldHandler, Object newHandler) { + if (!hasAttempted) { + injectList(); + } + + if (hasSuccess) { + for (ReplacedArrayList 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 replacedList : replacedLists) { + replacedList.removeMapping(oldHandler); + } + } + } + + /** + * Undoes everything. + */ + public void cleanupAll() { + if (replacedLists.size() > 0) { + // Repair the underlying lists + for (ReplacedArrayList replacedList : replacedLists) { + replacedList.revertAll(); + } + for (VolatileField field : listFields) { + field.revertValue(); + } + + listFields.clear(); + replacedLists.clear(); + } + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java index 129a4bc1..5e008f79 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java @@ -1,154 +1,154 @@ -/* - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2012 Kristian S. Stangeland - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program; - * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA - * 02111-1307 USA - */ - -package com.comphenix.protocol.injector.player; - -import java.util.concurrent.ConcurrentMap; - -import org.bukkit.Server; -import org.bukkit.entity.Player; - -import com.comphenix.protocol.error.ErrorReporter; -import com.comphenix.protocol.error.Report; -import com.comphenix.protocol.error.ReportType; -import com.comphenix.protocol.injector.GamePhase; -import com.comphenix.protocol.injector.player.PlayerInjectionHandler.ConflictStrategy; -import com.comphenix.protocol.injector.server.TemporaryPlayerFactory; -import com.comphenix.protocol.utility.MinecraftReflection; -import com.google.common.collect.Maps; - -/** - * Injects every NetLoginHandler created by the server. - * - * @author Kristian - */ -class NetLoginInjector { - public static final ReportType REPORT_CANNOT_HOOK_LOGIN_HANDLER = new ReportType("Unable to hook %s."); - public static final ReportType REPORT_CANNOT_CLEANUP_LOGIN_HANDLER = new ReportType("Cannot cleanup %s."); - - private ConcurrentMap injectedLogins = Maps.newConcurrentMap(); - - // Handles every hook - private ProxyPlayerInjectionHandler injectionHandler; - - // Create temporary players - private TemporaryPlayerFactory playerFactory = new TemporaryPlayerFactory(); - - // The current error reporter - private ErrorReporter reporter; - private Server server; - - public NetLoginInjector(ErrorReporter reporter, Server server, ProxyPlayerInjectionHandler injectionHandler) { - this.reporter = reporter; - this.server = server; - this.injectionHandler = injectionHandler; - } - - /** - * Invoked when a NetLoginHandler has been created. - * @param inserting - the new NetLoginHandler. - * @return An injected NetLoginHandler, or the original object. - */ - public Object onNetLoginCreated(Object inserting) { - try { - // Make sure we actually need to inject during this phase - if (!injectionHandler.isInjectionNecessary(GamePhase.LOGIN)) - return inserting; - - Player temporary = playerFactory.createTemporaryPlayer(server); - // Note that we bail out if there's an existing player injector - PlayerInjector injector = injectionHandler.injectPlayer( - temporary, inserting, ConflictStrategy.BAIL_OUT, GamePhase.LOGIN); - - if (injector != null) { - // Update injector as well - TemporaryPlayerFactory.setInjectorInPlayer(temporary, injector); - injector.updateOnLogin = true; - - // Save the login - injectedLogins.putIfAbsent(inserting, injector); - } - - // NetServerInjector can never work (currently), so we don't need to replace the NetLoginHandler - return inserting; - - } catch (Throwable e) { - // Minecraft can't handle this, so we'll deal with it here - reporter.reportDetailed(this, - Report.newBuilder(REPORT_CANNOT_HOOK_LOGIN_HANDLER). - messageParam(MinecraftReflection.getNetLoginHandlerName()). - callerParam(inserting, injectionHandler). - error(e) - ); - return inserting; - } - } - - /** - * Invoked when a NetLoginHandler should be reverted. - * @param inserting - the original NetLoginHandler. - * @return An injected NetLoginHandler, or the original object. - */ - public synchronized void cleanup(Object removing) { - PlayerInjector injected = injectedLogins.get(removing); - - if (injected != null) { - try { - PlayerInjector newInjector = null; - Player player = injected.getPlayer(); - - // Clean up list - injectedLogins.remove(removing); - - // No need to clean up twice - if (injected.isClean()) - return; - - // Hack to clean up other references - newInjector = injectionHandler.getInjectorByNetworkHandler(injected.getNetworkManager()); - injectionHandler.uninjectPlayer(player); - - // Update NetworkManager - if (newInjector != null) { - if (injected instanceof NetworkObjectInjector) { - newInjector.setNetworkManager(injected.getNetworkManager(), true); - } - } - - } catch (Throwable e) { - // Don't leak this to Minecraft - reporter.reportDetailed(this, - Report.newBuilder(REPORT_CANNOT_CLEANUP_LOGIN_HANDLER). - messageParam(MinecraftReflection.getNetLoginHandlerName()). - callerParam(removing). - error(e) - ); - } - } - } - - /** - * Remove all injected hooks. - */ - public void cleanupAll() { - for (PlayerInjector injector : injectedLogins.values()) { - injector.cleanupAll(); - } - - injectedLogins.clear(); - } -} +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +package com.comphenix.protocol.injector.player; + +import java.util.concurrent.ConcurrentMap; + +import org.bukkit.Server; +import org.bukkit.entity.Player; + +import com.comphenix.protocol.error.ErrorReporter; +import com.comphenix.protocol.error.Report; +import com.comphenix.protocol.error.ReportType; +import com.comphenix.protocol.injector.GamePhase; +import com.comphenix.protocol.injector.player.PlayerInjectionHandler.ConflictStrategy; +import com.comphenix.protocol.injector.server.TemporaryPlayerFactory; +import com.comphenix.protocol.utility.MinecraftReflection; +import com.google.common.collect.Maps; + +/** + * Injects every NetLoginHandler created by the server. + * + * @author Kristian + */ +class NetLoginInjector { + public static final ReportType REPORT_CANNOT_HOOK_LOGIN_HANDLER = new ReportType("Unable to hook %s."); + public static final ReportType REPORT_CANNOT_CLEANUP_LOGIN_HANDLER = new ReportType("Cannot cleanup %s."); + + private ConcurrentMap injectedLogins = Maps.newConcurrentMap(); + + // Handles every hook + private ProxyPlayerInjectionHandler injectionHandler; + + // Create temporary players + private TemporaryPlayerFactory playerFactory = new TemporaryPlayerFactory(); + + // The current error reporter + private ErrorReporter reporter; + private Server server; + + public NetLoginInjector(ErrorReporter reporter, Server server, ProxyPlayerInjectionHandler injectionHandler) { + this.reporter = reporter; + this.server = server; + this.injectionHandler = injectionHandler; + } + + /** + * Invoked when a NetLoginHandler has been created. + * @param inserting - the new NetLoginHandler. + * @return An injected NetLoginHandler, or the original object. + */ + public Object onNetLoginCreated(Object inserting) { + try { + // Make sure we actually need to inject during this phase + if (!injectionHandler.isInjectionNecessary(GamePhase.LOGIN)) + return inserting; + + Player temporary = playerFactory.createTemporaryPlayer(server); + // Note that we bail out if there's an existing player injector + PlayerInjector injector = injectionHandler.injectPlayer( + temporary, inserting, ConflictStrategy.BAIL_OUT, GamePhase.LOGIN); + + if (injector != null) { + // Update injector as well + TemporaryPlayerFactory.setInjectorInPlayer(temporary, injector); + injector.updateOnLogin = true; + + // Save the login + injectedLogins.putIfAbsent(inserting, injector); + } + + // NetServerInjector can never work (currently), so we don't need to replace the NetLoginHandler + return inserting; + + } catch (Throwable e) { + // Minecraft can't handle this, so we'll deal with it here + reporter.reportDetailed(this, + Report.newBuilder(REPORT_CANNOT_HOOK_LOGIN_HANDLER). + messageParam(MinecraftReflection.getNetLoginHandlerName()). + callerParam(inserting, injectionHandler). + error(e) + ); + return inserting; + } + } + + /** + * Invoked when a NetLoginHandler should be reverted. + * @param inserting - the original NetLoginHandler. + * @return An injected NetLoginHandler, or the original object. + */ + public synchronized void cleanup(Object removing) { + PlayerInjector injected = injectedLogins.get(removing); + + if (injected != null) { + try { + PlayerInjector newInjector = null; + Player player = injected.getPlayer(); + + // Clean up list + injectedLogins.remove(removing); + + // No need to clean up twice + if (injected.isClean()) + return; + + // Hack to clean up other references + newInjector = injectionHandler.getInjectorByNetworkHandler(injected.getNetworkManager()); + injectionHandler.uninjectPlayer(player); + + // Update NetworkManager + if (newInjector != null) { + if (injected instanceof NetworkObjectInjector) { + newInjector.setNetworkManager(injected.getNetworkManager(), true); + } + } + + } catch (Throwable e) { + // Don't leak this to Minecraft + reporter.reportDetailed(this, + Report.newBuilder(REPORT_CANNOT_CLEANUP_LOGIN_HANDLER). + messageParam(MinecraftReflection.getNetLoginHandlerName()). + callerParam(removing). + error(e) + ); + } + } + } + + /** + * Remove all injected hooks. + */ + public void cleanupAll() { + for (PlayerInjector injector : injectedLogins.values()) { + injector.cleanupAll(); + } + + injectedLogins.clear(); + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java index 85669c69..bb8f4a2b 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java @@ -161,9 +161,4 @@ public interface PlayerInjectionHandler { * Close any lingering proxy injections. */ public abstract void close(); - - /** - * Perform any action that must be delayed until the world(s) has loaded. - */ - public abstract void postWorldLoaded(); } \ No newline at end of file diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java index 15570caf..584cb8af 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java @@ -144,14 +144,6 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { this.serverInjection = new InjectedServerConnection(reporter, inputStreamLookup, server, netLoginInjector); 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. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/AbstractInputStreamLookup.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/AbstractInputStreamLookup.java index 5fde4cdc..0af83561 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/AbstractInputStreamLookup.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/AbstractInputStreamLookup.java @@ -1,87 +1,82 @@ -package com.comphenix.protocol.injector.server; - -import java.io.InputStream; -import java.net.Socket; -import java.net.SocketAddress; -import org.bukkit.Server; -import org.bukkit.entity.Player; - -import com.comphenix.protocol.error.ErrorReporter; - -public abstract class AbstractInputStreamLookup { - // Error reporter - protected final ErrorReporter reporter; - - // Reference to the server itself - protected final Server server; - - protected AbstractInputStreamLookup(ErrorReporter reporter, Server server) { - this.reporter = reporter; - this.server = server; - } - - /** - * Inject the given server thread or dedicated connection. - * @param container - class that contains a ServerSocket field. - */ - public abstract void inject(Object container); - - /** - * Invoked when the world has loaded. - */ - public abstract void postWorldLoaded(); - - /** - * Retrieve the associated socket injector for a player. - * @param input - the indentifying filtered input stream. - * @return The socket injector we have associated with this player. - */ - public abstract SocketInjector waitSocketInjector(InputStream input); - - /** - * Retrieve an injector by its socket. - * @param socket - the socket. - * @return The socket injector. - */ - public abstract SocketInjector waitSocketInjector(Socket socket); - - /** - * Retrieve a injector by its address. - * @param address - the address of the socket. - * @return The socket injector, or NULL if not found. - */ - public abstract SocketInjector waitSocketInjector(SocketAddress address); - - /** - * Attempt to get a socket injector without blocking the thread. - * @param address - the address to lookup. - * @return The socket injector, or NULL if not found. - */ - public abstract SocketInjector peekSocketInjector(SocketAddress address); - - /** - * Associate a given socket address to the provided socket injector. - * @param address - the socket address to associate. - * @param injector - the injector. - */ - public abstract void setSocketInjector(SocketAddress address, SocketInjector injector); - - /** - * If a player can hold a reference to its parent injector, this method will update that reference. - * @param previous - the previous injector. - * @param current - the new injector. - */ - protected void onPreviousSocketOverwritten(SocketInjector previous, SocketInjector current) { - Player player = previous.getPlayer(); - - // Default implementation - if (player instanceof InjectorContainer) { - TemporaryPlayerFactory.setInjectorInPlayer(player, current); - } - } - - /** - * Invoked when the injection should be undone. - */ - public abstract void cleanupAll(); +package com.comphenix.protocol.injector.server; + +import java.io.InputStream; +import java.net.Socket; +import java.net.SocketAddress; +import org.bukkit.Server; +import org.bukkit.entity.Player; + +import com.comphenix.protocol.error.ErrorReporter; + +public abstract class AbstractInputStreamLookup { + // Error reporter + protected final ErrorReporter reporter; + + // Reference to the server itself + protected final Server server; + + protected AbstractInputStreamLookup(ErrorReporter reporter, Server server) { + this.reporter = reporter; + this.server = server; + } + + /** + * Inject the given server thread or dedicated connection. + * @param container - class that contains a ServerSocket field. + */ + public abstract void inject(Object container); + + /** + * Retrieve the associated socket injector for a player. + * @param input - the indentifying filtered input stream. + * @return The socket injector we have associated with this player. + */ + public abstract SocketInjector waitSocketInjector(InputStream input); + + /** + * Retrieve an injector by its socket. + * @param socket - the socket. + * @return The socket injector. + */ + public abstract SocketInjector waitSocketInjector(Socket socket); + + /** + * Retrieve a injector by its address. + * @param address - the address of the socket. + * @return The socket injector, or NULL if not found. + */ + public abstract SocketInjector waitSocketInjector(SocketAddress address); + + /** + * Attempt to get a socket injector without blocking the thread. + * @param address - the address to lookup. + * @return The socket injector, or NULL if not found. + */ + public abstract SocketInjector peekSocketInjector(SocketAddress address); + + /** + * Associate a given socket address to the provided socket injector. + * @param address - the socket address to associate. + * @param injector - the injector. + */ + public abstract void setSocketInjector(SocketAddress address, SocketInjector injector); + + /** + * If a player can hold a reference to its parent injector, this method will update that reference. + * @param previous - the previous injector. + * @param current - the new injector. + */ + protected void onPreviousSocketOverwritten(SocketInjector previous, SocketInjector current) { + Player player = previous.getPlayer(); + + // Default implementation + if (player instanceof InjectorContainer) { + TemporaryPlayerFactory.setInjectorInPlayer(player, current); + } + } + + /** + * Invoked when the injection should be undone. + */ + public abstract void cleanupAll(); } \ No newline at end of file diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/BukkitSocketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/BukkitSocketInjector.java index 8e72db01..e4f55330 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/BukkitSocketInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/BukkitSocketInjector.java @@ -10,36 +10,14 @@ import java.util.List; import org.bukkit.entity.Player; public class BukkitSocketInjector implements SocketInjector { - /** - * Represents a single send packet command. - * @author Kristian - */ - static class SendPacketCommand { - private final Object packet; - private final boolean filtered; - - public SendPacketCommand(Object packet, boolean filtered) { - this.packet = packet; - this.filtered = filtered; - } - - public Object getPacket() { - return packet; - } - - public boolean isFiltered() { - return filtered; - } - } - private Player player; // Queue of server packets - private List syncronizedQueue = Collections.synchronizedList(new ArrayList()); + private List syncronizedQueue = Collections.synchronizedList(new ArrayList()); /** * Represents a temporary socket injector. - * @param temporaryPlayer - + * @param temporaryPlayer - a temporary player. */ public BukkitSocketInjector(Player player) { if (player == null) @@ -65,7 +43,7 @@ public class BukkitSocketInjector implements SocketInjector { @Override public void sendServerPacket(Object packet, boolean filtered) throws InvocationTargetException { - SendPacketCommand command = new SendPacketCommand(packet, filtered); + QueuedSendPacket command = new QueuedSendPacket(packet, filtered); // Queue until we can find something better syncronizedQueue.add(command); @@ -86,7 +64,7 @@ public class BukkitSocketInjector implements SocketInjector { // Transmit all queued packets to a different injector. try { synchronized(syncronizedQueue) { - for (SendPacketCommand command : syncronizedQueue) { + for (QueuedSendPacket command : syncronizedQueue) { delegate.sendServerPacket(command.getPacket(), command.isFiltered()); } syncronizedQueue.clear(); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamReflectLookup.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamReflectLookup.java index 37f5d6b6..564687d0 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamReflectLookup.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamReflectLookup.java @@ -1,191 +1,186 @@ -package com.comphenix.protocol.injector.server; - -import java.io.FilterInputStream; -import java.io.InputStream; -import java.lang.reflect.Field; -import java.net.Socket; -import java.net.SocketAddress; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.TimeUnit; - -import org.bukkit.Server; - -import com.comphenix.protocol.concurrency.BlockingHashMap; -import com.comphenix.protocol.error.ErrorReporter; -import com.comphenix.protocol.reflect.FieldAccessException; -import com.comphenix.protocol.reflect.FieldUtils; -import com.comphenix.protocol.reflect.FuzzyReflection; -import com.google.common.collect.MapMaker; - -class InputStreamReflectLookup extends AbstractInputStreamLookup { - // Used to access the inner input stream of a filtered input stream - private static Field filteredInputField; - - // The default lookup timeout - private static final long DEFAULT_TIMEOUT = 2000; // ms - - // Using weak keys and values ensures that we will not hold up garbage collection - protected BlockingHashMap addressLookup = new BlockingHashMap(); - protected ConcurrentMap inputLookup = new MapMaker().weakValues().makeMap(); - - // The timeout - private final long injectorTimeout; - - public InputStreamReflectLookup(ErrorReporter reporter, Server server) { - this(reporter, server, DEFAULT_TIMEOUT); - } - - /** - * Initialize a reflect lookup with a given default injector timeout. - *

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

+ * This timeout defines the maximum amount of time to wait until an injector has been discovered. + * @param reporter - the error reporter. + * @param server - the current Bukkit server. + * @param injectorTimeout - the injector timeout. + */ + public InputStreamReflectLookup(ErrorReporter reporter, Server server, long injectorTimeout) { + super(reporter, server); + this.injectorTimeout = injectorTimeout; + } + + @Override + public void inject(Object container) { + // Do nothing + } + + @Override + public SocketInjector peekSocketInjector(SocketAddress address) { + try { + return addressLookup.get(address, 0, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + // Whatever + return null; + } + } + + @Override + public SocketInjector waitSocketInjector(SocketAddress address) { + try { + // Note that we actually SWALLOW interrupts here - this is because Minecraft uses interrupts to + // periodically wake up waiting readers and writers. We have to wait for the dedicated server thread + // to catch up, so we'll swallow these interrupts. + // + // TODO: Consider if we should raise the thread priority of the dedicated server listener thread. + return addressLookup.get(address, injectorTimeout, TimeUnit.MILLISECONDS, true); + } catch (InterruptedException e) { + // This cannot be! + throw new IllegalStateException("Impossible exception occured!", e); + } + } + + @Override + public SocketInjector waitSocketInjector(Socket socket) { + return waitSocketInjector(socket.getRemoteSocketAddress()); + } + + @Override + public SocketInjector waitSocketInjector(InputStream input) { + try { + SocketAddress address = waitSocketAddress(input); + + // Guard against NPE + if (address != null) + return waitSocketInjector(address); + else + return null; + } catch (IllegalAccessException e) { + throw new FieldAccessException("Cannot find or access socket field for " + input, e); + } + } + + /** + * Use reflection to get the underlying socket address from an input stream. + * @param stream - the socket stream to lookup. + * @return The underlying socket address, or NULL if not found. + * @throws IllegalAccessException Unable to access socket field. + */ + private SocketAddress waitSocketAddress(InputStream stream) throws IllegalAccessException { + // Extra check, just in case + if (stream instanceof FilterInputStream) + return waitSocketAddress(getInputStream((FilterInputStream) stream)); + + SocketAddress result = inputLookup.get(stream); + + if (result == null) { + Socket socket = lookupSocket(stream); + + // Save it + result = socket.getRemoteSocketAddress(); + inputLookup.put(stream, result); + } + return result; + } + + /** + * Retrieve the underlying input stream that is associated with a given filter input stream. + * @param filtered - the filter input stream. + * @return The underlying input stream that is being filtered. + * @throws FieldAccessException Unable to access input stream. + */ + protected static InputStream getInputStream(FilterInputStream filtered) { + if (filteredInputField == null) + filteredInputField = FuzzyReflection.fromClass(FilterInputStream.class, true). + getFieldByType("in", InputStream.class); + + InputStream current = filtered; + + try { + // Iterate until we find the real input stream + while (current instanceof FilterInputStream) { + current = (InputStream) FieldUtils.readField(filteredInputField, current, true); + } + return current; + } catch (IllegalAccessException e) { + throw new FieldAccessException("Cannot access filtered input field.", e); + } + } + + @Override + public void setSocketInjector(SocketAddress address, SocketInjector injector) { + if (address == null) + throw new IllegalArgumentException("address cannot be NULL"); + if (injector == null) + throw new IllegalArgumentException("injector cannot be NULL."); + + SocketInjector previous = addressLookup.put(address, injector); + + // Any previous temporary players will also be associated + if (previous != null) { + // Update the reference to any previous injector + onPreviousSocketOverwritten(previous, injector); + } + } + + @Override + public void cleanupAll() { + // Do nothing + } + + /** + * Lookup the underlying socket of a stream through reflection. + * @param stream - the socket stream. + * @return The underlying socket. + * @throws IllegalAccessException If reflection failed. + */ + private static Socket lookupSocket(InputStream stream) throws IllegalAccessException { + if (stream instanceof FilterInputStream) { + return lookupSocket(getInputStream((FilterInputStream) stream)); + } else { + // Just do it + Field socketField = FuzzyReflection.fromObject(stream, true). + getFieldByType("socket", Socket.class); + + return (Socket) FieldUtils.readField(socketField, stream, true); + } + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/QueuedSendPacket.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/QueuedSendPacket.java new file mode 100644 index 00000000..e12f47a8 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/QueuedSendPacket.java @@ -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; + } +} \ No newline at end of file diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactory.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactory.java index 8c2e0e05..62db7f2a 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactory.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactory.java @@ -1,197 +1,197 @@ -/* - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2012 Kristian S. Stangeland - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program; - * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA - * 02111-1307 USA - */ - -package com.comphenix.protocol.injector.server; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -import net.sf.cglib.proxy.Callback; -import net.sf.cglib.proxy.CallbackFilter; -import net.sf.cglib.proxy.Enhancer; -import net.sf.cglib.proxy.MethodInterceptor; -import net.sf.cglib.proxy.MethodProxy; -import net.sf.cglib.proxy.NoOp; - -import org.bukkit.Server; -import org.bukkit.entity.Player; - -import com.comphenix.protocol.injector.PacketConstructor; -import com.comphenix.protocol.reflect.FieldAccessException; - -/** - * Create fake player instances that represents pre-authenticated clients. - */ -public class TemporaryPlayerFactory { - // Helpful constructors - private final PacketConstructor chatPacket; - - // Prevent too many class creations - private static CallbackFilter callbackFilter; - - public TemporaryPlayerFactory() { - chatPacket = PacketConstructor.DEFAULT.withPacket(3, new Object[] { "DEMO" }); - } - - /** - * Retrieve the injector from a given player if it contains one. - * @param player - the player that may contain a reference to a player injector. - * @return The referenced player injector, or NULL if none can be found. - */ - public static SocketInjector getInjectorFromPlayer(Player player) { - if (player instanceof InjectorContainer) { - return ((InjectorContainer) player).getInjector(); - } - return null; - } - - /** - * Set the player injector, if possible. - * @param player - the player to update. - * @param injector - the injector to store. - */ - public static void setInjectorInPlayer(Player player, SocketInjector injector) { - ((InjectorContainer) player).setInjector(injector); - } - - /** - * Construct a temporary player that supports a subset of every player command. - *

- * Supported methods include: - *

    - *
  • getPlayer()
  • - *
  • getAddress()
  • - *
  • getServer()
  • - *
  • chat(String)
  • - *
  • sendMessage(String)
  • - *
  • sendMessage(String[])
  • - *
  • kickPlayer(String)
  • - *
- *

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

+ * Supported methods include: + *

    + *
  • getPlayer()
  • + *
  • getAddress()
  • + *
  • getServer()
  • + *
  • chat(String)
  • + *
  • sendMessage(String)
  • + *
  • sendMessage(String[])
  • + *
  • kickPlayer(String)
  • + *
+ *

+ * Note that a temporary player has not yet been assigned a name, and thus cannot be + * uniquely identified. Use the address instead. + * @param injector - the player injector used. + * @param server - the current server. + * @return A temporary player instance. + */ + public Player createTemporaryPlayer(final Server server) { + + // Default implementation + Callback implementation = new MethodInterceptor() { + @Override + public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { + + String methodName = method.getName(); + SocketInjector injector = ((InjectorContainer) obj).getInjector(); + + if (injector == null) + throw new IllegalStateException("Unable to find injector."); + + // Use the socket to get the address + if (methodName.equalsIgnoreCase("isOnline")) + return injector.getSocket() != null && injector.getSocket().isConnected(); + if (methodName.equalsIgnoreCase("getName")) + return "UNKNOWN[" + injector.getSocket().getRemoteSocketAddress() + "]"; + if (methodName.equalsIgnoreCase("getPlayer")) + return injector.getUpdatedPlayer(); + if (methodName.equalsIgnoreCase("getAddress")) + return injector.getAddress(); + if (methodName.equalsIgnoreCase("getServer")) + return server; + + try { + // Handle send message methods + if (methodName.equalsIgnoreCase("chat") || methodName.equalsIgnoreCase("sendMessage")) { + Object argument = args[0]; + + // Dynamic overloading + if (argument instanceof String) { + return sendMessage(injector, (String) argument); + } else if (argument instanceof String[]) { + for (String message : (String[]) argument) { + sendMessage(injector, message); + } + return null; + } + } + } catch (InvocationTargetException e) { + throw e.getCause(); + } + + // Also, handle kicking + if (methodName.equalsIgnoreCase("kickPlayer")) { + injector.disconnect((String) args[0]); + return null; + } + + // Ignore all other methods + throw new UnsupportedOperationException( + "The method " + method.getName() + " is not supported for temporary players."); + } + }; + + // Shared callback filter + if (callbackFilter == null) { + callbackFilter = new CallbackFilter() { + @Override + public int accept(Method method) { + // Do not override the object method or the superclass methods + if (method.getDeclaringClass().equals(Object.class) || + method.getDeclaringClass().equals(InjectorContainer.class)) + return 0; + else + return 1; + } + }; + } + + // CGLib is amazing + Enhancer ex = new Enhancer(); + ex.setSuperclass(InjectorContainer.class); + ex.setInterfaces(new Class[] { Player.class }); + ex.setCallbacks(new Callback[] { NoOp.INSTANCE, implementation }); + ex.setCallbackFilter(callbackFilter); + + return (Player) ex.create(); + } + + /** + * Construct a temporary player with the given associated socket injector. + * @param server - the parent server. + * @param injector - the referenced socket injector. + * @return The temporary player. + */ + public Player createTemporaryPlayer(Server server, SocketInjector injector) { + Player temporary = createTemporaryPlayer(server); + + ((InjectorContainer) temporary).setInjector(injector); + return temporary; + } + + /** + * Send a message to the given client. + * @param injector - the injector representing the client. + * @param message - a message. + * @return Always NULL. + * @throws InvocationTargetException If the message couldn't be sent. + * @throws FieldAccessException If we were unable to construct the message packet. + */ + private Object sendMessage(SocketInjector injector, String message) throws InvocationTargetException, FieldAccessException { + injector.sendServerPacket(chatPacket.createPacket(message).getHandle(), false); + return null; + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPlayerHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPlayerHandler.java index 8df7f70c..dd64e3ad 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPlayerHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPlayerHandler.java @@ -114,12 +114,7 @@ class DummyPlayerHandler implements PlayerInjectionHandler { public void checkListener(Set listeners) { // Yes, really } - - @Override - public void postWorldLoaded() { - // Do nothing - } - + @Override public void updatePlayer(Player player) { // Do nothing