mirror of
https://github.com/PaperMC/Paper.git
synced 2025-05-19 13:40:24 -07:00
Remap reflection calls in plugins using internals
Co-authored-by: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
This commit is contained in:
parent
4cd66b0305
commit
fae673b95c
@ -63,6 +63,12 @@ dependencies {
|
|||||||
testImplementation("org.junit-pioneer:junit-pioneer:2.2.0") // Paper - CartesianTest
|
testImplementation("org.junit-pioneer:junit-pioneer:2.2.0") // Paper - CartesianTest
|
||||||
implementation("net.neoforged:srgutils:1.0.9") // Paper - mappings handling
|
implementation("net.neoforged:srgutils:1.0.9") // Paper - mappings handling
|
||||||
implementation("net.neoforged:AutoRenamingTool:2.0.3") // Paper - remap plugins
|
implementation("net.neoforged:AutoRenamingTool:2.0.3") // Paper - remap plugins
|
||||||
|
// Paper start - Remap reflection
|
||||||
|
val reflectionRewriterVersion = "0.0.3"
|
||||||
|
implementation("io.papermc:reflection-rewriter:$reflectionRewriterVersion")
|
||||||
|
implementation("io.papermc:reflection-rewriter-runtime:$reflectionRewriterVersion")
|
||||||
|
implementation("io.papermc:reflection-rewriter-proxy-generator:$reflectionRewriterVersion")
|
||||||
|
// Paper end - Remap reflection
|
||||||
}
|
}
|
||||||
|
|
||||||
paperweight {
|
paperweight {
|
||||||
|
@ -5,6 +5,7 @@ import com.google.common.collect.ImmutableBiMap;
|
|||||||
import com.mojang.logging.LogUtils;
|
import com.mojang.logging.LogUtils;
|
||||||
import io.leangen.geantyref.TypeToken;
|
import io.leangen.geantyref.TypeToken;
|
||||||
import io.papermc.paper.configuration.serializer.collections.MapSerializer;
|
import io.papermc.paper.configuration.serializer.collections.MapSerializer;
|
||||||
|
import io.papermc.paper.util.MappingEnvironment;
|
||||||
import io.papermc.paper.util.ObfHelper;
|
import io.papermc.paper.util.ObfHelper;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -68,7 +69,7 @@ public final class PacketClassSerializer extends ScalarSerializer<Class<? extend
|
|||||||
@Override
|
@Override
|
||||||
protected @Nullable Object serialize(final Class<? extends Packet<?>> packetClass, final Predicate<Class<?>> typeSupported) {
|
protected @Nullable Object serialize(final Class<? extends Packet<?>> packetClass, final Predicate<Class<?>> typeSupported) {
|
||||||
final String name = packetClass.getName();
|
final String name = packetClass.getName();
|
||||||
@Nullable String mojName = ObfHelper.INSTANCE.mappingsByMojangName() == null ? name : MOJANG_TO_OBF.inverse().get(name); // if the mappings are null, running on moj-mapped server
|
@Nullable String mojName = ObfHelper.INSTANCE.mappingsByMojangName() == null || !MappingEnvironment.reobf() ? name : MOJANG_TO_OBF.inverse().get(name); // if the mappings are null, running on moj-mapped server
|
||||||
if (mojName == null && MOJANG_TO_OBF.containsKey(name)) {
|
if (mojName == null && MOJANG_TO_OBF.containsKey(name)) {
|
||||||
mojName = name;
|
mojName = name;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,187 @@
|
|||||||
|
package io.papermc.paper.plugin.entrypoint.classloader;
|
||||||
|
|
||||||
|
import io.papermc.paper.pluginremap.reflect.ReflectionRemapper;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
|
import java.net.JarURLConnection;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLClassLoader;
|
||||||
|
import java.security.CodeSigner;
|
||||||
|
import java.security.CodeSource;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.jar.Attributes;
|
||||||
|
import java.util.jar.Manifest;
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
import org.objectweb.asm.ClassReader;
|
||||||
|
import org.objectweb.asm.ClassVisitor;
|
||||||
|
import org.objectweb.asm.ClassWriter;
|
||||||
|
|
||||||
|
import static java.util.Objects.requireNonNullElse;
|
||||||
|
|
||||||
|
public final class BytecodeModifyingURLClassLoader extends URLClassLoader {
|
||||||
|
static {
|
||||||
|
ClassLoader.registerAsParallelCapable();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Object MISSING_MANIFEST = new Object();
|
||||||
|
|
||||||
|
private final Function<byte[], byte[]> modifier;
|
||||||
|
private final Map<String, Object> manifests = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public BytecodeModifyingURLClassLoader(
|
||||||
|
final URL[] urls,
|
||||||
|
final ClassLoader parent,
|
||||||
|
final Function<byte[], byte[]> modifier
|
||||||
|
) {
|
||||||
|
super(urls, parent);
|
||||||
|
this.modifier = modifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BytecodeModifyingURLClassLoader(
|
||||||
|
final URL[] urls,
|
||||||
|
final ClassLoader parent
|
||||||
|
) {
|
||||||
|
this(urls, parent, bytes -> {
|
||||||
|
final ClassReader classReader = new ClassReader(bytes);
|
||||||
|
final ClassWriter classWriter = new ClassWriter(classReader, 0);
|
||||||
|
final ClassVisitor visitor = ReflectionRemapper.visitor(classWriter);
|
||||||
|
if (visitor == classWriter) {
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
classReader.accept(visitor, 0);
|
||||||
|
return classWriter.toByteArray();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<?> findClass(final String name) throws ClassNotFoundException {
|
||||||
|
final Class<?> result;
|
||||||
|
final String path = name.replace('.', '/').concat(".class");
|
||||||
|
final URL url = this.findResource(path);
|
||||||
|
if (url != null) {
|
||||||
|
try {
|
||||||
|
result = this.defineClass(name, url);
|
||||||
|
} catch (final IOException e) {
|
||||||
|
throw new ClassNotFoundException(name, e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result = null;
|
||||||
|
}
|
||||||
|
if (result == null) {
|
||||||
|
throw new ClassNotFoundException(name);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Class<?> defineClass(String name, URL url) throws IOException {
|
||||||
|
int i = name.lastIndexOf('.');
|
||||||
|
if (i != -1) {
|
||||||
|
String pkgname = name.substring(0, i);
|
||||||
|
// Check if package already loaded.
|
||||||
|
final @Nullable Manifest man = this.manifestFor(url);
|
||||||
|
final URL jarUrl = URI.create(jarName(url)).toURL();
|
||||||
|
if (this.getAndVerifyPackage(pkgname, man, jarUrl) == null) {
|
||||||
|
try {
|
||||||
|
if (man != null) {
|
||||||
|
this.definePackage(pkgname, man, jarUrl);
|
||||||
|
} else {
|
||||||
|
this.definePackage(pkgname, null, null, null, null, null, null, null);
|
||||||
|
}
|
||||||
|
} catch (IllegalArgumentException iae) {
|
||||||
|
// parallel-capable class loaders: re-verify in case of a
|
||||||
|
// race condition
|
||||||
|
if (this.getAndVerifyPackage(pkgname, man, jarUrl) == null) {
|
||||||
|
// Should never happen
|
||||||
|
throw new AssertionError("Cannot find package " +
|
||||||
|
pkgname);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final byte[] bytes;
|
||||||
|
try (final InputStream is = url.openStream()) {
|
||||||
|
bytes = is.readAllBytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
final byte[] modified = this.modifier.apply(bytes);
|
||||||
|
|
||||||
|
final CodeSource cs = new CodeSource(url, (CodeSigner[]) null);
|
||||||
|
return this.defineClass(name, modified, 0, modified.length, cs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Package getAndVerifyPackage(
|
||||||
|
String pkgname,
|
||||||
|
Manifest man, URL url
|
||||||
|
) {
|
||||||
|
Package pkg = getDefinedPackage(pkgname);
|
||||||
|
if (pkg != null) {
|
||||||
|
// Package found, so check package sealing.
|
||||||
|
if (pkg.isSealed()) {
|
||||||
|
// Verify that code source URL is the same.
|
||||||
|
if (!pkg.isSealed(url)) {
|
||||||
|
throw new SecurityException(
|
||||||
|
"sealing violation: package " + pkgname + " is sealed");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Make sure we are not attempting to seal the package
|
||||||
|
// at this code source URL.
|
||||||
|
if ((man != null) && this.isSealed(pkgname, man)) {
|
||||||
|
throw new SecurityException(
|
||||||
|
"sealing violation: can't seal package " + pkgname +
|
||||||
|
": already loaded");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pkg;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSealed(String name, Manifest man) {
|
||||||
|
Attributes attr = man.getAttributes(name.replace('.', '/').concat("/"));
|
||||||
|
String sealed = null;
|
||||||
|
if (attr != null) {
|
||||||
|
sealed = attr.getValue(Attributes.Name.SEALED);
|
||||||
|
}
|
||||||
|
if (sealed == null) {
|
||||||
|
if ((attr = man.getMainAttributes()) != null) {
|
||||||
|
sealed = attr.getValue(Attributes.Name.SEALED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "true".equalsIgnoreCase(sealed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Nullable Manifest manifestFor(final URL url) throws IOException {
|
||||||
|
Manifest man = null;
|
||||||
|
if (url.getProtocol().equals("jar")) {
|
||||||
|
try {
|
||||||
|
final Object computedManifest = this.manifests.computeIfAbsent(jarName(url), $ -> {
|
||||||
|
try {
|
||||||
|
final Manifest m = ((JarURLConnection) url.openConnection()).getManifest();
|
||||||
|
return requireNonNullElse(m, MISSING_MANIFEST);
|
||||||
|
} catch (final IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (computedManifest instanceof Manifest found) {
|
||||||
|
man = found;
|
||||||
|
}
|
||||||
|
} catch (final UncheckedIOException e) {
|
||||||
|
throw e.getCause();
|
||||||
|
} catch (final IllegalArgumentException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return man;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String jarName(final URL sourceUrl) {
|
||||||
|
final int exclamationIdx = sourceUrl.getPath().lastIndexOf('!');
|
||||||
|
if (exclamationIdx != -1) {
|
||||||
|
return sourceUrl.getPath().substring(0, exclamationIdx);
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Could not find jar for URL " + sourceUrl);
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,6 @@ public class PaperClassloaderBytecodeModifier implements ClassloaderBytecodeModi
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] modify(PluginMeta configuration, byte[] bytecode) {
|
public byte[] modify(PluginMeta configuration, byte[] bytecode) {
|
||||||
return bytecode;
|
return io.papermc.paper.pluginremap.reflect.ReflectionRemapper.processClass(bytecode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,12 @@ package io.papermc.paper.plugin.loader;
|
|||||||
|
|
||||||
import io.papermc.paper.plugin.PluginInitializerManager;
|
import io.papermc.paper.plugin.PluginInitializerManager;
|
||||||
import io.papermc.paper.plugin.bootstrap.PluginProviderContext;
|
import io.papermc.paper.plugin.bootstrap.PluginProviderContext;
|
||||||
|
import io.papermc.paper.plugin.entrypoint.classloader.BytecodeModifyingURLClassLoader;
|
||||||
|
import io.papermc.paper.plugin.entrypoint.classloader.PaperPluginClassLoader;
|
||||||
import io.papermc.paper.plugin.loader.library.ClassPathLibrary;
|
import io.papermc.paper.plugin.loader.library.ClassPathLibrary;
|
||||||
import io.papermc.paper.plugin.loader.library.PaperLibraryStore;
|
import io.papermc.paper.plugin.loader.library.PaperLibraryStore;
|
||||||
import io.papermc.paper.plugin.entrypoint.classloader.PaperPluginClassLoader;
|
|
||||||
import io.papermc.paper.plugin.provider.configuration.PaperPluginMeta;
|
import io.papermc.paper.plugin.provider.configuration.PaperPluginMeta;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import io.papermc.paper.util.MappingEnvironment;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
@ -17,6 +17,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.jar.JarFile;
|
import java.util.jar.JarFile;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public class PaperClasspathBuilder implements PluginClasspathBuilder {
|
public class PaperClasspathBuilder implements PluginClasspathBuilder {
|
||||||
|
|
||||||
@ -60,7 +61,10 @@ public class PaperClasspathBuilder implements PluginClasspathBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return new PaperPluginClassLoader(logger, source, jarFile, configuration, this.getClass().getClassLoader(), new URLClassLoader(urls, getClass().getClassLoader()));
|
final URLClassLoader libraryLoader = MappingEnvironment.DISABLE_PLUGIN_REMAPPING
|
||||||
|
? new URLClassLoader(urls, this.getClass().getClassLoader())
|
||||||
|
: new BytecodeModifyingURLClassLoader(urls, this.getClass().getClassLoader());
|
||||||
|
return new PaperPluginClassLoader(logger, source, jarFile, configuration, this.getClass().getClassLoader(), libraryLoader);
|
||||||
} catch (IOException exception) {
|
} catch (IOException exception) {
|
||||||
throw new RuntimeException(exception);
|
throw new RuntimeException(exception);
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
package io.papermc.paper.plugin.provider.type.spigot;
|
package io.papermc.paper.plugin.provider.type.spigot;
|
||||||
|
|
||||||
|
import io.papermc.paper.plugin.entrypoint.classloader.BytecodeModifyingURLClassLoader;
|
||||||
import io.papermc.paper.plugin.provider.configuration.serializer.constraints.PluginConfigConstraints;
|
import io.papermc.paper.plugin.provider.configuration.serializer.constraints.PluginConfigConstraints;
|
||||||
import io.papermc.paper.plugin.provider.type.PluginTypeFactory;
|
import io.papermc.paper.plugin.provider.type.PluginTypeFactory;
|
||||||
|
import io.papermc.paper.util.MappingEnvironment;
|
||||||
import org.bukkit.plugin.InvalidDescriptionException;
|
import org.bukkit.plugin.InvalidDescriptionException;
|
||||||
import org.bukkit.plugin.PluginDescriptionFile;
|
import org.bukkit.plugin.PluginDescriptionFile;
|
||||||
|
import org.bukkit.plugin.java.LibraryLoader;
|
||||||
import org.yaml.snakeyaml.error.YAMLException;
|
import org.yaml.snakeyaml.error.YAMLException;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -15,6 +18,12 @@ import java.util.jar.JarFile;
|
|||||||
|
|
||||||
class SpigotPluginProviderFactory implements PluginTypeFactory<SpigotPluginProvider, PluginDescriptionFile> {
|
class SpigotPluginProviderFactory implements PluginTypeFactory<SpigotPluginProvider, PluginDescriptionFile> {
|
||||||
|
|
||||||
|
static {
|
||||||
|
if (!MappingEnvironment.DISABLE_PLUGIN_REMAPPING) {
|
||||||
|
LibraryLoader.LIBRARY_LOADER_FACTORY = BytecodeModifyingURLClassLoader::new;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SpigotPluginProvider build(JarFile file, PluginDescriptionFile configuration, Path source) throws InvalidDescriptionException {
|
public SpigotPluginProvider build(JarFile file, PluginDescriptionFile configuration, Path source) throws InvalidDescriptionException {
|
||||||
// Copied from SimplePluginManager#loadPlugins
|
// Copied from SimplePluginManager#loadPlugins
|
||||||
|
@ -0,0 +1,211 @@
|
|||||||
|
package io.papermc.paper.pluginremap.reflect;
|
||||||
|
|
||||||
|
import com.mojang.logging.LogUtils;
|
||||||
|
import io.papermc.paper.util.MappingEnvironment;
|
||||||
|
import io.papermc.paper.util.ObfHelper;
|
||||||
|
import io.papermc.reflectionrewriter.runtime.AbstractDefaultRulesReflectionProxy;
|
||||||
|
import io.papermc.reflectionrewriter.runtime.DefineClassReflectionProxy;
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.security.CodeSource;
|
||||||
|
import java.security.ProtectionDomain;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
import org.checkerframework.framework.qual.DefaultQualifier;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
// todo proper inheritance handling
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@DefaultQualifier(NonNull.class)
|
||||||
|
public final class PaperReflection extends AbstractDefaultRulesReflectionProxy implements DefineClassReflectionProxy {
|
||||||
|
// concat to avoid being rewritten by shadow
|
||||||
|
private static final Logger LOGGER = LogUtils.getLogger();
|
||||||
|
private static final String CB_PACKAGE_PREFIX = "org.bukkit.".concat("craftbukkit.");
|
||||||
|
private static final String LEGACY_CB_PACKAGE_PREFIX = "org.bukkit.".concat("craftbukkit.") + MappingEnvironment.LEGACY_CB_VERSION + ".";
|
||||||
|
|
||||||
|
private final DefineClassReflectionProxy defineClassProxy;
|
||||||
|
private final Map<String, ObfHelper.ClassMapping> mappingsByMojangName;
|
||||||
|
private final Map<String, ObfHelper.ClassMapping> mappingsByObfName;
|
||||||
|
// Reflection does not care about method return values, so this map removes the return value descriptor from the key
|
||||||
|
private final Map<String, Map<String, String>> strippedMethodMappings;
|
||||||
|
|
||||||
|
PaperReflection() {
|
||||||
|
this.defineClassProxy = DefineClassReflectionProxy.create(PaperReflection::processClass);
|
||||||
|
if (!MappingEnvironment.hasMappings()) {
|
||||||
|
this.mappingsByMojangName = Map.of();
|
||||||
|
this.mappingsByObfName = Map.of();
|
||||||
|
this.strippedMethodMappings = Map.of();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final ObfHelper obfHelper = ObfHelper.INSTANCE;
|
||||||
|
this.mappingsByMojangName = Objects.requireNonNull(obfHelper.mappingsByMojangName(), "mappingsByMojangName");
|
||||||
|
this.mappingsByObfName = Objects.requireNonNull(obfHelper.mappingsByObfName(), "mappingsByObfName");
|
||||||
|
this.strippedMethodMappings = this.mappingsByMojangName.entrySet().stream().collect(Collectors.toUnmodifiableMap(
|
||||||
|
Map.Entry::getKey,
|
||||||
|
entry -> entry.getValue().strippedMethods()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String mapClassName(final String name) {
|
||||||
|
final ObfHelper.@Nullable ClassMapping mapping = this.mappingsByObfName.get(name);
|
||||||
|
return mapping != null ? mapping.mojangName() : removeCraftBukkitRelocation(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String mapDeclaredMethodName(final Class<?> clazz, final String name, final Class<?> @Nullable ... parameterTypes) {
|
||||||
|
final @Nullable Map<String, String> mapping = this.strippedMethodMappings.get(clazz.getName());
|
||||||
|
if (mapping == null) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
return mapping.getOrDefault(strippedMethodKey(name, parameterTypes), name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String mapMethodName(final Class<?> clazz, final String name, final Class<?> @Nullable ... parameterTypes) {
|
||||||
|
final @Nullable String mapped = this.findMappedMethodName(clazz, name, parameterTypes);
|
||||||
|
return mapped != null ? mapped : name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String mapDeclaredFieldName(final Class<?> clazz, final String name) {
|
||||||
|
final ObfHelper.@Nullable ClassMapping mapping = this.mappingsByMojangName.get(clazz.getName());
|
||||||
|
if (mapping == null) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
return mapping.fieldsByObf().getOrDefault(name, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String mapFieldName(final Class<?> clazz, final String name) {
|
||||||
|
final @Nullable String mapped = this.findMappedFieldName(clazz, name);
|
||||||
|
return mapped != null ? mapped : name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Nullable String findMappedMethodName(final Class<?> clazz, final String name, final Class<?> @Nullable ... parameterTypes) {
|
||||||
|
final Map<String, String> map = this.strippedMethodMappings.get(clazz.getName());
|
||||||
|
@Nullable String mapped = null;
|
||||||
|
if (map != null) {
|
||||||
|
mapped = map.get(strippedMethodKey(name, parameterTypes));
|
||||||
|
if (mapped != null) {
|
||||||
|
return mapped;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// JVM checks super before interfaces
|
||||||
|
final Class<?> superClass = clazz.getSuperclass();
|
||||||
|
if (superClass != null) {
|
||||||
|
mapped = this.findMappedMethodName(superClass, name, parameterTypes);
|
||||||
|
}
|
||||||
|
if (mapped == null) {
|
||||||
|
for (final Class<?> i : clazz.getInterfaces()) {
|
||||||
|
mapped = this.findMappedMethodName(i, name, parameterTypes);
|
||||||
|
if (mapped != null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Nullable String findMappedFieldName(final Class<?> clazz, final String name) {
|
||||||
|
final ObfHelper.ClassMapping mapping = this.mappingsByMojangName.get(clazz.getName());
|
||||||
|
@Nullable String mapped = null;
|
||||||
|
if (mapping != null) {
|
||||||
|
mapped = mapping.fieldsByObf().get(name);
|
||||||
|
if (mapped != null) {
|
||||||
|
return mapped;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The JVM checks super before interfaces
|
||||||
|
final Class<?> superClass = clazz.getSuperclass();
|
||||||
|
if (superClass != null) {
|
||||||
|
mapped = this.findMappedFieldName(superClass, name);
|
||||||
|
}
|
||||||
|
if (mapped == null) {
|
||||||
|
for (final Class<?> i : clazz.getInterfaces()) {
|
||||||
|
mapped = this.findMappedFieldName(i, name);
|
||||||
|
if (mapped != null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String strippedMethodKey(final String methodName, final Class<?> @Nullable ... parameterTypes) {
|
||||||
|
return methodName + parameterDescriptor(parameterTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String parameterDescriptor(final Class<?> @Nullable ... parameterTypes) {
|
||||||
|
if (parameterTypes == null) {
|
||||||
|
// Null parameterTypes is treated as an empty array
|
||||||
|
return "()";
|
||||||
|
}
|
||||||
|
final StringBuilder builder = new StringBuilder();
|
||||||
|
builder.append('(');
|
||||||
|
for (final Class<?> parameterType : parameterTypes) {
|
||||||
|
builder.append(parameterType.descriptorString());
|
||||||
|
}
|
||||||
|
builder.append(')');
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String removeCraftBukkitRelocation(final String name) {
|
||||||
|
if (MappingEnvironment.hasMappings()) {
|
||||||
|
// Relocation is applied in reobf, and when mappings are present they handle the relocation
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
if (name.startsWith(LEGACY_CB_PACKAGE_PREFIX)) {
|
||||||
|
return CB_PACKAGE_PREFIX + name.substring(LEGACY_CB_PACKAGE_PREFIX.length());
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> defineClass(final Object loader, final byte[] b, final int off, final int len) throws ClassFormatError {
|
||||||
|
return this.defineClassProxy.defineClass(loader, b, off, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> defineClass(final Object loader, final String name, final byte[] b, final int off, final int len) throws ClassFormatError {
|
||||||
|
return this.defineClassProxy.defineClass(loader, name, b, off, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> defineClass(final Object loader, final @Nullable String name, final byte[] b, final int off, final int len, final @Nullable ProtectionDomain protectionDomain) throws ClassFormatError {
|
||||||
|
return this.defineClassProxy.defineClass(loader, name, b, off, len, protectionDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> defineClass(final Object loader, final String name, final ByteBuffer b, final ProtectionDomain protectionDomain) throws ClassFormatError {
|
||||||
|
return this.defineClassProxy.defineClass(loader, name, b, protectionDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> defineClass(final Object secureLoader, final String name, final byte[] b, final int off, final int len, final CodeSource cs) {
|
||||||
|
return this.defineClassProxy.defineClass(secureLoader, name, b, off, len, cs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> defineClass(final Object secureLoader, final String name, final ByteBuffer b, final CodeSource cs) {
|
||||||
|
return this.defineClassProxy.defineClass(secureLoader, name, b, cs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> defineClass(final MethodHandles.Lookup lookup, final byte[] bytes) throws IllegalAccessException {
|
||||||
|
return this.defineClassProxy.defineClass(lookup, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo apply bytecode remap here as well
|
||||||
|
private static byte[] processClass(final byte[] bytes) {
|
||||||
|
try {
|
||||||
|
return ReflectionRemapper.processClass(bytes);
|
||||||
|
} catch (final Exception ex) {
|
||||||
|
LOGGER.warn("Failed to process class bytes", ex);
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
package io.papermc.paper.pluginremap.reflect;
|
||||||
|
|
||||||
|
import io.papermc.asm.ClassInfoProvider;
|
||||||
|
import io.papermc.asm.RewriteRuleVisitorFactory;
|
||||||
|
import io.papermc.paper.util.MappingEnvironment;
|
||||||
|
import io.papermc.reflectionrewriter.BaseReflectionRules;
|
||||||
|
import io.papermc.reflectionrewriter.DefineClassRule;
|
||||||
|
import io.papermc.reflectionrewriter.proxygenerator.ProxyGenerator;
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
|
import org.checkerframework.framework.qual.DefaultQualifier;
|
||||||
|
import org.objectweb.asm.ClassReader;
|
||||||
|
import org.objectweb.asm.ClassVisitor;
|
||||||
|
import org.objectweb.asm.ClassWriter;
|
||||||
|
import org.objectweb.asm.Opcodes;
|
||||||
|
|
||||||
|
@DefaultQualifier(NonNull.class)
|
||||||
|
public final class ReflectionRemapper {
|
||||||
|
private static final String PAPER_REFLECTION_HOLDER = "io.papermc.paper.pluginremap.reflect.PaperReflectionHolder";
|
||||||
|
private static final String PAPER_REFLECTION_HOLDER_DESC = PAPER_REFLECTION_HOLDER.replace('.', '/');
|
||||||
|
private static final RewriteRuleVisitorFactory VISITOR_FACTORY = RewriteRuleVisitorFactory.create(
|
||||||
|
Opcodes.ASM9,
|
||||||
|
chain -> chain.then(new BaseReflectionRules(PAPER_REFLECTION_HOLDER).rules())
|
||||||
|
.then(DefineClassRule.create(PAPER_REFLECTION_HOLDER_DESC, true)),
|
||||||
|
ClassInfoProvider.basic()
|
||||||
|
);
|
||||||
|
|
||||||
|
static {
|
||||||
|
if (!MappingEnvironment.reobf()) {
|
||||||
|
setupProxy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ReflectionRemapper() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ClassVisitor visitor(final ClassVisitor parent) {
|
||||||
|
if (MappingEnvironment.reobf() || MappingEnvironment.DISABLE_PLUGIN_REMAPPING) {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
return VISITOR_FACTORY.createVisitor(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] processClass(final byte[] bytes) {
|
||||||
|
if (MappingEnvironment.DISABLE_PLUGIN_REMAPPING) {
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
final ClassReader classReader = new ClassReader(bytes);
|
||||||
|
final ClassWriter classWriter = new ClassWriter(classReader, 0);
|
||||||
|
classReader.accept(ReflectionRemapper.visitor(classWriter), 0);
|
||||||
|
return classWriter.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setupProxy() {
|
||||||
|
try {
|
||||||
|
final byte[] bytes = ProxyGenerator.generateProxy(PaperReflection.class, PAPER_REFLECTION_HOLDER_DESC);
|
||||||
|
final MethodHandles.Lookup lookup = MethodHandles.lookup();
|
||||||
|
final Class<?> generated = lookup.defineClass(bytes);
|
||||||
|
final Method init = generated.getDeclaredMethod("init", PaperReflection.class);
|
||||||
|
init.invoke(null, new PaperReflection());
|
||||||
|
} catch (final ReflectiveOperationException ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,8 @@ import org.checkerframework.framework.qual.DefaultQualifier;
|
|||||||
|
|
||||||
@DefaultQualifier(NonNull.class)
|
@DefaultQualifier(NonNull.class)
|
||||||
public final class MappingEnvironment {
|
public final class MappingEnvironment {
|
||||||
|
public static final boolean DISABLE_PLUGIN_REMAPPING = Boolean.getBoolean("paper.disablePluginRemapping");
|
||||||
|
public static final String LEGACY_CB_VERSION = "v1_21_R3";
|
||||||
private static final @Nullable String MAPPINGS_HASH = readMappingsHash();
|
private static final @Nullable String MAPPINGS_HASH = readMappingsHash();
|
||||||
private static final boolean REOBF = checkReobf();
|
private static final boolean REOBF = checkReobf();
|
||||||
|
|
||||||
|
@ -29,6 +29,9 @@ public enum StacktraceDeobfuscator {
|
|||||||
});
|
});
|
||||||
|
|
||||||
public void deobfuscateThrowable(final Throwable throwable) {
|
public void deobfuscateThrowable(final Throwable throwable) {
|
||||||
|
if (!MappingEnvironment.reobf()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (GlobalConfiguration.get() != null && !GlobalConfiguration.get().logging.deobfuscateStacktraces) { // handle null as true
|
if (GlobalConfiguration.get() != null && !GlobalConfiguration.get().logging.deobfuscateStacktraces) { // handle null as true
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -44,6 +47,9 @@ public enum StacktraceDeobfuscator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public StackTraceElement[] deobfuscateStacktrace(final StackTraceElement[] traceElements) {
|
public StackTraceElement[] deobfuscateStacktrace(final StackTraceElement[] traceElements) {
|
||||||
|
if (!MappingEnvironment.reobf()) {
|
||||||
|
return traceElements;
|
||||||
|
}
|
||||||
if (GlobalConfiguration.get() != null && !GlobalConfiguration.get().logging.deobfuscateStacktraces) { // handle null as true
|
if (GlobalConfiguration.get() != null && !GlobalConfiguration.get().logging.deobfuscateStacktraces) { // handle null as true
|
||||||
return traceElements;
|
return traceElements;
|
||||||
}
|
}
|
||||||
|
@ -131,36 +131,26 @@ public class Commodore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Paper start - Plugin rewrites
|
// Paper start - Plugin rewrites
|
||||||
private static final Map<String, String> SEARCH_AND_REMOVE = initReplacementsMap();
|
private static final String CB_PACKAGE_PREFIX = "org/bukkit/".concat("craftbukkit/");
|
||||||
private static Map<String, String> initReplacementsMap() {
|
private static final String LEGACY_CB_PACKAGE_PREFIX = CB_PACKAGE_PREFIX + io.papermc.paper.util.MappingEnvironment.LEGACY_CB_VERSION + "/";
|
||||||
Map<String, String> getAndRemove = new HashMap<>();
|
private static String runtimeCbPkgPrefix() {
|
||||||
// Be wary of maven shade's relocations
|
if (io.papermc.paper.util.MappingEnvironment.reobf()) {
|
||||||
|
return LEGACY_CB_PACKAGE_PREFIX;
|
||||||
final java.util.jar.Manifest manifest = io.papermc.paper.util.JarManifests.manifest(Commodore.class);
|
|
||||||
if (Boolean.getBoolean( "debug.rewriteForIde") && manifest != null)
|
|
||||||
{
|
|
||||||
// unversion incoming calls for pre-relocate debug work
|
|
||||||
final String NMS_REVISION_PACKAGE = "v" + manifest.getMainAttributes().getValue("CraftBukkit-Package-Version") + "/";
|
|
||||||
|
|
||||||
getAndRemove.put("org/bukkit/".concat("craftbukkit/" + NMS_REVISION_PACKAGE), NMS_REVISION_PACKAGE);
|
|
||||||
}
|
}
|
||||||
|
return CB_PACKAGE_PREFIX;
|
||||||
return getAndRemove;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
private static String getOriginalOrRewrite(@Nonnull String original)
|
private static String getOriginalOrRewrite(@Nonnull String original)
|
||||||
{
|
{
|
||||||
String rewrite = null;
|
// Relocation is applied in reobf, and when mappings are present they handle the relocation
|
||||||
for ( Map.Entry<String, String> entry : SEARCH_AND_REMOVE.entrySet() )
|
if (!io.papermc.paper.util.MappingEnvironment.reobf() && !io.papermc.paper.util.MappingEnvironment.hasMappings()) {
|
||||||
{
|
if (original.contains(LEGACY_CB_PACKAGE_PREFIX)) {
|
||||||
if ( original.contains( entry.getKey() ) )
|
original = original.replace(LEGACY_CB_PACKAGE_PREFIX, CB_PACKAGE_PREFIX);
|
||||||
{
|
|
||||||
rewrite = original.replace( entry.getValue(), "" );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return rewrite != null ? rewrite : original;
|
return original;
|
||||||
}
|
}
|
||||||
// Paper end - Plugin rewrites
|
// Paper end - Plugin rewrites
|
||||||
|
|
||||||
@ -245,6 +235,7 @@ public class Commodore {
|
|||||||
visitor = new LimitedClassRemapper(cw, new SimpleRemapper(Commodore.ENUM_RENAMES));
|
visitor = new LimitedClassRemapper(cw, new SimpleRemapper(Commodore.ENUM_RENAMES));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
visitor = io.papermc.paper.pluginremap.reflect.ReflectionRemapper.visitor(visitor); // Paper
|
||||||
cr.accept(new ClassRemapper(new ClassVisitor(Opcodes.ASM9, visitor) {
|
cr.accept(new ClassRemapper(new ClassVisitor(Opcodes.ASM9, visitor) {
|
||||||
final Set<RerouteMethodData> rerouteMethodData = new HashSet<>();
|
final Set<RerouteMethodData> rerouteMethodData = new HashSet<>();
|
||||||
String className;
|
String className;
|
||||||
|
@ -75,6 +75,7 @@ import org.bukkit.potion.PotionType;
|
|||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
public final class CraftMagicNumbers implements UnsafeValues {
|
public final class CraftMagicNumbers implements UnsafeValues {
|
||||||
public static final CraftMagicNumbers INSTANCE = new CraftMagicNumbers();
|
public static final CraftMagicNumbers INSTANCE = new CraftMagicNumbers();
|
||||||
|
public static final boolean DISABLE_OLD_API_SUPPORT = Boolean.getBoolean("paper.disableOldApiSupport"); // Paper
|
||||||
|
|
||||||
private final Commodore commodore = new Commodore();
|
private final Commodore commodore = new Commodore();
|
||||||
|
|
||||||
@ -347,7 +348,7 @@ public final class CraftMagicNumbers implements UnsafeValues {
|
|||||||
throw new InvalidPluginException("Plugin API version " + pdf.getAPIVersion() + " is lower than the minimum allowed version. Please update or replace it.");
|
throw new InvalidPluginException("Plugin API version " + pdf.getAPIVersion() + " is lower than the minimum allowed version. Please update or replace it.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toCheck.isOlderThan(ApiVersion.FLATTENING)) {
|
if (!DISABLE_OLD_API_SUPPORT && toCheck.isOlderThan(ApiVersion.FLATTENING)) { // Paper
|
||||||
CraftLegacy.init();
|
CraftLegacy.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -362,6 +363,12 @@ public final class CraftMagicNumbers implements UnsafeValues {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] processClass(PluginDescriptionFile pdf, String path, byte[] clazz) {
|
public byte[] processClass(PluginDescriptionFile pdf, String path, byte[] clazz) {
|
||||||
|
// Paper start
|
||||||
|
if (DISABLE_OLD_API_SUPPORT) {
|
||||||
|
// Make sure we still go through our reflection rewriting if needed
|
||||||
|
return io.papermc.paper.pluginremap.reflect.ReflectionRemapper.processClass(clazz);
|
||||||
|
}
|
||||||
|
// Paper end
|
||||||
try {
|
try {
|
||||||
clazz = this.commodore.convert(clazz, pdf.getName(), ApiVersion.getOrCreateVersion(pdf.getAPIVersion()), ((CraftServer) Bukkit.getServer()).activeCompatibilities);
|
clazz = this.commodore.convert(clazz, pdf.getName(), ApiVersion.getOrCreateVersion(pdf.getAPIVersion()), ((CraftServer) Bukkit.getServer()).activeCompatibilities);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user