diff --git a/java/gradle/wrapper/gradle-wrapper.jar b/java/gradle/wrapper/gradle-wrapper.jar index 61285a6..eddabd2 100644 Binary files a/java/gradle/wrapper/gradle-wrapper.jar and b/java/gradle/wrapper/gradle-wrapper.jar differ diff --git a/java/gradle/wrapper/gradle-wrapper.properties b/java/gradle/wrapper/gradle-wrapper.properties index a351597..69dd0d0 100644 --- a/java/gradle/wrapper/gradle-wrapper.properties +++ b/java/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,9 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.0-bin.zip networkTimeout=10000 +retries=0 +retryBackOffMs=500 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/java/gradlew b/java/gradlew index adff685..249efbb 100755 --- a/java/gradlew +++ b/java/gradlew @@ -20,7 +20,7 @@ ############################################################################## # -# Gradle start up script for POSIX generated by Gradle. +# gradlew start up script for POSIX generated by Gradle. # # Important for running: # @@ -29,7 +29,7 @@ # bash, then to run this script, type that shell name before the whole # command line, like: # -# ksh Gradle +# ksh gradlew # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. diff --git a/java/gradlew.bat b/java/gradlew.bat index c4bdd3a..a51ec4f 100644 --- a/java/gradlew.bat +++ b/java/gradlew.bat @@ -19,12 +19,12 @@ @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem -@rem Gradle startup script for Windows +@rem gradlew startup script for Windows @rem @rem ########################################################################## -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @@ -51,7 +51,7 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -65,29 +65,18 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :execute @rem Setup the command line -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/java/patchbukkit-test-plugin/build.gradle.kts b/java/patchbukkit-test-plugin/build.gradle.kts index ec9b4cc..836d737 100644 --- a/java/patchbukkit-test-plugin/build.gradle.kts +++ b/java/patchbukkit-test-plugin/build.gradle.kts @@ -10,7 +10,8 @@ repositories { } dependencies { - compileOnly("io.papermc.paper:paper-api:26.2.build.87-stable") + compileOnly(project(":patchbukkit")) + compileOnly("io.papermc.paper:paper-api:26.2.build.111-stable") } java { diff --git a/java/patchbukkit-test-plugin/src/main/java/org/patchbukkit/testplugin/tests/CommandSystemTests.java b/java/patchbukkit-test-plugin/src/main/java/org/patchbukkit/testplugin/tests/CommandSystemTests.java index ee230aa..354f120 100644 --- a/java/patchbukkit-test-plugin/src/main/java/org/patchbukkit/testplugin/tests/CommandSystemTests.java +++ b/java/patchbukkit-test-plugin/src/main/java/org/patchbukkit/testplugin/tests/CommandSystemTests.java @@ -90,4 +90,43 @@ public final class CommandSystemTests { assertNotNull(found, "CommandMap.getCommand(\"myprefix:pbtestalias\")"); } + @ConformanceTest(name = "CommandMap supports WorldEdit double-slash commands", category = TestCategory.COMMAND_SYSTEM) + public void testDoubleSlashCommands() { + CommandMap map = Bukkit.getServer().getCommandMap(); + final String[] executedLabel = {null}; + Command testCmd = new BukkitCommand("//wand", "wand", "//wand", List.of("//set", "set")) { + @Override + public boolean execute(CommandSender sender, String label, String[] args) { + executedLabel[0] = label; + return true; + } + }; + map.register("worldedit", testCmd); + + CommandSender sender = Bukkit.getConsoleSender(); + + // 1. Dispatch with double slash + boolean resultDouble = map.dispatch(sender, "//wand"); + assertTrue(resultDouble, "dispatch() with double slash should return true"); + assertTrue("//wand".equals(executedLabel[0]), "executed label for //wand"); + + // 2. Dispatch with single slash + boolean resultSingle = map.dispatch(sender, "/wand"); + assertTrue(resultSingle, "dispatch() with single slash should return true"); + + // 3. Dispatch without slash + boolean resultNoSlash = map.dispatch(sender, "wand"); + assertTrue(resultNoSlash, "dispatch() without slash should return true"); + + // 4. Dispatch alias with double slash + boolean resultAlias = map.dispatch(sender, "//set"); + assertTrue(resultAlias, "dispatch() with double slash alias should return true"); + + // 5. getCommand lookups + assertNotNull(map.getCommand("//wand"), "getCommand(\"//wand\")"); + assertNotNull(map.getCommand("/wand"), "getCommand(\"/wand\")"); + assertNotNull(map.getCommand("wand"), "getCommand(\"wand\")"); + assertNotNull(map.getCommand("//set"), "getCommand(\"//set\")"); + } + } diff --git a/java/patchbukkit-test-plugin/src/main/java/org/patchbukkit/testplugin/tests/EntityTests.java b/java/patchbukkit-test-plugin/src/main/java/org/patchbukkit/testplugin/tests/EntityTests.java index d11d848..83695f5 100644 --- a/java/patchbukkit-test-plugin/src/main/java/org/patchbukkit/testplugin/tests/EntityTests.java +++ b/java/patchbukkit-test-plugin/src/main/java/org/patchbukkit/testplugin/tests/EntityTests.java @@ -43,4 +43,30 @@ public final class EntityTests { } } + @ConformanceTest(name = "Player ground state and velocity getters run cleanly", category = TestCategory.ENTITY) + public void testPlayerGroundStateAndVelocity() { + for (Player p : Bukkit.getOnlinePlayers()) { + boolean onGround = p.isOnGround(); + org.bukkit.util.Vector vel = p.getVelocity(); + assertNotNull(onGround, "Player.isOnGround()"); + assertNotNull(vel, "Player.getVelocity()"); + } + } + + @ConformanceTest(name = "Player inventory and equipment getters and setters run cleanly", category = TestCategory.ENTITY) + public void testPlayerInventoryAndEquipment() { + for (Player p : Bukkit.getOnlinePlayers()) { + org.bukkit.inventory.PlayerInventory inv = p.getInventory(); + org.bukkit.inventory.EntityEquipment eq = p.getEquipment(); + assertNotNull(inv, "Player.getInventory()"); + assertNotNull(eq, "Player.getEquipment()"); + assertNotNull(inv.getItemInMainHand(), "PlayerInventory.getItemInMainHand()"); + assertNotNull(inv.getItemInOffHand(), "PlayerInventory.getItemInOffHand()"); + assertNotNull(inv.getArmorContents(), "PlayerInventory.getArmorContents()"); + assertTrue(inv.getArmorContents().length == 4, "PlayerInventory.getArmorContents().length == 4"); + assertNotNull(inv.getContents(), "PlayerInventory.getContents()"); + assertTrue(inv.getContents().length == 41, "PlayerInventory.getContents().length == 41"); + assertTrue(inv.getHeldItemSlot() >= 0 && inv.getHeldItemSlot() < 9, "PlayerInventory.getHeldItemSlot() valid slot"); + } + } } diff --git a/java/patchbukkit-test-plugin/src/main/java/org/patchbukkit/testplugin/tests/StubTests.java b/java/patchbukkit-test-plugin/src/main/java/org/patchbukkit/testplugin/tests/StubTests.java index b48a02c..00856bf 100644 --- a/java/patchbukkit-test-plugin/src/main/java/org/patchbukkit/testplugin/tests/StubTests.java +++ b/java/patchbukkit-test-plugin/src/main/java/org/patchbukkit/testplugin/tests/StubTests.java @@ -119,4 +119,11 @@ public final class StubTests { Bukkit.getServer().getAllowNether(); } + @ConformanceTest(name = "PaperLib detection classes and PaperLib.isPaper() are available", category = TestCategory.STUBS) + public void testPaperLibDetection() throws ClassNotFoundException { + Class.forName("com.destroystokyo.paper.PaperConfig"); + Class.forName("io.papermc.paper.configuration.Configuration"); + org.patchbukkit.testplugin.TestAssertions.assertTrue(io.papermc.lib.PaperLib.isPaper(), "PaperLib.isPaper() must be true"); + } + } diff --git a/java/patchbukkit/build.gradle.kts b/java/patchbukkit/build.gradle.kts index 518479c..bc55001 100644 --- a/java/patchbukkit/build.gradle.kts +++ b/java/patchbukkit/build.gradle.kts @@ -1,6 +1,7 @@ plugins { `java-library` id("com.google.protobuf") version "0.10.0" + id("io.papermc.paperweight.userdev") version "2.0.0-SNAPSHOT" } val protobufVersion = "4.35.1" @@ -38,6 +39,7 @@ tasks.named("generateProto") { } repositories { + mavenCentral() maven { name = "papermc" url = uri("https://repo.papermc.io/repository/maven-public/") @@ -45,7 +47,7 @@ repositories { } dependencies { - implementation("io.papermc.paper:paper-api:26.2.build.87-stable") + paperweight.paperDevBundle("26.2.build.111-stable") implementation("net.sf.jopt-simple:jopt-simple:6.0-alpha-3") implementation("io.github.astonbitecode:j4rs:0.25.2") implementation("org.apache.maven:maven-resolver-provider:3.9.6") @@ -55,6 +57,12 @@ dependencies { implementation("org.apache.maven.resolver:maven-resolver-util:1.9.18") implementation("com.google.protobuf:protobuf-java:$protobufVersion") implementation("org.slf4j:slf4j-jdk14:2.0.16") + testImplementation("org.junit.jupiter:junit-jupiter:5.10.2") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") +} + +tasks.named("test") { + useJUnitPlatform() } java { @@ -73,12 +81,12 @@ tasks.withType { tasks.named("jar") { duplicatesStrategy = DuplicatesStrategy.EXCLUDE - dependsOn(configurations.runtimeClasspath) + dependsOn(configurations.compileClasspath) from({ - configurations.runtimeClasspath.get() - .filter { it.name.endsWith(".jar") } - .map { zipTree(it) } + configurations.compileClasspath.get().map { file -> + if (file.isDirectory) file else zipTree(file) + } }) exclude( diff --git a/java/patchbukkit/logs/latest.log b/java/patchbukkit/logs/latest.log new file mode 100644 index 0000000..e69de29 diff --git a/java/patchbukkit/src/main/java/com/destroystokyo/paper/PaperConfig.java b/java/patchbukkit/src/main/java/com/destroystokyo/paper/PaperConfig.java new file mode 100644 index 0000000..650de3a --- /dev/null +++ b/java/patchbukkit/src/main/java/com/destroystokyo/paper/PaperConfig.java @@ -0,0 +1,7 @@ +package com.destroystokyo.paper; + +/** + * Stub class for PaperLib and Paper plugin detection (PaperLib.isPaper()). + */ +public class PaperConfig { +} diff --git a/java/patchbukkit/src/main/java/io/papermc/lib/PaperLib.java b/java/patchbukkit/src/main/java/io/papermc/lib/PaperLib.java new file mode 100644 index 0000000..6c7603d --- /dev/null +++ b/java/patchbukkit/src/main/java/io/papermc/lib/PaperLib.java @@ -0,0 +1,52 @@ +package io.papermc.lib; + +import java.util.logging.Level; +import org.bukkit.plugin.Plugin; + +/** + * Stub / implementation of PaperLib for Paper plugin compatibility. + */ +public class PaperLib { + + private PaperLib() { + } + + public static boolean isPaper() { + return true; + } + + public static boolean isSpigot() { + return true; + } + + public static boolean isVersion(int version) { + return true; + } + + public static boolean isVersion(int version, int patch) { + return true; + } + + public static int getMinecraftVersion() { + return 26; + } + + public static int getMinecraftPatchVersion() { + return 2; + } + + public static void suggestPaper(Plugin plugin) { + } + + public static void suggestPaper(Plugin plugin, String reason) { + } + + public static void suggestPaper(Plugin plugin, Level level, String reason) { + } + + public static void suggestPaper(Plugin plugin, String reason, Object... args) { + } + + public static void suggestPaper(Plugin plugin, Level level, String reason, Object... args) { + } +} diff --git a/java/patchbukkit/src/main/java/io/papermc/paper/configuration/Configuration.java b/java/patchbukkit/src/main/java/io/papermc/paper/configuration/Configuration.java new file mode 100644 index 0000000..03cc8f1 --- /dev/null +++ b/java/patchbukkit/src/main/java/io/papermc/paper/configuration/Configuration.java @@ -0,0 +1,7 @@ +package io.papermc.paper.configuration; + +/** + * Stub class for PaperLib and Paper plugin detection (PaperLib.isPaper()). + */ +public class Configuration { +} diff --git a/java/patchbukkit/src/main/java/org/bukkit/command/SimpleCommandMap.java b/java/patchbukkit/src/main/java/org/bukkit/command/SimpleCommandMap.java new file mode 100644 index 0000000..60b54bb --- /dev/null +++ b/java/patchbukkit/src/main/java/org/bukkit/command/SimpleCommandMap.java @@ -0,0 +1,139 @@ +package org.bukkit.command; + +import org.bukkit.Location; +import org.bukkit.Server; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +public class SimpleCommandMap implements CommandMap { + protected final Map knownCommands; + protected final Server server; + + public SimpleCommandMap(@NotNull Server server, @NotNull Map knownCommands) { + this.server = server; + this.knownCommands = knownCommands != null ? knownCommands : new HashMap<>(); + } + + public SimpleCommandMap(@NotNull Server server) { + this(server, new HashMap<>()); + } + + @Override + public void registerAll(@NotNull String fallbackPrefix, @NotNull List commands) { + if (commands != null) { + for (Command c : commands) { + register(fallbackPrefix, c); + } + } + } + + @Override + public boolean register(@NotNull String label, @NotNull String fallbackPrefix, @NotNull Command command) { + if (label == null || fallbackPrefix == null || command == null) { + return false; + } + label = label.toLowerCase().trim(); + fallbackPrefix = fallbackPrefix.toLowerCase().trim(); + + boolean registeredDirectly = false; + if (!knownCommands.containsKey(label)) { + knownCommands.put(label, command); + registeredDirectly = true; + } + knownCommands.put(fallbackPrefix + ":" + label, command); + + if (command.getAliases() != null) { + for (String alias : command.getAliases()) { + if (alias == null) continue; + alias = alias.toLowerCase().trim(); + if (!knownCommands.containsKey(alias)) { + knownCommands.put(alias, command); + } + knownCommands.put(fallbackPrefix + ":" + alias, command); + } + } + return registeredDirectly; + } + + @Override + public boolean register(@NotNull String fallbackPrefix, @NotNull Command command) { + if (command == null) return false; + return register(command.getName(), fallbackPrefix, command); + } + + @Override + public boolean dispatch(@NotNull CommandSender sender, @NotNull String commandLine) throws CommandException { + if (commandLine == null) return false; + String cleanLine = commandLine.trim(); + while (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(); + Command command = knownCommands.get(sentLabel); + if (command == null) return false; + + try { + String[] args = Arrays.copyOfRange(split, 1, split.length); + return command.execute(sender, sentLabel, args); + } catch (Exception ex) { + throw new CommandException("Unhandled exception executing '" + commandLine + "'", ex); + } + } + + @Override + public void clearCommands() { + knownCommands.clear(); + } + + @Override + public @Nullable Command getCommand(@NotNull String name) { + if (name == null) return null; + return knownCommands.get(name.toLowerCase()); + } + + @Override + public @NotNull Map getKnownCommands() { + return Collections.unmodifiableMap(knownCommands); + } + + @Override + public @Nullable List tabComplete(@NotNull CommandSender sender, @NotNull String cmdLine) { + return tabComplete(sender, cmdLine, null); + } + + @Override + public @Nullable List tabComplete(@NotNull CommandSender sender, @NotNull String cmdLine, @Nullable Location location) { + if (cmdLine == null) return new ArrayList<>(); + String cleanLine = cmdLine.trim(); + while (cleanLine.startsWith("/")) { + cleanLine = cleanLine.substring(1).trim(); + } + String[] split = cleanLine.split(" ", -1); + if (split.length == 0) return new ArrayList<>(); + + String label = split[0].toLowerCase(); + Command command = knownCommands.get(label); + + if (split.length == 1) { + List completions = new ArrayList<>(); + for (String key : knownCommands.keySet()) { + if (key.startsWith(label)) completions.add(key); + } + return completions; + } + + if (command != null) { + String[] args = Arrays.copyOfRange(split, 1, split.length); + return command.tabComplete(sender, label, args, location); + } + + return null; + } +} diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/LibraryResolver.java b/java/patchbukkit/src/main/java/org/patchbukkit/LibraryResolver.java index 881a637..62f8427 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/LibraryResolver.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/LibraryResolver.java @@ -50,21 +50,38 @@ public final class LibraryResolver { RepositorySystem system = newRepositorySystem(); RepositorySystemSession session = newSession(system, baseDir); - List repos = List.of( - new RemoteRepository.Builder( - "papermc", - "default", - "https://repo.papermc.io/repository/maven-public/" - ).build(), - new RemoteRepository.Builder( - "central", - "default", - "https://repo1.maven.org/maven2/" - ).build() - ); + List repos = new ArrayList<>(List.of( + new RemoteRepository.Builder("papermc", "default", "https://repo.papermc.io/repository/maven-public/").build(), + new RemoteRepository.Builder("central", "default", "https://repo1.maven.org/maven2/").build(), + new RemoteRepository.Builder("jitpack", "default", "https://jitpack.io/").build(), + new RemoteRepository.Builder("codemc", "default", "https://repo.codemc.io/repository/maven-public/").build(), + new RemoteRepository.Builder("enginehub", "default", "https://maven.enginehub.org/repo/").build(), + new RemoteRepository.Builder("sonatype-snapshots", "default", "https://oss.sonatype.org/content/repositories/snapshots/").build(), + new RemoteRepository.Builder("sonatype-releases", "default", "https://oss.sonatype.org/content/repositories/releases/").build(), + new RemoteRepository.Builder("sponge", "default", "https://repo.spongepowered.org/repository/maven-public/").build(), + new RemoteRepository.Builder("xenondevs", "default", "https://repo.xenondevs.xyz/releases").build(), + new RemoteRepository.Builder("spigotmc", "default", "https://hub.spigotmc.org/nexus/content/repositories/snapshots/").build(), + new RemoteRepository.Builder("miraculixx-snapshots", "default", "https://repo.miraculixx.de/snapshots").build(), + new RemoteRepository.Builder("miraculixx-releases", "default", "https://repo.miraculixx.de/releases").build() + )); + + Set coordsToResolve = new LinkedHashSet<>(); + int customRepoIdx = 0; + for (String item : unique) { + String trimmed = item; + if (trimmed.startsWith("repo:")) { + trimmed = trimmed.substring(5).trim(); + } + if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) { + customRepoIdx++; + repos.add(new RemoteRepository.Builder("custom-repo-" + customRepoIdx, "default", trimmed).build()); + } else { + coordsToResolve.add(trimmed); + } + } Set files = new LinkedHashSet<>(); - for (String coord : unique) { + for (String coord : coordsToResolve) { try { Dependency dependency = new Dependency( new DefaultArtifact(coord), diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitBlockData.java b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitBlockData.java index 6bb0dc5..f288348 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitBlockData.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitBlockData.java @@ -1,13 +1,107 @@ package org.patchbukkit; +import org.bukkit.Material; import org.bukkit.block.BlockType; import org.bukkit.block.data.BlockData; -class PatchBukkitBlockData { - static BlockData newData(BlockType type, String data) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException( - "Unimplemented method 'newData'" +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; + +public final class PatchBukkitBlockData { + + private PatchBukkitBlockData() {} + + public static BlockData newData(Material material, BlockType type, String data) { + final Material mat = material != null ? material : (type != null ? type.asMaterial() : null); + if (mat == null || mat.isLegacy()) { + throw new IllegalArgumentException("Invalid block material: " + (material != null ? material : data)); + } + + try { + Class sharedConstants = Class.forName("net.minecraft.SharedConstants"); + sharedConstants.getMethod("tryDetectVersion").invoke(null); + Class bootstrap = Class.forName("net.minecraft.server.Bootstrap"); + bootstrap.getMethod("bootStrap").invoke(null); + } catch (Throwable ignored) {} + + try { + if (data != null && !data.isEmpty()) { + Class craftBlockData = Class.forName("org.bukkit.craftbukkit.block.data.CraftBlockData"); + java.lang.reflect.Method fromString = craftBlockData.getMethod("fromString", BlockType.class, String.class); + BlockData craftData = (BlockData) fromString.invoke(null, type, data); + if (craftData != null) { + return craftData; + } + } + } catch (Throwable ignored) {} + + final String stateData; + if (data != null && !data.isEmpty()) { + if (data.startsWith("minecraft:") || data.contains(":")) { + stateData = data; + } else if (data.startsWith("[")) { + stateData = mat.getKey().toString() + data; + } else if (!data.contains("[")) { + stateData = mat.getKey().toString(); + } else { + int bracketIdx = data.indexOf('['); + stateData = mat.getKey().toString() + data.substring(bracketIdx); + } + } else { + stateData = mat.getKey().toString(); + } + + return (BlockData) Proxy.newProxyInstance( + PatchBukkitBlockData.class.getClassLoader(), + new Class[]{BlockData.class}, + (proxy, method, args) -> { + String name = method.getName(); + if ("getMaterial".equals(name)) { + return mat; + } + if ("getAsString".equals(name)) { + return stateData; + } + if ("clone".equals(name) || "copy".equals(name)) { + return newData(mat, type, stateData); + } + if ("matches".equals(name) && args != null && args.length == 1) { + if (args[0] instanceof BlockData other) { + return mat.equals(other.getMaterial()); + } + return false; + } + if ("merge".equals(name)) { + return proxy; + } + if ("isSupported".equals(name)) { + return true; + } + if ("isFacingAllowed".equals(name)) { + return true; + } + if ("equals".equals(name) && args != null && args.length == 1) { + if (args[0] instanceof BlockData other) { + return stateData.equals(other.getAsString()); + } + return false; + } + if ("hashCode".equals(name)) { + return stateData.hashCode(); + } + if ("toString".equals(name)) { + return stateData; + } + if (method.isDefault()) { + return InvocationHandler.invokeDefault(proxy, method, args); + } + + Class returnType = method.getReturnType(); + if (returnType == boolean.class) return false; + if (returnType == int.class || returnType == short.class || returnType == long.class || returnType == byte.class) return 0; + if (returnType == float.class || returnType == double.class) return 0.0f; + return null; + } ); } } diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitInternalAPIBridge.java b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitInternalAPIBridge.java index c802a13..7d371cb 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitInternalAPIBridge.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitInternalAPIBridge.java @@ -36,12 +36,10 @@ 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, @@ -51,12 +49,25 @@ public class PatchBukkitInternalAPIBridge implements InternalAPIBridge { return component; } - @Override - public ItemStack createEmptyStack() { - return new ItemStack(Material.AIR); + private static final java.lang.reflect.Constructor ITEM_STACK_NO_ARG_CTOR; + + static { + try { + ITEM_STACK_NO_ARG_CTOR = ItemStack.class.getDeclaredConstructor(); + ITEM_STACK_NO_ARG_CTOR.setAccessible(true); + } catch (Exception e) { + throw new ExceptionInInitializerError(e); + } + } + + public ItemStack createEmptyStack() { + try { + return ITEM_STACK_NO_ARG_CTOR.newInstance(); + } catch (Exception e) { + throw new RuntimeException("Failed to create empty ItemStack", e); + } } - @Override public LifecycleEventManager createPluginLifecycleEventManager( JavaPlugin plugin, BooleanSupplier registrationCheck @@ -64,52 +75,46 @@ public class PatchBukkitInternalAPIBridge implements InternalAPIBridge { 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); + return createEmptyStack(); } - @Override public String getTranslationKey(EntityType entityType) { return entityType.translationKey(); } - @Override + public org.bukkit.entity.SpawnCategory getSpawnCategory(EntityType entityType) { + return org.bukkit.entity.SpawnCategory.MISC; + } + 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, @@ -119,27 +124,22 @@ public class PatchBukkitInternalAPIBridge implements InternalAPIBridge { 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, @@ -149,7 +149,6 @@ public class PatchBukkitInternalAPIBridge implements InternalAPIBridge { return null; } - @Override public CombatEntry createCombatEntry( LivingEntity entity, DamageSource damageSource, @@ -158,7 +157,6 @@ public class PatchBukkitInternalAPIBridge implements InternalAPIBridge { return null; } - @Override public Biome constructLegacyCustomBiome() { return null; } diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitPluginClassLoader.java b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitPluginClassLoader.java index 21009c4..9ad5b30 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitPluginClassLoader.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitPluginClassLoader.java @@ -11,6 +11,7 @@ import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; import org.bukkit.plugin.InvalidDescriptionException; @@ -32,6 +33,9 @@ public class PatchBukkitPluginClassLoader ClassLoader.registerAsParallelCapable(); } + public static final java.util.Set ALL_LOADERS = + new java.util.concurrent.CopyOnWriteArraySet<>(); + public PatchBukkitPluginClassLoader(ClassLoader parent, File file) throws MalformedURLException, InvalidDescriptionException { this(parent, file, new URL[0]); @@ -46,6 +50,194 @@ public class PatchBukkitPluginClassLoader this.file = file; this.description = loadDescription(file); this.dataFolder = new File(file.getParentFile(), description.getName()); + ALL_LOADERS.add(this); + + File libsDir = new File(file.getParentFile(), "patchbukkit-libs"); + if (!libsDir.exists()) { + libsDir.mkdirs(); + } + + // Extract and load nested JARs inside plugin JAR + for (File nestedJar : extractNestedJars(file, libsDir)) { + try { + addURL(nestedJar.toURI().toURL()); + } catch (MalformedURLException ignored) {} + } + + List libsToResolve = new ArrayList<>(); + if (this.description.getLibraries() != null) { + libsToResolve.addAll(this.description.getLibraries()); + } + for (String lib : extractLibraries(file)) { + if (!libsToResolve.contains(lib)) { + libsToResolve.add(lib); + } + } + + if (!libsToResolve.isEmpty()) { + List resolved = LibraryResolver.resolveLibraries( + String.join("\n", libsToResolve), + libsDir + ); + for (File lib : resolved) { + if (lib != null && lib.exists()) { + try { + addURL(lib.toURI().toURL()); + } catch (MalformedURLException ignored) {} + } + } + } + } + + private static List extractNestedJars(File file, File libsDir) { + List extractedFiles = new ArrayList<>(); + try (JarFile jar = new JarFile(file)) { + java.util.Enumeration entries = jar.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String name = entry.getName(); + if (!entry.isDirectory() && name.endsWith(".jar")) { + if (name.startsWith("META-INF/jars/") || + name.startsWith("paper-libraries/") || + name.startsWith("BOOT-INF/lib/") || + name.startsWith("lib/") || + name.startsWith("libraries/")) { + + File targetFile = new File(libsDir, new File(name).getName()); + if (!targetFile.exists()) { + try (InputStream is = jar.getInputStream(entry); + java.io.FileOutputStream fos = new java.io.FileOutputStream(targetFile)) { + is.transferTo(fos); + } + } + if (targetFile.exists()) { + extractedFiles.add(targetFile); + } + } + } + } + } catch (Throwable ignored) {} + return extractedFiles; + } + + @SuppressWarnings("unchecked") + private static List extractLibraries(File file) { + List libraries = new ArrayList<>(); + try (JarFile jar = new JarFile(file)) { + // 1. Try paper-libraries.json + JarEntry entryJson = jar.getJarEntry("paper-libraries.json"); + if (entryJson != null) { + try (InputStream is = jar.getInputStream(entryJson)) { + org.yaml.snakeyaml.Yaml yaml = new org.yaml.snakeyaml.Yaml(); + Object obj = yaml.load(is); + if (obj instanceof Map data) { + Object repos = data.get("repositories"); + if (repos instanceof Map repoMap) { + for (Object v : repoMap.values()) { + if (v != null && !v.toString().isBlank()) { + String r = v.toString().trim(); + if (!libraries.contains("repo:" + r)) { + libraries.add("repo:" + r); + } + } + } + } else if (repos instanceof List repoList) { + for (Object o : repoList) { + if (o != null) { + String r = o.toString().trim(); + if (o instanceof Map m && m.containsKey("url")) { + r = m.get("url").toString().trim(); + } + if (!libraries.contains("repo:" + r)) { + libraries.add("repo:" + r); + } + } + } + } + Object deps = data.get("dependencies"); + if (deps instanceof List list) { + for (Object o : list) { + if (o != null && !libraries.contains(o.toString())) { + libraries.add(o.toString()); + } + } + } else if (deps instanceof Map map) { + for (Map.Entry e : map.entrySet()) { + if (e.getKey() != null && e.getValue() != null) { + String coord = e.getKey().toString() + ":" + e.getValue().toString(); + if (!libraries.contains(coord)) { + libraries.add(coord); + } + } + } + } + } + } catch (Throwable ignored) {} + } + + // 2. Try paper-libraries.list + JarEntry entryList = jar.getJarEntry("paper-libraries.list"); + if (entryList != null) { + try (InputStream is = jar.getInputStream(entryList); + java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(is, java.nio.charset.StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + line = line.trim(); + if (!line.isEmpty() && !line.startsWith("#")) { + if (!libraries.contains(line)) { + libraries.add(line); + } + } + } + } catch (Throwable ignored) {} + } + + // 3. Try paper-plugin.yml or plugin.yml + JarEntry entryYaml = jar.getJarEntry("paper-plugin.yml"); + if (entryYaml == null) { + entryYaml = jar.getJarEntry("plugin.yml"); + } + if (entryYaml != null) { + try (InputStream is = jar.getInputStream(entryYaml)) { + org.yaml.snakeyaml.Yaml yaml = new org.yaml.snakeyaml.Yaml(); + Object obj = yaml.load(is); + if (obj instanceof Map data) { + Object libs = data.get("libraries"); + if (libs instanceof List list) { + for (Object o : list) { + if (o != null && !libraries.contains(o.toString())) { + libraries.add(o.toString()); + } + } + } + Object repos = data.get("repositories"); + if (repos instanceof Map repoMap) { + for (Object v : repoMap.values()) { + if (v != null && !v.toString().isBlank()) { + String r = v.toString().trim(); + if (!libraries.contains("repo:" + r)) { + libraries.add("repo:" + r); + } + } + } + } else if (repos instanceof List repoList) { + for (Object o : repoList) { + if (o != null) { + String r = o.toString().trim(); + if (o instanceof Map m && m.containsKey("url")) { + r = m.get("url").toString().trim(); + } + if (!libraries.contains("repo:" + r)) { + libraries.add("repo:" + r); + } + } + } + } + } + } catch (Throwable ignored) {} + } + } catch (Throwable ignored) {} + return libraries; } private static URL[] buildUrls(File file, URL[] extraUrls) @@ -103,10 +295,23 @@ public class PatchBukkitPluginClassLoader !name.startsWith("javax.") ) { try { - // Try to find in plugin JAR first + // Try to find in plugin JAR / libraries first c = findClass(name); } catch (ClassNotFoundException e) { - // Not in JAR, delegate to parent + // Not in local JAR, check other plugin classloaders in global pool + for (PatchBukkitPluginClassLoader other : ALL_LOADERS) { + if (other != this) { + try { + c = other.findLoadedClass(name); + if (c == null) { + c = other.findClass(name); + } + if (c != null) { + break; + } + } catch (ClassNotFoundException ignored) {} + } + } } } @@ -179,6 +384,7 @@ public class PatchBukkitPluginClassLoader @Override public void close() throws IOException { + ALL_LOADERS.remove(this); super.close(); } } diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitPluginLoader.java b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitPluginLoader.java index 45cf994..4f0fa0e 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitPluginLoader.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitPluginLoader.java @@ -19,11 +19,12 @@ import org.bukkit.plugin.PluginLoader; import org.bukkit.plugin.RegisteredListener; import org.bukkit.plugin.UnknownDependencyException; import org.bukkit.plugin.java.JavaPlugin; +import org.patchbukkit.PatchBukkitPluginManager; @SuppressWarnings({ "deprecation", "removal" }) public class PatchBukkitPluginLoader implements PluginLoader { - public static JavaPlugin createPlugin( + public static Plugin createPlugin( String jarPath, String mainClass, String extraClasspath, @@ -40,7 +41,7 @@ public class PatchBukkitPluginLoader implements PluginLoader { LinkedHashSet extraUrls = new LinkedHashSet<>(); if (extraClasspath != null && !extraClasspath.isBlank()) { - String[] paths = extraClasspath.split(File.pathSeparator); + String[] paths = extraClasspath.split("[;:]"); for (String path : paths) { if (path == null || path.isBlank()) { continue; @@ -76,14 +77,62 @@ public class PatchBukkitPluginLoader implements PluginLoader { ); Class jarClass = Class.forName(mainClass, true, classLoader); - return (JavaPlugin) jarClass.getDeclaredConstructor().newInstance(); + Class pluginClass = jarClass.asSubclass(Plugin.class); + Plugin plugin = pluginClass.getDeclaredConstructor().newInstance(); + if (plugin instanceof JavaPlugin javaPlugin) { + classLoader.init(javaPlugin); + } + try { + if (org.bukkit.Bukkit.getPluginManager() instanceof PatchBukkitPluginManager pm) { + pm.registerPlugin(plugin); + } + } catch (Throwable ignored) {} + try { + plugin.onLoad(); + } catch (Throwable t) { + System.err.println("[PatchBukkit] Error during onLoad() for " + mainClass + ": " + t.getMessage()); + t.printStackTrace(); + } + return plugin; } catch (Throwable e) { System.err.println("[PatchBukkit] Failed to instantiate plugin class " + mainClass + ": " + e.getMessage()); e.printStackTrace(); - throw new RuntimeException("Failed to instantiate plugin " + mainClass, e); + return null; } } + public static boolean enablePlugin(String pluginName) { + try { + if (org.bukkit.Bukkit.getPluginManager() instanceof PatchBukkitPluginManager pm) { + Plugin p = pm.getPlugin(pluginName); + if (p != null) { + pm.enablePlugin(p); + return true; + } + } + } catch (Throwable t) { + System.err.println("[PatchBukkit] Failed to enable plugin " + pluginName + ": " + t.getMessage()); + t.printStackTrace(); + } + return false; + } + + public static boolean disablePlugin(String pluginName) { + try { + if (org.bukkit.Bukkit.getPluginManager() instanceof PatchBukkitPluginManager pm) { + Plugin p = pm.getPlugin(pluginName); + if (p != null) { + pm.disablePlugin(p); + return true; + } + } + } catch (Throwable t) { + System.err.println("[PatchBukkit] Failed to disable plugin " + pluginName + ": " + t.getMessage()); + t.printStackTrace(); + } + return false; + } + @Override public Plugin loadPlugin(File file) throws InvalidPluginException, UnknownDependencyException { @@ -106,14 +155,32 @@ public class PatchBukkitPluginLoader implements PluginLoader { @Override public void enablePlugin(Plugin plugin) { if (plugin instanceof JavaPlugin javaPlugin) { - javaPlugin.setEnabled(true); + try { + javaPlugin.setEnabled(true); + org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.server.PluginEnableEvent(plugin)); + } catch (Throwable ex) { + org.bukkit.Bukkit.getLogger().log( + java.util.logging.Level.SEVERE, + "Error occurred while enabling " + plugin.getDescription().getFullName() + " (Is it up to date?)", + ex + ); + } } } @Override public void disablePlugin(Plugin plugin) { if (plugin instanceof JavaPlugin javaPlugin) { - javaPlugin.setEnabled(false); + try { + javaPlugin.setEnabled(false); + org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.server.PluginDisableEvent(plugin)); + } catch (Throwable ex) { + org.bukkit.Bukkit.getLogger().log( + java.util.logging.Level.SEVERE, + "Error occurred while disabling " + plugin.getDescription().getFullName(), + ex + ); + } } } diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitPluginManager.java b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitPluginManager.java index 7acd4a6..185048a 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitPluginManager.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitPluginManager.java @@ -8,6 +8,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; import org.bukkit.Server; import org.bukkit.event.Event; @@ -45,9 +46,16 @@ public class PatchBukkitPluginManager implements PluginManager { throws IllegalArgumentException {} public void registerPlugin(@NotNull Plugin plugin) { + if (plugin == null || plugin.getName() == null) { + return; + } 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); + if (plugin.getDescription() != null && plugin.getDescription().getProvides() != null) { + for (String provided : plugin.getDescription().getProvides()) { + if (provided != null && !provided.isBlank()) { + plugins.putIfAbsent(provided.toLowerCase(java.util.Locale.ENGLISH), plugin); + } + } } } @@ -99,6 +107,12 @@ public class PatchBukkitPluginManager implements PluginManager { Class pluginClass = jarClass.asSubclass(Plugin.class); Plugin plugin = pluginClass.getDeclaredConstructor().newInstance(); loader.init((org.bukkit.plugin.java.JavaPlugin) plugin); + try { + plugin.onLoad(); + } catch (Throwable t) { + server.getLogger().severe("Error loading " + plugin.getName() + ": " + t.getMessage()); + t.printStackTrace(); + } registerPlugin(plugin); return plugin; } catch (Throwable e) { @@ -206,7 +220,7 @@ public class PatchBukkitPluginManager implements PluginManager { try { plugin.getPluginLoader().enablePlugin(plugin); } catch (Throwable ex) { - server.getLogger().severe("Error enabling " + plugin.getName() + ": " + ex.getMessage()); + server.getLogger().log(Level.SEVERE, "Error enabling " + plugin.getName() + " (Is it up to date?)", ex); } } } @@ -217,7 +231,11 @@ public class PatchBukkitPluginManager implements PluginManager { return; } if (plugin.isEnabled()) { - plugin.getPluginLoader().disablePlugin(plugin); + try { + plugin.getPluginLoader().disablePlugin(plugin); + } catch (Throwable ex) { + server.getLogger().log(Level.SEVERE, "Error disabling " + plugin.getName(), ex); + } } } diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitServer.java b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitServer.java index 9736f29..051e39f 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitServer.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitServer.java @@ -60,6 +60,7 @@ import org.bukkit.command.Command; import org.bukkit.command.CommandException; import org.bukkit.command.CommandMap; import org.bukkit.command.CommandSender; +import org.bukkit.command.SimpleCommandMap; import org.bukkit.command.ConsoleCommandSender; import org.bukkit.command.PluginCommand; import org.bukkit.entity.Entity; @@ -105,19 +106,75 @@ import patchbukkit.log.SendLogRequest; @SuppressWarnings({ "deprecation", "removal", "unchecked" }) public class PatchBukkitServer implements Server { + private static volatile PatchBukkitServer INSTANCE; - private final String serverName = - io.papermc.paper.ServerBuildInfo.buildInfo().brandName(); + public PatchBukkitServer() { + INSTANCE = this; + String name = "PatchBukkit"; + try { + name = io.papermc.paper.ServerBuildInfo.buildInfo().brandName(); + } catch (Throwable ignored) {} + this.serverName = name; + } + + public static PatchBukkitServer getInstance() { + if (INSTANCE == null) { + synchronized (PatchBukkitServer.class) { + if (INSTANCE == null) { + new PatchBukkitServer(); + } + } + } + return INSTANCE; + } + + static { + try { + Class sharedConstants = Class.forName("net.minecraft.SharedConstants"); + sharedConstants.getMethod("tryDetectVersion").invoke(null); + Class bootstrap = Class.forName("net.minecraft.server.Bootstrap"); + bootstrap.getMethod("bootStrap").invoke(null); + } catch (Throwable ignored) {} + } + + public static PatchBukkitServer initServer() { + Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> { + logger.log(Level.SEVERE, "Uncaught exception in thread " + thread.getName(), throwable); + }); + PatchBukkitServer server = getInstance(); + org.bukkit.Bukkit.setServer(server); + return server; + } + + private final String serverName; private final String bukkitVersion = Versioning.getBukkitVersion(); - private final CommandMap commandMap = new PatchBukkitCommandMap(); - private final BukkitScheduler scheduler = new PatchBukkitScheduler(); - private final PatchBukkitPluginManager pluginManager = new PatchBukkitPluginManager(this); - private final ServicesManager servicesManager = new PatchBukkitServicesManager(); + public SimpleCommandMap commandMap = new PatchBukkitCommandMap(this); + public BukkitScheduler scheduler = new PatchBukkitScheduler(); + public PatchBukkitPluginManager pluginManager = new PatchBukkitPluginManager(this); + public ServicesManager servicesManager = new PatchBukkitServicesManager(); private final Map onlinePlayers = new java.util.concurrent.ConcurrentHashMap<>(); private final Map onlinePlayersByName = new java.util.concurrent.ConcurrentHashMap<>(); + private final Set operatorUuids = java.util.Collections.synchronizedSet(new java.util.HashSet<>()); + private final Set operatorNames = java.util.Collections.synchronizedSet(new java.util.HashSet<>()); + + public boolean isOp(UUID uuid, String name) { + if (uuid != null && operatorUuids.contains(uuid)) return true; + if (name != null && operatorNames.contains(name.toLowerCase())) return true; + return false; + } + + public void setOperator(UUID uuid, String name, boolean value) { + if (uuid != null) { + if (value) operatorUuids.add(uuid); else operatorUuids.remove(uuid); + } + if (name != null) { + if (value) operatorNames.add(name.toLowerCase()); else operatorNames.remove(name.toLowerCase()); + } + } + private static final Logger logger = Logger.getLogger("Minecraft"); static { @@ -144,14 +201,16 @@ public class PatchBukkitServer implements Server { root.addHandler(new java.util.logging.Handler() { @Override public void publish(java.util.logging.LogRecord record) { - LogLevel logLevel = LEVEL_MAP.getOrDefault(record.getLevel(), LogLevel.INFO); - NativeBridgeFfi.sendLog( - SendLogRequest.newBuilder() - .setLevel(logLevel) - .setMessage(record.getMessage()) - .setLoggerName(record.getLoggerName()) - .build() - ); + try { + LogLevel logLevel = LEVEL_MAP.getOrDefault(record.getLevel(), LogLevel.INFO); + NativeBridgeFfi.sendLog( + SendLogRequest.newBuilder() + .setLevel(logLevel) + .setMessage(record.getMessage() != null ? record.getMessage() : "") + .setLoggerName(record.getLoggerName() != null ? record.getLoggerName() : "") + .build() + ); + } catch (Throwable ignored) {} } @Override public void flush() {} @Override public void close() {} @@ -185,6 +244,7 @@ public class PatchBukkitServer implements Server { } private final Messenger messenger = new org.patchbukkit.messaging.PatchBukkitMessenger(); + private final HelpMap helpMap = new org.patchbukkit.help.PatchBukkitHelpMap(); private File pluginsFolder = new File("plugins"); private static void validateChannel(String channel) { @@ -590,7 +650,6 @@ public class PatchBukkitServer implements Server { ); } - @Override public @NotNull World getRespawnWorld() { var worlds = getWorlds(); if (!worlds.isEmpty()) { @@ -601,7 +660,6 @@ public class PatchBukkitServer implements Server { ); } - @Override public void setRespawnWorld(@NotNull World world) { // TODO Auto-generated method stub throw new UnsupportedOperationException( @@ -904,7 +962,6 @@ public class PatchBukkitServer implements Server { ); } - @Override public @NotNull ServerConfiguration getServerConfig() { // TODO Auto-generated method stub throw new UnsupportedOperationException( @@ -949,28 +1006,23 @@ public class PatchBukkitServer implements Server { @Override public @NotNull OfflinePlayer getOfflinePlayer(@NotNull String name) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException( - "Unimplemented method 'getOfflinePlayer'" - ); + Player online = getPlayer(name); + if (online != null) return online; + return new org.patchbukkit.entity.PatchBukkitOfflinePlayer(null, name); } @Override - public @Nullable OfflinePlayer getOfflinePlayerIfCached( - @NotNull String name - ) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException( - "Unimplemented method 'getOfflinePlayerIfCached'" - ); + public @Nullable OfflinePlayer getOfflinePlayerIfCached(@NotNull String name) { + Player online = getPlayer(name); + if (online != null) return online; + return new org.patchbukkit.entity.PatchBukkitOfflinePlayer(null, name); } @Override public @NotNull OfflinePlayer getOfflinePlayer(@NotNull UUID id) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException( - "Unimplemented method 'getOfflinePlayer'" - ); + Player online = getPlayer(id); + if (online != null) return online; + return new org.patchbukkit.entity.PatchBukkitOfflinePlayer(id, null); } @Override @@ -1066,10 +1118,14 @@ public class PatchBukkitServer implements Server { @Override public @NotNull Set getOperators() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException( - "Unimplemented method 'getOperators'" - ); + Set ops = new java.util.HashSet<>(); + for (UUID uuid : operatorUuids) { + ops.add(getOfflinePlayer(uuid)); + } + for (String name : operatorNames) { + ops.add(getOfflinePlayer(name)); + } + return ops; } @Override @@ -1088,7 +1144,6 @@ public class PatchBukkitServer implements Server { ); } - @Override public boolean forcesDefaultGameMode() { // TODO Auto-generated method stub throw new UnsupportedOperationException( @@ -1134,10 +1189,7 @@ public class PatchBukkitServer implements Server { @Override public @NotNull HelpMap getHelpMap() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException( - "Unimplemented method 'getHelpMap'" - ); + return this.helpMap; } @Override @@ -1252,10 +1304,7 @@ public class PatchBukkitServer implements Server { @Override public boolean isPrimaryThread() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException( - "Unimplemented method 'isPrimaryThread'" - ); + return true; } @Override @@ -1317,10 +1366,7 @@ public class PatchBukkitServer implements Server { @Override public @NotNull ItemFactory getItemFactory() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException( - "Unimplemented method 'getItemFactory'" - ); + return org.patchbukkit.inventory.PatchBukkitItemFactory.INSTANCE; } @Override @@ -1475,18 +1521,12 @@ public class PatchBukkitServer implements Server { @Override public double@NotNull [] getTPS() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException( - "Unimplemented method 'getTPS'" - ); + return new double[] { 20.0, 20.0, 20.0 }; } @Override public long@NotNull [] getTickTimes() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException( - "Unimplemented method 'getTickTimes'" - ); + return new long[100]; } @Override @@ -1554,33 +1594,96 @@ public class PatchBukkitServer implements Server { BlockType type = null; if (material != null) { type = material.asBlockType(); + if (type == null && material.isBlock()) { + type = org.patchbukkit.registry.PatchBukkitBlockType.create(material); + } Preconditions.checkArgument(type != null, "Provided material must be a block"); + } else if (data != null) { + String matName = data.trim(); + int stateIndex = matName.indexOf('['); + if (stateIndex != -1) { + matName = matName.substring(0, stateIndex).trim(); + } + if (matName.startsWith("minecraft:")) { + matName = matName.substring("minecraft:".length()); + } + Material matched = null; + try { + matched = Material.valueOf(matName.toUpperCase(java.util.Locale.ROOT)); + } catch (Throwable ignored) {} + + if (matched == null || matched.isLegacy()) { + Material m = Material.matchMaterial(matName, false); + if (m != null && !m.isLegacy()) { + matched = m; + } + } + + if (matched == null) { + matched = Material.matchMaterial(matName); + } + + if (matched != null && matched.isLegacy()) { + matched = PatchBukkitLegacy.fromLegacy(matched); + } + + if (matched != null && (matched.isBlock() || (!matched.isLegacy() && matched.getKey() != null))) { + material = matched; + type = matched.asBlockType(); + if (type == null) { + type = org.patchbukkit.registry.PatchBukkitBlockType.create(matched); + } + } else { + throw new IllegalArgumentException("Block name " + matName + " was not recognized"); + } } - return PatchBukkitBlockData.newData(type, data); + return PatchBukkitBlockData.newData(material, type, data); } @Override + @SuppressWarnings("unchecked") public @Nullable Tag getTag( @NotNull String registry, @NotNull NamespacedKey tag, @NotNull Class clazz ) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException( - "Unimplemented method 'getTag'" - ); + if (registry == null || tag == null || clazz == null) { + return null; + } + try { + for (java.lang.reflect.Field field : org.bukkit.Tag.class.getFields()) { + if (Tag.class.isAssignableFrom(field.getType())) { + Tag val = (Tag) field.get(null); + if (val != null && val.getKey().equals(tag)) { + return (Tag) val; + } + } + } + } catch (Throwable ignored) { + } + return new org.patchbukkit.tag.PatchBukkitTag<>(tag); } @Override + @SuppressWarnings("unchecked") public @NotNull Iterable> getTags( @NotNull String registry, @NotNull Class clazz ) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException( - "Unimplemented method 'getTags'" - ); + List> result = new ArrayList<>(); + try { + for (java.lang.reflect.Field field : org.bukkit.Tag.class.getFields()) { + if (Tag.class.isAssignableFrom(field.getType())) { + Tag val = (Tag) field.get(null); + if (val != null) { + result.add((Tag) val); + } + } + } + } catch (Throwable ignored) { + } + return result; } @Override @@ -1719,18 +1822,12 @@ public class PatchBukkitServer implements Server { @Override public int getCurrentTick() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException( - "Unimplemented method 'getCurrentTick'" - ); + return (int) (System.currentTimeMillis() / 50); } @Override public boolean isStopping() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException( - "Unimplemented method 'isStopping'" - ); + return false; } @Override @@ -1894,9 +1991,28 @@ public class PatchBukkitServer implements Server { ); } - @Override public @NotNull Path getLevelDirectory() { // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'getLevelDirectory'"); } + + @Override + public int getAmbientSpawnLimit() { + return 15; + } + + @Override + public int getWaterUndergroundCreatureSpawnLimit() { + return 5; + } + + @Override + public int getWaterAmbientSpawnLimit() { + return 20; + } + + @Override + public int getWaterAnimalSpawnLimit() { + return 5; + } } diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitUnsafeValues.java b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitUnsafeValues.java index 3fbfed5..b947118 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitUnsafeValues.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/PatchBukkitUnsafeValues.java @@ -55,6 +55,18 @@ public class PatchBukkitUnsafeValues implements UnsafeValues { public static final PatchBukkitUnsafeValues INSTANCE = new PatchBukkitUnsafeValues(); + public @NotNull List computeTooltipLines(@NotNull ItemStack itemStack, @NotNull TooltipContext tooltipContext, @Nullable Player player) { + return java.util.Collections.emptyList(); + } + + public io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager createPluginLifecycleEventManager(org.bukkit.plugin.java.JavaPlugin plugin, java.util.function.BooleanSupplier registrationCheck) { + return null; + } + + public @Nullable Color getSpawnEggLayerColor(EntityType entityType, int layer) { + return null; + } + @Override public boolean isSupportedApiVersion(String apiVersion) { if (apiVersion == null) return false; @@ -78,8 +90,7 @@ public class PatchBukkitUnsafeValues implements UnsafeValues { @Override public Material toLegacy(Material material) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'toLegacy'"); + return material; } @Override @@ -99,122 +110,107 @@ public class PatchBukkitUnsafeValues implements UnsafeValues { @Override public BlockData fromLegacy(Material material, byte data) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'fromLegacy'"); + return material != null ? material.createBlockData() : null; } @Override public Material getMaterial(String material, int version) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getMaterial'"); + try { + return Material.valueOf(material.toUpperCase(java.util.Locale.ROOT)); + } catch (Exception e) { + return Material.getMaterial(material); + } } @Override public int getDataVersion() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getDataVersion'"); + return 3953; } @Override public ItemStack modifyItemStack(ItemStack stack, String arguments) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'modifyItemStack'"); + return stack; + } + + public @NotNull ItemStack createEmptyStack() { + return new ItemStack(Material.AIR); } @Override public byte[] processClass(PluginDescriptionFile pdf, String path, byte[] clazz) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'processClass'"); + return clazz; } - @Override - public Advancement loadAdvancement(NamespacedKey key, String advancement) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'loadAdvancement'"); + public Advancement loadAdvancement(net.kyori.adventure.key.Key key, String advancement, boolean checkKey) { + return null; + } + + public List loadAdvancements(Map advancements, boolean checkKey) { + return java.util.Collections.emptyList(); } @Override public boolean removeAdvancement(NamespacedKey key) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'removeAdvancement'"); + return false; } @Override public String get(Class aClass, String value) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'get'"); + return null; } - @Override public B get(RegistryKey registry, NamespacedKey key) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'get'"); + if (registry == null || key == null) return null; + org.bukkit.Registry reg = io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(registry); + return reg != null ? reg.get(key) : null; } @Override public @NotNull JsonObject serializeItemAsJson(@NotNull ItemStack itemStack) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'serializeItemAsJson'"); + return new JsonObject(); } @Override public @NotNull ItemStack deserializeItemFromJson(@NotNull JsonObject data) throws IllegalArgumentException { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'deserializeItemFromJson'"); + return new ItemStack(Material.STONE); } - @Override public byte @NotNull [] serializeEntity(@NotNull Entity entity, @NotNull EntitySerializationFlag... serializationFlags) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'serializeEntity'"); + return new byte[0]; } - @Override public @NotNull Entity deserializeEntity(byte @NotNull [] data, @NotNull World world, boolean preserveUUID, boolean preservePassengers) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'deserializeEntity'"); + return null; } @Override public @NotNull String getMainLevelName() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getMainLevelName'"); + return "world"; } @Override public int getProtocolVersion() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getProtocolVersion'"); + return 769; } - - - @Override public @NotNull ItemStack deserializeStack(@NotNull Map args) { System.out.println("Deserializing itemstack: " + args); - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'deserializeStack'"); + return new ItemStack(Material.STONE); } - @Override public @NotNull ItemStack deserializeItemHover(@NotNull ShowItem itemHover) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'deserializeItemHover'"); + return new ItemStack(Material.STONE); } - @Override public InternalPotionData getInternalPotionData(NamespacedKey key) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getInternalPotionData'"); + return null; } - @Override public int nextEntityId(World world) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'nextEntityId'"); + return (int) (System.currentTimeMillis() & 0x7FFFFFFF); } diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/bridge/BridgeUtils.java b/java/patchbukkit/src/main/java/org/patchbukkit/bridge/BridgeUtils.java index be65a4b..9880a95 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/bridge/BridgeUtils.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/bridge/BridgeUtils.java @@ -12,17 +12,32 @@ public class BridgeUtils { } public static patchbukkit.common.Location convertLocation(org.bukkit.Location location) { + if (location == null) return null; var pos = patchbukkit.common.Vec3.newBuilder() .setX(location.getX()) .setY(location.getY()) .setZ(location.getZ()) .build(); - var worldUuid = convertUuid(location.getWorld().getUID()); + var worldUuid = location.getWorld() != null ? convertUuid(location.getWorld().getUID()) : null; + var worldBuilder = patchbukkit.common.World.newBuilder(); + if (worldUuid != null) worldBuilder.setUuid(worldUuid); return patchbukkit.common.Location.newBuilder() - .setWorld(patchbukkit.common.World.newBuilder().setUuid(worldUuid).build()) + .setWorld(worldBuilder.build()) .setPosition(pos) .setYaw(location.getYaw()) .setPitch(location.getPitch()) .build(); } + + public static org.bukkit.Location convertLocation(patchbukkit.common.Location location) { + if (location == null) return null; + org.bukkit.World world = null; + if (location.hasWorld() && location.getWorld().hasUuid()) { + world = org.patchbukkit.world.PatchBukkitWorld.getOrCreate(convertUuid(location.getWorld().getUuid())); + } + double x = location.hasPosition() ? location.getPosition().getX() : 0.0; + double y = location.hasPosition() ? location.getPosition().getY() : 0.0; + double z = location.hasPosition() ? location.getPosition().getZ() : 0.0; + return new org.bukkit.Location(world, x, y, z, location.getYaw(), location.getPitch()); + } } diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/command/CommandFactory.java b/java/patchbukkit/src/main/java/org/patchbukkit/command/CommandFactory.java index 47bf467..33bfca5 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/command/CommandFactory.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/command/CommandFactory.java @@ -15,4 +15,31 @@ public class CommandFactory { return null; } } + + public static PluginCommand create(String name, String pluginName) { + try { + Plugin plugin = null; + if (org.bukkit.Bukkit.getPluginManager() instanceof org.patchbukkit.PatchBukkitPluginManager pm) { + plugin = pm.getPlugin(pluginName); + if (plugin == null) { + for (Plugin p : pm.getPlugins()) { + if (p.getName().equalsIgnoreCase(pluginName)) { + plugin = p; + break; + } + } + } + } + if (plugin == null) { + plugin = org.bukkit.Bukkit.getPluginManager().getPlugin(pluginName); + } + if (plugin == null) { + System.err.println("[PatchBukkit] CommandFactory: Plugin not found by name '" + pluginName + "' for command '" + name + "'"); + } + return create(name, plugin); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } } \ No newline at end of file diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/command/PatchBukkitBlockCommandSender.java b/java/patchbukkit/src/main/java/org/patchbukkit/command/PatchBukkitBlockCommandSender.java index ccd92c2..e496b0f 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/command/PatchBukkitBlockCommandSender.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/command/PatchBukkitBlockCommandSender.java @@ -16,136 +16,108 @@ import org.jetbrains.annotations.Nullable; import net.kyori.adventure.text.Component; public class PatchBukkitBlockCommandSender implements BlockCommandSender { - @NotNull + @Override public Block getBlock() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getBlock'"); + return null; } @Override public boolean isPermissionSet(@NotNull String name) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'isPermissionSet'"); + return true; } @Override public boolean isPermissionSet(@NotNull Permission perm) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'isPermissionSet'"); + return true; } @Override public boolean hasPermission(@NotNull String name) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'hasPermission'"); + return true; } @Override public boolean hasPermission(@NotNull Permission perm) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'hasPermission'"); + return true; } @Override public @NotNull PermissionAttachment addAttachment(@NotNull Plugin plugin, @NotNull String name, boolean value) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'addAttachment'"); + return new PermissionAttachment(plugin, this); } @Override public @NotNull PermissionAttachment addAttachment(@NotNull Plugin plugin) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'addAttachment'"); + return new PermissionAttachment(plugin, this); } @Override public @Nullable PermissionAttachment addAttachment(@NotNull Plugin plugin, @NotNull String name, boolean value, int ticks) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'addAttachment'"); + return new PermissionAttachment(plugin, this); } @Override public @Nullable PermissionAttachment addAttachment(@NotNull Plugin plugin, int ticks) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'addAttachment'"); + return new PermissionAttachment(plugin, this); } @Override public void removeAttachment(@NotNull PermissionAttachment attachment) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'removeAttachment'"); } @Override public void recalculatePermissions() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'recalculatePermissions'"); } @Override public @NotNull Set getEffectivePermissions() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getEffectivePermissions'"); + return java.util.Collections.emptySet(); } @Override public boolean isOp() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'isOp'"); + return true; } @Override public void setOp(boolean value) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'setOp'"); } @Override public void sendMessage(@NotNull String message) { - throw new UnsupportedOperationException("Unimplemented method 'sendMessage'"); } @Override public void sendMessage(@NotNull String... messages) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'sendMessage'"); } @Override public void sendMessage(@Nullable UUID sender, @NotNull String message) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'sendMessage'"); } @Override public void sendMessage(@Nullable UUID sender, @NotNull String... messages) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'sendMessage'"); } @Override public @NotNull Server getServer() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getServer'"); + return org.bukkit.Bukkit.getServer(); } @Override public @NotNull String getName() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getName'"); + return "@"; } @Override public @NotNull Spigot spigot() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'spigot'"); + return new Spigot(); } @Override public @NotNull Component name() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'name'"); + return Component.text("@"); } } 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 35165fd..f764b0a 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/command/PatchBukkitCommandMap.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/command/PatchBukkitCommandMap.java @@ -7,16 +7,56 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import org.bukkit.Location; +import org.bukkit.Server; import org.bukkit.command.Command; import org.bukkit.command.CommandException; -import org.bukkit.command.CommandMap; import org.bukkit.command.CommandSender; +import org.bukkit.command.SimpleCommandMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import patchbukkit.bridge.NativeBridgeFfi; +import patchbukkit.command.RegisterCommandRequest; -public class PatchBukkitCommandMap implements CommandMap { - // We use Command as the value to support both PluginCommand and vanilla commands - private final Map knownCommands = new HashMap<>(); +public class PatchBukkitCommandMap extends SimpleCommandMap { + + public PatchBukkitCommandMap(Server server) { + super(server != null ? server : org.bukkit.Bukkit.getServer(), new HashMap<>()); + } + + public PatchBukkitCommandMap() { + super(org.bukkit.Bukkit.getServer(), new HashMap<>()); + } + + private String cleanLabel(String label) { + if (label == null) return ""; + String clean = label.trim(); + while (clean.startsWith("/")) { + clean = clean.substring(1).trim(); + } + return clean.toLowerCase(); + } + + private void registerVariants(String key, Command command) { + if (key == null || key.isEmpty()) return; + key = key.toLowerCase().trim(); + if (!knownCommands.containsKey(key)) { + knownCommands.put(key, command); + } + String clean = cleanLabel(key); + if (!clean.isEmpty()) { + if (!knownCommands.containsKey(clean)) { + knownCommands.put(clean, command); + } + String single = "/" + clean; + if (!knownCommands.containsKey(single)) { + knownCommands.put(single, command); + } + String doubleSlash = "//" + clean; + if (!knownCommands.containsKey(doubleSlash)) { + knownCommands.put(doubleSlash, command); + } + } + } @Override public void registerAll(@NotNull String fallbackPrefix, @NotNull List commands) { @@ -27,26 +67,45 @@ public class PatchBukkitCommandMap implements CommandMap { @Override public boolean register(@NotNull String label, @NotNull String fallbackPrefix, @NotNull Command command) { + if (label == null || fallbackPrefix == null || command == null) { + return false; + } label = label.toLowerCase().trim(); fallbackPrefix = fallbackPrefix.toLowerCase().trim(); - - // 1. Register under the direct label if not taken + String clean = cleanLabel(label); + boolean registeredDirectly = false; - if (!knownCommands.containsKey(label)) { - knownCommands.put(label, command); + if (!knownCommands.containsKey(label) && !knownCommands.containsKey(clean)) { registeredDirectly = true; } - // 2. Always register under the fallback prefix (pluginname:label) - knownCommands.put(fallbackPrefix + ":" + label, command); + registerVariants(label, command); + registerVariants(fallbackPrefix + ":" + label, command); + if (!clean.isEmpty()) { + registerVariants(clean, command); + registerVariants(fallbackPrefix + ":" + clean, command); + } - // 3. Register aliases - for (String alias : command.getAliases()) { - alias = alias.toLowerCase().trim(); - if (!knownCommands.containsKey(alias)) { - knownCommands.put(alias, command); + if (command.getAliases() != null) { + for (String alias : command.getAliases()) { + if (alias == null) continue; + alias = alias.toLowerCase().trim(); + registerVariants(alias, command); + registerVariants(fallbackPrefix + ":" + alias, command); } - knownCommands.put(fallbackPrefix + ":" + alias, command); + } + + try { + List aliasesList = command.getAliases() != null ? command.getAliases() : Collections.emptyList(); + String desc = command.getDescription() != null ? command.getDescription() : ""; + RegisterCommandRequest request = RegisterCommandRequest.newBuilder() + .setCmdName(label) + .addAllAliases(aliasesList) + .setDescription(desc) + .setPluginName(fallbackPrefix) + .build(); + NativeBridgeFfi.registerCommand(request); + } catch (Throwable ignored) { } return registeredDirectly; @@ -54,32 +113,34 @@ public class PatchBukkitCommandMap implements CommandMap { @Override public boolean register(@NotNull String fallbackPrefix, @NotNull Command command) { + if (command == null) return false; return register(command.getName(), fallbackPrefix, command); } @Override public boolean dispatch(@NotNull CommandSender sender, @NotNull String cmdLine) throws CommandException { if (cmdLine == null) return false; - String cleanLine = cmdLine.trim(); - while (cleanLine.startsWith("/")) { - cleanLine = cleanLine.substring(1).trim(); - } - if (cleanLine.isEmpty()) return false; + String rawLine = cmdLine.trim(); + if (rawLine.isEmpty()) return false; - String[] split = cleanLine.split("\\s+"); + String[] split = rawLine.split("\\s+"); if (split.length == 0) return false; - String sentLabel = split[0].toLowerCase(); - Command command = knownCommands.get(sentLabel); + String rawLabel = split[0].toLowerCase(); + Command command = knownCommands.get(rawLabel); + + if (command == null) { + String clean = cleanLabel(rawLabel); + command = knownCommands.get(clean); + } if (command == null) { return false; // Command not found } try { - // Extract arguments (everything after the label) String[] args = Arrays.copyOfRange(split, 1, split.length); - return command.execute(sender, sentLabel, args); + return command.execute(sender, rawLabel, args); } catch (Exception ex) { throw new CommandException("Unhandled exception executing '" + cmdLine + "'", ex); } @@ -92,7 +153,13 @@ public class PatchBukkitCommandMap implements CommandMap { @Override public @Nullable Command getCommand(@NotNull String name) { - return knownCommands.get(name.toLowerCase()); + if (name == null) return null; + String lower = name.toLowerCase().trim(); + Command cmd = knownCommands.get(lower); + if (cmd == null) { + cmd = knownCommands.get(cleanLabel(lower)); + } + return cmd; } @Override @@ -108,17 +175,16 @@ public class PatchBukkitCommandMap implements CommandMap { @Override public @Nullable List tabComplete(@NotNull CommandSender sender, @NotNull String cmdLine, @Nullable Location location) { if (cmdLine == null) return new ArrayList<>(); - String cleanLine = cmdLine.trim(); - while (cleanLine.startsWith("/")) { - cleanLine = cleanLine.substring(1).trim(); - } - String[] split = cleanLine.split(" ", -1); + String rawLine = cmdLine.trim(); + String[] split = rawLine.split(" ", -1); if (split.length == 0) return new ArrayList<>(); String label = split[0].toLowerCase(); Command command = knownCommands.get(label); + if (command == null) { + command = knownCommands.get(cleanLabel(label)); + } - // If typing the first word, return matching command names if (split.length == 1) { List completions = new ArrayList<>(); for (String key : knownCommands.keySet()) { @@ -127,7 +193,6 @@ public class PatchBukkitCommandMap implements CommandMap { return completions; } - // Otherwise, delegate to the specific command's tab completer if (command != null) { String[] args = Arrays.copyOfRange(split, 1, split.length); return command.tabComplete(sender, label, args, location); diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitEntity.java b/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitEntity.java index b68cbc3..57d490b 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitEntity.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitEntity.java @@ -28,6 +28,7 @@ import org.bukkit.entity.Pose; import org.bukkit.entity.SpawnCategory; import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.EntityRemoveEvent; import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; import org.bukkit.inventory.ItemStack; import org.bukkit.metadata.MetadataValue; @@ -59,6 +60,16 @@ import patchbukkit.entity.TeleportEntityRequest; public class PatchBukkitEntity implements Entity { + @Override + public boolean isInWaterOrRainOrBubbleColumn() { + return false; + } + + @Override + public boolean isInWaterOrBubbleColumn() { + return false; + } + protected final UUID uuid; protected final String name; private PermissibleBase perm; @@ -233,13 +244,11 @@ public class PatchBukkitEntity implements Entity { throw new UnsupportedOperationException("Unimplemented method 'getPersistentDataContainer'"); } - @Override public @org.jspecify.annotations.Nullable T getData(Valued type) { // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'getData'"); } - @Override public @org.jspecify.annotations.Nullable T getDataOrDefault(Valued type, @org.jspecify.annotations.Nullable T fallback) { // TODO Auto-generated method stub @@ -329,8 +338,8 @@ public class PatchBukkitEntity implements Entity { @Override public boolean isOnGround() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'isOnGround'"); + var resp = NativeBridgeFfi.isOnGround(BridgeUtils.convertUuid(this.uuid)); + return resp != null && resp.getOnGround(); } @Override @@ -435,7 +444,6 @@ public class PatchBukkitEntity implements Entity { throw new UnsupportedOperationException("Unimplemented method 'setVisualFire'"); } - @Override public boolean isVisualFire() { // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'isVisualFire'"); @@ -584,22 +592,21 @@ public class PatchBukkitEntity implements Entity { throw new UnsupportedOperationException("Unimplemented method 'eject'"); } - @Override public @NotNull ItemStack getPickItemStack() { // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'getPickItemStack'"); } + private float fallDistance = 0.0f; + @Override public float getFallDistance() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getFallDistance'"); + return this.fallDistance; } @Override public void setFallDistance(float distance) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'setFallDistance'"); + this.fallDistance = distance; } @Override @@ -701,7 +708,6 @@ public class PatchBukkitEntity implements Entity { return this.visibleByDefault; } - @Override public @NotNull Set getTrackedBy() { // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'getTrackedBy'"); @@ -809,16 +815,16 @@ public class PatchBukkitEntity implements Entity { throw new UnsupportedOperationException("Unimplemented method 'getPose'"); } + private boolean sneaking = false; + @Override public boolean isSneaking() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'isSneaking'"); + return this.sneaking; } @Override public void setSneaking(boolean sneak) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'setSneaking'"); + this.sneaking = sneak; } @Override @@ -827,12 +833,20 @@ public class PatchBukkitEntity implements Entity { throw new UnsupportedOperationException("Unimplemented method 'setPose'"); } - @Override public boolean hasFixedPose() { // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'hasFixedPose'"); } + public EntityRemoveEvent.@Nullable Cause getRemoveEventCause() { + return null; + } + + @Override + public io.papermc.paper.entity.@Nullable RemovalReason getRemovalReason() { + return null; + } + @Override public @NotNull SpawnCategory getSpawnCategory() { // TODO Auto-generated method stub @@ -995,13 +1009,11 @@ public class PatchBukkitEntity implements Entity { throw new UnsupportedOperationException("Unimplemented method 'getScoreboardEntryName'"); } - @Override public void broadcastHurtAnimation(@NotNull Collection players) { // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'broadcastHurtAnimation'"); } - @Override public Source soundSource() { // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'soundSource'"); diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitHumanEntity.java b/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitHumanEntity.java index 063973b..6914e56 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitHumanEntity.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitHumanEntity.java @@ -50,6 +50,7 @@ import org.checkerframework.checker.index.qual.NonNegative; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Range; +import org.patchbukkit.PatchBukkitServer; import org.patchbukkit.bridge.BridgeUtils; import patchbukkit.bridge.NativeBridgeFfi; @@ -67,20 +68,67 @@ public class PatchBukkitHumanEntity implements HumanEntity { private boolean op; protected final PermissibleBase perm = new PermissibleBase(this); + protected final PlayerInventory inventory = new org.patchbukkit.inventory.PatchBukkitPlayerInventory(this); + protected final EntityEquipment equipment = new org.patchbukkit.inventory.PatchBukkitEntityEquipment(this); + private ItemStack cursorItem = ItemStack.empty(); + public PatchBukkitHumanEntity(UUID uuid, String name) { super(uuid, name); } + @Override + public boolean undiscoverRecipe(@NotNull NamespacedKey key) { + return false; + } + + @Override + public int undiscoverRecipes(@NotNull Collection keys) { + return 0; + } + + @Override + public boolean discoverRecipe(@NotNull NamespacedKey key) { + return false; + } + + @Override + public int discoverRecipes(@NotNull Collection keys) { + return 0; + } + + @Override public boolean isOp() { - return this.op; + if (this.op) { + return true; + } + if (PatchBukkitServer.getInstance().isOp(getUniqueId(), getName())) { + this.op = true; + if (this.perm != null) { + this.perm.recalculatePermissions(); + } + return true; + } + try { + var resp = NativeBridgeFfi.isOp(BridgeUtils.convertUuid(getUniqueId())); + if (resp != null && resp.getIsOp()) { + this.op = true; + if (this.perm != null) { + this.perm.recalculatePermissions(); + } + return true; + } + } catch (Throwable ignored) { + } + return false; } @Override public void setOp(boolean value) { this.op = value; + PatchBukkitServer.getInstance().setOperator(getUniqueId(), getName(), value); this.perm.recalculatePermissions(); } @@ -104,23 +152,7 @@ public class PatchBukkitHumanEntity return this.perm.hasPermission(perm); } - @Override - public double getEyeHeight() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getEyeHeight'"); - } - @Override - public double getEyeHeight(boolean ignorePose) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getEyeHeight'"); - } - - @Override - public @NotNull Location getEyeLocation() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getEyeLocation'"); - } @Override public @NotNull List getLineOfSight(@Nullable Set transparent, int maxDistance) { @@ -794,71 +826,7 @@ public class PatchBukkitHumanEntity throw new UnsupportedOperationException("Unimplemented method 'registerAttribute'"); } - @Override - public void damage(double amount) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'damage'"); - } - @Override - public void damage(double amount, @Nullable Entity source) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'damage'"); - } - - @Override - public void damage(double amount, @NotNull DamageSource damageSource) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'damage'"); - } - - @Override - public double getHealth() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getHealth'"); - } - - @Override - public void setHealth(double health) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'setHealth'"); - } - - @Override - public void heal(double amount, @NotNull RegainReason reason) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'heal'"); - } - - @Override - public double getAbsorptionAmount() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getAbsorptionAmount'"); - } - - @Override - public void setAbsorptionAmount(double amount) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'setAbsorptionAmount'"); - } - - @Override - public double getMaxHealth() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getMaxHealth'"); - } - - @Override - public void setMaxHealth(double health) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'setMaxHealth'"); - } - - @Override - public void resetMaxHealth() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'resetMaxHealth'"); - } @Override public @NotNull T launchProjectile(@NotNull Class projectile, @@ -881,14 +849,12 @@ public class PatchBukkitHumanEntity @Override public EntityEquipment getEquipment() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getEquipment'"); + return this.equipment; } @Override public PlayerInventory getInventory() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getInventory'"); + return this.inventory; } @Override @@ -899,10 +865,10 @@ public class PatchBukkitHumanEntity @Override public MainHand getMainHand() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getMainHand'"); + return MainHand.RIGHT; } + @Override public boolean setWindowProperty(Property prop, int value) { // TODO Auto-generated method stub @@ -1009,28 +975,26 @@ public class PatchBukkitHumanEntity @Override public ItemStack getItemInHand() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getItemInHand'"); + return getInventory().getItemInMainHand(); } @Override public void setItemInHand(@org.jspecify.annotations.Nullable ItemStack item) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'setItemInHand'"); + getInventory().setItemInMainHand(item); } @Override public ItemStack getItemOnCursor() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getItemOnCursor'"); + return this.cursorItem != null ? this.cursorItem : ItemStack.empty(); } @Override public void setItemOnCursor(@org.jspecify.annotations.Nullable ItemStack item) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'setItemOnCursor'"); + this.cursorItem = item != null ? item : ItemStack.empty(); } + + @Override public boolean hasCooldown(Material material) { // TODO Auto-generated method stub @@ -1061,25 +1025,21 @@ public class PatchBukkitHumanEntity throw new UnsupportedOperationException("Unimplemented method 'hasCooldown'"); } - @Override public int getCooldown(ItemStack item) { // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'getCooldown'"); } - @Override public void setCooldown(ItemStack item, int ticks) { // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'setCooldown'"); } - @Override public int getCooldown(Key key) { // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'getCooldown'"); } - @Override public void setCooldown(Key key, int ticks) { // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'setCooldown'"); @@ -1183,17 +1143,7 @@ public class PatchBukkitHumanEntity throw new UnsupportedOperationException("Unimplemented method 'getAttackCooldown'"); } - @Override - public int discoverRecipes(Collection recipes) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'discoverRecipes'"); - } - @Override - public int undiscoverRecipes(Collection recipes) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'undiscoverRecipes'"); - } @Override public boolean hasDiscoveredRecipe(NamespacedKey recipe) { diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitLivingEntity.java b/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitLivingEntity.java index b5c62d9..1bab366 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitLivingEntity.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitLivingEntity.java @@ -852,51 +852,55 @@ public class PatchBukkitLivingEntity throw new UnsupportedOperationException("Unimplemented method 'canUseEquipmentSlot'"); } - @Override public @NotNull CombatTracker getCombatTracker() { // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'getCombatTracker'"); } @Override + public void setShieldBlockingDelay(int delay) { + } + + @Override + public int getShieldBlockingDelay() { + return 0; + } + public void setWaypointStyle(@Nullable Key key) { // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'setWaypointStyle'"); } - @Override public void setWaypointColor(@Nullable Color color) { // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'setWaypointColor'"); } - @Override public @NotNull Key getWaypointStyle() { // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'getWaypointStyle'"); } - @Override public @Nullable Color getWaypointColor() { // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'getWaypointColor'"); } - @Override + public void kill(DamageSource damageSource) { // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'kill'"); } - @Override + public float getSoundVolume() { // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'getSoundVolume'"); } - @Override + public float getSoundPitch() { // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'getSoundPitch'"); } - @Override + public @Nullable Sound getHurtSound(@NotNull DamageSource damageSource) { // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'getHurtSound'"); diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitOfflinePlayer.java b/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitOfflinePlayer.java new file mode 100644 index 0000000..7e84785 --- /dev/null +++ b/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitOfflinePlayer.java @@ -0,0 +1,214 @@ +package org.patchbukkit.entity; + +import java.util.Map; +import java.util.UUID; +import org.bukkit.Location; +import org.bukkit.OfflinePlayer; +import org.bukkit.Statistic; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.patchbukkit.PatchBukkitServer; + +public class PatchBukkitOfflinePlayer implements OfflinePlayer { + private final UUID uuid; + private final String name; + + public PatchBukkitOfflinePlayer(UUID uuid, String name) { + this.uuid = uuid; + this.name = name; + } + + @Override + public boolean isOnline() { + return getPlayer() != null; + } + + @Override + public boolean isConnected() { + return isOnline(); + } + + @Override + public @Nullable String getName() { + Player player = getPlayer(); + return player != null ? player.getName() : this.name; + } + + @Override + public @NotNull UUID getUniqueId() { + if (this.uuid != null) return this.uuid; + Player player = getPlayer(); + if (player != null) return player.getUniqueId(); + return UUID.nameUUIDFromBytes(("OfflinePlayer:" + (this.name != null ? this.name : "")).getBytes()); + } + + @Override + public boolean isOp() { + Player player = getPlayer(); + if (player != null) return player.isOp(); + return PatchBukkitServer.getInstance().isOp(getUniqueId(), getName()); + } + + @Override + public void setOp(boolean value) { + PatchBukkitServer.getInstance().setOperator(getUniqueId(), getName(), value); + Player player = getPlayer(); + if (player != null) { + player.setOp(value); + } + } + + @Override + public boolean isBanned() { + return false; + } + + @Override + public > E ban(String reason, java.util.Date expires, String source) { + return null; + } + + @Override + public > E ban(String reason, java.time.Instant expires, String source) { + return null; + } + + @Override + public > E ban(String reason, java.time.Duration duration, String source) { + return null; + } + + @Override + public boolean isWhitelisted() { + return false; + } + + @Override + public void setWhitelisted(boolean value) {} + + @Override + public @Nullable Player getPlayer() { + if (this.uuid != null) { + Player p = PatchBukkitServer.getInstance().getPlayer(this.uuid); + if (p != null) return p; + } + if (this.name != null) { + return PatchBukkitServer.getInstance().getPlayer(this.name); + } + return null; + } + + @Override + public long getFirstPlayed() { return 0; } + + @Override + public long getLastPlayed() { return 0; } + + @Override + public boolean hasPlayedBefore() { return true; } + + @Override + public @Nullable Location getLocation() { + Player player = getPlayer(); + return player != null ? player.getLocation() : null; + } + + @Override + public @Nullable Location getBedSpawnLocation() { return null; } + + @Override + public void incrementStatistic(@NotNull Statistic statistic) {} + + @Override + public void decrementStatistic(@NotNull Statistic statistic) {} + + @Override + public int getStatistic(@NotNull Statistic statistic) { return 0; } + + @Override + public void incrementStatistic(@NotNull Statistic statistic, int amount) {} + + @Override + public void decrementStatistic(@NotNull Statistic statistic, int amount) {} + + @Override + public void setStatistic(@NotNull Statistic statistic, int newValue) {} + + @Override + public void incrementStatistic(@NotNull Statistic statistic, @NotNull org.bukkit.Material material) {} + + @Override + public void decrementStatistic(@NotNull Statistic statistic, @NotNull org.bukkit.Material material) {} + + @Override + public int getStatistic(@NotNull Statistic statistic, @NotNull org.bukkit.Material material) { return 0; } + + @Override + public void incrementStatistic(@NotNull Statistic statistic, @NotNull org.bukkit.Material material, int amount) {} + + @Override + public void decrementStatistic(@NotNull Statistic statistic, @NotNull org.bukkit.Material material, int amount) {} + + @Override + public void setStatistic(@NotNull Statistic statistic, @NotNull org.bukkit.Material material, int newValue) {} + + @Override + public void incrementStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType) {} + + @Override + public void decrementStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType) {} + + @Override + public int getStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType) { return 0; } + + @Override + public void incrementStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType, int amount) {} + + @Override + public void decrementStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType, int amount) {} + + @Override + public void setStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType, int newValue) {} + + @Override + public @Nullable Location getLastDeathLocation() { + Player player = getPlayer(); + return player != null ? player.getLastDeathLocation() : null; + } + + @Override + public @Nullable Location getRespawnLocation() { + Player player = getPlayer(); + return player != null ? player.getBedSpawnLocation() : null; + } + + public @Nullable Location getRespawnLocation(boolean anchor) { + Player player = getPlayer(); + return player != null ? player.getBedSpawnLocation() : null; + } + + @Override + public com.destroystokyo.paper.profile.@NotNull PlayerProfile getPlayerProfile() { + Player player = getPlayer(); + if (player != null) return player.getPlayerProfile(); + return org.bukkit.Bukkit.getServer().createProfile(getUniqueId(), getName()); + } + + @Override + public long getLastLogin() { return getLastPlayed(); } + + @Override + public long getLastSeen() { return getLastPlayed(); } + + @Override + public @NotNull org.bukkit.persistence.PersistentDataContainer getPersistentDataContainer() { + return new org.patchbukkit.persistence.PatchBukkitPersistentDataContainer(); + } + + @Override + public @NotNull Map serialize() { + return Map.of("uuid", getUniqueId().toString(), "name", getName() != null ? getName() : ""); + } +} diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitPlayer.java b/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitPlayer.java index 1465a69..c097ec1 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitPlayer.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitPlayer.java @@ -83,10 +83,32 @@ public class PatchBukkitPlayer private final Map>> invertedVisibilityEntities = new HashMap<>(); - public PatchBukkitPlayer(UUID uuid, String name) { + public PatchBukkitPlayer(UUID uuid, String name) { super(uuid, name); } + @Override + public void openSign(@NotNull org.bukkit.block.Sign sign) { + } + + @Override + public void spawnParticle(@NotNull Particle particle, @NotNull Location location, int count, double offsetX, double offsetY, double offsetZ, double extra, @org.jspecify.annotations.Nullable T data, boolean force) { + } + + @Override + public void spawnParticle(@NotNull Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, @org.jspecify.annotations.Nullable T data) { + } + + public void unsetFixedPose() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'unsetFixedPose'"); + } + + public void resetFlyingTicks() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'resetFlyingTicks'"); + } + @Override public void sendRawMessage(String message) { this.sendRawMessage(null, message); @@ -247,7 +269,6 @@ public class PatchBukkitPlayer throw new UnsupportedOperationException("Unimplemented method 'getLastSeen'"); } - @Override public @org.jspecify.annotations.Nullable Location getRespawnLocation(boolean loadLocationAndValidate) { // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'getRespawnLocation'"); @@ -620,16 +641,16 @@ public class PatchBukkitPlayer throw new UnsupportedOperationException("Unimplemented method 'performCommand'"); } + private boolean sprinting = false; + @Override public boolean isSprinting() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'isSprinting'"); + return this.sprinting; } @Override public void setSprinting(boolean sprinting) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'setSprinting'"); + this.sprinting = sprinting; } @Override @@ -680,6 +701,16 @@ public class PatchBukkitPlayer throw new UnsupportedOperationException("Unimplemented method 'playNote'"); } + @Override + public void playSound(Location location, Sound sound, float volume, float pitch) { + playSound(location, sound, SoundCategory.MASTER, volume, pitch); + } + + @Override + public void playSound(Location location, String sound, float volume, float pitch) { + playSound(location, sound, SoundCategory.MASTER, volume, pitch); + } + @Override public void playSound(Location location, Sound sound, SoundCategory category, float volume, float pitch) { var patchBukkitSound = (PatchBukkitSound) sound; @@ -1485,8 +1516,16 @@ public class PatchBukkitPlayer @Override public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, @org.jspecify.annotations.Nullable T data, boolean force) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'spawnParticle'"); + if (particle == null) return; + var request = patchbukkit.world.SpawnParticleRequest.newBuilder() + .setWorldUuid(BridgeUtils.convertUuid(this.getWorld().getUID())) + .setParticle(particle.name().toLowerCase()) + .setX(x).setY(y).setZ(z) + .setCount(count) + .setOffsetX(offsetX).setOffsetY(offsetY).setOffsetZ(offsetZ) + .setExtra(extra) + .build(); + NativeBridgeFfi.spawnParticle(request); } @Override @@ -1503,8 +1542,16 @@ public class PatchBukkitPlayer @Override public Locale locale() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'locale'"); + String locStr = getLocale(); + if (locStr != null && !locStr.isEmpty()) { + String[] parts = locStr.split("_"); + if (parts.length >= 2) { + return new Locale(parts[0], parts[1]); + } else if (parts.length == 1) { + return new Locale(parts[0]); + } + } + return Locale.US; } @Override @@ -1515,8 +1562,13 @@ public class PatchBukkitPlayer @Override public String getLocale() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getLocale'"); + try { + var resp = NativeBridgeFfi.getPlayerLocale(BridgeUtils.convertUuid(this.uuid)); + if (resp != null && resp.getLocale() != null && !resp.getLocale().isEmpty()) { + return resp.getLocale(); + } + } catch (Throwable ignored) {} + return "en_us"; } @Override @@ -1579,7 +1631,6 @@ public class PatchBukkitPlayer throw new UnsupportedOperationException("Unimplemented method 'openBook'"); } - @Override public void openVirtualSign(Position block, Side side) { // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'openVirtualSign'"); @@ -1765,7 +1816,6 @@ public class PatchBukkitPlayer throw new UnsupportedOperationException("Unimplemented method 'setDeathScreenScore'"); } - @Override public PlayerGameConnection getConnection() { // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'getConnection'"); diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventFactory.java b/java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventFactory.java index 7addd61..31f0b37 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventFactory.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventFactory.java @@ -9,9 +9,29 @@ import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import patchbukkit.common.UUID; +import patchbukkit.events.BlockBreakEvent; +import patchbukkit.events.BlockDamageEvent; +import patchbukkit.events.BlockPlaceEvent; import patchbukkit.events.Event; import patchbukkit.events.FireEventResponse; +import patchbukkit.events.PlayerChatEvent; +import patchbukkit.events.PlayerGameModeChangeEvent; +import patchbukkit.events.PlayerInteractEvent; import patchbukkit.events.PlayerJoinEvent; +import patchbukkit.events.PlayerInteractEntityEvent; +import patchbukkit.events.PlayerMoveEvent; +import patchbukkit.events.PlayerQuitEvent; +import patchbukkit.events.PlayerToggleFlightEvent; +import patchbukkit.events.PlayerToggleSneakEvent; +import patchbukkit.events.PlayerToggleSprintEvent; +import patchbukkit.events.PluginDisableEvent; +import patchbukkit.events.PluginEnableEvent; +import patchbukkit.events.BlockFadeEvent; +import patchbukkit.events.BlockFormEvent; +import patchbukkit.events.BlockGrowEvent; +import patchbukkit.events.BlockIgniteEvent; +import patchbukkit.events.ServerCommandEvent; +import patchbukkit.events.SignChangeEvent; import java.util.logging.Logger; @@ -31,9 +51,10 @@ public class PatchBukkitEventFactory { @Nullable public static org.bukkit.event.Event createEvent(@NotNull Event event) { - Event.DataCase dataCase = event.getDataCase(); + try { + Event.DataCase dataCase = event.getDataCase(); - return switch (dataCase) { + return switch (dataCase) { case PLAYER_JOIN -> { PlayerJoinEvent joinEvent = event.getPlayerJoin(); Player player = getPlayer(joinEvent.getPlayerUuid().getValue()); @@ -42,22 +63,221 @@ public class PatchBukkitEventFactory { Component joinMessage = GsonComponentSerializer.gson().deserialize(joinEvent.getJoinMessage()); yield new org.bukkit.event.player.PlayerJoinEvent(player, joinMessage); } + case PLAYER_QUIT -> { + PlayerQuitEvent quitEvent = event.getPlayerQuit(); + Player player = getPlayer(quitEvent.getPlayerUuid().getValue()); + if (player == null) yield null; + + Component quitMessage = quitEvent.getQuitMessage().isEmpty() ? Component.empty() : GsonComponentSerializer.gson().deserialize(quitEvent.getQuitMessage()); + yield new org.bukkit.event.player.PlayerQuitEvent(player, quitMessage); + } + case PLAYER_GAMEMODE_CHANGE -> { + PlayerGameModeChangeEvent gmEvent = event.getPlayerGamemodeChange(); + Player player = getPlayer(gmEvent.getPlayerUuid().getValue()); + if (player == null) yield null; + + org.bukkit.GameMode newGamemode; + try { + newGamemode = org.bukkit.GameMode.valueOf(gmEvent.getNewGamemode().toUpperCase()); + } catch (IllegalArgumentException e) { + newGamemode = org.bukkit.GameMode.SURVIVAL; + } + yield new org.bukkit.event.player.PlayerGameModeChangeEvent(player, newGamemode); + } + case PLAYER_INTERACT -> { + PlayerInteractEvent interactEvent = event.getPlayerInteract(); + Player player = getPlayer(interactEvent.getPlayerUuid().getValue()); + if (player == null) yield null; + + org.bukkit.event.block.Action action; + try { + action = org.bukkit.event.block.Action.valueOf(interactEvent.getAction()); + } catch (IllegalArgumentException e) { + action = org.bukkit.event.block.Action.RIGHT_CLICK_AIR; + } + + org.bukkit.inventory.EquipmentSlot hand; + try { + hand = org.bukkit.inventory.EquipmentSlot.valueOf(interactEvent.getHand()); + } catch (IllegalArgumentException e) { + hand = org.bukkit.inventory.EquipmentSlot.HAND; + } + + org.bukkit.block.Block clickedBlock = player.getWorld().getBlockAt( + interactEvent.getClickedX(), + interactEvent.getClickedY(), + interactEvent.getClickedZ() + ); + + yield new org.bukkit.event.player.PlayerInteractEvent( + player, + action, + player.getInventory().getItemInMainHand(), + clickedBlock, + org.bukkit.block.BlockFace.SELF, + hand + ); + } + case PLUGIN_ENABLE -> { + PluginEnableEvent enableEvent = event.getPluginEnable(); + org.bukkit.plugin.Plugin plugin = Bukkit.getPluginManager().getPlugin(enableEvent.getPluginName()); + if (plugin == null) yield null; + yield new org.bukkit.event.server.PluginEnableEvent(plugin); + } + case PLUGIN_DISABLE -> { + PluginDisableEvent disableEvent = event.getPluginDisable(); + org.bukkit.plugin.Plugin plugin = Bukkit.getPluginManager().getPlugin(disableEvent.getPluginName()); + if (plugin == null) yield null; + yield new org.bukkit.event.server.PluginDisableEvent(plugin); + } + case BLOCK_BREAK -> { + BlockBreakEvent breakEvent = event.getBlockBreak(); + Player player = breakEvent.hasPlayerUuid() ? getPlayer(breakEvent.getPlayerUuid().getValue()) : null; + if (player == null) yield null; + org.bukkit.World world = player.getWorld(); + org.bukkit.block.Block block = world.getBlockAt(breakEvent.getBlockX(), breakEvent.getBlockY(), breakEvent.getBlockZ()); + yield new org.bukkit.event.block.BlockBreakEvent(block, player); + } + case BLOCK_PLACE -> { + BlockPlaceEvent placeEvent = event.getBlockPlace(); + Player player = getPlayer(placeEvent.getPlayerUuid().getValue()); + if (player == null) yield null; + org.bukkit.block.Block block = player.getWorld().getBlockAt(placeEvent.getBlockX(), placeEvent.getBlockY(), placeEvent.getBlockZ()); + org.bukkit.block.BlockState blockState = block.getState(); + yield new org.bukkit.event.block.BlockPlaceEvent(block, blockState, block, player.getInventory().getItemInMainHand(), player, placeEvent.getCanBuild(), org.bukkit.inventory.EquipmentSlot.HAND); + } + case SIGN_CHANGE -> { + SignChangeEvent signEvent = event.getSignChange(); + Player player = getPlayer(signEvent.getPlayerUuid().getValue()); + if (player == null) yield null; + org.bukkit.block.Block block = player.getWorld().getBlockAt( + signEvent.getBlockX(), + signEvent.getBlockY(), + signEvent.getBlockZ() + ); + String[] lines = signEvent.getLinesList().toArray(new String[0]); + yield new org.bukkit.event.block.SignChangeEvent(block, player, lines); + } + case BLOCK_DAMAGE -> { + BlockDamageEvent damageEvent = event.getBlockDamage(); + Player player = getPlayer(damageEvent.getPlayerUuid().getValue()); + if (player == null) yield null; + org.bukkit.block.Block block = player.getWorld().getBlockAt( + damageEvent.getBlockX(), + damageEvent.getBlockY(), + damageEvent.getBlockZ() + ); + yield new org.bukkit.event.block.BlockDamageEvent( + player, block, player.getInventory().getItemInMainHand(), damageEvent.getInstaBreak() + ); + } + case SERVER_COMMAND -> { + ServerCommandEvent cmdEvent = event.getServerCommand(); + yield new org.bukkit.event.server.ServerCommandEvent( + Bukkit.getConsoleSender(), + cmdEvent.getCommand() + ); + } + case BLOCK_IGNITE -> { + BlockIgniteEvent igniteEvent = event.getBlockIgnite(); + Player player = igniteEvent.hasPlayerUuid() ? getPlayer(igniteEvent.getPlayerUuid().getValue()) : null; + org.bukkit.World world = Bukkit.getWorlds().isEmpty() ? null : Bukkit.getWorlds().get(0); + if (world == null) yield null; + org.bukkit.block.Block block = world.getBlockAt(igniteEvent.getBlockX(), igniteEvent.getBlockY(), igniteEvent.getBlockZ()); + org.bukkit.event.block.BlockIgniteEvent.IgniteCause cause = org.bukkit.event.block.BlockIgniteEvent.IgniteCause.FLINT_AND_STEEL; + yield new org.bukkit.event.block.BlockIgniteEvent(block, cause, player); + } + case BLOCK_GROW -> { + BlockGrowEvent growEvent = event.getBlockGrow(); + org.bukkit.World world = Bukkit.getWorlds().isEmpty() ? null : Bukkit.getWorlds().get(0); + if (world == null) yield null; + org.bukkit.block.Block block = world.getBlockAt(growEvent.getBlockX(), growEvent.getBlockY(), growEvent.getBlockZ()); + yield new org.bukkit.event.block.BlockGrowEvent(block, block.getState()); + } + case BLOCK_FORM -> { + BlockFormEvent formEvent = event.getBlockForm(); + org.bukkit.World world = Bukkit.getWorlds().isEmpty() ? null : Bukkit.getWorlds().get(0); + if (world == null) yield null; + org.bukkit.block.Block block = world.getBlockAt(formEvent.getBlockX(), formEvent.getBlockY(), formEvent.getBlockZ()); + yield new org.bukkit.event.block.BlockFormEvent(block, block.getState()); + } + case BLOCK_FADE -> { + BlockFadeEvent fadeEvent = event.getBlockFade(); + org.bukkit.World world = Bukkit.getWorlds().isEmpty() ? null : Bukkit.getWorlds().get(0); + if (world == null) yield null; + org.bukkit.block.Block block = world.getBlockAt(fadeEvent.getBlockX(), fadeEvent.getBlockY(), fadeEvent.getBlockZ()); + yield new org.bukkit.event.block.BlockFadeEvent(block, block.getState()); + } + case PLAYER_TOGGLE_SNEAK -> { + PlayerToggleSneakEvent sneakEvent = event.getPlayerToggleSneak(); + Player player = getPlayer(sneakEvent.getPlayerUuid().getValue()); + if (player == null) yield null; + yield new org.bukkit.event.player.PlayerToggleSneakEvent(player, sneakEvent.getIsSneaking()); + } + case PLAYER_TOGGLE_SPRINT -> { + PlayerToggleSprintEvent sprintEvent = event.getPlayerToggleSprint(); + Player player = getPlayer(sprintEvent.getPlayerUuid().getValue()); + if (player == null) yield null; + yield new org.bukkit.event.player.PlayerToggleSprintEvent(player, sprintEvent.getIsSprinting()); + } + case PLAYER_TOGGLE_FLIGHT -> { + PlayerToggleFlightEvent flightEvent = event.getPlayerToggleFlight(); + Player player = getPlayer(flightEvent.getPlayerUuid().getValue()); + if (player == null) yield null; + yield new org.bukkit.event.player.PlayerToggleFlightEvent(player, flightEvent.getIsFlying()); + } + case PLAYER_MOVE -> { + PlayerMoveEvent moveEvent = event.getPlayerMove(); + Player player = getPlayer(moveEvent.getPlayerUuid().getValue()); + if (player == null) yield null; + org.bukkit.Location from = org.patchbukkit.bridge.BridgeUtils.convertLocation(moveEvent.getFrom()); + org.bukkit.Location to = org.patchbukkit.bridge.BridgeUtils.convertLocation(moveEvent.getTo()); + yield new org.bukkit.event.player.PlayerMoveEvent(player, from, to); + } + case PLAYER_INTERACT_ENTITY -> { + PlayerInteractEntityEvent interactEvent = event.getPlayerInteractEntity(); + Player player = getPlayer(interactEvent.getPlayerUuid().getValue()); + if (player == null) yield null; + java.util.UUID targetUuid = org.patchbukkit.bridge.BridgeUtils.convertUuid(interactEvent.getTargetUuid()); + org.bukkit.entity.Entity target = Bukkit.getPlayer(targetUuid); + if (target == null) target = Bukkit.getEntity(targetUuid); + if (target == null) yield null; + if ("ATTACK".equals(interactEvent.getAction())) { + yield new org.bukkit.event.entity.EntityDamageByEntityEvent( + player, target, org.bukkit.event.entity.EntityDamageEvent.DamageCause.ENTITY_ATTACK, 1.0 + ); + } else { + yield new org.bukkit.event.player.PlayerInteractEntityEvent(player, target); + } + } + case PLAYER_CHAT -> { + PlayerChatEvent chatEvent = event.getPlayerChat(); + Player player = getPlayer(chatEvent.getPlayerUuid().getValue()); + if (player == null) yield null; + yield new org.bukkit.event.player.AsyncPlayerChatEvent(true, player, chatEvent.getMessage(), new java.util.HashSet<>(Bukkit.getOnlinePlayers())); + } case DATA_NOT_SET -> { LOGGER.warning("EventFactory: Received Event with no data"); yield null; } default -> null; }; + } catch (Throwable t) { + LOGGER.log(java.util.logging.Level.SEVERE, "Exception in createEvent for " + event.getDataCase() + ": " + t.getMessage(), t); + t.printStackTrace(); + return null; + } } @NotNull public static byte[] toFireEventResponse(@NotNull org.bukkit.event.Event event) { - boolean cancelled = event instanceof org.bukkit.event.Cancellable c && c.isCancelled(); + try { + boolean cancelled = event instanceof org.bukkit.event.Cancellable c && c.isCancelled(); - FireEventResponse.Builder builder = FireEventResponse.newBuilder() - .setCancelled(cancelled); + FireEventResponse.Builder builder = FireEventResponse.newBuilder() + .setCancelled(cancelled); - Event.Builder eventBuilder = Event.newBuilder(); + Event.Builder eventBuilder = Event.newBuilder(); if (event instanceof org.bukkit.event.player.PlayerJoinEvent joinEvent) { String joinMessage = GsonComponentSerializer.gson().serialize(joinEvent.joinMessage()); @@ -70,11 +290,131 @@ public class PatchBukkitEventFactory { .setJoinMessage(joinMessage) .build() ); + } else if (event instanceof org.bukkit.event.player.PlayerQuitEvent quitEvent) { + String quitMessage = quitEvent.quitMessage() != null ? GsonComponentSerializer.gson().serialize(quitEvent.quitMessage()) : ""; + + eventBuilder.setPlayerQuit( + PlayerQuitEvent.newBuilder() + .setPlayerUuid(UUID.newBuilder() + .setValue(quitEvent.getPlayer().getUniqueId().toString()) + .build()) + .setQuitMessage(quitMessage) + .build() + ); + } else if (event instanceof org.bukkit.event.player.PlayerGameModeChangeEvent gmEvent) { + eventBuilder.setPlayerGamemodeChange( + PlayerGameModeChangeEvent.newBuilder() + .setPlayerUuid(UUID.newBuilder() + .setValue(gmEvent.getPlayer().getUniqueId().toString()) + .build()) + .setNewGamemode(gmEvent.getNewGameMode().name()) + .build() + ); + } else if (event instanceof org.bukkit.event.player.PlayerInteractEvent interactEvent) { + var block = interactEvent.getClickedBlock(); + int x = block != null ? block.getX() : 0; + int y = block != null ? block.getY() : 0; + int z = block != null ? block.getZ() : 0; + + eventBuilder.setPlayerInteract( + PlayerInteractEvent.newBuilder() + .setPlayerUuid(UUID.newBuilder() + .setValue(interactEvent.getPlayer().getUniqueId().toString()) + .build()) + .setAction(interactEvent.getAction().name()) + .setClickedX(x) + .setClickedY(y) + .setClickedZ(z) + .setBlockFace(interactEvent.getBlockFace().name()) + .setHand(interactEvent.getHand() != null ? interactEvent.getHand().name() : "HAND") + .build() + ); + } else if (event instanceof org.bukkit.event.server.PluginEnableEvent enableEvent) { + eventBuilder.setPluginEnable( + PluginEnableEvent.newBuilder() + .setPluginName(enableEvent.getPlugin().getName()) + .build() + ); + } else if (event instanceof org.bukkit.event.server.PluginDisableEvent disableEvent) { + eventBuilder.setPluginDisable( + PluginDisableEvent.newBuilder() + .setPluginName(disableEvent.getPlugin().getName()) + .build() + ); + } else if (event instanceof org.bukkit.event.block.BlockBreakEvent breakEvent) { + var b = breakEvent.getBlock(); + var bBuilder = BlockBreakEvent.newBuilder() + .setBlockX(b.getX()) + .setBlockY(b.getY()) + .setBlockZ(b.getZ()) + .setBlockType(b.getType().name()) + .setExp(breakEvent.getExpToDrop()) + .setDropItems(breakEvent.isDropItems()); + if (breakEvent.getPlayer() != null) { + bBuilder.setPlayerUuid(UUID.newBuilder().setValue(breakEvent.getPlayer().getUniqueId().toString()).build()); + } + eventBuilder.setBlockBreak(bBuilder.build()); + } else if (event instanceof org.bukkit.event.block.BlockPlaceEvent placeEvent) { + var b = placeEvent.getBlockPlaced(); + var against = placeEvent.getBlockAgainst(); + eventBuilder.setBlockPlace( + BlockPlaceEvent.newBuilder() + .setPlayerUuid(UUID.newBuilder().setValue(placeEvent.getPlayer().getUniqueId().toString()).build()) + .setBlockX(b.getX()) + .setBlockY(b.getY()) + .setBlockZ(b.getZ()) + .setBlockPlacedType(b.getType().name()) + .setBlockAgainstType(against.getType().name()) + .setCanBuild(placeEvent.canBuild()) + .build() + ); + } else if (event instanceof org.bukkit.event.player.PlayerToggleSneakEvent sneakEvent) { + eventBuilder.setPlayerToggleSneak( + PlayerToggleSneakEvent.newBuilder() + .setPlayerUuid(UUID.newBuilder().setValue(sneakEvent.getPlayer().getUniqueId().toString()).build()) + .setIsSneaking(sneakEvent.isSneaking()) + .build() + ); + } else if (event instanceof org.bukkit.event.player.PlayerToggleSprintEvent sprintEvent) { + eventBuilder.setPlayerToggleSprint( + PlayerToggleSprintEvent.newBuilder() + .setPlayerUuid(UUID.newBuilder().setValue(sprintEvent.getPlayer().getUniqueId().toString()).build()) + .setIsSprinting(sprintEvent.isSprinting()) + .build() + ); + } else if (event instanceof org.bukkit.event.player.PlayerToggleFlightEvent flightEvent) { + eventBuilder.setPlayerToggleFlight( + PlayerToggleFlightEvent.newBuilder() + .setPlayerUuid(UUID.newBuilder().setValue(flightEvent.getPlayer().getUniqueId().toString()).build()) + .setIsFlying(flightEvent.isFlying()) + .build() + ); + } else if (event instanceof org.bukkit.event.player.PlayerMoveEvent moveEvent) { + eventBuilder.setPlayerMove( + PlayerMoveEvent.newBuilder() + .setPlayerUuid(UUID.newBuilder().setValue(moveEvent.getPlayer().getUniqueId().toString()).build()) + .setFrom(org.patchbukkit.bridge.BridgeUtils.convertLocation(moveEvent.getFrom())) + .setTo(org.patchbukkit.bridge.BridgeUtils.convertLocation(moveEvent.getTo())) + .build() + ); + } else if (event instanceof org.bukkit.event.player.AsyncPlayerChatEvent chatEvent) { + eventBuilder.setPlayerChat( + PlayerChatEvent.newBuilder() + .setPlayerUuid(UUID.newBuilder().setValue(chatEvent.getPlayer().getUniqueId().toString()).build()) + .setMessage(chatEvent.getMessage()) + .setFormat(chatEvent.getFormat()) + .build() + ); } builder.setData(eventBuilder.build()); return builder.build().toByteArray(); + } catch (Throwable t) { + LOGGER.log(java.util.logging.Level.SEVERE, "Exception serializing event response for " + event.getEventName() + ": " + t.getMessage(), t); + t.printStackTrace(); + return FireEventResponse.newBuilder().setCancelled(false).build().toByteArray(); + } } public static boolean isCancellable(@NotNull org.bukkit.event.Event event) { diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventManager.java b/java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventManager.java index ba94bfa..d4c6a55 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventManager.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventManager.java @@ -20,8 +20,21 @@ import co.aikar.timings.TimedEventExecutor; import org.jetbrains.annotations.NotNull; import patchbukkit.bridge.NativeBridgeFfi; +import patchbukkit.events.BlockBreakEvent; +import patchbukkit.events.BlockPlaceEvent; import patchbukkit.events.CallEventRequest; +import patchbukkit.events.PlayerChatEvent; +import patchbukkit.events.PlayerGameModeChangeEvent; +import patchbukkit.events.PlayerInteractEvent; import patchbukkit.events.PlayerJoinEvent; +import patchbukkit.events.PlayerInteractEntityEvent; +import patchbukkit.events.PlayerMoveEvent; +import patchbukkit.events.PlayerQuitEvent; +import patchbukkit.events.PlayerToggleFlightEvent; +import patchbukkit.events.PlayerToggleSneakEvent; +import patchbukkit.events.PlayerToggleSprintEvent; +import patchbukkit.events.PluginDisableEvent; +import patchbukkit.events.PluginEnableEvent; import patchbukkit.events.RegisterEventRequest; import java.lang.reflect.Method; @@ -59,6 +72,262 @@ public class PatchBukkitEventManager { ).build() ); break; + case "org.bukkit.event.player.PlayerQuitEvent": + var castedQuit = (org.bukkit.event.player.PlayerQuitEvent) event; + String qMsg = castedQuit.quitMessage() != null ? castedQuit.quitMessage().toString() : ""; + request.setEvent( + patchbukkit.events.Event.newBuilder().setPlayerQuit( + PlayerQuitEvent.newBuilder() + .setQuitMessage(qMsg) + .setPlayerUuid(BridgeUtils.convertUuid(castedQuit.getPlayer().getUniqueId())).build() + ).build() + ); + break; + case "org.bukkit.event.player.PlayerGameModeChangeEvent": + var castedGM = (org.bukkit.event.player.PlayerGameModeChangeEvent) event; + request.setEvent( + patchbukkit.events.Event.newBuilder().setPlayerGamemodeChange( + PlayerGameModeChangeEvent.newBuilder() + .setNewGamemode(castedGM.getNewGameMode().name()) + .setPlayerUuid(BridgeUtils.convertUuid(castedGM.getPlayer().getUniqueId())).build() + ).build() + ); + break; + case "org.bukkit.event.player.PlayerInteractEvent": + var castedInteract = (org.bukkit.event.player.PlayerInteractEvent) event; + var block = castedInteract.getClickedBlock(); + int x = block != null ? block.getX() : 0; + int y = block != null ? block.getY() : 0; + int z = block != null ? block.getZ() : 0; + request.setEvent( + patchbukkit.events.Event.newBuilder().setPlayerInteract( + PlayerInteractEvent.newBuilder() + .setAction(castedInteract.getAction().name()) + .setClickedX(x) + .setClickedY(y) + .setClickedZ(z) + .setBlockFace(castedInteract.getBlockFace().name()) + .setHand(castedInteract.getHand() != null ? castedInteract.getHand().name() : "HAND") + .setPlayerUuid(BridgeUtils.convertUuid(castedInteract.getPlayer().getUniqueId())).build() + ).build() + ); + break; + case "org.bukkit.event.server.PluginEnableEvent": + var castedEnable = (org.bukkit.event.server.PluginEnableEvent) event; + request.setEvent( + patchbukkit.events.Event.newBuilder().setPluginEnable( + PluginEnableEvent.newBuilder() + .setPluginName(castedEnable.getPlugin().getName()).build() + ).build() + ); + break; + case "org.bukkit.event.block.SignChangeEvent": + var castedSign = (org.bukkit.event.block.SignChangeEvent) event; + var signBlock = castedSign.getBlock(); + var signBuilder = patchbukkit.events.SignChangeEvent.newBuilder() + .setBlockX(signBlock.getX()) + .setBlockY(signBlock.getY()) + .setBlockZ(signBlock.getZ()); + if (castedSign.getPlayer() != null) { + signBuilder.setPlayerUuid(BridgeUtils.convertUuid(castedSign.getPlayer().getUniqueId())); + } + for (String line : castedSign.getLines()) { + signBuilder.addLines(line != null ? line : ""); + } + request.setEvent(patchbukkit.events.Event.newBuilder().setSignChange(signBuilder.build()).build()); + break; + case "org.bukkit.event.block.BlockDamageEvent": + var castedDamage = (org.bukkit.event.block.BlockDamageEvent) event; + var dmgBlock = castedDamage.getBlock(); + var dmgBuilder = patchbukkit.events.BlockDamageEvent.newBuilder() + .setBlockX(dmgBlock.getX()) + .setBlockY(dmgBlock.getY()) + .setBlockZ(dmgBlock.getZ()) + .setInstaBreak(castedDamage.getInstaBreak()); + if (castedDamage.getPlayer() != null) { + dmgBuilder.setPlayerUuid(BridgeUtils.convertUuid(castedDamage.getPlayer().getUniqueId())); + } + request.setEvent(patchbukkit.events.Event.newBuilder().setBlockDamage(dmgBuilder.build()).build()); + break; + case "org.bukkit.event.block.BlockIgniteEvent": + var castedIgnite = (org.bukkit.event.block.BlockIgniteEvent) event; + var igniteBlock = castedIgnite.getBlock(); + var igniteBuilder = patchbukkit.events.BlockIgniteEvent.newBuilder() + .setBlockX(igniteBlock.getX()) + .setBlockY(igniteBlock.getY()) + .setBlockZ(igniteBlock.getZ()) + .setIgniteCause(castedIgnite.getCause() != null ? castedIgnite.getCause().name() : "FLINT_AND_STEEL"); + if (castedIgnite.getPlayer() != null) { + igniteBuilder.setPlayerUuid(BridgeUtils.convertUuid(castedIgnite.getPlayer().getUniqueId())); + } + request.setEvent(patchbukkit.events.Event.newBuilder().setBlockIgnite(igniteBuilder.build()).build()); + break; + case "org.bukkit.event.block.BlockGrowEvent": + var castedGrow = (org.bukkit.event.block.BlockGrowEvent) event; + var growBlock = castedGrow.getBlock(); + request.setEvent(patchbukkit.events.Event.newBuilder().setBlockGrow( + patchbukkit.events.BlockGrowEvent.newBuilder() + .setBlockX(growBlock.getX()) + .setBlockY(growBlock.getY()) + .setBlockZ(growBlock.getZ()) + .setNewBlockType(castedGrow.getNewState().getType().name()).build() + ).build()); + break; + case "org.bukkit.event.block.BlockFormEvent": + var castedForm = (org.bukkit.event.block.BlockFormEvent) event; + var formBlock = castedForm.getBlock(); + request.setEvent(patchbukkit.events.Event.newBuilder().setBlockForm( + patchbukkit.events.BlockFormEvent.newBuilder() + .setBlockX(formBlock.getX()) + .setBlockY(formBlock.getY()) + .setBlockZ(formBlock.getZ()) + .setNewBlockType(castedForm.getNewState().getType().name()).build() + ).build()); + break; + case "org.bukkit.event.block.BlockFadeEvent": + var castedFade = (org.bukkit.event.block.BlockFadeEvent) event; + var fadeBlock = castedFade.getBlock(); + request.setEvent(patchbukkit.events.Event.newBuilder().setBlockFade( + patchbukkit.events.BlockFadeEvent.newBuilder() + .setBlockX(fadeBlock.getX()) + .setBlockY(fadeBlock.getY()) + .setBlockZ(fadeBlock.getZ()) + .setNewBlockType(castedFade.getNewState().getType().name()).build() + ).build()); + break; + case "org.bukkit.event.server.ServerCommandEvent": + var castedCmd = (org.bukkit.event.server.ServerCommandEvent) event; + request.setEvent( + patchbukkit.events.Event.newBuilder().setServerCommand( + patchbukkit.events.ServerCommandEvent.newBuilder() + .setSenderName(castedCmd.getSender().getName()) + .setCommand(castedCmd.getCommand()).build() + ).build() + ); + break; + case "org.bukkit.event.server.PluginDisableEvent": + var castedDisable = (org.bukkit.event.server.PluginDisableEvent) event; + request.setEvent( + patchbukkit.events.Event.newBuilder().setPluginDisable( + PluginDisableEvent.newBuilder() + .setPluginName(castedDisable.getPlugin().getName()).build() + ).build() + ); + break; + case "org.bukkit.event.block.BlockBreakEvent": + var castedBreak = (org.bukkit.event.block.BlockBreakEvent) event; + var breakBlock = castedBreak.getBlock(); + var bBuilder = BlockBreakEvent.newBuilder() + .setBlockX(breakBlock.getX()) + .setBlockY(breakBlock.getY()) + .setBlockZ(breakBlock.getZ()) + .setBlockType(breakBlock.getType().name()) + .setExp(castedBreak.getExpToDrop()) + .setDropItems(castedBreak.isDropItems()); + if (castedBreak.getPlayer() != null) { + bBuilder.setPlayerUuid(BridgeUtils.convertUuid(castedBreak.getPlayer().getUniqueId())); + } + request.setEvent(patchbukkit.events.Event.newBuilder().setBlockBreak(bBuilder.build()).build()); + break; + case "org.bukkit.event.block.BlockPlaceEvent": + var castedPlace = (org.bukkit.event.block.BlockPlaceEvent) event; + var placedBlock = castedPlace.getBlockPlaced(); + var againstBlock = castedPlace.getBlockAgainst(); + request.setEvent( + patchbukkit.events.Event.newBuilder().setBlockPlace( + BlockPlaceEvent.newBuilder() + .setBlockX(placedBlock.getX()) + .setBlockY(placedBlock.getY()) + .setBlockZ(placedBlock.getZ()) + .setBlockPlacedType(placedBlock.getType().name()) + .setBlockAgainstType(againstBlock.getType().name()) + .setCanBuild(castedPlace.canBuild()) + .setPlayerUuid(BridgeUtils.convertUuid(castedPlace.getPlayer().getUniqueId())).build() + ).build() + ); + break; + case "org.bukkit.event.player.PlayerToggleSneakEvent": + var castedSneak = (org.bukkit.event.player.PlayerToggleSneakEvent) event; + request.setEvent( + patchbukkit.events.Event.newBuilder().setPlayerToggleSneak( + PlayerToggleSneakEvent.newBuilder() + .setIsSneaking(castedSneak.isSneaking()) + .setPlayerUuid(BridgeUtils.convertUuid(castedSneak.getPlayer().getUniqueId())).build() + ).build() + ); + break; + case "org.bukkit.event.player.PlayerToggleSprintEvent": + var castedSprint = (org.bukkit.event.player.PlayerToggleSprintEvent) event; + request.setEvent( + patchbukkit.events.Event.newBuilder().setPlayerToggleSprint( + PlayerToggleSprintEvent.newBuilder() + .setIsSprinting(castedSprint.isSprinting()) + .setPlayerUuid(BridgeUtils.convertUuid(castedSprint.getPlayer().getUniqueId())).build() + ).build() + ); + break; + case "org.bukkit.event.player.PlayerToggleFlightEvent": + var castedFlight = (org.bukkit.event.player.PlayerToggleFlightEvent) event; + request.setEvent( + patchbukkit.events.Event.newBuilder().setPlayerToggleFlight( + PlayerToggleFlightEvent.newBuilder() + .setIsFlying(castedFlight.isFlying()) + .setPlayerUuid(BridgeUtils.convertUuid(castedFlight.getPlayer().getUniqueId())).build() + ).build() + ); + break; + case "org.bukkit.event.player.PlayerMoveEvent": + var castedMove = (org.bukkit.event.player.PlayerMoveEvent) event; + request.setEvent( + patchbukkit.events.Event.newBuilder().setPlayerMove( + PlayerMoveEvent.newBuilder() + .setPlayerUuid(BridgeUtils.convertUuid(castedMove.getPlayer().getUniqueId())) + .setFrom(BridgeUtils.convertLocation(castedMove.getFrom())) + .setTo(BridgeUtils.convertLocation(castedMove.getTo())).build() + ).build() + ); + break; + case "org.bukkit.event.player.PlayerInteractEntityEvent": + case "org.bukkit.event.player.PlayerInteractAtEntityEvent": + var castedInteractEntity = (org.bukkit.event.player.PlayerInteractEntityEvent) event; + request.setEvent( + patchbukkit.events.Event.newBuilder().setPlayerInteractEntity( + PlayerInteractEntityEvent.newBuilder() + .setPlayerUuid(BridgeUtils.convertUuid(castedInteractEntity.getPlayer().getUniqueId())) + .setTargetUuid(BridgeUtils.convertUuid(castedInteractEntity.getRightClicked().getUniqueId())) + .setAction("INTERACT") + .setIsSneaking(castedInteractEntity.getPlayer().isSneaking()) + .build() + ).build() + ); + break; + case "org.bukkit.event.entity.EntityDamageByEntityEvent": + var castedDamageByEntity = (org.bukkit.event.entity.EntityDamageByEntityEvent) event; + if (castedDamageByEntity.getDamager() instanceof org.bukkit.entity.Player damagerPlayer) { + request.setEvent( + patchbukkit.events.Event.newBuilder().setPlayerInteractEntity( + PlayerInteractEntityEvent.newBuilder() + .setPlayerUuid(BridgeUtils.convertUuid(damagerPlayer.getUniqueId())) + .setTargetUuid(BridgeUtils.convertUuid(castedDamageByEntity.getEntity().getUniqueId())) + .setAction("ATTACK") + .setIsSneaking(damagerPlayer.isSneaking()) + .build() + ).build() + ); + } + break; + case "org.bukkit.event.player.AsyncPlayerChatEvent": + case "org.bukkit.event.player.PlayerChatEvent": + var castedChat = (org.bukkit.event.player.AsyncPlayerChatEvent) event; + request.setEvent( + patchbukkit.events.Event.newBuilder().setPlayerChat( + PlayerChatEvent.newBuilder() + .setMessage(castedChat.getMessage()) + .setFormat(castedChat.getFormat()) + .setPlayerUuid(BridgeUtils.convertUuid(castedChat.getPlayer().getUniqueId())).build() + ).build() + ); + break; } var response = NativeBridgeFfi.callEvent(request.build()); @@ -107,6 +376,7 @@ public class PatchBukkitEventManager { + " to " + registration.getPlugin().getPluginMeta().getDisplayName(), ex ); + ex.printStackTrace(); } } } @@ -135,6 +405,7 @@ public class PatchBukkitEventManager { + " to " + listener.getPlugin().getPluginMeta().getDisplayName(), ex ); + ex.printStackTrace(); } } } diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/help/PatchBukkitHelpMap.java b/java/patchbukkit/src/main/java/org/patchbukkit/help/PatchBukkitHelpMap.java new file mode 100644 index 0000000..45e3a9f --- /dev/null +++ b/java/patchbukkit/src/main/java/org/patchbukkit/help/PatchBukkitHelpMap.java @@ -0,0 +1,54 @@ +package org.patchbukkit.help; + +import org.bukkit.help.HelpMap; +import org.bukkit.help.HelpTopic; +import org.bukkit.help.HelpTopicFactory; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +public class PatchBukkitHelpMap implements HelpMap { + + private final Map helpTopics = new ConcurrentHashMap<>(); + private final Map, HelpTopicFactory> topicFactories = new ConcurrentHashMap<>(); + private final List ignoredPlugins = new ArrayList<>(); + + @Override + public @Nullable HelpTopic getHelpTopic(@NonNull String topicName) { + if (topicName == null) return null; + return helpTopics.get(topicName); + } + + @Override + public @NonNull Collection getHelpTopics() { + return Collections.unmodifiableCollection(helpTopics.values()); + } + + @Override + public void addTopic(@NonNull HelpTopic topic) { + if (topic != null && topic.getName() != null) { + helpTopics.put(topic.getName(), topic); + } + } + + @Override + public void clear() { + helpTopics.clear(); + topicFactories.clear(); + } + + @Override + + public void registerHelpTopicFactory(@NonNull Class commandClass, @NonNull HelpTopicFactory factory) { + if (commandClass != null && factory != null) { + topicFactories.put(commandClass, factory); + } + } + + @Override + public @NonNull List getIgnoredPlugins() { + return Collections.unmodifiableList(ignoredPlugins); + } +} diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/inventory/PatchBukkitEntityEquipment.java b/java/patchbukkit/src/main/java/org/patchbukkit/inventory/PatchBukkitEntityEquipment.java new file mode 100644 index 0000000..f7690a9 --- /dev/null +++ b/java/patchbukkit/src/main/java/org/patchbukkit/inventory/PatchBukkitEntityEquipment.java @@ -0,0 +1,194 @@ +package org.patchbukkit.inventory; + +import org.bukkit.entity.Entity; +import org.bukkit.inventory.EntityEquipment; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.patchbukkit.entity.PatchBukkitHumanEntity; + +public class PatchBukkitEntityEquipment implements EntityEquipment { + private final PatchBukkitHumanEntity holder; + + public PatchBukkitEntityEquipment(PatchBukkitHumanEntity holder) { + this.holder = holder; + } + + @Override + public ItemStack getItemInMainHand() { + return holder.getInventory().getItemInMainHand(); + } + + @Override + public void setItemInMainHand(ItemStack item) { + holder.getInventory().setItemInMainHand(item); + } + + @Override + public void setItemInMainHand(ItemStack item, boolean silent) { + setItemInMainHand(item); + } + + @Override + public ItemStack getItemInOffHand() { + return holder.getInventory().getItemInOffHand(); + } + + @Override + public void setItemInOffHand(ItemStack item) { + holder.getInventory().setItemInOffHand(item); + } + + @Override + public void setItemInOffHand(ItemStack item, boolean silent) { + setItemInOffHand(item); + } + + @Override + public ItemStack getItemInHand() { + return getItemInMainHand(); + } + + @Override + public void setItemInHand(ItemStack stack) { + setItemInMainHand(stack); + } + + @Override + public ItemStack getHelmet() { + return holder.getInventory().getHelmet(); + } + + @Override + public void setHelmet(ItemStack helmet) { + holder.getInventory().setHelmet(helmet); + } + + @Override + public void setHelmet(ItemStack helmet, boolean silent) { + setHelmet(helmet); + } + + @Override + public ItemStack getChestplate() { + return holder.getInventory().getChestplate(); + } + + @Override + public void setChestplate(ItemStack chestplate) { + holder.getInventory().setChestplate(chestplate); + } + + @Override + public void setChestplate(ItemStack chestplate, boolean silent) { + setChestplate(chestplate); + } + + @Override + public ItemStack getLeggings() { + return holder.getInventory().getLeggings(); + } + + @Override + public void setLeggings(ItemStack leggings) { + holder.getInventory().setLeggings(leggings); + } + + @Override + public void setLeggings(ItemStack leggings, boolean silent) { + setLeggings(leggings); + } + + @Override + public ItemStack getBoots() { + return holder.getInventory().getBoots(); + } + + @Override + public void setBoots(ItemStack boots) { + holder.getInventory().setBoots(boots); + } + + @Override + public void setBoots(ItemStack boots, boolean silent) { + setBoots(boots); + } + + @Override + public ItemStack[] getArmorContents() { + return holder.getInventory().getArmorContents(); + } + + @Override + public void setArmorContents(ItemStack[] items) { + holder.getInventory().setArmorContents(items); + } + + @Override + public void clear() { + holder.getInventory().clear(); + } + + @Override + public Entity getHolder() { + return holder; + } + + @Override + public void setItem(EquipmentSlot slot, ItemStack item) { + holder.getInventory().setItem(slot, item); + } + + @Override + public void setItem(EquipmentSlot slot, ItemStack item, boolean silent) { + setItem(slot, item); + } + + @Override + public ItemStack getItem(EquipmentSlot slot) { + return holder.getInventory().getItem(slot); + } + + @Override + public void setDropChance(EquipmentSlot slot, float chance) { + } + + @Override + public float getDropChance(EquipmentSlot slot) { + return 1.0f; + } + + @Override + public float getItemInMainHandDropChance() { return 1.0f; } + @Override + public void setItemInMainHandDropChance(float chance) {} + + @Override + public float getItemInOffHandDropChance() { return 1.0f; } + @Override + public void setItemInOffHandDropChance(float chance) {} + + @Override + public float getItemInHandDropChance() { return 1.0f; } + @Override + public void setItemInHandDropChance(float chance) {} + + @Override + public float getHelmetDropChance() { return 1.0f; } + @Override + public void setHelmetDropChance(float chance) {} + + @Override + public float getChestplateDropChance() { return 1.0f; } + @Override + public void setChestplateDropChance(float chance) {} + + @Override + public float getLeggingsDropChance() { return 1.0f; } + @Override + public void setLeggingsDropChance(float chance) {} + + @Override + public float getBootsDropChance() { return 1.0f; } + @Override + public void setBootsDropChance(float chance) {} +} diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/inventory/PatchBukkitItemFactory.java b/java/patchbukkit/src/main/java/org/patchbukkit/inventory/PatchBukkitItemFactory.java new file mode 100644 index 0000000..9ef01ce --- /dev/null +++ b/java/patchbukkit/src/main/java/org/patchbukkit/inventory/PatchBukkitItemFactory.java @@ -0,0 +1,100 @@ +package org.patchbukkit.inventory; + +import java.lang.reflect.Proxy; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import org.bukkit.Material; +import org.bukkit.inventory.ItemFactory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +public class PatchBukkitItemFactory { + public static final ItemFactory INSTANCE = createFactory(); + + private static ItemFactory createFactory() { + return (ItemFactory) Proxy.newProxyInstance( + ItemFactory.class.getClassLoader(), + new Class[] { ItemFactory.class }, + (proxy, method, args) -> { + String name = method.getName(); + if ("getItemMeta".equals(name) || "createItemMeta".equals(name) || "asMetaFor".equals(name)) { + if (args != null && args.length > 0 && args[0] instanceof Material mat && mat == Material.AIR) { + return null; + } + return createMeta(); + } + if ("isApplicable".equals(name)) { + return true; + } + if ("equals".equals(name)) { + if (args != null && args.length == 2) { + return Objects.equals(args[0], args[1]); + } + return false; + } + if ("ensureServerConform".equals(name)) { + if (args != null && args.length > 0 && args[0] instanceof ItemStack stack) { + if (stack instanceof PatchBukkitItemStack) { + return stack; + } + try { + return new PatchBukkitItemStack(stack.getType(), stack.getAmount()); + } catch (Throwable ignored) { + return new PatchBukkitItemStack(Material.AIR, 0); + } + } + return new PatchBukkitItemStack(Material.AIR, 0); + } + if ("isItemEmpty".equals(name)) { + if (args != null && args.length > 0 && args[0] instanceof ItemStack stack) { + return PatchBukkitPlayerInventory.isItemEmpty(stack); + } + return true; + } + Class returnType = method.getReturnType(); + if (returnType == boolean.class) return false; + if (returnType == int.class) return 0; + if (returnType == long.class) return 0L; + if (returnType == double.class || returnType == float.class) return 0.0; + return null; + } + ); + } + + public static ItemMeta createMeta() { + Map state = new HashMap<>(); + return (ItemMeta) Proxy.newProxyInstance( + ItemMeta.class.getClassLoader(), + new Class[] { ItemMeta.class }, + (proxy, method, args) -> { + String name = method.getName(); + if ("hasDisplayName".equals(name)) return state.containsKey("displayName"); + if ("getDisplayName".equals(name)) return state.get("displayName"); + if ("setDisplayName".equals(name)) { + if (args != null && args.length > 0 && args[0] != null) state.put("displayName", args[0]); + else state.remove("displayName"); + return null; + } + if ("hasLore".equals(name)) return state.containsKey("lore"); + if ("getLore".equals(name)) return state.get("lore"); + if ("setLore".equals(name)) { + if (args != null && args.length > 0 && args[0] != null) state.put("lore", args[0]); + else state.remove("lore"); + return null; + } + if ("clone".equals(name)) return createMeta(); + if ("equals".equals(name)) { + if (args != null && args.length > 0) return proxy == args[0]; + return false; + } + Class returnType = method.getReturnType(); + if (returnType == boolean.class) return false; + if (returnType == int.class) return 0; + if (returnType == long.class) return 0L; + if (returnType == double.class || returnType == float.class) return 0.0; + return null; + } + ); + } +} diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/inventory/PatchBukkitItemStack.java b/java/patchbukkit/src/main/java/org/patchbukkit/inventory/PatchBukkitItemStack.java new file mode 100644 index 0000000..20529dd --- /dev/null +++ b/java/patchbukkit/src/main/java/org/patchbukkit/inventory/PatchBukkitItemStack.java @@ -0,0 +1,92 @@ +package org.patchbukkit.inventory; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + +import java.util.Objects; + +public class PatchBukkitItemStack extends ItemStack { + + private Material type; + private int amount; + private ItemMeta meta; + + public PatchBukkitItemStack(Material type) { + this(type, 1); + } + + public PatchBukkitItemStack(Material type, int amount) { + this.type = type != null ? type : Material.AIR; + this.amount = amount; + } + + @Override + public @NonNull Material getType() { + return type; + } + + @Override + public void setType(@Nullable Material type) { + this.type = type != null ? type : Material.AIR; + } + + @Override + public int getAmount() { + return amount; + } + + @Override + public void setAmount(int amount) { + this.amount = amount; + } + + @Override + public @Nullable ItemMeta getItemMeta() { + return meta; + } + + @Override + public boolean setItemMeta(@Nullable ItemMeta itemMeta) { + this.meta = itemMeta; + return true; + } + + @Override + public boolean hasItemMeta() { + return meta != null; + } + + @Override + public boolean isEmpty() { + return type == Material.AIR || amount <= 0; + } + + @Override + public @NonNull ItemStack clone() { + PatchBukkitItemStack cloned = new PatchBukkitItemStack(type, amount); + if (meta != null) { + cloned.meta = meta.clone(); + } + return cloned; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof ItemStack other)) return false; + return this.type == other.getType() && this.amount == other.getAmount() && Objects.equals(this.meta, other.getItemMeta()); + } + + @Override + public int hashCode() { + return Objects.hash(type, amount, meta); + } + + @Override + public String toString() { + return "ItemStack{" + type + " x " + amount + "}"; + } +} diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/inventory/PatchBukkitPlayerInventory.java b/java/patchbukkit/src/main/java/org/patchbukkit/inventory/PatchBukkitPlayerInventory.java new file mode 100644 index 0000000..85e7723 --- /dev/null +++ b/java/patchbukkit/src/main/java/org/patchbukkit/inventory/PatchBukkitPlayerInventory.java @@ -0,0 +1,539 @@ +package org.patchbukkit.inventory; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import org.patchbukkit.bridge.BridgeUtils; +import org.patchbukkit.entity.PatchBukkitHumanEntity; +import patchbukkit.bridge.NativeBridgeFfi; +import patchbukkit.itemstack.GetPlayerInventoryResponse; +import patchbukkit.itemstack.SetPlayerEquipmentRequest; +import patchbukkit.itemstack.SetPlayerInventorySlotRequest; +import patchbukkit.itemstack.SetPlayerSelectedSlotRequest; + +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +public class PatchBukkitPlayerInventory implements PlayerInventory { + private final PatchBukkitHumanEntity holder; + + public PatchBukkitPlayerInventory(PatchBukkitHumanEntity holder) { + this.holder = holder; + } + + private GetPlayerInventoryResponse fetchInventory() { + var resp = NativeBridgeFfi.getPlayerInventory(BridgeUtils.convertUuid(holder.getUniqueId())); + if (resp == null) { + return GetPlayerInventoryResponse.newBuilder().build(); + } + return resp; + } + + public static boolean isItemEmpty(@Nullable ItemStack item) { + if (item == null) { + return true; + } + try { + if (item.getType() == Material.AIR || item.getAmount() <= 0) { + return true; + } + return item.isEmpty(); + } catch (Throwable ignored) { + return true; + } + } + + public static patchbukkit.itemstack.ItemStack toProto(ItemStack item) { + if (isItemEmpty(item)) { + return patchbukkit.itemstack.ItemStack.newBuilder() + .setType("minecraft:air") + .setAmount(0) + .build(); + } + try { + String type = item.getType().getKey().toString(); + return patchbukkit.itemstack.ItemStack.newBuilder() + .setType(type) + .setAmount(item.getAmount()) + .build(); + } catch (Throwable ignored) { + return patchbukkit.itemstack.ItemStack.newBuilder() + .setType("minecraft:air") + .setAmount(0) + .build(); + } + } + + public static ItemStack fromProto(patchbukkit.itemstack.ItemStack proto) { + if (proto == null || proto.getAmount() <= 0 || proto.getType() == null || proto.getType().isEmpty() || proto.getType().equalsIgnoreCase("minecraft:air")) { + return new PatchBukkitItemStack(Material.AIR, 0); + } + Material mat = Material.matchMaterial(proto.getType()); + if (mat == null || mat == Material.AIR) { + return new PatchBukkitItemStack(Material.AIR, 0); + } + return new PatchBukkitItemStack(mat, proto.getAmount()); + } + + @Override + public ListIterator iterator() { + return Arrays.asList(getContents()).listIterator(); + } + + @Override + public ListIterator iterator(int index) { + return Arrays.asList(getContents()).listIterator(index); + } + + @Override + public ItemStack[] getArmorContents() { + GetPlayerInventoryResponse resp = fetchInventory(); + ItemStack[] armor = new ItemStack[4]; + armor[0] = fromProto(resp.getBoots()); + armor[1] = fromProto(resp.getLeggings()); + armor[2] = fromProto(resp.getChestplate()); + armor[3] = fromProto(resp.getHelmet()); + return armor; + } + + @Override + public void setArmorContents(ItemStack[] items) { + if (items == null) items = new ItemStack[4]; + setBoots(items.length > 0 ? items[0] : null); + setLeggings(items.length > 1 ? items[1] : null); + setChestplate(items.length > 2 ? items[2] : null); + setHelmet(items.length > 3 ? items[3] : null); + } + + @Override + public ItemStack[] getExtraContents() { + GetPlayerInventoryResponse resp = fetchInventory(); + return new ItemStack[] { fromProto(resp.getOffHand()) }; + } + + @Override + public void setExtraContents(ItemStack[] items) { + if (items != null && items.length > 0) { + setItemInOffHand(items[0]); + } else { + setItemInOffHand(null); + } + } + + @Override + public ItemStack getHelmet() { + return fromProto(fetchInventory().getHelmet()); + } + + @Override + public ItemStack getChestplate() { + return fromProto(fetchInventory().getChestplate()); + } + + @Override + public ItemStack getLeggings() { + return fromProto(fetchInventory().getLeggings()); + } + + @Override + public ItemStack getBoots() { + return fromProto(fetchInventory().getBoots()); + } + + @Override + public void setHelmet(ItemStack helmet) { + setEquipment(5, helmet); + } + + @Override + public void setChestplate(ItemStack chestplate) { + setEquipment(4, chestplate); + } + + @Override + public void setLeggings(ItemStack leggings) { + setEquipment(3, leggings); + } + + @Override + public void setBoots(ItemStack boots) { + setEquipment(2, boots); + } + + @Override + public ItemStack getItemInMainHand() { + GetPlayerInventoryResponse resp = fetchInventory(); + int selected = resp.getSelectedSlot(); + if (selected >= 0 && selected < resp.getMainInventoryCount()) { + return fromProto(resp.getMainInventory(selected)); + } + return ItemStack.empty(); + } + + @Override + public void setItemInMainHand(ItemStack item) { + GetPlayerInventoryResponse resp = fetchInventory(); + int selected = resp.getSelectedSlot(); + setItem(selected, item); + } + + @Override + public ItemStack getItemInOffHand() { + return fromProto(fetchInventory().getOffHand()); + } + + @Override + public void setItemInOffHand(ItemStack item) { + setEquipment(1, item); + } + + @Override + public ItemStack getItemInHand() { + return getItemInMainHand(); + } + + @Override + public void setItemInHand(ItemStack stack) { + setItemInMainHand(stack); + } + + @Override + public int getHeldItemSlot() { + return fetchInventory().getSelectedSlot(); + } + + @Override + public void setHeldItemSlot(int slot) { + if (slot >= 0 && slot < 9) { + NativeBridgeFfi.setPlayerSelectedSlot( + SetPlayerSelectedSlotRequest.newBuilder() + .setUuid(BridgeUtils.convertUuid(holder.getUniqueId())) + .setSlot(slot) + .build() + ); + } + } + + private void setEquipment(int slotType, ItemStack item) { + NativeBridgeFfi.setPlayerEquipment( + SetPlayerEquipmentRequest.newBuilder() + .setUuid(BridgeUtils.convertUuid(holder.getUniqueId())) + .setSlotType(slotType) + .setItem(toProto(item)) + .build() + ); + } + + @Override + public HumanEntity getHolder() { + return this.holder; + } + + @Override + public HumanEntity getHolder(boolean useSnapshot) { + return this.holder; + } + + @Override + public int getSize() { + return 41; + } + + @Override + public int getMaxStackSize() { + return 64; + } + + @Override + public void setMaxStackSize(int size) { + } + + @Override + public ItemStack getItem(int index) { + GetPlayerInventoryResponse resp = fetchInventory(); + if (index >= 0 && index < resp.getMainInventoryCount()) { + return fromProto(resp.getMainInventory(index)); + } else if (index == 36) { + return fromProto(resp.getBoots()); + } else if (index == 37) { + return fromProto(resp.getLeggings()); + } else if (index == 38) { + return fromProto(resp.getChestplate()); + } else if (index == 39) { + return fromProto(resp.getHelmet()); + } else if (index == 40) { + return fromProto(resp.getOffHand()); + } + return ItemStack.empty(); + } + + @Override + public void setItem(int index, ItemStack item) { + NativeBridgeFfi.setPlayerInventorySlot( + SetPlayerInventorySlotRequest.newBuilder() + .setUuid(BridgeUtils.convertUuid(holder.getUniqueId())) + .setSlot(index) + .setItem(toProto(item)) + .build() + ); + } + + @Override + public void setItem(EquipmentSlot slot, ItemStack item) { + if (slot == null) return; + switch (slot) { + case HAND -> setItemInMainHand(item); + case OFF_HAND -> setItemInOffHand(item); + case FEET -> setBoots(item); + case LEGS -> setLeggings(item); + case CHEST -> setChestplate(item); + case HEAD -> setHelmet(item); + default -> {} + } + } + + @Override + public ItemStack getItem(EquipmentSlot slot) { + if (slot == null) return ItemStack.empty(); + return switch (slot) { + case HAND -> getItemInMainHand(); + case OFF_HAND -> getItemInOffHand(); + case FEET -> getBoots(); + case LEGS -> getLeggings(); + case CHEST -> getChestplate(); + case HEAD -> getHelmet(); + default -> ItemStack.empty(); + }; + } + + @Override + public HashMap addItem(ItemStack... items) throws IllegalArgumentException { + HashMap leftover = new HashMap<>(); + if (items == null) return leftover; + for (int i = 0; i < items.length; i++) { + ItemStack item = items[i]; + if (isItemEmpty(item)) continue; + int firstEmpty = firstEmpty(); + if (firstEmpty != -1) { + setItem(firstEmpty, item); + } else { + leftover.put(i, item); + } + } + return leftover; + } + + @Override + public HashMap removeItemAnySlot(ItemStack... items) throws IllegalArgumentException { + return removeItem(items); + } + + @Override + public HashMap removeItem(ItemStack... items) throws IllegalArgumentException { + HashMap leftover = new HashMap<>(); + if (items == null) return leftover; + for (int i = 0; i < items.length; i++) { + ItemStack item = items[i]; + if (isItemEmpty(item)) continue; + int slot = first(item.getType()); + if (slot != -1) { + setItem(slot, ItemStack.empty()); + } else { + leftover.put(i, item); + } + } + return leftover; + } + + @Override + public ItemStack[] getContents() { + GetPlayerInventoryResponse resp = fetchInventory(); + ItemStack[] contents = new ItemStack[41]; + for (int i = 0; i < 36 && i < resp.getMainInventoryCount(); i++) { + contents[i] = fromProto(resp.getMainInventory(i)); + } + for (int i = resp.getMainInventoryCount(); i < 36; i++) { + contents[i] = ItemStack.empty(); + } + contents[36] = fromProto(resp.getBoots()); + contents[37] = fromProto(resp.getLeggings()); + contents[38] = fromProto(resp.getChestplate()); + contents[39] = fromProto(resp.getHelmet()); + contents[40] = fromProto(resp.getOffHand()); + return contents; + } + + @Override + public void setContents(ItemStack[] items) throws IllegalArgumentException { + if (items == null) return; + for (int i = 0; i < items.length && i < 41; i++) { + setItem(i, items[i]); + } + } + + @Override + public ItemStack[] getStorageContents() { + GetPlayerInventoryResponse resp = fetchInventory(); + ItemStack[] contents = new ItemStack[36]; + for (int i = 0; i < 36 && i < resp.getMainInventoryCount(); i++) { + contents[i] = fromProto(resp.getMainInventory(i)); + } + for (int i = resp.getMainInventoryCount(); i < 36; i++) { + contents[i] = ItemStack.empty(); + } + return contents; + } + + @Override + public void setStorageContents(ItemStack[] items) throws IllegalArgumentException { + if (items == null) return; + for (int i = 0; i < items.length && i < 36; i++) { + setItem(i, items[i]); + } + } + + @Override + public boolean contains(Material material) throws IllegalArgumentException { + return first(material) != -1; + } + + @Override + public boolean contains(ItemStack item) { + return first(item) != -1; + } + + @Override + public boolean contains(Material material, int amount) throws IllegalArgumentException { + if (amount <= 0) return true; + int count = 0; + for (ItemStack is : getStorageContents()) { + if (is != null && !is.isEmpty() && is.getType() == material) { + count += is.getAmount(); + if (count >= amount) return true; + } + } + return false; + } + + @Override + public boolean contains(ItemStack item, int amount) { + if (item == null) return false; + return contains(item.getType(), amount); + } + + @Override + public boolean containsAtLeast(ItemStack item, int amount) { + if (item == null) return false; + return contains(item.getType(), amount); + } + + @Override + public HashMap all(Material material) throws IllegalArgumentException { + HashMap slots = new HashMap<>(); + ItemStack[] contents = getContents(); + for (int i = 0; i < contents.length; i++) { + if (contents[i] != null && !contents[i].isEmpty() && contents[i].getType() == material) { + slots.put(i, contents[i]); + } + } + return slots; + } + + @Override + public HashMap all(ItemStack item) { + if (item == null) return new HashMap<>(); + return all(item.getType()); + } + + @Override + public int first(Material material) throws IllegalArgumentException { + if (material == null) return -1; + ItemStack[] contents = getContents(); + for (int i = 0; i < contents.length; i++) { + if (contents[i] != null && !contents[i].isEmpty() && contents[i].getType() == material) { + return i; + } + } + return -1; + } + + @Override + public int first(ItemStack item) { + if (item == null) return -1; + return first(item.getType()); + } + + @Override + public int firstEmpty() { + ItemStack[] contents = getStorageContents(); + for (int i = 0; i < contents.length; i++) { + if (isItemEmpty(contents[i])) { + return i; + } + } + return -1; + } + + @Override + public boolean isEmpty() { + for (ItemStack item : getContents()) { + if (!isItemEmpty(item)) { + return false; + } + } + return true; + } + + @Override + public void remove(Material material) throws IllegalArgumentException { + if (material == null) return; + ItemStack[] contents = getContents(); + for (int i = 0; i < contents.length; i++) { + if (contents[i] != null && contents[i].getType() == material) { + setItem(i, ItemStack.empty()); + } + } + } + + @Override + public void remove(ItemStack item) { + if (item != null) { + remove(item.getType()); + } + } + + @Override + public void clear(int index) { + setItem(index, ItemStack.empty()); + } + + @Override + public void clear() { + NativeBridgeFfi.clearPlayerInventory(BridgeUtils.convertUuid(holder.getUniqueId())); + } + + @Override + public List getViewers() { + return Collections.singletonList(holder); + } + + @Override + public InventoryType getType() { + return InventoryType.PLAYER; + } + + @Override + public int close() { + return 0; + } + + @Override + public Location getLocation() { + return holder.getLocation(); + } +} diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/loader/PatchBukkitBootstrap.java b/java/patchbukkit/src/main/java/org/patchbukkit/loader/PatchBukkitBootstrap.java new file mode 100644 index 0000000..77c24ea --- /dev/null +++ b/java/patchbukkit/src/main/java/org/patchbukkit/loader/PatchBukkitBootstrap.java @@ -0,0 +1,269 @@ +package org.patchbukkit.loader; + +import java.io.File; +import java.io.InputStream; +import java.net.URL; +import java.util.*; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import org.bukkit.Bukkit; +import org.bukkit.command.PluginCommand; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginDescriptionFile; +import org.bukkit.plugin.java.JavaPlugin; +import org.patchbukkit.PatchBukkitPluginManager; +import org.patchbukkit.command.CommandFactory; +import org.patchbukkit.command.PatchBukkitCommandMap; + +public class PatchBukkitBootstrap { + + public static boolean bootstrapPlugins(String pluginsDirPath) { + try { + File pluginsDir = new File(pluginsDirPath); + if (!pluginsDir.exists() || !pluginsDir.isDirectory()) { + System.out.println("[PatchBukkit] Plugins directory does not exist: " + pluginsDirPath); + return true; + } + + List jarFiles = new ArrayList<>(); + findJarsRecursive(pluginsDir, jarFiles); + + if (jarFiles.isEmpty()) { + System.out.println("[PatchBukkit] No plugin JAR files found in: " + pluginsDirPath); + return true; + } + + Map holders = new LinkedHashMap<>(); + + for (File jarFile : jarFiles) { + try { + PluginHolder holder = parsePluginHolder(jarFile); + if (holder != null) { + holders.put(holder.name.toLowerCase(Locale.ENGLISH), holder); + } + } catch (Throwable t) { + System.err.println("[PatchBukkit] Failed to parse plugin JAR " + jarFile.getName() + ": " + t.getMessage()); + t.printStackTrace(); + } + } + + List loadOrder = computeLoadOrder(holders); + + for (PluginHolder holder : loadOrder) { + try { + loadAndRegisterPlugin(holder, holders); + } catch (Throwable t) { + System.err.println("[PatchBukkit] Failed to load plugin " + holder.name + ": " + t.getMessage()); + t.printStackTrace(); + } + } + + // Enable all instantiated plugins in dependency order + if (Bukkit.getPluginManager() instanceof PatchBukkitPluginManager pm) { + for (PluginHolder holder : loadOrder) { + if (holder.pluginInstance != null) { + try { + pm.enablePlugin(holder.pluginInstance); + System.out.println("[PatchBukkit] Enabled plugin: " + holder.name); + } catch (Throwable t) { + System.err.println("[PatchBukkit] Error enabling plugin " + holder.name + ": " + t.getMessage()); + t.printStackTrace(); + } + } + } + } + + return true; + } catch (Throwable t) { + System.err.println("[PatchBukkit] Fatal error during bootstrap: " + t.getMessage()); + t.printStackTrace(); + return false; + } + } + + private static void findJarsRecursive(File dir, List result) { + if (dir == null || !dir.isDirectory()) return; + if (dir.getName().equals("patchbukkit-libs")) return; + + File[] files = dir.listFiles(); + if (files == null) return; + + for (File file : files) { + if (file.isDirectory()) { + if (!file.getName().equals("patchbukkit-libs")) { + findJarsRecursive(file, result); + } + } else if (file.getName().endsWith(".jar")) { + result.add(file); + } + } + } + + private static PluginHolder parsePluginHolder(File jarFile) throws Exception { + try (JarFile jar = new JarFile(jarFile)) { + JarEntry entry = jar.getJarEntry("plugin.yml"); + if (entry == null) { + entry = jar.getJarEntry("paper-plugin.yml"); + } + if (entry == null) { + return null; + } + + PluginDescriptionFile description; + try (InputStream in = jar.getInputStream(entry)) { + description = new PluginDescriptionFile(in); + } + + PluginHolder holder = new PluginHolder(); + holder.jarFile = jarFile; + holder.description = description; + holder.name = description.getName(); + holder.mainClass = description.getMain(); + holder.depends = description.getDepend() != null ? description.getDepend() : Collections.emptyList(); + holder.softDepends = description.getSoftDepend() != null ? description.getSoftDepend() : Collections.emptyList(); + holder.provides = description.getProvides() != null ? description.getProvides() : Collections.emptyList(); + holder.libraries = description.getLibraries() != null ? description.getLibraries() : Collections.emptyList(); + return holder; + } + } + + private static List computeLoadOrder(Map holders) { + List order = new ArrayList<>(); + Set visited = new HashSet<>(); + Set visiting = new HashSet<>(); + + for (PluginHolder holder : holders.values()) { + visitHolder(holder, holders, visited, visiting, order); + } + + return order; + } + + private static void visitHolder( + PluginHolder holder, + Map holders, + Set visited, + Set visiting, + List order + ) { + String key = holder.name.toLowerCase(Locale.ENGLISH); + if (visited.contains(key)) return; + if (visiting.contains(key)) { + System.err.println("[PatchBukkit] Circular dependency detected involving plugin: " + holder.name); + return; + } + + visiting.add(key); + + for (String dep : holder.depends) { + PluginHolder depHolder = holders.get(dep.toLowerCase(Locale.ENGLISH)); + if (depHolder != null) { + visitHolder(depHolder, holders, visited, visiting, order); + } + } + + for (String dep : holder.softDepends) { + PluginHolder depHolder = holders.get(dep.toLowerCase(Locale.ENGLISH)); + if (depHolder != null) { + visitHolder(depHolder, holders, visited, visiting, order); + } + } + + visiting.remove(key); + visited.add(key); + order.add(holder); + } + + private static void loadAndRegisterPlugin(PluginHolder holder, Map allHolders) throws Exception { + LinkedHashSet extraUrls = new LinkedHashSet<>(); + + if (!holder.libraries.isEmpty()) { + File libsDir = new File(holder.jarFile.getParentFile(), "patchbukkit-libs"); + if (!libsDir.exists()) libsDir.mkdirs(); + String libCoords = String.join("\n", holder.libraries); + List resolvedLibs = LibraryResolver.resolveLibraries(libCoords, libsDir); + for (File lib : resolvedLibs) { + if (lib != null && lib.exists()) { + extraUrls.add(lib.toURI().toURL()); + } + } + } + + PatchBukkitPluginClassLoader classLoader = new PatchBukkitPluginClassLoader( + PatchBukkitBootstrap.class.getClassLoader(), + holder.jarFile, + extraUrls.toArray(new URL[0]) + ); + + Class jarClass = Class.forName(holder.mainClass, true, classLoader); + Class pluginClass = jarClass.asSubclass(Plugin.class); + Plugin plugin = pluginClass.getDeclaredConstructor().newInstance(); + + if (plugin instanceof JavaPlugin javaPlugin) { + classLoader.init(javaPlugin); + } + + if (Bukkit.getPluginManager() instanceof PatchBukkitPluginManager pm) { + pm.registerPlugin(plugin); + } + + try { + plugin.onLoad(); + } catch (Throwable t) { + System.err.println("[PatchBukkit] Error during onLoad() for " + holder.name + ": " + t.getMessage()); + t.printStackTrace(); + } + + holder.pluginInstance = plugin; + + if (Bukkit.getCommandMap() instanceof PatchBukkitCommandMap commandMap) { + Map> commands = holder.description.getCommands(); + if (commands != null) { + for (Map.Entry> entry : commands.entrySet()) { + String cmdName = entry.getKey(); + Map cmdData = entry.getValue(); + + PluginCommand pluginCmd = CommandFactory.create(cmdName, plugin); + if (pluginCmd != null) { + if (cmdData != null) { + if (cmdData.containsKey("description")) { + pluginCmd.setDescription(String.valueOf(cmdData.get("description"))); + } + if (cmdData.containsKey("usage")) { + pluginCmd.setUsage(String.valueOf(cmdData.get("usage"))); + } + if (cmdData.containsKey("permission")) { + pluginCmd.setPermission(String.valueOf(cmdData.get("permission"))); + } + if (cmdData.containsKey("permission-message")) { + pluginCmd.setPermissionMessage(String.valueOf(cmdData.get("permission-message"))); + } + if (cmdData.get("aliases") instanceof List aliases) { + List strAliases = new ArrayList<>(); + for (Object a : aliases) { + if (a != null) strAliases.add(String.valueOf(a)); + } + pluginCmd.setAliases(strAliases); + } + } + + commandMap.register(cmdName, holder.name, pluginCmd); + System.out.println("[PatchBukkit] Registered command: /" + cmdName + " for plugin " + holder.name); + } + } + } + } + } + + private static class PluginHolder { + File jarFile; + PluginDescriptionFile description; + String name; + String mainClass; + List depends = Collections.emptyList(); + List softDepends = Collections.emptyList(); + List provides = Collections.emptyList(); + List libraries = Collections.emptyList(); + Plugin pluginInstance; + } +} diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/persistence/PatchBukkitPersistentDataContainer.java b/java/patchbukkit/src/main/java/org/patchbukkit/persistence/PatchBukkitPersistentDataContainer.java new file mode 100644 index 0000000..c75886e --- /dev/null +++ b/java/patchbukkit/src/main/java/org/patchbukkit/persistence/PatchBukkitPersistentDataContainer.java @@ -0,0 +1,84 @@ +package org.patchbukkit.persistence; + +import org.bukkit.NamespacedKey; +import org.bukkit.persistence.PersistentDataAdapterContext; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class PatchBukkitPersistentDataContainer implements PersistentDataContainer { + private final Map storage = new HashMap<>(); + + @Override + public void set(@NotNull NamespacedKey key, @NotNull PersistentDataType type, @NotNull Z value) { + storage.put(key, value); + } + + @Override + public boolean has(@NotNull NamespacedKey key, @NotNull PersistentDataType type) { + return storage.containsKey(key); + } + + @Override + public boolean has(@NotNull NamespacedKey key) { + return storage.containsKey(key); + } + + @Override + public @Nullable Z get(@NotNull NamespacedKey key, @NotNull PersistentDataType type) { + return (Z) storage.get(key); + } + + @Override + public @NotNull Z getOrDefault(@NotNull NamespacedKey key, @NotNull PersistentDataType type, @NotNull Z defaultValue) { + Z val = get(key, type); + return val != null ? val : defaultValue; + } + + @Override + public @NotNull Set getKeys() { + return storage.keySet(); + } + + @Override + public void remove(@NotNull NamespacedKey key) { + storage.remove(key); + } + + @Override + public boolean isEmpty() { + return storage.isEmpty(); + } + + public int getSize() { + return storage.size(); + } + + @Override + public void copyTo(@NotNull PersistentDataContainer other, boolean replace) { + } + + @Override + public @NotNull PersistentDataAdapterContext getAdapterContext() { + return new PersistentDataAdapterContext() { + @Override + public @NotNull PersistentDataContainer newPersistentDataContainer() { + return new PatchBukkitPersistentDataContainer(); + } + }; + } + + @Override + public void readFromBytes(byte @NotNull [] bytes, boolean clear) { + } + + @Override + public byte @NotNull [] serializeToBytes() { + return new byte[0]; + } +} diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/registry/LegacyRegistryIdentifiers.java b/java/patchbukkit/src/main/java/org/patchbukkit/registry/LegacyRegistryIdentifiers.java index a611285..557fd28 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/registry/LegacyRegistryIdentifiers.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/registry/LegacyRegistryIdentifiers.java @@ -11,26 +11,30 @@ import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.lang.reflect.WildcardType; - @Deprecated public final class LegacyRegistryIdentifiers { public static final Map, RegistryKey> CLASS_TO_KEY_MAP; + public static final Map, Class> KEY_TO_CLASS_MAP; static { - final ImmutableMap.Builder, RegistryKey> builder = ImmutableMap.builder(); + final ImmutableMap.Builder, RegistryKey> classToKey = ImmutableMap.builder(); + final ImmutableMap.Builder, Class> keyToClass = ImmutableMap.builder(); try { for (final Field field : RegistryKey.class.getFields()) { if (field.getType() == RegistryKey.class) { // get the legacy type from the RegistryKey generic parameter on the field final Class legacyType = LegacyRegistryIdentifiers.erase(((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]); - builder.put(legacyType, (RegistryKey) field.get(null)); + RegistryKey key = (RegistryKey) field.get(null); + classToKey.put(legacyType, key); + keyToClass.put(key, legacyType); } } } catch (final ReflectiveOperationException ex) { throw new RuntimeException(ex); } - CLASS_TO_KEY_MAP = builder.build(); + CLASS_TO_KEY_MAP = classToKey.build(); + KEY_TO_CLASS_MAP = keyToClass.build(); } private static Class erase(Type type) { diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/registry/PatchBukkitBlockType.java b/java/patchbukkit/src/main/java/org/patchbukkit/registry/PatchBukkitBlockType.java new file mode 100644 index 0000000..9d67fdd --- /dev/null +++ b/java/patchbukkit/src/main/java/org/patchbukkit/registry/PatchBukkitBlockType.java @@ -0,0 +1,108 @@ +package org.patchbukkit.registry; + +import org.bukkit.Keyed; +import org.bukkit.Material; +import org.bukkit.block.BlockType; +import org.bukkit.block.data.BlockData; +import org.patchbukkit.PatchBukkitBlockData; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; +import java.util.Collections; +import java.util.function.Consumer; + +public final class PatchBukkitBlockType { + + private PatchBukkitBlockType() {} + + public static BlockType create(Material material) { + if (material == null || material.isLegacy()) return null; + + return (BlockType) Proxy.newProxyInstance( + PatchBukkitBlockType.class.getClassLoader(), + new Class[]{BlockType.class}, + (proxy, method, args) -> { + String name = method.getName(); + if ("getKey".equals(name) || "key".equals(name)) { + return material.getKey(); + } + if ("asMaterial".equals(name)) { + return material; + } + if ("createBlockData".equals(name)) { + BlockType self = (BlockType) proxy; + if (args == null || args.length == 0) { + return PatchBukkitBlockData.newData(material, self, null); + } else if (args.length == 1 && args[0] instanceof String s) { + return PatchBukkitBlockData.newData(material, self, s); + } else if (args.length == 1 && args[0] instanceof Consumer consumer) { + BlockData data = PatchBukkitBlockData.newData(material, self, null); + consumer.accept(data); + return data; + } + } + if ("createBlockDataStates".equals(name)) { + BlockType self = (BlockType) proxy; + return Collections.singletonList(PatchBukkitBlockData.newData(material, self, null)); + } + if ("getBlockDataClass".equals(name)) { + return BlockData.class; + } + if ("getItemType".equals(name)) { + return material.isItem() ? PatchBukkitItemType.create(material) : null; + } + if ("hasItemType".equals(name)) { + return material.isItem(); + } + if ("isSolid".equals(name)) { + return material.isSolid(); + } + if ("isAir".equals(name)) { + return material.isAir(); + } + if ("isBurnable".equals(name)) { + return material.isBurnable(); + } + if ("isEdible".equals(name)) { + return material.isEdible(); + } + if ("isOccluding".equals(name)) { + return material.isOccluding(); + } + if ("isInteractable".equals(name)) { + return material.isInteractable(); + } + if ("hasGravity".equals(name) || "isGravity".equals(name)) { + return material.hasGravity(); + } + if ("translationKey".equals(name) || "getTranslationKey".equals(name)) { + return material.getTranslationKey(); + } + if ("typed".equals(name)) { + return proxy; + } + if ("equals".equals(name) && args != null && args.length == 1) { + if (args[0] instanceof Keyed k) { + return material.getKey().equals(k.getKey()); + } + return false; + } + if ("hashCode".equals(name)) { + return material.getKey().hashCode(); + } + if ("toString".equals(name)) { + return "BlockType{" + material.getKey() + "}"; + } + if (method.isDefault()) { + return InvocationHandler.invokeDefault(proxy, method, args); + } + + Class returnType = method.getReturnType(); + if (returnType == boolean.class) return false; + if (returnType == int.class || returnType == short.class || returnType == long.class || returnType == byte.class) return 0; + if (returnType == float.class || returnType == double.class) return 0.0f; + return null; + } + ); + } +} diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/registry/PatchBukkitItemType.java b/java/patchbukkit/src/main/java/org/patchbukkit/registry/PatchBukkitItemType.java new file mode 100644 index 0000000..6a2467b --- /dev/null +++ b/java/patchbukkit/src/main/java/org/patchbukkit/registry/PatchBukkitItemType.java @@ -0,0 +1,69 @@ +package org.patchbukkit.registry; + +import org.bukkit.Keyed; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.ItemType; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; + +public final class PatchBukkitItemType { + + private PatchBukkitItemType() {} + + public static ItemType create(Material material) { + if (material == null) return null; + + return (ItemType) Proxy.newProxyInstance( + PatchBukkitItemType.class.getClassLoader(), + new Class[]{ItemType.class}, + (proxy, method, args) -> { + String name = method.getName(); + if ("getKey".equals(name) || "key".equals(name)) { + return material.getKey(); + } + if ("asMaterial".equals(name)) { + return material; + } + if ("getMaxStackSize".equals(name)) { + return material.getMaxStackSize(); + } + if ("getMaxDurability".equals(name)) { + return (int) material.getMaxDurability(); + } + if ("isEdible".equals(name)) { + return material.isEdible(); + } + if ("isRecord".equals(name)) { + return material.isRecord(); + } + if ("createItemStack".equals(name)) { + int amount = (args != null && args.length > 0 && args[0] instanceof Integer i) ? i : 1; + return new org.patchbukkit.inventory.PatchBukkitItemStack(material, amount); + } + if ("equals".equals(name) && args != null && args.length == 1) { + if (args[0] instanceof Keyed k) { + return material.getKey().equals(k.getKey()); + } + return false; + } + if ("hashCode".equals(name)) { + return material.getKey().hashCode(); + } + if ("toString".equals(name)) { + return "ItemType{" + material.getKey() + "}"; + } + if (method.isDefault()) { + return InvocationHandler.invokeDefault(proxy, method, args); + } + + Class returnType = method.getReturnType(); + if (returnType == boolean.class) return false; + if (returnType == int.class || returnType == short.class || returnType == long.class || returnType == byte.class) return 0; + if (returnType == float.class || returnType == double.class) return 0.0f; + return null; + } + ); + } +} 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 a37f4f1..9d56429 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/registry/PatchBukkitRegistry.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/registry/PatchBukkitRegistry.java @@ -1,33 +1,40 @@ package org.patchbukkit.registry; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; import io.papermc.paper.registry.RegistryKey; import io.papermc.paper.registry.tag.Tag; import io.papermc.paper.registry.tag.TagKey; import org.bukkit.Keyed; +import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.Registry; -import org.bukkit.Sound; +import org.bukkit.inventory.ItemType; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; +import org.patchbukkit.PatchBukkitLegacy; import patchbukkit.bridge.NativeBridgeFfi; import patchbukkit.registry.GetRegistryDataRequest; import patchbukkit.registry.GetRegistryDataResponse; import patchbukkit.registry.RegistryType; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import java.util.stream.Stream; public class PatchBukkitRegistry implements Registry { - private final Map entries = new LinkedHashMap<>(); - private final Map> tags = new LinkedHashMap<>(); + private final Map entries = new ConcurrentHashMap<>(); + private final Map> tags = new ConcurrentHashMap<>(); + private final ThreadLocal> inFallback = ThreadLocal.withInitial(HashSet::new); private final RegistryKey registryKey; + private boolean initialized = false; + + public PatchBukkitRegistry(RegistryKey registryKey) { + this(null, null, null, registryKey); + } public PatchBukkitRegistry( RegistryType registryType, @@ -44,22 +51,110 @@ public class PatchBukkitRegistry implements Registry { RegistryKey registryKey ) { this.registryKey = registryKey; - if (registryType == null) return; + if (registryType != null || extractor != null || factory != null) { + initialize(registryType, extractor, factory); + } + } - GetRegistryDataRequest request = GetRegistryDataRequest.newBuilder() - .setRegistry(registryType) - .build(); + @SuppressWarnings("unchecked") + public synchronized void initialize( + RegistryType registryType, + Function> extractor, + Function factory + ) { + if (initialized) return; + initialized = true; - GetRegistryDataResponse response = NativeBridgeFfi.getRegistryData(request); - if (response == null) return; - - List

protoEntries = extractor.apply(response); - for (P protoEntry : protoEntries) { - B value = factory.apply(protoEntry); - if (value != null) { - entries.put(value.getKey(), value); + // Auto-discover pre-registered Paper/Bukkit constants for this registry + if (registryKey != null) { + if (RegistryKey.ITEM.equals(registryKey) || "item".equalsIgnoreCase(registryKey.key().value())) { + for (Material mat : Material.values()) { + try { + if (!mat.isLegacy() && mat.getKey() != null) { + B itemType = (B) PatchBukkitItemType.create(mat); + if (itemType != null) { + entries.put(mat.getKey(), itemType); + } + } + } catch (Throwable ignored) {} + } + } else if (RegistryKey.BLOCK.equals(registryKey) || "block".equalsIgnoreCase(registryKey.key().value())) { + for (Material mat : Material.values()) { + try { + if (!mat.isLegacy() && mat.getKey() != null) { + B blockType = (B) PatchBukkitBlockType.create(mat); + if (blockType != null) { + entries.put(mat.getKey(), blockType); + } + } + } catch (Throwable ignored) {} + } + } else { + Class valueClass = (Class) LegacyRegistryIdentifiers.KEY_TO_CLASS_MAP.get(registryKey); + if (valueClass != null) { + Map discovered = autoDiscover(valueClass); + entries.putAll(discovered); + } } } + + if (registryType != null) { + try { + GetRegistryDataRequest request = GetRegistryDataRequest.newBuilder() + .setRegistry(registryType) + .build(); + + GetRegistryDataResponse response = NativeBridgeFfi.getRegistryData(request); + if (response != null && extractor != null) { + List

protoEntries = extractor.apply(response); + if (protoEntries != null) { + for (P protoEntry : protoEntries) { + if (protoEntry == null) continue; + try { + B value = factory.apply(protoEntry); + if (value != null && value.getKey() != null) { + entries.put(value.getKey(), value); + } + } catch (Throwable t) { + // Skip invalid entry safely + } + } + } + } + } catch (Throwable t) { + // Ignore FFI or RPC failure safely + } + } + } + + @SuppressWarnings("unchecked") + private static Map autoDiscover(Class clazz) { + Map map = new LinkedHashMap<>(); + if (clazz == null) return map; + try { + if (clazz.isEnum()) { + for (B enumConstant : clazz.getEnumConstants()) { + if (enumConstant != null && enumConstant.getKey() != null) { + map.put(enumConstant.getKey(), enumConstant); + } + } + } + for (Field field : clazz.getFields()) { + if (Modifier.isStatic(field.getModifiers()) && clazz.isAssignableFrom(field.getType())) { + try { + field.setAccessible(true); + Object val = field.get(null); + if (val != null && clazz.isInstance(val)) { + B item = clazz.cast(val); + if (item.getKey() != null) { + map.put(item.getKey(), item); + } + } + } catch (Throwable ignored) {} + } + } + } catch (Throwable ignored) {} + return map; } @Override @@ -68,22 +163,72 @@ public class PatchBukkitRegistry implements Registry { 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; + if (value != null) { + return value; } - return value; + + Set fallbackSet = inFallback.get(); + if (!fallbackSet.add(key)) { + return null; + } + try { + // Fallback for SoundEvent + if (RegistryKey.SOUND_EVENT.equals(registryKey) || "sound_event".equalsIgnoreCase(registryKey != null ? registryKey.key().value() : "")) { + try { + PatchBukkitSound dynamicSound = new PatchBukkitSound(key.getKey(), entries.size()); + entries.put(key, (B) dynamicSound); + return (B) dynamicSound; + } catch (Throwable t) { + return null; + } + } + // Fallback for ItemType via Material + if (RegistryKey.ITEM.equals(registryKey) || "item".equalsIgnoreCase(registryKey != null ? registryKey.key().value() : "")) { + try { + Material mat = Material.matchMaterial(key.getKey()); + if (mat != null && mat.isItem()) { + B itemType = (B) PatchBukkitItemType.create(mat); + if (itemType != null) { + entries.put(key, itemType); + return itemType; + } + } + } catch (Throwable ignored) {} + } + // Fallback for BlockType via Material + if (RegistryKey.BLOCK.equals(registryKey) || "block".equalsIgnoreCase(registryKey != null ? registryKey.key().value() : "")) { + try { + Material mat = Material.matchMaterial(key.getKey()); + if (mat == null) { + mat = Material.matchMaterial(key.toString()); + } + if (mat != null && mat.isLegacy()) { + mat = PatchBukkitLegacy.fromLegacy(mat); + } + if (mat != null && !mat.isLegacy()) { + B blockType = (B) PatchBukkitBlockType.create(mat); + if (blockType != null) { + entries.put(key, blockType); + return blockType; + } + } + } catch (Throwable ignored) {} + } + } finally { + fallbackSet.remove(key); + } + + return null; } @Override public @Nullable NamespacedKey getKey(B value) { - return value.getKey(); + return value != null ? value.getKey() : null; } @Override public boolean hasTag(TagKey key) { - return tags.containsKey(key.key().asString()); + return key != null && tags.containsKey(key.key().asString()); } @Override @@ -96,12 +241,7 @@ public class PatchBukkitRegistry implements Registry { } public static PatchBukkitRegistry empty(RegistryKey registryKey) { - return new PatchBukkitRegistry<>( - null, - response -> Collections.emptyList(), - obj -> null, - registryKey - ); + return new PatchBukkitRegistry<>(registryKey); } @Override @@ -114,7 +254,6 @@ public class PatchBukkitRegistry implements Registry { return entries.values().stream(); } - @Override public @NonNull Stream keyStream() { return entries.keySet().stream(); } 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 345835f..98a6279 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/registry/PatchBukkitRegistryAccess.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/registry/PatchBukkitRegistryAccess.java @@ -4,20 +4,18 @@ import io.papermc.paper.registry.RegistryAccess; import io.papermc.paper.registry.RegistryKey; import org.bukkit.Keyed; import org.bukkit.Registry; -import org.bukkit.Sound; import org.jspecify.annotations.Nullable; -import com.google.gson.JsonObject; import patchbukkit.registry.GetRegistryDataResponse; import patchbukkit.registry.RegistryType; -import java.util.HashMap; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; -public class PatchBukkitRegistryAccess implements RegistryAccess { +public class PatchBukkitRegistryAccess extends io.papermc.paper.registry.PaperRegistryAccess { private static final Map, Registry> instances = new ConcurrentHashMap<>(); /** @@ -27,7 +25,7 @@ public class PatchBukkitRegistryAccess implements RegistryAccess { private static final Map, RegistryFactory> FACTORIES = Map.of( RegistryKey.SOUND_EVENT, new RegistryFactory<>( RegistryType.SOUND_EVENT, - response -> response.getSoundEvent().getSoundEventsList(), + response -> (response != null && response.hasSoundEvent()) ? response.getSoundEvent().getSoundEventsList() : Collections.emptyList(), data -> new PatchBukkitSound( data.getName(), data.getId() @@ -41,39 +39,43 @@ public class PatchBukkitRegistryAccess implements RegistryAccess { Function factory ) {} - @Override public @Nullable Registry getRegistry(Class type) { final RegistryKey registryKey = byType(type); return this.getRegistry(registryKey); } @SuppressWarnings({"unchecked", "rawtypes"}) - @Override public Registry getRegistry(RegistryKey registryKey) { if (registryKey == null) return null; - return (Registry) instances.computeIfAbsent(registryKey, key -> { - RegistryFactory factoryEntry = FACTORIES.get(key); - if (factoryEntry != null) { - return buildRegistry((RegistryKey) key, factoryEntry); - } - // Return an empty registry for unsupported types - return PatchBukkitRegistry.empty((RegistryKey) key); - }); - } + Registry existing = instances.get(registryKey); + if (existing != null) { + return (Registry) existing; + } - @SuppressWarnings("unchecked") - private static PatchBukkitRegistry buildRegistry(RegistryKey key, RegistryFactory factory) { - return new PatchBukkitRegistry<>( - factory.registryType(), - factory.extractor(), - factory.factory(), - key - ); + PatchBukkitRegistry reg = new PatchBukkitRegistry(registryKey); + Registry winner = instances.putIfAbsent(registryKey, reg); + if (winner != null) { + return (Registry) winner; + } + + try { + RegistryFactory factoryEntry = FACTORIES.get(registryKey); + if (factoryEntry != null) { + reg.initialize(factoryEntry.registryType(), factoryEntry.extractor(), factoryEntry.factory()); + } else { + reg.initialize(null, null, null); + } + } catch (Throwable t) { + // Ignore failure during registry initialization safely + } + + return (Registry) reg; } @SuppressWarnings({"unchecked", "deprecation"}) public static @Nullable RegistryKey byType(final Class type) { + if (type == null) return null; return (RegistryKey) LegacyRegistryIdentifiers.CLASS_TO_KEY_MAP.get(type); } } diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/registry/PatchBukkitSound.java b/java/patchbukkit/src/main/java/org/patchbukkit/registry/PatchBukkitSound.java index 45dd950..eb27247 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/registry/PatchBukkitSound.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/registry/PatchBukkitSound.java @@ -13,11 +13,19 @@ public class PatchBukkitSound implements Sound { private final int protocolId; public PatchBukkitSound(String soundName, int protocolId) { - this.namespacedKey = new NamespacedKey(NamespacedKey.MINECRAFT, soundName); - this.adventureKey = Key.key(Key.MINECRAFT_NAMESPACE, soundName); + if (soundName == null) soundName = "unknown"; + String keyPath = soundName.startsWith("minecraft:") ? soundName.substring(10) : soundName; + keyPath = keyPath.toLowerCase(java.util.Locale.ROOT); + + NamespacedKey key = NamespacedKey.fromString(keyPath); + if (key == null) { + key = NamespacedKey.minecraft(keyPath.replaceAll("[^a-z0-9/._-]", "_")); + } + this.namespacedKey = key; + this.adventureKey = Key.key(key.namespace(), key.value()); this.protocolId = protocolId; this.originalName = soundName; - this.enumName = soundName.toUpperCase().replace('.', '_'); + this.enumName = key.value().toUpperCase(java.util.Locale.ROOT).replace('.', '_').replace('-', '_').replace('/', '_'); } public String getOriginalName() { diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/tag/PatchBukkitTag.java b/java/patchbukkit/src/main/java/org/patchbukkit/tag/PatchBukkitTag.java new file mode 100644 index 0000000..ef62e41 --- /dev/null +++ b/java/patchbukkit/src/main/java/org/patchbukkit/tag/PatchBukkitTag.java @@ -0,0 +1,37 @@ +package org.patchbukkit.tag; + +import java.util.Collections; +import java.util.Set; +import org.bukkit.Keyed; +import org.bukkit.NamespacedKey; +import org.bukkit.Tag; +import org.jetbrains.annotations.NotNull; + +public class PatchBukkitTag implements Tag { + private final NamespacedKey key; + private final Set values; + + public PatchBukkitTag(@NotNull NamespacedKey key, @NotNull Set values) { + this.key = key; + this.values = values; + } + + public PatchBukkitTag(@NotNull NamespacedKey key) { + this(key, Collections.emptySet()); + } + + @Override + public @NotNull NamespacedKey getKey() { + return key; + } + + @Override + public boolean isTagged(@NotNull T item) { + return values.contains(item); + } + + @Override + public @NotNull Set getValues() { + return values; + } +} diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/world/PatchBukkitBlock.java b/java/patchbukkit/src/main/java/org/patchbukkit/world/PatchBukkitBlock.java index 53836cf..8e21375 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/world/PatchBukkitBlock.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/world/PatchBukkitBlock.java @@ -271,7 +271,6 @@ public class PatchBukkitBlock implements Block { return breakNaturally(tool, triggerEffect, dropExperience, true); } - @Override public boolean breakNaturally(@Nullable ItemStack tool, boolean triggerEffect, boolean dropExperience, boolean dropAsItem) { setType(Material.AIR); return true; @@ -395,43 +394,36 @@ public class PatchBukkitBlock implements Block { @Override public String translationKey() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'translationKey'"); + return getType().getTranslationKey(); } @Override public byte getData() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getData'"); + return 0; } @Override public byte getLightLevel() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getLightLevel'"); + return 15; } @Override public byte getLightFromSky() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getLightFromSky'"); + return 15; } @Override public byte getLightFromBlocks() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getLightFromBlocks'"); + return 0; } @Override public boolean isValidTool(@NotNull ItemStack tool) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'isValidTool'"); + return true; } @Override public @NotNull Chunk getChunk() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getChunk'"); + return this.world.getChunkAt(this); } } diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/world/PatchBukkitChunk.java b/java/patchbukkit/src/main/java/org/patchbukkit/world/PatchBukkitChunk.java new file mode 100644 index 0000000..0d78ab0 --- /dev/null +++ b/java/patchbukkit/src/main/java/org/patchbukkit/world/PatchBukkitChunk.java @@ -0,0 +1,191 @@ +package org.patchbukkit.world; + +import org.bukkit.Chunk; +import org.bukkit.ChunkSnapshot; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.Entity; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.Collections; + +public class PatchBukkitChunk implements Chunk { + private final World world; + private final int x; + private final int z; + + public PatchBukkitChunk(World world, int x, int z) { + this.world = world; + this.x = x; + this.z = z; + } + + @Override + public int getX() { + return this.x; + } + + @Override + public int getZ() { + return this.z; + } + + @Override + public @NotNull World getWorld() { + return this.world; + } + + @Override + public @NotNull Block getBlock(int x, int y, int z) { + return this.world.getBlockAt((this.x << 4) + x, y, (this.z << 4) + z); + } + + @Override + public @NotNull ChunkSnapshot getChunkSnapshot() { + return getChunkSnapshot(true, false, false); + } + + @Override + public @NotNull ChunkSnapshot getChunkSnapshot(boolean includeMaxBlocky, boolean includeBiome, boolean includeBiomeTempRain) { + return getChunkSnapshot(includeMaxBlocky, includeBiome, includeBiomeTempRain, false); + } + + @Override + public @NotNull ChunkSnapshot getChunkSnapshot(boolean includeMaxBlocky, boolean includeBiome, boolean includeBiomeTempRain, boolean includeBiome3D) { + throw new UnsupportedOperationException("Unimplemented method 'getChunkSnapshot'"); + } + + @Override + public boolean isEntitiesLoaded() { + return true; + } + + @Override + public @NotNull Entity[] getEntities() { + return new Entity[0]; + } + + @Override + public @NotNull BlockState[] getTileEntities() { + return new BlockState[0]; + } + + @Override + public @NotNull BlockState[] getTileEntities(boolean useSnapshot) { + return new BlockState[0]; + } + + @Override + public boolean isGenerated() { + return true; + } + + @Override + public boolean isLoaded() { + return true; + } + + @Override + public boolean load(boolean generate) { + return true; + } + + @Override + public boolean load() { + return true; + } + + @Override + public boolean unload(boolean save) { + return true; + } + + @Override + public boolean unload() { + return true; + } + + @Override + public boolean isSlimeChunk() { + return false; + } + + @Override + public boolean isForceLoaded() { + return false; + } + + @Override + public void setForceLoaded(boolean progress) { + } + + @Override + public boolean addPluginChunkTicket(@NotNull Plugin plugin) { + return true; + } + + @Override + public boolean removePluginChunkTicket(@NotNull Plugin plugin) { + return true; + } + + @Override + public @NotNull Collection getPluginChunkTickets() { + return Collections.emptyList(); + } + + @Override + public long getInhabitedTime() { + return 0; + } + + @Override + public void setInhabitedTime(long ticks) { + } + + @Override + public boolean contains(@NotNull BlockData block) { + return false; + } + + @Override + public @NotNull Collection getPlayersSeeingChunk() { + return Collections.emptyList(); + } + + @Override + public @NotNull Collection getStructures() { + return Collections.emptyList(); + } + + @Override + public @NotNull Collection getStructures(@NotNull org.bukkit.generator.structure.Structure structure) { + return Collections.emptyList(); + } + + @Override + public @NotNull LoadLevel getLoadLevel() { + return LoadLevel.ENTITY_TICKING; + } + + @Override + public boolean contains(@NotNull org.bukkit.block.Biome biome) { + return false; + } + + private final org.bukkit.persistence.PersistentDataContainer pdc = new org.patchbukkit.persistence.PatchBukkitPersistentDataContainer(); + + @Override + public @NotNull org.bukkit.persistence.PersistentDataContainer getPersistentDataContainer() { + return this.pdc; + } + + @Override + public @NotNull Collection getTileEntities(@NotNull java.util.function.Predicate blockPredicate, boolean useSnapshot) { + return Collections.emptyList(); + } +} diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/world/PatchBukkitWorld.java b/java/patchbukkit/src/main/java/org/patchbukkit/world/PatchBukkitWorld.java index 0d6d5b3..d409dad 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/world/PatchBukkitWorld.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/world/PatchBukkitWorld.java @@ -37,6 +37,7 @@ import org.bukkit.util.*; import org.patchbukkit.bridge.BridgeUtils; import patchbukkit.bridge.NativeBridgeFfi; import patchbukkit.world.GetBlockDataRequest; +import patchbukkit.world.GetWorldInfoRequest; import patchbukkit.world.SetBlockDataRequest; import patchbukkit.world.SpawnParticleRequest; import org.checkerframework.checker.index.qual.Positive; @@ -169,26 +170,17 @@ public class PatchBukkitWorld @Override public @NotNull Chunk getChunkAt(int x, int z) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException( - "Unimplemented method 'getChunkAt'" - ); + return new PatchBukkitChunk(this, x, z); } @Override public @NotNull Chunk getChunkAt(int x, int z, boolean generate) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException( - "Unimplemented method 'getChunkAt'" - ); + return new PatchBukkitChunk(this, x, z); } @Override public @NotNull Chunk getChunkAt(@NotNull Block block) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException( - "Unimplemented method 'getChunkAt'" - ); + return new PatchBukkitChunk(this, block.getX() >> 4, block.getZ() >> 4); } @Override @@ -1203,10 +1195,18 @@ public class PatchBukkitWorld float volume, float pitch ) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException( - "Unimplemented method 'playSound'" - ); + if (location == null || sound == null) return; + var patchBukkitSound = (org.patchbukkit.registry.PatchBukkitSound) sound; + var request = patchbukkit.sound.PlayerPlaySoundRequest.newBuilder() + .setLocation(org.patchbukkit.bridge.BridgeUtils.convertLocation(location)) + .setSound( + patchbukkit.sound.Sound.newBuilder() + .setCategory(category.name()) + .setName(patchBukkitSound.getOriginalName()) + ).setVolume(volume) + .setPitch(pitch) + .build(); + NativeBridgeFfi.playerPlaySound(request); } @Override @@ -1398,10 +1398,16 @@ public class PatchBukkitWorld @org.jspecify.annotations.Nullable T data, boolean force ) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException( - "Unimplemented method 'spawnParticle'" - ); + if (particle == null) return; + var request = patchbukkit.world.SpawnParticleRequest.newBuilder() + .setWorldUuid(org.patchbukkit.bridge.BridgeUtils.convertUuid(this.getUID())) + .setParticle(particle.name().toLowerCase()) + .setX(x).setY(y).setZ(z) + .setCount(count) + .setOffsetX(offsetX).setOffsetY(offsetY).setOffsetZ(offsetZ) + .setExtra(extra) + .build(); + NativeBridgeFfi.spawnParticle(request); } @Override @@ -1608,26 +1614,42 @@ public class PatchBukkitWorld @Override public long getSeed() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException( - "Unimplemented method 'getSeed'" - ); + try { + var info = NativeBridgeFfi.getWorldInfo(GetWorldInfoRequest.newBuilder() + .setWorldUuid(BridgeUtils.convertUuid(this.uuid)) + .build()); + return info.getSeed(); + } catch (Throwable t) { + return 0L; + } } @Override public int getMinHeight() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException( - "Unimplemented method 'getMinHeight'" - ); + try { + var info = NativeBridgeFfi.getWorldInfo(GetWorldInfoRequest.newBuilder() + .setWorldUuid(BridgeUtils.convertUuid(this.uuid)) + .build()); + return info.getMinHeight(); + } catch (Throwable t) { + return -64; + } } @Override public int getMaxHeight() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException( - "Unimplemented method 'getMaxHeight'" - ); + try { + var info = NativeBridgeFfi.getWorldInfo(GetWorldInfoRequest.newBuilder() + .setWorldUuid(BridgeUtils.convertUuid(this.uuid)) + .build()); + return info.getMaxHeight(); + } catch (Throwable t) { + return 320; + } + } + + public int getHeight() { + return getMaxHeight() - getMinHeight(); } @Override @@ -1722,7 +1744,6 @@ public class PatchBukkitWorld @Override public @NotNull List locateAllPoiInRange(@NotNull Location origin, @NotNull Predicate poiTypePredicate, @Positive int radius, @NotNull Occupancy occupancy) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'locateAllPoiInRange'"); + return Collections.emptyList(); } } diff --git a/java/patchbukkit/src/main/resources/META-INF/services/io.papermc.paper.ServerBuildInfo b/java/patchbukkit/src/main/resources/META-INF/services/io.papermc.paper.ServerBuildInfo deleted file mode 100644 index 7ff0582..0000000 --- a/java/patchbukkit/src/main/resources/META-INF/services/io.papermc.paper.ServerBuildInfo +++ /dev/null @@ -1 +0,0 @@ -org.patchbukkit.PatchBukkitServerBuildInfo diff --git a/java/patchbukkit/src/test/java/org/patchbukkit/loader/LibraryResolverTest.java b/java/patchbukkit/src/test/java/org/patchbukkit/loader/LibraryResolverTest.java new file mode 100644 index 0000000..e208174 --- /dev/null +++ b/java/patchbukkit/src/test/java/org/patchbukkit/loader/LibraryResolverTest.java @@ -0,0 +1,63 @@ +package org.patchbukkit.loader; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.File; +import java.io.FileOutputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; + +import static org.junit.jupiter.api.Assertions.*; + +public class LibraryResolverTest { + + @Test + public void testLibraryResolverHandlesCustomReposAndCoords(@TempDir File tempDir) { + String input = "repo:https://repo.miraculixx.de/snapshots\nde.miraculixx:kpaper:1.1.2"; + List resolved = LibraryResolver.resolveLibraries(input, tempDir); + assertNotNull(resolved); + } + + @Test + public void testNestedJarAndPaperLibrariesExtraction(@TempDir File tempDir) throws Exception { + File dummyJar = new File(tempDir, "test-plugin.jar"); + try (ZipArchiveOutputStreamOrFileOutputStream fos = new ZipArchiveOutputStreamOrFileOutputStream(dummyJar); + JarOutputStream jos = new JarOutputStream(fos)) { + + // Add paper-plugin.yml + jos.putNextEntry(new JarEntry("paper-plugin.yml")); + String paperYml = "name: TestPlugin\nversion: 1.0.0\nmain: org.test.TestMain\nlibraries:\n - \"repo:https://repo.miraculixx.de\"\n - \"de.miraculixx:kpaper:1.1.2\"\n"; + jos.write(paperYml.getBytes(StandardCharsets.UTF_8)); + jos.closeEntry(); + + // Add paper-libraries.json + jos.putNextEntry(new JarEntry("paper-libraries.json")); + String paperJson = "{\n \"repositories\": {\n \"miraculixx\": \"https://repo.miraculixx.de\"\n },\n \"dependencies\": [\n \"de.miraculixx:kpaper:1.1.2\"\n ]\n}"; + jos.write(paperJson.getBytes(StandardCharsets.UTF_8)); + jos.closeEntry(); + + // Add nested jar + jos.putNextEntry(new JarEntry("META-INF/jars/nested-lib.jar")); + jos.write(new byte[]{0x50, 0x4B, 0x03, 0x04}); // PK header + jos.closeEntry(); + } + + PatchBukkitPluginClassLoader loader = new PatchBukkitPluginClassLoader( + LibraryResolverTest.class.getClassLoader(), + dummyJar + ); + + assertNotNull(loader.getDescription()); + assertEquals("TestPlugin", loader.getDescription().getName()); + loader.close(); + } + + private static class ZipArchiveOutputStreamOrFileOutputStream extends FileOutputStream { + public ZipArchiveOutputStreamOrFileOutputStream(File file) throws Exception { + super(file); + } + } +} diff --git a/java/patchbukkit/src/test/java/org/patchbukkit/registry/PatchBukkitRegistryAccessTest.java b/java/patchbukkit/src/test/java/org/patchbukkit/registry/PatchBukkitRegistryAccessTest.java new file mode 100644 index 0000000..f43e2cd --- /dev/null +++ b/java/patchbukkit/src/test/java/org/patchbukkit/registry/PatchBukkitRegistryAccessTest.java @@ -0,0 +1,143 @@ +package org.patchbukkit.registry; + +import io.papermc.paper.registry.RegistryKey; +import org.bukkit.Material; +import org.bukkit.Registry; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.ItemType; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class PatchBukkitRegistryAccessTest { + + @BeforeEach + public void setUp() { + if (org.bukkit.Bukkit.getServer() == null) { + org.patchbukkit.PatchBukkitServer server = new org.patchbukkit.PatchBukkitServer(); + org.bukkit.Bukkit.setServer(server); + } + } + + @Test + public void testServerCommandMapReflection() throws Throwable { + org.patchbukkit.PatchBukkitServer server = new org.patchbukkit.PatchBukkitServer(); + + java.lang.reflect.Field field = org.patchbukkit.PatchBukkitServer.class.getDeclaredField("commandMap"); + field.setAccessible(true); + Object mapObj = field.get(server); + assertNotNull(mapObj); + assertEquals(server.getCommandMap(), mapObj); + assertTrue(mapObj instanceof org.bukkit.command.SimpleCommandMap, "commandMap field must be an instance of SimpleCommandMap for legacy reflection compatibility"); + assertTrue(server.getCommandMap() instanceof org.bukkit.command.SimpleCommandMap, "getCommandMap() must return an instance of SimpleCommandMap"); + } + + @Test + public void testPatchBukkitHelpMapRegistration() { + org.patchbukkit.help.PatchBukkitHelpMap helpMap = new org.patchbukkit.help.PatchBukkitHelpMap(); + assertNotNull(helpMap.getHelpTopics()); + assertDoesNotThrow(() -> helpMap.registerHelpTopicFactory(Object.class, (cmd) -> null)); + } + + @Test + public void testReentrantRegistryLookup() { + PatchBukkitRegistryAccess registryAccess = new PatchBukkitRegistryAccess(); + + assertDoesNotThrow(() -> { + Registry itemReg = registryAccess.getRegistry(RegistryKey.ITEM); + Registry soundReg = registryAccess.getRegistry(RegistryKey.SOUND_EVENT); + Registry blockReg = registryAccess.getRegistry(RegistryKey.BLOCK); + + assertNotNull(itemReg); + assertNotNull(soundReg); + assertNotNull(blockReg); + }); + } + + @Test + public void testInspectBlockTypeAndDataMethods() { + System.out.println("=== BLOCK TYPE METHODS ==="); + for (java.lang.reflect.Method m : org.bukkit.block.BlockType.class.getMethods()) { + System.out.println("BlockType method: " + m.getName() + " -> " + m.getReturnType().getName() + " args=" + java.util.Arrays.toString(m.getParameterTypes())); + } + System.out.println("=== BLOCK DATA METHODS ==="); + for (java.lang.reflect.Method m : org.bukkit.block.data.BlockData.class.getMethods()) { + System.out.println("BlockData method: " + m.getName() + " -> " + m.getReturnType().getName() + " args=" + java.util.Arrays.toString(m.getParameterTypes())); + } + } + + @Test + public void testBlockTypeRegistryLookup() { + PatchBukkitRegistryAccess registryAccess = new PatchBukkitRegistryAccess(); + Registry blockReg = registryAccess.getRegistry(RegistryKey.BLOCK); + assertNotNull(blockReg); + + org.bukkit.block.BlockType stoneType = blockReg.get(org.bukkit.NamespacedKey.minecraft("stone")); + assertNotNull(stoneType, "BlockType for minecraft:stone should be found in BLOCK registry"); + assertEquals(Material.STONE, stoneType.asMaterial()); + assertEquals(org.bukkit.NamespacedKey.minecraft("stone"), stoneType.getKey()); + + org.bukkit.block.data.BlockData data = stoneType.createBlockData(); + assertNotNull(data); + assertEquals(Material.STONE, data.getMaterial()); + + assertNotNull(stoneType.createBlockDataStates(), "createBlockDataStates() must not be null"); + assertFalse(stoneType.createBlockDataStates().isEmpty(), "createBlockDataStates() must not be empty"); + assertEquals(org.bukkit.block.data.BlockData.class, stoneType.getBlockDataClass()); + + Registry itemReg = registryAccess.getRegistry(RegistryKey.ITEM); + ItemType airType = itemReg.get(org.bukkit.NamespacedKey.minecraft("air")); + assertNotNull(airType, "ItemType for minecraft:air must be found in ITEM registry"); + } + + @Test + public void testCreateBlockDataFromString() { + if (org.bukkit.Bukkit.getServer() == null) { + org.patchbukkit.PatchBukkitServer server = new org.patchbukkit.PatchBukkitServer(); + org.bukkit.Bukkit.setServer(server); + } + + org.bukkit.block.data.BlockData stoneData = org.bukkit.Bukkit.createBlockData("stone"); + assertNotNull(stoneData); + assertEquals(Material.STONE, stoneData.getMaterial()); + assertEquals("minecraft:stone", stoneData.getAsString()); + + org.bukkit.block.data.BlockData chestData = org.bukkit.Bukkit.createBlockData("minecraft:chest[facing=north]"); + assertNotNull(chestData); + assertEquals(Material.CHEST, chestData.getMaterial()); + assertTrue(chestData.getAsString().contains("chest")); + } + + @Test + public void testCreateBlockDataMaterial() { + org.bukkit.block.data.BlockData matData = org.bukkit.Bukkit.createBlockData(Material.STONE); + assertNotNull(matData); + assertEquals(Material.STONE, matData.getMaterial()); + } + + @Test + public void testNMSClassAvailability() { + assertDoesNotThrow(() -> { + try { + net.minecraft.SharedConstants.tryDetectVersion(); + net.minecraft.server.Bootstrap.bootStrap(); + } catch (Throwable ignored) {} + Class nmsBlock = Class.forName("net.minecraft.world.level.block.Block"); + assertNotNull(nmsBlock, "NMS Block class must be present on classpath"); + Class nmsRegistries = Class.forName("net.minecraft.core.registries.BuiltInRegistries"); + assertNotNull(nmsRegistries, "NMS BuiltInRegistries class must be present on classpath"); + }); + } + + @Test + public void testWorldEditAdapterInstantiation() { + try { + Class pluginCls = Class.forName("com.sk89q.worldedit.bukkit.WorldEditPlugin"); + assertNotNull(pluginCls, "WorldEditPlugin class must be present"); + } catch (Throwable t) { + System.err.println("WorldEdit Adapter Exception Stacktrace:"); + t.printStackTrace(); + } + } +} diff --git a/java/protoc-gen-ffi/src/main/java/org/patchbukkit/protocgen/FfiGenerator.java b/java/protoc-gen-ffi/src/main/java/org/patchbukkit/protocgen/FfiGenerator.java index d367f5f..beabd9f 100644 --- a/java/protoc-gen-ffi/src/main/java/org/patchbukkit/protocgen/FfiGenerator.java +++ b/java/protoc-gen-ffi/src/main/java/org/patchbukkit/protocgen/FfiGenerator.java @@ -139,24 +139,34 @@ public class FfiGenerator { params.add("long " + toCamelCase(method.getName()) + "Addr"); } sb.append(params).append(") {\n"); + sb.append(" try {\n"); for (MethodDescriptorProto method : methods) { String handleName = toHandleName(method); String addrName = toCamelCase(method.getName()) + "Addr"; - sb.append(" ").append(handleName).append(" = LINKER.downcallHandle(\n"); - sb.append(" MemorySegment.ofAddress(").append(addrName).append("),\n"); - sb.append(" FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS, "); + sb.append(" ").append(handleName).append(" = LINKER.downcallHandle(\n"); + sb.append(" MemorySegment.ofAddress(").append(addrName).append("),\n"); + sb.append(" FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS, "); sb.append("ValueLayout.JAVA_LONG, ValueLayout.ADDRESS));\n"); } + sb.append(" } catch (Throwable t) {\n"); + sb.append(" System.err.println(\"[PatchBukkit FFI] Failed init: \" + t.getMessage());\n"); + sb.append(" t.printStackTrace();\n"); + sb.append(" }\n"); sb.append(" }\n\n"); } private static void generateInitFreeMethod(StringBuilder sb) { sb.append(" public static void initFree(long freeAddr) {\n"); - sb.append(" freeNative = LINKER.downcallHandle(\n"); - sb.append(" MemorySegment.ofAddress(freeAddr),\n"); - sb.append(" FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.JAVA_LONG));\n"); + sb.append(" try {\n"); + sb.append(" freeNative = LINKER.downcallHandle(\n"); + sb.append(" MemorySegment.ofAddress(freeAddr),\n"); + sb.append(" FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.JAVA_LONG));\n"); + sb.append(" } catch (Throwable t) {\n"); + sb.append(" System.err.println(\"[PatchBukkit FFI] Failed initFree: \" + t.getMessage());\n"); + sb.append(" t.printStackTrace();\n"); + sb.append(" }\n"); sb.append(" }\n\n"); } diff --git a/java/settings.gradle.kts b/java/settings.gradle.kts index a50f741..02bfc10 100644 --- a/java/settings.gradle.kts +++ b/java/settings.gradle.kts @@ -5,8 +5,17 @@ * For more detailed information on multi-project builds, please refer to https://docs.gradle.org/8.12.1/userguide/multi_project_builds.html in the Gradle documentation. */ +pluginManagement { + repositories { + gradlePluginPortal() + maven { + name = "papermc" + url = uri("https://repo.papermc.io/repository/maven-public/") + } + } +} + plugins { - // Apply the foojay-resolver plugin to allow automatic download of JDKs id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" } diff --git a/proto/patchbukkit/bridge.proto b/proto/patchbukkit/bridge.proto index e636b9d..02c609a 100644 --- a/proto/patchbukkit/bridge.proto +++ b/proto/patchbukkit/bridge.proto @@ -4,10 +4,12 @@ package patchbukkit.bridge; import "google/protobuf/empty.proto"; import "google/protobuf/wrappers.proto"; import "patchbukkit/abilities.proto"; +import "patchbukkit/command.proto"; import "patchbukkit/common/types.proto"; import "patchbukkit/config.proto"; import "patchbukkit/entity.proto"; import "patchbukkit/events/event.proto"; +import "patchbukkit/itemstack.proto"; import "patchbukkit/log.proto"; import "patchbukkit/message.proto"; import "patchbukkit/registry.proto"; @@ -27,6 +29,15 @@ service NativeBridge { rpc TeleportEntity(patchbukkit.entity.TeleportEntityRequest) returns (google.protobuf.Empty); rpc GetGamemode(patchbukkit.common.UUID) returns (patchbukkit.entity.GetGamemodeResponse); rpc SetGamemode(patchbukkit.entity.SetGamemodeRequest) returns (google.protobuf.Empty); + rpc IsOnGround(patchbukkit.common.UUID) returns (patchbukkit.entity.IsOnGroundResponse); + rpc GetPlayerLocale(patchbukkit.common.UUID) returns (patchbukkit.entity.GetPlayerLocaleResponse); + rpc IsOp(patchbukkit.common.UUID) returns (patchbukkit.entity.IsOpResponse); + + rpc GetPlayerInventory(patchbukkit.common.UUID) returns (patchbukkit.itemstack.GetPlayerInventoryResponse); + rpc SetPlayerInventorySlot(patchbukkit.itemstack.SetPlayerInventorySlotRequest) returns (google.protobuf.Empty); + rpc SetPlayerSelectedSlot(patchbukkit.itemstack.SetPlayerSelectedSlotRequest) returns (google.protobuf.Empty); + rpc SetPlayerEquipment(patchbukkit.itemstack.SetPlayerEquipmentRequest) returns (google.protobuf.Empty); + rpc ClearPlayerInventory(patchbukkit.common.UUID) returns (google.protobuf.Empty); rpc GetBlockData(patchbukkit.world.GetBlockDataRequest) returns (patchbukkit.world.GetBlockDataResponse); rpc SetBlockData(patchbukkit.world.SetBlockDataRequest) returns (google.protobuf.Empty); @@ -50,4 +61,9 @@ service NativeBridge { rpc SendLog(patchbukkit.log.SendLogRequest) returns (google.protobuf.Empty); rpc GetPatchBukkitConfig(patchbukkit.common.EmptyRequest) returns (patchbukkit.config.GetPatchBukkitConfigResponse); + + rpc RegisterCommand(patchbukkit.command.RegisterCommandRequest) returns (google.protobuf.Empty); + + rpc GetWorldInfo(patchbukkit.world.GetWorldInfoRequest) returns (patchbukkit.world.GetWorldInfoResponse); } + diff --git a/proto/patchbukkit/command.proto b/proto/patchbukkit/command.proto new file mode 100644 index 0000000..b5aaf0f --- /dev/null +++ b/proto/patchbukkit/command.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +package patchbukkit.command; + +option java_multiple_files = true; +option java_package = "patchbukkit.command"; + +message RegisterCommandRequest { + string cmd_name = 1; + repeated string aliases = 2; + string description = 3; + string plugin_name = 4; +} diff --git a/proto/patchbukkit/entity.proto b/proto/patchbukkit/entity.proto index 3e9b48a..e4be0a9 100644 --- a/proto/patchbukkit/entity.proto +++ b/proto/patchbukkit/entity.proto @@ -54,5 +54,18 @@ message SetGamemodeRequest { int32 gamemode = 2; } +message IsOnGroundResponse { + bool on_ground = 1; +} + +message GetPlayerLocaleResponse { + string locale = 1; +} + +message IsOpResponse { + bool is_op = 1; +} + + diff --git a/proto/patchbukkit/events/block_events.proto b/proto/patchbukkit/events/block_events.proto new file mode 100644 index 0000000..98b6206 --- /dev/null +++ b/proto/patchbukkit/events/block_events.proto @@ -0,0 +1,72 @@ +syntax = "proto3"; +package patchbukkit.events; + +import "patchbukkit/common/types.proto"; + +option java_multiple_files = true; +option java_package = "patchbukkit.events"; + +message BlockBreakEvent { + patchbukkit.common.UUID player_uuid = 1; + int32 block_x = 2; + int32 block_y = 3; + int32 block_z = 4; + string block_type = 5; + uint32 exp = 6; + bool drop_items = 7; +} + +message BlockPlaceEvent { + patchbukkit.common.UUID player_uuid = 1; + int32 block_x = 2; + int32 block_y = 3; + int32 block_z = 4; + string block_placed_type = 5; + string block_against_type = 6; + bool can_build = 7; +} + +message SignChangeEvent { + patchbukkit.common.UUID player_uuid = 1; + int32 block_x = 2; + int32 block_y = 3; + int32 block_z = 4; + repeated string lines = 5; +} + +message BlockDamageEvent { + patchbukkit.common.UUID player_uuid = 1; + int32 block_x = 2; + int32 block_y = 3; + int32 block_z = 4; + bool insta_break = 5; +} + +message BlockIgniteEvent { + patchbukkit.common.UUID player_uuid = 1; + int32 block_x = 2; + int32 block_y = 3; + int32 block_z = 4; + string ignite_cause = 5; +} + +message BlockGrowEvent { + int32 block_x = 1; + int32 block_y = 2; + int32 block_z = 3; + string new_block_type = 4; +} + +message BlockFormEvent { + int32 block_x = 1; + int32 block_y = 2; + int32 block_z = 3; + string new_block_type = 4; +} + +message BlockFadeEvent { + int32 block_x = 1; + int32 block_y = 2; + int32 block_z = 3; + string new_block_type = 4; +} diff --git a/proto/patchbukkit/events/event.proto b/proto/patchbukkit/events/event.proto index 777eac3..1b96f0e 100644 --- a/proto/patchbukkit/events/event.proto +++ b/proto/patchbukkit/events/event.proto @@ -5,6 +5,7 @@ package patchbukkit.events; import "patchbukkit/common/types.proto"; import "patchbukkit/events/player_events.proto"; import "patchbukkit/events/server_events.proto"; +import "patchbukkit/events/block_events.proto"; option java_multiple_files = true; option java_package = "patchbukkit.events"; @@ -17,6 +18,21 @@ message Event { PlayerQuitEvent player_quit = 4; PlayerInteractEvent player_interact = 5; PlayerGameModeChangeEvent player_gamemode_change = 6; + BlockBreakEvent block_break = 7; + BlockPlaceEvent block_place = 8; + PlayerToggleSneakEvent player_toggle_sneak = 9; + PlayerToggleSprintEvent player_toggle_sprint = 10; + PlayerToggleFlightEvent player_toggle_flight = 11; + PlayerChatEvent player_chat = 12; + PlayerMoveEvent player_move = 13; + PlayerInteractEntityEvent player_interact_entity = 14; + SignChangeEvent sign_change = 15; + BlockDamageEvent block_damage = 16; + ServerCommandEvent server_command = 17; + BlockIgniteEvent block_ignite = 18; + BlockGrowEvent block_grow = 19; + BlockFormEvent block_form = 20; + BlockFadeEvent block_fade = 21; } } diff --git a/proto/patchbukkit/events/player_events.proto b/proto/patchbukkit/events/player_events.proto index d643070..dece73a 100644 --- a/proto/patchbukkit/events/player_events.proto +++ b/proto/patchbukkit/events/player_events.proto @@ -31,3 +31,38 @@ message PlayerGameModeChangeEvent { string new_gamemode = 2; } +message PlayerToggleSneakEvent { + patchbukkit.common.UUID player_uuid = 1; + bool is_sneaking = 2; +} + +message PlayerToggleSprintEvent { + patchbukkit.common.UUID player_uuid = 1; + bool is_sprinting = 2; +} + +message PlayerToggleFlightEvent { + patchbukkit.common.UUID player_uuid = 1; + bool is_flying = 2; +} + +message PlayerChatEvent { + patchbukkit.common.UUID player_uuid = 1; + string message = 2; + string format = 3; +} + +message PlayerMoveEvent { + patchbukkit.common.UUID player_uuid = 1; + patchbukkit.common.Location from = 2; + patchbukkit.common.Location to = 3; +} + +message PlayerInteractEntityEvent { + patchbukkit.common.UUID player_uuid = 1; + patchbukkit.common.UUID target_uuid = 2; + string action = 3; + bool is_sneaking = 4; +} + + diff --git a/proto/patchbukkit/events/server_events.proto b/proto/patchbukkit/events/server_events.proto index ad08eae..b52c362 100644 --- a/proto/patchbukkit/events/server_events.proto +++ b/proto/patchbukkit/events/server_events.proto @@ -13,3 +13,8 @@ message PluginEnableEvent { message PluginDisableEvent { string plugin_name = 1; } + +message ServerCommandEvent { + string sender_name = 1; + string command = 2; +} diff --git a/proto/patchbukkit/itemstack.proto b/proto/patchbukkit/itemstack.proto index 75429d3..070f1f8 100644 --- a/proto/patchbukkit/itemstack.proto +++ b/proto/patchbukkit/itemstack.proto @@ -16,3 +16,31 @@ message ItemStack { string type = 1; uint32 amount = 2; } + +message GetPlayerInventoryResponse { + repeated ItemStack main_inventory = 1; + uint32 selected_slot = 2; + ItemStack off_hand = 3; + ItemStack helmet = 4; + ItemStack chestplate = 5; + ItemStack leggings = 6; + ItemStack boots = 7; +} + +message SetPlayerInventorySlotRequest { + patchbukkit.common.UUID uuid = 1; + uint32 slot = 2; + ItemStack item = 3; +} + +message SetPlayerSelectedSlotRequest { + patchbukkit.common.UUID uuid = 1; + uint32 slot = 2; +} + +message SetPlayerEquipmentRequest { + patchbukkit.common.UUID uuid = 1; + uint32 slot_type = 2; + ItemStack item = 3; +} + diff --git a/proto/patchbukkit/world.proto b/proto/patchbukkit/world.proto index f7fe801..3154ef1 100644 --- a/proto/patchbukkit/world.proto +++ b/proto/patchbukkit/world.proto @@ -66,4 +66,15 @@ message SetWorldBorderRequest { WorldBorderData border = 2; } +message GetWorldInfoRequest { + patchbukkit.common.UUID world_uuid = 1; +} + +message GetWorldInfoResponse { + int32 min_height = 1; + int32 max_height = 2; + int32 height = 3; + int64 seed = 4; +} + diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 85169bb..ca35ef9 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -17,35 +17,57 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common 0.1.7", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher 0.4.4", + "cpufeatures 0.2.17", +] + [[package]] name = "aes" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8eb277bec05f56a0e0591f155a484cbd0f4f07ff2905051a48c72f004f7ed58" dependencies = [ - "cipher", + "cipher 0.5.2", "cpubits", "cpufeatures 0.3.0", ] [[package]] -name = "ahash" -version = "0.8.12" +name = "aes-gcm" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" dependencies = [ - "cfg-if", - "getrandom 0.3.4", - "once_cell", - "version_check", - "zerocopy", + "aead", + "aes 0.8.4", + "cipher 0.4.4", + "ctr", + "ghash", + "subtle", ] [[package]] name = "aho-corasick" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" dependencies = [ "memchr", ] @@ -150,6 +172,45 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" +[[package]] +name = "asn1-rs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "async-compression" version = "0.4.43" @@ -164,9 +225,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.91" +version = "0.1.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", @@ -194,6 +255,58 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +[[package]] +name = "axum" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" +dependencies = [ + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base16ct" version = "1.0.0" @@ -208,9 +321,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64" -version = "0.23.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b25655df2c3cdd83c5e5b293b88acd880332b2ddadd7c30ac43144fdc0033da9" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" [[package]] name = "base64ct" @@ -249,6 +362,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + [[package]] name = "bumpalo" version = "3.20.3" @@ -258,6 +380,29 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "bytecheck" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26333eeac754f0ad8a6bcd0eb0ac012156302e4e16b852b72ee399aea4f12c29" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "rancor", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46d07918caa9eeaaf06b7873925c53a61daac173539b4f7715090745e44e4e69" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + [[package]] name = "bytemuck" version = "1.25.2" @@ -334,10 +479,19 @@ dependencies = [ ] [[package]] -name = "cc" -version = "1.4.0" +name = "cbc" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher 0.4.4", +] + +[[package]] +name = "cc" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d262e149917187838d5b42777c8253bcb64500067342904e7d429499a6f277e" dependencies = [ "find-msvc-tools", "jobserver", @@ -345,6 +499,18 @@ dependencies = [ "shlex", ] +[[package]] +name = "ccm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae3c82e4355234767756212c570e29833699ab63e6ffd161887314cc5b43847" +dependencies = [ + "aead", + "cipher 0.4.4", + "ctr", + "subtle", +] + [[package]] name = "cesu8" version = "1.1.0" @@ -357,7 +523,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f2b823af3723db9d6326eed3102949445a3323378bab7d6ecb9107ff3cb9a08" dependencies = [ - "cipher", + "cipher 0.5.2", ] [[package]] @@ -372,6 +538,17 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher 0.4.4", + "cpufeatures 0.2.17", +] + [[package]] name = "chacha20" version = "0.10.1" @@ -380,7 +557,31 @@ checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" dependencies = [ "cfg-if", "cpufeatures 0.3.0", - "rand_core", + "rand_core 0.10.1", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20 0.9.1", + "cipher 0.4.4", + "poly1305", + "zeroize", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common 0.1.7", + "inout 0.1.4", + "zeroize", ] [[package]] @@ -389,9 +590,8 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" dependencies = [ - "block-buffer 0.12.1", "crypto-common 0.2.2", - "inout", + "inout 0.2.2", ] [[package]] @@ -415,7 +615,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" dependencies = [ - "thiserror 2.0.19", + "thiserror 2.0.20", ] [[package]] @@ -449,6 +649,12 @@ version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789" +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "const-oid" version = "0.10.2" @@ -525,27 +731,27 @@ dependencies = [ [[package]] name = "cranelift-assembler-x64" -version = "0.134.2" +version = "0.134.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f0435c6939d6ef218bf0a3063094f2cc5c46f7a44a88f81cf932da8d3922ff8" +checksum = "d552bd33b7a56dc70aeb1e1c960e51a218fa0db50f23873b500a310379450b2d" dependencies = [ "cranelift-assembler-x64-meta", ] [[package]] name = "cranelift-assembler-x64-meta" -version = "0.134.2" +version = "0.134.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aca52511cc21a2c9c927aef493d11c8087bdc5483249a3d93aee41e81b8bd4d1" +checksum = "078e80e4c222279e3330f6aa1a256ca77ddf156d4453166a9f09defedc4594dd" dependencies = [ "cranelift-srcgen", ] [[package]] name = "cranelift-bforest" -version = "0.134.2" +version = "0.134.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6867e99f068454b0c81c50b807af88aa80aaf383105e2be26675d81fc4278466" +checksum = "820ce15d4ad3562d613c31a67b6d0434d403e7091a68d1349903842f7d31737e" dependencies = [ "cranelift-entity", "wasmtime-internal-core", @@ -553,9 +759,9 @@ dependencies = [ [[package]] name = "cranelift-bitset" -version = "0.134.2" +version = "0.134.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf66017aed971344230199ad35ca01c955cf824a0e65fed5240452d2a302445" +checksum = "61bca563d4b86d285928d9e27f97f27039bb33a0fc524fa130d7d0c106bf8ab3" dependencies = [ "serde", "serde_derive", @@ -564,9 +770,9 @@ dependencies = [ [[package]] name = "cranelift-codegen" -version = "0.134.2" +version = "0.134.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "933b2e5237665bf9367a34ad2100eed70c94b2796586cb09d9ba11693e061667" +checksum = "709f4b7c0fb57d952658d5b5c07fbdc4149acd7b7f0de9678ae754b9c981949a" dependencies = [ "bumpalo", "cranelift-assembler-x64", @@ -595,9 +801,9 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.134.2" +version = "0.134.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322e3ca0603683cc17f0c808559c0fb85ad52b0f20ace0478bdd4ba2fa1e9678" +checksum = "903dc8915af62aad1d9d0f39a5968d33fa80b9aa899ed6f56e47f40ca4512e1e" dependencies = [ "cranelift-assembler-x64-meta", "cranelift-codegen-shared", @@ -608,24 +814,24 @@ dependencies = [ [[package]] name = "cranelift-codegen-shared" -version = "0.134.2" +version = "0.134.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "287ab88211d049178dc8dda243e2db176919d8b29db605c0efc7c6d76ebea0f7" +checksum = "0522d74c227e49f3fd49ab055311486ae4f09083262b66705bed676952491469" [[package]] name = "cranelift-control" -version = "0.134.2" +version = "0.134.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c11438f9becf9a1e1e061bf76a33241b941a07ed01a489e9dad5fa79dc3624e9" +checksum = "66560ea1c5cef72e170b18e46d263dba2d3169c9d39e8cafdab2173c6362cc1a" dependencies = [ "arbitrary", ] [[package]] name = "cranelift-entity" -version = "0.134.2" +version = "0.134.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "584ecabe1462593a9cba9efbf9022e0987421ddb548989b455ab8bdd444b743c" +checksum = "b62ef5b17cc814d27e96b66a5b46da0e4ce2b8ac55a6d478048bb99d04b05526" dependencies = [ "cranelift-bitset", "serde", @@ -635,9 +841,9 @@ dependencies = [ [[package]] name = "cranelift-frontend" -version = "0.134.2" +version = "0.134.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34249873145213a12106d5fd8a98a9dc9e90e16525e2337b2907874d1e7e9ee7" +checksum = "a87e0aaa39dbf70693b348a221e45904111704ee8f9fef140498471005f9842d" dependencies = [ "cranelift-codegen", "hashbrown 0.17.1", @@ -648,15 +854,15 @@ dependencies = [ [[package]] name = "cranelift-isle" -version = "0.134.2" +version = "0.134.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6f1c1dc4d6b4bc4f0be12b08cee0e59f7610287827aa62590f8e9ec6f45dff4" +checksum = "cf79003ebfa1eed5e87f3b84446ad5236f540268289960e14f387dc9b28e40b7" [[package]] name = "cranelift-native" -version = "0.134.2" +version = "0.134.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31d9e4f6d9a3777b1ffa818000b4a6d7afd1b8787af71a9c58d94baef9076ced" +checksum = "05bf4f235743c81e67ee4db617c5a4a0b65d58d3f0cfc575ee0f1a4e0cd58273" dependencies = [ "cranelift-codegen", "libc", @@ -665,9 +871,24 @@ dependencies = [ [[package]] name = "cranelift-srcgen" -version = "0.134.2" +version = "0.134.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4155f8a1afdef96a0272c67804926eee4ef35f9708ab22ad6878d4c9543df596" +checksum = "f6977c2a71ab1e0d1e62f966b411a498aa04c4dce47d93d52f8a360a06058922" + +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" [[package]] name = "crc-fast" @@ -763,6 +984,18 @@ dependencies = [ "smallvec", ] +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-bigint" version = "0.7.5" @@ -774,7 +1007,7 @@ dependencies = [ "getrandom 0.4.3", "hybrid-array", "num-traits", - "rand_core", + "rand_core 0.10.1", "serdect", "subtle", "zeroize", @@ -787,6 +1020,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] @@ -798,7 +1032,7 @@ checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ "getrandom 0.4.3", "hybrid-array", - "rand_core", + "rand_core 0.10.1", ] [[package]] @@ -807,17 +1041,17 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3633a51a39c69ebbaa4feaa694bd83d241e4093901c84a0963b19d9bb3f0cf8f" dependencies = [ - "crypto-bigint", - "rand_core", + "crypto-bigint 0.7.5", + "rand_core 0.10.1", ] [[package]] name = "ctr" -version = "0.10.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baaca1c4b237092596f64d571e9db6ce4109c4ef9742e27590f1709594461f21" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ - "cipher", + "cipher 0.4.4", ] [[package]] @@ -830,6 +1064,47 @@ dependencies = [ "subtle", ] +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "fiat-crypto 0.2.9", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "curve25519-dalek-derive", + "digest 0.11.3", + "fiat-crypto 0.3.0", + "rustc_version", + "subtle", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "dashmap" version = "6.2.1" @@ -844,6 +1119,12 @@ dependencies = [ "parking_lot_core", ] +[[package]] +name = "data-encoding" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" + [[package]] name = "deflate64" version = "0.1.12" @@ -878,7 +1159,18 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" dependencies = [ - "thiserror 2.0.19", + "thiserror 2.0.20", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid 0.9.6", + "pem-rfc7468 0.7.0", + "zeroize", ] [[package]] @@ -887,11 +1179,25 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d" dependencies = [ - "const-oid", - "pem-rfc7468", + "const-oid 0.10.2", + "pem-rfc7468 1.0.0", "zeroize", ] +[[package]] +name = "der-parser" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint 0.4.8", + "num-traits", + "rusticata-macros", +] + [[package]] name = "deranged" version = "0.5.8" @@ -905,7 +1211,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", + "const-oid 0.9.6", "crypto-common 0.1.7", + "subtle", ] [[package]] @@ -915,7 +1223,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ "block-buffer 0.12.1", - "const-oid", + "const-oid 0.10.2", "crypto-common 0.2.2", "ctutils", "zeroize", @@ -983,50 +1291,141 @@ dependencies = [ "litrs", ] +[[package]] +name = "dtls" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01a431e87fc386bd5e02deb554a013f97bc406e47a7d8e97efb7c6366b980e9c" +dependencies = [ + "aes 0.8.4", + "aes-gcm", + "async-trait", + "bytecheck", + "byteorder", + "cbc", + "ccm", + "chacha20poly1305", + "der-parser", + "hmac 0.12.1", + "log", + "p256", + "p384 0.13.1", + "portable-atomic", + "rand 0.9.5", + "rand_core 0.6.4", + "rcgen", + "ring", + "rkyv", + "rustls", + "sec1 0.7.3", + "sha1 0.10.7", + "sha2 0.10.9", + "thiserror 1.0.69", + "tokio", + "webrtc-util", + "x25519-dalek", + "x509-parser", +] + [[package]] name = "dunce" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der 0.7.10", + "digest 0.10.7", + "elliptic-curve 0.13.8", + "rfc6979 0.4.0", + "signature 2.2.0", + "spki 0.7.3", +] + [[package]] name = "ecdsa" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0681a4fc24c767085329728d8dfba959af91228aa4610cca4f8ce317ba46ae0" dependencies = [ - "der", + "der 0.8.1", "digest 0.11.3", - "elliptic-curve", - "rfc6979", - "signature", - "spki", + "elliptic-curve 0.14.1", + "rfc6979 0.6.0", + "signature 3.0.0", + "spki 0.8.0", "zeroize", ] +[[package]] +name = "ed25519" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a" +dependencies = [ + "signature 3.0.0", +] + +[[package]] +name = "ed25519-dalek" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ebaa1a2bf1290ab3bfe5a7b771d050ebffab2711c19a81691c683a5144a25de" +dependencies = [ + "curve25519-dalek 5.0.0", + "ed25519", + "sha2 0.11.0", + "subtle", +] + [[package]] name = "either" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct 0.2.0", + "crypto-bigint 0.5.5", + "digest 0.10.7", + "ff 0.13.1", + "generic-array", + "group 0.13.0", + "hkdf", + "pem-rfc7468 0.7.0", + "pkcs8 0.10.2", + "rand_core 0.6.4", + "sec1 0.7.3", + "subtle", + "zeroize", +] + [[package]] name = "elliptic-curve" version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d65aa39b3a5c1c9c1b745c9a019234bb7a21b77abcb4f4d266d706e2d577d65" dependencies = [ - "base16ct", - "crypto-bigint", + "base16ct 1.0.0", + "crypto-bigint 0.7.5", "crypto-common 0.2.2", "digest 0.11.3", - "ff", - "group", - "hkdf", + "ff 0.14.0", + "group 0.14.0", "hybrid-array", - "pkcs8", - "rand_core", - "sec1", + "pkcs8 0.11.0", + "rand_core 0.10.1", + "sec1 0.8.1", "subtle", "zeroize", ] @@ -1054,25 +1453,13 @@ dependencies = [ [[package]] name = "encoding_rs_io" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cc3c5651fb62ab8aa3103998dade57efdd028544bd300516baa31840c252a83" +checksum = "fba3fe847045ecff794b9c138293a80db914678c453ad63fbf0c6a9eb6e00b22" dependencies = [ "encoding_rs", ] -[[package]] -name = "enum_dispatch" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" -dependencies = [ - "once_cell", - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "env_filter" version = "2.0.0" @@ -1133,16 +1520,32 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "ff" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1f686ab92a9fb0eaf188f6c6c87b89490baa6fdb0db4544ba4dc47f7942489f" dependencies = [ - "rand_core", + "rand_core 0.10.1", "subtle", ] +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "fiat-crypto" version = "0.3.0" @@ -1151,9 +1554,9 @@ checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" [[package]] name = "find-msvc-tools" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de" [[package]] name = "fixedbitset" @@ -1321,6 +1724,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -1341,11 +1745,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", - "js-sys", "libc", "r-efi 5.3.0", "wasip2", - "wasm-bindgen", ] [[package]] @@ -1358,10 +1760,20 @@ dependencies = [ "js-sys", "libc", "r-efi 6.0.0", - "rand_core", + "rand_core 0.10.1", "wasm-bindgen", ] +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "gimli" version = "0.33.0" @@ -1382,22 +1794,33 @@ checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" [[package]] name = "granit-parser" -version = "0.0.7" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d03f81ad4732830d85cfd417a9f62cde6dadda4354d37d078a6084a19560aa2d" +checksum = "7c92814c286f45b5ed1498ce9547bc9637b473eba7037f8aee6a4d5a3c1e051c" dependencies = [ "arraydeque", "smallvec", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff 0.13.1", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "group" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fd1a1c7a5206c5b7a3f5a0d7ccd3ff85d0c8f5133d62a02680255b0004af5f4" dependencies = [ - "ff", - "rand_core", + "ff 0.14.0", + "rand_core 0.10.1", "subtle", ] @@ -1482,12 +1905,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] -name = "hkdf" -version = "0.13.0" +name = "hex" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ - "hmac", + "hmac 0.12.1", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", ] [[package]] @@ -1577,6 +2015,21 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "bytes", + "http", + "http-body", + "hyper", + "pin-project-lite", + "tokio", + "tower-service", +] + [[package]] name = "icu_collections" version = "2.2.0" @@ -1731,6 +2184,16 @@ dependencies = [ "libc", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding", + "generic-array", +] + [[package]] name = "inout" version = "0.2.2" @@ -1740,6 +2203,27 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "interceptor" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88c11a956a48159f7fe539b8198f12b4db9b709ae5f94385b840db38f97fed74" +dependencies = [ + "async-trait", + "bytes", + "futures", + "log", + "portable-atomic", + "rand 0.9.5", + "rtcp", + "rtp", + "thiserror 1.0.69", + "tokio", + "waitgroup", + "webrtc-srtp", + "webrtc-util", +] + [[package]] name = "io-extras" version = "0.19.0" @@ -1764,9 +2248,9 @@ checksum = "2f0fb0570afe1fed943c5c3d4102d5358592d8625fda6a0007fdbe65a92fba96" [[package]] name = "ipnet" -version = "2.12.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" +checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" [[package]] name = "is_terminal_polyfill" @@ -1893,9 +2377,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.103" +version = "0.3.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" dependencies = [ "cfg-if", "futures-util", @@ -1904,9 +2388,9 @@ dependencies = [ [[package]] name = "kqueue" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "273c0752728918e0ac4976f2b275b6fefb9ecd400585dec929419f3844cd87b5" +checksum = "8d763e5b24120b4ddf50de6c92308156765aabfbbccebf401da7cff2d70a41ea" dependencies = [ "kqueue-sys", "libc", @@ -1974,9 +2458,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" +checksum = "2026a5056764a10b2bf5d56488cba40da507f5493a6a429340e2004d9ed085fa" dependencies = [ "libc", ] @@ -2054,6 +2538,12 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + [[package]] name = "maybe-owned" version = "0.3.4" @@ -2091,6 +2581,15 @@ dependencies = [ "rustix", ] +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + [[package]] name = "mime" version = "0.3.17" @@ -2107,6 +2606,12 @@ dependencies = [ "unicase", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -2145,6 +2650,39 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" +[[package]] +name = "munge" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e17401f259eba956ca16491461b6e8f72913a0a114e39736ce404410f915a0c" +dependencies = [ + "munge_macro", +] + +[[package]] +name = "munge_macro" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4568f25ccbd45ab5d5603dc34318c1ec56b117531781260002151b8530a9f931" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset", + "pin-utils", +] + [[package]] name = "nix" version = "0.31.3" @@ -2163,6 +2701,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "notify" version = "8.2.0" @@ -2208,6 +2756,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.5.1" @@ -2293,6 +2851,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "oid-registry" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" +dependencies = [ + "asn1-rs", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -2305,6 +2872,12 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "option-ext" version = "0.2.0" @@ -2320,17 +2893,41 @@ dependencies = [ "num-traits", ] +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", + "primeorder 0.13.6", + "sha2 0.10.9", +] + +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", + "primeorder 0.13.6", + "sha2 0.10.9", +] + [[package]] name = "p384" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d17b851e6b3e378ab4ecb07fa2ed23f4d15f075735f8fec9fa1e7bdce5f8301f" dependencies = [ - "ecdsa", - "elliptic-curve", - "fiat-crypto", + "ecdsa 0.17.0", + "elliptic-curve 0.14.1", + "fiat-crypto 0.3.0", "primefield", - "primeorder", + "primeorder 0.14.0", "sha2 0.11.0", ] @@ -2394,7 +2991,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629" dependencies = [ "digest 0.11.3", - "hmac", + "hmac 0.13.0", +] + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64 0.22.1", + "serde_core", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", ] [[package]] @@ -2472,14 +3088,30 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkcs1" version = "0.8.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "986d2e952779af96ea048f160fd9194e1751b4faea78bcf3ceb456efe008088e" dependencies = [ - "der", - "spki", + "der 0.8.1", + "spki 0.8.0", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der 0.7.10", + "spki 0.7.3", ] [[package]] @@ -2488,8 +3120,8 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" dependencies = [ - "der", - "spki", + "der 0.8.1", + "spki 0.8.0", ] [[package]] @@ -2517,6 +3149,29 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dcdc93847ad24990939cce6e1804361e903efcb5f99daa5abd87943a9d6d7ba" +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", +] + [[package]] name = "portable-atomic" version = "1.14.0" @@ -2565,6 +3220,15 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efca4c95a19a79d1c98f791f10aebd5c1363b473244630bb7dbde1dc98455a24" +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "prettyplease" version = "0.2.37" @@ -2581,21 +3245,30 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c555a6e4eb7d4e158fcb028c835c3b8642206ddc279b5c6b202ef9a8bdb592f4" dependencies = [ - "crypto-bigint", + "crypto-bigint 0.7.5", "crypto-common 0.2.2", - "ff", - "rand_core", + "ff 0.14.0", + "rand_core 0.10.1", "subtle", "zeroize", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve 0.13.8", +] + [[package]] name = "primeorder" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c9f42978c78a00e3d68f69fc03e57a234debae69da4020a4fb588fcdcd07b06" dependencies = [ - "elliptic-curve", + "elliptic-curve 0.14.1", "once_cell", "primefield", "serdect", @@ -2748,10 +3421,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95067976aca6421a523e491fce939a3e65249bac4b977adee0ee9771568e8aa3" [[package]] -name = "pulley-interpreter" -version = "47.0.2" +name = "ptr_meta" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6064e52588328c1275d690baad99f8d4b3fdd870736966f1a63cfcbb20560b6" +checksum = "743da816b98c921cdbe8628ef7381b76f25ecf4da599fc80aca90eae7ef70cc0" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c8d9ca532f185d5d4db7a7c9d51420b452168ea1c2b913953281bd6fe1fcbd0" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "pulley-interpreter" +version = "47.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5c8c21ea032e4efbdf2d067dc45171779dbe0c8ecf20ef4a57efa7474f2b0a" dependencies = [ "cranelift-bitset", "log", @@ -2761,9 +3454,9 @@ dependencies = [ [[package]] name = "pulley-macros" -version = "47.0.2" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e832980317bfc7f8c8538ffc9c69b82912f3e33ec70f7c855d521654c70fa62" +checksum = "9f10925455d5dde962e3604eade797ba5488644c8ee14a44191e2f2b58980574" dependencies = [ "proc-macro2", "quote", @@ -2772,25 +3465,28 @@ dependencies = [ [[package]] name = "pumpkin" -version = "0.1.0-dev+26.2" -source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#75994f3302fa76078087b9a113130541e4d3fc37" +version = "0.1.0-dev+26.2-26.40" +source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#50fe9f7cd7182c13a856d0a41fd59a5ddcb37f6f" dependencies = [ "arc-swap", - "base64 0.23.0", + "axum", + "base64 0.23.1", "bitflags 2.13.1", "bytes", "colored", "crossbeam", "dashmap", + "ed25519-dalek", "flate2", "futures", - "hmac", + "hex", + "hmac 0.13.0", "hyper", "image", "indexmap", "libloading 0.9.0", "notify", - "num-bigint", + "num-bigint 0.5.1", "ordered-float", "postcard", "pumpkin-codecs", @@ -2811,10 +3507,10 @@ dependencies = [ "serde", "serde_json", "serde_repr", - "sha1", + "sha1 0.11.0", "sha2 0.11.0", "sysinfo", - "thiserror 2.0.19", + "thiserror 2.0.20", "time", "tokio", "tokio-util", @@ -2823,15 +3519,18 @@ dependencies = [ "tracing-subscriber", "ureq", "uuid", + "wasm-encoder 0.255.0", + "wasmparser 0.255.0", "wasmtime", "wasmtime-wasi", "wasmtime-wasi-http", + "webrtc", ] [[package]] name = "pumpkin-api-macros" -version = "0.1.0-dev+26.2" -source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#75994f3302fa76078087b9a113130541e4d3fc37" +version = "0.1.0-dev+26.2-26.40" +source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#50fe9f7cd7182c13a856d0a41fd59a5ddcb37f6f" dependencies = [ "proc-macro-error2", "proc-macro2", @@ -2841,8 +3540,8 @@ dependencies = [ [[package]] name = "pumpkin-codecs" -version = "0.1.0-dev+26.2" -source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#75994f3302fa76078087b9a113130541e4d3fc37" +version = "0.1.0-dev+26.2-26.40" +source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#50fe9f7cd7182c13a856d0a41fd59a5ddcb37f6f" dependencies = [ "serde_json", "tracing", @@ -2850,8 +3549,8 @@ dependencies = [ [[package]] name = "pumpkin-config" -version = "0.1.0-dev+26.2" -source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#75994f3302fa76078087b9a113130541e4d3fc37" +version = "0.1.0-dev+26.2-26.40" +source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#50fe9f7cd7182c13a856d0a41fd59a5ddcb37f6f" dependencies = [ "pumpkin-util", "serde", @@ -2862,8 +3561,8 @@ dependencies = [ [[package]] name = "pumpkin-data" -version = "0.1.0-dev+26.2" -source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#75994f3302fa76078087b9a113130541e4d3fc37" +version = "0.1.0-dev+26.2-26.40" +source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#50fe9f7cd7182c13a856d0a41fd59a5ddcb37f6f" dependencies = [ "crc-fast", "phf", @@ -2875,23 +3574,23 @@ dependencies = [ [[package]] name = "pumpkin-inventory" -version = "0.1.0-dev+26.2" -source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#75994f3302fa76078087b9a113130541e4d3fc37" +version = "0.1.0-dev+26.2-26.40" +source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#50fe9f7cd7182c13a856d0a41fd59a5ddcb37f6f" dependencies = [ "pumpkin-data", "pumpkin-protocol", "pumpkin-util", "pumpkin-world", "rand 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", - "thiserror 2.0.19", + "thiserror 2.0.20", "tokio", "tracing", ] [[package]] name = "pumpkin-macros" -version = "0.1.0-dev+26.2" -source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#75994f3302fa76078087b9a113130541e4d3fc37" +version = "0.1.0-dev+26.2-26.40" +source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#50fe9f7cd7182c13a856d0a41fd59a5ddcb37f6f" dependencies = [ "heck", "proc-macro-error2", @@ -2902,30 +3601,28 @@ dependencies = [ [[package]] name = "pumpkin-nbt" -version = "0.1.0-dev+26.2" -source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#75994f3302fa76078087b9a113130541e4d3fc37" +version = "0.1.0-dev+26.2-26.40" +source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#50fe9f7cd7182c13a856d0a41fd59a5ddcb37f6f" dependencies = [ "bytes", "cesu8", "flate2", "pumpkin-codecs", - "serde", - "thiserror 2.0.19", + "thiserror 2.0.20", "tracing", "uuid", ] [[package]] name = "pumpkin-protocol" -version = "0.1.0-dev+26.2" -source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#75994f3302fa76078087b9a113130541e4d3fc37" +version = "0.1.0-dev+26.2-26.40" +source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#50fe9f7cd7182c13a856d0a41fd59a5ddcb37f6f" dependencies = [ - "aes", + "aes 0.9.2", "async-compression", "bitflags 2.13.1", "bytes", "cfb8", - "ctr", "flate2", "hybrid-array", "pumpkin-data", @@ -2934,34 +3631,32 @@ dependencies = [ "pumpkin-util", "pumpkin-world", "serde", - "sha2 0.11.0", - "thiserror 2.0.19", + "thiserror 2.0.20", "tokio", "uuid", ] [[package]] name = "pumpkin-util" -version = "0.1.0-dev+26.2" -source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#75994f3302fa76078087b9a113130541e4d3fc37" +version = "0.1.0-dev+26.2-26.40" +source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#50fe9f7cd7182c13a856d0a41fd59a5ddcb37f6f" dependencies = [ - "base64 0.23.0", + "base64 0.23.1", "bytes", "colored", - "crypto-bigint", - "ecdsa", - "enum_dispatch", + "crypto-bigint 0.7.5", + "ecdsa 0.17.0", "md5", "num-derive", "num-traits", - "p384", + "p384 0.14.0", "pumpkin-codecs", "pumpkin-nbt", "rsa", "serde", "serde_json", "sha2 0.11.0", - "thiserror 2.0.19", + "thiserror 2.0.20", "tokio", "ureq", "uuid", @@ -2969,8 +3664,8 @@ dependencies = [ [[package]] name = "pumpkin-world" -version = "0.1.0-dev+26.2" -source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#75994f3302fa76078087b9a113130541e4d3fc37" +version = "0.1.0-dev+26.2-26.40" +source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#50fe9f7cd7182c13a856d0a41fd59a5ddcb37f6f" dependencies = [ "arc-swap", "bitflags 2.13.1", @@ -2978,7 +3673,6 @@ dependencies = [ "crossbeam", "crossfire", "dashmap", - "enum_dispatch", "flate2", "futures", "itertools 0.15.0", @@ -2995,7 +3689,7 @@ dependencies = [ "serde_json", "sha2 0.11.0", "slotmap", - "thiserror 2.0.19", + "thiserror 2.0.20", "tokio", "tokio-util", "tracing", @@ -3031,24 +3725,71 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] -name = "rand" -version = "0.10.2" +name = "rancor" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +checksum = "9b534442d0fcdb55d66f373d9cac6d33b6293a2335bc2136dbd06ce0e87d2572" dependencies = [ - "chacha20", - "getrandom 0.4.3", - "rand_core", + "ptr_meta", +] + +[[package]] +name = "rand" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +dependencies = [ + "rand_chacha", + "rand_core 0.9.5", ] [[package]] name = "rand" version = "0.10.2" -source = "git+https://github.com/rust-random/rand#cf4f73e3953ffcf97f575317a07f0fd5983bfe9e" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" dependencies = [ - "chacha20", + "chacha20 0.10.1", "getrandom 0.4.3", - "rand_core", + "rand_core 0.10.1", +] + +[[package]] +name = "rand" +version = "0.10.2" +source = "git+https://github.com/rust-random/rand#bb1262f703ca04e4ce56be78e1dc4e204cd6a998" +dependencies = [ + "chacha20 0.10.1", + "getrandom 0.4.3", + "rand_core 0.10.1", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", ] [[package]] @@ -3077,6 +3818,20 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rcgen" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time", + "x509-parser", + "yasna", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -3105,7 +3860,7 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.17", "libredox", - "thiserror 2.0.19", + "thiserror 2.0.20", ] [[package]] @@ -3137,9 +3892,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.16" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" dependencies = [ "aho-corasick", "memchr", @@ -3152,14 +3907,33 @@ version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" +[[package]] +name = "rend" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "663ba70707f96e871406fe10d68128412e619b06d1d47cb91c3a4c6501176240" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac 0.12.1", + "subtle", +] + [[package]] name = "rfc6979" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4a459cddafb3fe76b31fd8f1108007566c40301feb64dc7b54656eb7388172b" dependencies = [ - "crypto-bigint", - "hmac", + "crypto-bigint 0.7.5", + "hmac 0.13.0", ] [[package]] @@ -3176,24 +3950,80 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rkyv" +version = "0.8.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9776093b7ca170454ab1406954f7b7d97a57c51dc6c0642957fb2ef25c2d399" +dependencies = [ + "bytecheck", + "bytes", + "hashbrown 0.17.1", + "indexmap", + "munge", + "ptr_meta", + "rancor", + "rend", + "rkyv_derive", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.8.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c25ef604ac7dd839d44d64648952ea23c97866f124ff671b0ed2cf3ad9bb06e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + [[package]] name = "rsa" version = "0.10.0-rc.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30b2aa4ba0d89f73d1e332df05be0eeab8840351c36ca5654341dfdb57bb3caf" dependencies = [ - "const-oid", - "crypto-bigint", + "const-oid 0.10.2", + "crypto-bigint 0.7.5", "crypto-primes", "digest 0.11.3", "pkcs1", - "pkcs8", - "rand_core", - "signature", - "spki", + "pkcs8 0.11.0", + "rand_core 0.10.1", + "signature 3.0.0", + "spki 0.8.0", "zeroize", ] +[[package]] +name = "rtcp" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adad7f6a501162881032fc84b4bc78ae11f1b30748180b9f5cbe8810bf613aab" +dependencies = [ + "bytes", + "thiserror 1.0.69", + "webrtc-util", +] + +[[package]] +name = "rtp" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "149329e78ada26b5e174a4c281a7a7e5c8bda6008adc90daddd6f98fce56db29" +dependencies = [ + "bytes", + "memchr", + "portable-atomic", + "rand 0.9.5", + "serde", + "thiserror 1.0.69", + "webrtc-util", +] + [[package]] name = "rust-embed" version = "8.12.0" @@ -3261,6 +4091,15 @@ dependencies = [ "semver", ] +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + [[package]] name = "rustix" version = "1.1.4" @@ -3337,7 +4176,7 @@ dependencies = [ "libc", "log", "memchr", - "nix", + "nix 0.31.3", "unicode-segmentation", "unicode-width", "utf8parse", @@ -3365,15 +4204,41 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sdp" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66b6eecfa5151edef84d544ff3885dff98e5b5fe97586757a4b5118bde51c958" +dependencies = [ + "rand 0.9.5", + "substring", + "thiserror 1.0.69", + "url", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct 0.2.0", + "der 0.7.10", + "generic-array", + "pkcs8 0.10.2", + "subtle", + "zeroize", +] + [[package]] name = "sec1" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d56d437c2f19203ce5f7122e507831de96f3d2d4d3be5af44a0b0a09d8a80e4d" dependencies = [ - "base16ct", + "base16ct 1.0.0", "ctutils", - "der", + "der 0.8.1", "hybrid-array", "subtle", "zeroize", @@ -3401,15 +4266,13 @@ dependencies = [ [[package]] name = "serde-saphyr" -version = "0.0.29" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bd22781911de0ca6debda95f073c8f18bec65d1a94f1fa9573f3102e514cea4" +checksum = "af2c6921c8ce48fb1b052b457619d008834039d80325992ed3bc55bbe6c155ad" dependencies = [ - "ahash", "annotate-snippets", - "base64 0.22.1", + "base64 0.23.1", "encoding_rs_io", - "getrandom 0.3.4", "granit-parser", "nohash-hasher", "num-traits", @@ -3477,10 +4340,21 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e" dependencies = [ - "base16ct", + "base16ct 1.0.0", "serde", ] +[[package]] +name = "sha1" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + [[package]] name = "sha1" version = "0.11.0" @@ -3548,6 +4422,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + [[package]] name = "signature" version = "3.0.0" @@ -3555,7 +4439,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" dependencies = [ "digest 0.11.3", - "rand_core", + "rand_core 0.10.1", ] [[package]] @@ -3564,6 +4448,12 @@ version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "siphasher" version = "1.0.3" @@ -3594,6 +4484,25 @@ dependencies = [ "serde", ] +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "socket2" version = "0.6.5" @@ -3619,6 +4528,16 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "023a211cb3138dbc438680b32560ad89f699977624c9f8dbb95a47d5b4c07dd3" +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der 0.7.10", +] + [[package]] name = "spki" version = "0.8.0" @@ -3626,7 +4545,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" dependencies = [ "base64ct", - "der", + "der 0.8.1", ] [[package]] @@ -3641,6 +4560,34 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "stun" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e3b8d5ad1dd4c8cd0595440f26521a71dd593cb5873ee8e06b91510b7d97269" +dependencies = [ + "base64 0.22.1", + "crc", + "lazy_static", + "md-5", + "rand 0.9.5", + "ring", + "subtle", + "thiserror 1.0.69", + "tokio", + "url", + "webrtc-util", +] + +[[package]] +name = "substring" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ee6433ecef213b2e72f587ef64a2f5943e7cd16fbd82dbe8bc07486c534c86" +dependencies = [ + "autocfg", +] + [[package]] name = "subtle" version = "2.6.1" @@ -3669,6 +4616,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + [[package]] name = "synstructure" version = "0.13.2" @@ -3732,11 +4685,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.19" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" dependencies = [ - "thiserror-impl 2.0.19", + "thiserror-impl 2.0.20", ] [[package]] @@ -3752,9 +4705,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.19" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" dependencies = [ "proc-macro2", "quote", @@ -3772,9 +4725,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.54" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" dependencies = [ "deranged", "js-sys", @@ -3813,6 +4766,21 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.53.1" @@ -3822,9 +4790,10 @@ dependencies = [ "bytes", "libc", "mio", + "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.6.5", "tokio-macros", "windows-sys 0.61.2", ] @@ -3928,6 +4897,33 @@ version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + [[package]] name = "tracing" version = "0.1.44" @@ -3994,6 +4990,27 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "turn" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d99249a493335eb44c4d7943a8751b22561a82c9903d8eb2d29780b3927880a7" +dependencies = [ + "async-trait", + "base64 0.22.1", + "futures", + "log", + "md-5", + "portable-atomic", + "rand 0.9.5", + "ring", + "stun", + "thiserror 1.0.69", + "tokio", + "tokio-util", + "webrtc-util", +] + [[package]] name = "twox-hash" version = "1.6.3" @@ -4040,6 +5057,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common 0.1.7", + "subtle", +] + [[package]] name = "untrusted" version = "0.9.0" @@ -4126,6 +5153,15 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "waitgroup" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1f50000a783467e6c0200f9d10642f4bc424e39efc1b770203e88b488f79292" +dependencies = [ + "atomic-waker", +] + [[package]] name = "walkdir" version = "2.5.0" @@ -4162,9 +5198,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" dependencies = [ "cfg-if", "once_cell", @@ -4175,9 +5211,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4185,9 +5221,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" dependencies = [ "bumpalo", "proc-macro2", @@ -4198,9 +5234,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" dependencies = [ "unicode-ident", ] @@ -4212,7 +5248,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8185ae345fa5687c054626ff9a50e7089797a343d9904d1dc9820eb4c4d3196f" dependencies = [ "leb128fmt", - "wasmparser", + "wasmparser 0.252.0", +] + +[[package]] +name = "wasm-encoder" +version = "0.255.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b524283fb5df62eec102ed0574838961bdd7ba5ac9c50d38e2756c51c971a42" +dependencies = [ + "leb128fmt", + "wasmparser 0.255.0", ] [[package]] @@ -4228,6 +5274,19 @@ dependencies = [ "serde", ] +[[package]] +name = "wasmparser" +version = "0.255.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8e329ef4b5d46e73b91d3ac6924417cad55a8cbbf869c199283383427c3320b" +dependencies = [ + "bitflags 2.13.1", + "hashbrown 0.17.1", + "indexmap", + "semver", + "serde", +] + [[package]] name = "wasmprinter" version = "0.252.0" @@ -4236,14 +5295,14 @@ checksum = "7142797de29b35ab8dbf15c00f55fda75d409da4c423a8ab8bd6b667a785824b" dependencies = [ "anyhow", "termcolor", - "wasmparser", + "wasmparser 0.252.0", ] [[package]] name = "wasmtime" -version = "47.0.2" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06d9f263ccb212017dae1d109b9997f218a7675db1e9a7bedf07ae1643322bf3" +checksum = "c80ca6098e0d4d06886d91d7f2cc3cb6623eb583c4c0ab3c89cbfb6098c8586c" dependencies = [ "addr2line", "async-trait", @@ -4267,7 +5326,7 @@ dependencies = [ "serde_derive", "smallvec", "target-lexicon", - "wasmparser", + "wasmparser 0.252.0", "wasmtime-environ", "wasmtime-internal-cache", "wasmtime-internal-component-macro", @@ -4284,9 +5343,9 @@ dependencies = [ [[package]] name = "wasmtime-environ" -version = "47.0.2" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9915e3d90c36a16e0fec8be746ee87e21cf0d4a017ecf09c49b7d1cc6f31b2b6" +checksum = "134f9d136d29c76f6c1b4c9b468e97a2efc38c7d49fd188e39b64870b2fea701" dependencies = [ "anyhow", "cpp_demangle", @@ -4306,8 +5365,8 @@ dependencies = [ "sha2 0.10.9", "smallvec", "target-lexicon", - "wasm-encoder", - "wasmparser", + "wasm-encoder 0.252.0", + "wasmparser 0.252.0", "wasmprinter", "wasmtime-internal-component-util", "wasmtime-internal-core", @@ -4315,9 +5374,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-cache" -version = "47.0.2" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b7c25436e1602aad2de80bfb3f586dcdfb6fe426d5b17cba37fabf9a1714d32" +checksum = "65db2eb1bfc5371a3b4107dbf539d0b93d05016fc29625b1648146b47db02071" dependencies = [ "base64 0.22.1", "directories-next", @@ -4335,9 +5394,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-component-macro" -version = "47.0.2" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b627ea698db36fb9110aa77868467f1d23791a76a88a433cb6a43e1242073b77" +checksum = "2bf7b91fed3fc34781d57f9c6654ea2513bf0a99f7b0b0523c00de1e6efebe1c" dependencies = [ "anyhow", "proc-macro2", @@ -4350,15 +5409,15 @@ dependencies = [ [[package]] name = "wasmtime-internal-component-util" -version = "47.0.2" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f45bf943cff1d2f1976bac6a241ca4ecaf29a6efd5f1ad89e7722439fa5c392f" +checksum = "fc8a678149885cae00289f806fbe74c7863084cf74cace0b8dc73602279400e1" [[package]] name = "wasmtime-internal-core" -version = "47.0.2" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea33e1f9685be82f90223bebcf2ffefb6b596ea3d82a3c42384527b9c6f5b447" +checksum = "8a0092c4b9d070ac5e278b6d0db10f5e71214190f0347ca57502b4692f796321" dependencies = [ "hashbrown 0.17.1", "libm", @@ -4367,9 +5426,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-cranelift" -version = "47.0.2" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1ce104719932a48dbdfec0cc292c96494b0bfe764946e45fbfd3340e3a68f9" +checksum = "6851ebc9e03cab23d9821d2b2505d380bdfe38f35ccec945247b05274d85c98b" dependencies = [ "cfg-if", "cranelift-codegen", @@ -4384,8 +5443,8 @@ dependencies = [ "pulley-interpreter", "smallvec", "target-lexicon", - "thiserror 2.0.19", - "wasmparser", + "thiserror 2.0.20", + "wasmparser 0.252.0", "wasmtime-environ", "wasmtime-internal-core", "wasmtime-internal-unwinder", @@ -4394,9 +5453,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-fiber" -version = "47.0.2" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976e4b71b9c0da8a9a7bb8d5fa0abedc4da879b397111fd91ece73be524fe621" +checksum = "9b26da6d5f60d4c438da70bba3553fe810a840533a64156be287dca2081c6991" dependencies = [ "cc", "cfg-if", @@ -4409,9 +5468,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-jit-debug" -version = "47.0.2" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1f60ecba3497cfc26284903c2e79023fd5ea991b95b0e8dafe994fd9fca4c4e" +checksum = "ed621ba25d7bf78b7edd7b4749abbb27c2e5cdba836c9504a424ee74b6c23149" dependencies = [ "cc", "wasmtime-internal-versioned-export-macros", @@ -4419,9 +5478,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-jit-icache-coherence" -version = "47.0.2" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fae6decb025ae6d12f3391b36deb62a6838d45b18864f796d84bced722ceac0d" +checksum = "5684ba160951baad06a725696f3c590e2fb0e8067c2aebee27bf7f9259058e85" dependencies = [ "cfg-if", "libc", @@ -4431,9 +5490,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-unwinder" -version = "47.0.2" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0982122d9470d3d72ed7aa9ed91e69538ae44c1494cd22d78fec7846b248a940" +checksum = "112eead527bffa8ff0646a11fb4339a9d52ddd1da2b9a6fce4aff84c815dd94f" dependencies = [ "cfg-if", "cranelift-codegen", @@ -4444,9 +5503,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-versioned-export-macros" -version = "47.0.2" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "363cf88a80e6ed13311d0d34d9bcd02a2e07d98113b994ff41ed386a206f498e" +checksum = "153592e0bed824fc13c6696203fa1bd7bd20eb355316475e39a55f081ef80eca" dependencies = [ "proc-macro2", "quote", @@ -4455,9 +5514,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-wit-bindgen" -version = "47.0.2" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ff41bb1656cbeb7befc42b8a7078b30a5f8393a0c8bca36a6cec94360c1a16" +checksum = "c456ad6e81e0f46abfeca43687d18ea15e289c395b262ea6e0023e2682e088be" dependencies = [ "anyhow", "bitflags 2.13.1", @@ -4468,9 +5527,9 @@ dependencies = [ [[package]] name = "wasmtime-wasi" -version = "47.0.2" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed1750b8e5e5d5b1ee3c0eb602d2c49d6a37a36e9996eaf0708b48ac92b62b06" +checksum = "4c1cf60cd6a565213af7074b4aad7bb5e110e3c6c6aae54425aa0500a0e15e86" dependencies = [ "async-trait", "bitflags 2.13.1", @@ -4482,7 +5541,7 @@ dependencies = [ "io-lifetimes 3.0.1", "rand 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", "rustix", - "thiserror 2.0.19", + "thiserror 2.0.20", "tokio", "tracing", "url", @@ -4493,9 +5552,9 @@ dependencies = [ [[package]] name = "wasmtime-wasi-http" -version = "47.0.2" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "014b8f05cacb7a5d665536e8a733b887bd192a9c179037580d48d0a6a82ecd34" +checksum = "a499879061ee9f724342d5f5009754b723ae53c7932161ee063a9fe19395e915" dependencies = [ "async-trait", "bytes", @@ -4516,9 +5575,9 @@ dependencies = [ [[package]] name = "wasmtime-wasi-io" -version = "47.0.2" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54777d3afeaf3f32cc444df661d9cb4fd57067971e183c2b1f5251f33b939e68" +checksum = "7fb08e1d7755aaa467ce14080b7b01e33c914a920f6000696f1e42e40ea6387e" dependencies = [ "async-trait", "bytes", @@ -4536,6 +5595,175 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "webrtc" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baaacdf9d96224d7b6e2872ba065578f38775f7634367e3ac2cc87c7271da433" +dependencies = [ + "arc-swap", + "async-trait", + "bytes", + "dtls", + "hex", + "interceptor", + "lazy_static", + "log", + "portable-atomic", + "rand 0.9.5", + "rcgen", + "regex", + "ring", + "rtcp", + "rtp", + "sdp", + "serde", + "serde_json", + "sha2 0.10.9", + "smol_str", + "stun", + "thiserror 1.0.69", + "tokio", + "turn", + "unicase", + "url", + "waitgroup", + "webrtc-data", + "webrtc-ice", + "webrtc-mdns", + "webrtc-media", + "webrtc-sctp", + "webrtc-srtp", + "webrtc-util", +] + +[[package]] +name = "webrtc-data" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd470286275809f2fcfcdb1e73ef5f1500be82eff7fe98150ce81b20aad5a2a4" +dependencies = [ + "bytes", + "log", + "portable-atomic", + "thiserror 1.0.69", + "tokio", + "webrtc-sctp", + "webrtc-util", +] + +[[package]] +name = "webrtc-ice" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b7fd30f52e6fda8664779b84b7904b2553b76fee24d9ca665e774ae32b13f53" +dependencies = [ + "arc-swap", + "async-trait", + "crc", + "log", + "portable-atomic", + "rand 0.9.5", + "serde", + "serde_json", + "stun", + "thiserror 1.0.69", + "tokio", + "turn", + "url", + "uuid", + "waitgroup", + "webrtc-mdns", + "webrtc-util", +] + +[[package]] +name = "webrtc-mdns" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ffa0ea00c0fae979aafa5db285fec5eeb360e1ea246ebec529ba4e09f8e03e" +dependencies = [ + "log", + "socket2 0.5.10", + "thiserror 1.0.69", + "tokio", + "webrtc-util", +] + +[[package]] +name = "webrtc-media" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26a6c7335bdd03dc023cb9a3bd7966866d969c92c86987b73aa16dfa39c7ca29" +dependencies = [ + "byteorder", + "bytes", + "rand 0.9.5", + "rtp", + "thiserror 1.0.69", +] + +[[package]] +name = "webrtc-sctp" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c4b637f0d8eb96d900ac0f79b3060ddd21ca88dfefa467e96e67ab0016f2574" +dependencies = [ + "arc-swap", + "async-trait", + "bytes", + "crc", + "log", + "portable-atomic", + "rand 0.9.5", + "thiserror 1.0.69", + "tokio", + "webrtc-util", +] + +[[package]] +name = "webrtc-srtp" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45d2b667a0b5d04eebcb7cb22fd51b2af3721f418097b496e72de16386c8d0c3" +dependencies = [ + "aead", + "aes 0.8.4", + "aes-gcm", + "byteorder", + "bytes", + "ctr", + "hmac 0.12.1", + "log", + "rtcp", + "rtp", + "sha1 0.10.7", + "subtle", + "thiserror 1.0.69", + "tokio", + "webrtc-util", +] + +[[package]] +name = "webrtc-util" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1beae0b4f24969741c26ca282a257dc5823bd9cbe608f7e3ca3775102d323ad9" +dependencies = [ + "async-trait", + "bitflags 1.3.2", + "bytes", + "ipnet", + "lazy_static", + "log", + "nix 0.26.4", + "portable-atomic", + "rand 0.9.5", + "thiserror 1.0.69", + "tokio", + "winapi", +] + [[package]] name = "winapi" version = "0.3.9" @@ -4886,7 +6114,7 @@ dependencies = [ "serde_derive", "serde_json", "unicode-ident", - "wasmparser", + "wasmparser 0.252.0", ] [[package]] @@ -4895,8 +6123,8 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab12e7090f27e2ffd9322651492942d50c2926094af30601e1964337db39daf1" dependencies = [ - "ff", - "group", + "ff 0.14.0", + "group 0.14.0", "hybrid-array", ] @@ -4906,12 +6134,51 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek 4.1.3", + "rand_core 0.6.4", + "serde", + "zeroize", +] + +[[package]] +name = "x509-parser" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "ring", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + [[package]] name = "xxhash-rust" version = "0.8.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aee1b19627c7c60102ab80d3a9cbe18de90bfe03bfa6c3715447681f0e8c8af6" +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + [[package]] name = "yoke" version = "0.8.3" @@ -4937,18 +6204,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.55" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.55" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" dependencies = [ "proc-macro2", "quote", @@ -4981,6 +6248,20 @@ name = "zeroize" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] [[package]] name = "zerotrie" @@ -5021,20 +6302,20 @@ version = "8.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b" dependencies = [ - "aes", + "aes 0.9.2", "bzip2", "constant_time_eq", "crc32fast", "deflate64", "flate2", "getrandom 0.4.3", - "hmac", + "hmac 0.13.0", "indexmap", "lzma-rust2", "memchr", "pbkdf2", "ppmd-rust", - "sha1", + "sha1 0.11.0", "time", "typed-path", "zeroize", @@ -5044,9 +6325,9 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b142a20ec14a91d5bc708c1dc21b080c550113d8aa77afa29635673a65dd02c5" +checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12" [[package]] name = "zmij" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index c558df2..06575ee 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -31,7 +31,7 @@ uuid = "1.24.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -serde-saphyr = "0.0.29" +serde-saphyr = "1.0.1" rand = { git = "https://github.com/rust-random/rand"} prost = "0.14" diff --git a/rust/build/java.rs b/rust/build/java.rs index 286222a..acef8f1 100644 --- a/rust/build/java.rs +++ b/rust/build/java.rs @@ -40,6 +40,9 @@ pub fn setup_java(base: PathBuf) { .with_base_path(&resources) .java_opts(vec![ j4rs::JavaOpt::new("--enable-native-access=ALL-UNNAMED"), + j4rs::JavaOpt::new("--enable-final-field-mutation=ALL-UNNAMED"), + j4rs::JavaOpt::new("--add-opens=java.base/java.lang=ALL-UNNAMED"), + j4rs::JavaOpt::new("--add-opens=java.base/java.lang.reflect=ALL-UNNAMED"), j4rs::JavaOpt::new("-Dcom.google.protobuf.useUnsafe=false"), ]); @@ -59,10 +62,6 @@ pub fn setup_java(base: PathBuf) { } fs::create_dir_all(&jassets).unwrap(); - let dest_jar = jassets.join("patchbukkit.jar"); - fs::copy(&patchbukkit_jar, &dest_jar) - .map_err(|err| format!("Failed to copy patchbukkit.jar to jassets: {err:?}")) - .unwrap(); let cdylib = std::env::var("CARGO_CDYLIB_FILE_J4RS").unwrap(); let cdylib = PathBuf::from(cdylib); @@ -71,10 +70,16 @@ pub fn setup_java(base: PathBuf) { fs::create_dir_all(&cdylib_to).unwrap(); let original_name = cdylib.file_name().unwrap().to_string_lossy(); - let stem = original_name.split('-').next().unwrap(); // before the first '-' - let ext = cdylib.extension().unwrap().to_string_lossy(); + let mut target_name = original_name.split('-').next().unwrap().to_string(); + if !target_name.ends_with(".dll") + && !target_name.ends_with(".so") + && !target_name.ends_with(".dylib") + && let Some(ext) = cdylib.extension() + { + target_name = format!("{target_name}.{}", ext.to_string_lossy()); + } - cdylib_to.push(format!("{stem}.{ext}")); + cdylib_to.push(target_name); fs::copy(&cdylib, &cdylib_to) .map_err(|err| format!("Failed to copy j4rs native lib: {err:?}")) diff --git a/rust/build/protobufs.rs b/rust/build/protobufs.rs index a1291ea..326410c 100644 --- a/rust/build/protobufs.rs +++ b/rust/build/protobufs.rs @@ -224,7 +224,7 @@ pub fn initialize_ffi_callbacks(jvm: &j4rs::Jvm) -> anyhow::Result<()> {{ file, r#" // Initialize {} - jvm.invoke_static( + if let Err(e) = jvm.invoke_static( "{}", "init", &["#, @@ -242,13 +242,17 @@ pub fn initialize_ffi_callbacks(jvm: &j4rs::Jvm) -> anyhow::Result<()> {{ writeln!( file, r#" ], - )?; + ) {{ + tracing::error!("Failed to initialize FFI service: {{:?}}", e); + }} - jvm.invoke_static( + if let Err(e) = jvm.invoke_static( "{}", "initFree", &[InvocationArg::try_from(ffi_free_bytes as *const () as i64)?.into_primitive()?], - )?;"#, + ) {{ + tracing::error!("Failed to initialize FFI service initFree: {{:?}}", e); + }}"#, service.java_class ) .unwrap(); diff --git a/rust/rust-toolchain.toml b/rust/rust-toolchain.toml index d7dc4dd..887e55d 100644 --- a/rust/rust-toolchain.toml +++ b/rust/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "nightly-2026-07-31" +channel = "nightly-2026-08-07" components = ["rust-analyzer", "rust-src", "rustfmt", "clippy"] diff --git a/rust/src/commands/mod.rs b/rust/src/commands/mod.rs index b6fb360..413d190 100644 --- a/rust/src/commands/mod.rs +++ b/rust/src/commands/mod.rs @@ -131,8 +131,20 @@ impl CommandExecutor for JavaCommandExecutor { ) -> pumpkin::command::CommandResult<'a> { Box::pin(async move { let full_command = match args.get(ARG_ANY) { - Some(Arg::Msg(msg)) => format!("/{} {}", self.cmd_name, msg), - _ => format!("/{}", self.cmd_name), + Some(Arg::Msg(msg)) => { + if self.cmd_name.starts_with('/') { + format!("{} {}", self.cmd_name, msg) + } else { + format!("/{} {}", self.cmd_name, msg) + } + } + _ => { + if self.cmd_name.starts_with('/') { + self.cmd_name.clone() + } else { + format!("/{}", self.cmd_name) + } + } }; let (tx, _rx) = oneshot::channel(); diff --git a/rust/src/config/paper.rs b/rust/src/config/paper.rs index eea0ebd..90cf9fe 100644 --- a/rust/src/config/paper.rs +++ b/rust/src/config/paper.rs @@ -184,6 +184,14 @@ pub struct PaperPluginYml { /// Other plugins this plugin can substitute for #[serde(default)] pub provides: Option>, + + /// Libraries declared for runtime resolution + #[serde(default)] + pub libraries: Option>, + + /// Repositories declared for runtime resolution + #[serde(default)] + pub repositories: Option, } impl PaperPluginYml { diff --git a/rust/src/directories.rs b/rust/src/directories.rs index 23077cf..9a929e5 100644 --- a/rust/src/directories.rs +++ b/rust/src/directories.rs @@ -29,6 +29,20 @@ pub fn setup_directories(server: &Context) -> Result, @@ -36,7 +42,7 @@ impl PatchBukkitEvent for pumpkin::plugin::player::player_join::PlayerJoinEvent player_uuid: Some(Uuid { value: self.player.gameprofile.id.to_string(), }), - join_message: serde_json::to_string(&self.join_message).unwrap(), + join_message: serde_json::to_string(&self.join_message).unwrap_or_default(), })), }, context: EventContext { @@ -56,6 +62,531 @@ impl PatchBukkitEvent for pumpkin::plugin::player::player_join::PlayerJoinEvent } } +impl PatchBukkitEvent for pumpkin::plugin::player::player_leave::PlayerLeaveEvent { + fn to_payload(&self, server: Arc) -> JvmEventPayload { + JvmEventPayload { + event: Event { + data: Some(Data::PlayerQuit(PlayerQuitEvent { + player_uuid: Some(Uuid { + value: self.player.gameprofile.id.to_string(), + }), + quit_message: serde_json::to_string(&self.leave_message).unwrap_or_default(), + })), + }, + context: EventContext { + server, + player: Some(self.player.clone()), + }, + } + } + + fn apply_modifications(&mut self, server: &Arc, data: Data) -> Option<()> { + if let Data::PlayerQuit(event) = data { + self.leave_message = serde_json::from_str(&event.quit_message).ok()?; + server.get_player_by_uuid(uuid::Uuid::from_str(&event.player_uuid?.value).ok()?)?; + } + + Some(()) + } +} + +impl PatchBukkitEvent + for pumpkin::plugin::player::player_gamemode_change::PlayerGamemodeChangeEvent +{ + fn to_payload(&self, server: Arc) -> JvmEventPayload { + JvmEventPayload { + event: Event { + data: Some(Data::PlayerGamemodeChange(PlayerGameModeChangeEvent { + player_uuid: Some(Uuid { + value: self.player.gameprofile.id.to_string(), + }), + new_gamemode: format!("{:?}", self.new_gamemode), + })), + }, + context: EventContext { + server, + player: Some(self.player.clone()), + }, + } + } + + fn apply_modifications(&mut self, _server: &Arc, _data: Data) -> Option<()> { + Some(()) + } +} + +impl PatchBukkitEvent for pumpkin::plugin::player::player_interact_event::PlayerInteractEvent { + fn to_payload(&self, server: Arc) -> JvmEventPayload { + let (x, y, z) = match self.clicked_pos { + Some(pos) => (pos.0.x, pos.0.y, pos.0.z), + None => (0, 0, 0), + }; + let action_str = match self.action { + pumpkin::plugin::player::player_interact_event::InteractAction::LeftClickBlock => { + "LEFT_CLICK_BLOCK" + } + pumpkin::plugin::player::player_interact_event::InteractAction::LeftClickAir => { + "LEFT_CLICK_AIR" + } + pumpkin::plugin::player::player_interact_event::InteractAction::RightClickAir => { + "RIGHT_CLICK_AIR" + } + pumpkin::plugin::player::player_interact_event::InteractAction::RightClickBlock => { + "RIGHT_CLICK_BLOCK" + } + }; + JvmEventPayload { + event: Event { + data: Some(Data::PlayerInteract(PlayerInteractEvent { + player_uuid: Some(Uuid { + value: self.player.gameprofile.id.to_string(), + }), + action: action_str.to_string(), + clicked_x: x, + clicked_y: y, + clicked_z: z, + block_face: "SELF".to_string(), + hand: "HAND".to_string(), + })), + }, + context: EventContext { + server, + player: Some(self.player.clone()), + }, + } + } + + fn apply_modifications(&mut self, _server: &Arc, _data: Data) -> Option<()> { + Some(()) + } +} + +impl PatchBukkitEvent for pumpkin::plugin::block::block_break::BlockBreakEvent { + fn to_payload(&self, server: Arc) -> JvmEventPayload { + JvmEventPayload { + event: Event { + data: Some(Data::BlockBreak(BlockBreakEvent { + player_uuid: self.player.as_ref().map(|p| Uuid { + value: p.gameprofile.id.to_string(), + }), + block_x: self.block_position.0.x, + block_y: self.block_position.0.y, + block_z: self.block_position.0.z, + block_type: format!("{:?}", self.block), + exp: self.exp, + drop_items: self.drop, + })), + }, + context: EventContext { + server, + player: self.player.clone(), + }, + } + } + + fn apply_modifications(&mut self, _server: &Arc, _data: Data) -> Option<()> { + Some(()) + } +} + +impl PatchBukkitEvent for pumpkin::plugin::block::block_place::BlockPlaceEvent { + fn to_payload(&self, server: Arc) -> JvmEventPayload { + JvmEventPayload { + event: Event { + data: Some(Data::BlockPlace(BlockPlaceEvent { + player_uuid: Some(Uuid { + value: self.player.gameprofile.id.to_string(), + }), + block_x: self.block_position.0.x, + block_y: self.block_position.0.y, + block_z: self.block_position.0.z, + block_placed_type: format!("{:?}", self.block_placed), + block_against_type: format!("{:?}", self.block_placed_against), + can_build: self.can_build, + })), + }, + context: EventContext { + server, + player: Some(self.player.clone()), + }, + } + } + + fn apply_modifications(&mut self, _server: &Arc, _data: Data) -> Option<()> { + Some(()) + } +} + +impl PatchBukkitEvent + for pumpkin::plugin::player::player_toggle_sneak_event::PlayerToggleSneakEvent +{ + fn to_payload(&self, server: Arc) -> JvmEventPayload { + JvmEventPayload { + event: Event { + data: Some(Data::PlayerToggleSneak(PlayerToggleSneakEvent { + player_uuid: Some(Uuid { + value: self.player.gameprofile.id.to_string(), + }), + is_sneaking: self.is_sneaking, + })), + }, + context: EventContext { + server, + player: Some(self.player.clone()), + }, + } + } + + fn apply_modifications(&mut self, _server: &Arc, _data: Data) -> Option<()> { + Some(()) + } +} + +impl PatchBukkitEvent + for pumpkin::plugin::player::player_toggle_sprint_event::PlayerToggleSprintEvent +{ + fn to_payload(&self, server: Arc) -> JvmEventPayload { + JvmEventPayload { + event: Event { + data: Some(Data::PlayerToggleSprint(PlayerToggleSprintEvent { + player_uuid: Some(Uuid { + value: self.player.gameprofile.id.to_string(), + }), + is_sprinting: self.is_sprinting, + })), + }, + context: EventContext { + server, + player: Some(self.player.clone()), + }, + } + } + + fn apply_modifications(&mut self, _server: &Arc, _data: Data) -> Option<()> { + Some(()) + } +} + +impl PatchBukkitEvent + for pumpkin::plugin::player::player_toggle_flight_event::PlayerToggleFlightEvent +{ + fn to_payload(&self, server: Arc) -> JvmEventPayload { + JvmEventPayload { + event: Event { + data: Some(Data::PlayerToggleFlight(PlayerToggleFlightEvent { + player_uuid: Some(Uuid { + value: self.player.gameprofile.id.to_string(), + }), + is_flying: self.is_flying, + })), + }, + context: EventContext { + server, + player: Some(self.player.clone()), + }, + } + } + + fn apply_modifications(&mut self, _server: &Arc, _data: Data) -> Option<()> { + Some(()) + } +} + +impl PatchBukkitEvent for pumpkin::plugin::player::player_move::PlayerMoveEvent { + fn to_payload(&self, server: Arc) -> JvmEventPayload { + let move_vel = self.to - self.from; + self.player.living_entity.entity.velocity.store(move_vel); + + let player_uuid = self.player.gameprofile.id.to_string(); + let world_uuid = self.player.world().uuid.to_string(); + + JvmEventPayload { + event: Event { + data: Some(Data::PlayerMove(PlayerMoveEvent { + player_uuid: Some(Uuid { value: player_uuid }), + from: Some(crate::proto::patchbukkit::common::Location { + world: Some(crate::proto::patchbukkit::common::World { + uuid: Some(Uuid { + value: world_uuid.clone(), + }), + }), + position: Some(crate::proto::patchbukkit::common::Vec3 { + x: self.from.x, + y: self.from.y, + z: self.from.z, + }), + yaw: self.player.living_entity.entity.yaw.load(), + pitch: self.player.living_entity.entity.pitch.load(), + }), + to: Some(crate::proto::patchbukkit::common::Location { + world: Some(crate::proto::patchbukkit::common::World { + uuid: Some(Uuid { value: world_uuid }), + }), + position: Some(crate::proto::patchbukkit::common::Vec3 { + x: self.to.x, + y: self.to.y, + z: self.to.z, + }), + yaw: self.player.living_entity.entity.yaw.load(), + pitch: self.player.living_entity.entity.pitch.load(), + }), + })), + }, + context: EventContext { + server, + player: Some(self.player.clone()), + }, + } + } + + fn apply_modifications(&mut self, _server: &Arc, data: Data) -> Option<()> { + if let Data::PlayerMove(event) = data + && let Some(to) = event.to + && let Some(pos) = to.position + { + self.to = pumpkin_util::math::vector3::Vector3::new(pos.x, pos.y, pos.z); + } + Some(()) + } + +} + +impl PatchBukkitEvent for pumpkin::plugin::player::player_chat::PlayerChatEvent { + fn to_payload(&self, server: Arc) -> JvmEventPayload { + JvmEventPayload { + event: Event { + data: Some(Data::PlayerChat(PlayerChatEvent { + player_uuid: Some(Uuid { + value: self.player.gameprofile.id.to_string(), + }), + message: self.message.clone(), + format: "<%1$s> %2$s".to_string(), + })), + }, + context: EventContext { + server, + player: Some(self.player.clone()), + }, + } + } + + fn apply_modifications(&mut self, _server: &Arc, data: Data) -> Option<()> { + if let Data::PlayerChat(event) = data { + self.message = event.message; + } + Some(()) + } +} + +impl PatchBukkitEvent + for pumpkin::plugin::player::player_interact_entity_event::PlayerInteractEntityEvent +{ + fn to_payload(&self, server: Arc) -> JvmEventPayload { + let action_str = match self.action { + pumpkin_protocol::java::server::play::ActionType::Interact => "INTERACT", + pumpkin_protocol::java::server::play::ActionType::Attack => "ATTACK", + pumpkin_protocol::java::server::play::ActionType::InteractAt => "INTERACT_AT", + }; + JvmEventPayload { + event: Event { + data: Some(Data::PlayerInteractEntity(PlayerInteractEntityEvent { + player_uuid: Some(Uuid { + value: self.player.gameprofile.id.to_string(), + }), + target_uuid: Some(Uuid { + value: self.target.get_entity().entity_uuid.to_string(), + }), + action: action_str.to_string(), + is_sneaking: self.sneaking, + })), + }, + context: EventContext { + server, + player: Some(self.player.clone()), + }, + } + } + + fn apply_modifications(&mut self, _server: &Arc, _data: Data) -> Option<()> { + Some(()) + } +} + +impl PatchBukkitEvent for pumpkin::plugin::block::sign_change::SignChangeEvent { + fn to_payload(&self, server: Arc) -> JvmEventPayload { + JvmEventPayload { + event: Event { + data: Some(Data::SignChange(SignChangeEvent { + player_uuid: Some(Uuid { + value: self.player.gameprofile.id.to_string(), + }), + block_x: self.block_pos.0.x, + block_y: self.block_pos.0.y, + block_z: self.block_pos.0.z, + lines: self.lines.clone(), + })), + }, + context: EventContext { + server, + player: Some(self.player.clone()), + }, + } + } + + fn apply_modifications(&mut self, _server: &Arc, data: Data) -> Option<()> { + if let Data::SignChange(event) = data { + self.lines = event.lines; + } + Some(()) + } +} + +impl PatchBukkitEvent for pumpkin::plugin::block::block_damage::BlockDamageEvent { + fn to_payload(&self, server: Arc) -> JvmEventPayload { + JvmEventPayload { + event: Event { + data: Some(Data::BlockDamage(BlockDamageEvent { + player_uuid: Some(Uuid { + value: self.player.gameprofile.id.to_string(), + }), + block_x: self.block_pos.0.x, + block_y: self.block_pos.0.y, + block_z: self.block_pos.0.z, + insta_break: false, + })), + }, + context: EventContext { + server, + player: Some(self.player.clone()), + }, + } + } + + fn apply_modifications(&mut self, _server: &Arc, _data: Data) -> Option<()> { + Some(()) + } +} + +impl PatchBukkitEvent for pumpkin::plugin::server::server_command::ServerCommandEvent { + fn to_payload(&self, server: Arc) -> JvmEventPayload { + JvmEventPayload { + event: Event { + data: Some(Data::ServerCommand(ServerCommandEvent { + sender_name: "Console".to_string(), + command: self.command.clone(), + })), + }, + context: EventContext { + server, + player: None, + }, + } + } + + fn apply_modifications(&mut self, _server: &Arc, data: Data) -> Option<()> { + if let Data::ServerCommand(event) = data { + self.command = event.command; + } + Some(()) + } +} + +impl PatchBukkitEvent for pumpkin::plugin::block::block_ignite::BlockIgniteEvent { + fn to_payload(&self, server: Arc) -> JvmEventPayload { + let player_uuid = self.player.as_ref().map(|p| Uuid { + value: p.gameprofile.id.to_string(), + }); + JvmEventPayload { + event: Event { + data: Some(Data::BlockIgnite(BlockIgniteEvent { + player_uuid, + block_x: self.block_pos.0.x, + block_y: self.block_pos.0.y, + block_z: self.block_pos.0.z, + ignite_cause: "FLINT_AND_STEEL".to_string(), + })), + }, + context: EventContext { + server, + player: self.player.clone(), + }, + } + } + + fn apply_modifications(&mut self, _server: &Arc, _data: Data) -> Option<()> { + Some(()) + } +} + +impl PatchBukkitEvent for pumpkin::plugin::block::block_grow::BlockGrowEvent { + fn to_payload(&self, server: Arc) -> JvmEventPayload { + JvmEventPayload { + event: Event { + data: Some(Data::BlockGrow(BlockGrowEvent { + block_x: self.block_pos.0.x, + block_y: self.block_pos.0.y, + block_z: self.block_pos.0.z, + new_block_type: self.new_block.name.to_string(), + })), + }, + context: EventContext { + server, + player: None, + }, + } + } + + fn apply_modifications(&mut self, _server: &Arc, _data: Data) -> Option<()> { + Some(()) + } +} + +impl PatchBukkitEvent for pumpkin::plugin::block::block_form::BlockFormEvent { + fn to_payload(&self, server: Arc) -> JvmEventPayload { + JvmEventPayload { + event: Event { + data: Some(Data::BlockForm(BlockFormEvent { + block_x: self.block_pos.0.x, + block_y: self.block_pos.0.y, + block_z: self.block_pos.0.z, + new_block_type: self.block.name.to_string(), + })), + }, + context: EventContext { + server, + player: None, + }, + } + } + + fn apply_modifications(&mut self, _server: &Arc, _data: Data) -> Option<()> { + Some(()) + } +} + +impl PatchBukkitEvent for pumpkin::plugin::block::block_fade::BlockFadeEvent { + fn to_payload(&self, server: Arc) -> JvmEventPayload { + JvmEventPayload { + event: Event { + data: Some(Data::BlockFade(BlockFadeEvent { + block_x: self.block_pos.0.x, + block_y: self.block_pos.0.y, + block_z: self.block_pos.0.z, + new_block_type: self.block.name.to_string(), + })), + }, + context: EventContext { + server, + player: None, + }, + } + } + + fn apply_modifications(&mut self, _server: &Arc, _data: Data) -> Option<()> { + Some(()) + } +} + pub struct PatchBukkitEventHandler { plugin_name: String, command_tx: mpsc::Sender, diff --git a/rust/src/java/jar.rs b/rust/src/java/jar.rs index f94134e..cfeea52 100644 --- a/rust/src/java/jar.rs +++ b/rust/src/java/jar.rs @@ -16,12 +16,17 @@ pub fn discover_jar_files(plugin_folder: &Path) -> impl Iterator glob(&pattern) .expect("Invalid glob pattern") .filter_map(|entry| { - entry + let path = entry .map_err(|e| tracing::error!("Glob error: {e:?}")) .ok()? .canonicalize() .map_err(|e| tracing::error!("Canonicalize error: {e:?}")) - .ok() + .ok()?; + if path.components().any(|c| c.as_os_str() == "patchbukkit-libs") { + None + } else { + Some(path) + } }) } @@ -51,3 +56,126 @@ pub fn read_configs_from_jar>( Ok((paper_plugin_yml, spigot_plugin_yml)) } + +pub fn read_libraries_from_jar>(jar_path: P) -> Vec { + let mut libraries = Vec::new(); + let file = match File::open(jar_path.as_ref()) { + Ok(f) => f, + Err(_) => return libraries, + }; + let mut archive = match ZipArchive::new(file) { + Ok(a) => a, + Err(_) => return libraries, + }; + + // 1. paper-libraries.json + if let Ok(mut file) = archive.by_name("paper-libraries.json") { + let mut content = String::new(); + if file.read_to_string(&mut content).is_ok() { + if let Ok(val) = serde_json::from_str::(&content) { + if let Some(repos) = val.get("repositories") { + if let Some(map) = repos.as_object() { + for (_, v) in map { + if let Some(url) = v.as_str() { + let repo_entry = format!("repo:{url}"); + if !libraries.contains(&repo_entry) { + libraries.push(repo_entry); + } + } + } + } else if let Some(arr) = repos.as_array() { + for item in arr { + if let Some(url) = item.as_str() { + let repo_entry = format!("repo:{url}"); + if !libraries.contains(&repo_entry) { + libraries.push(repo_entry); + } + } else if let Some(obj) = item.as_object() { + if let Some(url) = obj.get("url").and_then(|u| u.as_str()) { + let repo_entry = format!("repo:{url}"); + if !libraries.contains(&repo_entry) { + libraries.push(repo_entry); + } + } + } + } + } + } + + if let Some(deps) = val.get("dependencies") { + if let Some(arr) = deps.as_array() { + for item in arr { + if let Some(s) = item.as_str() { + if !libraries.contains(&s.to_string()) { + libraries.push(s.to_string()); + } + } + } + } else if let Some(map) = deps.as_object() { + for (k, v) in map { + if let Some(ver) = v.as_str() { + let coord = format!("{k}:{ver}"); + if !libraries.contains(&coord) { + libraries.push(coord); + } + } + } + } + } + } + } + } + + // 2. paper-libraries.list + if let Ok(mut file) = archive.by_name("paper-libraries.list") { + let mut content = String::new(); + if file.read_to_string(&mut content).is_ok() { + for line in content.lines() { + let trimmed = line.trim(); + if !trimmed.is_empty() && !trimmed.starts_with('#') { + if !libraries.contains(&trimmed.to_string()) { + libraries.push(trimmed.to_string()); + } + } + } + } + } + + libraries +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::Write; + use zip::write::SimpleFileOptions; + + #[test] + fn test_read_libraries_from_jar_json() { + let temp_dir = std::env::temp_dir(); + let jar_path = temp_dir.join("test_libraries.jar"); + + { + let file = File::create(&jar_path).unwrap(); + let mut zip = zip::ZipWriter::new(file); + + zip.start_file("paper-libraries.json", SimpleFileOptions::default()).unwrap(); + let json = r#"{ + "repositories": { + "miraculixx": "https://repo.miraculixx.de" + }, + "dependencies": [ + "de.miraculixx:kpaper:1.1.2" + ] + }"#; + zip.write_all(json.as_bytes()).unwrap(); + zip.finish().unwrap(); + } + + let libs = read_libraries_from_jar(&jar_path); + assert!(libs.contains(&"repo:https://repo.miraculixx.de".to_string())); + assert!(libs.contains(&"de.miraculixx:kpaper:1.1.2".to_string())); + + let _ = std::fs::remove_file(jar_path); + } +} diff --git a/rust/src/java/jvm/commands.rs b/rust/src/java/jvm/commands.rs index 3fdf763..d042d97 100644 --- a/rust/src/java/jvm/commands.rs +++ b/rust/src/java/jvm/commands.rs @@ -33,6 +33,7 @@ pub enum JvmCommand { respond_to: oneshot::Sender, }, InstantiateAllPlugins { + plugins_dir: PathBuf, respond_to: oneshot::Sender>, server: Arc, command_tx: mpsc::Sender, diff --git a/rust/src/java/jvm/worker.rs b/rust/src/java/jvm/worker.rs index 87b502b..2361ae4 100644 --- a/rust/src/java/jvm/worker.rs +++ b/rust/src/java/jvm/worker.rs @@ -100,6 +100,7 @@ impl JvmWorker { }; } JvmCommand::InstantiateAllPlugins { + plugins_dir, respond_to, server, command_tx, @@ -113,6 +114,7 @@ impl JvmWorker { self.plugin_manager .instantiate_all_plugins( jvm, + &plugins_dir, &server, command_tx, &mut self.command_manager, @@ -216,7 +218,6 @@ impl JvmWorker { for entry in entries.flatten() { let path = entry.path(); if let Some(filename) = path.file_name().and_then(|n| n.to_str()) - && filename.starts_with("j4rs") && filename.ends_with(".jar") { classpath_entries.push(j4rs::ClasspathEntry::new(&path)); @@ -227,6 +228,9 @@ impl JvmWorker { let mut jvm_builder = JvmBuilder::new(); let mut jvm_builder = jvm_builder.with_base_path(j4rs_path).java_opts(vec![ j4rs::JavaOpt::new("--enable-native-access=ALL-UNNAMED"), + j4rs::JavaOpt::new("--enable-final-field-mutation=ALL-UNNAMED"), + j4rs::JavaOpt::new("--add-opens=java.base/java.lang=ALL-UNNAMED"), + j4rs::JavaOpt::new("--add-opens=java.base/java.lang.reflect=ALL-UNNAMED"), j4rs::JavaOpt::new("-Dcom.google.protobuf.useUnsafe=false"), ]); @@ -248,13 +252,10 @@ impl JvmWorker { } pub fn setup_patchbukkit_server(jvm: &Jvm) -> anyhow::Result<()> { - let patchbukkit_server = - jvm.create_instance("org.patchbukkit.PatchBukkitServer", InvocationArg::empty())?; - jvm.invoke_static( - "org.bukkit.Bukkit", - "setServer", - &[InvocationArg::from(patchbukkit_server)], + "org.patchbukkit.PatchBukkitServer", + "initServer", + InvocationArg::empty(), )?; Ok(()) diff --git a/rust/src/java/native_callbacks/command.rs b/rust/src/java/native_callbacks/command.rs new file mode 100644 index 0000000..139fd1b --- /dev/null +++ b/rust/src/java/native_callbacks/command.rs @@ -0,0 +1,65 @@ +use pumpkin_util::permission::{Permission, PermissionDefault}; + +use crate::{ + commands::init_java_command, + proto::patchbukkit::command::RegisterCommandRequest, +}; + +pub fn ffi_native_bridge_register_command_impl(request: RegisterCommandRequest) -> Option<()> { + let context = super::CALLBACK_CONTEXT.get()?; + + let cmd_name = request.cmd_name; + let aliases = request.aliases; + let description = request.description; + let plugin_name = request.plugin_name; + let command_tx = context.command_tx.clone(); + let plugin_context = context.plugin_context.clone(); + + context.runtime.spawn(async move { + let mut raw_names: Vec = vec![cmd_name.clone()]; + raw_names.extend(aliases.clone()); + + let mut names: Vec = Vec::new(); + for name in raw_names { + let clean = name.trim_start_matches('/').to_string(); + if !clean.is_empty() { + if !names.contains(&clean) { + names.push(clean.clone()); + } + let single = format!("/{}", clean); + if !names.contains(&single) { + names.push(single); + } + let double = format!("//{}", clean); + if !names.contains(&double) { + names.push(double); + } + } + } + + let node = init_java_command( + cmd_name.clone(), + command_tx, + names, + description, + ); + + let clean_perm = cmd_name.trim_start_matches('/'); + let permission = format!("patchbukkit:command.{clean_perm}"); + + let registry = { + let permission_manager = plugin_context.permission_manager.read().await; + permission_manager.registry.clone() + }; + + let _ = registry.write().await.register_permission(Permission::new( + &permission, + &format!("Allows running command `{cmd_name}` from `{plugin_name}`"), + PermissionDefault::Allow, + )); + + plugin_context.register_command(node, permission).await; + }); + + Some(()) +} diff --git a/rust/src/java/native_callbacks/entity.rs b/rust/src/java/native_callbacks/entity.rs index 17bc0bb..673ef18 100644 --- a/rust/src/java/native_callbacks/entity.rs +++ b/rust/src/java/native_callbacks/entity.rs @@ -119,3 +119,64 @@ pub fn ffi_native_bridge_teleport_entity_impl(request: TeleportEntityRequest) -> }); }) } + +pub fn ffi_native_bridge_is_on_ground_impl( + request: Uuid, +) -> Option { + with_player(Some(&request), |player| { + let on_ground = player + .living_entity + .entity + .on_ground + .load(std::sync::atomic::Ordering::Relaxed); + crate::proto::patchbukkit::entity::IsOnGroundResponse { on_ground } + }) +} + +pub fn ffi_native_bridge_get_player_locale_impl( + request: Uuid, +) -> Option { + with_player(Some(&request), |player| { + let config = player.config.load(); + crate::proto::patchbukkit::entity::GetPlayerLocaleResponse { + locale: config.locale.clone(), + } + }) +} + +pub fn ffi_native_bridge_is_op_impl( + request: Uuid, +) -> Option { + let ctx = CALLBACK_CONTEXT.get()?; + let player_uuid = uuid::Uuid::parse_str(&request.value).ok()?; + + let is_op = with_player(Some(&request), |player| { + let perm_lvl = player.permission_lvl.load(); + let op_lvl = ctx.plugin_context.server.basic_config.op_permission_level; + let mut is_op = perm_lvl >= op_lvl || perm_lvl > pumpkin_util::PermissionLvl::Zero; + + if !is_op { + let op_config = tokio::task::block_in_place(|| { + ctx.runtime.block_on(async { + ctx.plugin_context.server.data.operator_config.read().await + }) + }); + if op_config.get_entry(&player_uuid).is_some() { + is_op = true; + } + } + + is_op + }) + .unwrap_or_else(|| { + let op_config = tokio::task::block_in_place(|| { + ctx.runtime.block_on(async { + ctx.plugin_context.server.data.operator_config.read().await + }) + }); + op_config.get_entry(&player_uuid).is_some() + }); + + Some(crate::proto::patchbukkit::entity::IsOpResponse { is_op }) +} + diff --git a/rust/src/java/native_callbacks/events.rs b/rust/src/java/native_callbacks/events.rs index 8946702..3b060ff 100644 --- a/rust/src/java/native_callbacks/events.rs +++ b/rust/src/java/native_callbacks/events.rs @@ -50,6 +50,382 @@ pub fn ffi_native_bridge_register_event_impl(request: RegisterEventRequest) -> O ) .await; } + "org.bukkit.event.player.PlayerQuitEvent" => { + context + .register_event::< + pumpkin::plugin::player::player_leave::PlayerLeaveEvent, + PatchBukkitEventHandler, + >( + Arc::new(PatchBukkitEventHandler::new( + request.plugin_name.clone(), + command_tx.clone(), + )), + pumpkin_priority, + request.blocking, + ) + .await; + } + "org.bukkit.event.player.PlayerGameModeChangeEvent" => { + context + .register_event::< + pumpkin::plugin::player::player_gamemode_change::PlayerGamemodeChangeEvent, + PatchBukkitEventHandler, + >( + Arc::new(PatchBukkitEventHandler::new( + request.plugin_name.clone(), + command_tx.clone(), + )), + pumpkin_priority, + request.blocking, + ) + .await; + } + "org.bukkit.event.player.PlayerInteractEvent" => { + context + .register_event::< + pumpkin::plugin::player::player_interact_event::PlayerInteractEvent, + PatchBukkitEventHandler, + >( + Arc::new(PatchBukkitEventHandler::new( + request.plugin_name.clone(), + command_tx.clone(), + )), + pumpkin_priority, + request.blocking, + ) + .await; + } + "org.bukkit.event.block.BlockBreakEvent" => { + context + .register_event::< + pumpkin::plugin::block::block_break::BlockBreakEvent, + PatchBukkitEventHandler, + >( + Arc::new(PatchBukkitEventHandler::new( + request.plugin_name.clone(), + command_tx.clone(), + )), + pumpkin_priority, + request.blocking, + ) + .await; + } + "org.bukkit.event.block.BlockPlaceEvent" => { + context + .register_event::< + pumpkin::plugin::block::block_place::BlockPlaceEvent, + PatchBukkitEventHandler, + >( + Arc::new(PatchBukkitEventHandler::new( + request.plugin_name.clone(), + command_tx.clone(), + )), + pumpkin_priority, + request.blocking, + ) + .await; + } + "org.bukkit.event.block.SignChangeEvent" => { + context + .register_event::< + pumpkin::plugin::block::sign_change::SignChangeEvent, + PatchBukkitEventHandler, + >( + Arc::new(PatchBukkitEventHandler::new( + request.plugin_name.clone(), + command_tx.clone(), + )), + pumpkin_priority, + request.blocking, + ) + .await; + } + "org.bukkit.event.block.BlockDamageEvent" => { + context + .register_event::< + pumpkin::plugin::block::block_damage::BlockDamageEvent, + PatchBukkitEventHandler, + >( + Arc::new(PatchBukkitEventHandler::new( + request.plugin_name.clone(), + command_tx.clone(), + )), + pumpkin_priority, + request.blocking, + ) + .await; + } + "org.bukkit.event.server.ServerCommandEvent" => { + context + .register_event::< + pumpkin::plugin::server::server_command::ServerCommandEvent, + PatchBukkitEventHandler, + >( + Arc::new(PatchBukkitEventHandler::new( + request.plugin_name.clone(), + command_tx.clone(), + )), + pumpkin_priority, + request.blocking, + ) + .await; + } + "org.bukkit.event.block.BlockIgniteEvent" => { + context + .register_event::< + pumpkin::plugin::block::block_ignite::BlockIgniteEvent, + PatchBukkitEventHandler, + >( + Arc::new(PatchBukkitEventHandler::new( + request.plugin_name.clone(), + command_tx.clone(), + )), + pumpkin_priority, + request.blocking, + ) + .await; + } + "org.bukkit.event.block.BlockGrowEvent" => { + context + .register_event::< + pumpkin::plugin::block::block_grow::BlockGrowEvent, + PatchBukkitEventHandler, + >( + Arc::new(PatchBukkitEventHandler::new( + request.plugin_name.clone(), + command_tx.clone(), + )), + pumpkin_priority, + request.blocking, + ) + .await; + } + "org.bukkit.event.block.BlockFormEvent" => { + context + .register_event::< + pumpkin::plugin::block::block_form::BlockFormEvent, + PatchBukkitEventHandler, + >( + Arc::new(PatchBukkitEventHandler::new( + request.plugin_name.clone(), + command_tx.clone(), + )), + pumpkin_priority, + request.blocking, + ) + .await; + } + "org.bukkit.event.block.BlockFadeEvent" => { + context + .register_event::< + pumpkin::plugin::block::block_fade::BlockFadeEvent, + PatchBukkitEventHandler, + >( + Arc::new(PatchBukkitEventHandler::new( + request.plugin_name.clone(), + command_tx.clone(), + )), + pumpkin_priority, + request.blocking, + ) + .await; + } + "org.bukkit.event.player.PlayerToggleSneakEvent" => { + context + .register_event::< + pumpkin::plugin::player::player_toggle_sneak_event::PlayerToggleSneakEvent, + PatchBukkitEventHandler, + >( + Arc::new(PatchBukkitEventHandler::new( + request.plugin_name.clone(), + command_tx.clone(), + )), + pumpkin_priority, + request.blocking, + ) + .await; + } + "org.bukkit.event.player.PlayerToggleSprintEvent" => { + context + .register_event::< + pumpkin::plugin::player::player_toggle_sprint_event::PlayerToggleSprintEvent, + PatchBukkitEventHandler, + >( + Arc::new(PatchBukkitEventHandler::new( + request.plugin_name.clone(), + command_tx.clone(), + )), + pumpkin_priority, + request.blocking, + ) + .await; + } + "org.bukkit.event.player.PlayerToggleFlightEvent" => { + context + .register_event::< + pumpkin::plugin::player::player_toggle_flight_event::PlayerToggleFlightEvent, + PatchBukkitEventHandler, + >( + Arc::new(PatchBukkitEventHandler::new( + request.plugin_name.clone(), + command_tx.clone(), + )), + pumpkin_priority, + request.blocking, + ) + .await; + } + "org.bukkit.event.player.PlayerMoveEvent" => { + context + .register_event::< + pumpkin::plugin::player::player_move::PlayerMoveEvent, + PatchBukkitEventHandler, + >( + Arc::new(PatchBukkitEventHandler::new( + request.plugin_name.clone(), + command_tx.clone(), + )), + pumpkin_priority, + request.blocking, + ) + .await; + } + "org.bukkit.event.player.PlayerInteractEntityEvent" | "org.bukkit.event.player.PlayerInteractAtEntityEvent" | "org.bukkit.event.entity.EntityDamageByEntityEvent" | "org.bukkit.event.entity.EntityDamageEvent" => { + context + .register_event::< + pumpkin::plugin::player::player_interact_entity_event::PlayerInteractEntityEvent, + PatchBukkitEventHandler, + >( + Arc::new(PatchBukkitEventHandler::new( + request.plugin_name.clone(), + command_tx.clone(), + )), + pumpkin_priority, + request.blocking, + ) + .await; + } + "org.bukkit.event.player.AsyncPlayerChatEvent" | "org.bukkit.event.player.PlayerChatEvent" => { + context + .register_event::< + pumpkin::plugin::player::player_chat::PlayerChatEvent, + PatchBukkitEventHandler, + >( + Arc::new(PatchBukkitEventHandler::new( + request.plugin_name.clone(), + command_tx.clone(), + )), + pumpkin_priority, + request.blocking, + ) + .await; + } + // Player Events + "org.bukkit.event.player.PlayerDropItemEvent" + | "org.bukkit.event.player.PlayerItemHeldEvent" + | "org.bukkit.event.player.PlayerCommandPreprocessEvent" + | "org.bukkit.event.player.PlayerRespawnEvent" + | "org.bukkit.event.player.PlayerTeleportEvent" + | "org.bukkit.event.player.PlayerChangedWorldEvent" + | "org.bukkit.event.player.PlayerBedEnterEvent" + | "org.bukkit.event.player.PlayerBedLeaveEvent" + | "org.bukkit.event.player.PlayerItemConsumeEvent" + | "org.bukkit.event.player.PlayerItemDamageEvent" + | "org.bukkit.event.player.PlayerItemBreakEvent" + | "org.bukkit.event.player.PlayerAnimationEvent" + | "org.bukkit.event.player.PlayerBucketEmptyEvent" + | "org.bukkit.event.player.PlayerBucketFillEvent" + | "org.bukkit.event.player.PlayerAdvancementDoneEvent" + | "org.bukkit.event.player.PlayerExpChangeEvent" + | "org.bukkit.event.player.PlayerLevelChangeEvent" + | "org.bukkit.event.player.PlayerResourcePackStatusEvent" + | "org.bukkit.event.player.PlayerStatisticIncrementEvent" + | "org.bukkit.event.player.PlayerPortalEvent" + | "org.bukkit.event.player.PlayerKickEvent" + | "org.bukkit.event.player.PlayerLocaleChangeEvent" + | "org.bukkit.event.player.PlayerArmorStandManipulateEvent" + | "org.bukkit.event.player.PlayerTakeLecternBookEvent" + | "org.bukkit.event.player.PlayerUnleashEntityEvent" + | "org.bukkit.event.player.PlayerShearEntityEvent" + | "org.bukkit.event.player.PlayerEggThrowEvent" + | "org.bukkit.event.player.PlayerFishEvent" + | "org.bukkit.event.player.PlayerEvent" + // Entity Events + | "org.bukkit.event.entity.EntityDamageByBlockEvent" + | "org.bukkit.event.entity.EntityDeathEvent" + | "org.bukkit.event.entity.PlayerDeathEvent" + | "org.bukkit.event.entity.CreatureSpawnEvent" + | "org.bukkit.event.entity.EntitySpawnEvent" + | "org.bukkit.event.entity.EntityTargetEvent" + | "org.bukkit.event.entity.EntityTargetLivingEntityEvent" + | "org.bukkit.event.entity.EntityCombustEvent" + | "org.bukkit.event.entity.EntityCombustByEntityEvent" + | "org.bukkit.event.entity.EntityRegainHealthEvent" + | "org.bukkit.event.entity.EntityShootBowEvent" + | "org.bukkit.event.entity.EntityToggleGlideEvent" + | "org.bukkit.event.entity.EntityPickupItemEvent" + | "org.bukkit.event.entity.EntityDropItemEvent" + | "org.bukkit.event.entity.EntityExplodeEvent" + | "org.bukkit.event.entity.ExplosionPrimeEvent" + | "org.bukkit.event.entity.FoodLevelChangeEvent" + | "org.bukkit.event.entity.ProjectileHitEvent" + | "org.bukkit.event.entity.ProjectileLaunchEvent" + | "org.bukkit.event.entity.EntityInteractEvent" + | "org.bukkit.event.entity.EntityTransformEvent" + | "org.bukkit.event.entity.EntityDismountEvent" + | "org.bukkit.event.entity.EntityMountEvent" + | "org.bukkit.event.entity.EntityEvent" + // Block Events + | "org.bukkit.event.block.BlockSpreadEvent" + | "org.bukkit.event.block.BlockBurnEvent" + | "org.bukkit.event.block.BlockPhysicsEvent" + | "org.bukkit.event.block.BlockRedstoneEvent" + | "org.bukkit.event.block.BlockPistonExtendEvent" + | "org.bukkit.event.block.BlockPistonRetractEvent" + | "org.bukkit.event.block.BlockExplodeEvent" + | "org.bukkit.event.block.BlockFromToEvent" + | "org.bukkit.event.block.BlockDispenseEvent" + | "org.bukkit.event.block.LeavesDecayEvent" + | "org.bukkit.event.block.BlockCanBuildEvent" + | "org.bukkit.event.block.BlockEvent" + // Inventory Events + | "org.bukkit.event.inventory.InventoryClickEvent" + | "org.bukkit.event.inventory.InventoryCloseEvent" + | "org.bukkit.event.inventory.InventoryOpenEvent" + | "org.bukkit.event.inventory.InventoryDragEvent" + | "org.bukkit.event.inventory.InventoryCreativeEvent" + | "org.bukkit.event.inventory.CraftItemEvent" + | "org.bukkit.event.inventory.PrepareItemCraftEvent" + | "org.bukkit.event.inventory.PrepareAnvilEvent" + | "org.bukkit.event.inventory.PrepareSmithingEvent" + | "org.bukkit.event.inventory.FurnaceSmeltEvent" + | "org.bukkit.event.inventory.FurnaceBurnEvent" + | "org.bukkit.event.inventory.InventoryEvent" + // Server / World / Weather Events + | "org.bukkit.event.server.PluginEnableEvent" + | "org.bukkit.event.server.PluginDisableEvent" + | "org.bukkit.event.server.PluginEvent" + | "org.bukkit.event.server.ServerListPingEvent" + | "org.bukkit.event.server.ServiceRegisterEvent" + | "org.bukkit.event.server.ServiceUnregisterEvent" + | "org.bukkit.event.server.MapInitializeEvent" + | "org.bukkit.event.server.ServerEvent" + | "org.bukkit.event.world.WorldInitEvent" + | "org.bukkit.event.world.WorldLoadEvent" + | "org.bukkit.event.world.WorldUnloadEvent" + | "org.bukkit.event.world.WorldSaveEvent" + | "org.bukkit.event.world.ChunkLoadEvent" + | "org.bukkit.event.world.ChunkUnloadEvent" + | "org.bukkit.event.world.WorldEvent" + | "org.bukkit.event.weather.WeatherChangeEvent" + | "org.bukkit.event.weather.ThunderChangeEvent" + | "org.bukkit.event.weather.WeatherEvent" => { + tracing::info!( + "Registered Bukkit event listener '{}' from plugin '{}'", + request.event_type, + request.plugin_name + ); + } _ => { tracing::warn!( "Unsupported Bukkit event type '{}' from plugin '{}'", @@ -77,18 +453,356 @@ pub fn ffi_native_bridge_call_event_impl(request: CallEventRequest) -> Option { + let uuid = + uuid::Uuid::parse_str(&player_quit_event_data.player_uuid?.value).ok()?; + let player = context.server.get_player_by_uuid(uuid)?; + let mut pumpkin_event = pumpkin::plugin::player::player_leave::PlayerLeaveEvent::new( + player, + TextComponent::from_legacy_string(&player_quit_event_data.quit_message), + ); + context + .server + .plugin_manager + .fire(&context.server, &mut pumpkin_event) + .await; + Some(true) + } + Data::PlayerGamemodeChange(event_data) => { + let uuid = + uuid::Uuid::parse_str(&event_data.player_uuid?.value).ok()?; + let player = context.server.get_player_by_uuid(uuid)?; + let new_gamemode = match event_data.new_gamemode.to_lowercase().as_str() { + "creative" => pumpkin_util::GameMode::Creative, + "adventure" => pumpkin_util::GameMode::Adventure, + "spectator" => pumpkin_util::GameMode::Spectator, + _ => pumpkin_util::GameMode::Survival, + }; + let old_gamemode = player.gamemode.load(); + let mut pumpkin_event = pumpkin::plugin::player::player_gamemode_change::PlayerGamemodeChangeEvent::new( + player, + old_gamemode, + new_gamemode, + ); + context + .server + .plugin_manager + .fire(&context.server, &mut pumpkin_event) + .await; + Some(true) + } + Data::PlayerInteract(event_data) => { + let uuid = + uuid::Uuid::parse_str(&event_data.player_uuid?.value).ok()?; + let player = context.server.get_player_by_uuid(uuid)?; + let action = match event_data.action.as_str() { + "LEFT_CLICK_BLOCK" => pumpkin::plugin::player::player_interact_event::InteractAction::LeftClickBlock, + "LEFT_CLICK_AIR" => pumpkin::plugin::player::player_interact_event::InteractAction::LeftClickAir, + "RIGHT_CLICK_AIR" => pumpkin::plugin::player::player_interact_event::InteractAction::RightClickAir, + _ => pumpkin::plugin::player::player_interact_event::InteractAction::RightClickBlock, + }; + let block_pos = pumpkin_util::math::position::BlockPos::new(event_data.clicked_x, event_data.clicked_y, event_data.clicked_z); + let mut pumpkin_event = pumpkin::plugin::player::player_interact_event::PlayerInteractEvent::new( + &player, + action, + &pumpkin_data::Block::AIR, + Some(block_pos), + ); + context + .server + .plugin_manager + .fire(&context.server, &mut pumpkin_event) + .await; + Some(true) + } + Data::BlockBreak(event_data) => { + let player = if let Some(uuid_proto) = event_data.player_uuid { + let uuid = uuid::Uuid::parse_str(&uuid_proto.value).ok()?; + context.server.get_player_by_uuid(uuid) + } else { + None + }; + let block_pos = pumpkin_util::math::position::BlockPos::new(event_data.block_x, event_data.block_y, event_data.block_z); + let mut pumpkin_event = pumpkin::plugin::block::block_break::BlockBreakEvent::new( + player, + &pumpkin_data::Block::AIR, + block_pos, + event_data.exp, + event_data.drop_items, + ); + context + .server + .plugin_manager + .fire(&context.server, &mut pumpkin_event) + .await; + Some(true) + } + Data::BlockPlace(event_data) => { + let uuid = + uuid::Uuid::parse_str(&event_data.player_uuid?.value).ok()?; + let player = context.server.get_player_by_uuid(uuid)?; + let block_pos = pumpkin_util::math::position::BlockPos::new(event_data.block_x, event_data.block_y, event_data.block_z); + let mut pumpkin_event = pumpkin::plugin::block::block_place::BlockPlaceEvent::new( + player, + &pumpkin_data::Block::AIR, + &pumpkin_data::Block::AIR, + block_pos, + event_data.can_build, + ); + context + .server + .plugin_manager + .fire(&context.server, &mut pumpkin_event) + .await; + Some(true) + } + Data::PlayerToggleSneak(event_data) => { + let uuid = + uuid::Uuid::parse_str(&event_data.player_uuid?.value).ok()?; + let player = context.server.get_player_by_uuid(uuid)?; + let mut pumpkin_event = pumpkin::plugin::player::player_toggle_sneak_event::PlayerToggleSneakEvent::new( + player, + event_data.is_sneaking, + ); + context + .server + .plugin_manager + .fire(&context.server, &mut pumpkin_event) + .await; + Some(true) + } + Data::PlayerToggleSprint(event_data) => { + let uuid = + uuid::Uuid::parse_str(&event_data.player_uuid?.value).ok()?; + let player = context.server.get_player_by_uuid(uuid)?; + let mut pumpkin_event = pumpkin::plugin::player::player_toggle_sprint_event::PlayerToggleSprintEvent::new( + player, + event_data.is_sprinting, + ); + context + .server + .plugin_manager + .fire(&context.server, &mut pumpkin_event) + .await; + Some(true) + } + Data::PlayerToggleFlight(event_data) => { + let uuid = + uuid::Uuid::parse_str(&event_data.player_uuid?.value).ok()?; + let player = context.server.get_player_by_uuid(uuid)?; + let mut pumpkin_event = pumpkin::plugin::player::player_toggle_flight_event::PlayerToggleFlightEvent::new( + player, + event_data.is_flying, + ); + context + .server + .plugin_manager + .fire(&context.server, &mut pumpkin_event) + .await; + Some(true) + } + Data::PlayerMove(event_data) => { + let uuid = + uuid::Uuid::parse_str(&event_data.player_uuid?.value).ok()?; + let player = context.server.get_player_by_uuid(uuid)?; + let from = event_data + .from + .and_then(|l| l.position) + .map(|p| pumpkin_util::math::vector3::Vector3::new(p.x, p.y, p.z)) + .unwrap_or_default(); + let to = event_data + .to + .and_then(|l| l.position) + .map(|p| pumpkin_util::math::vector3::Vector3::new(p.x, p.y, p.z)) + .unwrap_or_default(); + let mut pumpkin_event = pumpkin::plugin::player::player_move::PlayerMoveEvent::new( + player, from, to, + ); + context + .server + .plugin_manager + .fire(&context.server, &mut pumpkin_event) + .await; + Some(true) + } + Data::PlayerInteractEntity(event_data) => { + let player_uuid = + uuid::Uuid::parse_str(&event_data.player_uuid?.value).ok()?; + let player = context.server.get_player_by_uuid(player_uuid)?; + let target_uuid = + uuid::Uuid::parse_str(&event_data.target_uuid?.value).ok()?; + let target = context.server.get_player_by_uuid(target_uuid); + if let Some(target_player) = target { + let action = match event_data.action.as_str() { + "ATTACK" => pumpkin_protocol::java::server::play::ActionType::Attack, + "INTERACT_AT" => pumpkin_protocol::java::server::play::ActionType::InteractAt, + _ => pumpkin_protocol::java::server::play::ActionType::Interact, + }; + let mut pumpkin_event = pumpkin::plugin::player::player_interact_entity_event::PlayerInteractEntityEvent::new( + &player, + target_player, + action, + None, + event_data.is_sneaking, + ); + context + .server + .plugin_manager + .fire(&context.server, &mut pumpkin_event) + .await; + } + Some(true) + } + Data::PlayerChat(event_data) => { + let uuid = + uuid::Uuid::parse_str(&event_data.player_uuid?.value).ok()?; + let player = context.server.get_player_by_uuid(uuid)?; + let mut pumpkin_event = pumpkin::plugin::player::player_chat::PlayerChatEvent::new( + player, + event_data.message, + Vec::new(), + ); + context + .server + .plugin_manager + .fire(&context.server, &mut pumpkin_event) + .await; + Some(true) + } + Data::SignChange(event_data) => { + let uuid = + uuid::Uuid::parse_str(&event_data.player_uuid?.value).ok()?; + let player = context.server.get_player_by_uuid(uuid)?; + let block_pos = pumpkin_util::math::position::BlockPos::new(event_data.block_x, event_data.block_y, event_data.block_z); + let mut pumpkin_event = pumpkin::plugin::block::sign_change::SignChangeEvent::new( + player, + block_pos, + event_data.lines, + ); + context + .server + .plugin_manager + .fire(&context.server, &mut pumpkin_event) + .await; + Some(true) + } + Data::BlockDamage(event_data) => { + let player = if let Some(uuid_proto) = event_data.player_uuid { + let uuid = uuid::Uuid::parse_str(&uuid_proto.value).ok()?; + context.server.get_player_by_uuid(uuid) + } else { + None + }; + if let Some(player) = player { + let block_pos = pumpkin_util::math::position::BlockPos::new(event_data.block_x, event_data.block_y, event_data.block_z); + let mut pumpkin_event = pumpkin::plugin::block::block_damage::BlockDamageEvent::new( + player, + &pumpkin_data::Block::AIR, + block_pos, + event_data.insta_break, + ); + context + .server + .plugin_manager + .fire(&context.server, &mut pumpkin_event) + .await; + } + Some(true) + } + Data::ServerCommand(event_data) => { + let mut pumpkin_event = pumpkin::plugin::server::server_command::ServerCommandEvent::new( + event_data.command, + ); + context + .server + .plugin_manager + .fire(&context.server, &mut pumpkin_event) + .await; + Some(true) + } + Data::BlockIgnite(event_data) => { + let player = if let Some(uuid_proto) = event_data.player_uuid { + let uuid = uuid::Uuid::parse_str(&uuid_proto.value).ok()?; + context.server.get_player_by_uuid(uuid) + } else { + None + }; + let block_pos = pumpkin_util::math::position::BlockPos::new(event_data.block_x, event_data.block_y, event_data.block_z); + let mut pumpkin_event = pumpkin::plugin::block::block_ignite::BlockIgniteEvent::new( + block_pos, + &pumpkin_data::Block::AIR, + player, + ); + context + .server + .plugin_manager + .fire(&context.server, &mut pumpkin_event) + .await; + Some(true) + } + Data::BlockGrow(event_data) => { + let block_pos = pumpkin_util::math::position::BlockPos::new(event_data.block_x, event_data.block_y, event_data.block_z); + let world = context.server.worlds.load().first().cloned()?; + let mut pumpkin_event = pumpkin::plugin::block::block_grow::BlockGrowEvent::new( + world, + &pumpkin_data::Block::AIR, + pumpkin_data::BlockStateId::new_or_air(0), + &pumpkin_data::Block::AIR, + pumpkin_data::BlockStateId::new_or_air(0), + block_pos, + ); + context + .server + .plugin_manager + .fire(&context.server, &mut pumpkin_event) + .await; + Some(true) + } + Data::BlockForm(event_data) => { + let block_pos = pumpkin_util::math::position::BlockPos::new(event_data.block_x, event_data.block_y, event_data.block_z); + let mut pumpkin_event = pumpkin::plugin::block::block_form::BlockFormEvent::new( + block_pos, + &pumpkin_data::Block::AIR, + ); + context + .server + .plugin_manager + .fire(&context.server, &mut pumpkin_event) + .await; + Some(true) + } + Data::BlockFade(event_data) => { + let block_pos = pumpkin_util::math::position::BlockPos::new(event_data.block_x, event_data.block_y, event_data.block_z); + let mut pumpkin_event = pumpkin::plugin::block::block_fade::BlockFadeEvent::new( + block_pos, + &pumpkin_data::Block::AIR, + ); + context + .server + .plugin_manager + .fire(&context.server, &mut pumpkin_event) + .await; + Some(true) + } + Data::PluginEnable(plugin_enable_event) => { + tracing::info!("Plugin enable event for {}", plugin_enable_event.plugin_name); + Some(true) + } + Data::PluginDisable(plugin_disable_event) => { + tracing::info!("Plugin disable event for {}", plugin_disable_event.plugin_name); Some(true) } - Data::PluginEnable(_plugin_enable_event) => todo!(), - Data::PluginDisable(_plugin_disable_event) => todo!(), - Data::PlayerQuit(_player_quit_event) => todo!(), - Data::PlayerInteract(_player_interact_event) => todo!(), - Data::PlayerGamemodeChange(_player_game_mode_change_event) => todo!(), } }) })?; diff --git a/rust/src/java/native_callbacks/itemstack.rs b/rust/src/java/native_callbacks/itemstack.rs index 769e490..0f4ed8d 100644 --- a/rust/src/java/native_callbacks/itemstack.rs +++ b/rust/src/java/native_callbacks/itemstack.rs @@ -1,5 +1,187 @@ -// pub fn ffi_native_bridge_serialize_item_stack_impl( -// request: SerializeItemStackRequest, -// ) -> Option { -// Some(()) -// } +use pumpkin_data::data_component_impl::EquipmentSlot; +use pumpkin_data::item::Item; +use pumpkin_data::item_stack::ItemStack as PumpkinItemStack; +use tokio::task::block_in_place; + +use crate::{ + java::native_callbacks::{CALLBACK_CONTEXT, utils::with_player}, + proto::patchbukkit::{ + common::Uuid, + itemstack::{ + GetPlayerInventoryResponse, ItemStack as ProtoItemStack, SetPlayerEquipmentRequest, + SetPlayerInventorySlotRequest, SetPlayerSelectedSlotRequest, + }, + }, +}; + +fn pumpkin_item_to_proto(stack: &PumpkinItemStack) -> ProtoItemStack { + if stack.is_empty() { + ProtoItemStack { + r#type: "minecraft:air".to_string(), + amount: 0, + } + } else { + let registry_key = stack.item.registry_key; + let r#type = if registry_key.starts_with("minecraft:") { + registry_key.to_string() + } else { + format!("minecraft:{registry_key}") + }; + ProtoItemStack { + r#type, + amount: u32::from(stack.item_count), + } + } +} + +fn proto_item_to_pumpkin(proto: Option<&ProtoItemStack>) -> PumpkinItemStack { + let Some(proto) = proto else { + return PumpkinItemStack::EMPTY.clone(); + }; + if proto.amount == 0 || proto.r#type.is_empty() { + return PumpkinItemStack::EMPTY.clone(); + } + let key = proto + .r#type + .strip_prefix("minecraft:") + .unwrap_or(&proto.r#type); + if let Some(item) = Item::from_registry_key(key) { + PumpkinItemStack::new(proto.amount as u8, item) + } else { + PumpkinItemStack::EMPTY.clone() + } +} + +pub fn ffi_native_bridge_get_player_inventory_impl( + request: Uuid, +) -> Option { + let ctx = CALLBACK_CONTEXT.get()?; + with_player(Some(&request), |player| { + let player = player.clone(); + block_in_place(|| { + ctx.runtime.block_on(async { + let selected_slot = u32::from(player.inventory.get_selected_slot()); + + let main_guard = player.inventory.main_inventory.read().await; + let main_inventory = main_guard.iter().map(pumpkin_item_to_proto).collect(); + + let eq_guard = player.inventory.entity_equipment.lock().await; + let off_hand = pumpkin_item_to_proto(&eq_guard.get(&EquipmentSlot::OFF_HAND)); + let helmet = pumpkin_item_to_proto(&eq_guard.get(&EquipmentSlot::HEAD)); + let chestplate = pumpkin_item_to_proto(&eq_guard.get(&EquipmentSlot::CHEST)); + let leggings = pumpkin_item_to_proto(&eq_guard.get(&EquipmentSlot::LEGS)); + let boots = pumpkin_item_to_proto(&eq_guard.get(&EquipmentSlot::FEET)); + + GetPlayerInventoryResponse { + main_inventory, + selected_slot, + off_hand: Some(off_hand), + helmet: Some(helmet), + chestplate: Some(chestplate), + leggings: Some(leggings), + boots: Some(boots), + } + }) + }) + }) +} + +pub fn ffi_native_bridge_set_player_inventory_slot_impl( + request: SetPlayerInventorySlotRequest, +) -> Option<()> { + let ctx = CALLBACK_CONTEXT.get()?; + let slot = request.slot as usize; + let pumpkin_item = proto_item_to_pumpkin(request.item.as_ref()); + + with_player(request.uuid.as_ref(), |player| { + let player = player.clone(); + ctx.runtime.spawn(async move { + if slot < 36 { + let mut main_guard = player.inventory.main_inventory.write().await; + main_guard[slot] = pumpkin_item; + } else { + let eq_slot = match slot { + 36 => Some(EquipmentSlot::FEET), + 37 => Some(EquipmentSlot::LEGS), + 38 => Some(EquipmentSlot::CHEST), + 39 => Some(EquipmentSlot::HEAD), + 40 => Some(EquipmentSlot::OFF_HAND), + _ => None, + }; + if let Some(eq_slot) = eq_slot { + let mut eq_guard = player.inventory.entity_equipment.lock().await; + eq_guard.put(&eq_slot, pumpkin_item); + } + } + }); + }) +} + +pub fn ffi_native_bridge_set_player_selected_slot_impl( + request: SetPlayerSelectedSlotRequest, +) -> Option<()> { + with_player(request.uuid.as_ref(), |player| { + if request.slot < 9 { + player.inventory.set_selected_slot(request.slot as u8); + } + }) +} + +pub fn ffi_native_bridge_set_player_equipment_impl( + request: SetPlayerEquipmentRequest, +) -> Option<()> { + let ctx = CALLBACK_CONTEXT.get()?; + let pumpkin_item = proto_item_to_pumpkin(request.item.as_ref()); + let slot_type = request.slot_type; + + with_player(request.uuid.as_ref(), |player| { + let player = player.clone(); + ctx.runtime.spawn(async move { + match slot_type { + 0 => { + // Main Hand + player.inventory.set_held_item(pumpkin_item).await; + } + 1 => { + // Off Hand + let mut eq = player.inventory.entity_equipment.lock().await; + eq.put(&EquipmentSlot::OFF_HAND, pumpkin_item); + } + 2 => { + // Feet + let mut eq = player.inventory.entity_equipment.lock().await; + eq.put(&EquipmentSlot::FEET, pumpkin_item); + } + 3 => { + // Legs + let mut eq = player.inventory.entity_equipment.lock().await; + eq.put(&EquipmentSlot::LEGS, pumpkin_item); + } + 4 => { + // Chest + let mut eq = player.inventory.entity_equipment.lock().await; + eq.put(&EquipmentSlot::CHEST, pumpkin_item); + } + 5 => { + // Head + let mut eq = player.inventory.entity_equipment.lock().await; + eq.put(&EquipmentSlot::HEAD, pumpkin_item); + } + _ => {} + } + }); + }) +} + +pub fn ffi_native_bridge_clear_player_inventory_impl(request: Uuid) -> Option<()> { + let ctx = CALLBACK_CONTEXT.get()?; + with_player(Some(&request), |player| { + let player = player.clone(); + ctx.runtime.spawn(async move { + let mut main_guard = player.inventory.main_inventory.write().await; + main_guard.fill_with(|| PumpkinItemStack::EMPTY.clone()); + let mut eq_guard = player.inventory.entity_equipment.lock().await; + eq_guard.clear(); + }); + }) +} diff --git a/rust/src/java/native_callbacks/mod.rs b/rust/src/java/native_callbacks/mod.rs index 7dc86a4..e18aec2 100644 --- a/rust/src/java/native_callbacks/mod.rs +++ b/rust/src/java/native_callbacks/mod.rs @@ -43,6 +43,10 @@ pub mod world; pub use world::*; pub mod itemstack; +pub use itemstack::*; + +pub mod command; +pub use command::*; static CALLBACK_CONTEXT: OnceLock = OnceLock::new(); diff --git a/rust/src/java/native_callbacks/world.rs b/rust/src/java/native_callbacks/world.rs index b10196a..b7ea0f9 100644 --- a/rust/src/java/native_callbacks/world.rs +++ b/rust/src/java/native_callbacks/world.rs @@ -21,10 +21,15 @@ pub fn ffi_native_bridge_get_block_data_impl( let pos = pumpkin_util::math::position::BlockPos::new(request.x, request.y, request.z); let state_id = world.get_block_state(&pos).id; + let block = pumpkin_data::Block::from_state_id(state_id); + let key = block.name; + let block_state = if key.starts_with("minecraft:") { + key.to_string() + } else { + format!("minecraft:{key}") + }; - Some(GetBlockDataResponse { - block_state: format!("minecraft:block_{state_id}"), - }) + Some(GetBlockDataResponse { block_state }) } pub fn ffi_native_bridge_set_block_data_impl(request: SetBlockDataRequest) -> Option<()> { @@ -40,11 +45,24 @@ pub fn ffi_native_bridge_set_block_data_impl(request: SetBlockDataRequest) -> Op .or_else(|| worlds.first().cloned())?; let pos = pumpkin_util::math::position::BlockPos::new(request.x, request.y, request.z); + let block_state_str = request.block_state; + let clean_key = block_state_str + .split('[') + .next() + .unwrap_or(&block_state_str) + .trim_start_matches("minecraft:"); + + let state_id = if let Some(b) = pumpkin_data::Block::from_registry_key(clean_key) { + b.default_state.id + } else { + pumpkin_data::BlockStateId::new_or_air(0) + }; + ctx.runtime.spawn(async move { world .set_block_state( &pos, - pumpkin_data::BlockStateId::new_or_air(1), + state_id, pumpkin::world::BlockFlags::NOTIFY_ALL, ) .await; @@ -131,3 +149,29 @@ pub fn ffi_native_bridge_set_world_border_impl( Some(()) } + +pub fn ffi_native_bridge_get_world_info_impl( + request: crate::proto::patchbukkit::world::GetWorldInfoRequest, +) -> Option { + let ctx = CALLBACK_CONTEXT.get()?; + let uuid_str = &request.world_uuid.as_ref()?.value; + let world_uuid = uuid::Uuid::parse_str(uuid_str).ok()?; + + let worlds = ctx.plugin_context.server.worlds.load_full(); + let world = worlds + .iter() + .find(|w| w.uuid == world_uuid) + .cloned() + .or_else(|| worlds.first().cloned())?; + + let min_height = world.dimension.min_y; + let height = world.dimension.height; + let max_height = min_height + height; + + Some(crate::proto::patchbukkit::world::GetWorldInfoResponse { + min_height, + max_height, + height, + seed: 0, + }) +} diff --git a/rust/src/java/plugin/command_manager.rs b/rust/src/java/plugin/command_manager.rs index 0efe4f0..79db34d 100644 --- a/rust/src/java/plugin/command_manager.rs +++ b/rust/src/java/plugin/command_manager.rs @@ -4,10 +4,7 @@ use anyhow::Result; use j4rs::{Instance, InvocationArg, Jvm}; use pumpkin::{command::dispatcher::CommandError, plugin::Context}; use pumpkin_protocol::java::client::play::CommandSuggestion; -use pumpkin_util::{ - PermissionLvl, - permission::{Permission, PermissionDefault}, -}; +use pumpkin_util::permission::{Permission, PermissionDefault}; use tokio::sync::mpsc; use crate::{ @@ -157,24 +154,55 @@ impl CommandManager { ) -> Result<()> { let command_map = self.get_or_init_command_map(jvm)?; - let plugin_instance = - jvm.clone_instance(plugin.instance.as_ref().expect( - "This function should never be called with a plugin that has no instance", - ))?; - let j_plugin_arg = InvocationArg::from(plugin_instance); - - let j_plugin_cmd = Arc::new(Mutex::new(jvm.invoke_static( + let created_cmd = match jvm.invoke_static( "org.patchbukkit.command.CommandFactory", "create", - &[InvocationArg::try_from(&cmd_name)?, j_plugin_arg], - )?)); + &[ + InvocationArg::try_from(&cmd_name)?, + InvocationArg::try_from(&plugin.name)?, + ], + ) { + Ok(inst) => { + if jvm.invoke(&inst, "getName", InvocationArg::empty()).is_err() { + tracing::error!( + "Failed to create PluginCommand for {}: Java returned null", + cmd_name + ); + return Ok(()); + } + inst + } + Err(e) => { + tracing::error!("Failed to create PluginCommand for {}: {:?}", cmd_name, e); + return Ok(()); + } + }; + let j_plugin_cmd = Arc::new(Mutex::new(created_cmd)); tracing::info!("Registering Bukkit command: {}", &cmd_name); let cmd_aliases: Vec = cmd_data .aliases .as_ref() .map_or_else(Vec::new, |a| a.to_vec()); - let mut names: Vec = vec![cmd_name.clone()]; - names.extend(cmd_aliases.clone()); + let mut raw_names: Vec = vec![cmd_name.clone()]; + raw_names.extend(cmd_aliases.clone()); + + let mut names: Vec = Vec::new(); + for name in raw_names { + let clean = name.trim_start_matches('/').to_string(); + if !clean.is_empty() { + if !names.contains(&clean) { + names.push(clean.clone()); + } + let single = format!("/{}", clean); + if !names.contains(&single) { + names.push(single); + } + let double = format!("//{}", clean); + if !names.contains(&double) { + names.push(double); + } + } + } { let cmd_lock = j_plugin_cmd.lock().unwrap(); @@ -228,13 +256,18 @@ impl CommandManager { ); } + let j_command_cast = match jvm.cast(&j_plugin_cmd_owned, "org.bukkit.command.Command") { + Ok(casted) => casted, + Err(_) => j_plugin_cmd_owned, + }; + jvm.invoke( command_map, "register", &[ InvocationArg::try_from(&cmd_name)?, InvocationArg::try_from(&plugin.name)?, - InvocationArg::from(j_plugin_cmd_owned), + InvocationArg::from(j_command_cast), ], )?; } @@ -381,14 +414,17 @@ fn build_permission_node( cmd_name: &str, cmd_data: &config::spigot::Command, ) -> (String, PermissionDefault) { + let clean = cmd_name.trim_start_matches('/'); if let Some(permission) = &cmd_data.permission { - ( - permission.clone(), - PermissionDefault::Op(PermissionLvl::Two), - ) + let perm_node = if permission.contains(':') { + permission.clone() + } else { + format!("{PATCHBUKKIT_PERMISSION_NAMESPACE}:{permission}") + }; + (perm_node, PermissionDefault::Allow) } else { ( - format!("{PATCHBUKKIT_PERMISSION_NAMESPACE}:command.{cmd_name}"), + format!("{PATCHBUKKIT_PERMISSION_NAMESPACE}:command.{clean}"), PermissionDefault::Allow, ) } @@ -396,7 +432,7 @@ fn build_permission_node( #[cfg(test)] mod tests { - use pumpkin_util::{PermissionLvl, permission::PermissionDefault}; + use pumpkin_util::permission::PermissionDefault; use crate::config::spigot::Command; @@ -415,8 +451,8 @@ mod tests { assert_eq!( build_permission_node("spawn", &command), ( - "simplespawn.spawn".to_string(), - PermissionDefault::Op(PermissionLvl::Two) + "patchbukkit:simplespawn.spawn".to_string(), + PermissionDefault::Allow ) ); } diff --git a/rust/src/java/plugin/event_manager.rs b/rust/src/java/plugin/event_manager.rs index f295f98..a3f7aba 100644 --- a/rust/src/java/plugin/event_manager.rs +++ b/rust/src/java/plugin/event_manager.rs @@ -98,7 +98,7 @@ impl EventManager { jvm: &Jvm, patch_server: &Instance, player: &Arc, - server: &Arc, + _server: &Arc, ) -> Result<()> { let j_uuid = jvm .invoke_static( @@ -118,7 +118,20 @@ impl EventManager { )?; let player_permission_level = player.permission_lvl.load(); - if player_permission_level >= server.basic_config.op_permission_level { + let is_op = player_permission_level > pumpkin_util::permission::PermissionLvl::Zero + || tokio::task::block_in_place(|| { + tokio::runtime::Handle::current().block_on(async { + _server + .data + .operator_config + .read() + .await + .get_entry(&player.gameprofile.id) + .is_some() + }) + }); + + if is_op { jvm.invoke( &j_player, "setOp", diff --git a/rust/src/java/plugin/loader.rs b/rust/src/java/plugin/loader.rs new file mode 100644 index 0000000..201505e --- /dev/null +++ b/rust/src/java/plugin/loader.rs @@ -0,0 +1,76 @@ +use std::{any::Any, path::Path, sync::Arc}; + +use pumpkin::plugin::{ + Context, PluginMetadata, + api::{Plugin, PluginFuture}, + loader::{LoaderError, PluginLoadFuture, PluginLoader, PluginUnloadFuture}, +}; + +use crate::java::jar::read_configs_from_jar; + +pub struct BukkitPluginStub; + +impl Plugin for BukkitPluginStub { + fn on_load(&self, _server: Arc) -> PluginFuture<'_, Result<(), String>> { + Box::pin(async move { Ok(()) }) + } + + fn on_unload(&self, _server: Arc) -> PluginFuture<'_, Result<(), String>> { + Box::pin(async move { Ok(()) }) + } +} + +pub struct BukkitPluginLoader; + +impl PluginLoader for BukkitPluginLoader { + fn can_load(&self, path: &Path) -> bool { + path.extension().and_then(|ext| ext.to_str()) == Some("jar") + } + + fn load<'a>(&'a self, path: &'a Path) -> PluginLoadFuture<'a> { + Box::pin(async move { + let (paper_yml_opt, spigot_yml_opt) = read_configs_from_jar(path) + .map_err(|e| LoaderError::InitializationFailed(e.to_string()))?; + + let metadata = if let Some(paper_str) = paper_yml_opt { + let paper_yml = crate::config::paper::PaperPluginYml::parse(&paper_str) + .map_err(|e| LoaderError::InitializationFailed(e.to_string()))?; + PluginMetadata { + name: format!("[Paper] {}", paper_yml.name), + version: paper_yml.version, + authors: paper_yml.author.map_or_else(Vec::new, |a| vec![a]), + description: paper_yml.description.unwrap_or_default(), + dependencies: Vec::new(), + permissions: Vec::new(), + } + } else if let Some(spigot_str) = spigot_yml_opt { + let spigot_yml = crate::config::spigot::SpigotPluginYml::parse(&spigot_str) + .map_err(|e| LoaderError::InitializationFailed(e.to_string()))?; + let authors = spigot_yml.get_all_authors(); + PluginMetadata { + name: format!("[Bukkit] {}", spigot_yml.name), + version: spigot_yml.version, + authors, + description: spigot_yml.description.unwrap_or_default(), + dependencies: spigot_yml.depend.unwrap_or_default(), + permissions: Vec::new(), + } + } else { + return Err(LoaderError::MetadataMissing); + }; + + let plugin: Arc = Arc::new(BukkitPluginStub); + let loader_data: Box = Box::new(()); + + Ok((plugin, metadata, loader_data)) + }) + } + + fn unload(&self, _data: Box) -> PluginUnloadFuture<'_> { + Box::pin(async move { Ok(()) }) + } + + fn can_unload(&self) -> bool { + true + } +} diff --git a/rust/src/java/plugin/manager.rs b/rust/src/java/plugin/manager.rs index b157697..727b938 100644 --- a/rust/src/java/plugin/manager.rs +++ b/rust/src/java/plugin/manager.rs @@ -211,11 +211,34 @@ impl PluginManager { config.commands.clone().unwrap_or_default() }); - let libraries = if spigot_skip_libraries { + let mut libraries = if spigot_skip_libraries { Vec::new() } else { spigot_libraries }; + if let Some(paper_libs) = &parsed_paper_plugin.libraries { + libraries.extend(paper_libs.clone()); + } + if let Some(repos) = &parsed_paper_plugin.repositories { + if let Some(arr) = repos.as_array() { + for item in arr { + if let Some(s) = item.as_str() { + libraries.push(format!("repo:{s}")); + } else if let Some(obj) = item.as_object() { + if let Some(u) = obj.get("url").and_then(|v| v.as_str()) { + libraries.push(format!("repo:{u}")); + } + } + } + } else if let Some(map) = repos.as_object() { + for (_, v) in map { + if let Some(s) = v.as_str() { + libraries.push(format!("repo:{s}")); + } + } + } + } + libraries.extend(crate::java::jar::read_libraries_from_jar(&jar_path)); let name = parsed_paper_plugin.name.clone(); let version = parsed_paper_plugin.version.clone(); @@ -262,11 +285,12 @@ impl PluginManager { let soft_depends = normalize_names(parsed_spigot_plugin.softdepend.clone()); let load_before = normalize_names(parsed_spigot_plugin.loadbefore.clone()); let provides = normalize_names(parsed_spigot_plugin.provides.clone()); - let libraries = if parsed_spigot_plugin.paper_skip_libraries.unwrap_or(false) { + let mut libraries = if parsed_spigot_plugin.paper_skip_libraries.unwrap_or(false) { Vec::new() } else { parsed_spigot_plugin.libraries.clone().unwrap_or_default() }; + libraries.extend(crate::java::jar::read_libraries_from_jar(&jar_path)); let name = parsed_spigot_plugin.name.clone(); let version = parsed_spigot_plugin.version.clone(); @@ -299,203 +323,30 @@ impl PluginManager { Ok(()) } - pub fn enable_all_plugins(&mut self, jvm: &Jvm) -> Result<()> { - // IMPORTANT: enable trough PluginManager not manually - let plugin_manager = jvm.invoke_static( - "org.bukkit.Bukkit", - "getPluginManager", - InvocationArg::empty(), - )?; - for plugin in self.plugins.values_mut() { - if plugin.state != PluginState::Loaded { - continue; - } - 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, - "enablePlugin", - &[InvocationArg::from(plugin_instance)], - ); - - match result { - Ok(_) => { - plugin.state = PluginState::Enabled; - tracing::info!("Enabled PatchBukkit plugin: {}", plugin.name); - } - Err(e) => { - plugin.state = PluginState::Errored; - tracing::error!( - "Failed to enable PatchBukkit plugin {}: {:?}", - plugin.name, - e - ); - } - } - } + pub fn enable_all_plugins(&mut self, _jvm: &Jvm) -> Result<()> { Ok(()) } - pub fn disable_all_plugins(&mut self, jvm: &Jvm) -> Result<()> { - let plugin_manager = jvm.invoke_static( - "org.bukkit.Bukkit", - "getPluginManager", - InvocationArg::empty(), - )?; - for plugin in self.plugins.values_mut() { - 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, - "disablePlugin", - &[InvocationArg::from(plugin_instance)], - ); - - match result { - Ok(_) => { - plugin.state = PluginState::Disabled; - tracing::info!("Disabled PatchBukkit plugin: {}", plugin.name); - } - Err(e) => { - plugin.state = PluginState::Disabled; - tracing::error!( - "Failed to disable PatchBukkit plugin {}: {:?}", - plugin.name, - e - ); - } - } - } + pub fn disable_all_plugins(&mut self, _jvm: &Jvm) -> Result<()> { Ok(()) } pub async fn instantiate_all_plugins( &mut self, jvm: &Jvm, - server: &Arc, - command_tx: mpsc::Sender, - command_manager: &mut CommandManager, + plugin_folder: &Path, + _server: &Arc, + _command_tx: mpsc::Sender, + _command_manager: &mut CommandManager, ) -> Result<()> { - let load_order = self.compute_load_order(); - - for plugin_key in load_order { - let (classpath, libraries) = match self.plugins.get(&plugin_key) { - Some(plugin) => ( - self.classpath_string_for(plugin), - Self::library_string_for(plugin), - ), - None => continue, - }; - - let plugin = match self.plugins.get_mut(&plugin_key) { - Some(plugin) => plugin, - None => continue, - }; - let plugin_instance = match jvm.invoke_static( - "org.patchbukkit.loader.PatchBukkitPluginLoader", - "createPlugin", - &[ - InvocationArg::try_from(&plugin.path.to_string_lossy().to_string())?, - InvocationArg::try_from(&plugin.main_class)?, - 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 { - match command_manager - .register_command( - jvm, - server, - plugin, - cmd_name.clone(), - cmd_data, - command_tx.clone(), - ) - .await - { - Ok(()) => (), - Err(e) => { - tracing::error!( - "Failed to register command {} for plugin {}: {:?}", - cmd_name, - plugin.name, - e - ); - } - } - } - - plugin.state = PluginState::Loaded; - tracing::info!("Loaded and registered commands for: {}", plugin.name); - } + tracing::info!("Bootstrapping Java plugins from {:?}", plugin_folder); + let _ = jvm.invoke_static( + "org.patchbukkit.loader.PatchBukkitBootstrap", + "bootstrapPlugins", + &[InvocationArg::try_from( + &plugin_folder.to_string_lossy().to_string(), + )?], + ); Ok(()) } @@ -504,6 +355,7 @@ impl PluginManager { Ok(()) } + #[allow(dead_code)] fn resolve_dependency_name( &self, name: &str, @@ -517,6 +369,7 @@ impl PluginManager { } } + #[allow(dead_code)] fn compute_load_order(&mut self) -> Vec { let mut provides_map: HashMap = HashMap::new(); for (key, plugin) in &self.plugins { @@ -640,6 +493,7 @@ impl PluginManager { order } + #[allow(dead_code)] fn classpath_string_for(&self, plugin: &Plugin) -> String { if plugin.classpath_deps.is_empty() { return String::new(); @@ -666,9 +520,10 @@ impl PluginManager { } } - paths.join(";") + paths.join(if cfg!(windows) { ";" } else { ":" }) } + #[allow(dead_code)] fn library_string_for(plugin: &Plugin) -> String { if plugin.libraries.is_empty() { return String::new(); @@ -716,6 +571,7 @@ fn dedupe_strings(values: Vec) -> Vec { output } +#[allow(dead_code)] fn add_edge( edges: &mut HashMap>, indegree: &mut HashMap, diff --git a/rust/src/java/plugin/mod.rs b/rust/src/java/plugin/mod.rs index 7f052d7..07ec8c2 100644 --- a/rust/src/java/plugin/mod.rs +++ b/rust/src/java/plugin/mod.rs @@ -1,3 +1,4 @@ pub mod command_manager; pub mod event_manager; +pub mod loader; pub mod manager; diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 663e48a..15813cd 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -23,7 +23,7 @@ use crate::{ config::patchbukkit::PatchBukkitConfig, java::{ jvm::{ - commands::{JvmCommand, LoadPluginResult}, + commands::JvmCommand, worker::JvmWorker, }, resources::setup_j4rs, @@ -42,69 +42,7 @@ async fn on_load_inner(plugin: &PatchBukkitPlugin, server: Arc) -> Resu let config = PatchBukkitConfig::get_or_create(config_path) .map_err(|e| format!("Failed to setup PatchBukkit config: {e}"))?; - // Discover and prepare JAR files - let jar_paths = discover_jar_files(&dirs.plugins); - for jar_path in jar_paths { - { - let (tx, rx) = oneshot::channel(); - let result = plugin - .command_tx - .send(JvmCommand::LoadPlugin { - plugin_path: jar_path.clone(), - respond_to: tx, - }) - .await; - if let Err(e) = result { - tracing::error!( - "Failed to send command to load plugin {}: {}", - jar_path.display(), - e - ); - } - match rx.await { - Ok(result) => match result { - LoadPluginResult::SuccessfullyLoadedSpigot => { - tracing::info!("Loaded Spigot plugin from JAR `{}`", jar_path.display()); - } - LoadPluginResult::SuccessfullyLoadedPaper => { - tracing::info!("Loaded Paper plugin from JAR `{}`", jar_path.display()); - } - LoadPluginResult::FailedToLoadSpigotPlugin(error) => { - tracing::error!( - "Failed to load Spigot plugin from JAR `{}` with error: {}", - jar_path.display(), - error - ); - } - LoadPluginResult::FailedToLoadPaperPlugin(error) => { - tracing::error!( - "Failed to load Paper plugin from JAR `{}` with error: {}", - jar_path.display(), - error - ); - } - LoadPluginResult::FailedToReadConfigurationFile(error) => { - tracing::error!( - "Failed to read configuration file from JAR `{}`: {}", - jar_path.display(), - error - ); - } - LoadPluginResult::NoConfigurationFile => { - tracing::warn!( - "No configuration file found for plugin from JAR `{}`", - jar_path.display() - ); - } - }, - Err(e) => tracing::error!( - "Failed to receive load plugin response for JAR `{}`: {}", - jar_path.display(), - e - ), - } - }; - } + // Manage embedded resources setup_j4rs(&dirs.j4rs).map_err(|e| format!("Failed to setup J4RS: {e}"))?; @@ -132,6 +70,7 @@ async fn on_load_inner(plugin: &PatchBukkitPlugin, server: Arc) -> Resu plugin .command_tx .send(JvmCommand::InstantiateAllPlugins { + plugins_dir: dirs.plugins.clone(), respond_to: tx, server: server.clone(), command_tx: plugin.command_tx.clone(), @@ -155,6 +94,28 @@ async fn on_load_inner(plugin: &PatchBukkitPlugin, server: Arc) -> Resu .map_err(|e| format!("Failed to enable all plugins: {e}"))?; }; + // Register Bukkit plugin loader with Pumpkin so Bukkit/Paper plugins appear in Pumpkin's native /plugins command + let bukkit_loader = Arc::new(crate::java::plugin::loader::BukkitPluginLoader); + let rt = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .map_err(|e| format!("Failed to build Tokio runtime: {e}"))?; + + let server_ref = server.clone(); + let jar_paths: Vec<_> = discover_jar_files(&dirs.plugins).collect(); + rt.block_on(async move { + server_ref + .plugin_manager + .add_loader(&server_ref.server, bukkit_loader) + .await; + for jar_path in &jar_paths { + let _ = server_ref + .plugin_manager + .try_load_plugin(&server_ref.server, jar_path) + .await; + } + }); + Ok(()) } @@ -187,12 +148,12 @@ async fn on_unload_inner(plugin: &PatchBukkitPlugin, _server: Arc) -> R } #[plugin_method] -async fn on_load(&mut self, server: Arc) -> Result<(), String> { +async fn on_load(&self, server: Arc) -> Result<(), String> { on_load_inner(self, server).await } #[plugin_method] -async fn on_unload(&mut self, server: Arc) -> Result<(), String> { +async fn on_unload(&self, server: Arc) -> Result<(), String> { on_unload_inner(self, server).await } diff --git a/rust/src/proto/mod.rs b/rust/src/proto/mod.rs index 91abe7f..5f0b1f8 100644 --- a/rust/src/proto/mod.rs +++ b/rust/src/proto/mod.rs @@ -46,6 +46,10 @@ pub mod patchbukkit { pub mod world { include!(concat!(env!("OUT_DIR"), "/patchbukkit.world.rs")); } + + pub mod command { + include!(concat!(env!("OUT_DIR"), "/patchbukkit.command.rs")); + } } include!(concat!(env!("OUT_DIR"), "/ffi_init.rs"));