diff --git a/pom.xml b/pom.xml index aaf7cc360..198d6b463 100644 --- a/pom.xml +++ b/pom.xml @@ -310,6 +310,10 @@ junit * + + io.netty + * + diff --git a/src/main/java/com/comphenix/protocol/PacketStream.java b/src/main/java/com/comphenix/protocol/PacketStream.java index 5df88d6d7..8d9a7c2e9 100644 --- a/src/main/java/com/comphenix/protocol/PacketStream.java +++ b/src/main/java/com/comphenix/protocol/PacketStream.java @@ -17,98 +17,87 @@ package com.comphenix.protocol; -import java.lang.reflect.InvocationTargetException; - -import org.bukkit.entity.Player; - import com.comphenix.protocol.events.ListenerPriority; import com.comphenix.protocol.events.NetworkMarker; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.injector.netty.WirePacket; +import org.bukkit.entity.Player; /** * Represents a object capable of sending or receiving packets. - * + * * @author Kristian */ public interface PacketStream { + /** * Send a packet to the given player. + * * @param receiver - the reciever. - * @param packet - packet to send. - * @throws InvocationTargetException - if an error occured when sending the packet. + * @param packet - packet to send. */ - public void sendServerPacket(Player receiver, PacketContainer packet) - throws InvocationTargetException; + void sendServerPacket(Player receiver, PacketContainer packet); /** * Send a packet to the given player. + * * @param receiver - the reciever. - * @param packet - packet to send. - * @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}. - * @throws InvocationTargetException - if an error occured when sending the packet. + * @param packet - packet to send. + * @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}. */ - public void sendServerPacket(Player receiver, PacketContainer packet, boolean filters) - throws InvocationTargetException; - + void sendServerPacket(Player receiver, PacketContainer packet, boolean filters); + /** * Send a packet to the given player. + * * @param receiver - the receiver. - * @param packet - packet to send. - * @param marker - the network marker to use. - * @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}. - * @throws InvocationTargetException - if an error occured when sending the packet. + * @param packet - packet to send. + * @param marker - the network marker to use. + * @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}. */ - public void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters) - throws InvocationTargetException; + void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters); /** * Send a wire packet to the given player. + * * @param receiver - the receiver. - * @param id - packet id. - * @param bytes - packet bytes. - * @throws InvocationTargetException if an error occured when sending the packet. + * @param id - packet id. + * @param bytes - packet bytes. */ - public void sendWirePacket(Player receiver, int id, byte[] bytes) throws InvocationTargetException; + void sendWirePacket(Player receiver, int id, byte[] bytes); /** * Send a wire packet to the given player. + * * @param receiver - the receiver. - * @param packet - packet to send. - * @throws InvocationTargetException if an error occured when sending the packet. + * @param packet - packet to send. */ - public void sendWirePacket(Player receiver, WirePacket packet) throws InvocationTargetException; + void sendWirePacket(Player receiver, WirePacket packet); /** * Simulate recieving a certain packet from a given player. + * * @param sender - the sender. * @param packet - the packet that was sent. - * @throws InvocationTargetException If the reflection machinery failed. - * @throws IllegalAccessException If the underlying method caused an error. */ - public void recieveClientPacket(Player sender, PacketContainer packet) - throws IllegalAccessException, InvocationTargetException; + void receiveClientPacket(Player sender, PacketContainer packet); /** * Simulate recieving a certain packet from a given player. - * @param sender - the sender. - * @param packet - the packet that was sent. + * + * @param sender - the sender. + * @param packet - the packet that was sent. * @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}. - * @throws InvocationTargetException If the reflection machinery failed. - * @throws IllegalAccessException If the underlying method caused an error. */ - public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters) - throws IllegalAccessException, InvocationTargetException; - + void receiveClientPacket(Player sender, PacketContainer packet, boolean filters); + /** * Simulate recieving a certain packet from a given player. - * @param sender - the sender. - * @param packet - the packet that was sent. - * @param marker - the network marker to use. + * + * @param sender - the sender. + * @param packet - the packet that was sent. + * @param marker - the network marker to use. * @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}. - * @throws InvocationTargetException If the reflection machinery failed. - * @throws IllegalAccessException If the underlying method caused an error. */ - public void recieveClientPacket(Player sender, PacketContainer packet, NetworkMarker marker, boolean filters) - throws IllegalAccessException, InvocationTargetException; + void receiveClientPacket(Player sender, PacketContainer packet, NetworkMarker marker, boolean filters); } diff --git a/src/main/java/com/comphenix/protocol/ProtocolConfig.java b/src/main/java/com/comphenix/protocol/ProtocolConfig.java index 7710f0f91..bab936894 100644 --- a/src/main/java/com/comphenix/protocol/ProtocolConfig.java +++ b/src/main/java/com/comphenix/protocol/ProtocolConfig.java @@ -1,43 +1,39 @@ /** - * 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 + * 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; +import com.google.common.base.Charsets; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.common.io.Files; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; -import java.util.Locale; - import org.bukkit.configuration.Configuration; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.plugin.Plugin; -import com.comphenix.protocol.injector.PlayerInjectHooks; -import com.google.common.base.Charsets; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; -import com.google.common.io.Files; - /** * Represents the configuration of ProtocolLib. - * + * * @author Kristian */ public class ProtocolConfig { + private static final String LAST_UPDATE_FILE = "lastupdate"; private static final String SECTION_GLOBAL = "global"; @@ -98,7 +94,7 @@ public void reloadConfig() { /** * Load the last update time stamp from the file system. - * + * * @return Last update time stamp. */ private long loadLastUpdate() { @@ -119,7 +115,7 @@ private long loadLastUpdate() { /** * Store the given time stamp. - * + * * @param value - time stamp to store. */ private void saveLastUpdate(long value) { @@ -128,8 +124,9 @@ private void saveLastUpdate(long value) { // The data folder must exist dataFile.getParentFile().mkdirs(); - if (dataFile.exists()) + if (dataFile.exists()) { dataFile.delete(); + } try { Files.write(Long.toString(value), dataFile, Charsets.UTF_8); @@ -140,7 +137,7 @@ private void saveLastUpdate(long value) { /** * Retrieve the file that is used to store the update time stamp. - * + * * @return File storing the update time stamp. */ private File getLastUpdateFile() { @@ -149,7 +146,7 @@ private File getLastUpdateFile() { /** * Load data sections. - * + * * @param copyDefaults - whether or not to copy configuration defaults. */ private void loadSections(boolean copyDefaults) { @@ -167,8 +164,9 @@ private void loadSections(boolean copyDefaults) { if (copyDefaults && (!getFile().exists() || global == null || updater == null)) { loadingSections = true; - if (config != null) + if (config != null) { config.options().copyDefaults(true); + } plugin.saveDefaultConfig(); plugin.reloadConfig(); loadingSections = false; @@ -180,7 +178,7 @@ private void loadSections(boolean copyDefaults) { /** * Set a particular configuration key value pair. - * + * * @param section - the configuration root. * @param path - the path to the key. * @param value - the value to set. @@ -210,7 +208,7 @@ private T getUpdaterValue(String path, T def) { /** * Retrieve a reference to the configuration file. - * + * * @return Configuration file on disk. */ public File getFile() { @@ -219,7 +217,7 @@ public File getFile() { /** * Determine if detailed error reporting is enabled. Default FALSE. - * + * * @return TRUE if it is enabled, FALSE otherwise. */ public boolean isDetailedErrorReporting() { @@ -228,7 +226,7 @@ public boolean isDetailedErrorReporting() { /** * Set whether or not detailed error reporting is enabled. - * + * * @param value - TRUE if it is enabled, FALSE otherwise. */ public void setDetailedErrorReporting(boolean value) { @@ -237,7 +235,7 @@ public void setDetailedErrorReporting(boolean value) { /** * Retrieve whether or not ProtocolLib should determine if a new version has been released. - * + * * @return TRUE if it should do this automatically, FALSE otherwise. */ public boolean isAutoNotify() { @@ -246,7 +244,7 @@ public boolean isAutoNotify() { /** * Set whether or not ProtocolLib should determine if a new version has been released. - * + * * @param value - TRUE to do this automatically, FALSE otherwise. */ public void setAutoNotify(boolean value) { @@ -256,7 +254,7 @@ public void setAutoNotify(boolean value) { /** * Retrieve whether or not ProtocolLib should automatically download the new version. - * + * * @return TRUE if it should, FALSE otherwise. */ public boolean isAutoDownload() { @@ -265,7 +263,7 @@ public boolean isAutoDownload() { /** * Set whether or not ProtocolLib should automatically download the new version. - * + * * @param value - TRUE if it should. FALSE otherwise. */ public void setAutoDownload(boolean value) { @@ -277,7 +275,7 @@ public void setAutoDownload(boolean value) { * Determine whether or not debug mode is enabled. *

* This grants access to the filter command. - * + * * @return TRUE if it is, FALSE otherwise. */ public boolean isDebug() { @@ -286,7 +284,7 @@ public boolean isDebug() { /** * Set whether or not debug mode is enabled. - * + * * @param value - TRUE if it is enabled, FALSE otherwise. */ public void setDebug(boolean value) { @@ -296,7 +294,7 @@ public void setDebug(boolean value) { /** * Retrieve an immutable list of every suppressed report type. - * + * * @return Every suppressed report type. */ public ImmutableList getSuppressedReports() { @@ -305,7 +303,7 @@ public ImmutableList getSuppressedReports() { /** * Set the list of suppressed report types, - * + * * @param reports - suppressed report types. */ public void setSuppressedReports(List reports) { @@ -315,7 +313,7 @@ public void setSuppressedReports(List reports) { /** * Retrieve the amount of time to wait until checking for a new update. - * + * * @return The amount of time to wait. */ public long getAutoDelay() { @@ -327,20 +325,21 @@ public long getAutoDelay() { * Set the amount of time to wait until checking for a new update. *

* This time must be greater than 59 seconds. - * + * * @param delaySeconds - the amount of time to wait. */ public void setAutoDelay(long delaySeconds) { // Silently fix the delay - if (delaySeconds < DEFAULT_UPDATER_DELAY) + if (delaySeconds < DEFAULT_UPDATER_DELAY) { delaySeconds = DEFAULT_UPDATER_DELAY; + } setConfig(updater, UPDATER_DELAY, delaySeconds); modCount++; } /** * The version of Minecraft to ignore the built-in safety feature. - * + * * @return The version to ignore ProtocolLib's satefy. */ public String getIgnoreVersionCheck() { @@ -351,7 +350,7 @@ public String getIgnoreVersionCheck() { * Sets under which version of Minecraft the version safety feature will be ignored. *

* This is useful if a server operator has tested and verified that a version of ProtocolLib works, but doesn't want or can't upgrade to a newer version. - * + * * @param ignoreVersion - the version of Minecraft where the satefy will be disabled. */ public void setIgnoreVersionCheck(String ignoreVersion) { @@ -361,7 +360,7 @@ public void setIgnoreVersionCheck(String ignoreVersion) { /** * Retrieve whether or not metrics is enabled. - * + * * @return TRUE if metrics is enabled, FALSE otherwise. */ public boolean isMetricsEnabled() { @@ -372,7 +371,7 @@ public boolean isMetricsEnabled() { * Set whether or not metrics is enabled. *

* This setting will take effect next time ProtocolLib is started. - * + * * @param enabled - whether or not metrics is enabled. */ public void setMetricsEnabled(boolean enabled) { @@ -382,7 +381,7 @@ public void setMetricsEnabled(boolean enabled) { /** * Retrieve whether or not the background compiler for structure modifiers is enabled or not. - * + * * @return TRUE if it is enabled, FALSE otherwise. */ public boolean isBackgroundCompilerEnabled() { @@ -393,7 +392,7 @@ public boolean isBackgroundCompilerEnabled() { * Set whether or not the background compiler for structure modifiers is enabled or not. *

* This setting will take effect next time ProtocolLib is started. - * + * * @param enabled - TRUE if is enabled/running, FALSE otherwise. */ public void setBackgroundCompilerEnabled(boolean enabled) { @@ -403,7 +402,7 @@ public void setBackgroundCompilerEnabled(boolean enabled) { /** * Retrieve the last time we updated, in seconds since 1970.01.01 00:00. - * + * * @return Last update time. */ public long getAutoLastTime() { @@ -414,7 +413,7 @@ public long getAutoLastTime() { * Set the last time we updated, in seconds since 1970.01.01 00:00. *

* Note that this is not considered to modify the configuration, so the modification count will not be incremented. - * + * * @param lastTimeSeconds - new last update time. */ public void setAutoLastTime(long lastTimeSeconds) { @@ -424,7 +423,7 @@ public void setAutoLastTime(long lastTimeSeconds) { /** * Retrieve the unique name of the script engine to use for filtering. - * + * * @return Script engine to use. */ public String getScriptEngineName() { @@ -435,7 +434,7 @@ public String getScriptEngineName() { * Set the unique name of the script engine to use for filtering. *

* This setting will take effect next time ProtocolLib is started. - * + * * @param name - name of the script engine to use. */ public void setScriptEngineName(String name) { @@ -443,45 +442,9 @@ public void setScriptEngineName(String name) { modCount++; } - /** - * Retrieve the default injection method. - * - * @return Default method. - */ - public PlayerInjectHooks getDefaultMethod() { - return PlayerInjectHooks.NETWORK_SERVER_OBJECT; - } - - /** - * Retrieve the injection method that has been set in the configuration, or use a default value. - * - * @return Injection method to use. - * @throws IllegalArgumentException If the configuration option is malformed. - */ - public PlayerInjectHooks getInjectionMethod() throws IllegalArgumentException { - String text = global.getString(INJECTION_METHOD); - - // Default hook if nothing has been set - PlayerInjectHooks hook = getDefaultMethod(); - - if (text != null) - hook = PlayerInjectHooks.valueOf(text.toUpperCase(Locale.ENGLISH).replace(" ", "_")); - return hook; - } - - /** - * Set the starting injection method to use. - * - * @return Injection method. - */ - public void setInjectionMethod(PlayerInjectHooks hook) { - setConfig(global, INJECTION_METHOD, hook.name()); - modCount++; - } - /** * Retrieve the number of modifications made to this configuration. - * + * * @return The number of modifications. */ public int getModificationCount() { @@ -492,10 +455,12 @@ public int getModificationCount() { * Save the current configuration file. */ public void saveAll() { - if (valuesChanged) + if (valuesChanged) { saveLastUpdate(lastUpdateTime); - if (configChanged) + } + if (configChanged) { plugin.saveConfig(); + } // And we're done valuesChanged = false; diff --git a/src/main/java/com/comphenix/protocol/ProtocolLib.java b/src/main/java/com/comphenix/protocol/ProtocolLib.java index 04e18069d..34c7ecbc2 100644 --- a/src/main/java/com/comphenix/protocol/ProtocolLib.java +++ b/src/main/java/com/comphenix/protocol/ProtocolLib.java @@ -1,49 +1,50 @@ /** - * 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 + * 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; -import java.io.File; -import java.io.IOException; -import java.util.Set; -import java.util.logging.Handler; -import java.util.logging.Level; -import java.util.logging.LogRecord; -import java.util.logging.Logger; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - import com.comphenix.protocol.async.AsyncFilterManager; -import com.comphenix.protocol.error.*; -import com.comphenix.protocol.injector.DelayedSingleTask; +import com.comphenix.protocol.error.BasicErrorReporter; +import com.comphenix.protocol.error.DelegatedErrorReporter; +import com.comphenix.protocol.error.DetailedErrorReporter; +import com.comphenix.protocol.error.ErrorReporter; +import com.comphenix.protocol.error.Report; +import com.comphenix.protocol.error.ReportType; import com.comphenix.protocol.injector.InternalManager; import com.comphenix.protocol.injector.PacketFilterManager; -import com.comphenix.protocol.injector.PlayerInjectHooks; import com.comphenix.protocol.metrics.Statistics; import com.comphenix.protocol.reflect.compiler.BackgroundCompiler; import com.comphenix.protocol.updater.Updater; import com.comphenix.protocol.updater.Updater.UpdateType; -import com.comphenix.protocol.utility.ChatExtensions; import com.comphenix.protocol.utility.ByteBuddyFactory; -import com.comphenix.protocol.utility.NettyVersion; +import com.comphenix.protocol.utility.ChatExtensions; import com.comphenix.protocol.utility.MinecraftVersion; +import com.comphenix.protocol.utility.NettyVersion; import com.google.common.base.Splitter; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; - +import java.io.File; +import java.io.IOException; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.bukkit.Server; import org.bukkit.command.CommandExecutor; import org.bukkit.command.PluginCommand; @@ -57,111 +58,91 @@ * @author Kristian */ public class ProtocolLib extends JavaPlugin { + // Every possible error or warning report type - public static final ReportType REPORT_CANNOT_LOAD_CONFIG = new ReportType("Cannot load configuration"); - public static final ReportType REPORT_CANNOT_DELETE_CONFIG = new ReportType("Cannot delete old ProtocolLib configuration."); - public static final ReportType REPORT_CANNOT_PARSE_INJECTION_METHOD = new ReportType("Cannot parse injection method. Using default."); + public static final ReportType REPORT_CANNOT_DELETE_CONFIG = new ReportType( + "Cannot delete old ProtocolLib configuration."); public static final ReportType REPORT_PLUGIN_LOAD_ERROR = new ReportType("Cannot load ProtocolLib."); + public static final ReportType REPORT_CANNOT_LOAD_CONFIG = new ReportType("Cannot load configuration"); public static final ReportType REPORT_PLUGIN_ENABLE_ERROR = new ReportType("Cannot enable ProtocolLib."); - public static final ReportType REPORT_METRICS_IO_ERROR = new ReportType("Unable to enable metrics due to network problems."); - public static final ReportType REPORT_METRICS_GENERIC_ERROR = new ReportType("Unable to enable metrics due to network problems."); + public static final ReportType REPORT_METRICS_IO_ERROR = new ReportType( + "Unable to enable metrics due to network problems."); + public static final ReportType REPORT_METRICS_GENERIC_ERROR = new ReportType( + "Unable to enable metrics due to network problems."); - public static final ReportType REPORT_CANNOT_PARSE_MINECRAFT_VERSION = new ReportType("Unable to retrieve current Minecraft version. Assuming %s"); - public static final ReportType REPORT_CANNOT_DETECT_CONFLICTING_PLUGINS = new ReportType("Unable to detect conflicting plugin versions."); + public static final ReportType REPORT_CANNOT_PARSE_MINECRAFT_VERSION = new ReportType( + "Unable to retrieve current Minecraft version. Assuming %s"); + public static final ReportType REPORT_CANNOT_DETECT_CONFLICTING_PLUGINS = new ReportType( + "Unable to detect conflicting plugin versions."); public static final ReportType REPORT_CANNOT_REGISTER_COMMAND = new ReportType("Cannot register command %s: %s"); - public static final ReportType REPORT_CANNOT_CREATE_TIMEOUT_TASK = new ReportType("Unable to create packet timeout task."); + public static final ReportType REPORT_CANNOT_CREATE_TIMEOUT_TASK = new ReportType( + "Unable to create packet timeout task."); public static final ReportType REPORT_CANNOT_UPDATE_PLUGIN = new ReportType("Cannot perform automatic updates."); - // Update information - static final String BUKKIT_DEV_SLUG = "protocollib"; - static final int BUKKIT_DEV_ID = 45564; - - // Different commands - private enum ProtocolCommand { - FILTER, - PACKET, - PROTOCOL, - LOGGING; - } - /** * The number of milliseconds per second. */ - static final long MILLI_PER_SECOND = 1000; + static final long MILLI_PER_SECOND = TimeUnit.SECONDS.toMillis(1); + private static final int ASYNC_MANAGER_DELAY = 1; private static final String PERMISSION_INFO = "protocol.info"; - // There should only be one protocol manager, so we'll make it static - private static InternalManager protocolManager; + public static boolean UPDATES_DISABLED = false; - // Error reporter - private static ErrorReporter reporter = new BasicErrorReporter(); - - // Strongly typed configuration + // these fields are only existing once, we can make them static + private static Logger logger; private static ProtocolConfig config; - // Metrics and statistics - private Statistics statistics; + private static InternalManager protocolManager; + private static ErrorReporter reporter = new BasicErrorReporter(); - // Structure compiler + private Statistics statistics; private BackgroundCompiler backgroundCompiler; - // Used to clean up server packets that have expired, but mostly required to simulate - // recieving client packets. private int packetTask = -1; private int tickCounter = 0; - private static final int ASYNC_MANAGER_DELAY = 1; - - // Used to unhook players after a delay - private DelayedSingleTask unhookTask; - - // Settings/options private int configExpectedMod = -1; - // Updater + // updater private Updater updater; - public static boolean UPDATES_DISABLED; - - // Logger - private static Logger logger; private Handler redirectHandler; - // Commands + // commands private CommandProtocol commandProtocol; private CommandPacket commandPacket; private CommandFilter commandFilter; private PacketLogging packetLogging; - // Whether or not disable is not needed + // Whether disabling field resetting is needed private boolean skipDisable; @Override public void onLoad() { // Logging - logger = getLogger(); + logger = this.getLogger(); ProtocolLogger.init(this); // Initialize enhancer factory - ByteBuddyFactory.getInstance().setClassLoader(getClassLoader()); + ByteBuddyFactory.getInstance().setClassLoader(this.getClassLoader()); // Add global parameters DetailedErrorReporter detailedReporter = new DetailedErrorReporter(this); - reporter = getFilteredReporter(detailedReporter); + reporter = this.getFilteredReporter(detailedReporter); // Configuration - saveDefaultConfig(); - reloadConfig(); + this.saveDefaultConfig(); + this.reloadConfig(); try { config = new ProtocolConfig(this); - } catch (Exception e) { - reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_CONFIG).error(e)); + } catch (Exception exception) { + reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_CONFIG).error(exception)); // Load it again - if (deleteConfig()) { + if (this.deleteConfig()) { config = new ProtocolConfig(this); } else { reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_DELETE_CONFIG)); @@ -171,11 +152,11 @@ public void onLoad() { // Print the state of the debug mode if (config.isDebug()) { logger.warning("Debug mode is enabled!"); - logger.info("Detected netty version: " + NettyVersion.getVersion()); + logger.info("Detected netty version: " + NettyVersion.getVersion()); } else { NettyVersion.getVersion(); // this will cache the version } - + // And the state of the error reporter if (config.isDetailedErrorReporting()) { detailedReporter.setDetailedReporting(true); @@ -184,52 +165,33 @@ public void onLoad() { try { // Check for other versions - checkConflictingVersions(); + this.checkConflictingVersions(); // Handle unexpected Minecraft versions - MinecraftVersion version = verifyMinecraftVersion(); + MinecraftVersion version = this.verifyMinecraftVersion(); // Set updater - this will not perform any update automatically - updater = Updater.create(this, BUKKIT_DEV_ID, getFile(), UpdateType.NO_DOWNLOAD, true); + this.updater = Updater.create(this, 0, this.getFile(), UpdateType.NO_DOWNLOAD, true); - unhookTask = new DelayedSingleTask(this); + // api init protocolManager = PacketFilterManager.newBuilder() - .classLoader(getClassLoader()) - .server(getServer()) + .server(this.getServer()) .library(this) .minecraftVersion(version) - .unhookTask(unhookTask) .reporter(reporter) .build(); - - // Initialize the API ProtocolLibrary.init(this, config, protocolManager, reporter); // Setup error reporter detailedReporter.addGlobalParameter("manager", protocolManager); - // Update injection hook - try { - PlayerInjectHooks hook = config.getInjectionMethod(); - - // Only update the hook if it's different - if (!protocolManager.getPlayerHook().equals(hook)) { - logger.info("Changing player hook from " + protocolManager.getPlayerHook() + " to " + hook); - protocolManager.setPlayerHook(hook); - } - } catch (IllegalArgumentException e) { - reporter.reportWarning(config, Report.newBuilder(REPORT_CANNOT_PARSE_INJECTION_METHOD).error(e)); - } - // Send logging information to player listeners too - initializeCommands(); - setupBroadcastUsers(PERMISSION_INFO); + this.initializeCommands(); + this.setupBroadcastUsers(PERMISSION_INFO); - } catch (OutOfMemoryError e) { - throw e; - } catch (Throwable e) { + } catch (Exception e) { reporter.reportDetailed(this, Report.newBuilder(REPORT_PLUGIN_LOAD_ERROR).error(e).callerParam(protocolManager)); - disablePlugin(); + this.disablePlugin(); } } @@ -241,18 +203,18 @@ private void initializeCommands() { for (ProtocolCommand command : ProtocolCommand.values()) { try { switch (command) { - case PROTOCOL: - commandProtocol = new CommandProtocol(reporter, this, updater, config); - break; - case FILTER: - commandFilter = new CommandFilter(reporter, this, config); - break; - case PACKET: - commandPacket = new CommandPacket(reporter, this, logger, commandFilter, protocolManager); - break; - case LOGGING: - packetLogging = new PacketLogging(this, protocolManager); - break; + case PROTOCOL: + this.commandProtocol = new CommandProtocol(reporter, this, this.updater, config); + break; + case FILTER: + this.commandFilter = new CommandFilter(reporter, this, config); + break; + case PACKET: + this.commandPacket = new CommandPacket(reporter, this, logger, this.commandFilter, protocolManager); + break; + case LOGGING: + this.packetLogging = new PacketLogging(this, protocolManager); + break; } } catch (OutOfMemoryError e) { throw e; @@ -267,6 +229,7 @@ private void initializeCommands() { /** * Retrieve a error reporter that may be filtered by the configuration. + * * @return The new default error reporter. */ private ErrorReporter getFilteredReporter(ErrorReporter reporter) { @@ -280,19 +243,20 @@ protected Report filterReport(Object sender, Report report, boolean detailed) { String canonicalName = ReportType.getReportName(sender, report.getType()); String reportName = Iterables.getLast(Splitter.on("#").split(canonicalName)).toUpperCase(); - if (config != null && config.getModificationCount() != lastModCount) { + if (config != null && config.getModificationCount() != this.lastModCount) { // Update our cached set again - reports = Sets.newHashSet(config.getSuppressedReports()); - lastModCount = config.getModificationCount(); + this.reports = Sets.newHashSet(config.getSuppressedReports()); + this.lastModCount = config.getModificationCount(); } // Cancel reports either on the full canonical name, or just the report name - if (reports.contains(canonicalName) || reports.contains(reportName)) + if (this.reports.contains(canonicalName) || this.reports.contains(reportName)) { return null; + } } catch (Exception e) { // Only report this with a minor message - logger.warning("Error filtering reports: " + e.toString()); + logger.warning("Error filtering reports: " + e); } // Don't filter anything return report; @@ -316,16 +280,17 @@ public void reloadConfig() { private void setupBroadcastUsers(final String permission) { // Guard against multiple calls - if (redirectHandler != null) + if (this.redirectHandler != null) { return; + } // Broadcast information to every user too - redirectHandler = new Handler() { + this.redirectHandler = new Handler() { @Override public void publish(LogRecord record) { // Only display warnings and above if (record.getLevel().intValue() >= Level.WARNING.intValue()) { - commandPacket.broadcastMessageSilently(record.getMessage(), permission); + ProtocolLib.this.commandPacket.broadcastMessageSilently(record.getMessage(), permission); } } @@ -340,23 +305,19 @@ public void close() throws SecurityException { } }; - logger.addHandler(redirectHandler); + logger.addHandler(this.redirectHandler); } @Override public void onEnable() { try { - Server server = getServer(); + Server server = this.getServer(); PluginManager manager = server.getPluginManager(); - // Don't do anything else! - if (manager == null) - return; - // Silly plugin reloaders! if (protocolManager == null) { Logger directLogging = Logger.getLogger("Minecraft"); - String[] message = new String[] { + String[] message = new String[]{ " ProtocolLib does not support plugin reloaders! ", " Please use the built-in reload command! " }; @@ -365,17 +326,17 @@ public void onEnable() { directLogging.severe(line); } - disablePlugin(); + this.disablePlugin(); return; } // Check for incompatible plugins - checkForIncompatibility(manager); + this.checkForIncompatibility(manager); // Initialize background compiler - if (backgroundCompiler == null && config.isBackgroundCompilerEnabled()) { - backgroundCompiler = new BackgroundCompiler(getClassLoader(), reporter); - BackgroundCompiler.setInstance(backgroundCompiler); + if (this.backgroundCompiler == null && config.isBackgroundCompilerEnabled()) { + this.backgroundCompiler = new BackgroundCompiler(this.getClassLoader(), reporter); + BackgroundCompiler.setInstance(this.backgroundCompiler); logger.info("Started structure compiler thread."); } else { @@ -383,41 +344,40 @@ public void onEnable() { } // Set up command handlers - registerCommand(CommandProtocol.NAME, commandProtocol); - registerCommand(CommandPacket.NAME, commandPacket); - registerCommand(CommandFilter.NAME, commandFilter); - registerCommand(PacketLogging.NAME, packetLogging); + this.registerCommand(CommandProtocol.NAME, this.commandProtocol); + this.registerCommand(CommandPacket.NAME, this.commandPacket); + this.registerCommand(CommandFilter.NAME, this.commandFilter); + this.registerCommand(PacketLogging.NAME, this.packetLogging); // Player login and logout events protocolManager.registerEvents(manager, this); // Worker that ensures that async packets are eventually sent // It also performs the update check. - createPacketTask(server); + this.createPacketTask(server); } catch (OutOfMemoryError e) { throw e; } catch (Throwable e) { reporter.reportDetailed(this, Report.newBuilder(REPORT_PLUGIN_ENABLE_ERROR).error(e)); - disablePlugin(); + this.disablePlugin(); return; } // Try to enable statistics try { if (config.isMetricsEnabled()) { - statistics = new Statistics(this); + this.statistics = new Statistics(this); } } catch (OutOfMemoryError e) { throw e; } catch (IOException e) { - reporter.reportDetailed(this, Report.newBuilder(REPORT_METRICS_IO_ERROR).error(e).callerParam(statistics)); + reporter.reportDetailed(this, Report.newBuilder(REPORT_METRICS_IO_ERROR).error(e).callerParam(this.statistics)); } catch (Throwable e) { - reporter.reportDetailed(this, Report.newBuilder(REPORT_METRICS_GENERIC_ERROR).error(e).callerParam(statistics)); + reporter.reportDetailed(this, Report.newBuilder(REPORT_METRICS_GENERIC_ERROR).error(e).callerParam( + this.statistics)); } } - // Plugin authors: Notify me to remove these - private void checkForIncompatibility(PluginManager manager) { for (String plugin : ProtocolLibrary.INCOMPATIBLE) { if (manager.getPlugin(plugin) != null) { @@ -434,26 +394,31 @@ private void checkForIncompatibility(PluginManager manager) { } } + // Plugin authors: Notify me to remove these + // Used to check Minecraft version private MinecraftVersion verifyMinecraftVersion() { MinecraftVersion minimum = new MinecraftVersion(ProtocolLibrary.MINIMUM_MINECRAFT_VERSION); MinecraftVersion maximum = new MinecraftVersion(ProtocolLibrary.MAXIMUM_MINECRAFT_VERSION); try { - MinecraftVersion current = new MinecraftVersion(getServer()); + MinecraftVersion current = new MinecraftVersion(this.getServer()); // Skip certain versions if (!config.getIgnoreVersionCheck().equals(current.getVersion())) { // We'll just warn the user for now - if (current.compareTo(minimum) < 0) + if (current.compareTo(minimum) < 0) { logger.warning("Version " + current + " is lower than the minimum " + minimum); - if (current.compareTo(maximum) > 0) + } + if (current.compareTo(maximum) > 0) { logger.warning("Version " + current + " has not yet been tested! Proceed with caution."); + } } return current; } catch (Exception e) { - reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_PARSE_MINECRAFT_VERSION).error(e).messageParam(maximum)); + reporter.reportWarning(this, + Report.newBuilder(REPORT_CANNOT_PARSE_MINECRAFT_VERSION).error(e).messageParam(maximum)); // Unknown version - just assume it is the latest return maximum; @@ -462,16 +427,16 @@ private MinecraftVersion verifyMinecraftVersion() { private void checkConflictingVersions() { Pattern ourPlugin = Pattern.compile("ProtocolLib-(.*)\\.jar"); - MinecraftVersion currentVersion = new MinecraftVersion(getDescription().getVersion()); + MinecraftVersion currentVersion = new MinecraftVersion(this.getDescription().getVersion()); MinecraftVersion newestVersion = null; // Skip the file that contains this current instance however - File loadedFile = getFile(); + File loadedFile = this.getFile(); try { // Scan the plugin folder for newer versions of ProtocolLib // The plugin folder isn't always plugins/ - File pluginFolder = getDataFolder().getParentFile(); + File pluginFolder = this.getDataFolder().getParentFile(); File[] candidates = pluginFolder.listFiles(); if (candidates != null) { @@ -499,7 +464,7 @@ private void checkConflictingVersions() { // See if the newest version is actually higher if (newestVersion != null && currentVersion.compareTo(newestVersion) < 0) { // We don't need to set internal classes or instances to NULL - that would break the other loaded plugin - skipDisable = true; + this.skipDisable = true; throw new IllegalStateException(String.format( "Detected a newer version of ProtocolLib (%s) in plugin folder than the current (%s). Disabling.", @@ -510,10 +475,11 @@ private void checkConflictingVersions() { private void registerCommand(String name, CommandExecutor executor) { try { // Ignore these - they must have printed an error already - if (executor == null) + if (executor == null) { return; + } - PluginCommand command = getCommand(name); + PluginCommand command = this.getCommand(name); // Try to load the command if (command != null) { @@ -522,7 +488,8 @@ private void registerCommand(String name, CommandExecutor executor) { throw new RuntimeException("plugin.yml might be corrupt."); } } catch (RuntimeException e) { - reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_REGISTER_COMMAND).messageParam(name, e.getMessage()).error(e)); + reporter.reportWarning(this, + Report.newBuilder(REPORT_CANNOT_REGISTER_COMMAND).messageParam(name, e.getMessage()).error(e)); } } @@ -530,44 +497,42 @@ private void registerCommand(String name, CommandExecutor executor) { * Disable the current plugin. */ private void disablePlugin() { - getServer().getPluginManager().disablePlugin(this); + this.getServer().getPluginManager().disablePlugin(this); } private void createPacketTask(Server server) { try { - if (packetTask >= 0) + if (this.packetTask >= 0) { throw new IllegalStateException("Packet task has already been created"); + } // Attempt to create task - packetTask = server.getScheduler().scheduleSyncRepeatingTask(this, new Runnable() { - @Override - public void run() { - AsyncFilterManager manager = (AsyncFilterManager) protocolManager.getAsynchronousManager(); + this.packetTask = server.getScheduler().scheduleSyncRepeatingTask(this, () -> { + AsyncFilterManager manager = (AsyncFilterManager) protocolManager.getAsynchronousManager(); - // We KNOW we're on the main thread at the moment - manager.sendProcessedPackets(tickCounter++, true); + // We KNOW we're on the main thread at the moment + manager.sendProcessedPackets(ProtocolLib.this.tickCounter++, true); - // House keeping - updateConfiguration(); + // House keeping + ProtocolLib.this.updateConfiguration(); - // Check for updates too - if (!UPDATES_DISABLED && (tickCounter % 20) == 0) { - checkUpdates(); - } + // Check for updates too + if (!UPDATES_DISABLED && (ProtocolLib.this.tickCounter % 20) == 0) { + ProtocolLib.this.checkUpdates(); } }, ASYNC_MANAGER_DELAY, ASYNC_MANAGER_DELAY); } catch (OutOfMemoryError e) { throw e; } catch (Throwable e) { - if (packetTask == -1) { + if (this.packetTask == -1) { reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_CREATE_TIMEOUT_TASK).error(e)); } } } private void updateConfiguration() { - if (config != null && config.getModificationCount() != configExpectedMod) { - configExpectedMod = config.getModificationCount(); + if (config != null && config.getModificationCount() != this.configExpectedMod) { + this.configExpectedMod = config.getModificationCount(); // Update the debug flag protocolManager.setDebug(config.isDebug()); @@ -577,19 +542,20 @@ private void updateConfiguration() { private void checkUpdates() { // Ignore milliseconds - it's pointless long currentTime = System.currentTimeMillis() / MILLI_PER_SECOND; - + try { long updateTime = config.getAutoLastTime() + config.getAutoDelay(); // Should we update? - if (currentTime > updateTime && !updater.isChecking()) { + if (currentTime > updateTime && !this.updater.isChecking()) { // Initiate the update as if it came from the console - if (config.isAutoDownload()) - commandProtocol.updateVersion(getServer().getConsoleSender(), false); - else if (config.isAutoNotify()) - commandProtocol.checkVersion(getServer().getConsoleSender(), false); - else - commandProtocol.updateFinished(); + if (config.isAutoDownload()) { + this.commandProtocol.updateVersion(this.getServer().getConsoleSender(), false); + } else if (config.isAutoNotify()) { + this.commandProtocol.checkVersion(this.getServer().getConsoleSender(), false); + } else { + this.commandProtocol.updateFinished(); + } } } catch (Exception e) { reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_UPDATE_PLUGIN).error(e)); @@ -599,36 +565,35 @@ else if (config.isAutoNotify()) @Override public void onDisable() { - if (skipDisable) { + if (this.skipDisable) { return; } // Disable compiler - if (backgroundCompiler != null) { - backgroundCompiler.shutdownAll(); - backgroundCompiler = null; + if (this.backgroundCompiler != null) { + this.backgroundCompiler.shutdownAll(); + this.backgroundCompiler = null; BackgroundCompiler.setInstance(null); } // Clean up - if (packetTask >= 0) { - getServer().getScheduler().cancelTask(packetTask); - packetTask = -1; + if (this.packetTask >= 0) { + this.getServer().getScheduler().cancelTask(this.packetTask); + this.packetTask = -1; } // And redirect handler too - if (redirectHandler != null) { - logger.removeHandler(redirectHandler); + if (this.redirectHandler != null) { + logger.removeHandler(this.redirectHandler); } - if (protocolManager != null) + if (protocolManager != null) { protocolManager.close(); - else + } else { return; // Plugin reloaders! + } - if (unhookTask != null) - unhookTask.close(); protocolManager = null; - statistics = null; + this.statistics = null; // To clean up global parameters reporter = new BasicErrorReporter(); @@ -637,15 +602,24 @@ public void onDisable() { /** * Retrieve the metrics instance used to measure users of this library. *

- * Note that this method may return NULL when the server is reloading or shutting down. It is also - * NULL if metrics has been disabled. + * Note that this method may return NULL when the server is reloading or shutting down. It is also NULL if metrics has + * been disabled. + * * @return Metrics instance container. */ public Statistics getStatistics() { - return statistics; + return this.statistics; } public ProtocolConfig getProtocolConfig() { return config; } + + // Different commands + private enum ProtocolCommand { + FILTER, + PACKET, + PROTOCOL, + LOGGING + } } diff --git a/src/main/java/com/comphenix/protocol/ProtocolManager.java b/src/main/java/com/comphenix/protocol/ProtocolManager.java index b73942dc8..2f45bb2a4 100644 --- a/src/main/java/com/comphenix/protocol/ProtocolManager.java +++ b/src/main/java/com/comphenix/protocol/ProtocolManager.java @@ -17,16 +17,6 @@ package com.comphenix.protocol; -import java.lang.reflect.InvocationTargetException; -import java.util.List; -import java.util.Set; - -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; -import org.bukkit.plugin.Plugin; - import com.comphenix.protocol.async.AsyncMarker; import com.comphenix.protocol.events.ListenerPriority; import com.comphenix.protocol.events.ListeningWhitelist; @@ -36,81 +26,94 @@ import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.utility.MinecraftVersion; import com.google.common.collect.ImmutableSet; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; /** * Represents an API for accessing the Minecraft protocol. + * * @author Kristian */ public interface ProtocolManager extends PacketStream { + /** * Retrieve the protocol version of a given player. *

- * This only really makes sense of a server that support clients of multiple Minecraft versions, such as Spigot #1628. + * This only really makes sense of a server that support clients of multiple Minecraft versions, such as Spigot + * #1628. + * * @param player - the player. * @return The associated protocol version, or {@link Integer#MIN_VALUE} if unknown. */ int getProtocolVersion(Player player); - + /** * Send a packet to the given player. *

- * Re-sending a previously cancelled packet is discouraged. Use {@link AsyncMarker#incrementProcessingDelay()} - * to delay a packet until a certain condition has been met. - * + * Re-sending a previously cancelled packet is discouraged. Use {@link AsyncMarker#incrementProcessingDelay()} to + * delay a packet until a certain condition has been met. + * * @param receiver - the receiver. - * @param packet - packet to send. - * @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}. - * @throws InvocationTargetException - if an error occurred when sending the packet. + * @param packet - packet to send. + * @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}. */ @Override - void sendServerPacket(Player receiver, PacketContainer packet, boolean filters) - throws InvocationTargetException; - + void sendServerPacket(Player receiver, PacketContainer packet, boolean filters); + /** * Simulate receiving a certain packet from a given player. *

- * Receiving a previously cancelled packet is discouraged. Use {@link AsyncMarker#incrementProcessingDelay()} - * to delay a packet until a certain condition has been met. - * - * @param sender - the sender. - * @param packet - the packet that was sent. + * Receiving a previously cancelled packet is discouraged. Use {@link AsyncMarker#incrementProcessingDelay()} to delay + * a packet until a certain condition has been met. + * + * @param sender - the sender. + * @param packet - the packet that was sent. * @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}. - * @throws InvocationTargetException If the reflection machinery failed. - * @throws IllegalAccessException If the underlying method caused an error. */ @Override - void recieveClientPacket(Player sender, PacketContainer packet, boolean filters) - throws IllegalAccessException, InvocationTargetException; - + void receiveClientPacket(Player sender, PacketContainer packet, boolean filters); + /** * Broadcast a given packet to every connected player on the server. + * * @param packet - the packet to broadcast. * @throws FieldAccessException If we were unable to send the packet due to reflection problems. */ void broadcastServerPacket(PacketContainer packet); - + /** * Broadcast a packet to every player that is receiving information about a given entity. *

- * This is usually every player in the same world within an observable distance. If the entity is a - * player, it will only be included if includeTracker is TRUE. - * @param packet - the packet to broadcast. - * @param entity - the entity whose trackers we will inform. + * This is usually every player in the same world within an observable distance. If the entity is a player, it will + * only be included if includeTracker is TRUE. + * + * @param packet - the packet to broadcast. + * @param entity - the entity whose trackers we will inform. * @param includeTracker - whether or not to also transmit the packet to the entity, if it is a tracker. * @throws FieldAccessException If we were unable to send the packet due to reflection problems. */ void broadcastServerPacket(PacketContainer packet, Entity entity, boolean includeTracker); - + /** * Broadcast a packet to every player within the given maximum observer distance. - * @param packet - the packet to broadcast. - * @param origin - the origin to consider when calculating the distance to each observer. + * + * @param packet - the packet to broadcast. + * @param origin - the origin to consider when calculating the distance to each observer. * @param maxObserverDistance - the maximum distance to the origin. */ void broadcastServerPacket(PacketContainer packet, Location origin, int maxObserverDistance); - + + void broadcastServerPacket(PacketContainer packet, Collection targetPlayers); + /** * Retrieves a list of every registered packet listener. + * * @return Every registered packet listener. */ ImmutableSet getPacketListeners(); @@ -118,9 +121,9 @@ void recieveClientPacket(Player sender, PacketContainer packet, boolean filters) /** * Adds a packet listener. *

- * Adding an already registered listener has no effect. If you need to change the packets - * the current listener is observing, you must first remove the packet listener before you - * can register it again. + * Adding an already registered listener has no effect. If you need to change the packets the current listener is + * observing, you must first remove the packet listener before you can register it again. + * * @param listener - new packet listener. */ void addPacketListener(PacketListener listener); @@ -129,98 +132,109 @@ void recieveClientPacket(Player sender, PacketContainer packet, boolean filters) * Removes a given packet listener. *

* Attempting to remove a listener that doesn't exist has no effect. + * * @param listener - the packet listener to remove. */ void removePacketListener(PacketListener listener); /** * Removes every listener associated with the given plugin. + * * @param plugin - the plugin to unload. */ void removePacketListeners(Plugin plugin); /** * Constructs a new encapsulated Minecraft packet with the given ID. - * @param type - packet type. + * + * @param type - packet type. * @return New encapsulated Minecraft packet. */ PacketContainer createPacket(PacketType type); - + /** * Constructs a new encapsulated Minecraft packet with the given ID. *

- * If set to true, the forceDefaults option will force the system to automatically - * give non-primitive fields in the packet sensible default values. For instance, certain - * packets - like Packet60Explosion - require a List or Set to be non-null. If the - * forceDefaults option is true, the List or Set will be automatically created. - * - * @param type - packet type. + * If set to true, the forceDefaults option will force the system to automatically give non-primitive fields in + * the packet sensible default values. For instance, certain packets - like Packet60Explosion - require a List or Set + * to be non-null. If the forceDefaults option is true, the List or Set will be automatically created. + * + * @param type - packet type. * @param forceDefaults - TRUE to use sensible defaults in most fields, FALSE otherwise. * @return New encapsulated Minecraft packet. */ PacketContainer createPacket(PacketType type, boolean forceDefaults); - + /** * Construct a packet using the special builtin Minecraft constructors. - * @param type - the packet type. + * + * @param type - the packet type. * @param arguments - arguments that will be passed to the constructor. * @return The packet constructor. */ PacketConstructor createPacketConstructor(PacketType type, Object... arguments); - + /** * Completely resend an entity to a list of clients. *

- * Note that this method is NOT thread safe. If you call this method from anything - * but the main thread, it will throw an exception. - * @param entity - entity to refresh. + * Note that this method is NOT thread safe. If you call this method from anything but the main thread, it will throw + * an exception. + * + * @param entity - entity to refresh. * @param observers - the clients to update. */ - void updateEntity(Entity entity, List observers) throws FieldAccessException; - + void updateEntity(Entity entity, List observers); + /** * Retrieve the associated entity. + * * @param container - the world the entity belongs to. - * @param id - the unique ID of the entity. + * @param id - the unique ID of the entity. * @return The associated entity. * @throws FieldAccessException Reflection failed. */ - Entity getEntityFromID(World container, int id) throws FieldAccessException; - + Entity getEntityFromID(World container, int id); + /** * Retrieve every client that is receiving information about a given entity. + * * @param entity - the entity that is being tracked. * @return Every client/player that is tracking the given entity. * @throws FieldAccessException If reflection failed. */ - List getEntityTrackers(Entity entity) throws FieldAccessException; + List getEntityTrackers(Entity entity); /** * Retrieves a immutable set containing the type of the sent server packets that will be observed by listeners. + * * @return Every filtered server packet. */ Set getSendingFilterTypes(); /** * Retrieves a immutable set containing the type of the received client packets that will be observed by listeners. + * * @return Every filtered client packet. */ Set getReceivingFilterTypes(); - + /** * Retrieve the current Minecraft version. + * * @return The current version. */ MinecraftVersion getMinecraftVersion(); - + /** * Determines whether or not this protocol manager has been disabled. + * * @return TRUE if it has, FALSE otherwise. */ boolean isClosed(); /** * Retrieve the current asynchronous packet manager. + * * @return Asynchronous packet manager. */ AsynchronousManager getAsynchronousManager(); diff --git a/src/main/java/com/comphenix/protocol/async/AsyncMarker.java b/src/main/java/com/comphenix/protocol/async/AsyncMarker.java index df253d2aa..d60482a24 100644 --- a/src/main/java/com/comphenix/protocol/async/AsyncMarker.java +++ b/src/main/java/com/comphenix/protocol/async/AsyncMarker.java @@ -17,15 +17,6 @@ package com.comphenix.protocol.async; -import java.io.IOException; -import java.io.Serializable; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Iterator; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.logging.Level; - import com.comphenix.protocol.PacketStream; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.ProtocolLogger; @@ -37,16 +28,24 @@ import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftVersion; import com.google.common.primitives.Longs; +import java.io.IOException; +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; /** * Contains information about the packet that is being processed by asynchronous listeners. *

* Asynchronous listeners can use this to set packet timeout or transmission order. - * + * * @author Kristian */ public class AsyncMarker implements Serializable, Comparable { - + /** * Generated by Eclipse. */ @@ -56,52 +55,52 @@ public class AsyncMarker implements Serializable, Comparable { * Default number of milliseconds until a packet will rejected. */ public static final int DEFAULT_TIMEOUT_DELTA = 1800 * 1000; - + /** * Default number of packets to skip. */ public static final int DEFAULT_SENDING_DELTA = 0; - + /** * The packet stream responsible for transmitting the packet when it's done processing. */ private transient PacketStream packetStream; - + /** * Current list of async packet listeners. */ private transient Iterator> listenerTraversal; - + // Timeout handling private long initialTime; private long timeout; - + // Packet order private long originalSendingIndex; private long newSendingIndex; - + // Used to determine if a packet must be reordered in the sending queue private Long queuedSendingIndex; - + // Whether or not the packet has been processed by the listeners private volatile boolean processed; - + // Whether or not the packet has been sent private volatile boolean transmitted; - + // Whether or not the asynchronous processing itself should be cancelled private volatile boolean asyncCancelled; - + // Whether or not to delay processing private AtomicInteger processingDelay = new AtomicInteger(); - + // Used to synchronize processing on the shared PacketEvent private Object processingLock = new Object(); - + // Used to identify the asynchronous worker private transient AsyncListenerHandler listenerHandler; private transient int workerID; - + // Determine if Minecraft processes this packet asynchronously private volatile static Method isMinecraftAsync; private volatile static boolean alwaysSync; @@ -113,18 +112,18 @@ public class AsyncMarker implements Serializable, Comparable { AsyncMarker(PacketStream packetStream, long sendingIndex, long initialTime, long timeoutDelta) { if (packetStream == null) throw new IllegalArgumentException("packetStream cannot be NULL"); - + this.packetStream = packetStream; - + // Timeout this.initialTime = initialTime; this.timeout = initialTime + timeoutDelta; - + // Sending index this.originalSendingIndex = sendingIndex; this.newSendingIndex = sendingIndex; } - + /** * Retrieve the time the packet was initially queued for asynchronous processing. * @return The initial time in number of milliseconds since 01.01.1970 00:00. @@ -140,7 +139,7 @@ public long getInitialTime() { public long getTimeout() { return timeout; } - + /** * Set the time the packet will be forcefully rejected. * @param timeout - time to reject the packet, in milliseconds since 01.01.1970 00:00. @@ -219,13 +218,13 @@ void setProcessed(boolean processed) { *

* It is recommended that processing outside a packet listener is wrapped in a synchronized block * using the {@link #getProcessingLock()} method. - * + * * @return The new processing delay. */ public int incrementProcessingDelay() { return processingDelay.incrementAndGet(); } - + /** * Decrement the number of times this packet must be signalled as done before it's transmitted. * @return The new processing delay. If zero, the packet should be sent. @@ -233,7 +232,7 @@ public int incrementProcessingDelay() { int decrementProcessingDelay() { return processingDelay.decrementAndGet(); } - + /** * Retrieve the number of times a packet must be signalled to be done before it's sent. * @return Number of processing delays. @@ -241,7 +240,7 @@ int decrementProcessingDelay() { public int getProcessingDelay() { return processingDelay.get(); } - + /** * Whether or not this packet is or has been queued for processing. * @return TRUE if it has, FALSE otherwise. @@ -296,7 +295,7 @@ public boolean isTransmitted() { public boolean hasExpired() { return hasExpired(System.currentTimeMillis()); } - + /** * Determine if this packet has expired given this time. * @param currentTime - the current time in milliseconds since 01.01.1970 00:00. @@ -305,7 +304,7 @@ public boolean hasExpired() { public boolean hasExpired(long currentTime) { return timeout < currentTime; } - + /** * Determine if the asynchronous handling should be cancelled. * @return TRUE if it should, FALSE otherwise. @@ -319,7 +318,7 @@ public boolean isAsyncCancelled() { *

* This is only relevant during the synchronous processing. Asynchronous * listeners should use the normal cancel-field to cancel a PacketEvent. - * + * * @param asyncCancelled - TRUE to cancel it, FALSE otherwise. */ public void setAsyncCancelled(boolean asyncCancelled) { @@ -369,7 +368,7 @@ void setWorkerID(int workerID) { Iterator> getListenerTraversal() { return listenerTraversal; } - + /** * Set the iterator for the next listener. * @param listenerTraversal - the new async packet listener iterator. @@ -377,28 +376,22 @@ Iterator> getListenerTraversal() { void setListenerTraversal(Iterator> listenerTraversal) { this.listenerTraversal = listenerTraversal; } - + /** * Transmit a given packet to the current packet stream. * @param event - the packet to send. * @throws IOException If the packet couldn't be sent. */ void sendPacket(PacketEvent event) throws IOException { - try { - if (event.isServerPacket()) { - packetStream.sendServerPacket(event.getPlayer(), event.getPacket(), NetworkMarker.getNetworkMarker(event), false); - } else { - packetStream.recieveClientPacket(event.getPlayer(), event.getPacket(), NetworkMarker.getNetworkMarker(event), false); - } - transmitted = true; - - } catch (InvocationTargetException e) { - throw new IOException("Cannot send packet", e); - } catch (IllegalAccessException e) { - throw new IOException("Cannot send packet", e); + if (event.isServerPacket()) { + packetStream.sendServerPacket(event.getPlayer(), event.getPacket(), NetworkMarker.getNetworkMarker(event), false); + } else { + packetStream.receiveClientPacket(event.getPlayer(), event.getPacket(), NetworkMarker.getNetworkMarker(event), + false); } + transmitted = true; } - + /** * Determine if Minecraft allows asynchronous processing of this packet. * @param event - packet event @@ -413,7 +406,7 @@ public boolean isMinecraftAsync(PacketEvent event) throws FieldAccessException { // This will occur in 1.2.5 (or possibly in later versions) List methods = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()). getMethodListByParameters(boolean.class, new Class[] {}); - + // Try to look for boolean methods if (methods.size() == 2) { isMinecraftAsync = methods.get(1); @@ -460,7 +453,7 @@ public boolean isMinecraftAsync(PacketEvent event) throws FieldAccessException { } } } - + @Override public int compareTo(AsyncMarker o) { if (o == null) @@ -468,7 +461,7 @@ public int compareTo(AsyncMarker o) { else return Longs.compare(getNewSendingIndex(), o.getNewSendingIndex()); } - + @Override public boolean equals(Object other) { // Standard equals @@ -479,7 +472,7 @@ public boolean equals(Object other) { else return false; } - + @Override public int hashCode() { return Longs.hashCode(getNewSendingIndex()); diff --git a/src/main/java/com/comphenix/protocol/async/PacketSendingQueue.java b/src/main/java/com/comphenix/protocol/async/PacketSendingQueue.java index 95579114b..1e5e1be82 100644 --- a/src/main/java/com/comphenix/protocol/async/PacketSendingQueue.java +++ b/src/main/java/com/comphenix/protocol/async/PacketSendingQueue.java @@ -2,58 +2,51 @@ * 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 + * 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. + * 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 + * 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.async; +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.events.PacketEvent; +import com.comphenix.protocol.reflect.FieldAccessException; import java.io.IOException; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.PriorityBlockingQueue; - import org.bukkit.entity.Player; -import com.comphenix.protocol.PacketType; -import com.comphenix.protocol.ProtocolLibrary; -import com.comphenix.protocol.error.Report; -import com.comphenix.protocol.error.ReportType; -import com.comphenix.protocol.events.PacketEvent; -import com.comphenix.protocol.injector.PlayerLoggedOutException; -import com.comphenix.protocol.reflect.FieldAccessException; - /** * Represents packets ready to be transmitted to a client. + * * @author Kristian */ abstract class PacketSendingQueue { - public static final ReportType REPORT_DROPPED_PACKET = new ReportType("Warning: Dropped packet index %s of type %s."); - + public static final int INITIAL_CAPACITY = 10; - + // Whether or not packet transmission must occur on a specific thread + private final boolean notThreadSafe; private PriorityBlockingQueue sendingQueue; - // Asynchronous packet sending private Executor asynchronousSender; - // Whether or not packet transmission must occur on a specific thread - private final boolean notThreadSafe; // Whether or not we've run the cleanup procedure private boolean cleanedUp = false; - + /** * Create a packet sending queue. + * * @param notThreadSafe - whether or not to synchronize with the main thread or a background thread. */ public PacketSendingQueue(boolean notThreadSafe, Executor asynchronousSender) { @@ -61,44 +54,47 @@ public PacketSendingQueue(boolean notThreadSafe, Executor asynchronousSender) { this.notThreadSafe = notThreadSafe; this.asynchronousSender = asynchronousSender; } - + /** * Number of packet events in the queue. + * * @return The number of packet events in the queue. */ public int size() { return sendingQueue.size(); } - + /** - * Enqueue a packet for sending. + * Enqueue a packet for sending. + * * @param packet - packet to queue. */ public void enqueue(PacketEvent packet) { sendingQueue.add(new PacketEventHolder(packet)); } - + /** * Invoked when one of the packets have finished processing. + * * @param packetUpdated - the packet that has now been updated. - * @param onMainThread - whether or not this is occuring on the main thread. + * @param onMainThread - whether or not this is occuring on the main thread. */ public synchronized void signalPacketUpdate(PacketEvent packetUpdated, boolean onMainThread) { - + AsyncMarker marker = packetUpdated.getAsyncMarker(); - + // Should we reorder the event? if (marker.getQueuedSendingIndex() != marker.getNewSendingIndex() && !marker.hasExpired()) { PacketEvent copy = PacketEvent.fromSynchronous(packetUpdated, marker); - + // "Cancel" the original event packetUpdated.setReadOnly(false); packetUpdated.setCancelled(true); - + // Enqueue the copy with the new sending index enqueue(copy); } - + // Mark this packet as finished marker.setProcessed(true); trySendPackets(onMainThread); @@ -111,79 +107,81 @@ public synchronized void signalPacketUpdate(PacketEvent packetUpdated, boolean o */ public synchronized void signalPacketUpdate(List packetsRemoved, boolean onMainThread) { Set lookup = new HashSet(packetsRemoved); - + // Note that this is O(n), so it might be expensive for (PacketEventHolder holder : sendingQueue) { PacketEvent event = holder.getEvent(); - + if (lookup.contains(event.getPacketType())) { event.getAsyncMarker().setProcessed(true); } } - + // This is likely to have changed the situation a bit trySendPackets(onMainThread); } - + /** * Attempt to send any remaining packets. + * * @param onMainThread - whether or not this is occuring on the main thread. */ public void trySendPackets(boolean onMainThread) { // Whether or not to continue sending packets boolean sending = true; - + // Transmit as many packets as we can while (sending) { PacketEventHolder holder = sendingQueue.poll(); - + if (holder != null) { sending = processPacketHolder(onMainThread, holder); - + if (!sending) { // Add it back again sendingQueue.add(holder); } - + } else { // No more packets to send sending = false; } } } - + /** * Invoked when a packet might be ready for transmission. + * * @param onMainThread - TRUE if we're on the main thread, FALSE otherwise. - * @param holder - packet container. + * @param holder - packet container. * @return TRUE to continue sending packets, FALSE otherwise. */ private boolean processPacketHolder(boolean onMainThread, final PacketEventHolder holder) { PacketEvent current = holder.getEvent(); AsyncMarker marker = current.getAsyncMarker(); boolean hasExpired = marker.hasExpired(); - + // Guard in cause the queue is closed if (cleanedUp) { return true; } - + // End condition? if (marker.isProcessed() || hasExpired) { if (hasExpired) { // Notify timeout listeners onPacketTimeout(current); - + // Recompute marker = current.getAsyncMarker(); hasExpired = marker.hasExpired(); - + // Could happen due to the timeout listeners if (!marker.isProcessed() && !hasExpired) { return false; } } - + // Is it okay to send the packet? if (!current.isCancelled() && !hasExpired) { // Make sure we're on the main thread @@ -191,12 +189,12 @@ private boolean processPacketHolder(boolean onMainThread, final PacketEventHolde try { boolean wantAsync = marker.isMinecraftAsync(current); boolean wantSync = !wantAsync; - + // Wait for the next main thread heartbeat if we haven't fulfilled our promise if (!onMainThread && wantSync) { return false; } - + // Let's give it what it wants if (onMainThread && wantAsync) { asynchronousSender.execute(new Runnable() { @@ -206,50 +204,51 @@ public void run() { processPacketHolder(false, holder); } }); - + // Scheduler will do the rest return true; } - + } catch (FieldAccessException e) { e.printStackTrace(); - + // Just drop the packet return true; } - } - + } + // Silently skip players that have logged out if (isOnline(current.getPlayer())) { sendPacket(current); } - } - + } + // Drop the packet return true; } - + // Add it back and stop sending return false; } - + /** * Invoked when a packet has timed out. + * * @param event - the timed out packet. */ protected abstract void onPacketTimeout(PacketEvent event); - + private boolean isOnline(Player player) { return player != null && player.isOnline(); } - + /** * Send every packet, regardless of the processing state. */ private void forceSend() { while (true) { PacketEventHolder holder = sendingQueue.poll(); - + if (holder != null) { sendPacket(holder.getEvent()); } else { @@ -257,9 +256,10 @@ private void forceSend() { } } } - + /** * Whether or not the packet transmission must synchronize with the main thread. + * * @return TRUE if it must, FALSE otherwise. */ public boolean isSynchronizeMain() { @@ -268,24 +268,16 @@ public boolean isSynchronizeMain() { /** * Transmit a packet, if it hasn't already. + * * @param event - the packet to transmit. */ private void sendPacket(PacketEvent event) { - - AsyncMarker marker = event.getAsyncMarker(); - try { // Don't send a packet twice + AsyncMarker marker = event.getAsyncMarker(); if (marker != null && !marker.isTransmitted()) { marker.sendPacket(event); } - - } catch (PlayerLoggedOutException e) { - ProtocolLibrary.getErrorReporter().reportDebug(this, Report.newBuilder(REPORT_DROPPED_PACKET). - messageParam(marker.getOriginalSendingIndex(), event.getPacketType()). - callerParam(event) - ); - } catch (IOException e) { // Just print the error e.printStackTrace(); @@ -299,7 +291,7 @@ public void cleanupAll() { if (!cleanedUp) { // Note that the cleanup itself will always occur on the main thread forceSend(); - + // And we're done cleanedUp = true; } diff --git a/src/main/java/com/comphenix/protocol/concurrency/AbstractConcurrentListenerMultimap.java b/src/main/java/com/comphenix/protocol/concurrency/AbstractConcurrentListenerMultimap.java index 589972fac..86d513a4c 100644 --- a/src/main/java/com/comphenix/protocol/concurrency/AbstractConcurrentListenerMultimap.java +++ b/src/main/java/com/comphenix/protocol/concurrency/AbstractConcurrentListenerMultimap.java @@ -2,21 +2,25 @@ * 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 + * 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. + * 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 + * 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.concurrency; +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.events.ListeningWhitelist; +import com.comphenix.protocol.injector.PrioritizedListener; +import com.google.common.collect.Iterables; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -24,81 +28,78 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import com.comphenix.protocol.PacketType; -import com.comphenix.protocol.events.ListeningWhitelist; -import com.comphenix.protocol.injector.PrioritizedListener; -import com.google.common.collect.Iterables; - /** * A thread-safe implementation of a listener multimap. - * + * * @author Kristian */ -public abstract class AbstractConcurrentListenerMultimap { +public abstract class AbstractConcurrentListenerMultimap { + // The core of our map - private ConcurrentMap>> mapListeners; - + private final ConcurrentMap>> mapListeners; + public AbstractConcurrentListenerMultimap() { - mapListeners = new ConcurrentHashMap>>(); + this.mapListeners = new ConcurrentHashMap<>(); } - + /** * Adds a listener to its requested list of packet receivers. - * @param listener - listener with a list of packets to receive notifications for. + * + * @param listener - listener with a list of packets to receive notifications for. * @param whitelist - the packet whitelist to use. */ - public void addListener(TListener listener, ListeningWhitelist whitelist) { - PrioritizedListener prioritized = new PrioritizedListener(listener, whitelist.getPriority()); - + public void addListener(T listener, ListeningWhitelist whitelist) { + PrioritizedListener prioritized = new PrioritizedListener<>(listener, whitelist.getPriority()); for (PacketType type : whitelist.getTypes()) { - addListener(type, prioritized); + this.addListener(type, prioritized); } } - + // Add the listener to a specific packet notifcation list - private void addListener(PacketType type, PrioritizedListener listener) { - SortedCopyOnWriteArray> list = mapListeners.get(type); - + private void addListener(PacketType type, PrioritizedListener listener) { + SortedCopyOnWriteArray> list = this.mapListeners.get(type); + // We don't want to create this for every lookup if (list == null) { // It would be nice if we could use a PriorityBlockingQueue, but it doesn't preseve iterator order, // which is a essential feature for our purposes. - final SortedCopyOnWriteArray> value = new SortedCopyOnWriteArray>(); + final SortedCopyOnWriteArray> value = new SortedCopyOnWriteArray>(); // We may end up creating multiple multisets, but we'll agree on which to use - list = mapListeners.putIfAbsent(type, value); - + list = this.mapListeners.putIfAbsent(type, value); + if (list == null) { list = value; } } - + // Thread safe list.add(listener); } - + /** * Removes the given listener from the packet event list. - * @param listener - listener to remove. + * + * @param listener - listener to remove. * @param whitelist - the packet whitelist that was used. * @return Every packet ID that was removed due to no listeners. */ - public List removeListener(TListener listener, ListeningWhitelist whitelist) { + public List removeListener(T listener, ListeningWhitelist whitelist) { List removedPackets = new ArrayList(); - + // Again, not terribly efficient. But adding or removing listeners should be a rare event. for (PacketType type : whitelist.getTypes()) { - SortedCopyOnWriteArray> list = mapListeners.get(type); - + SortedCopyOnWriteArray> list = this.mapListeners.get(type); + // Remove any listeners if (list != null) { // Don't remove from newly created lists if (list.size() > 0) { // Remove this listener. Note that priority is generally ignored. - list.remove(new PrioritizedListener(listener, whitelist.getPriority())); - + list.remove(new PrioritizedListener(listener, whitelist.getPriority())); + if (list.size() == 0) { - mapListeners.remove(type); + this.mapListeners.remove(type); removedPackets.add(type); } } @@ -107,38 +108,41 @@ public List removeListener(TListener listener, ListeningWhitelist wh } return removedPackets; } - + /** * Retrieve the registered listeners, in order from the lowest to the highest priority. *

* The returned list is thread-safe and doesn't require synchronization. + * * @param type - packet type. * @return Registered listeners. */ - public Collection> getListener(PacketType type) { - return mapListeners.get(type); + public Collection> getListener(PacketType type) { + return this.mapListeners.get(type); } - + /** * Retrieve every listener. + * * @return Every listener. */ - public Iterable> values() { - return Iterables.concat(mapListeners.values()); + public Iterable> values() { + return Iterables.concat(this.mapListeners.values()); } - + /** * Retrieve every registered packet type: + * * @return Registered packet type. */ public Set keySet() { - return mapListeners.keySet(); + return this.mapListeners.keySet(); } - + /** * Remove all packet listeners. */ protected void clearListeners() { - mapListeners.clear(); + this.mapListeners.clear(); } } diff --git a/src/main/java/com/comphenix/protocol/concurrency/PacketTypeSet.java b/src/main/java/com/comphenix/protocol/concurrency/PacketTypeSet.java index 0b73e764c..5231a7a56 100644 --- a/src/main/java/com/comphenix/protocol/concurrency/PacketTypeSet.java +++ b/src/main/java/com/comphenix/protocol/concurrency/PacketTypeSet.java @@ -1,125 +1,135 @@ package com.comphenix.protocol.concurrency; +import com.comphenix.protocol.PacketType; +import com.google.common.collect.ImmutableSet; import java.util.Collection; -import java.util.Collections; +import java.util.HashSet; import java.util.Set; -import com.comphenix.protocol.PacketType; -import com.comphenix.protocol.injector.packet.PacketRegistry; -import com.google.common.base.Preconditions; -import com.google.common.collect.Maps; - /** * Represents a concurrent set of packet types. + * * @author Kristian */ public class PacketTypeSet { - private Set types = Collections.newSetFromMap(Maps.newConcurrentMap()); - private Set> classes = Collections.newSetFromMap(Maps., Boolean>newConcurrentMap()); - + + private final Set types; + private final Set> classes; + public PacketTypeSet() { - // Do nothing + this.types = new HashSet<>(16, 0.9f); + this.classes = new HashSet<>(16, 0.9f); } - + public PacketTypeSet(Collection values) { + this.types = new HashSet<>(values.size(), 0.9f); + this.classes = new HashSet<>(values.size(), 0.9f); + for (PacketType type : values) { - addType(type); + this.addType(type); } } - + /** * Add a particular type to the set. + * * @param type - the type to add. */ - public synchronized void addType(PacketType type) { + public void addType(PacketType type) { + this.types.add(type); + Class packetClass = type.getPacketClass(); - types.add(Preconditions.checkNotNull(type, "type cannot be NULL.")); - if (packetClass != null) { - classes.add(type.getPacketClass()); + this.classes.add(packetClass); } } - + /** * Add the given types to the set of packet types. + * * @param types - the types to add. */ - public synchronized void addAll(Iterable types) { + public void addAll(Iterable types) { for (PacketType type : types) { - addType(type); + this.addType(type); } } - + /** * Remove a particular type to the set. + * * @param type - the type to remove. */ - public synchronized void removeType(PacketType type) { + public void removeType(PacketType type) { + this.types.remove(type); + Class packetClass = type.getPacketClass(); - types.remove(Preconditions.checkNotNull(type, "type cannot be NULL.")); - if (packetClass != null) { - classes.remove(packetClass); + this.classes.remove(packetClass); } } - + /** * Remove the given types from the set. + * * @param types Types to remove */ - public synchronized void removeAll(Iterable types) { + public void removeAll(Iterable types) { for (PacketType type : types) { - removeType(type); + this.removeType(type); } } /** * Determine if the given packet type exists in the set. + * * @param type - the type to find. * @return TRUE if it does, FALSE otherwise. */ public boolean contains(PacketType type) { - return types.contains(type); + return this.types.contains(type); } - + /** * Determine if a packet type with the given packet class exists in the set. + * * @param packetClass - the class to find. * @return TRUE if it does, FALSE otherwise. */ public boolean contains(Class packetClass) { - return classes.contains(packetClass); + return this.classes.contains(packetClass); } - + /** * Determine if the type of a packet is in the current set. + * * @param packet - the packet. * @return TRUE if it is, FALSE otherwise. */ public boolean containsPacket(Object packet) { - if (packet == null) - return false; - return classes.contains(packet.getClass()); + return packet != null && this.classes.contains(packet.getClass()); } - + /** * Retrieve a view of this packet type set. + * * @return The packet type values. */ public Set values() { - return types; + return ImmutableSet.copyOf(this.types); } - + /** * Retrieve the number of entries in the set. + * * @return The number of entries. */ public int size() { - return types.size(); + return this.types.size(); } - - public synchronized void clear() { - types.clear(); - classes.clear(); + + public void clear() { + this.types.clear(); + this.classes.clear(); } } diff --git a/src/main/java/com/comphenix/protocol/concurrency/SortedCopyOnWriteArray.java b/src/main/java/com/comphenix/protocol/concurrency/SortedCopyOnWriteArray.java index ed1d84c8d..06a6d9fc1 100644 --- a/src/main/java/com/comphenix/protocol/concurrency/SortedCopyOnWriteArray.java +++ b/src/main/java/com/comphenix/protocol/concurrency/SortedCopyOnWriteArray.java @@ -2,243 +2,260 @@ * 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 + * 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. + * 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 + * 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.concurrency; +import com.google.common.base.Objects; +import com.google.common.collect.Iterables; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; -import com.google.common.base.Objects; -import com.google.common.collect.Iterables; - /** * An implicitly sorted array list that preserves insertion order and maintains duplicates. + * * @param - type of the elements in the list. */ public class SortedCopyOnWriteArray> implements Collection { + // Prevent reordering private volatile List list; - + /** * Construct an empty sorted array. */ public SortedCopyOnWriteArray() { - list = new ArrayList(); + this.list = new LinkedList<>(); } - + /** * Create a sorted array from the given list. The elements will be automatically sorted. + * * @param wrapped - the collection whose elements are to be placed into the list. */ public SortedCopyOnWriteArray(Collection wrapped) { - this.list = new ArrayList(wrapped); + this.list = new LinkedList<>(wrapped); } - + /** - * Create a sorted array from the given list. + * Create a sorted array from the given list. + * * @param wrapped - the collection whose elements are to be placed into the list. - * @param sort - TRUE to automatically sort the collection, FALSE if it is already sorted. + * @param sort - TRUE to automatically sort the collection, FALSE if it is already sorted. */ public SortedCopyOnWriteArray(Collection wrapped, boolean sort) { this.list = new ArrayList(wrapped); - + if (sort) { - Collections.sort(list); + Collections.sort(this.list); } } - + /** * Inserts the given element in the proper location. + * * @param value - element to insert. */ @Override public synchronized boolean add(T value) { - // We use NULL as a special marker, so we don't allow it - if (value == null) - throw new IllegalArgumentException("value cannot be NULL"); - - List copy = new ArrayList(); - - for (T element : list) { - // If the value is now greater than the current element, it should be placed right before it - if (value != null && value.compareTo(element) < 0) { - copy.add(value); - value = null; - } - copy.add(element); - } - - // Don't forget to add it - if (value != null) - copy.add(value); - - list = copy; - return true; - } - + // We use NULL as a special marker, so we don't allow it + if (value == null) { + throw new IllegalArgumentException("value cannot be NULL"); + } + + List copy = new ArrayList(); + + for (T element : this.list) { + // If the value is now greater than the current element, it should be placed right before it + if (value != null && value.compareTo(element) < 0) { + copy.add(value); + value = null; + } + copy.add(element); + } + + // Don't forget to add it + if (value != null) { + copy.add(value); + } + + this.list = copy; + return true; + } + @Override - public synchronized boolean addAll(Collection values) { - if (values == null) + public synchronized boolean addAll(Collection values) { + if (values == null) { throw new IllegalArgumentException("values cannot be NULL"); - if (values.size() == 0) + } + if (values.size() == 0) { return false; - - List copy = new ArrayList(); - - // Insert the new content and sort it - copy.addAll(list); - copy.addAll(values); - Collections.sort(copy); - - list = copy; - return true; - } - - /** - * Removes from the list by making a new list with every element except the one given. - *

- * Objects will be compared using the given objects equals() method. - * @param value - value to remove. - */ + } + + List copy = new ArrayList(); + + // Insert the new content and sort it + copy.addAll(this.list); + copy.addAll(values); + Collections.sort(copy); + + this.list = copy; + return true; + } + + /** + * Removes from the list by making a new list with every element except the one given. + *

+ * Objects will be compared using the given objects equals() method. + * + * @param value - value to remove. + */ @Override - public synchronized boolean remove(Object value) { - List copy = new ArrayList(); - boolean result = false; - - // Note that there's not much to be gained from using BinarySearch, as we - // have to copy (and thus read) the entire list regardless. - - // Copy every element except the one given to us. - for (T element : list) { - if (!Objects.equal(value, element)) { - copy.add(element); - } else { - result = true; - } - } - - list = copy; - return result; - } - + public synchronized boolean remove(Object value) { + List copy = new ArrayList(); + boolean result = false; + + // Note that there's not much to be gained from using BinarySearch, as we + // have to copy (and thus read) the entire list regardless. + + // Copy every element except the one given to us. + for (T element : this.list) { + if (!Objects.equal(value, element)) { + copy.add(element); + } else { + result = true; + } + } + + this.list = copy; + return result; + } + @Override public boolean removeAll(Collection values) { // Special cases - if (values == null) + if (values == null) { throw new IllegalArgumentException("values cannot be NULL"); - if (values.size() == 0) + } + if (values.size() == 0) { return false; - + } + List copy = new ArrayList(); - - copy.addAll(list); + + copy.addAll(this.list); copy.removeAll(values); - - list = copy; + + this.list = copy; return true; } @Override public boolean retainAll(Collection values) { // Special cases - if (values == null) + if (values == null) { throw new IllegalArgumentException("values cannot be NULL"); - if (values.size() == 0) + } + if (values.size() == 0) { return false; - + } + List copy = new ArrayList(); - - copy.addAll(list); + + copy.addAll(this.list); copy.removeAll(values); - - list = copy; + + this.list = copy; return true; } - - /** - * Removes from the list by making a copy of every element except the one with the given index. - * @param index - index of the element to remove. - */ - public synchronized void remove(int index) { - List copy = new ArrayList(list); - - copy.remove(index); - list = copy; - } - - /** - * Retrieves an element by index. - * @param index - index of element to retrieve. - * @return The element at the given location. - */ - public T get(int index) { - return list.get(index); - } - - /** - * Retrieve the size of the list. - * @return Size of the list. - */ - public int size() { - return list.size(); - } - - /** - * Retrieves an iterator over the elements in the given list. - * Warning: No not attempt to remove elements using the iterator. - */ + + /** + * Removes from the list by making a copy of every element except the one with the given index. + * + * @param index - index of the element to remove. + */ + public synchronized void remove(int index) { + List copy = new ArrayList(this.list); + + copy.remove(index); + this.list = copy; + } + + /** + * Retrieves an element by index. + * + * @param index - index of element to retrieve. + * @return The element at the given location. + */ + public T get(int index) { + return this.list.get(index); + } + + /** + * Retrieve the size of the list. + * + * @return Size of the list. + */ + public int size() { + return this.list.size(); + } + + /** + * Retrieves an iterator over the elements in the given list. Warning: No not attempt to remove elements using the + * iterator. + */ public Iterator iterator() { - return Iterables.unmodifiableIterable(list).iterator(); + return Iterables.unmodifiableIterable(this.list).iterator(); } - + @Override public void clear() { - list = new ArrayList(); + this.list = new ArrayList(); } @Override public boolean contains(Object value) { - return list.contains(value); + return this.list.contains(value); } @Override public boolean containsAll(Collection values) { - return list.containsAll(values); + return this.list.containsAll(values); } @Override public boolean isEmpty() { - return list.isEmpty(); + return this.list.isEmpty(); } @Override public Object[] toArray() { - return list.toArray(); + return this.list.toArray(); } @SuppressWarnings("hiding") @Override public T[] toArray(T[] a) { - return list.toArray(a); + return this.list.toArray(a); } - + @Override public String toString() { - return list.toString(); + return this.list.toString(); } } diff --git a/src/main/java/com/comphenix/protocol/events/ListenerOptions.java b/src/main/java/com/comphenix/protocol/events/ListenerOptions.java index 590149347..3233796ba 100644 --- a/src/main/java/com/comphenix/protocol/events/ListenerOptions.java +++ b/src/main/java/com/comphenix/protocol/events/ListenerOptions.java @@ -2,29 +2,24 @@ import com.comphenix.protocol.injector.GamePhase; - /** * Represents additional options a listener may require. - * + * * @author Kristian */ public enum ListenerOptions { + /** - * Retrieve the serialized client packet as it appears on the network stream. - */ - INTERCEPT_INPUT_BUFFER, - - /** - * Disable the automatic game phase detection that will normally force {@link GamePhase#LOGIN} when - * a packet ID is known to be transmitted during login. + * Disable the automatic game phase detection that will normally force {@link GamePhase#LOGIN} when a packet ID is + * known to be transmitted during login. */ DISABLE_GAMEPHASE_DETECTION, - + /** * Do not verify that the owning plugin has a vaid plugin.yml. */ SKIP_PLUGIN_VERIFIER, - + /** * Notify ProtocolLib that {@link PacketListener#onPacketSending(PacketEvent)} is thread safe. */ diff --git a/src/main/java/com/comphenix/protocol/events/ListeningWhitelist.java b/src/main/java/com/comphenix/protocol/events/ListeningWhitelist.java index 01a5c974d..6923b933d 100644 --- a/src/main/java/com/comphenix/protocol/events/ListeningWhitelist.java +++ b/src/main/java/com/comphenix/protocol/events/ListeningWhitelist.java @@ -17,20 +17,24 @@ package com.comphenix.protocol.events; -import java.util.*; - import com.comphenix.protocol.PacketType; import com.comphenix.protocol.injector.GamePhase; -import com.comphenix.protocol.injector.packet.PacketRegistry; import com.google.common.base.Objects; import com.google.common.collect.Sets; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Set; /** * Determines which packets will be observed by a listener, and with what priority. - + * * @author Kristian */ public class ListeningWhitelist { + /** * A whitelist with no packets - indicates that the listener shouldn't observe any packets. */ @@ -55,8 +59,72 @@ private ListeningWhitelist(ListenerPriority priority) { this.options = EnumSet.noneOf(ListenerOptions.class); } + /** + * Determine if the given whitelist is empty or not. + * + * @param whitelist - the whitelist to test. + * @return TRUE if the whitelist is empty, FALSE otherwise. + */ + public static boolean isEmpty(ListeningWhitelist whitelist) { + if (whitelist == EMPTY_WHITELIST) { + return true; + } else if (whitelist == null) { + return true; + } else { + return whitelist.getTypes().isEmpty(); + } + } + + /** + * Construct a new builder of whitelists. + * + * @return New whitelist builder. + */ + public static Builder newBuilder() { + return new Builder(null); + } + + /** + * Construct a new builder of whitelists initialized to the same values as the template. + * + * @param template - the template object. + * @return New whitelist builder. + */ + public static Builder newBuilder(ListeningWhitelist template) { + return new Builder(template); + } + + /** + * Construct a copy of a given enum. + * + * @param options - the options to copy, or NULL to indicate the empty set. + * @return A copy of the enum set. + */ + private static > EnumSet safeEnumSet(Collection options, Class enumClass) { + if (options != null && !options.isEmpty()) { + return EnumSet.copyOf(options); + } else { + return EnumSet.noneOf(enumClass); + } + } + + /** + * Construct a copy of a given set. + * + * @param set - the set to copy. + * @return The copied set. + */ + private static Set safeSet(Collection set) { + if (set != null) { + return Sets.newHashSet(set); + } else { + return Collections.emptySet(); + } + } + /** * Whether or not this whitelist has any enabled packets. + * * @return TRUE if there are any packets, FALSE otherwise. */ public boolean isEnabled() { @@ -65,6 +133,7 @@ public boolean isEnabled() { /** * Retrieve the priority in the execution order of the packet listener. Highest priority will be executed last. + * * @return Execution order in terms of priority. */ public ListenerPriority getPriority() { @@ -73,22 +142,25 @@ public ListenerPriority getPriority() { /** * Retrieves a set of the packets that will be observed by the listeners. + * * @return Packet whitelist. */ public Set getTypes() { return types; } - + /** * Retrieve which game phase this listener is active under. + * * @return The active game phase. */ public GamePhase getGamePhase() { return gamePhase; } - + /** * Retrieve every special option associated with this whitelist. + * * @return Every special option. */ public Set getOptions() { @@ -100,20 +172,6 @@ public int hashCode() { return Objects.hashCode(priority, types, gamePhase, options); } - /** - * Determine if the given whitelist is empty or not. - * @param whitelist - the whitelist to test. - * @return TRUE if the whitelist is empty, FALSE otherwise. - */ - public static boolean isEmpty(ListeningWhitelist whitelist) { - if (whitelist == EMPTY_WHITELIST) - return true; - else if (whitelist == null) - return true; - else - return whitelist.getTypes().isEmpty(); - } - @Override public boolean equals(final Object obj) { if (obj instanceof ListeningWhitelist) { @@ -129,67 +187,30 @@ public boolean equals(final Object obj) { @Override public String toString() { - if (this == EMPTY_WHITELIST) + if (this == EMPTY_WHITELIST) { return "EMPTY_WHITELIST"; - else - return "ListeningWhitelist[priority=" + priority + ", packets=" + types + ", gamephase=" + gamePhase + ", options=" + options + "]"; - } - - /** - * Construct a new builder of whitelists. - * @return New whitelist builder. - */ - public static Builder newBuilder() { - return new Builder(null); - } - - /** - * Construct a new builder of whitelists initialized to the same values as the template. - * @param template - the template object. - * @return New whitelist builder. - */ - public static Builder newBuilder(ListeningWhitelist template) { - return new Builder(template); - } - - /** - * Construct a copy of a given enum. - * @param options - the options to copy, or NULL to indicate the empty set. - * @return A copy of the enum set. - */ - private static > EnumSet safeEnumSet(Collection options, Class enumClass) { - if (options != null && !options.isEmpty()) { - return EnumSet.copyOf(options); } else { - return EnumSet.noneOf(enumClass); + return "ListeningWhitelist[priority=" + priority + ", packets=" + types + ", gamephase=" + gamePhase + + ", options=" + options + "]"; } } - - /** - * Construct a copy of a given set. - * @param set - the set to copy. - * @return The copied set. - */ - private static Set safeSet(Collection set) { - if (set != null) - return Sets.newHashSet(set); - else - return Collections.emptySet(); - } /** * Represents a builder of whitelists. + * * @author Kristian */ public static class Builder { + // Default values private ListenerPriority priority = ListenerPriority.NORMAL; private Set types = Sets.newHashSet(); private GamePhase gamePhase = GamePhase.PLAYING; private Set options = Sets.newHashSet(); - + /** * Construct a new listening whitelist template. + * * @param template - the template. */ private Builder(ListeningWhitelist template) { @@ -200,9 +221,10 @@ private Builder(ListeningWhitelist template) { options(template.getOptions()); } } - + /** * Set the priority to use when constructing new whitelists. + * * @param priority - the priority. * @return This builder, for chaining. */ @@ -210,49 +232,55 @@ public Builder priority(ListenerPriority priority) { this.priority = priority; return this; } - + /** * Set the priority of the whitelist to monitor. + * * @return This builder, for chaining. */ public Builder monitor() { return priority(ListenerPriority.MONITOR); } - + /** * Set the priority of the whitelist to normal. + * * @return This builder, for chaining. */ public Builder normal() { return priority(ListenerPriority.NORMAL); } - + /** * Set the priority of the whitelist to lowest. + * * @return This builder, for chaining. */ public Builder lowest() { return priority(ListenerPriority.LOWEST); } - + /** * Set the priority of the whitelist to low. + * * @return This builder, for chaining. */ public Builder low() { return priority(ListenerPriority.LOW); } - + /** * Set the priority of the whitelist to highest. + * * @return This builder, for chaining. */ public Builder highest() { return priority(ListenerPriority.HIGHEST); } - + /** * Set the priority of the whitelist to high. + * * @return This builder, for chaining. */ public Builder high() { @@ -261,6 +289,7 @@ public Builder high() { /** * Set the whitelist of packet types to copy when constructing new whitelists. + * * @param types - the whitelist of packets. * @return This builder, for chaining. */ @@ -268,9 +297,10 @@ public Builder types(PacketType... types) { this.types = safeSet(Sets.newHashSet(types)); return this; } - + /** * Set the whitelist of packet types to copy when constructing new whitelists. + * * @param types - the whitelist of packets. * @return This builder, for chaining. */ @@ -278,9 +308,10 @@ public Builder types(Collection types) { this.types = safeSet(types); return this; } - + /** * Set the gamephase to use when constructing new whitelists. + * * @param gamePhase - the gamephase. * @return This builder, for chaining. */ @@ -288,16 +319,19 @@ public Builder gamePhase(GamePhase gamePhase) { this.gamePhase = gamePhase; return this; } - + /** * Set the gamephase to {@link GamePhase#BOTH}. + * * @return This builder, for chaining. */ public Builder gamePhaseBoth() { return gamePhase(GamePhase.BOTH); } + /** * Set the options to copy when constructing new whitelists. + * * @param options - the options. * @return This builder, for chaining. */ @@ -305,9 +339,10 @@ public Builder options(Set options) { this.options = safeSet(options); return this; } - + /** * Set the options to copy when constructing new whitelists. + * * @param options - the options. * @return This builder, for chaining. */ @@ -315,9 +350,10 @@ public Builder options(Collection options) { this.options = safeSet(options); return this; } - + /** * Set the options to copy when constructing new whitelists. + * * @param serverOptions - the options array. * @return This builder, for chaining. */ @@ -325,32 +361,36 @@ public Builder options(ListenerOptions[] serverOptions) { this.options = safeSet(Sets.newHashSet(serverOptions)); return this; } - + /** * Options to merge into the current set of options. + * * @param serverOptions - the options array. * @return This builder, for chaining. */ public Builder mergeOptions(ListenerOptions... serverOptions) { return mergeOptions(Arrays.asList(serverOptions)); } - + /** * Options to merge into the current set of options. + * * @param serverOptions - the options array. * @return This builder, for chaining. */ public Builder mergeOptions(Collection serverOptions) { - if (options == null) + if (options == null) { return options(serverOptions); - + } + // Merge the options this.options.addAll(serverOptions); return this; } - + /** * Construct a new whitelist from the values in this builder. + * * @return The new whitelist. */ public ListeningWhitelist build() { diff --git a/src/main/java/com/comphenix/protocol/events/NetworkMarker.java b/src/main/java/com/comphenix/protocol/events/NetworkMarker.java index 0ff7a79e0..2231e88b9 100644 --- a/src/main/java/com/comphenix/protocol/events/NetworkMarker.java +++ b/src/main/java/com/comphenix/protocol/events/NetworkMarker.java @@ -1,421 +1,155 @@ package com.comphenix.protocol.events; -import java.io.ByteArrayInputStream; -import java.io.DataInputStream; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.PriorityQueue; - -import javax.annotation.Nonnull; - -import org.bukkit.entity.Player; - import com.comphenix.protocol.PacketType; import com.comphenix.protocol.ProtocolManager; -import com.comphenix.protocol.utility.ByteBufferInputStream; -import com.comphenix.protocol.utility.MinecraftReflection; -import com.comphenix.protocol.utility.StreamSerializer; import com.google.common.base.Preconditions; -import com.google.common.collect.Lists; -import com.google.common.primitives.Ints; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import javax.annotation.Nonnull; +import org.bukkit.entity.Player; /** - * Marker containing the serialized packet data seen from the network, - * or output handlers that will serialize the current packet. - * + * Marker containing the serialized packet data seen from the network, or output handlers that will serialize the + * current packet. + * * @author Kristian */ -public abstract class NetworkMarker { - public static class EmptyBufferMarker extends NetworkMarker { - public EmptyBufferMarker(@Nonnull ConnectionSide side) { - super(side, (byte[]) null, null); - } +public class NetworkMarker { - @Override - protected DataInputStream skipHeader(DataInputStream input) throws IOException { - throw new IllegalStateException("Buffer is empty."); - } + // The input data + private final PacketType type; + private final ConnectionSide side; - @Override - protected ByteBuffer addHeader(ByteBuffer buffer, PacketType type) { - throw new IllegalStateException("Buffer is empty."); - } + // Post-processing of the packet + private Set postListeners; + private Set scheduledPackets; - @Override - protected DataInputStream addHeader(DataInputStream input, PacketType type) { - throw new IllegalStateException("Buffer is empty."); - } - } - - // Custom network handler - private PriorityQueue outputHandlers; - // Post listeners - private List postListeners; - // Post packets - private List scheduledPackets; - - // The input buffer - private ByteBuffer inputBuffer; - private final ConnectionSide side; - private final PacketType type; - - // Cache serializer too - private StreamSerializer serializer; - - /** - * Construct a new network marker. - * @param side - which side this marker belongs to. - * @param inputBuffer - the read serialized packet data. - * @param type - packet type - */ - public NetworkMarker(@Nonnull ConnectionSide side, ByteBuffer inputBuffer, PacketType type) { - this.side = Preconditions.checkNotNull(side, "side cannot be NULL."); - this.inputBuffer = inputBuffer; - this.type = type; - } - /** * Construct a new network marker. *

* The input buffer is only non-null for client-side packets. + * * @param side - which side this marker belongs to. - * @param inputBuffer - the read serialized packet data. * @param type - packet type */ - public NetworkMarker(@Nonnull ConnectionSide side, byte[] inputBuffer, PacketType type) { + public NetworkMarker(@Nonnull ConnectionSide side, PacketType type) { this.side = Preconditions.checkNotNull(side, "side cannot be NULL."); this.type = type; - - if (inputBuffer != null) { - this.inputBuffer = ByteBuffer.wrap(inputBuffer); - } - } - - /** - * Retrieve whether or not this marker belongs to a client or a server side packet. - * @return The side the parent packet belongs to. - */ - public ConnectionSide getSide() { - return side; } /** - * Retrieve a utility class for serializing and deserializing Minecraft objects. - * @return Serialization utility class. - */ - public StreamSerializer getSerializer() { - if (serializer == null) - serializer = new StreamSerializer(); - return serializer; - } - - /** - * Retrieve the serialized packet data (excluding the header by default) from the network input stream. - *

- * The returned buffer is read-only. If the parent event is a server side packet this - * method throws {@link IllegalStateException}. - *

- * It returns NULL if the packet was transmitted by a plugin locally. - * @return A byte buffer containing the raw packet data read from the network. - */ - public ByteBuffer getInputBuffer() { - return getInputBuffer(true); - } - - /** - * Retrieve the serialized packet data from the network input stream. - *

- * The returned buffer is read-only. If the parent event is a server side packet this - * method throws {@link IllegalStateException}. - *

- * It returns NULL if the packet was transmitted by a plugin locally. - * @param excludeHeader - whether or not to exclude the packet ID header. - * @return A byte buffer containing the raw packet data read from the network. - */ - public ByteBuffer getInputBuffer(boolean excludeHeader) { - if (side.isForServer()) - throw new IllegalStateException("Server-side packets have no input buffer."); - - if (inputBuffer != null) { - ByteBuffer result = inputBuffer.asReadOnlyBuffer(); - - try { - if (excludeHeader) - result = skipHeader(result); - else - result = addHeader(result, type); - } catch (IOException e) { - throw new RuntimeException("Cannot skip packet header.", e); - } - return result; - } - return null; - } - - /** - * Retrieve the serialized packet data (excluding the header by default) as an input stream. - *

- * The data is exactly the same as in {@link #getInputBuffer()}. - * @see #getInputBuffer() - * @return The incoming serialized packet data as a stream, or NULL if the packet was transmitted locally. + * Determine if the given marker has any post listeners. + * + * @param marker - the marker to check. + * @return TRUE if it does, FALSE otherwise. */ - public DataInputStream getInputStream() { - return getInputStream(true); + public static boolean hasPostListeners(NetworkMarker marker) { + return marker != null && !marker.getPostListeners().isEmpty(); } - + /** - * Retrieve the serialized packet data as an input stream. + * Retrieve the network marker of a particular event without creating it. *

- * The data is exactly the same as in {@link #getInputBuffer()}. - * @see #getInputBuffer() - * @param excludeHeader - whether or not to exclude the packet ID header. - * @return The incoming serialized packet data as a stream, or NULL if the packet was transmitted locally. - */ - @SuppressWarnings("resource") - public DataInputStream getInputStream(boolean excludeHeader) { - if (side.isForServer()) - throw new IllegalStateException("Server-side packets have no input buffer."); - if (inputBuffer == null) - return null; - - DataInputStream input = new DataInputStream( - new ByteArrayInputStream(inputBuffer.array()) - ); - - try { - if (excludeHeader) - input = skipHeader(input); - else - input = addHeader(input, type); - } catch (IOException e) { - throw new RuntimeException("Cannot skip packet header.", e); - } - return input; - } - - /** - * Whether or not the output handlers have to write a packet header. - * @return TRUE if they do, FALSE otherwise. + * This is an internal method that should not be used by API users. + * + * @param event - the event. + * @return The network marker. */ - public boolean requireOutputHeader() { - return MinecraftReflection.isUsingNetty(); + public static NetworkMarker getNetworkMarker(PacketEvent event) { + return event.networkMarker; } - + /** - * Enqueue the given output handler for managing how the current packet will be written to the network stream. - *

- * Note that output handlers are not serialized, as most consumers will probably implement them using anonymous classes. - * It is not safe to serialize anonymous classes, as their name depend on the order in which they are declared in the parent class. + * Retrieve the scheduled packets of a particular network marker without constructing the list. *

- * This can only be invoked on server side packet events. - * @param handler - the handler that will take part in serializing the packet. - * @return TRUE if it was added, FALSE if it has already been added. + * This is an internal method that should not be used by API users. + * + * @param marker - the marker. + * @return The list, or NULL if not found or initialized. */ - public boolean addOutputHandler(@Nonnull PacketOutputHandler handler) { - checkServerSide(); - Preconditions.checkNotNull(handler, "handler cannot be NULL."); - - // Lazy initialization - it's imperative that we save space and time here - if (outputHandlers == null) { - outputHandlers = new PriorityQueue(10, new Comparator() { - @Override - public int compare(PacketOutputHandler o1, PacketOutputHandler o2) { - return Ints.compare(o1.getPriority().getSlot(), o2.getPriority().getSlot()); - } - }); - } - return outputHandlers.add(handler); + public static Set readScheduledPackets(NetworkMarker marker) { + return marker.scheduledPackets; } - + /** - * Remove a given output handler from the serialization queue. - *

- * This can only be invoked on server side packet events. - * @param handler - the handler to remove. - * @return TRUE if the handler was removed, FALSE otherwise. + * Retrieve whether or not this marker belongs to a client or a server side packet. + * + * @return The side the parent packet belongs to. */ - public boolean removeOutputHandler(@Nonnull PacketOutputHandler handler) { - checkServerSide(); - Preconditions.checkNotNull(handler, "handler cannot be NULL."); - - if (outputHandlers != null) { - return outputHandlers.remove(handler); - } - return false; + public ConnectionSide getSide() { + return this.side; } - - /** - * Retrieve every registered output handler in no particular order. - * @return Every registered output handler. - */ - @Nonnull - public Collection getOutputHandlers() { - if (outputHandlers != null) { - return outputHandlers; - } else { - return Collections.emptyList(); - } + + public PacketType getType() { + return this.type; } - + /** - * Add a listener that is invoked after a packet has been successfully sent to the client, or received - * by the server. + * Add a listener that is invoked after a packet has been successfully sent to the client, or received by the server. *

- * Received packets are not guarenteed to have been fully processed, but packets passed - * to {@link ProtocolManager#recieveClientPacket(Player, PacketContainer)} will be processed after the - * current packet event. + * Received packets are not guarenteed to have been fully processed, but packets passed to {@link + * ProtocolManager#receiveClientPacket(Player, PacketContainer)} will be processed after the current packet event. *

- * Note that post listeners will be executed asynchronously off the main thread. They are not executed - * in any defined order. + * Note that post listeners will be executed asynchronously off the main thread. They are not executed in any defined + * order. + * * @param listener - the listener that will be invoked. * @return TRUE if it was added. */ public boolean addPostListener(PacketPostListener listener) { - if (postListeners == null) - postListeners = Lists.newArrayList(); - return postListeners.add(listener); + if (this.postListeners == null) { + this.postListeners = new HashSet<>(); + } + + return this.postListeners.add(listener); } - + /** * Remove the first instance of the given listener. + * * @param listener - listener to remove. * @return TRUE if it was removed, FALSE otherwise. */ public boolean removePostListener(PacketPostListener listener) { - if (postListeners != null) { - return postListeners.remove(listener); + if (this.postListeners != null) { + return this.postListeners.remove(listener); } + return false; } - + /** * Retrieve an immutable view of all the listeners that will be invoked once the packet has been sent or received. + * * @return Every post packet listener. Never NULL. */ - public List getPostListeners() { - return postListeners != null ? Collections.unmodifiableList(postListeners) : Collections.emptyList(); + public Set getPostListeners() { + return this.postListeners != null ? this.postListeners : Collections.emptySet(); } - + /** - * Retrieve a list of packets that will be schedule (in-order) when the current packet has been successfully transmitted. + * Retrieve a list of packets that will be schedule (in-order) when the current packet has been successfully + * transmitted. *

* This list is modifiable. + * * @return List of packets that will be scheduled. */ - public List getScheduledPackets() { - if (scheduledPackets == null) - scheduledPackets = Lists.newArrayList(); - return scheduledPackets; + public Set getScheduledPackets() { + if (this.scheduledPackets == null) { + this.scheduledPackets = new HashSet<>(); + } + + return this.scheduledPackets; } - + /** * Ensure that the packet event is server side. */ private void checkServerSide() { - if (side.isForClient()) { + if (this.side.isForClient()) { throw new IllegalStateException("Must be a server side packet."); } } - - /** - * Return a byte buffer without the header in the current packet. - *

- * It's safe to modify the position of the buffer. - * @param buffer - a read-only byte source. - * @return A byte buffer without the header in the current packet. - * @throws IOException If integer reading fails - */ - protected ByteBuffer skipHeader(ByteBuffer buffer) throws IOException { - skipHeader(new DataInputStream(new ByteBufferInputStream(buffer))); - return buffer; - } - - /** - * Return an input stream without the header in the current packet. - *

- * It's safe to modify the input stream. - * @param input - input stream - * @return An input stream without the header - * @throws IOException If integer reading fails - */ - protected abstract DataInputStream skipHeader(DataInputStream input) throws IOException; - - /** - * Return the byte buffer prepended with the packet header. - * @param buffer - the read-only byte buffer. - * @param type - the current packet. - * @return The byte buffer. - */ - protected abstract ByteBuffer addHeader(ByteBuffer buffer, PacketType type); - - /** - * Return the input stream prepended with the packet header. - * @param input - the input stream. - * @param type - the current packet. - * @return The byte buffer. - */ - protected abstract DataInputStream addHeader(DataInputStream input, PacketType type); - - /** - * Determine if the given marker has any output handlers. - * @param marker - the marker to check. - * @return TRUE if it does, FALSE otherwise. - */ - public static boolean hasOutputHandlers(NetworkMarker marker) { - return marker != null && !marker.getOutputHandlers().isEmpty(); - } - - /** - * Determine if the given marker has any post listeners. - * @param marker - the marker to check. - * @return TRUE if it does, FALSE otherwise. - */ - public static boolean hasPostListeners(NetworkMarker marker) { - return marker != null && !marker.getPostListeners().isEmpty(); - } - - /** - * Retrieve the byte buffer stored in the given marker. - * @param marker - the marker. - * @return The byte buffer, or NULL if not found. - */ - public static byte[] getByteBuffer(NetworkMarker marker) { - if (marker != null) { - ByteBuffer buffer = marker.getInputBuffer(); - - if (buffer != null) { - byte[] data = new byte[buffer.remaining()]; - - buffer.get(data, 0, data.length); - return data; - } - } - return null; - } - - /** - * Retrieve the network marker of a particular event without creating it. - *

- * This is an internal method that should not be used by API users. - * @param event - the event. - * @return The network marker. - */ - public static NetworkMarker getNetworkMarker(PacketEvent event) { - return event.networkMarker; - } - - /** - * Retrieve the scheduled packets of a particular network marker without constructing the list. - *

- * This is an internal method that should not be used by API users. - * @param marker - the marker. - * @return The list, or NULL if not found or initialized. - */ - public static List readScheduledPackets(NetworkMarker marker) { - return marker.scheduledPackets; - } } diff --git a/src/main/java/com/comphenix/protocol/events/PacketAdapter.java b/src/main/java/com/comphenix/protocol/events/PacketAdapter.java index 29cd316e1..45a4cb36b 100644 --- a/src/main/java/com/comphenix/protocol/events/PacketAdapter.java +++ b/src/main/java/com/comphenix/protocol/events/PacketAdapter.java @@ -17,86 +17,93 @@ package com.comphenix.protocol.events; -import java.util.List; -import java.util.Set; -import javax.annotation.Nonnull; - import com.comphenix.protocol.PacketType; import com.comphenix.protocol.injector.GamePhase; import com.google.common.base.Preconditions; import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; import com.google.common.collect.Sets; - +import java.util.Set; +import javax.annotation.Nonnull; import org.bukkit.plugin.Plugin; /** * Represents a packet listener with useful constructors. *

* Remember to override onPacketReceiving() and onPacketSending(), depending on the ConnectionSide. + * * @author Kristian */ public abstract class PacketAdapter implements PacketListener { + protected Plugin plugin; protected ConnectionSide connectionSide; protected ListeningWhitelist receivingWhitelist = ListeningWhitelist.EMPTY_WHITELIST; protected ListeningWhitelist sendingWhitelist = ListeningWhitelist.EMPTY_WHITELIST; /** - * Initialize a packet adapter using a collection of parameters. Use {@link #params()} to get an instance to this builder. + * Initialize a packet adapter using a collection of parameters. Use {@link #params()} to get an instance to this + * builder. + * * @param params - the parameters. */ public PacketAdapter(@Nonnull AdapterParameteters params) { this( - checkValidity(params).plugin, params.connectionSide, params.listenerPriority, - params.gamePhase, params.options, params.packets + checkValidity(params).plugin, params.connectionSide, params.listenerPriority, + params.gamePhase, params.options, params.packets ); } - + /** * Initialize a packet listener with the given parameters. + * * @param plugin - the plugin. - * @param types - the packet types. + * @param types - the packet types. */ public PacketAdapter(Plugin plugin, PacketType... types) { this(plugin, ListenerPriority.NORMAL, types); } - + /** * Initialize a packet listener with the given parameters. + * * @param plugin - the plugin. - * @param types - the packet types. + * @param types - the packet types. */ public PacketAdapter(Plugin plugin, Iterable types) { this(params(plugin, Iterables.toArray(types, PacketType.class))); } - + /** * Initialize a packet listener with the given parameters. - * @param plugin - the plugin. + * + * @param plugin - the plugin. * @param listenerPriority - the priority. - * @param types - the packet types. + * @param types - the packet types. */ public PacketAdapter(Plugin plugin, ListenerPriority listenerPriority, Iterable types) { this(params(plugin, Iterables.toArray(types, PacketType.class)).listenerPriority(listenerPriority)); } - + /** * Initialize a packet listener with the given parameters. - * @param plugin - the plugin. + * + * @param plugin - the plugin. * @param listenerPriority - the priority. - * @param types - the packet types. - * @param options - the options. + * @param types - the packet types. + * @param options - the options. */ - public PacketAdapter(Plugin plugin, ListenerPriority listenerPriority, Iterable types, ListenerOptions... options) { - this(params(plugin, Iterables.toArray(types, PacketType.class)).listenerPriority(listenerPriority).options(options)); + public PacketAdapter(Plugin plugin, ListenerPriority listenerPriority, Iterable types, + ListenerOptions... options) { + this( + params(plugin, Iterables.toArray(types, PacketType.class)).listenerPriority(listenerPriority).options(options)); } - + /** * Initialize a packet listener with the given parameters. - * @param plugin - the plugin. + * + * @param plugin - the plugin. * @param listenerPriority - the priority. - * @param types - the packet types. + * @param types - the packet types. */ public PacketAdapter(Plugin plugin, ListenerPriority listenerPriority, PacketType... types) { this(params(plugin, types).listenerPriority(listenerPriority)); @@ -106,102 +113,69 @@ public PacketAdapter(Plugin plugin, ListenerPriority listenerPriority, PacketTyp private PacketAdapter( Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, GamePhase gamePhase, ListenerOptions[] options, PacketType... packets) { - - if (plugin == null) + + if (plugin == null) { throw new IllegalArgumentException("plugin cannot be null"); - if (connectionSide == null) + } + if (connectionSide == null) { throw new IllegalArgumentException("connectionSide cannot be null"); - if (listenerPriority == null) + } + if (listenerPriority == null) { throw new IllegalArgumentException("listenerPriority cannot be null"); - if (gamePhase == null) + } + if (gamePhase == null) { throw new IllegalArgumentException("gamePhase cannot be NULL"); - if (packets == null) + } + if (packets == null) { throw new IllegalArgumentException("packets cannot be null"); - if (options == null) + } + if (options == null) { throw new IllegalArgumentException("options cannot be null"); - - ListenerOptions[] serverOptions = options; - ListenerOptions[] clientOptions = options; - - // Special case that allows us to specify optionIntercept(). - if (connectionSide == ConnectionSide.BOTH) { - serverOptions = except(serverOptions, new ListenerOptions[0], - ListenerOptions.INTERCEPT_INPUT_BUFFER); } - + // Add whitelists - if (connectionSide.isForServer()) - sendingWhitelist = ListeningWhitelist.newBuilder(). - priority(listenerPriority). - types(packets). - gamePhase(gamePhase). - options(serverOptions). - build(); - - if (connectionSide.isForClient()) - receivingWhitelist = ListeningWhitelist.newBuilder(). - priority(listenerPriority). - types(packets). - gamePhase(gamePhase). - options(clientOptions). - build(); - + if (connectionSide.isForServer()) { + sendingWhitelist = ListeningWhitelist.newBuilder() + .priority(listenerPriority) + .types(packets) + .gamePhase(gamePhase) + .options(options) + .build(); + } + + if (connectionSide.isForClient()) { + receivingWhitelist = ListeningWhitelist.newBuilder() + .priority(listenerPriority) + .types(packets) + .gamePhase(gamePhase) + .options(options) + .build(); + } + this.plugin = plugin; this.connectionSide = connectionSide; } - - // Remove a given element from an array - private static T[] except(T[] values, T[] buffer, T except) { - List result = Lists.newArrayList(values); - - result.remove(except); - return result.toArray(buffer); - } - - @Override - public void onPacketReceiving(PacketEvent event) { - // Lets prevent some bugs - throw new IllegalStateException("Override onPacketReceiving to get notifcations of received packets!"); - } - - @Override - public void onPacketSending(PacketEvent event) { - // Lets prevent some bugs - throw new IllegalStateException("Override onPacketSending to get notifcations of sent packets!"); - } - - @Override - public ListeningWhitelist getReceivingWhitelist() { - return receivingWhitelist; - } - - @Override - public ListeningWhitelist getSendingWhitelist() { - return sendingWhitelist; - } - - @Override - public Plugin getPlugin() { - return plugin; - } - + /** * Retrieves the name of the plugin that has been associated with the listener. + * * @param listener - the listener. * @return Name of the associated plugin. */ public static String getPluginName(PacketListener listener) { return getPluginName(listener.getPlugin()); } - + /** * Retrieves the name of the given plugin. + * * @param plugin - the plugin. * @return Name of the given plugin. */ public static String getPluginName(Plugin plugin) { - if (plugin == null) + if (plugin == null) { return "UNKNOWN"; + } try { return plugin.getName(); @@ -209,20 +183,12 @@ public static String getPluginName(Plugin plugin) { return plugin.toString(); } } - - @Override - public String toString() { - // This is used by the error reporter - return String.format("PacketAdapter[plugin=%s, sending=%s, receiving=%s]", - getPluginName(this), - sendingWhitelist, - receivingWhitelist); - } - + /** * Construct a helper object for passing parameters to the packet adapter. *

* This is often simpler and better than passing them directly to each constructor. + * * @return Helper object. */ public static AdapterParameteters params() { @@ -233,32 +199,91 @@ public static AdapterParameteters params() { * Construct a helper object for passing parameters to the packet adapter. *

* This is often simpler and better than passing them directly to each constructor. - * @param plugin - the plugin that spawned this listener. + * + * @param plugin - the plugin that spawned this listener. * @param packets - the packet types the listener is looking for. * @return Helper object. */ public static AdapterParameteters params(Plugin plugin, PacketType... packets) { return new AdapterParameteters().plugin(plugin).types(packets); } - + + /** + * Determine if the required parameters are set. + */ + private static AdapterParameteters checkValidity(AdapterParameteters params) { + if (params == null) { + throw new IllegalArgumentException("params cannot be NULL."); + } + if (params.plugin == null) { + throw new IllegalStateException("Plugin was never set in the parameters."); + } + if (params.connectionSide == null) { + throw new IllegalStateException("Connection side was never set in the parameters."); + } + if (params.packets == null) { + throw new IllegalStateException("Packet IDs was never set in the parameters."); + } + return params; + } + + @Override + public void onPacketReceiving(PacketEvent event) { + // Lets prevent some bugs + throw new IllegalStateException("Override onPacketReceiving to get notifcations of received packets!"); + } + + @Override + public void onPacketSending(PacketEvent event) { + // Lets prevent some bugs + throw new IllegalStateException("Override onPacketSending to get notifcations of sent packets!"); + } + + @Override + public ListeningWhitelist getReceivingWhitelist() { + return receivingWhitelist; + } + + @Override + public ListeningWhitelist getSendingWhitelist() { + return sendingWhitelist; + } + + @Override + public Plugin getPlugin() { + return plugin; + } + + @Override + public String toString() { + // This is used by the error reporter + return String.format("PacketAdapter[plugin=%s, sending=%s, receiving=%s]", + getPluginName(this), + sendingWhitelist, + receivingWhitelist); + } + /** * Represents a builder for passing parameters to the packet adapter constructor. *

* Note: Never make spelling mistakes in a public API! + * * @author Kristian */ public static class AdapterParameteters { + private Plugin plugin; private ConnectionSide connectionSide; private PacketType[] packets; - + // Parameters with default values private GamePhase gamePhase = GamePhase.PLAYING; private ListenerOptions[] options = new ListenerOptions[0]; private ListenerPriority listenerPriority = ListenerPriority.NORMAL; - + /** * Set the plugin that spawned this listener. This parameter is required. + * * @param plugin - the plugin. * @return This builder, for chaining. */ @@ -266,9 +291,10 @@ public AdapterParameteters plugin(@Nonnull Plugin plugin) { this.plugin = Preconditions.checkNotNull(plugin, "plugin cannot be NULL."); return this; } - + /** * Set the packet types this listener is looking for. This parameter is required. + * * @param connectionSide - the new packet type. * @return This builder, for chaining. */ @@ -276,27 +302,30 @@ public AdapterParameteters connectionSide(@Nonnull ConnectionSide connectionSide this.connectionSide = Preconditions.checkNotNull(connectionSide, "connectionside cannot be NULL."); return this; } - + /** * Set this adapter to also look for client-side packets. + * * @return This builder, for chaining. */ public AdapterParameteters clientSide() { return connectionSide(ConnectionSide.add(connectionSide, ConnectionSide.CLIENT_SIDE)); } - + /** * Set this adapter to also look for server-side packets. + * * @return This builder, for chaining. */ public AdapterParameteters serverSide() { return connectionSide(ConnectionSide.add(connectionSide, ConnectionSide.SERVER_SIDE)); } - + /** * Set the the event priority, where the execution is in ascending order from lowest to highest. *

* Default is {@link ListenerPriority#NORMAL}. + * * @param listenerPriority - the new event priority. * @return This builder, for chaining. */ @@ -304,11 +333,13 @@ public AdapterParameteters listenerPriority(@Nonnull ListenerPriority listenerPr this.listenerPriority = Preconditions.checkNotNull(listenerPriority, "listener priority cannot be NULL."); return this; } - + /** - * Set which game phase this listener is active under. This is a hint for ProtocolLib to start intercepting login packets. + * Set which game phase this listener is active under. This is a hint for ProtocolLib to start intercepting login + * packets. *

* Default is {@link GamePhase#PLAYING}, which will not intercept login packets. + * * @param gamePhase - the new game phase. * @return This builder, for chaining. */ @@ -316,19 +347,22 @@ public AdapterParameteters gamePhase(@Nonnull GamePhase gamePhase) { this.gamePhase = Preconditions.checkNotNull(gamePhase, "gamePhase cannot be NULL."); return this; } - + /** * Set the game phase to {@link GamePhase#LOGIN}, allowing ProtocolLib to intercept login packets. + * * @return This builder, for chaining. */ public AdapterParameteters loginPhase() { return gamePhase(GamePhase.LOGIN); } - + /** - * Set listener options that decide whether or not to intercept the raw packet data as read from the network stream. + * Set listener options that decide whether or not to intercept the raw packet data as read from the network + * stream. *

* The default is to disable this raw packet interception. + * * @param options - every option to use. * @return This builder, for chaining. */ @@ -338,9 +372,11 @@ public AdapterParameteters options(@Nonnull ListenerOptions... options) { } /** - * Set listener options that decide whether or not to intercept the raw packet data as read from the network stream. + * Set listener options that decide whether or not to intercept the raw packet data as read from the network + * stream. *

* The default is to disable this raw packet interception. + * * @param options - every option to use. * @return This builder, for chaining. */ @@ -349,9 +385,10 @@ public AdapterParameteters options(@Nonnull Set optio this.options = options.toArray(new ListenerOptions[0]); return this; } - + /** * Add a given option to the current builder. + * * @param option - the option to add. * @return This builder, for chaining. */ @@ -364,19 +401,12 @@ private AdapterParameteters addOption(ListenerOptions option) { return options(current); } } - - /** - * Set the listener option to {@link ListenerOptions#INTERCEPT_INPUT_BUFFER}, causing ProtocolLib to read the raw packet data from the network stream. - * @return This builder, for chaining. - */ - public AdapterParameteters optionIntercept() { - return addOption(ListenerOptions.INTERCEPT_INPUT_BUFFER); - } /** * Set the listener option to {@link ListenerOptions#ASYNC}, indicating that our listener is thread safe. *

* This allows ProtocolLib to perform certain optimizations. + * * @return This builder, for chaining. */ public AdapterParameteters optionAsync() { @@ -387,6 +417,7 @@ public AdapterParameteters optionAsync() { * Set the packet types the listener is looking for. *

* This parameter is required. + * * @param packets - the packet types to look for. * @return This builder, for chaining. */ @@ -398,16 +429,18 @@ public AdapterParameteters types(@Nonnull PacketType... packets) { } } this.packets = Preconditions.checkNotNull(packets, "packets cannot be NULL"); - - if (packets.length == 0) + + if (packets.length == 0) { throw new IllegalArgumentException("Passed an empty packet type array."); + } return this; } - + /** * Set the packet types the listener is looking for. *

* This parameter is required. + * * @param packets - a set of packet types to look for. * @return This builder, for chaining. */ @@ -415,19 +448,4 @@ public AdapterParameteters types(@Nonnull Set packets) { return types(packets.toArray(new PacketType[0])); } } - - /** - * Determine if the required parameters are set. - */ - private static AdapterParameteters checkValidity(AdapterParameteters params) { - if (params == null) - throw new IllegalArgumentException("params cannot be NULL."); - if (params.plugin == null) - throw new IllegalStateException("Plugin was never set in the parameters."); - if (params.connectionSide == null) - throw new IllegalStateException("Connection side was never set in the parameters."); - if (params.packets == null) - throw new IllegalStateException("Packet IDs was never set in the parameters."); - return params; - } } diff --git a/src/main/java/com/comphenix/protocol/events/PacketEvent.java b/src/main/java/com/comphenix/protocol/events/PacketEvent.java index 815f9bf16..248a48fd5 100644 --- a/src/main/java/com/comphenix/protocol/events/PacketEvent.java +++ b/src/main/java/com/comphenix/protocol/events/PacketEvent.java @@ -17,64 +17,60 @@ package com.comphenix.protocol.events; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.lang.ref.WeakReference; -import java.util.EventObject; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.event.Cancellable; - import com.comphenix.protocol.PacketType; import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.async.AsyncMarker; import com.comphenix.protocol.error.PluginContext; import com.comphenix.protocol.error.Report; import com.comphenix.protocol.error.ReportType; -import com.comphenix.protocol.injector.server.TemporaryPlayer; +import com.comphenix.protocol.injector.temporary.TemporaryPlayer; import com.google.common.base.Objects; import com.google.common.base.Preconditions; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimaps; import com.google.common.collect.SetMultimap; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.lang.ref.WeakReference; +import java.util.EventObject; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; /** - * Represents a packet sending or receiving event. Changes to the packet will be - * reflected in the final version to be sent or received. It is also possible to cancel an event. + * Represents a packet sending or receiving event. Changes to the packet will be reflected in the final version to be + * sent or received. It is also possible to cancel an event. + * * @author Kristian */ public class PacketEvent extends EventObject implements Cancellable { + public static final ReportType REPORT_CHANGING_PACKET_TYPE_IS_CONFUSING = new ReportType( "Plugin %s changed packet type from %s to %s in packet listener. This is confusing for other plugins! (Not an error, though!)"); - + private static final SetMultimap CHANGE_WARNINGS = Multimaps.synchronizedSetMultimap(HashMultimap.create()); - + /** * Automatically generated by Eclipse. */ private static final long serialVersionUID = -5360289379097430620L; - + // Network input and output handlers + NetworkMarker networkMarker; private transient WeakReference playerReference; - private PacketContainer packet; private boolean serverPacket; private boolean cancel; - private AsyncMarker asyncMarker; private boolean asynchronous; - - // Network input and output handlers - NetworkMarker networkMarker; - // Whether or not a packet event is read only private boolean readOnly; private boolean filtered; - + /** * Use the static constructors to create instances of this event. + * * @param source - the event source. */ public PacketEvent(Object source) { @@ -85,8 +81,9 @@ public PacketEvent(Object source) { private PacketEvent(Object source, PacketContainer packet, Player player, boolean serverPacket) { this(source, packet, null, player, serverPacket, true); } - - private PacketEvent(Object source, PacketContainer packet, NetworkMarker marker, Player player, boolean serverPacket, boolean filtered) { + + private PacketEvent(Object source, PacketContainer packet, NetworkMarker marker, Player player, boolean serverPacket, + boolean filtered) { super(source); this.packet = packet; this.playerReference = new WeakReference<>(player); @@ -94,7 +91,7 @@ private PacketEvent(Object source, PacketContainer packet, NetworkMarker marker, this.serverPacket = serverPacket; this.filtered = filtered; } - + private PacketEvent(PacketEvent origial, AsyncMarker asyncMarker) { super(origial.source); this.packet = origial.packet; @@ -109,6 +106,7 @@ private PacketEvent(PacketEvent origial, AsyncMarker asyncMarker) { /** * Creates an event representing a client packet transmission. + * * @param source - the event source. * @param packet - the packet. * @param client - the client that sent the packet. @@ -117,9 +115,10 @@ private PacketEvent(PacketEvent origial, AsyncMarker asyncMarker) { public static PacketEvent fromClient(Object source, PacketContainer packet, Player client) { return new PacketEvent(source, packet, client, false); } - + /** * Creates an event representing a client packet transmission. + * * @param source - the event source. * @param packet - the packet. * @param marker - the network marker. @@ -129,85 +128,94 @@ public static PacketEvent fromClient(Object source, PacketContainer packet, Play public static PacketEvent fromClient(Object source, PacketContainer packet, NetworkMarker marker, Player client) { return new PacketEvent(source, packet, marker, client, false, true); } - + /** * Creates an event representing a client packet transmission. *

* If filtered is FALSE, then this event is only processed by packet monitors. - * @param source - the event source. - * @param packet - the packet. - * @param marker - the network marker. - * @param client - the client that sent the packet. + * + * @param source - the event source. + * @param packet - the packet. + * @param marker - the network marker. + * @param client - the client that sent the packet. * @param filtered - whether or not this packet event is processed by every packet listener. * @return The event. */ - public static PacketEvent fromClient(Object source, PacketContainer packet, NetworkMarker marker, Player client, boolean filtered) { + public static PacketEvent fromClient(Object source, PacketContainer packet, NetworkMarker marker, Player client, + boolean filtered) { return new PacketEvent(source, packet, marker, client, false, filtered); } - + /** * Creates an event representing a server packet transmission. - * @param source - the event source. - * @param packet - the packet. + * + * @param source - the event source. + * @param packet - the packet. * @param recipient - the client that will receieve the packet. * @return The event. */ - public static PacketEvent fromServer(Object source, PacketContainer packet, Player recipient) { + public static PacketEvent fromServer(Object source, PacketContainer packet, Player recipient) { return new PacketEvent(source, packet, recipient, true); } - + /** * Creates an event representing a server packet transmission. - * @param source - the event source. - * @param packet - the packet. - * @param marker - the network marker. + * + * @param source - the event source. + * @param packet - the packet. + * @param marker - the network marker. * @param recipient - the client that will receieve the packet. * @return The event. */ public static PacketEvent fromServer(Object source, PacketContainer packet, NetworkMarker marker, Player recipient) { return new PacketEvent(source, packet, marker, recipient, true, true); } - + /** * Creates an event representing a server packet transmission. *

* If filtered is FALSE, then this event is only processed by packet monitors. - * @param source - the event source. - * @param packet - the packet. - * @param marker - the network marker. + * + * @param source - the event source. + * @param packet - the packet. + * @param marker - the network marker. * @param recipient - the client that will receieve the packet. - * @param filtered - whether or not this packet event is processed by every packet listener. + * @param filtered - whether or not this packet event is processed by every packet listener. * @return The event. */ - public static PacketEvent fromServer(Object source, PacketContainer packet, NetworkMarker marker, Player recipient, boolean filtered) { + public static PacketEvent fromServer(Object source, PacketContainer packet, NetworkMarker marker, Player recipient, + boolean filtered) { return new PacketEvent(source, packet, marker, recipient, true, filtered); } - + /** * Create an asynchronous packet event from a synchronous event and a async marker. - * @param event - the original synchronous event. + * + * @param event - the original synchronous event. * @param marker - the asynchronous marker. * @return The new packet event. */ public static PacketEvent fromSynchronous(PacketEvent event, AsyncMarker marker) { return new PacketEvent(event, marker); } - + /** * Determine if we are executing the packet event in an asynchronous thread. *

* If so, you must synchronize all calls to the Bukkit API. *

- * Generally, most server packets are executed on the main thread, whereas client packets - * are all executed asynchronously. + * Generally, most server packets are executed on the main thread, whereas client packets are all executed + * asynchronously. + * * @return TRUE if we are, FALSE otherwise. */ public boolean isAsync() { return !Bukkit.isPrimaryThread(); } - + /** * Retrieves the packet that will be sent to the player. + * * @return Packet to send to the player. */ public PacketContainer getPacket() { @@ -216,14 +224,17 @@ public PacketContainer getPacket() { /** * Replace the packet that will be sent to the player. + * * @param packet - the packet that will be sent instead. */ public void setPacket(PacketContainer packet) { - if (readOnly) + if (readOnly) { throw new IllegalStateException("The packet event is read-only."); - if (packet == null) + } + if (packet == null) { throw new IllegalArgumentException("Cannot set packet to NULL. Use setCancelled() instead."); - + } + // Change warnings final PacketType oldType = this.packet.getType(); final PacketType newType = packet.getType(); @@ -232,53 +243,77 @@ public void setPacket(PacketContainer packet) { if (CHANGE_WARNINGS.put(oldType, newType)) { ProtocolLibrary.getErrorReporter().reportWarning(this, Report.newBuilder(REPORT_CHANGING_PACKET_TYPE_IS_CONFUSING). - messageParam(PluginContext.getPluginCaller(new Exception()), oldType, newType). - build()); + messageParam(PluginContext.getPluginCaller(new Exception()), oldType, newType). + build()); } } this.packet = packet; } - + /** * Retrieves the packet ID. *

* Deprecated: Use {@link #getPacketType()} instead. + * * @return The current packet ID. */ @Deprecated public int getPacketID() { return packet.getId(); } - + /** * Retrieve the packet type. + * * @return The type. */ public PacketType getPacketType() { return packet.getType(); } - + /** * Retrieves whether or not the packet should be cancelled. + * * @return TRUE if it should be cancelled, FALSE otherwise. */ @Override public boolean isCancelled() { return cancel; } - + + /** + * Sets whether or not the packet should be cancelled. Uncancelling is possible. + *

+ * Warning: A cancelled packet should never be re-transmitted. Use the asynchronous + * packet manager if you need to perform extensive processing. It should also be used if you need to synchronize with + * the main thread. + *

+ * This ensures that other plugins can work with the same packet. + *

+ * An asynchronous listener can also delay a packet indefinitely without having to block its thread. + * + * @param cancel - TRUE if it should be cancelled, FALSE otherwise. + */ + @Override + public void setCancelled(boolean cancel) { + if (readOnly) { + throw new IllegalStateException("The packet event is read-only."); + } + this.cancel = cancel; + } + /** * Retrieve the object responsible for managing the serialized input and output of a packet. *

- * Note that the serialized input data is only available for client-side packets, and the output handlers - * can only be applied to server-side packets. + * Note that the serialized input data is only available for client-side packets, and the output handlers can only be + * applied to server-side packets. + * * @return The network manager. */ public NetworkMarker getNetworkMarker() { if (networkMarker == null) { if (isServerPacket()) { - networkMarker = new NetworkMarker.EmptyBufferMarker( - serverPacket ? ConnectionSide.SERVER_SIDE : ConnectionSide.CLIENT_SIDE); + networkMarker = new NetworkMarker(serverPacket ? ConnectionSide.SERVER_SIDE : ConnectionSide.CLIENT_SIDE, this.getPacketType()); } else { throw new IllegalStateException("Add the option ListenerOptions.INTERCEPT_INPUT_BUFFER to your listener."); } @@ -290,31 +325,12 @@ public NetworkMarker getNetworkMarker() { * Update the network manager. *

* This method is internal - do not call. + * * @param networkMarker - the new network manager. */ public void setNetworkMarker(NetworkMarker networkMarker) { this.networkMarker = Preconditions.checkNotNull(networkMarker, "marker cannot be NULL"); } - - /** - * Sets whether or not the packet should be cancelled. Uncancelling is possible. - *

- * Warning: A cancelled packet should never be re-transmitted. Use the asynchronous - * packet manager if you need to perform extensive processing. It should also be used - * if you need to synchronize with the main thread. - *

- * This ensures that other plugins can work with the same packet. - *

- * An asynchronous listener can also delay a packet indefinitely without having to block its thread. - * - * @param cancel - TRUE if it should be cancelled, FALSE otherwise. - */ - @Override - public void setCancelled(boolean cancel) { - if (readOnly) - throw new IllegalStateException("The packet event is read-only."); - this.cancel = cancel; - } private WeakReference getPlayerReference() { Player player = playerReference.get(); @@ -332,6 +348,7 @@ private WeakReference getPlayerReference() { /** * Retrieves the player that has sent the packet or is receiving it. + * * @return The player associated with this event. */ public Player getPlayer() { @@ -350,7 +367,7 @@ public Player getPlayer() { *

  • kickPlayer
  • *
  • isOnline
  • * - * + *

    * Anything else will throw an UnsupportedOperationException. Use this check before calling other methods when * dealing with packets early in the login sequence or if you get the aforementioned exception. * @@ -359,27 +376,29 @@ public Player getPlayer() { public boolean isPlayerTemporary() { return getPlayer() instanceof TemporaryPlayer; } - + /** * Determine if this packet is filtered by every packet listener. *

    * If not, it will only be intercepted by monitor packets. + * * @return TRUE if it is, FALSE otherwise. */ public boolean isFiltered() { return filtered; } - + /** * Whether or not this packet was created by the server. *

    * Most listeners can deduce this by noting which listener method was invoked. + * * @return TRUE if the packet was created by the server, FALSE if it was created by a client. */ public boolean isServerPacket() { return serverPacket; } - + /** * Retrieve the asynchronous marker. *

    @@ -387,54 +406,62 @@ public boolean isServerPacket() { * asynchronous event, the marker is used to correctly pass the packet around to the different threads. *

    * Note that if there are no asynchronous events that can receive this packet, the marker is NULL. + * * @return The current asynchronous marker, or NULL. */ public AsyncMarker getAsyncMarker() { return asyncMarker; } + /** * Set the asynchronous marker. *

    - * If the marker is non-null at the end of an synchronous event processing, the packet will be scheduled - * to be processed asynchronously with the given settings. + * If the marker is non-null at the end of an synchronous event processing, the packet will be scheduled to be + * processed asynchronously with the given settings. *

    * Note that if there are no asynchronous events that can receive this packet, the marker should be NULL. + * * @param asyncMarker - the new asynchronous marker, or NULL. * @throws IllegalStateException If the current event is asynchronous. */ public void setAsyncMarker(AsyncMarker asyncMarker) { - if (isAsynchronous()) + if (isAsynchronous()) { throw new IllegalStateException("The marker is immutable for asynchronous events"); - if (readOnly) + } + if (readOnly) { throw new IllegalStateException("The packet event is read-only."); + } this.asyncMarker = asyncMarker; } /** * Determine if the current packet event is read only. *

    - * This is used to ensure that a monitor listener doesn't accidentally alter the state of the event. However, - * it is still possible to modify the packet itself, as it would require too many resources to verify its integrity. + * This is used to ensure that a monitor listener doesn't accidentally alter the state of the event. However, it is + * still possible to modify the packet itself, as it would require too many resources to verify its integrity. *

    * Thus, the packet is considered immutable if the packet event is read only. + * * @return TRUE if it is, FALSE otherwise. */ public boolean isReadOnly() { return readOnly; } - + /** * Set the read-only state of this packet event. *

    * This will be reset for every packet listener. + * * @param readOnly - TRUE if it is read-only, FALSE otherwise. */ public void setReadOnly(boolean readOnly) { this.readOnly = readOnly; } - + /** * Determine if the packet event has been executed asynchronously or not. + * * @return TRUE if this packet event is asynchronous, FALSE otherwise. */ public boolean isAsynchronous() { @@ -445,14 +472,16 @@ public boolean isAsynchronous() { * Schedule a packet for sending or receiving after the current packet event is successful. *

    * The packet will be added to {@link NetworkMarker#getScheduledPackets()}. + * * @param scheduled - the packet to transmit or receive. */ public void schedule(ScheduledPacket scheduled) { getNetworkMarker().getScheduledPackets().add(scheduled); } - + /** * Unschedule a specific packet. + * * @param scheduled - the scheduled packet. * @return TRUE if it was unscheduled, FALSE otherwise. */ @@ -462,9 +491,9 @@ public boolean unschedule(ScheduledPacket scheduled) { } return false; } - + private void writeObject(ObjectOutputStream output) throws IOException { - // Default serialization + // Default serialization output.defaultWriteObject(); // Write the name of the player (or NULL if it's not set) @@ -473,17 +502,17 @@ private void writeObject(ObjectOutputStream output) throws IOException { } private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException { - // Default deserialization + // Default deserialization input.defaultReadObject(); final SerializedOfflinePlayer serialized = (SerializedOfflinePlayer) input.readObject(); - + // Better than nothing - if (serialized != null) { - // Store it, to prevent weak reference from cleaning up the reference - Player offlinePlayer = serialized.getPlayer(); - playerReference = new WeakReference<>(offlinePlayer); - } + if (serialized != null) { + // Store it, to prevent weak reference from cleaning up the reference + Player offlinePlayer = serialized.getPlayer(); + playerReference = new WeakReference<>(offlinePlayer); + } } @Override diff --git a/src/main/java/com/comphenix/protocol/events/ScheduledPacket.java b/src/main/java/com/comphenix/protocol/events/ScheduledPacket.java index cb4ddf94e..46fdc5895 100644 --- a/src/main/java/com/comphenix/protocol/events/ScheduledPacket.java +++ b/src/main/java/com/comphenix/protocol/events/ScheduledPacket.java @@ -1,7 +1,5 @@ package com.comphenix.protocol.events; -import java.lang.reflect.InvocationTargetException; - import org.bukkit.entity.Player; import com.comphenix.protocol.PacketStream; @@ -121,17 +119,11 @@ public void schedule() { */ public void schedule(PacketStream stream) { Preconditions.checkNotNull(stream, "stream cannot be NULL"); - - try { - if (getSender() == Sender.CLIENT) { - stream.recieveClientPacket(getTarget(), getPacket(), isFiltered()); - } else { - stream.sendServerPacket(getTarget(), getPacket(), isFiltered()); - } - } catch (InvocationTargetException e) { - throw new RuntimeException("Cannot send packet " + this + " to " + stream); - } catch (IllegalAccessException e) { - throw new RuntimeException("Cannot send packet " + this + " to " + stream); + + if (getSender() == Sender.CLIENT) { + stream.receiveClientPacket(getTarget(), getPacket(), isFiltered()); + } else { + stream.sendServerPacket(getTarget(), getPacket(), isFiltered()); } } diff --git a/src/main/java/com/comphenix/protocol/injector/BukkitUnwrapper.java b/src/main/java/com/comphenix/protocol/injector/BukkitUnwrapper.java index e32566220..440f91a7d 100644 --- a/src/main/java/com/comphenix/protocol/injector/BukkitUnwrapper.java +++ b/src/main/java/com/comphenix/protocol/injector/BukkitUnwrapper.java @@ -17,16 +17,6 @@ package com.comphenix.protocol.injector; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Collection; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.Report; @@ -36,6 +26,14 @@ import com.comphenix.protocol.reflect.instances.DefaultInstances; import com.comphenix.protocol.utility.MinecraftReflection; import com.google.common.primitives.Primitives; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; /** * Represents an object capable of converting wrapped Bukkit objects into NMS objects. @@ -45,123 +43,136 @@ *

  • org.bukkit.entity.Player to net.minecraft.server.EntityPlayer
  • *
  • org.bukkit.World to net.minecraft.server.WorldServer
  • * - * + * * @author Kristian */ public class BukkitUnwrapper implements Unwrapper { - private static BukkitUnwrapper DEFAULT; public static final ReportType REPORT_ILLEGAL_ARGUMENT = new ReportType("Illegal argument."); public static final ReportType REPORT_SECURITY_LIMITATION = new ReportType("Security limitation."); public static final ReportType REPORT_CANNOT_FIND_UNWRAP_METHOD = new ReportType("Cannot find method."); - public static final ReportType REPORT_CANNOT_READ_FIELD_HANDLE = new ReportType("Cannot read field 'handle'."); - - private static Map, Unwrapper> unwrapperCache = new ConcurrentHashMap, Unwrapper>(); - + + private static final Map, Unwrapper> UNWRAPPER_CACHE = new ConcurrentHashMap, Unwrapper>(); + private static BukkitUnwrapper DEFAULT; + // The current error reporter private final ErrorReporter reporter; - + + /** + * Construct a new Bukkit unwrapper with ProtocolLib's default error reporter. + */ + public BukkitUnwrapper() { + this(ProtocolLibrary.getErrorReporter()); + } + + /** + * Construct a new Bukkit unwrapper with the given error reporter. + * + * @param reporter - the error reporter to use. + */ + public BukkitUnwrapper(ErrorReporter reporter) { + this.reporter = reporter; + } + /** * Retrieve the default instance of the Bukkit unwrapper. + * * @return The default instance. */ public static BukkitUnwrapper getInstance() { ErrorReporter currentReporter = ProtocolLibrary.getErrorReporter(); - + // Also recreate the unwrapper if the error reporter has changed if (DEFAULT == null || DEFAULT.reporter != currentReporter) { DEFAULT = new BukkitUnwrapper(currentReporter); } return DEFAULT; } - - /** - * Construct a new Bukkit unwrapper with ProtocolLib's default error reporter. - */ - public BukkitUnwrapper() { - this(ProtocolLibrary.getErrorReporter()); - } - - /** - * Construct a new Bukkit unwrapper with the given error reporter. - * @param reporter - the error reporter to use. - */ - public BukkitUnwrapper(ErrorReporter reporter) { - this.reporter = reporter; + + private static Class checkClass(Class input, Class expected, Class result) { + if (expected.isAssignableFrom(input)) { + return result; + } + return null; } - + @SuppressWarnings("unchecked") @Override public Object unwrapItem(Object wrappedObject) { // Special case - if (wrappedObject == null) + if (wrappedObject == null) { return null; + } Class currentClass = PacketConstructor.getClass(wrappedObject); - + // No need to unwrap primitives - if (currentClass.isPrimitive() || currentClass.equals(String.class)) + if (currentClass.isPrimitive() || currentClass.equals(String.class)) { return null; - + } + // Next, check for types that doesn't have a getHandle() if (wrappedObject instanceof Collection) { return handleCollection((Collection) wrappedObject); } else if (Primitives.isWrapperType(currentClass) || wrappedObject instanceof String) { return null; } - + Unwrapper specificUnwrapper = getSpecificUnwrapper(currentClass); - + // Retrieve the handle - if (specificUnwrapper != null) + if (specificUnwrapper != null) { return specificUnwrapper.unwrapItem(wrappedObject); - else + } else { return null; + } } - + // Handle a collection of items private Object handleCollection(Collection wrappedObject) { - + @SuppressWarnings("unchecked") Collection copy = DefaultInstances.DEFAULT.getDefault(wrappedObject.getClass()); - + if (copy != null) { // Unwrap every element for (Object element : wrappedObject) { copy.add(unwrapItem(element)); } return copy; - + } else { // Impossible return null; } } - + /** * Retrieve a cached class unwrapper for the given class. + * * @param type - the type of the class. * @return An unwrapper for the given class. */ private Unwrapper getSpecificUnwrapper(final Class type) { // See if we're already determined this - if (unwrapperCache.containsKey(type)) { + if (UNWRAPPER_CACHE.containsKey(type)) { // We will never remove from the cache, so this ought to be thread safe - return unwrapperCache.get(type); + return UNWRAPPER_CACHE.get(type); } - + try { final Method find = type.getMethod("getHandle"); - + // It's thread safe, as getMethod should return the same handle Unwrapper methodUnwrapper = new Unwrapper() { @Override public Object unwrapItem(Object wrappedObject) { try { - if (wrappedObject instanceof Class) + if (wrappedObject instanceof Class) { return checkClass((Class) wrappedObject, type, find.getReturnType()); + } return find.invoke(wrappedObject); - + } catch (IllegalArgumentException e) { reporter.reportDetailed(this, Report.newBuilder(REPORT_ILLEGAL_ARGUMENT).error(e).callerParam(wrappedObject, find) @@ -173,14 +184,14 @@ public Object unwrapItem(Object wrappedObject) { // This is really bad throw new RuntimeException("Minecraft error.", e); } - + return null; } }; - - unwrapperCache.put(type, methodUnwrapper); + + UNWRAPPER_CACHE.put(type, methodUnwrapper); return methodUnwrapper; - + } catch (SecurityException e) { reporter.reportDetailed(this, Report.newBuilder(REPORT_SECURITY_LIMITATION).error(e).callerParam(type) @@ -188,16 +199,18 @@ public Object unwrapItem(Object wrappedObject) { } catch (NoSuchMethodException e) { // Maybe it's a proxy? Unwrapper proxyUnwrapper = getProxyUnwrapper(type); - if (proxyUnwrapper != null) + if (proxyUnwrapper != null) { return proxyUnwrapper; + } // Try getting the field unwrapper too Unwrapper fieldUnwrapper = getFieldUnwrapper(type); - if (fieldUnwrapper != null) + if (fieldUnwrapper != null) { return fieldUnwrapper; - else + } else { reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_FIND_UNWRAP_METHOD).error(e).callerParam(type)); + } } // Default method @@ -226,7 +239,7 @@ public Object unwrapItem(Object wrapped) { } }; - unwrapperCache.put(type, unwrapper); + UNWRAPPER_CACHE.put(type, unwrapper); return unwrapper; } } catch (Throwable ignored) { @@ -237,20 +250,22 @@ public Object unwrapItem(Object wrapped) { /** * Retrieve a cached unwrapper using the handle field. + * * @param type - a cached field unwrapper. * @return The cached field unwrapper. */ private Unwrapper getFieldUnwrapper(final Class type) { final Field find = FieldUtils.getField(type, "handle", true); - + // See if we succeeded if (find != null) { Unwrapper fieldUnwrapper = new Unwrapper() { @Override public Object unwrapItem(Object wrappedObject) { try { - if (wrappedObject instanceof Class) + if (wrappedObject instanceof Class) { return checkClass((Class) wrappedObject, type, find.getType()); + } return FieldUtils.readField(find, wrappedObject, true); } catch (IllegalAccessException e) { reporter.reportDetailed(this, @@ -260,10 +275,10 @@ public Object unwrapItem(Object wrappedObject) { } } }; - - unwrapperCache.put(type, fieldUnwrapper); + + UNWRAPPER_CACHE.put(type, fieldUnwrapper); return fieldUnwrapper; - + } else { // Inform about this too reporter.reportDetailed(this, @@ -272,11 +287,4 @@ public Object unwrapItem(Object wrappedObject) { return null; } } - - private static Class checkClass(Class input, Class expected, Class result) { - if (expected.isAssignableFrom(input)) { - return result; - } - return null; - } } diff --git a/src/main/java/com/comphenix/protocol/injector/DelayedSingleTask.java b/src/main/java/com/comphenix/protocol/injector/DelayedSingleTask.java deleted file mode 100644 index 41242067b..000000000 --- a/src/main/java/com/comphenix/protocol/injector/DelayedSingleTask.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * 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 org.bukkit.plugin.Plugin; -import org.bukkit.scheduler.BukkitScheduler; - -/** - * Represents a single delayed task. - * - * @author Kristian - */ -public class DelayedSingleTask { - - protected int taskID = -1; - protected Plugin plugin; - protected BukkitScheduler scheduler; - protected boolean closed; - - /** - * Create a single task scheduler. - * @param plugin - owner plugin. - */ - public DelayedSingleTask(Plugin plugin) { - this.plugin = plugin; - this.scheduler = plugin.getServer().getScheduler(); - } - - /** - * Create a single task scheduler. - * @param plugin - owner plugin. - * @param scheduler - specialized scheduler. - */ - public DelayedSingleTask(Plugin plugin, BukkitScheduler scheduler) { - this.plugin = plugin; - this.scheduler = scheduler; - } - - /** - * Schedule a single task for execution. - *

    - * Any previously scheduled task will be automatically cancelled. - *

    - * Note that a tick delay of zero will execute the task immediately. - * - * @param ticksDelay - number of ticks before the task is executed. - * @param task - the task to schedule. - * @return TRUE if the task was successfully scheduled or executed, FALSE otherwise. - */ - public boolean schedule(long ticksDelay, Runnable task) { - if (ticksDelay < 0) - throw new IllegalArgumentException("Tick delay cannot be negative."); - if (task == null) - throw new IllegalArgumentException("task cannot be NULL"); - if (closed) - return false; - - // Special case - if (ticksDelay == 0) { - task.run(); - return true; - } - - // Boilerplate, boilerplate - final Runnable dispatch = task; - - // Don't run multiple tasks! - cancel(); - taskID = scheduler.scheduleSyncDelayedTask(plugin, new Runnable() { - @Override - public void run() { - dispatch.run(); - taskID = -1; - } - }, ticksDelay); - - return isRunning(); - } - - /** - * Whether or not a future task is scheduled to be executed. - * @return TRUE if a current task has been scheduled for execution, FALSE otherwise. - */ - public boolean isRunning() { - return taskID >= 0; - } - - /** - * Cancel a future task from being executed. - * @return TRUE if a task was cancelled, FALSE otherwise. - */ - public boolean cancel() { - if (isRunning()) { - scheduler.cancelTask(taskID); - taskID = -1; - return true; - } else { - return false; - } - } - - /** - * Retrieve the raw task ID. - * @return Raw task ID, or negative one if no task has been scheduled. - */ - public int getTaskID() { - return taskID; - } - - /** - * Retrieve the plugin this task belongs to. - * @return The plugin scheduling the current taks. - */ - public Plugin getPlugin() { - return plugin; - } - - /** - * Stop the current task and all future tasks scheduled by this instance. - */ - public synchronized void close() { - if (!closed) { - cancel(); - plugin = null; - scheduler = null; - closed = true; - } - } - - @Override - protected void finalize() throws Throwable { - close(); - } -} diff --git a/src/main/java/com/comphenix/protocol/injector/EntityUtilities.java b/src/main/java/com/comphenix/protocol/injector/EntityUtilities.java index e3f2fde68..ddcf669ee 100644 --- a/src/main/java/com/comphenix/protocol/injector/EntityUtilities.java +++ b/src/main/java/com/comphenix/protocol/injector/EntityUtilities.java @@ -17,10 +17,6 @@ package com.comphenix.protocol.injector; -import java.lang.reflect.Modifier; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; - import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.accessors.Accessors; @@ -33,7 +29,12 @@ import com.comphenix.protocol.utility.MinecraftVersion; import com.comphenix.protocol.wrappers.WrappedIntHashMap; import com.google.common.collect.Lists; - +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import org.apache.commons.lang.Validate; import org.bukkit.World; import org.bukkit.entity.Entity; @@ -45,38 +46,42 @@ * @author Kristian */ class EntityUtilities { - private static final boolean NEW_TRACKER = MinecraftVersion.VILLAGE_UPDATE.atOrAbove(); - private static final EntityUtilities INSTANCE = new EntityUtilities(); - public static EntityUtilities getInstance() { - return INSTANCE; - } + private static final EntityUtilities INSTANCE = new EntityUtilities(); + private static final boolean NEW_TRACKER = MinecraftVersion.VILLAGE_UPDATE.atOrAbove(); - private EntityUtilities() { } + private final Map, MethodAccessor> scanPlayersMethods = new HashMap<>(); + private FieldAccessor chunkMapField; private FieldAccessor entityTrackerField; - private FieldAccessor trackedEntitiesField; private FieldAccessor trackedPlayersField; + private FieldAccessor trackedEntitiesField; + + private MethodAccessor getChunkProvider; - private Map, MethodAccessor> scanPlayersMethods = new HashMap<>(); + private EntityUtilities() { + } + + public static EntityUtilities getInstance() { + return INSTANCE; + } public void updateEntity(Entity entity, List observers) { if (entity == null || !entity.isValid()) { return; } - Collection trackedPlayers = getTrackedPlayers(entity); - List nmsPlayers = unwrapBukkit(observers); - - List removingEntries = MinecraftVersion.CAVES_CLIFFS_1.atOrAbove() ? - getPlayerConnections(nmsPlayers) : nmsPlayers; + Collection trackedPlayers = this.getTrackedPlayers(entity); + List nmsPlayers = this.unwrapBukkit(observers); + List removingEntries = + MinecraftVersion.CAVES_CLIFFS_1.atOrAbove() ? this.getPlayerConnections(nmsPlayers) : nmsPlayers; trackedPlayers.removeAll(removingEntries); - Object trackerEntry = getEntityTrackerEntry(entity.getWorld(), entity.getEntityId()); - // there can be multiple different entity tracker entry impls, see GH-732.... - scanPlayersMethods.computeIfAbsent(trackerEntry.getClass(), this::findScanPlayers).invoke(trackerEntry, nmsPlayers); + Object trackerEntry = this.getEntityTrackerEntry(entity.getWorld(), entity.getEntityId()); + this.scanPlayersMethods.computeIfAbsent(trackerEntry.getClass(), this::findScanPlayers) + .invoke(trackerEntry, nmsPlayers); } private MethodAccessor findScanPlayers(Class trackerClass) { @@ -86,13 +91,15 @@ private MethodAccessor findScanPlayers(Class trackerClass) { } FuzzyReflection fuzzy = FuzzyReflection.fromClass(trackerClass, true); - return Accessors.getMethodAccessor( - fuzzy.getMethod( - FuzzyMethodContract.newBuilder().returnTypeVoid().parameterExactArray(List.class).build())); + return Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract.newBuilder() + .returnTypeVoid() + .parameterExactArray(List.class) + .build())); } /** * Retrieve every client that is receiving information about a given entity. + * * @param entity - the entity that is being tracked. * @return Every client/player that is tracking the given entity. * @throws FieldAccessException If reflection failed. @@ -103,7 +110,7 @@ public List getEntityTrackers(Entity entity) { } List result = new ArrayList<>(); - Collection trackedPlayers = getTrackedPlayers(entity); + Collection trackedPlayers = this.getTrackedPlayers(entity); // Wrap every player - we also ensure that the underlying tracker list is immutable for (Object tracker : trackedPlayers) { @@ -120,68 +127,63 @@ public List getEntityTrackers(Entity entity) { private Collection getTrackedPlayers(Entity entity) { Validate.notNull(entity, "entity cannot be null"); - Object trackerEntry = getEntityTrackerEntry(entity.getWorld(), entity.getEntityId()); + Object trackerEntry = this.getEntityTrackerEntry(entity.getWorld(), entity.getEntityId()); Validate.notNull(trackerEntry, "Could not find entity trackers for " + entity); - if (trackedPlayersField == null) { - trackedPlayersField = Accessors.getFieldAccessor(FuzzyReflection.fromObject(trackerEntry).getFieldByType("java\\.util\\..*")); + if (this.trackedPlayersField == null) { + this.trackedPlayersField = Accessors.getFieldAccessor( + FuzzyReflection.fromObject(trackerEntry).getFieldByType("java\\.util\\..*")); } - Validate.notNull(trackedPlayersField, "Could not find trackedPlayers field"); + Validate.notNull(this.trackedPlayersField, "Could not find trackedPlayers field"); - Object value = trackedPlayersField.get(trackerEntry); + Object value = this.trackedPlayersField.get(trackerEntry); if (value instanceof Collection) { return (Collection) value; } else if (value instanceof Map) { return ((Map) value).keySet(); } else { // Please. No more changes. - throw new IllegalStateException("trackedPlayers field was an unknown type: expected Collection or Map, but got " + value.getClass()); + throw new IllegalStateException( + "trackedPlayers field was an unknown type: expected Collection or Map, but got " + value.getClass()); } } - private MethodAccessor getChunkProvider; - private FieldAccessor chunkMapField; - @SuppressWarnings("unchecked") private Object getNewEntityTracker(Object worldServer, int entityId) { - if (getChunkProvider == null) { + if (this.getChunkProvider == null) { Class chunkProviderClass = MinecraftReflection.getChunkProviderServer(); - getChunkProvider = Accessors.getMethodAccessor( - FuzzyReflection.fromClass(worldServer.getClass(), false).getMethod( - FuzzyMethodContract.newBuilder().parameterCount(0).returnTypeExact(chunkProviderClass).build())); + this.getChunkProvider = Accessors.getMethodAccessor(FuzzyReflection.fromClass(worldServer.getClass(), false) + .getMethod(FuzzyMethodContract.newBuilder().parameterCount(0).returnTypeExact(chunkProviderClass).build())); } - Object chunkProvider = getChunkProvider.invoke(worldServer); + Object chunkProvider = this.getChunkProvider.invoke(worldServer); - if (chunkMapField == null) { + if (this.chunkMapField == null) { Class chunkMapClass = MinecraftReflection.getPlayerChunkMap(); - chunkMapField = Accessors.getFieldAccessor( - FuzzyReflection.fromClass(chunkProvider.getClass(), false).getField( - FuzzyFieldContract.newBuilder().typeExact(chunkMapClass).build())); + this.chunkMapField = Accessors.getFieldAccessor(FuzzyReflection.fromClass(chunkProvider.getClass(), false) + .getField(FuzzyFieldContract.newBuilder().typeExact(chunkMapClass).build())); } - Object playerChunkMap = chunkMapField.get(chunkProvider); + Object playerChunkMap = this.chunkMapField.get(chunkProvider); - if (trackedEntitiesField == null) { + if (this.trackedEntitiesField == null) { if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) { - trackedEntitiesField = Accessors.getFieldAccessor( - FuzzyReflection.fromClass(playerChunkMap.getClass(), true).getField( - FuzzyFieldContract.newBuilder() - .banModifier(Modifier.STATIC) - .requirePublic() - .typeExact(MinecraftReflection.getInt2ObjectMapClass()) - .build() - ) - ); + this.trackedEntitiesField = Accessors.getFieldAccessor( + FuzzyReflection.fromClass(playerChunkMap.getClass(), true) + .getField(FuzzyFieldContract.newBuilder() + .banModifier(Modifier.STATIC) + .requirePublic() + .typeExact(MinecraftReflection.getInt2ObjectMapClass()) + .build())); } else { - trackedEntitiesField = Accessors.getFieldAccessor( - FuzzyReflection.fromClass(playerChunkMap.getClass(), false).getField( - FuzzyFieldContract.newBuilder().typeDerivedOf(Map.class).nameExact("trackedEntities").build())); + this.trackedEntitiesField = Accessors.getFieldAccessor( + FuzzyReflection.fromClass(playerChunkMap.getClass(), false).getField( + FuzzyFieldContract.newBuilder().typeDerivedOf(Map.class).nameExact("trackedEntities").build())); } } - Map trackedEntities = (Map) trackedEntitiesField.get(playerChunkMap); + Map trackedEntities = (Map) this.trackedEntitiesField.get(playerChunkMap); return trackedEntities.get(entityId); } @@ -190,88 +192,28 @@ private Object getEntityTrackerEntry(World world, int entityID) { Object worldServer = unwrapper.unwrapItem(world); if (NEW_TRACKER) { - return getNewEntityTracker(worldServer, entityID); + return this.getNewEntityTracker(worldServer, entityID); } - if (entityTrackerField == null) - entityTrackerField = Accessors.getFieldAccessor(FuzzyReflection.fromObject(worldServer). - getFieldByType("tracker", MinecraftReflection.getEntityTrackerClass())); + if (this.entityTrackerField == null) { + this.entityTrackerField = Accessors.getFieldAccessor(FuzzyReflection.fromObject(worldServer). + getFieldByType("tracker", MinecraftReflection.getEntityTrackerClass())); + } // Get the tracker - Object tracker = entityTrackerField.get(worldServer); + Object tracker = this.entityTrackerField.get(worldServer); // Looking for an IntHashMap in the tracker entry - if (trackedEntitiesField == null) { - trackedEntitiesField = Accessors.getFieldAccessor(FuzzyReflection.fromObject(tracker, false) + if (this.trackedEntitiesField == null) { + this.trackedEntitiesField = Accessors.getFieldAccessor(FuzzyReflection.fromObject(tracker, false) .getFieldByType("trackedEntities", MinecraftReflection.getIntHashMapClass())); } // Read the map - Object trackedEntities = trackedEntitiesField.get(tracker); + Object trackedEntities = this.trackedEntitiesField.get(tracker); return WrappedIntHashMap.fromHandle(trackedEntities).get(entityID); } - private Map, FieldAccessor> trackerFields = new ConcurrentHashMap<>(); - private MethodAccessor getEntityFromId; - - /** - * Retrieve entity from a ID, even it it's newly created. - * @return The associated entity. - * @throws FieldAccessException Reflection error. - */ - public Entity getEntityFromID(World world, int entityID) { - Validate.notNull(world, "world cannot be null"); - Validate.isTrue(entityID >= 0, "entityID cannot be negative"); - - try { - // first, try to read from the world - // this should be good enough for most cases, but only exists in 1.14+ - if (NEW_TRACKER) { - Object worldServer = BukkitUnwrapper.getInstance().unwrapItem(world); - - if (getEntityFromId == null) { - FuzzyReflection fuzzy = FuzzyReflection.fromClass(worldServer.getClass(), false); - getEntityFromId = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract.newBuilder() - .parameterExactArray(int.class) - .returnTypeExact(MinecraftReflection.getEntityClass()) - .build())); - } - - Object entity = getEntityFromId.invoke(worldServer, entityID); - if (entity != null) { - return (Entity) MinecraftReflection.getBukkitEntity(entity); - } - } - - // then go into the trackers - Object trackerEntry = getEntityTrackerEntry(world, entityID); - Object tracker = null; - - if (trackerEntry != null) { - // plugins like citizens will use their own tracker class, so cache the result - FieldAccessor trackerField = trackerFields.computeIfAbsent(trackerEntry.getClass(), x -> { - // get the first entity field - try { - return Accessors.getFieldAccessor(FuzzyReflection.fromClass(trackerEntry.getClass(), true) - .getField(FuzzyFieldContract.newBuilder().typeExact(MinecraftReflection.getEntityClass()).build())); - } catch (Exception ex) { - // try with the default class - Class trackerEntryClass = MinecraftReflection.getEntityTrackerClass(); - return Accessors.getFieldAccessor(FuzzyReflection.fromClass(trackerEntryClass, true) - .getField(FuzzyFieldContract.newBuilder().typeExact(MinecraftReflection.getEntityClass()).build())); - } - }); - - tracker = trackerField.get(trackerEntry); - } - - // If the tracker is NULL, we'll just assume this entity doesn't exist - return tracker != null ? (Entity) MinecraftReflection.getBukkitEntity(tracker) : null; - } catch (Exception e) { - throw new FieldAccessException("Cannot find entity from ID " + entityID + ".", e); - } - } - private List getPlayerConnections(List nmsPlayers) { List connections = new ArrayList<>(nmsPlayers.size()); nmsPlayers.forEach(nmsPlayer -> connections.add(MinecraftFields.getPlayerConnection(nmsPlayer))); @@ -286,10 +228,11 @@ private List unwrapBukkit(List players) { for (Player player : players) { Object result = unwrapper.unwrapItem(player); - if (result != null) + if (result != null) { output.add(result); - else + } else { throw new IllegalArgumentException("Cannot unwrap item " + player); + } } return output; diff --git a/src/main/java/com/comphenix/protocol/injector/GamePhase.java b/src/main/java/com/comphenix/protocol/injector/GamePhase.java index 399b3350a..1d38bfafa 100644 --- a/src/main/java/com/comphenix/protocol/injector/GamePhase.java +++ b/src/main/java/com/comphenix/protocol/injector/GamePhase.java @@ -2,16 +2,16 @@ * 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 + * 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. + * 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 + * 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 */ @@ -19,7 +19,7 @@ /** * The current player phase. This is used to limit the number of different injections. - * + * * @author Kristian */ public enum GamePhase { @@ -27,27 +27,29 @@ public enum GamePhase { * Only listen for packets sent or received before a player has logged in. */ LOGIN, - + /** * Only listen for packets sent or received after a player has logged in. */ PLAYING, - + /** * Listen for every sent and received packet. */ BOTH; - + /** * Determine if the current value represents the login phase. + * * @return TRUE if it does, FALSE otherwise. */ public boolean hasLogin() { return this == LOGIN || this == BOTH; } - + /** * Determine if the current value represents the playing phase. + * * @return TRUE if it does, FALSE otherwise. */ public boolean hasPlaying() { diff --git a/src/main/java/com/comphenix/protocol/injector/InternalManager.java b/src/main/java/com/comphenix/protocol/injector/InternalManager.java index 15f67b7d4..5046882bf 100644 --- a/src/main/java/com/comphenix/protocol/injector/InternalManager.java +++ b/src/main/java/com/comphenix/protocol/injector/InternalManager.java @@ -1,49 +1,40 @@ package com.comphenix.protocol.injector; +import com.comphenix.protocol.ProtocolManager; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginManager; -import com.comphenix.protocol.ProtocolManager; - /** * 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. + * @param plugin - the parent plugin. */ - public void registerEvents(PluginManager manager, final Plugin plugin); - + void registerEvents(PluginManager manager, final Plugin plugin); + /** * Called when ProtocolLib is closing. */ - public void close(); + void close(); /** * Determine if debug mode is enabled. + * * @return TRUE if it is, FALSE otherwise. */ - public boolean isDebug(); - + boolean isDebug(); + /** * Set whether or not debug mode is enabled. + * * @param debug - TRUE if it is, FALSE otherwise. */ - public void setDebug(boolean debug); + void setDebug(boolean debug); } diff --git a/src/main/java/com/comphenix/protocol/injector/ListenerInvoker.java b/src/main/java/com/comphenix/protocol/injector/ListenerInvoker.java index 3aecb5172..a11be58e7 100644 --- a/src/main/java/com/comphenix/protocol/injector/ListenerInvoker.java +++ b/src/main/java/com/comphenix/protocol/injector/ListenerInvoker.java @@ -2,16 +2,16 @@ * 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 + * 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. + * 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 + * 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 */ @@ -22,25 +22,28 @@ /** * 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. */ - void invokePacketRecieving(PacketEvent event); + void invokePacketReceiving(PacketEvent event); /** * Invokes the given packet event for every registered listener. + * * @param event - the packet event to invoke. */ void invokePacketSending(PacketEvent event); /** * Retrieve the associated type of a packet. + * * @param packet - the packet. * @return The packet type. */ diff --git a/src/main/java/com/comphenix/protocol/injector/NetworkProcessor.java b/src/main/java/com/comphenix/protocol/injector/NetworkProcessor.java index fbd033e35..772111074 100644 --- a/src/main/java/com/comphenix/protocol/injector/NetworkProcessor.java +++ b/src/main/java/com/comphenix/protocol/injector/NetworkProcessor.java @@ -1,108 +1,73 @@ package com.comphenix.protocol.injector; -import java.util.List; -import java.util.PriorityQueue; - import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.ProtocolManager; import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.events.NetworkMarker; import com.comphenix.protocol.events.PacketEvent; -import com.comphenix.protocol.events.PacketOutputHandler; import com.comphenix.protocol.events.PacketPostListener; import com.comphenix.protocol.events.ScheduledPacket; +import java.util.Set; /** * Represents a processor for network markers. + * * @author Kristian */ public class NetworkProcessor { - private ErrorReporter reporter; - + + private final ErrorReporter reporter; + /** * Construct a new network processor. + * * @param reporter - the reporter. */ public NetworkProcessor(ErrorReporter reporter) { this.reporter = reporter; } - - /** - * Process the serialized packet byte array with the given network marker. - * @param event - current packet event. - * @param marker - the network marker. - * @param input - the input array. - * @return The output array. - */ - public byte[] processOutput(PacketEvent event, NetworkMarker marker, final byte[] input) { - // Bit of a hack - but we need the performance - PriorityQueue handlers = (PriorityQueue) - marker.getOutputHandlers(); - byte[] output = input; - - // Let each handler prepare the actual output - while (!handlers.isEmpty()) { - PacketOutputHandler handler = handlers.poll(); - - try { - byte[] changed = handler.handle(event, output); - - // Don't break just because a plugin returned NULL - if (changed != null) { - output = changed; - } else { - throw new IllegalStateException("Handler cannot return a NULL array."); - } - } catch (OutOfMemoryError e) { - throw e; - } catch (ThreadDeath e) { - throw e; - } catch (Throwable e) { - reporter.reportMinimal(handler.getPlugin(), "PacketOutputHandler.handle()", e); - } - } - return output; - } /** * Invoke the post listeners and packet transmission, if any. - * @param event - PacketEvent + * + * @param event - PacketEvent * @param marker - the network marker, or NULL. */ public void invokePostEvent(PacketEvent event, NetworkMarker marker) { - if (marker == null) + if (marker == null) { return; - - if (NetworkMarker.hasPostListeners(marker)) { + } + + if (event != null && NetworkMarker.hasPostListeners(marker)) { // Invoke every sent listener for (PacketPostListener listener : marker.getPostListeners()) { try { listener.onPostEvent(event); - } catch (OutOfMemoryError e) { - throw e; - } catch (ThreadDeath e) { + } catch (OutOfMemoryError | ThreadDeath e) { throw e; } catch (Throwable e) { - reporter.reportMinimal(listener.getPlugin(), "SentListener.run()", e); + this.reporter.reportMinimal(listener.getPlugin(), "SentListener.run()", e); } } } - sendScheduledPackets(marker); + + this.sendScheduledPackets(marker); } - + /** * Send any scheduled packets. + * * @param marker - the network marker. */ private void sendScheduledPackets(NetworkMarker marker) { // Next, invoke post packet transmission - List scheduled = NetworkMarker.readScheduledPackets(marker); + Set scheduled = NetworkMarker.readScheduledPackets(marker); ProtocolManager manager = ProtocolLibrary.getProtocolManager(); - + if (scheduled != null) { for (ScheduledPacket packet : scheduled) { packet.schedule(manager); } } } - } +} diff --git a/src/main/java/com/comphenix/protocol/injector/PacketConstructor.java b/src/main/java/com/comphenix/protocol/injector/PacketConstructor.java index 9e76d4e2f..275c7c9db 100644 --- a/src/main/java/com/comphenix/protocol/injector/PacketConstructor.java +++ b/src/main/java/com/comphenix/protocol/injector/PacketConstructor.java @@ -17,11 +17,6 @@ package com.comphenix.protocol.injector; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.util.List; -import java.util.Optional; - import com.comphenix.protocol.PacketType; import com.comphenix.protocol.error.RethrowErrorReporter; import com.comphenix.protocol.events.PacketContainer; @@ -31,70 +26,117 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.primitives.Primitives; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.List; /** * A packet constructor that uses an internal Minecraft. - * @author Kristian * + * @author Kristian */ public class PacketConstructor { + /** * A packet constructor that automatically converts Bukkit types to their NMS conterpart. *

    * Remember to call withPacket(). */ public static PacketConstructor DEFAULT = new PacketConstructor(null); - + // The constructor method that's actually responsible for creating the packet - private Constructor constructorMethod; - + private final Constructor constructorMethod; + // Used to unwrap Bukkit objects + private final List unwrappers; + // The packet ID private PacketType type; - - // Used to unwrap Bukkit objects - private List unwrappers; - // Parameters that need to be unwrapped private Unwrapper[] paramUnwrapper; - + private PacketConstructor(Constructor constructorMethod) { this.constructorMethod = constructorMethod; - this.unwrappers = Lists.newArrayList((Unwrapper) new BukkitUnwrapper(new RethrowErrorReporter() )); + this.unwrappers = Lists.newArrayList((Unwrapper) new BukkitUnwrapper(new RethrowErrorReporter())); this.unwrappers.addAll(BukkitConverters.getUnwrappers()); } - - private PacketConstructor(PacketType type, Constructor constructorMethod, List unwrappers, Unwrapper[] paramUnwrapper) { + + private PacketConstructor(PacketType type, Constructor constructorMethod, List unwrappers, + Unwrapper[] paramUnwrapper) { this.type = type; this.constructorMethod = constructorMethod; this.unwrappers = unwrappers; this.paramUnwrapper = paramUnwrapper; } - + + // Determine if a method with the types 'params' can be called with 'types' + private static boolean isCompatible(Class[] types, Class[] params) { + + // Determine if the types are similar + if (params.length == types.length) { + for (int i = 0; i < params.length; i++) { + Class inputType = types[i]; + Class paramType = params[i]; + + // The input type is always wrapped + if (!inputType.isPrimitive() && paramType.isPrimitive()) { + // Wrap it + paramType = Primitives.wrap(paramType); + } + + // Compare assignability + if (!paramType.isAssignableFrom(inputType)) { + return false; + } + } + + return true; + } + + // Parameter count must match + return false; + } + + /** + * Retrieve the class of an object, or just the class if it already is a class object. + * + * @param obj - the object. + * @return The class of an object. + */ + public static Class getClass(Object obj) { + if (obj instanceof Class) { + return (Class) obj; + } + return obj.getClass(); + } + public ImmutableList getUnwrappers() { return ImmutableList.copyOf(unwrappers); } - + /** * Retrieve the id of the packets this constructor creates. *

    * Deprecated: Use {@link #getType()} instead. + * * @return The ID of the packets this constructor will create. */ @Deprecated public int getPacketID() { return type.getCurrentId(); } - + /** * Retrieve the type of the packets this constructor creates. + * * @return The type of the created packets. */ public PacketType getType() { return type; } - + /** * Return a copy of the current constructor with a different list of unwrappers. + * * @param unwrappers - list of unwrappers that convert Bukkit wrappers into the equivalent NMS classes. * @return A constructor with a different set of unwrappers. */ @@ -106,7 +148,8 @@ public PacketConstructor withUnwrappers(List unwrappers) { * Create a packet constructor that creates packets using the given types. *

    * Note that if you pass a Class as a value, it will use its type directly. - * @param type - the type of the packet to create. + * + * @param type - the type of the packet to create. * @param values - the values that will match each parameter in the desired constructor. * @return A packet constructor with these types. * @throws IllegalArgumentException If no packet constructor could be created with these types. @@ -115,15 +158,15 @@ public PacketConstructor withPacket(PacketType type, Object[] values) { Class[] types = new Class[values.length]; Throwable lastException = null; Unwrapper[] paramUnwrapper = new Unwrapper[values.length]; - + for (int i = 0; i < types.length; i++) { // Default type if (values[i] != null) { types[i] = PacketConstructor.getClass(values[i]); - + for (Unwrapper unwrapper : unwrappers) { Object result = null; - + try { result = unwrapper.unwrapItem(values[i]); } catch (OutOfMemoryError e) { @@ -133,7 +176,7 @@ public PacketConstructor withPacket(PacketType type, Object[] values) { } catch (Throwable e) { lastException = e; } - + // Update type we're searching for if (result != null) { types[i] = PacketConstructor.getClass(result); @@ -141,7 +184,7 @@ public PacketConstructor withPacket(PacketType type, Object[] values) { break; } } - + } else { // Try it types[i] = Object.class; @@ -161,14 +204,15 @@ public PacketConstructor withPacket(PacketType type, Object[] values) { } throw new IllegalArgumentException("No suitable constructor could be found.", lastException); } - + /** * Construct a packet using the special builtin Minecraft constructors. + * * @param values - values containing Bukkit wrapped items to pass to Minecraft. * @return The created packet. - * @throws FieldAccessException Failure due to a security limitation. + * @throws FieldAccessException Failure due to a security limitation. * @throws IllegalArgumentException Arguments doesn't match the constructor. - * @throws RuntimeException Minecraft threw an exception. + * @throws RuntimeException Minecraft threw an exception. */ public PacketContainer createPacket(Object... values) throws FieldAccessException { try { @@ -178,10 +222,10 @@ public PacketContainer createPacket(Object... values) throws FieldAccessExceptio values[i] = paramUnwrapper[i].unwrapItem(values[i]); } } - + Object nmsPacket = constructorMethod.newInstance(values); return new PacketContainer(type, nmsPacket); - + } catch (IllegalArgumentException e) { throw e; } catch (InstantiationException e) { @@ -192,57 +236,20 @@ public PacketContainer createPacket(Object... values) throws FieldAccessExceptio throw new RuntimeException("Minecraft error.", e); } } - - // Determine if a method with the types 'params' can be called with 'types' - private static boolean isCompatible(Class[] types, Class[] params) { - - // Determine if the types are similar - if (params.length == types.length) { - for (int i = 0; i < params.length; i++) { - Class inputType = types[i]; - Class paramType = params[i]; - - // The input type is always wrapped - if (!inputType.isPrimitive() && paramType.isPrimitive()) { - // Wrap it - paramType = Primitives.wrap(paramType); - } - - // Compare assignability - if (!paramType.isAssignableFrom(inputType)) { - return false; - } - } - - return true; - } - - // Parameter count must match - return false; - } - - /** - * Retrieve the class of an object, or just the class if it already is a class object. - * @param obj - the object. - * @return The class of an object. - */ - public static Class getClass(Object obj) { - if (obj instanceof Class) - return (Class) obj; - return obj.getClass(); - } /** * Represents a unwrapper for a constructor parameter. - * + * * @author Kristian */ public static interface Unwrapper { + /** * Convert the given wrapped object to the equivalent net.minecraft.server object. *

    - * Note that we may pass in a class instead of object - in that case, the unwrapper should - * return the equivalent NMS class. + * Note that we may pass in a class instead of object - in that case, the unwrapper should return the equivalent NMS + * class. + * * @param wrappedObject - wrapped object or class. * @return The equivalent net.minecraft.server object or class. */ diff --git a/src/main/java/com/comphenix/protocol/injector/PacketFilterBuilder.java b/src/main/java/com/comphenix/protocol/injector/PacketFilterBuilder.java index fe8c4c3d1..6cc6ca30e 100644 --- a/src/main/java/com/comphenix/protocol/injector/PacketFilterBuilder.java +++ b/src/main/java/com/comphenix/protocol/injector/PacketFilterBuilder.java @@ -1,191 +1,122 @@ package com.comphenix.protocol.injector; -import javax.annotation.Nonnull; - import com.comphenix.protocol.async.AsyncFilterManager; import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.utility.MinecraftVersion; - +import javax.annotation.Nonnull; import org.bukkit.Server; import org.bukkit.plugin.Plugin; public class PacketFilterBuilder { - 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. + * @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; + return this.server; } /** * Retrieve a reference to the current ProtocolLib instance. + * * @return ProtocolLib. */ public Plugin getLibrary() { - return library; + return this.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; + return this.mcVersion; } /** * Retrieve the error reporter. + * * @return Error reporter. */ public ErrorReporter getReporter() { - return reporter; + return this.reporter; } /** * Retrieve the asynchronous manager. *

    * This is first constructed the {@link #build()} method. + * * @return The asynchronous manager. */ public AsyncFilterManager getAsyncManager() { - return asyncManager; + return this.asyncManager; } - + /** * Create a new packet filter manager. + * * @return A new packet filter manager. */ public InternalManager build() { - if (reporter == null) + if (this.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; + } - return 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; + this.asyncManager = new AsyncFilterManager(this.reporter, this.server.getScheduler()); + return new PacketFilterManager(this); } } diff --git a/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java b/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java index 8aba85c42..6cb234f13 100644 --- a/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java @@ -1,58 +1,37 @@ -/* - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2012 Kristian S. Stangeland - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program; - * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA - * 02111-1307 USA - */ - package com.comphenix.protocol.injector; -import java.lang.reflect.InvocationTargetException; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Predicate; - import com.comphenix.protocol.AsynchronousManager; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.PacketType.Sender; import com.comphenix.protocol.async.AsyncFilterManager; -import com.comphenix.protocol.async.AsyncMarker; import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.Report; import com.comphenix.protocol.error.ReportType; -import com.comphenix.protocol.events.*; -import com.comphenix.protocol.injector.netty.ProtocolInjector; +import com.comphenix.protocol.events.ListenerOptions; +import com.comphenix.protocol.events.ListenerPriority; +import com.comphenix.protocol.events.ListeningWhitelist; +import com.comphenix.protocol.events.NetworkMarker; +import com.comphenix.protocol.events.PacketAdapter; +import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.events.PacketEvent; +import com.comphenix.protocol.events.PacketListener; +import com.comphenix.protocol.injector.PluginVerifier.VerificationResult; import com.comphenix.protocol.injector.netty.WirePacket; +import com.comphenix.protocol.injector.netty.manager.NetworkManagerInjector; import com.comphenix.protocol.injector.packet.PacketInjector; import com.comphenix.protocol.injector.packet.PacketRegistry; import com.comphenix.protocol.injector.player.PlayerInjectionHandler; import com.comphenix.protocol.injector.player.PlayerInjectionHandler.ConflictStrategy; -import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftVersion; -import com.comphenix.protocol.utility.Util; -import com.google.common.base.Objects; -import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Sets; - import io.netty.channel.Channel; - -import org.bukkit.Bukkit; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; import org.bukkit.Location; import org.bukkit.Server; import org.bukkit.World; @@ -68,974 +47,581 @@ import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginManager; -public final class PacketFilterManager implements 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"); - - public static final ReportType REPORT_PLUGIN_DEPEND_MISSING = - new ReportType("%s doesn't depend on ProtocolLib. Check that its plugin.yml has a 'depend' directive."); - - // Registering packet IDs that are not supported - public static final ReportType REPORT_UNSUPPORTED_SERVER_PACKET = new ReportType("[%s] Unsupported server packet in current Minecraft version: %s"); - public static final ReportType REPORT_UNSUPPORTED_CLIENT_PACKET = new ReportType("[%s] Unsupported client packet in current Minecraft version: %s"); - - // Problems injecting and uninjecting players - public static final ReportType REPORT_CANNOT_UNINJECT_PLAYER = new ReportType("Unable to uninject net handler for player."); - public static final ReportType REPORT_CANNOT_UNINJECT_OFFLINE_PLAYER = new ReportType("Unable to uninject logged off player."); - public static final ReportType REPORT_CANNOT_INJECT_PLAYER = new ReportType("Unable to inject player."); - - public static final ReportType REPORT_CANNOT_UNREGISTER_PLUGIN = new ReportType("Unable to handle disabled plugin."); - public static final ReportType REPORT_PLUGIN_VERIFIER_ERROR = new ReportType("Verifier error: %s"); - - /** - * The number of ticks in a second. - */ - public static final int TICKS_PER_SECOND = 20; +public class PacketFilterManager implements ListenerInvoker, InternalManager { - // The amount of time to wait until we actually unhook every player - private static final int UNHOOK_DELAY = 5 * TICKS_PER_SECOND; + // plugin verifier reports + private static final ReportType PLUGIN_VERIFIER_ERROR = new ReportType("Plugin verifier error: %s"); + private static final ReportType INVALID_PLUGIN_VERIFY = new ReportType("Plugin %s does not %s on ProtocolLib"); - // Delayed unhook - private DelayedSingleTask unhookTask; + // listener registration reports + private static final ReportType UNSUPPORTED_PACKET = new ReportType( + "Plugin %s tried to register listener for unknown packet %s [direction: to %s]"); - // Create a concurrent set - private Set packetListeners = Collections.newSetFromMap(new ConcurrentHashMap<>()); + // bukkit references + private final Plugin plugin; + private final Server server; - // Packet injection - private PacketInjector packetInjector; + // protocol lib references + private final ErrorReporter reporter; + private final MinecraftVersion minecraftVersion; + private final AsyncFilterManager asyncFilterManager; - // Different injection types per game phase - private PlayerInjectionHandler playerInjection; + private final PluginVerifier pluginVerifier; - // Whether or not a packet must be input buffered - private volatile Set inputBufferedPackets = Sets.newHashSet(); + // packet listeners + private final SortedPacketListenerList inboundListeners; + private final SortedPacketListenerList outboundListeners; - // The two listener containers - private SortedPacketListenerList recievedListeners; - private SortedPacketListenerList sendingListeners; + // only for api lookups + private final Set registeredListeners; - // Whether or not this class has been closed - private volatile boolean hasClosed; + // injectors + private final PacketInjector packetInjector; + private final PlayerInjectionHandler playerInjectionHandler; + private final NetworkManagerInjector networkManagerInjector; - // The default class loader - private ClassLoader classLoader; + // status of this manager + private boolean debug = false; + private boolean closed = false; + private boolean injected = false; - // Error repoter - private ErrorReporter reporter; - - // The current server - private Server server; - - // The current ProtocolLib library - private Plugin library; - - // The async packet handler - private AsyncFilterManager asyncFilterManager; - - // Valid server and client packets - private boolean knowsServerPackets; - private boolean knowsClientPackets; - - // Ensure that we're not performing too may injections - private AtomicInteger phaseLoginCount = new AtomicInteger(0); - private AtomicInteger phasePlayingCount = new AtomicInteger(0); - - // Whether or not plugins are using the send/receive methods - private AtomicBoolean packetCreation = new AtomicBoolean(); - - // Netty injector (for 1.7.2) - private ProtocolInjector nettyInjector; - - // Plugin verifier - private PluginVerifier pluginVerifier; - - // Whether or not Location.distance(Location) exists - we assume this is the case - private boolean hasRecycleDistance = true; - - // The current Minecraft version - private MinecraftVersion minecraftVersion; - - // Debug mode - private boolean debug; - - /** - * Only create instances of this class if ProtocolLib is disabled. - * @param builder - PacketFilterBuilder - */ public PacketFilterManager(PacketFilterBuilder builder) { - // Used to determine if injection is needed - Predicate isInjectionNecessary = 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(); - this.sendingListeners = new SortedPacketListenerList(); - - // References - this.unhookTask = builder.getUnhookTask(); + // bukkit references + this.plugin = builder.getLibrary(); this.server = builder.getServer(); - this.classLoader = builder.getClassLoader(); + + // protocol lib references this.reporter = builder.getReporter(); + this.minecraftVersion = builder.getMinecraftVersion(); + this.asyncFilterManager = builder.getAsyncManager(); - // The plugin verifier - we don't want to stop ProtocolLib just because its failing - try { - this.pluginVerifier = new PluginVerifier(builder.getLibrary()); - } catch (OutOfMemoryError e) { - throw e; - } catch (Throwable e) { - reporter.reportWarning(this, Report.newBuilder(REPORT_PLUGIN_VERIFIER_ERROR). - messageParam(e.getMessage()).error(e)); - } + // other stuff + this.asyncFilterManager.setManager(this); + this.pluginVerifier = initializePluginVerifier(this, builder.getLibrary(), builder.getReporter()); - // Prepare version - this.minecraftVersion = builder.getMinecraftVersion(); + // packet listeners + this.registeredListeners = new HashSet<>(); + this.inboundListeners = new SortedPacketListenerList(); + this.outboundListeners = new SortedPacketListenerList(); - this.nettyInjector = new ProtocolInjector(builder.getLibrary(), this, reporter); - this.playerInjection = nettyInjector.getPlayerInjector(); - this.packetInjector = nettyInjector.getPacketInjector(); + // injectors + this.networkManagerInjector = new NetworkManagerInjector( + builder.getLibrary(), + builder.getServer(), + this, + builder.getReporter()); + this.packetInjector = this.networkManagerInjector.getPacketInjector(); + this.playerInjectionHandler = this.networkManagerInjector.getPlayerInjectionHandler(); - this.asyncFilterManager = builder.getAsyncManager(); - this.library = builder.getLibrary(); + // ensure that all packet types are loaded and synced + PacketRegistry.getClientPacketTypes(); + PacketRegistry.getServerPacketTypes(); - // Attempt to load the list of server and client packets - try { - knowsServerPackets = PacketRegistry.getClientPacketTypes() != null; - knowsClientPackets = PacketRegistry.getServerPacketTypes() != null; - } catch (FieldAccessException e) { - reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_PACKET_LIST).error(e)); + // hook into all connected players + for (Player player : this.server.getOnlinePlayers()) { + this.playerInjectionHandler.injectPlayer(player, ConflictStrategy.BAIL_OUT); } } - /** - * Construct a new packet filter builder. - * @return New builder. - */ public static PacketFilterBuilder newBuilder() { return new PacketFilterBuilder(); } - @Override - public int getProtocolVersion(Player player) { - return playerInjection.getProtocolVersion(player); - } - - @Override - public MinecraftVersion getMinecraftVersion() { - return minecraftVersion; - } - - @Override - public AsynchronousManager getAsynchronousManager() { - return asyncFilterManager; - } - - @Override - public boolean isDebug() { - return debug; - } - - @Override - public void setDebug(boolean debug) { - this.debug = debug; - - // Inform components that can handle debug mode - if (nettyInjector != null) { - nettyInjector.setDebug(debug); + private static PluginVerifier initializePluginVerifier( + PacketFilterManager requester, + Plugin plugin, + ErrorReporter reporter + ) { + try { + // do this here to prevent us from exploding just because the verifier fails + return new PluginVerifier(plugin); + } catch (Exception exception) { + reporter.reportWarning(requester, Report.newBuilder(PLUGIN_VERIFIER_ERROR) + .error(exception) + .messageParam(exception.getMessage()) + .build()); + return null; } } - /** - * Retrieves how the server packets are read. - * @return Injection method for reading server packets. - */ @Override - public PlayerInjectHooks getPlayerHook() { - return playerInjection.getPlayerHook(); + public void sendServerPacket(Player receiver, PacketContainer packet) { + this.sendServerPacket(receiver, packet, true); } - /** - * 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); + public void sendServerPacket(Player receiver, PacketContainer packet, boolean filters) { + this.sendServerPacket(receiver, packet, null, filters); } @Override - public ImmutableSet getPacketListeners() { - return ImmutableSet.copyOf(packetListeners); - } - - /** - * Warn of common programming mistakes. - * @param plugin - plugin to check. - */ - private void printPluginWarnings(Plugin plugin) { - if (pluginVerifier == null) - return; - - try { - switch (pluginVerifier.verify(plugin)) { - case NO_DEPEND: - reporter.reportWarning(this, Report.newBuilder(REPORT_PLUGIN_DEPEND_MISSING).messageParam(plugin.getName())); - case VALID: - // Do nothing - break; - } - } catch (Exception e) { - reporter.reportWarning(this, Report.newBuilder(REPORT_PLUGIN_VERIFIER_ERROR).messageParam(e.getMessage())); - } - } - - @Override - public void addPacketListener(PacketListener listener) { - if (listener == null) - throw new IllegalArgumentException("listener cannot be NULL."); - - // A listener can only be added once - if (packetListeners.contains(listener)) - return; - ListeningWhitelist sending = listener.getSendingWhitelist(); - ListeningWhitelist receiving = listener.getReceivingWhitelist(); - boolean hasSending = sending != null && sending.isEnabled(); - boolean hasReceiving = receiving != null && receiving.isEnabled(); - - // Check plugin - if (!(hasSending && sending.getOptions().contains(ListenerOptions.SKIP_PLUGIN_VERIFIER)) && - !(hasReceiving && receiving.getOptions().contains(ListenerOptions.SKIP_PLUGIN_VERIFIER))) { - - printPluginWarnings(listener.getPlugin()); - } - - if (hasSending || hasReceiving) { - // Add listeners and hooks - if (hasSending) { - // This doesn't make any sense - if (sending.getOptions().contains(ListenerOptions.INTERCEPT_INPUT_BUFFER)) { - throw new IllegalArgumentException("Sending whitelist cannot require input bufferes to be intercepted."); + public void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters) { + if (!this.closed) { + // if we skip the packet events later when actually writing into the pipeline we at least notify all + // monitor listeners before doing so - they will not be able to change the event tho + if (!filters) { + // ensure we are on the main thread if any listener requires that + if (this.playerInjectionHandler.hasMainThreadListener(packet.getType()) && !this.server.isPrimaryThread()) { + NetworkMarker copy = marker; // okay fine + this.server.getScheduler().scheduleSyncDelayedTask( + this.plugin, + () -> this.sendServerPacket(receiver, packet, copy, true)); + return; } - verifyWhitelist(listener, sending); - sendingListeners.addListener(listener, sending); - enablePacketFilters(listener, sending.getTypes()); + // construct the event and post to all monitor listeners + PacketEvent event = PacketEvent.fromServer(this, packet, marker, receiver, false); + this.outboundListeners.invokePacketSending(this.reporter, event, ListenerPriority.MONITOR); - // Make sure this is possible - playerInjection.checkListener(listener); - } - if (hasSending) - incrementPhases(processPhase(sending)); - - // Handle receivers after senders - if (hasReceiving) { - verifyWhitelist(listener, receiving); - recievedListeners.addListener(listener, receiving); - enablePacketFilters(listener, receiving.getTypes()); + // update the marker of the event without accidentally constructing it + marker = NetworkMarker.getNetworkMarker(event); } - if (hasReceiving) - incrementPhases(processPhase(receiving)); - // Inform our injected hooks - packetListeners.add(listener); - updateRequireInputBuffers(); + // process outbound + this.playerInjectionHandler.sendServerPacket(receiver, packet, marker, filters); } } - /** - * Determine if a given packet may be sent during login. - * @param type - the packet type. - * @return TRUE if it may, FALSE otherwise. - */ - public boolean isLoginPacket(PacketType type) { - return PacketType.Login.Client.getInstance().hasMember(type) || - PacketType.Login.Server.getInstance().hasMember(type) || - PacketType.Status.Client.getInstance().hasMember(type) || - PacketType.Status.Server.getInstance().hasMember(type); + @Override + public void sendWirePacket(Player receiver, int id, byte[] bytes) { + this.sendWirePacket(receiver, new WirePacket(id, bytes)); } - private GamePhase processPhase(ListeningWhitelist whitelist) { - // Determine if this is a login packet, ensuring that gamephase detection is enabled - if (!whitelist.getGamePhase().hasLogin() && - !whitelist.getOptions().contains(ListenerOptions.DISABLE_GAMEPHASE_DETECTION)) { - - for (PacketType type : whitelist.getTypes()) { - if (isLoginPacket(type)) { - return GamePhase.BOTH; - } + @Override + public void sendWirePacket(Player receiver, WirePacket packet) { + if (!this.closed) { + // special case - we just throw the wire packet down the pipeline without processing it + Channel outboundChannel = this.playerInjectionHandler.getChannel(receiver); + if (outboundChannel == null) { + throw new IllegalArgumentException("Unable to obtain connection of player " + receiver); } - } - return whitelist.getGamePhase(); - } - - /** - * Invoked when we need to update the input buffer set. - */ - private void updateRequireInputBuffers() { - Set updated = Sets.newHashSet(); - - for (PacketListener listener : packetListeners) { - ListeningWhitelist whitelist = listener.getReceivingWhitelist(); - // We only check the recieving whitelist - if (whitelist.getOptions().contains(ListenerOptions.INTERCEPT_INPUT_BUFFER)) { - updated.addAll(whitelist.getTypes()); - } + outboundChannel.writeAndFlush(packet); } - // Update it - this.inputBufferedPackets = updated; - this.packetInjector.inputBuffersChanged(updated); } - /** - * Invoked to handle the different game phases of a added listener. - * @param phase - listener's game game phase. - */ - private void incrementPhases(GamePhase phase) { - if (phase.hasLogin()) - phaseLoginCount.incrementAndGet(); - - // We may have to inject into every current player - if (phase.hasPlaying()) { - if (phasePlayingCount.incrementAndGet() == 1) { - // If we're about to uninitialize every player, cancel that instead - if (unhookTask.isRunning()) - unhookTask.cancel(); - else - // Inject our hook into already existing players - initializePlayers(Util.getOnlinePlayers()); - } - } + @Override + public void receiveClientPacket(Player sender, PacketContainer packet) { + this.receiveClientPacket(sender, packet, true); } - /** - * Invoked to handle the different game phases of a removed listener. - * @param phase - listener's game game phase. - */ - private void decrementPhases(GamePhase phase) { - if (phase.hasLogin()) - phaseLoginCount.decrementAndGet(); - - // We may have to inject into every current player - if (phase.hasPlaying()) { - if (phasePlayingCount.decrementAndGet() == 0) { - // Schedule unhooking in the future - unhookTask.schedule(UNHOOK_DELAY, new Runnable() { - @Override - public void run() { - // Inject our hook into already existing players - uninitializePlayers(Util.getOnlinePlayers()); - } - }); - } - } + @Override + public void receiveClientPacket(Player sender, PacketContainer packet, boolean filters) { + this.receiveClientPacket(sender, packet, null, filters); } - /** - * Determine if the packet IDs in a whitelist is valid. - * @param listener - the listener that will be mentioned in the error. - * @param whitelist - whitelist of packet IDs. - * @throws IllegalArgumentException If the whitelist is illegal. - */ @Override - public void verifyWhitelist(PacketListener listener, ListeningWhitelist whitelist) { - for (PacketType type : whitelist.getTypes()) { - if (type == null) { - throw new IllegalArgumentException(String.format("Packet type in in listener %s was NULL.", - PacketAdapter.getPluginName(listener)) - ); + public void receiveClientPacket(Player sender, PacketContainer packet, NetworkMarker marker, boolean filters) { + if (!this.closed) { + // make sure we are on the main thread if any listener of the packet needs it + if (this.playerInjectionHandler.hasMainThreadListener(packet.getType()) && !this.server.isPrimaryThread()) { + this.server.getScheduler().scheduleSyncDelayedTask( + this.plugin, + () -> this.receiveClientPacket(sender, packet, marker, filters)); + return; } - } - } - @Override - public void removePacketListener(PacketListener listener) { - if (listener == null) - throw new IllegalArgumentException("listener cannot be NULL"); + Object nmsPacket = packet.getHandle(); - List sendingRemoved = null; - List receivingRemoved = null; + // make sure packets which are force received through this method are never cancelled + boolean oldCancelState = this.packetInjector.isCancelled(nmsPacket); + this.packetInjector.setCancelled(nmsPacket, false); - ListeningWhitelist sending = listener.getSendingWhitelist(); - ListeningWhitelist receiving = listener.getReceivingWhitelist(); + // check to which listeners we need to post the packet + if (filters) { + // post to all listeners + PacketEvent event = this.packetInjector.packetReceived(packet, sender); + if (event.isCancelled()) { + return; + } - // Remove from the overal list of listeners - if (!packetListeners.remove(listener)) - return; + // prevent possible de-sync + nmsPacket = event.getPacket().getHandle(); + } else { + PacketEvent event = PacketEvent.fromClient(this, packet, marker, sender, false); + this.inboundListeners.invokePacketRecieving(this.reporter, event, ListenerPriority.MONITOR); + } - // Remove listeners and phases - if (sending != null && sending.isEnabled()) { - sendingRemoved = sendingListeners.removeListener(listener, sending); - decrementPhases(processPhase(sending)); + // post to the player inject, reset our cancel state change + this.playerInjectionHandler.receiveClientPacket(sender, nmsPacket); + this.packetInjector.setCancelled(nmsPacket, oldCancelState); } - if (receiving != null && receiving.isEnabled()) { - receivingRemoved = recievedListeners.removeListener(listener, receiving); - decrementPhases(processPhase(receiving)); - } - - // Remove hooks, if needed - if (sendingRemoved != null && sendingRemoved.size() > 0) - disablePacketFilters(ConnectionSide.SERVER_SIDE, sendingRemoved); - if (receivingRemoved != null && receivingRemoved.size() > 0) - disablePacketFilters(ConnectionSide.CLIENT_SIDE, receivingRemoved); - updateRequireInputBuffers(); } @Override - public void removePacketListeners(Plugin plugin) { - // Iterate through every packet listener - for (PacketListener listener : packetListeners) { - // Remove the listener - if (Objects.equal(listener.getPlugin(), plugin)) { - removePacketListener(listener); - } - } - - // Do the same for the asynchronous events - asyncFilterManager.unregisterAsyncHandlers(plugin); + public int getProtocolVersion(Player player) { + return this.playerInjectionHandler.getProtocolVersion(player); } @Override - public void invokePacketRecieving(PacketEvent event) { - if (!hasClosed) { - handlePacket(recievedListeners, event, false); - } + public void broadcastServerPacket(PacketContainer packet) { + this.broadcastServerPacket(packet, this.server.getOnlinePlayers()); } @Override - public void invokePacketSending(PacketEvent event) { - if (!hasClosed) { - handlePacket(sendingListeners, event, true); - } - } - - /** - * Handle a packet sending or receiving event. - *

    - * Note that we also handle asynchronous events. - * @param packetListeners - packet listeners that will receive this event. - * @param event - the evnet to broadcast. - */ - private void handlePacket(SortedPacketListenerList packetListeners, PacketEvent event, boolean sending) { - // By default, asynchronous packets are queued for processing - if (asyncFilterManager.hasAsynchronousListeners(event)) { - event.setAsyncMarker(asyncFilterManager.createAsyncMarker()); - } - - // Process synchronous events - if (sending) - packetListeners.invokePacketSending(reporter, event); - else - packetListeners.invokePacketRecieving(reporter, event); - - // To cancel asynchronous processing, use the async marker - if (!event.isCancelled() && !hasAsyncCancelled(event.getAsyncMarker())) { - asyncFilterManager.enqueueSyncPacket(event, event.getAsyncMarker()); + public void broadcastServerPacket(PacketContainer packet, Entity entity, boolean includeTracker) { + if (!this.closed) { + Collection trackers = this.getEntityTrackers(entity); + if (includeTracker && entity instanceof Player) { + trackers.add((Player) entity); + } - // The above makes a copy of the event, so it's safe to cancel it - event.setReadOnly(false); - event.setCancelled(true); + this.broadcastServerPacket(packet, trackers); } } - // NULL marker mean we're dealing with no asynchronous listeners - private boolean hasAsyncCancelled(AsyncMarker marker) { - return marker == null || marker.isAsyncCancelled(); - } - - /** - * Enables packet events for a given packet ID. - *

    - * Note that all packets are disabled by default. - * - * @param listener - the listener that requested to enable these filters. - * @param packets - the packet id(s). - */ - private void enablePacketFilters(PacketListener listener, Iterable packets) { - // Note the difference between unsupported and valid. - // Every packet ID between and including 0 - 255 is valid, but only a subset is supported. - - for (PacketType type : packets) { - // Only register server packets that are actually supported by Minecraft - if (type.getSender() == Sender.SERVER) { - // Note that we may update the packet list here - if (!knowsServerPackets || PacketRegistry.getServerPacketTypes().contains(type)) - playerInjection.addPacketHandler(type, listener.getSendingWhitelist().getOptions()); - else - reporter.reportWarning(this, - Report.newBuilder(REPORT_UNSUPPORTED_SERVER_PACKET).messageParam(PacketAdapter.getPluginName(listener), type) - ); + @Override + public void broadcastServerPacket(PacketContainer packet, Location origin, int maxObserverDistance) { + if (!this.closed) { + World world = origin.getWorld(); + if (world == null) { + throw new IllegalArgumentException("The given location " + origin + " has no world associated!"); } - // As above, only for client packets - if (type.getSender() == Sender.CLIENT && packetInjector != null) { - if (!knowsClientPackets || PacketRegistry.getClientPacketTypes().contains(type)) - packetInjector.addPacketHandler(type, listener.getReceivingWhitelist().getOptions()); - else - reporter.reportWarning(this, - Report.newBuilder(REPORT_UNSUPPORTED_CLIENT_PACKET).messageParam(PacketAdapter.getPluginName(listener), type) - ); + Location copy = origin.clone(); + int maxDistance = maxObserverDistance * maxObserverDistance; + + // filter out all target players + Collection targetPlayers = new HashSet<>(); + for (Player player : world.getPlayers()) { + if (player.getLocation().distanceSquared(copy) <= maxDistance) { + targetPlayers.add(player); + } } - } - } - /** - * Disables packet events from a given packet ID. - * @param packets - the packet id(s). - * @param side - which side the event no longer should arrive from. - */ - private void disablePacketFilters(ConnectionSide side, Iterable packets) { - if (side == null) - throw new IllegalArgumentException("side cannot be NULL."); - - for (PacketType type : packets) { - if (side.isForServer()) - playerInjection.removePacketHandler(type); - if (side.isForClient() && packetInjector != null) - packetInjector.removePacketHandler(type); + this.broadcastServerPacket(packet, targetPlayers); } } @Override - public void broadcastServerPacket(PacketContainer packet) { - Preconditions.checkNotNull(packet, "packet cannot be NULL."); - broadcastServerPacket(packet, Util.getOnlinePlayers()); + public void broadcastServerPacket(PacketContainer packet, Collection targetPlayers) { + for (Player player : targetPlayers) { + this.sendServerPacket(player, packet); + } } @Override - public void broadcastServerPacket(PacketContainer packet, Entity entity, boolean includeTracker) { - Preconditions.checkNotNull(packet, "packet cannot be NULL."); - Preconditions.checkNotNull(entity, "entity cannot be NULL."); - List trackers = getEntityTrackers(entity); - - // Only add it if it's a player - if (includeTracker && entity instanceof Player) { - trackers.add((Player) entity); - } - broadcastServerPacket(packet, trackers); + public ImmutableSet getPacketListeners() { + return ImmutableSet.copyOf(this.registeredListeners); } @Override - public void broadcastServerPacket(PacketContainer packet, Location origin, int maxObserverDistance) { - try { - // Square the maximum too - int maxDistance = maxObserverDistance * maxObserverDistance; + public void addPacketListener(PacketListener listener) { + if (!this.closed && !this.registeredListeners.contains(listener)) { + // get the packet types which we should actually send + ListeningWhitelist outbound = listener.getSendingWhitelist(); + ListeningWhitelist inbound = listener.getReceivingWhitelist(); + + // verify plugin if needed + if (this.shouldVerifyPlugin(outbound, inbound)) { + this.printPluginWarnings(listener.getPlugin()); + } - World world = origin.getWorld(); - Location recycle = origin.clone(); + // register as outbound listener if anything outbound is handled + if (outbound != null && outbound.isEnabled()) { + // verification + this.verifyWhitelist(listener, outbound); + this.playerInjectionHandler.checkListener(listener); - // Only broadcast the packet to nearby players - for (Player player : Util.getOnlinePlayers()) { - if (world.equals(player.getWorld()) && - getDistanceSquared(origin, recycle, player) <= maxDistance) { + // registration + this.registeredListeners.add(listener); + this.outboundListeners.addListener(listener, outbound); - sendServerPacket(player, packet); - } + // let the injectors know about the change as well + this.registerPacketListenerInInjectors(listener, outbound.getTypes()); } - } catch (InvocationTargetException e) { - throw new FieldAccessException("Unable to send server packet.", e); - } - } - - /** - * Retrieve the squared distance between a location and a player. - * @param origin - the origin location. - * @param recycle - a location object to be recycled, if supported. - * @param player - the player. - * @return The squared distance between the player and the origin, - */ - private double getDistanceSquared(Location origin, Location recycle, Player player) { - if (hasRecycleDistance) { - try { - return player.getLocation(recycle).distanceSquared(origin); - } catch (Error e) { - // Damn it - hasRecycleDistance = false; - } - } + // register as inbound listener if anything outbound is handled + if (inbound != null && inbound.isEnabled()) { + // verification + this.verifyWhitelist(listener, inbound); + this.playerInjectionHandler.checkListener(listener); - // The fallback method - return player.getLocation().distanceSquared(origin); - } + // registration + this.registeredListeners.add(listener); + this.inboundListeners.addListener(listener, inbound); - /** - * Broadcast a packet to a given iterable of players. - * @param packet - the packet to broadcast. - * @param players - the iterable of players. - */ - private void broadcastServerPacket(PacketContainer packet, Iterable players) { - try { - for (Player player : players) { - sendServerPacket(player, packet); + // let the injectors know about the change as well + this.registerPacketListenerInInjectors(listener, inbound.getTypes()); } - } catch (InvocationTargetException e) { - throw new FieldAccessException("Unable to send server packet.", e); } } @Override - public void sendServerPacket(Player reciever, PacketContainer packet) throws InvocationTargetException { - sendServerPacket(reciever, packet, null, true); - } + public void removePacketListener(PacketListener listener) { + if (!this.closed && this.registeredListeners.remove(listener)) { + ListeningWhitelist outbound = listener.getSendingWhitelist(); + ListeningWhitelist inbound = listener.getReceivingWhitelist(); + + // remove outbound listeners (if any) + if (outbound != null && outbound.isEnabled()) { + Collection removed = this.outboundListeners.removeListener(listener, outbound); + if (!removed.isEmpty()) { + this.unregisterPacketListenerInInjectors(removed); + } + } - @Override - public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) throws InvocationTargetException { - sendServerPacket(reciever, packet, null, filters); + // remove inbound listeners (if any) + if (inbound != null && inbound.isEnabled()) { + Collection removed = this.inboundListeners.removeListener(listener, inbound); + if (!removed.isEmpty()) { + this.unregisterPacketListenerInInjectors(removed); + } + } + } } @Override - public void sendServerPacket(final Player receiver, final PacketContainer packet, NetworkMarker marker, final boolean filters) throws InvocationTargetException { - if (receiver == null) - throw new IllegalArgumentException("receiver cannot be NULL."); - if (packet == null) - throw new IllegalArgumentException("packet cannot be NULL."); - if (packet.getType().getSender() == Sender.CLIENT) - throw new IllegalArgumentException("Packet of sender CLIENT cannot be sent to a client."); - - // We may have to enable player injection indefinitely after this - if (packetCreation.compareAndSet(false, true)) - incrementPhases(GamePhase.PLAYING); - - if (!filters) { - // We may have to delay the packet due to non-asynchronous monitor listeners - if (!Bukkit.isPrimaryThread() && playerInjection.hasMainThreadListener(packet.getType())) { - final NetworkMarker copy = marker; - - server.getScheduler().scheduleSyncDelayedTask(library, new Runnable() { - @Override - public void run() { - try { - // Prevent infinite loops - if (!Bukkit.isPrimaryThread()) - throw new IllegalStateException("Scheduled task was not executed on the main thread!"); - sendServerPacket(receiver, packet, copy, filters); - } catch (Exception e) { - reporter.reportMinimal(library, "sendServerPacket-run()", e); - } - } - }); - return; + public void removePacketListeners(Plugin plugin) { + for (PacketListener listener : this.getPacketListeners()) { + if (Objects.equals(listener.getPlugin(), plugin)) { + this.removePacketListener(listener); } - - PacketEvent event = PacketEvent.fromServer(this, packet, marker, receiver, false); - sendingListeners.invokePacketSending(reporter, event, ListenerPriority.MONITOR); - marker = NetworkMarker.getNetworkMarker(event); } - playerInjection.sendServerPacket(receiver, packet, marker, filters); } @Override - public void sendWirePacket(Player receiver, int id, byte[] bytes) throws InvocationTargetException { - WirePacket packet = new WirePacket(id, bytes); - sendWirePacket(receiver, packet); + public PacketContainer createPacket(PacketType type) { + return this.createPacket(type, true); } @Override - public void sendWirePacket(Player receiver, WirePacket packet) throws InvocationTargetException { - Channel channel = playerInjection.getChannel(receiver); - if (channel == null) { - throw new InvocationTargetException(new NullPointerException(), "Failed to obtain channel for " + receiver.getName()); + public PacketContainer createPacket(PacketType type, boolean forceDefaults) { + PacketContainer container = new PacketContainer(type); + if (forceDefaults) { + container.getModifier().writeDefaults(); } - channel.writeAndFlush(packet); - } - - @Override - public void recieveClientPacket(Player sender, PacketContainer packet) throws IllegalAccessException, InvocationTargetException { - recieveClientPacket(sender, packet, null, true); - } - - @Override - public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters) throws IllegalAccessException, InvocationTargetException { - recieveClientPacket(sender, packet, null, filters); + return container; } @Override - public void recieveClientPacket(Player sender, PacketContainer packet, NetworkMarker marker, boolean filters) throws IllegalAccessException, InvocationTargetException { - if (sender == null) - throw new IllegalArgumentException("sender cannot be NULL."); - if (packet == null) - throw new IllegalArgumentException("packet cannot be NULL."); - if (packet.getType().getSender() == Sender.SERVER) - throw new IllegalArgumentException("Packet of sender SERVER cannot be sent to the server."); - - // And here too - if (packetCreation.compareAndSet(false, true)) - incrementPhases(GamePhase.PLAYING); - - Object mcPacket = packet.getHandle(); - boolean cancelled = packetInjector.isCancelled(mcPacket); - - // Make sure the packet isn't cancelled - if (cancelled) { - packetInjector.setCancelled(mcPacket, false); - } - - if (filters) { - byte[] data = NetworkMarker.getByteBuffer(marker); - PacketEvent event = packetInjector.packetRecieved(packet, sender, data); - - if (!event.isCancelled()) - mcPacket = event.getPacket().getHandle(); - else - return; - - } else { - // Let the monitors know though - recievedListeners.invokePacketSending( - reporter, - PacketEvent.fromClient(this, packet, marker, sender, false), - ListenerPriority.MONITOR); - } - - playerInjection.recieveClientPacket(sender, mcPacket); - - // Let it stay cancelled - if (cancelled) { - packetInjector.setCancelled(mcPacket, true); - } + public PacketConstructor createPacketConstructor(PacketType type, Object... arguments) { + return PacketConstructor.DEFAULT.withPacket(type, arguments); } @Override - public PacketContainer createPacket(PacketType type) { - return createPacket(type, true); + public void updateEntity(Entity entity, List observers) { + EntityUtilities.getInstance().updateEntity(entity, observers); } @Override - public PacketContainer createPacket(PacketType type, boolean forceDefaults) { - PacketContainer packet = new PacketContainer(type); - - // Use any default values if possible - if (forceDefaults) { - try { - packet.getModifier().writeDefaults(); - } catch (FieldAccessException e) { - throw new RuntimeException("Security exception.", e); + public Entity getEntityFromID(World container, int id) { + for (Entity entity : container.getEntities()) { + if (entity.getEntityId() == id) { + return entity; } } - - return packet; + return null; } @Override - public PacketConstructor createPacketConstructor(PacketType type, Object... arguments) { - return PacketConstructor.DEFAULT.withPacket(type, arguments); + public List getEntityTrackers(Entity entity) { + return EntityUtilities.getInstance().getEntityTrackers(entity); } @Override public Set getSendingFilterTypes() { - return Collections.unmodifiableSet(playerInjection.getSendingFilters()); + return Collections.unmodifiableSet(this.playerInjectionHandler.getSendingFilters()); } @Override public Set getReceivingFilterTypes() { - return Collections.unmodifiableSet(packetInjector.getPacketHandlers()); + return Collections.unmodifiableSet(this.packetInjector.getPacketHandlers()); } @Override - public void updateEntity(Entity entity, List observers) throws FieldAccessException { - EntityUtilities.getInstance().updateEntity(entity, observers); + public MinecraftVersion getMinecraftVersion() { + return this.minecraftVersion; } @Override - public Entity getEntityFromID(World container, int id) throws FieldAccessException { - return EntityUtilities.getInstance().getEntityFromID(container, id); + public boolean isClosed() { + return this.closed; } @Override - public List getEntityTrackers(Entity entity) throws FieldAccessException { - return EntityUtilities.getInstance().getEntityTrackers(entity); - } - - /** - * Initialize the packet injection for every player. - * @param players - list of players to inject. - */ - public void initializePlayers(List players) { - for (Player player : players) - playerInjection.injectPlayer(player, ConflictStrategy.OVERRIDE); + public AsynchronousManager getAsynchronousManager() { + return this.asyncFilterManager; } - /** - * Uninitialize the packet injection of every player. - * @param players - list of players to uninject. - */ - public void uninitializePlayers(List players) { - for (Player player : players) { - playerInjection.uninjectPlayer(player); + @Override + public void verifyWhitelist(PacketListener listener, ListeningWhitelist whitelist) { + for (PacketType type : whitelist.getTypes()) { + if (type == null) { + throw new IllegalArgumentException("Null packet type in listener of " + PacketAdapter.getPluginName(listener)); + } } } - /** - * Register this protocol manager on Bukkit. - * - * @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 (nettyInjector != null) - nettyInjector.inject(); - - manager.registerEvents(new Listener() { + public void registerEvents(PluginManager manager, Plugin plugin) { + if (!this.closed && !this.injected) { + // prevent duplicate event registrations / injections + this.injected = true; + this.networkManagerInjector.inject(); - @EventHandler(priority = EventPriority.LOWEST) - public void onPlayerLogin(PlayerLoginEvent event) { - PacketFilterManager.this.onPlayerLogin(event); - } + // all listeners we need, this is a bit messy, but it makes the job correctly + manager.registerEvents(new Listener() { - @EventHandler(priority = EventPriority.LOWEST) - public void onPrePlayerJoin(PlayerJoinEvent event) { - PacketFilterManager.this.onPrePlayerJoin(event); + @EventHandler(priority = EventPriority.LOWEST) + public void handleLogin(PlayerLoginEvent event) { + PacketFilterManager.this.playerInjectionHandler.updatePlayer(event.getPlayer()); } - @EventHandler(priority = EventPriority.MONITOR) - public void onPlayerJoin(PlayerJoinEvent event) { - PacketFilterManager.this.onPlayerJoin(event); + @EventHandler(priority = EventPriority.LOWEST) + public void handleJoin(PlayerJoinEvent event) { + PacketFilterManager.this.playerInjectionHandler.updatePlayer(event.getPlayer()); } - @EventHandler(priority = EventPriority.MONITOR) - public void onPlayerQuit(PlayerQuitEvent event) { - PacketFilterManager.this.onPlayerQuit(event); + @EventHandler(priority = EventPriority.MONITOR) + public void handleQuit(PlayerQuitEvent event) { + PacketFilterManager.this.asyncFilterManager.removePlayer(event.getPlayer()); + PacketFilterManager.this.playerInjectionHandler.handleDisconnect(event.getPlayer()); } - @EventHandler(priority = EventPriority.MONITOR) - public void onPluginDisabled(PluginDisableEvent event) { - PacketFilterManager.this.onPluginDisabled(event, plugin); + @EventHandler(priority = EventPriority.MONITOR) + public void handlePluginUnload(PluginDisableEvent event) { + // don't do this for our plugin! + if (event.getPlugin() != PacketFilterManager.this.plugin) { + PacketFilterManager.this.removePacketListeners(event.getPlugin()); + } } - - }, plugin); + }, plugin); + } } - private void onPlayerLogin(PlayerLoginEvent event) { - playerInjection.updatePlayer(event.getPlayer()); - } + @Override + public void close() { + if (!this.closed) { + // mark as closed directly to prevent duplicate calls + this.closed = true; + this.injected = false; - private void onPrePlayerJoin(PlayerJoinEvent event) { - playerInjection.updatePlayer(event.getPlayer()); - } + // uninject all clutter + this.networkManagerInjector.close(); + this.playerInjectionHandler.close(); - private void onPlayerJoin(PlayerJoinEvent event) { - try { - // Let's clean up the other injection first. - playerInjection.uninjectPlayer(event.getPlayer().getAddress()); - playerInjection.injectPlayer(event.getPlayer(), ConflictStrategy.OVERRIDE); - } catch (Exception e) { - reporter.reportDetailed(PacketFilterManager.this, - Report.newBuilder(REPORT_CANNOT_INJECT_PLAYER).callerParam(event).error(e) - ); + // cleanup + this.registeredListeners.clear(); + this.packetInjector.cleanupAll(); + this.asyncFilterManager.cleanupAll(); } - } + } - private void onPlayerQuit(PlayerQuitEvent event) { - try { - Player player = event.getPlayer(); - - asyncFilterManager.removePlayer(player); - playerInjection.handleDisconnect(player); - playerInjection.uninjectPlayer(player); - } catch (Exception e) { - reporter.reportDetailed(PacketFilterManager.this, - Report.newBuilder(REPORT_CANNOT_UNINJECT_OFFLINE_PLAYER).callerParam(event).error(e) - ); - } - } + @Override + public boolean isDebug() { + return this.debug; + } - private void onPluginDisabled(PluginDisableEvent event, Plugin protocolLibrary) { - try { - // Clean up in case the plugin forgets - if (event.getPlugin() != protocolLibrary) { - removePacketListeners(event.getPlugin()); - } - } catch (Exception e) { - reporter.reportDetailed(PacketFilterManager.this, - Report.newBuilder(REPORT_CANNOT_UNREGISTER_PLUGIN).callerParam(event).error(e) - ); + @Override + public void setDebug(boolean debug) { + this.debug = debug; + } + + @Override + public void invokePacketReceiving(PacketEvent event) { + if (!this.closed) { + this.postPacketToListeners(this.inboundListeners, event, false); } - } - - /** - * Retrieve the number of listeners that expect packets during playing. - * @return Number of listeners. - */ - private int getPhasePlayingCount() { - return phasePlayingCount.get(); } - /** - * Retrieve the number of listeners that expect packets during login. - * @return Number of listeners - */ - private int getPhaseLoginCount() { - return phaseLoginCount.get(); + @Override + public void invokePacketSending(PacketEvent event) { + if (!this.closed) { + this.postPacketToListeners(this.outboundListeners, event, true); + } } @Override public PacketType getPacketType(Object packet) { - if (packet == null) - throw new IllegalArgumentException("Packet cannot be NULL."); - if (!MinecraftReflection.isPacketClass(packet)) - throw new IllegalArgumentException("The given object " + packet + " is not a packet."); + if (!MinecraftReflection.isPacketClass(packet)) { + throw new IllegalArgumentException("Given packet is not a minecraft packet instance"); + } PacketType type = PacketRegistry.getPacketType(packet.getClass()); - if (type != null) { return type; + } + + throw new IllegalArgumentException("Unable to associate given packet " + packet + " with a registered packet!"); + } + + private void postPacketToListeners(SortedPacketListenerList listeners, PacketEvent event, boolean outbound) { + // append async marker if any async listener for the packet was registered + if (this.asyncFilterManager.hasAsynchronousListeners(event)) { + event.setAsyncMarker(this.asyncFilterManager.createAsyncMarker()); + } + + // post to sync listeners + if (outbound) { + listeners.invokePacketSending(this.reporter, event); } else { - throw new IllegalArgumentException( - "Unable to find associated packet of " + packet + ": Lookup returned NULL."); + listeners.invokePacketRecieving(this.reporter, event); + } + + // check if we need to post the packet to the async handler + if (!event.isCancelled() && event.getAsyncMarker() != null && !event.getAsyncMarker().isAsyncCancelled()) { + this.asyncFilterManager.enqueueSyncPacket(event, event.getAsyncMarker()); + + // cancel the packet here for async processing (enqueueSyncPacket will create a copy of the event) + event.setReadOnly(false); + event.setCancelled(true); } } - /** - * Retrieves the current plugin class loader. - * @return Class loader. - */ - public ClassLoader getClassLoader() { - return classLoader; + private boolean shouldVerifyPlugin(ListeningWhitelist out, ListeningWhitelist in) { + if (out != null && out.isEnabled() && !out.getOptions().contains(ListenerOptions.SKIP_PLUGIN_VERIFIER)) { + return true; + } + + return in != null && in.isEnabled() && !in.getOptions().contains(ListenerOptions.SKIP_PLUGIN_VERIFIER); } - @Override - public boolean isClosed() { - return hasClosed; + private void printPluginWarnings(Plugin plugin) { + // check if we were able to initialize the plugin verifier + if (this.pluginVerifier != null) { + VerificationResult result = this.pluginVerifier.verify(plugin); + if (result != VerificationResult.VALID) { + this.reporter.reportWarning(this, Report.newBuilder(INVALID_PLUGIN_VERIFY) + .messageParam(plugin.getName(), result) + .build()); + } + } } - @Override - public void close() { - // Guard - if (hasClosed) - return; - - // Remove packet handlers - if (packetInjector != null) - packetInjector.cleanupAll(); - if (nettyInjector != null) - nettyInjector.close(); - - // Remove server handler - playerInjection.close(); - hasClosed = true; - - // Remove listeners - packetListeners.clear(); - recievedListeners = null; - sendingListeners = null; - - // Clean up async handlers. We have to do this last. - asyncFilterManager.cleanupAll(); + private void registerPacketListenerInInjectors(PacketListener listener, Collection packetTypes) { + for (PacketType packetType : packetTypes) { + // check the packet direction + if (packetType.getSender() == Sender.SERVER) { + // check if the packet is registered on the server side + if (PacketRegistry.getServerPacketTypes().contains(packetType)) { + this.playerInjectionHandler.addPacketHandler(packetType, listener.getSendingWhitelist().getOptions()); + } else { + this.reporter.reportWarning(this, Report.newBuilder(UNSUPPORTED_PACKET) + .messageParam(PacketAdapter.getPluginName(listener), packetType, packetType.getSender()) + .build()); + } + } else if (packetType.getSender() == Sender.CLIENT) { + // check if the packet is registered on the client side + if (PacketRegistry.getClientPacketTypes().contains(packetType)) { + this.packetInjector.addPacketHandler(packetType, listener.getReceivingWhitelist().getOptions()); + } else { + this.reporter.reportWarning(this, Report.newBuilder(UNSUPPORTED_PACKET) + .messageParam(PacketAdapter.getPluginName(listener), packetType, packetType.getSender()) + .build()); + } + } + } } - @Override - protected void finalize() throws Throwable { - close(); + private void unregisterPacketListenerInInjectors(Collection packetTypes) { + for (PacketType packetType : packetTypes) { + if (packetType.getSender() == Sender.SERVER) { + this.playerInjectionHandler.removePacketHandler(packetType); + } else if (packetType.getSender() == Sender.CLIENT) { + this.packetInjector.removePacketHandler(packetType); + } + } } } diff --git a/src/main/java/com/comphenix/protocol/injector/PlayerInjectHooks.java b/src/main/java/com/comphenix/protocol/injector/PlayerInjectHooks.java deleted file mode 100644 index 57bd15dba..000000000 --- a/src/main/java/com/comphenix/protocol/injector/PlayerInjectHooks.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.comphenix.protocol.injector; - -/** - * Sets the inject hook type. Different types allow for maximum compatibility. - * @author Kristian - */ -public enum PlayerInjectHooks { - /** - * The injection hook that does nothing. Set when every other inject hook fails. - */ - NONE, - - /** - * Override the network handler object itself. Only works in 1.3. - *

    - * Cannot intercept MapChunk packets. - */ - NETWORK_MANAGER_OBJECT, - - /** - * Override the packet queue lists in NetworkHandler. - *

    - * Cannot intercept MapChunk packets. - */ - NETWORK_HANDLER_FIELDS, - - /** - * Override the server handler object. Versatile, but a tad slower. - */ - NETWORK_SERVER_OBJECT; -} diff --git a/src/main/java/com/comphenix/protocol/injector/PlayerLoggedOutException.java b/src/main/java/com/comphenix/protocol/injector/PlayerLoggedOutException.java deleted file mode 100644 index ced31b2e3..000000000 --- a/src/main/java/com/comphenix/protocol/injector/PlayerLoggedOutException.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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; - -/** - * Invoked when attempting to use a player that has already logged out. - * - * @author Kristian - */ -public class PlayerLoggedOutException extends RuntimeException { - - /** - * Generated by Eclipse. - */ - private static final long serialVersionUID = 4889257862160145234L; - - public PlayerLoggedOutException() { - // Default error message - super("Cannot inject a player that has already logged out."); - } - - public PlayerLoggedOutException(String message, Throwable cause) { - super(message, cause); - } - - public PlayerLoggedOutException(String message) { - super(message); - } - - public PlayerLoggedOutException(Throwable cause) { - super(cause); - } - - /** - * Construct an exception from a formatted message. - * @param message - the message to format. - * @param params - parameters. - * @return The formated exception - */ - public static PlayerLoggedOutException fromFormat(String message, Object... params) { - return new PlayerLoggedOutException(String.format(message, params)); - } -} diff --git a/src/main/java/com/comphenix/protocol/injector/StructureCache.java b/src/main/java/com/comphenix/protocol/injector/StructureCache.java index f2010253c..698cca87f 100644 --- a/src/main/java/com/comphenix/protocol/injector/StructureCache.java +++ b/src/main/java/com/comphenix/protocol/injector/StructureCache.java @@ -17,17 +17,11 @@ package com.comphenix.protocol.injector; -import java.util.HashSet; -import java.util.Set; -import java.util.function.Supplier; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - import com.comphenix.protocol.PacketType; import com.comphenix.protocol.injector.packet.PacketRegistry; +import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; -import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.compiler.BackgroundCompiler; import com.comphenix.protocol.reflect.compiler.CompiledStructureModifier; import com.comphenix.protocol.reflect.instances.DefaultInstances; @@ -35,35 +29,42 @@ import com.comphenix.protocol.utility.MinecraftMethods; import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.ZeroBuffer; - -import io.netty.buffer.ByteBuf; import com.google.common.base.Preconditions; - +import io.netty.buffer.ByteBuf; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; import net.bytebuddy.implementation.FixedValue; import net.bytebuddy.matcher.ElementMatchers; /** * Caches structure modifiers. + * * @author Kristian */ public class StructureCache { + + // prevent duplicate compilations + private static final Set COMPILING = new HashSet<>(); + // Structure modifiers - private static final ConcurrentMap> structureModifiers = new ConcurrentHashMap<>(); - // invocation cache for packets - private static final ConcurrentMap, Supplier> PACKET_INSTANCE_CREATORS = new ConcurrentHashMap<>(); + private static final Map, Supplier> PACKET_INSTANCE_CREATORS = new ConcurrentHashMap<>(); + private static final Map> STRUCTURE_MODIFIER_CACHE = new ConcurrentHashMap<>(); + // packet data serializer which always returns an empty nbt tag compound - private static boolean trickTried; + private static boolean TRICK_TRIED = false; private static ConstructorAccessor TRICKED_DATA_SERIALIZER; - private static final Set compiling = new HashSet<>(); - public static Object newPacket(Class clazz) { Object result = DefaultInstances.DEFAULT.create(clazz); if (result == null) { return PACKET_INSTANCE_CREATORS.computeIfAbsent(clazz, $ -> { - ConstructorAccessor accessor = Accessors.getConstructorAccessorOrNull(clazz, MinecraftReflection.getPacketDataSerializerClass()); + ConstructorAccessor accessor = Accessors.getConstructorAccessorOrNull(clazz, + MinecraftReflection.getPacketDataSerializerClass()); if (accessor != null) { return () -> { try { @@ -88,6 +89,7 @@ public static Object newPacket(Class clazz) { /** * Creates an empty Minecraft packet of the given type. + * * @param type - packet type. * @return Created packet. */ @@ -97,6 +99,7 @@ public static Object newPacket(PacketType type) { /** * Retrieve a cached structure modifier for the given packet type. + * * @param type - packet type. * @return A structure modifier. */ @@ -107,6 +110,7 @@ public static StructureModifier getStructure(PacketType type) { /** * Retrieve a cached structure modifier given a packet type. + * * @param packetType - packet type. * @return A structure modifier. */ @@ -117,8 +121,9 @@ public static StructureModifier getStructure(Class packetType) { /** * Retrieve a cached structure modifier given a packet type. + * * @param packetType - packet type. - * @param compile - whether or not to asynchronously compile the structure modifier. + * @param compile - whether or not to asynchronously compile the structure modifier. * @return A structure modifier. */ public static StructureModifier getStructure(Class packetType, boolean compile) { @@ -130,53 +135,43 @@ public static StructureModifier getStructure(Class packetType, boolea /** * Retrieve a cached structure modifier for the given packet type. - * @param type - packet type. - * @param compile - whether or not to asynchronously compile the structure modifier. + * + * @param packetType - packet type. + * @param compile - whether or not to asynchronously compile the structure modifier. * @return A structure modifier. */ - public static StructureModifier getStructure(final PacketType type, boolean compile) { - Preconditions.checkNotNull(type, "type cannot be null"); - StructureModifier result = structureModifiers.get(type); - - // We don't want to create this for every lookup - if (result == null) { - // Use the vanilla class definition - final StructureModifier value = new StructureModifier<>( - PacketRegistry.getPacketClassFromType(type), MinecraftReflection.getPacketClass(), true); - - result = structureModifiers.putIfAbsent(type, value); - - // We may end up creating multiple modifiers, but we'll agree on which to use - if (result == null) { - result = value; + public static StructureModifier getStructure(final PacketType packetType, boolean compile) { + Preconditions.checkNotNull(packetType, "type cannot be null"); + + StructureModifier modifier = STRUCTURE_MODIFIER_CACHE.computeIfAbsent(packetType, type -> { + Class packetClass = PacketRegistry.getPacketClassFromType(type); + return new StructureModifier<>(packetClass, MinecraftReflection.getPacketClass(), true); + }); + + // check if we should compile the structure modifier now + if (compile && !(modifier instanceof CompiledStructureModifier) && COMPILING.add(packetType)) { + // compile now + BackgroundCompiler compiler = BackgroundCompiler.getInstance(); + if (compiler != null) { + compiler.scheduleCompilation( + modifier, + compiled -> STRUCTURE_MODIFIER_CACHE.put(packetType, compiled)); } } - // Automatically compile the structure modifier - if (compile && !(result instanceof CompiledStructureModifier)) { - // Compilation is many orders of magnitude slower than synchronization - synchronized (compiling) { - final BackgroundCompiler compiler = BackgroundCompiler.getInstance(); - - if (!compiling.contains(type) && compiler != null) { - compiler.scheduleCompilation(result, - compiledModifier -> structureModifiers.put(type, compiledModifier)); - compiling.add(type); - } - } - } - return result; + return modifier; } /** * Creates a packet data serializer sub-class if needed to allow the fixed read of a NbtTagCompound because of a null * check in the MapChunk packet constructor. + * * @return an accessor to a constructor which creates a data serializer. */ private static ConstructorAccessor getTrickDataSerializerOrNull() { - if (TRICKED_DATA_SERIALIZER == null && !trickTried) { + if (TRICKED_DATA_SERIALIZER == null && !TRICK_TRIED) { // ensure that we only try once to create the class - trickTried = true; + TRICK_TRIED = true; try { // create an empty instance of a nbt tag compound that we can re-use when needed Object compound = Accessors.getConstructorAccessor(MinecraftReflection.getNBTCompoundClass()).invoke(); diff --git a/src/main/java/com/comphenix/protocol/injector/netty/AbstractPlayerHandler.java b/src/main/java/com/comphenix/protocol/injector/netty/AbstractPlayerHandler.java deleted file mode 100644 index 44880e504..000000000 --- a/src/main/java/com/comphenix/protocol/injector/netty/AbstractPlayerHandler.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.comphenix.protocol.injector.netty; - -import java.io.DataInputStream; -import java.util.Set; - -import org.bukkit.entity.Player; - -import com.comphenix.protocol.PacketType; -import com.comphenix.protocol.concurrency.PacketTypeSet; -import com.comphenix.protocol.events.ListenerOptions; -import com.comphenix.protocol.events.PacketListener; -import com.comphenix.protocol.injector.GamePhase; -import com.comphenix.protocol.injector.PlayerInjectHooks; -import com.comphenix.protocol.injector.player.PlayerInjectionHandler; - -public abstract class AbstractPlayerHandler implements PlayerInjectionHandler { - protected PacketTypeSet sendingFilters; - - public AbstractPlayerHandler(PacketTypeSet sendingFilters) { - this.sendingFilters = sendingFilters; - } - - @Override - public void setPlayerHook(GamePhase phase, PlayerInjectHooks playerHook) { - throw new UnsupportedOperationException("This is not needed in Spigot."); - } - - @Override - public void setPlayerHook(PlayerInjectHooks playerHook) { - throw new UnsupportedOperationException("This is not needed in Spigot."); - } - - @Override - public void addPacketHandler(PacketType type, Set options) { - sendingFilters.addType(type); - } - - @Override - public void removePacketHandler(PacketType type) { - sendingFilters.removeType(type); - } - - @Override - public Set getSendingFilters() { - return sendingFilters.values(); - } - - @Override - public void close() { - sendingFilters.clear(); - } - - @Override - public PlayerInjectHooks getPlayerHook(GamePhase phase) { - return PlayerInjectHooks.NETWORK_SERVER_OBJECT; - } - - @Override - public boolean canRecievePackets() { - return true; - } - - @Override - public PlayerInjectHooks getPlayerHook() { - // Pretend that we do - return PlayerInjectHooks.NETWORK_SERVER_OBJECT; - } - - @Override - public Player getPlayerByConnection(DataInputStream inputStream) throws InterruptedException { - throw new UnsupportedOperationException("This is not needed in Spigot."); - } - - @Override - public void checkListener(PacketListener listener) { - // They're all fine! - } - - @Override - public void checkListener(Set listeners) { - // Yes, really - } -} diff --git a/src/main/java/com/comphenix/protocol/injector/netty/BootstrapList.java b/src/main/java/com/comphenix/protocol/injector/netty/BootstrapList.java deleted file mode 100644 index 945bb5a58..000000000 --- a/src/main/java/com/comphenix/protocol/injector/netty/BootstrapList.java +++ /dev/null @@ -1,234 +0,0 @@ -/** - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2015 dmulloy2 - * - * 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.netty; - -import io.netty.channel.Channel; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelHandler; - -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.ListIterator; -import java.util.concurrent.Callable; - -import com.google.common.collect.Lists; - -public class BootstrapList implements List { - private List delegate; - private ChannelHandler handler; - - /** - * Construct a new bootstrap list. - * @param delegate - the delegate. - * @param handler - the channel handler to add. - */ - public BootstrapList(List delegate, ChannelHandler handler) { - this.delegate = delegate; - this.handler = handler; - - // Process all existing bootstraps - for (Object item : this) { - processElement(item); - } - } - - @Override - public synchronized boolean add(Object element) { - processElement(element); - return delegate.add(element); - } - - @Override - public synchronized boolean addAll(Collection collection) { - List copy = Lists.newArrayList(collection); - - // Process the collection before we pass it on - for (Object element : copy) { - processElement(element); - } - return delegate.addAll(copy); - } - - @Override - public synchronized Object set(int index, Object element) { - Object old = delegate.set(index, element); - - // Handle the old future, and the newly inserted future - if (old != element) { - unprocessElement(old); - processElement(element); - } - return old; - } - - /** - * Process a single element. - * @param element - the element. - */ - protected void processElement(Object element) { - if (element instanceof ChannelFuture) { - processBootstrap((ChannelFuture) element); - } - } - - /** - * Unprocess a single element. - * @param element - the element to unprocess. - */ - protected void unprocessElement(Object element) { - if (element instanceof ChannelFuture) { - unprocessBootstrap((ChannelFuture) element); - } - } - - /** - * Process a single channel future. - * @param future - the future. - */ - protected void processBootstrap(ChannelFuture future) { - // Important: Must be addFirst() - future.channel().pipeline().addFirst(handler); - } - - /** - * Revert any changes we made to the channel future. - * @param future - the future. - */ - protected void unprocessBootstrap(ChannelFuture future) { - final Channel channel = future.channel(); - - // For thread safety - see ChannelInjector.close() - channel.eventLoop().submit(new Callable() { - @Override - public Object call() throws Exception { - channel.pipeline().remove(handler); - return null; - } - }); - } - - /** - * Close and revert all changes. - */ - public synchronized void close() { - for (Object element : this) - unprocessElement(element); - } - - // Boiler plate - @Override - public synchronized int size() { - return delegate.size(); - } - - @Override - public synchronized boolean isEmpty() { - return delegate.isEmpty(); - } - - @Override - public boolean contains(Object o) { - return delegate.contains(o); - } - - @Override - public synchronized Iterator iterator() { - return delegate.iterator(); - } - - @Override - public synchronized Object[] toArray() { - return delegate.toArray(); - } - - @Override - public synchronized T[] toArray(T[] a) { - return delegate.toArray(a); - } - - @Override - public synchronized boolean remove(Object o) { - return delegate.remove(o); - } - - @Override - public synchronized boolean containsAll(Collection c) { - return delegate.containsAll(c); - } - - @Override - public synchronized boolean addAll(int index, Collection c) { - return delegate.addAll(index, c); - } - - @Override - public synchronized boolean removeAll(Collection c) { - return delegate.removeAll(c); - } - - @Override - public synchronized boolean retainAll(Collection c) { - return delegate.retainAll(c); - } - - @Override - public synchronized void clear() { - delegate.clear(); - } - - @Override - public synchronized Object get(int index) { - return delegate.get(index); - } - - @Override - public synchronized void add(int index, Object element) { - delegate.add(index, element); - } - - @Override - public synchronized Object remove(int index) { - return delegate.remove(index); - } - - @Override - public synchronized int indexOf(Object o) { - return delegate.indexOf(o); - } - - @Override - public synchronized int lastIndexOf(Object o) { - return delegate.lastIndexOf(o); - } - - @Override - public synchronized ListIterator listIterator() { - return delegate.listIterator(); - } - - @Override - public synchronized ListIterator listIterator(int index) { - return delegate.listIterator(index); - } - - @Override - public synchronized List subList(int fromIndex, int toIndex) { - return delegate.subList(fromIndex, toIndex); - } - // End boiler plate -} diff --git a/src/main/java/com/comphenix/protocol/injector/netty/ChannelInjector.java b/src/main/java/com/comphenix/protocol/injector/netty/ChannelInjector.java deleted file mode 100644 index 37acbf803..000000000 --- a/src/main/java/com/comphenix/protocol/injector/netty/ChannelInjector.java +++ /dev/null @@ -1,1028 +0,0 @@ -/** - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2015 dmulloy2 - * - * 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.netty; - -import com.comphenix.protocol.PacketType; -import com.comphenix.protocol.PacketType.Protocol; -import com.comphenix.protocol.ProtocolLibrary; -import com.comphenix.protocol.error.Report; -import com.comphenix.protocol.error.ReportType; -import com.comphenix.protocol.events.ConnectionSide; -import com.comphenix.protocol.events.NetworkMarker; -import com.comphenix.protocol.events.PacketEvent; -import com.comphenix.protocol.injector.NetworkProcessor; -import com.comphenix.protocol.injector.server.SocketInjector; -import com.comphenix.protocol.reflect.FuzzyReflection; -import com.comphenix.protocol.reflect.VolatileField; -import com.comphenix.protocol.reflect.accessors.Accessors; -import com.comphenix.protocol.reflect.accessors.FieldAccessor; -import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract; -import com.comphenix.protocol.utility.ByteBuddyFactory; -import com.comphenix.protocol.utility.ByteBuddyGenerated; -import com.comphenix.protocol.utility.MinecraftFields; -import com.comphenix.protocol.utility.MinecraftMethods; -import com.comphenix.protocol.utility.MinecraftProtocolVersion; -import com.comphenix.protocol.utility.MinecraftReflection; -import com.comphenix.protocol.utility.ObjectReconstructor; -import com.comphenix.protocol.wrappers.Pair; -import com.comphenix.protocol.wrappers.WrappedGameProfile; - -import com.google.common.base.Preconditions; - -import io.netty.buffer.ByteBuf; -import io.netty.channel.*; -import io.netty.handler.codec.ByteToMessageDecoder; -import io.netty.handler.codec.MessageToByteEncoder; -import io.netty.util.AttributeKey; -import io.netty.util.internal.TypeParameterMatcher; - -import org.apache.commons.lang.Validate; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.net.Socket; -import java.net.SocketAddress; -import java.util.*; -import java.util.Map.Entry; -import java.util.concurrent.Callable; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; -/** - * Represents a channel injector. - * @author Kristian - */ -public class ChannelInjector extends ByteToMessageDecoder implements Injector { - private static final ReportType REPORT_CANNOT_INTERCEPT_SERVER_PACKET = new ReportType("Unable to intercept a written server packet."); - private static final ReportType REPORT_CANNOT_INTERCEPT_CLIENT_PACKET = new ReportType("Unable to intercept a read client packet."); - private static final ReportType REPORT_CANNOT_EXECUTE_IN_CHANNEL_THREAD = new ReportType("Cannot execute code in channel thread."); - private static final ReportType REPORT_CANNOT_SEND_PACKET = new ReportType("Unable to send packet %s to %s"); - - /** - * Indicates that a packet has bypassed packet listeners. - */ - private static final PacketEvent BYPASSED_PACKET = new PacketEvent(ChannelInjector.class); - - // The login packet - private static Class PACKET_LOGIN_CLIENT = null; - private static FieldAccessor LOGIN_GAME_PROFILE = null; - - // Versioning - private static Class PACKET_SET_PROTOCOL = null; - private static FieldAccessor PROTOCOL_VERSION_ACCESSOR = null; - - private static AtomicInteger keyId = new AtomicInteger(); - private static AttributeKey PROTOCOL_KEY; - - static { - try { - PROTOCOL_KEY = AttributeKey.valueOf("PROTOCOL-" + keyId.getAndIncrement()); - } catch (Exception ex) { - throw new RuntimeException("Encountered an error caused by a reload! Please properly restart your server!", ex); - } - - Method hiddenClassMethod = null; - try { - if (Float.parseFloat(System.getProperty("java.class.version")) >= 59) { - hiddenClassMethod = Class.class.getMethod("isHidden"); - } - } catch (NoSuchMethodException ignored) { - - } - IS_HIDDEN_CLASS = hiddenClassMethod; - } - - // Starting in Java 15 (59), the lambdas are hidden classes and we cannot use reflection to update - // the values anymore. Instead, the object will have to be reconstructed. - private static final Map, ObjectReconstructor> RECONSTRUCTORS = new ConcurrentHashMap<>(); - private static final Method IS_HIDDEN_CLASS; - - // Saved accessors - private Method decodeBuffer; - private Method encodeBuffer; - private static FieldAccessor ENCODER_TYPE_MATCHER; - - // For retrieving the protocol - private static FieldAccessor PROTOCOL_ACCESSOR; - - // The factory that created this injector - private final InjectionFactory factory; - - // The player, or temporary player - private Player player; - private Player updated; - private String playerName; - - // The player connection - private Object playerConnection; - - // The current network manager and channel - private final Object networkManager; - private final Channel originalChannel; - private final VolatileField channelField; - - // Known network markers - private final Map packetMarker = new WeakHashMap<>(); - - /** - * Indicate that this packet has been processed by event listeners. - *

    - * This must never be set outside the channel pipeline's thread. - */ - private PacketEvent currentEvent; - - /** - * A packet event that should be processed by the write method. - */ - private PacketEvent finalEvent; - - /** - * A queue of packets that were sent with filtered=false - */ - private final PacketFilterQueue unfilteredProcessedPackets = new PacketFilterQueue(); - - // Other handlers - private ByteToMessageDecoder vanillaDecoder; - private MessageToByteEncoder vanillaEncoder; - - private final Deque finishQueue = new ArrayDeque<>(); - - // The channel listener - private final ChannelListener channelListener; - - // Processing network markers - private final NetworkProcessor processor; - - // Closed - private boolean injected; - private boolean closed; - - /** - * Construct a new channel injector. - * @param player - the current player, or temporary player. - * @param networkManager - its network manager. - * @param channel - its channel. - * @param channelListener - a listener. - * @param factory - the factory that created this injector - */ - ChannelInjector(Player player, Object networkManager, Channel channel, ChannelListener channelListener, InjectionFactory factory) { - this.player = Preconditions.checkNotNull(player, "player cannot be NULL"); - this.networkManager = Preconditions.checkNotNull(networkManager, "networkMananger cannot be NULL"); - this.originalChannel = Preconditions.checkNotNull(channel, "channel cannot be NULL"); - this.channelListener = Preconditions.checkNotNull(channelListener, "channelListener cannot be NULL"); - this.factory = Preconditions.checkNotNull(factory, "factory cannot be NULL"); - this.processor = new NetworkProcessor(ProtocolLibrary.getErrorReporter()); - - // Get the channel field - this.channelField = new VolatileField(FuzzyReflection - .fromObject(networkManager, true) - .getFieldByType("channel", Channel.class), networkManager, true); - } - - /** - * Get the version of the current protocol. - * @return The version. - */ - @Override - public int getProtocolVersion() { - Integer value = originalChannel.attr(PROTOCOL_KEY).get(); - return value != null ? value : MinecraftProtocolVersion.getCurrentVersion(); - } - - private void updateBufferMethods() { - try { - decodeBuffer = vanillaDecoder.getClass().getDeclaredMethod("decode", - ChannelHandlerContext.class, ByteBuf.class, List.class); - decodeBuffer.setAccessible(true); - } catch (NoSuchMethodException ex) { - throw new IllegalArgumentException("Unable to find decode method in " + vanillaDecoder.getClass()); - } - - try { - encodeBuffer = vanillaEncoder.getClass().getDeclaredMethod("encode", - ChannelHandlerContext.class, Object.class, ByteBuf.class); - encodeBuffer.setAccessible(true); - } catch (NoSuchMethodException ex) { - throw new IllegalArgumentException("Unable to find encode method in " + vanillaEncoder.getClass()); - } - } - - @Override - @SuppressWarnings("unchecked") - public boolean inject() { - synchronized (networkManager) { - if (closed) - return false; - if (originalChannel instanceof ByteBuddyFactory) - return false; - if (!originalChannel.isActive()) - return false; - - // Main thread? We should synchronize with the channel thread, otherwise we might see a - // pipeline with only some of the handlers removed - if (Bukkit.isPrimaryThread()) { - // Just like in the close() method, we'll avoid blocking the main thread - executeInChannelThread(this::inject); - return false; // We don't know - } - - // Don't inject the same channel twice - if (findChannelHandler(originalChannel, ChannelInjector.class) != null) { - return false; - } - - // Get the vanilla decoder, so we don't have to replicate the work - vanillaDecoder = (ByteToMessageDecoder) originalChannel.pipeline().get("decoder"); - vanillaEncoder = (MessageToByteEncoder) originalChannel.pipeline().get("encoder"); - - if (vanillaDecoder == null) - throw new IllegalArgumentException("Unable to find vanilla decoder in " + originalChannel.pipeline()); - if (vanillaEncoder == null) - throw new IllegalArgumentException("Unable to find vanilla encoder in " + originalChannel.pipeline()); - patchEncoder(vanillaEncoder); - - updateBufferMethods(); - - // Intercept sent packets - MessageToByteEncoder protocolEncoder = new MessageToByteEncoder() { - @Override - protected void encode(ChannelHandlerContext ctx, Object packet, ByteBuf output) throws Exception { - if (packet instanceof WirePacket) { - // Special case for wire format - ChannelInjector.this.encodeWirePacket((WirePacket) packet, output); - } else { - ChannelInjector.this.encode(ctx, packet, output); - } - } - - @Override - public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception { - super.write(ctx, packet, promise); - ChannelInjector.this.finalWrite(packet); - } - - @Override - public boolean acceptOutboundMessage(Object msg) { - return msg instanceof WirePacket || MinecraftReflection.getPacketClass().isAssignableFrom(msg.getClass()); - } - }; - - // Intercept recieved packets - ChannelInboundHandlerAdapter finishHandler = new ChannelInboundHandlerAdapter() { - @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) { - // Execute context first - ctx.fireChannelRead(msg); - ChannelInjector.this.finishRead(); - } - }; - - // Insert our handlers - note that we effectively replace the vanilla encoder/decoder - originalChannel.pipeline().addBefore("decoder", "protocol_lib_decoder", this); - originalChannel.pipeline().addBefore("protocol_lib_decoder", "protocol_lib_finish", finishHandler); - originalChannel.pipeline().addAfter("encoder", "protocol_lib_encoder", protocolEncoder); - - // Intercept all write methods - channelField.setValue(new ChannelProxy(originalChannel, MinecraftReflection.getPacketClass()) { - // Compatibility with Spigot 1.8 - private final PipelineProxy pipelineProxy = new PipelineProxy(originalChannel.pipeline(), this) { - @Override - public ChannelPipeline addBefore(String baseName, String name, ChannelHandler handler) { - // Correct the position of the decoder - if ("decoder".equals(baseName)) { - if (super.get("protocol_lib_decoder") != null && guessCompression(handler)) { - super.addBefore("protocol_lib_decoder", name, handler); - return this; - } - } - - return super.addBefore(baseName, name, handler); - } - }; - - @Override - public ChannelPipeline pipeline() { - return pipelineProxy; - } - - @Override - protected Callable onMessageScheduled(final Callable callable, FieldAccessor packetAccessor) { - Pair, PacketEvent> handled = handleScheduled(callable, packetAccessor); - - // Handle cancelled events - if (handled.getSecond() != null && handled.getSecond().isCancelled()) - return null; - - return () -> { - T result; - - // This field must only be updated in the pipeline thread - currentEvent = handled.getSecond(); - result = handled.getFirst().call(); - currentEvent = null; - return result; - }; - } - - @Override - protected Runnable onMessageScheduled(final Runnable runnable, FieldAccessor packetAccessor) { - Pair handled = handleScheduled(runnable, packetAccessor); - - // Handle cancelled events - if (handled.getSecond() != null && handled.getSecond().isCancelled()) - return null; - - return () -> { - currentEvent = handled.getSecond(); - handled.getFirst().run(); - currentEvent = null; - }; - } - - Pair handleScheduled(T instance, FieldAccessor accessor) { - // Let the filters handle this packet - Object original = accessor.get(instance); - - // See if we've been instructed not to process packets - if (unfilteredProcessedPackets.contains(original)) { - NetworkMarker marker = getMarker(original); - - if (marker != null) { - PacketEvent result = new PacketEvent(ChannelInjector.class); - result.setNetworkMarker(marker); - return new Pair<>(instance, result); - } else { - return new Pair<>(instance, BYPASSED_PACKET); - } - } - - PacketEvent event = processSending(original); - if (event != null && !event.isCancelled()) { - Object changed = event.getPacket().getHandle(); - - // Change packet to be scheduled - if (original != changed) { - instance = (T) (isHiddenClass(instance.getClass()) ? - updatePacketMessageReconstruct(instance, changed, accessor) : - updatePacketMessageSetReflection(instance, changed, accessor)); - } - } - return new Pair<>(instance, event != null ? event : BYPASSED_PACKET); - } - }); - - injected = true; - return true; - } - } - - /** - * Changes the packet in a packet message using a {@link FieldAccessor}. - */ - private static Object updatePacketMessageSetReflection(Object instance, Object newPacket, FieldAccessor accessor) { - accessor.set(instance, newPacket); - return instance; - } - - /** - * Changes the packet in a packet message using a {@link ObjectReconstructor}. - */ - private static Object updatePacketMessageReconstruct(Object instance, Object newPacket, FieldAccessor accessor) { - final ObjectReconstructor objectReconstructor = - RECONSTRUCTORS.computeIfAbsent(instance.getClass(), ObjectReconstructor::new); - - final Object[] values = objectReconstructor.getValues(instance); - final Field[] fields = objectReconstructor.getFields(); - for (int idx = 0; idx < fields.length; ++idx) - if (fields[idx].equals(accessor.getField())) - values[idx] = newPacket; - - return objectReconstructor.reconstruct(values); - } - - /** - * Determine if the given object is a compressor or decompressor. - * @param handler - object to test. - * @return TRUE if it is, FALSE if not or unknown. - */ - private boolean guessCompression(ChannelHandler handler) { - String className = handler != null ? handler.getClass().getCanonicalName() : ""; - return className.contains("Compressor") || className.contains("Decompressor"); - } - - /** - * Process a given message on the packet listeners. - * @param message - the message/packet. - * @return The resulting message/packet. - */ - private PacketEvent processSending(Object message) { - return channelListener.onPacketSending(ChannelInjector.this, message, getMarker(message)); - } - - /** - * This method patches the encoder so that it skips already created packets. - * @param encoder - the encoder to patch. - */ - private void patchEncoder(MessageToByteEncoder encoder) { - if (ENCODER_TYPE_MATCHER == null) { - ENCODER_TYPE_MATCHER = Accessors.getFieldAccessor(encoder.getClass(), "matcher", true); - } - - ENCODER_TYPE_MATCHER.set(encoder, TypeParameterMatcher.get(MinecraftReflection.getPacketClass())); - } - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { - if (channelListener.isDebug()) { - cause.printStackTrace(); - } - - super.exceptionCaught(ctx, cause); - } - - private void encodeWirePacket(WirePacket packet, ByteBuf output) { - packet.writeId(output); - packet.writeBytes(output); - } - - /** - * Encode a packet to a byte buffer, taking over for the standard Minecraft encoder. - * @param ctx - the current context. - * @param packet - the packet to encode to a byte array. - * @param output - the output byte array. - */ - private void encode(ChannelHandlerContext ctx, Object packet, ByteBuf output) throws Exception { - NetworkMarker marker = null; - PacketEvent event = currentEvent; - - try { - // Skip every kind of non-filtered packet - if (unfilteredProcessedPackets.remove(packet)) { - return; - } - - // This packet has not been seen by the main thread - if (event == null) { - Class clazz = packet.getClass(); - - // Schedule the transmission on the main thread instead - if (channelListener.hasMainThreadListener(clazz)) { - // Delay the packet - scheduleMainThread(packet); - packet = null; - } else { - event = processSending(packet); - - // Handle the output - if (event != null) { - packet = !event.isCancelled() ? event.getPacket().getHandle() : null; - } - } - } - - if (event != null) { - // Retrieve marker without accidentally constructing it - marker = NetworkMarker.getNetworkMarker(event); - } - - // Process output handler - if (packet != null && event != null && NetworkMarker.hasOutputHandlers(marker)) { - ByteBuf packetBuffer = ctx.alloc().buffer(); - encodeBuffer.invoke(vanillaEncoder, ctx, packet, packetBuffer); - - // Let each handler prepare the actual output - byte[] data = processor.processOutput(event, marker, getBytes(packetBuffer)); - - // Write the result - output.writeBytes(data); - packet = null; - - // Sent listeners? - finalEvent = event; - } - } catch (InvocationTargetException ex) { - if (ex.getCause() instanceof Exception) { - throw (Exception) ex.getCause(); - } - } catch (Exception e) { - channelListener.getReporter().reportDetailed(this, - Report.newBuilder(REPORT_CANNOT_INTERCEPT_SERVER_PACKET).callerParam(packet).error(e).build()); - } finally { - // Attempt to handle the packet nevertheless - if (packet != null) { - try { - encodeBuffer.invoke(vanillaEncoder, ctx, packet, output); - } catch (InvocationTargetException ex) { - if (ex.getCause() instanceof Exception) { - //noinspection ThrowFromFinallyBlock - throw (Exception) ex.getCause(); - } - } - - finalEvent = event; - } - } - } - - /** - * Invoked when a packet has been written to the channel - */ - private void finalWrite(Object packet) { - PacketEvent event = finalEvent; - - if (event != null) { - // Necessary to prevent infinite loops - finalEvent = null; - currentEvent = null; - - processor.invokePostEvent(event, NetworkMarker.getNetworkMarker(event)); - - // remove the stored packet markers to prevent memory leaks - // See: https://github.com/dmulloy2/ProtocolLib/issues/1509 - packetMarker.remove(packet); - } - } - - private void scheduleMainThread(final Object packetCopy) { - // Don't use BukkitExecutors for this - it has a bit of overhead - Bukkit.getScheduler().scheduleSyncDelayedTask(factory.getPlugin(), () -> { - if (!closed) { - invokeSendPacket(packetCopy); - } - }); - } - - @Override - protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuffer, List packets) throws Exception { - try { - try { - decodeBuffer.invoke(vanillaDecoder, ctx, byteBuffer, packets); - } catch (IllegalArgumentException ex) { - updateBufferMethods(); - decodeBuffer.invoke(vanillaDecoder, ctx, byteBuffer, packets); - } - - // Reset queue - finishQueue.clear(); - - for (ListIterator it = packets.listIterator(); it.hasNext(); ) { - Object input = it.next(); - Class packetClass = input.getClass(); - NetworkMarker marker = null; - - // Special case! - handleLogin(packetClass, input); - - if (channelListener.includeBuffer(packetClass)) { - if (byteBuffer.readableBytes() != 0) { - byteBuffer.resetReaderIndex(); - marker = new NettyNetworkMarker(ConnectionSide.CLIENT_SIDE, getBytes(byteBuffer)); - } - } - - PacketEvent output = channelListener.onPacketReceiving(this, input, marker); - - // Handle packet changes - if (output != null) { - if (output.isCancelled()) { - it.remove(); - } else { - if (output.getPacket().getHandle() != input) { - it.set(output.getPacket().getHandle()); - } - - finishQueue.addLast(output); - } - } - } - } catch (InvocationTargetException ex) { - if (ex.getCause() instanceof Exception) { - throw (Exception) ex.getCause(); - } - } catch (Exception e) { - channelListener.getReporter().reportDetailed(this, - Report.newBuilder(REPORT_CANNOT_INTERCEPT_CLIENT_PACKET).callerParam(byteBuffer).error(e).build()); - } - } - - /** - * Invoked after our decoder - */ - private void finishRead() { - // Assume same order - PacketEvent event = finishQueue.pollFirst(); - - if (event != null) { - NetworkMarker marker = NetworkMarker.getNetworkMarker(event); - - if (marker != null) { - processor.invokePostEvent(event, marker); - } - } - } - - /** - * Invoked when we may need to handle the login packet. - * @param packetClass - the packet class. - * @param packet - the packet. - */ - private void handleLogin(Class packetClass, Object packet) { - // Try to find the login packet class - if (PACKET_LOGIN_CLIENT == null) { - PACKET_LOGIN_CLIENT = PacketType.Login.Client.START.getPacketClass(); - } - - // If we can't, there's an issue - if (PACKET_LOGIN_CLIENT == null) { - throw new IllegalStateException("Failed to obtain login start packet. Did you build Spigot with BuildTools?"); - } - - if (LOGIN_GAME_PROFILE == null) { - LOGIN_GAME_PROFILE = Accessors.getFieldAccessor(PACKET_LOGIN_CLIENT, MinecraftReflection.getGameProfileClass(), true); - } - - // See if we are dealing with the login packet - if (PACKET_LOGIN_CLIENT.equals(packetClass)) { - WrappedGameProfile profile = WrappedGameProfile.fromHandle(LOGIN_GAME_PROFILE.get(packet)); - - // Save the channel injector - factory.cacheInjector(profile.getName(), this); - } - - if (PACKET_SET_PROTOCOL == null) { - try { - PACKET_SET_PROTOCOL = PacketType.Handshake.Client.SET_PROTOCOL.getPacketClass(); - - // resolve the protocol version field here - if we're unable to find it we don't need to worry about it - Field protocolVersionField = FuzzyReflection.fromClass(PACKET_SET_PROTOCOL, true).getField(FuzzyFieldContract.newBuilder() - .banModifier(Modifier.STATIC) - .typeExact(int.class) - .build()); - PROTOCOL_VERSION_ACCESSOR = Accessors.getFieldAccessor(protocolVersionField); - } catch (Throwable ex) { - PACKET_SET_PROTOCOL = getClass(); // If we can't find it don't worry about it - } - } - - if (PACKET_SET_PROTOCOL.equals(packetClass)) { - try { - int protocol = (int) PROTOCOL_VERSION_ACCESSOR.get(packet); - originalChannel.attr(PROTOCOL_KEY).set(protocol); - } catch (Throwable ex) { - // Oh well - } - } - } - - @Override - public void channelActive(ChannelHandlerContext ctx) throws Exception { - super.channelActive(ctx); - - // See NetworkManager.channelActive(ChannelHandlerContext) for why - if (channelField != null) { - channelField.refreshValue(); - } - } - - /** - * Retrieve every byte in the given byte buffer. - * @param buffer - the buffer. - * @return The bytes. - */ - private byte[] getBytes(ByteBuf buffer) { - byte[] data = new byte[buffer.readableBytes()]; - - buffer.readBytes(data); - return data; - } - - /** - * Disconnect the current player. - * @param message - the disconnect message, if possible. - */ - private void disconnect(String message) { - // If we're logging in, we can only close the channel - if (playerConnection == null || player instanceof ByteBuddyGenerated) { - originalChannel.disconnect(); - } else { - // Call the disconnect method - try { - MinecraftMethods.getDisconnectMethod(playerConnection.getClass()). - invoke(playerConnection, message); - } catch (Exception e) { - throw new IllegalArgumentException("Unable to invoke disconnect method.", e); - } - } - } - - @Override - public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) { - saveMarker(packet, marker); - - if (!filtered) { - unfilteredProcessedPackets.add(packet); - } - - invokeSendPacket(packet); - } - - /** - * Invoke the sendPacket method in Minecraft. - * @param packet - the packet to send. - */ - private void invokeSendPacket(Object packet) { - Validate.isTrue(!closed, "cannot send packets to a closed channel"); - - // Attempt to send the packet with NetworkMarker.handle(), or the PlayerConnection if its active - try { - if (player instanceof ByteBuddyGenerated) { - MinecraftMethods.getNetworkManagerHandleMethod().invoke(networkManager, packet); - } else { - MinecraftMethods.getSendPacketMethod().invoke(getPlayerConnection(), packet); - } - } catch (Throwable ex) { - ProtocolLibrary.getErrorReporter().reportWarning(this, - Report.newBuilder(REPORT_CANNOT_SEND_PACKET).messageParam(packet, playerName).error(ex).build()); - } - } - - @Override - public void recieveClientPacket(final Object packet) { - // TODO: Ensure the packet listeners are executed in the channel thread. - - // Execute this in the channel thread - Runnable action = () -> { - try { - MinecraftMethods.getNetworkManagerReadPacketMethod().invoke(networkManager, null, packet); - } catch (Exception e) { - // Inform the user - ProtocolLibrary.getErrorReporter().reportMinimal(factory.getPlugin(), "recieveClientPacket", e); - } - }; - - // Execute in the worker thread - if (originalChannel.eventLoop().inEventLoop()) { - action.run(); - } else { - originalChannel.eventLoop().execute(action); - } - } - - @Override - public Protocol getCurrentProtocol() { - if (PROTOCOL_ACCESSOR == null) { - PROTOCOL_ACCESSOR = Accessors.getFieldAccessor( - networkManager.getClass(), MinecraftReflection.getEnumProtocolClass(), true); - } - return Protocol.fromVanilla((Enum) PROTOCOL_ACCESSOR.get(networkManager)); - } - - /** - * Retrieve the player connection of the current player. - * @return The player connection. - */ - private Object getPlayerConnection() { - if (playerConnection == null) { - Player player = getPlayer(); - if (player == null) { - throw new IllegalStateException("cannot send packet to offline player" + (playerName != null ? " " + playerName : "")); - } - - playerConnection = MinecraftFields.getPlayerConnection(player); - } - - return playerConnection; - } - - @Override - public NetworkMarker getMarker(Object packet) { - return packetMarker.get(packet); - } - - @Override - public void saveMarker(Object packet, NetworkMarker marker) { - if (marker != null) { - packetMarker.put(packet, marker); - } - } - - @Override - public Player getPlayer() { - if (player == null && playerName != null) { - return Bukkit.getPlayerExact(playerName); - } - - return player; - } - - /** - * Set the player instance. - * @param player - current instance. - */ - @Override - public void setPlayer(Player player) { - this.player = player; - this.playerName = player.getName(); - } - - /** - * Set the updated player instance. - * @param updated - updated instance. - */ - @Override - public void setUpdatedPlayer(Player updated) { - this.updated = updated; - this.playerName = updated.getName(); - } - - @Override - public boolean isInjected() { - return injected; - } - - /** - * Determine if this channel has been closed and cleaned up. - * @return TRUE if it has, FALSE otherwise. - */ - @Override - public boolean isClosed() { - return closed; - } - - @Override - public void close() { - if (!closed) { - closed = true; - - if (injected) { - channelField.revertValue(); - - // Calling remove() in the main thread will block the main thread, which may lead - // to a deadlock: - // http://pastebin.com/L3SBVKzp - // - // ProtocolLib executes this close() method through a PlayerQuitEvent in the main thread, - // which has implicitly aquired a lock on SimplePluginManager (see SimplePluginManager.callEvent(Event)). - // Unfortunately, the remove() method will schedule the removal on one of the Netty worker threads if - // it's called from a different thread, blocking until the removal has been confirmed. - // - // This is bad enough (Rule #1: Don't block the main thread), but the real trouble starts if the same - // worker thread happens to be handling a server ping connection when this removal task is scheduled. - // In that case, it may attempt to invoke an asynchronous ServerPingEvent (see PacketStatusListener) - // using SimplePluginManager.callEvent(). But, since this has already been locked by the main thread, - // we end up with a deadlock. The main thread is waiting for the worker thread to process the task, and - // the worker thread is waiting for the main thread to finish executing PlayerQuitEvent. - // - // TL;DR: Concurrency is hard. - executeInChannelThread(() -> { - String[] handlers = new String[] { - "protocol_lib_decoder", "protocol_lib_finish", "protocol_lib_encoder" - }; - - for (String handler : handlers) { - try { - originalChannel.pipeline().remove(handler); - } catch (NoSuchElementException e) { - // Ignore - } - } - }); - - // Clear cache - factory.invalidate(player); - - // Clear player instances - // Should help fix memory leaks - this.player = null; - this.updated = null; - } - } - } - - - - /** - * Execute a specific command in the channel thread. - *

    - * Exceptions are printed through the standard error reporter mechanism. - * @param command - the command to execute. - */ - private void executeInChannelThread(final Runnable command) { - originalChannel.eventLoop().execute(() -> { - try { - command.run(); - } catch (Exception e) { - ProtocolLibrary.getErrorReporter().reportDetailed(ChannelInjector.this, - Report.newBuilder(REPORT_CANNOT_EXECUTE_IN_CHANNEL_THREAD).error(e).build()); - } - }); - } - - /** - * Find the first channel handler that is assignable to a given type. - * @param channel - the channel. - * @param clazz - the type. - * @return The first handler, or NULL. - */ - static ChannelHandler findChannelHandler(Channel channel, Class clazz) { - for (Entry entry : channel.pipeline()) { - if (clazz.isAssignableFrom(entry.getValue().getClass())) { - return entry.getValue(); - } - } - return null; - } - - /** - * Represents a socket injector that foreards to the current channel injector. - * @author Kristian - */ - public static class ChannelSocketInjector implements SocketInjector { - private final ChannelInjector injector; - - ChannelSocketInjector(ChannelInjector injector) { - this.injector = Preconditions.checkNotNull(injector, "injector cannot be NULL"); - } - - @Override - public Socket getSocket() { - return SocketAdapter.adapt(injector.originalChannel); - } - - @Override - public SocketAddress getAddress() { - return injector.originalChannel.remoteAddress(); - } - - @Override - public void disconnect(String message) { - injector.disconnect(message); - } - - @Override - public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) { - injector.sendServerPacket(packet, marker, filtered); - } - - @Override - public Player getPlayer() { - return injector.getPlayer(); - } - - @Override - public Player getUpdatedPlayer() { - return injector.updated != null ? injector.updated : getPlayer(); - } - - @Override - public void transferState(SocketInjector delegate) { - // Do nothing - } - - @Override - public void setUpdatedPlayer(Player updatedPlayer) { - injector.setPlayer(updatedPlayer); - } - - @Override - public boolean isConnected() { - return injector.originalChannel.isActive(); - } - - ChannelInjector getChannelInjector() { - return injector; - } - } - - public Channel getChannel() { - return originalChannel; - } - - private static boolean isHiddenClass(Class clz) { - if (IS_HIDDEN_CLASS == null) { - return false; - } - try { - return (Boolean) IS_HIDDEN_CLASS.invoke(clz); - } catch (Exception e) { - throw new RuntimeException("Failed to determine whether class '" + clz.getName() + "' is hidden or not", e); - } - } -} diff --git a/src/main/java/com/comphenix/protocol/injector/netty/ChannelListener.java b/src/main/java/com/comphenix/protocol/injector/netty/ChannelListener.java index ababc7853..c00a2d0dd 100644 --- a/src/main/java/com/comphenix/protocol/injector/netty/ChannelListener.java +++ b/src/main/java/com/comphenix/protocol/injector/netty/ChannelListener.java @@ -1,66 +1,70 @@ package com.comphenix.protocol.injector.netty; +import com.comphenix.protocol.PacketType; import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.events.NetworkMarker; import com.comphenix.protocol.events.PacketEvent; /** * Represents a listener for received or sent packets. + * * @author Kristian */ public interface ChannelListener { + /** * Invoked when a packet is being sent to the client. *

    * This is invoked on the main thread. + * * @param injector - the channel injector. - * @param packet - the packet. - * @param marker - the network marker. + * @param packet - the packet. + * @param marker - the network marker. * @return The packet even that was passed to the listeners, with a possible packet change, or NULL. */ - public PacketEvent onPacketSending(Injector injector, Object packet, NetworkMarker marker); - + PacketEvent onPacketSending(Injector injector, Object packet, NetworkMarker marker); + /** * Invoked when a packet is being received from a client. *

    * This is invoked on an asynchronous worker thread. + * * @param injector - the channel injector. - * @param packet - the packet. - * @param marker - the associated network marker, if any. + * @param packet - the packet. + * @param marker - the associated network marker, if any. * @return The packet even that was passed to the listeners, with a possible packet change, or NULL. */ - public PacketEvent onPacketReceiving(Injector injector, Object packet, NetworkMarker marker); - + PacketEvent onPacketReceiving(Injector injector, Object packet, NetworkMarker marker); + /** * Determine if there is a packet listener for the given packet. + * * @param packetClass - the packet class to check. * @return TRUE if there is such a listener, FALSE otherwise. */ - public boolean hasListener(Class packetClass); - + boolean hasListener(Class packetClass); + /** * Determine if there is a server packet listener that must be executed on the main thread. + * * @param packetClass - the packet class to check. * @return TRUE if there is, FALSE otherwise. */ - public boolean hasMainThreadListener(Class packetClass); - - /** - * Determine if we need the buffer data of a given client side packet. - * @param packetClass - the packet class. - * @return TRUE if we do, FALSE otherwise. - */ - public boolean includeBuffer(Class packetClass); - + boolean hasMainThreadListener(Class packetClass); + + boolean hasMainThreadListener(PacketType type); + /** * Retrieve the current error reporter. + * * @return The error reporter. */ - public ErrorReporter getReporter(); - - /** - * Determine if debug mode is enabled. - * @return TRUE if it is, FALSE otherwise. - */ + ErrorReporter getReporter(); + + /** + * Determine if debug mode is enabled. + * + * @return TRUE if it is, FALSE otherwise. + */ boolean isDebug(); } diff --git a/src/main/java/com/comphenix/protocol/injector/netty/ChannelProxy.java b/src/main/java/com/comphenix/protocol/injector/netty/ChannelProxy.java deleted file mode 100644 index 3afe0c279..000000000 --- a/src/main/java/com/comphenix/protocol/injector/netty/ChannelProxy.java +++ /dev/null @@ -1,352 +0,0 @@ -/** - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2015 dmulloy2 - * - * 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.netty; - -import java.lang.reflect.Field; -import java.net.SocketAddress; -import java.util.Map; -import java.util.concurrent.Callable; - -import com.comphenix.protocol.reflect.accessors.Accessors; -import com.comphenix.protocol.reflect.accessors.FieldAccessor; -import com.google.common.collect.Maps; - -import io.netty.buffer.ByteBufAllocator; -import io.netty.channel.Channel; -import io.netty.channel.ChannelConfig; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelMetadata; -import io.netty.channel.ChannelPipeline; -import io.netty.channel.ChannelProgressivePromise; -import io.netty.channel.ChannelPromise; -import io.netty.channel.EventLoop; -import io.netty.util.Attribute; -import io.netty.util.AttributeKey; - -public abstract class ChannelProxy implements Channel { - // Mark that a certain object does not contain a message field - private static final FieldAccessor MARK_NO_MESSAGE = new FieldAccessor() { - @Override - public void set(Object instance, Object value) { } - @Override - public Object get(Object instance) { return null; } - @Override - public Field getField() { return null; }; - }; - - // Looking up packets in inner classes - private static Map, FieldAccessor> MESSAGE_LOOKUP = Maps.newConcurrentMap(); - - // The underlying channel - protected Channel delegate; - protected Class messageClass; - - // Event loop proxy - private transient EventLoopProxy loopProxy; - - public ChannelProxy(Channel delegate, Class messageClass) { - this.delegate = delegate; - this.messageClass = messageClass; - } - - /** - * Invoked when a packet is scheduled for transmission in the event loop. - * @param Type - * @param callable - callable to schedule for execution. - * @param packetAccessor - accessor for modifying the packet in the callable. - * @return The callable that will be scheduled, or NULL to cancel. - */ - protected abstract Callable onMessageScheduled(Callable callable, FieldAccessor packetAccessor); - - /** - * Invoked when a packet is scheduled for transmission in the event loop. - * @param runnable - the runnable that contains a packet to be scheduled. - * @param packetAccessor - accessor for modifying the packet in the runnable. - * @return The runnable that will be scheduled, or NULL to cancel. - */ - protected abstract Runnable onMessageScheduled(Runnable runnable, FieldAccessor packetAccessor); - - @Override - public Attribute attr(AttributeKey paramAttributeKey) { - return delegate.attr(paramAttributeKey); - } - - @Override - public ChannelFuture bind(SocketAddress paramSocketAddress) { - return delegate.bind(paramSocketAddress); - } - - @Override - public ChannelPipeline pipeline() { - return delegate.pipeline(); - } - - @Override - public ChannelFuture connect(SocketAddress paramSocketAddress) { - return delegate.connect(paramSocketAddress); - } - - @Override - public ByteBufAllocator alloc() { - return delegate.alloc(); - } - - @Override - public ChannelPromise newPromise() { - return delegate.newPromise(); - } - - @Override - public EventLoop eventLoop() { - if (loopProxy == null) { - loopProxy = new EventLoopProxy() { - @Override - protected EventLoop getDelegate() { - return delegate.eventLoop(); - } - - @Override - protected Runnable schedulingRunnable(final Runnable runnable) { - final FieldAccessor accessor = getMessageAccessor(runnable); - - if (accessor != null) { - Runnable result = onMessageScheduled(runnable, accessor);; - return result != null ? result : getEmptyRunnable(); - } - return runnable; - } - - @Override - protected Callable schedulingCallable(Callable callable) { - FieldAccessor accessor = getMessageAccessor(callable); - - if (accessor != null) { - Callable result = onMessageScheduled(callable, accessor);; - return result != null ? result : EventLoopProxy.getEmptyCallable(); - } - return callable; - } - }; - } - return loopProxy; - } - - /** - * Retrieve a way to access the packet field of an object. - * @param value - the object. - * @return The packet field accessor, or NULL if not found. - */ - private FieldAccessor getMessageAccessor(Object value) { - Class clazz = value.getClass(); - FieldAccessor accessor = MESSAGE_LOOKUP.get(clazz); - - if (accessor == null) { - try { - accessor = Accessors.getFieldAccessor(clazz, messageClass, true); - } catch (IllegalArgumentException e) { - accessor = MARK_NO_MESSAGE; - } - // Save the result - MESSAGE_LOOKUP.put(clazz, accessor); - } - return accessor != MARK_NO_MESSAGE ? accessor : null; - } - - @Override - public ChannelFuture connect(SocketAddress paramSocketAddress1, - SocketAddress paramSocketAddress2) { - return delegate.connect(paramSocketAddress1, paramSocketAddress2); - } - - @Override - public ChannelProgressivePromise newProgressivePromise() { - return delegate.newProgressivePromise(); - } - - @Override - public Channel parent() { - return delegate.parent(); - } - - @Override - public ChannelConfig config() { - return delegate.config(); - } - - @Override - public ChannelFuture newSucceededFuture() { - return delegate.newSucceededFuture(); - } - - @Override - public boolean isOpen() { - return delegate.isOpen(); - } - - @Override - public ChannelFuture disconnect() { - return delegate.disconnect(); - } - - @Override - public boolean isRegistered() { - return delegate.isRegistered(); - } - - @Override - public ChannelFuture newFailedFuture(Throwable paramThrowable) { - return delegate.newFailedFuture(paramThrowable); - } - - @Override - public ChannelFuture close() { - return delegate.close(); - } - - @Override - public boolean isActive() { - return delegate.isActive(); - } - - @Override - @Deprecated - public ChannelFuture deregister() { - return delegate.deregister(); - } - - @Override - public ChannelPromise voidPromise() { - return delegate.voidPromise(); - } - - @Override - public ChannelMetadata metadata() { - return delegate.metadata(); - } - - @Override - public ChannelFuture bind(SocketAddress paramSocketAddress, - ChannelPromise paramChannelPromise) { - return delegate.bind(paramSocketAddress, paramChannelPromise); - } - - @Override - public SocketAddress localAddress() { - return delegate.localAddress(); - } - - @Override - public SocketAddress remoteAddress() { - return delegate.remoteAddress(); - } - - @Override - public ChannelFuture connect(SocketAddress paramSocketAddress, - ChannelPromise paramChannelPromise) { - return delegate.connect(paramSocketAddress, paramChannelPromise); - } - - @Override - public ChannelFuture closeFuture() { - return delegate.closeFuture(); - } - - @Override - public boolean isWritable() { - return delegate.isWritable(); - } - - @Override - public Channel flush() { - return delegate.flush(); - } - - @Override - public ChannelFuture connect(SocketAddress paramSocketAddress1, - SocketAddress paramSocketAddress2, ChannelPromise paramChannelPromise) { - return delegate.connect(paramSocketAddress1, paramSocketAddress2, paramChannelPromise); - } - - @Override - public Channel read() { - return delegate.read(); - } - - @Override - public Unsafe unsafe() { - return delegate.unsafe(); - } - - @Override - public ChannelFuture disconnect(ChannelPromise paramChannelPromise) { - return delegate.disconnect(paramChannelPromise); - } - - @Override - public ChannelFuture close(ChannelPromise paramChannelPromise) { - return delegate.close(paramChannelPromise); - } - - @Override - @Deprecated - public ChannelFuture deregister(ChannelPromise paramChannelPromise) { - return delegate.deregister(paramChannelPromise); - } - - @Override - public ChannelFuture write(Object paramObject) { - return delegate.write(paramObject); - } - - @Override - public ChannelFuture write(Object paramObject, ChannelPromise paramChannelPromise) { - return delegate.write(paramObject, paramChannelPromise); - } - - @Override - public ChannelFuture writeAndFlush(Object paramObject, ChannelPromise paramChannelPromise) { - return delegate.writeAndFlush(paramObject, paramChannelPromise); - } - - @Override - public ChannelFuture writeAndFlush(Object paramObject) { - return delegate.writeAndFlush(paramObject); - } - - @Override - public int compareTo(Channel o) { - return delegate.compareTo(o); - } - - /* Added in Netty 4.1, seem to be unused - public long bytesBeforeUnwritable() { - return delegate.bytesBeforeUnwritable(); - } - - public long bytesBeforeWritable() { - return delegate.bytesBeforeWritable(); - } - - public ChannelId id() { - return delegate.id(); - } - - public boolean hasAttr(AttributeKey key) { - return delegate.hasAttr(key); - } - */ -} diff --git a/src/main/java/com/comphenix/protocol/injector/netty/EventLoopProxy.java b/src/main/java/com/comphenix/protocol/injector/netty/EventLoopProxy.java deleted file mode 100644 index 7e2eed598..000000000 --- a/src/main/java/com/comphenix/protocol/injector/netty/EventLoopProxy.java +++ /dev/null @@ -1,265 +0,0 @@ -/** - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2015 dmulloy2 - * - * 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.netty; - -import io.netty.channel.Channel; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelPromise; -import io.netty.channel.EventLoop; -import io.netty.channel.EventLoopGroup; -import io.netty.util.concurrent.EventExecutor; -import io.netty.util.concurrent.Future; -import io.netty.util.concurrent.ProgressivePromise; -import io.netty.util.concurrent.Promise; -import io.netty.util.concurrent.ScheduledFuture; - -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -/** - * An event loop proxy. - * @author Kristian. - */ -abstract class EventLoopProxy implements EventLoop { - private static final Runnable EMPTY_RUNNABLE = new Runnable() { - @Override - public void run() { - // Do nothing - } - }; - private static final Callable EMPTY_CALLABLE = new Callable() { - @Override - public Object call() throws Exception { - return null; - }; - }; - - /** - * Retrieve the underlying event loop. - * @return The event loop. - */ - protected abstract EventLoop getDelegate(); - - /** - * Retrieve a callable that does nothing but return NULL. - * @return The empty callable. - */ - @SuppressWarnings("unchecked") - public static Callable getEmptyCallable() { - return (Callable) EMPTY_CALLABLE; - } - - /** - * Retrieve a runnable that does nothing. - * @return A NO-OP runnable. - */ - public static Runnable getEmptyRunnable() { - return EMPTY_RUNNABLE; - } - - /** - * Invoked when a runnable is being scheduled. - * @param runnable - the runnable that is scheduling. - * @return The runnable to schedule instead. Cannot be NULL. - */ - protected abstract Runnable schedulingRunnable(Runnable runnable); - - /** - * Invoked when a callable is being scheduled. - * @param runnable - the callable that is scheduling. - * @return The callable to schedule instead. Cannot be NULL. - */ - protected abstract Callable schedulingCallable(Callable callable); - - @Override - public void execute(Runnable command) { - getDelegate().execute(schedulingRunnable(command)); - } - - @Override - public Future submit(Callable action) { - return getDelegate().submit(schedulingCallable(action)); - } - - @Override - public Future submit(Runnable action, T arg1) { - return getDelegate().submit(schedulingRunnable(action), arg1); - } - - @Override - public Future submit(Runnable action) { - return getDelegate().submit(schedulingRunnable(action)); - } - - @Override - public ScheduledFuture schedule(Callable action, long arg1, TimeUnit arg2) { - return getDelegate().schedule(schedulingCallable(action), arg1, arg2); - } - - @Override - public ScheduledFuture schedule(Runnable action, long arg1, TimeUnit arg2) { - return getDelegate().schedule(schedulingRunnable(action), arg1, arg2); - } - - @Override - public ScheduledFuture scheduleAtFixedRate(Runnable action, long arg1, long arg2, TimeUnit arg3) { - return getDelegate().scheduleAtFixedRate(schedulingRunnable(action), arg1, arg2, arg3); - } - - @Override - public ScheduledFuture scheduleWithFixedDelay(Runnable action, long arg1, long arg2, TimeUnit arg3) { - return getDelegate().scheduleWithFixedDelay(schedulingRunnable(action), arg1, arg2, arg3); - } - - // Boiler plate: - @Override - public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { - return getDelegate().awaitTermination(timeout, unit); - } - - @Override - public boolean inEventLoop() { - return getDelegate().inEventLoop(); - } - - @Override - public boolean inEventLoop(Thread arg0) { - return getDelegate().inEventLoop(arg0); - } - - @Override - public boolean isShutdown() { - return getDelegate().isShutdown(); - } - - @Override - public boolean isTerminated() { - return getDelegate().isTerminated(); - } - - @Override - public List> invokeAll(Collection> tasks) - throws InterruptedException { - return getDelegate().invokeAll(tasks); - } - - @Override - public List> invokeAll(Collection> tasks, long timeout, - TimeUnit unit) throws InterruptedException { - return getDelegate().invokeAll(tasks, timeout, unit); - } - - @Override - public T invokeAny(Collection> tasks) throws InterruptedException, - ExecutionException { - return getDelegate().invokeAny(tasks); - } - - @Override - public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) - throws InterruptedException, ExecutionException, TimeoutException { - return getDelegate().invokeAny(tasks, timeout, unit); - } - - @Override - public boolean isShuttingDown() { - return getDelegate().isShuttingDown(); - } - - @Override - public Iterator iterator() { - return getDelegate().iterator(); - } - - @Override - public Future newFailedFuture(Throwable arg0) { - return getDelegate().newFailedFuture(arg0); - } - - @Override - public EventLoop next() { - return ((EventLoopGroup) getDelegate()).next(); - } - - @Override - public ProgressivePromise newProgressivePromise() { - return getDelegate().newProgressivePromise(); - } - - @Override - public Promise newPromise() { - return getDelegate().newPromise(); - } - - @Override - public Future newSucceededFuture(V arg0) { - return getDelegate().newSucceededFuture(arg0); - } - - @Override - public EventLoopGroup parent() { - return getDelegate().parent(); - } - - @Override - public ChannelFuture register(Channel arg0, ChannelPromise arg1) { - return getDelegate().register(arg0, arg1); - } - - @Override - public ChannelFuture register(Channel arg0) { - return getDelegate().register(arg0); - } - - @Override - public Future shutdownGracefully() { - return getDelegate().shutdownGracefully(); - } - - @Override - public Future shutdownGracefully(long arg0, long arg1, TimeUnit arg2) { - return getDelegate().shutdownGracefully(arg0, arg1, arg2); - } - - @Override - public Future terminationFuture() { - return getDelegate().terminationFuture(); - } - - @Override - @Deprecated - public void shutdown() { - getDelegate().shutdown(); - } - - @Override - @Deprecated - public List shutdownNow() { - return getDelegate().shutdownNow(); - } - - /* - public ChannelFuture register(ChannelPromise promise) { - return getDelegate().register(promise); - } - */ -} diff --git a/src/main/java/com/comphenix/protocol/injector/netty/InjectionFactory.java b/src/main/java/com/comphenix/protocol/injector/netty/InjectionFactory.java deleted file mode 100644 index 53627795d..000000000 --- a/src/main/java/com/comphenix/protocol/injector/netty/InjectionFactory.java +++ /dev/null @@ -1,236 +0,0 @@ -/** - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2015 dmulloy2 - * - * 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.netty; - -import io.netty.channel.Channel; - -import java.util.concurrent.ConcurrentMap; - -import javax.annotation.Nonnull; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.plugin.Plugin; - -import com.comphenix.protocol.injector.netty.ChannelInjector.ChannelSocketInjector; -import com.comphenix.protocol.injector.server.SocketInjector; -import com.comphenix.protocol.injector.server.TemporaryPlayerFactory; -import com.comphenix.protocol.reflect.FuzzyReflection; -import com.comphenix.protocol.utility.MinecraftFields; -import com.comphenix.protocol.utility.MinecraftReflection; -import com.google.common.collect.MapMaker; - -/** - * Represents an injector factory. - *

    - * Note that the factory will return {@link ClosedInjector} when the factory is closed. - * @author Kristian - */ -public class InjectionFactory { - // This should work as long as the injectors are, uh, injected - private final ConcurrentMap playerLookup = new MapMaker().weakKeys().weakValues().makeMap(); - private final ConcurrentMap nameLookup = new MapMaker().weakValues().makeMap(); - - // Whether or not the factory is closed - private volatile boolean closed; - - // The current plugin - private final Plugin plugin; - - public InjectionFactory(Plugin plugin) { - this.plugin = plugin; - } - - /** - * Retrieve the main plugin associated with this injection factory. - * @return The main plugin. - */ - public Plugin getPlugin() { - return plugin; - } - - /** - * Construct or retrieve a channel injector from an existing Bukkit player. - * @param player - the existing Bukkit player. - * @param listener - the listener. - * @return A new injector, an existing injector associated with this player, or a closed injector. - */ - @Nonnull - public Injector fromPlayer(Player player, ChannelListener listener) { - if (closed) - return new ClosedInjector(player); - Injector injector = playerLookup.get(player); - - // Find a temporary injector as well - if (injector == null) - injector = getTemporaryInjector(player); - if (injector != null && !injector.isClosed()) - return injector; - - Object networkManager = MinecraftFields.getNetworkManager(player); - - // Must be a temporary Bukkit player - if (networkManager == null) { - return fromName(player.getName(), player); - } - Channel channel = FuzzyReflection.getFieldValue(networkManager, Channel.class, true); - - // See if a channel has already been created - injector = (ChannelInjector) ChannelInjector.findChannelHandler(channel, ChannelInjector.class); - - if (injector != null) { - // Update the player instance - playerLookup.remove(injector.getPlayer()); - injector.setPlayer(player); - } else { - injector = new ChannelInjector(player, networkManager, channel, listener, this); - } - - // Cache injector and return - cacheInjector(player, injector); - return injector; - } - - /** - * Retrieve a cached injector from a name. - *

    - * The injector may be NULL if the plugin has been reloaded during a player login. - * @param name - the name. - * @param player - the player. - * @return The cached injector, or a closed injector if it could not be found. - */ - public Injector fromName(String name, Player player) { - if (!closed) { - Injector injector = nameLookup.get(name); - - // We can only retrieve cached injectors - if (injector != null) { - // Update instance - injector.setUpdatedPlayer(player); - return injector; - } - } - return new ClosedInjector(player); - } - - /** - * Construct a new channel injector for the given channel. - * @param channel - the channel. - * @param listener - the listener. - * @param playerFactory - a temporary player creator. - * @return The channel injector, or a closed injector. - */ - @Nonnull - public Injector fromChannel(Channel channel, ChannelListener listener, TemporaryPlayerFactory playerFactory) { - if (closed) - return new ClosedInjector(null); - - Object networkManager = findNetworkManager(channel); - Player temporaryPlayer = playerFactory.createTemporaryPlayer(Bukkit.getServer()); - ChannelInjector injector = new ChannelInjector(temporaryPlayer, networkManager, channel, listener, this); - - // Initialize temporary player - TemporaryPlayerFactory.setInjectorInPlayer(temporaryPlayer, new ChannelSocketInjector(injector)); - return injector; - } - - /** - * Invalidate a cached injector. - * @param player - the associated player. - * @return The cached injector, or NULL if nothing was cached. - */ - public Injector invalidate(Player player) { - Injector injector = playerLookup.remove(player); - - nameLookup.remove(player.getName()); - return injector; - } - - /** - * Cache an injector by player. - * @param player - the player. - * @param injector - the injector to cache. - * @return The previously cached injector. - */ - public Injector cacheInjector(Player player, Injector injector) { - nameLookup.put(player.getName(), injector); - return playerLookup.put(player, injector); - } - - /** - * Cache an injector by name alone. - * @param name - the name to lookup. - * @param injector - the injector. - * @return The cached injector. - */ - public Injector cacheInjector(String name, Injector injector) { - return nameLookup.put(name, injector); - } - - /** - * Retrieve the associated channel injector. - * @param player - the temporary player, or normal Bukkit player. - * @return The associated injector, or NULL if this is a Bukkit player. - */ - private ChannelInjector getTemporaryInjector(Player player) { - SocketInjector injector = TemporaryPlayerFactory.getInjectorFromPlayer(player); - - if (injector != null) { - return ((ChannelSocketInjector) injector).getChannelInjector(); - } - return null; - } - - /** - * Find the network manager in a channel's pipeline. - * @param channel - the channel. - * @return The network manager. - */ - private Object findNetworkManager(Channel channel) { - // Find the network manager - Object networkManager = ChannelInjector.findChannelHandler(channel, MinecraftReflection.getNetworkManagerClass()); - - if (networkManager != null) - return networkManager; - throw new IllegalArgumentException("Unable to find NetworkManager in " + channel); - } - - /** - * Determine if the factory is closed. - *

    - * If it is, all new injectors will be closed by default. - * @return TRUE if it is closed, FALSE otherwise. - */ - public boolean isClosed() { - return closed; - } - - /** - * Close all injectors created by this factory, and cease the creation of new injections. - */ - public synchronized void close() { - if (!closed) { - closed = true; - - // Close everything - for (Injector injector : playerLookup.values()) - injector.close(); - for (Injector injector : nameLookup.values()) - injector.close(); - } - } -} diff --git a/src/main/java/com/comphenix/protocol/injector/netty/Injector.java b/src/main/java/com/comphenix/protocol/injector/netty/Injector.java index 2ada4a94a..f05b14e5f 100644 --- a/src/main/java/com/comphenix/protocol/injector/netty/Injector.java +++ b/src/main/java/com/comphenix/protocol/injector/netty/Injector.java @@ -1,97 +1,100 @@ package com.comphenix.protocol.injector.netty; -import org.bukkit.entity.Player; - import com.comphenix.protocol.PacketType.Protocol; import com.comphenix.protocol.events.NetworkMarker; +import org.bukkit.entity.Player; /** * Represents an injected client connection. + * * @author Kristian */ public interface Injector { + /** * Retrieve the current protocol version of the player. + * * @return Protocol version. */ - public abstract int getProtocolVersion(); - + int getProtocolVersion(); + /** * Inject the current channel. *

    * Note that only active channels can be injected. + * * @return TRUE if we injected the channel, false if we could not inject or it was already injected. */ - public abstract boolean inject(); + boolean inject(); + + void uninject(); /** * Close the current injector. */ - public abstract void close(); + void close(); /** * Send a packet to a player's client. - * @param packet - the packet to send. - * @param marker - the network marker. + * + * @param packet - the packet to send. + * @param marker - the network marker. * @param filtered - whether or not the packet is filtered. */ - public abstract void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered); + void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered); - /** - * Recieve a packet on the server. - * @param packet - the (NMS) packet to send. - */ - public abstract void recieveClientPacket(Object packet); + void receiveClientPacket(Object packet); /** * Retrieve the current protocol state. + * * @return The current protocol. */ - public abstract Protocol getCurrentProtocol(); + Protocol getCurrentProtocol(); /** * Retrieve the network marker associated with a given packet. + * * @param packet - the packet. * @return The network marker. */ - public abstract NetworkMarker getMarker(Object packet); + NetworkMarker getMarker(Object packet); /** * Associate a given network marker with a specific packet. + * * @param packet - the NMS packet. * @param marker - the associated marker. */ - public abstract void saveMarker(Object packet, NetworkMarker marker); + void saveMarker(Object packet, NetworkMarker marker); /** * Retrieve the current player or temporary player associated with the injector. + * * @return The current player. */ - public abstract Player getPlayer(); + Player getPlayer(); /** * Set the current player instance. + * * @param player - the current player. */ - public abstract void setPlayer(Player player); - + void setPlayer(Player player); + + void disconnect(String message); + /** * Determine if the channel has already been injected. + * * @return TRUE if it has, FALSE otherwise. */ - public abstract boolean isInjected(); + boolean isInjected(); /** * Determine if this channel has been closed and cleaned up. + * * @return TRUE if it has, FALSE otherwise. */ - public abstract boolean isClosed(); - - /** - * Set the updated player instance. - *

    - * This will not replace the current instance, but it will allow PacketEvent to provide additional player information. - * @param player - the more up-to-date player. - */ - public abstract void setUpdatedPlayer(Player player); + boolean isClosed(); } diff --git a/src/main/java/com/comphenix/protocol/injector/netty/NettyNetworkMarker.java b/src/main/java/com/comphenix/protocol/injector/netty/NettyNetworkMarker.java deleted file mode 100644 index 3241b2580..000000000 --- a/src/main/java/com/comphenix/protocol/injector/netty/NettyNetworkMarker.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.comphenix.protocol.injector.netty; - -import java.io.DataInputStream; -import java.io.IOException; -import java.nio.ByteBuffer; - -import javax.annotation.Nonnull; - -import com.comphenix.protocol.PacketType; -import com.comphenix.protocol.events.ConnectionSide; -import com.comphenix.protocol.events.NetworkMarker; - -public class NettyNetworkMarker extends NetworkMarker { - public NettyNetworkMarker(@Nonnull ConnectionSide side, byte[] inputBuffer) { - super(side, inputBuffer, null); - } - - public NettyNetworkMarker(@Nonnull ConnectionSide side, ByteBuffer inputBuffer) { - super(side, inputBuffer, null); - } - - @Override - protected DataInputStream skipHeader(DataInputStream input) throws IOException { - // Skip the variable int containing the packet ID - getSerializer().deserializeVarInt(input); - return input; - } - - @Override - protected ByteBuffer addHeader(ByteBuffer buffer, PacketType type) { - // We don't have to add anything - it's already there - return buffer; - } - - @Override - protected DataInputStream addHeader(DataInputStream input, PacketType type) { - // As above - return input; - } -} diff --git a/src/main/java/com/comphenix/protocol/injector/netty/PacketFilterQueue.java b/src/main/java/com/comphenix/protocol/injector/netty/PacketFilterQueue.java deleted file mode 100644 index 6f56f9b6f..000000000 --- a/src/main/java/com/comphenix/protocol/injector/netty/PacketFilterQueue.java +++ /dev/null @@ -1,83 +0,0 @@ -/** - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2015 dmulloy2 - * - * 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.netty; - -import java.util.ArrayDeque; -import java.util.Queue; - -/** - * Stores packets that need to be sent without being handled by the listeners (filtered=false). - * When other packets sent after sending the packet are removed, the packet is removed as well - * to prevent a memory leak, assuming a consistent send order is in place. - * - * @author bergerkiller - */ -public class PacketFilterQueue { - private Queue queue = new ArrayDeque<>(); - - /** - * Adds a packet to this queue, indicating further on that it should not be filtered. - * - * @param packet - */ - public synchronized void add(Object packet) { - queue.add(packet); - } - - /** - * Checks whether a packet is contained inside this queue, indicating - * it should not be filtered. - * - * @param packet - * @return True if contained and packet should not be filtered (filtered=false) - */ - public synchronized boolean contains(Object packet) { - return queue.contains(packet); - } - - /** - * Checks whether a packet is contained inside this queue and removes it if so. - * Other packets marked in this queue that were sent before this packet are - * removed from the queue also, avoiding memory leaks because of dropped packets. - * - * @param packet - * @return True if contained and packet should not be filtered (filtered=false) - */ - public synchronized boolean remove(Object packet) { - if (queue.isEmpty()) { - // Nothing in the queue - return false; - } else if (queue.peek() == packet) { - // First in the queue (expected) - queue.poll(); - return true; - } else if (!queue.contains(packet)) { - // There are unfiltered packets, but this one is not - return false; - } else { - // We have skipped over some packets (unexpected) - // Poll packets until we find it - while (queue.poll() != packet) { - if (queue.isEmpty()) { - // This should never happen! But to avoid infinite loop. - return false; - } - } - return true; - } - } -} diff --git a/src/main/java/com/comphenix/protocol/injector/netty/PipelineProxy.java b/src/main/java/com/comphenix/protocol/injector/netty/PipelineProxy.java deleted file mode 100644 index 8405c8fad..000000000 --- a/src/main/java/com/comphenix/protocol/injector/netty/PipelineProxy.java +++ /dev/null @@ -1,388 +0,0 @@ -/** - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2015 dmulloy2 - * - * 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.netty; - -import java.net.SocketAddress; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -import io.netty.channel.*; -import io.netty.util.concurrent.EventExecutorGroup; - -/** - * A pipeline proxy. - * @author Kristian - */ -public class PipelineProxy implements ChannelPipeline { - protected final ChannelPipeline pipeline; - protected final Channel channel; - - public PipelineProxy(ChannelPipeline pipeline, Channel channel) { - this.pipeline = pipeline; - this.channel = channel; - } - - @Override - public ChannelPipeline addAfter(EventExecutorGroup arg0, String arg1, String arg2, ChannelHandler arg3) { - pipeline.addAfter(arg0, arg1, arg2, arg3); - return this; - } - - @Override - public ChannelPipeline addAfter(String arg0, String arg1, ChannelHandler arg2) { - pipeline.addAfter(arg0, arg1, arg2); - return this; - } - - @Override - public ChannelPipeline addBefore(EventExecutorGroup arg0, String arg1, String arg2, ChannelHandler arg3) { - pipeline.addBefore(arg0, arg1, arg2, arg3); - return this; - } - - @Override - public ChannelPipeline addBefore(String arg0, String arg1, ChannelHandler arg2) { - pipeline.addBefore(arg0, arg1, arg2); - return this; - } - - @Override - public ChannelPipeline addFirst(ChannelHandler... arg0) { - pipeline.addFirst(arg0); - return this; - } - - @Override - public ChannelPipeline addFirst(EventExecutorGroup arg0, ChannelHandler... arg1) { - pipeline.addFirst(arg0, arg1); - return this; - } - - @Override - public ChannelPipeline addFirst(EventExecutorGroup arg0, String arg1, ChannelHandler arg2) { - pipeline.addFirst(arg0, arg1, arg2); - return this; - } - - @Override - public ChannelPipeline addFirst(String arg0, ChannelHandler arg1) { - pipeline.addFirst(arg0, arg1); - return this; - } - - @Override - public ChannelPipeline addLast(ChannelHandler... arg0) { - pipeline.addLast(arg0); - return this; - } - - @Override - public ChannelPipeline addLast(EventExecutorGroup arg0, ChannelHandler... arg1) { - pipeline.addLast(arg0, arg1); - return this; - } - - @Override - public ChannelPipeline addLast(EventExecutorGroup arg0, String arg1, ChannelHandler arg2) { - pipeline.addLast(arg0, arg1, arg2); - return this; - } - - @Override - public ChannelPipeline addLast(String arg0, ChannelHandler arg1) { - pipeline.addLast(arg0, arg1); - return this; - } - - @Override - public ChannelFuture bind(SocketAddress arg0, ChannelPromise arg1) { - return pipeline.bind(arg0, arg1); - } - - @Override - public ChannelFuture bind(SocketAddress arg0) { - return pipeline.bind(arg0); - } - - @Override - public Channel channel() { - return channel; - } - - @Override - public ChannelFuture close() { - return pipeline.close(); - } - - @Override - public ChannelFuture close(ChannelPromise arg0) { - return pipeline.close(arg0); - } - - @Override - public ChannelFuture connect(SocketAddress arg0, ChannelPromise arg1) { - return pipeline.connect(arg0, arg1); - } - - @Override - public ChannelFuture connect(SocketAddress arg0, SocketAddress arg1, ChannelPromise arg2) { - return pipeline.connect(arg0, arg1, arg2); - } - - @Override - public ChannelFuture connect(SocketAddress arg0, SocketAddress arg1) { - return pipeline.connect(arg0, arg1); - } - - @Override - public ChannelFuture connect(SocketAddress arg0) { - return pipeline.connect(arg0); - } - - @Override - public ChannelHandlerContext context(ChannelHandler arg0) { - return pipeline.context(arg0); - } - - @Override - public ChannelHandlerContext context(Class arg0) { - return pipeline.context(arg0); - } - - @Override - public ChannelHandlerContext context(String arg0) { - return pipeline.context(arg0); - } - - // We have to call the depreciated methods to properly implement the proxy - @Override - public ChannelFuture deregister() { - return pipeline.deregister(); - } - - @Override - public ChannelFuture deregister(ChannelPromise arg0) { - return pipeline.deregister(arg0); - } - - @Override - public ChannelPipeline fireChannelUnregistered() { - pipeline.fireChannelUnregistered(); - return this; - } - - @Override - public ChannelFuture disconnect() { - return pipeline.disconnect(); - } - - @Override - public ChannelFuture disconnect(ChannelPromise arg0) { - return pipeline.disconnect(arg0); - } - - @Override - public ChannelPipeline fireChannelActive() { - pipeline.fireChannelActive(); - return this; - } - - @Override - public ChannelPipeline fireChannelInactive() { - pipeline.fireChannelInactive(); - return this; - } - - @Override - public ChannelPipeline fireChannelRead(Object arg0) { - pipeline.fireChannelRead(arg0); - return this; - } - - @Override - public ChannelPipeline fireChannelReadComplete() { - pipeline.fireChannelReadComplete(); - return this; - } - - @Override - public ChannelPipeline fireChannelRegistered() { - pipeline.fireChannelRegistered(); - return this; - } - - @Override - public ChannelPipeline fireChannelWritabilityChanged() { - pipeline.fireChannelWritabilityChanged(); - return this; - } - - @Override - public ChannelPipeline fireExceptionCaught(Throwable arg0) { - pipeline.fireExceptionCaught(arg0); - return this; - } - - @Override - public ChannelPipeline fireUserEventTriggered(Object arg0) { - pipeline.fireUserEventTriggered(arg0); - return this; - } - - @Override - public ChannelHandler first() { - return pipeline.first(); - } - - @Override - public ChannelHandlerContext firstContext() { - return pipeline.firstContext(); - } - - @Override - public ChannelPipeline flush() { - pipeline.flush(); - return this; - } - - @Override - public T get(Class arg0) { - return pipeline.get(arg0); - } - - @Override - public ChannelHandler get(String arg0) { - return pipeline.get(arg0); - } - - @Override - public Iterator> iterator() { - return pipeline.iterator(); - } - - @Override - public ChannelHandler last() { - return pipeline.last(); - } - - @Override - public ChannelHandlerContext lastContext() { - return pipeline.lastContext(); - } - - @Override - public List names() { - return pipeline.names(); - } - - @Override - public ChannelPipeline read() { - pipeline.read(); - return this; - } - - @Override - public ChannelPipeline remove(ChannelHandler arg0) { - pipeline.remove(arg0); - return this; - } - - @Override - public T remove(Class arg0) { - return pipeline.remove(arg0); - } - - @Override - public ChannelHandler remove(String arg0) { - return pipeline.remove(arg0); - } - - @Override - public ChannelHandler removeFirst() { - return pipeline.removeFirst(); - } - - @Override - public ChannelHandler removeLast() { - return pipeline.removeLast(); - } - - @Override - public ChannelPipeline replace(ChannelHandler arg0, String arg1, ChannelHandler arg2) { - pipeline.replace(arg0, arg1, arg2); - return this; - } - - @Override - public T replace(Class arg0, String arg1, ChannelHandler arg2) { - return pipeline.replace(arg0, arg1, arg2); - } - - @Override - public ChannelHandler replace(String arg0, String arg1, ChannelHandler arg2) { - return pipeline.replace(arg0, arg1, arg2); - } - - @Override - public Map toMap() { - return pipeline.toMap(); - } - - @Override - public ChannelFuture write(Object arg0, ChannelPromise arg1) { - return pipeline.write(arg0, arg1); - } - - @Override - public ChannelFuture write(Object arg0) { - return pipeline.write(arg0); - } - - @Override - public ChannelFuture writeAndFlush(Object arg0, ChannelPromise arg1) { - return pipeline.writeAndFlush(arg0, arg1); - } - - @Override - public ChannelFuture writeAndFlush(Object arg0) { - return pipeline.writeAndFlush(arg0); - } - - /* Added in Netty 4.1, seem to be unused - public ChannelFuture newFailedFuture(Throwable ex) { - return pipeline.newFailedFuture(ex); - } - - public ChannelProgressivePromise newProgressivePromise() { - return pipeline.newProgressivePromise(); - } - - public ChannelPromise newPromise() { - return pipeline.newPromise(); - } - - public ChannelFuture newSucceededFuture() { - return pipeline.newSucceededFuture(); - } - - public ChannelPromise voidPromise() { - return pipeline.voidPromise(); - } - */ -} \ No newline at end of file diff --git a/src/main/java/com/comphenix/protocol/injector/netty/ProtocolInjector.java b/src/main/java/com/comphenix/protocol/injector/netty/ProtocolInjector.java deleted file mode 100644 index a30f6d8e5..000000000 --- a/src/main/java/com/comphenix/protocol/injector/netty/ProtocolInjector.java +++ /dev/null @@ -1,467 +0,0 @@ -/** - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2015 dmulloy2 - * - * 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.netty; - -import java.io.InputStream; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; -import java.net.InetSocketAddress; -import java.util.List; -import java.util.Set; - -import org.bukkit.entity.Player; -import org.bukkit.plugin.Plugin; - -import com.comphenix.protocol.PacketType; -import com.comphenix.protocol.ProtocolLogger; -import com.comphenix.protocol.concurrency.PacketTypeSet; -import com.comphenix.protocol.error.ErrorReporter; -import com.comphenix.protocol.error.Report; -import com.comphenix.protocol.error.ReportType; -import com.comphenix.protocol.events.ConnectionSide; -import com.comphenix.protocol.events.ListenerOptions; -import com.comphenix.protocol.events.NetworkMarker; -import com.comphenix.protocol.events.PacketContainer; -import com.comphenix.protocol.events.PacketEvent; -import com.comphenix.protocol.injector.ListenerInvoker; -import com.comphenix.protocol.injector.packet.PacketInjector; -import com.comphenix.protocol.injector.packet.PacketRegistry; -import com.comphenix.protocol.injector.player.PlayerInjectionHandler; -import com.comphenix.protocol.injector.server.TemporaryPlayerFactory; -import com.comphenix.protocol.reflect.FuzzyReflection; -import com.comphenix.protocol.reflect.VolatileField; -import com.comphenix.protocol.utility.NettyVersion; -import com.comphenix.protocol.utility.MinecraftReflection; -import com.comphenix.protocol.utility.MinecraftVersion; -import com.google.common.collect.Lists; - -import io.netty.channel.Channel; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelHandler; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandler; -import io.netty.channel.ChannelInboundHandlerAdapter; -import io.netty.channel.ChannelInitializer; - -public class ProtocolInjector implements ChannelListener { - public static final ReportType REPORT_CANNOT_INJECT_INCOMING_CHANNEL = new ReportType("Unable to inject incoming channel %s."); - - private volatile boolean injected; - private volatile boolean closed; - - // The temporary player factory - private TemporaryPlayerFactory playerFactory = new TemporaryPlayerFactory(); - private List bootstrapFields = Lists.newArrayList(); - - // The channel injector factory - private InjectionFactory injectionFactory; - - // List of network managers - private volatile List networkManagers; - - // Different sending filters - private PacketTypeSet sendingFilters = new PacketTypeSet(); - private PacketTypeSet reveivedFilters = new PacketTypeSet(); - - // Packets that must be executed on the main thread - private PacketTypeSet mainThreadFilters = new PacketTypeSet(); - - // Which packets are buffered - private PacketTypeSet bufferedPackets = new PacketTypeSet(); - private ListenerInvoker invoker; - - // Handle errors - private ErrorReporter reporter; - private boolean debug; - - public ProtocolInjector(Plugin plugin, ListenerInvoker invoker, ErrorReporter reporter) { - this.injectionFactory = new InjectionFactory(plugin); - this.invoker = invoker; - this.reporter = reporter; - } - - @Override - public boolean isDebug() { - return debug; - } - - /** - * Set whether or not the debug mode is enabled. - * @param debug - TRUE if it is, FALSE otherwise. - */ - public void setDebug(boolean debug) { - this.debug = debug; - } - - /** - * Inject into the spigot connection class. - */ - @SuppressWarnings("unchecked") - public synchronized void inject() { - if (injected) - throw new IllegalStateException("Cannot inject twice."); - try { - FuzzyReflection fuzzyServer = FuzzyReflection.fromClass(MinecraftReflection.getMinecraftServerClass()); - List serverConnectionMethods = fuzzyServer.getMethodListByParameters(MinecraftReflection.getServerConnectionClass(), new Class[] {}); - - // Get the server connection - Object server = fuzzyServer.getSingleton(); - Object serverConnection = null; - - for (Method method : serverConnectionMethods) { - try { - serverConnection = method.invoke(server); - - // Continue until we get a server connection - if (serverConnection != null) { - break; - } - } catch (Exception ex) { - ProtocolLogger.debug("Encountered an exception invoking " + method, ex); - } - } - - if (serverConnection == null) { - throw new ReflectiveOperationException("Failed to obtain server connection"); - } - - // Handle connected channels - final ChannelInboundHandler endInitProtocol = new ChannelInitializer() { - @Override - protected void initChannel(final Channel channel) throws Exception { - try { - synchronized (networkManagers) { - // For some reason it needs to be delayed when using netty 4.1.24 (minecraft 1.12) or newer, - // but the delay breaks older minecraft versions - // TODO I see this more as a temporary hotfix than a permanent solution - - // Check if the netty version is greater than 4.1.24, that's the version bundled with spigot 1.12 - NettyVersion ver = NettyVersion.getVersion(); - if ((ver.isValid() && ver.isGreaterThan(4,1,24)) || - MinecraftVersion.getCurrentVersion().getMinor() >= 12) { // fallback if netty version couldn't be detected - channel.eventLoop().submit(() -> - injectionFactory.fromChannel(channel, ProtocolInjector.this, playerFactory).inject()); - } else { - injectionFactory.fromChannel(channel, ProtocolInjector.this, playerFactory).inject(); - } - } - } catch (Exception ex) { - reporter.reportDetailed(ProtocolInjector.this, Report.newBuilder(REPORT_CANNOT_INJECT_INCOMING_CHANNEL).messageParam(channel).error(ex)); - } - } - }; - - // This is executed before Minecraft's channel handler - final ChannelInboundHandler beginInitProtocol = new ChannelInitializer() { - @Override - protected void initChannel(Channel channel) throws Exception { - // Our only job is to add init protocol - channel.pipeline().addLast(endInitProtocol); - } - }; - - // Add our handler to newly created channels - final ChannelHandler connectionHandler = new ChannelInboundHandlerAdapter() { - @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - Channel channel = (Channel) msg; - - // Prepare to initialize ths channel - channel.pipeline().addFirst(beginInitProtocol); - ctx.fireChannelRead(msg); - } - - @Override - public boolean isSharable() { - // Needed if multiple objects are stored in the bootstrap list - return true; - } - }; - - FuzzyReflection fuzzy = FuzzyReflection.fromObject(serverConnection, true); - - try { - Field field = fuzzy.getParameterizedField(List.class, MinecraftReflection.getNetworkManagerClass()); - field.setAccessible(true); - - networkManagers = (List) field.get(serverConnection); - } catch (Exception ex) { - ProtocolLogger.debug("Encountered an exception checking list fields", ex); - - Method method = fuzzy.getMethodByParameters("getNetworkManagers", List.class, - new Class[] { serverConnection.getClass() }); - method.setAccessible(true); - - networkManagers = (List) method.invoke(null, serverConnection); - } - - if (networkManagers == null) { - throw new ReflectiveOperationException("Failed to obtain list of network managers"); - } - - // Insert ProtocolLib's connection interceptor - bootstrapFields = getBootstrapFields(serverConnection); - - for (VolatileField field : bootstrapFields) { - final List list = (List) field.getValue(); - - // We don't have to override this list - if (list == networkManagers) { - continue; - } - - // Synchronize with each list before we attempt to replace them. - field.setValue(new BootstrapList(list, connectionHandler)); - } - - injected = true; - } catch (Exception e) { - throw new RuntimeException("Unable to inject channel futures.", e); - } - } - - @Override - public boolean hasListener(Class packetClass) { - return reveivedFilters.contains(packetClass) || sendingFilters.contains(packetClass); - } - - @Override - public boolean hasMainThreadListener(Class packetClass) { - return mainThreadFilters.contains(packetClass); - } - - @Override - public ErrorReporter getReporter() { - return reporter; - } - - /** - * Inject our packet handling into a specific player. - * @param player Player to inject into - */ - public void injectPlayer(Player player) { - injectionFactory.fromPlayer(player, this).inject(); - } - - private List getBootstrapFields(Object serverConnection) { - List result = Lists.newArrayList(); - - // Find and (possibly) proxy every list - for (Field field : FuzzyReflection.fromObject(serverConnection, true).getFieldListByType(List.class)) { - VolatileField volatileField = new VolatileField(field, serverConnection, true).toSynchronized(); - - @SuppressWarnings("unchecked") - List list = (List) volatileField.getValue(); - - if (list.size() == 0 || list.get(0) instanceof ChannelFuture) { - result.add(volatileField); - } - } - - return result; - } - - /** - * Clean up any remaning injections. - */ - public synchronized void close() { - if (!closed) { - closed = true; - - for (VolatileField field : bootstrapFields) { - Object value = field.getValue(); - - // Undo the processed channels, if any - if (value instanceof BootstrapList) { - ((BootstrapList) value).close(); - } - field.revertValue(); - } - // Uninject all the players - injectionFactory.close(); - } - } - - @Override - public PacketEvent onPacketSending(Injector injector, Object packet, NetworkMarker marker) { - Class clazz = packet.getClass(); - - if (sendingFilters.contains(clazz) || marker != null) { - try { - PacketContainer container = new PacketContainer(PacketRegistry.getPacketType(clazz), packet); - return packetQueued(container, injector.getPlayer(), marker); - } catch (LinkageError e) { - System.err.println("[ProtocolLib] Encountered a LinkageError (likely a misbehaving wrapper), please report this!"); - e.printStackTrace(); - } - } - - // Don't change anything - return null; - } - - @Override - public PacketEvent onPacketReceiving(Injector injector, Object packet, NetworkMarker marker) { - Class clazz = packet.getClass(); - - if (reveivedFilters.contains(clazz) || marker != null) { - PacketContainer container = new PacketContainer(PacketRegistry.getPacketType(clazz), packet); - return packetReceived(container, injector.getPlayer(), marker); - } - - // Don't change anything - return null; - } - - @Override - public boolean includeBuffer(Class packetClass) { - return bufferedPackets.contains(packetClass); - } - - /** - * Called to inform the event listeners of a queued packet. - * @param packet - the packet that is to be sent. - * @param receiver - the receiver of this packet. - * @return The packet event that was used. - */ - private PacketEvent packetQueued(PacketContainer packet, Player receiver, NetworkMarker marker) { - PacketEvent event = PacketEvent.fromServer(this, packet, marker, receiver); - - invoker.invokePacketSending(event); - return event; - } - - /** - * Called to inform the event listeners of a received packet. - * @param packet - the packet that has been receieved. - * @param sender - the client packet. - * @param marker - the network marker. - * @return The packet event that was used. - */ - private PacketEvent packetReceived(PacketContainer packet, Player sender, NetworkMarker marker) { - PacketEvent event = PacketEvent.fromClient(this, packet, marker, sender); - - invoker.invokePacketRecieving(event); - return event; - } - - // Server side - public PlayerInjectionHandler getPlayerInjector() { - return new AbstractPlayerHandler(sendingFilters) { - private ChannelListener listener = ProtocolInjector.this; - - @Override - public int getProtocolVersion(Player player) { - return injectionFactory.fromPlayer(player, listener).getProtocolVersion(); - } - - @Override - public void updatePlayer(Player player) { - injectionFactory.fromPlayer(player, listener).inject(); - } - - @Override - public void injectPlayer(Player player, ConflictStrategy strategy) { - injectionFactory.fromPlayer(player, listener).inject(); - } - - @Override - public boolean uninjectPlayer(InetSocketAddress address) { - // Ignore this too - return true; - } - - @Override - public void addPacketHandler(PacketType type, Set options) { - if (!type.isAsyncForced() && (options == null || !options.contains(ListenerOptions.ASYNC))) - mainThreadFilters.addType(type); - super.addPacketHandler(type, options); - } - - @Override - public void removePacketHandler(PacketType type) { - mainThreadFilters.removeType(type); - super.removePacketHandler(type); - } - - @Override - public boolean uninjectPlayer(Player player) { - // Just let Netty clean this up - return true; - } - - @Override - public void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters) throws InvocationTargetException { - injectionFactory.fromPlayer(receiver, listener).sendServerPacket(packet.getHandle(), marker, filters); - } - - @Override - public boolean hasMainThreadListener(PacketType type) { - return mainThreadFilters.contains(type); - } - - @Override - public void recieveClientPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException { - injectionFactory.fromPlayer(player, listener).recieveClientPacket(mcPacket); - } - - @Override - public PacketEvent handlePacketRecieved(PacketContainer packet, InputStream input, byte[] buffered) { - // Ignore this - return null; - } - - @Override - public void handleDisconnect(Player player) { - injectionFactory.fromPlayer(player, listener).close(); - } - - @Override - public Channel getChannel(Player player) { - Injector injector = injectionFactory.fromPlayer(player, listener); - if (injector instanceof ChannelInjector) { - return ((ChannelInjector) injector).getChannel(); - } - - return null; - } - }; - } - - /** - * Retrieve a view of this protocol injector as a packet injector. - * @return The packet injector. - */ - // Client side - public PacketInjector getPacketInjector() { - return new AbstractPacketInjector(reveivedFilters) { - @Override - public PacketEvent packetRecieved(PacketContainer packet, Player client, byte[] buffered) { - NetworkMarker marker = buffered != null ? new NettyNetworkMarker(ConnectionSide.CLIENT_SIDE, buffered) : null; - injectionFactory.fromPlayer(client, ProtocolInjector.this).saveMarker(packet.getHandle(), marker); - return packetReceived(packet, client, marker); - } - - @Override - public void inputBuffersChanged(Set set) { - bufferedPackets = new PacketTypeSet(set); - } - }; - } -} diff --git a/src/main/java/com/comphenix/protocol/injector/netty/SocketAdapter.java b/src/main/java/com/comphenix/protocol/injector/netty/SocketAdapter.java deleted file mode 100644 index 6cc0dbeec..000000000 --- a/src/main/java/com/comphenix/protocol/injector/netty/SocketAdapter.java +++ /dev/null @@ -1,268 +0,0 @@ -/** - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2015 dmulloy2 - * - * 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.netty; - -import io.netty.channel.Channel; -import io.netty.channel.ChannelOption; -import io.netty.channel.socket.SocketChannel; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.InetAddress; -import java.net.Socket; -import java.net.SocketAddress; -import java.net.SocketException; - -/** - * This class wraps a Netty {@link Channel} in a {@link Socket}. It overrides - * all methods in {@link Socket} to ensure that calls are not mistakingly made - * to the unsupported super socket. All operations that can be sanely applied to - * a {@link Channel} are implemented here. Those which cannot will throw an - * {@link UnsupportedOperationException}. - */ -// Thanks MD5. :) -public class SocketAdapter extends Socket { - private final SocketChannel ch; - - private SocketAdapter(SocketChannel ch) { - this.ch = ch; - } - - public static SocketAdapter adapt(Channel ch) { - if (!(ch instanceof SocketChannel)) { - return null; - } - return new SocketAdapter((SocketChannel) ch); - } - - @Override - public void bind(SocketAddress bindpoint) throws IOException { - ch.bind(bindpoint).syncUninterruptibly(); - } - - @Override - public synchronized void close() throws IOException { - ch.close().syncUninterruptibly(); - } - - @Override - public void connect(SocketAddress endpoint) throws IOException { - ch.connect(endpoint).syncUninterruptibly(); - } - - @Override - public void connect(SocketAddress endpoint, int timeout) throws IOException { - ch.config().setConnectTimeoutMillis(timeout); - ch.connect(endpoint).syncUninterruptibly(); - } - - @Override - public boolean equals(Object obj) { - return obj instanceof SocketAdapter && ch.equals(((SocketAdapter) obj).ch); - } - - @Override - public java.nio.channels.SocketChannel getChannel() { - throw new UnsupportedOperationException("Operation not supported on Channel wrapper."); - } - - @Override - public InetAddress getInetAddress() { - return ch.remoteAddress().getAddress(); - } - - @Override - public InputStream getInputStream() throws IOException { - throw new UnsupportedOperationException("Operation not supported on Channel wrapper."); - } - - @Override - public boolean getKeepAlive() throws SocketException { - return ch.config().getOption(ChannelOption.SO_KEEPALIVE); - } - - @Override - public InetAddress getLocalAddress() { - return ch.localAddress().getAddress(); - } - - @Override - public int getLocalPort() { - return ch.localAddress().getPort(); - } - - @Override - public SocketAddress getLocalSocketAddress() { - return ch.localAddress(); - } - - @Override - public boolean getOOBInline() throws SocketException { - throw new UnsupportedOperationException("Operation not supported on Channel wrapper."); - } - - @Override - public OutputStream getOutputStream() throws IOException { - throw new UnsupportedOperationException("Operation not supported on Channel wrapper."); - } - - @Override - public int getPort() { - return ch.remoteAddress().getPort(); - } - - @Override - public synchronized int getReceiveBufferSize() throws SocketException { - return ch.config().getOption(ChannelOption.SO_RCVBUF); - } - - @Override - public SocketAddress getRemoteSocketAddress() { - return ch.remoteAddress(); - } - - @Override - public boolean getReuseAddress() throws SocketException { - return ch.config().getOption(ChannelOption.SO_REUSEADDR); - } - - @Override - public synchronized int getSendBufferSize() throws SocketException { - return ch.config().getOption(ChannelOption.SO_SNDBUF); - } - - @Override - public int getSoLinger() throws SocketException { - return ch.config().getOption(ChannelOption.SO_LINGER); - } - - @Override - public synchronized int getSoTimeout() throws SocketException { - throw new UnsupportedOperationException("Operation not supported on Channel wrapper."); - } - - @Override - public boolean getTcpNoDelay() throws SocketException { - return ch.config().getOption(ChannelOption.TCP_NODELAY); - } - - @Override - public int getTrafficClass() throws SocketException { - return ch.config().getOption(ChannelOption.IP_TOS); - } - - @Override - public int hashCode() { - return ch.hashCode(); - } - - @Override - public boolean isBound() { - return ch.localAddress() != null; - } - - @Override - public boolean isClosed() { - return !ch.isOpen(); - } - - @Override - public boolean isConnected() { - return ch.isActive(); - } - - @Override - public boolean isInputShutdown() { - return ch.isInputShutdown(); - } - - @Override - public boolean isOutputShutdown() { - return ch.isOutputShutdown(); - } - - @Override - public void sendUrgentData(int data) throws IOException { - throw new UnsupportedOperationException("Operation not supported on Channel wrapper."); - } - - @Override - public void setKeepAlive(boolean on) throws SocketException { - ch.config().setOption(ChannelOption.SO_KEEPALIVE, on); - } - - @Override - public void setOOBInline(boolean on) throws SocketException { - throw new UnsupportedOperationException("Operation not supported on Channel wrapper."); - } - - @Override - public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) { - throw new UnsupportedOperationException("Operation not supported on Channel wrapper."); - } - - @Override - public synchronized void setReceiveBufferSize(int size) throws SocketException { - ch.config().setOption(ChannelOption.SO_RCVBUF, size); - } - - @Override - public void setReuseAddress(boolean on) throws SocketException { - ch.config().setOption(ChannelOption.SO_REUSEADDR, on); - } - - @Override - public synchronized void setSendBufferSize(int size) throws SocketException { - ch.config().setOption(ChannelOption.SO_SNDBUF, size); - } - - @Override - public void setSoLinger(boolean on, int linger) throws SocketException { - ch.config().setOption(ChannelOption.SO_LINGER, linger); - } - - @Override - public synchronized void setSoTimeout(int timeout) throws SocketException { - throw new UnsupportedOperationException("Operation not supported on Channel wrapper."); - } - - @Override - public void setTcpNoDelay(boolean on) throws SocketException { - ch.config().setOption(ChannelOption.TCP_NODELAY, on); - } - - @Override - public void setTrafficClass(int tc) throws SocketException { - ch.config().setOption(ChannelOption.IP_TOS, tc); - } - - @Override - public void shutdownInput() throws IOException { - throw new UnsupportedOperationException("Operation not supported on Channel wrapper."); - } - - @Override - public void shutdownOutput() throws IOException { - ch.shutdownOutput().syncUninterruptibly(); - } - - @Override - public String toString() { - return ch.toString(); - } -} diff --git a/src/main/java/com/comphenix/protocol/injector/netty/WirePacket.java b/src/main/java/com/comphenix/protocol/injector/netty/WirePacket.java index 821de55b3..727f27410 100644 --- a/src/main/java/com/comphenix/protocol/injector/netty/WirePacket.java +++ b/src/main/java/com/comphenix/protocol/injector/netty/WirePacket.java @@ -1,263 +1,262 @@ -/** - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2015 dmulloy2 - * - * 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.netty; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - -import java.lang.reflect.Method; -import java.util.Arrays; - -import com.comphenix.protocol.PacketType; -import com.comphenix.protocol.events.PacketContainer; -import com.comphenix.protocol.utility.MinecraftMethods; -import com.comphenix.protocol.utility.MinecraftReflection; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; - -/** - * A packet represented only by its id and bytes. - * @author dmulloy2 - */ -public class WirePacket { - private final int id; - private final byte[] bytes; - - /** - * Constructs a new WirePacket with a given type and contents - * @param type Type of the packet - * @param bytes Contents of the packet - */ - public WirePacket(PacketType type, byte[] bytes) { - this.id = checkNotNull(type, "type cannot be null").getCurrentId(); - this.bytes = bytes; - } - - /** - * Constructs a new WirePacket with a given id and contents - * @param id ID of the packet - * @param bytes Contents of the packet - */ - public WirePacket(int id, byte[] bytes) { - this.id = id; - this.bytes = bytes; - } - - /** - * Gets this packet's ID - * @return The ID - */ - public int getId() { - return id; - } - - /** - * Gets this packet's contents as a byte array - * @return The contents - */ - public byte[] getBytes() { - return bytes; - } - - /** - * Writes the id of this packet to a given output - * @param output Output to write to - */ - public void writeId(ByteBuf output) { - writeVarInt(output, id); - } - - /** - * Writes the contents of this packet to a given output - * @param output Output to write to - */ - public void writeBytes(ByteBuf output) { - checkNotNull(output, "output cannot be null!"); - output.writeBytes(bytes); - } - - /** - * Fully writes the ID and contents of this packet to a given output - * @param output Output to write to - */ - public void writeFully(ByteBuf output) { - writeId(output); - writeBytes(output); - } - - /** - * Serializes this packet into a byte buffer - * @return The buffer - */ - public ByteBuf serialize() { - ByteBuf buffer = Unpooled.buffer(); - writeFully(buffer); - return buffer; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - - if (obj instanceof WirePacket) { - WirePacket that = (WirePacket) obj; - return this.id == that.id && - Arrays.equals(this.bytes, that.bytes); - } - - return false; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + Arrays.hashCode(bytes); - result = prime * result + id; - return result; - } - - @Override - public String toString() { - return "WirePacket[id=" + id + ", bytes=" + Arrays.toString(bytes) + "]"; - } - - private static byte[] getBytes(ByteBuf buffer) { - byte[] array = new byte[buffer.readableBytes()]; - buffer.readBytes(array); - return array; - } - - /** - * Creates a WirePacket from an existing PacketContainer - * @param packet Existing packet - * @return The resulting WirePacket - */ - public static WirePacket fromPacket(PacketContainer packet) { - int id = packet.getType().getCurrentId(); - return new WirePacket(id, bytesFromPacket(packet)); - } - - /** - * Creates a byte array from an existing PacketContainer containing all the - * bytes from that packet - * - * @param packet Existing packet - * @return the byte array - */ - public static byte[] bytesFromPacket(PacketContainer packet) { - checkNotNull(packet, "packet cannot be null!"); - - ByteBuf buffer = PacketContainer.createPacketBuffer(); - ByteBuf store = PacketContainer.createPacketBuffer(); - - // Read the bytes once - Method write = MinecraftMethods.getPacketWriteByteBufMethod(); - - try { - write.invoke(packet.getHandle(), buffer); - } catch (ReflectiveOperationException ex) { - throw new RuntimeException("Failed to read packet contents.", ex); - } - - byte[] bytes = getBytes(buffer); - - buffer.release(); - - // Rewrite them to the packet to avoid issues with certain packets - if (packet.getType() == PacketType.Play.Server.CUSTOM_PAYLOAD - || packet.getType() == PacketType.Play.Client.CUSTOM_PAYLOAD) { - // Make a copy of the array before writing - byte[] ret = Arrays.copyOf(bytes, bytes.length); - store.writeBytes(bytes); - - Method read = MinecraftMethods.getPacketReadByteBufMethod(); - - try { - read.invoke(packet.getHandle(), store); - } catch (ReflectiveOperationException ex) { - throw new RuntimeException("Failed to rewrite packet contents.", ex); - } - - return ret; - } - - store.release(); - - return bytes; - } - - /** - * Creates a WirePacket from an existing Minecraft packet - * @param packet Existing Minecraft packet - * @return The resulting WirePacket - * @throws IllegalArgumentException If the packet is null or not a Minecraft packet - */ - public static WirePacket fromPacket(Object packet) { - checkNotNull(packet, "packet cannot be null!"); - checkArgument(MinecraftReflection.isPacketClass(packet), "packet must be a Minecraft packet"); - - PacketType type = PacketType.fromClass(packet.getClass()); - int id = type.getCurrentId(); - - ByteBuf buffer = PacketContainer.createPacketBuffer(); - Method write = MinecraftMethods.getPacketWriteByteBufMethod(); - - try { - write.invoke(packet, buffer); - } catch (ReflectiveOperationException ex) { - throw new RuntimeException("Failed to serialize packet contents.", ex); - } - - byte[] bytes = getBytes(buffer); - - buffer.release(); - - return new WirePacket(id, bytes); - } - - public static void writeVarInt(ByteBuf output, int i) { - checkNotNull(output, "output cannot be null!"); - - while ((i & -128) != 0) { - output.writeByte(i & 127 | 128); - i >>>= 7; - } - - output.writeByte(i); - } - - public static int readVarInt(ByteBuf input) { - checkNotNull(input, "input cannot be null!"); - - int i = 0; - int j = 0; - - byte b0; - - do { - b0 = input.readByte(); - i |= (b0 & 127) << j++ * 7; - if (j > 5) { - throw new RuntimeException("VarInt too big"); - } - } while ((b0 & 128) == 128); - - return i; - } -} +/** + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. Copyright (C) 2015 dmulloy2 + *

    + * 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.netty; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.utility.MinecraftMethods; +import com.comphenix.protocol.utility.MinecraftReflection; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import java.lang.reflect.Method; +import java.util.Arrays; + +/** + * A packet represented only by its id and bytes. + * + * @author dmulloy2 + */ +public class WirePacket { + + private final int id; + private final byte[] bytes; + + /** + * Constructs a new WirePacket with a given type and contents + * @param type Type of the packet + * @param bytes Contents of the packet + */ + public WirePacket(PacketType type, byte[] bytes) { + this.id = checkNotNull(type, "type cannot be null").getCurrentId(); + this.bytes = bytes; + } + + /** + * Constructs a new WirePacket with a given id and contents + * @param id ID of the packet + * @param bytes Contents of the packet + */ + public WirePacket(int id, byte[] bytes) { + this.id = id; + this.bytes = bytes; + } + + private static byte[] getBytes(ByteBuf buffer) { + byte[] array = new byte[buffer.readableBytes()]; + buffer.readBytes(array); + return array; + } + + /** + * Creates a WirePacket from an existing PacketContainer + * @param packet Existing packet + * @return The resulting WirePacket + */ + public static WirePacket fromPacket(PacketContainer packet) { + int id = packet.getType().getCurrentId(); + return new WirePacket(id, bytesFromPacket(packet)); + } + + /** + * Creates a byte array from an existing PacketContainer containing all the + * bytes from that packet + * + * @param packet Existing packet + * @return the byte array + */ + public static byte[] bytesFromPacket(PacketContainer packet) { + checkNotNull(packet, "packet cannot be null!"); + + ByteBuf buffer = PacketContainer.createPacketBuffer(); + ByteBuf store = PacketContainer.createPacketBuffer(); + + // Read the bytes once + Method write = MinecraftMethods.getPacketWriteByteBufMethod(); + + try { + write.invoke(packet.getHandle(), buffer); + } catch (ReflectiveOperationException ex) { + throw new RuntimeException("Failed to read packet contents.", ex); + } + + byte[] bytes = getBytes(buffer); + + buffer.release(); + + // Rewrite them to the packet to avoid issues with certain packets + if (packet.getType() == PacketType.Play.Server.CUSTOM_PAYLOAD + || packet.getType() == PacketType.Play.Client.CUSTOM_PAYLOAD) { + // Make a copy of the array before writing + byte[] ret = Arrays.copyOf(bytes, bytes.length); + store.writeBytes(bytes); + + Method read = MinecraftMethods.getPacketReadByteBufMethod(); + + try { + read.invoke(packet.getHandle(), store); + } catch (ReflectiveOperationException ex) { + throw new RuntimeException("Failed to rewrite packet contents.", ex); + } + + return ret; + } + + store.release(); + + return bytes; + } + + /** + * Creates a WirePacket from an existing Minecraft packet + * @param packet Existing Minecraft packet + * @return The resulting WirePacket + * @throws IllegalArgumentException If the packet is null or not a Minecraft packet + */ + public static WirePacket fromPacket(Object packet) { + checkNotNull(packet, "packet cannot be null!"); + checkArgument(MinecraftReflection.isPacketClass(packet), "packet must be a Minecraft packet"); + + PacketType type = PacketType.fromClass(packet.getClass()); + int id = type.getCurrentId(); + + ByteBuf buffer = PacketContainer.createPacketBuffer(); + Method write = MinecraftMethods.getPacketWriteByteBufMethod(); + + try { + write.invoke(packet, buffer); + } catch (ReflectiveOperationException ex) { + throw new RuntimeException("Failed to serialize packet contents.", ex); + } + + byte[] bytes = getBytes(buffer); + + buffer.release(); + + return new WirePacket(id, bytes); + } + + public static void writeVarInt(ByteBuf output, int i) { + checkNotNull(output, "output cannot be null!"); + + while ((i & -128) != 0) { + output.writeByte(i & 127 | 128); + i >>>= 7; + } + + output.writeByte(i); + } + + public static int readVarInt(ByteBuf input) { + checkNotNull(input, "input cannot be null!"); + + int i = 0; + int j = 0; + + byte b0; + + do { + b0 = input.readByte(); + i |= (b0 & 127) << j++ * 7; + if (j > 5) { + throw new RuntimeException("VarInt too big"); + } + } while ((b0 & 128) == 128); + + return i; + } + + /** + * Gets this packet's ID + * @return The ID + */ + public int getId() { + return this.id; + } + + /** + * Gets this packet's contents as a byte array + * @return The contents + */ + public byte[] getBytes() { + return this.bytes; + } + + /** + * Writes the id of this packet to a given output + * @param output Output to write to + */ + public void writeId(ByteBuf output) { + writeVarInt(output, this.id); + } + + /** + * Writes the contents of this packet to a given output + * @param output Output to write to + */ + public void writeBytes(ByteBuf output) { + checkNotNull(output, "output cannot be null!"); + output.writeBytes(this.bytes); + } + + /** + * Fully writes the ID and contents of this packet to a given output + * @param output Output to write to + */ + public void writeFully(ByteBuf output) { + this.writeId(output); + this.writeBytes(output); + } + + /** + * Serializes this packet into a byte buffer + * @return The buffer + */ + public ByteBuf serialize() { + ByteBuf buffer = Unpooled.buffer(); + this.writeFully(buffer); + return buffer; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj instanceof WirePacket) { + WirePacket that = (WirePacket) obj; + return this.id == that.id && Arrays.equals(this.bytes, that.bytes); + } + + return false; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(this.bytes); + result = prime * result + this.id; + return result; + } + + @Override + public String toString() { + return "WirePacket[id=" + this.id + ", bytes=" + Arrays.toString(this.bytes) + "]"; + } +} diff --git a/src/main/java/com/comphenix/protocol/injector/netty/ClosedInjector.java b/src/main/java/com/comphenix/protocol/injector/netty/channel/EmptyInjector.java similarity index 66% rename from src/main/java/com/comphenix/protocol/injector/netty/ClosedInjector.java rename to src/main/java/com/comphenix/protocol/injector/netty/channel/EmptyInjector.java index aff4055e9..be4a2cbbb 100644 --- a/src/main/java/com/comphenix/protocol/injector/netty/ClosedInjector.java +++ b/src/main/java/com/comphenix/protocol/injector/netty/channel/EmptyInjector.java @@ -1,43 +1,44 @@ -package com.comphenix.protocol.injector.netty; - -import org.bukkit.entity.Player; +package com.comphenix.protocol.injector.netty.channel; import com.comphenix.protocol.PacketType.Protocol; import com.comphenix.protocol.events.NetworkMarker; +import com.comphenix.protocol.injector.netty.Injector; +import org.bukkit.entity.Player; + +final class EmptyInjector implements Injector { + + public static final Injector WITHOUT_PLAYER = new EmptyInjector(null); -/** - * Represents a closed injector. - * @author Kristian - */ -public class ClosedInjector implements Injector { private Player player; - /** - * Construct a new injector that is always closed. - * @param player - the associated player. - */ - public ClosedInjector(Player player) { + public EmptyInjector(Player player) { this.player = player; } + @Override + public int getProtocolVersion() { + return Integer.MIN_VALUE; + } + @Override public boolean inject() { return false; } + @Override + public void uninject() { + } + @Override public void close() { - // Do nothing } @Override public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) { - // Do nothing } @Override - public void recieveClientPacket(Object packet) { - // Do nothing + public void receiveClientPacket(Object packet) { } @Override @@ -52,24 +53,22 @@ public NetworkMarker getMarker(Object packet) { @Override public void saveMarker(Object packet, NetworkMarker marker) { - // Do nothing } - @Override - public void setUpdatedPlayer(Player player) { - // Do nothing - } - @Override public Player getPlayer() { - return player; + return this.player; } - + @Override public void setPlayer(Player player) { this.player = player; } + @Override + public void disconnect(String message) { + } + @Override public boolean isInjected() { return false; @@ -79,9 +78,4 @@ public boolean isInjected() { public boolean isClosed() { return true; } - - @Override - public int getProtocolVersion() { - return Integer.MIN_VALUE; - } } diff --git a/src/main/java/com/comphenix/protocol/injector/netty/channel/InboundPacketInterceptor.java b/src/main/java/com/comphenix/protocol/injector/netty/channel/InboundPacketInterceptor.java new file mode 100644 index 000000000..b5474fd39 --- /dev/null +++ b/src/main/java/com/comphenix/protocol/injector/netty/channel/InboundPacketInterceptor.java @@ -0,0 +1,58 @@ +package com.comphenix.protocol.injector.netty.channel; + +import com.comphenix.protocol.events.NetworkMarker; +import com.comphenix.protocol.events.PacketEvent; +import com.comphenix.protocol.injector.NetworkProcessor; +import com.comphenix.protocol.injector.netty.ChannelListener; +import com.comphenix.protocol.utility.MinecraftReflection; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; + +final class InboundPacketInterceptor extends ChannelInboundHandlerAdapter { + + private final NettyChannelInjector injector; + private final ChannelListener channelListener; + private final NetworkProcessor networkProcessor; + + public InboundPacketInterceptor(NettyChannelInjector injector, ChannelListener listener, NetworkProcessor processor) { + this.injector = injector; + this.channelListener = listener; + this.networkProcessor = processor; + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) { + if (this.shouldInterceptMessage(msg)) { + // process the login if the packet is one before posting the packet to any handler to provide "real" data + // the method invocation will do nothing if the packet is not a login packet + this.injector.tryProcessLogin(msg); + + // call packet handlers, a null result indicates that we shouldn't change anything + PacketEvent interceptionResult = this.channelListener.onPacketReceiving(this.injector, msg, null); + if (interceptionResult == null) { + ctx.fireChannelRead(msg); + return; + } + + // fire the intercepted packet down the pipeline if it wasn't cancelled + if (!interceptionResult.isCancelled()) { + ctx.fireChannelRead(interceptionResult.getPacket().getHandle()); + + // check if there were any post events added the packet after we fired it down the pipeline + // we use this way as we don't want to construct a new network manager accidentally + NetworkMarker marker = NetworkMarker.getNetworkMarker(interceptionResult); + if (marker != null) { + this.networkProcessor.invokePostEvent(interceptionResult, marker); + } + } + } else { + // just pass the message down the pipeline + ctx.fireChannelRead(msg); + } + } + + private boolean shouldInterceptMessage(Object msg) { + // only intercept minecraft packets and no garbage from other stuff in the channel + return MinecraftReflection.getPacketClass().isAssignableFrom(msg.getClass()); + } +} diff --git a/src/main/java/com/comphenix/protocol/injector/netty/channel/InjectionFactory.java b/src/main/java/com/comphenix/protocol/injector/netty/channel/InjectionFactory.java new file mode 100644 index 000000000..0aa67355f --- /dev/null +++ b/src/main/java/com/comphenix/protocol/injector/netty/channel/InjectionFactory.java @@ -0,0 +1,283 @@ +/** + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. Copyright (C) 2015 dmulloy2 + *

    + * 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.netty.channel; + +import com.comphenix.protocol.error.ErrorReporter; +import com.comphenix.protocol.injector.netty.ChannelListener; +import com.comphenix.protocol.injector.netty.Injector; +import com.comphenix.protocol.injector.temporary.MinimalInjector; +import com.comphenix.protocol.injector.temporary.TemporaryPlayerFactory; +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.utility.MinecraftFields; +import com.comphenix.protocol.utility.MinecraftReflection; +import com.google.common.collect.MapMaker; +import io.netty.channel.Channel; +import java.util.concurrent.ConcurrentMap; +import javax.annotation.Nonnull; +import org.bukkit.Server; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +/** + * Represents an injector factory. + *

    + * Note that the factory will return {@link EmptyInjector} when the factory is closed. + * + * @author Kristian + */ +public class InjectionFactory { + + // This should work as long as the injectors are, uh, injected + private final ConcurrentMap nameLookup = new MapMaker().weakValues().makeMap(); + private final ConcurrentMap playerLookup = new MapMaker().weakKeys().weakValues().makeMap(); + + // bukkit stuff + private final Plugin plugin; + private final Server server; + + // protocol lib stuff + private final ErrorReporter errorReporter; + + // state of the factory + private boolean closed; + + public InjectionFactory(Plugin plugin, Server server, ErrorReporter errorReporter) { + this.plugin = plugin; + this.server = server; + this.errorReporter = errorReporter; + } + + /** + * Retrieve the main plugin associated with this injection factory. + * + * @return The main plugin. + */ + public Plugin getPlugin() { + return this.plugin; + } + + /** + * Construct or retrieve a channel injector from an existing Bukkit player. + * + * @param player - the existing Bukkit player. + * @param listener - the listener. + * @return A new injector, an existing injector associated with this player, or a closed injector. + */ + @Nonnull + public Injector fromPlayer(Player player, ChannelListener listener) { + if (this.closed) { + return new EmptyInjector(player); + } + + // try to get the injector using the player reference first + Injector injector = this.playerLookup.get(player); + if (injector == null) { + injector = this.getTemporaryInjector(player); + } + + // check if we found an injector + if (injector != null && !injector.isClosed()) { + return injector; + } + + // check if a network manager is present, if not maybe we cached the player temporarily + Object networkManager = MinecraftFields.getNetworkManager(player); + if (networkManager == null) { + return this.fromName(player.getName(), player); + } + + // get the channel of the player and check if we already hooked into it + Channel channel = FuzzyReflection.getFieldValue(networkManager, Channel.class, true); + injector = NettyChannelInjector.findInjector(channel); + + if (injector != null) { + // check if the new player is not the old one, this saves us a bit when many calls to the method are made + if (injector.getPlayer() != player || !this.playerLookup.containsKey(player)) { + this.playerLookup.remove(injector.getPlayer()); + this.cacheInjector(player, injector); + // re-set the player of the injection + injector.setPlayer(player); + } + } else { + // construct a new injector as it seems like we have none yet + injector = new NettyChannelInjector(this.server, networkManager, channel, listener, this, this.errorReporter); + this.cacheInjector(player, injector); + } + + // definitely not null + return injector; + } + + /** + * Retrieve a cached injector from a name. + *

    + * The injector may be NULL if the plugin has been reloaded during a player login. + * + * @param name - the name. + * @param player - the player. + * @return The cached injector, or a closed injector if it could not be found. + */ + public Injector fromName(String name, Player player) { + if (this.closed) { + return new EmptyInjector(player); + } + + // check if we have a player with that name cached + Injector injector = this.nameLookup.get(name); + if (injector != null) { + injector.setPlayer(player); + return injector; + } + + return new EmptyInjector(player); + } + + /** + * Construct a new channel injector for the given channel. + * + * @param channel - the channel. + * @param listener - the listener. + * @param playerFactory - a temporary player creator. + * @return The channel injector, or a closed injector. + */ + @Nonnull + public Injector fromChannel(Channel channel, ChannelListener listener, TemporaryPlayerFactory playerFactory) { + if (this.closed) { + return EmptyInjector.WITHOUT_PLAYER; + } + + Object netManager = this.findNetworkManager(channel); + Player temporaryPlayer = playerFactory.createTemporaryPlayer(this.server); + + NettyChannelInjector injector = new NettyChannelInjector( + this.server, + netManager, + channel, + listener, + this, + this.errorReporter); + MinimalInjector minimalInjector = new NettyChannelMinimalInjector(injector); + + // Initialize temporary player + TemporaryPlayerFactory.setInjectorInPlayer(temporaryPlayer, minimalInjector); + return injector; + } + + /** + * Invalidate a cached injector. + * + * @param player - the associated player. + * @return The cached injector, or NULL if nothing was cached. + */ + public Injector invalidate(Player player, String name) { + Injector injector = null; + + // try the name first, more unsafe but works 99% of the time + if (name != null) { + injector = this.nameLookup.remove(name); + } + + // if we have a player then use that as the safe removal way + if (player != null) { + injector = this.playerLookup.remove(player); + } + + return injector; + } + + /** + * Cache an injector by player. + * + * @param player - the player. + * @param injector - the injector to cache. + * @return The previously cached injector. + */ + public Injector cacheInjector(Player player, Injector injector) { + this.nameLookup.put(player.getName(), injector); + return this.playerLookup.put(player, injector); + } + + /** + * Cache an injector by name alone. + * + * @param name - the name to lookup. + * @param injector - the injector. + * @return The cached injector. + */ + public Injector cacheInjector(String name, Injector injector) { + return this.nameLookup.put(name, injector); + } + + /** + * Retrieve the associated channel injector. + * + * @param player - the temporary player, or normal Bukkit player. + * @return The associated injector, or NULL if this is a Bukkit player. + */ + private NettyChannelInjector getTemporaryInjector(Player player) { + MinimalInjector injector = TemporaryPlayerFactory.getInjectorFromPlayer(player); + if (injector instanceof NettyChannelMinimalInjector) { + return ((NettyChannelMinimalInjector) injector).getInjector(); + } + + return null; + } + + /** + * Find the network manager in a channel's pipeline. + * + * @param channel - the channel. + * @return The network manager. + */ + private Object findNetworkManager(Channel channel) { + // Find the network manager + Object networkManager = NettyChannelInjector.findChannelHandler(channel, + MinecraftReflection.getNetworkManagerClass()); + if (networkManager != null) { + return networkManager; + } + + throw new IllegalArgumentException("Unable to find NetworkManager in " + channel); + } + + /** + * Determine if the factory is closed. + *

    + * If it is, all new injectors will be closed by default. + * + * @return TRUE if it is closed, FALSE otherwise. + */ + public boolean isClosed() { + return this.closed; + } + + /** + * Close all injectors created by this factory, and cease the creation of new injections. + */ + public void close() { + if (!this.closed) { + this.closed = true; + + // Close everything + for (Injector injector : this.playerLookup.values()) { + injector.close(); + } + + for (Injector injector : this.nameLookup.values()) { + injector.close(); + } + } + } +} diff --git a/src/main/java/com/comphenix/protocol/injector/netty/channel/NettyChannelInjector.java b/src/main/java/com/comphenix/protocol/injector/netty/channel/NettyChannelInjector.java new file mode 100644 index 000000000..812ea97ea --- /dev/null +++ b/src/main/java/com/comphenix/protocol/injector/netty/channel/NettyChannelInjector.java @@ -0,0 +1,595 @@ +package com.comphenix.protocol.injector.netty.channel; + +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.PacketType.Protocol; +import com.comphenix.protocol.error.ErrorReporter; +import com.comphenix.protocol.error.Report; +import com.comphenix.protocol.error.ReportType; +import com.comphenix.protocol.events.NetworkMarker; +import com.comphenix.protocol.events.PacketEvent; +import com.comphenix.protocol.injector.NetworkProcessor; +import com.comphenix.protocol.injector.netty.ChannelListener; +import com.comphenix.protocol.injector.netty.Injector; +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.accessors.FieldAccessor; +import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract; +import com.comphenix.protocol.utility.ByteBuddyFactory; +import com.comphenix.protocol.utility.ByteBuddyGenerated; +import com.comphenix.protocol.utility.MinecraftFields; +import com.comphenix.protocol.utility.MinecraftMethods; +import com.comphenix.protocol.utility.MinecraftProtocolVersion; +import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.wrappers.WrappedGameProfile; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler; +import io.netty.util.AttributeKey; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; +import org.bukkit.Server; +import org.bukkit.entity.Player; + +public class NettyChannelInjector implements Injector { + + // an accessor used when we're unable to retrieve the actual packet field in an outbound packet send + private static final FieldAccessor NO_OP_ACCESSOR = new FieldAccessor() { + @Override + public Object get(Object instance) { + return null; + } + + @Override + public void set(Object instance, Object value) { + } + + @Override + public Field getField() { + return null; + } + }; + + private static final String INTERCEPTOR_NAME = "protocol_lib_inbound_interceptor"; + private static final String WIRE_PACKET_ENCODER_NAME = "protocol_lib_wire_packet_encoder"; + + // all registered channel handlers to easier make sure we unregister them all from the pipeline + private static final String[] PROTOCOL_LIB_HANDLERS = new String[]{ + WIRE_PACKET_ENCODER_NAME, INTERCEPTOR_NAME + }; + + private static final ReportType REPORT_CANNOT_SEND_PACKET = new ReportType("Unable to send packet %s to %s"); + + private static final WirePacketEncoder WIRE_PACKET_ENCODER = new WirePacketEncoder(); + private static final Map, FieldAccessor> PACKET_ACCESSORS = new ConcurrentHashMap<>(16, 0.9f); + + private static final Class LOGIN_PACKET_START_CLASS = PacketType.Login.Client.START.getPacketClass(); + private static final Class PACKET_PROTOCOL_CLASS = PacketType.Handshake.Client.SET_PROTOCOL.getPacketClass(); + + private static final AttributeKey PROTOCOL_VERSION = AttributeKey.valueOf(getRandomKey()); + private static final AttributeKey INJECTOR = AttributeKey.valueOf(getRandomKey()); + + // lazy initialized fields, if we don't need them we don't bother about them + private static FieldAccessor LOGIN_GAME_PROFILE; + private static FieldAccessor PROTOCOL_VERSION_ACCESSOR; + + // bukkit stuff + private final Server server; + + // protocol lib stuff we need + private final ErrorReporter errorReporter; + private final NetworkProcessor networkProcessor; + + // references + private final Object networkManager; + private final Channel wrappedChannel; + private final ChannelListener channelListener; + private final InjectionFactory injectionFactory; + + private final FieldAccessor channelField; + + private final Set skippedPackets = new LinkedHashSet<>(); + private final Map savedMarkers = new WeakHashMap<>(16, 0.9f); + + // status of this injector + private volatile boolean closed = false; + private volatile boolean injected = false; + + // information about the player belonging to this injector + private String playerName; + private Player resolvedPlayer; + + // lazy initialized fields, if we don't need them we don't bother about them + private Object playerConnection; + private FieldAccessor protocolAccessor; + + public NettyChannelInjector( + Server server, + Object netManager, + Channel channel, + ChannelListener listener, + InjectionFactory injector, + ErrorReporter errorReporter + ) { + // bukkit stuff + this.server = server; + + // protocol lib stuff + this.errorReporter = errorReporter; + this.networkProcessor = new NetworkProcessor(errorReporter); + + // references + this.networkManager = netManager; + this.wrappedChannel = channel; + this.channelListener = listener; + this.injectionFactory = injector; + + // register us into the channel + this.wrappedChannel.attr(INJECTOR).set(this); + + // read the channel field from the network manager given to this method + // we re-read this field every time as plugins/spigot forks might give us different network manager types + Field channelField = FuzzyReflection.fromObject(netManager, true).getField(FuzzyFieldContract.newBuilder() + .typeExact(Channel.class) + .banModifier(Modifier.STATIC) + .build()); + this.channelField = Accessors.getFieldAccessor(channelField, true); + + // hook here into the close future to be 100% sure that this injector gets closed when the channel we wrap gets closed + // normally we listen to the disconnect event, but there is a very small period of time, between the login and actual + // join that is not covered by the disconnect event and may lead to unexpected injector states... + this.wrappedChannel.closeFuture().addListener(future -> this.close()); + } + + static NettyChannelInjector findInjector(Channel channel) { + return channel.attr(INJECTOR).get(); + } + + static Object findChannelHandler(Channel channel, Class type) { + for (Entry entry : channel.pipeline()) { + if (type.isAssignableFrom(entry.getValue().getClass())) { + return entry.getValue(); + } + } + return null; + } + + private static String getRandomKey() { + return Long.toString(System.nanoTime()); + } + + private static boolean hasProtocolLibHandler(Channel channel) { + for (String handler : PROTOCOL_LIB_HANDLERS) { + if (channel.pipeline().get(handler) != null) { + return true; + } + } + return false; + } + + @Override + public int getProtocolVersion() { + Integer protocolVersion = this.wrappedChannel.attr(PROTOCOL_VERSION).get(); + return protocolVersion == null ? MinecraftProtocolVersion.getCurrentVersion() : protocolVersion; + } + + @Override + public boolean inject() { + // we only do this on the channel event loop to prevent blocking the main server thread + // and to be sure that the netty pipeline view we get is up-to-date + if (this.wrappedChannel.eventLoop().inEventLoop()) { + // ensure that we should actually inject into the channel + if (this.closed || this.wrappedChannel instanceof ByteBuddyFactory || !this.wrappedChannel.isActive()) { + return false; + } + + // check here if we need to rewrite the channel field and do so + // minecraft overrides the channel field when the channel actually becomes active, so we need to ensure that our + // proxied channel is always on that field - therefore this rewrite is event before we check if we're already + // injected into the channel + this.rewriteChannelField(); + + // check if we already injected into the channel + if (hasProtocolLibHandler(this.wrappedChannel)) { + return false; + } + + // inject our handlers + this.wrappedChannel.pipeline().addAfter("encoder", WIRE_PACKET_ENCODER_NAME, WIRE_PACKET_ENCODER); + this.wrappedChannel.pipeline().addAfter( + "decoder", + INTERCEPTOR_NAME, + new InboundPacketInterceptor(this, this.channelListener, this.networkProcessor)); + + this.injected = true; + return true; + } else { + // re-run in event loop, return false as we cannot be sure if the injection actually worked + this.ensureInEventLoop(this::inject); + return false; + } + } + + @Override + public void uninject() { + // ensure that we injected into the channel before trying to remove anything from it + if (this.injected) { + // uninject on the event loop to ensure the instant visibility of the change and prevent blocks of other threads + if (this.wrappedChannel.eventLoop().inEventLoop()) { + this.injected = false; + + // remove known references to us + this.wrappedChannel.attr(INJECTOR).remove(); + this.channelField.set(this.networkManager, this.wrappedChannel); + + for (String handler : PROTOCOL_LIB_HANDLERS) { + try { + this.wrappedChannel.pipeline().remove(handler); + } catch (NoSuchElementException ignored) { + // ignore that one, probably an edge case + } + } + } else { + this.ensureInEventLoop(this::uninject); + } + } + } + + @Override + public void close() { + // ensure that the injector wasn't close before + if (!this.closed) { + this.closed = true; + + // remove all of our references from the channel + this.uninject(); + + // cleanup + this.savedMarkers.clear(); + this.skippedPackets.clear(); + + // wipe this injector completely + this.injectionFactory.invalidate(this.getPlayer(), this.playerName); + } + } + + @Override + public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) { + // do not send the packet if this injector was already closed / is not injected yet + if (this.closed || !this.injected) { + return; + } + + // register the packet as filtered if we shouldn't post it to any listener + if (!filtered) { + this.skippedPackets.add(packet); + } + + // save the given packet marker and send the packet + this.saveMarker(packet, marker); + try { + if (this.resolvedPlayer instanceof ByteBuddyGenerated) { + MinecraftMethods.getNetworkManagerHandleMethod().invoke(this.networkManager, packet); + } else { + // ensure that the player is properly connected before sending + Object playerConnection = this.getPlayerConnection(); + if (playerConnection != null) { + MinecraftMethods.getSendPacketMethod().invoke(playerConnection, packet); + } + } + } catch (Exception exception) { + this.errorReporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_SEND_PACKET) + .messageParam(packet, this.playerName) + .error(exception) + .build()); + } + } + + @Override + public void receiveClientPacket(Object packet) { + // do not do that if we're not injected or this injector was closed + if (this.closed || !this.injected) { + return; + } + + Runnable receiveAction = () -> { + try { + // try to invoke the method, this should normally not fail + MinecraftMethods.getNetworkManagerReadPacketMethod().invoke(this.networkManager, null, packet); + } catch (Exception exception) { + // 99% the user gave wrong information to the server + this.errorReporter.reportMinimal(this.injectionFactory.getPlugin(), "receiveClientPacket", exception); + } + }; + + // execute the action on the event loop rather than any thread which we should potentially not block + if (this.wrappedChannel.eventLoop().inEventLoop()) { + receiveAction.run(); + } else { + this.ensureInEventLoop(receiveAction); + } + } + + @Override + public Protocol getCurrentProtocol() { + // ensure that the accessor to the protocol field is available + if (this.protocolAccessor == null) { + this.protocolAccessor = Accessors.getFieldAccessor( + this.networkManager.getClass(), + MinecraftReflection.getEnumProtocolClass(), + true); + } + + Object nmsProtocol = this.protocolAccessor.get(this.networkManager); + return Protocol.fromVanilla((Enum) nmsProtocol); + } + + @Override + public NetworkMarker getMarker(Object packet) { + return this.savedMarkers.get(packet); + } + + @Override + public void saveMarker(Object packet, NetworkMarker marker) { + if (marker != null && !this.closed) { + this.savedMarkers.put(packet, marker); + } + } + + @Override + public Player getPlayer() { + // if the player was already resolved there is no need to do further lookups + if (this.resolvedPlayer != null) { + return this.resolvedPlayer; + } + + // check if the name of the player is already known to the injector + if (this.playerName != null) { + this.resolvedPlayer = this.server.getPlayerExact(this.playerName); + } + + // either we resolved it or we didn't... + return this.resolvedPlayer; + } + + @Override + public void setPlayer(Player player) { + this.resolvedPlayer = player; + this.playerName = player.getName(); + } + + @Override + public void disconnect(String message) { + // we're still during pre-login, just close the connection + if (this.playerConnection == null || this.resolvedPlayer instanceof ByteBuddyGenerated) { + this.wrappedChannel.disconnect(); + } else { + try { + // try to call the disconnect method on the player + MinecraftMethods.getDisconnectMethod(this.playerConnection.getClass()).invoke(this.playerConnection, message); + } catch (Exception exception) { + throw new IllegalArgumentException("Unable to disconnect the current injector", exception); + } + } + } + + @Override + public boolean isInjected() { + return this.injected; + } + + @Override + public boolean isClosed() { + return this.closed; + } + + void tryProcessLogin(Object packet) { + // check if the given packet is a login packet + if (LOGIN_PACKET_START_CLASS != null && LOGIN_PACKET_START_CLASS.equals(packet.getClass())) { + // ensure that the game profile accessor is available + if (LOGIN_GAME_PROFILE == null) { + LOGIN_GAME_PROFILE = Accessors.getFieldAccessor( + LOGIN_PACKET_START_CLASS, + MinecraftReflection.getGameProfileClass(), + true); + } + + // the client only sends the name but the server wraps it into a GameProfile, so here we are + WrappedGameProfile profile = WrappedGameProfile.fromHandle(LOGIN_GAME_PROFILE.get(packet)); + + // cache the injector and the player name + this.playerName = profile.getName(); + this.injectionFactory.cacheInjector(profile.getName(), this); + + return; + } + + // protocol version begin + if (PACKET_PROTOCOL_CLASS != null && PACKET_PROTOCOL_CLASS.equals(packet.getClass())) { + // ensure the protocol version accessor is available + if (PROTOCOL_VERSION_ACCESSOR == null) { + try { + Field ver = FuzzyReflection.fromClass(PACKET_PROTOCOL_CLASS, true).getField(FuzzyFieldContract.newBuilder() + .banModifier(Modifier.STATIC) + .typeExact(int.class) + .build()); + PROTOCOL_VERSION_ACCESSOR = Accessors.getFieldAccessor(ver, true); + } catch (IllegalArgumentException exception) { + // unable to resolve that field, continue no-op + PROTOCOL_VERSION_ACCESSOR = NO_OP_ACCESSOR; + } + } + + // read the protocol version from the field if available + if (PROTOCOL_VERSION_ACCESSOR != NO_OP_ACCESSOR) { + int protocolVersion = (int) PROTOCOL_VERSION_ACCESSOR.get(packet); + this.wrappedChannel.attr(PROTOCOL_VERSION).set(protocolVersion); + } + } + } + + private void rewriteChannelField() { + // check if we need to rewrite the channel or if the channel is already correct (prevent wrapping a wrapped channel) + Object currentChannel = this.channelField.get(this.networkManager); + if (currentChannel instanceof NettyChannelProxy) { + return; + } + + // the field is not correct, rewrite now to our handler + Channel ch = new NettyChannelProxy(this.wrappedChannel, new NettyEventLoopProxy(this.wrappedChannel.eventLoop()) { + @Override + protected Runnable proxyRunnable(Runnable original) { + return NettyChannelInjector.this.processOutbound(original); + } + + @Override + protected Callable proxyCallable(Callable original) { + return NettyChannelInjector.this.processOutbound(original); + } + }); + this.channelField.set(this.networkManager, ch); + } + + private void ensureInEventLoop(Runnable runnable) { + this.wrappedChannel.eventLoop().execute(runnable); + } + + private T processOutbound(T action) { + // get the accessor to the packet field + // if we are unable to look up the accessor then just return the runnable, probably nothing of our business + FieldAccessor packetAccessor = this.lookupPacketAccessor(action); + if (packetAccessor == NO_OP_ACCESSOR) { + return action; + } + + // get the packet field and ensure that the field is actually present (should always be, just to be sure) + Object packet = packetAccessor.get(action); + if (packet == null) { + return action; + } + + // filter out all packets which were explicitly send to not be processed by any event + NetworkMarker marker = this.savedMarkers.remove(packet); + if (this.skippedPackets.remove(packet)) { + // if a marker was set there might be scheduled packets to execute after the packet send + // for this to work we need to proxy the input action to provide access to them + if (marker != null) { + return this.proxyAction(action, null, marker); + } + + // nothing special, just no processing + return action; + } + + // no listener and no marker - no magic :) + if (!this.channelListener.hasListener(packet.getClass()) && marker == null) { + return action; + } + + // ensure that we are on the main thread if we need to + if (this.channelListener.hasMainThreadListener(packet.getClass()) && !this.server.isPrimaryThread()) { + // not on the main thread but we are required to be - re-schedule the packet on the main thread + this.server.getScheduler().scheduleSyncDelayedTask( + this.injectionFactory.getPlugin(), + () -> this.sendServerPacket(packet, null, false)); + return null; + } + + // ensure that we're not on the main thread if we don't need to + if (!this.channelListener.hasMainThreadListener(packet.getClass()) && this.server.isPrimaryThread()) { + // re-schedule async + this.server.getScheduler().runTaskAsynchronously( + this.injectionFactory.getPlugin(), + () -> this.sendServerPacket(packet, null, false)); + return null; + } + + // call all listeners which are listening to the outbound packet, if any + // null indicates that no listener was affected by the packet, meaning that we can directly send the original packet + PacketEvent event = this.channelListener.onPacketSending(this, packet, marker); + if (event == null) { + return action; + } + + // if the event wasn't cancelled by this action we must recheck if the packet changed during the method call + if (!event.isCancelled()) { + // rewrite the packet in the given action if the packet was changed during the event call + Object interceptedPacket = event.getPacket().getHandle(); + if (interceptedPacket != packet) { + packetAccessor.set(action, interceptedPacket); + } + + // this is essential to do this way as a call to getMarker on the event will construct a new marker instance if needed + // we just want to know here if there is a marker to proceed correctly + // if the marker is null we can just schedule the action as we don't need to do anything after the packet was sent + NetworkMarker eventMarker = NetworkMarker.getNetworkMarker(event); + if (eventMarker == null) { + return action; + } + + // we need to wrap the action to call the listeners set in the marker + return this.proxyAction(action, event, eventMarker); + } + + // return null if the event was cancelled to schedule a no-op event + return null; + } + + @SuppressWarnings("unchecked") + private T proxyAction(T action, PacketEvent event, NetworkMarker marker) { + // hack - we only know that the given action is either a runnable or callable, but we need to work out which thing + // it is exactly to proceed correctly here. + if (action instanceof Runnable) { + // easier thing to do - just wrap the runnable in a new one + return (T) (Runnable) () -> { + ((Runnable) action).run(); + this.networkProcessor.invokePostEvent(event, marker); + }; + } else if (action instanceof Callable) { + // okay this is a bit harder now - we need to wrap the action and return the value of it + return (T) (Callable) () -> { + Object value = ((Callable) action).call(); + this.networkProcessor.invokePostEvent(event, marker); + return value; + }; + } else { + throw new IllegalStateException("Unexpected input action of type " + action.getClass()); + } + } + + private FieldAccessor lookupPacketAccessor(Object action) { + return PACKET_ACCESSORS.computeIfAbsent(action.getClass(), clazz -> { + try { + return Accessors.getFieldAccessor(action.getClass(), MinecraftReflection.getPacketClass(), true); + } catch (IllegalArgumentException exception) { + // no such field found :( + return NO_OP_ACCESSOR; + } + }); + } + + private Object getPlayerConnection() { + // resolve the player connection if needed + if (this.playerConnection == null) { + Player target = this.getPlayer(); + if (target == null) { + return null; + } + + this.playerConnection = MinecraftFields.getPlayerConnection(target); + } + + // cannot be null at this point + return this.playerConnection; + } + + public Channel getWrappedChannel() { + return this.wrappedChannel; + } +} diff --git a/src/main/java/com/comphenix/protocol/injector/netty/channel/NettyChannelMinimalInjector.java b/src/main/java/com/comphenix/protocol/injector/netty/channel/NettyChannelMinimalInjector.java new file mode 100644 index 000000000..a353d0a5b --- /dev/null +++ b/src/main/java/com/comphenix/protocol/injector/netty/channel/NettyChannelMinimalInjector.java @@ -0,0 +1,44 @@ +package com.comphenix.protocol.injector.netty.channel; + +import com.comphenix.protocol.events.NetworkMarker; +import com.comphenix.protocol.injector.temporary.MinimalInjector; +import java.net.SocketAddress; +import org.bukkit.entity.Player; + +final class NettyChannelMinimalInjector implements MinimalInjector { + + private final NettyChannelInjector injector; + + public NettyChannelMinimalInjector(NettyChannelInjector injector) { + this.injector = injector; + } + + @Override + public SocketAddress getAddress() { + return this.injector.getWrappedChannel().remoteAddress(); + } + + @Override + public void disconnect(String message) { + this.injector.disconnect(message); + } + + @Override + public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) { + this.injector.sendServerPacket(packet, marker, filtered); + } + + @Override + public Player getPlayer() { + return this.injector.getPlayer(); + } + + @Override + public boolean isConnected() { + return this.injector.getWrappedChannel().isActive(); + } + + public NettyChannelInjector getInjector() { + return this.injector; + } +} diff --git a/src/main/java/com/comphenix/protocol/injector/netty/channel/NettyChannelProxy.java b/src/main/java/com/comphenix/protocol/injector/netty/channel/NettyChannelProxy.java new file mode 100644 index 000000000..42f85cd33 --- /dev/null +++ b/src/main/java/com/comphenix/protocol/injector/netty/channel/NettyChannelProxy.java @@ -0,0 +1,227 @@ +package com.comphenix.protocol.injector.netty.channel; + +import io.netty.buffer.ByteBufAllocator; +import io.netty.channel.Channel; +import io.netty.channel.ChannelConfig; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelMetadata; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.ChannelProgressivePromise; +import io.netty.channel.ChannelPromise; +import io.netty.channel.EventLoop; +import io.netty.util.Attribute; +import io.netty.util.AttributeKey; +import java.net.SocketAddress; +import org.jetbrains.annotations.NotNull; + +/** + * A netty channel which has all methods delegated to another given channel except for the event loop which is proxied + * separately. This class can not extend from AbstractChannel as the {@code newUnsafe()} method is protected and can + * therefore not be called in the delegate channel. + */ +final class NettyChannelProxy implements Channel { + + private final Channel delegate; + private final EventLoop eventLoop; + + public NettyChannelProxy(Channel delegate, EventLoop eventLoop) { + this.delegate = delegate; + this.eventLoop = eventLoop; + } + + @Override + public EventLoop eventLoop() { + return this.eventLoop; + } + + @Override + public Channel parent() { + return this.delegate.parent(); + } + + @Override + public ChannelConfig config() { + return this.delegate.config(); + } + + @Override + public boolean isOpen() { + return this.delegate.isOpen(); + } + + @Override + public boolean isRegistered() { + return this.delegate.isRegistered(); + } + + @Override + public boolean isActive() { + return this.delegate.isActive(); + } + + @Override + public ChannelMetadata metadata() { + return this.delegate.metadata(); + } + + @Override + public SocketAddress localAddress() { + return this.delegate.localAddress(); + } + + @Override + public SocketAddress remoteAddress() { + return this.delegate.remoteAddress(); + } + + @Override + public ChannelFuture closeFuture() { + return this.delegate.closeFuture(); + } + + @Override + public boolean isWritable() { + return this.delegate.isWritable(); + } + + @Override + public Unsafe unsafe() { + return this.delegate.unsafe(); + } + + @Override + public ChannelPipeline pipeline() { + return this.delegate.pipeline(); + } + + @Override + public ByteBufAllocator alloc() { + return this.delegate.alloc(); + } + + @Override + public ChannelPromise newPromise() { + return this.delegate.newPromise(); + } + + @Override + public ChannelProgressivePromise newProgressivePromise() { + return this.delegate.newProgressivePromise(); + } + + @Override + public ChannelFuture newSucceededFuture() { + return this.delegate.newSucceededFuture(); + } + + @Override + public ChannelFuture newFailedFuture(Throwable cause) { + return this.delegate.newFailedFuture(cause); + } + + @Override + public ChannelPromise voidPromise() { + return this.delegate.voidPromise(); + } + + @Override + public ChannelFuture bind(SocketAddress localAddress) { + return this.delegate.bind(localAddress); + } + + @Override + public ChannelFuture connect(SocketAddress remoteAddress) { + return this.delegate.connect(remoteAddress); + } + + @Override + public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) { + return this.delegate.connect(remoteAddress, localAddress); + } + + @Override + public ChannelFuture disconnect() { + return this.delegate.disconnect(); + } + + @Override + public ChannelFuture close() { + return this.delegate.close(); + } + + @Override + public ChannelFuture deregister() { + return this.delegate.deregister(); + } + + @Override + public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) { + return this.delegate.bind(localAddress, promise); + } + + @Override + public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) { + return this.delegate.connect(remoteAddress, promise); + } + + @Override + public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress, + ChannelPromise promise) { + return this.delegate.connect(remoteAddress, localAddress, promise); + } + + @Override + public ChannelFuture disconnect(ChannelPromise promise) { + return this.delegate.disconnect(promise); + } + + @Override + public ChannelFuture close(ChannelPromise promise) { + return this.delegate.close(promise); + } + + @Override + public ChannelFuture deregister(ChannelPromise promise) { + return this.delegate.deregister(promise); + } + + @Override + public Channel read() { + return this.delegate.read(); + } + + @Override + public ChannelFuture write(Object msg) { + return this.delegate.write(msg); + } + + @Override + public ChannelFuture write(Object msg, ChannelPromise promise) { + return this.delegate.write(msg, promise); + } + + @Override + public Channel flush() { + return this.delegate.flush(); + } + + @Override + public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) { + return this.delegate.writeAndFlush(msg, promise); + } + + @Override + public ChannelFuture writeAndFlush(Object msg) { + return this.delegate.writeAndFlush(msg); + } + + @Override + public Attribute attr(AttributeKey key) { + return this.delegate.attr(key); + } + + @Override + public int compareTo(@NotNull Channel o) { + return this.delegate.compareTo(o); + } +} diff --git a/src/main/java/com/comphenix/protocol/injector/netty/channel/NettyEventLoopProxy.java b/src/main/java/com/comphenix/protocol/injector/netty/channel/NettyEventLoopProxy.java new file mode 100644 index 000000000..1e5e33faf --- /dev/null +++ b/src/main/java/com/comphenix/protocol/injector/netty/channel/NettyEventLoopProxy.java @@ -0,0 +1,245 @@ +package com.comphenix.protocol.injector.netty.channel; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelPromise; +import io.netty.channel.EventLoop; +import io.netty.channel.EventLoopGroup; +import io.netty.util.concurrent.EventExecutor; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.ProgressivePromise; +import io.netty.util.concurrent.Promise; +import io.netty.util.concurrent.ScheduledFuture; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Spliterator; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; + +/** + * An abstract event loop implementation which delegates all calls to a given event loop, but proxies all calls which + * schedule something on the event loop to methods which decide what should happen to the scheduled task. + */ +abstract class NettyEventLoopProxy implements EventLoop { + + private static final Callable EMPTY_CALLABLE = () -> null; + private static final Runnable EMPTY_RUNNABLE = () -> { + }; + + private final EventLoop delegate; + + public NettyEventLoopProxy(EventLoop delegate) { + this.delegate = delegate; + } + + /** + * Proxies the given runnable. The returned runnable will be executed instead of the original. If this method returns + * null a no-op runnable will be scheduled instead, preventing the original action from happening. + * + * @param original the runnable to proxy. + * @return the runnable to execute instead, null to execute no action. + */ + protected abstract Runnable proxyRunnable(Runnable original); + + /** + * Proxies the given callable. The returned callable will be executed instead of the original. If this method returns + * null a callable which always returns null will be scheduled instead, preventing the original action from + * happening. + * + * @param original the callable to proxy. + * @param the return type of the original callable. + * @return the callable to execute instead of the original, null to use a no-op callable instead. + */ + protected abstract Callable proxyCallable(Callable original); + + @Override + public EventLoopGroup parent() { + return this.delegate.parent(); + } + + @Override + public EventLoop next() { + return this.delegate.next(); + } + + @Override + public boolean inEventLoop() { + return this.delegate.inEventLoop(); + } + + @Override + public boolean inEventLoop(Thread thread) { + return this.delegate.inEventLoop(thread); + } + + @Override + public Promise newPromise() { + return this.delegate.newPromise(); + } + + @Override + public ProgressivePromise newProgressivePromise() { + return this.delegate.newProgressivePromise(); + } + + @Override + public Future newSucceededFuture(V result) { + return this.delegate.newSucceededFuture(result); + } + + @Override + public Future newFailedFuture(Throwable cause) { + return this.delegate.newFailedFuture(cause); + } + + @Override + public boolean isShuttingDown() { + return this.delegate.isShuttingDown(); + } + + @Override + public Future shutdownGracefully() { + return this.delegate.shutdownGracefully(); + } + + @Override + public Future shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit) { + return this.delegate.shutdownGracefully(quietPeriod, timeout, unit); + } + + @Override + public Future terminationFuture() { + return this.delegate.terminationFuture(); + } + + @Override + @Deprecated + public void shutdown() { + this.delegate.shutdown(); + } + + @Override + @Deprecated + public List shutdownNow() { + return this.delegate.shutdownNow(); + } + + @Override + public Iterator iterator() { + return this.delegate.iterator(); + } + + @Override + public Future submit(Runnable task) { + Runnable proxied = this.proxyRunnable(task); + return this.delegate.submit(proxied == null ? EMPTY_RUNNABLE : proxied); + } + + @Override + public Future submit(Runnable task, T result) { + Runnable proxied = this.proxyRunnable(task); + return this.delegate.submit(proxied == null ? EMPTY_RUNNABLE : proxied, result); + } + + @Override + @SuppressWarnings("unchecked") + public Future submit(Callable task) { + Callable proxied = this.proxyCallable(task); + return this.delegate.submit(proxied == null ? (Callable) EMPTY_CALLABLE : proxied); + } + + @Override + public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { + Runnable proxied = this.proxyRunnable(command); + return this.delegate.schedule(proxied == null ? EMPTY_RUNNABLE : proxied, delay, unit); + } + + @Override + @SuppressWarnings("unchecked") + public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { + Callable proxied = this.proxyCallable(callable); + return this.delegate.schedule(callable == null ? (Callable) EMPTY_CALLABLE : proxied, delay, unit); + } + + @Override + public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { + Runnable proxied = this.proxyRunnable(command); + return this.delegate.scheduleAtFixedRate(proxied == null ? EMPTY_RUNNABLE : proxied, initialDelay, period, unit); + } + + @Override + public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { + Runnable proxied = this.proxyRunnable(command); + return this.delegate.scheduleWithFixedDelay(proxied == null ? EMPTY_RUNNABLE : proxied, initialDelay, delay, unit); + } + + @Override + public boolean isShutdown() { + return this.delegate.isShutdown(); + } + + @Override + public boolean isTerminated() { + return this.delegate.isTerminated(); + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + return this.delegate.awaitTermination(timeout, unit); + } + + @Override + public List> invokeAll(Collection> tasks) + throws InterruptedException { + return this.delegate.invokeAll(tasks); + } + + @Override + public List> invokeAll(Collection> tasks, long timeout, + TimeUnit unit) throws InterruptedException { + return this.delegate.invokeAll(tasks, timeout, unit); + } + + @Override + public T invokeAny(Collection> tasks) throws InterruptedException, ExecutionException { + return this.delegate.invokeAny(tasks); + } + + @Override + public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + return this.delegate.invokeAny(tasks, timeout, unit); + } + + @Override + public void execute(Runnable command) { + Runnable proxied = this.proxyRunnable(command); + if (proxied != null) { + this.delegate.execute(proxied); + } + } + + @Override + public void forEach(Consumer action) { + this.delegate.forEach(action); + } + + @Override + public Spliterator spliterator() { + return this.delegate.spliterator(); + } + + @Override + public ChannelFuture register(Channel channel) { + return this.delegate.register(channel); + } + + @Override + public ChannelFuture register(Channel channel, ChannelPromise promise) { + return this.delegate.register(channel, promise); + } +} diff --git a/src/main/java/com/comphenix/protocol/injector/netty/channel/WirePacketEncoder.java b/src/main/java/com/comphenix/protocol/injector/netty/channel/WirePacketEncoder.java new file mode 100644 index 000000000..94817cd27 --- /dev/null +++ b/src/main/java/com/comphenix/protocol/injector/netty/channel/WirePacketEncoder.java @@ -0,0 +1,25 @@ +package com.comphenix.protocol.injector.netty.channel; + +import com.comphenix.protocol.injector.netty.WirePacket; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; + +final class WirePacketEncoder extends MessageToByteEncoder { + + @Override + protected void encode(ChannelHandlerContext ctx, WirePacket msg, ByteBuf out) throws Exception { + msg.writeFully(out); + } + + @Override + public boolean acceptOutboundMessage(Object msg) { + return msg instanceof WirePacket; + } + + @Override + public boolean isSharable() { + // we do it this way to prevent the lookup overheat + return true; + } +} diff --git a/src/main/java/com/comphenix/protocol/injector/netty/manager/InjectionChannelInboundHandler.java b/src/main/java/com/comphenix/protocol/injector/netty/manager/InjectionChannelInboundHandler.java new file mode 100644 index 000000000..71c861be7 --- /dev/null +++ b/src/main/java/com/comphenix/protocol/injector/netty/manager/InjectionChannelInboundHandler.java @@ -0,0 +1,53 @@ +package com.comphenix.protocol.injector.netty.manager; + +import com.comphenix.protocol.error.Report; +import com.comphenix.protocol.error.ReportType; +import com.comphenix.protocol.injector.netty.channel.InjectionFactory; +import com.comphenix.protocol.injector.temporary.TemporaryPlayerFactory; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; + +final class InjectionChannelInboundHandler extends ChannelInboundHandlerAdapter { + + private static final ReportType CANNOT_INJECT_CHANNEL = new ReportType("Unable to inject incoming channel %s."); + + private final InjectionFactory factory; + private final NetworkManagerInjector listener; + private final TemporaryPlayerFactory playerFactory; + + public InjectionChannelInboundHandler( + InjectionFactory factory, + NetworkManagerInjector listener, + TemporaryPlayerFactory playerFactory + ) { + this.factory = factory; + this.listener = listener; + this.playerFactory = playerFactory; + } + + @Override + public void channelActive(ChannelHandlerContext ctx) { + // the channel is now active, at this point minecraft has eventually prepared everything in the connection + // of the player so that we can come in and hook as we are after the minecraft handler + try { + this.factory.fromChannel(ctx.channel(), this.listener, this.playerFactory).inject(); + } catch (Exception exception) { + this.listener.getReporter().reportDetailed(this.listener, Report.newBuilder(CANNOT_INJECT_CHANNEL) + .messageParam(ctx.channel()) + .error(exception) + .build()); + } + + // remove this handler from the pipeline now to prevent multiple injections + ctx.channel().pipeline().remove(this); + + // fire it down the pipeline in case someone else needs it + ctx.fireChannelActive(); + } + + @Override + public boolean isSharable() { + // we do it this way to prevent the lookup overheat + return true; + } +} diff --git a/src/main/java/com/comphenix/protocol/injector/netty/manager/InjectionChannelInitializer.java b/src/main/java/com/comphenix/protocol/injector/netty/manager/InjectionChannelInitializer.java new file mode 100644 index 000000000..08b10d416 --- /dev/null +++ b/src/main/java/com/comphenix/protocol/injector/netty/manager/InjectionChannelInitializer.java @@ -0,0 +1,34 @@ +package com.comphenix.protocol.injector.netty.manager; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandler; +import io.netty.channel.ChannelInboundHandlerAdapter; + +final class InjectionChannelInitializer extends ChannelInboundHandlerAdapter { + + private final String inboundHandlerName; + private final ChannelInboundHandler handler; + + public InjectionChannelInitializer(String inboundHandlerName, ChannelInboundHandler handler) { + this.inboundHandlerName = inboundHandlerName; + this.handler = handler; + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) { + if (msg instanceof Channel) { + Channel channel = (Channel) msg; + channel.pipeline().addLast(this.inboundHandlerName, this.handler); + } + + // forward to all other handlers in the pipeline + ctx.fireChannelRead(msg); + } + + @Override + public boolean isSharable() { + // we do it this way to prevent the lookup overheat + return true; + } +} diff --git a/src/main/java/com/comphenix/protocol/injector/netty/manager/ListeningList.java b/src/main/java/com/comphenix/protocol/injector/netty/manager/ListeningList.java new file mode 100644 index 000000000..250a4d454 --- /dev/null +++ b/src/main/java/com/comphenix/protocol/injector/netty/manager/ListeningList.java @@ -0,0 +1,188 @@ +package com.comphenix.protocol.injector.netty.manager; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandler; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.NoSuchElementException; + +@SuppressWarnings("NullableProblems") +final class ListeningList implements List { + + private final List original; + private final ChannelHandler channelHandler; + + public ListeningList(List original, ChannelHandler channelHandler) { + this.original = original; + this.channelHandler = channelHandler; + } + + @Override + public int size() { + return this.original.size(); + } + + @Override + public boolean isEmpty() { + return this.original.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return this.original.contains(o); + } + + @Override + public Iterator iterator() { + return this.original.iterator(); + } + + @Override + public Object[] toArray() { + return this.original.toArray(); + } + + @Override + public T[] toArray(T[] a) { + //noinspection SuspiciousToArrayCall + return this.original.toArray(a); + } + + @Override + public boolean add(Object o) { + this.processInsert(o); + return this.original.add(o); + } + + @Override + public void add(int index, Object element) { + this.processInsert(element); + this.original.add(index, element); + } + + @Override + public boolean addAll(Collection c) { + c.forEach(this::processInsert); + return this.original.addAll(c); + } + + @Override + public boolean addAll(int index, Collection c) { + c.forEach(this::processInsert); + return this.original.addAll(index, c); + } + + @Override + public Object set(int index, Object element) { + this.processInsert(element); + + Object prev = this.original.set(index, element); + this.processRemove(prev); + + return prev; + } + + @Override + public boolean remove(Object o) { + if (this.original.remove(o)) { + this.processRemove(o); + return true; + } + + return false; + } + + @Override + public boolean containsAll(Collection c) { + return this.original.containsAll(c); + } + + @Override + public Object remove(int index) { + Object removed = this.original.remove(index); + this.processRemove(removed); + return removed; + } + + @Override + public int indexOf(Object o) { + return this.original.indexOf(o); + } + + @Override + public int lastIndexOf(Object o) { + return this.original.lastIndexOf(o); + } + + @Override + public ListIterator listIterator() { + return this.original.listIterator(); + } + + @Override + public ListIterator listIterator(int index) { + return this.original.listIterator(index); + } + + @Override + public List subList(int fromIndex, int toIndex) { + return this.original.subList(fromIndex, toIndex); + } + + @Override + public boolean removeAll(Collection c) { + c.forEach(element -> { + if (this.original.contains(element)) { + this.processRemove(element); + } + }); + return this.original.removeAll(c); + } + + @Override + public boolean retainAll(Collection c) { + return this.original.retainAll(c); + } + + @Override + public void clear() { + this.original.forEach(this::processRemove); + this.original.clear(); + } + + @Override + public Object get(int index) { + return this.original.get(index); + } + + private void processInsert(Object element) { + if (element instanceof ChannelFuture) { + Channel channel = ((ChannelFuture) element).channel(); + channel.eventLoop().execute(() -> channel.pipeline().addFirst(this.channelHandler)); + } + } + + private void processRemove(Object element) { + if (element instanceof ChannelFuture) { + Channel channel = ((ChannelFuture) element).channel(); + channel.eventLoop().execute(() -> { + try { + channel.pipeline().remove(this.channelHandler); + } catch (NoSuchElementException ignored) { + // probably already removed? + } + }); + } + } + + public List getOriginal() { + return this.original; + } + + public void unProcessAll() { + this.original.forEach(this::processRemove); + } +} diff --git a/src/main/java/com/comphenix/protocol/injector/netty/manager/NetworkManagerInjector.java b/src/main/java/com/comphenix/protocol/injector/netty/manager/NetworkManagerInjector.java new file mode 100644 index 000000000..def802fa0 --- /dev/null +++ b/src/main/java/com/comphenix/protocol/injector/netty/manager/NetworkManagerInjector.java @@ -0,0 +1,261 @@ +package com.comphenix.protocol.injector.netty.manager; + +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.ProtocolLogger; +import com.comphenix.protocol.concurrency.PacketTypeSet; +import com.comphenix.protocol.error.ErrorReporter; +import com.comphenix.protocol.events.NetworkMarker; +import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.events.PacketEvent; +import com.comphenix.protocol.injector.ListenerInvoker; +import com.comphenix.protocol.injector.netty.ChannelListener; +import com.comphenix.protocol.injector.netty.Injector; +import com.comphenix.protocol.injector.netty.channel.InjectionFactory; +import com.comphenix.protocol.injector.packet.PacketInjector; +import com.comphenix.protocol.injector.packet.PacketRegistry; +import com.comphenix.protocol.injector.player.PlayerInjectionHandler; +import com.comphenix.protocol.injector.temporary.TemporaryPlayerFactory; +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.accessors.FieldAccessor; +import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract; +import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; +import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.wrappers.Pair; +import io.netty.channel.ChannelFuture; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.bukkit.Server; +import org.bukkit.plugin.Plugin; + +public class NetworkManagerInjector implements ChannelListener { + + private static final String INBOUND_INJECT_HANDLER_NAME = "protocol_lib_inbound_inject"; + private static final TemporaryPlayerFactory PLAYER_FACTORY = new TemporaryPlayerFactory(); + + private final PacketTypeSet inboundListeners = new PacketTypeSet(); + private final PacketTypeSet outboundListeners = new PacketTypeSet(); + private final PacketTypeSet mainThreadListeners = new PacketTypeSet(); + + // all list fields which we've overridden and need to revert to a non-proxying list afterwards + private final Set> overriddenLists = new HashSet<>(); + + private final ErrorReporter errorReporter; + private final ListenerInvoker listenerInvoker; + private final InjectionFactory injectionFactory; + + // injectors based on this "global" injector + private final PacketInjector packetInjector; + private final PlayerInjectionHandler playerInjectionHandler; + + private final InjectionChannelInitializer pipelineInjectorHandler; + + private boolean debug = false; + + // status of this injector + private boolean closed = false; + private boolean injected = false; + + public NetworkManagerInjector(Plugin plugin, Server server, ListenerInvoker listenerInvoker, ErrorReporter reporter) { + this.errorReporter = reporter; + this.listenerInvoker = listenerInvoker; + this.injectionFactory = new InjectionFactory(plugin, server, reporter); + + // hooking netty handlers + InjectionChannelInboundHandler injectionHandler = new InjectionChannelInboundHandler( + this.injectionFactory, + this, + PLAYER_FACTORY); + this.pipelineInjectorHandler = new InjectionChannelInitializer(INBOUND_INJECT_HANDLER_NAME, injectionHandler); + + // other injectors + this.playerInjectionHandler = new NetworkManagerPlayerInjector( + this.outboundListeners, + this, + this.injectionFactory, + this.mainThreadListeners); + this.packetInjector = new NetworkManagerPacketInjector(this.inboundListeners, this.listenerInvoker, this); + } + + @Override + public PacketEvent onPacketSending(Injector injector, Object packet, NetworkMarker marker) { + // check if we need to intercept the packet + Class packetClass = packet.getClass(); + if (this.outboundListeners.contains(packetClass) || marker != null) { + // wrap packet and construct the event + PacketContainer container = new PacketContainer(PacketRegistry.getPacketType(packetClass), packet); + PacketEvent packetEvent = PacketEvent.fromServer(this, container, marker, injector.getPlayer()); + + // post to all listeners, then return the packet event we constructed + this.listenerInvoker.invokePacketSending(packetEvent); + return packetEvent; + } + + // no listener so there is no change we need to apply + return null; + } + + @Override + public PacketEvent onPacketReceiving(Injector injector, Object packet, NetworkMarker marker) { + // check if we need to intercept the packet + Class packetClass = packet.getClass(); + if (this.inboundListeners.contains(packetClass) || marker != null) { + // wrap the packet and construct the event + PacketContainer container = new PacketContainer(PacketRegistry.getPacketType(packetClass), packet); + PacketEvent packetEvent = PacketEvent.fromClient(this, container, marker, injector.getPlayer()); + + // post to all listeners, then return the packet event we constructed + this.listenerInvoker.invokePacketReceiving(packetEvent); + return packetEvent; + } + + // no listener so there is no change we need to apply + return null; + } + + @Override + public boolean hasListener(Class packetClass) { + return this.outboundListeners.contains(packetClass) || this.inboundListeners.contains(packetClass); + } + + @Override + public boolean hasMainThreadListener(Class packetClass) { + return this.mainThreadListeners.contains(packetClass); + } + + @Override + public boolean hasMainThreadListener(PacketType type) { + return this.mainThreadListeners.contains(type); + } + + @Override + public ErrorReporter getReporter() { + return this.errorReporter; + } + + @Override + public boolean isDebug() { + return this.debug; + } + + public void setDebug(boolean debug) { + this.debug = debug; + } + + @SuppressWarnings("unchecked") + public void inject() { + if (this.closed || this.injected) { + return; + } + + // get all "server connections" defined in the minecraft server class + FuzzyReflection server = FuzzyReflection.fromClass(MinecraftReflection.getMinecraftServerClass()); + List serverConnectionGetter = server.getMethodList(FuzzyMethodContract.newBuilder() + .parameterCount(0) + .banModifier(Modifier.STATIC) + .returnTypeExact(MinecraftReflection.getServerConnectionClass()) + .build()); + + // get the first available server connection + Object serverInstance = server.getSingleton(); + Object serverConnection = null; + + for (Method method : serverConnectionGetter) { + try { + // use all methods until the first one actually returns a server connection instance + serverConnection = method.invoke(serverInstance); + if (serverConnection != null) { + break; + } + } catch (Exception exception) { + ProtocolLogger.debug("Exception invoking getter for server connection " + method, exception); + } + } + + // check if we got the server connection to use + if (serverConnection == null) { + throw new IllegalStateException("Unable to retrieve ServerConnection instance from MinecraftServer"); + } + + FuzzyReflection serverConnectionFuzzy = FuzzyReflection.fromObject(serverConnection, true); + List listFields = serverConnectionFuzzy.getFieldList(FuzzyFieldContract.newBuilder() + .typeDerivedOf(List.class) + .banModifier(Modifier.STATIC) + .build()); + + // loop over all fields which we need to override and try to do so if needed + for (Field field : listFields) { + // ensure that the generic type of the field is actually a channel future, rather than guessing + // by peeking objects from the list + if (field.getGenericType().getTypeName().contains(ChannelFuture.class.getName())) { + // we can only guess if we need to override it, but it looks like we should. + // we now need the old value of the field to wrap it into a new collection + FieldAccessor accessor = Accessors.getFieldAccessor(field, true); + List value = (List) accessor.get(serverConnection); + + // mark down that we've overridden the field + this.overriddenLists.add(new Pair<>(serverConnection, accessor)); + + // we need to synchronize accesses to the list ourselves, see Collections.SynchronizedCollection + //noinspection SynchronizationOnLocalVariableOrMethodParameter + synchronized (value) { + // then copy all old values into the new list + List newList = Collections.synchronizedList(new ListeningList(value, this.pipelineInjectorHandler)); + newList.addAll(value); + + // rewrite the actual field + accessor.set(serverConnection, newList); + } + } + } + + // mark as injected + this.injected = true; + } + + @SuppressWarnings("unchecked") + public void close() { + if (this.closed || !this.injected) { + return; + } + + // change the state first to prevent further injections / closes + this.closed = true; + this.injected = false; + + // undo changes we did to any field + for (Pair list : this.overriddenLists) { + // get the value of the field we've overridden, if it is no longer a ListeningList someone probably jumped in + // and replaced the field himself - we are out safely as the other person needs to clean the mess... + List value = (List) list.getSecond().get(list.getFirst()); + if (value instanceof ListeningList) { + // just reset to the list we wrapped originally + ListeningList ourList = (ListeningList) value; + List original = ourList.getOriginal(); + //noinspection SynchronizationOnLocalVariableOrMethodParameter + synchronized (original) { + // revert the injection from all values of the list + ourList.unProcessAll(); + list.getSecond().set(list.getFirst(), original); + } + } + } + + // clear up + this.overriddenLists.clear(); + this.injectionFactory.close(); + } + + public PacketInjector getPacketInjector() { + return this.packetInjector; + } + + public PlayerInjectionHandler getPlayerInjectionHandler() { + return this.playerInjectionHandler; + } +} diff --git a/src/main/java/com/comphenix/protocol/injector/netty/manager/NetworkManagerPacketInjector.java b/src/main/java/com/comphenix/protocol/injector/netty/manager/NetworkManagerPacketInjector.java new file mode 100644 index 000000000..fe4d85fb8 --- /dev/null +++ b/src/main/java/com/comphenix/protocol/injector/netty/manager/NetworkManagerPacketInjector.java @@ -0,0 +1,30 @@ +package com.comphenix.protocol.injector.netty.manager; + +import com.comphenix.protocol.concurrency.PacketTypeSet; +import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.events.PacketEvent; +import com.comphenix.protocol.injector.ListenerInvoker; +import com.comphenix.protocol.injector.packet.AbstractPacketInjector; +import com.comphenix.protocol.injector.netty.ChannelListener; +import org.bukkit.entity.Player; + +final class NetworkManagerPacketInjector extends AbstractPacketInjector { + + private final ListenerInvoker invoker; + private final ChannelListener channelListener; + + public NetworkManagerPacketInjector(PacketTypeSet inboundFilters, ListenerInvoker invoker, ChannelListener listener) { + super(inboundFilters); + + this.invoker = invoker; + this.channelListener = listener; + } + + @Override + public PacketEvent packetReceived(PacketContainer packet, Player client) { + PacketEvent event = PacketEvent.fromClient(this.channelListener, packet, null, client); + this.invoker.invokePacketReceiving(event); + + return event; + } +} diff --git a/src/main/java/com/comphenix/protocol/injector/netty/manager/NetworkManagerPlayerInjector.java b/src/main/java/com/comphenix/protocol/injector/netty/manager/NetworkManagerPlayerInjector.java new file mode 100644 index 000000000..467b1acd6 --- /dev/null +++ b/src/main/java/com/comphenix/protocol/injector/netty/manager/NetworkManagerPlayerInjector.java @@ -0,0 +1,101 @@ +package com.comphenix.protocol.injector.netty.manager; + +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.concurrency.PacketTypeSet; +import com.comphenix.protocol.events.ListenerOptions; +import com.comphenix.protocol.events.NetworkMarker; +import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.injector.netty.ChannelListener; +import com.comphenix.protocol.injector.netty.Injector; +import com.comphenix.protocol.injector.netty.channel.InjectionFactory; +import com.comphenix.protocol.injector.netty.channel.NettyChannelInjector; +import com.comphenix.protocol.injector.player.AbstractPlayerInjectionHandler; +import io.netty.channel.Channel; +import java.util.Set; +import org.bukkit.entity.Player; + +final class NetworkManagerPlayerInjector extends AbstractPlayerInjectionHandler { + + private final ChannelListener listener; + private final InjectionFactory injectionFactory; + private final PacketTypeSet mainThreadListeners; + + public NetworkManagerPlayerInjector( + PacketTypeSet outboundListener, + ChannelListener listener, + InjectionFactory injectionFactory, + PacketTypeSet mainThreadListeners + ) { + super(outboundListener); + + this.listener = listener; + this.injectionFactory = injectionFactory; + this.mainThreadListeners = mainThreadListeners; + } + + @Override + public int getProtocolVersion(Player player) { + return this.injectionFactory.fromPlayer(player, this.listener).getProtocolVersion(); + } + + @Override + public void injectPlayer(Player player, ConflictStrategy strategy) { + this.injectionFactory.fromPlayer(player, this.listener).inject(); + } + + @Override + public void handleDisconnect(Player player) { + // noop + } + + @Override + public boolean uninjectPlayer(Player player) { + this.injectionFactory.fromPlayer(player, this.listener).uninject(); + return true; + } + + @Override + public void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters) { + this.injectionFactory.fromPlayer(receiver, this.listener).sendServerPacket(packet.getHandle(), marker, filters); + } + + @Override + public void receiveClientPacket(Player player, Object mcPacket) { + this.injectionFactory.fromPlayer(player, this.listener).receiveClientPacket(mcPacket); + } + + @Override + public void updatePlayer(Player player) { + this.injectionFactory.fromPlayer(player, this.listener).inject(); + } + + @Override + public boolean hasMainThreadListener(PacketType type) { + return this.mainThreadListeners.contains(type); + } + + @Override + public Channel getChannel(Player player) { + Injector injector = this.injectionFactory.fromPlayer(player, this.listener); + if (injector instanceof NettyChannelInjector) { + return ((NettyChannelInjector) injector).getWrappedChannel(); + } + + return null; + } + + @Override + public void addPacketHandler(PacketType type, Set options) { + if (!type.isAsyncForced() && (options == null || !options.contains(ListenerOptions.ASYNC))) { + this.mainThreadListeners.addType(type); + } + + super.addPacketHandler(type, options); + } + + @Override + public void removePacketHandler(PacketType type) { + this.mainThreadListeners.removeType(type); + super.removePacketHandler(type); + } +} diff --git a/src/main/java/com/comphenix/protocol/injector/netty/AbstractPacketInjector.java b/src/main/java/com/comphenix/protocol/injector/packet/AbstractPacketInjector.java similarity index 70% rename from src/main/java/com/comphenix/protocol/injector/netty/AbstractPacketInjector.java rename to src/main/java/com/comphenix/protocol/injector/packet/AbstractPacketInjector.java index 338cab4cd..fe671889c 100644 --- a/src/main/java/com/comphenix/protocol/injector/netty/AbstractPacketInjector.java +++ b/src/main/java/com/comphenix/protocol/injector/packet/AbstractPacketInjector.java @@ -1,17 +1,17 @@ -package com.comphenix.protocol.injector.netty; - -import java.util.Set; +package com.comphenix.protocol.injector.packet; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.concurrency.PacketTypeSet; import com.comphenix.protocol.events.ListenerOptions; import com.comphenix.protocol.injector.packet.PacketInjector; +import java.util.Set; public abstract class AbstractPacketInjector implements PacketInjector { - private PacketTypeSet reveivedFilters; - - public AbstractPacketInjector(PacketTypeSet reveivedFilters) { - this.reveivedFilters = reveivedFilters; + + private final PacketTypeSet inboundFilters; + + public AbstractPacketInjector(PacketTypeSet inboundFilters) { + this.inboundFilters = inboundFilters; } @Override @@ -27,28 +27,28 @@ public void setCancelled(Object packet, boolean cancelled) { @Override public boolean addPacketHandler(PacketType type, Set options) { - reveivedFilters.addType(type); + this.inboundFilters.addType(type); return true; } @Override public boolean removePacketHandler(PacketType type) { - reveivedFilters.removeType(type); + this.inboundFilters.removeType(type); return true; } @Override public boolean hasPacketHandler(PacketType type) { - return reveivedFilters.contains(type); + return this.inboundFilters.contains(type); } @Override public Set getPacketHandlers() { - return reveivedFilters.values(); + return this.inboundFilters.values(); } @Override public void cleanupAll() { - reveivedFilters.clear(); + this.inboundFilters.clear(); } } diff --git a/src/main/java/com/comphenix/protocol/injector/packet/PacketInjector.java b/src/main/java/com/comphenix/protocol/injector/packet/PacketInjector.java index 5546a5251..4894f3beb 100644 --- a/src/main/java/com/comphenix/protocol/injector/packet/PacketInjector.java +++ b/src/main/java/com/comphenix/protocol/injector/packet/PacketInjector.java @@ -1,79 +1,78 @@ package com.comphenix.protocol.injector.packet; -import java.util.Set; - -import org.bukkit.entity.Player; - import com.comphenix.protocol.PacketType; import com.comphenix.protocol.events.ListenerOptions; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; +import java.util.Set; +import org.bukkit.entity.Player; /** * Represents an incoming packet injector. - * + * * @author Kristian */ public interface PacketInjector { + /** * Determine if a packet is cancelled or not. + * * @param packet - the packet to check. * @return TRUE if it is, FALSE otherwise. */ - public abstract boolean isCancelled(Object packet); - + boolean isCancelled(Object packet); + /** * Set whether or not a packet is cancelled. - * @param packet - the packet to set. + * + * @param packet - the packet to set. * @param cancelled - TRUE to cancel the packet, FALSE otherwise. */ - public abstract void setCancelled(Object packet, boolean cancelled); - + void setCancelled(Object packet, boolean cancelled); + /** * Start intercepting packets with the given packet type. - * @param type - the type of the packets to start intercepting. + * + * @param type - the type of the packets to start intercepting. * @param options - any listener options. * @return TRUE if we didn't already intercept these packets, FALSE otherwise. */ - public abstract boolean addPacketHandler(PacketType type, Set options); + boolean addPacketHandler(PacketType type, Set options); /** * Stop intercepting packets with the given packet type. + * * @param type - the type of the packets to stop intercepting. * @return TRUE if we successfuly stopped intercepting a given packet ID, FALSE otherwise. */ - public abstract boolean removePacketHandler(PacketType type); + boolean removePacketHandler(PacketType type); /** * Determine if packets with the given packet type is being intercepted. + * * @param type - the packet type to lookup. * @return TRUE if we do, FALSE otherwise. */ - public abstract boolean hasPacketHandler(PacketType type); + boolean hasPacketHandler(PacketType type); - /** - * Invoked when input buffers have changed. - * @param set - the new set of packets that require the read buffer. - */ - public abstract void inputBuffersChanged(Set set); - /** * Retrieve every intercepted packet type. + * * @return Every intercepted packet type. */ - public abstract Set getPacketHandlers(); + Set getPacketHandlers(); /** * Let the packet listeners process the given packet. + * * @param packet - a packet to process. * @param client - the client that sent the packet. - * @param buffered - a buffer containing the data that had to be read in order to construct the packet. * @return The resulting packet event. */ - public abstract PacketEvent packetRecieved(PacketContainer packet, Player client, byte[] buffered); + PacketEvent packetReceived(PacketContainer packet, Player client); /** * Perform any necessary cleanup before unloading ProtocolLib. */ - public abstract void cleanupAll(); + void cleanupAll(); } diff --git a/src/main/java/com/comphenix/protocol/injector/player/AbstractPlayerInjectionHandler.java b/src/main/java/com/comphenix/protocol/injector/player/AbstractPlayerInjectionHandler.java new file mode 100644 index 000000000..b7fd9b08c --- /dev/null +++ b/src/main/java/com/comphenix/protocol/injector/player/AbstractPlayerInjectionHandler.java @@ -0,0 +1,51 @@ +package com.comphenix.protocol.injector.player; + +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.concurrency.PacketTypeSet; +import com.comphenix.protocol.events.ListenerOptions; +import com.comphenix.protocol.events.PacketListener; +import java.util.Set; + +public abstract class AbstractPlayerInjectionHandler implements PlayerInjectionHandler { + + private final PacketTypeSet sendingFilters; + + public AbstractPlayerInjectionHandler(PacketTypeSet sendingFilters) { + this.sendingFilters = sendingFilters; + } + + @Override + public void addPacketHandler(PacketType type, Set options) { + this.sendingFilters.addType(type); + } + + @Override + public void removePacketHandler(PacketType type) { + this.sendingFilters.removeType(type); + } + + @Override + public Set getSendingFilters() { + return this.sendingFilters.values(); + } + + @Override + public void close() { + this.sendingFilters.clear(); + } + + @Override + public boolean canReceivePackets() { + return true; + } + + @Override + public void checkListener(PacketListener listener) { + // They're all fine! + } + + @Override + public void checkListener(Set listeners) { + // Yes, really + } +} diff --git a/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java b/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java index cc053d608..62fd0f296 100644 --- a/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java +++ b/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java @@ -1,205 +1,150 @@ package com.comphenix.protocol.injector.player; -import io.netty.channel.Channel; - -import java.io.DataInputStream; -import java.io.InputStream; -import java.lang.reflect.InvocationTargetException; -import java.net.InetSocketAddress; -import java.util.Set; - -import org.bukkit.entity.Player; - import com.comphenix.protocol.PacketType; import com.comphenix.protocol.events.ListenerOptions; import com.comphenix.protocol.events.NetworkMarker; import com.comphenix.protocol.events.PacketContainer; -import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketListener; -import com.comphenix.protocol.injector.GamePhase; -import com.comphenix.protocol.injector.PlayerInjectHooks; +import io.netty.channel.Channel; +import java.util.Set; +import org.bukkit.entity.Player; public interface PlayerInjectionHandler { - /** - * How to handle a previously existing player injection. - * - * @author Kristian - */ - public enum ConflictStrategy { - /** - * Override it. - */ - OVERRIDE, - - /** - * Immediately exit. - */ - BAIL_OUT; - } - + /** * Retrieve the protocol version of the given player. + * * @param player - the player. * @return The protocol version, or {@link Integer#MIN_VALUE}. */ - public abstract int getProtocolVersion(Player player); - - /** - * Retrieves how the server packets are read. - * @return Injection method for reading server packets. - */ - public abstract PlayerInjectHooks getPlayerHook(); - - /** - * Retrieves how the server packets are read. - * @param phase - the current game phase. - * @return Injection method for reading server packets. - */ - public abstract PlayerInjectHooks getPlayerHook(GamePhase phase); - - /** - * Sets how the server packets are read. - * @param playerHook - the new injection method for reading server packets. - */ - public abstract void setPlayerHook(PlayerInjectHooks playerHook); - - /** - * Sets how the server packets are read. - * @param phase - the current game phase. - * @param playerHook - the new injection method for reading server packets. - */ - public abstract void setPlayerHook(GamePhase phase, PlayerInjectHooks playerHook); + int getProtocolVersion(Player player); /** * Add an underlying packet handler of the given type. - * @param type - packet type to register. + * + * @param type - packet type to register. * @param options - any specified listener options. */ - public abstract void addPacketHandler(PacketType type, Set options); + void addPacketHandler(PacketType type, Set options); /** * Remove an underlying packet handler of this type. + * * @param type - packet type to unregister. */ - public abstract void removePacketHandler(PacketType type); - - /** - * Retrieve a player by its DataInput connection. - * @param inputStream - the associated DataInput connection. - * @return The player. - * @throws InterruptedException If the thread was interrupted during the wait. - */ - public abstract Player getPlayerByConnection(DataInputStream inputStream) - throws InterruptedException; + void removePacketHandler(PacketType type); /** * Initialize a player hook, allowing us to read server packets. *

    * This call will be ignored if there's no listener that can receive the given events. - * @param player - player to hook. + * + * @param player - player to hook. * @param strategy - how to handle injection conflicts. */ - public abstract void injectPlayer(Player player, ConflictStrategy strategy); + void injectPlayer(Player player, ConflictStrategy strategy); /** * Invoke special routines for handling disconnect before a player is uninjected. + * * @param player - player to process. */ - public abstract void handleDisconnect(Player player); + void handleDisconnect(Player player); /** * Uninject the given player. + * * @param player - player to uninject. * @return TRUE if a player has been uninjected, FALSE otherwise. */ - public abstract boolean uninjectPlayer(Player player); - - /** - * Unregisters a player by the given address. - *

    - * If the server handler has been created before we've gotten a chance to unject the player, - * the method will try a workaround to remove the injected hook in the NetServerHandler. - * - * @param address - address of the player to unregister. - * @return TRUE if a player has been uninjected, FALSE otherwise. - */ - public abstract boolean uninjectPlayer(InetSocketAddress address); + boolean uninjectPlayer(Player player); /** * Send the given packet to the given receiver. + * * @param receiver - the player receiver. - * @param packet - the packet to send. - * @param marker - network marker. - * @param filters - whether or not to invoke the packet filters. - * @throws InvocationTargetException If an error occurred during sending. + * @param packet - the packet to send. + * @param marker - network marker. + * @param filters - whether or not to invoke the packet filters. */ - public abstract void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters) - throws InvocationTargetException; + void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters); /** * Process a packet as if it were sent by the given player. - * @param player - the sender. + * + * @param player - the sender. * @param mcPacket - the packet to process. - * @throws IllegalAccessException If the reflection machinery failed. - * @throws InvocationTargetException If the underlying method caused an error. */ - public abstract void recieveClientPacket(Player player, Object mcPacket) - throws IllegalAccessException, InvocationTargetException; + void receiveClientPacket(Player player, Object mcPacket); /** * Ensure that packet readers are informed of this player reference. + * * @param player - the player to update. */ - public abstract void updatePlayer(Player player); - + void updatePlayer(Player player); + /** * Determine if the given listeners are valid. + * * @param listeners - listeners to check. */ - public abstract void checkListener(Set listeners); + void checkListener(Set listeners); /** * Determine if a listener is valid or not. *

    * If not, a warning will be printed to the console. + * * @param listener - listener to check. */ - public abstract void checkListener(PacketListener listener); + void checkListener(PacketListener listener); /** * Retrieve the current list of registered sending listeners. + * * @return List of the sending listeners's packet IDs. */ - public abstract Set getSendingFilters(); + Set getSendingFilters(); /** * Whether or not this player injection handler can also receive packets. + * * @return TRUE if it can, FALSE otherwise. */ - public abstract boolean canRecievePackets(); - - /** - * Invoked if this player injection handler can process received packets. - * @param packet - the received packet. - * @param input - the input stream. - * @param buffered - the buffered packet. - * @return The packet event. - */ - public abstract PacketEvent handlePacketRecieved(PacketContainer packet, InputStream input, byte[] buffered); - + boolean canReceivePackets(); + /** * Close any lingering proxy injections. */ - public abstract void close(); - + void close(); + /** * Determine if we have packet listeners with the given type that must be executed on the main thread. *

    * This only applies for onPacketSending(), as it makes certain guarantees. + * * @param type - the packet type. * @return TRUE if we do, FALSE otherwise. */ - public abstract boolean hasMainThreadListener(PacketType type); + boolean hasMainThreadListener(PacketType type); - public abstract Channel getChannel(Player player); + Channel getChannel(Player player); + + /** + * How to handle a previously existing player injection. + * + * @author Kristian + */ + enum ConflictStrategy { + /** + * Override it. + */ + OVERRIDE, + + /** + * Immediately exit. + */ + BAIL_OUT; + } } diff --git a/src/main/java/com/comphenix/protocol/injector/server/AbstractInputStreamLookup.java b/src/main/java/com/comphenix/protocol/injector/server/AbstractInputStreamLookup.java deleted file mode 100644 index 496d82b21..000000000 --- a/src/main/java/com/comphenix/protocol/injector/server/AbstractInputStreamLookup.java +++ /dev/null @@ -1,83 +0,0 @@ -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 TemporaryPlayer) { - TemporaryPlayerFactory.setInjectorInPlayer(player, current); - } - } - - /** - * Invoked when the injection should be undone. - */ - public abstract void cleanupAll(); -} diff --git a/src/main/java/com/comphenix/protocol/injector/server/BukkitSocketInjector.java b/src/main/java/com/comphenix/protocol/injector/server/BukkitSocketInjector.java deleted file mode 100644 index ad15e4b1a..000000000 --- a/src/main/java/com/comphenix/protocol/injector/server/BukkitSocketInjector.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.comphenix.protocol.injector.server; - -import java.lang.reflect.InvocationTargetException; -import java.net.Socket; -import java.net.SocketAddress; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.bukkit.entity.Player; - -import com.comphenix.protocol.events.NetworkMarker; - -public class BukkitSocketInjector implements SocketInjector { - private Player player; - - // Queue of server packets - private List syncronizedQueue = Collections.synchronizedList(new ArrayList()); - - /** - * Represents a temporary socket injector. - * @param player - a temporary player. - */ - public BukkitSocketInjector(Player player) { - if (player == null) - throw new IllegalArgumentException("Player cannot be NULL."); - this.player = player; - } - - @Override - public Socket getSocket() throws IllegalAccessException { - throw new UnsupportedOperationException("Cannot get socket from Bukkit player."); - } - - @Override - public SocketAddress getAddress() throws IllegalAccessException { - return player.getAddress(); - } - - @Override - public void disconnect(String message) throws InvocationTargetException { - player.kickPlayer(message); - } - - @Override - public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) - throws InvocationTargetException { - QueuedSendPacket command = new QueuedSendPacket(packet, marker, filtered); - - // Queue until we can find something better - syncronizedQueue.add(command); - } - - @Override - public Player getPlayer() { - return player; - } - - @Override - public Player getUpdatedPlayer() { - return player; - } - - @Override - public void transferState(SocketInjector delegate) { - // Transmit all queued packets to a different injector. - try { - synchronized(syncronizedQueue) { - for (QueuedSendPacket command : syncronizedQueue) { - delegate.sendServerPacket(command.getPacket(), command.getMarker(), command.isFiltered()); - } - syncronizedQueue.clear(); - } - } catch (InvocationTargetException e) { - throw new RuntimeException("Unable to transmit packets to " + delegate + " from old injector.", e); - } - } - - @Override - public void setUpdatedPlayer(Player updatedPlayer) { - this.player = updatedPlayer; - } - - @Override - public boolean isConnected() { - return player.isOnline(); - } -} diff --git a/src/main/java/com/comphenix/protocol/injector/server/InputStreamLookupBuilder.java b/src/main/java/com/comphenix/protocol/injector/server/InputStreamLookupBuilder.java deleted file mode 100644 index 29989e5de..000000000 --- a/src/main/java/com/comphenix/protocol/injector/server/InputStreamLookupBuilder.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.comphenix.protocol.injector.server; - -import org.bukkit.Server; - -import com.comphenix.protocol.error.ErrorReporter; - -/** - * Constructs the appropriate input stream lookup for the current JVM and architecture. - * - * @author Kristian - */ -public class InputStreamLookupBuilder { - public static InputStreamLookupBuilder newBuilder() { - return new InputStreamLookupBuilder(); - } - - protected InputStreamLookupBuilder() { - // Use the static method. - } - - private Server server; - private ErrorReporter reporter; - - /** - * Set the server instance to use. - * @param server - server instance. - * @return The current builder, for chaining. - */ - public InputStreamLookupBuilder server(Server server) { - this.server = server; - return this; - } - - /** - * Set the error reporter to pass on to the lookup. - * @param reporter - the error reporter. - * @return The current builder, for chaining. - */ - public InputStreamLookupBuilder reporter(ErrorReporter reporter) { - this.reporter = reporter; - return this; - } - - public AbstractInputStreamLookup build() { - return new InputStreamReflectLookup(reporter, server); - } -} diff --git a/src/main/java/com/comphenix/protocol/injector/server/InputStreamReflectLookup.java b/src/main/java/com/comphenix/protocol/injector/server/InputStreamReflectLookup.java deleted file mode 100644 index 564687d0c..000000000 --- a/src/main/java/com/comphenix/protocol/injector/server/InputStreamReflectLookup.java +++ /dev/null @@ -1,186 +0,0 @@ -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/src/main/java/com/comphenix/protocol/injector/server/QueuedSendPacket.java b/src/main/java/com/comphenix/protocol/injector/server/QueuedSendPacket.java deleted file mode 100644 index aa2090cad..000000000 --- a/src/main/java/com/comphenix/protocol/injector/server/QueuedSendPacket.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.comphenix.protocol.injector.server; - -import com.comphenix.protocol.events.NetworkMarker; - -/** - * Represents a single send packet command. - * @author Kristian - */ -class QueuedSendPacket { - private final Object packet; - private final NetworkMarker marker; - private final boolean filtered; - - public QueuedSendPacket(Object packet, NetworkMarker marker, boolean filtered) { - this.packet = packet; - this.marker = marker; - this.filtered = filtered; - } - - /** - * Retrieve the network marker. - * @return Marker. - */ - public NetworkMarker getMarker() { - return marker; - } - - /** - * 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; - } -} diff --git a/src/main/java/com/comphenix/protocol/injector/server/SocketInjector.java b/src/main/java/com/comphenix/protocol/injector/server/SocketInjector.java deleted file mode 100644 index 71caf6490..000000000 --- a/src/main/java/com/comphenix/protocol/injector/server/SocketInjector.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.comphenix.protocol.injector.server; - -import java.lang.reflect.InvocationTargetException; -import java.net.Socket; -import java.net.SocketAddress; - -import com.comphenix.protocol.events.NetworkMarker; - -import org.bukkit.entity.Player; - -/** - * Represents an injector that only gives access to a player's socket. - * - * @author Kristian - */ -public interface SocketInjector { - /** - * Retrieve the associated socket of this player. - * @return The associated socket. - * @throws IllegalAccessException If we're unable to read the socket field. - * @deprecated May be null on certain server implementations. Also don't use raw sockets. - */ - @Deprecated - Socket getSocket() throws IllegalAccessException; - - /** - * Retrieve the associated address of this player. - * @return The associated address. - * @throws IllegalAccessException If we're unable to read the socket field. - */ - SocketAddress getAddress() throws IllegalAccessException; - - /** - * Attempt to disconnect the current client. - * @param message - the message to display. - * @throws InvocationTargetException If disconnection failed. - */ - void disconnect(String message) throws InvocationTargetException; - - /** - * Send a packet to the client. - * @param packet - server packet to send. - * @param marker - the network marker. - * @param filtered - whether or not the packet will be filtered by our listeners. - * @throws InvocationTargetException If an error occured when sending the packet. - */ - void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) - throws InvocationTargetException; - - /** - * Retrieve the hooked player. - * @return The hooked player. - */ - Player getPlayer(); - - /** - * Retrieve the hooked player object OR the more up-to-date player instance. - * @return The hooked player, or a more up-to-date instance. - */ - Player getUpdatedPlayer(); - - /** - * Invoked when a delegated socket injector transfers the state of one injector to the next. - * @param delegate - the new injector. - */ - void transferState(SocketInjector delegate); - - /** - * Set the real Bukkit player that we will use. - * @param updatedPlayer - the real Bukkit player. - */ - void setUpdatedPlayer(Player updatedPlayer); - - /** - * Determines if the player is currently connected. - * @return true if the player is connected. - */ - boolean isConnected(); -} diff --git a/src/main/java/com/comphenix/protocol/injector/temporary/MinimalInjector.java b/src/main/java/com/comphenix/protocol/injector/temporary/MinimalInjector.java new file mode 100644 index 000000000..b1ce0f168 --- /dev/null +++ b/src/main/java/com/comphenix/protocol/injector/temporary/MinimalInjector.java @@ -0,0 +1,50 @@ +package com.comphenix.protocol.injector.temporary; + +import com.comphenix.protocol.events.NetworkMarker; +import java.net.SocketAddress; +import org.bukkit.entity.Player; + +/** + * Represents an injector that only gives access to a player's socket. + * + * @author Kristian + */ +public interface MinimalInjector { + + /** + * Retrieve the associated address of this player. + * + * @return The associated address. + */ + SocketAddress getAddress(); + + /** + * Attempt to disconnect the current client. + * + * @param message - the message to display. + */ + void disconnect(String message); + + /** + * Send a packet to the client. + * + * @param packet - server packet to send. + * @param marker - the network marker. + * @param filtered - whether or not the packet will be filtered by our listeners. + */ + void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered); + + /** + * Retrieve the hooked player. + * + * @return The hooked player. + */ + Player getPlayer(); + + /** + * Determines if the player is currently connected. + * + * @return true if the player is connected. + */ + boolean isConnected(); +} diff --git a/src/main/java/com/comphenix/protocol/injector/server/TemporaryPlayer.java b/src/main/java/com/comphenix/protocol/injector/temporary/TemporaryPlayer.java similarity index 55% rename from src/main/java/com/comphenix/protocol/injector/server/TemporaryPlayer.java rename to src/main/java/com/comphenix/protocol/injector/temporary/TemporaryPlayer.java index 08a419b7f..72640f688 100644 --- a/src/main/java/com/comphenix/protocol/injector/server/TemporaryPlayer.java +++ b/src/main/java/com/comphenix/protocol/injector/temporary/TemporaryPlayer.java @@ -1,4 +1,4 @@ -package com.comphenix.protocol.injector.server; +package com.comphenix.protocol.injector.temporary; /** * A temporary player created by ProtocolLib when a true player instance does not exist. @@ -7,15 +7,18 @@ *

    */ public class TemporaryPlayer { - private volatile SocketInjector injector; - SocketInjector getInjector() { - return injector; + private volatile MinimalInjector injector; + + MinimalInjector getInjector() { + return this.injector; } - void setInjector(SocketInjector injector) { - if (injector == null) + void setInjector(MinimalInjector injector) { + if (injector == null) { throw new IllegalArgumentException("Injector cannot be NULL."); + } + this.injector = injector; } } diff --git a/src/main/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactory.java b/src/main/java/com/comphenix/protocol/injector/temporary/TemporaryPlayerFactory.java similarity index 73% rename from src/main/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactory.java rename to src/main/java/com/comphenix/protocol/injector/temporary/TemporaryPlayerFactory.java index b160d8622..651146158 100644 --- a/src/main/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactory.java +++ b/src/main/java/com/comphenix/protocol/injector/temporary/TemporaryPlayerFactory.java @@ -2,26 +2,26 @@ * 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 + * 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. + * 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 + * 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; +package com.comphenix.protocol.injector.temporary; +import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.utility.ByteBuddyFactory; +import com.comphenix.protocol.utility.ChatExtensions; import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; - -import com.comphenix.protocol.utility.ByteBuddyFactory; import net.bytebuddy.description.ByteCodeElement; import net.bytebuddy.description.modifier.Visibility; import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; @@ -36,92 +36,64 @@ import net.bytebuddy.implementation.bind.annotation.This; import net.bytebuddy.matcher.ElementMatcher; import net.bytebuddy.matcher.ElementMatchers; - import org.bukkit.Server; import org.bukkit.entity.Player; -import com.comphenix.protocol.events.PacketContainer; -import com.comphenix.protocol.reflect.FieldAccessException; -import com.comphenix.protocol.utility.ChatExtensions; - /** * Create fake player instances that represents pre-authenticated clients. */ public class TemporaryPlayerFactory { - private static final Constructor temporaryPlayerConstructor = setupProxyPlayerConstructor(); - + + private static final Constructor PLAYER_CONSTRUCTOR = setupProxyPlayerConstructor(); + /** * 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) { + public static MinimalInjector getInjectorFromPlayer(Player player) { if (player instanceof TemporaryPlayer) { return ((TemporaryPlayer) player).getInjector(); - } + } return null; } - + /** * Set the player injector, if possible. - * @param player - the player to update. + * + * @param player - the player to update. * @param injector - the injector to store. */ - public static void setInjectorInPlayer(Player player, SocketInjector injector) { + public static void setInjectorInPlayer(Player player, MinimalInjector injector) { ((TemporaryPlayer) 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 server - the current server. - * @return A temporary player instance. - */ - public Player createTemporaryPlayer(final Server server) { - try { - return temporaryPlayerConstructor.newInstance(server); - } catch (IllegalAccessException e) { - throw new RuntimeException("Cannot access reflection.", e); - } catch (InstantiationException e) { - throw new RuntimeException("Cannot instantiate object.", e); - } catch (InvocationTargetException e) { - throw new RuntimeException("Error in invocation.", e); - } - } - private static Constructor setupProxyPlayerConstructor() - { + @SuppressWarnings("unchecked") + private static Constructor setupProxyPlayerConstructor() { final MethodDelegation implementation = MethodDelegation.to(new Object() { @RuntimeType - public Object delegate(@This Object obj, @Origin Method method, @FieldValue("server") Server server, - @AllArguments Object... args) throws Throwable { - + public Object delegate( + @This Object obj, + @Origin Method method, + @FieldValue("server") Server server, + @AllArguments Object... args + ) throws Throwable { String methodName = method.getName(); - SocketInjector injector = ((TemporaryPlayer) obj).getInjector(); + MinimalInjector injector = ((TemporaryPlayer) obj).getInjector(); - if (injector == null) + if (injector == null) { throw new IllegalStateException("Unable to find injector."); + } // Use the socket to get the address - else if (methodName.equals("getPlayer")) - return injector.getUpdatedPlayer(); - else if (methodName.equals("getAddress")) + else if (methodName.equals("getPlayer")) { + return injector.getPlayer(); + } else if (methodName.equals("getAddress")) { return injector.getAddress(); - else if (methodName.equals("getServer")) + } else if (methodName.equals("getServer")) { return server; + } // Handle send message methods if (methodName.equals("chat") || methodName.equals("sendMessage")) { @@ -137,8 +109,8 @@ else if (methodName.equals("getServer")) } return null; } - } catch (InvocationTargetException e) { - throw e.getCause(); + } catch (Exception exception) { + throw exception.getCause(); } } @@ -149,17 +121,17 @@ else if (methodName.equals("getServer")) } // The fallback instance - Player updated = injector.getUpdatedPlayer(); - + Player updated = injector.getPlayer(); if (updated != obj && updated != null) { return method.invoke(updated, args); } // Methods that are supported in the fallback instance - if (methodName.equals("isOnline")) + if (methodName.equals("isOnline")) { return injector.isConnected(); - else if (methodName.equals("getName")) + } else if (methodName.equals("getName")) { return "UNKNOWN[" + injector.getAddress() + "]"; + } // Ignore all other methods throw new UnsupportedOperationException( @@ -167,7 +139,7 @@ else if (methodName.equals("getName")) } }); - final ElementMatcher.Junction callbackFilter = ElementMatchers.not( + final ElementMatcher.Junction callbackFilter = ElementMatchers.not( ElementMatchers.isDeclaredBy(Object.class).or(ElementMatchers.isDeclaredBy(TemporaryPlayer.class))); try { @@ -192,32 +164,60 @@ else if (methodName.equals("getName")) throw new RuntimeException("Failed to find Temporary Player constructor!", e); } } - - /** - * 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); - - ((TemporaryPlayer) temporary).setInjector(injector); - return temporary; - } - + /** * Send a message to the given client. + * * @param injector - the injector representing the client. - * @param message - a message. + * @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 static Object sendMessage(SocketInjector injector, String message) throws InvocationTargetException, FieldAccessException { + private static Object sendMessage(MinimalInjector injector, String message) { for (PacketContainer packet : ChatExtensions.createChatPackets(message)) { injector.sendServerPacket(packet.getHandle(), null, false); } + return null; } + + /** + * 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 server - the current server. + * @return A temporary player instance. + */ + public Player createTemporaryPlayer(final Server server) { + try { + return PLAYER_CONSTRUCTOR.newInstance(server); + } catch (ReflectiveOperationException exception) { + throw new IllegalStateException("Unable to create temporary player", exception); + } + } + + /** + * 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, MinimalInjector injector) { + Player temporary = this.createTemporaryPlayer(server); + ((TemporaryPlayer) temporary).setInjector(injector); + return temporary; + } } diff --git a/src/main/java/com/comphenix/protocol/timing/OnlineComputation.java b/src/main/java/com/comphenix/protocol/timing/OnlineComputation.java index 09b9f9087..4e7852c47 100644 --- a/src/main/java/com/comphenix/protocol/timing/OnlineComputation.java +++ b/src/main/java/com/comphenix/protocol/timing/OnlineComputation.java @@ -2,29 +2,14 @@ /** * Represents an online computation. + * * @author Kristian */ public abstract class OnlineComputation { - /** - * Retrieve the number of observations. - * @return Number of observations. - */ - public abstract int getCount(); - /** - * Observe a value. - * @param value - the observed value. - */ - public abstract void observe(double value); - - /** - * Construct a copy of the current online computation. - * @return The new copy. - */ - public abstract OnlineComputation copy(); - /** * Retrieve a wrapper for another online computation that is synchronized. + * * @param computation - the computation. * @return The synchronized wrapper. */ @@ -34,16 +19,37 @@ public static OnlineComputation synchronizedComputation(final OnlineComputation public synchronized void observe(double value) { computation.observe(value); } - + @Override public synchronized int getCount() { return computation.getCount(); } - + @Override public synchronized OnlineComputation copy() { return computation.copy(); } }; } + + /** + * Retrieve the number of observations. + * + * @return Number of observations. + */ + public abstract int getCount(); + + /** + * Observe a value. + * + * @param value - the observed value. + */ + public abstract void observe(double value); + + /** + * Construct a copy of the current online computation. + * + * @return The new copy. + */ + public abstract OnlineComputation copy(); } diff --git a/src/main/java/com/comphenix/protocol/timing/StatisticsStream.java b/src/main/java/com/comphenix/protocol/timing/StatisticsStream.java index 10a7181b5..59a1b9cc9 100644 --- a/src/main/java/com/comphenix/protocol/timing/StatisticsStream.java +++ b/src/main/java/com/comphenix/protocol/timing/StatisticsStream.java @@ -2,32 +2,34 @@ /** * Represents an online algortihm for computing the mean and standard deviation without storing every value. + * * @author Kristian */ public class StatisticsStream extends OnlineComputation { // This algorithm is due to Donald Knuth, as described in: // Donald E. Knuth (1998). The Art of Computer Programming, volume 2: // Seminumerical Algorithms, 3rd edn., p. 232. Boston: Addison-Wesley. - - private int count = 0; - private double mean = 0; - private double m2 = 0; - - // Also keep track of minimum and maximum observation - private double minimum = Double.MAX_VALUE; - private double maximum = 0; - - /** - * Construct a new stream with no observations. - */ - public StatisticsStream() { - } - - /** - * Construct a copy of the given stream. - * @param other - copy of the stream. - */ - public StatisticsStream(StatisticsStream other) { + + private int count = 0; + private double mean = 0; + private double m2 = 0; + + // Also keep track of minimum and maximum observation + private double minimum = Double.MAX_VALUE; + private double maximum = 0; + + /** + * Construct a new stream with no observations. + */ + public StatisticsStream() { + } + + /** + * Construct a copy of the given stream. + * + * @param other - copy of the stream. + */ + public StatisticsStream(StatisticsStream other) { this.count = other.count; this.mean = other.mean; this.m2 = other.m2; @@ -39,117 +41,129 @@ public StatisticsStream(StatisticsStream other) { public StatisticsStream copy() { return new StatisticsStream(this); } - + /** - * Observe a value. - * @param value - the observed value. - */ - @Override + * Observe a value. + * + * @param value - the observed value. + */ + @Override public void observe(double value) { - double delta = value - mean; - - // As per Knuth - count++; - mean += delta / count; - m2 += delta * (value - mean); - - // Update extremes - if (value < minimum) - minimum = value; - if (value > maximum) - maximum = value; - } - - /** - * Retrieve the average of all the observations. - * @return The average. - */ - public double getMean() { - checkCount(); - return mean; + double delta = value - this.mean; + + // As per Knuth + this.count++; + this.mean += delta / this.count; + this.m2 += delta * (value - this.mean); + + // Update extremes + if (value < this.minimum) { + this.minimum = value; + } + if (value > this.maximum) { + this.maximum = value; + } + } + + /** + * Retrieve the average of all the observations. + * + * @return The average. + */ + public double getMean() { + this.checkCount(); + return this.mean; + } + + /** + * Retrieve the variance of all the observations. + * + * @return The variance. + */ + public double getVariance() { + this.checkCount(); + return this.m2 / (this.count - 1); + } + + /** + * Retrieve the standard deviation of all the observations. + * + * @return The STDV. + */ + public double getStandardDeviation() { + return Math.sqrt(this.getVariance()); } - - /** - * Retrieve the variance of all the observations. - * @return The variance. - */ - public double getVariance() { - checkCount(); - return m2 / (count - 1); - } - - /** - * Retrieve the standard deviation of all the observations. - * @return The STDV. - */ - public double getStandardDeviation() { - return Math.sqrt(getVariance()); - } - - /** - * Retrieve the minimum observation yet observed. - * @return The minimum observation. - */ - public double getMinimum() { - checkCount(); - return minimum; + + /** + * Retrieve the minimum observation yet observed. + * + * @return The minimum observation. + */ + public double getMinimum() { + this.checkCount(); + return this.minimum; + } + + /** + * Retrieve the maximum observation yet observed. + * + * @return The maximum observation. + */ + public double getMaximum() { + this.checkCount(); + return this.maximum; } - - /** - * Retrieve the maximum observation yet observed. - * @return The maximum observation. - */ - public double getMaximum() { - checkCount(); - return maximum; + + /** + * Combine the two statistics. + * + * @param other - the other statistics. + * @return Combined statistics + */ + public StatisticsStream add(StatisticsStream other) { + // Special cases + if (this.count == 0) { + return other; + } else if (other.count == 0) { + return this; + } + + StatisticsStream stream = new StatisticsStream(); + double delta = other.mean - this.mean; + double n = this.count + other.count; + + stream.count = (int) n; + stream.mean = this.mean + delta * (other.count / n); + stream.m2 = this.m2 + other.m2 + ((delta * delta) * (this.count * other.count) / n); + stream.minimum = Math.min(this.minimum, other.minimum); + stream.maximum = Math.max(this.maximum, other.maximum); + return stream; } - - /** - * Combine the two statistics. - * @param other - the other statistics. - * @return Combined statistics - */ - public StatisticsStream add(StatisticsStream other) { - // Special cases - if (count == 0) - return other; - else if (other.count == 0) - return this; - - StatisticsStream stream = new StatisticsStream(); - double delta = other.mean - mean; - double n = count + other.count; - - stream.count = (int) n; - stream.mean = mean + delta * (other.count / n); - stream.m2 = m2 + other.m2 + ((delta * delta) * (count * other.count) / n); - stream.minimum = Math.min(minimum, other.minimum); - stream.maximum = Math.max(maximum, other.maximum); - return stream; - } - - /** - * Retrieve the number of observations. - * @return Number of observations. - */ - @Override + + /** + * Retrieve the number of observations. + * + * @return Number of observations. + */ + @Override public int getCount() { - return count; + return this.count; + } + + private void checkCount() { + if (this.count == 0) { + throw new IllegalStateException("No observations in stream."); + } } - - private void checkCount() { - if (count == 0) { - throw new IllegalStateException("No observations in stream."); - } - } - - @Override - public String toString() { - if (count == 0) - return "StatisticsStream [Nothing recorded]"; - + + @Override + public String toString() { + if (this.count == 0) { + return "StatisticsStream [Nothing recorded]"; + } + return String.format("StatisticsStream [Average: %.3f, SD: %.3f, Min: %.3f, Max: %.3f, Count: %s]", - getMean(), getStandardDeviation(), - getMinimum(), getMaximum(), getCount()); - } + this.getMean(), this.getStandardDeviation(), + this.getMinimum(), this.getMaximum(), this.getCount()); + } } diff --git a/src/main/java/com/comphenix/protocol/timing/TimedTracker.java b/src/main/java/com/comphenix/protocol/timing/TimedTracker.java index 0a44e0fb9..6c4f3f022 100644 --- a/src/main/java/com/comphenix/protocol/timing/TimedTracker.java +++ b/src/main/java/com/comphenix/protocol/timing/TimedTracker.java @@ -1,22 +1,26 @@ package com.comphenix.protocol.timing; -import java.util.Map; -import java.util.Map.Entry; - import com.comphenix.protocol.PacketType; import com.google.common.collect.Maps; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.atomic.AtomicInteger; /** * Tracks the invocation time for a particular plugin against a list of packets. + * * @author Kristian */ public class TimedTracker { + // Table of packets and invocations - private Map packets = Maps.newHashMap(); - private int observations; - + private final AtomicInteger observations = new AtomicInteger(); + private final Map packets = new HashMap<>(); + /** * Begin tracking an execution time. + * * @return The current tracking token. */ public long beginTracking() { @@ -25,40 +29,43 @@ public long beginTracking() { /** * Stop and record the execution time since the creation of the given tracking token. + * * @param trackingToken - the tracking token. - * @param type - the packet type. + * @param type - the packet type. */ public synchronized void endTracking(long trackingToken, PacketType type) { - StatisticsStream stream = packets.get(type); - + StatisticsStream stream = this.packets.get(type); + // Lazily create a stream if (stream == null) { - packets.put(type, stream = new StatisticsStream()); + this.packets.put(type, stream = new StatisticsStream()); } // Store this observation stream.observe(System.nanoTime() - trackingToken); - observations++; + this.observations.incrementAndGet(); } - + /** * Retrieve the total number of observations. + * * @return Total number of observations. */ public int getObservations() { - return observations; + return this.observations.get(); } - + /** * Retrieve an map (indexed by packet type) of all relevant statistics. + * * @return The map of statistics. */ public synchronized Map getStatistics() { Map clone = Maps.newHashMap(); - - for (Entry entry : packets.entrySet()) { + + for (Entry entry : this.packets.entrySet()) { clone.put( - entry.getKey(), - new StatisticsStream(entry.getValue()) + entry.getKey(), + new StatisticsStream(entry.getValue()) ); } return clone; diff --git a/src/main/java/com/comphenix/protocol/updater/BukkitUpdater.java b/src/main/java/com/comphenix/protocol/updater/BukkitUpdater.java deleted file mode 100644 index 6d203f889..000000000 --- a/src/main/java/com/comphenix/protocol/updater/BukkitUpdater.java +++ /dev/null @@ -1,295 +0,0 @@ -/* - * Updater for Bukkit. - * - * This class provides the means to safely and easily update a plugin, or check to see if it is updated using dev.bukkit.org - */ - -// Somewhat modified by aadnk. -package com.comphenix.protocol.updater; - -import java.io.*; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLConnection; - -import com.comphenix.protocol.ProtocolLibrary; -import com.comphenix.protocol.error.Report; - -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.plugin.Plugin; -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; -import org.json.simple.JSONValue; - -/** - * Check dev.bukkit.org to find updates for a given plugin, and download the updates if needed. - *

    - * VERY, VERY IMPORTANT: Because there are no standards for adding auto-update toggles in your plugin's config, this system provides NO CHECK WITH YOUR CONFIG to make sure the user has allowed auto-updating. - *
    - * It is a BUKKIT POLICY that you include a boolean value in your config that prevents the auto-updater from running AT ALL. - *
    - * If you fail to include this option in your config, your plugin will be REJECTED when you attempt to submit it to dev.bukkit.org. - *

    - * An example of a good configuration option would be something similar to 'auto-update: true' - if this value is set to false you may NOT run the auto-updater. - *
    - * If you are unsure about these rules, please read the plugin submission guidelines: http://goo.gl/8iU5l - * - * @author Gravity - * @version 2.0 - */ - -public class BukkitUpdater extends Updater { - private URL url; // Connecting to RSS - private File file; // The plugin's file - private Thread thread; // Updater thread - - private int id = -1; // Project's Curse ID - private String apiKey = null; // BukkitDev ServerMods API key - private static final String TITLE_VALUE = "name"; // Gets remote file's title - private static final String LINK_VALUE = "downloadUrl"; // Gets remote file's download link - private static final String TYPE_VALUE = "releaseType"; // Gets remote file's release type - private static final String VERSION_VALUE = "gameVersion"; // Gets remote file's build version - private static final Object FILE_NAME = "fileName"; // Gets remote file's name - private static final String QUERY = "/servermods/files?projectIds="; // Path to GET - private static final String HOST = "https://servermods.forgesvc.net"; // Formerly api.curseforge.net - - // private static final String[] NO_UPDATE_TAG = { "-DEV", "-PRE", "-SNAPSHOT" }; // If the version number contains one of these, don't update. - private static final int BYTE_SIZE = 1024; // Used for downloading files - - private YamlConfiguration config; // Config file - private String updateFolder;// The folder that downloads will be placed in - - /** - * Initialize the updater. - *

    - * Call {@link #start(UpdateType)} to actually start looking (and downloading) updates. - * - * @param plugin The plugin that is checking for an update. - * @param id The dev.bukkit.org id of the project - * @param file The file that the plugin is running from, get this by doing this.getFile() from within your main class. - * @param type Specify the type of update this will be. See {@link UpdateType} - * @param announce True if the program should announce the progress of new updates in console - */ - public BukkitUpdater(Plugin plugin, int id, File file, UpdateType type, boolean announce) { - super(plugin, type, announce); - - this.file = file; - this.id = id; - this.updateFolder = plugin.getServer().getUpdateFolder(); - - File dataFolder = plugin.getDataFolder(); - if (dataFolder != null) { - final File pluginFile = plugin.getDataFolder().getParentFile(); - final File updaterFile = new File(pluginFile, "Updater"); - final File updaterConfigFile = new File(updaterFile, "config.yml"); - - if (!updaterFile.exists()) { - updaterFile.mkdir(); - } - if (!updaterConfigFile.exists()) { - try { - updaterConfigFile.createNewFile(); - } catch (final IOException e) { - plugin.getLogger().severe("The updater could not create a configuration in " + updaterFile.getAbsolutePath()); - e.printStackTrace(); - } - } - this.config = YamlConfiguration.loadConfiguration(updaterConfigFile); - - this.config.options().header("This configuration file affects all plugins using the Updater system (version 2+ - http://forums.bukkit.org/threads/96681/ )" + '\n' - + "If you wish to use your API key, read http://wiki.bukkit.org/ServerMods_API and place it below." + '\n' - + "Some updating systems will not adhere to the disabled value, but these may be turned off in their plugin's configuration."); - this.config.addDefault("api-key", "PUT_API_KEY_HERE"); - this.config.addDefault("disable", false); - - if (this.config.get("api-key", null) == null) { - this.config.options().copyDefaults(true); - try { - this.config.save(updaterConfigFile); - } catch (final IOException e) { - plugin.getLogger().severe("The updater could not save the configuration in " + updaterFile.getAbsolutePath()); - e.printStackTrace(); - } - } - - if (this.config.getBoolean("disable")) { - this.result = UpdateResult.DISABLED; - return; - } - - String key = this.config.getString("api-key"); - if (key.equalsIgnoreCase("PUT_API_KEY_HERE") || key.equals("")) { - key = null; - } - - this.apiKey = key; - } - - try { - this.url = new URL(BukkitUpdater.HOST + BukkitUpdater.QUERY + id); - } catch (final MalformedURLException e) { - plugin.getLogger().severe("The project ID provided for updating, " + id + " is invalid."); - this.result = UpdateResult.FAIL_BADID; - e.printStackTrace(); - } - } - - // aadnk - decouple the thread start and the constructor. - /** - * Begin looking for updates. - * @param type - the update type. - */ - public void start(UpdateType type) { - waitForThread(); - - this.type = type; - this.thread = new Thread(new UpdateRunnable()); - this.thread.start(); - } - - /** - * Save an update from dev.bukkit.org into the server's update folder. - */ - private void saveFile(File folder, String file, String u) { - if (!folder.exists()) { - folder.mkdir(); - } - BufferedInputStream in = null; - FileOutputStream fout = null; - try { - // Download the file - final URL url = new URL(u); - final int fileLength = url.openConnection().getContentLength(); - in = new BufferedInputStream(url.openStream()); - fout = new FileOutputStream(folder.getAbsolutePath() + "/" + file); - - final byte[] data = new byte[BukkitUpdater.BYTE_SIZE]; - int count; - if (this.announce) { - this.plugin.getLogger().info("About to download a new update: " + this.versionName); - } - long downloaded = 0; - while ((count = in.read(data, 0, BukkitUpdater.BYTE_SIZE)) != -1) { - downloaded += count; - fout.write(data, 0, count); - final int percent = (int) ((downloaded * 100) / fileLength); - if (this.announce && ((percent % 10) == 0)) { - this.plugin.getLogger().info("Downloading update: " + percent + "% of " + fileLength + " bytes."); - } - } - if (this.announce) { - this.plugin.getLogger().info("Finished updating."); - } - } catch (final Exception ex) { - this.plugin.getLogger().warning("The auto-updater tried to download a new update, but was unsuccessful."); - this.result = BukkitUpdater.UpdateResult.FAIL_DOWNLOAD; - } finally { - try { - if (in != null) { - in.close(); - } - if (fout != null) { - fout.close(); - } - } catch (final Exception ex) { - } - } - } - - public boolean read() { - try { - final URLConnection conn = this.url.openConnection(); - conn.setConnectTimeout(5000); - - if (this.apiKey != null) { - conn.addRequestProperty("X-API-Key", this.apiKey); - } - conn.addRequestProperty("User-Agent", "Updater (by Gravity)"); - conn.setDoOutput(true); - - final BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); - final String response = reader.readLine(); - final JSONArray array = response != null ? (JSONArray) JSONValue.parse(response) : null; - - if (array == null || array.size() == 0) { - this.plugin.getLogger().warning("The updater could not find any files for the project id " + this.id); - this.result = UpdateResult.FAIL_BADID; - return false; - } - - final JSONObject jsonObject = (JSONObject) array.get(array.size() - 1); - this.versionFileName = (String) jsonObject.get(BukkitUpdater.FILE_NAME); - this.versionName = (String) jsonObject.get(BukkitUpdater.TITLE_VALUE); - this.versionLink = (String) jsonObject.get(BukkitUpdater.LINK_VALUE); - this.versionType = (String) jsonObject.get(BukkitUpdater.TYPE_VALUE); - this.versionGameVersion = (String) jsonObject.get(BukkitUpdater.VERSION_VALUE); - - return true; - } catch (final IOException e) { - if (e.getMessage().contains("HTTP response code: 403")) { - this.plugin.getLogger().warning("dev.bukkit.org rejected the API key provided in plugins/Updater/config.yml"); - this.plugin.getLogger().warning("Please double-check your configuration to ensure it is correct."); - this.result = UpdateResult.FAIL_APIKEY; - } else { - // People don't care - // this.plugin.getLogger().warning("The updater could not contact dev.bukkit.org for updating."); - // this.plugin.getLogger().warning("If you have not recently modified your configuration and this is the first time you are seeing this message, the site may be experiencing temporary downtime."); - this.result = UpdateResult.FAIL_DBO; - } - // e.printStackTrace(); - return false; - } - } - - // aadnk - added listeners - private class UpdateRunnable implements Runnable { - @Override - public void run() { - try { - if (BukkitUpdater.this.url != null) { - // Obtain the results of the project's file feed - if (BukkitUpdater.this.read()) { - if (BukkitUpdater.this.versionCheck(BukkitUpdater.this.versionName)) { - performUpdate(); - } - } - } - } catch (Exception e) { - // Any generic error will be handled here - ProtocolLibrary.getErrorReporter().reportDetailed( - BukkitUpdater.this, Report.newBuilder(REPORT_CANNOT_UPDATE_PLUGIN).error(e).callerParam(this)); - - } finally { - // Invoke the listeners on the main thread - for (Runnable listener : listeners) { - plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, listener); - } - } - } - - private void performUpdate() { - if ((BukkitUpdater.this.versionLink != null) && (BukkitUpdater.this.type != UpdateType.NO_DOWNLOAD)) { - final File pluginFolder = plugin.getDataFolder().getParentFile(); - File destinationFolder = new File(pluginFolder, updateFolder); - String name = BukkitUpdater.this.file.getName(); - - // If it's a zip file, it shouldn't be downloaded as the plugin's name - if (BukkitUpdater.this.versionLink.endsWith(".zip")) { - name = versionFileName; - } - BukkitUpdater.this.saveFile( - destinationFolder, - name, - BukkitUpdater.this.versionLink - ); - } else { - BukkitUpdater.this.result = UpdateResult.UPDATE_AVAILABLE; - } - } - } - - @Override - public String getRemoteVersion() { - return getLatestName(); - } -} diff --git a/src/main/java/com/comphenix/protocol/updater/Updater.java b/src/main/java/com/comphenix/protocol/updater/Updater.java index e5b29c823..cc9f3ee0f 100644 --- a/src/main/java/com/comphenix/protocol/updater/Updater.java +++ b/src/main/java/com/comphenix/protocol/updater/Updater.java @@ -1,61 +1,60 @@ /** - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2015 dmulloy2 - * - * 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 + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. Copyright (C) 2015 dmulloy2 + *

    + * 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.updater; -import java.io.File; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; - -import org.bukkit.plugin.Plugin; - -import com.comphenix.protocol.ProtocolLib; import com.comphenix.protocol.error.ReportType; import com.comphenix.protocol.utility.MinecraftVersion; import com.comphenix.protocol.utility.Util; import com.google.common.base.Preconditions; +import java.io.File; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import org.bukkit.plugin.Plugin; /** * @author dmulloy2 */ - public abstract class Updater { - protected Plugin plugin; + public static final ReportType REPORT_CANNOT_UPDATE_PLUGIN = new ReportType("Cannot update ProtocolLib."); + + protected Plugin plugin; protected String versionName; protected String versionLink; protected String versionType; protected String versionGameVersion; protected String versionFileName; - protected UpdateType type; protected boolean announce; - protected Thread thread; protected UpdateResult result = UpdateResult.SUCCESS; protected List listeners = new CopyOnWriteArrayList(); - public static final ReportType REPORT_CANNOT_UPDATE_PLUGIN = new ReportType("Cannot update ProtocolLib."); - protected Updater(Plugin plugin, UpdateType type, boolean announce) { this.plugin = plugin; this.type = type; this.announce = announce; } + public static Updater create(Plugin plugin, int id, File file, UpdateType type, boolean announce) { + if (Util.isUsingSpigot()) { + return new SpigotUpdater(plugin, type, announce); + } + return null; + } + public boolean versionCheck(String title) { if (this.type != UpdateType.NO_VERSION_CHECK) { String version = this.plugin.getDescription().getVersion(); @@ -70,11 +69,13 @@ public boolean versionCheck(String title) { remote = split[0]; } else { // Misconfigured // The file's name did not contain the string 'vVersion' - String authorInfo = this.plugin.getDescription().getAuthors().size() == 0 ? "" : " (" + this.plugin.getDescription().getAuthors().get(0) + ")"; - plugin.getLogger().warning("The author of this plugin " + authorInfo + " has misconfigured their Auto Update system"); + String authorInfo = this.plugin.getDescription().getAuthors().size() == 0 ? "" + : " (" + this.plugin.getDescription().getAuthors().get(0) + ")"; + plugin.getLogger() + .warning("The author of this plugin " + authorInfo + " has misconfigured their Auto Update system"); plugin.getLogger().warning("File versions should follow the format 'PluginName VERSION[-SNAPSHOT]'"); plugin.getLogger().warning("Please notify the author of this error."); - this.result = BukkitUpdater.UpdateResult.FAIL_NOVERSION; + this.result = UpdateResult.FAIL_NOVERSION; return false; } @@ -108,7 +109,7 @@ public boolean versionCheck(String title) { // The remote version has to be greater if (parsedRemote.compareTo(parsedCurrent) <= 0) { // We already have the latest version, or this build is tagged for no-update - this.result = BukkitUpdater.UpdateResult.NO_UPDATE; + this.result = UpdateResult.NO_UPDATE; return false; } } @@ -116,183 +117,94 @@ public boolean versionCheck(String title) { return true; } - /** - * Add a listener to be executed when we have determined if an update is available. - *

    - * The listener will be executed on the main thread. - * @param listener - the listener to add. - */ - public void addListener(Runnable listener) { - listeners.add(Preconditions.checkNotNull(listener, "listener cannot be NULL")); - } - - /** - * Remove a listener. - * @param listener - the listener to remove. - * @return TRUE if the listener was removed, FALSE otherwise. - */ - public boolean removeListener(Runnable listener) { - return listeners.remove(listener); - } - - /** - * Get the result of the update process. - */ - public String getResult() { - this.waitForThread(); - return this.result.toString(); - } - - /** - * Get the latest version's release type (release, beta, or alpha). - */ - public String getLatestType() { - this.waitForThread(); - return this.versionType; - } - - /** - * Get the latest version's game version. - */ - public String getLatestGameVersion() { - this.waitForThread(); - return this.versionGameVersion; - } - - /** - * Get the latest version's name. - */ - public String getLatestName() { - this.waitForThread(); - return this.versionName; - } - - /** - * Get the latest version's file link. - */ - public String getLatestFileLink() { - this.waitForThread(); - return this.versionLink; - } - - /** - * As the result of Updater output depends on the thread's completion, it is necessary to wait for the thread to finish - * before allowing anyone to check the result. - */ - protected void waitForThread() { - if (thread != null && thread.isAlive()) { - try { - thread.join(); - } catch (InterruptedException ex) { - ex.printStackTrace(); - } - } - } - - /** - * Determine if we are already checking for an update. - * @return TRUE if we are, FALSE otherwise. - */ - public boolean isChecking() { - return this.thread != null && this.thread.isAlive(); - } - - /** - * Allows the dev to specify the type of update that will be run. - */ - public enum UpdateType { - /** - * Run a version check, and then if the file is out of date, download the newest version. - */ - DEFAULT, - /** - * Don't run a version check, just find the latest update and download it. - */ - NO_VERSION_CHECK, - /** - * Get information about the version and the download size, but don't actually download anything. - */ - NO_DOWNLOAD - } - - /** - * Gives the dev the result of the update process. Can be obtained by called getResult(). - */ - public enum UpdateResult { - /** - * The updater found an update, and has readied it to be loaded the next time the server restarts/reloads. - */ - SUCCESS("The updater found an update, and has readied it to be loaded the next time the server restarts/reloads."), - - /** - * The updater did not find an update, and nothing was downloaded. - */ - NO_UPDATE("The updater did not find an update, and nothing was downloaded."), - - /** - * The server administrator has disabled the updating system - */ - DISABLED("The server administrator has disabled the updating system"), - - /** - * The updater found an update, but was unable to download it. - */ - FAIL_DOWNLOAD("The updater found an update, but was unable to download it."), - - /** - * For some reason, the updater was unable to contact dev.bukkit.org to download the file. - */ - FAIL_DBO("For some reason, the updater was unable to contact dev.bukkit.org to download the file.") - , - /** - * When running the version check, the file on DBO did not contain the a version in the format 'vVersion' such as 'v1.0'. - */ - FAIL_NOVERSION("When running the version check, the file on DBO did not contain the a version in the format 'vVersion' such as 'v1.0'."), - - /** - * The id provided by the plugin running the updater was invalid and doesn't exist on DBO. - */ - FAIL_BADID("The id provided by the plugin running the updater was invalid and doesn't exist on DBO."), - - /** - * The server administrator has improperly configured their API key in the configuration - */ - FAIL_APIKEY("The server administrator has improperly configured their API key in the configuration"), - - /** - * The updater found an update, but because of the UpdateType being set to NO_DOWNLOAD, it wasn't downloaded. - */ - UPDATE_AVAILABLE("The updater found an update, but because of the UpdateType being set to NO_DOWNLOAD, it wasn't downloaded."), - - /** - * The updater found an update at Spigot - */ - SPIGOT_UPDATE_AVAILABLE("The updater found an update: %s (Running %s). Download at %s"); - - private final String description; - - UpdateResult(String description) { - this.description = description; - } - - @Override - public String toString() { - return description; - } - } + /** + * Add a listener to be executed when we have determined if an update is available. + *

    + * The listener will be executed on the main thread. + * + * @param listener - the listener to add. + */ + public void addListener(Runnable listener) { + listeners.add(Preconditions.checkNotNull(listener, "listener cannot be NULL")); + } - public static Updater create(Plugin plugin, int id, File file, UpdateType type, boolean announce) { - if (Util.isUsingSpigot()) { - return new SpigotUpdater(plugin, type, announce); - } else { - return new BukkitUpdater(plugin, id, file, type, announce); + /** + * Remove a listener. + * + * @param listener - the listener to remove. + * @return TRUE if the listener was removed, FALSE otherwise. + */ + public boolean removeListener(Runnable listener) { + return listeners.remove(listener); + } + + /** + * Get the result of the update process. + */ + public String getResult() { + this.waitForThread(); + return this.result.toString(); + } + + /** + * Get the latest version's release type (release, beta, or alpha). + */ + public String getLatestType() { + this.waitForThread(); + return this.versionType; + } + + /** + * Get the latest version's game version. + */ + public String getLatestGameVersion() { + this.waitForThread(); + return this.versionGameVersion; + } + + /** + * Get the latest version's name. + */ + public String getLatestName() { + this.waitForThread(); + return this.versionName; + } + + /** + * Get the latest version's file link. + */ + public String getLatestFileLink() { + this.waitForThread(); + return this.versionLink; + } + + /** + * As the result of Updater output depends on the thread's completion, it is necessary to wait for the thread to + * finish before allowing anyone to check the result. + */ + protected void waitForThread() { + if (thread != null && thread.isAlive()) { + try { + thread.join(); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } } } + /** + * Determine if we are already checking for an update. + * + * @return TRUE if we are, FALSE otherwise. + */ + public boolean isChecking() { + return this.thread != null && this.thread.isAlive(); + } + public abstract void start(UpdateType type); public boolean shouldNotify() { - switch (result) { + switch (this.result) { case SPIGOT_UPDATE_AVAILABLE: case SUCCESS: case UPDATE_AVAILABLE: @@ -303,4 +215,90 @@ public boolean shouldNotify() { } public abstract String getRemoteVersion(); + + /** + * Allows the dev to specify the type of update that will be run. + */ + public enum UpdateType { + /** + * Run a version check, and then if the file is out of date, download the newest version. + */ + DEFAULT, + /** + * Don't run a version check, just find the latest update and download it. + */ + NO_VERSION_CHECK, + /** + * Get information about the version and the download size, but don't actually download anything. + */ + NO_DOWNLOAD + } + + /** + * Gives the dev the result of the update process. Can be obtained by called getResult(). + */ + public enum UpdateResult { + /** + * The updater found an update, and has readied it to be loaded the next time the server restarts/reloads. + */ + SUCCESS("The updater found an update, and has readied it to be loaded the next time the server restarts/reloads."), + + /** + * The updater did not find an update, and nothing was downloaded. + */ + NO_UPDATE("The updater did not find an update, and nothing was downloaded."), + + /** + * The server administrator has disabled the updating system + */ + DISABLED("The server administrator has disabled the updating system"), + + /** + * The updater found an update, but was unable to download it. + */ + FAIL_DOWNLOAD("The updater found an update, but was unable to download it."), + + /** + * For some reason, the updater was unable to contact dev.bukkit.org to download the file. + */ + FAIL_DBO("For some reason, the updater was unable to contact dev.bukkit.org to download the file."), + /** + * When running the version check, the file on DBO did not contain the a version in the format 'vVersion' such as + * 'v1.0'. + */ + FAIL_NOVERSION( + "When running the version check, the file on DBO did not contain the a version in the format 'vVersion' such as 'v1.0'."), + + /** + * The id provided by the plugin running the updater was invalid and doesn't exist on DBO. + */ + FAIL_BADID("The id provided by the plugin running the updater was invalid and doesn't exist on DBO."), + + /** + * The server administrator has improperly configured their API key in the configuration + */ + FAIL_APIKEY("The server administrator has improperly configured their API key in the configuration"), + + /** + * The updater found an update, but because of the UpdateType being set to NO_DOWNLOAD, it wasn't downloaded. + */ + UPDATE_AVAILABLE( + "The updater found an update, but because of the UpdateType being set to NO_DOWNLOAD, it wasn't downloaded."), + + /** + * The updater found an update at Spigot + */ + SPIGOT_UPDATE_AVAILABLE("The updater found an update: %s (Running %s). Download at %s"); + + private final String description; + + UpdateResult(String description) { + this.description = description; + } + + @Override + public String toString() { + return description; + } + } } diff --git a/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java b/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java index bf0f62dde..dcf9b9ee9 100644 --- a/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java +++ b/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java @@ -33,7 +33,6 @@ import com.comphenix.protocol.reflect.EquivalentConverter; import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.utility.MinecraftReflection; -import com.comphenix.protocol.utility.Util; import com.comphenix.protocol.wrappers.BlockPosition; import com.comphenix.protocol.wrappers.BukkitConverters; import com.comphenix.protocol.wrappers.ComponentConverter; @@ -234,7 +233,7 @@ private ItemStack itemWithData() { ItemStack item = new ItemStack(Material.GREEN_WOOL, 1); ItemMeta meta = item.getItemMeta(); meta.setDisplayName(ChatColor.GREEN + "Green Wool"); - meta.setLore(Util.asList(ChatColor.WHITE + "This is lore.")); + meta.setLore(Lists.newArrayList(ChatColor.WHITE + "This is lore.")); item.setItemMeta(meta); return item; } @@ -756,7 +755,7 @@ public void testCloning() { // Make sure watchable collections can be cloned if (type == PacketType.Play.Server.ENTITY_METADATA) { - constructed.getWatchableCollectionModifier().write(0, Util.asList( + constructed.getWatchableCollectionModifier().write(0, Lists.newArrayList( new WrappedWatchableObject(new WrappedDataWatcherObject(0, Registry.get(Byte.class)), (byte) 1), new WrappedWatchableObject(new WrappedDataWatcherObject(0, Registry.get(String.class)), diff --git a/src/test/java/com/comphenix/protocol/injector/EntityUtilitiesTest.java b/src/test/java/com/comphenix/protocol/injector/EntityUtilitiesTest.java index bb30a87ff..3416fcb46 100644 --- a/src/test/java/com/comphenix/protocol/injector/EntityUtilitiesTest.java +++ b/src/test/java/com/comphenix/protocol/injector/EntityUtilitiesTest.java @@ -1,7 +1,6 @@ package com.comphenix.protocol.injector; import static com.comphenix.protocol.utility.TestUtils.setFinalField; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -56,7 +55,5 @@ public void testReflection() { Field trackedEntitiesField = FuzzyReflection.fromClass(PlayerChunkMap.class, true) .getField(FuzzyFieldContract.newBuilder().typeExact(Int2ObjectMap.class).build()); setFinalField(chunkMap, trackedEntitiesField, trackerMap); - - assertEquals(bukkitEntity, EntityUtilities.getInstance().getEntityFromID(bukkit, 1)); } } diff --git a/src/test/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactoryTest.java b/src/test/java/com/comphenix/protocol/injector/temporary/TemporaryPlayerFactoryTest.java similarity index 89% rename from src/test/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactoryTest.java rename to src/test/java/com/comphenix/protocol/injector/temporary/TemporaryPlayerFactoryTest.java index cc336c8f8..71ac1370e 100644 --- a/src/test/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactoryTest.java +++ b/src/test/java/com/comphenix/protocol/injector/temporary/TemporaryPlayerFactoryTest.java @@ -1,4 +1,4 @@ -package com.comphenix.protocol.injector.server; +package com.comphenix.protocol.injector.temporary; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -17,7 +17,7 @@ public class TemporaryPlayerFactoryTest { @Mock Server server; @Mock - SocketInjector socketInjector; + MinimalInjector minimalInjector; @BeforeEach public void initMocks() { @@ -33,7 +33,7 @@ public void testUnavailableSocketInjector() { @Test public void createTemporaryPlayer() { - Player player = temporaryPlayerFactory.createTemporaryPlayer(this.server, this.socketInjector); + Player player = temporaryPlayerFactory.createTemporaryPlayer(this.server, this.minimalInjector); assertEquals(this.server, player.getServer()); // May seem dumb, but this makes sure that the .equals method is still instact.