Adding partial support for MCPC-Plus 1.7.2.

This doesn't include handling the different package names of 
net.minecraft.util.io.netty in MCPC.
This commit is contained in:
Kristian S. Stangeland 2014-05-03 19:13:20 +02:00
parent 71ce362c8e
commit 8a2e696363
4 changed files with 131 additions and 29 deletions

View File

@ -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.
* <p>
* 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());

View File

@ -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('/', '.');

View File

@ -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<T extends BlockState> {
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<T extends BlockState> {
* 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<T extends BlockState> {
* @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<T extends BlockState> {
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<T extends BlockState> {
created = EMPTY_ACCESSOR;
}
if (field != null) {
created = new TileEntityAccessor<T>(field);
created = new TileEntityAccessor<T>(field, state);
}
accessor = cachedAccessors.putIfAbsent(craftBlockState, created);

View File

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