From 395d77e721b6dd289d6d9c0ef61033a36d653180 Mon Sep 17 00:00:00 2001 From: Dan Mulloy Date: Sat, 14 Nov 2015 14:31:45 -0500 Subject: [PATCH] Reimplement auto updater, fix legacy packets Also a few minor bug fixes and improvements Fixes #127 --- .../comphenix/protocol/CommandProtocol.java | 109 +++++--- .../com/comphenix/protocol/PacketType.java | 95 ++++++- .../comphenix/protocol/PacketTypeLookup.java | 60 ++++- .../comphenix/protocol/ProtocolConfig.java | 234 +++++++++++++++-- .../comphenix/protocol/ProtocolLibrary.java | 43 ++- .../compat/netty/LegacyProtocolRegistry.java | 103 ++++++++ .../independent/NettyProtocolRegistry.java | 104 ++++++++ .../injector/netty/NettyProtocolRegistry.java | 246 ------------------ .../injector/netty/ProtocolRegistry.java | 125 +++++++++ .../injector/packet/PacketRegistry.java | 15 +- .../com/comphenix/protocol/utility/Util.java | 12 +- .../protocol/wrappers/EnumWrappers.java | 2 + ProtocolLib/src/main/resources/config.yml | 8 + ProtocolLib/src/main/resources/plugin.yml | 2 +- .../protocol/MinecraftVersionTest.java | 16 +- .../comphenix/protocol/PacketTypeTest.java | 7 +- 16 files changed, 846 insertions(+), 335 deletions(-) create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/compat/netty/LegacyProtocolRegistry.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/compat/netty/independent/NettyProtocolRegistry.java delete mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/NettyProtocolRegistry.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/ProtocolRegistry.java diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandProtocol.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandProtocol.java index 555e092e..b73c64a7 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandProtocol.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandProtocol.java @@ -1,4 +1,4 @@ -/* +/** * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * 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 * 02111-1307 USA */ - package com.comphenix.protocol; import java.io.File; @@ -36,7 +35,9 @@ import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.timing.TimedListenerManager; 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. @@ -48,38 +49,56 @@ class CommandProtocol extends CommandBase { * Name of this command. */ public static final String NAME = "protocol"; - + 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); this.plugin = plugin; + this.updater = updater; + this.config = config; } - + @Override protected boolean handleCommand(CommandSender sender, String[] args) { String subCommand = args[0]; // 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); - 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); - else if (subCommand.equalsIgnoreCase("listeners")) - printListeners(sender); - else if (subCommand.equalsIgnoreCase("version")) + } else if (subCommand.equalsIgnoreCase("listeners")) { + printListeners(sender, args); + } else if (subCommand.equalsIgnoreCase("version")) { printVersion(sender); - else if (subCommand.equalsIgnoreCase("dump")) + } else if (subCommand.equalsIgnoreCase("dump")) { dump(sender); - else + } else { return false; + } + 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 - private void printListeners(final CommandSender sender) { + private void printListeners(final CommandSender sender, String[] args) { ProtocolManager manager = ProtocolLibrary.getProtocolManager(); - + sender.sendMessage(ChatColor.GOLD + "Packet listeners:"); for (PacketListener listener : manager.getPacketListeners()) { sender.sendMessage(ChatColor.GOLD + " - " + listener); @@ -91,15 +110,37 @@ class CommandProtocol extends CommandBase { sender.sendMessage(ChatColor.GOLD + " - " + listener); } } + + 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) { TimedListenerManager manager = TimedListenerManager.getInstance(); boolean state = !manager.isTiming(); // toggle - + // Parse the boolean parameter if (args.length == 2) { Boolean parsed = parseBoolean(toQueue(args, 2), "start"); - + if (parsed != null) { state = parsed; } else { @@ -110,7 +151,7 @@ class CommandProtocol extends CommandBase { sender.sendMessage(ChatColor.RED + "Too many parameters."); return; } - + // Now change the state if (state) { if (manager.startTiming()) @@ -126,19 +167,35 @@ class CommandProtocol extends CommandBase { } } } - + private void saveTimings(TimedListenerManager manager) { try { File destination = new File(plugin.getDataFolder(), "Timings - " + System.currentTimeMillis() + ".txt"); TimingReportGenerator generator = new TimingReportGenerator(); - + // Print to a text file generator.saveTo(destination, manager); manager.clear(); + } catch (IOException 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) { 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"); } - public void reloadConfiguration(CommandSender sender) { - plugin.reloadConfig(); - sender.sendMessage(ChatColor.YELLOW + "Reloaded configuration!"); - } - private static SimpleDateFormat FILE_FORMAT; private static SimpleDateFormat TIMESTAMP_FORMAT; @@ -207,10 +259,7 @@ class CommandProtocol extends CommandBase { ProtocolLibrary.getStaticLogger().log(Level.SEVERE, "Failed to create dump:", ex); sender.sendMessage(ChatColor.RED + "Failed to create dump! Check console!"); } finally { - try { - closer.close(); - } catch (IOException ex1) { - } + closer.close(); } } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/PacketType.java b/ProtocolLib/src/main/java/com/comphenix/protocol/PacketType.java index 4e760b08..7491221e 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/PacketType.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/PacketType.java @@ -4,6 +4,7 @@ import java.io.Serializable; import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.Future; @@ -11,6 +12,7 @@ import java.util.concurrent.Future; import org.apache.commons.lang.WordUtils; import org.bukkit.Bukkit; +import com.comphenix.protocol.PacketTypeLookup.ClassLookup; import com.comphenix.protocol.events.ConnectionSide; import com.comphenix.protocol.injector.packet.PacketRegistry; import com.comphenix.protocol.reflect.ObjectEnum; @@ -635,7 +637,9 @@ public class PacketType implements Serializable, Comparable { * @param packetId - the packet ID. * @return The corresponding packet type. * @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) { PacketType type = getLookup().getFromCurrent(protocol, sender, packetId); @@ -645,13 +649,34 @@ public class PacketType implements Serializable, Comparable { "(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. * @param protocol - the protocol. * @param sender - the sender. * @param packetId - the packet ID. * @return TRUE if it exists, FALSE otherwise. + * @deprecated IDs are no longer reliable */ + @Deprecated public static boolean hasCurrent(Protocol protocol, Sender sender, int packetId) { return getLookup().getFromCurrent(protocol, sender, packetId) != null; } @@ -680,7 +705,7 @@ public class PacketType implements Serializable, Comparable { } /** - * 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. *

