Reimplement auto updater, fix legacy packets

Also a few minor bug fixes and improvements
Fixes #127
This commit is contained in:
Dan Mulloy 2015-11-14 14:31:45 -05:00
parent e84fca699d
commit 395d77e721
16 changed files with 846 additions and 335 deletions

View File

@ -1,4 +1,4 @@
/* /**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland * Copyright (C) 2012 Kristian S. Stangeland
* *
@ -14,7 +14,6 @@
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol; package com.comphenix.protocol;
import java.io.File; import java.io.File;
@ -36,7 +35,9 @@ import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.timing.TimedListenerManager; import com.comphenix.protocol.timing.TimedListenerManager;
import com.comphenix.protocol.timing.TimingReportGenerator; import com.comphenix.protocol.timing.TimingReportGenerator;
import com.google.common.io.Closer; import com.comphenix.protocol.updater.Updater;
import com.comphenix.protocol.updater.Updater.UpdateType;
import com.comphenix.protocol.utility.Closer;
/** /**
* Handles the "protocol" administration command. * Handles the "protocol" administration command.
@ -50,10 +51,14 @@ class CommandProtocol extends CommandBase {
public static final String NAME = "protocol"; public static final String NAME = "protocol";
private Plugin plugin; private Plugin plugin;
private Updater updater;
private ProtocolConfig config;
public CommandProtocol(ErrorReporter reporter, Plugin plugin) { public CommandProtocol(ErrorReporter reporter, Plugin plugin, Updater updater, ProtocolConfig config) {
super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 1); super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 1);
this.plugin = plugin; this.plugin = plugin;
this.updater = updater;
this.config = config;
} }
@Override @Override
@ -61,23 +66,37 @@ class CommandProtocol extends CommandBase {
String subCommand = args[0]; String subCommand = args[0];
// Only return TRUE if we executed the correct command // Only return TRUE if we executed the correct command
if (subCommand.equalsIgnoreCase("config") || subCommand.equalsIgnoreCase("reload")) if (subCommand.equalsIgnoreCase("config") || subCommand.equalsIgnoreCase("reload")) {
reloadConfiguration(sender); reloadConfiguration(sender);
else if (subCommand.equalsIgnoreCase("timings")) } else if (subCommand.equalsIgnoreCase("check")) {
checkVersion(sender);
} else if (subCommand.equalsIgnoreCase("update")) {
updateVersion(sender);
} else if (subCommand.equalsIgnoreCase("timings")) {
toggleTimings(sender, args); toggleTimings(sender, args);
else if (subCommand.equalsIgnoreCase("listeners")) } else if (subCommand.equalsIgnoreCase("listeners")) {
printListeners(sender); printListeners(sender, args);
else if (subCommand.equalsIgnoreCase("version")) } else if (subCommand.equalsIgnoreCase("version")) {
printVersion(sender); printVersion(sender);
else if (subCommand.equalsIgnoreCase("dump")) } else if (subCommand.equalsIgnoreCase("dump")) {
dump(sender); dump(sender);
else } else {
return false; return false;
}
return true; return true;
} }
public void checkVersion(final CommandSender sender) {
performUpdate(sender, UpdateType.NO_DOWNLOAD);
}
public void updateVersion(final CommandSender sender) {
performUpdate(sender, UpdateType.DEFAULT);
}
// Display every listener on the server // Display every listener on the server
private void printListeners(final CommandSender sender) { private void printListeners(final CommandSender sender, String[] args) {
ProtocolManager manager = ProtocolLibrary.getProtocolManager(); ProtocolManager manager = ProtocolLibrary.getProtocolManager();
sender.sendMessage(ChatColor.GOLD + "Packet listeners:"); sender.sendMessage(ChatColor.GOLD + "Packet listeners:");
@ -92,6 +111,28 @@ class CommandProtocol extends CommandBase {
} }
} }
private void performUpdate(final CommandSender sender, UpdateType type) {
if (updater.isChecking()) {
sender.sendMessage(ChatColor.RED + "Already checking for an update.");
return;
}
// Perform on an async thread
Runnable notify = new Runnable() {
@Override
public void run() {
if (updater.shouldNotify() || config.isDebug()) {
sender.sendMessage(ChatColor.YELLOW + "[ProtocolLib] " + updater.getResult());
}
updater.removeListener(this);
updateFinished();
}
};
updater.start(type);
updater.addListener(notify);
}
private void toggleTimings(CommandSender sender, String[] args) { private void toggleTimings(CommandSender sender, String[] args) {
TimedListenerManager manager = TimedListenerManager.getInstance(); TimedListenerManager manager = TimedListenerManager.getInstance();
boolean state = !manager.isTiming(); // toggle boolean state = !manager.isTiming(); // toggle
@ -135,11 +176,27 @@ class CommandProtocol extends CommandBase {
// Print to a text file // Print to a text file
generator.saveTo(destination, manager); generator.saveTo(destination, manager);
manager.clear(); manager.clear();
} catch (IOException e) { } catch (IOException e) {
reporter.reportMinimal(plugin, "saveTimings()", e); reporter.reportMinimal(plugin, "saveTimings()", e);
} }
} }
/**
* Prevent further automatic updates until the next delay.
*/
public void updateFinished() {
long currentTime = System.currentTimeMillis() / ProtocolLibrary.MILLI_PER_SECOND;
config.setAutoLastTime(currentTime);
config.saveAll();
}
public void reloadConfiguration(CommandSender sender) {
plugin.reloadConfig();
sender.sendMessage(ChatColor.YELLOW + "Reloaded configuration!");
}
private void printVersion(CommandSender sender) { private void printVersion(CommandSender sender) {
PluginDescriptionFile desc = plugin.getDescription(); PluginDescriptionFile desc = plugin.getDescription();
@ -148,11 +205,6 @@ class CommandProtocol extends CommandBase {
sender.sendMessage(ChatColor.WHITE + "Issues: " + ChatColor.GREEN + "https://github.com/dmulloy2/ProtocolLib/issues"); sender.sendMessage(ChatColor.WHITE + "Issues: " + ChatColor.GREEN + "https://github.com/dmulloy2/ProtocolLib/issues");
} }
public void reloadConfiguration(CommandSender sender) {
plugin.reloadConfig();
sender.sendMessage(ChatColor.YELLOW + "Reloaded configuration!");
}
private static SimpleDateFormat FILE_FORMAT; private static SimpleDateFormat FILE_FORMAT;
private static SimpleDateFormat TIMESTAMP_FORMAT; private static SimpleDateFormat TIMESTAMP_FORMAT;
@ -207,10 +259,7 @@ class CommandProtocol extends CommandBase {
ProtocolLibrary.getStaticLogger().log(Level.SEVERE, "Failed to create dump:", ex); ProtocolLibrary.getStaticLogger().log(Level.SEVERE, "Failed to create dump:", ex);
sender.sendMessage(ChatColor.RED + "Failed to create dump! Check console!"); sender.sendMessage(ChatColor.RED + "Failed to create dump! Check console!");
} finally { } finally {
try { closer.close();
closer.close();
} catch (IOException ex1) {
}
} }
} }
} }

View File

