Fix plugin loading exception

This commit is contained in:
Bjorn Beishline 2026-01-21 21:07:14 -07:00
parent 9f6ea9d3d4
commit 228c508df0
5 changed files with 185 additions and 15 deletions

View File

@ -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();
}
}

View File

@ -4,16 +4,32 @@ import io.papermc.paper.plugin.provider.util.ProviderUtil;
import java.io.File; import java.io.File;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
public class PluginLoader { 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 { 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( return ProviderUtil.loadClass(
mainClass, mainClass,
JavaPlugin.class, JavaPlugin.class,
this.getClass().getClassLoader() classLoader
); );
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();

View File

@ -1,13 +1,9 @@
use std::path::PathBuf; use std::path::PathBuf;
use j4rs::{ClasspathEntry, InvocationArg, Jvm, JvmBuilder}; use j4rs::{InvocationArg, Jvm, JvmBuilder};
pub fn initialize_jvm( pub fn initialize_jvm(j4rs_folder: &PathBuf) -> Result<Jvm, String> {
classpath_entries: Vec<ClasspathEntry>,
j4rs_folder: &PathBuf,
) -> Result<Jvm, String> {
let jvm = JvmBuilder::new() let jvm = JvmBuilder::new()
.classpath_entries(classpath_entries)
.with_base_path(j4rs_folder) .with_base_path(j4rs_folder)
.build() .build()
.map_err(|err| format!("JVM failed to init: {:?}", err))?; .map_err(|err| format!("JVM failed to init: {:?}", err))?;

View File

@ -1,6 +1,5 @@
use std::sync::Arc; use std::sync::Arc;
use j4rs::ClasspathEntry;
use pumpkin::plugin::Context; use pumpkin::plugin::Context;
use pumpkin_api_macros::{plugin_impl, plugin_method}; use pumpkin_api_macros::{plugin_impl, plugin_method};
@ -60,17 +59,13 @@ async fn on_load_inner(_plugin: &mut MyPlugin, server: Arc<Context>) -> Result<(
} }
} }
} }
let classpath_entries = jar_paths
.iter()
.map(|entry| ClasspathEntry::new(entry))
.collect();
// Manage embedded resources // Manage embedded resources
cleanup_stale_files(&dirs.j4rs); cleanup_stale_files(&dirs.j4rs);
sync_embedded_resources(&dirs.j4rs)?; sync_embedded_resources(&dirs.j4rs)?;
// Initialize JVM and PatchBukkit server // Initialize JVM and PatchBukkit server
let jvm = initialize_jvm(classpath_entries, &dirs.j4rs)?; let jvm = initialize_jvm(&dirs.j4rs)?;
setup_patchbukkit_server(&jvm)?; setup_patchbukkit_server(&jvm)?;
plugin_manager plugin_manager

View File

@ -158,7 +158,10 @@ impl PluginManager {
let result = jvm.invoke( let result = jvm.invoke(
&loader, &loader,
"createPlugin", "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 { match result {