Add backwards compatibility for versions 1.0 thru 1.7.10

This commit is contained in:
Dan Mulloy 2015-03-29 22:56:11 -04:00
parent 2c743e1c89
commit addfacb19c
13 changed files with 475 additions and 257 deletions

View File

@ -163,3 +163,5 @@ pip-log.txt
# Mac crap
.DS_Store
/target
/target

View File

@ -12,7 +12,7 @@
<properties>
<project.build.sourceEncoding>cp1252</project.build.sourceEncoding>
<powermock.version>1.5</powermock.version>
<jarName>ProtocolLib</jarName>
</properties>
<distributionManagement>
@ -75,6 +75,7 @@
<exclude>org.spigotmc:spigot</exclude>
<exclude>org.spigotmc:spigot-api</exclude>
<exclude>junit:junit</exclude>
<exclude>com.google*</exclude>
</excludes>
</artifactSet>
</configuration>
@ -99,7 +100,7 @@
<archive>
<addMavenDescriptor>false</addMavenDescriptor>
</archive>
<finalName>${project.name}</finalName>
<finalName>${jarName}</finalName>
</configuration>
</plugin>
@ -186,6 +187,62 @@
</plugins>
</build>
</profile>
<profile>
<id>backwards-compat</id>
<properties>
<jarName>ProtocolLib-Legacy</jarName>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<shadedArtifactAttached>false</shadedArtifactAttached>
<createDependencyReducedPom>false</createDependencyReducedPom>
<relocations>
<relocation>
<pattern>net.sf</pattern>
<shadedPattern>com.comphenix.net.sf</shadedPattern>
</relocation>
<relocation>
<pattern>com.google.common</pattern>
<shadedPattern>com.comphenix.protocol.compat.com.google.common</shadedPattern>
</relocation>
<relocation>
<pattern>io.netty</pattern>
<shadedPattern>net.minecraft.util.io.netty</shadedPattern>
</relocation>
</relocations>
<artifactSet>
<excludes>
<exclude>org.spigotmc:spigot</exclude>
<exclude>org.spigotmc:spigot-api</exclude>
<exclude>junit:junit</exclude>
</excludes>
</artifactSet>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<scm>
@ -264,14 +321,24 @@
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
<version>1.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>${powermock.version}</version>
<version>1.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>17.0</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.0.26.Final</version>
</dependency>
</dependencies>
</project>

View File

