From 7ca3e916a575779a35b443b95bd04b403139cd43 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 9 Dec 2012 04:23:26 +0100 Subject: [PATCH] Add a warning message when ProtocolLib is started on untested version. May decide to simply stop ProtocolLib unless this feature is specifically disabled. --- .../comphenix/protocol/MinecraftVersion.java | 153 ++++++++++++++++++ .../comphenix/protocol/ProtocolConfig.java | 22 +++ .../comphenix/protocol/ProtocolLibrary.java | 47 ++++-- .../protocol/utility/MinecraftReflection.java | 10 +- .../protocol/MinecraftVersionTest.java | 27 ++++ 5 files changed, 239 insertions(+), 20 deletions(-) create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/MinecraftVersion.java create mode 100644 ProtocolLib/src/test/java/com/comphenix/protocol/MinecraftVersionTest.java diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/MinecraftVersion.java b/ProtocolLib/src/main/java/com/comphenix/protocol/MinecraftVersion.java new file mode 100644 index 00000000..78ff9e0c --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/MinecraftVersion.java @@ -0,0 +1,153 @@ +package com.comphenix.protocol; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.bukkit.Server; + +import com.google.common.base.Objects; +import com.google.common.collect.ComparisonChain; + +/** + * Determine the current Minecraft version. + * + * @author Kristian + */ +class MinecraftVersion implements Comparable { + /** + * Regular expression used to parse version strings. + */ + private static final String VERSION_PATTERN = ".*\\(MC:\\s*((?:\\d+\\.)*\\d)\\s*\\)"; + + private final int major; + private final int minor; + private final int build; + + /** + * Determine the current Minecraft version. + * @param server - the Bukkit server that will be used to examine the MC version. + */ + public MinecraftVersion(Server server) { + this(extractVersion(server.getVersion())); + } + + /** + * Construct a version object from the format major.minor.build. + * @param versionOnly - the version in text form. + */ + public MinecraftVersion(String versionOnly) { + int[] numbers = parseVersion(versionOnly); + + this.major = numbers[0]; + this.minor = numbers[1]; + this.build = numbers[2]; + } + + /** + * Construct a version object directly. + * @param major - major version number. + * @param minor - minor version number. + * @param build - build version number. + */ + public MinecraftVersion(int major, int minor, int build) { + this.major = major; + this.minor = minor; + this.build = build; + } + + private int[] parseVersion(String version) { + String[] elements = version.split("\\."); + int[] numbers = new int[3]; + + // Make sure it's even a valid version + if (elements.length < 1) + throw new IllegalStateException("Corrupt MC version: " + version); + + // The String 1 or 1.2 is interpreted as 1.0.0 and 1.2.0 respectively. + for (int i = 0; i < Math.min(numbers.length, elements.length); i++) + numbers[i] = Integer.parseInt(elements[i].trim()); + return numbers; + } + + /** + * Major version number + * @return Current major version number. + */ + public int getMajor() { + return major; + } + + /** + * Minor version number + * @return Current minor version number. + */ + public int getMinor() { + return minor; + } + + /** + * Build version number + * @return Current build version number. + */ + public int getBuild() { + return build; + } + + @Override + public int compareTo(MinecraftVersion o) { + if (o == null) + return 1; + + return ComparisonChain.start(). + compare(major, o.major). + compare(minor, o.minor). + compare(build, o.build). + result(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) + return false; + if (obj == this) + return true; + + if (obj instanceof MinecraftVersion) { + MinecraftVersion other = (MinecraftVersion) obj; + + return major == other.major && + minor == other.minor && + build == other.build; + } + + return false; + } + + @Override + public int hashCode() { + return Objects.hashCode(major, minor, build); + } + + @Override + public String toString() { + // Convert to a String that we can parse back again + return String.format("(MC: %s.%s.%s)", major, minor, build); + } + + /** + * Extract the Minecraft version from CraftBukkit itself. + * @param server - the server object representing CraftBukkit. + * @return The underlying MC version. + * @throws IllegalStateException If we could not parse the version string. + */ + public static String extractVersion(String text) { + Pattern versionPattern = Pattern.compile(VERSION_PATTERN); + Matcher version = versionPattern.matcher(text); + + if (version.matches() && version.group(1) != null) { + return version.group(1); + } else { + throw new IllegalStateException("Cannot parse version String '" + text + "'"); + } + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java index 7e771e0b..f135fa97 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java @@ -18,6 +18,8 @@ class ProtocolConfig { private static final String METRICS_ENABLED = "metrics"; + private static final String IGNORE_VERSION_CHECK = "ignore version check"; + private static final String BACKGROUND_COMPILER_ENABLED = "background compiler"; private static final String UPDATER_NOTIFY = "notify"; @@ -148,6 +150,26 @@ class ProtocolConfig { public long getAutoLastTime() { return updater.getLong(UPDATER_LAST_TIME, 0); } + + /** + * The version of Minecraft to ignore the built-in safety feature. + * @return The version to ignore ProtocolLib's satefy. + */ + public String getIgnoreVersionCheck() { + return global.getString(IGNORE_VERSION_CHECK, ""); + } + + /** + * Sets under which version of Minecraft the version safety feature will be ignored. + *

+ * This is useful if a server operator has tested and verified that a version of ProtocolLib works, + * but doesn't want or can't upgrade to a newer version. + * + * @param ignoreVersion - the version of Minecraft where the satefy will be disabled. + */ + public void setIgnoreVersionCheck(String ignoreVersion) { + global.set(IGNORE_VERSION_CHECK, ignoreVersion); + } /** * Retrieve whether or not metrics is enabled. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java index e3f8e09d..e9c887c4 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java @@ -43,6 +43,15 @@ import com.comphenix.protocol.reflect.compiler.BackgroundCompiler; * @author Kristian */ public class ProtocolLibrary extends JavaPlugin { + /** + * The minimum version ProtocolLib has been tested with. + */ + private static final String MINIMUM_MINECRAFT_VERSION = "1.0.0"; + + /** + * The maximum version ProtocolLib has been tested with, + */ + private static final String MAXIMUM_MINECRAFT_VERSION = "1.4.5"; /** * The number of milliseconds per second. @@ -188,13 +197,13 @@ public class ProtocolLibrary extends JavaPlugin { logger.info("Structure compiler thread has been disabled."); } + // Handle unexpected Minecraft versions + verifyMinecraftVersion(); + // Set up command handlers registerCommand(CommandProtocol.NAME, commandProtocol); registerCommand(CommandPacket.NAME, commandPacket); - // Notify server managers of incompatible plugins - checkForIncompatibility(manager); - // Player login and logout events protocolManager.registerEvents(manager, this); @@ -220,6 +229,26 @@ public class ProtocolLibrary extends JavaPlugin { } } + // Used to check Minecraft version + private void verifyMinecraftVersion() { + try { + MinecraftVersion minimum = new MinecraftVersion(MINIMUM_MINECRAFT_VERSION); + MinecraftVersion maximum = new MinecraftVersion(MAXIMUM_MINECRAFT_VERSION); + MinecraftVersion current = new MinecraftVersion(getServer()); + + // Skip certain versions + if (!config.getIgnoreVersionCheck().equals(current.toString())) { + // We'll just warn the user for now + if (current.compareTo(minimum) < 0) + reporter.reportWarning(this, "Version " + current + " is lower than the minimum " + minimum); + if (current.compareTo(maximum) > 0) + reporter.reportWarning(this, "Version " + current + " has not yet been tested! Proceed with caution."); + } + } catch (Exception e) { + reporter.reportWarning(this, "Unable to retrieve current Minecraft version.", e); + } + } + private void registerCommand(String name, CommandExecutor executor) { try { if (executor == null) @@ -296,18 +325,6 @@ public class ProtocolLibrary extends JavaPlugin { } } - private void checkForIncompatibility(PluginManager manager) { - // Plugin authors: Notify me to remove these - String[] incompatiblePlugins = {}; - - for (String plugin : incompatiblePlugins) { - if (manager.getPlugin(plugin) != null) { - // Check for versions, ect. - reporter.reportWarning(this, "Detected incompatible plugin: " + plugin); - } - } - } - @Override public void onDisable() { // Disable compiler diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java index af3d5e3d..ded29674 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java @@ -26,8 +26,8 @@ public class MinecraftReflection { /** * The package name of all the classes that belongs to the native code in Minecraft. */ - private static String MINECRAFT_SERVER_PACKAGE = "net.minecraft.server"; - + private static String MINECRAFT_PREFIX_PACKAGE = "net.minecraft.server"; + private static String MINECRAFT_FULL_PACKAGE = null; private static String CRAFTBUKKIT_PACKAGE = null; @@ -74,7 +74,7 @@ public class MinecraftReflection { } } else { - throw new IllegalStateException("Cannot find Bukkit. Is it running?"); + throw new IllegalStateException("Could not find Bukkit. Is it running?"); } } @@ -117,7 +117,7 @@ public class MinecraftReflection { throw new IllegalArgumentException("Cannot determine the type of a null object."); // Doesn't matter if we don't check for the version here - return obj.getClass().getName().startsWith(MINECRAFT_SERVER_PACKAGE); + return obj.getClass().getName().startsWith(MINECRAFT_PREFIX_PACKAGE); } /** @@ -131,7 +131,7 @@ public class MinecraftReflection { throw new IllegalArgumentException("Cannot determine the type of a null object."); String javaName = obj.getClass().getName(); - return javaName.startsWith(MINECRAFT_SERVER_PACKAGE) && javaName.endsWith(className); + return javaName.startsWith(MINECRAFT_PREFIX_PACKAGE) && javaName.endsWith(className); } /** diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/MinecraftVersionTest.java b/ProtocolLib/src/test/java/com/comphenix/protocol/MinecraftVersionTest.java new file mode 100644 index 00000000..b69c29fd --- /dev/null +++ b/ProtocolLib/src/test/java/com/comphenix/protocol/MinecraftVersionTest.java @@ -0,0 +1,27 @@ +package com.comphenix.protocol; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class MinecraftVersionTest { + + @Test + public void testComparision() { + MinecraftVersion within = new MinecraftVersion(1, 2, 5); + MinecraftVersion outside = new MinecraftVersion(1, 7, 0); + + MinecraftVersion lower = new MinecraftVersion(1, 0, 0); + MinecraftVersion highest = new MinecraftVersion(1, 4, 5); + + // Make sure this is valid + assertTrue(lower.compareTo(within) < 0 && within.compareTo(highest) < 0); + assertFalse(outside.compareTo(within) < 0 && outside.compareTo(highest) < 0); + } + + 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"); + } +}