@ -4,6 +4,7 @@ import java.io.Serializable;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.Future; import java.util.concurrent.Future;
@ -11,6 +12,7 @@ import java.util.concurrent.Future;
import org.apache.commons.lang.WordUtils; import org.apache.commons.lang.WordUtils;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import com.comphenix.protocol.PacketTypeLookup.ClassLookup;
import com.comphenix.protocol.events.ConnectionSide; import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.injector.packet.PacketRegistry; import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.reflect.ObjectEnum; import com.comphenix.protocol.reflect.ObjectEnum;
@ -635,7 +637,9 @@ public class PacketType implements Serializable, Comparable<PacketType> {
* @param packetId - the packet ID. * @param packetId - the packet ID.
* @return The corresponding packet type. * @return The corresponding packet type.
* @throws IllegalArgumentException If the current packet could not be found. * @throws IllegalArgumentException If the current packet could not be found.
* @deprecated IDs are no longer reliable
*/ */
@Deprecated
public static PacketType findCurrent(Protocol protocol, Sender sender, int packetId) { public static PacketType findCurrent(Protocol protocol, Sender sender, int packetId) {
PacketType type = getLookup().getFromCurrent(protocol, sender, packetId); PacketType type = getLookup().getFromCurrent(protocol, sender, packetId);
@ -645,13 +649,34 @@ public class PacketType implements Serializable, Comparable<PacketType> {
"(Protocol: " + protocol + ", Sender: " + sender + ")"); "(Protocol: " + protocol + ", Sender: " + sender + ")");
} }
public static PacketType findCurrent(Protocol protocol, Sender sender, String name) {
name = format(protocol, sender, name);
PacketType type = getLookup().getFromCurrent(protocol, sender, name);
if (type != null) {
return type;
} else {
throw new IllegalArgumentException("Cannot find packet " + name +
"(Protocol: " + protocol + ", Sender: " + sender + ")");
}
}
private static String format(Protocol protocol, Sender sender, String name) {
if (name.contains("Packet"))
return name;
return String.format("Packet%s%s%s", protocol.getPacketName(), sender.getPacketName(), name);
}
/** /**
* Determine if the given packet exists. * Determine if the given packet exists.
* @param protocol - the protocol. * @param protocol - the protocol.
* @param sender - the sender. * @param sender - the sender.
* @param packetId - the packet ID. * @param packetId - the packet ID.
* @return TRUE if it exists, FALSE otherwise. * @return TRUE if it exists, FALSE otherwise.
* @deprecated IDs are no longer reliable
*/ */
@Deprecated
public static boolean hasCurrent(Protocol protocol, Sender sender, int packetId) { public static boolean hasCurrent(Protocol protocol, Sender sender, int packetId) {
return getLookup().getFromCurrent(protocol, sender, packetId) != null; return getLookup().getFromCurrent(protocol, sender, packetId) != null;
} }
@ -680,7 +705,7 @@ public class PacketType implements Serializable, Comparable<PacketType> {
} }
/** /**
* Retrieve a packet type from a protocol, sender and packet ID. * Retrieve a packet type from a protocol, sender and packet ID, for pre-1.8.
* <p> * <p>
* The packet will automatically be registered if its missing. * The packet will automatically be registered if its missing.
* @param protocol - the current protocol. * @param protocol - the current protocol.
@ -689,21 +714,59 @@ public class PacketType implements Serializable, Comparable<PacketType> {
* @param packetClass - the packet class * @param packetClass - the packet class
* @return The corresponding packet type. * @return The corresponding packet type.
*/ */
public static PacketType fromCurrent(Protocol protocol, Sender sender, int packetId, Class<?> packetClass) { public static PacketType fromID(Protocol protocol, Sender sender, int packetId, Class<?> packetClass) {
String className = packetClass.getSimpleName(); PacketType type = getLookup().getFromCurrent(protocol, sender, packetId);
for (PacketType type : PacketType.values()) {
for (String name : type.classNames) { if (type == null) {
if (className.equals(name)) { type = new PacketType(protocol, sender, packetId, -1, PROTOCOL_VERSION, packetClass.getName());
return type; type.dynamic = true;
}
} // Many may be scheduled, but only the first will be executed
scheduleRegister(type, "Dynamic-" + UUID.randomUUID().toString());
} }
PacketType type = new PacketType(protocol, sender, packetId, -1, PROTOCOL_VERSION, className); return type;
type.dynamic = true; }
/**
* Retrieve a packet type from a protocol, sender, ID, and class for 1.8+
* <p>
* The packet will automatically be registered if its missing.
* @param protocol - the current protocol.
* @param sender - the sender.
* @param packetId - the packet ID. Can be UNKNOWN_PACKET.
* @param packetClass - the packet class.
* @return The corresponding packet type.
*/
public static PacketType fromCurrent(Protocol protocol, Sender sender, int packetId, Class<?> packetClass) {
ClassLookup lookup = getLookup().getClassLookup();
Map<String, PacketType> map = lookup.getMap(protocol, sender);
// Check the map first
String className = packetClass.getSimpleName();
PacketType type = map.get(className);
if (type == null) {
// Then check any aliases
for (PacketType check : map.values()) {
String[] aliases = check.getClassNames();
if (aliases.length > 1) {
for (String alias : aliases) {
if (alias.equals(className)) {
// We have a match!
type = check;
}
}
}
}
// Guess we don't support this packet :/
type = new PacketType(protocol, sender, packetId, -1, PROTOCOL_VERSION, className);
type.dynamic = true;
// Many may be scheduled, but only the first will be executed
scheduleRegister(type, "Dynamic-" + UUID.randomUUID().toString());
}
// Many may be scheduled, but only the first will be executed
scheduleRegister(type, "Dynamic-" + UUID.randomUUID().toString());
return type; return type;
} }
@ -826,7 +889,7 @@ public class PacketType implements Serializable, Comparable<PacketType> {
this.classNames = new String[names.length]; this.classNames = new String[names.length];
for (int i = 0; i < classNames.length; i++) { for (int i = 0; i < classNames.length; i++) {
classNames[i] = String.format("Packet%s%s%s", protocol.getPacketName(), sender.getPacketName(), names[i]); classNames[i] = format(protocol, sender, names[i]);
} }
} }
@ -894,6 +957,10 @@ public class PacketType implements Serializable, Comparable<PacketType> {
return currentId; return currentId;
} }
public String[] getClassNames() {
return classNames;
}
/** /**
* Retrieve the equivalent packet class. * Retrieve the equivalent packet class.
* @return The packet class, or NULL if not found. * @return The packet class, or NULL if not found.

View File

@ -2,6 +2,8 @@ package com.comphenix.protocol;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.comphenix.protocol.PacketType.Protocol; import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.PacketType.Sender; import com.comphenix.protocol.PacketType.Sender;
@ -15,7 +17,7 @@ import com.google.common.collect.Multimap;
* @author Kristian * @author Kristian
*/ */
class PacketTypeLookup { class PacketTypeLookup {
private static class ProtocolSenderLookup { public static class ProtocolSenderLookup {
// Unroll lookup for performance reasons // Unroll lookup for performance reasons
public final IntegerMap<PacketType> HANDSHAKE_CLIENT = IntegerMap.newMap(); public final IntegerMap<PacketType> HANDSHAKE_CLIENT = IntegerMap.newMap();
public final IntegerMap<PacketType> HANDSHAKE_SERVER = IntegerMap.newMap(); public final IntegerMap<PacketType> HANDSHAKE_SERVER = IntegerMap.newMap();
@ -48,13 +50,49 @@ class PacketTypeLookup {
} }
} }
public static class ClassLookup {
// Unroll lookup for performance reasons
public final Map<String, PacketType> HANDSHAKE_CLIENT = new ConcurrentHashMap<String, PacketType>();
public final Map<String, PacketType> HANDSHAKE_SERVER = new ConcurrentHashMap<String, PacketType>();
public final Map<String, PacketType> GAME_CLIENT = new ConcurrentHashMap<String, PacketType>();
public final Map<String, PacketType> GAME_SERVER = new ConcurrentHashMap<String, PacketType>();
public final Map<String, PacketType> STATUS_CLIENT = new ConcurrentHashMap<String, PacketType>();
public final Map<String, PacketType> STATUS_SERVER = new ConcurrentHashMap<String, PacketType>();
public final Map<String, PacketType> LOGIN_CLIENT = new ConcurrentHashMap<String, PacketType>();
public final Map<String, PacketType> LOGIN_SERVER = new ConcurrentHashMap<String, PacketType>();
/**
* Retrieve the correct integer map for a specific protocol and sender.
* @param protocol - the protocol.
* @param sender - the sender.
* @return The integer map of packets.
*/
public Map<String, PacketType> getMap(Protocol protocol, Sender sender) {
switch (protocol) {
case HANDSHAKING:
return sender == Sender.CLIENT ? HANDSHAKE_CLIENT : HANDSHAKE_SERVER;
case PLAY:
return sender == Sender.CLIENT ? GAME_CLIENT : GAME_SERVER;
case STATUS:
return sender == Sender.CLIENT ? STATUS_CLIENT : STATUS_SERVER;
case LOGIN:
return sender == Sender.CLIENT ? LOGIN_CLIENT : LOGIN_SERVER;
default:
throw new IllegalArgumentException("Unable to find protocol " + protocol);
}
}
}
// Packet IDs from 1.6.4 and below // Packet IDs from 1.6.4 and below
private final IntegerMap<PacketType> legacyLookup = new IntegerMap<PacketType>(); private final IntegerMap<PacketType> legacyLookup = new IntegerMap<PacketType>();
private final IntegerMap<PacketType> serverLookup = new IntegerMap<PacketType>(); private final IntegerMap<PacketType> serverLookup = new IntegerMap<PacketType>();
private final IntegerMap<PacketType> clientLookup = new IntegerMap<PacketType>(); private final IntegerMap<PacketType> clientLookup = new IntegerMap<PacketType>();
// Packets for 1.7.2 // Packets for 1.7.2
private final ProtocolSenderLookup currentLookup = new ProtocolSenderLookup(); private final ProtocolSenderLookup idLookup = new ProtocolSenderLookup();
// Packets for 1.8+
private final ClassLookup classLookup = new ClassLookup();
// Packets based on name // Packets based on name
private final Multimap<String, PacketType> nameLookup = HashMultimap.create(); private final Multimap<String, PacketType> nameLookup = HashMultimap.create();
@ -79,9 +117,9 @@ class PacketTypeLookup {
} }
// Skip unknown current packets // Skip unknown current packets
if (type.getCurrentId() != PacketType.UNKNOWN_PACKET) { if (type.getCurrentId() != PacketType.UNKNOWN_PACKET) {
currentLookup.getMap(type.getProtocol(), type.getSender()).put(type.getCurrentId(), type); idLookup.getMap(type.getProtocol(), type.getSender()).put(type.getCurrentId(), type);
classLookup.getMap(type.getProtocol(), type.getSender()).put(type.getClassNames()[0], type);
} }
// Save name
nameLookup.put(type.name(), type); nameLookup.put(type.name(), type);
} }
return this; return this;
@ -132,8 +170,18 @@ class PacketTypeLookup {
* @param sender - the sender. * @param sender - the sender.
* @param packetId - the packet ID. * @param packetId - the packet ID.
* @return The corresponding packet type, or NULL if not found. * @return The corresponding packet type, or NULL if not found.
* @deprecated IDs are no longer reliable
*/ */
@Deprecated
public PacketType getFromCurrent(Protocol protocol, Sender sender, int packetId) { public PacketType getFromCurrent(Protocol protocol, Sender sender, int packetId) {
return currentLookup.getMap(protocol, sender).get(packetId); return idLookup.getMap(protocol, sender).get(packetId);
}
public PacketType getFromCurrent(Protocol protocol, Sender sender, String name) {
return classLookup.getMap(protocol, sender).get(name);
}
public ClassLookup getClassLookup() {
return classLookup;
} }
} }

