From 88ccd5e1feda186f05ca0a3b3656a53c59744921 Mon Sep 17 00:00:00 2001 From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> Date: Sun, 28 Apr 2024 14:46:14 -0700 Subject: [PATCH] Remap plugin libraries with namespace set to spigot --- patches/server/0020-Plugin-remapping.patch | 126 +++++++-- ...ion-calls-in-plugins-using-internals.patch | 255 +++++++++++++++++ ...-Modify-library-loader-jars-bytecode.patch | 261 ------------------ ...braries-with-namespace-set-to-spigot.patch | 210 -------------- 4 files changed, 363 insertions(+), 489 deletions(-) delete mode 100644 patches/server/1046-Modify-library-loader-jars-bytecode.patch delete mode 100644 patches/server/1047-Remap-plugin-libraries-with-namespace-set-to-spigot.patch diff --git a/patches/server/0020-Plugin-remapping.patch b/patches/server/0020-Plugin-remapping.patch index 7f868edfd..9cfcaa768 100644 --- a/patches/server/0020-Plugin-remapping.patch +++ b/patches/server/0020-Plugin-remapping.patch @@ -66,22 +66,24 @@ index 65fb16941fa7e3a9b300696fb6bd2b562bca48cd..5ffd1d7c130e01a4a7516b361e48bfaf + mainClass.set(null as String?) +} diff --git a/src/main/java/io/papermc/paper/plugin/PluginInitializerManager.java b/src/main/java/io/papermc/paper/plugin/PluginInitializerManager.java -index 708e5bb9bbf0476fcc2c4b92c6830b094703b43e..bb1cfa8ea8b11fc36ea72c8e382b8554bccd0ce5 100644 +index 708e5bb9bbf0476fcc2c4b92c6830b094703b43e..6f14cb9a73faa1d0ae2939d08809d9f6c2a99e1d 100644 --- a/src/main/java/io/papermc/paper/plugin/PluginInitializerManager.java +++ b/src/main/java/io/papermc/paper/plugin/PluginInitializerManager.java -@@ -6,10 +6,10 @@ import io.papermc.paper.plugin.entrypoint.Entrypoint; +@@ -6,10 +6,12 @@ import io.papermc.paper.plugin.entrypoint.Entrypoint; import io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler; import io.papermc.paper.plugin.provider.PluginProvider; import io.papermc.paper.plugin.provider.type.paper.PaperPluginParent; +import io.papermc.paper.pluginremap.PluginRemapper; ++import java.util.function.Function; import joptsimple.OptionSet; import net.minecraft.server.dedicated.DedicatedServer; import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.craftbukkit.CraftServer; ++import org.bukkit.plugin.java.LibraryLoader; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; -@@ -25,10 +25,14 @@ public class PluginInitializerManager { +@@ -25,10 +27,15 @@ public class PluginInitializerManager { private static PluginInitializerManager impl; private final Path pluginDirectory; private final Path updateDirectory; @@ -93,10 +95,11 @@ index 708e5bb9bbf0476fcc2c4b92c6830b094703b43e..bb1cfa8ea8b11fc36ea72c8e382b8554 + this.pluginRemapper = Boolean.getBoolean("paper.disable-plugin-rewriting") + ? null + : PluginRemapper.create(pluginDirectory); ++ LibraryLoader.REMAPPER = this.pluginRemapper == null ? Function.identity() : this.pluginRemapper::remapLibraries; } private static PluginInitializerManager parse(@NotNull final OptionSet minecraftOptionSet) throws Exception { -@@ -96,6 +100,7 @@ public class PluginInitializerManager { +@@ -96,6 +103,7 @@ public class PluginInitializerManager { public static void load(OptionSet optionSet) throws Exception { // We have to load the bukkit configuration inorder to get the update folder location. io.papermc.paper.plugin.PluginInitializerManager pluginSystem = io.papermc.paper.plugin.PluginInitializerManager.init(optionSet); @@ -104,6 +107,31 @@ index 708e5bb9bbf0476fcc2c4b92c6830b094703b43e..bb1cfa8ea8b11fc36ea72c8e382b8554 // Register the default plugin directory io.papermc.paper.plugin.util.EntrypointUtil.registerProvidersFromSource(io.papermc.paper.plugin.provider.source.DirectoryProviderSource.INSTANCE, pluginSystem.pluginDirectoryPath()); +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 f38ecd7f65dc24e4a3f0bc675e3730287ac353f1..f576060c8fe872772bbafe2016fc9b83a3c095f1 100644 +--- a/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java ++++ b/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java +@@ -1,5 +1,6 @@ + package io.papermc.paper.plugin.loader; + ++import io.papermc.paper.plugin.PluginInitializerManager; + import io.papermc.paper.plugin.bootstrap.PluginProviderContext; + import io.papermc.paper.plugin.loader.library.ClassPathLibrary; + import io.papermc.paper.plugin.loader.library.PaperLibraryStore; +@@ -45,9 +46,12 @@ public class PaperClasspathBuilder implements PluginClasspathBuilder { + } + + List paths = paperLibraryStore.getPaths(); ++ if (PluginInitializerManager.instance().pluginRemapper != null) { ++ paths = PluginInitializerManager.instance().pluginRemapper.remapLibraries(paths); ++ } + URL[] urls = new URL[paths.size()]; + for (int i = 0; i < paths.size(); i++) { +- Path path = paperLibraryStore.getPaths().get(i); ++ Path path = paths.get(i); + try { + urls[i] = path.toUri().toURL(); + } catch (MalformedURLException e) { diff --git a/src/main/java/io/papermc/paper/plugin/provider/source/DirectoryProviderSource.java b/src/main/java/io/papermc/paper/plugin/provider/source/DirectoryProviderSource.java index 226f457db6c1461c943c157b2b91e7450abc9dc6..0846d3a904e470ae1920c5c8be3df9c5dfc3de27 100644 --- a/src/main/java/io/papermc/paper/plugin/provider/source/DirectoryProviderSource.java @@ -347,10 +375,10 @@ index 0000000000000000000000000000000000000000..3a5bb5d2a45654385ca0bc15c81ef953 +} diff --git a/src/main/java/io/papermc/paper/pluginremap/PluginRemapper.java b/src/main/java/io/papermc/paper/pluginremap/PluginRemapper.java new file mode 100644 -index 0000000000000000000000000000000000000000..6f1e4ae352dcc6aacd9703b1653701f93974d1bd +index 0000000000000000000000000000000000000000..a1ce1307b2834f2415bdddbf42d80e2d69a480e1 --- /dev/null +++ b/src/main/java/io/papermc/paper/pluginremap/PluginRemapper.java -@@ -0,0 +1,371 @@ +@@ -0,0 +1,433 @@ +package io.papermc.paper.pluginremap; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; @@ -397,6 +425,7 @@ index 0000000000000000000000000000000000000000..6f1e4ae352dcc6aacd9703b1653701f9 + public static final boolean DEBUG_LOGGING = Boolean.getBoolean("Paper.PluginRemapperDebug"); + private static final String PAPER_REMAPPED = ".paper-remapped"; + private static final String UNKNOWN_ORIGIN = "unknown-origin"; ++ private static final String LIBRARIES = "libraries"; + private static final String EXTRA_PLUGINS = "extra-plugins"; + private static final String REMAP_CLASSPATH = "remap-classpath"; + private static final String REVERSED_MAPPINGS = "mappings/reversed"; @@ -407,6 +436,7 @@ index 0000000000000000000000000000000000000000..6f1e4ae352dcc6aacd9703b1653701f9 + private final RemappedPluginIndex remappedPlugins; + private final RemappedPluginIndex extraPlugins; + private final UnknownOriginRemappedPluginIndex unknownOrigin; ++ private final UnknownOriginRemappedPluginIndex libraries; + private @Nullable CompletableFuture reversedMappings; + + public PluginRemapper(final Path pluginsDir) { @@ -418,6 +448,7 @@ index 0000000000000000000000000000000000000000..6f1e4ae352dcc6aacd9703b1653701f9 + this.remappedPlugins = new RemappedPluginIndex(remappedPlugins, false); + this.extraPlugins = new RemappedPluginIndex(this.remappedPlugins.dir().resolve(EXTRA_PLUGINS), true); + this.unknownOrigin = new UnknownOriginRemappedPluginIndex(this.remappedPlugins.dir().resolve(UNKNOWN_ORIGIN)); ++ this.libraries = new UnknownOriginRemappedPluginIndex(this.remappedPlugins.dir().resolve(LIBRARIES)); + } + + public static @Nullable PluginRemapper create(final Path pluginsDir) { @@ -446,6 +477,7 @@ index 0000000000000000000000000000000000000000..6f1e4ae352dcc6aacd9703b1653701f9 + this.remappedPlugins.write(); + this.extraPlugins.write(); + this.unknownOrigin.write(clean); ++ this.libraries.write(clean); + } + + // Called on startup and reload @@ -465,6 +497,29 @@ index 0000000000000000000000000000000000000000..6f1e4ae352dcc6aacd9703b1653701f9 + this.save(false); + } + ++ public List remapLibraries(final List libraries) { ++ final List> tasks = new ArrayList<>(); ++ for (final Path lib : libraries) { ++ if (!lib.getFileName().toString().endsWith(".jar")) { ++ if (DEBUG_LOGGING) { ++ LOGGER.info("Library '{}' is not a jar.", libraries); ++ } ++ tasks.add(CompletableFuture.completedFuture(lib)); ++ continue; ++ } ++ final @Nullable Path cached = this.libraries.getIfPresent(lib); ++ if (cached != null) { ++ if (DEBUG_LOGGING) { ++ LOGGER.info("Library '{}' has not changed since last remap.", libraries); ++ } ++ tasks.add(CompletableFuture.completedFuture(cached)); ++ continue; ++ } ++ tasks.add(this.remapLibrary(this.libraries, lib)); ++ } ++ return waitForAll(tasks); ++ } ++ + public Path rewritePlugin(final Path plugin) { + // Already remapped + if (plugin.getParent().equals(this.remappedPlugins.dir()) @@ -585,6 +640,20 @@ index 0000000000000000000000000000000000000000..6f1e4ae352dcc6aacd9703b1653701f9 + }, executor).thenCompose(f -> f); + } + ++ private CompletableFuture remapPlugin( ++ final RemappedPluginIndex index, ++ final Path inputFile ++ ) { ++ return this.remap(index, inputFile, false); ++ } ++ ++ private CompletableFuture remapLibrary( ++ final RemappedPluginIndex index, ++ final Path inputFile ++ ) { ++ return this.remap(index, inputFile, true); ++ } ++ + /** + * Returns the remapped file if remapping was necessary, otherwise null. + * @@ -592,7 +661,11 @@ index 0000000000000000000000000000000000000000..6f1e4ae352dcc6aacd9703b1653701f9 + * @param inputFile input file + * @return remapped file, or inputFile if no remapping was necessary + */ -+ private CompletableFuture remapPlugin(final RemappedPluginIndex index, final Path inputFile) { ++ private CompletableFuture remap( ++ final RemappedPluginIndex index, ++ final Path inputFile, ++ final boolean library ++ ) { + final Path destination = index.input(inputFile); + + try (final FileSystem fs = FileSystems.newFileSystem(inputFile, new HashMap<>())) { @@ -608,18 +681,35 @@ index 0000000000000000000000000000000000000000..6f1e4ae352dcc6aacd9703b1653701f9 + } else { + ns = null; + } -+ if (ns != null && (ns.equals(InsertManifestAttribute.MOJANG_NAMESPACE) || ns.equals(InsertManifestAttribute.MOJANG_PLUS_YARN_NAMESPACE))) { -+ if (DEBUG_LOGGING) { -+ LOGGER.info("Plugin '{}' is already Mojang mapped.", inputFile); ++ final boolean mojangMappedManifest = ns != null && (ns.equals(InsertManifestAttribute.MOJANG_NAMESPACE) || ns.equals(InsertManifestAttribute.MOJANG_PLUS_YARN_NAMESPACE)); ++ if (library) { ++ if (mojangMappedManifest) { ++ if (DEBUG_LOGGING) { ++ LOGGER.info("Library '{}' is already Mojang mapped.", inputFile); ++ } ++ index.skip(inputFile); ++ return CompletableFuture.completedFuture(inputFile); ++ } else if (ns == null) { ++ if (DEBUG_LOGGING) { ++ LOGGER.info("Library '{}' does not specify a mappings namespace (not remapping).", inputFile); ++ } ++ index.skip(inputFile); ++ return CompletableFuture.completedFuture(inputFile); + } -+ index.skip(inputFile); -+ return CompletableFuture.completedFuture(inputFile); -+ } else if (ns == null && Files.exists(fs.getPath(PluginFileType.PAPER_PLUGIN_YML))) { -+ if (DEBUG_LOGGING) { -+ LOGGER.info("Plugin '{}' is a Paper plugin with no namespace specified.", inputFile); ++ } else { ++ if (mojangMappedManifest) { ++ if (DEBUG_LOGGING) { ++ LOGGER.info("Plugin '{}' is already Mojang mapped.", inputFile); ++ } ++ index.skip(inputFile); ++ return CompletableFuture.completedFuture(inputFile); ++ } else if (ns == null && Files.exists(fs.getPath(PluginFileType.PAPER_PLUGIN_YML))) { ++ if (DEBUG_LOGGING) { ++ LOGGER.info("Plugin '{}' is a Paper plugin with no namespace specified.", inputFile); ++ } ++ index.skip(inputFile); ++ return CompletableFuture.completedFuture(inputFile); + } -+ index.skip(inputFile); -+ return CompletableFuture.completedFuture(inputFile); + } + } catch (final IOException ex) { + throw new RuntimeException("Failed to open plugin jar " + inputFile, ex); @@ -643,7 +733,7 @@ index 0000000000000000000000000000000000000000..6f1e4ae352dcc6aacd9703b1653701f9 + } catch (final Exception ex) { + throw new RuntimeException("Failed to remap plugin jar '" + inputFile + "'", ex); + } -+ LOGGER.info("Done remapping plugin '{}' in {}ms.", inputFile, System.currentTimeMillis() - start); ++ LOGGER.info("Done remapping {} '{}' in {}ms.", library ? "library" : "plugin", inputFile, System.currentTimeMillis() - start); + return destination; + }, this.threadPool); + } 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 index f24134e20..1ac8ec3f8 100644 --- a/patches/server/0022-Remap-reflection-calls-in-plugins-using-internals.patch +++ b/patches/server/0022-Remap-reflection-calls-in-plugins-using-internals.patch @@ -43,6 +43,197 @@ index 893ad5e7c2d32ccd64962d95d146bbd317c28ab8..3d73ea0e63c97b2b08e719b7be7af389 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..405416dc3d1c8c58b4e0c880d8751ca319188f62 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/BytecodeModifyingURLClassLoader.java +@@ -0,0 +1,185 @@ ++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.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 modifier; ++ private final Map manifests = new ConcurrentHashMap<>(); ++ ++ public BytecodeModifyingURLClassLoader( ++ final URL[] urls, ++ final ClassLoader parent, ++ final Function 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); ++ if (this.getAndVerifyPackage(pkgname, man, url) == null) { ++ try { ++ if (man != null) { ++ this.definePackage(pkgname, man, url); ++ } 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, url) == 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..39182cdd17473da0123dc7172dce507eab29fedc 100644 --- a/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/PaperClassloaderBytecodeModifier.java @@ -66,6 +257,70 @@ index f9a2c55a354c877749db3f92956de802ae575788..39182cdd17473da0123dc7172dce507e + return classWriter.toByteArray(); } } +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..82032370e7896b621e37ee3726016440e177619f 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,11 @@ 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 java.io.IOException; + import java.net.MalformedURLException; + import java.net.URL; +@@ -17,6 +16,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 +60,8 @@ public class PaperClasspathBuilder implements PluginClasspathBuilder { + } + + try { +- return new PaperPluginClassLoader(logger, source, jarFile, configuration, this.getClass().getClassLoader(), new URLClassLoader(urls, getClass().getClassLoader())); ++ final URLClassLoader libraryLoader = 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..31f05a7336ea124d24a5059652a2950a9f672758 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,11 @@ + 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 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 +17,10 @@ import java.util.jar.JarFile; + + class SpigotPluginProviderFactory implements PluginTypeFactory { + ++ static { ++ 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..c653d1f5af8b407cfba715e6027dbb695046892a diff --git a/patches/server/1046-Modify-library-loader-jars-bytecode.patch b/patches/server/1046-Modify-library-loader-jars-bytecode.patch deleted file mode 100644 index 126560a15..000000000 --- a/patches/server/1046-Modify-library-loader-jars-bytecode.patch +++ /dev/null @@ -1,261 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Sun, 28 Apr 2024 11:12:14 -0700 -Subject: [PATCH] Modify library loader jars bytecode - - -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..405416dc3d1c8c58b4e0c880d8751ca319188f62 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/BytecodeModifyingURLClassLoader.java -@@ -0,0 +1,185 @@ -+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.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 modifier; -+ private final Map manifests = new ConcurrentHashMap<>(); -+ -+ public BytecodeModifyingURLClassLoader( -+ final URL[] urls, -+ final ClassLoader parent, -+ final Function 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); -+ if (this.getAndVerifyPackage(pkgname, man, url) == null) { -+ try { -+ if (man != null) { -+ this.definePackage(pkgname, man, url); -+ } 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, url) == 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/loader/PaperClasspathBuilder.java b/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java -index f38ecd7f65dc24e4a3f0bc675e3730287ac353f1..ca6cb891e9da9d7e08f1a82fab212d2063cc9ef6 100644 ---- a/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java -+++ b/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java -@@ -1,12 +1,11 @@ - package io.papermc.paper.plugin.loader; - - 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 java.io.IOException; - import java.net.MalformedURLException; - import java.net.URL; -@@ -16,6 +15,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 { - -@@ -56,7 +56,8 @@ public class PaperClasspathBuilder implements PluginClasspathBuilder { - } - - try { -- return new PaperPluginClassLoader(logger, source, jarFile, configuration, this.getClass().getClassLoader(), new URLClassLoader(urls, getClass().getClassLoader())); -+ final URLClassLoader libraryLoader = 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..31f05a7336ea124d24a5059652a2950a9f672758 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,11 @@ - 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 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 +17,10 @@ import java.util.jar.JarFile; - - class SpigotPluginProviderFactory implements PluginTypeFactory { - -+ static { -+ 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/patches/server/1047-Remap-plugin-libraries-with-namespace-set-to-spigot.patch b/patches/server/1047-Remap-plugin-libraries-with-namespace-set-to-spigot.patch deleted file mode 100644 index 03df91b0f..000000000 --- a/patches/server/1047-Remap-plugin-libraries-with-namespace-set-to-spigot.patch +++ /dev/null @@ -1,210 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Sun, 28 Apr 2024 13:54:18 -0700 -Subject: [PATCH] Remap plugin libraries with namespace set to spigot - - -diff --git a/src/main/java/io/papermc/paper/plugin/PluginInitializerManager.java b/src/main/java/io/papermc/paper/plugin/PluginInitializerManager.java -index bb1cfa8ea8b11fc36ea72c8e382b8554bccd0ce5..6f14cb9a73faa1d0ae2939d08809d9f6c2a99e1d 100644 ---- a/src/main/java/io/papermc/paper/plugin/PluginInitializerManager.java -+++ b/src/main/java/io/papermc/paper/plugin/PluginInitializerManager.java -@@ -7,9 +7,11 @@ import io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler; - import io.papermc.paper.plugin.provider.PluginProvider; - import io.papermc.paper.plugin.provider.type.paper.PaperPluginParent; - import io.papermc.paper.pluginremap.PluginRemapper; -+import java.util.function.Function; - import joptsimple.OptionSet; - import net.minecraft.server.dedicated.DedicatedServer; - import org.bukkit.configuration.file.YamlConfiguration; -+import org.bukkit.plugin.java.LibraryLoader; - import org.jetbrains.annotations.NotNull; - import org.jetbrains.annotations.Nullable; - import org.slf4j.Logger; -@@ -33,6 +35,7 @@ public class PluginInitializerManager { - this.pluginRemapper = Boolean.getBoolean("paper.disable-plugin-rewriting") - ? null - : PluginRemapper.create(pluginDirectory); -+ LibraryLoader.REMAPPER = this.pluginRemapper == null ? Function.identity() : this.pluginRemapper::remapLibraries; - } - - private static PluginInitializerManager parse(@NotNull final OptionSet minecraftOptionSet) throws Exception { -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 ca6cb891e9da9d7e08f1a82fab212d2063cc9ef6..82032370e7896b621e37ee3726016440e177619f 100644 ---- a/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java -+++ b/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java -@@ -1,5 +1,6 @@ - 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; -@@ -45,9 +46,12 @@ public class PaperClasspathBuilder implements PluginClasspathBuilder { - } - - List paths = paperLibraryStore.getPaths(); -+ if (PluginInitializerManager.instance().pluginRemapper != null) { -+ paths = PluginInitializerManager.instance().pluginRemapper.remapLibraries(paths); -+ } - URL[] urls = new URL[paths.size()]; - for (int i = 0; i < paths.size(); i++) { -- Path path = paperLibraryStore.getPaths().get(i); -+ Path path = paths.get(i); - try { - urls[i] = path.toUri().toURL(); - } catch (MalformedURLException e) { -diff --git a/src/main/java/io/papermc/paper/pluginremap/PluginRemapper.java b/src/main/java/io/papermc/paper/pluginremap/PluginRemapper.java -index 6f1e4ae352dcc6aacd9703b1653701f93974d1bd..a1ce1307b2834f2415bdddbf42d80e2d69a480e1 100644 ---- a/src/main/java/io/papermc/paper/pluginremap/PluginRemapper.java -+++ b/src/main/java/io/papermc/paper/pluginremap/PluginRemapper.java -@@ -44,6 +44,7 @@ public final class PluginRemapper { - public static final boolean DEBUG_LOGGING = Boolean.getBoolean("Paper.PluginRemapperDebug"); - private static final String PAPER_REMAPPED = ".paper-remapped"; - private static final String UNKNOWN_ORIGIN = "unknown-origin"; -+ private static final String LIBRARIES = "libraries"; - private static final String EXTRA_PLUGINS = "extra-plugins"; - private static final String REMAP_CLASSPATH = "remap-classpath"; - private static final String REVERSED_MAPPINGS = "mappings/reversed"; -@@ -54,6 +55,7 @@ public final class PluginRemapper { - private final RemappedPluginIndex remappedPlugins; - private final RemappedPluginIndex extraPlugins; - private final UnknownOriginRemappedPluginIndex unknownOrigin; -+ private final UnknownOriginRemappedPluginIndex libraries; - private @Nullable CompletableFuture reversedMappings; - - public PluginRemapper(final Path pluginsDir) { -@@ -65,6 +67,7 @@ public final class PluginRemapper { - this.remappedPlugins = new RemappedPluginIndex(remappedPlugins, false); - this.extraPlugins = new RemappedPluginIndex(this.remappedPlugins.dir().resolve(EXTRA_PLUGINS), true); - this.unknownOrigin = new UnknownOriginRemappedPluginIndex(this.remappedPlugins.dir().resolve(UNKNOWN_ORIGIN)); -+ this.libraries = new UnknownOriginRemappedPluginIndex(this.remappedPlugins.dir().resolve(LIBRARIES)); - } - - public static @Nullable PluginRemapper create(final Path pluginsDir) { -@@ -93,6 +96,7 @@ public final class PluginRemapper { - this.remappedPlugins.write(); - this.extraPlugins.write(); - this.unknownOrigin.write(clean); -+ this.libraries.write(clean); - } - - // Called on startup and reload -@@ -112,6 +116,29 @@ public final class PluginRemapper { - this.save(false); - } - -+ public List remapLibraries(final List libraries) { -+ final List> tasks = new ArrayList<>(); -+ for (final Path lib : libraries) { -+ if (!lib.getFileName().toString().endsWith(".jar")) { -+ if (DEBUG_LOGGING) { -+ LOGGER.info("Library '{}' is not a jar.", libraries); -+ } -+ tasks.add(CompletableFuture.completedFuture(lib)); -+ continue; -+ } -+ final @Nullable Path cached = this.libraries.getIfPresent(lib); -+ if (cached != null) { -+ if (DEBUG_LOGGING) { -+ LOGGER.info("Library '{}' has not changed since last remap.", libraries); -+ } -+ tasks.add(CompletableFuture.completedFuture(cached)); -+ continue; -+ } -+ tasks.add(this.remapLibrary(this.libraries, lib)); -+ } -+ return waitForAll(tasks); -+ } -+ - public Path rewritePlugin(final Path plugin) { - // Already remapped - if (plugin.getParent().equals(this.remappedPlugins.dir()) -@@ -232,6 +259,20 @@ public final class PluginRemapper { - }, executor).thenCompose(f -> f); - } - -+ private CompletableFuture remapPlugin( -+ final RemappedPluginIndex index, -+ final Path inputFile -+ ) { -+ return this.remap(index, inputFile, false); -+ } -+ -+ private CompletableFuture remapLibrary( -+ final RemappedPluginIndex index, -+ final Path inputFile -+ ) { -+ return this.remap(index, inputFile, true); -+ } -+ - /** - * Returns the remapped file if remapping was necessary, otherwise null. - * -@@ -239,7 +280,11 @@ public final class PluginRemapper { - * @param inputFile input file - * @return remapped file, or inputFile if no remapping was necessary - */ -- private CompletableFuture remapPlugin(final RemappedPluginIndex index, final Path inputFile) { -+ private CompletableFuture remap( -+ final RemappedPluginIndex index, -+ final Path inputFile, -+ final boolean library -+ ) { - final Path destination = index.input(inputFile); - - try (final FileSystem fs = FileSystems.newFileSystem(inputFile, new HashMap<>())) { -@@ -255,18 +300,35 @@ public final class PluginRemapper { - } else { - ns = null; - } -- if (ns != null && (ns.equals(InsertManifestAttribute.MOJANG_NAMESPACE) || ns.equals(InsertManifestAttribute.MOJANG_PLUS_YARN_NAMESPACE))) { -- if (DEBUG_LOGGING) { -- LOGGER.info("Plugin '{}' is already Mojang mapped.", inputFile); -+ final boolean mojangMappedManifest = ns != null && (ns.equals(InsertManifestAttribute.MOJANG_NAMESPACE) || ns.equals(InsertManifestAttribute.MOJANG_PLUS_YARN_NAMESPACE)); -+ if (library) { -+ if (mojangMappedManifest) { -+ if (DEBUG_LOGGING) { -+ LOGGER.info("Library '{}' is already Mojang mapped.", inputFile); -+ } -+ index.skip(inputFile); -+ return CompletableFuture.completedFuture(inputFile); -+ } else if (ns == null) { -+ if (DEBUG_LOGGING) { -+ LOGGER.info("Library '{}' does not specify a mappings namespace (not remapping).", inputFile); -+ } -+ index.skip(inputFile); -+ return CompletableFuture.completedFuture(inputFile); - } -- index.skip(inputFile); -- return CompletableFuture.completedFuture(inputFile); -- } else if (ns == null && Files.exists(fs.getPath(PluginFileType.PAPER_PLUGIN_YML))) { -- if (DEBUG_LOGGING) { -- LOGGER.info("Plugin '{}' is a Paper plugin with no namespace specified.", inputFile); -+ } else { -+ if (mojangMappedManifest) { -+ if (DEBUG_LOGGING) { -+ LOGGER.info("Plugin '{}' is already Mojang mapped.", inputFile); -+ } -+ index.skip(inputFile); -+ return CompletableFuture.completedFuture(inputFile); -+ } else if (ns == null && Files.exists(fs.getPath(PluginFileType.PAPER_PLUGIN_YML))) { -+ if (DEBUG_LOGGING) { -+ LOGGER.info("Plugin '{}' is a Paper plugin with no namespace specified.", inputFile); -+ } -+ index.skip(inputFile); -+ return CompletableFuture.completedFuture(inputFile); - } -- index.skip(inputFile); -- return CompletableFuture.completedFuture(inputFile); - } - } catch (final IOException ex) { - throw new RuntimeException("Failed to open plugin jar " + inputFile, ex); -@@ -290,7 +352,7 @@ public final class PluginRemapper { - } catch (final Exception ex) { - throw new RuntimeException("Failed to remap plugin jar '" + inputFile + "'", ex); - } -- LOGGER.info("Done remapping plugin '{}' in {}ms.", inputFile, System.currentTimeMillis() - start); -+ LOGGER.info("Done remapping {} '{}' in {}ms.", library ? "library" : "plugin", inputFile, System.currentTimeMillis() - start); - return destination; - }, this.threadPool); - }