From b59f55e2e5f06c52c42df26f999428c0ae32654b Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 2 Feb 2014 16:50:56 +0100 Subject: [PATCH] Added support for serializing/deserializing WrappedServerPing to JSON. --- ProtocolLib/.classpath | 2 +- .../org.eclipse.core.resources.prefs | 1 + .../protocol/reflect/accessors/Accessors.java | 45 ++++++++++- .../protocol/utility/MinecraftReflection.java | 74 ++++++++++++++++++- .../protocol/wrappers/WrappedServerPing.java | 27 +++++++ 5 files changed, 146 insertions(+), 3 deletions(-) diff --git a/ProtocolLib/.classpath b/ProtocolLib/.classpath index b3408dbb..78235e66 100644 --- a/ProtocolLib/.classpath +++ b/ProtocolLib/.classpath @@ -12,7 +12,7 @@ - + diff --git a/ProtocolLib/.settings/org.eclipse.core.resources.prefs b/ProtocolLib/.settings/org.eclipse.core.resources.prefs index 78478a74..41ec3322 100644 --- a/ProtocolLib/.settings/org.eclipse.core.resources.prefs +++ b/ProtocolLib/.settings/org.eclipse.core.resources.prefs @@ -2,4 +2,5 @@ eclipse.preferences.version=1 encoding//src/main/java=cp1252 encoding//src/main/resources=cp1252 encoding//src/test/java=cp1252 +encoding//src/test/resources=cp1252 encoding/=cp1252 diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/accessors/Accessors.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/accessors/Accessors.java index a0336d58..640f0f63 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/accessors/Accessors.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/accessors/Accessors.java @@ -4,7 +4,6 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.List; - import com.comphenix.protocol.reflect.ExactReflection; import com.comphenix.protocol.reflect.FuzzyReflection; import com.google.common.base.Joiner; @@ -106,6 +105,50 @@ public final class Accessors { field.setAccessible(true); return new DefaultFieldAccessor(field); } + + /** + * Retrieve a field accessor that will cache the content of the field. + *

