mirror of
https://github.com/dmulloy2/ProtocolLib.git
synced 2024-11-24 11:36:51 +01:00
Added a wrapper for ServerPing fields.
This commit is contained in:
parent
e8759d0b72
commit
bdc739317b
@ -12,6 +12,7 @@
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" path="src/test/resources"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
|
||||
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||
<attributes>
|
||||
|
@ -69,6 +69,7 @@ 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.WrappedServerPing;
|
||||
import com.comphenix.protocol.wrappers.WrappedWatchableObject;
|
||||
import com.comphenix.protocol.wrappers.nbt.NbtBase;
|
||||
import com.google.common.base.Function;
|
||||
@ -496,12 +497,25 @@ public class PacketContainer implements Serializable {
|
||||
* <p>
|
||||
* This modifier will automatically marshall between WrappedChatComponent and the
|
||||
* internal Minecraft GameProfile.
|
||||
* @return A modifier for GameProfile fields.
|
||||
* @return A modifier for ChatComponent fields.
|
||||
*/
|
||||
public StructureModifier<WrappedChatComponent> getChatComponents() {
|
||||
// Convert to and from the Bukkit wrapper
|
||||
return structureModifier.<WrappedChatComponent>withType(
|
||||
MinecraftReflection.getIChatBaseComponent(), BukkitConverters.getWrappedChatComponentConverter());
|
||||
MinecraftReflection.getIChatBaseComponentClass(), BukkitConverters.getWrappedChatComponentConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a read/write structure for the ServerPing fields in the following packet: <br>
|
||||
* <ul>
|
||||
* <li>{@link PacketType.Status.Server#OUT_SERVER_INFO}
|
||||
* </ul>
|
||||
* @return A modifier for ServerPing fields.
|
||||
*/
|
||||
public StructureModifier<WrappedServerPing> getServerPings() {
|
||||
// Convert to and from the wrapper
|
||||
return structureModifier.<WrappedServerPing>withType(
|
||||
MinecraftReflection.getServerPingClass(), BukkitConverters.getWrappedServerPingConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,10 +1,13 @@
|
||||
package com.comphenix.protocol.reflect.accessors;
|
||||
|
||||
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;
|
||||
|
||||
public final class Accessors {
|
||||
/**
|
||||
@ -46,7 +49,7 @@ public final class Accessors {
|
||||
* @param instanceClass - the type of the instance to retrieve.
|
||||
* @param fieldClass - type of the field to retrieve.
|
||||
* @param forceAccess - whether or not to look for private and protected fields.
|
||||
* @return The value of that field.
|
||||
* @return The field accessor.
|
||||
* @throws IllegalArgumentException If the field cannot be found.
|
||||
*/
|
||||
public static FieldAccessor getFieldAccessor(Class<?> instanceClass, Class<?> fieldClass, boolean forceAccess) {
|
||||
@ -55,6 +58,23 @@ public final class Accessors {
|
||||
return Accessors.getFieldAccessor(field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an accessor (in declared order) for every field of the givne type.
|
||||
* @param instanceClass - the type of the instance to retrieve.
|
||||
* @param fieldClass - type of the field(s) to retrieve.
|
||||
* @param forceAccess - whether or not to look for private and protected fields.
|
||||
* @return The accessors.
|
||||
*/
|
||||
public static FieldAccessor[] getFieldAccessorArray(Class<?> instanceClass, Class<?> fieldClass, boolean forceAccess) {
|
||||
List<Field> fields = FuzzyReflection.fromClass(instanceClass, forceAccess).getFieldListByType(fieldClass);
|
||||
FieldAccessor[] accessors = new FieldAccessor[fields.size()];
|
||||
|
||||
for (int i = 0; i < accessors.length; i++) {
|
||||
accessors[i] = getFieldAccessor(fields.get(i));
|
||||
}
|
||||
return accessors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an accessor for the first field of the given type.
|
||||
* @param instanceClass - the type of the instance to retrieve.
|
||||
@ -98,7 +118,7 @@ public final class Accessors {
|
||||
return accessor;
|
||||
return new SynchronizedFieldAccessor(accessor);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve a method accessor for a method with the given name and signature.
|
||||
* @param instanceClass - the parent class.
|
||||
@ -119,6 +139,33 @@ public final class Accessors {
|
||||
return new DefaultMethodAccessor(method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a constructor accessor for a constructor with the given signature.
|
||||
* @param instanceClass - the parent class.
|
||||
* @param parameters - the parameters.
|
||||
* @return The constructor accessor.
|
||||
*/
|
||||
public static ConstructorAccessor getConstructorAccessor(Class<?> instanceClass, Class<?>... parameters) {
|
||||
try {
|
||||
return getConstructorAccessor(instanceClass.getDeclaredConstructor(parameters));
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"Unable to find constructor %s(%s).", instanceClass, Joiner.on(",").join(parameters))
|
||||
);
|
||||
} catch (SecurityException e) {
|
||||
throw new IllegalStateException("Cannot access constructors.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a constructor accessor for a particular constructor, avoding checked exceptions.
|
||||
* @param constructor - the constructor to access.
|
||||
* @return The method accessor.
|
||||
*/
|
||||
public static ConstructorAccessor getConstructorAccessor(final Constructor<?> constructor) {
|
||||
return new DefaultConstrutorAccessor(constructor);
|
||||
}
|
||||
|
||||
// Seal this class
|
||||
private Accessors() {
|
||||
}
|
||||
|
@ -0,0 +1,18 @@
|
||||
package com.comphenix.protocol.reflect.accessors;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
|
||||
public interface ConstructorAccessor {
|
||||
/**
|
||||
* Invoke the underlying constructor.
|
||||
* @param args - the arguments to pass to the method.
|
||||
* @return The return value, or NULL for void methods.
|
||||
*/
|
||||
public Object invoke(Object... args);
|
||||
|
||||
/**
|
||||
* Retrieve the underlying constructor.
|
||||
* @return The method.
|
||||
*/
|
||||
public Constructor<?> getConstructor();
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package com.comphenix.protocol.reflect.accessors;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
final class DefaultConstrutorAccessor implements ConstructorAccessor {
|
||||
private final Constructor<?> constructor;
|
||||
|
||||
public DefaultConstrutorAccessor(Constructor<?> method) {
|
||||
this.constructor = method;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Object... args) {
|
||||
try {
|
||||
return constructor.newInstance(args);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IllegalStateException("Cannot use reflection.", e);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw e;
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException("An internal error occured.", e.getCause());
|
||||
} catch (InstantiationException e) {
|
||||
throw new RuntimeException("Cannot instantiate object.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Constructor<?> getConstructor() {
|
||||
return constructor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return constructor != null ? constructor.hashCode() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
|
||||
if (obj instanceof DefaultConstrutorAccessor) {
|
||||
DefaultConstrutorAccessor other = (DefaultConstrutorAccessor) obj;
|
||||
return other.constructor == constructor;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DefaultConstrutorAccessor [constructor=" + constructor + "]";
|
||||
}
|
||||
}
|
@ -114,6 +114,15 @@ public class FuzzyFieldContract extends AbstractFuzzyMember<Field> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Match a field by its type.
|
||||
* @param matcher - the type to match.
|
||||
* @return The field contract.
|
||||
*/
|
||||
public static FuzzyFieldContract matchType(AbstractFuzzyMatcher<Class<?>> matcher) {
|
||||
return newBuilder().typeMatches(matcher).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new fuzzy field contract builder.
|
||||
* @return New fuzzy field contract builder.
|
||||
|
@ -207,6 +207,20 @@ public class FuzzyMethodContract extends AbstractFuzzyMember<MethodInfo> {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new required parameters by type and order for any matching method.
|
||||
* @param type - the types of every parameters in order.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder parameterExactArray(Class<?>... types) {
|
||||
parameterCount(types.length);
|
||||
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
parameterExactType(types[i], i);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new required parameter whose type must be a superclass of the given type.
|
||||
* <p>
|
||||
|
@ -650,7 +650,7 @@ public class MinecraftReflection {
|
||||
* Retrieve the IChatBaseComponent class.
|
||||
* @return The IChatBaseComponent.
|
||||
*/
|
||||
public static Class<?> getIChatBaseComponent() {
|
||||
public static Class<?> getIChatBaseComponentClass() {
|
||||
try {
|
||||
return getMinecraftClass("IChatBaseComponent");
|
||||
} catch (RuntimeException e) {
|
||||
@ -666,7 +666,7 @@ public class MinecraftReflection {
|
||||
* @return The serializer class.
|
||||
* @throws IllegalStateException If the class could not be found or deduced.
|
||||
*/
|
||||
public static Class<?> getChatSerializer() {
|
||||
public static Class<?> getChatSerializerClass() {
|
||||
try {
|
||||
return getMinecraftClass("ChatSerializer");
|
||||
} catch (RuntimeException e) {
|
||||
@ -692,6 +692,92 @@ public class MinecraftReflection {
|
||||
throw new IllegalStateException("Cannot find ChatSerializer class.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the ServerPing class in Minecraft 1.7.2.
|
||||
* @return The ServerPing class.
|
||||
*/
|
||||
public static Class<?> getServerPingClass() {
|
||||
if (!isUsingNetty())
|
||||
throw new IllegalStateException("ServerPing is only supported in 1.7.2.");
|
||||
|
||||
try {
|
||||
return getMinecraftClass("ServerPing");
|
||||
} catch (RuntimeException e) {
|
||||
Class<?> statusServerInfo = PacketType.Status.Server.OUT_SERVER_INFO.getPacketClass();
|
||||
|
||||
// Find a server ping object
|
||||
AbstractFuzzyMatcher<Class<?>> serverPingContract = FuzzyClassContract.newBuilder().
|
||||
field(FuzzyFieldContract.newBuilder().typeExact(String.class).build()).
|
||||
field(FuzzyFieldContract.newBuilder().typeDerivedOf(getIChatBaseComponentClass()).build()).
|
||||
build().
|
||||
and(getMinecraftObjectMatcher());
|
||||
|
||||
return setMinecraftClass("ServerPing",
|
||||
FuzzyReflection.fromClass(statusServerInfo, true).
|
||||
getField(FuzzyFieldContract.matchType(serverPingContract)).getType());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the ServerPingServerData class in Minecraft 1.7.2.
|
||||
* @return The ServerPingServerData class.
|
||||
*/
|
||||
public static Class<?> getServerPingServerDataClass() {
|
||||
if (!isUsingNetty())
|
||||
throw new IllegalStateException("ServerPingServerData is only supported in 1.7.2.");
|
||||
|
||||
try {
|
||||
return getMinecraftClass("ServerPingServerData");
|
||||
} catch (RuntimeException e) {
|
||||
Class<?> serverPing = getServerPingClass();
|
||||
|
||||
// Find a server ping object
|
||||
AbstractFuzzyMatcher<Class<?>> serverDataContract = FuzzyClassContract.newBuilder().
|
||||
constructor(FuzzyMethodContract.newBuilder().parameterExactArray(String.class, int.class)).
|
||||
build().
|
||||
and(getMinecraftObjectMatcher());
|
||||
|
||||
return setMinecraftClass("ServerPingServerData", getTypeFromField(serverPing, serverDataContract));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the ServerPingPlayerSample class in Minecraft 1.7.2.
|
||||
* @return The ServerPingPlayerSample class.
|
||||
*/
|
||||
public static Class<?> getServerPingPlayerSampleClass() {
|
||||
if (!isUsingNetty())
|
||||
throw new IllegalStateException("ServerPingPlayerSample is only supported in 1.7.2.");
|
||||
|
||||
try {
|
||||
return getMinecraftClass("ServerPingPlayerSample");
|
||||
} catch (RuntimeException e) {
|
||||
Class<?> serverPing = getServerPingClass();
|
||||
|
||||
// Find a server ping object
|
||||
AbstractFuzzyMatcher<Class<?>> serverPlayerContract = FuzzyClassContract.newBuilder().
|
||||
constructor(FuzzyMethodContract.newBuilder().parameterExactArray(int.class, int.class)).
|
||||
field(FuzzyFieldContract.newBuilder().typeExact(GameProfile[].class)).
|
||||
build().
|
||||
and(getMinecraftObjectMatcher());
|
||||
|
||||
return setMinecraftClass("ServerPingPlayerSample", getTypeFromField(serverPing, serverPlayerContract));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the type of the field whose type matches.
|
||||
* @param clazz - the declaring type.
|
||||
* @param fieldTypeMatcher - the field type matcher.
|
||||
* @return The type of the field.
|
||||
*/
|
||||
private static Class<?> getTypeFromField(Class<?> clazz, AbstractFuzzyMatcher<Class<?>> fieldTypeMatcher) {
|
||||
final FuzzyFieldContract fieldMatcher = FuzzyFieldContract.matchType(fieldTypeMatcher);
|
||||
|
||||
return FuzzyReflection.fromClass(clazz, true).
|
||||
getField(fieldMatcher).getType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this Minecraft version is using Netty.
|
||||
* <p>
|
||||
|
@ -18,6 +18,7 @@
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
@ -32,6 +33,7 @@ import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
import com.comphenix.protocol.injector.PacketConstructor;
|
||||
@ -48,6 +50,7 @@ import com.comphenix.protocol.wrappers.nbt.NbtFactory;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
/**
|
||||
* Contains several useful equivalent converters for normal Bukkit types.
|
||||
@ -234,6 +237,58 @@ public class BukkitConverters {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an equivalent converter for an array of generic items.
|
||||
* <p>
|
||||
* The array is wrapped in a list.
|
||||
* @param genericItemType - the generic item type.
|
||||
* @param itemConverter - an equivalent converter for the generic type.
|
||||
* @return An equivalent converter.
|
||||
*/
|
||||
public static <T> EquivalentConverter<Iterable<? extends T>> getArrayConverter(
|
||||
final Class<?> genericItemType, final EquivalentConverter<T> itemConverter) {
|
||||
// Convert to and from the wrapper
|
||||
return new IgnoreNullConverter<Iterable<? extends T>>() {
|
||||
@Override
|
||||
protected List<T> getSpecificValue(Object generic) {
|
||||
if (generic instanceof Object[]) {
|
||||
ImmutableList.Builder<T> builder = ImmutableList.builder();
|
||||
|
||||
// Copy everything to a new list
|
||||
for (Object item : (Object[]) generic) {
|
||||
T result = itemConverter.getSpecific(item);
|
||||
builder.add(result);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
// Not valid
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getGenericValue(Class<?> genericType, Iterable<? extends T> specific) {
|
||||
List<T> list = Lists.newArrayList(specific);
|
||||
Object[] output = (Object[]) Array.newInstance(genericType, list.size());
|
||||
|
||||
// Convert each object
|
||||
for (int i = 0; i < output.length; i++) {
|
||||
Object converted = itemConverter.getGeneric(genericItemType, list.get(i));
|
||||
output[i] = converted;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Class<Iterable<? extends T>> getSpecificType() {
|
||||
// Damn you Java
|
||||
Class<?> dummy = Iterable.class;
|
||||
return (Class<Iterable<? extends T>>) dummy;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a converter for wrapped attribute snapshots.
|
||||
* @return Wrapped attribute snapshot converter.
|
||||
@ -506,6 +561,29 @@ public class BukkitConverters {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the converter for the ServerPing packet in {@link PacketType.Status.Server#OUT_SERVER_INFO}.
|
||||
* @return Server ping converter.
|
||||
*/
|
||||
public static EquivalentConverter<WrappedServerPing> getWrappedServerPingConverter() {
|
||||
return new IgnoreNullConverter<WrappedServerPing>() {
|
||||
@Override
|
||||
protected Object getGenericValue(Class<?> genericType, WrappedServerPing specific) {
|
||||
return specific.getHandle();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected WrappedServerPing getSpecificValue(Object generic) {
|
||||
return WrappedServerPing.fromHandle(generic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<WrappedServerPing> getSpecificType() {
|
||||
return WrappedServerPing.class;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the converter used to convert between a PotionEffect and the equivalent NMS Mobeffect.
|
||||
* @return The potion effect converter.
|
||||
@ -664,7 +742,7 @@ public class BukkitConverters {
|
||||
// Types added in 1.7.2
|
||||
if (MinecraftReflection.isUsingNetty()) {
|
||||
builder.put(MinecraftReflection.getGameProfileClass(), (EquivalentConverter) getWrappedGameProfileConverter());
|
||||
builder.put(MinecraftReflection.getIChatBaseComponent(), (EquivalentConverter) getWrappedChatComponentConverter());
|
||||
builder.put(MinecraftReflection.getIChatBaseComponentClass(), (EquivalentConverter) getWrappedChatComponentConverter());
|
||||
}
|
||||
genericConverters = builder.build();
|
||||
}
|
||||
|
@ -12,8 +12,8 @@ import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
* @author Kristian
|
||||
*/
|
||||
public class WrappedChatComponent extends AbstractWrapper {
|
||||
private static final Class<?> SERIALIZER = MinecraftReflection.getChatSerializer();
|
||||
private static final Class<?> COMPONENT = MinecraftReflection.getIChatBaseComponent();
|
||||
private static final Class<?> SERIALIZER = MinecraftReflection.getChatSerializerClass();
|
||||
private static final Class<?> COMPONENT = MinecraftReflection.getIChatBaseComponentClass();
|
||||
private static MethodAccessor SERIALIZE_COMPONENT = null;
|
||||
private static MethodAccessor DESERIALIZE_COMPONENT = null;
|
||||
private static MethodAccessor CONSTRUCT_COMPONENT = null;
|
||||
@ -35,7 +35,7 @@ public class WrappedChatComponent extends AbstractWrapper {
|
||||
private transient String cache;
|
||||
|
||||
private WrappedChatComponent(Object handle, String cache) {
|
||||
super(MinecraftReflection.getIChatBaseComponent());
|
||||
super(MinecraftReflection.getIChatBaseComponentClass());
|
||||
setHandle(handle);
|
||||
this.cache = cache;
|
||||
}
|
||||
|
@ -0,0 +1,347 @@
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.RenderedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import net.minecraft.util.com.mojang.authlib.GameProfile;
|
||||
import net.minecraft.util.io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.util.io.netty.buffer.Unpooled;
|
||||
import net.minecraft.util.io.netty.handler.codec.base64.Base64;
|
||||
|
||||
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.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
/**
|
||||
* Represents a server ping packet data.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class WrappedServerPing extends AbstractWrapper {
|
||||
// Server ping fields
|
||||
private static Class<?> SERVER_PING = MinecraftReflection.getServerPingClass();
|
||||
private static ConstructorAccessor SERVER_PING_CONSTRUCTOR = Accessors.getConstructorAccessor(SERVER_PING);
|
||||
private static FieldAccessor DESCRIPTION = Accessors.getFieldAccessor(SERVER_PING, MinecraftReflection.getIChatBaseComponentClass(), true);
|
||||
private static FieldAccessor PLAYERS = Accessors.getFieldAccessor(SERVER_PING, MinecraftReflection.getServerPingPlayerSampleClass(), true);
|
||||
private static FieldAccessor VERSION = Accessors.getFieldAccessor(SERVER_PING, MinecraftReflection.getServerPingServerDataClass(), true);
|
||||
private static FieldAccessor FAVICON = Accessors.getFieldAccessor(SERVER_PING, String.class, true);
|
||||
|
||||
// For converting to the underlying array
|
||||
private static EquivalentConverter<Iterable<? extends WrappedGameProfile>> PROFILE_CONVERT =
|
||||
BukkitConverters.getArrayConverter(GameProfile[].class, BukkitConverters.getWrappedGameProfileConverter());
|
||||
|
||||
// Server ping player sample fields
|
||||
private static Class<?> PLAYERS_CLASS = MinecraftReflection.getServerPingPlayerSampleClass();
|
||||
private static ConstructorAccessor PLAYERS_CONSTRUCTOR = Accessors.getConstructorAccessor(PLAYERS_CLASS, int.class, int.class);
|
||||
private static FieldAccessor[] PLAYERS_INTS = Accessors.getFieldAccessorArray(PLAYERS_CLASS, int.class, true);
|
||||
private static FieldAccessor PLAYERS_PROFILES = Accessors.getFieldAccessor(PLAYERS_CLASS, GameProfile[].class, true);
|
||||
private static FieldAccessor PLAYERS_MAXIMUM = PLAYERS_INTS[0];
|
||||
private static FieldAccessor PLAYERS_ONLINE = PLAYERS_INTS[1];
|
||||
|
||||
// Server data fields
|
||||
private static Class<?> VERSION_CLASS = MinecraftReflection.getServerPingServerDataClass();
|
||||
private static ConstructorAccessor VERSION_CONSTRUCTOR = Accessors.getConstructorAccessor(VERSION_CLASS, String.class, int.class);
|
||||
private static FieldAccessor VERSION_NAME = Accessors.getFieldAccessor(VERSION_CLASS, String.class, true);
|
||||
private static FieldAccessor VERSION_PROTOCOL = Accessors.getFieldAccessor(VERSION_CLASS, int.class, true);
|
||||
|
||||
// Get profile from player
|
||||
private static FieldAccessor ENTITY_HUMAN_PROFILE = Accessors.getFieldAccessor(
|
||||
MinecraftReflection.getEntityPlayerClass().getSuperclass(), GameProfile.class, true);
|
||||
|
||||
// Inner class
|
||||
private Object players;
|
||||
private Object version;
|
||||
|
||||
/**
|
||||
* Construct a new server ping initialized with empty values.
|
||||
*/
|
||||
public WrappedServerPing() {
|
||||
super(MinecraftReflection.getServerPingClass());
|
||||
setHandle(SERVER_PING_CONSTRUCTOR.invoke());
|
||||
this.players = PLAYERS_CONSTRUCTOR.invoke(0, 0);
|
||||
this.version = VERSION_CONSTRUCTOR.invoke(MinecraftVersion.WORLD_UPDATE.toString(), 4);
|
||||
PLAYERS.set(handle, players);
|
||||
VERSION.set(handle, version);
|
||||
}
|
||||
|
||||
private WrappedServerPing(Object handle) {
|
||||
super(MinecraftReflection.getServerPingClass());
|
||||
setHandle(handle);
|
||||
this.players = PLAYERS.get(handle);
|
||||
this.version = VERSION.get(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a wrapped server ping from a native NMS object.
|
||||
* @param handle - the native object.
|
||||
* @return The wrapped server ping object.
|
||||
*/
|
||||
public static WrappedServerPing fromHandle(Object handle) {
|
||||
return new WrappedServerPing(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the message of the day.
|
||||
* @return The messge of the day.
|
||||
*/
|
||||
public WrappedChatComponent getMotD() {
|
||||
return WrappedChatComponent.fromHandle(DESCRIPTION.get(handle));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the message of the day.
|
||||
* @param description - message of the day.
|
||||
*/
|
||||
public void setMotD(WrappedChatComponent description) {
|
||||
DESCRIPTION.set(handle, description.getHandle());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the message of the day.
|
||||
* <p>
|
||||
* <b>Warning:</b> Only the first line will be transmitted.
|
||||
* @param description - the message.
|
||||
*/
|
||||
public void setMotD(String message) {
|
||||
setMotD(WrappedChatComponent.fromChatMessage(message)[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the compressed PNG file that is being displayed as a favicon.
|
||||
* @return The favicon.
|
||||
*/
|
||||
public CompressedImage getFavicon() {
|
||||
return CompressedImage.fromEncodedText((String) FAVICON.get(handle));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the compressed PNG file that is being displayed.
|
||||
* @param image - the new compressed image.
|
||||
*/
|
||||
public void setFavicon(CompressedImage image) {
|
||||
FAVICON.set(handle, image.toEncodedText());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the displayed number of online players.
|
||||
* @return The displayed number.
|
||||
*/
|
||||
public int getPlayersOnline() {
|
||||
return (Integer) PLAYERS_ONLINE.get(players);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the displayed number of online players.
|
||||
* @param online - online players.
|
||||
*/
|
||||
public void setPlayersOnline(int online) {
|
||||
PLAYERS_ONLINE.set(players, online);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the displayed maximum number of players.
|
||||
* @return The maximum number.
|
||||
*/
|
||||
public int getPlayersMaximum() {
|
||||
return (Integer) PLAYERS_MAXIMUM.get(players);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the displayed maximum number of players.
|
||||
* @param maximum - maximum player count.
|
||||
*/
|
||||
public void setPlayersMaximum(int maximum) {
|
||||
PLAYERS_MAXIMUM.set(players, maximum);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a copy of all the logged in players.
|
||||
* @return Logged in players.
|
||||
*/
|
||||
public ImmutableList<WrappedGameProfile> getPlayers() {
|
||||
return ImmutableList.copyOf(PROFILE_CONVERT.getSpecific(PLAYERS_PROFILES.get(players)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the displayed list of logged in players.
|
||||
* @param profile - every logged in player.
|
||||
*/
|
||||
public void setPlayers(Iterable<? extends WrappedGameProfile> profile) {
|
||||
PLAYERS_PROFILES.set(handle, PROFILE_CONVERT.getGeneric(GameProfile[].class, profile));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the displayed lst of logged in players.
|
||||
* @param players - the players to display.
|
||||
*/
|
||||
public void setBukkitPlayers(Iterable<? extends Player> players) {
|
||||
List<WrappedGameProfile> profiles = Lists.newArrayList();
|
||||
|
||||
for (Player player : players) {
|
||||
GameProfile profile = (GameProfile) ENTITY_HUMAN_PROFILE.get(BukkitUnwrapper.getInstance().unwrapItem(player));
|
||||
profiles.add(new WrappedGameProfile(profile.getId(), profile.getName()));
|
||||
}
|
||||
setPlayers(profiles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the version name of the current server.
|
||||
* @return The version name.
|
||||
*/
|
||||
public String getVersionName() {
|
||||
return (String) VERSION_NAME.get(version);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the version name of the current server.
|
||||
* @param name - the new version name.
|
||||
*/
|
||||
public void setVersionName(String name) {
|
||||
VERSION_NAME.set(version, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the protocol number.
|
||||
* @return The protocol.
|
||||
*/
|
||||
public int getVersionProtocol() {
|
||||
return (Integer) VERSION_PROTOCOL.get(version);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the version protocol
|
||||
* @param protocol - the protocol number.
|
||||
*/
|
||||
public void setVersionProtocol(int protocol) {
|
||||
VERSION_PROTOCOL.set(version, protocol);
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a compressed favicon.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class CompressedImage {
|
||||
private final String mime;
|
||||
private final byte[] data;
|
||||
|
||||
/**
|
||||
* Construct a new compressed image.
|
||||
* @param mime - the mime type.
|
||||
* @param data - the raw compressed image data.
|
||||
*/
|
||||
public CompressedImage(String mime, byte[] data) {
|
||||
this.mime = Preconditions.checkNotNull(mime, "mime cannot be NULL");
|
||||
this.data = Preconditions.checkNotNull(data, "data cannot be NULL");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a compressed image from an input stream.
|
||||
* @param input - the PNG as an input stream.
|
||||
* @return The compressed image.
|
||||
* @throws IOException If we cannot read the input stream.
|
||||
*/
|
||||
public static CompressedImage fromPng(InputStream input) throws IOException {
|
||||
return new CompressedImage("image/png", ByteStreams.toByteArray(input));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a compressed image from a byte array of a PNG file.
|
||||
* @param data - the file as a byte array.
|
||||
* @return The compressed image.
|
||||
*/
|
||||
public static CompressedImage fromPng(byte[] data) {
|
||||
return new CompressedImage("image/png", data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a compressed image from an image.
|
||||
* @param image - the image.
|
||||
* @throws IOException If we were unable to compress the image.
|
||||
*/
|
||||
public static CompressedImage fromPng(RenderedImage image) throws IOException {
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
ImageIO.write(image, "png", output);
|
||||
return new CompressedImage("image/png", output.toByteArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a compressed image from an encoded text.
|
||||
* @param text - the encoded text.
|
||||
* @return The corresponding compressed image.
|
||||
*/
|
||||
public static CompressedImage fromEncodedText(String text) {
|
||||
String mime = null;
|
||||
byte[] data = null;
|
||||
|
||||
for (String segment : Splitter.on(";").split(text)) {
|
||||
if (segment.startsWith("data:")) {
|
||||
mime = segment.substring(5);
|
||||
} else if (segment.startsWith("base64,")) {
|
||||
byte[] encoded = segment.substring(7).getBytes(Charsets.UTF_8);
|
||||
ByteBuf decoded = Base64.decode(Unpooled.wrappedBuffer(encoded));
|
||||
|
||||
// Read into a byte array
|
||||
data = new byte[decoded.readableBytes()];
|
||||
decoded.readBytes(data);
|
||||
} else {
|
||||
// We will ignore these segments
|
||||
}
|
||||
}
|
||||
return new CompressedImage(mime, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the MIME type of the image.
|
||||
* <p>
|
||||
* This is image/png in vanilla Minecraft.
|
||||
* @return The MIME type.
|
||||
*/
|
||||
public String getMime() {
|
||||
return mime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a copy of the underlying data array.
|
||||
* @return The underlying compressed image.
|
||||
*/
|
||||
public byte[] getDataCopy() {
|
||||
return data.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Uncompress and return the stored image.
|
||||
* @return The image.
|
||||
* @throws IOException If the image data could not be decoded.
|
||||
*/
|
||||
public BufferedImage getImage() throws IOException {
|
||||
return ImageIO.read(new ByteArrayInputStream(data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the compressed image to encoded text.
|
||||
* @return The encoded text.
|
||||
*/
|
||||
public String toEncodedText() {
|
||||
return "data:" + mime + ";base64," + Base64.encode(Unpooled.wrappedBuffer(data)).toString(Charsets.UTF_8);
|
||||
}
|
||||
}
|
||||
}
|
@ -5,6 +5,9 @@ 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 net.minecraft.server.v1_7_R1.ServerPing;
|
||||
import net.minecraft.server.v1_7_R1.ServerPingPlayerSample;
|
||||
import net.minecraft.server.v1_7_R1.ServerPingServerData;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
@ -37,11 +40,26 @@ public class MinecraftReflectionTest {
|
||||
|
||||
@Test
|
||||
public void testChatComponent() {
|
||||
assertEquals(IChatBaseComponent.class, MinecraftReflection.getIChatBaseComponent());
|
||||
assertEquals(IChatBaseComponent.class, MinecraftReflection.getIChatBaseComponentClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChatSerializer() {
|
||||
assertEquals(ChatSerializer.class, MinecraftReflection.getChatSerializer());
|
||||
assertEquals(ChatSerializer.class, MinecraftReflection.getChatSerializerClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerPing() {
|
||||
assertEquals(ServerPing.class, MinecraftReflection.getServerPingClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerPingPlayerSample() {
|
||||
assertEquals(ServerPingPlayerSample.class, MinecraftReflection.getServerPingPlayerSampleClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerPingServerData() {
|
||||
assertEquals(ServerPingServerData.class, MinecraftReflection.getServerPingServerDataClass());
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,41 @@
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.comphenix.protocol.BukkitInitialization;
|
||||
import com.comphenix.protocol.wrappers.WrappedServerPing.CompressedImage;
|
||||
import com.google.common.io.Resources;
|
||||
|
||||
public class WrappedServerPingTest {
|
||||
@BeforeClass
|
||||
public static void initializeBukkit() throws IllegalAccessException {
|
||||
BukkitInitialization.initializePackage();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() throws IOException {
|
||||
CompressedImage tux = CompressedImage.fromPng(Resources.getResource("tux.png").openStream());
|
||||
byte[] original = tux.getDataCopy();
|
||||
|
||||
WrappedServerPing serverPing = new WrappedServerPing();
|
||||
serverPing.setMotD("Hello, this is a test.");
|
||||
serverPing.setPlayersOnline(5);
|
||||
serverPing.setPlayersMaximum(10);
|
||||
serverPing.setVersionName("Minecraft 123");
|
||||
serverPing.setVersionProtocol(4);
|
||||
serverPing.setFavicon(tux);
|
||||
|
||||
assertEquals(5, serverPing.getPlayersOnline());
|
||||
assertEquals(10, serverPing.getPlayersMaximum());
|
||||
assertEquals("Minecraft 123", serverPing.getVersionName());
|
||||
assertEquals(4, serverPing.getVersionProtocol());
|
||||
|
||||
assertArrayEquals(original, serverPing.getFavicon().getDataCopy());
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user