Added the ability to read and modify server-side chat messages.
This introduces the new WrappedChatComponent class, which can be accessed through PacketContainer.getChatComponents().
Dieser Commit ist enthalten in:
Ursprung
1aaf272878
Commit
154d73ae51
@ -66,6 +66,7 @@ import com.comphenix.protocol.utility.StreamSerializer;
|
||||
import com.comphenix.protocol.wrappers.BukkitConverters;
|
||||
import com.comphenix.protocol.wrappers.ChunkPosition;
|
||||
import com.comphenix.protocol.wrappers.WrappedAttribute;
|
||||
import com.comphenix.protocol.wrappers.WrappedChatComponent;
|
||||
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
|
||||
import com.comphenix.protocol.wrappers.WrappedGameProfile;
|
||||
import com.comphenix.protocol.wrappers.WrappedWatchableObject;
|
||||
@ -478,7 +479,7 @@ public class PacketContainer implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for game profiles.
|
||||
* Retrieves a read/write structure for game profiles in Minecraft 1.7.2.
|
||||
* <p>
|
||||
* This modifier will automatically marshall between WrappedGameProfile and the
|
||||
* internal Minecraft GameProfile.
|
||||
@ -490,6 +491,19 @@ public class PacketContainer implements Serializable {
|
||||
GameProfile.class, BukkitConverters.getWrappedGameProfileConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for chat components in Minecraft 1.7.2.
|
||||
* <p>
|
||||
* This modifier will automatically marshall between WrappedChatComponent and the
|
||||
* internal Minecraft GameProfile.
|
||||
* @return A modifier for GameProfile fields.
|
||||
*/
|
||||
public StructureModifier<WrappedChatComponent> getChatComponents() {
|
||||
// Convert to and from the Bukkit wrapper
|
||||
return structureModifier.<WrappedChatComponent>withType(
|
||||
MinecraftReflection.getIChatBaseComponent(), BukkitConverters.getWrappedChatComponentConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the ID of this packet.
|
||||
* <p>
|
||||
|
@ -6,6 +6,7 @@ import java.util.List;
|
||||
|
||||
import com.comphenix.protocol.reflect.compiler.EmptyClassVisitor;
|
||||
import com.comphenix.protocol.reflect.compiler.EmptyMethodVisitor;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import net.sf.cglib.asm.ClassReader;
|
||||
@ -28,10 +29,19 @@ public class ClassAnalyser {
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
public String getOwnerClass() {
|
||||
public String getOwnerName() {
|
||||
return ownerClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the associated owner class.
|
||||
* @return The owner class.
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public Class<?> getOwnerClass() throws ClassNotFoundException {
|
||||
return AsmMethod.class.getClassLoader().loadClass(getOwnerName().replace('/', '.'));
|
||||
}
|
||||
|
||||
public String getMethodName() {
|
||||
return methodName;
|
||||
}
|
||||
@ -57,7 +67,18 @@ public class ClassAnalyser {
|
||||
* @throws IOException Cannot access the parent class.
|
||||
*/
|
||||
public List<AsmMethod> getMethodCalls(Method method) throws IOException {
|
||||
final ClassReader reader = new ClassReader(method.getDeclaringClass().getCanonicalName());
|
||||
return getMethodCalls(method.getDeclaringClass(), method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every method calls in the given method.
|
||||
* @param clazz - the parent class.
|
||||
* @param method - the method to analyse.
|
||||
* @return The method calls.
|
||||
* @throws IOException Cannot access the parent class.
|
||||
*/
|
||||
public List<AsmMethod> getMethodCalls(Class<?> clazz, Method method) throws IOException {
|
||||
final ClassReader reader = new ClassReader(clazz.getCanonicalName());
|
||||
final List<AsmMethod> output = Lists.newArrayList();
|
||||
|
||||
// The method we are looking for
|
||||
|
@ -543,6 +543,14 @@ public class MinecraftReflection {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the CraftChatMessage in Minecraft 1.7.2.
|
||||
* @return The CraftChatMessage class.
|
||||
*/
|
||||
public static Class<?> getCraftChatMessage() {
|
||||
return getCraftBukkitClass("util.CraftChatMessage");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the WorldServer (NMS) class.
|
||||
* @return The WorldServer class.
|
||||
@ -637,6 +645,52 @@ public class MinecraftReflection {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the IChatBaseComponent class.
|
||||
* @return The IChatBaseComponent.
|
||||
*/
|
||||
public static Class<?> getIChatBaseComponent() {
|
||||
try {
|
||||
return getMinecraftClass("IChatBaseComponent");
|
||||
} catch (RuntimeException e) {
|
||||
return setMinecraftClass("IChatBaseComponent",
|
||||
FuzzyReflection.getMethodAccessor(getCraftChatMessage(), "fromString", String.class).
|
||||
getMethod().getReturnType().getComponentType()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to find the ChatSerializer class.
|
||||
* @return The serializer class.
|
||||
* @throws IllegalStateException If the class could not be found or deduced.
|
||||
*/
|
||||
public static Class<?> getChatSerializer() {
|
||||
try {
|
||||
return getMinecraftClass("ChatSerializer");
|
||||
} catch (RuntimeException e) {
|
||||
// Analyse the ASM
|
||||
try {
|
||||
List<AsmMethod> methodCalls = ClassAnalyser.getDefault().getMethodCalls(
|
||||
PacketType.Play.Server.CHAT.getPacketClass(),
|
||||
MinecraftMethods.getPacketReadByteBufMethod()
|
||||
);
|
||||
Class<?> packetSerializer = getPacketDataSerializerClass();
|
||||
|
||||
for (AsmMethod method : methodCalls) {
|
||||
Class<?> owner = method.getOwnerClass();
|
||||
|
||||
if (isMinecraftClass(owner) && !owner.equals(packetSerializer)) {
|
||||
return setMinecraftClass("ChatSerializer", owner);
|
||||
}
|
||||
}
|
||||
} catch (Exception e1) {
|
||||
throw new IllegalStateException("Cannot find ChatSerializer class.", e);
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Cannot find ChatSerializer class.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this Minecraft version is using Netty.
|
||||
* <p>
|
||||
@ -1300,7 +1354,7 @@ public class MinecraftReflection {
|
||||
try {
|
||||
// Now -- we inspect all the method calls within that method, and use the first foreign Minecraft class
|
||||
for (AsmMethod method : ClassAnalyser.getDefault().getMethodCalls(writeNbt)) {
|
||||
Class<?> owner = MinecraftReflection.class.getClassLoader().loadClass(method.getOwnerClass().replace('/', '.'));
|
||||
Class<?> owner = method.getOwnerClass();
|
||||
|
||||
if (!packetSerializer.equals(owner) && isMinecraftClass(owner)) {
|
||||
return setMinecraftClass("NBTCompressedStreamTools", owner);
|
||||
|
@ -257,6 +257,29 @@ public class BukkitConverters {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a converter for wrapped chat components.
|
||||
* @return Wrapped chat componetns.
|
||||
*/
|
||||
public static EquivalentConverter<WrappedChatComponent> getWrappedChatComponentConverter() {
|
||||
return new IgnoreNullConverter<WrappedChatComponent>() {
|
||||
@Override
|
||||
protected Object getGenericValue(Class<?> genericType, WrappedChatComponent specific) {
|
||||
return specific.getHandle();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected WrappedChatComponent getSpecificValue(Object generic) {
|
||||
return WrappedChatComponent.fromHandle(generic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<WrappedChatComponent> getSpecificType() {
|
||||
return WrappedChatComponent.class;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a converter for wrapped attribute snapshots.
|
||||
* @return Wrapped attribute snapshot converter.
|
||||
@ -601,6 +624,12 @@ public class BukkitConverters {
|
||||
put(WrappedWatchableObject.class, (EquivalentConverter) getWatchableObjectConverter()).
|
||||
put(PotionEffect.class, (EquivalentConverter) getPotionEffectConverter());
|
||||
|
||||
// Types added in 1.7.2
|
||||
if (MinecraftReflection.isUsingNetty()) {
|
||||
builder.put(WrappedGameProfile.class, (EquivalentConverter) getWrappedGameProfileConverter());
|
||||
builder.put(WrappedChatComponent.class, (EquivalentConverter) getWrappedChatComponentConverter());
|
||||
}
|
||||
|
||||
if (hasWorldType)
|
||||
builder.put(WorldType.class, (EquivalentConverter) getWorldTypeConverter());
|
||||
if (hasAttributeSnapshot)
|
||||
@ -626,11 +655,17 @@ public class BukkitConverters {
|
||||
put(MinecraftReflection.getNBTCompoundClass(), (EquivalentConverter) getNbtConverter()).
|
||||
put(MinecraftReflection.getWatchableObjectClass(), (EquivalentConverter) getWatchableObjectConverter()).
|
||||
put(MinecraftReflection.getMobEffectClass(), (EquivalentConverter) getPotionEffectConverter());
|
||||
|
||||
|
||||
if (hasWorldType)
|
||||
builder.put(MinecraftReflection.getWorldTypeClass(), (EquivalentConverter) getWorldTypeConverter());
|
||||
if (hasAttributeSnapshot)
|
||||
builder.put(MinecraftReflection.getAttributeSnapshotClass(), (EquivalentConverter) getWrappedAttributeConverter());
|
||||
|
||||
// Types added in 1.7.2
|
||||
if (MinecraftReflection.isUsingNetty()) {
|
||||
builder.put(MinecraftReflection.getGameProfileClass(), (EquivalentConverter) getWrappedGameProfileConverter());
|
||||
builder.put(MinecraftReflection.getIChatBaseComponent(), (EquivalentConverter) getWrappedChatComponentConverter());
|
||||
}
|
||||
genericConverters = builder.build();
|
||||
}
|
||||
return genericConverters;
|
||||
|
@ -0,0 +1,125 @@
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection.MethodAccessor;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
|
||||
/**
|
||||
* Represents a chat component added in Minecraft 1.7.2
|
||||
* @author Kristian
|
||||
*/
|
||||
public class WrappedChatComponent {
|
||||
private static final Class<?> SERIALIZER = MinecraftReflection.getChatSerializer();
|
||||
private static final Class<?> COMPONENT = MinecraftReflection.getIChatBaseComponent();
|
||||
private static MethodAccessor SERIALIZE_COMPONENT = null;
|
||||
private static MethodAccessor DESERIALIZE_COMPONENT = null;
|
||||
private static MethodAccessor CONSTRUCT_COMPONENT = null;
|
||||
|
||||
static {
|
||||
FuzzyReflection fuzzy = FuzzyReflection.fromClass(SERIALIZER);
|
||||
|
||||
// Retrieve the correct methods
|
||||
SERIALIZE_COMPONENT = FuzzyReflection.getMethodAccessor(
|
||||
fuzzy.getMethodByParameters("serialize", String.class, new Class<?>[] { COMPONENT }));
|
||||
DESERIALIZE_COMPONENT = FuzzyReflection.getMethodAccessor(
|
||||
fuzzy.getMethodByParameters("serialize", COMPONENT, new Class<?>[] { String.class }));
|
||||
|
||||
// Get a component from a standard Minecraft message
|
||||
CONSTRUCT_COMPONENT = FuzzyReflection.getMethodAccessor(
|
||||
MinecraftReflection.getCraftChatMessage(), "fromString", String.class);
|
||||
}
|
||||
|
||||
private Object handle;
|
||||
private transient String cache;
|
||||
|
||||
private WrappedChatComponent(Object handle, String cache) {
|
||||
this.handle = handle;
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new chat component wrapper around the given NMS object.
|
||||
* @param handle - the NMS object.
|
||||
* @return The wrapper.
|
||||
*/
|
||||
public static WrappedChatComponent fromHandle(Object handle) {
|
||||
if (handle == null)
|
||||
throw new IllegalArgumentException("handle cannot be NULL.");
|
||||
if (!COMPONENT.isAssignableFrom(handle.getClass()))
|
||||
throw new IllegalArgumentException("handle (" + handle + ") is not a " + COMPONENT);
|
||||
return new WrappedChatComponent(handle, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new chat component wrapper from the given JSON string.
|
||||
* @param json - the json.
|
||||
* @return The chat component wrapper.
|
||||
*/
|
||||
public static WrappedChatComponent fromJson(String json) {
|
||||
return new WrappedChatComponent(DESERIALIZE_COMPONENT.invoke(null, json), json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an array of chat components from a standard Minecraft message.
|
||||
* <p>
|
||||
* This uses {@link ChatColor} for formating.
|
||||
* @param message - the message.
|
||||
* @return The equivalent chat components.
|
||||
*/
|
||||
public static WrappedChatComponent[] fromChatMessage(String message) {
|
||||
Object[] components = (Object[]) CONSTRUCT_COMPONENT.invoke(null, message);
|
||||
WrappedChatComponent[] result = new WrappedChatComponent[components.length];
|
||||
|
||||
for (int i = 0; i < components.length; i++) {
|
||||
result[i] = fromHandle(components[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a copy of this component as a JSON string.
|
||||
* <p>
|
||||
* Note that any modifications to this JSON string will not update the current component.
|
||||
* @return The JSON representation of this object.
|
||||
*/
|
||||
public String getJson() {
|
||||
if (cache == null) {
|
||||
cache = (String) SERIALIZE_COMPONENT.invoke(null, handle);
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the content of this component using a JSON object.
|
||||
* @param obj - the JSON that represents the new component.
|
||||
*/
|
||||
public void setJson(String obj) {
|
||||
this.handle = DESERIALIZE_COMPONENT.invoke(null, obj);
|
||||
this.cache = obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the underlying IChatBaseComponent instance.
|
||||
* @return The underlying instance.
|
||||
*/
|
||||
public Object getHandle() {
|
||||
return handle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this)
|
||||
return true;
|
||||
if (obj instanceof WrappedChatComponent) {
|
||||
return ((WrappedChatComponent) obj).handle.equals(handle);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return handle.hashCode();
|
||||
}
|
||||
}
|
@ -32,6 +32,7 @@ import org.apache.commons.lang.builder.ToStringStyle;
|
||||
// Will have to be updated for every version though
|
||||
import org.bukkit.craftbukkit.v1_7_R1.inventory.CraftItemFactory;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.WorldType;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
@ -53,6 +54,7 @@ import com.comphenix.protocol.utility.MinecraftMethods;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.wrappers.BukkitConverters;
|
||||
import com.comphenix.protocol.wrappers.ChunkPosition;
|
||||
import com.comphenix.protocol.wrappers.WrappedChatComponent;
|
||||
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
|
||||
import com.comphenix.protocol.wrappers.WrappedGameProfile;
|
||||
import com.comphenix.protocol.wrappers.WrappedWatchableObject;
|
||||
@ -336,6 +338,16 @@ public class PacketContainerTest {
|
||||
assertEquals(profile, spawnEntity.getGameProfiles().read(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChatComponents() {
|
||||
PacketContainer chatPacket = new PacketContainer(PacketType.Play.Server.CHAT);
|
||||
chatPacket.getChatComponents().write(0,
|
||||
WrappedChatComponent.fromChatMessage("You shall not " + ChatColor.ITALIC + "pass!")[0]);
|
||||
|
||||
assertEquals("{\"extra\":[\"You shall not \",{\"italic\":true,\"text\":\"pass!\"}],\"text\":\"\"}",
|
||||
chatPacket.getChatComponents().read(0).getJson());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerialization() {
|
||||
PacketContainer chat = new PacketContainer(PacketType.Play.Client.CHAT);
|
||||
|
@ -2,6 +2,8 @@ package com.comphenix.protocol.utility;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import net.minecraft.server.v1_7_R1.ChatSerializer;
|
||||
import net.minecraft.server.v1_7_R1.IChatBaseComponent;
|
||||
import net.minecraft.server.v1_7_R1.NBTCompressedStreamTools;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
@ -32,4 +34,14 @@ public class MinecraftReflectionTest {
|
||||
public void testNbtStreamTools() {
|
||||
assertEquals(NBTCompressedStreamTools.class, MinecraftReflection.getNbtCompressedStreamToolsClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChatComponent() {
|
||||
assertEquals(IChatBaseComponent.class, MinecraftReflection.getIChatBaseComponent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChatSerializer() {
|
||||
assertEquals(ChatSerializer.class, MinecraftReflection.getChatSerializer());
|
||||
}
|
||||
}
|
||||
|
In neuem Issue referenzieren
Einen Benutzer sperren