@ -83,7 +83,7 @@ public class ProtocolLibrary extends JavaPlugin {
/**
* The minimum version ProtocolLib has been tested with.
*/
public static final String MINIMUM_MINECRAFT_VERSION = "1.8";
public static final String MINIMUM_MINECRAFT_VERSION = "1.0";
/**
* The maximum version ProtocolLib has been tested with,

View File

@ -50,9 +50,9 @@ import com.comphenix.protocol.utility.MinecraftFields;
import com.comphenix.protocol.utility.MinecraftMethods;
import com.comphenix.protocol.utility.MinecraftProtocolVersion;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.google.common.base.Preconditions;
import com.google.common.collect.MapMaker;
import com.mojang.authlib.GameProfile;
/**
* Represents a channel injector.
@ -563,13 +563,14 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
PACKET_LOGIN_CLIENT = loginClass;
}
if (loginClient == null) {
loginClient = Accessors.getFieldAccessor(PACKET_LOGIN_CLIENT, GameProfile.class, true);
loginClient = Accessors.getFieldAccessor(PACKET_LOGIN_CLIENT, MinecraftReflection.getGameProfileClass(), true);
LOGIN_GAME_PROFILE = loginClient;
}
// See if we are dealing with the login packet
if (loginClass.equals(packetClass)) {
GameProfile profile = (GameProfile) loginClient.get(packet);
// GameProfile profile = (GameProfile) loginClient.get(packet);
WrappedGameProfile profile = WrappedGameProfile.fromHandle(loginClient.get(packet));
// Save the channel injector
factory.cacheInjector(profile.getName(), this);

View File

@ -12,8 +12,10 @@ import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.injector.packet.MapContainer;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
@ -109,6 +111,12 @@ public class NettyProtocolRegistry {
private synchronized void initialize() {
Object[] protocols = enumProtocol.getEnumConstants();
// TODO: Fins a better less than 1.7 check
if (MinecraftVersion.getCurrentVersion().compareTo(MinecraftVersion.BOUNTIFUL_UPDATE) <= 0) {
initialize17();
return;
}
// ID to Packet class maps
Map<Object, Map<Integer, Class<?>>> serverMaps = Maps.newLinkedHashMap();
Map<Object, Map<Integer, Class<?>>> clientMaps = Maps.newLinkedHashMap();
@ -140,13 +148,13 @@ public class NettyProtocolRegistry {
result.containers.add(new MapContainer(map));
}
// // Heuristic - there are more server packets than client packets
// if (sum(clientMaps) > sum(serverMaps)) {
// // Swap if this is violated
// List<Map<Integer, Class<?>>> temp = serverMaps;
// serverMaps = clientMaps;
// clientMaps = temp;
// }
// Heuristic - there are more server packets than client packets
/* if (sum(clientMaps) > sum(serverMaps)) {
// Swap if this is violated
List<Map<Integer, Class<?>>> temp = serverMaps;
serverMaps = clientMaps;
clientMaps = temp;
} */
for (int i = 0; i < protocols.length; i++) {
Object protocol = protocols[i];
@ -159,6 +167,50 @@ public class NettyProtocolRegistry {
if (clientMaps.containsKey(protocol))
associatePackets(result, clientMaps.get(protocol), equivalent, Sender.CLIENT);
}
// Exchange (thread safe, as we have only one writer)
this.register = result;
}
private synchronized void initialize17() {
final Object[] protocols = enumProtocol.getEnumConstants();
List<Map<Integer, Class<?>>> serverMaps = Lists.newArrayList();
List<Map<Integer, Class<?>>> clientMaps = Lists.newArrayList();
StructureModifier<Object> modifier = null;
// Result
Register result = new Register();
for (Object protocol : protocols) {
if (modifier == null)
modifier = new StructureModifier<Object>(protocol.getClass().getSuperclass(), false);
StructureModifier<Map<Integer, Class<?>>> maps = modifier.withTarget(protocol).withType(Map.class);
serverMaps.add(maps.read(0));
clientMaps.add(maps.read(1));
}
// Maps we have to occationally check have changed
for (Map<Integer, Class<?>> map : Iterables.concat(serverMaps, clientMaps)) {
result.containers.add(new MapContainer(map));
}
// Heuristic - there are more server packets than client packets
if (sum(clientMaps) > sum(serverMaps)) {
// Swap if this is violated
List<Map<Integer, Class<?>>> temp = serverMaps;
serverMaps = clientMaps;
clientMaps = temp;
}
for (int i = 0; i < protocols.length; i++) {
Enum<?> enumProtocol = (Enum<?>) protocols[i];
Protocol equivalent = Protocol.fromVanilla(enumProtocol);
// Associate known types
associatePackets(result, serverMaps.get(i), equivalent, Sender.SERVER);
associatePackets(result, clientMaps.get(i), equivalent, Sender.CLIENT);
}
// Exchange (thread safe, as we have only one writer)
this.register = result;
}
@ -175,16 +227,16 @@ public class NettyProtocolRegistry {
}
}
// /**
// * Retrieve the number of mapping in all the maps.
// * @param maps - iterable of maps.
// * @return The sum of all the entries.
// */
// private int sum(Iterable<? extends Map<Integer, Class<?>>> maps) {
// int count = 0;
//
// for (Map<Integer, Class<?>> map : maps)
// count += map.size();
// return count;
// }
}
/**
* Retrieve the number of mapping in all the maps.
* @param maps - iterable of maps.
* @return The sum of all the entries.
*/
private int sum(Iterable<? extends Map<Integer, Class<?>>> maps) {
int count = 0;
for (Map<Integer, Class<?>> map : maps)
count += map.size();
return count;
}
}

View File

