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