some fixes

This commit is contained in:
Alexander Medvedev 2026-07-25 23:20:16 +02:00
parent 9b8fd5730f
commit 1182c7e8fe
15 changed files with 311 additions and 78 deletions

View File

@ -1,9 +1,9 @@
plugins { plugins {
`java-library` `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 { sourceSets {
main { main {
@ -47,7 +47,7 @@ repositories {
dependencies { dependencies {
implementation("io.papermc.paper:paper-api:26.2.build.+") implementation("io.papermc.paper:paper-api:26.2.build.+")
implementation("net.sf.jopt-simple:jopt-simple:6.0-alpha-3") 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:maven-resolver-provider:3.9.6")
implementation("org.apache.maven.resolver:maven-resolver-impl:1.9.18") implementation("org.apache.maven.resolver:maven-resolver-impl:1.9.18")
implementation("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.18") implementation("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.18")

View File

@ -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<Plugin> 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<Pose> validMannequinPoses() {
return Set.of();
}
@Override
public <MODERN, LEGACY> GameRule<LEGACY> legacyGameRuleBridge(
GameRule<MODERN> modernRule,
Function<LEGACY, MODERN> toModern,
Function<MODERN, LEGACY> toLegacy,
Class<LEGACY> 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<CommandSourceStack> restricted(Predicate<CommandSourceStack> 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;
}
}

View File

@ -76,9 +76,10 @@ public class PatchBukkitPluginLoader implements PluginLoader {
Class<?> jarClass = Class.forName(mainClass, true, classLoader); Class<?> jarClass = Class.forName(mainClass, true, classLoader);
return (JavaPlugin) jarClass.getDeclaredConstructor().newInstance(); 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(); e.printStackTrace();
return null; throw new RuntimeException("Failed to instantiate plugin " + mainClass, e);
} }
} }

View File

@ -44,23 +44,29 @@ public class PatchBukkitPluginManager implements PluginManager {
throws IllegalArgumentException {} throws IllegalArgumentException {}
public void registerPlugin(@NotNull Plugin plugin) { 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) { 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() { public @NotNull Plugin[] getPlugins() {
return plugins.values().toArray(new Plugin[0]); return plugins.values().stream().distinct().toArray(Plugin[]::new);
} }
@Override @Override
public boolean isPluginEnabled(@NotNull String name) { public boolean isPluginEnabled(@NotNull String name) {
Plugin plugin = getPlugin(name); Plugin plugin = getPlugin(name);
return plugin != null && plugin.isEnabled(); return isPluginEnabled(plugin);
} }
@Override @Override
@ -152,6 +158,9 @@ public class PatchBukkitPluginManager implements PluginManager {
@Override @Override
public void enablePlugin(@NotNull Plugin plugin) { public void enablePlugin(@NotNull Plugin plugin) {
if (plugin == null) {
return;
}
if (!plugin.isEnabled()) { if (!plugin.isEnabled()) {
try { try {
plugin.getPluginLoader().enablePlugin(plugin); plugin.getPluginLoader().enablePlugin(plugin);
@ -163,6 +172,9 @@ public class PatchBukkitPluginManager implements PluginManager {
@Override @Override
public void disablePlugin(@NotNull Plugin plugin) { public void disablePlugin(@NotNull Plugin plugin) {
if (plugin == null) {
return;
}
if (plugin.isEnabled()) { if (plugin.isEnabled()) {
plugin.getPluginLoader().disablePlugin(plugin); plugin.getPluginLoader().disablePlugin(plugin);
} }

View File

@ -1589,10 +1589,7 @@ public class PatchBukkitServer implements Server {
public <T extends Keyed> @Nullable Registry<T> getRegistry( public <T extends Keyed> @Nullable Registry<T> getRegistry(
@NotNull Class<T> tClass @NotNull Class<T> tClass
) { ) {
// TODO Auto-generated method stub return io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(tClass);
throw new UnsupportedOperationException(
"Unimplemented method 'getRegistry'"
);
} }
@Override @Override

View File

@ -59,8 +59,14 @@ public class PatchBukkitCommandMap implements CommandMap {
@Override @Override
public boolean dispatch(@NotNull CommandSender sender, @NotNull String cmdLine) throws CommandException { public boolean dispatch(@NotNull CommandSender sender, @NotNull String cmdLine) throws CommandException {
// Remove leading slash if present and split arguments if (cmdLine == null) return false;
String[] split = (cmdLine.startsWith("/") ? cmdLine.substring(1) : cmdLine).split(" "); 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; if (split.length == 0) return false;
String sentLabel = split[0].toLowerCase(); String sentLabel = split[0].toLowerCase();

View File

@ -136,19 +136,15 @@ public class PatchBukkitCommandSender implements CommandSender, ServerOperator {
if (message == null) { if (message == null) {
return; return;
} }
if (this.server != null && this.server.getLogger() != null) {
this.server.getLogger().info(message);
} else {
LogLevel logLevel = LogLevel.INFO; LogLevel logLevel = LogLevel.INFO;
NativeBridgeFfi.sendLog( NativeBridgeFfi.sendLog(
SendLogRequest.newBuilder() SendLogRequest.newBuilder()
.setLevel(logLevel) .setLevel(logLevel)
.setMessage(ChatColor.stripColor(message)) .setMessage(ChatColor.stripColor(message))
.setLoggerName("console") .setLoggerName("Console")
.build() .build()
); );
} }
}
@Override @Override
public void sendMessage(@NotNull String... messages) { public void sendMessage(@NotNull String... messages) {

View File

@ -10,6 +10,7 @@ import io.papermc.paper.registry.tag.TagKey;
import org.bukkit.Keyed; import org.bukkit.Keyed;
import org.bukkit.NamespacedKey; import org.bukkit.NamespacedKey;
import org.bukkit.Registry; import org.bukkit.Registry;
import org.bukkit.Sound;
import org.jspecify.annotations.NonNull; import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import patchbukkit.bridge.NativeBridgeFfi; import patchbukkit.bridge.NativeBridgeFfi;
@ -26,11 +27,23 @@ public class PatchBukkitRegistry<P, B extends Keyed> implements Registry<B> {
private final Map<NamespacedKey, B> entries = new LinkedHashMap<>(); private final Map<NamespacedKey, B> entries = new LinkedHashMap<>();
private final Map<String, PatchBukkitTag<B>> tags = new LinkedHashMap<>(); private final Map<String, PatchBukkitTag<B>> tags = new LinkedHashMap<>();
private final RegistryKey<B> registryKey;
public PatchBukkitRegistry( public PatchBukkitRegistry(
RegistryType registryType, RegistryType registryType,
Function<GetRegistryDataResponse, List<P>> extractor, Function<GetRegistryDataResponse, List<P>> extractor,
Function<P, B> factory Function<P, B> factory
) { ) {
this(registryType, extractor, factory, null);
}
public PatchBukkitRegistry(
RegistryType registryType,
Function<GetRegistryDataResponse, List<P>> extractor,
Function<P, B> factory,
RegistryKey<B> registryKey
) {
this.registryKey = registryKey;
if (registryType == null) return; if (registryType == null) return;
GetRegistryDataRequest request = GetRegistryDataRequest.newBuilder() GetRegistryDataRequest request = GetRegistryDataRequest.newBuilder()
@ -49,39 +62,18 @@ public class PatchBukkitRegistry<P, B extends Keyed> implements Registry<B> {
} }
} }
private void loadTags(JsonObject tagMap, RegistryKey<B> registryKey) {
for (Map.Entry<String, JsonElement> entry : tagMap.entrySet()) {
String tagName = entry.getKey();
JsonArray members = entry.getValue().getAsJsonArray();
Set<NamespacedKey> 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<B> tagKey = TagKey.create(registryKey, tagKeyName);
tags.put(tagName, new PatchBukkitTag<>(tagKey, registryKey, memberKeys));
}
}
@Override @Override
public @Nullable B get(NamespacedKey key) { 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 @Override
@ -107,7 +99,8 @@ public class PatchBukkitRegistry<P, B extends Keyed> implements Registry<B> {
return new PatchBukkitRegistry<>( return new PatchBukkitRegistry<>(
null, null,
response -> Collections.emptyList(), response -> Collections.emptyList(),
obj -> null obj -> null,
registryKey
); );
} }

View File

@ -55,7 +55,7 @@ public class PatchBukkitRegistryAccess implements RegistryAccess {
return (Registry<T>) instances.computeIfAbsent(registryKey, key -> { return (Registry<T>) instances.computeIfAbsent(registryKey, key -> {
RegistryFactory<?, ?> factoryEntry = FACTORIES.get(key); RegistryFactory<?, ?> factoryEntry = FACTORIES.get(key);
if (factoryEntry != null) { if (factoryEntry != null) {
return buildRegistry(factoryEntry); return buildRegistry((RegistryKey) key, factoryEntry);
} }
// Return an empty registry for unsupported types // Return an empty registry for unsupported types
return PatchBukkitRegistry.empty((RegistryKey) key); return PatchBukkitRegistry.empty((RegistryKey) key);
@ -63,11 +63,12 @@ public class PatchBukkitRegistryAccess implements RegistryAccess {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private static <P, B extends Keyed> PatchBukkitRegistry<P, B> buildRegistry(RegistryFactory<P, B> factory) { private static <P, B extends Keyed> PatchBukkitRegistry<P, B> buildRegistry(RegistryKey<B> key, RegistryFactory<P, B> factory) {
return new PatchBukkitRegistry<>( return new PatchBukkitRegistry<>(
factory.registryType(), factory.registryType(),
factory.extractor(), factory.extractor(),
factory.factory() factory.factory(),
key
); );
} }

View File

@ -0,0 +1 @@
org.patchbukkit.PatchBukkitInternalAPIBridge

4
rust/Cargo.lock generated
View File

@ -1800,9 +1800,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]] [[package]]
name = "j4rs" name = "j4rs"
version = "0.24.0" version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19df11b7e099dfa6c1d3d9ec600dc95d4130b24a2196181cbb7dfcd75936f4a9" checksum = "1fbaca95c5c7799ed193fc8aa789568f9d31a9d01381460227ef0074fea1855f"
dependencies = [ dependencies = [
"cesu8", "cesu8",
"dunce", "dunce",

View File

@ -21,7 +21,7 @@ pumpkin-api-macros = { git = "https://github.com/Pumpkin-MC/Pumpkin.git", branch
tokio = "1" tokio = "1"
tracing = "0.1" tracing = "0.1"
glob = "0.3.4" glob = "0.3.4"
j4rs = { version = "=0.24.0" } j4rs = { version = "=0.25.0" }
rust-embed = { version = "8.12.0", features = ["interpolate-folder-path"] } rust-embed = { version = "8.12.0", features = ["interpolate-folder-path"] }
walkdir = "2" walkdir = "2"
zip = "8.6" zip = "8.6"
@ -39,7 +39,7 @@ prost = "0.14"
toml = "1.1" toml = "1.1"
[build-dependencies] [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" env_logger = "0.11.11"
log = "0.4" log = "0.4"
prost-build = "0.14" prost-build = "0.14"

View File

@ -307,11 +307,37 @@ impl PluginManager {
InvocationArg::empty(), InvocationArg::empty(),
)?; )?;
for plugin in self.plugins.values_mut() { for plugin in self.plugins.values_mut() {
if plugin.instance.is_none() { if plugin.state != PluginState::Loaded {
continue; continue;
} }
let plugin_instance = plugin.instance.as_ref().unwrap(); let plugin_instance = match plugin.instance.as_ref() {
let plugin_instance = jvm.clone_instance(plugin_instance).unwrap(); 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( let result = jvm.invoke(
&plugin_manager, &plugin_manager,
@ -344,11 +370,21 @@ impl PluginManager {
InvocationArg::empty(), InvocationArg::empty(),
)?; )?;
for plugin in self.plugins.values_mut() { for plugin in self.plugins.values_mut() {
if plugin.instance.is_none() { 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; continue;
} }
let plugin_instance = plugin.instance.as_ref().unwrap(); },
let plugin_instance = jvm.clone_instance(plugin_instance).unwrap(); None => continue,
};
let result = jvm.invoke( let result = jvm.invoke(
&plugin_manager, &plugin_manager,
@ -396,7 +432,7 @@ impl PluginManager {
Some(plugin) => plugin, Some(plugin) => plugin,
None => continue, None => continue,
}; };
let plugin_instance = jvm.invoke_static( let plugin_instance = match jvm.invoke_static(
"org.patchbukkit.loader.PatchBukkitPluginLoader", "org.patchbukkit.loader.PatchBukkitPluginLoader",
"createPlugin", "createPlugin",
&[ &[
@ -405,7 +441,32 @@ impl PluginManager {
InvocationArg::try_from(&classpath)?, InvocationArg::try_from(&classpath)?,
InvocationArg::try_from(&libraries)?, 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); plugin.instance = Some(plugin_instance);
for (cmd_name, cmd_data) in &plugin.commands { for (cmd_name, cmd_data) in &plugin.commands {