* The packet will automatically be registered if its missing. * @param protocol - the current protocol. @@ -689,21 +714,59 @@ public class PacketType implements Serializable, Comparable { * @param packetClass - the packet class * @return The corresponding packet type. */ - public static PacketType fromCurrent(Protocol protocol, Sender sender, int packetId, Class packetClass) { - String className = packetClass.getSimpleName(); - for (PacketType type : PacketType.values()) { - for (String name : type.classNames) { - if (className.equals(name)) { - return type; - } - } + public static PacketType fromID(Protocol protocol, Sender sender, int packetId, Class packetClass) { + PacketType type = getLookup().getFromCurrent(protocol, sender, packetId); + + if (type == null) { + type = new PacketType(protocol, sender, packetId, -1, PROTOCOL_VERSION, packetClass.getName()); + 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); - type.dynamic = true; + return type; + } + + /** + * Retrieve a packet type from a protocol, sender, ID, and class for 1.8+ + *

+ * 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 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; } @@ -826,7 +889,7 @@ public class PacketType implements Serializable, Comparable { this.classNames = new String[names.length]; 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 { return currentId; } + public String[] getClassNames() { + return classNames; + } + /** * Retrieve the equivalent packet class. * @return The packet class, or NULL if not found. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/PacketTypeLookup.java b/ProtocolLib/src/main/java/com/comphenix/protocol/PacketTypeLookup.java index 796d81ea..83950bd3 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/PacketTypeLookup.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/PacketTypeLookup.java @@ -2,6 +2,8 @@ package com.comphenix.protocol; import java.util.Collection; import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import com.comphenix.protocol.PacketType.Protocol; import com.comphenix.protocol.PacketType.Sender; @@ -15,7 +17,7 @@ import com.google.common.collect.Multimap; * @author Kristian */ class PacketTypeLookup { - private static class ProtocolSenderLookup { + public static class ProtocolSenderLookup { // Unroll lookup for performance reasons public final IntegerMap HANDSHAKE_CLIENT = IntegerMap.newMap(); public final IntegerMap HANDSHAKE_SERVER = IntegerMap.newMap(); @@ -47,6 +49,39 @@ class PacketTypeLookup { } } } + + public static class ClassLookup { + // Unroll lookup for performance reasons + public final Map HANDSHAKE_CLIENT = new ConcurrentHashMap(); + public final Map HANDSHAKE_SERVER = new ConcurrentHashMap(); + public final Map GAME_CLIENT = new ConcurrentHashMap(); + public final Map GAME_SERVER = new ConcurrentHashMap(); + public final Map STATUS_CLIENT = new ConcurrentHashMap(); + public final Map STATUS_SERVER = new ConcurrentHashMap(); + public final Map LOGIN_CLIENT = new ConcurrentHashMap(); + public final Map LOGIN_SERVER = new ConcurrentHashMap(); + + /** + * 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 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 private final IntegerMap legacyLookup = new IntegerMap(); @@ -54,11 +89,14 @@ class PacketTypeLookup { private final IntegerMap clientLookup = new IntegerMap(); // 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 private final Multimap nameLookup = HashMultimap.create(); - + /** * Add a collection of packet types to the lookup. * @param types - the types to add. @@ -79,9 +117,9 @@ class PacketTypeLookup { } // Skip unknown current packets 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); } return this; @@ -132,8 +170,18 @@ class PacketTypeLookup { * @param sender - the sender. * @param packetId - the packet ID. * @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) { - 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; } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java index 974bd082..9cf6430a 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java @@ -2,21 +2,22 @@ * 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; import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -25,8 +26,10 @@ import org.bukkit.configuration.ConfigurationSection; import org.bukkit.plugin.Plugin; import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; +import com.google.common.base.Charsets; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; +import com.google.common.io.Files; /** * Represents the configuration of ProtocolLib. @@ -34,7 +37,10 @@ import com.google.common.collect.Lists; * @author Kristian */ public class ProtocolConfig { + private static final String LAST_UPDATE_FILE = "lastupdate"; + private static final String SECTION_GLOBAL = "global"; + private static final String SECTION_AUTOUPDATER = "auto updater"; 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 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 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 valuesChanged; // Modifications private int modCount; public ProtocolConfig(Plugin plugin) { + this(plugin, plugin.getConfig()); + } + + public ProtocolConfig(Plugin plugin, Configuration config) { this.plugin = plugin; reloadConfig(); } @@ -68,24 +91,118 @@ public class ProtocolConfig { public void reloadConfig() { // Reset configChanged = false; + valuesChanged = false; modCount++; this.config = plugin.getConfig(); - if (config != null) { - config.options().copyDefaults(true); - global = config.getConfigurationSection(SECTION_GLOBAL); + this.lastUpdateTime = loadLastUpdate(); + loadSections(!loadingSections); + } + + /** + * 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) { configChanged = true; section.set(path, value); } @SuppressWarnings("unchecked") - private T getGlobalValue(String name, T def) { + private T getGlobalValue(String path, T def) { try { - return (T) global.get(name, def); + return (T) global.get(path, def); + } catch (Throwable ex) { + return def; + } + } + + @SuppressWarnings("unchecked") + private T getUpdaterValue(String path, T def) { + try { + return (T) updater.get(path, def); } catch (Throwable ex) { return def; } @@ -118,6 +235,44 @@ public class ProtocolConfig { 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. *

@@ -158,6 +313,31 @@ public class ProtocolConfig { 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. + *

+ * 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. * @@ -221,13 +401,34 @@ public class ProtocolConfig { 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. + *

+ * 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. * * @return Script engine to use. */ 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. * - * @param hook Injection method + * @return Injection method. */ public void setInjectionMethod(PlayerInjectHooks hook) { setConfig(global, INJECTION_METHOD, hook.name()); @@ -291,10 +492,13 @@ public class ProtocolConfig { * Save the current configuration file. */ public void saveAll() { + if (valuesChanged) + saveLastUpdate(lastUpdateTime); if (configChanged) plugin.saveConfig(); // And we're done + valuesChanged = false; configChanged = false; } -} +} \ No newline at end of file diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java index c7f8d2dd..4198ace5 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java @@ -50,6 +50,8 @@ import com.comphenix.protocol.injector.PacketFilterManager; import com.comphenix.protocol.injector.PacketFilterManager.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.EnhancerFactory; 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"; + // Update information + static final String BUKKIT_DEV_SLUG = "protocollib"; + static final int BUKKIT_DEV_ID = 45564; + // Different commands private enum ProtocolCommand { FILTER, @@ -142,6 +148,10 @@ public class ProtocolLibrary extends JavaPlugin { // Settings/options private int configExpectedMod = -1; + // Updater + private Updater updater; + private static boolean UPDATES_DISABLED; + // Logger private static Logger logger; private Handler redirectHandler; @@ -205,6 +215,9 @@ public class ProtocolLibrary extends JavaPlugin { // Handle unexpected Minecraft versions 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); protocolManager = PacketFilterManager.newBuilder() .classLoader(getClassLoader()) @@ -254,7 +267,7 @@ public class ProtocolLibrary extends JavaPlugin { try { switch (command) { case PROTOCOL: - commandProtocol = new CommandProtocol(reporter, this); + commandProtocol = new CommandProtocol(reporter, this, updater, config); break; case FILTER: commandFilter = new CommandFilter(reporter, this, config); @@ -559,6 +572,11 @@ public class ProtocolLibrary extends JavaPlugin { // House keeping updateConfiguration(); + + // Check for updates too + if (!UPDATES_DISABLED && (tickCounter % 20) == 0) { + checkUpdates(); + } } }, ASYNC_MANAGER_DELAY, ASYNC_MANAGER_DELAY); } 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 public void onDisable() { if (skipDisable) { diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/compat/netty/LegacyProtocolRegistry.java b/ProtocolLib/src/main/java/com/comphenix/protocol/compat/netty/LegacyProtocolRegistry.java new file mode 100644 index 00000000..f78ff44b --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/compat/netty/LegacyProtocolRegistry.java @@ -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>> serverMaps = Lists.newArrayList(); + List>> clientMaps = Lists.newArrayList(); + StructureModifier modifier = null; + + // Result + Register result = new Register(); + + for (Object protocol : protocols) { + if (modifier == null) + modifier = new StructureModifier(protocol.getClass().getSuperclass(), false); + StructureModifier>> 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> 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>> 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> lookup, Protocol protocol, Sender sender) { + for (Entry> 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 + } + } + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/compat/netty/independent/NettyProtocolRegistry.java b/ProtocolLib/src/main/java/com/comphenix/protocol/compat/netty/independent/NettyProtocolRegistry.java new file mode 100644 index 00000000..fa80d470 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/compat/netty/independent/NettyProtocolRegistry.java @@ -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>> serverMaps = Maps.newLinkedHashMap(); + Map>> clientMaps = Maps.newLinkedHashMap(); + + Register result = new Register(); + StructureModifier modifier = null; + + // Iterate through the protocols + for (Object protocol : protocols) { + if (modifier == null) + modifier = new StructureModifier(protocol.getClass().getSuperclass(), false); + StructureModifier>>> maps = modifier.withTarget(protocol).withType(Map.class); + for (Entry>> 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> map : serverMaps.values()) { + result.containers.add(new MapContainer(map)); + } + + for (Map> 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> lookup, Protocol protocol, Sender sender) { + for (Entry> 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 + } + } + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/NettyProtocolRegistry.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/NettyProtocolRegistry.java deleted file mode 100644 index 9823fd9b..00000000 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/NettyProtocolRegistry.java +++ /dev/null @@ -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> typeToClass = HashBiMap.create(); - public volatile Set serverPackets = Sets.newHashSet(); - public volatile Set clientPackets = Sets.newHashSet(); - public List 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> getPacketTypeLookup() { - return Collections.unmodifiableMap(register.typeToClass); - } - - /** - * Retrieve an immutable view of the class to packet type lookup. - * @return The packet type lookup. - */ - public Map, PacketType> getPacketClassLookup() { - return Collections.unmodifiableMap(register.typeToClass.inverse()); - } - - /** - * Retrieve every known client packet, from every protocol. - * @return Every client packet. - */ - public Set getClientPackets() { - return Collections.unmodifiableSet(register.clientPackets); - } - - /** - * Retrieve every known server packet, from every protocol. - * @return Every server packet. - */ - public Set getServerPackets() { - return Collections.unmodifiableSet(register.serverPackets); - } - - /** - * Ensure that our local register is up-to-date with Minecraft. - *

- * 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>> serverMaps = Maps.newLinkedHashMap(); - Map>> clientMaps = Maps.newLinkedHashMap(); - - Register result = new Register(); - StructureModifier modifier = null; - - // Iterate through the protocols - for (Object protocol : protocols) { - if (modifier == null) - modifier = new StructureModifier(protocol.getClass().getSuperclass(), false); - StructureModifier>>> maps = modifier.withTarget(protocol).withType(Map.class); - for (Entry>> 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> map : serverMaps.values()) { - result.containers.add(new MapContainer(map)); - } - - for (Map> 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>> 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>> serverMaps = Lists.newArrayList(); - List>> clientMaps = Lists.newArrayList(); - StructureModifier modifier = null; - - // Result - Register result = new Register(); - - for (Object protocol : protocols) { - if (modifier == null) - modifier = new StructureModifier(protocol.getClass().getSuperclass(), false); - StructureModifier>> 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> 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>> 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> lookup, Protocol protocol, Sender sender) { - for (Entry> 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>> maps) { - int count = 0; - - for (Map> map : maps) - count += map.size(); - return count; - } -} \ No newline at end of file diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/ProtocolRegistry.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/ProtocolRegistry.java new file mode 100644 index 00000000..278cb6d1 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/ProtocolRegistry.java @@ -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> typeToClass = HashBiMap.create(); + public volatile Set serverPackets = Sets.newHashSet(); + public volatile Set clientPackets = Sets.newHashSet(); + public List 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> getPacketTypeLookup() { + return Collections.unmodifiableMap(register.typeToClass); + } + + /** + * Retrieve an immutable view of the class to packet type lookup. + * @return The packet type lookup. + */ + public Map, PacketType> getPacketClassLookup() { + return Collections.unmodifiableMap(register.typeToClass.inverse()); + } + + /** + * Retrieve every known client packet, from every protocol. + * @return Every client packet. + */ + public Set getClientPackets() { + return Collections.unmodifiableSet(register.clientPackets); + } + + /** + * Retrieve every known server packet, from every protocol. + * @return Every server packet. + */ + public Set getServerPackets() { + return Collections.unmodifiableSet(register.serverPackets); + } + + /** + * Ensure that our local register is up-to-date with Minecraft. + *

+ * 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> 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>> maps) { + int count = 0; + + for (Map> map : maps) + count += map.size(); + return count; + } +} \ No newline at end of file diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketRegistry.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketRegistry.java index 75bcc0f2..9a30d7bd 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketRegistry.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketRegistry.java @@ -25,12 +25,15 @@ import java.util.Set; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.PacketType.Sender; 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.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.reflect.FieldAccessException; import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.utility.MinecraftVersion; import com.comphenix.protocol.wrappers.TroveWrapper.CannotFindTroveNoEntryValue; import com.google.common.base.Function; import com.google.common.collect.Maps; @@ -49,7 +52,7 @@ public class PacketRegistry { // Two different packet registry private static volatile LegacyPacketRegistry LEGACY; - private static volatile NettyProtocolRegistry NETTY; + private static volatile ProtocolRegistry NETTY; // Cached for legacy private static volatile Set NETTY_SERVER_PACKETS; @@ -77,13 +80,17 @@ public class PacketRegistry { // Check for netty if (MinecraftReflection.isUsingNetty()) { if (NETTY == null) { - NETTY = new NettyProtocolRegistry(); + if (MinecraftVersion.getCurrentVersion().isAtLeast(MinecraftVersion.BOUNTIFUL_UPDATE)) { + NETTY = new NettyProtocolRegistry(); + } else { + NETTY = new LegacyProtocolRegistry(); + } } } else { initializeLegacy(); } } - + /** * Determine if the given packet type is supported on the current server. * @param type - the type to check. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/Util.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/Util.java index 19b09bd1..9cf19c10 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/Util.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/Util.java @@ -36,20 +36,12 @@ import com.comphenix.protocol.reflect.accessors.MethodAccessor; public class Util { private static MethodAccessor getOnlinePlayers; private static boolean reflectionRequired; - private static boolean isUsingSpigot; static { try { Method method = Bukkit.class.getMethod("getOnlinePlayers"); getOnlinePlayers = Accessors.getMethodAccessor(method); reflectionRequired = !method.getReturnType().isAssignableFrom(Collection.class); - - try { - Class.forName("org.bukkit.entity.Player.Spigot"); - isUsingSpigot = true; - } catch (ClassNotFoundException ex) { - isUsingSpigot = false; - } } catch (Throwable 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 - * 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. */ public static boolean isUsingSpigot() { - return isUsingSpigot; + return Bukkit.getServer().getVersion().contains("Spigot"); } } \ No newline at end of file diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java index 2c8d7ed0..610608dc 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java @@ -295,6 +295,8 @@ public abstract class EnumWrappers { if (INITIALIZED) return; + INITIALIZED = true; + PROTOCOL_CLASS = getEnum(PacketType.Handshake.Client.SET_PROTOCOL.getPacketClass(), 0); CLIENT_COMMAND_CLASS = getEnum(PacketType.Play.Client.CLIENT_COMMAND.getPacketClass(), 0); CHAT_VISIBILITY_CLASS = getEnum(PacketType.Play.Client.SETTINGS.getPacketClass(), 0); diff --git a/ProtocolLib/src/main/resources/config.yml b/ProtocolLib/src/main/resources/config.yml index 3464c816..04224b2a 100644 --- a/ProtocolLib/src/main/resources/config.yml +++ b/ProtocolLib/src/main/resources/config.yml @@ -1,4 +1,12 @@ 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 # Automatically compile structure modifiers diff --git a/ProtocolLib/src/main/resources/plugin.yml b/ProtocolLib/src/main/resources/plugin.yml index fa35a50e..87c10a68 100644 --- a/ProtocolLib/src/main/resources/plugin.yml +++ b/ProtocolLib/src/main/resources/plugin.yml @@ -10,7 +10,7 @@ database: false commands: protocol: description: Performs administrative tasks regarding ProtocolLib. - usage: / config|timings|listeners|version|dump + usage: / config|check|update|timings|listeners|version|dump permission: protocol.admin permission-message: You don't have packet: diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/MinecraftVersionTest.java b/ProtocolLib/src/test/java/com/comphenix/protocol/MinecraftVersionTest.java index 75d2efc0..b893ac43 100644 --- a/ProtocolLib/src/test/java/com/comphenix/protocol/MinecraftVersionTest.java +++ b/ProtocolLib/src/test/java/com/comphenix/protocol/MinecraftVersionTest.java @@ -24,7 +24,6 @@ import static org.junit.Assert.assertTrue; import org.junit.Test; import com.comphenix.protocol.utility.MinecraftVersion; -import com.comphenix.protocol.utility.SnapshotVersion; public class MinecraftVersionTest { @Test @@ -35,20 +34,27 @@ public class MinecraftVersionTest { MinecraftVersion lower = new MinecraftVersion(1, 0, 0); MinecraftVersion highest = new MinecraftVersion(1, 4, 5); + MinecraftVersion atLeast = new MinecraftVersion(1, 8, 8); + // Make sure this is valid assertTrue(lower.compareTo(within) < 0 && within.compareTo(highest) < 0); assertFalse(outside.compareTo(within) < 0 && outside.compareTo(highest) < 0); + assertTrue(atLeast.isAtLeast(MinecraftVersion.BOUNTIFUL_UPDATE)); } - @Test + /* @Test public void testSnapshotVersion() { MinecraftVersion version = MinecraftVersion.fromServerVersion("git-Spigot-1119 (MC: 13w39b)"); assertEquals(version.getSnapshot(), new SnapshotVersion("13w39b")); - } - + } */ + + @Test public void testParsing() { 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("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)); } } diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/PacketTypeTest.java b/ProtocolLib/src/test/java/com/comphenix/protocol/PacketTypeTest.java index 33803e04..0174cd65 100644 --- a/ProtocolLib/src/test/java/com/comphenix/protocol/PacketTypeTest.java +++ b/ProtocolLib/src/test/java/com/comphenix/protocol/PacketTypeTest.java @@ -11,7 +11,8 @@ import org.junit.Test; import com.comphenix.protocol.PacketType.Protocol; 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 { @@ -22,12 +23,12 @@ public class PacketTypeTest { @Test 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 public void ensureAllExist() { - NettyProtocolRegistry registry = new NettyProtocolRegistry(); + ProtocolRegistry registry = new NettyProtocolRegistry(); Map> lookup = registry.getPacketTypeLookup(); for (Entry> entry : lookup.entrySet()) { PacketType type = entry.getKey();