+ * Note that we don't check if the underlying field has changed after the value has been cached, + * so it's best to use this on final fields. + * @param inner - the accessor. + * @return A cached field accessor. + */ + public static FieldAccessor getCached(final FieldAccessor inner) { + return new FieldAccessor() { + private final Object EMPTY = new Object(); + private volatile Object value = EMPTY; + + @Override + public void set(Object instance, Object value) { + inner.set(instance, value); + update(value); + } + + @Override + public Object get(Object instance) { + Object cache = value; + + if (cache != EMPTY) + return cache; + return update(inner.get(instance)); + } + + /** + * Update the cached value. + * @param value - the value to cache. + * @return The cached value. + */ + private Object update(Object value) { + return this.value = value; + } + + @Override + public Field getField() { + return inner.getField(); + } + }; + } /** * Retrieve a field accessor where the write operation is synchronized on the current field value. 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 569695bb..da5996e4 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java @@ -78,6 +78,7 @@ import com.google.common.base.Joiner; public class MinecraftReflection { public static final ReportType REPORT_CANNOT_FIND_MCPC_REMAPPER = new ReportType("Cannot find MCPC remapper."); public static final ReportType REPORT_CANNOT_LOAD_CPC_REMAPPER = new ReportType("Unable to load MCPC remapper."); + public static final ReportType REPORT_NON_CRAFTBUKKIT_LIBRARY_PACKAGE = new ReportType("Cannot find standard Minecraft library location. Assuming MCPC."); /** * Regular expression that matches a Minecraft object. @@ -101,7 +102,12 @@ public class MinecraftReflection { * The package name of all the classes that belongs to the native code in Minecraft. */ private static String MINECRAFT_PREFIX_PACKAGE = "net.minecraft.server"; - + + /** + * The package with all the library classes. + */ + private static String MINECRAFT_LIBRARY_PACKAGE = "net.minecraft.util"; + /** * Represents a regular expression that will match the version string in a package: * org.bukkit.craftbukkit.v1_6_R2 -> v1_6_R2 @@ -114,6 +120,7 @@ public class MinecraftReflection { // Package private for the purpose of unit testing static CachedPackage minecraftPackage; static CachedPackage craftbukkitPackage; + static CachedPackage libraryPackage; // org.bukkit.craftbukkit private static Constructor craftNMSConstructor; @@ -199,6 +206,9 @@ public class MinecraftReflection { // Libigot patch handleLibigot(); + // Minecraft library package + handleLibraryPackage(); + // Next, do the same for CraftEntity.getHandle() in order to get the correct Minecraft package Class craftEntity = getCraftEntityClass(); Method getHandle = craftEntity.getMethod("getHandle"); @@ -244,7 +254,30 @@ public class MinecraftReflection { throw new IllegalStateException("Could not find Bukkit. Is it running?"); } } + + /** + * Retrieve the Minecraft library package string. + * @return The library package. + */ + private static String getMinecraftLibraryPackage() { + getMinecraftPackage(); + return MINECRAFT_LIBRARY_PACKAGE; + } + private static void handleLibraryPackage() { + try { + MINECRAFT_LIBRARY_PACKAGE = "net.minecraft.util"; + // Try loading Google GSON + getClassSource().loadClass(CachedPackage.combine(MINECRAFT_LIBRARY_PACKAGE, "com.google.gson.Gson")); + + } catch (Exception e) { + // Assume it's MCPC + MINECRAFT_LIBRARY_PACKAGE = ""; + ProtocolLibrary.getErrorReporter().reportWarning(MinecraftReflection.class, + Report.newBuilder(REPORT_NON_CRAFTBUKKIT_LIBRARY_PACKAGE)); + } + } + /** * Retrieve the package version of the underlying CraftBukkit server. * @return The package version, or NULL if not applicable (before 1.4.6). @@ -1552,6 +1585,20 @@ public class MinecraftReflection { } } + /** + * Retrieve the google.gson.Gson class used by Minecraft. + * @return The GSON class. + */ + public static Class getMinecraftGsonClass() { + try { + return getMinecraftLibraryClass("com.google.gson.Gson"); + } catch (RuntimeException e) { + Class match = FuzzyReflection.fromClass(PacketType.Status.Server.OUT_SERVER_INFO.getPacketClass()). + getFieldByType(".*\\.google\\.gson\\.Gson").getType(); + return setMinecraftLibraryClass("com.google.gson.Gson", match); + } + } + /** * Determine if a given method retrieved by ASM is a constructor. * @param name - the name of the method. @@ -1766,6 +1813,31 @@ public class MinecraftReflection { return minecraftPackage.getPackageClass(className); } + /** + * Retrieve the class object of a specific Minecraft library class. + * @param className - the specific library Minecraft class. + * @return Class object. + * @throws RuntimeException If we are unable to find the given class. + */ + public static Class getMinecraftLibraryClass(String className) { + if (libraryPackage == null) + libraryPackage = new CachedPackage(getMinecraftLibraryPackage(), getClassSource()); + return libraryPackage.getPackageClass(className); + } + + /** + * Set the class object for the specific library class. + * @param className - name of the Minecraft library class. + * @param clazz - the new class object. + * @return The provided clazz object. + */ + private static Class setMinecraftLibraryClass(String className, Class clazz) { + if (libraryPackage == null) + libraryPackage = new CachedPackage(getMinecraftLibraryPackage(), getClassSource()); + libraryPackage.setPackageClass(className, clazz); + return clazz; + } + /** * Set the class object for the specific Minecraft class. * @param className - name of the Minecraft class. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedServerPing.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedServerPing.java index a5283a6c..44cbfb13 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedServerPing.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedServerPing.java @@ -20,11 +20,13 @@ import net.minecraft.util.io.netty.buffer.Unpooled; import net.minecraft.util.io.netty.handler.codec.base64.Base64; import net.minecraft.util.io.netty.util.IllegalReferenceCountException; +import com.comphenix.protocol.PacketType; import com.comphenix.protocol.injector.BukkitUnwrapper; import com.comphenix.protocol.reflect.EquivalentConverter; import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; import com.comphenix.protocol.reflect.accessors.FieldAccessor; +import com.comphenix.protocol.reflect.accessors.MethodAccessor; import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftVersion; import com.google.common.base.Charsets; @@ -59,6 +61,14 @@ public class WrappedServerPing extends AbstractWrapper { private static FieldAccessor PLAYERS_MAXIMUM = PLAYERS_INTS[0]; private static FieldAccessor PLAYERS_ONLINE = PLAYERS_INTS[1]; + // Server ping serialization + private static Class GSON_CLASS = MinecraftReflection.getMinecraftGsonClass(); + private static MethodAccessor GSON_TO_JSON = Accessors.getMethodAccessor(GSON_CLASS, "toJson", Object.class); + private static MethodAccessor GSON_FROM_JSON = Accessors.getMethodAccessor(GSON_CLASS, "fromJson", String.class, Class.class); + private static FieldAccessor PING_GSON = Accessors.getCached(Accessors.getFieldAccessor( + PacketType.Status.Server.OUT_SERVER_INFO.getPacketClass(), GSON_CLASS, true + )); + // Server data fields private static Class VERSION_CLASS = MinecraftReflection.getServerPingServerDataClass(); private static ConstructorAccessor VERSION_CONSTRUCTOR = Accessors.getConstructorAccessor(VERSION_CLASS, String.class, int.class); @@ -117,6 +127,15 @@ public class WrappedServerPing extends AbstractWrapper { return new WrappedServerPing(handle); } + /** + * Construct a wrapper server ping from an encoded JSON string. + * @param json - the JSON string. + * @return The wrapped server ping. + */ + public static WrappedServerPing fromJson(String json) { + return fromHandle(GSON_FROM_JSON.invoke(PING_GSON.get(null), json, SERVER_PING)); + } + /** * Retrieve the message of the day. * @return The messge of the day. @@ -329,6 +348,14 @@ public class WrappedServerPing extends AbstractWrapper { return copy; } + /** + * Retrieve the underlying JSON representation of this server ping. + * @return The JSON representation. + */ + public String toJson() { + return (String) GSON_TO_JSON.invoke(PING_GSON.get(null), handle); + } + /** * Represents a compressed favicon. * @author Kristian