diff options
Diffstat (limited to 'patches/server/0022-Remap-reflection-calls-in-plugins-using-internals.patch')
-rw-r--r-- | patches/server/0022-Remap-reflection-calls-in-plugins-using-internals.patch | 740 |
1 files changed, 740 insertions, 0 deletions
diff --git a/patches/server/0022-Remap-reflection-calls-in-plugins-using-internals.patch b/patches/server/0022-Remap-reflection-calls-in-plugins-using-internals.patch new file mode 100644 index 0000000000..ebcc5735fb --- /dev/null +++ b/patches/server/0022-Remap-reflection-calls-in-plugins-using-internals.patch @@ -0,0 +1,740 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke <[email protected]> +Date: Sun, 30 Oct 2022 23:47:26 +0100 +Subject: [PATCH] Remap reflection calls in plugins using internals + +Co-authored-by: Jason Penilla <[email protected]> + +diff --git a/build.gradle.kts b/build.gradle.kts +index 884ac16677ee3f52174c7bbf7b34896bcbf04bbc..49749e2bd8a4af96d2091fa1bccd876c2abb9e12 100644 +--- a/build.gradle.kts ++++ b/build.gradle.kts +@@ -62,6 +62,12 @@ dependencies { + testImplementation("org.junit-pioneer:junit-pioneer:2.2.0") // Paper - CartesianTest + implementation("net.neoforged:srgutils:1.0.9") // Paper - mappings handling + 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 { +diff --git a/src/main/java/io/papermc/paper/configuration/serializer/PacketClassSerializer.java b/src/main/java/io/papermc/paper/configuration/serializer/PacketClassSerializer.java +index b61935052154e76b1b8cb49868c96c52f34a41d1..a2fe12513b93ded71517955ef3e52c925f56f7d1 100644 +--- a/src/main/java/io/papermc/paper/configuration/serializer/PacketClassSerializer.java ++++ b/src/main/java/io/papermc/paper/configuration/serializer/PacketClassSerializer.java +@@ -5,6 +5,7 @@ import com.google.common.collect.ImmutableBiMap; + import com.mojang.logging.LogUtils; + import io.leangen.geantyref.TypeToken; + import io.papermc.paper.configuration.serializer.collections.MapSerializer; ++import io.papermc.paper.util.MappingEnvironment; + import io.papermc.paper.util.ObfHelper; + import java.lang.reflect.Type; + import java.util.List; +@@ -68,7 +69,7 @@ public final class PacketClassSerializer extends ScalarSerializer<Class<? extend + @Override + protected @Nullable Object serialize(final Class<? extends Packet<?>> packetClass, final Predicate<Class<?>> typeSupported) { + 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)) { + mojName = name; + } +diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/BytecodeModifyingURLClassLoader.java b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/BytecodeModifyingURLClassLoader.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1240c061c121e8d5eb9add4e5e21955ee6df9368 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/BytecodeModifyingURLClassLoader.java +@@ -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); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/PaperClassloaderBytecodeModifier.java b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/PaperClassloaderBytecodeModifier.java +index f9a2c55a354c877749db3f92956de802ae575788..0e734c07dbe82ba4c319a237f9e79b08b57b997f 100644 +--- a/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/PaperClassloaderBytecodeModifier.java ++++ b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/PaperClassloaderBytecodeModifier.java +@@ -7,6 +7,6 @@ public class PaperClassloaderBytecodeModifier implements ClassloaderBytecodeModi + + @Override + public byte[] modify(PluginMeta configuration, byte[] bytecode) { +- return bytecode; ++ return io.papermc.paper.pluginremap.reflect.ReflectionRemapper.processClass(bytecode); + } + } +diff --git a/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java b/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java +index f576060c8fe872772bbafe2016fc9b83a3c095f1..f871a329eb52da077f58d0ceaaabd3349f84cad0 100644 +--- a/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java ++++ b/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java +@@ -2,12 +2,12 @@ package io.papermc.paper.plugin.loader; + + import io.papermc.paper.plugin.PluginInitializerManager; + 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.PaperLibraryStore; +-import io.papermc.paper.plugin.entrypoint.classloader.PaperPluginClassLoader; + 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.net.MalformedURLException; + import java.net.URL; +@@ -17,6 +17,7 @@ import java.util.ArrayList; + import java.util.List; + import java.util.jar.JarFile; + import java.util.logging.Logger; ++import org.jetbrains.annotations.NotNull; + + public class PaperClasspathBuilder implements PluginClasspathBuilder { + +@@ -60,7 +61,10 @@ public class PaperClasspathBuilder implements PluginClasspathBuilder { + } + + 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) { + throw new RuntimeException(exception); + } +diff --git a/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProviderFactory.java b/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProviderFactory.java +index bdd9bc8a414719b9f1d6f01f90539ddb8603a878..1bf0fa1530b8e5f94d726d0313b7a00f675b500c 100644 +--- a/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProviderFactory.java ++++ b/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProviderFactory.java +@@ -1,9 +1,12 @@ + 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.type.PluginTypeFactory; ++import io.papermc.paper.util.MappingEnvironment; + import org.bukkit.plugin.InvalidDescriptionException; + import org.bukkit.plugin.PluginDescriptionFile; ++import org.bukkit.plugin.java.LibraryLoader; + import org.yaml.snakeyaml.error.YAMLException; + + import java.io.IOException; +@@ -15,6 +18,12 @@ import java.util.jar.JarFile; + + class SpigotPluginProviderFactory implements PluginTypeFactory<SpigotPluginProvider, PluginDescriptionFile> { + ++ static { ++ if (!MappingEnvironment.DISABLE_PLUGIN_REMAPPING) { ++ LibraryLoader.LIBRARY_LOADER_FACTORY = BytecodeModifyingURLClassLoader::new; ++ } ++ } ++ + @Override + public SpigotPluginProvider build(JarFile file, PluginDescriptionFile configuration, Path source) throws InvalidDescriptionException { + // Copied from SimplePluginManager#loadPlugins +diff --git a/src/main/java/io/papermc/paper/pluginremap/reflect/PaperReflection.java b/src/main/java/io/papermc/paper/pluginremap/reflect/PaperReflection.java +new file mode 100644 +index 0000000000000000000000000000000000000000..92bc8e4933ff13764fa2ac7f3729216332e202c9 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/pluginremap/reflect/PaperReflection.java +@@ -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; ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/pluginremap/reflect/ReflectionRemapper.java b/src/main/java/io/papermc/paper/pluginremap/reflect/ReflectionRemapper.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a3045afbc0cc057e99189b909367b21cf6a9e03f +--- /dev/null ++++ b/src/main/java/io/papermc/paper/pluginremap/reflect/ReflectionRemapper.java +@@ -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); ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/util/MappingEnvironment.java b/src/main/java/io/papermc/paper/util/MappingEnvironment.java +index 8e4229634d41a42b3d93948eebb77def7c0c72b1..4477944f632a6b3936960ee80f9d898d3b7eed19 100644 +--- a/src/main/java/io/papermc/paper/util/MappingEnvironment.java ++++ b/src/main/java/io/papermc/paper/util/MappingEnvironment.java +@@ -10,6 +10,8 @@ import org.checkerframework.framework.qual.DefaultQualifier; + + @DefaultQualifier(NonNull.class) + public final class MappingEnvironment { ++ public static final boolean DISABLE_PLUGIN_REMAPPING = Boolean.getBoolean("paper.disablePluginRemapping"); ++ public static final String LEGACY_CB_VERSION = "v1_21_R2"; + private static final @Nullable String MAPPINGS_HASH = readMappingsHash(); + private static final boolean REOBF = checkReobf(); + +diff --git a/src/main/java/io/papermc/paper/util/StacktraceDeobfuscator.java b/src/main/java/io/papermc/paper/util/StacktraceDeobfuscator.java +index 242811578a786e3807a1a7019d472d5a68f87116..0b65fdf53124f3dd042b2363b1b8df8e1ca7de00 100644 +--- a/src/main/java/io/papermc/paper/util/StacktraceDeobfuscator.java ++++ b/src/main/java/io/papermc/paper/util/StacktraceDeobfuscator.java +@@ -29,6 +29,9 @@ public enum StacktraceDeobfuscator { + }); + + public void deobfuscateThrowable(final Throwable throwable) { ++ if (!MappingEnvironment.reobf()) { ++ return; ++ } + if (GlobalConfiguration.get() != null && !GlobalConfiguration.get().logging.deobfuscateStacktraces) { // handle null as true + return; + } +@@ -44,6 +47,9 @@ public enum StacktraceDeobfuscator { + } + + public StackTraceElement[] deobfuscateStacktrace(final StackTraceElement[] traceElements) { ++ if (!MappingEnvironment.reobf()) { ++ return traceElements; ++ } + if (GlobalConfiguration.get() != null && !GlobalConfiguration.get().logging.deobfuscateStacktraces) { // handle null as true + return traceElements; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java +index 7d2d5c4ee244e7118a4c38628e2e69c79b11b98d..371d31266a532e59c49dbb106e354296b119fa5e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java +@@ -131,36 +131,26 @@ public class Commodore { + } + + // Paper start - Plugin rewrites +- private static final Map<String, String> SEARCH_AND_REMOVE = initReplacementsMap(); +- private static Map<String, String> initReplacementsMap() { +- Map<String, String> getAndRemove = new HashMap<>(); +- // Be wary of maven shade's relocations +- +- 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); ++ private static final String CB_PACKAGE_PREFIX = "org/bukkit/".concat("craftbukkit/"); ++ private static final String LEGACY_CB_PACKAGE_PREFIX = CB_PACKAGE_PREFIX + io.papermc.paper.util.MappingEnvironment.LEGACY_CB_VERSION + "/"; ++ private static String runtimeCbPkgPrefix() { ++ if (io.papermc.paper.util.MappingEnvironment.reobf()) { ++ return LEGACY_CB_PACKAGE_PREFIX; + } +- +- return getAndRemove; ++ return CB_PACKAGE_PREFIX; + } + + @Nonnull + private static String getOriginalOrRewrite(@Nonnull String original) + { +- String rewrite = null; +- for ( Map.Entry<String, String> entry : SEARCH_AND_REMOVE.entrySet() ) +- { +- if ( original.contains( entry.getKey() ) ) +- { +- rewrite = original.replace( entry.getValue(), "" ); ++ // Relocation is applied in reobf, and when mappings are present they handle the relocation ++ if (!io.papermc.paper.util.MappingEnvironment.reobf() && !io.papermc.paper.util.MappingEnvironment.hasMappings()) { ++ if (original.contains(LEGACY_CB_PACKAGE_PREFIX)) { ++ original = original.replace(LEGACY_CB_PACKAGE_PREFIX, CB_PACKAGE_PREFIX); + } + } + +- return rewrite != null ? rewrite : original; ++ return original; + } + // Paper end - Plugin rewrites + +@@ -245,6 +235,7 @@ public class Commodore { + 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) { + final Set<RerouteMethodData> rerouteMethodData = new HashSet<>(); + String className; +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 8e747d48fb76c3f8bacb28f270754d5caeca4445..07316f0043639c608dddadd1b3af871f4c3129d0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -75,6 +75,7 @@ import org.bukkit.potion.PotionType; + @SuppressWarnings("deprecation") + public final class CraftMagicNumbers implements UnsafeValues { + 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(); + +@@ -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."); + } + +- if (toCheck.isOlderThan(ApiVersion.FLATTENING)) { ++ if (!DISABLE_OLD_API_SUPPORT && toCheck.isOlderThan(ApiVersion.FLATTENING)) { // Paper + CraftLegacy.init(); + } + +@@ -362,6 +363,12 @@ public final class CraftMagicNumbers implements UnsafeValues { + + @Override + 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 { + clazz = this.commodore.convert(clazz, pdf.getName(), ApiVersion.getOrCreateVersion(pdf.getAPIVersion()), ((CraftServer) Bukkit.getServer()).activeCompatibilities); + } catch (Exception ex) { |