mirror of
https://github.com/dmulloy2/ProtocolLib.git
synced 2024-11-05 02:10:14 +01:00
Use the MCPC JAR remapper when loading classes. Fixes #11
This will allow plugins to use MinecraftReflection.getMinecraftClass() in both CraftBukkit and MCPC.
This commit is contained in:
parent
8c8ca3746b
commit
4be582ef87
@ -19,6 +19,7 @@ package com.comphenix.protocol.utility;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
/**
|
||||
@ -27,12 +28,19 @@ import com.google.common.collect.Maps;
|
||||
* @author Kristian
|
||||
*/
|
||||
class CachedPackage {
|
||||
private Map<String, Class<?>> cache;
|
||||
private String packageName;
|
||||
private final Map<String, Class<?>> cache;
|
||||
private final String packageName;
|
||||
private final ClassSource source;
|
||||
|
||||
public CachedPackage(String packageName) {
|
||||
/**
|
||||
* Construct a new cached package.
|
||||
* @param packageName - the name of the current package.
|
||||
* @param source - the class source.
|
||||
*/
|
||||
public CachedPackage(String packageName, ClassSource source) {
|
||||
this.packageName = packageName;
|
||||
this.cache = Maps.newConcurrentMap();
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -57,13 +65,11 @@ class CachedPackage {
|
||||
// Concurrency is not a problem - we don't care if we look up a class twice
|
||||
if (result == null) {
|
||||
// Look up the class dynamically
|
||||
result = CachedPackage.class.getClassLoader().
|
||||
loadClass(combine(packageName, className));
|
||||
result = source.loadClass(combine(packageName, className));
|
||||
cache.put(className, result);
|
||||
}
|
||||
|
||||
}
|
||||
return result;
|
||||
|
||||
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new RuntimeException("Cannot find class " + className, e);
|
||||
}
|
||||
@ -75,9 +81,11 @@ class CachedPackage {
|
||||
* @param className - the class name.
|
||||
* @return We full class path.
|
||||
*/
|
||||
private String combine(String packageName, String className) {
|
||||
if (packageName.length() == 0)
|
||||
public static String combine(String packageName, String className) {
|
||||
if (Strings.isNullOrEmpty(packageName))
|
||||
return className;
|
||||
if (Strings.isNullOrEmpty(className))
|
||||
return packageName;
|
||||
return packageName + "." + className;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,37 @@
|
||||
package com.comphenix.protocol.utility;
|
||||
|
||||
/**
|
||||
* Represents an abstract class loader that can only retrieve classes by their canonical name.
|
||||
* @author Kristian
|
||||
*/
|
||||
abstract class ClassSource {
|
||||
/**
|
||||
* Construct a class source from the current class loader.
|
||||
* @return A package source.
|
||||
*/
|
||||
public static ClassSource fromClassLoader() {
|
||||
return fromClassLoader(ClassSource.class.getClassLoader());
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a class source from the given class loader.
|
||||
* @param loader - the class loader.
|
||||
* @return The corresponding package source.
|
||||
*/
|
||||
public static ClassSource fromClassLoader(final ClassLoader loader) {
|
||||
return new ClassSource() {
|
||||
@Override
|
||||
public Class<?> loadClass(String canonicalName) throws ClassNotFoundException {
|
||||
return loader.loadClass(canonicalName);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a class by name.
|
||||
* @param canonicalName - the full canonical name of the class.
|
||||
* @return The corresponding class
|
||||
* @throws ClassNotFoundException If the class could not be found.
|
||||
*/
|
||||
public abstract Class<?> loadClass(String canonicalName) throws ClassNotFoundException;
|
||||
}
|
@ -30,6 +30,7 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
@ -42,6 +43,10 @@ import org.bukkit.Bukkit;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.injector.BukkitUnwrapper;
|
||||
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
@ -52,6 +57,8 @@ import com.comphenix.protocol.reflect.fuzzy.FuzzyClassContract;
|
||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
|
||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyMatchers;
|
||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
|
||||
import com.comphenix.protocol.utility.RemappedClassSource.RemapperUnavaibleException;
|
||||
import com.comphenix.protocol.utility.RemappedClassSource.RemapperUnavaibleException.Reason;
|
||||
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
|
||||
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
|
||||
import com.comphenix.protocol.wrappers.nbt.NbtType;
|
||||
@ -63,6 +70,9 @@ import com.google.common.base.Joiner;
|
||||
* @author Kristian
|
||||
*/
|
||||
public class MinecraftReflection {
|
||||
public static final ReportType REPORT_CANNOT_FIND_MCPC_REMAPPER = new ReportType("Cannot find MCPC remapper.");
|
||||
public static final ReportType REPORT_CANNOT_LOAD_CPC_REMAPPER = new ReportType("Unable to load MCPC remapper.");
|
||||
|
||||
/**
|
||||
* Regular expression that matches a Minecraft object.
|
||||
* <p>
|
||||
@ -76,11 +86,22 @@ public class MinecraftReflection {
|
||||
*/
|
||||
private static String DYNAMIC_PACKAGE_MATCHER = null;
|
||||
|
||||
/**
|
||||
* The Entity package in Forge 1.5.2
|
||||
*/
|
||||
private static final String FORGE_ENTITY_PACKAGE = "net.minecraft.entity";
|
||||
|
||||
/**
|
||||
* The package name of all the classes that belongs to the native code in Minecraft.
|
||||
*/
|
||||
private static String MINECRAFT_PREFIX_PACKAGE = "net.minecraft.server";
|
||||
|
||||
/**
|
||||
* Represents a regular expression that will match the version string in a package:
|
||||
* org.bukkit.craftbukkit.v1_6_R2 -> v1_6_R2
|
||||
*/
|
||||
private static final Pattern PACKAGE_VERSION_MATCHER = Pattern.compile(".*\\.(v\\d+_\\d+_\\w*\\d+)");
|
||||
|
||||
private static String MINECRAFT_FULL_PACKAGE = null;
|
||||
private static String CRAFTBUKKIT_PACKAGE = null;
|
||||
|
||||
@ -99,9 +120,15 @@ public class MinecraftReflection {
|
||||
private static Method craftBukkitMethod;
|
||||
private static boolean craftItemStackFailed;
|
||||
|
||||
// The NMS version
|
||||
private static String packageVersion;
|
||||
|
||||
// net.minecraft.server
|
||||
private static Class<?> itemStackArrayClass;
|
||||
|
||||
// The current class source
|
||||
private static ClassSource classSource;
|
||||
|
||||
/**
|
||||
* Whether or not we're currently initializing the reflection handler.
|
||||
*/
|
||||
@ -152,6 +179,12 @@ public class MinecraftReflection {
|
||||
Class<?> craftClass = craftServer.getClass();
|
||||
CRAFTBUKKIT_PACKAGE = getPackage(craftClass.getCanonicalName());
|
||||
|
||||
// Parse the package version
|
||||
Matcher packageMatcher = PACKAGE_VERSION_MATCHER.matcher(CRAFTBUKKIT_PACKAGE);
|
||||
if (packageMatcher.matches()) {
|
||||
packageVersion = packageMatcher.group(1);
|
||||
}
|
||||
|
||||
// Libigot patch
|
||||
handleLibigot();
|
||||
|
||||
@ -161,12 +194,18 @@ public class MinecraftReflection {
|
||||
|
||||
MINECRAFT_FULL_PACKAGE = getPackage(getHandle.getReturnType().getCanonicalName());
|
||||
|
||||
// Pretty important invariant
|
||||
// Pretty important invariantt
|
||||
if (!MINECRAFT_FULL_PACKAGE.startsWith(MINECRAFT_PREFIX_PACKAGE)) {
|
||||
// Assume they're the same instead
|
||||
MINECRAFT_PREFIX_PACKAGE = MINECRAFT_FULL_PACKAGE;
|
||||
|
||||
// The package is usualy flat, so go with that assumtion
|
||||
// See if we got the Forge entity package
|
||||
if (MINECRAFT_FULL_PACKAGE.equals(FORGE_ENTITY_PACKAGE)) {
|
||||
// USe the standard NMS versioned package
|
||||
MINECRAFT_FULL_PACKAGE = CachedPackage.combine(MINECRAFT_PREFIX_PACKAGE, packageVersion);
|
||||
} else {
|
||||
// Assume they're the same instead
|
||||
MINECRAFT_PREFIX_PACKAGE = MINECRAFT_FULL_PACKAGE;
|
||||
}
|
||||
|
||||
// The package is usualy flat, so go with that assumption
|
||||
String matcher =
|
||||
(MINECRAFT_PREFIX_PACKAGE.length() > 0 ?
|
||||
Pattern.quote(MINECRAFT_PREFIX_PACKAGE + ".") : "") + "\\w+";
|
||||
@ -195,6 +234,15 @@ public class MinecraftReflection {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the package version of the underlying CraftBukkit server.
|
||||
* @return The package version, or NULL if not applicable (before 1.4.6).
|
||||
*/
|
||||
public static String getPackageVersion() {
|
||||
getMinecraftPackage();
|
||||
return packageVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the dynamic package matcher.
|
||||
* @param regex - the Minecraft package regex.
|
||||
@ -1260,7 +1308,7 @@ public class MinecraftReflection {
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static Class getCraftBukkitClass(String className) {
|
||||
if (craftbukkitPackage == null)
|
||||
craftbukkitPackage = new CachedPackage(getCraftBukkitPackage());
|
||||
craftbukkitPackage = new CachedPackage(getCraftBukkitPackage(), getClassSource());
|
||||
return craftbukkitPackage.getPackageClass(className);
|
||||
}
|
||||
|
||||
@ -1272,7 +1320,7 @@ public class MinecraftReflection {
|
||||
*/
|
||||
public static Class<?> getMinecraftClass(String className) {
|
||||
if (minecraftPackage == null)
|
||||
minecraftPackage = new CachedPackage(getMinecraftPackage());
|
||||
minecraftPackage = new CachedPackage(getMinecraftPackage(), getClassSource());
|
||||
return minecraftPackage.getPackageClass(className);
|
||||
}
|
||||
|
||||
@ -1284,11 +1332,36 @@ public class MinecraftReflection {
|
||||
*/
|
||||
private static Class<?> setMinecraftClass(String className, Class<?> clazz) {
|
||||
if (minecraftPackage == null)
|
||||
minecraftPackage = new CachedPackage(getMinecraftPackage());
|
||||
minecraftPackage = new CachedPackage(getMinecraftPackage(), getClassSource());
|
||||
minecraftPackage.setPackageClass(className, clazz);
|
||||
return clazz;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current class source.
|
||||
* @return The class source.
|
||||
*/
|
||||
private static ClassSource getClassSource() {
|
||||
ErrorReporter reporter = ProtocolLibrary.getErrorReporter();
|
||||
|
||||
// Lazy pattern again
|
||||
if (classSource == null) {
|
||||
// Attempt to use MCPC
|
||||
try {
|
||||
return classSource = new RemappedClassSource().initialize();
|
||||
} catch (RemapperUnavaibleException e) {
|
||||
if (e.getReason() != Reason.MCPC_NOT_PRESENT)
|
||||
reporter.reportWarning(MinecraftReflection.class, Report.newBuilder(REPORT_CANNOT_FIND_MCPC_REMAPPER));
|
||||
} catch (Exception e) {
|
||||
reporter.reportWarning(MinecraftReflection.class, Report.newBuilder(REPORT_CANNOT_LOAD_CPC_REMAPPER));
|
||||
}
|
||||
|
||||
// Just use the default class loader
|
||||
classSource = ClassSource.fromClassLoader();
|
||||
}
|
||||
return classSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the first class that matches a specified Minecraft name.
|
||||
* @param className - the specific Minecraft class.
|
||||
|
@ -0,0 +1,147 @@
|
||||
package com.comphenix.protocol.utility;
|
||||
|
||||
// Thanks to Bergerkiller for his excellent hack. :D
|
||||
|
||||
// Copyright (C) 2013 bergerkiller
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to use,
|
||||
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
// Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.MethodUtils;
|
||||
import com.comphenix.protocol.utility.RemappedClassSource.RemapperUnavaibleException.Reason;
|
||||
|
||||
class RemappedClassSource extends ClassSource {
|
||||
private Object classRemapper;
|
||||
private Method mapType;
|
||||
private ClassLoader loader;
|
||||
|
||||
/**
|
||||
* Construct a new remapped class source using the default class loader.
|
||||
*/
|
||||
public RemappedClassSource() {
|
||||
this(RemappedClassSource.class.getClassLoader());
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new renampped class source with the provided class loader.
|
||||
* @param loader - the class loader.
|
||||
*/
|
||||
public RemappedClassSource(ClassLoader loader) {
|
||||
this.loader = loader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to load the MCPC remapper.
|
||||
* @return TRUE if we succeeded, FALSE otherwise.
|
||||
* @throws RemapperUnavaibleException If the remapper is not present.
|
||||
*/
|
||||
public RemappedClassSource initialize() {
|
||||
try {
|
||||
if (Bukkit.getServer() == null || !Bukkit.getServer().getVersion().contains("MCPC-Plus")) {
|
||||
throw new RemapperUnavaibleException(Reason.MCPC_NOT_PRESENT);
|
||||
}
|
||||
|
||||
// Obtain the Class remapper used by MCPC+
|
||||
this.classRemapper = FieldUtils.readField(getClass().getClassLoader(), "remapper", true);
|
||||
|
||||
if (this.classRemapper == null) {
|
||||
throw new RemapperUnavaibleException(Reason.REMAPPER_DISABLED);
|
||||
}
|
||||
|
||||
// Initialize some fields and methods used by the Jar Remapper
|
||||
Class<?> renamerClazz = classRemapper.getClass();
|
||||
|
||||
this.mapType = MethodUtils.getAccessibleMethod(renamerClazz, "map",
|
||||
new Class<?>[] { String.class });
|
||||
|
||||
return this;
|
||||
|
||||
} catch (RemapperUnavaibleException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
// Damn it
|
||||
throw new RuntimeException("Cannot access MCPC remapper.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> loadClass(String canonicalName) throws ClassNotFoundException {
|
||||
final String remapped = getClassName(canonicalName);
|
||||
|
||||
try {
|
||||
return loader.loadClass(remapped);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new ClassNotFoundException("Cannot find " + canonicalName + "(Remapped: " + remapped + ")");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the obfuscated class name given an unobfuscated canonical class name.
|
||||
* @param path - the canonical class name.
|
||||
* @return The obfuscated class name.
|
||||
*/
|
||||
private String getClassName(String path) {
|
||||
try {
|
||||
String remapped = (String) mapType.invoke(classRemapper, path.replace('.', '/'));
|
||||
return remapped.replace('/', '.');
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Cannot remap class name.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static class RemapperUnavaibleException extends RuntimeException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public enum Reason {
|
||||
MCPC_NOT_PRESENT("The server is not running MCPC+"),
|
||||
REMAPPER_DISABLED("Running an MCPC+ server but the remapper is unavailable. Please turn it on!");
|
||||
|
||||
private final String message;
|
||||
|
||||
private Reason(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a human-readable version of this reason.
|
||||
* @return The human-readable verison.
|
||||
*/
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
private final Reason reason;
|
||||
|
||||
public RemapperUnavaibleException(Reason reason) {
|
||||
super(reason.getMessage());
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the reasont he remapper is unavailable.
|
||||
* @return The reason.
|
||||
*/
|
||||
public Reason getReason() {
|
||||
return reason;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user