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 {
`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")

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

View File

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

View File

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

View File

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

View File

@ -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

View File

@ -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<P, B extends Keyed> implements Registry<B> {
private final Map<NamespacedKey, B> entries = new LinkedHashMap<>();
private final Map<String, PatchBukkitTag<B>> tags = new LinkedHashMap<>();
private final RegistryKey<B> registryKey;
public PatchBukkitRegistry(
RegistryType registryType,
Function<GetRegistryDataResponse, List<P>> extractor,
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;
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
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<P, B extends Keyed> implements Registry<B> {
return new PatchBukkitRegistry<>(
null,
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 -> {
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 <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<>(
factory.registryType(),
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]]
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",

View File

@ -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"

View File

@ -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 {