diff --git a/README.md b/README.md index eb36f22..2ea6a9e 100644 --- a/README.md +++ b/README.md @@ -7,17 +7,21 @@ A plugin for [PumpkinMC](https://pumpkinmc.org/) that adds support for [PaperMC] > [!IMPORTANT] > Currently PatchBukkit is in heavy development, as such releases are not yet available. -1. Install [java](https://docs.papermc.io/misc/java-install/) -2. Download the plugin in releases `patchbukkit-mac.dylib`, `patchbukkit-linux-aarch64.so`, `patchbukkit-linux-x86.so`, or `patchbukkit-windows-x86.dll`. -3. Add it into your PumpkinMC plugin directory (this gets created after runing PumpkinMC for the first time). -4. Run PumpkinMC again, and you should see a new directory in PumpkinMC called `patchbukkit`. You can add any `bukkit`, `spigot`, or `paper` plugin you wish, and it will run. +1. **Requirement**: Install Java 25 or newer. +2. **Download**: Grab the library matching your operating system from the Releases page: + +- patchbukkit-windows-x86.dll (Windows) +- patchbukkit-linux-x86.so (Linux x86_64) +- patchbukkit-linux-aarch64.so (Linux ARM64/ Raspberry Pi) +- patchbukkit-mac.dylib (macOS) + +3. **Deploy**: Place the downloaded file into your PumpkinMC plugins/ directory. (Run PumpkinMC once to generate this folder if it doesn't exist). +4. **Initialize**: Restart PumpkinMC. This creates a new /patchbukkit directory in your server root. +5. **Add Plugins**: Drop your .jar plugin files (Paper/Spigot/Bukkit) into the newly created patchbukkit/ folder and restart. ## Development If you wish to contribute to PatchBukkit, follow the following steps: -1. Build PumpkinMC from source with the `nightly-2025-12-11` rust toolchain. -2. Clone this repository -3. `cd` into the `java` directory and run `./gradlew build` -4. `cd` into the `rust` directory and run `cargo build` -5. Copy the `target/debug/patchbukkit` binary to the `plugins` directory in your PumpkinMC server. +1. Run `./build.sh` +2. Copy the `target/debug/patchbukkit` binary to the `plugins` directory in your PumpkinMC server. diff --git a/rust/Cargo.lock b/rust/Cargo.lock index c588fd6..3b92cdd 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1689,7 +1689,6 @@ version = "0.1.0" dependencies = [ "anyhow", "env_logger", - "futures", "glob", "j4rs", "j4rs_derive", @@ -1963,7 +1962,7 @@ dependencies = [ [[package]] name = "pumpkin" version = "0.1.0-dev+1.21.11" -source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#72bd4dfbef6326063b1bc0752e2789402f5c9197" +source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#f93be68caa3969502c13d5d284da59fe1017ed9a" dependencies = [ "arc-swap", "base64", @@ -2006,7 +2005,7 @@ dependencies = [ [[package]] name = "pumpkin-api-macros" version = "0.1.0-dev+1.21.11" -source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#72bd4dfbef6326063b1bc0752e2789402f5c9197" +source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#f93be68caa3969502c13d5d284da59fe1017ed9a" dependencies = [ "proc-macro-error2", "proc-macro2", @@ -2017,7 +2016,7 @@ dependencies = [ [[package]] name = "pumpkin-config" version = "0.1.0-dev+1.21.11" -source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#72bd4dfbef6326063b1bc0752e2789402f5c9197" +source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#f93be68caa3969502c13d5d284da59fe1017ed9a" dependencies = [ "log", "pumpkin-util", @@ -2029,7 +2028,7 @@ dependencies = [ [[package]] name = "pumpkin-data" version = "0.1.0-dev+1.21.11" -source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#72bd4dfbef6326063b1bc0752e2789402f5c9197" +source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#f93be68caa3969502c13d5d284da59fe1017ed9a" dependencies = [ "crc-fast", "heck", @@ -2049,7 +2048,7 @@ dependencies = [ [[package]] name = "pumpkin-inventory" version = "0.1.0-dev+1.21.11" -source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#72bd4dfbef6326063b1bc0752e2789402f5c9197" +source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#f93be68caa3969502c13d5d284da59fe1017ed9a" dependencies = [ "crossbeam-utils", "log", @@ -2064,7 +2063,7 @@ dependencies = [ [[package]] name = "pumpkin-macros" version = "0.1.0-dev+1.21.11" -source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#72bd4dfbef6326063b1bc0752e2789402f5c9197" +source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#f93be68caa3969502c13d5d284da59fe1017ed9a" dependencies = [ "proc-macro-error2", "proc-macro2", @@ -2076,7 +2075,7 @@ dependencies = [ [[package]] name = "pumpkin-nbt" version = "0.1.0-dev+1.21.11" -source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#72bd4dfbef6326063b1bc0752e2789402f5c9197" +source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#f93be68caa3969502c13d5d284da59fe1017ed9a" dependencies = [ "bytes", "cesu8", @@ -2088,7 +2087,7 @@ dependencies = [ [[package]] name = "pumpkin-protocol" version = "0.1.0-dev+1.21.11" -source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#72bd4dfbef6326063b1bc0752e2789402f5c9197" +source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#f93be68caa3969502c13d5d284da59fe1017ed9a" dependencies = [ "aes", "async-compression", @@ -2110,7 +2109,7 @@ dependencies = [ [[package]] name = "pumpkin-util" version = "0.1.0-dev+1.21.11" -source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#72bd4dfbef6326063b1bc0752e2789402f5c9197" +source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#f93be68caa3969502c13d5d284da59fe1017ed9a" dependencies = [ "base64", "bytes", @@ -2135,7 +2134,7 @@ dependencies = [ [[package]] name = "pumpkin-world" version = "0.1.0-dev+1.21.11" -source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#72bd4dfbef6326063b1bc0752e2789402f5c9197" +source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#f93be68caa3969502c13d5d284da59fe1017ed9a" dependencies = [ "bitflags", "bytes", @@ -2713,9 +2712,9 @@ checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "slotmap" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 7b9f2a0..2369647 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -11,7 +11,6 @@ pumpkin = { git = "https://github.com/Pumpkin-MC/Pumpkin.git", branch = "master" pumpkin-util = { git = "https://github.com/Pumpkin-MC/Pumpkin.git", branch = "master", package = "pumpkin-util" } pumpkin-api-macros = { git = "https://github.com/Pumpkin-MC/Pumpkin.git", branch = "master", package = "pumpkin-api-macros" } -futures = "0.3" tokio = "1" log = "0.4" glob = "0.3.3" diff --git a/rust/src/directories.rs b/rust/src/directories.rs index e6a664e..8c517cb 100644 --- a/rust/src/directories.rs +++ b/rust/src/directories.rs @@ -21,8 +21,8 @@ pub fn setup_directories(server: &Context) -> Result Vec { - let pattern = format!("{}/**/*.jar", plugin_folder.to_string_lossy()); - let mut entries = Vec::new(); +pub fn discover_jar_files(plugin_folder: &PathBuf) -> impl Iterator { + let pattern = format!("{}/**/*.jar", plugin_folder.display()); - for entry in glob(&pattern).expect("Failed to read glob pattern") { - match entry { - Ok(inner_path) => match inner_path.canonicalize() { - Ok(path) => entries.push(path), - Err(e) => log::error!("Failed to convert path to string: {:?}", e), - }, - Err(e) => log::error!("Failed to canonicalize path: {:?}", e), - } - } - - entries + glob(&pattern) + .expect("Invalid glob pattern") + .filter_map(|entry| { + entry + .map_err(|e| log::error!("Glob error: {:?}", e)) + .ok()? + .canonicalize() + .map_err(|e| log::error!("Canonicalize error: {:?}", e)) + .ok() + }) } pub fn read_configs_from_jar>( diff --git a/rust/src/java/jvm/worker.rs b/rust/src/java/jvm/worker.rs index 6c9388c..8943d2a 100644 --- a/rust/src/java/jvm/worker.rs +++ b/rust/src/java/jvm/worker.rs @@ -99,7 +99,8 @@ impl JvmWorker { let _ = respond_to.send( self.plugin_manager - .instantiate_all_plugins(jvm, &server, command_tx), + .instantiate_all_plugins(jvm, &server, command_tx) + .await, ); } JvmCommand::EnableAllPlugins { respond_to } => { diff --git a/rust/src/java/resources.rs b/rust/src/java/resources.rs index 3f1893f..44fa0d7 100644 --- a/rust/src/java/resources.rs +++ b/rust/src/java/resources.rs @@ -1,5 +1,10 @@ -use std::{collections::HashSet, fs, path::PathBuf}; +use std::{ + collections::HashSet, + fs, + path::{Path, PathBuf}, +}; +use anyhow::{Context, Result}; use rust_embed::Embed; #[derive(Embed)] @@ -7,55 +12,64 @@ use rust_embed::Embed; pub struct Resources; pub fn cleanup_stale_files(j4rs_folder: &PathBuf) { - let allowed: HashSet = Resources::iter().map(|p| p.to_string()).collect(); + let allowed: HashSet = Resources::iter() + .map(|p| PathBuf::from(p.to_string())) + .collect(); for entry in walkdir::WalkDir::new(j4rs_folder) .into_iter() .filter_map(|e| e.ok()) .filter(|e| e.file_type().is_file()) { - let rel_path = entry - .path() - .strip_prefix(j4rs_folder) - .unwrap() - .to_string_lossy() - .to_string(); + let path = entry.path(); - if !allowed.contains(&rel_path) { - log::warn!("Removing stale j4rs file: {rel_path}"); - let _ = fs::remove_file(entry.path()); + if let Ok(rel_path) = path.strip_prefix(j4rs_folder) { + if !allowed.contains(rel_path) { + log::warn!("Removing stale j4rs file: {}", rel_path.display()); + if let Err(e) = fs::remove_file(path) { + log::error!("Failed to remove stale file {}: {}", path.display(), e); + } + } } } } -pub fn sync_embedded_resources(j4rs_folder: &PathBuf) -> Result<(), String> { +pub fn sync_embedded_resources(j4rs_folder: &Path) -> Result<(), String> { for resource_path_str in Resources::iter() { let resource_path = j4rs_folder.join(resource_path_str.to_string()); let resource = Resources::get(&resource_path_str).unwrap(); if !resource_path.exists() { + log::info!("Extracting new resource: {}", resource_path.display()); write_resource(&resource_path, &resource.data)?; } else { update_resource_if_changed(&resource_path, &resource.data)?; } } - Ok(()) } -fn write_resource(path: &PathBuf, data: &[u8]) -> Result<(), String> { - let parent = path.parent().ok_or("Resource has no parent directory")?; - fs::create_dir_all(parent) - .map_err(|err| format!("Failed to create parent for resource: {:?}", err))?; +fn write_resource(path: &Path, data: &[u8]) -> Result<(), String> { + if let Some(parent) = path.parent() { + fs::create_dir_all(parent) + .map_err(|_| format!("Failed to create directory: {}", parent.display()))?; + } - fs::write(path, data).map_err(|err| format!("Failed to add resource: {:?}", err)) + fs::write(path, data).map_err(|_| format!("Failed to write resource: {}", path.display()))?; + Ok(()) } -fn update_resource_if_changed(path: &PathBuf, new_data: &[u8]) -> Result<(), String> { - let old_data = fs::read(path).map_err(|err| format!("Failed to read resource: {:?}", err))?; +fn update_resource_if_changed(path: &Path, new_data: &[u8]) -> Result<(), String> { + // Quick check: If file sizes differ, it's definitely changed, TODO: use Hash ? + let metadata = fs::metadata(path).ok(); + let size_matches = metadata + .map(|m| m.len() == new_data.len() as u64) + .unwrap_or(false); - if new_data != old_data { - fs::write(path, new_data).map_err(|err| format!("Failed to update resource: {:?}", err))?; + if !size_matches || fs::read(path).map_err(|e| e.to_string())? != new_data { + log::debug!("Updating changed resource: {}", path.display()); + fs::write(path, new_data) + .map_err(|_| format!("Failed to update resource: {}", path.display()))?; } Ok(()) diff --git a/rust/src/plugin/manager.rs b/rust/src/plugin/manager.rs index e4fc5c0..2184124 100644 --- a/rust/src/plugin/manager.rs +++ b/rust/src/plugin/manager.rs @@ -288,7 +288,7 @@ impl PluginManager { Ok(()) } - pub fn instantiate_all_plugins( + pub async fn instantiate_all_plugins( &mut self, jvm: &Jvm, server: &Arc, @@ -381,17 +381,15 @@ impl PluginManager { // }; let permission = format!("patchbukkit:{}", cmd_name); - futures::executor::block_on(async { - server - .register_permission(Permission::new( - &permission, - &permission, - PermissionDefault::Allow, - )) - .await - .unwrap(); - server.register_command(node, permission).await - }); + server + .register_permission(Permission::new( + &permission, + &permission, + PermissionDefault::Allow, + )) + .await + .unwrap(); + server.register_command(node, permission).await } plugin.state = PluginState::Loaded;