diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java index 07330826..29dfebaf 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java @@ -173,8 +173,14 @@ public class ProtocolLibrary extends JavaPlugin { updater = new Updater(this, logger, "protocollib", getFile(), "protocol.info"); unhookTask = new DelayedSingleTask(this); - protocolManager = PacketFilterManager.createManager( - getClassLoader(), getServer(), this, version, unhookTask, reporter); + protocolManager = PacketFilterManager.newBuilder(). + classLoader(getClassLoader()). + server(getServer()). + library(this). + minecraftVersion(version). + unhookTask(unhookTask). + reporter(reporter). + build(); // Setup error reporter detailedReporter.addGlobalParameter("manager", protocolManager); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterBuilder.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterBuilder.java new file mode 100644 index 00000000..1edad3c8 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterBuilder.java @@ -0,0 +1,255 @@ +package com.comphenix.protocol.injector; + +import javax.annotation.Nonnull; + +import org.bukkit.Server; +import org.bukkit.event.world.WorldInitEvent; +import org.bukkit.plugin.Plugin; + +import com.comphenix.executors.BukkitFutures; +import com.comphenix.protocol.async.AsyncFilterManager; +import com.comphenix.protocol.error.ErrorReporter; +import com.comphenix.protocol.error.Report; +import com.comphenix.protocol.error.ReportType; +import com.comphenix.protocol.injector.player.InjectedServerConnection; +import com.comphenix.protocol.injector.spigot.SpigotPacketInjector; +import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.utility.MinecraftVersion; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; + +public class PacketFilterBuilder { + public static final ReportType REPORT_TEMPORARY_EVENT_ERROR = new ReportType("Unable to register or handle temporary event."); + + private ClassLoader classLoader; + private Server server; + private Plugin library; + private MinecraftVersion mcVersion; + private DelayedSingleTask unhookTask; + private ErrorReporter reporter; + + // Whether or not we need to enable Netty + private AsyncFilterManager asyncManager; + private boolean nettyEnabled; + + /** + * Update the current class loader. + * @param classLoader - current class loader. + * @return This builder, for chaining. + */ + public PacketFilterBuilder classLoader(@Nonnull ClassLoader classLoader) { + if (classLoader == null) + throw new IllegalArgumentException("classLoader cannot be NULL."); + this.classLoader = classLoader; + return this; + } + + /** + * Set the current server. + * @param server - current server. + * @return This builder, for chaining. + */ + public PacketFilterBuilder server(@Nonnull Server server) { + if (server == null) + throw new IllegalArgumentException("server cannot be NULL."); + this.server = server; + return this; + } + + /** + * Set a reference to the plugin instance of ProtocolLib. + * @param library - plugin instance. + * @return This builder, for chaining. + */ + public PacketFilterBuilder library(@Nonnull Plugin library) { + if (library == null) + throw new IllegalArgumentException("library cannot be NULL."); + this.library = library; + return this; + } + + /** + * Set the current Minecraft version. + * @param mcVersion - Minecraft version. + * @return This builder, for chaining. + */ + public PacketFilterBuilder minecraftVersion(@Nonnull MinecraftVersion mcVersion) { + if (mcVersion == null) + throw new IllegalArgumentException("minecraftVersion cannot be NULL."); + this.mcVersion = mcVersion; + return this; + } + + /** + * Set the task used to delay unhooking when ProtocolLib is no in use. + * @param unhookTask - the unhook task. + * @return This builder, for chaining. + */ + public PacketFilterBuilder unhookTask(@Nonnull DelayedSingleTask unhookTask) { + if (unhookTask == null) + throw new IllegalArgumentException("unhookTask cannot be NULL."); + this.unhookTask = unhookTask; + return this; + } + + /** + * Set the error reporter. + * @param reporter - new error reporter. + * @return This builder, for chaining. + */ + public PacketFilterBuilder reporter(@Nonnull ErrorReporter reporter) { + if (reporter == null) + throw new IllegalArgumentException("reporter cannot be NULL."); + this.reporter = reporter; + return this; + } + + /** + * Determine if we should prepare to hook Netty in Spigot. + *

+ * This is calculated in the {@link #build()} method. + * @return TRUE if we should, FALSE otherwise. + */ + public boolean isNettyEnabled() { + return nettyEnabled; + } + + /** + * Retrieve the class loader set in this builder. + * @return The class loader. + */ + public ClassLoader getClassLoader() { + return classLoader; + } + + /** + * Retrieve the current CraftBukkit server. + * @return Current server. + */ + public Server getServer() { + return server; + } + + /** + * Retrieve a reference to the current ProtocolLib instance. + * @return ProtocolLib. + */ + public Plugin getLibrary() { + return library; + } + + /** + * Retrieve the current Minecraft version. + * @return Current version. + */ + public MinecraftVersion getMinecraftVersion() { + return mcVersion; + } + + /** + * Retrieve the task that is used to delay unhooking when ProtocolLib is no in use. + * @return The unhook task. + */ + public DelayedSingleTask getUnhookTask() { + return unhookTask; + } + + /** + * Retrieve the error reporter. + * @return Error reporter. + */ + public ErrorReporter getReporter() { + return reporter; + } + + /** + * Retrieve the asynchronous manager. + *

+ * This is first constructed the {@link #build()} method. + * @return The asynchronous manager. + */ + public AsyncFilterManager getAsyncManager() { + return asyncManager; + } + + /** + * Create a new packet filter manager. + * @return A new packet filter manager. + */ + public InternalManager build() { + if (reporter == null) + throw new IllegalArgumentException("reporter cannot be NULL."); + if (classLoader == null) + throw new IllegalArgumentException("classLoader cannot be NULL."); + + asyncManager = new AsyncFilterManager(reporter, server.getScheduler()); + nettyEnabled = false; + + // Spigot + if (SpigotPacketInjector.canUseSpigotListener()) { + // If the server hasn't loaded yet - wait + if (InjectedServerConnection.getServerConnection(reporter, server) == null) { + // We need to delay this until we know if Netty is enabled + final DelayedPacketManager delayed = new DelayedPacketManager(reporter); + + // They must reference each other + delayed.setAsynchronousManager(asyncManager); + asyncManager.setManager(delayed); + + Futures.addCallback(BukkitFutures.nextEvent(library, WorldInitEvent.class), + new FutureCallback() { + @Override + public void onSuccess(WorldInitEvent event) { + // Nevermind + if (delayed.isClosed()) + return; + + try { + registerSpigot(delayed); + } catch (Exception e) { + onFailure(e); + } + } + + @Override + public void onFailure(Throwable error) { + reporter.reportWarning(PacketFilterBuilder.this, Report + .newBuilder(REPORT_TEMPORARY_EVENT_ERROR).error(error)); + } + }); + + System.out.println("Delaying due to Spigot"); + + // Let plugins use this version instead + return delayed; + } else { + nettyEnabled = !MinecraftReflection.isMinecraftObject( + InjectedServerConnection.getServerConnection(reporter, server)); + } + } + + // Otherwise - construct the packet filter manager right away + return buildInternal(); + } + + private void registerSpigot(DelayedPacketManager delayed) { + // Use netty if we have a non-standard ServerConnection class + nettyEnabled = !MinecraftReflection.isMinecraftObject( + InjectedServerConnection.getServerConnection(reporter, server)); + + // Switch to the standard manager + delayed.setDelegate(buildInternal()); + } + + /** + * Construct a new packet filter manager without checking for Netty. + * @return A new packet filter manager. + */ + private PacketFilterManager buildInternal() { + PacketFilterManager manager = new PacketFilterManager(this); + + // It's a cyclic reference, but it's too late to fix now + asyncManager.setManager(manager); + return manager; + } +} 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 a95309ed..2848cfe5 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java @@ -22,7 +22,6 @@ import java.lang.reflect.Method; import java.util.Collections; import java.util.List; import java.util.Set; -import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -43,11 +42,9 @@ 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; @@ -59,7 +56,6 @@ 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,12 +63,9 @@ import com.comphenix.protocol.injector.spigot.SpigotPacketInjector; import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.utility.MinecraftReflection; -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, InternalManager { @@ -94,8 +87,6 @@ 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 @@ -182,17 +173,7 @@ 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, - 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."); - + public PacketFilterManager(PacketFilterBuilder builder) { // Used to determine if injection is needed Predicate isInjectionNecessary = new Predicate() { @Override @@ -213,16 +194,16 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok this.sendingListeners = new SortedPacketListenerList(); // References - this.unhookTask = unhookTask; - this.server = server; - this.classLoader = classLoader; - this.reporter = reporter; + this.unhookTask = builder.getUnhookTask(); + this.server = builder.getServer(); + this.classLoader = builder.getClassLoader(); + this.reporter = builder.getReporter(); // The plugin verifier - this.pluginVerifier = new PluginVerifier(library); + this.pluginVerifier = new PluginVerifier(builder.getLibrary()); // Use the correct injection type - if (nettyEnabled) { + if (builder.isNettyEnabled()) { spigotInjector = new SpigotPacketInjector(classLoader, reporter, this, server); this.playerInjection = spigotInjector.getPlayerHandler(); this.packetInjector = spigotInjector.getPacketInjector(); @@ -236,7 +217,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok classLoader(classLoader). packetListeners(packetListeners). injectionFilter(isInjectionNecessary). - version(mcVersion). + version(builder.getMinecraftVersion()). buildHandler(); this.packetInjector = PacketInjectorBuilder.newBuilder(). @@ -246,7 +227,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok playerInjection(playerInjection). buildInjector(); } - this.asyncFilterManager = asyncManager; + this.asyncFilterManager = builder.getAsyncManager(); // Attempt to load the list of server and client packets try { @@ -256,87 +237,15 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_PACKET_LIST).error(e)); } } - - 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); - - // They must reference each other - delayed.setAsynchronousManager(asyncManager); - asyncManager.setManager(delayed); - final Callable registerSpigot = new Callable() { - @Override - public Object call() throws Exception { - // 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()); - return null; - } - }; - - // If the server hasn't loaded yet - wait - if (server.getWorlds().size() == 0) { - Futures.addCallback(BukkitFutures.nextEvent(library, WorldInitEvent.class), new FutureCallback() { - @Override - public void onSuccess(WorldInitEvent event) { - // Nevermind - if (delayed.isClosed()) - return; - - try { - registerSpigot.call(); - } catch (Exception e) { - onFailure(e); - } - } - - @Override - public void onFailure(Throwable error) { - reporter.reportWarning(PacketFilterManager.class, Report.newBuilder(REPORT_TEMPORARY_EVENT_ERROR).error(error)); - } - }); - - } else { - // Do it now - try { - registerSpigot.call(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - // 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; - } + /** + * Construct a new packet filter builder. + * @return New builder. + */ + public static PacketFilterBuilder newBuilder() { + return new PacketFilterBuilder(); } - + @Override public AsynchronousManager getAsynchronousManager() { return asyncFilterManager; 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 b114feaf..7ae571c2 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 @@ -31,6 +31,7 @@ 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.FieldAccessException; import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.ObjectWriter; @@ -98,6 +99,27 @@ public class InjectedServerConnection { this.netLoginInjector = netLoginInjector; } + /** + * Retrieve the current server connection. + * @param reporter - error reproter. + * @param server - the current server. + * @return The current server connection, or NULL if it hasn't been initialized yet. + * @throws FieldAccessException Reflection error. + */ + public static Object getServerConnection(ErrorReporter reporter, Server server) { + try { + // Now we are probably able to check for Netty + InjectedServerConnection inspector = new InjectedServerConnection(reporter, null, server, null); + return inspector.getServerConnection(); + } catch (IllegalAccessException e) { + throw new FieldAccessException("Reflection error.", e); + } catch (IllegalArgumentException e) { + throw new FieldAccessException("Corrupt data.", e); + } catch (InvocationTargetException e) { + throw new FieldAccessException("Minecraft error.", e); + } + } + /** * Initial reflective detective work. Will be automatically called by most methods in this class. */