@ -1,5 +1,18 @@
/**
* (c) 2015 dmulloy2
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 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.injector.netty;

View File

@ -52,7 +52,7 @@ public final class Accessors {
* @return The field accessor.
* @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) {
// Get a field accessor
Field field = FuzzyReflection.fromClass(instanceClass, forceAccess).getFieldByType(null, fieldClass);
return Accessors.getFieldAccessor(field);
@ -65,7 +65,7 @@ public final class Accessors {
* @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) {
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()];
@ -83,7 +83,7 @@ public final class Accessors {
* @return The value of that field.
* @throws IllegalArgumentException If the field cannot be found.
*/
public static FieldAccessor getFieldAccessor(Class<?> instanceClass, String fieldName, boolean forceAccess) {
public static FieldAccessor getFieldAccessor(Class<?> instanceClass, String fieldName, boolean forceAccess) {
return Accessors.getFieldAccessor(ExactReflection.fromClass(instanceClass, true).getField(fieldName));
}
@ -120,13 +120,27 @@ public final class Accessors {
// Verify the type
if (fieldType.isAssignableFrom(accessor.getField().getType())) {
return accessor;
return accessor;
}
return null;
} catch (IllegalArgumentException e) {
return null;
}
}
/**
* Retrieve a method accessor for a field with the given name and equivalent type, or NULL.
* @param clazz - the declaration class.
* @param methodName - the method name.
* @return The method accessor, or NULL if not found.
*/
public static MethodAccessor getMethodAcccessorOrNull(Class<?> clazz, String methodName) {
try {
return Accessors.getMethodAccessor(clazz, methodName);
} catch (IllegalArgumentException e) {
return null;
}
}
/**
* Find a specific constructor in a class.
@ -145,7 +159,7 @@ public final class Accessors {
/**
* Retrieve a field accessor that will cache the content of the field.
* <p>
* Note that we don't check if the underlying field has changed after the value has been cached,
* Note that we don't check if the underlying field has changed after the value has been cached,
* so it's best to use this on final fields.
* @param inner - the accessor.
* @return A cached field accessor.

View File

@ -40,12 +40,21 @@ public class BukkitCloner implements Cloner {
List<Class<?>> classes = Lists.newArrayList();
classes.add(MinecraftReflection.getItemStackClass());
classes.add(MinecraftReflection.getBlockPositionClass());
classes.add(MinecraftReflection.getDataWatcherClass());
// Try to add position classes
try {
classes.add(MinecraftReflection.getBlockPositionClass());
} catch (Throwable ex) { }
try {
classes.add(MinecraftReflection.getChunkPositionClass());
} catch (Throwable ex) { }
if (MinecraftReflection.isUsingNetty()) {
classes.add(MinecraftReflection.getServerPingClass());
}
this.clonableClasses = classes.toArray(new Class<?>[0]);
}

View File

@ -290,7 +290,6 @@ public class DefaultInstances implements InstanceProvider {
return createInstance(type, minimum, types, params);
}
} catch (Exception e) {
// Nope, we couldn't create this type. Might for instance be NotConstructableException.
}

View File

@ -596,8 +596,17 @@ public class MinecraftReflection {
if (!isUsingNetty())
throw new IllegalStateException("GameProfile does not exist in version 1.6.4 and earlier.");
// Yay, we can actually refer to it directly
return GameProfile.class;
try {
return GameProfile.class;
} catch (Throwable ex) {
// As far as I can tell, the named entity spawn packet is the only packet that uses GameProfiles
FuzzyReflection reflection = FuzzyReflection.fromClass(PacketType.Play.Server.NAMED_ENTITY_SPAWN.getPacketClass(), true);
FuzzyFieldContract contract = FuzzyFieldContract.newBuilder()
.banModifier(Modifier.STATIC)
.typeMatches(FuzzyMatchers.matchRegex("(.*)(GameProfile)", 1))
.build();
return reflection.getField(contract).getType();
}
}
/**

View File

@ -1,5 +1,18 @@
/**
* (c) 2015 dmulloy2
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 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;

View File

@ -31,24 +31,29 @@ import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level;
import javax.annotation.Nullable;
import org.bukkit.entity.Entity;
import org.bukkit.inventory.ItemStack;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.injector.BukkitUnwrapper;
import com.comphenix.protocol.reflect.FieldAccessException;
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;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.reflect.accessors.ReadOnlyFieldAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.collection.ConvertedMap;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterators;
import com.google.common.collect.Maps;
/**
* Wraps a DataWatcher that is used to transmit arbitrary key-value pairs with a given entity.
@ -56,127 +61,127 @@ import com.google.common.collect.Iterators;
* @author Kristian
*/
public class WrappedDataWatcher extends AbstractWrapper implements Iterable<WrappedWatchableObject> {
// /**
// * Every custom watchable type in Spigot #1628 and above.
// * @author Kristian
// */
// public enum CustomType {
// BYTE_SHORT("org.spigotmc.ProtocolData$ByteShort", 0, short.class),
// DUAL_BYTE("org.spigotmc.ProtocolData$DualByte", 0, byte.class, byte.class),
// HIDDEN_BYTE("org.spigotmc.ProtocolData$HiddenByte", 0, byte.class),
// INT_BYTE("org.spigotmc.ProtocolData$IntByte", 2, int.class, byte.class),
// DUAL_INT("org.spigotmc.ProtocolData$DualInt", 2, int.class, int.class);
//
// private Class<?> spigotClass;
// private ConstructorAccessor constructor;
// private FieldAccessor secondaryValue;
// private int typeId;
//
// private CustomType(String className, int typeId, Class<?>... parameters) {
// try {
// this.spigotClass = Class.forName(className);
// this.constructor = Accessors.getConstructorAccessor(spigotClass, parameters);
// this.secondaryValue = parameters.length > 1 ? Accessors.getFieldAccessor(spigotClass, "value2", true) : null;
//
// } catch (ClassNotFoundException e) {
// ProtocolLibrary.log(Level.WARNING, "Unable to find " + className);
// this.spigotClass = null;
// }
// this.typeId = typeId;
// }
//
// /**
// * Construct a new instance of this Spigot type.
// * @param value - the value. Cannot be NULL.
// * @return The instance to construct.
// */
// Object newInstance(Object value) {
// return newInstance(value, null);
// }
//
// /**
// * Construct a new instance of this Spigot type.
// * <p>
// * The secondary value may be NULL if this custom type does not contain a secondary value.
// * @param value - the value.
// * @param secondary - optional secondary value.
// * @return
// */
// Object newInstance(Object value, Object secondary) {
// Preconditions.checkNotNull(value, "value cannot be NULL.");
//
// if (hasSecondary()) {
// return constructor.invoke(value, secondary);
// } else {
// if (secondary != null) {
// throw new IllegalArgumentException("Cannot construct " + this + " with a secondary value");
// }
// return constructor.invoke(value);
// }
// }
//
// /**
// * Set the secondary value of a given type.
// * @param instance - the instance.
// * @param secondary - the secondary value.
// */
// void setSecondary(Object instance, Object secondary) {
// if (!hasSecondary()) {
// throw new IllegalArgumentException(this + " does not have a secondary value.");
// }
// secondaryValue.set(instance, secondary);
// }
//
// /**
// * Get the secondary value of a type.
// * @param instance - the instance.
// * @return The secondary value.
// */
// Object getSecondary(Object instance) {
// if (!hasSecondary()) {
// throw new IllegalArgumentException(this + " does not have a secondary value.");
// }
// return secondaryValue.get(instance);
// }
//
// /**
// * Determine if this type has a secondary value.
// * @return TRUE if it does, FALSE otherwise.
// */
// public boolean hasSecondary() {
// return secondaryValue != null;
// }
//
// /**
// * Underlying Spigot class.
// * @return The class.
// */
// public Class<?> getSpigotClass() {
// return spigotClass;
// }
//
// /**
// * The equivalent type ID.
// * @return The equivalent ID.
// */
// public int getTypeId() {
// return typeId;
// }
//
// /**
// * Retrieve the custom Spigot type of a value.
// * @param value - the value.
// * @return The Spigot type, or NULL if not found.
// */
// public static CustomType fromValue(Object value) {
// for (CustomType type : CustomType.values()) {
// if (type.getSpigotClass().isInstance(value)) {
// return type;
// }
// }
// return null;
// }
// }
/**
* Every custom watchable type in Spigot #1628 and above.
* @author Kristian
*/
public enum CustomType {
BYTE_SHORT("org.spigotmc.ProtocolData$ByteShort", 0, short.class),
DUAL_BYTE("org.spigotmc.ProtocolData$DualByte", 0, byte.class, byte.class),
HIDDEN_BYTE("org.spigotmc.ProtocolData$HiddenByte", 0, byte.class),
INT_BYTE("org.spigotmc.ProtocolData$IntByte", 2, int.class, byte.class),
DUAL_INT("org.spigotmc.ProtocolData$DualInt", 2, int.class, int.class);
private Class<?> spigotClass;
private ConstructorAccessor constructor;
private FieldAccessor secondaryValue;
private int typeId;
private CustomType(String className, int typeId, Class<?>... parameters) {
try {
this.spigotClass = Class.forName(className);
this.constructor = Accessors.getConstructorAccessor(spigotClass, parameters);
this.secondaryValue = parameters.length > 1 ? Accessors.getFieldAccessor(spigotClass, "value2", true) : null;
} catch (ClassNotFoundException e) {
ProtocolLibrary.log(Level.WARNING, "Unable to find " + className);
this.spigotClass = null;
}
this.typeId = typeId;
}
/**
* Construct a new instance of this Spigot type.
* @param value - the value. Cannot be NULL.
* @return The instance to construct.
*/
Object newInstance(Object value) {
return newInstance(value, null);
}
/**
* Construct a new instance of this Spigot type.
* <p>
* The secondary value may be NULL if this custom type does not contain a secondary value.
* @param value - the value.
* @param secondary - optional secondary value.
* @return
*/
Object newInstance(Object value, Object secondary) {
Preconditions.checkNotNull(value, "value cannot be NULL.");
if (hasSecondary()) {
return constructor.invoke(value, secondary);
} else {
if (secondary != null) {
throw new IllegalArgumentException("Cannot construct " + this + " with a secondary value");
}
return constructor.invoke(value);
}
}
/**
* Set the secondary value of a given type.
* @param instance - the instance.
* @param secondary - the secondary value.
*/
void setSecondary(Object instance, Object secondary) {
if (!hasSecondary()) {
throw new IllegalArgumentException(this + " does not have a secondary value.");
}
secondaryValue.set(instance, secondary);
}
/**
* Get the secondary value of a type.
* @param instance - the instance.
* @return The secondary value.
*/
Object getSecondary(Object instance) {
if (!hasSecondary()) {
throw new IllegalArgumentException(this + " does not have a secondary value.");
}
return secondaryValue.get(instance);
}
/**
* Determine if this type has a secondary value.
* @return TRUE if it does, FALSE otherwise.
*/
public boolean hasSecondary() {
return secondaryValue != null;
}
/**
* Underlying Spigot class.
* @return The class.
*/
public Class<?> getSpigotClass() {
return spigotClass;
}
/**
* The equivalent type ID.
* @return The equivalent ID.
*/
public int getTypeId() {
return typeId;
}
/**
* Retrieve the custom Spigot type of a value.
* @param value - the value.
* @return The Spigot type, or NULL if not found.
*/
public static CustomType fromValue(Object value) {
for (CustomType type : CustomType.values()) {
if (type.getSpigotClass().isInstance(value)) {
return type;
}
}
return null;
}
}
/**
* Used to assign integer IDs to given types.
@ -601,20 +606,20 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable<Wrap
}
}
// /**
// * Set a watched byte with an optional secondary value.
// * @param index - index of the watched byte.
// * @param newValue - the new watched value.
// * @param secondary - optional secondary value.
// * @param update - whether or not to refresh every listening client.
// * @throws FieldAccessException Cannot read underlying field.
// */
// public void setObject(int index, Object newValue, Object secondary, boolean update, CustomType type) throws FieldAccessException {
// Object created = type.newInstance(newValue, secondary);
//
// // Now update the watcher
// setObject(index, created, update);
// }
/**
* Set a watched byte with an optional secondary value.
* @param index - index of the watched byte.
* @param newValue - the new watched value.
* @param secondary - optional secondary value.
* @param update - whether or not to refresh every listening client.
* @throws FieldAccessException Cannot read underlying field.
*/
public void setObject(int index, Object newValue, Object secondary, boolean update, CustomType type) throws FieldAccessException {
Object created = type.newInstance(newValue, secondary);
// Now update the watcher
setObject(index, created, update);
}
private Object getWatchedObject(int index) throws FieldAccessException {
// We use the get-method first and foremost
@ -718,7 +723,7 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable<Wrap
initializeSpigot(fuzzy);
// Any custom types
// CUSTOM_MAP = initializeCustom();
CUSTOM_MAP = initializeCustom();
// Initialize static type type
TYPE_MAP = (Map<Class<?>, Integer>) TYPE_MAP_ACCESSOR.get(null);
@ -737,17 +742,17 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable<Wrap
initializeMethods(fuzzy);
}
// // For Spigot's bountiful update patch
// private static Map<Class<?>, Integer> initializeCustom() {
// Map<Class<?>, Integer> map = Maps.newHashMap();
//
// for (CustomType type : CustomType.values()) {
// if (type.getSpigotClass() != null) {
// map.put(type.getSpigotClass(), type.getTypeId());
// }
// }
// return map;
// }
// For Spigot's bountiful update patch
private static Map<Class<?>, Integer> initializeCustom() {
Map<Class<?>, Integer> map = Maps.newHashMap();
for (CustomType type : CustomType.values()) {
if (type.getSpigotClass() != null) {
map.put(type.getSpigotClass(), type.getTypeId());
}
}
return map;
}
// TODO: Remove, as this was fixed in build #1189 of Spigot
private static void initializeSpigot(FuzzyReflection fuzzy) {

View File

@ -14,13 +14,12 @@ import com.comphenix.protocol.injector.BukkitUnwrapper;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.collection.ConvertedMultimap;
import com.google.common.base.Charsets;
import com.google.common.base.Objects;
import com.google.common.collect.Multimap;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
/**
* Represents a wrapper for a game profile.
@ -28,62 +27,79 @@ import com.mojang.authlib.properties.Property;
*/
public class WrappedGameProfile extends AbstractWrapper {
public static final ReportType REPORT_INVALID_UUID = new ReportType("Plugin %s created a profile with '%s' as an UUID.");
// Version 1.7.2 and 1.7.8 respectively
private static final ConstructorAccessor CREATE_STRING_STRING = Accessors.getConstructorAccessorOrNull(GameProfile.class, String.class, String.class);
private static final FieldAccessor GET_UUID_STRING = Accessors.getFieldAcccessorOrNull(GameProfile.class, "id", String.class);
private static final Class<?> GAME_PROFILE = MinecraftReflection.getGameProfileClass();
private static final ConstructorAccessor CREATE_STRING_STRING = Accessors.getConstructorAccessorOrNull(
GAME_PROFILE, String.class, String.class);
private static final ConstructorAccessor CREATE_UUID_STRING = Accessors.getConstructorAccessorOrNull(
GAME_PROFILE, UUID.class, String.class);
private static final FieldAccessor GET_UUID_STRING = Accessors.getFieldAcccessorOrNull(
GAME_PROFILE, "id", String.class);
private static final MethodAccessor GET_ID = Accessors.getMethodAcccessorOrNull(
GAME_PROFILE, "getId");
private static final MethodAccessor GET_NAME = Accessors.getMethodAcccessorOrNull(
GAME_PROFILE, "getName");
private static final MethodAccessor GET_PROPERTIES = Accessors.getMethodAcccessorOrNull(
GAME_PROFILE, "getProperties");
private static final MethodAccessor IS_COMPLETE = Accessors.getMethodAcccessorOrNull(
GAME_PROFILE, "isComplete");
// Fetching game profile
private static FieldAccessor PLAYER_PROFILE;
private static FieldAccessor OFFLINE_PROFILE;
// Property map
private Multimap<String, WrappedSignedProperty> propertyMap;
// Parsed UUID
private volatile UUID parsedUUID;
// Profile from a handle
private WrappedGameProfile(Object profile) {
super(GameProfile.class);
super(GAME_PROFILE);
setHandle(profile);
}
/**
* Retrieve the associated game profile of a player.
* <p>
* Note that this may not exist in the current Minecraft version.
*
* @param player - the player.
* @return The game profile.
*/
public static WrappedGameProfile fromPlayer(Player player) {
FieldAccessor accessor = PLAYER_PROFILE;
Object nmsPlayer = BukkitUnwrapper.getInstance().unwrapItem(player);
if (accessor == null) {
accessor = Accessors.getFieldAccessor(MinecraftReflection.getEntityHumanClass(), GameProfile.class, true);
accessor = Accessors.getFieldAccessor(MinecraftReflection.getEntityHumanClass(), GAME_PROFILE, true);
PLAYER_PROFILE = accessor;
}
Object nmsPlayer = BukkitUnwrapper.getInstance().unwrapItem(player);
return WrappedGameProfile.fromHandle(PLAYER_PROFILE.get(nmsPlayer));
}
/**
* Retrieve the associated game profile of an offline player.
* <p>
* Note that this may not exist in the current Minecraft version.
*
* @param player - the offline player.
* @return The game profile.
*/
public static WrappedGameProfile fromOfflinePlayer(OfflinePlayer player) {
FieldAccessor accessor = OFFLINE_PROFILE;
if (accessor == null) {
accessor = Accessors.getFieldAccessor(player.getClass(), GameProfile.class, true);
accessor = Accessors.getFieldAccessor(player.getClass(), GAME_PROFILE, true);
OFFLINE_PROFILE = accessor;
}
return WrappedGameProfile.fromHandle(OFFLINE_PROFILE.get(player));
}
/**
* Construct a new game profile with the given properties.
* <p>
@ -91,49 +107,58 @@ public class WrappedGameProfile extends AbstractWrapper {
* IDs that cannot be parsed as an UUID will be hashed and form a version 3 UUID instead.
* <p>
* This method is deprecated for Minecraft 1.7.8 and above.
*
* @param id - the UUID of the player.
* @param name - the name of the player.
*/
@Deprecated
public WrappedGameProfile(String id, String name) {
super(GameProfile.class);
super(GAME_PROFILE);
if (CREATE_STRING_STRING != null) {
setHandle(CREATE_STRING_STRING.invoke(id, name));
} else if (CREATE_UUID_STRING != null) {
setHandle(CREATE_UUID_STRING.invoke(parseUUID(id), name));
} else {
setHandle(new GameProfile(parseUUID(id), name));
throw new IllegalArgumentException("Unsupported GameProfile constructor.");
}
}
/**
* Construct a new game profile with the given properties.
* <p>
* Note that at least one of the parameters must be non-null.
*
* @param uuid - the UUID of the player, or NULL.
* @param name - the name of the player, or NULL.
*/
public WrappedGameProfile(UUID uuid, String name) {
super(GameProfile.class);
super(GAME_PROFILE);
if (CREATE_STRING_STRING != null) {
setHandle(CREATE_STRING_STRING.invoke(uuid != null ? uuid.toString() : null, name));
} else if (CREATE_UUID_STRING != null) {
setHandle(CREATE_UUID_STRING.invoke(uuid, name));
} else {
setHandle(new GameProfile(uuid, name));
throw new IllegalArgumentException("Unsupported GameProfile constructor.");
}
}
/**
* Construct a wrapper around an existing game profile.
*
* @param profile - the underlying profile, or NULL.
*/
public static WrappedGameProfile fromHandle(Object handle) {
if (handle == null)
return null;
return new WrappedGameProfile(handle);
}
/**
* Parse an UUID using very lax rules, as specified in {@link #WrappedGameProfile(UUID, String)}.
*
* @param id - text.
* @return The corresponding UUID.
* @throws IllegalArgumentException If we cannot parse the text.
@ -143,13 +168,10 @@ public class WrappedGameProfile extends AbstractWrapper {
return id != null ? UUID.fromString(id) : null;
} catch (IllegalArgumentException e) {
// Warn once every hour (per plugin)
ProtocolLibrary.getErrorReporter().reportWarning(
WrappedGameProfile.class,
Report.newBuilder(REPORT_INVALID_UUID).
rateLimit(1, TimeUnit.HOURS).
messageParam(PluginContext.getPluginCaller(new Exception()), id)
);
ProtocolLibrary.getErrorReporter()
.reportWarning(WrappedGameProfile.class, Report.newBuilder(REPORT_INVALID_UUID)
.rateLimit(1, TimeUnit.HOURS)
.messageParam(PluginContext.getPluginCaller(new Exception()), id));
return UUID.nameUUIDFromBytes(id.getBytes(Charsets.UTF_8));
}
}
@ -157,70 +179,86 @@ public class WrappedGameProfile extends AbstractWrapper {
/**
* Retrieve the UUID of the player.
* <p>
* Note that Minecraft 1.7.5 and earlier doesn't use UUIDs internally, and it may not be possible
* to convert the string to an UUID.
* Note that Minecraft 1.7.5 and earlier doesn't use UUIDs internally, and it may not be possible to convert the string to an UUID.
* <p>
* We use the same lax conversion as in {@link #WrappedGameProfile(String, String)}.
*
* @return The UUID, or NULL if the UUID is NULL.
* @throws IllegalStateException If we cannot parse the internal ID as an UUID.
*/
public UUID getUUID() {
UUID uuid = parsedUUID;
if (uuid == null) {
try {
if (GET_UUID_STRING != null) {
uuid = parseUUID(getId());
} else if (GET_ID != null) {
uuid = (UUID) GET_ID.invoke(handle);
} else {
uuid = getProfile().getId();
throw new IllegalStateException("Unsupported getId() method");
}
// Cache for later
parsedUUID = uuid;
} catch (IllegalArgumentException e) {
throw new IllegalStateException("Cannot parse ID " + getId() + " as an UUID in player profile " + getName());
throw new IllegalStateException("Cannot parse ID " + getId() + " as an UUID in player profile " + getName(), e);
}
}
return uuid;
}
/**
* Retrieve the textual representation of the player's UUID.
* <p>
* Note that there's nothing stopping plugins from creating non-standard UUIDs.
* <p>
* In Minecraft 1.7.8 and later, this simply returns the string form of {@link #getUUID()}.
*
* @return The UUID of the player, or NULL if not computed.
*/
public String getId() {
if (GET_UUID_STRING != null)
if (GET_UUID_STRING != null) {
return (String) GET_UUID_STRING.get(handle);
final GameProfile profile = getProfile();
return profile.getId() != null ? profile.getId().toString() : null;
} else if (GET_ID != null) {
UUID uuid = (UUID) GET_ID.invoke(handle);
return uuid != null ? uuid.toString() : null;
} else {
throw new IllegalStateException("Unsupported getId() method");
}
}
/**
* Retrieve the name of the player.
*
* @return The player name.
*/
public String getName() {
return getProfile().getName();
if (GET_NAME != null) {
return (String) GET_NAME.invoke(handle);
} else {
throw new IllegalStateException("Unsupported getName() method");
}
}
/**
* Retrieve the property map of signed values.
*
* @return Property map.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public Multimap<String, WrappedSignedProperty> getProperties() {
Multimap<String, WrappedSignedProperty> result = propertyMap;
if (result == null) {
result = new ConvertedMultimap<String, Property, WrappedSignedProperty>(
GuavaWrappers.getBukkitMultimap(getProfile().getProperties())) {
Multimap properties = (Multimap) GET_PROPERTIES.invoke(handle);
result = new ConvertedMultimap<String, Object, WrappedSignedProperty>(GuavaWrappers.getBukkitMultimap(properties)) {
@Override
protected Property toInner(WrappedSignedProperty outer) {
return (Property) outer.handle;
protected Object toInner(WrappedSignedProperty outer) {
return outer.handle;
}
@Override
protected Object toInnerObject(Object outer) {
if (outer instanceof WrappedSignedProperty) {
@ -228,9 +266,9 @@ public class WrappedGameProfile extends AbstractWrapper {
}
return outer;
}
@Override
protected WrappedSignedProperty toOuter(Property inner) {
protected WrappedSignedProperty toOuter(Object inner) {
return WrappedSignedProperty.fromHandle(inner);
}
};
@ -238,61 +276,57 @@ public class WrappedGameProfile extends AbstractWrapper {
}
return result;
}
/**
* Retrieve the underlying GameProfile.
* @return The GameProfile.
*/
private GameProfile getProfile() {
return (GameProfile) handle;
}
/**
* Construct a new game profile with the same ID, but different name.
*
* @param name - the new name of the profile to create.
* @return The new game profile.
*/
public WrappedGameProfile withName(String name) {
return new WrappedGameProfile(getId(), name);
}
/**
* Construct a new game profile with the same name, but different id.
*
* @param id - the new id of the profile to create.
* @return The new game profile.
*/
public WrappedGameProfile withId(String id) {
return new WrappedGameProfile(id, getName());
}
/**
* Determine if the game profile contains both an UUID and a name.
*
* @return TRUE if it does, FALSE otherwise.
*/
public boolean isComplete() {
return getProfile().isComplete();
return (Boolean) IS_COMPLETE.invoke(handle);
}
@Override
public String toString() {
return String.valueOf(getProfile());
return String.valueOf(getHandle());
}
@Override
public int hashCode() {
// Mojang's hashCode() is broken, it doesn't handle NULL id or name. So we implement our own
return Objects.hashCode(getId(), getName());
}
@Override
public boolean equals(Object obj) {
if (obj == this)
return true;
if (obj instanceof WrappedGameProfile) {
WrappedGameProfile other = (WrappedGameProfile) obj;
return Objects.equal(getProfile(), other.getProfile());
return Objects.equal(getHandle(), other.getHandle());
}
return false;
}
}
}