Added a wrapper for ServerPing fields.

This commit is contained in:
Kristian S. Stangeland 2013-12-09 23:09:08 +01:00
parent e8759d0b72
commit bdc739317b
13 changed files with 739 additions and 12 deletions

View File

@ -12,6 +12,7 @@
<attribute name="maven.pomderived" value="true"/> <attribute name="maven.pomderived" value="true"/>
</attributes> </attributes>
</classpathentry> </classpathentry>
<classpathentry kind="src" path="src/test/resources"/>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/> <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"> <classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes> <attributes>

View File

@ -69,6 +69,7 @@ import com.comphenix.protocol.wrappers.WrappedAttribute;
import com.comphenix.protocol.wrappers.WrappedChatComponent; import com.comphenix.protocol.wrappers.WrappedChatComponent;
import com.comphenix.protocol.wrappers.WrappedDataWatcher; import com.comphenix.protocol.wrappers.WrappedDataWatcher;
import com.comphenix.protocol.wrappers.WrappedGameProfile; import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.comphenix.protocol.wrappers.WrappedServerPing;
import com.comphenix.protocol.wrappers.WrappedWatchableObject; import com.comphenix.protocol.wrappers.WrappedWatchableObject;
import com.comphenix.protocol.wrappers.nbt.NbtBase; import com.comphenix.protocol.wrappers.nbt.NbtBase;
import com.google.common.base.Function; import com.google.common.base.Function;
@ -496,12 +497,25 @@ public class PacketContainer implements Serializable {
* <p> * <p>
* This modifier will automatically marshall between WrappedChatComponent and the * This modifier will automatically marshall between WrappedChatComponent and the
* internal Minecraft GameProfile. * internal Minecraft GameProfile.
* @return A modifier for GameProfile fields. * @return A modifier for ChatComponent fields.
*/ */
public StructureModifier<WrappedChatComponent> getChatComponents() { public StructureModifier<WrappedChatComponent> getChatComponents() {
// Convert to and from the Bukkit wrapper // Convert to and from the Bukkit wrapper
return structureModifier.<WrappedChatComponent>withType( 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());
} }
/** /**

View File

@ -1,10 +1,13 @@
package com.comphenix.protocol.reflect.accessors; package com.comphenix.protocol.reflect.accessors;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.List;
import com.comphenix.protocol.reflect.ExactReflection; import com.comphenix.protocol.reflect.ExactReflection;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.FuzzyReflection;
import com.google.common.base.Joiner;
public final class Accessors { public final class Accessors {
/** /**
@ -46,7 +49,7 @@ public final class Accessors {
* @param instanceClass - the type of the instance to retrieve. * @param instanceClass - the type of the instance to retrieve.
* @param fieldClass - type of the field to retrieve. * @param fieldClass - type of the field to retrieve.
* @param forceAccess - whether or not to look for private and protected fields. * @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. * @throws IllegalArgumentException If the field cannot be found.
*/ */
public static FieldAccessor getFieldAccessor(Class<?> instanceClass, Class<?> fieldClass, boolean forceAccess) { public static FieldAccessor getFieldAccessor(Class<?> instanceClass, Class<?> fieldClass, boolean forceAccess) {
@ -55,6 +58,23 @@ public final class Accessors {
return Accessors.getFieldAccessor(field); 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. * Retrieve an accessor for the first field of the given type.
* @param instanceClass - the type of the instance to retrieve. * @param instanceClass - the type of the instance to retrieve.
@ -98,7 +118,7 @@ public final class Accessors {
return accessor; return accessor;
return new SynchronizedFieldAccessor(accessor); return new SynchronizedFieldAccessor(accessor);
} }
/** /**
* Retrieve a method accessor for a method with the given name and signature. * Retrieve a method accessor for a method with the given name and signature.
* @param instanceClass - the parent class. * @param instanceClass - the parent class.
@ -119,6 +139,33 @@ public final class Accessors {
return new DefaultMethodAccessor(method); 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 // Seal this class
private Accessors() { private Accessors() {
} }

View File

@ -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();
}

View File

@ -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 + "]";
}
}

View File

@ -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 a new fuzzy field contract builder.
* @return New fuzzy field contract builder. * @return New fuzzy field contract builder.

View File

@ -207,6 +207,20 @@ public class FuzzyMethodContract extends AbstractFuzzyMember<MethodInfo> {
return this; 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. * Add a new required parameter whose type must be a superclass of the given type.
* <p> * <p>

View File

@ -650,7 +650,7 @@ public class MinecraftReflection {
* Retrieve the IChatBaseComponent class. * Retrieve the IChatBaseComponent class.
* @return The IChatBaseComponent. * @return The IChatBaseComponent.
*/ */
public static Class<?> getIChatBaseComponent() { public static Class<?> getIChatBaseComponentClass() {
try { try {
return getMinecraftClass("IChatBaseComponent"); return getMinecraftClass("IChatBaseComponent");
} catch (RuntimeException e) { } catch (RuntimeException e) {
@ -666,7 +666,7 @@ public class MinecraftReflection {
* @return The serializer class. * @return The serializer class.
* @throws IllegalStateException If the class could not be found or deduced. * @throws IllegalStateException If the class could not be found or deduced.
*/ */
public static Class<?> getChatSerializer() { public static Class<?> getChatSerializerClass() {
try { try {
return getMinecraftClass("ChatSerializer"); return getMinecraftClass("ChatSerializer");
} catch (RuntimeException e) { } catch (RuntimeException e) {
@ -692,6 +692,92 @@ public class MinecraftReflection {
throw new IllegalStateException("Cannot find ChatSerializer class."); 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. * Determine if this Minecraft version is using Netty.
* <p> * <p>

View File

@ -18,6 +18,7 @@
package com.comphenix.protocol.wrappers; package com.comphenix.protocol.wrappers;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
@ -32,6 +33,7 @@ import org.bukkit.inventory.ItemStack;
import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType; import org.bukkit.potion.PotionEffectType;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager; import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.injector.PacketConstructor; 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.base.Objects;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
/** /**
* Contains several useful equivalent converters for normal Bukkit types. * 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. * Retrieve a converter for wrapped attribute snapshots.
* @return Wrapped attribute snapshot converter. * @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. * Retrieve the converter used to convert between a PotionEffect and the equivalent NMS Mobeffect.
* @return The potion effect converter. * @return The potion effect converter.
@ -664,7 +742,7 @@ public class BukkitConverters {
// Types added in 1.7.2 // Types added in 1.7.2
if (MinecraftReflection.isUsingNetty()) { if (MinecraftReflection.isUsingNetty()) {
builder.put(MinecraftReflection.getGameProfileClass(), (EquivalentConverter) getWrappedGameProfileConverter()); builder.put(MinecraftReflection.getGameProfileClass(), (EquivalentConverter) getWrappedGameProfileConverter());
builder.put(MinecraftReflection.getIChatBaseComponent(), (EquivalentConverter) getWrappedChatComponentConverter()); builder.put(MinecraftReflection.getIChatBaseComponentClass(), (EquivalentConverter) getWrappedChatComponentConverter());
} }
genericConverters = builder.build(); genericConverters = builder.build();
} }

View File

@ -12,8 +12,8 @@ import com.comphenix.protocol.utility.MinecraftReflection;
* @author Kristian * @author Kristian
*/ */
public class WrappedChatComponent extends AbstractWrapper { public class WrappedChatComponent extends AbstractWrapper {
private static final Class<?> SERIALIZER = MinecraftReflection.getChatSerializer(); private static final Class<?> SERIALIZER = MinecraftReflection.getChatSerializerClass();
private static final Class<?> COMPONENT = MinecraftReflection.getIChatBaseComponent(); private static final Class<?> COMPONENT = MinecraftReflection.getIChatBaseComponentClass();
private static MethodAccessor SERIALIZE_COMPONENT = null; private static MethodAccessor SERIALIZE_COMPONENT = null;
private static MethodAccessor DESERIALIZE_COMPONENT = null; private static MethodAccessor DESERIALIZE_COMPONENT = null;
private static MethodAccessor CONSTRUCT_COMPONENT = null; private static MethodAccessor CONSTRUCT_COMPONENT = null;
@ -35,7 +35,7 @@ public class WrappedChatComponent extends AbstractWrapper {
private transient String cache; private transient String cache;
private WrappedChatComponent(Object handle, String cache) { private WrappedChatComponent(Object handle, String cache) {
super(MinecraftReflection.getIChatBaseComponent()); super(MinecraftReflection.getIChatBaseComponentClass());
setHandle(handle); setHandle(handle);
this.cache = cache; this.cache = cache;
} }

View File

@ -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);
}
}
}

View File

@ -5,6 +5,9 @@ import static org.junit.Assert.*;
import net.minecraft.server.v1_7_R1.ChatSerializer; import net.minecraft.server.v1_7_R1.ChatSerializer;
import net.minecraft.server.v1_7_R1.IChatBaseComponent; import net.minecraft.server.v1_7_R1.IChatBaseComponent;
import net.minecraft.server.v1_7_R1.NBTCompressedStreamTools; 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.AfterClass;
import org.junit.BeforeClass; import org.junit.BeforeClass;
@ -37,11 +40,26 @@ public class MinecraftReflectionTest {
@Test @Test
public void testChatComponent() { public void testChatComponent() {
assertEquals(IChatBaseComponent.class, MinecraftReflection.getIChatBaseComponent()); assertEquals(IChatBaseComponent.class, MinecraftReflection.getIChatBaseComponentClass());
} }
@Test @Test
public void testChatSerializer() { 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());
} }
} }

View File

@ -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());
}
}