diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java index f356e5e2..eb79d630 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java @@ -25,6 +25,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.net.InetAddress; import java.net.ServerSocket; import java.util.HashSet; import java.util.List; @@ -84,13 +85,18 @@ public class MinecraftReflection { public static final ReportType REPORT_CANNOT_LOAD_CPC_REMAPPER = new ReportType("Unable to load MCPC remapper."); public static final ReportType REPORT_NON_CRAFTBUKKIT_LIBRARY_PACKAGE = new ReportType("Cannot find standard Minecraft library location. Assuming MCPC."); + /** + * Regular expression that matches a canonical Java class. + */ + private static final String CANONICAL_REGEX = "(\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*\\.)+\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"; + /** * Regular expression that matches a Minecraft object. *

* Replaced by the method {@link #getMinecraftObjectRegex()}. */ @Deprecated - public static final String MINECRAFT_OBJECT = "net\\.minecraft(\\.\\w+)+"; + public static final String MINECRAFT_OBJECT = "net\\.minecraft\\." + CANONICAL_REGEX; /** * Regular expression computed dynamically. @@ -213,6 +219,15 @@ public class MinecraftReflection { Matcher packageMatcher = PACKAGE_VERSION_MATCHER.matcher(CRAFTBUKKIT_PACKAGE); if (packageMatcher.matches()) { packageVersion = packageMatcher.group(1); + } else { + MinecraftVersion version = new MinecraftVersion(craftServer); + + // See if we need a package version + if (MinecraftVersion.SCARY_UPDATE.compareTo(version) <= 0) { + // Just assume R1 - it's probably fine + packageVersion = "v" + version.getMajor() + "_" + version.getMinor() + "_R1"; + System.err.println("[ProtocolLib] Assuming package version: " + packageVersion); + } } // Libigot patch @@ -241,7 +256,7 @@ public class MinecraftReflection { // The package is usualy flat, so go with that assumption String matcher = (MINECRAFT_PREFIX_PACKAGE.length() > 0 ? - Pattern.quote(MINECRAFT_PREFIX_PACKAGE + ".") : "") + "\\w+"; + Pattern.quote(MINECRAFT_PREFIX_PACKAGE + ".") : "") + CANONICAL_REGEX; // We'll still accept the default location, however setDynamicPackageMatcher("(" + matcher + ")|(" + MINECRAFT_OBJECT + ")"); @@ -266,7 +281,7 @@ public class MinecraftReflection { throw new IllegalStateException("Could not find Bukkit. Is it running?"); } } - + /** * Retrieve the Minecraft library package string. * @return The library package. @@ -1249,11 +1264,11 @@ public class MinecraftReflection { public static Class getWatchableObjectClass() { try { return getMinecraftClass("WatchableObject"); - } catch (RuntimeException e) { + } catch (RuntimeException e) { Method selected = FuzzyReflection.fromClass(getDataWatcherClass(), true). getMethod(FuzzyMethodContract.newBuilder(). requireModifier(Modifier.STATIC). - parameterDerivedOf(DataOutput.class, 0). + parameterDerivedOf(isUsingNetty() ? getPacketDataSerializerClass() : DataOutput.class, 0). parameterMatches(getMinecraftObjectMatcher(), 1). build()); @@ -1270,19 +1285,37 @@ public class MinecraftReflection { try { return getMinecraftClass("ServerConnection"); } catch (RuntimeException e) { - FuzzyClassContract serverConnectionContract = FuzzyClassContract.newBuilder(). + Method selected = null; + FuzzyClassContract.Builder serverConnectionContract = FuzzyClassContract.newBuilder(). constructor(FuzzyMethodContract.newBuilder(). parameterExactType(getMinecraftServerClass()). - parameterCount(1)). - method(FuzzyMethodContract.newBuilder(). - parameterExactType(getNetServerHandlerClass())). - build(); + parameterCount(1)); - Method selected = FuzzyReflection.fromClass(getMinecraftServerClass()). - getMethod(FuzzyMethodContract.newBuilder(). - requireModifier(Modifier.ABSTRACT). - returnTypeMatches(serverConnectionContract). - build()); + if (isUsingNetty()) { + serverConnectionContract. + method(FuzzyMethodContract.newBuilder(). + parameterDerivedOf(InetAddress.class, 0). + parameterDerivedOf(int.class, 1). + parameterCount(2) + ); + + selected = FuzzyReflection.fromClass(getMinecraftServerClass()). + getMethod(FuzzyMethodContract.newBuilder(). + requireModifier(Modifier.PUBLIC). + returnTypeMatches(serverConnectionContract.build()). + build()); + + } else { + serverConnectionContract. + method(FuzzyMethodContract.newBuilder(). + parameterExactType(getNetServerHandlerClass())); + + selected = FuzzyReflection.fromClass(getMinecraftServerClass()). + getMethod(FuzzyMethodContract.newBuilder(). + requireModifier(Modifier.ABSTRACT). + returnTypeMatches(serverConnectionContract.build()). + build()); + } // Use the return type return setMinecraftClass("ServerConnection", selected.getReturnType()); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/RemappedClassSource.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/RemappedClassSource.java index 1672883b..32caec24 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/RemappedClassSource.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/RemappedClassSource.java @@ -98,7 +98,7 @@ class RemappedClassSource extends ClassSource { * @param path - the canonical class name. * @return The obfuscated class name. */ - private String getClassName(String path) { + public String getClassName(String path) { try { String remapped = (String) mapType.invoke(classRemapper, path.replace('.', '/')); return remapped.replace('/', '.'); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/TileEntityAccessor.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/TileEntityAccessor.java index 1bfed734..6ca96f7c 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/TileEntityAccessor.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/TileEntityAccessor.java @@ -1,20 +1,27 @@ package com.comphenix.protocol.wrappers.nbt; import java.io.IOException; +import java.lang.reflect.Method; import java.util.concurrent.ConcurrentMap; import net.minecraft.server.v1_7_R3.NBTTagCompound; +import net.minecraft.server.v1_7_R3.TileEntityChest; import net.sf.cglib.asm.ClassReader; import net.sf.cglib.asm.MethodVisitor; import net.sf.cglib.asm.Opcodes; +import net.sf.cglib.proxy.Enhancer; +import net.sf.cglib.proxy.MethodInterceptor; +import net.sf.cglib.proxy.MethodProxy; import org.bukkit.block.BlockState; +import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.FieldAccessor; import com.comphenix.protocol.reflect.accessors.MethodAccessor; import com.comphenix.protocol.reflect.compiler.EmptyClassVisitor; import com.comphenix.protocol.reflect.compiler.EmptyMethodVisitor; +import com.comphenix.protocol.utility.EnhancerFactory; import com.comphenix.protocol.utility.MinecraftReflection; import com.google.common.collect.Maps; @@ -37,6 +44,10 @@ class TileEntityAccessor { private MethodAccessor readCompound; private MethodAccessor writeCompound; + // For CGLib detection + private boolean writeDetected; + private boolean readDetected; + private TileEntityAccessor() { // Do nothing } @@ -45,18 +56,31 @@ class TileEntityAccessor { * Construct a new tile entity accessor. * @param tileEntityField - the tile entity field. * @param tileEntity - the tile entity. + * @param tile - the block state. * @throws IOException Cannot read tile entity. */ - private TileEntityAccessor(FieldAccessor tileEntityField) { + private TileEntityAccessor(FieldAccessor tileEntityField, T state) { if (tileEntityField != null) { this.tileEntityField = tileEntityField; + Class type = tileEntityField.getField().getType(); // Possible read/write methods try { - findSerializationMethods(tileEntityField.getField().getType()); - } catch (IOException e) { - throw new RuntimeException("Cannot find read/write methods.", e); + findMethodsUsingASM(type); + } catch (IOException ex1) { + try { + // Much slower though + findMethodUsingCGLib(state); + } catch (Exception ex2) { + throw new RuntimeException("Cannot find read/write methods in " + type, ex2); + } } + + // Ensure we found them + if (readCompound == null) + throw new RuntimeException("Unable to find read method in " + type); + if (writeCompound == null) + throw new RuntimeException("Unable to find write method in " + type); } } @@ -66,11 +90,11 @@ class TileEntityAccessor { * @param nbtCompoundClass - the compound clas. * @throws IOException If we cannot find these methods. */ - private void findSerializationMethods(final Class tileEntityClass) throws IOException { + private void findMethodsUsingASM(final Class tileEntityClass) throws IOException { final Class nbtCompoundClass = MinecraftReflection.getNBTCompoundClass(); - final ClassReader reader = new ClassReader(tileEntityClass.getCanonicalName()); - final String tagCompoundName = getJarName(NBTTagCompound.class); + + final String tagCompoundName = getJarName(MinecraftReflection.getNBTCompoundClass()); final String expectedDesc = "(L" + tagCompoundName + ";)V"; reader.accept(new EmptyClassVisitor() { @@ -113,12 +137,51 @@ class TileEntityAccessor { return null; } }, 0); + } + + /** + * Find the read/write methods in TileEntity. + * @param blockState - the block state. + * @throws IOException If we cannot find these methods. + */ + private void findMethodUsingCGLib(T blockState) throws IOException { + final Class nbtCompoundClass = MinecraftReflection.getNBTCompoundClass(); - // Ensure we found them - if (readCompound == null) - throw new RuntimeException("Unable to find read method in " + tileEntityClass); - if (writeCompound == null) - throw new RuntimeException("Unable to find write method in " + tileEntityClass); + // This is a much slower method, but it is necessary in MCPC + Enhancer enhancer = EnhancerFactory.getInstance().createEnhancer(); + enhancer.setSuperclass(nbtCompoundClass); + enhancer.setCallback(new MethodInterceptor() { + @Override + public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { + if (method.getReturnType().equals(Void.TYPE)) { + // Write method + writeDetected = true; + } else { + // Read method + readDetected = true; + } + throw new RuntimeException("Stop execution."); + } + }); + Object compound = enhancer.create(); + Object tileEntity = tileEntityField.get(blockState); + + // Look in every read/write like method + for (Method method : FuzzyReflection.fromObject(tileEntity, true). + getMethodListByParameters(Void.TYPE, new Class[] { nbtCompoundClass })) { + + try { + readDetected = false; + writeDetected = false; + method.invoke(tileEntity, compound); + } catch (Exception e) { + // Okay - see if we detected a write or read + if (readDetected) + readCompound = Accessors.getMethodAccessor(method, true); + if (writeDetected) + writeCompound = Accessors.getMethodAccessor(method, true); + } + } } /** @@ -177,7 +240,7 @@ class TileEntityAccessor { created = EMPTY_ACCESSOR; } if (field != null) { - created = new TileEntityAccessor(field); + created = new TileEntityAccessor(field, state); } accessor = cachedAccessors.putIfAbsent(craftBlockState, created); diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/utility/MinecraftReflectionTest.java b/ProtocolLib/src/test/java/com/comphenix/protocol/utility/MinecraftReflectionTest.java index e1fca608..d5404849 100644 --- a/ProtocolLib/src/test/java/com/comphenix/protocol/utility/MinecraftReflectionTest.java +++ b/ProtocolLib/src/test/java/com/comphenix/protocol/utility/MinecraftReflectionTest.java @@ -11,6 +11,7 @@ import net.minecraft.server.v1_7_R3.NBTCompressedStreamTools; import net.minecraft.server.v1_7_R3.ServerPing; import net.minecraft.server.v1_7_R3.ServerPingPlayerSample; import net.minecraft.server.v1_7_R3.ServerPingServerData; +import net.minecraft.server.v1_7_R3.WatchableObject; import org.bukkit.block.Block; import org.bukkit.entity.Entity; @@ -104,4 +105,9 @@ public class MinecraftReflectionTest { public void testChunkCoordIntPair() { assertEquals(ChunkCoordIntPair.class, MinecraftReflection.getChunkCoordIntPair()); } + + @Test + public void testWatchableObject() { + assertEquals(WatchableObject.class, MinecraftReflection.getWatchableObjectClass()); + } }