From 144723af1852bd8e1e1a887043fca3a52afdff7a Mon Sep 17 00:00:00 2001 From: Dan Mulloy Date: Sat, 6 Aug 2016 14:40:49 -0400 Subject: [PATCH] Ensure we're always using leniency with components Fixes #252 --- .../protocol/wrappers/ComponentConverter.java | 1 - .../protocol/wrappers/ComponentParser.java | 59 +++++++++++++++++++ .../wrappers/WrappedChatComponent.java | 55 ++++++++++------- 3 files changed, 92 insertions(+), 23 deletions(-) create mode 100644 modules/API/src/main/java/com/comphenix/protocol/wrappers/ComponentParser.java diff --git a/modules/API/src/main/java/com/comphenix/protocol/wrappers/ComponentConverter.java b/modules/API/src/main/java/com/comphenix/protocol/wrappers/ComponentConverter.java index 1af399b8..d2a132c6 100644 --- a/modules/API/src/main/java/com/comphenix/protocol/wrappers/ComponentConverter.java +++ b/modules/API/src/main/java/com/comphenix/protocol/wrappers/ComponentConverter.java @@ -25,7 +25,6 @@ import net.md_5.bungee.chat.ComponentSerializer; * Note: The BungeeCord Chat API is not included in CraftBukkit. * @author dmulloy2 */ - public final class ComponentConverter { private ComponentConverter() { diff --git a/modules/API/src/main/java/com/comphenix/protocol/wrappers/ComponentParser.java b/modules/API/src/main/java/com/comphenix/protocol/wrappers/ComponentParser.java new file mode 100644 index 00000000..7f3b0573 --- /dev/null +++ b/modules/API/src/main/java/com/comphenix/protocol/wrappers/ComponentParser.java @@ -0,0 +1,59 @@ +/** + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2016 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.wrappers; + +import java.io.IOException; +import java.io.StringReader; +import java.lang.reflect.Method; + +/** + * Handles component parsing in 1.8 + * @author dmulloy2 + */ +public class ComponentParser { + + private ComponentParser() { + } + + public static Object deserialize(Object gson, Class component, StringReader str) { + try { + com.google.gson.stream.JsonReader reader = new com.google.gson.stream.JsonReader(str); + reader.setLenient(true); + return ((com.google.gson.Gson) gson).getAdapter(component).read(reader); + } catch (IOException ex) { + throw new RuntimeException("Failed to read JSON", ex); + } catch (LinkageError er) { + return deserializeLegacy(gson, component, str); + } + } + + // Should only be needed on 1.8. + private static Object deserializeLegacy(Object gson, Class component, StringReader str) { + try { + Class readerClass = Class.forName("org.bukkit.craftbukkit.libs.com.google.gson.stream.JsonReader"); + Object reader = readerClass.getConstructor(StringReader.class).newInstance(str); + Method setLenient = readerClass.getMethod("setLenienent", boolean.class); + setLenient.invoke(reader, true); + Method getAdapter = gson.getClass().getMethod("getAdapter", Class.class); + Object adapter = getAdapter.invoke(gson, component); + Method read = adapter.getClass().getMethod("read", readerClass); + return read.invoke(adapter, reader); + } catch (ReflectiveOperationException ex) { + throw new RuntimeException("Failed to read JSON", ex); + } + } +} \ No newline at end of file diff --git a/modules/API/src/main/java/com/comphenix/protocol/wrappers/WrappedChatComponent.java b/modules/API/src/main/java/com/comphenix/protocol/wrappers/WrappedChatComponent.java index f542a808..172fed34 100644 --- a/modules/API/src/main/java/com/comphenix/protocol/wrappers/WrappedChatComponent.java +++ b/modules/API/src/main/java/com/comphenix/protocol/wrappers/WrappedChatComponent.java @@ -1,10 +1,10 @@ package com.comphenix.protocol.wrappers; -import java.lang.reflect.Method; -import java.util.List; +import java.io.StringReader; import org.bukkit.ChatColor; +import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; @@ -19,18 +19,35 @@ import com.google.common.base.Preconditions; public class WrappedChatComponent extends AbstractWrapper { private static final Class SERIALIZER = MinecraftReflection.getChatSerializerClass(); private static final Class COMPONENT = MinecraftReflection.getIChatBaseComponentClass(); + private static final Class GSON_CLASS = MinecraftReflection.getMinecraftGsonClass(); + + private static Object GSON = null; + private static MethodAccessor DESERIALIZE = null; + private static MethodAccessor SERIALIZE_COMPONENT = null; - private static MethodAccessor DESERIALIZE_COMPONENT = null; private static MethodAccessor CONSTRUCT_COMPONENT = null; private static ConstructorAccessor CONSTRUCT_TEXT_COMPONENT = null; - + static { - FuzzyReflection fuzzy = FuzzyReflection.fromClass(SERIALIZER); + FuzzyReflection fuzzy = FuzzyReflection.fromClass(SERIALIZER, true); // Retrieve the correct methods SERIALIZE_COMPONENT = Accessors.getMethodAccessor(fuzzy.getMethodByParameters("serialize", /* a */ String.class, new Class[] { COMPONENT })); - DESERIALIZE_COMPONENT = findDeserialize(fuzzy); + + try { + GSON = FieldUtils.readStaticField(fuzzy.getFieldByType("gson", GSON_CLASS), true); + } catch (IllegalAccessException ex) { + throw new RuntimeException("Failed to obtain GSON field", ex); + } + + try { + DESERIALIZE = Accessors.getMethodAccessor(FuzzyReflection.fromClass(MinecraftReflection.getMinecraftClass("ChatDeserializer"), true) + .getMethodByParameters("deserialize", Object.class, new Class[] { GSON_CLASS, String.class, Class.class, boolean.class })); + } catch (IllegalArgumentException ex) { + // We'll handle it in the ComponentParser + DESERIALIZE = null; + } // Get a component from a standard Minecraft message CONSTRUCT_COMPONENT = Accessors.getMethodAccessor(MinecraftReflection.getCraftChatMessage(), "fromString", String.class); @@ -39,23 +56,17 @@ public class WrappedChatComponent extends AbstractWrapper { CONSTRUCT_TEXT_COMPONENT = Accessors.getConstructorAccessor(MinecraftReflection.getChatComponentTextClass(), String.class); } - private static MethodAccessor findDeserialize(FuzzyReflection fuzzy) { - List methods = fuzzy.getMethodListByParameters(COMPONENT, new Class[] { String.class }); - if (methods.isEmpty()) { - throw new IllegalArgumentException("Unable to find deserialize method in " + fuzzy.getSource().getName()); + private static Object deserialize(String json) { + // Should be non-null on 1.9 and up + if (DESERIALIZE != null) { + return DESERIALIZE.invoke(null, GSON, json, COMPONENT, true); } - // Try to find b, we want leniency - for (Method method : methods) { - if (method.getName().equals("b")) { - return Accessors.getMethodAccessor(method); - } - } - - // Oh well - return Accessors.getMethodAccessor(methods.get(0)); + // Mock leniency behavior in 1.8 + StringReader str = new StringReader(json); + return ComponentParser.deserialize(GSON, COMPONENT, str); } - + private transient String cache; private WrappedChatComponent(Object handle, String cache) { @@ -79,7 +90,7 @@ public class WrappedChatComponent extends AbstractWrapper { * @return The chat component wrapper. */ public static WrappedChatComponent fromJson(String json) { - return new WrappedChatComponent(DESERIALIZE_COMPONENT.invoke(null, json), json); + return new WrappedChatComponent(deserialize(json), json); } /** @@ -127,7 +138,7 @@ public class WrappedChatComponent extends AbstractWrapper { * @param obj - the JSON that represents the new component. */ public void setJson(String obj) { - this.handle = DESERIALIZE_COMPONENT.invoke(null, obj); + this.handle = deserialize(obj); this.cache = obj; }