From 228c508df0539476251dafc7d47d0bc9ce632347 Mon Sep 17 00:00:00 2001 From: Bjorn Beishline <75190918+BjornTheProgrammer@users.noreply.github.com> Date: Wed, 21 Jan 2026 21:07:14 -0700 Subject: [PATCH] Fix plugin loading exception --- .../PatchBukkitPluginClassLoader.java | 160 ++++++++++++++++++ .../java/org/patchbukkit/PluginLoader.java | 20 ++- rust/src/java/jvm.rs | 8 +- rust/src/lib.rs | 7 +- rust/src/plugin/manager.rs | 5 +- 5 files changed, 185 insertions(+), 15 deletions(-) create mode 100644 java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitPluginClassLoader.java diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitPluginClassLoader.java b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitPluginClassLoader.java new file mode 100644 index 0000000..8cf4eee --- /dev/null +++ b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitPluginClassLoader.java @@ -0,0 +1,160 @@ +package org.patchbukkit.loader; + +import io.papermc.paper.plugin.configuration.PluginMeta; +import io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader; +import io.papermc.paper.plugin.provider.classloader.PluginClassLoaderGroup; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import org.bukkit.plugin.InvalidDescriptionException; +import org.bukkit.plugin.PluginDescriptionFile; +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.Nullable; + +public class PatchBukkitPluginClassLoader + extends URLClassLoader + implements ConfiguredPluginClassLoader +{ + + private final PluginDescriptionFile description; + private final File dataFolder; + private final File file; + private JavaPlugin plugin; + + static { + ClassLoader.registerAsParallelCapable(); + } + + public PatchBukkitPluginClassLoader(ClassLoader parent, File file) + throws MalformedURLException, InvalidDescriptionException { + super(new URL[] { file.toURI().toURL() }, parent); + this.file = file; + this.description = loadDescription(file); + this.dataFolder = new File(file.getParentFile(), description.getName()); + } + + private static PluginDescriptionFile loadDescription(File file) + throws InvalidDescriptionException { + try (JarFile jar = new JarFile(file)) { + JarEntry entry = jar.getJarEntry("paper-plugin.yml"); + if (entry == null) { + entry = jar.getJarEntry("plugin.yml"); + } + if (entry == null) { + throw new InvalidDescriptionException( + "Jar does not contain plugin.yml" + ); + } + try (InputStream stream = jar.getInputStream(entry)) { + return new PluginDescriptionFile(stream); + } + } catch (IOException e) { + throw new InvalidDescriptionException(e); + } + } + + /** + * Child-first class loading: try to load from plugin JAR before delegating to parent. + * This is critical for plugin classes to be loaded by this classloader. + */ + @Override + protected Class loadClass(String name, boolean resolve) + throws ClassNotFoundException { + synchronized (getClassLoadingLock(name)) { + // First, check if already loaded + Class c = findLoadedClass(name); + + if (c == null) { + // For plugin-specific classes, try to load from JAR first (child-first) + // For JDK and server classes, delegate to parent + if ( + !name.startsWith("java.") && + !name.startsWith("jdk.") && + !name.startsWith("sun.") && + !name.startsWith("javax.") + ) { + try { + // Try to find in plugin JAR first + c = findClass(name); + } catch (ClassNotFoundException e) { + // Not in JAR, delegate to parent + } + } + + // If not found in JAR (or is a system class), delegate to parent + if (c == null) { + c = getParent().loadClass(name); + } + } + + if (resolve) { + resolveClass(c); + } + return c; + } + } + + @Override + public PluginMeta getConfiguration() { + return description; + } + + @Override + public Class loadClass( + String name, + boolean resolve, + boolean checkGlobal, + boolean checkLibraries + ) throws ClassNotFoundException { + return loadClass(name, resolve); + } + + @Override + public void init(JavaPlugin plugin) { + this.plugin = plugin; + if (!dataFolder.exists()) { + dataFolder.mkdirs(); + } + plugin.init( + org.bukkit.Bukkit.getServer(), + description, + dataFolder, + file, + this, + description, + com.destroystokyo.paper.utils.PaperPluginLogger.getLogger( + description + ) + ); + } + + @Override + @Nullable + public JavaPlugin getPlugin() { + return plugin; + } + + @Override + @Nullable + public PluginClassLoaderGroup getGroup() { + return null; + } + + public PluginDescriptionFile getDescription() { + return description; + } + + public File getDataFolder() { + return dataFolder; + } + + @Override + public void close() throws IOException { + super.close(); + } +} diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/PluginLoader.java b/java/patchbukkit/src/main/java/org/patchbukkit/PluginLoader.java index f5db3d0..478985d 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/PluginLoader.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/PluginLoader.java @@ -4,16 +4,32 @@ import io.papermc.paper.plugin.provider.util.ProviderUtil; import java.io.File; import java.net.URL; import java.net.URLClassLoader; +import java.util.logging.Level; +import java.util.logging.Logger; import org.bukkit.plugin.java.JavaPlugin; public class PluginLoader { - public JavaPlugin createPlugin(String mainClass) { + private static final Logger LOGGER = Logger.getLogger("PatchBukkit"); + + public static JavaPlugin createPlugin(String jarPath, String mainClass) { try { + File jarFile = new File(jarPath); + if (!jarFile.exists()) { + LOGGER.severe("Plugin file does not exist: " + jarPath); + return null; + } + + PatchBukkitPluginClassLoader classLoader = + new PatchBukkitPluginClassLoader( + PluginLoader.class.getClassLoader(), + jarFile + ); + return ProviderUtil.loadClass( mainClass, JavaPlugin.class, - this.getClass().getClassLoader() + classLoader ); } catch (Exception e) { e.printStackTrace(); diff --git a/rust/src/java/jvm.rs b/rust/src/java/jvm.rs index 07da561..7009b03 100644 --- a/rust/src/java/jvm.rs +++ b/rust/src/java/jvm.rs @@ -1,13 +1,9 @@ use std::path::PathBuf; -use j4rs::{ClasspathEntry, InvocationArg, Jvm, JvmBuilder}; +use j4rs::{InvocationArg, Jvm, JvmBuilder}; -pub fn initialize_jvm( - classpath_entries: Vec, - j4rs_folder: &PathBuf, -) -> Result { +pub fn initialize_jvm(j4rs_folder: &PathBuf) -> Result { let jvm = JvmBuilder::new() - .classpath_entries(classpath_entries) .with_base_path(j4rs_folder) .build() .map_err(|err| format!("JVM failed to init: {:?}", err))?; diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 3dd5f8f..33df551 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1,6 +1,5 @@ use std::sync::Arc; -use j4rs::ClasspathEntry; use pumpkin::plugin::Context; use pumpkin_api_macros::{plugin_impl, plugin_method}; @@ -60,17 +59,13 @@ async fn on_load_inner(_plugin: &mut MyPlugin, server: Arc) -> Result<( } } } - let classpath_entries = jar_paths - .iter() - .map(|entry| ClasspathEntry::new(entry)) - .collect(); // Manage embedded resources cleanup_stale_files(&dirs.j4rs); sync_embedded_resources(&dirs.j4rs)?; // Initialize JVM and PatchBukkit server - let jvm = initialize_jvm(classpath_entries, &dirs.j4rs)?; + let jvm = initialize_jvm(&dirs.j4rs)?; setup_patchbukkit_server(&jvm)?; plugin_manager diff --git a/rust/src/plugin/manager.rs b/rust/src/plugin/manager.rs index b97b67f..2199447 100644 --- a/rust/src/plugin/manager.rs +++ b/rust/src/plugin/manager.rs @@ -158,7 +158,10 @@ impl PluginManager { let result = jvm.invoke( &loader, "createPlugin", - &[InvocationArg::try_from(&plugin.main_class)?], + &[ + InvocationArg::try_from(&plugin.path.to_string_lossy().to_string())?, + InvocationArg::try_from(&plugin.main_class)?, + ], ); match result {