Out/In bound protocol injection improvements (#1524)

* Clear up some stuff, fix location of wire packet encoder
* Ensure that the player injection cache is always up-to-date
* Make uninjection from a channel more reliable
* Don't schedule an empty runnable if there is no need to do that
* Remove unnecessary throw declarations from some methods
* Adjust uninjection to remove the injector reference as well
* improve channel future injection in network manager
This commit is contained in:
Pasqual Koschmieder 2022-03-08 04:09:04 +01:00 committed by GitHub
parent f0059f39f6
commit 073bfa2b86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
78 changed files with 5312 additions and 8401 deletions

View File

@ -310,6 +310,10 @@
<groupId>junit</groupId>
<artifactId>*</artifactId>
</exclusion>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>

View File

@ -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.
*/
public void sendServerPacket(Player receiver, PacketContainer packet)
throws InvocationTargetException;
/**
* 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.
*/
public void sendServerPacket(Player receiver, PacketContainer packet, boolean filters)
throws InvocationTargetException;
void sendServerPacket(Player receiver, PacketContainer packet);
/**
* 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 receiver - the reciever.
* @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, NetworkMarker marker, 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}.
*/
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);
}

View File

@ -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
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.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;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.bukkit.configuration.Configuration;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.plugin.Plugin;
/**
* 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 class ProtocolConfig {
/**
* Load the last update time stamp from the file system.
*
*
* @return Last update time stamp.
*/
private long loadLastUpdate() {
@ -119,7 +115,7 @@ public class ProtocolConfig {
/**
* Store the given time stamp.
*
*
* @param value - time stamp to store.
*/
private void saveLastUpdate(long value) {
@ -128,8 +124,9 @@ public class ProtocolConfig {
// 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 @@ public class ProtocolConfig {
/**
* 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 @@ public class ProtocolConfig {
/**
* Load data sections.
*
*
* @param copyDefaults - whether or not to copy configuration defaults.
*/
private void loadSections(boolean copyDefaults) {
@ -167,8 +164,9 @@ public class ProtocolConfig {
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 @@ public class ProtocolConfig {
/**
* 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 @@ public class ProtocolConfig {
/**
* Retrieve a reference to the configuration file.
*
*
* @return Configuration file on disk.
*/
public File getFile() {
@ -219,7 +217,7 @@ public class ProtocolConfig {
/**
* 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 class ProtocolConfig {
/**
* 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 class ProtocolConfig {
/**
* 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 class ProtocolConfig {
/**
* 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 class ProtocolConfig {
/**
* 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 class ProtocolConfig {
/**
* 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 class ProtocolConfig {
* Determine whether or not debug mode is enabled.
* <p>
* This grants access to the filter command.
*
*
* @return TRUE if it is, FALSE otherwise.
*/
public boolean isDebug() {
@ -286,7 +284,7 @@ public class ProtocolConfig {
/**
* 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 class ProtocolConfig {
/**
* Retrieve an immutable list of every suppressed report type.
*
*
* @return Every suppressed report type.
*/
public ImmutableList<String> getSuppressedReports() {
@ -305,7 +303,7 @@ public class ProtocolConfig {
/**
* Set the list of suppressed report types,
*
*
* @param reports - suppressed report types.
*/
public void setSuppressedReports(List<String> reports) {
@ -315,7 +313,7 @@ public class ProtocolConfig {
/**
* 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 class ProtocolConfig {
* Set the amount of time to wait until checking for a new update.
* <p>
* 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 class ProtocolConfig {
* Sets under which version of Minecraft the version safety feature will be ignored.
* <p>
* 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 class ProtocolConfig {
/**
* Retrieve whether or not metrics is enabled.
*
*
* @return TRUE if metrics is enabled, FALSE otherwise.
*/
public boolean isMetricsEnabled() {
@ -372,7 +371,7 @@ public class ProtocolConfig {
* Set whether or not metrics is enabled.
* <p>
* 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 class ProtocolConfig {
/**
* 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 class ProtocolConfig {
* Set whether or not the background compiler for structure modifiers is enabled or not.
* <p>
* 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 class ProtocolConfig {
/**
* 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 class ProtocolConfig {
* Set the last time we updated, in seconds since 1970.01.01 00:00.
* <p>
* 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 class ProtocolConfig {
/**
* 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 class ProtocolConfig {
* Set the unique name of the script engine to use for filtering.
* <p>
* 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 class ProtocolConfig {
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 class ProtocolConfig {
* 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;

View File

@ -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
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.comphenix.protocol.async.AsyncFilterManager;
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.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.ByteBuddyFactory;
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 com.comphenix.protocol.async.AsyncFilterManager;
import com.comphenix.protocol.error.*;
import com.comphenix.protocol.injector.DelayedSingleTask;
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.MinecraftVersion;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import org.bukkit.Server;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.PluginCommand;
@ -57,111 +58,91 @@ import org.bukkit.plugin.java.JavaPlugin;
* @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 class ProtocolLib extends JavaPlugin {
// 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 class ProtocolLib extends JavaPlugin {
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 @@ public class ProtocolLib extends JavaPlugin {
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 @@ public class ProtocolLib extends JavaPlugin {
/**
* 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 @@ public class ProtocolLib extends JavaPlugin {
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 class ProtocolLib extends JavaPlugin {
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 class ProtocolLib extends JavaPlugin {
}
};
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 class ProtocolLib extends JavaPlugin {
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 class ProtocolLib extends JavaPlugin {
}
// 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 @@ public class ProtocolLib extends JavaPlugin {
}
}
// 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 @@ public class ProtocolLib extends JavaPlugin {
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 @@ public class ProtocolLib extends JavaPlugin {
// 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 @@ public class ProtocolLib extends JavaPlugin {
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 @@ public class ProtocolLib extends JavaPlugin {
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 @@ public class ProtocolLib extends JavaPlugin {
* 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 @@ public class ProtocolLib extends JavaPlugin {
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 @@ public class ProtocolLib extends JavaPlugin {
@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 class ProtocolLib extends JavaPlugin {
/**
* Retrieve the metrics instance used to measure users of this library.
* <p>
* 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
}
}

View File

@ -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.injector.PacketConstructor;
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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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 <i>includeTracker</i> 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 <i>includeTracker</i> 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<? extends Player> targetPlayers);
/**
* Retrieves a list of every registered packet listener.
*
* @return Every registered packet listener.
*/
ImmutableSet<PacketListener> getPacketListeners();
@ -118,9 +121,9 @@ public interface ProtocolManager extends PacketStream {
/**
* Adds a packet listener.
* <p>
* 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 @@ public interface ProtocolManager extends PacketStream {
* Removes a given packet listener.
* <p>
* 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.
* <p>
* If set to true, the <i>forceDefaults</i> 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 <i>forceDefaults</i> 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.
* <p>
* 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<Player> observers) throws FieldAccessException;
void updateEntity(Entity entity, List<Player> 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<Player> getEntityTrackers(Entity entity) throws FieldAccessException;
List<Player> 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<PacketType> 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<PacketType> 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();

View File

@ -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.reflect.FuzzyReflection;
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.
* <p>
* Asynchronous listeners can use this to set packet timeout or transmission order.
*
*
* @author Kristian
*/
public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
/**
* Generated by Eclipse.
*/
@ -56,52 +55,52 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
* 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<PrioritizedListener<AsyncListenerHandler>> 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> {
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 class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
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 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
* <p>
* 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 class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
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 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
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 class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
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 class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
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 class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
* <p>
* 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 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
Iterator<PrioritizedListener<AsyncListenerHandler>> getListenerTraversal() {
return listenerTraversal;
}
/**
* Set the iterator for the next listener.
* @param listenerTraversal - the new async packet listener iterator.
@ -377,28 +376,22 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
void setListenerTraversal(Iterator<PrioritizedListener<AsyncListenerHandler>> 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 class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
// This will occur in 1.2.5 (or possibly in later versions)
List<Method> 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 class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
}
}
}
@Override
public int compareTo(AsyncMarker o) {
if (o == null)
@ -468,7 +461,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
else
return Longs.compare(getNewSendingIndex(), o.getNewSendingIndex());
}
@Override
public boolean equals(Object other) {
// Standard equals
@ -479,7 +472,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
else
return false;
}
@Override
public int hashCode() {
return Longs.hashCode(getNewSendingIndex());

View File

@ -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;
private PriorityBlockingQueue<PacketEventHolder> sendingQueue;
// Asynchronous packet sending
private Executor asynchronousSender;
// Whether or not packet transmission must occur on a specific thread
private final boolean notThreadSafe;
private PriorityBlockingQueue<PacketEventHolder> sendingQueue;
// Asynchronous packet sending
private Executor asynchronousSender;
// 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 @@ abstract class PacketSendingQueue {
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 @@ abstract class PacketSendingQueue {
*/
public synchronized void signalPacketUpdate(List<PacketType> packetsRemoved, boolean onMainThread) {
Set<PacketType> lookup = new HashSet<PacketType>(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 @@ abstract class PacketSendingQueue {
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 @@ abstract class PacketSendingQueue {
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 @@ abstract class PacketSendingQueue {
}
}
}
/**
* 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 @@ abstract class PacketSendingQueue {
/**
* 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 @@ abstract class PacketSendingQueue {
if (!cleanedUp) {
// Note that the cleanup itself will always occur on the main thread
forceSend();
// And we're done
cleanedUp = true;
}

View File

@ -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.Set;
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<TListener> {
public abstract class AbstractConcurrentListenerMultimap<T> {
// The core of our map
private ConcurrentMap<PacketType, SortedCopyOnWriteArray<PrioritizedListener<TListener>>> mapListeners;
private final ConcurrentMap<PacketType, SortedCopyOnWriteArray<PrioritizedListener<T>>> mapListeners;
public AbstractConcurrentListenerMultimap() {
mapListeners = new ConcurrentHashMap<PacketType, SortedCopyOnWriteArray<PrioritizedListener<TListener>>>();
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<TListener> prioritized = new PrioritizedListener<TListener>(listener, whitelist.getPriority());
public void addListener(T listener, ListeningWhitelist whitelist) {
PrioritizedListener<T> 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<TListener> listener) {
SortedCopyOnWriteArray<PrioritizedListener<TListener>> list = mapListeners.get(type);
private void addListener(PacketType type, PrioritizedListener<T> listener) {
SortedCopyOnWriteArray<PrioritizedListener<T>> 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<PrioritizedListener<TListener>> value = new SortedCopyOnWriteArray<PrioritizedListener<TListener>>();
final SortedCopyOnWriteArray<PrioritizedListener<T>> value = new SortedCopyOnWriteArray<PrioritizedListener<T>>();
// 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<PacketType> removeListener(TListener listener, ListeningWhitelist whitelist) {
public List<PacketType> removeListener(T listener, ListeningWhitelist whitelist) {
List<PacketType> removedPackets = new ArrayList<PacketType>();
// Again, not terribly efficient. But adding or removing listeners should be a rare event.
for (PacketType type : whitelist.getTypes()) {
SortedCopyOnWriteArray<PrioritizedListener<TListener>> list = mapListeners.get(type);
SortedCopyOnWriteArray<PrioritizedListener<T>> 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<TListener>(listener, whitelist.getPriority()));
list.remove(new PrioritizedListener<T>(listener, whitelist.getPriority()));
if (list.size() == 0) {
mapListeners.remove(type);
this.mapListeners.remove(type);
removedPackets.add(type);
}
}
@ -107,38 +108,41 @@ public abstract class AbstractConcurrentListenerMultimap<TListener> {
}
return removedPackets;
}
/**
* Retrieve the registered listeners, in order from the lowest to the highest priority.
* <p>
* The returned list is thread-safe and doesn't require synchronization.
*
* @param type - packet type.
* @return Registered listeners.
*/
public Collection<PrioritizedListener<TListener>> getListener(PacketType type) {
return mapListeners.get(type);
public Collection<PrioritizedListener<T>> getListener(PacketType type) {
return this.mapListeners.get(type);
}
/**
* Retrieve every listener.
*
* @return Every listener.
*/
public Iterable<PrioritizedListener<TListener>> values() {
return Iterables.concat(mapListeners.values());
public Iterable<PrioritizedListener<T>> values() {
return Iterables.concat(this.mapListeners.values());
}
/**
* Retrieve every registered packet type:
*
* @return Registered packet type.
*/
public Set<PacketType> keySet() {
return mapListeners.keySet();
return this.mapListeners.keySet();
}
/**
* Remove all packet listeners.
*/
protected void clearListeners() {
mapListeners.clear();
this.mapListeners.clear();
}
}

View File

@ -1,125 +1,135 @@
package com.comphenix.protocol.concurrency;
import java.util.Collection;
import java.util.Collections;
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;
import com.google.common.collect.ImmutableSet;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
/**
* Represents a concurrent set of packet types.
*
* @author Kristian
*/
public class PacketTypeSet {
private Set<PacketType> types = Collections.newSetFromMap(Maps.<PacketType, Boolean>newConcurrentMap());
private Set<Class<?>> classes = Collections.newSetFromMap(Maps.<Class<?>, Boolean>newConcurrentMap());
private final Set<PacketType> types;
private final Set<Class<?>> classes;
public PacketTypeSet() {
// Do nothing
this.types = new HashSet<>(16, 0.9f);
this.classes = new HashSet<>(16, 0.9f);
}
public PacketTypeSet(Collection<? extends PacketType> 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<? extends PacketType> types) {
public void addAll(Iterable<? extends PacketType> 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<? extends PacketType> types) {
public void removeAll(Iterable<? extends PacketType> 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<PacketType> 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();
}
}

View File

@ -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 <T> - type of the elements in the list.
*/
public class SortedCopyOnWriteArray<T extends Comparable<T>> implements Collection<T> {
// Prevent reordering
private volatile List<T> list;
/**
* Construct an empty sorted array.
*/
public SortedCopyOnWriteArray() {
list = new ArrayList<T>();
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<T> wrapped) {
this.list = new ArrayList<T>(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<T> wrapped, boolean sort) {
this.list = new ArrayList<T>(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<T> copy = new ArrayList<T>();
// We use NULL as a special marker, so we don't allow it
if (value == null) {
throw new IllegalArgumentException("value cannot be NULL");
}
List<T> copy = new ArrayList<T>();
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;
}
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;
}
@Override
public synchronized boolean addAll(Collection<? extends T> values) {
if (values == null)
public synchronized boolean addAll(Collection<? extends T> values) {
if (values == null) {
throw new IllegalArgumentException("values cannot be NULL");
if (values.size() == 0)
}
if (values.size() == 0) {
return false;
List<T> copy = new ArrayList<T>();
// 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.
* <p>
* Objects will be compared using the given objects equals() method.
* @param value - value to remove.
*/
@Override
public synchronized boolean remove(Object value) {
List<T> copy = new ArrayList<T>();
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<T> copy = new ArrayList<T>();
// 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.
* <p>
* Objects will be compared using the given objects equals() method.
*
* @param value - value to remove.
*/
@Override
public synchronized boolean remove(Object value) {
List<T> copy = new ArrayList<T>();
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;
}
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<T> copy = new ArrayList<T>();
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<T> copy = new ArrayList<T>();
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<T> copy = new ArrayList<T>(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.
*/
public Iterator<T> iterator() {
return Iterables.unmodifiableIterable(list).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<T> copy = new ArrayList<T>(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<T> iterator() {
return Iterables.unmodifiableIterable(this.list).iterator();
}
@Override
public void clear() {
list = new ArrayList<T>();
this.list = new ArrayList<T>();
}
@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> T[] toArray(T[] a) {
return list.toArray(a);
return this.list.toArray(a);
}
@Override
public String toString() {
return list.toString();
return this.list.toString();
}
}

View File

@ -2,29 +2,24 @@ package com.comphenix.protocol.events;
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.
*/

View File

@ -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 @@ public class ListeningWhitelist {
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 <T extends Enum<T>> EnumSet<T> safeEnumSet(Collection<T> options, Class<T> 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 <T> Set<T> safeSet(Collection<T> 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 class ListeningWhitelist {
/**
* 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 class ListeningWhitelist {
/**
* Retrieves a set of the packets that will be observed by the listeners.
*
* @return Packet whitelist.
*/
public Set<PacketType> 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<ListenerOptions> getOptions() {
@ -100,20 +172,6 @@ public class ListeningWhitelist {
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 class ListeningWhitelist {
@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 <T extends Enum<T>> EnumSet<T> safeEnumSet(Collection<T> options, Class<T> 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 <T> Set<T> safeSet(Collection<T> 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<PacketType> types = Sets.newHashSet();
private GamePhase gamePhase = GamePhase.PLAYING;
private Set<ListenerOptions> options = Sets.newHashSet();
/**
* Construct a new listening whitelist template.
*
* @param template - the template.
*/
private Builder(ListeningWhitelist template) {
@ -200,9 +221,10 @@ public class ListeningWhitelist {
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 class ListeningWhitelist {
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 class ListeningWhitelist {
/**
* 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 class ListeningWhitelist {
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 class ListeningWhitelist {
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 class ListeningWhitelist {
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 class ListeningWhitelist {
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 class ListeningWhitelist {
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 class ListeningWhitelist {
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<ListenerOptions> 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() {

View File

@ -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.");
}
@Override
protected ByteBuffer addHeader(ByteBuffer buffer, PacketType type) {
throw new IllegalStateException("Buffer is empty.");
}
@Override
protected DataInputStream addHeader(DataInputStream input, PacketType type) {
throw new IllegalStateException("Buffer is empty.");
}
}
// Custom network handler
private PriorityQueue<PacketOutputHandler> outputHandlers;
// Post listeners
private List<PacketPostListener> postListeners;
// Post packets
private List<ScheduledPacket> scheduledPackets;
// The input buffer
private ByteBuffer inputBuffer;
private final ConnectionSide side;
// The input data
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;
}
private final ConnectionSide side;
// Post-processing of the packet
private Set<PacketPostListener> postListeners;
private Set<ScheduledPacket> scheduledPackets;
/**
* Construct a new network marker.
* <p>
* 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.
* <p>
* The returned buffer is read-only. If the parent event is a server side packet this
* method throws {@link IllegalStateException}.
* <p>
* 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.
* <p>
* The returned buffer is read-only. If the parent event is a server side packet this
* method throws {@link IllegalStateException}.
* <p>
* 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.
* <p>
* 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.
*/
public DataInputStream getInputStream() {
return getInputStream(true);
}
/**
* Retrieve the serialized packet data as an input stream.
* <p>
* 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.
*/
public boolean requireOutputHeader() {
return MinecraftReflection.isUsingNetty();
}
/**
* Enqueue the given output handler for managing how the current packet will be written to the network stream.
* <p>
* 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.
* <p>
* 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.
*/
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<PacketOutputHandler>(10, new Comparator<PacketOutputHandler>() {
@Override
public int compare(PacketOutputHandler o1, PacketOutputHandler o2) {
return Ints.compare(o1.getPriority().getSlot(), o2.getPriority().getSlot());
}
});
}
return outputHandlers.add(handler);
}
/**
* Remove a given output handler from the serialization queue.
* <p>
* 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.
*/
public boolean removeOutputHandler(@Nonnull PacketOutputHandler handler) {
checkServerSide();
Preconditions.checkNotNull(handler, "handler cannot be NULL.");
if (outputHandlers != null) {
return outputHandlers.remove(handler);
}
return false;
}
/**
* Retrieve every registered output handler in no particular order.
* @return Every registered output handler.
*/
@Nonnull
public Collection<PacketOutputHandler> getOutputHandlers() {
if (outputHandlers != null) {
return outputHandlers;
} else {
return Collections.emptyList();
}
}
/**
* Add a listener that is invoked after a packet has been successfully sent to the client, or received
* by the server.
* <p>
* 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.
* <p>
* 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);
}
/**
* 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);
}
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<PacketPostListener> getPostListeners() {
return postListeners != null ? Collections.unmodifiableList(postListeners) : Collections.<PacketPostListener>emptyList();
}
/**
* Retrieve a list of packets that will be schedule (in-order) when the current packet has been successfully transmitted.
* <p>
* This list is modifiable.
* @return List of packets that will be scheduled.
*/
public List<ScheduledPacket> getScheduledPackets() {
if (scheduledPackets == null)
scheduledPackets = Lists.newArrayList();
return scheduledPackets;
}
/**
* Ensure that the packet event is server side.
*/
private void checkServerSide() {
if (side.isForClient()) {
throw new IllegalStateException("Must be a server side packet.");
}
}
/**
* Return a byte buffer without the header in the current packet.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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<ScheduledPacket> readScheduledPackets(NetworkMarker marker) {
public static Set<ScheduledPacket> readScheduledPackets(NetworkMarker marker) {
return marker.scheduledPackets;
}
/**
* 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 this.side;
}
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.
* <p>
* 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.
* <p>
* 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 (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 (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 Set<PacketPostListener> 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.
* <p>
* This list is modifiable.
*
* @return List of packets that will be scheduled.
*/
public Set<ScheduledPacket> getScheduledPackets() {
if (this.scheduledPackets == null) {
this.scheduledPackets = new HashSet<>();
}
return this.scheduledPackets;
}
/**
* Ensure that the packet event is server side.
*/
private void checkServerSide() {
if (this.side.isForClient()) {
throw new IllegalStateException("Must be a server side packet.");
}
}
}

View File

@ -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.
* <p>
* 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<? extends PacketType> 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<? extends PacketType> 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<? extends PacketType> types, ListenerOptions... options) {
this(params(plugin, Iterables.toArray(types, PacketType.class)).listenerPriority(listenerPriority).options(options));
public PacketAdapter(Plugin plugin, ListenerPriority listenerPriority, Iterable<? extends PacketType> 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 abstract class PacketAdapter implements PacketListener {
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)
throw new IllegalArgumentException("connectionSide cannot be null");
if (listenerPriority == null)
throw new IllegalArgumentException("listenerPriority cannot be null");
if (gamePhase == null)
throw new IllegalArgumentException("gamePhase cannot be NULL");
if (packets == null)
throw new IllegalArgumentException("packets cannot be 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);
}
if (connectionSide == null) {
throw new IllegalArgumentException("connectionSide cannot be null");
}
if (listenerPriority == null) {
throw new IllegalArgumentException("listenerPriority cannot be null");
}
if (gamePhase == null) {
throw new IllegalArgumentException("gamePhase cannot be NULL");
}
if (packets == null) {
throw new IllegalArgumentException("packets cannot be null");
}
if (options == null) {
throw new IllegalArgumentException("options cannot be null");
}
// 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> T[] except(T[] values, T[] buffer, T except) {
List<T> 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 abstract class PacketAdapter implements PacketListener {
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.
* <p>
* 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 abstract class PacketAdapter implements PacketListener {
* Construct a helper object for passing parameters to the packet adapter.
* <p>
* 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.
* <p>
* 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 abstract class PacketAdapter implements PacketListener {
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 abstract class PacketAdapter implements PacketListener {
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.
* <p>
* Default is {@link ListenerPriority#NORMAL}.
*
* @param listenerPriority - the new event priority.
* @return This builder, for chaining.
*/
@ -304,11 +333,13 @@ public abstract class PacketAdapter implements PacketListener {
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.
* <p>
* 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 abstract class PacketAdapter implements PacketListener {
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.
* <p>
* 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 abstract class PacketAdapter implements PacketListener {
}
/**
* 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.
* <p>
* 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 abstract class PacketAdapter implements PacketListener {
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 @@ public abstract class PacketAdapter implements PacketListener {
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.
* <p>
* This allows ProtocolLib to perform certain optimizations.
*
* @return This builder, for chaining.
*/
public AdapterParameteters optionAsync() {
@ -387,6 +417,7 @@ public abstract class PacketAdapter implements PacketListener {
* Set the packet types the listener is looking for.
* <p>
* This parameter is required.
*
* @param packets - the packet types to look for.
* @return This builder, for chaining.
*/
@ -398,16 +429,18 @@ public abstract class PacketAdapter implements PacketListener {
}
}
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.
* <p>
* This parameter is required.
*
* @param packets - a set of packet types to look for.
* @return This builder, for chaining.
*/
@ -415,19 +448,4 @@ public abstract class PacketAdapter implements PacketListener {
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;
}
}

View File

@ -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<PacketType, PacketType> 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<Player> 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 class PacketEvent extends EventObject implements Cancellable {
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 @@ public class PacketEvent extends EventObject implements Cancellable {
this.serverPacket = serverPacket;
this.filtered = filtered;
}
private PacketEvent(PacketEvent origial, AsyncMarker asyncMarker) {
super(origial.source);
this.packet = origial.packet;
@ -109,6 +106,7 @@ public class PacketEvent extends EventObject implements Cancellable {
/**
* 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 @@ public class PacketEvent extends EventObject implements Cancellable {
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 class PacketEvent extends EventObject implements Cancellable {
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.
* <p>
* If <i>filtered</i> 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.
* <p>
* If <i>filtered</i> 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.
* <p>
* If so, you must synchronize all calls to the Bukkit API.
* <p>
* 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 class PacketEvent extends EventObject implements Cancellable {
/**
* 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 class PacketEvent extends EventObject implements Cancellable {
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.
* <p>
* 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.
* <p>
* <b>Warning</b>: 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.
* <p>
* This ensures that other plugins can work with the same packet.
* <p>
* 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.
* <p>
* 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 class PacketEvent extends EventObject implements Cancellable {
* Update the network manager.
* <p>
* 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.
* <p>
* <b>Warning</b>: 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.
* <p>
* This ensures that other plugins can work with the same packet.
* <p>
* 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<Player> getPlayerReference() {
Player player = playerReference.get();
@ -332,6 +348,7 @@ public class PacketEvent extends EventObject implements Cancellable {
/**
* 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 class PacketEvent extends EventObject implements Cancellable {
* <li>kickPlayer</li>
* <li>isOnline</li>
* </ul>
*
* <p>
* 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 class PacketEvent extends EventObject implements Cancellable {
public boolean isPlayerTemporary() {
return getPlayer() instanceof TemporaryPlayer;
}
/**
* Determine if this packet is filtered by every packet listener.
* <p>
* 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.
* <p>
* 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.
* <p>
@ -387,54 +406,62 @@ public class PacketEvent extends EventObject implements Cancellable {
* asynchronous event, the marker is used to correctly pass the packet around to the different threads.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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 class PacketEvent extends EventObject implements Cancellable {
* Schedule a packet for sending or receiving after the current packet event is successful.
* <p>
* 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 class PacketEvent extends EventObject implements Cancellable {
}
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 @@ public class PacketEvent extends EventObject implements Cancellable {
}
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

View File

@ -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 class ScheduledPacket {
*/
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());
}
}

View File

@ -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.FieldUtils;
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 @@ import com.google.common.primitives.Primitives;
* <li>org.bukkit.entity.Player to net.minecraft.server.EntityPlayer</li>
* <li>org.bukkit.World to net.minecraft.server.WorldServer</li>
* </ul>
*
*
* @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<Class<?>, Unwrapper> unwrapperCache = new ConcurrentHashMap<Class<?>, Unwrapper>();
private static final Map<Class<?>, Unwrapper> UNWRAPPER_CACHE = new ConcurrentHashMap<Class<?>, Unwrapper>();
private static BukkitUnwrapper DEFAULT;
// The current error reporter
private final ErrorReporter 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;
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;
}
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<Object>) 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<Object> wrappedObject) {
@SuppressWarnings("unchecked")
Collection<Object> 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 class BukkitUnwrapper implements Unwrapper {
// 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 class BukkitUnwrapper implements Unwrapper {
} 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 class BukkitUnwrapper implements Unwrapper {
}
};
unwrapperCache.put(type, unwrapper);
UNWRAPPER_CACHE.put(type, unwrapper);
return unwrapper;
}
} catch (Throwable ignored) {
@ -237,20 +250,22 @@ public class BukkitUnwrapper implements Unwrapper {
/**
* 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 class BukkitUnwrapper implements Unwrapper {
}
}
};
unwrapperCache.put(type, fieldUnwrapper);
UNWRAPPER_CACHE.put(type, fieldUnwrapper);
return fieldUnwrapper;
} else {
// Inform about this too
reporter.reportDetailed(this,
@ -272,11 +287,4 @@ public class BukkitUnwrapper implements Unwrapper {
return null;
}
}
private static Class<?> checkClass(Class<?> input, Class<?> expected, Class<?> result) {
if (expected.isAssignableFrom(input)) {
return result;
}
return null;
}
}

View File

@ -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.
* <p>
* Any previously scheduled task will be automatically cancelled.
* <p>
* 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();
}
}

View File

@ -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.MinecraftReflection;
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 @@ import org.bukkit.entity.Player;
* @author Kristian
*/
class EntityUtilities {
private static final boolean NEW_TRACKER = MinecraftVersion.VILLAGE_UPDATE.atOrAbove();
private static final EntityUtilities INSTANCE = new EntityUtilities();
private static final boolean NEW_TRACKER = MinecraftVersion.VILLAGE_UPDATE.atOrAbove();
private final Map<Class<?>, MethodAccessor> scanPlayersMethods = new HashMap<>();
private FieldAccessor chunkMapField;
private FieldAccessor entityTrackerField;
private FieldAccessor trackedPlayersField;
private FieldAccessor trackedEntitiesField;
private MethodAccessor getChunkProvider;
private EntityUtilities() {
}
public static EntityUtilities getInstance() {
return INSTANCE;
}
private EntityUtilities() { }
private FieldAccessor entityTrackerField;
private FieldAccessor trackedEntitiesField;
private FieldAccessor trackedPlayersField;
private Map<Class<?>, MethodAccessor> scanPlayersMethods = new HashMap<>();
public void updateEntity(Entity entity, List<Player> observers) {
if (entity == null || !entity.isValid()) {
return;
}
Collection<?> trackedPlayers = getTrackedPlayers(entity);
List<Object> nmsPlayers = unwrapBukkit(observers);
List<Object> removingEntries = MinecraftVersion.CAVES_CLIFFS_1.atOrAbove() ?
getPlayerConnections(nmsPlayers) : nmsPlayers;
Collection<?> trackedPlayers = this.getTrackedPlayers(entity);
List<Object> nmsPlayers = this.unwrapBukkit(observers);
List<Object> 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 @@ class EntityUtilities {
}
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 @@ class EntityUtilities {
}
List<Player> 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 @@ class EntityUtilities {
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<Integer, Object> trackedEntities = (Map<Integer, Object>) trackedEntitiesField.get(playerChunkMap);
Map<Integer, Object> trackedEntities = (Map<Integer, Object>) this.trackedEntitiesField.get(playerChunkMap);
return trackedEntities.get(entityId);
}
@ -190,88 +192,28 @@ class EntityUtilities {
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<Class<?>, 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<Object> getPlayerConnections(List<Object> nmsPlayers) {
List<Object> connections = new ArrayList<>(nmsPlayers.size());
nmsPlayers.forEach(nmsPlayer -> connections.add(MinecraftFields.getPlayerConnection(nmsPlayer)));
@ -286,10 +228,11 @@ class EntityUtilities {
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;

View File

@ -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 @@ package com.comphenix.protocol.injector;
/**
* 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() {

View File

@ -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);
}

View File

@ -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 @@ import com.comphenix.protocol.events.PacketEvent;
/**
* Represents an object that initiate the packet listeners.
*
*
* @author Kristian
*/
public interface ListenerInvoker {
/**
* Invokes the given packet event for every registered listener.
*
* @param event - the packet event to invoke.
*/
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.
*/

View File

@ -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<PacketOutputHandler> handlers = (PriorityQueue<PacketOutputHandler>)
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<ScheduledPacket> scheduled = NetworkMarker.readScheduledPackets(marker);
Set<ScheduledPacket> scheduled = NetworkMarker.readScheduledPackets(marker);
ProtocolManager manager = ProtocolLibrary.getProtocolManager();
if (scheduled != null) {
for (ScheduledPacket packet : scheduled) {
packet.schedule(manager);
}
}
}
}
}

View File

@ -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.comphenix.protocol.wrappers.BukkitConverters;
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.
* <p>
* 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<Unwrapper> unwrappers;
// The packet ID
private PacketType type;
// Used to unwrap Bukkit objects
private List<Unwrapper> 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<Unwrapper> unwrappers, Unwrapper[] paramUnwrapper) {
private PacketConstructor(PacketType type, Constructor<?> constructorMethod, List<Unwrapper> 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<Unwrapper> getUnwrappers() {
return ImmutableList.copyOf(unwrappers);
}
/**
* Retrieve the id of the packets this constructor creates.
* <p>
* 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 class PacketConstructor {
* Create a packet constructor that creates packets using the given types.
* <p>
* 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 class PacketConstructor {
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 class PacketConstructor {
} catch (Throwable e) {
lastException = e;
}
// Update type we're searching for
if (result != null) {
types[i] = PacketConstructor.getClass(result);
@ -141,7 +184,7 @@ public class PacketConstructor {
break;
}
}
} else {
// Try it
types[i] = Object.class;
@ -161,14 +204,15 @@ public class PacketConstructor {
}
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 class PacketConstructor {
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 class PacketConstructor {
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.
* <p>
* 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.
*/

View File

@ -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.
* <p>
* 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.
* <p>
* 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);
}
}

View File

@ -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.
* <p>
* Cannot intercept MapChunk packets.
*/
NETWORK_MANAGER_OBJECT,
/**
* Override the packet queue lists in NetworkHandler.
* <p>
* Cannot intercept MapChunk packets.
*/
NETWORK_HANDLER_FIELDS,
/**
* Override the server handler object. Versatile, but a tad slower.
*/
NETWORK_SERVER_OBJECT;
}

View File

@ -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));
}
}

View File

@ -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.ByteBuddyFactory;
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 {
// Structure modifiers
private static final ConcurrentMap<PacketType, StructureModifier<Object>> structureModifiers = new ConcurrentHashMap<>();
// invocation cache for packets
private static final ConcurrentMap<Class<?>, Supplier<Object>> PACKET_INSTANCE_CREATORS = new ConcurrentHashMap<>();
// packet data serializer which always returns an empty nbt tag compound
private static boolean trickTried;
private static ConstructorAccessor TRICKED_DATA_SERIALIZER;
private static final Set<PacketType> compiling = new HashSet<>();
// prevent duplicate compilations
private static final Set<PacketType> COMPILING = new HashSet<>();
// Structure modifiers
private static final Map<Class<?>, Supplier<Object>> PACKET_INSTANCE_CREATORS = new ConcurrentHashMap<>();
private static final Map<PacketType, StructureModifier<Object>> STRUCTURE_MODIFIER_CACHE = new ConcurrentHashMap<>();
// packet data serializer which always returns an empty nbt tag compound
private static boolean TRICK_TRIED = false;
private static ConstructorAccessor TRICKED_DATA_SERIALIZER;
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 class StructureCache {
/**
* Creates an empty Minecraft packet of the given type.
*
* @param type - packet type.
* @return Created packet.
*/
@ -97,6 +99,7 @@ public class StructureCache {
/**
* Retrieve a cached structure modifier for the given packet type.
*
* @param type - packet type.
* @return A structure modifier.
*/
@ -107,6 +110,7 @@ public class StructureCache {
/**
* Retrieve a cached structure modifier given a packet type.
*
* @param packetType - packet type.
* @return A structure modifier.
*/
@ -117,8 +121,9 @@ public class StructureCache {
/**
* 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<Object> getStructure(Class<?> packetType, boolean compile) {
@ -130,53 +135,43 @@ public class StructureCache {
/**
* 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<Object> getStructure(final PacketType type, boolean compile) {
Preconditions.checkNotNull(type, "type cannot be null");
StructureModifier<Object> result = structureModifiers.get(type);
public static StructureModifier<Object> getStructure(final PacketType packetType, boolean compile) {
Preconditions.checkNotNull(packetType, "type cannot be null");
// We don't want to create this for every lookup
if (result == null) {
// Use the vanilla class definition
final StructureModifier<Object> value = new StructureModifier<>(
PacketRegistry.getPacketClassFromType(type), MinecraftReflection.getPacketClass(), true);
StructureModifier<Object> modifier = STRUCTURE_MODIFIER_CACHE.computeIfAbsent(packetType, type -> {
Class<?> packetClass = PacketRegistry.getPacketClassFromType(type);
return new StructureModifier<>(packetClass, 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;
// 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();

View File

@ -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<ListenerOptions> options) {
sendingFilters.addType(type);
}
@Override
public void removePacketHandler(PacketType type) {
sendingFilters.removeType(type);
}
@Override
public Set<PacketType> 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<PacketListener> listeners) {
// Yes, really
}
}

View File

@ -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<Object> {
private List<Object> delegate;
private ChannelHandler handler;
/**
* Construct a new bootstrap list.
* @param delegate - the delegate.
* @param handler - the channel handler to add.
*/
public BootstrapList(List<Object> 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<? extends Object> collection) {
List<Object> 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<Object>() {
@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<Object> iterator() {
return delegate.iterator();
}
@Override
public synchronized Object[] toArray() {
return delegate.toArray();
}
@Override
public synchronized <T> 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<? extends Object> 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<Object> listIterator() {
return delegate.listIterator();
}
@Override
public synchronized ListIterator<Object> listIterator(int index) {
return delegate.listIterator(index);
}
@Override
public synchronized List<Object> subList(int fromIndex, int toIndex) {
return delegate.subList(fromIndex, toIndex);
}
// End boiler plate
}

View File

@ -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.
* <p>
* 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.
* <p>
* 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();
}

View File

@ -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<Class<?>, 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 <T> 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 <T> Callable<T> onMessageScheduled(Callable<T> 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 <T> Attribute<T> attr(AttributeKey<T> 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 <T> Callable<T> schedulingCallable(Callable<T> callable) {
FieldAccessor accessor = getMessageAccessor(callable);
if (accessor != null) {
Callable<T> result = onMessageScheduled(callable, accessor);;
return result != null ? result : EventLoopProxy.<T>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 <T> boolean hasAttr(AttributeKey<T> key) {
return delegate.hasAttr(key);
}
*/
}

View File

@ -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<Object>() {
@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 <T> Callable<T> getEmptyCallable() {
return (Callable<T>) 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 <T> Callable<T> schedulingCallable(Callable<T> callable);
@Override
public void execute(Runnable command) {
getDelegate().execute(schedulingRunnable(command));
}
@Override
public <T> Future<T> submit(Callable<T> action) {
return getDelegate().submit(schedulingCallable(action));
}
@Override
public <T> Future<T> submit(Runnable action, T arg1) {
return getDelegate().submit(schedulingRunnable(action), arg1);
}
@Override
public Future<?> submit(Runnable action) {
return getDelegate().submit(schedulingRunnable(action));
}
@Override
public <V> ScheduledFuture<V> schedule(Callable<V> 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 <T> List<java.util.concurrent.Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException {
return getDelegate().invokeAll(tasks);
}
@Override
public <T> List<java.util.concurrent.Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout,
TimeUnit unit) throws InterruptedException {
return getDelegate().invokeAll(tasks, timeout, unit);
}
@Override
public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException,
ExecutionException {
return getDelegate().invokeAny(tasks);
}
@Override
public <T> T invokeAny(Collection<? extends Callable<T>> 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<EventExecutor> iterator() {
return getDelegate().iterator();
}
@Override
public <V> Future<V> newFailedFuture(Throwable arg0) {
return getDelegate().newFailedFuture(arg0);
}
@Override
public EventLoop next() {
return ((EventLoopGroup) getDelegate()).next();
}
@Override
public <V> ProgressivePromise<V> newProgressivePromise() {
return getDelegate().newProgressivePromise();
}
@Override
public <V> Promise<V> newPromise() {
return getDelegate().newPromise();
}
@Override
public <V> Future<V> 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<Runnable> shutdownNow() {
return getDelegate().shutdownNow();
}
/*
public ChannelFuture register(ChannelPromise promise) {
return getDelegate().register(promise);
}
*/
}

View File

@ -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.
* <p>
* 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<Player, Injector> playerLookup = new MapMaker().weakKeys().weakValues().makeMap();
private final ConcurrentMap<String, Injector> 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.
* <p>
* 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.
* <p>
* 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();
}
}
}

View File

@ -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.
* <p>
* 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.
* <p>
* 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();
}

View File

@ -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;
}
}

View File

@ -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<Object> 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;
}
}
}

View File

@ -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<? extends ChannelHandler> 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 extends ChannelHandler> T get(Class<T> arg0) {
return pipeline.get(arg0);
}
@Override
public ChannelHandler get(String arg0) {
return pipeline.get(arg0);
}
@Override
public Iterator<Entry<String, ChannelHandler>> iterator() {
return pipeline.iterator();
}
@Override
public ChannelHandler last() {
return pipeline.last();
}
@Override
public ChannelHandlerContext lastContext() {
return pipeline.lastContext();
}
@Override
public List<String> 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 extends ChannelHandler> T remove(Class<T> 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 extends ChannelHandler> T replace(Class<T> 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<String, ChannelHandler> 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();
}
*/
}

View File

@ -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<VolatileField> bootstrapFields = Lists.newArrayList();
// The channel injector factory
private InjectionFactory injectionFactory;
// List of network managers
private volatile List<Object> 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<Method> 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<Channel>() {
@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<Channel>() {
@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<Object>) 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<Object>) 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<Object> list = (List<Object>) 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<VolatileField> getBootstrapFields(Object serverConnection) {
List<VolatileField> 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<Object> list = (List<Object>) 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<ListenerOptions> 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<PacketType> set) {
bufferedPackets = new PacketTypeSet(set);
}
};
}
}

View File

@ -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();
}
}

View File

@ -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
* <p>
* 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.
* <p>
* 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.
* <p>
* 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) + "]";
}
}

View File

@ -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 class ClosedInjector implements Injector {
@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 class ClosedInjector implements Injector {
public boolean isClosed() {
return true;
}
@Override
public int getProtocolVersion() {
return Integer.MIN_VALUE;
}
}

View File

@ -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());
}
}

View File

@ -0,0 +1,283 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. Copyright (C) 2015 dmulloy2
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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<String, Injector> nameLookup = new MapMaker().weakValues().makeMap();
private final ConcurrentMap<Player, Injector> 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.
* <p>
* 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.
* <p>
* 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();
}
}
}
}

View File

@ -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<Class<?>, 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<Integer> PROTOCOL_VERSION = AttributeKey.valueOf(getRandomKey());
private static final AttributeKey<NettyChannelInjector> 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<Object> skippedPackets = new LinkedHashSet<>();
private final Map<Object, NetworkMarker> 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<String, ChannelHandler> 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 <T> Callable<T> proxyCallable(Callable<T> original) {
return NettyChannelInjector.this.processOutbound(original);
}
});
this.channelField.set(this.networkManager, ch);
}
private void ensureInEventLoop(Runnable runnable) {
this.wrappedChannel.eventLoop().execute(runnable);
}
private <T> 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> 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>) () -> {
Object value = ((Callable<Object>) 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;
}
}

View File

@ -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;
}
}

View File

@ -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 <T> Attribute<T> attr(AttributeKey<T> key) {
return this.delegate.attr(key);
}
@Override
public int compareTo(@NotNull Channel o) {
return this.delegate.compareTo(o);
}
}

View File

@ -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 <T> 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 <T> Callable<T> proxyCallable(Callable<T> 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 <V> Promise<V> newPromise() {
return this.delegate.newPromise();
}
@Override
public <V> ProgressivePromise<V> newProgressivePromise() {
return this.delegate.newProgressivePromise();
}
@Override
public <V> Future<V> newSucceededFuture(V result) {
return this.delegate.newSucceededFuture(result);
}
@Override
public <V> Future<V> 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<Runnable> shutdownNow() {
return this.delegate.shutdownNow();
}
@Override
public Iterator<EventExecutor> 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 <T> Future<T> submit(Runnable task, T result) {
Runnable proxied = this.proxyRunnable(task);
return this.delegate.submit(proxied == null ? EMPTY_RUNNABLE : proxied, result);
}
@Override
@SuppressWarnings("unchecked")
public <T> Future<T> submit(Callable<T> task) {
Callable<T> proxied = this.proxyCallable(task);
return this.delegate.submit(proxied == null ? (Callable<T>) 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 <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
Callable<V> proxied = this.proxyCallable(callable);
return this.delegate.schedule(callable == null ? (Callable<V>) 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 <T> List<java.util.concurrent.Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException {
return this.delegate.invokeAll(tasks);
}
@Override
public <T> List<java.util.concurrent.Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout,
TimeUnit unit) throws InterruptedException {
return this.delegate.invokeAll(tasks, timeout, unit);
}
@Override
public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException {
return this.delegate.invokeAny(tasks);
}
@Override
public <T> T invokeAny(Collection<? extends Callable<T>> 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<? super EventExecutor> action) {
this.delegate.forEach(action);
}
@Override
public Spliterator<EventExecutor> 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);
}
}

View File

@ -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<WirePacket> {
@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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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<Object> {
private final List<Object> original;
private final ChannelHandler channelHandler;
public ListeningList(List<Object> 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<Object> iterator() {
return this.original.iterator();
}
@Override
public Object[] toArray() {
return this.original.toArray();
}
@Override
public <T> 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<Object> listIterator() {
return this.original.listIterator();
}
@Override
public ListIterator<Object> listIterator(int index) {
return this.original.listIterator(index);
}
@Override
public List<Object> 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<Object> getOriginal() {
return this.original;
}
public void unProcessAll() {
this.original.forEach(this::processRemove);
}
}

View File

@ -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<Pair<Object, FieldAccessor>> 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<Method> 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<Field> 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<Object> value = (List<Object>) 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<Object> 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<Object, FieldAccessor> 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<Object> value = (List<Object>) list.getSecond().get(list.getFirst());
if (value instanceof ListeningList) {
// just reset to the list we wrapped originally
ListeningList ourList = (ListeningList) value;
List<Object> 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;
}
}

View File

@ -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;
}
}

View File

@ -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<ListenerOptions> 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);
}
}

View File

@ -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 abstract class AbstractPacketInjector implements PacketInjector {
@Override
public boolean addPacketHandler(PacketType type, Set<ListenerOptions> 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<PacketType> getPacketHandlers() {
return reveivedFilters.values();
return this.inboundFilters.values();
}
@Override
public void cleanupAll() {
reveivedFilters.clear();
this.inboundFilters.clear();
}
}

View File

@ -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<ListenerOptions> options);
boolean addPacketHandler(PacketType type, Set<ListenerOptions> 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<PacketType> set);
/**
* Retrieve every intercepted packet type.
*
* @return Every intercepted packet type.
*/
public abstract Set<PacketType> getPacketHandlers();
Set<PacketType> 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();
}

View File

@ -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<ListenerOptions> options) {
this.sendingFilters.addType(type);
}
@Override
public void removePacketHandler(PacketType type) {
this.sendingFilters.removeType(type);
}
@Override
public Set<PacketType> 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<PacketListener> listeners) {
// Yes, really
}
}

View File

@ -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<ListenerOptions> options);
void addPacketHandler(PacketType type, Set<ListenerOptions> 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.
* <p>
* 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.
* <p>
* 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<PacketListener> listeners);
void checkListener(Set<PacketListener> listeners);
/**
* Determine if a listener is valid or not.
* <p>
* 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<PacketType> getSendingFilters();
Set<PacketType> 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.
* <p>
* 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;
}
}

View File

@ -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();
}

View File

@ -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<QueuedSendPacket> syncronizedQueue = Collections.synchronizedList(new ArrayList<QueuedSendPacket>());
/**
* 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();
}
}

View File

@ -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);
}
}

View File

@ -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<SocketAddress, SocketInjector> addressLookup = new BlockingHashMap<SocketAddress, SocketInjector>();
protected ConcurrentMap<InputStream, SocketAddress> 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.
* <p>
* 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);
}
}
}

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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 @@ package com.comphenix.protocol.injector.server;
* </p>
*/
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;
}
}

View File

@ -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;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
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.Method;
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.RuntimeType;
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<? extends Player> temporaryPlayerConstructor = setupProxyPlayerConstructor();
private static final Constructor<? extends Player> 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.
* <p>
* Supported methods include:
* <ul>
* <li>getPlayer()</li>
* <li>getAddress()</li>
* <li>getServer()</li>
* <li>chat(String)</li>
* <li>sendMessage(String)</li>
* <li>sendMessage(String[])</li>
* <li>kickPlayer(String)</li>
* </ul>
* <p>
* 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<? extends Player> setupProxyPlayerConstructor()
{
@SuppressWarnings("unchecked")
private static Constructor<? extends Player> 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 @@ public class TemporaryPlayerFactory {
}
return null;
}
} catch (InvocationTargetException e) {
throw e.getCause();
} catch (Exception exception) {
throw exception.getCause();
}
}
@ -149,17 +121,17 @@ public class TemporaryPlayerFactory {
}
// 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 @@ public class TemporaryPlayerFactory {
}
});
final ElementMatcher.Junction<ByteCodeElement> callbackFilter = ElementMatchers.not(
final ElementMatcher.Junction<ByteCodeElement> callbackFilter = ElementMatchers.not(
ElementMatchers.isDeclaredBy(Object.class).or(ElementMatchers.isDeclaredBy(TemporaryPlayer.class)));
try {
@ -192,32 +164,60 @@ public class TemporaryPlayerFactory {
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.
* <p>
* Supported methods include:
* <ul>
* <li>getPlayer()</li>
* <li>getAddress()</li>
* <li>getServer()</li>
* <li>chat(String)</li>
* <li>sendMessage(String)</li>
* <li>sendMessage(String[])</li>
* <li>kickPlayer(String)</li>
* </ul>
* <p>
* 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;
}
}

View File

@ -2,29 +2,14 @@ package com.comphenix.protocol.timing;
/**
* 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 abstract class 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();
}

View File

@ -2,32 +2,34 @@ package com.comphenix.protocol.timing;
/**
* 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 class StatisticsStream extends OnlineComputation {
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 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 average of all the observations.
*
* @return The average.
*/
public double getMean() {
this.checkCount();
return this.mean;
}
/**
* Retrieve the maximum observation yet observed.
* @return The maximum observation.
*/
public double getMaximum() {
checkCount();
return maximum;
/**
* Retrieve the variance of all the observations.
*
* @return The variance.
*/
public double getVariance() {
this.checkCount();
return this.m2 / (this.count - 1);
}
/**
* 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 standard deviation of all the observations.
*
* @return The STDV.
*/
public double getStandardDeviation() {
return Math.sqrt(this.getVariance());
}
/**
* 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;
}
/**
* 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;
}
/**
* Retrieve the number of observations.
*
* @return Number of observations.
*/
@Override
public int getCount() {
return count;
return this.count;
}
private void checkCount() {
if (count == 0) {
throw new IllegalStateException("No observations in stream.");
}
}
@Override
public String toString() {
if (count == 0)
return "StatisticsStream [Nothing recorded]";
private void checkCount() {
if (this.count == 0) {
throw new IllegalStateException("No observations in stream.");
}
}
@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());
}
}

View File

@ -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<PacketType, StatisticsStream> packets = Maps.newHashMap();
private int observations;
private final AtomicInteger observations = new AtomicInteger();
private final Map<PacketType, StatisticsStream> packets = new HashMap<>();
/**
* Begin tracking an execution time.
*
* @return The current tracking token.
*/
public long beginTracking() {
@ -25,40 +29,43 @@ public class TimedTracker {
/**
* 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<PacketType, StatisticsStream> getStatistics() {
Map<PacketType, StatisticsStream> clone = Maps.newHashMap();
for (Entry<PacketType, StatisticsStream> entry : packets.entrySet()) {
for (Entry<PacketType, StatisticsStream> entry : this.packets.entrySet()) {
clone.put(
entry.getKey(),
new StatisticsStream(entry.getValue())
entry.getKey(),
new StatisticsStream(entry.getValue())
);
}
return clone;

View File

@ -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.
* <p/>
* <b>VERY, VERY IMPORTANT</b>: 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.
* <br>
* It is a <b>BUKKIT POLICY</b> that you include a boolean value in your config that prevents the auto-updater from running <b>AT ALL</b>.
* <br>
* If you fail to include this option in your config, your plugin will be <b>REJECTED</b> when you attempt to submit it to dev.bukkit.org.
* <p/>
* 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.
* <br>
* 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.
* <p>
* 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();
}
}

View File

@ -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
* <p>
* 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.
* <p>
* 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.
* <p>
* 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<Runnable> listeners = new CopyOnWriteArrayList<Runnable>();
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 abstract class Updater {
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 abstract class Updater {
// 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 abstract class Updater {
return true;
}
/**
* Add a listener to be executed when we have determined if an update is available.
* <p>
* 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();
}
/**
* Add a listener to be executed when we have determined if an update is available.
* <p>
* 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"));
}
/**
* Get the latest version's release type (release, beta, or alpha).
*/
public String getLatestType() {
this.waitForThread();
return this.versionType;
}
/**
* 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 latest version's game version.
*/
public String getLatestGameVersion() {
this.waitForThread();
return this.versionGameVersion;
}
/**
* Get the result of the update process.
*/
public String getResult() {
this.waitForThread();
return this.result.toString();
}
/**
* Get the latest version's name.
*/
public String getLatestName() {
this.waitForThread();
return this.versionName;
}
/**
* Get the latest version's release type (release, beta, or alpha).
*/
public String getLatestType() {
this.waitForThread();
return this.versionType;
}
/**
* Get the latest version's file link.
*/
public String getLatestFileLink() {
this.waitForThread();
return this.versionLink;
}
/**
* Get the latest version's game version.
*/
public String getLatestGameVersion() {
this.waitForThread();
return this.versionGameVersion;
}
/**
* 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();
}
}
}
/**
* Get the latest version's name.
*/
public String getLatestName() {
this.waitForThread();
return this.versionName;
}
/**
* 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();
}
/**
* Get the latest version's file link.
*/
public String getLatestFileLink() {
this.waitForThread();
return this.versionLink;
}
/**
* 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;
}
}
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);
/**
* 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 abstract class Updater {
}
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;
}
}
}

View File

@ -33,7 +33,6 @@ import com.comphenix.protocol.injector.PacketConstructor;
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 @@ public class PacketContainerTest {
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 class PacketContainerTest {
// 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)),

View File

@ -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 class EntityUtilitiesTest {
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));
}
}

View File

@ -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 class TemporaryPlayerFactoryTest {
@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.