View File

@ -17,6 +17,7 @@
package com.comphenix.protocol; package com.comphenix.protocol;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -25,8 +26,10 @@ import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.io.Files;
/** /**
* Represents the configuration of ProtocolLib. * Represents the configuration of ProtocolLib.
@ -34,7 +37,10 @@ import com.google.common.collect.Lists;
* @author Kristian * @author Kristian
*/ */
public class ProtocolConfig { public class ProtocolConfig {
private static final String LAST_UPDATE_FILE = "lastupdate";
private static final String SECTION_GLOBAL = "global"; private static final String SECTION_GLOBAL = "global";
private static final String SECTION_AUTOUPDATER = "auto updater";
private static final String METRICS_ENABLED = "metrics"; private static final String METRICS_ENABLED = "metrics";
@ -48,16 +54,33 @@ public class ProtocolConfig {
private static final String SCRIPT_ENGINE_NAME = "script engine"; private static final String SCRIPT_ENGINE_NAME = "script engine";
private static final String SUPPRESSED_REPORTS = "suppressed reports"; private static final String SUPPRESSED_REPORTS = "suppressed reports";
private static final String UPDATER_NOTIFY = "notify";
private static final String UPDATER_DOWNLAD = "download";
private static final String UPDATER_DELAY = "delay";
// Defaults
private static final long DEFAULT_UPDATER_DELAY = 43200;
private Plugin plugin; private Plugin plugin;
private Configuration config; private Configuration config;
private ConfigurationSection global; private boolean loadingSections;
private ConfigurationSection global;
private ConfigurationSection updater;
// Last update time
private long lastUpdateTime;
private boolean configChanged; private boolean configChanged;
private boolean valuesChanged;
// Modifications // Modifications
private int modCount; private int modCount;
public ProtocolConfig(Plugin plugin) { public ProtocolConfig(Plugin plugin) {
this(plugin, plugin.getConfig());
}
public ProtocolConfig(Plugin plugin, Configuration config) {
this.plugin = plugin; this.plugin = plugin;
reloadConfig(); reloadConfig();
} }
@ -68,24 +91,118 @@ public class ProtocolConfig {
public void reloadConfig() { public void reloadConfig() {
// Reset // Reset
configChanged = false; configChanged = false;
valuesChanged = false;
modCount++; modCount++;
this.config = plugin.getConfig(); this.config = plugin.getConfig();
if (config != null) { this.lastUpdateTime = loadLastUpdate();
config.options().copyDefaults(true); loadSections(!loadingSections);
global = config.getConfigurationSection(SECTION_GLOBAL); }
/**
* Load the last update time stamp from the file system.
*
* @return Last update time stamp.
*/
private long loadLastUpdate() {
File dataFile = getLastUpdateFile();
if (dataFile.exists()) {
try {
return Long.parseLong(Files.toString(dataFile, Charsets.UTF_8));
} catch (NumberFormatException e) {
plugin.getLogger().warning("Cannot parse " + dataFile + " as a number.");
} catch (IOException e) {
plugin.getLogger().warning("Cannot read " + dataFile);
}
}
// Default last update
return 0;
}
/**
* Store the given time stamp.
*
* @param value - time stamp to store.
*/
private void saveLastUpdate(long value) {
File dataFile = getLastUpdateFile();
// The data folder must exist
dataFile.getParentFile().mkdirs();
if (dataFile.exists())
dataFile.delete();
try {
Files.write(Long.toString(value), dataFile, Charsets.UTF_8);
} catch (IOException e) {
throw new RuntimeException("Cannot write " + dataFile, e);
} }
} }
/**
* Retrieve the file that is used to store the update time stamp.
*
* @return File storing the update time stamp.
*/
private File getLastUpdateFile() {
return new File(plugin.getDataFolder(), LAST_UPDATE_FILE);
}
/**
* Load data sections.
*
* @param copyDefaults - whether or not to copy configuration defaults.
*/
private void loadSections(boolean copyDefaults) {
if (config != null) {
global = config.getConfigurationSection(SECTION_GLOBAL);
}
if (global != null) {
updater = global.getConfigurationSection(SECTION_AUTOUPDATER);
}
// Automatically copy defaults
if (copyDefaults && (!getFile().exists() || global == null || updater == null)) {
loadingSections = true;
if (config != null)
config.options().copyDefaults(true);
plugin.saveDefaultConfig();
plugin.reloadConfig();
loadingSections = false;
// Inform the user
ProtocolLibrary.log("Created default configuration.");
}
}
/**
* 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.
*/
private void setConfig(ConfigurationSection section, String path, Object value) { private void setConfig(ConfigurationSection section, String path, Object value) {
configChanged = true; configChanged = true;
section.set(path, value); section.set(path, value);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private <T> T getGlobalValue(String name, T def) { private <T> T getGlobalValue(String path, T def) {
try { try {
return (T) global.get(name, def); return (T) global.get(path, def);
} catch (Throwable ex) {
return def;
}
}
@SuppressWarnings("unchecked")
private <T> T getUpdaterValue(String path, T def) {
try {
return (T) updater.get(path, def);
} catch (Throwable ex) { } catch (Throwable ex) {
return def; return def;
} }
@ -118,6 +235,44 @@ public class ProtocolConfig {
global.set(DETAILED_ERROR, value); global.set(DETAILED_ERROR, value);
} }
/**
* Retrieve whether or not ProtocolLib should determine if a new version has been released.
*
* @return TRUE if it should do this automatically, FALSE otherwise.
*/
public boolean isAutoNotify() {
return getUpdaterValue(UPDATER_NOTIFY, true);
}
/**
* 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) {
setConfig(updater, UPDATER_NOTIFY, value);
modCount++;
}
/**
* Retrieve whether or not ProtocolLib should automatically download the new version.
*
* @return TRUE if it should, FALSE otherwise.
*/
public boolean isAutoDownload() {
return updater != null && getUpdaterValue(UPDATER_DOWNLAD, true);
}
/**
* Set whether or not ProtocolLib should automatically download the new version.
*
* @param value - TRUE if it should. FALSE otherwise.
*/
public void setAutoDownload(boolean value) {
setConfig(updater, UPDATER_DOWNLAD, value);
modCount++;
}
/** /**
* Determine whether or not debug mode is enabled. * Determine whether or not debug mode is enabled.
* <p> * <p>
@ -158,6 +313,31 @@ public class ProtocolConfig {
modCount++; modCount++;
} }
/**
* Retrieve the amount of time to wait until checking for a new update.
*
* @return The amount of time to wait.
*/
public long getAutoDelay() {
// Note that the delay must be greater than 59 seconds
return Math.max(getUpdaterValue(UPDATER_DELAY, 0), DEFAULT_UPDATER_DELAY);
}
/**
* 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)
delaySeconds = DEFAULT_UPDATER_DELAY;
setConfig(updater, UPDATER_DELAY, delaySeconds);
modCount++;
}
/** /**
* The version of Minecraft to ignore the built-in safety feature. * The version of Minecraft to ignore the built-in safety feature.
* *
@ -221,13 +401,34 @@ public class ProtocolConfig {
modCount++; modCount++;
} }
/**
* Retrieve the last time we updated, in seconds since 1970.01.01 00:00.
*
* @return Last update time.
*/
public long getAutoLastTime() {
return lastUpdateTime;
}
/**
* 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) {
this.valuesChanged = true;
this.lastUpdateTime = lastTimeSeconds;
}
/** /**
* Retrieve the unique name of the script engine to use for filtering. * Retrieve the unique name of the script engine to use for filtering.
* *
* @return Script engine to use. * @return Script engine to use.
*/ */
public String getScriptEngineName() { public String getScriptEngineName() {
return global.getString(SCRIPT_ENGINE_NAME, "JavaScript"); return getGlobalValue(SCRIPT_ENGINE_NAME, "JavaScript");
} }
/** /**
@ -271,7 +472,7 @@ public class ProtocolConfig {
/** /**
* Set the starting injection method to use. * Set the starting injection method to use.
* *
* @param hook Injection method * @return Injection method.
*/ */
public void setInjectionMethod(PlayerInjectHooks hook) { public void setInjectionMethod(PlayerInjectHooks hook) {
setConfig(global, INJECTION_METHOD, hook.name()); setConfig(global, INJECTION_METHOD, hook.name());
@ -291,10 +492,13 @@ public class ProtocolConfig {
* Save the current configuration file. * Save the current configuration file.
*/ */
public void saveAll() { public void saveAll() {
if (valuesChanged)
saveLastUpdate(lastUpdateTime);
if (configChanged) if (configChanged)
plugin.saveConfig(); plugin.saveConfig();
// And we're done // And we're done
valuesChanged = false;
configChanged = false; configChanged = false;
} }
} }

View File

@ -50,6 +50,8 @@ import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
import com.comphenix.protocol.metrics.Statistics; import com.comphenix.protocol.metrics.Statistics;
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler; 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.ChatExtensions;
import com.comphenix.protocol.utility.EnhancerFactory; import com.comphenix.protocol.utility.EnhancerFactory;
import com.comphenix.protocol.utility.MinecraftVersion; import com.comphenix.protocol.utility.MinecraftVersion;
@ -97,6 +99,10 @@ public class ProtocolLibrary extends JavaPlugin {
*/ */
public static final String MINECRAFT_LAST_RELEASE_DATE = "2015-07-27"; public static final String MINECRAFT_LAST_RELEASE_DATE = "2015-07-27";
// Update information
static final String BUKKIT_DEV_SLUG = "protocollib";
static final int BUKKIT_DEV_ID = 45564;
// Different commands // Different commands
private enum ProtocolCommand { private enum ProtocolCommand {
FILTER, FILTER,
@ -142,6 +148,10 @@ public class ProtocolLibrary extends JavaPlugin {
// Settings/options // Settings/options
private int configExpectedMod = -1; private int configExpectedMod = -1;
// Updater
private Updater updater;
private static boolean UPDATES_DISABLED;
// Logger // Logger
private static Logger logger; private static Logger logger;
private Handler redirectHandler; private Handler redirectHandler;
@ -205,6 +215,9 @@ public class ProtocolLibrary extends JavaPlugin {
// Handle unexpected Minecraft versions // Handle unexpected Minecraft versions
MinecraftVersion version = verifyMinecraftVersion(); MinecraftVersion version = verifyMinecraftVersion();
// Set updater - this will not perform any update automatically
updater = Updater.create(this, BUKKIT_DEV_ID, getFile(), UpdateType.NO_DOWNLOAD, true);
unhookTask = new DelayedSingleTask(this); unhookTask = new DelayedSingleTask(this);
protocolManager = PacketFilterManager.newBuilder() protocolManager = PacketFilterManager.newBuilder()
.classLoader(getClassLoader()) .classLoader(getClassLoader())
@ -254,7 +267,7 @@ public class ProtocolLibrary extends JavaPlugin {
try { try {
switch (command) { switch (command) {
case PROTOCOL: case PROTOCOL:
commandProtocol = new CommandProtocol(reporter, this); commandProtocol = new CommandProtocol(reporter, this, updater, config);
break; break;
case FILTER: case FILTER:
commandFilter = new CommandFilter(reporter, this, config); commandFilter = new CommandFilter(reporter, this, config);
@ -559,6 +572,11 @@ public class ProtocolLibrary extends JavaPlugin {
// House keeping // House keeping
updateConfiguration(); updateConfiguration();
// Check for updates too
if (!UPDATES_DISABLED && (tickCounter % 20) == 0) {
checkUpdates();
}
} }
}, ASYNC_MANAGER_DELAY, ASYNC_MANAGER_DELAY); }, ASYNC_MANAGER_DELAY, ASYNC_MANAGER_DELAY);
} catch (OutOfMemoryError e) { } catch (OutOfMemoryError e) {
@ -581,6 +599,29 @@ public class ProtocolLibrary 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()) {
// Initiate the update as if it came from the console
if (config.isAutoDownload())
commandProtocol.updateVersion(getServer().getConsoleSender());
else if (config.isAutoNotify())
commandProtocol.checkVersion(getServer().getConsoleSender());
else
commandProtocol.updateFinished();
}
} catch (Exception e) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_UPDATE_PLUGIN).error(e));
UPDATES_DISABLED = true;
}
}
@Override @Override
public void onDisable() { public void onDisable() {
if (skipDisable) { if (skipDisable) {

View File

@ -0,0 +1,103 @@
/**
* 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.compat.netty;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.injector.netty.ProtocolRegistry;
import com.comphenix.protocol.injector.packet.MapContainer;
import com.comphenix.protocol.reflect.StructureModifier;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
/**
* @author dmulloy2
*/
public class LegacyProtocolRegistry extends ProtocolRegistry {
public LegacyProtocolRegistry() {
super();
}
@Override
protected void initialize() {
final Object[] protocols = enumProtocol.getEnumConstants();
List<Map<Integer, Class<?>>> serverMaps = Lists.newArrayList();
List<Map<Integer, Class<?>>> clientMaps = Lists.newArrayList();
StructureModifier<Object> modifier = null;
// Result
Register result = new Register();
for (Object protocol : protocols) {
if (modifier == null)
modifier = new StructureModifier<Object>(protocol.getClass().getSuperclass(), false);
StructureModifier<Map<Integer, Class<?>>> maps = modifier.withTarget(protocol).withType(Map.class);
serverMaps.add(maps.read(0));
clientMaps.add(maps.read(1));
}
// Maps we have to occationally check have changed
for (Map<Integer, Class<?>> map : Iterables.concat(serverMaps, clientMaps)) {
result.containers.add(new MapContainer(map));
}
// Heuristic - there are more server packets than client packets
if (sum(clientMaps) > sum(serverMaps)) {
// Swap if this is violated
List<Map<Integer, Class<?>>> temp = serverMaps;
serverMaps = clientMaps;
clientMaps = temp;
}
for (int i = 0; i < protocols.length; i++) {
Enum<?> enumProtocol = (Enum<?>) protocols[i];
Protocol equivalent = Protocol.fromVanilla(enumProtocol);
// Associate known types
associatePackets(result, serverMaps.get(i), equivalent, Sender.SERVER);
associatePackets(result, clientMaps.get(i), equivalent, Sender.CLIENT);
}
// Exchange (thread safe, as we have only one writer)
this.register = result;
}
@Override
protected void associatePackets(Register register, Map<Integer, Class<?>> lookup, Protocol protocol, Sender sender) {
for (Entry<Integer, Class<?>> entry : lookup.entrySet()) {
PacketType type = PacketType.fromID(protocol, sender, entry.getKey(), entry.getValue());
try {
register.typeToClass.put(type, entry.getValue());
if (sender == Sender.SERVER)
register.serverPackets.add(type);
if (sender == Sender.CLIENT)
register.clientPackets.add(type);
} catch (IllegalArgumentException ex) {
// Sometimes this happens with fake packets, just ignore it
}
}
}
}

View File

@ -0,0 +1,104 @@
/**
* 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.compat.netty.independent;
import java.util.Map;
import java.util.Map.Entry;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.injector.netty.ProtocolRegistry;
import com.comphenix.protocol.injector.packet.MapContainer;
import com.comphenix.protocol.reflect.StructureModifier;
import com.google.common.collect.Maps;
/**
* @author dmulloy2
*/
public class NettyProtocolRegistry extends ProtocolRegistry {
@Override
protected synchronized void initialize() {
Object[] protocols = enumProtocol.getEnumConstants();
// ID to Packet class maps
Map<Object, Map<Integer, Class<?>>> serverMaps = Maps.newLinkedHashMap();
Map<Object, Map<Integer, Class<?>>> clientMaps = Maps.newLinkedHashMap();
Register result = new Register();
StructureModifier<Object> modifier = null;
// Iterate through the protocols
for (Object protocol : protocols) {
if (modifier == null)
modifier = new StructureModifier<Object>(protocol.getClass().getSuperclass(), false);
StructureModifier<Map<Object, Map<Integer, Class<?>>>> maps = modifier.withTarget(protocol).withType(Map.class);
for (Entry<Object, Map<Integer, Class<?>>> entry : maps.read(0).entrySet()) {
String direction = entry.getKey().toString();
if (direction.contains("CLIENTBOUND")) { // Sent by Server
serverMaps.put(protocol, entry.getValue());
} else if (direction.contains("SERVERBOUND")) { // Sent by Client
clientMaps.put(protocol, entry.getValue());
}
}
}
// Maps we have to occationally check have changed
for (Map<Integer, Class<?>> map : serverMaps.values()) {
result.containers.add(new MapContainer(map));
}
for (Map<Integer, Class<?>> map : clientMaps.values()) {
result.containers.add(new MapContainer(map));
}
for (int i = 0; i < protocols.length; i++) {
Object protocol = protocols[i];
Enum<?> enumProtocol = (Enum<?>) protocol;
Protocol equivalent = Protocol.fromVanilla(enumProtocol);
// Associate known types
if (serverMaps.containsKey(protocol))
associatePackets(result, serverMaps.get(protocol), equivalent, Sender.SERVER);
if (clientMaps.containsKey(protocol))
associatePackets(result, clientMaps.get(protocol), equivalent, Sender.CLIENT);
}
// Exchange (thread safe, as we have only one writer)
this.register = result;
}
@Override
protected void associatePackets(Register register, Map<Integer, Class<?>> lookup, Protocol protocol, Sender sender) {
for (Entry<Integer, Class<?>> entry : lookup.entrySet()) {
PacketType type = PacketType.fromCurrent(protocol, sender, entry.getKey(), entry.getValue());
try {
register.typeToClass.put(type, entry.getValue());
if (sender == Sender.SERVER)
register.serverPackets.add(type);
if (sender == Sender.CLIENT)
register.clientPackets.add(type);
} catch (IllegalArgumentException ex) {
// Sometimes this happens with fake packets, just ignore it
}
}
}
}

View File

@ -1,246 +0,0 @@
package com.comphenix.protocol.injector.netty;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.compat.netty.Netty;
import com.comphenix.protocol.injector.packet.MapContainer;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
/**
* Represents a way of accessing the new netty Protocol enum.
* @author Kristian
*/
// TODO: Handle modifications to the BiMap
public class NettyProtocolRegistry {
/**
* Represents a register we are currently building.
* @author Kristian
*/
private static class Register {
// The main lookup table
public BiMap<PacketType, Class<?>> typeToClass = HashBiMap.create();
public volatile Set<PacketType> serverPackets = Sets.newHashSet();
public volatile Set<PacketType> clientPackets = Sets.newHashSet();
public List<MapContainer> containers = Lists.newArrayList();
/**
* Determine if the current register is outdated.
* @return TRUE if it is, FALSE otherwise.
*/
public boolean isOutdated() {
for (MapContainer container : containers) {
if (container.hasChanged()) {
return true;
}
}
return false;
}
}
private Class<?> enumProtocol;
// Current register
private volatile Register register;
public NettyProtocolRegistry() {
enumProtocol = MinecraftReflection.getEnumProtocolClass();
initialize();
}
/**
* Retrieve an immutable view of the packet type lookup.
* @return The packet type lookup.
*/
public Map<PacketType, Class<?>> getPacketTypeLookup() {
return Collections.unmodifiableMap(register.typeToClass);
}
/**
* Retrieve an immutable view of the class to packet type lookup.
* @return The packet type lookup.
*/
public Map<Class<?>, PacketType> getPacketClassLookup() {
return Collections.unmodifiableMap(register.typeToClass.inverse());
}
/**
* Retrieve every known client packet, from every protocol.
* @return Every client packet.
*/
public Set<PacketType> getClientPackets() {
return Collections.unmodifiableSet(register.clientPackets);
}
/**
* Retrieve every known server packet, from every protocol.
* @return Every server packet.
*/
public Set<PacketType> getServerPackets() {
return Collections.unmodifiableSet(register.serverPackets);
}
/**
* Ensure that our local register is up-to-date with Minecraft.
* <p>
* This operation may block the calling thread.
*/
public synchronized void synchronize() {
// See if the register is outdated
if (register.isOutdated()) {
initialize();
}
}
/**
* Load the packet lookup tables in each protocol.
*/
private synchronized void initialize() {
if (!Netty.isIndependent()) { // Check for 1.7
initialize17();
return;
}
Object[] protocols = enumProtocol.getEnumConstants();
// ID to Packet class maps
Map<Object, Map<Integer, Class<?>>> serverMaps = Maps.newLinkedHashMap();
Map<Object, Map<Integer, Class<?>>> clientMaps = Maps.newLinkedHashMap();
Register result = new Register();
StructureModifier<Object> modifier = null;
// Iterate through the protocols
for (Object protocol : protocols) {
if (modifier == null)
modifier = new StructureModifier<Object>(protocol.getClass().getSuperclass(), false);
StructureModifier<Map<Object, Map<Integer, Class<?>>>> maps = modifier.withTarget(protocol).withType(Map.class);
for (Entry<Object, Map<Integer, Class<?>>> entry : maps.read(0).entrySet()) {
String direction = entry.getKey().toString();
if (direction.contains("CLIENTBOUND")) { // Sent by Server
serverMaps.put(protocol, entry.getValue());
} else if (direction.contains("SERVERBOUND")) { // Sent by Client
clientMaps.put(protocol, entry.getValue());
}
}
}
// Maps we have to occationally check have changed
for (Map<Integer, Class<?>> map : serverMaps.values()) {
result.containers.add(new MapContainer(map));
}
for (Map<Integer, Class<?>> map : clientMaps.values()) {
result.containers.add(new MapContainer(map));
}
// Heuristic - there are more server packets than client packets
/* if (sum(clientMaps) > sum(serverMaps)) {
// Swap if this is violated
List<Map<Integer, Class<?>>> temp = serverMaps;
serverMaps = clientMaps;
clientMaps = temp;
} */
for (int i = 0; i < protocols.length; i++) {
Object protocol = protocols[i];
Enum<?> enumProtocol = (Enum<?>) protocol;
Protocol equivalent = Protocol.fromVanilla(enumProtocol);
// Associate known types
if (serverMaps.containsKey(protocol))
associatePackets(result, serverMaps.get(protocol), equivalent, Sender.SERVER);
if (clientMaps.containsKey(protocol))
associatePackets(result, clientMaps.get(protocol), equivalent, Sender.CLIENT);
}
// Exchange (thread safe, as we have only one writer)
this.register = result;
}
private synchronized void initialize17() {
final Object[] protocols = enumProtocol.getEnumConstants();
List<Map<Integer, Class<?>>> serverMaps = Lists.newArrayList();
List<Map<Integer, Class<?>>> clientMaps = Lists.newArrayList();
StructureModifier<Object> modifier = null;
// Result
Register result = new Register();
for (Object protocol : protocols) {
if (modifier == null)
modifier = new StructureModifier<Object>(protocol.getClass().getSuperclass(), false);
StructureModifier<Map<Integer, Class<?>>> maps = modifier.withTarget(protocol).withType(Map.class);
serverMaps.add(maps.read(0));
clientMaps.add(maps.read(1));
}
// Maps we have to occationally check have changed
for (Map<Integer, Class<?>> map : Iterables.concat(serverMaps, clientMaps)) {
result.containers.add(new MapContainer(map));
}
// Heuristic - there are more server packets than client packets
if (sum(clientMaps) > sum(serverMaps)) {
// Swap if this is violated
List<Map<Integer, Class<?>>> temp = serverMaps;
serverMaps = clientMaps;
clientMaps = temp;
}
for (int i = 0; i < protocols.length; i++) {
Enum<?> enumProtocol = (Enum<?>) protocols[i];
Protocol equivalent = Protocol.fromVanilla(enumProtocol);
// Associate known types
associatePackets(result, serverMaps.get(i), equivalent, Sender.SERVER);
associatePackets(result, clientMaps.get(i), equivalent, Sender.CLIENT);
}
// Exchange (thread safe, as we have only one writer)
this.register = result;
}
private void associatePackets(Register register, Map<Integer, Class<?>> lookup, Protocol protocol, Sender sender) {
for (Entry<Integer, Class<?>> entry : lookup.entrySet()) {
PacketType type = PacketType.fromCurrent(protocol, sender, entry.getKey(), entry.getValue());
try {
register.typeToClass.put(type, entry.getValue());
if (sender == Sender.SERVER)
register.serverPackets.add(type);
if (sender == Sender.CLIENT)
register.clientPackets.add(type);
} catch (IllegalArgumentException ex) {
// Sometimes this happens with fake packets, just ignore it
}
}
}
/**
* Retrieve the number of mapping in all the maps.
* @param maps - iterable of maps.
* @return The sum of all the entries.
*/
private int sum(Iterable<? extends Map<Integer, Class<?>>> maps) {
int count = 0;
for (Map<Integer, Class<?>> map : maps)
count += map.size();
return count;
}
}

View File

@ -0,0 +1,125 @@
package com.comphenix.protocol.injector.netty;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.injector.packet.MapContainer;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
/**
* Represents a way of accessing the new netty Protocol enum.
* @author Kristian
*/
// TODO: Handle modifications to the BiMap
public abstract class ProtocolRegistry {
/**
* Represents a register we are currently building.
* @author Kristian
*/
protected static class Register {
// The main lookup table
public BiMap<PacketType, Class<?>> typeToClass = HashBiMap.create();
public volatile Set<PacketType> serverPackets = Sets.newHashSet();
public volatile Set<PacketType> clientPackets = Sets.newHashSet();
public List<MapContainer> containers = Lists.newArrayList();
public Register() {
}
/**
* Determine if the current register is outdated.
* @return TRUE if it is, FALSE otherwise.
*/
public boolean isOutdated() {
for (MapContainer container : containers) {
if (container.hasChanged()) {
return true;
}
}
return false;
}
}
protected Class<?> enumProtocol;
// Current register
protected volatile Register register;
public ProtocolRegistry() {
enumProtocol = MinecraftReflection.getEnumProtocolClass();
initialize();
}
/**
* Retrieve an immutable view of the packet type lookup.
* @return The packet type lookup.
*/
public Map<PacketType, Class<?>> getPacketTypeLookup() {
return Collections.unmodifiableMap(register.typeToClass);
}
/**
* Retrieve an immutable view of the class to packet type lookup.
* @return The packet type lookup.
*/
public Map<Class<?>, PacketType> getPacketClassLookup() {
return Collections.unmodifiableMap(register.typeToClass.inverse());
}
/**
* Retrieve every known client packet, from every protocol.
* @return Every client packet.
*/
public Set<PacketType> getClientPackets() {
return Collections.unmodifiableSet(register.clientPackets);
}
/**
* Retrieve every known server packet, from every protocol.
* @return Every server packet.
*/
public Set<PacketType> getServerPackets() {
return Collections.unmodifiableSet(register.serverPackets);
}
/**
* Ensure that our local register is up-to-date with Minecraft.
* <p>
* This operation may block the calling thread.
*/
public synchronized void synchronize() {
// See if the register is outdated
if (register.isOutdated()) {
initialize();
}
}
/**
* Load the packet lookup tables in each protocol.
*/
protected abstract void initialize();
protected abstract void associatePackets(Register register, Map<Integer, Class<?>> lookup, Protocol protocol, Sender sender);
/**
* Retrieve the number of mapping in all the maps.
* @param maps - iterable of maps.
* @return The sum of all the entries.
*/
protected final int sum(Iterable<? extends Map<Integer, Class<?>>> maps) {
int count = 0;
for (Map<Integer, Class<?>> map : maps)
count += map.size();
return count;
}
}

View File

@ -25,12 +25,15 @@ import java.util.Set;
import com.comphenix.protocol.PacketType; import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Sender; import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.compat.netty.LegacyProtocolRegistry;
import com.comphenix.protocol.compat.netty.independent.NettyProtocolRegistry;
import com.comphenix.protocol.error.Report; import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType; import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.netty.NettyProtocolRegistry; import com.comphenix.protocol.injector.netty.ProtocolRegistry;
import com.comphenix.protocol.injector.packet.LegacyPacketRegistry.InsufficientPacketsException; import com.comphenix.protocol.injector.packet.LegacyPacketRegistry.InsufficientPacketsException;
import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.wrappers.TroveWrapper.CannotFindTroveNoEntryValue; import com.comphenix.protocol.wrappers.TroveWrapper.CannotFindTroveNoEntryValue;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
@ -49,7 +52,7 @@ public class PacketRegistry {
// Two different packet registry // Two different packet registry
private static volatile LegacyPacketRegistry LEGACY; private static volatile LegacyPacketRegistry LEGACY;
private static volatile NettyProtocolRegistry NETTY; private static volatile ProtocolRegistry NETTY;
// Cached for legacy // Cached for legacy
private static volatile Set<PacketType> NETTY_SERVER_PACKETS; private static volatile Set<PacketType> NETTY_SERVER_PACKETS;
@ -77,7 +80,11 @@ public class PacketRegistry {
// Check for netty // Check for netty
if (MinecraftReflection.isUsingNetty()) { if (MinecraftReflection.isUsingNetty()) {
if (NETTY == null) { if (NETTY == null) {
NETTY = new NettyProtocolRegistry(); if (MinecraftVersion.getCurrentVersion().isAtLeast(MinecraftVersion.BOUNTIFUL_UPDATE)) {
NETTY = new NettyProtocolRegistry();
} else {
NETTY = new LegacyProtocolRegistry();
}
} }
} else { } else {
initializeLegacy(); initializeLegacy();

View File

@ -36,20 +36,12 @@ import com.comphenix.protocol.reflect.accessors.MethodAccessor;
public class Util { public class Util {
private static MethodAccessor getOnlinePlayers; private static MethodAccessor getOnlinePlayers;
private static boolean reflectionRequired; private static boolean reflectionRequired;
private static boolean isUsingSpigot;
static { static {
try { try {
Method method = Bukkit.class.getMethod("getOnlinePlayers"); Method method = Bukkit.class.getMethod("getOnlinePlayers");
getOnlinePlayers = Accessors.getMethodAccessor(method); getOnlinePlayers = Accessors.getMethodAccessor(method);
reflectionRequired = !method.getReturnType().isAssignableFrom(Collection.class); reflectionRequired = !method.getReturnType().isAssignableFrom(Collection.class);
try {
Class.forName("org.bukkit.entity.Player.Spigot");
isUsingSpigot = true;
} catch (ClassNotFoundException ex) {
isUsingSpigot = false;
}
} catch (Throwable ex) { } catch (Throwable ex) {
throw new RuntimeException("Failed to obtain getOnlinePlayers method.", ex); throw new RuntimeException("Failed to obtain getOnlinePlayers method.", ex);
} }
@ -87,10 +79,10 @@ public class Util {
/** /**
* Whether or not this server is running Spigot. This works by checking * Whether or not this server is running Spigot. This works by checking
* for a Spigot-specific API class, in this case {@link Player.Spigot}. * the server version for the String "Spigot"
* @return True if it is, false if not. * @return True if it is, false if not.
*/ */
public static boolean isUsingSpigot() { public static boolean isUsingSpigot() {
return isUsingSpigot; return Bukkit.getServer().getVersion().contains("Spigot");
} }
} }

View File

@ -295,6 +295,8 @@ public abstract class EnumWrappers {
if (INITIALIZED) if (INITIALIZED)
return; return;
INITIALIZED = true;
PROTOCOL_CLASS = getEnum(PacketType.Handshake.Client.SET_PROTOCOL.getPacketClass(), 0); PROTOCOL_CLASS = getEnum(PacketType.Handshake.Client.SET_PROTOCOL.getPacketClass(), 0);
CLIENT_COMMAND_CLASS = getEnum(PacketType.Play.Client.CLIENT_COMMAND.getPacketClass(), 0); CLIENT_COMMAND_CLASS = getEnum(PacketType.Play.Client.CLIENT_COMMAND.getPacketClass(), 0);
CHAT_VISIBILITY_CLASS = getEnum(PacketType.Play.Client.SETTINGS.getPacketClass(), 0); CHAT_VISIBILITY_CLASS = getEnum(PacketType.Play.Client.SETTINGS.getPacketClass(), 0);

View File

@ -1,4 +1,12 @@
global: global:
# Settings for the automatic version updater
auto updater:
notify: true
download: false
# Number of seconds to wait until a new update is downloaded
delay: 43200 # 12 hours
metrics: true metrics: true
# Automatically compile structure modifiers # Automatically compile structure modifiers

View File

@ -10,7 +10,7 @@ database: false
commands: commands:
protocol: protocol:
description: Performs administrative tasks regarding ProtocolLib. description: Performs administrative tasks regarding ProtocolLib.
usage: /<command> config|timings|listeners|version|dump usage: /<command> config|check|update|timings|listeners|version|dump
permission: protocol.admin permission: protocol.admin
permission-message: You don't have <permission> permission-message: You don't have <permission>
packet: packet:

View File

@ -24,7 +24,6 @@ import static org.junit.Assert.assertTrue;
import org.junit.Test; import org.junit.Test;
import com.comphenix.protocol.utility.MinecraftVersion; import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.utility.SnapshotVersion;
public class MinecraftVersionTest { public class MinecraftVersionTest {
@Test @Test
@ -35,20 +34,27 @@ public class MinecraftVersionTest {
MinecraftVersion lower = new MinecraftVersion(1, 0, 0); MinecraftVersion lower = new MinecraftVersion(1, 0, 0);
MinecraftVersion highest = new MinecraftVersion(1, 4, 5); MinecraftVersion highest = new MinecraftVersion(1, 4, 5);
MinecraftVersion atLeast = new MinecraftVersion(1, 8, 8);
// Make sure this is valid // Make sure this is valid
assertTrue(lower.compareTo(within) < 0 && within.compareTo(highest) < 0); assertTrue(lower.compareTo(within) < 0 && within.compareTo(highest) < 0);
assertFalse(outside.compareTo(within) < 0 && outside.compareTo(highest) < 0); assertFalse(outside.compareTo(within) < 0 && outside.compareTo(highest) < 0);
assertTrue(atLeast.isAtLeast(MinecraftVersion.BOUNTIFUL_UPDATE));
} }
@Test /* @Test
public void testSnapshotVersion() { public void testSnapshotVersion() {
MinecraftVersion version = MinecraftVersion.fromServerVersion("git-Spigot-1119 (MC: 13w39b)"); MinecraftVersion version = MinecraftVersion.fromServerVersion("git-Spigot-1119 (MC: 13w39b)");
assertEquals(version.getSnapshot(), new SnapshotVersion("13w39b")); assertEquals(version.getSnapshot(), new SnapshotVersion("13w39b"));
} } */
@Test
public void testParsing() { public void testParsing() {
assertEquals(MinecraftVersion.extractVersion("CraftBukkit R3.0 (MC: 1.4.3)"), "1.4.3"); assertEquals(MinecraftVersion.extractVersion("CraftBukkit R3.0 (MC: 1.4.3)"), "1.4.3");
assertEquals(MinecraftVersion.extractVersion("CraftBukkit Test Beta 1 (MC: 1.10.01 )"), "1.10.01"); assertEquals(MinecraftVersion.extractVersion("CraftBukkit Test Beta 1 (MC: 1.10.01 )"), "1.10.01");
assertEquals(MinecraftVersion.extractVersion("Hello (MC: 2.3.4 ) "), "2.3.4"); assertEquals(MinecraftVersion.extractVersion("Hello (MC: 2.3.4)"), "2.3.4");
assertEquals(MinecraftVersion.fromServerVersion("git-Cauldron-Reloaded-1.7.10-1.1388.1.0 (MC: 1.7.10)"), new MinecraftVersion(1, 7, 10));
assertEquals(MinecraftVersion.fromServerVersion("git-Bukkit-18fbb24 (MC: 1.8.8)"), new MinecraftVersion(1, 8, 8));
} }
} }

View File

@ -11,7 +11,8 @@ import org.junit.Test;
import com.comphenix.protocol.PacketType.Protocol; import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.PacketType.Sender; import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.injector.netty.NettyProtocolRegistry; import com.comphenix.protocol.compat.netty.independent.NettyProtocolRegistry;
import com.comphenix.protocol.injector.netty.ProtocolRegistry;
public class PacketTypeTest { public class PacketTypeTest {
@ -22,12 +23,12 @@ public class PacketTypeTest {
@Test @Test
public void testFindCurrent() { public void testFindCurrent() {
assertEquals(PacketType.Play.Client.STEER_VEHICLE, PacketType.findCurrent(Protocol.PLAY, Sender.CLIENT, 12)); assertEquals(PacketType.Play.Client.STEER_VEHICLE, PacketType.findCurrent(Protocol.PLAY, Sender.CLIENT, "SteerVehicle"));
} }
@Test @Test
public void ensureAllExist() { public void ensureAllExist() {
NettyProtocolRegistry registry = new NettyProtocolRegistry(); ProtocolRegistry registry = new NettyProtocolRegistry();
Map<PacketType, Class<?>> lookup = registry.getPacketTypeLookup(); Map<PacketType, Class<?>> lookup = registry.getPacketTypeLookup();
for (Entry<PacketType, Class<?>> entry : lookup.entrySet()) { for (Entry<PacketType, Class<?>> entry : lookup.entrySet()) {
PacketType type = entry.getKey(); PacketType type = entry.getKey();