diff --git a/java/patchbukkit/build.gradle.kts b/java/patchbukkit/build.gradle.kts index d429c6d..7970cc1 100644 --- a/java/patchbukkit/build.gradle.kts +++ b/java/patchbukkit/build.gradle.kts @@ -1,9 +1,9 @@ plugins { `java-library` - id("com.google.protobuf") version "0.9.6" + id("com.google.protobuf") version "0.10.0" } -val protobufVersion = "4.33.5" +val protobufVersion = "4.35.1" sourceSets { main { @@ -47,7 +47,7 @@ repositories { dependencies { implementation("io.papermc.paper:paper-api:26.2.build.+") implementation("net.sf.jopt-simple:jopt-simple:6.0-alpha-3") - implementation("io.github.astonbitecode:j4rs:0.24.0") + implementation("io.github.astonbitecode:j4rs:0.25.0") implementation("org.apache.maven:maven-resolver-provider:3.9.6") implementation("org.apache.maven.resolver:maven-resolver-impl:1.9.18") implementation("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.18") diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitInternalAPIBridge.java b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitInternalAPIBridge.java new file mode 100644 index 0000000..c802a13 --- /dev/null +++ b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitInternalAPIBridge.java @@ -0,0 +1,165 @@ +package org.patchbukkit; + +import com.destroystokyo.paper.SkinParts; +import com.destroystokyo.paper.util.VersionFetcher; +import io.papermc.paper.InternalAPIBridge; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.datacomponent.item.ResolvableProfile; +import io.papermc.paper.entity.poi.PoiType; +import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager; +import io.papermc.paper.world.damagesource.CombatEntry; +import io.papermc.paper.world.damagesource.FallLocationType; +import java.io.IOException; +import java.util.Set; +import java.util.function.BooleanSupplier; +import java.util.function.Function; +import java.util.function.Predicate; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.flattener.ComponentFlattener; +import org.bukkit.GameRule; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.Statistic; +import org.bukkit.attribute.Attributable; +import org.bukkit.block.Biome; +import org.bukkit.command.CommandSender; +import org.bukkit.damage.DamageEffect; +import org.bukkit.damage.DamageSource; +import org.bukkit.damage.DamageType; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Pose; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; +import org.jspecify.annotations.Nullable; + +public class PatchBukkitInternalAPIBridge implements InternalAPIBridge { + @Override + public ComponentFlattener componentFlattener() { + return ComponentFlattener.basic(); + } + + @Override + public Component resolveWithContext( + Component component, + @Nullable CommandSender context, + @Nullable Entity entity, + boolean hasPermission + ) throws IOException { + return component; + } + + @Override + public ItemStack createEmptyStack() { + return new ItemStack(Material.AIR); + } + + @Override + public LifecycleEventManager createPluginLifecycleEventManager( + JavaPlugin plugin, + BooleanSupplier registrationCheck + ) { + return null; + } + + @Override + public String getStatisticCriteriaKey(Statistic statistic) { + return statistic.name().toLowerCase(); + } + + @Override + public @Nullable Attributable getDefaultEntityAttributes(NamespacedKey key) { + return null; + } + + @Override + public boolean hasDefaultEntityAttributes(NamespacedKey key) { + return false; + } + + @Override + public ItemStack deserializeItem(byte[] bytes) { + return new ItemStack(Material.AIR); + } + + @Override + public String getTranslationKey(EntityType entityType) { + return entityType.translationKey(); + } + + @Override + public @Nullable DamageEffect getDamageEffect(String key) { + return null; + } + + @Override + public DamageSource.Builder createDamageSourceBuilder(DamageType damageType) { + return null; + } + + @Override + public PoiType.Occupancy createOccupancy(String type) { + return null; + } + + @Override + public Set validMannequinPoses() { + return Set.of(); + } + + @Override + public GameRule legacyGameRuleBridge( + GameRule modernRule, + Function toModern, + Function toLegacy, + Class legacyType + ) { + return null; + } + + @Override + public Component defaultMannequinDescription() { + return Component.empty(); + } + + @Override + public SkinParts.Mutable allSkinParts() { + return null; + } + + @Override + public ResolvableProfile defaultMannequinProfile() { + return null; + } + + @Override + public Predicate restricted(Predicate predicate) { + return predicate; + } + + @Override + public CombatEntry createCombatEntry( + DamageSource damageSource, + float damage, + FallLocationType fallLocationType, + float fallDistance + ) { + return null; + } + + @Override + public CombatEntry createCombatEntry( + LivingEntity entity, + DamageSource damageSource, + float damage + ) { + return null; + } + + @Override + public Biome constructLegacyCustomBiome() { + return null; + } +} diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitPluginLoader.java b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitPluginLoader.java index 4a32cd4..5e1b2f8 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitPluginLoader.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitPluginLoader.java @@ -76,9 +76,10 @@ public class PatchBukkitPluginLoader implements PluginLoader { Class jarClass = Class.forName(mainClass, true, classLoader); return (JavaPlugin) jarClass.getDeclaredConstructor().newInstance(); - } catch (Exception e) { + } catch (Throwable e) { + System.err.println("[PatchBukkit] Failed to instantiate plugin class " + mainClass + ": " + e.getMessage()); e.printStackTrace(); - return null; + throw new RuntimeException("Failed to instantiate plugin " + mainClass, e); } } diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitPluginManager.java b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitPluginManager.java index d90de1b..e3e1627 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitPluginManager.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitPluginManager.java @@ -44,23 +44,29 @@ public class PatchBukkitPluginManager implements PluginManager { throws IllegalArgumentException {} public void registerPlugin(@NotNull Plugin plugin) { - plugins.put(plugin.getName().toLowerCase(), plugin); + plugins.put(plugin.getName().toLowerCase(java.util.Locale.ENGLISH), plugin); + for (String provided : plugin.getDescription().getProvides()) { + plugins.putIfAbsent(provided.toLowerCase(java.util.Locale.ENGLISH), plugin); + } } - @Override + @Override public @Nullable Plugin getPlugin(@NotNull String name) { - return plugins.get(name.toLowerCase()); + if (name == null) { + return null; + } + return plugins.get(name.replace(' ', '_').toLowerCase(java.util.Locale.ENGLISH)); } - @Override + @Override public @NotNull Plugin[] getPlugins() { - return plugins.values().toArray(new Plugin[0]); + return plugins.values().stream().distinct().toArray(Plugin[]::new); } @Override public boolean isPluginEnabled(@NotNull String name) { Plugin plugin = getPlugin(name); - return plugin != null && plugin.isEnabled(); + return isPluginEnabled(plugin); } @Override @@ -152,6 +158,9 @@ public class PatchBukkitPluginManager implements PluginManager { @Override public void enablePlugin(@NotNull Plugin plugin) { + if (plugin == null) { + return; + } if (!plugin.isEnabled()) { try { plugin.getPluginLoader().enablePlugin(plugin); @@ -163,6 +172,9 @@ public class PatchBukkitPluginManager implements PluginManager { @Override public void disablePlugin(@NotNull Plugin plugin) { + if (plugin == null) { + return; + } if (plugin.isEnabled()) { plugin.getPluginLoader().disablePlugin(plugin); } diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitServer.java b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitServer.java index 38b419a..cc46e5f 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitServer.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitServer.java @@ -1589,10 +1589,7 @@ public class PatchBukkitServer implements Server { public @Nullable Registry getRegistry( @NotNull Class tClass ) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException( - "Unimplemented method 'getRegistry'" - ); + return io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(tClass); } @Override diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/command/PatchBukkitCommandMap.java b/java/patchbukkit/src/main/java/org/patchbukkit/command/PatchBukkitCommandMap.java index 9007089..1426ab2 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/command/PatchBukkitCommandMap.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/command/PatchBukkitCommandMap.java @@ -59,8 +59,14 @@ public class PatchBukkitCommandMap implements CommandMap { @Override public boolean dispatch(@NotNull CommandSender sender, @NotNull String cmdLine) throws CommandException { - // Remove leading slash if present and split arguments - String[] split = (cmdLine.startsWith("/") ? cmdLine.substring(1) : cmdLine).split(" "); + if (cmdLine == null) return false; + String cleanLine = cmdLine.trim(); + if (cleanLine.startsWith("/")) { + cleanLine = cleanLine.substring(1).trim(); + } + if (cleanLine.isEmpty()) return false; + + String[] split = cleanLine.split("\\s+"); if (split.length == 0) return false; String sentLabel = split[0].toLowerCase(); diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/command/PatchBukkitCommandSender.java b/java/patchbukkit/src/main/java/org/patchbukkit/command/PatchBukkitCommandSender.java index 98603c5..884d3e2 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/command/PatchBukkitCommandSender.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/command/PatchBukkitCommandSender.java @@ -136,18 +136,14 @@ public class PatchBukkitCommandSender implements CommandSender, ServerOperator { if (message == null) { return; } - if (this.server != null && this.server.getLogger() != null) { - this.server.getLogger().info(message); - } else { - LogLevel logLevel = LogLevel.INFO; - NativeBridgeFfi.sendLog( - SendLogRequest.newBuilder() - .setLevel(logLevel) - .setMessage(ChatColor.stripColor(message)) - .setLoggerName("console") - .build() - ); - } + LogLevel logLevel = LogLevel.INFO; + NativeBridgeFfi.sendLog( + SendLogRequest.newBuilder() + .setLevel(logLevel) + .setMessage(ChatColor.stripColor(message)) + .setLoggerName("Console") + .build() + ); } @Override diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/registry/PatchBukkitRegistry.java b/java/patchbukkit/src/main/java/org/patchbukkit/registry/PatchBukkitRegistry.java index ee66859..a37f4f1 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/registry/PatchBukkitRegistry.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/registry/PatchBukkitRegistry.java @@ -10,6 +10,7 @@ import io.papermc.paper.registry.tag.TagKey; import org.bukkit.Keyed; import org.bukkit.NamespacedKey; import org.bukkit.Registry; +import org.bukkit.Sound; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import patchbukkit.bridge.NativeBridgeFfi; @@ -26,11 +27,23 @@ public class PatchBukkitRegistry implements Registry { private final Map entries = new LinkedHashMap<>(); private final Map> tags = new LinkedHashMap<>(); + private final RegistryKey registryKey; + public PatchBukkitRegistry( RegistryType registryType, Function> extractor, Function factory ) { + this(registryType, extractor, factory, null); + } + + public PatchBukkitRegistry( + RegistryType registryType, + Function> extractor, + Function factory, + RegistryKey registryKey + ) { + this.registryKey = registryKey; if (registryType == null) return; GetRegistryDataRequest request = GetRegistryDataRequest.newBuilder() @@ -49,39 +62,18 @@ public class PatchBukkitRegistry implements Registry { } } - - private void loadTags(JsonObject tagMap, RegistryKey registryKey) { - for (Map.Entry entry : tagMap.entrySet()) { - String tagName = entry.getKey(); - JsonArray members = entry.getValue().getAsJsonArray(); - - Set memberKeys = new LinkedHashSet<>(); - for (JsonElement member : members) { - String raw = member.getAsString(); - // Tag values may or may not have "minecraft:" prefix - NamespacedKey key; - if (raw.contains(":")) { - key = NamespacedKey.fromString(raw); - } else { - key = new NamespacedKey(NamespacedKey.MINECRAFT, raw); - } - if (key != null) memberKeys.add(key); - } - - NamespacedKey tagKeyName = tagName.contains(":") - ? NamespacedKey.fromString(tagName) - : new NamespacedKey(NamespacedKey.MINECRAFT, tagName); - - if (tagKeyName == null) continue; - - TagKey tagKey = TagKey.create(registryKey, tagKeyName); - tags.put(tagName, new PatchBukkitTag<>(tagKey, registryKey, memberKeys)); - } - } - @Override public @Nullable B get(NamespacedKey key) { - return entries.get(key); + if (key == null) { + return null; + } + B value = entries.get(key); + if (value == null && (RegistryKey.SOUND_EVENT.equals(registryKey) || "sound_event".equalsIgnoreCase(registryKey != null ? registryKey.key().value() : ""))) { + PatchBukkitSound dynamicSound = new PatchBukkitSound(key.getKey(), entries.size()); + entries.put(key, (B) dynamicSound); + return (B) dynamicSound; + } + return value; } @Override @@ -107,7 +99,8 @@ public class PatchBukkitRegistry implements Registry { return new PatchBukkitRegistry<>( null, response -> Collections.emptyList(), - obj -> null + obj -> null, + registryKey ); } diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/registry/PatchBukkitRegistryAccess.java b/java/patchbukkit/src/main/java/org/patchbukkit/registry/PatchBukkitRegistryAccess.java index 854db6f..345835f 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/registry/PatchBukkitRegistryAccess.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/registry/PatchBukkitRegistryAccess.java @@ -55,7 +55,7 @@ public class PatchBukkitRegistryAccess implements RegistryAccess { return (Registry) instances.computeIfAbsent(registryKey, key -> { RegistryFactory factoryEntry = FACTORIES.get(key); if (factoryEntry != null) { - return buildRegistry(factoryEntry); + return buildRegistry((RegistryKey) key, factoryEntry); } // Return an empty registry for unsupported types return PatchBukkitRegistry.empty((RegistryKey) key); @@ -63,11 +63,12 @@ public class PatchBukkitRegistryAccess implements RegistryAccess { } @SuppressWarnings("unchecked") - private static PatchBukkitRegistry buildRegistry(RegistryFactory factory) { + private static PatchBukkitRegistry buildRegistry(RegistryKey key, RegistryFactory factory) { return new PatchBukkitRegistry<>( factory.registryType(), factory.extractor(), - factory.factory() + factory.factory(), + key ); } diff --git a/java/patchbukkit/src/main/resources/META-INF/services/io.papermc.paper.InternalAPIBridge b/java/patchbukkit/src/main/resources/META-INF/services/io.papermc.paper.InternalAPIBridge new file mode 100644 index 0000000..3b1f480 --- /dev/null +++ b/java/patchbukkit/src/main/resources/META-INF/services/io.papermc.paper.InternalAPIBridge @@ -0,0 +1 @@ +org.patchbukkit.PatchBukkitInternalAPIBridge diff --git a/rust/Cargo.lock b/rust/Cargo.lock index d6d4bdf..4ea64ac 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1800,9 +1800,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "j4rs" -version = "0.24.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19df11b7e099dfa6c1d3d9ec600dc95d4130b24a2196181cbb7dfcd75936f4a9" +checksum = "1fbaca95c5c7799ed193fc8aa789568f9d31a9d01381460227ef0074fea1855f" dependencies = [ "cesu8", "dunce", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 0a6ab5b..e838107 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -21,7 +21,7 @@ pumpkin-api-macros = { git = "https://github.com/Pumpkin-MC/Pumpkin.git", branch tokio = "1" tracing = "0.1" glob = "0.3.4" -j4rs = { version = "=0.24.0" } +j4rs = { version = "=0.25.0" } rust-embed = { version = "8.12.0", features = ["interpolate-folder-path"] } walkdir = "2" zip = "8.6" @@ -39,7 +39,7 @@ prost = "0.14" toml = "1.1" [build-dependencies] -j4rs = { version = "=0.24.0", artifact = "cdylib", lib = true } +j4rs = { version = "=0.25.0", artifact = "cdylib", lib = true } env_logger = "0.11.11" log = "0.4" prost-build = "0.14" diff --git a/rust/resources/jassets/j4rs-0.24.0-jar-with-dependencies.jar b/rust/resources/jassets/j4rs-0.24.0-jar-with-dependencies.jar deleted file mode 100644 index 97e127e..0000000 Binary files a/rust/resources/jassets/j4rs-0.24.0-jar-with-dependencies.jar and /dev/null differ diff --git a/rust/resources/jassets/j4rs-0.25.3-SNAPSHOT-jar-with-dependencies.jar b/rust/resources/jassets/j4rs-0.25.3-SNAPSHOT-jar-with-dependencies.jar new file mode 100644 index 0000000..2771fb5 Binary files /dev/null and b/rust/resources/jassets/j4rs-0.25.3-SNAPSHOT-jar-with-dependencies.jar differ diff --git a/rust/src/java/plugin/manager.rs b/rust/src/java/plugin/manager.rs index 8754934..b157697 100644 --- a/rust/src/java/plugin/manager.rs +++ b/rust/src/java/plugin/manager.rs @@ -307,11 +307,37 @@ impl PluginManager { InvocationArg::empty(), )?; for plugin in self.plugins.values_mut() { - if plugin.instance.is_none() { + if plugin.state != PluginState::Loaded { continue; } - let plugin_instance = plugin.instance.as_ref().unwrap(); - let plugin_instance = jvm.clone_instance(plugin_instance).unwrap(); + let plugin_instance = match plugin.instance.as_ref() { + Some(instance) => match jvm.clone_instance(instance) { + Ok(cloned) => cloned, + Err(e) => { + plugin.state = PluginState::Errored; + tracing::error!( + "Failed to clone plugin instance handle for {}: {:?}", + plugin.name, + e + ); + continue; + } + }, + None => continue, + }; + + // Register plugin instance with Java PatchBukkitServer before enabling + if let Ok(server) = + jvm.invoke_static("org.bukkit.Bukkit", "getServer", InvocationArg::empty()) + { + let _ = jvm.invoke( + &server, + "registerPlugin", + &[InvocationArg::from( + jvm.clone_instance(&plugin_instance).unwrap(), + )], + ); + } let result = jvm.invoke( &plugin_manager, @@ -344,11 +370,21 @@ impl PluginManager { InvocationArg::empty(), )?; for plugin in self.plugins.values_mut() { - if plugin.instance.is_none() { - continue; - } - let plugin_instance = plugin.instance.as_ref().unwrap(); - let plugin_instance = jvm.clone_instance(plugin_instance).unwrap(); + let plugin_instance = match plugin.instance.as_ref() { + Some(instance) => match jvm.clone_instance(instance) { + Ok(cloned) => cloned, + Err(e) => { + plugin.state = PluginState::Errored; + tracing::error!( + "Failed to clone plugin instance handle for {}: {:?}", + plugin.name, + e + ); + continue; + } + }, + None => continue, + }; let result = jvm.invoke( &plugin_manager, @@ -396,7 +432,7 @@ impl PluginManager { Some(plugin) => plugin, None => continue, }; - let plugin_instance = jvm.invoke_static( + let plugin_instance = match jvm.invoke_static( "org.patchbukkit.loader.PatchBukkitPluginLoader", "createPlugin", &[ @@ -405,7 +441,32 @@ impl PluginManager { InvocationArg::try_from(&classpath)?, InvocationArg::try_from(&libraries)?, ], - )?; + ) { + Ok(instance) => { + // Check if j4rs created a global reference for a null Java object + if jvm + .invoke(&instance, "toString", InvocationArg::empty()) + .is_err() + { + plugin.state = PluginState::Errored; + tracing::error!( + "Failed to instantiate PatchBukkit plugin {}: Java returned null instance", + plugin.name + ); + continue; + } + instance + } + Err(e) => { + plugin.state = PluginState::Errored; + tracing::error!( + "Failed to instantiate PatchBukkit plugin {}: {:?}", + plugin.name, + e + ); + continue; + } + }; plugin.instance = Some(plugin_instance); for (cmd_name, cmd_data) in &plugin.commands {