port to 26.3
This commit is contained in:
parent
9d23556460
commit
1e8e203281
6
.gitignore
vendored
6
.gitignore
vendored
@ -56,3 +56,9 @@ java/.idea/
|
|||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
# Runtime configs
|
||||||
|
config/
|
||||||
|
java/patchbukkit/config/
|
||||||
|
logs/
|
||||||
|
java/patchbukkit/logs/
|
||||||
|
|||||||
@ -11,7 +11,7 @@ repositories {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly(project(":patchbukkit"))
|
compileOnly(project(":patchbukkit"))
|
||||||
compileOnly("io.papermc.paper:paper-api:26.2.build.117-stable")
|
compileOnly("io.papermc.paper:paper-api:26.3.build.8-alpha")
|
||||||
}
|
}
|
||||||
|
|
||||||
java {
|
java {
|
||||||
|
|||||||
@ -47,7 +47,7 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
paperweight.paperDevBundle("26.2.build.117-stable")
|
paperweight.paperDevBundle("26.3.build.8-alpha")
|
||||||
implementation("net.sf.jopt-simple:jopt-simple:6.0-alpha-3")
|
implementation("net.sf.jopt-simple:jopt-simple:6.0-alpha-3")
|
||||||
implementation("org.apache.maven:maven-resolver-provider:3.9.6")
|
implementation("org.apache.maven:maven-resolver-provider:3.9.6")
|
||||||
implementation("org.apache.maven.resolver:maven-resolver-impl:1.9.18")
|
implementation("org.apache.maven.resolver:maven-resolver-impl:1.9.18")
|
||||||
@ -55,13 +55,22 @@ dependencies {
|
|||||||
implementation("org.apache.maven.resolver:maven-resolver-transport-http:1.9.18")
|
implementation("org.apache.maven.resolver:maven-resolver-transport-http:1.9.18")
|
||||||
implementation("org.apache.maven.resolver:maven-resolver-util:1.9.18")
|
implementation("org.apache.maven.resolver:maven-resolver-util:1.9.18")
|
||||||
implementation("com.google.protobuf:protobuf-java:$protobufVersion")
|
implementation("com.google.protobuf:protobuf-java:$protobufVersion")
|
||||||
implementation("org.slf4j:slf4j-jdk14:2.0.16")
|
implementation("org.apache.logging.log4j:log4j-slf4j2-impl:2.26.0")
|
||||||
|
implementation("commons-logging:commons-logging:1.3.5")
|
||||||
|
implementation("commons-codec:commons-codec:1.18.0")
|
||||||
|
implementation("commons-collections:commons-collections:3.2.2")
|
||||||
|
implementation("net.bytebuddy:byte-buddy:1.15.11")
|
||||||
|
implementation("net.bytebuddy:byte-buddy-agent:1.15.11")
|
||||||
|
implementation("commons-lang:commons-lang:2.6")
|
||||||
|
implementation("org.xerial:sqlite-jdbc:3.47.1.0")
|
||||||
|
implementation("com.mysql:mysql-connector-j:9.1.0")
|
||||||
testImplementation("org.junit.jupiter:junit-jupiter:5.10.2")
|
testImplementation("org.junit.jupiter:junit-jupiter:5.10.2")
|
||||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.named<Test>("test") {
|
tasks.named<Test>("test") {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
|
jvmArgs("-Dnet.bytebuddy.experimental=true")
|
||||||
}
|
}
|
||||||
|
|
||||||
java {
|
java {
|
||||||
@ -79,6 +88,7 @@ tasks.withType<JavaCompile> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tasks.named<Jar>("jar") {
|
tasks.named<Jar>("jar") {
|
||||||
|
isZip64 = true
|
||||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
dependsOn(configurations.compileClasspath)
|
dependsOn(configurations.compileClasspath)
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
[19:04:38] [Test worker/INFO]: [MoonriseCommon] Paper is using 8 worker threads, 1 I/O threads
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
package org.patchbukkit;
|
||||||
|
|
||||||
|
public class CraftParticle {
|
||||||
|
|
||||||
|
public static org.bukkit.Particle minecraftToBukkit(net.minecraft.core.particles.ParticleType<?> nms) {
|
||||||
|
return org.bukkit.craftbukkit.CraftParticle.minecraftToBukkit(nms);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static net.minecraft.core.particles.ParticleType<?> bukkitToMinecraft(org.bukkit.Particle bukkit) {
|
||||||
|
return org.bukkit.craftbukkit.CraftParticle.bukkitToMinecraft(bukkit);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
package org.patchbukkit;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import org.patchbukkit.world.PatchBukkitWorld;
|
||||||
|
|
||||||
|
public class CraftWorld extends PatchBukkitWorld {
|
||||||
|
|
||||||
|
private ServerLevel handle;
|
||||||
|
|
||||||
|
public CraftWorld(UUID uuid) {
|
||||||
|
super(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerLevel getHandle() {
|
||||||
|
if (this.handle == null) {
|
||||||
|
try {
|
||||||
|
java.lang.reflect.Field f = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
|
||||||
|
f.setAccessible(true);
|
||||||
|
sun.misc.Unsafe unsafe = (sun.misc.Unsafe) f.get(null);
|
||||||
|
this.handle = (ServerLevel) unsafe.allocateInstance(ServerLevel.class);
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
}
|
||||||
|
return this.handle;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -100,11 +100,29 @@ public class PatchBukkitInternalAPIBridge implements InternalAPIBridge {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable DamageEffect getDamageEffect(String key) {
|
public @Nullable DamageEffect getDamageEffect(String key) {
|
||||||
return null;
|
if (key == null) return null;
|
||||||
|
try {
|
||||||
|
var craft = org.bukkit.craftbukkit.damage.CraftDamageEffect.getById(key);
|
||||||
|
if (craft != null) return craft;
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
|
||||||
|
return switch (key.toLowerCase(java.util.Locale.ROOT)) {
|
||||||
|
case "hurt" -> () -> org.bukkit.Sound.ENTITY_PLAYER_HURT;
|
||||||
|
case "thorns" -> () -> org.bukkit.Sound.ENCHANT_THORNS_HIT;
|
||||||
|
case "drowning" -> () -> org.bukkit.Sound.ENTITY_PLAYER_HURT_DROWN;
|
||||||
|
case "burning" -> () -> org.bukkit.Sound.ENTITY_PLAYER_HURT_ON_FIRE;
|
||||||
|
case "poking" -> () -> org.bukkit.Sound.ENTITY_PLAYER_HURT_SWEET_BERRY_BUSH;
|
||||||
|
case "freezing" -> () -> org.bukkit.Sound.ENTITY_PLAYER_HURT_FREEZE;
|
||||||
|
default -> () -> org.bukkit.Sound.ENTITY_PLAYER_HURT;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public DamageSource.Builder createDamageSourceBuilder(DamageType damageType) {
|
public DamageSource.Builder createDamageSourceBuilder(DamageType damageType) {
|
||||||
return null;
|
try {
|
||||||
|
return new org.bukkit.craftbukkit.damage.CraftDamageSourceBuilder(damageType);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public PoiType.Occupancy createOccupancy(String type) {
|
public PoiType.Occupancy createOccupancy(String type) {
|
||||||
|
|||||||
@ -14,6 +14,9 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.jar.JarEntry;
|
import java.util.jar.JarEntry;
|
||||||
import java.util.jar.JarFile;
|
import java.util.jar.JarFile;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
import org.bukkit.plugin.InvalidDescriptionException;
|
import org.bukkit.plugin.InvalidDescriptionException;
|
||||||
import org.bukkit.plugin.PluginDescriptionFile;
|
import org.bukkit.plugin.PluginDescriptionFile;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
@ -24,6 +27,7 @@ public class PatchBukkitPluginClassLoader
|
|||||||
implements ConfiguredPluginClassLoader
|
implements ConfiguredPluginClassLoader
|
||||||
{
|
{
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger("PatchBukkitPluginClassLoader");
|
||||||
private final PluginDescriptionFile description;
|
private final PluginDescriptionFile description;
|
||||||
private final File dataFolder;
|
private final File dataFolder;
|
||||||
private final File file;
|
private final File file;
|
||||||
@ -31,6 +35,9 @@ public class PatchBukkitPluginClassLoader
|
|||||||
|
|
||||||
static {
|
static {
|
||||||
ClassLoader.registerAsParallelCapable();
|
ClassLoader.registerAsParallelCapable();
|
||||||
|
try {
|
||||||
|
org.patchbukkit.PatchBukkitServer.ensureCraftRegistry();
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final java.util.Set<PatchBukkitPluginClassLoader> ALL_LOADERS =
|
public static final java.util.Set<PatchBukkitPluginClassLoader> ALL_LOADERS =
|
||||||
@ -57,6 +64,17 @@ public class PatchBukkitPluginClassLoader
|
|||||||
libsDir.mkdirs();
|
libsDir.mkdirs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure official Mojang server bytecode is cached and added to classpath
|
||||||
|
File mojangServerJar = MojangServerProvider.getOrDownloadServerJar(
|
||||||
|
libsDir,
|
||||||
|
org.patchbukkit.versioning.Versioning.getCurrentApiVersion()
|
||||||
|
);
|
||||||
|
if (mojangServerJar != null && mojangServerJar.exists()) {
|
||||||
|
try {
|
||||||
|
addURL(mojangServerJar.toURI().toURL());
|
||||||
|
} catch (MalformedURLException ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
// Extract and load nested JARs inside plugin JAR
|
// Extract and load nested JARs inside plugin JAR
|
||||||
for (File nestedJar : extractNestedJars(file, libsDir)) {
|
for (File nestedJar : extractNestedJars(file, libsDir)) {
|
||||||
try {
|
try {
|
||||||
@ -281,6 +299,9 @@ public class PatchBukkitPluginClassLoader
|
|||||||
@Override
|
@Override
|
||||||
protected Class<?> loadClass(String name, boolean resolve)
|
protected Class<?> loadClass(String name, boolean resolve)
|
||||||
throws ClassNotFoundException {
|
throws ClassNotFoundException {
|
||||||
|
if (name != null && name.indexOf('/') != -1) {
|
||||||
|
name = name.replace('/', '.');
|
||||||
|
}
|
||||||
synchronized (getClassLoadingLock(name)) {
|
synchronized (getClassLoadingLock(name)) {
|
||||||
// First, check if already loaded
|
// First, check if already loaded
|
||||||
Class<?> c = findLoadedClass(name);
|
Class<?> c = findLoadedClass(name);
|
||||||
@ -292,7 +313,13 @@ public class PatchBukkitPluginClassLoader
|
|||||||
!name.startsWith("java.") &&
|
!name.startsWith("java.") &&
|
||||||
!name.startsWith("jdk.") &&
|
!name.startsWith("jdk.") &&
|
||||||
!name.startsWith("sun.") &&
|
!name.startsWith("sun.") &&
|
||||||
!name.startsWith("javax.")
|
!name.startsWith("javax.") &&
|
||||||
|
!name.startsWith("org.bukkit.") &&
|
||||||
|
!name.startsWith("io.papermc.") &&
|
||||||
|
!name.startsWith("net.minecraft.") &&
|
||||||
|
!name.startsWith("org.spigotmc.") &&
|
||||||
|
!name.startsWith("com.destroystokyo.paper.") &&
|
||||||
|
!name.startsWith("org.patchbukkit.")
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
// Try to find in plugin JAR / libraries first
|
// Try to find in plugin JAR / libraries first
|
||||||
@ -315,9 +342,30 @@ public class PatchBukkitPluginClassLoader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If not found in JAR (or is a system class), delegate to parent
|
// If not found in JAR (or is a system/server class), delegate to parent
|
||||||
if (c == null) {
|
if (c == null) {
|
||||||
c = getParent().loadClass(name);
|
try {
|
||||||
|
c = getParent().loadClass(name);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
String remapped = remapLegacyClass(name);
|
||||||
|
if (remapped != null) {
|
||||||
|
try {
|
||||||
|
c = getParent().loadClass(remapped);
|
||||||
|
} catch (ClassNotFoundException ignored) {
|
||||||
|
try {
|
||||||
|
c = findClass(remapped);
|
||||||
|
} catch (ClassNotFoundException ignored2) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (c == null) {
|
||||||
|
try {
|
||||||
|
c = findClass(name);
|
||||||
|
} catch (ClassNotFoundException ignored3) {}
|
||||||
|
}
|
||||||
|
if (c == null) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -328,6 +376,64 @@ public class PatchBukkitPluginClassLoader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<?> findClass(String name) throws ClassNotFoundException {
|
||||||
|
Class<?> loaded = findLoadedClass(name);
|
||||||
|
if (loaded != null) return loaded;
|
||||||
|
|
||||||
|
String path = name.replace('.', '/').concat(".class");
|
||||||
|
URL resource = findResource(path);
|
||||||
|
if (resource != null) {
|
||||||
|
try (InputStream is = resource.openStream()) {
|
||||||
|
byte[] raw = is.readAllBytes();
|
||||||
|
byte[] transformed = BytecodeTransformer.transform(raw);
|
||||||
|
return defineClass(name, transformed, 0, transformed.length, (java.security.CodeSource) null);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
LOGGER.log(Level.FINE, "[PatchBukkit] Failed to define transformed class " + name, t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.findClass(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String remapLegacyClass(String name) {
|
||||||
|
if (name == null) return null;
|
||||||
|
|
||||||
|
// Missing dot in CraftBukkit package (e.g. from Cloud command framework where CB_PKG_VERSION was empty)
|
||||||
|
// e.g. org.bukkit.craftbukkitcommand.VanillaCommandWrapper -> org.bukkit.craftbukkit.command.VanillaCommandWrapper
|
||||||
|
if (name.startsWith("org.bukkit.craftbukkit") && !name.startsWith("org.bukkit.craftbukkit.")) {
|
||||||
|
return "org.bukkit.craftbukkit." + name.substring("org.bukkit.craftbukkit".length());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Versioned CraftBukkit (e.g. org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer -> org.bukkit.craftbukkit.entity.CraftPlayer)
|
||||||
|
if (name.startsWith("org.bukkit.craftbukkit.")) {
|
||||||
|
String unversioned = name.replaceFirst("^org\\.bukkit\\.craftbukkit\\.(v[0-9_]+R[0-9]+\\.)?", "org.bukkit.craftbukkit.");
|
||||||
|
if (!unversioned.equals(name)) {
|
||||||
|
return unversioned;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Legacy pre-1.17 NMS package (net.minecraft.server.v1_XX_RX.XYZ)
|
||||||
|
if (name.startsWith("net.minecraft.server.v")) {
|
||||||
|
String simpleName = name.substring(name.lastIndexOf('.') + 1);
|
||||||
|
return switch (simpleName) {
|
||||||
|
case "EntityPlayer" -> "net.minecraft.server.level.ServerPlayer";
|
||||||
|
case "MinecraftServer" -> "net.minecraft.server.MinecraftServer";
|
||||||
|
case "WorldServer" -> "net.minecraft.server.level.ServerLevel";
|
||||||
|
case "ItemStack" -> "net.minecraft.world.item.ItemStack";
|
||||||
|
case "Item" -> "net.minecraft.world.item.Item";
|
||||||
|
case "Block" -> "net.minecraft.world.level.block.Block";
|
||||||
|
case "Entity" -> "net.minecraft.world.entity.Entity";
|
||||||
|
case "NBTTagCompound" -> "net.minecraft.nbt.CompoundTag";
|
||||||
|
case "Packet" -> "net.minecraft.network.protocol.Packet";
|
||||||
|
case "PlayerConnection" -> "net.minecraft.server.network.ServerGamePacketListenerImpl";
|
||||||
|
case "World" -> "net.minecraft.world.level.Level";
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PluginMeta getConfiguration() {
|
public PluginMeta getConfiguration() {
|
||||||
return description;
|
return description;
|
||||||
|
|||||||
@ -114,6 +114,7 @@ public class PatchBukkitPluginManager implements PluginManager {
|
|||||||
plugin.onLoad();
|
plugin.onLoad();
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
server.getLogger().log(Level.SEVERE, "Error loading " + plugin.getName(), t);
|
server.getLogger().log(Level.SEVERE, "Error loading " + plugin.getName(), t);
|
||||||
|
t.printStackTrace();
|
||||||
}
|
}
|
||||||
registerPlugin(plugin);
|
registerPlugin(plugin);
|
||||||
return plugin;
|
return plugin;
|
||||||
@ -220,7 +221,11 @@ public class PatchBukkitPluginManager implements PluginManager {
|
|||||||
}
|
}
|
||||||
if (!plugin.isEnabled()) {
|
if (!plugin.isEnabled()) {
|
||||||
try {
|
try {
|
||||||
plugin.getPluginLoader().enablePlugin(plugin);
|
if (plugin instanceof org.bukkit.plugin.java.JavaPlugin javaPlugin) {
|
||||||
|
javaPlugin.setEnabled(true);
|
||||||
|
} else {
|
||||||
|
plugin.getPluginLoader().enablePlugin(plugin);
|
||||||
|
}
|
||||||
} catch (Throwable ex) {
|
} catch (Throwable ex) {
|
||||||
server.getLogger().log(Level.SEVERE, "Error enabling " + plugin.getName() + " (Is it up to date?)", ex);
|
server.getLogger().log(Level.SEVERE, "Error enabling " + plugin.getName() + " (Is it up to date?)", ex);
|
||||||
}
|
}
|
||||||
@ -234,7 +239,11 @@ public class PatchBukkitPluginManager implements PluginManager {
|
|||||||
}
|
}
|
||||||
if (plugin.isEnabled()) {
|
if (plugin.isEnabled()) {
|
||||||
try {
|
try {
|
||||||
plugin.getPluginLoader().disablePlugin(plugin);
|
if (plugin instanceof org.bukkit.plugin.java.JavaPlugin javaPlugin) {
|
||||||
|
javaPlugin.setEnabled(false);
|
||||||
|
} else {
|
||||||
|
plugin.getPluginLoader().disablePlugin(plugin);
|
||||||
|
}
|
||||||
} catch (Throwable ex) {
|
} catch (Throwable ex) {
|
||||||
server.getLogger().log(Level.SEVERE, "Error disabling " + plugin.getName(), ex);
|
server.getLogger().log(Level.SEVERE, "Error disabling " + plugin.getName(), ex);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import java.io.File;
|
|||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
@ -159,15 +160,14 @@ import patchbukkit.server.WhitelistEntryProto;
|
|||||||
|
|
||||||
@SuppressWarnings({ "deprecation", "removal", "unchecked" })
|
@SuppressWarnings({ "deprecation", "removal", "unchecked" })
|
||||||
public class PatchBukkitServer implements Server {
|
public class PatchBukkitServer implements Server {
|
||||||
|
private static final Logger logger = Logger.getLogger("Minecraft");
|
||||||
private static volatile PatchBukkitServer INSTANCE;
|
private static volatile PatchBukkitServer INSTANCE;
|
||||||
|
|
||||||
public PatchBukkitServer() {
|
public PatchBukkitServer() {
|
||||||
INSTANCE = this;
|
INSTANCE = this;
|
||||||
String name = "PatchBukkit";
|
|
||||||
try {
|
try {
|
||||||
name = io.papermc.paper.ServerBuildInfo.buildInfo().brandName();
|
this.serverName = io.papermc.paper.ServerBuildInfo.buildInfo().brandName();
|
||||||
} catch (Throwable ignored) {}
|
} catch (Throwable ignored) {}
|
||||||
this.serverName = name;
|
|
||||||
syncServerInfo();
|
syncServerInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,19 +189,307 @@ public class PatchBukkitServer implements Server {
|
|||||||
Class<?> bootstrap = Class.forName("net.minecraft.server.Bootstrap");
|
Class<?> bootstrap = Class.forName("net.minecraft.server.Bootstrap");
|
||||||
bootstrap.getMethod("bootStrap").invoke(null);
|
bootstrap.getMethod("bootStrap").invoke(null);
|
||||||
} catch (Throwable ignored) {}
|
} catch (Throwable ignored) {}
|
||||||
|
ensureGlobalConfiguration();
|
||||||
|
ensureCraftRegistry();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ensureGlobalConfiguration() {
|
||||||
|
try {
|
||||||
|
Class<?> globalConfigClass = Class.forName("io.papermc.paper.configuration.GlobalConfiguration");
|
||||||
|
java.lang.reflect.Method getMethod = globalConfigClass.getMethod("get");
|
||||||
|
if (getMethod.invoke(null) != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure Minecraft Bootstrap is executed before touching any Paper/Mojang configurations
|
||||||
|
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) {}
|
||||||
|
|
||||||
|
// 1. Try initializing with PaperConfigurations
|
||||||
|
try {
|
||||||
|
Class<?> paperConfigsClass = Class.forName("io.papermc.paper.configuration.PaperConfigurations");
|
||||||
|
java.nio.file.Path configDir = java.nio.file.Path.of("config");
|
||||||
|
if (!java.nio.file.Files.exists(configDir)) {
|
||||||
|
java.nio.file.Files.createDirectories(configDir);
|
||||||
|
}
|
||||||
|
Object configs = paperConfigsClass.getConstructor(java.nio.file.Path.class).newInstance(configDir);
|
||||||
|
Class<?> registryAccessClass = Class.forName("net.minecraft.core.RegistryAccess");
|
||||||
|
java.lang.reflect.Field emptyField = registryAccessClass.getField("EMPTY");
|
||||||
|
Object emptyRegistry = emptyField.get(null);
|
||||||
|
|
||||||
|
java.lang.reflect.Method initMethod = paperConfigsClass.getMethod("initializeGlobalConfiguration", registryAccessClass);
|
||||||
|
initMethod.invoke(configs, emptyRegistry);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
// 2. Fallback: instantiate GlobalConfiguration and populate inner configuration parts
|
||||||
|
try {
|
||||||
|
Object cfg = globalConfigClass.getConstructor().newInstance();
|
||||||
|
for (java.lang.reflect.Field field : globalConfigClass.getFields()) {
|
||||||
|
if (!java.lang.reflect.Modifier.isStatic(field.getModifiers()) && field.get(cfg) == null) {
|
||||||
|
Class<?> fieldType = field.getType();
|
||||||
|
try {
|
||||||
|
java.lang.reflect.Constructor<?> ctor = fieldType.getDeclaredConstructor(globalConfigClass);
|
||||||
|
ctor.setAccessible(true);
|
||||||
|
field.set(cfg, ctor.newInstance(cfg));
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
try {
|
||||||
|
java.lang.reflect.Constructor<?> ctor = fieldType.getDeclaredConstructor();
|
||||||
|
ctor.setAccessible(true);
|
||||||
|
field.set(cfg, ctor.newInstance());
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
java.lang.reflect.Method setMethod = globalConfigClass.getDeclaredMethod("set", globalConfigClass);
|
||||||
|
setMethod.setAccessible(true);
|
||||||
|
setMethod.invoke(null, cfg);
|
||||||
|
} catch (Throwable t2) {
|
||||||
|
logger.log(Level.WARNING, "[PatchBukkit] Failed to fallback-initialize GlobalConfiguration", t2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ClassNotFoundException ignored) {
|
||||||
|
// Not a Paper environment
|
||||||
|
} catch (Throwable t) {
|
||||||
|
logger.log(Level.WARNING, "[PatchBukkit] Unexpected error initializing GlobalConfiguration", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static synchronized void ensureCraftRegistry() {
|
||||||
|
try {
|
||||||
|
if (org.bukkit.craftbukkit.CraftRegistry.getMinecraftRegistry() != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
net.minecraft.core.RegistryAccess.Frozen frozenAccess =
|
||||||
|
net.minecraft.core.RegistryAccess.fromRegistryOfRegistries(net.minecraft.core.registries.BuiltInRegistries.REGISTRY);
|
||||||
|
org.bukkit.craftbukkit.CraftRegistry.setMinecraftRegistry(frozenAccess);
|
||||||
|
return;
|
||||||
|
} catch (IllegalStateException ignored) {
|
||||||
|
return;
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Class<?> craftRegistryClass = Class.forName("org.bukkit.craftbukkit.CraftRegistry");
|
||||||
|
java.lang.reflect.Method getMinecraftRegistryMethod = craftRegistryClass.getMethod("getMinecraftRegistry");
|
||||||
|
if (getMinecraftRegistryMethod.invoke(null) != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<?> regAccessClass = Class.forName("net.minecraft.core.RegistryAccess");
|
||||||
|
Class<?> builtInRegsClass = Class.forName("net.minecraft.core.registries.BuiltInRegistries");
|
||||||
|
Object rootRegistry = builtInRegsClass.getField("REGISTRY").get(null);
|
||||||
|
Object frozenAccess = regAccessClass.getMethod("fromRegistryOfRegistries", Class.forName("net.minecraft.core.Registry")).invoke(null, rootRegistry);
|
||||||
|
craftRegistryClass.getMethod("setMinecraftRegistry", regAccessClass).invoke(null, frozenAccess);
|
||||||
|
} catch (IllegalStateException ignored) {
|
||||||
|
// Already set by concurrent thread
|
||||||
|
} catch (Throwable t) {
|
||||||
|
if (logger != null) {
|
||||||
|
logger.log(Level.WARNING, "[PatchBukkit] Failed to initialize CraftRegistry", t);
|
||||||
|
} else {
|
||||||
|
System.err.println("[PatchBukkit] Failed to initialize CraftRegistry: " + t.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static volatile net.minecraft.server.network.ServerConnectionListener SERVER_CONNECTION;
|
||||||
|
private static volatile net.minecraft.server.dedicated.DedicatedServer DEDICATED_SERVER;
|
||||||
|
private static volatile net.minecraft.server.dedicated.DedicatedPlayerList DEDICATED_PLAYER_LIST;
|
||||||
|
private static volatile org.bukkit.craftbukkit.CraftServer CRAFT_SERVER;
|
||||||
|
|
||||||
|
public static net.minecraft.server.network.ServerConnectionListener getServerConnection() {
|
||||||
|
if (SERVER_CONNECTION == null) {
|
||||||
|
initMinecraftServerInstances();
|
||||||
|
}
|
||||||
|
return SERVER_CONNECTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static net.minecraft.server.dedicated.DedicatedServer getDedicatedServer() {
|
||||||
|
if (DEDICATED_SERVER == null) {
|
||||||
|
initMinecraftServerInstances();
|
||||||
|
}
|
||||||
|
return DEDICATED_SERVER;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static net.minecraft.server.dedicated.DedicatedPlayerList getDedicatedPlayerList() {
|
||||||
|
if (DEDICATED_PLAYER_LIST == null) {
|
||||||
|
initMinecraftServerInstances();
|
||||||
|
}
|
||||||
|
return DEDICATED_PLAYER_LIST;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static org.bukkit.craftbukkit.CraftServer getCraftServer() {
|
||||||
|
if (CRAFT_SERVER == null) {
|
||||||
|
initMinecraftServerInstances();
|
||||||
|
}
|
||||||
|
return CRAFT_SERVER;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static synchronized void initMinecraftServerInstances() {
|
||||||
|
if (SERVER_CONNECTION != null) return;
|
||||||
|
try {
|
||||||
|
ensureGlobalConfiguration();
|
||||||
|
ensureCraftRegistry();
|
||||||
|
|
||||||
|
Field unsafeField = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
|
||||||
|
unsafeField.setAccessible(true);
|
||||||
|
sun.misc.Unsafe unsafe = (sun.misc.Unsafe) unsafeField.get(null);
|
||||||
|
|
||||||
|
Class<?> dsClass = Class.forName("net.minecraft.server.dedicated.DedicatedServer");
|
||||||
|
DEDICATED_SERVER = (net.minecraft.server.dedicated.DedicatedServer) unsafe.allocateInstance(dsClass);
|
||||||
|
SERVER_CONNECTION = new net.minecraft.server.network.ServerConnectionListener(DEDICATED_SERVER);
|
||||||
|
|
||||||
|
for (Field f : net.minecraft.server.MinecraftServer.class.getDeclaredFields()) {
|
||||||
|
if (f.getType().equals(net.minecraft.server.network.ServerConnectionListener.class)) {
|
||||||
|
f.setAccessible(true);
|
||||||
|
f.set(DEDICATED_SERVER, SERVER_CONNECTION);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set static MinecraftServer.SERVER singleton
|
||||||
|
for (Field f : net.minecraft.server.MinecraftServer.class.getDeclaredFields()) {
|
||||||
|
if (java.lang.reflect.Modifier.isStatic(f.getModifiers()) && net.minecraft.server.MinecraftServer.class.isAssignableFrom(f.getType())) {
|
||||||
|
try {
|
||||||
|
unsafe.putObject(unsafe.staticFieldBase(f), unsafe.staticFieldOffset(f), DEDICATED_SERVER);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
try {
|
||||||
|
f.setAccessible(true);
|
||||||
|
f.set(null, DEDICATED_SERVER);
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<?> plClass = Class.forName("net.minecraft.server.dedicated.DedicatedPlayerList");
|
||||||
|
DEDICATED_PLAYER_LIST = (net.minecraft.server.dedicated.DedicatedPlayerList) unsafe.allocateInstance(plClass);
|
||||||
|
|
||||||
|
Field playersField = null;
|
||||||
|
Field playersByUUIDField = null;
|
||||||
|
Class<?> cur = DEDICATED_PLAYER_LIST.getClass();
|
||||||
|
while (cur != null) {
|
||||||
|
for (Field f : cur.getDeclaredFields()) {
|
||||||
|
if (f.getName().equals("players")) {
|
||||||
|
playersField = f;
|
||||||
|
} else if (f.getName().equals("playersByUUID")) {
|
||||||
|
playersByUUIDField = f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cur = cur.getSuperclass();
|
||||||
|
}
|
||||||
|
if (playersField != null) {
|
||||||
|
playersField.setAccessible(true);
|
||||||
|
playersField.set(DEDICATED_PLAYER_LIST, new java.util.concurrent.CopyOnWriteArrayList<>());
|
||||||
|
}
|
||||||
|
if (playersByUUIDField != null) {
|
||||||
|
playersByUUIDField.setAccessible(true);
|
||||||
|
playersByUUIDField.set(DEDICATED_PLAYER_LIST, new java.util.concurrent.ConcurrentHashMap<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Field f : net.minecraft.server.MinecraftServer.class.getDeclaredFields()) {
|
||||||
|
if (net.minecraft.server.players.PlayerList.class.isAssignableFrom(f.getType())) {
|
||||||
|
f.setAccessible(true);
|
||||||
|
f.set(DEDICATED_SERVER, DEDICATED_PLAYER_LIST);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<?> pldClass = Class.forName("net.minecraft.world.level.storage.PrimaryLevelData");
|
||||||
|
net.minecraft.world.level.storage.PrimaryLevelData pld = (net.minecraft.world.level.storage.PrimaryLevelData) unsafe.allocateInstance(pldClass);
|
||||||
|
for (Field f : pldClass.getDeclaredFields()) {
|
||||||
|
try {
|
||||||
|
f.setAccessible(true);
|
||||||
|
if (f.getName().equals("respawnDimension")) {
|
||||||
|
f.set(pld, net.minecraft.world.level.Level.OVERWORLD);
|
||||||
|
} else if (f.getName().equals("respawnData")) {
|
||||||
|
f.set(pld, net.minecraft.world.level.storage.LevelData.RespawnData.DEFAULT);
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Field f : net.minecraft.server.MinecraftServer.class.getDeclaredFields()) {
|
||||||
|
try {
|
||||||
|
f.setAccessible(true);
|
||||||
|
if (f.getName().equals("worldData") || net.minecraft.world.level.storage.WorldData.class.isAssignableFrom(f.getType())) {
|
||||||
|
f.set(DEDICATED_SERVER, pld);
|
||||||
|
} else if (f.getName().equals("effectiveRespawnData") || f.getType().equals(net.minecraft.world.level.storage.LevelData.RespawnData.class)) {
|
||||||
|
f.set(DEDICATED_SERVER, net.minecraft.world.level.storage.LevelData.RespawnData.DEFAULT);
|
||||||
|
} else if (f.getName().equals("levels")) {
|
||||||
|
f.set(DEDICATED_SERVER, new java.util.HashMap<>());
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<?> csClass = Class.forName("org.bukkit.craftbukkit.CraftServer");
|
||||||
|
CRAFT_SERVER = (org.bukkit.craftbukkit.CraftServer) unsafe.allocateInstance(csClass);
|
||||||
|
for (Field f : csClass.getDeclaredFields()) {
|
||||||
|
try {
|
||||||
|
f.setAccessible(true);
|
||||||
|
if (f.getName().equals("console")) {
|
||||||
|
f.set(CRAFT_SERVER, DEDICATED_SERVER);
|
||||||
|
} else if (f.getName().equals("playerList")) {
|
||||||
|
f.set(CRAFT_SERVER, DEDICATED_PLAYER_LIST);
|
||||||
|
} else if (f.getName().equals("serverName")) {
|
||||||
|
f.set(CRAFT_SERVER, "Paper");
|
||||||
|
} else if (f.getName().equals("serverVersion")) {
|
||||||
|
f.set(CRAFT_SERVER, org.patchbukkit.versioning.Versioning.getBukkitVersion());
|
||||||
|
} else if (f.getName().equals("bukkitVersion")) {
|
||||||
|
f.set(CRAFT_SERVER, org.patchbukkit.versioning.Versioning.getBukkitVersion());
|
||||||
|
} else if (f.getName().equals("logger")) {
|
||||||
|
f.set(CRAFT_SERVER, logger);
|
||||||
|
} else if (f.getName().equals("worlds")) {
|
||||||
|
f.set(CRAFT_SERVER, new java.util.concurrent.ConcurrentHashMap<>());
|
||||||
|
} else if (f.getName().equals("offlinePlayers")) {
|
||||||
|
f.set(CRAFT_SERVER, new java.util.concurrent.ConcurrentHashMap<>());
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Field f : net.minecraft.server.MinecraftServer.class.getDeclaredFields()) {
|
||||||
|
if (f.getName().equals("server") || f.getType().getName().contains("CraftServer")) {
|
||||||
|
f.setAccessible(true);
|
||||||
|
f.set(DEDICATED_SERVER, CRAFT_SERVER);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
logger.log(Level.WARNING, "[PatchBukkit] Failed to initialize DedicatedServer instances", t);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PatchBukkitServer initServer() {
|
public static PatchBukkitServer initServer() {
|
||||||
|
ensureGlobalConfiguration();
|
||||||
|
ensureCraftRegistry();
|
||||||
|
getServerConnection();
|
||||||
Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
|
Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
|
||||||
logger.log(Level.SEVERE, "Uncaught exception in thread " + thread.getName(), throwable);
|
logger.log(Level.SEVERE, "Uncaught exception in thread " + thread.getName(), throwable);
|
||||||
});
|
});
|
||||||
PatchBukkitServer server = getInstance();
|
PatchBukkitServer server = getInstance();
|
||||||
org.bukkit.Bukkit.setServer(server);
|
if (org.bukkit.Bukkit.getServer() == null) {
|
||||||
|
org.bukkit.Bukkit.setServer(server);
|
||||||
|
}
|
||||||
return server;
|
return server;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final String serverName;
|
public final net.minecraft.server.dedicated.DedicatedServer console = getDedicatedServer();
|
||||||
private final String bukkitVersion = Versioning.getBukkitVersion();
|
|
||||||
|
public net.minecraft.server.dedicated.DedicatedServer getServer() {
|
||||||
|
return console;
|
||||||
|
}
|
||||||
|
|
||||||
|
public net.minecraft.server.dedicated.DedicatedPlayerList getHandle() {
|
||||||
|
return getDedicatedPlayerList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public net.minecraft.server.network.ServerConnectionListener getConnection() {
|
||||||
|
return getServerConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String serverName = "Pumpkin";
|
||||||
|
private String bukkitVersion = Versioning.getBukkitVersion();
|
||||||
|
private String minecraftVersion = Versioning.getCurrentApiVersion();
|
||||||
|
private String serverVersion = "git-Paper-117 (MC: " + this.minecraftVersion + ")";
|
||||||
public SimpleCommandMap commandMap = new PatchBukkitCommandMap(this);
|
public SimpleCommandMap commandMap = new PatchBukkitCommandMap(this);
|
||||||
public BukkitScheduler scheduler = new PatchBukkitScheduler();
|
public BukkitScheduler scheduler = new PatchBukkitScheduler();
|
||||||
public PatchBukkitPluginManager pluginManager = new PatchBukkitPluginManager(this);
|
public PatchBukkitPluginManager pluginManager = new PatchBukkitPluginManager(this);
|
||||||
@ -315,6 +603,18 @@ public class PatchBukkitServer implements Server {
|
|||||||
try {
|
try {
|
||||||
ServerInfoResponse info = NativeBridgeFfi.getServerInfo(EmptyRequest.getDefaultInstance());
|
ServerInfoResponse info = NativeBridgeFfi.getServerInfo(EmptyRequest.getDefaultInstance());
|
||||||
if (info != null) {
|
if (info != null) {
|
||||||
|
if (info.getServerName() != null && !info.getServerName().isEmpty()) {
|
||||||
|
this.serverName = info.getServerName();
|
||||||
|
}
|
||||||
|
if (info.getBukkitVersion() != null && !info.getBukkitVersion().isEmpty()) {
|
||||||
|
this.bukkitVersion = info.getBukkitVersion();
|
||||||
|
}
|
||||||
|
if (info.getMinecraftVersion() != null && !info.getMinecraftVersion().isEmpty()) {
|
||||||
|
this.minecraftVersion = info.getMinecraftVersion();
|
||||||
|
}
|
||||||
|
if (info.getVersion() != null && !info.getVersion().isEmpty()) {
|
||||||
|
this.serverVersion = "git-Paper-" + info.getVersion() + " (MC: " + this.minecraftVersion + ")";
|
||||||
|
}
|
||||||
this.motd = info.getMotd();
|
this.motd = info.getMotd();
|
||||||
this.ip = info.getIp();
|
this.ip = info.getIp();
|
||||||
this.port = info.getPort();
|
this.port = info.getPort();
|
||||||
@ -379,14 +679,20 @@ public class PatchBukkitServer implements Server {
|
|||||||
} catch (Throwable ignored) {}
|
} catch (Throwable ignored) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final PrintStream ORIGINAL_OUT = System.out;
|
public static final PrintStream ORIGINAL_OUT = System.out;
|
||||||
private static final PrintStream ORIGINAL_ERR = System.err;
|
public static final PrintStream ORIGINAL_ERR = System.err;
|
||||||
private static final ThreadLocal<Boolean> IN_LOGGING = ThreadLocal.withInitial(() -> Boolean.FALSE);
|
private static final ThreadLocal<Boolean> IN_LOGGING = ThreadLocal.withInitial(() -> Boolean.FALSE);
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger("Minecraft");
|
public static boolean isLogging() {
|
||||||
|
return IN_LOGGING.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setLogging(boolean logging) {
|
||||||
|
IN_LOGGING.set(logging);
|
||||||
|
}
|
||||||
|
|
||||||
static {
|
static {
|
||||||
configureRootLogger();
|
configureLogging();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final Map<Level, LogLevel> LEVEL_MAP = Map.of(
|
public static final Map<Level, LogLevel> LEVEL_MAP = Map.of(
|
||||||
@ -399,29 +705,57 @@ public class PatchBukkitServer implements Server {
|
|||||||
Level.FINEST, LogLevel.FINEST
|
Level.FINEST, LogLevel.FINEST
|
||||||
);
|
);
|
||||||
|
|
||||||
private static void configureRootLogger() {
|
private static void configureLogging() {
|
||||||
|
configureLog4j();
|
||||||
|
configureJulLogger();
|
||||||
|
redirectSystemStreams();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void configureLog4j() {
|
||||||
|
try {
|
||||||
|
org.apache.logging.log4j.core.LoggerContext context =
|
||||||
|
(org.apache.logging.log4j.core.LoggerContext) org.apache.logging.log4j.LogManager.getContext(false);
|
||||||
|
org.apache.logging.log4j.core.config.Configuration config = context.getConfiguration();
|
||||||
|
|
||||||
|
org.patchbukkit.log.NativeBridgeLog4jAppender appender =
|
||||||
|
org.patchbukkit.log.NativeBridgeLog4jAppender.create("NativeBridge");
|
||||||
|
appender.start();
|
||||||
|
config.addAppender(appender);
|
||||||
|
|
||||||
|
org.apache.logging.log4j.core.config.LoggerConfig rootConfig = config.getRootLogger();
|
||||||
|
rootConfig.removeAppender("TerminalConsole");
|
||||||
|
rootConfig.removeAppender("ServerGuiConsole");
|
||||||
|
rootConfig.removeAppender("Async");
|
||||||
|
|
||||||
|
rootConfig.addAppender(appender, org.apache.logging.log4j.Level.ALL, null);
|
||||||
|
rootConfig.setLevel(org.apache.logging.log4j.Level.ALL);
|
||||||
|
context.updateLoggers();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
ORIGINAL_ERR.println("[PatchBukkit] Failed to initialize Log4j native bridge appender: " + t.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void configureJulLogger() {
|
||||||
Logger root = Logger.getLogger("");
|
Logger root = Logger.getLogger("");
|
||||||
root.setUseParentHandlers(false);
|
root.setUseParentHandlers(false);
|
||||||
for (java.util.logging.Handler h : root.getHandlers()) {
|
for (java.util.logging.Handler h : root.getHandlers()) {
|
||||||
root.removeHandler(h);
|
root.removeHandler(h);
|
||||||
}
|
}
|
||||||
root.setLevel(Level.ALL);
|
root.setLevel(Level.ALL);
|
||||||
root.addHandler(new java.util.logging.Handler() {
|
try {
|
||||||
@Override
|
root.addHandler(new org.bukkit.craftbukkit.util.ForwardLogHandler());
|
||||||
public void publish(java.util.logging.LogRecord record) {
|
} catch (Throwable t) {
|
||||||
if (record == null) return;
|
ORIGINAL_ERR.println("[PatchBukkit] Failed to install ForwardLogHandler, using fallback: " + t.getMessage());
|
||||||
if (IN_LOGGING.get()) return;
|
root.addHandler(new java.util.logging.Handler() {
|
||||||
IN_LOGGING.set(true);
|
@Override
|
||||||
try {
|
public void publish(java.util.logging.LogRecord record) {
|
||||||
LogLevel logLevel = LEVEL_MAP.getOrDefault(record.getLevel(), LogLevel.INFO);
|
if (record == null) return;
|
||||||
String message = formatLogRecord(record);
|
if (IN_LOGGING.get()) return;
|
||||||
String loggerName = record.getLoggerName() != null ? record.getLoggerName() : "";
|
IN_LOGGING.set(true);
|
||||||
if (loggerName.startsWith("jdk.") || loggerName.startsWith("sun.") || loggerName.startsWith("java.") || loggerName.startsWith("javax.")) {
|
|
||||||
if (record.getLevel().intValue() < Level.WARNING.intValue()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
|
LogLevel logLevel = LEVEL_MAP.getOrDefault(record.getLevel(), LogLevel.INFO);
|
||||||
|
String message = formatLogRecord(record);
|
||||||
|
String loggerName = record.getLoggerName() != null ? record.getLoggerName() : "";
|
||||||
NativeBridgeFfi.sendLog(
|
NativeBridgeFfi.sendLog(
|
||||||
SendLogRequest.newBuilder()
|
SendLogRequest.newBuilder()
|
||||||
.setLevel(logLevel)
|
.setLevel(logLevel)
|
||||||
@ -429,20 +763,15 @@ public class PatchBukkitServer implements Server {
|
|||||||
.setLoggerName(loggerName)
|
.setLoggerName(loggerName)
|
||||||
.build()
|
.build()
|
||||||
);
|
);
|
||||||
} catch (Throwable t) {
|
} catch (Throwable ignored) {
|
||||||
ORIGINAL_ERR.println("[" + record.getLevel() + "][" + loggerName + "] " + message);
|
} finally {
|
||||||
|
IN_LOGGING.set(false);
|
||||||
}
|
}
|
||||||
} catch (Throwable t) {
|
|
||||||
ORIGINAL_ERR.println("[RootLogger Error] Failed to publish record: " + t.getMessage());
|
|
||||||
} finally {
|
|
||||||
IN_LOGGING.set(false);
|
|
||||||
}
|
}
|
||||||
}
|
@Override public void flush() {}
|
||||||
@Override public void flush() {}
|
@Override public void close() {}
|
||||||
@Override public void close() {}
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
redirectSystemStreams();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String formatLogRecord(java.util.logging.LogRecord record) {
|
public static String formatLogRecord(java.util.logging.LogRecord record) {
|
||||||
@ -475,8 +804,8 @@ public class PatchBukkitServer implements Server {
|
|||||||
|
|
||||||
private static void redirectSystemStreams() {
|
private static void redirectSystemStreams() {
|
||||||
try {
|
try {
|
||||||
System.setOut(new LoggingPrintStream(ORIGINAL_OUT, Level.INFO, "System.out"));
|
System.setOut(new LoggingPrintStream(ORIGINAL_OUT, Level.INFO, "STDOUT"));
|
||||||
System.setErr(new LoggingPrintStream(ORIGINAL_ERR, Level.SEVERE, "System.err"));
|
System.setErr(new LoggingPrintStream(ORIGINAL_ERR, Level.SEVERE, "STDERR"));
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
ORIGINAL_ERR.println("[PatchBukkit] Failed to redirect System streams: " + t.getMessage());
|
ORIGINAL_ERR.println("[PatchBukkit] Failed to redirect System streams: " + t.getMessage());
|
||||||
}
|
}
|
||||||
@ -525,8 +854,17 @@ public class PatchBukkitServer implements Server {
|
|||||||
if (line.isEmpty() || IN_LOGGING.get()) return;
|
if (line.isEmpty() || IN_LOGGING.get()) return;
|
||||||
IN_LOGGING.set(true);
|
IN_LOGGING.set(true);
|
||||||
try {
|
try {
|
||||||
LogLevel logLevel = LEVEL_MAP.getOrDefault(level, LogLevel.INFO);
|
org.apache.logging.log4j.Logger log4jLogger = org.apache.logging.log4j.LogManager.getLogger(loggerName);
|
||||||
|
if (level == Level.SEVERE) {
|
||||||
|
log4jLogger.error(line);
|
||||||
|
} else if (level == Level.WARNING) {
|
||||||
|
log4jLogger.warn(line);
|
||||||
|
} else {
|
||||||
|
log4jLogger.info(line);
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
try {
|
try {
|
||||||
|
LogLevel logLevel = LEVEL_MAP.getOrDefault(level, LogLevel.INFO);
|
||||||
NativeBridgeFfi.sendLog(
|
NativeBridgeFfi.sendLog(
|
||||||
SendLogRequest.newBuilder()
|
SendLogRequest.newBuilder()
|
||||||
.setLevel(logLevel)
|
.setLevel(logLevel)
|
||||||
@ -547,9 +885,13 @@ public class PatchBukkitServer implements Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void registerPlayer(String uuidStr, String name, boolean isOp) {
|
public static void registerPlayer(String uuidStr, String name, boolean isOp) {
|
||||||
|
registerPlayer(uuidStr, name, isOp, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void registerPlayer(String uuidStr, String name, boolean isOp, int entityId) {
|
||||||
try {
|
try {
|
||||||
UUID uuid = UUID.fromString(uuidStr);
|
UUID uuid = UUID.fromString(uuidStr);
|
||||||
PatchBukkitPlayer player = new PatchBukkitPlayer(uuid, name);
|
PatchBukkitPlayer player = new org.patchbukkit.entity.CraftPlayer(uuid, name, entityId);
|
||||||
if (isOp) {
|
if (isOp) {
|
||||||
player.setOp(true);
|
player.setOp(true);
|
||||||
}
|
}
|
||||||
@ -635,7 +977,7 @@ public class PatchBukkitServer implements Server {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull String getVersion() {
|
public @NotNull String getVersion() {
|
||||||
return "26.2";
|
return this.serverVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -645,11 +987,7 @@ public class PatchBukkitServer implements Server {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull String getMinecraftVersion() {
|
public @NotNull String getMinecraftVersion() {
|
||||||
String version = this.bukkitVersion;
|
return this.minecraftVersion;
|
||||||
if (version != null && version.contains("-")) {
|
|
||||||
return version.split("-")[0];
|
|
||||||
}
|
|
||||||
return version != null && !version.equals("Unknown-Version") ? version : "1.21.4";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -35,12 +35,12 @@ public final class PatchBukkitServerBuildInfo implements ServerBuildInfo {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String minecraftVersionId() {
|
public String minecraftVersionId() {
|
||||||
return "26.2";
|
return org.patchbukkit.versioning.Versioning.getCurrentApiVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String minecraftVersionName() {
|
public String minecraftVersionName() {
|
||||||
return "26.2";
|
return org.patchbukkit.versioning.Versioning.getCurrentApiVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -0,0 +1,10 @@
|
|||||||
|
package org.patchbukkit.block.data;
|
||||||
|
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
|
||||||
|
public class CraftBlockData {
|
||||||
|
|
||||||
|
public static CraftBlockData fromData(BlockState data) {
|
||||||
|
return new CraftBlockData();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -157,7 +157,7 @@ public class PatchBukkitCommandMap extends SimpleCommandMap {
|
|||||||
java.util.UUID uuid = java.util.UUID.fromString(senderUuid);
|
java.util.UUID uuid = java.util.UUID.fromString(senderUuid);
|
||||||
sender = org.bukkit.Bukkit.getPlayer(uuid);
|
sender = org.bukkit.Bukkit.getPlayer(uuid);
|
||||||
if (sender == null) {
|
if (sender == null) {
|
||||||
sender = new org.patchbukkit.entity.PatchBukkitPlayer(uuid, senderName != null ? senderName : "Player");
|
sender = new org.patchbukkit.entity.CraftPlayer(uuid, senderName != null ? senderName : "Player");
|
||||||
}
|
}
|
||||||
if (sender instanceof org.patchbukkit.entity.PatchBukkitPlayer p) {
|
if (sender instanceof org.patchbukkit.entity.PatchBukkitPlayer p) {
|
||||||
p.setOp(isOp);
|
p.setOp(isOp);
|
||||||
@ -179,7 +179,7 @@ public class PatchBukkitCommandMap extends SimpleCommandMap {
|
|||||||
java.util.UUID uuid = java.util.UUID.fromString(senderUuid);
|
java.util.UUID uuid = java.util.UUID.fromString(senderUuid);
|
||||||
sender = org.bukkit.Bukkit.getPlayer(uuid);
|
sender = org.bukkit.Bukkit.getPlayer(uuid);
|
||||||
if (sender == null) {
|
if (sender == null) {
|
||||||
sender = new org.patchbukkit.entity.PatchBukkitPlayer(uuid, senderName != null ? senderName : "Player");
|
sender = new org.patchbukkit.entity.CraftPlayer(uuid, senderName != null ? senderName : "Player");
|
||||||
}
|
}
|
||||||
if (sender instanceof org.patchbukkit.entity.PatchBukkitPlayer p) {
|
if (sender instanceof org.patchbukkit.entity.PatchBukkitPlayer p) {
|
||||||
p.setOp(isOp);
|
p.setOp(isOp);
|
||||||
|
|||||||
@ -50,7 +50,8 @@ public class PatchBukkitConsoleCommandSender
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Server getServer() {
|
public @NotNull Server getServer() {
|
||||||
return Bukkit.getServer();
|
org.bukkit.craftbukkit.CraftServer cs = org.patchbukkit.PatchBukkitServer.getCraftServer();
|
||||||
|
return cs != null ? cs : Bukkit.getServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -0,0 +1,21 @@
|
|||||||
|
package org.patchbukkit.entity;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
import net.minecraft.world.entity.Entity;
|
||||||
|
|
||||||
|
public class CraftEntity extends PatchBukkitEntity {
|
||||||
|
|
||||||
|
private Entity handle;
|
||||||
|
|
||||||
|
public CraftEntity(UUID uuid, String name) {
|
||||||
|
super(uuid, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Entity getHandle() {
|
||||||
|
return this.handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHandle(Entity handle) {
|
||||||
|
this.handle = handle;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
package org.patchbukkit.entity;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
import org.patchbukkit.network.VirtualChannelManager;
|
||||||
|
|
||||||
|
public class CraftPlayer extends PatchBukkitPlayer {
|
||||||
|
|
||||||
|
public CraftPlayer(UUID uuid, String name) {
|
||||||
|
super(uuid, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CraftPlayer(UUID uuid, String name, int entityId) {
|
||||||
|
super(uuid, name, entityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CraftPlayer(org.patchbukkit.PatchBukkitServer server, ServerPlayer entity) {
|
||||||
|
super(entity.getUUID(), entity.getScoreboardName(), entity.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ServerPlayer getHandle() {
|
||||||
|
return VirtualChannelManager.getInstance().getOrCreateServerPlayer(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -86,19 +86,41 @@ public class PatchBukkitEntity implements Entity {
|
|||||||
private Location cachedLocation;
|
private Location cachedLocation;
|
||||||
private EntityType entityType = EntityType.UNKNOWN;
|
private EntityType entityType = EntityType.UNKNOWN;
|
||||||
|
|
||||||
public static Entity create(UUID uuid, EntityType type, Location loc) {
|
public static Entity create(UUID uuid, EntityType type, Location loc, int entityId) {
|
||||||
PatchBukkitEntity entity = new PatchBukkitEntity(uuid, type != null ? type.name() : "entity");
|
PatchBukkitEntity entity = new PatchBukkitEntity(uuid, type != null ? type.name() : "entity", entityId);
|
||||||
entity.entityType = type != null ? type : EntityType.UNKNOWN;
|
entity.entityType = type != null ? type : EntityType.UNKNOWN;
|
||||||
entity.cachedLocation = loc != null ? loc.clone() : new Location(null, 0, 0, 0);
|
entity.cachedLocation = loc != null ? loc.clone() : new Location(null, 0, 0, 0);
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Entity create(UUID uuid, EntityType type, Location loc) {
|
||||||
|
return create(uuid, type, loc, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final java.util.concurrent.atomic.AtomicInteger NEXT_ENTITY_ID = new java.util.concurrent.atomic.AtomicInteger(1);
|
||||||
|
protected int entityId;
|
||||||
|
|
||||||
|
public PatchBukkitEntity(
|
||||||
|
UUID uuid,
|
||||||
|
String name,
|
||||||
|
int entityId
|
||||||
|
) {
|
||||||
|
this.uuid = uuid;
|
||||||
|
this.name = name;
|
||||||
|
this.entityId = entityId > 0 ? entityId : -1;
|
||||||
|
}
|
||||||
|
|
||||||
public PatchBukkitEntity(
|
public PatchBukkitEntity(
|
||||||
UUID uuid,
|
UUID uuid,
|
||||||
String name
|
String name
|
||||||
) {
|
) {
|
||||||
this.uuid = uuid;
|
this(uuid, name, -1);
|
||||||
this.name = name;
|
}
|
||||||
|
|
||||||
|
public void setEntityId(int entityId) {
|
||||||
|
if (entityId > 0) {
|
||||||
|
this.entityId = entityId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -425,8 +447,18 @@ public class PatchBukkitEntity implements Entity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getEntityId() {
|
public int getEntityId() {
|
||||||
// TODO Auto-generated method stub
|
if (this.entityId <= 0) {
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'getEntityId'");
|
try {
|
||||||
|
var resp = NativeBridgeFfi.getEntityId(BridgeUtils.convertUuid(this.uuid));
|
||||||
|
if (resp != null && resp.getEntityId() > 0) {
|
||||||
|
this.entityId = resp.getEntityId();
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
}
|
||||||
|
if (this.entityId <= 0) {
|
||||||
|
this.entityId = NEXT_ENTITY_ID.incrementAndGet();
|
||||||
|
}
|
||||||
|
return this.entityId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -78,8 +78,12 @@ public class PatchBukkitHumanEntity
|
|||||||
private int sleepTicks = 0;
|
private int sleepTicks = 0;
|
||||||
private int enchantmentSeed = 0;
|
private int enchantmentSeed = 0;
|
||||||
|
|
||||||
|
public PatchBukkitHumanEntity(UUID uuid, String name, int entityId) {
|
||||||
|
super(uuid, name, entityId);
|
||||||
|
}
|
||||||
|
|
||||||
public PatchBukkitHumanEntity(UUID uuid, String name) {
|
public PatchBukkitHumanEntity(UUID uuid, String name) {
|
||||||
super(uuid, name);
|
this(uuid, name, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -60,9 +60,12 @@ public class PatchBukkitLivingEntity
|
|||||||
extends PatchBukkitEntity
|
extends PatchBukkitEntity
|
||||||
implements LivingEntity {
|
implements LivingEntity {
|
||||||
|
|
||||||
public PatchBukkitLivingEntity(UUID uuid,
|
public PatchBukkitLivingEntity(UUID uuid, String name, int entityId) {
|
||||||
String name) {
|
super(uuid, name, entityId);
|
||||||
super(uuid, name);
|
}
|
||||||
|
|
||||||
|
public PatchBukkitLivingEntity(UUID uuid, String name) {
|
||||||
|
this(uuid, name, -1);
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public double getEyeHeight() {
|
public double getEyeHeight() {
|
||||||
|
|||||||
@ -124,12 +124,23 @@ public class PatchBukkitPlayer extends PatchBukkitHumanEntity implements Player
|
|||||||
private int expCooldown = 0;
|
private int expCooldown = 0;
|
||||||
private TriState flyingFallDamage = TriState.NOT_SET;
|
private TriState flyingFallDamage = TriState.NOT_SET;
|
||||||
|
|
||||||
public PatchBukkitPlayer(UUID uuid, String name) {
|
public PatchBukkitPlayer(UUID uuid, String name, int entityId) {
|
||||||
super(uuid, name);
|
super(uuid, name, entityId);
|
||||||
this.displayName = name;
|
this.displayName = name;
|
||||||
this.playerListName = name;
|
this.playerListName = name;
|
||||||
this.firstPlayed = System.currentTimeMillis();
|
this.firstPlayed = System.currentTimeMillis();
|
||||||
this.lastLogin = System.currentTimeMillis();
|
this.lastLogin = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
public PatchBukkitPlayer(UUID uuid, String name) {
|
||||||
|
this(uuid, name, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public net.minecraft.server.level.ServerPlayer getHandle() {
|
||||||
|
return org.patchbukkit.network.VirtualChannelManager.getInstance().getOrCreateServerPlayer(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
this.spigot = new Player.Spigot() {
|
this.spigot = new Player.Spigot() {
|
||||||
@Override
|
@Override
|
||||||
public void sendMessage(@NotNull BaseComponent component) {
|
public void sendMessage(@NotNull BaseComponent component) {
|
||||||
@ -2054,6 +2065,11 @@ public class PatchBukkitPlayer extends PatchBukkitHumanEntity implements Player
|
|||||||
spawnParticle(particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, data);
|
spawnParticle(particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> void spawnParticle(@NotNull Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, double xSpread, double ySpread, @Nullable T data, boolean force, @NotNull Particle.RandomizationType randomizationType) {
|
||||||
|
spawnParticle(particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, data, force);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull AdvancementProgress getAdvancementProgress(@NotNull Advancement advancement) {
|
public @NotNull AdvancementProgress getAdvancementProgress(@NotNull Advancement advancement) {
|
||||||
return new AdvancementProgress() {
|
return new AdvancementProgress() {
|
||||||
|
|||||||
@ -146,8 +146,22 @@ public class PatchBukkitEventFactory {
|
|||||||
var ev = event.getMapInitialize();
|
var ev = event.getMapInitialize();
|
||||||
yield createGenericBukkitEvent("org.bukkit.event.server.MapInitializeEvent", ev);
|
yield createGenericBukkitEvent("org.bukkit.event.server.MapInitializeEvent", ev);
|
||||||
}
|
}
|
||||||
case PACKET_RECEIVED -> null;
|
case PACKET_RECEIVED -> {
|
||||||
case PACKET_SENT -> null;
|
var ev = event.getPacketReceived();
|
||||||
|
if (ev.hasPlayerUuid()) {
|
||||||
|
java.util.UUID uuid = java.util.UUID.fromString(ev.getPlayerUuid().getValue());
|
||||||
|
org.patchbukkit.network.VirtualChannelManager.getInstance().handlePacketReceived(uuid, ev.getPacketId(), ev.getPayload().toByteArray());
|
||||||
|
}
|
||||||
|
yield null;
|
||||||
|
}
|
||||||
|
case PACKET_SENT -> {
|
||||||
|
var ev = event.getPacketSent();
|
||||||
|
if (ev.hasPlayerUuid()) {
|
||||||
|
java.util.UUID uuid = java.util.UUID.fromString(ev.getPlayerUuid().getValue());
|
||||||
|
org.patchbukkit.network.VirtualChannelManager.getInstance().handlePacketSent(uuid, ev.getPacketId(), ev.getPayload().toByteArray());
|
||||||
|
}
|
||||||
|
yield null;
|
||||||
|
}
|
||||||
case PLUGIN_DISABLE -> {
|
case PLUGIN_DISABLE -> {
|
||||||
var ev = event.getPluginDisable();
|
var ev = event.getPluginDisable();
|
||||||
var plugin = Bukkit.getPluginManager().getPlugin(ev.getPluginName());
|
var plugin = Bukkit.getPluginManager().getPlugin(ev.getPluginName());
|
||||||
@ -687,6 +701,9 @@ public class PatchBukkitEventFactory {
|
|||||||
var ev = event.getPlayerJoin();
|
var ev = event.getPlayerJoin();
|
||||||
Player player = getPlayer(ev.getPlayerUuid().getValue());
|
Player player = getPlayer(ev.getPlayerUuid().getValue());
|
||||||
if (player == null) yield null;
|
if (player == null) yield null;
|
||||||
|
if (ev.getEntityId() > 0 && player instanceof org.patchbukkit.entity.PatchBukkitEntity pbe) {
|
||||||
|
pbe.setEntityId(ev.getEntityId());
|
||||||
|
}
|
||||||
Component msg = ev.getJoinMessage().isEmpty() ? Component.empty() : GsonComponentSerializer.gson().deserialize(ev.getJoinMessage());
|
Component msg = ev.getJoinMessage().isEmpty() ? Component.empty() : GsonComponentSerializer.gson().deserialize(ev.getJoinMessage());
|
||||||
yield new org.bukkit.event.player.PlayerJoinEvent(player, msg);
|
yield new org.bukkit.event.player.PlayerJoinEvent(player, msg);
|
||||||
}
|
}
|
||||||
@ -701,6 +718,11 @@ public class PatchBukkitEventFactory {
|
|||||||
case PLAYER_LEAVE -> {
|
case PLAYER_LEAVE -> {
|
||||||
var ev = event.getPlayerLeave();
|
var ev = event.getPlayerLeave();
|
||||||
Player player = getPlayer(ev.getPlayerUuid().getValue());
|
Player player = getPlayer(ev.getPlayerUuid().getValue());
|
||||||
|
if (ev.hasPlayerUuid()) {
|
||||||
|
org.patchbukkit.network.VirtualChannelManager.getInstance().removePlayer(
|
||||||
|
java.util.UUID.fromString(ev.getPlayerUuid().getValue())
|
||||||
|
);
|
||||||
|
}
|
||||||
if (player == null) yield null;
|
if (player == null) yield null;
|
||||||
Component msg = ev.getLeaveMessage().isEmpty() ? Component.empty() : GsonComponentSerializer.gson().deserialize(ev.getLeaveMessage());
|
Component msg = ev.getLeaveMessage().isEmpty() ? Component.empty() : GsonComponentSerializer.gson().deserialize(ev.getLeaveMessage());
|
||||||
yield new org.bukkit.event.player.PlayerQuitEvent(player, msg);
|
yield new org.bukkit.event.player.PlayerQuitEvent(player, msg);
|
||||||
@ -1293,7 +1315,7 @@ public class PatchBukkitEventFactory {
|
|||||||
java.util.UUID uuid = java.util.UUID.fromString(uuidStr);
|
java.util.UUID uuid = java.util.UUID.fromString(uuidStr);
|
||||||
Player player = Bukkit.getServer().getPlayer(uuid);
|
Player player = Bukkit.getServer().getPlayer(uuid);
|
||||||
if (player == null) {
|
if (player == null) {
|
||||||
player = new org.patchbukkit.entity.PatchBukkitPlayer(uuid, "Player");
|
player = new org.patchbukkit.entity.CraftPlayer(uuid, "Player");
|
||||||
if (Bukkit.getServer() instanceof org.patchbukkit.PatchBukkitServer server) {
|
if (Bukkit.getServer() instanceof org.patchbukkit.PatchBukkitServer server) {
|
||||||
server.registerPlayer(player);
|
server.registerPlayer(player);
|
||||||
}
|
}
|
||||||
@ -1306,7 +1328,7 @@ public class PatchBukkitEventFactory {
|
|||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public static Entity getEntity(int entityId) {
|
public static Entity getEntity(int entityId) {
|
||||||
return new org.patchbukkit.entity.PatchBukkitEntity(new java.util.UUID(0, entityId), "Entity-" + entityId);
|
return new org.patchbukkit.entity.CraftEntity(new java.util.UUID(0, entityId), "Entity-" + entityId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
|||||||
@ -0,0 +1,34 @@
|
|||||||
|
package org.patchbukkit.inventory;
|
||||||
|
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
|
||||||
|
public class CraftItemStack extends ItemStack {
|
||||||
|
|
||||||
|
public CraftItemStack() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static net.minecraft.world.item.ItemStack asNMSCopy(ItemStack original) {
|
||||||
|
try {
|
||||||
|
return org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(original);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ItemStack asBukkitCopy(net.minecraft.world.item.ItemStack original) {
|
||||||
|
try {
|
||||||
|
return org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(original);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ItemStack asCraftMirror(net.minecraft.world.item.ItemStack original) {
|
||||||
|
try {
|
||||||
|
return org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitMirror(original);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,105 @@
|
|||||||
|
package org.patchbukkit.loader;
|
||||||
|
|
||||||
|
import org.objectweb.asm.ClassReader;
|
||||||
|
import org.objectweb.asm.ClassVisitor;
|
||||||
|
import org.objectweb.asm.ClassWriter;
|
||||||
|
import org.objectweb.asm.MethodVisitor;
|
||||||
|
import org.objectweb.asm.Opcodes;
|
||||||
|
import org.objectweb.asm.commons.ClassRemapper;
|
||||||
|
import org.objectweb.asm.commons.Remapper;
|
||||||
|
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
public final class BytecodeTransformer {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger("BytecodeTransformer");
|
||||||
|
|
||||||
|
private static Remapper createRemapper(String currentClassName) {
|
||||||
|
final boolean isCraftBukkitReflection = currentClassName != null && currentClassName.contains("CraftBukkitReflection");
|
||||||
|
|
||||||
|
return new Remapper() {
|
||||||
|
@Override
|
||||||
|
public String map(String internalName) {
|
||||||
|
if (internalName == null) return null;
|
||||||
|
|
||||||
|
// Missing dot after craftbukkit (e.g. Cloud command framework: org/bukkit/craftbukkitcommand -> org/bukkit/craftbukkit/command)
|
||||||
|
if (internalName.startsWith("org/bukkit/craftbukkit") && !internalName.startsWith("org/bukkit/craftbukkit/")) {
|
||||||
|
return "org/bukkit/craftbukkit/" + internalName.substring("org/bukkit/craftbukkit".length());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remap legacy versioned CraftBukkit: org/bukkit/craftbukkit/v1_20_R3/... -> org/bukkit/craftbukkit/...
|
||||||
|
if (internalName.startsWith("org/bukkit/craftbukkit/v")) {
|
||||||
|
String unversioned = internalName.replaceFirst("^org/bukkit/craftbukkit/v[0-9_]+R[0-9]+/", "org/bukkit/craftbukkit/");
|
||||||
|
return map(unversioned);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep CraftServer as is inside CraftBukkitReflection to prevent breaking its reflection logic
|
||||||
|
if (isCraftBukkitReflection && internalName.equals("org/bukkit/craftbukkit/CraftServer")) {
|
||||||
|
return internalName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remap CraftServer -> PatchBukkitServer for seamless native casts and method calls
|
||||||
|
if (internalName.equals("org/bukkit/craftbukkit/CraftServer")) {
|
||||||
|
return "org/patchbukkit/PatchBukkitServer";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remap CraftPlayer -> PatchBukkitPlayer for seamless native casts and getHandle()
|
||||||
|
if (internalName.equals("org/bukkit/craftbukkit/entity/CraftPlayer")) {
|
||||||
|
return "org/patchbukkit/entity/PatchBukkitPlayer";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remap CraftScheduler -> BukkitScheduler
|
||||||
|
if (internalName.equals("org/bukkit/craftbukkit/scheduler/CraftScheduler")) {
|
||||||
|
return "org/bukkit/scheduler/BukkitScheduler";
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.map(internalName);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private BytecodeTransformer() {}
|
||||||
|
|
||||||
|
public static byte[] transform(byte[] classBytes) {
|
||||||
|
if (classBytes == null || classBytes.length == 0) {
|
||||||
|
return classBytes;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
ClassReader reader = new ClassReader(classBytes);
|
||||||
|
String className = reader.getClassName();
|
||||||
|
ClassWriter writer = new ClassWriter(reader, 0);
|
||||||
|
Remapper remapper = createRemapper(className);
|
||||||
|
ClassVisitor cv = new ClassRemapper(writer, remapper);
|
||||||
|
|
||||||
|
if (className != null && className.contains("CraftBukkitReflection")) {
|
||||||
|
cv = new ClassVisitor(Opcodes.ASM9, cv) {
|
||||||
|
@Override
|
||||||
|
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
|
||||||
|
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
|
||||||
|
if ("<clinit>".equals(name)) {
|
||||||
|
return new MethodVisitor(Opcodes.ASM9, mv) {
|
||||||
|
@Override
|
||||||
|
public void visitFieldInsn(int opcode, String owner, String fieldName, String fieldDesc) {
|
||||||
|
if (opcode == Opcodes.PUTSTATIC && "CB_PKG_VERSION".equals(fieldName)) {
|
||||||
|
// Pop whatever was calculated and push "."
|
||||||
|
super.visitInsn(Opcodes.POP);
|
||||||
|
super.visitLdcInsn(".");
|
||||||
|
}
|
||||||
|
super.visitFieldInsn(opcode, owner, fieldName, fieldDesc);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return mv;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.accept(cv, ClassReader.EXPAND_FRAMES);
|
||||||
|
return writer.toByteArray();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
LOGGER.log(Level.FINE, "[BytecodeTransformer] Failed to transform class bytecode, returning original", t);
|
||||||
|
return classBytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,178 @@
|
|||||||
|
package org.patchbukkit.loader;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.util.HexFormat;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles downloading and caching official Mojang server bytecode at runtime.
|
||||||
|
* This guarantees 100% authentic NMS classes and static reflection compliance
|
||||||
|
* without violating Mojang's EULA by redistributing proprietary bytecode.
|
||||||
|
*/
|
||||||
|
public final class MojangServerProvider {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger("MojangServerProvider");
|
||||||
|
private static final String MOJANG_MANIFEST_URL =
|
||||||
|
"https://piston-meta.mojang.com/mc/game/version_manifest_v2.json";
|
||||||
|
|
||||||
|
private MojangServerProvider() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures the official Mojang server jar is available in the target cache directory.
|
||||||
|
*
|
||||||
|
* @param cacheDir the folder to store cached server jars (e.g., patchbukkit-libs)
|
||||||
|
* @param mcVersion the target Minecraft version (e.g. "26.2" or "1.21.4")
|
||||||
|
* @return the File pointing to the cached server jar, or null if download failed
|
||||||
|
*/
|
||||||
|
public static File getOrDownloadServerJar(File cacheDir, String mcVersion) {
|
||||||
|
if (!cacheDir.exists()) {
|
||||||
|
cacheDir.mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
String versionKey = (mcVersion != null && !mcVersion.isBlank()) ? mcVersion : "latest";
|
||||||
|
File targetJar = new File(cacheDir, "mojang-server-" + versionKey + ".jar");
|
||||||
|
|
||||||
|
if (targetJar.exists() && targetJar.length() > 0) {
|
||||||
|
return targetJar;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Development fallback: check if paperweight taskCache has mappedServerJar.jar
|
||||||
|
File devMappedJar = findDevMappedServerJar();
|
||||||
|
if (devMappedJar != null && devMappedJar.exists() && devMappedJar.length() > 0) {
|
||||||
|
LOGGER.info("[PatchBukkit] Found local development mappedServerJar: " + devMappedJar.getAbsolutePath());
|
||||||
|
try {
|
||||||
|
Files.copy(devMappedJar.toPath(), targetJar.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
return targetJar;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
LOGGER.log(Level.WARNING, "Failed to copy local dev jar, proceeding to download: " + t.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
LOGGER.info("[PatchBukkit] Downloading official Mojang server bytecode for Minecraft " + versionKey + "...");
|
||||||
|
return downloadVanillaServer(targetJar, versionKey);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
LOGGER.log(Level.SEVERE, "[PatchBukkit] Failed to obtain Mojang server jar: " + t.getMessage(), t);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static File findDevMappedServerJar() {
|
||||||
|
File[] possibleLocations = new File[] {
|
||||||
|
new File(".gradle/caches/paperweight/taskCache/mappedServerJar.jar"),
|
||||||
|
new File("java/patchbukkit/.gradle/caches/paperweight/taskCache/mappedServerJar.jar"),
|
||||||
|
new File("../java/patchbukkit/.gradle/caches/paperweight/taskCache/mappedServerJar.jar")
|
||||||
|
};
|
||||||
|
for (File loc : possibleLocations) {
|
||||||
|
if (loc.exists()) {
|
||||||
|
return loc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static File downloadVanillaServer(File targetFile, String version) throws Exception {
|
||||||
|
// 1. Fetch Mojang version manifest
|
||||||
|
HttpURLConnection conn = (HttpURLConnection) URI.create(MOJANG_MANIFEST_URL).toURL().openConnection();
|
||||||
|
conn.setRequestProperty("User-Agent", "PatchBukkit-ServerProvider");
|
||||||
|
conn.setConnectTimeout(10000);
|
||||||
|
conn.setReadTimeout(15000);
|
||||||
|
|
||||||
|
JsonObject manifest;
|
||||||
|
try (InputStream in = conn.getInputStream();
|
||||||
|
InputStreamReader reader = new InputStreamReader(in, StandardCharsets.UTF_8)) {
|
||||||
|
manifest = JsonParser.parseReader(reader).getAsJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
String targetUrl = null;
|
||||||
|
String latestRelease = manifest.getAsJsonObject("latest").get("release").getAsString();
|
||||||
|
|
||||||
|
JsonArray versions = manifest.getAsJsonArray("versions");
|
||||||
|
for (JsonElement elem : versions) {
|
||||||
|
JsonObject vObj = elem.getAsJsonObject();
|
||||||
|
String id = vObj.get("id").getAsString();
|
||||||
|
if (id.equalsIgnoreCase(version)) {
|
||||||
|
targetUrl = vObj.get("url").getAsString();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to latest release if exact version tag not found
|
||||||
|
if (targetUrl == null) {
|
||||||
|
LOGGER.warning("[PatchBukkit] Version " + version + " not found in manifest; falling back to latest release: " + latestRelease);
|
||||||
|
for (JsonElement elem : versions) {
|
||||||
|
JsonObject vObj = elem.getAsJsonObject();
|
||||||
|
if (vObj.get("id").getAsString().equalsIgnoreCase(latestRelease)) {
|
||||||
|
targetUrl = vObj.get("url").getAsString();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetUrl == null) {
|
||||||
|
throw new IllegalStateException("Could not resolve download metadata for Minecraft version: " + version);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Fetch version details
|
||||||
|
HttpURLConnection vConn = (HttpURLConnection) URI.create(targetUrl).toURL().openConnection();
|
||||||
|
vConn.setRequestProperty("User-Agent", "PatchBukkit-ServerProvider");
|
||||||
|
vConn.setConnectTimeout(10000);
|
||||||
|
vConn.setReadTimeout(15000);
|
||||||
|
|
||||||
|
JsonObject vDetails;
|
||||||
|
try (InputStream in = vConn.getInputStream();
|
||||||
|
InputStreamReader reader = new InputStreamReader(in, StandardCharsets.UTF_8)) {
|
||||||
|
vDetails = JsonParser.parseReader(reader).getAsJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonObject serverDownload = vDetails.getAsJsonObject("downloads").getAsJsonObject("server");
|
||||||
|
String serverJarUrl = serverDownload.get("url").getAsString();
|
||||||
|
String expectedSha1 = serverDownload.get("sha1").getAsString();
|
||||||
|
|
||||||
|
// 3. Download server.jar with SHA-1 verification
|
||||||
|
File tempFile = new File(targetFile.getParentFile(), targetFile.getName() + ".downloading");
|
||||||
|
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
||||||
|
|
||||||
|
HttpURLConnection downloadConn = (HttpURLConnection) URI.create(serverJarUrl).toURL().openConnection();
|
||||||
|
downloadConn.setRequestProperty("User-Agent", "PatchBukkit-ServerProvider");
|
||||||
|
downloadConn.setConnectTimeout(15000);
|
||||||
|
downloadConn.setReadTimeout(30000);
|
||||||
|
|
||||||
|
try (InputStream in = new BufferedInputStream(downloadConn.getInputStream());
|
||||||
|
FileOutputStream out = new FileOutputStream(tempFile)) {
|
||||||
|
byte[] buf = new byte[8192];
|
||||||
|
int read;
|
||||||
|
while ((read = in.read(buf)) != -1) {
|
||||||
|
out.write(buf, 0, read);
|
||||||
|
digest.update(buf, 0, read);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String actualSha1 = HexFormat.of().formatHex(digest.digest());
|
||||||
|
if (!actualSha1.equalsIgnoreCase(expectedSha1)) {
|
||||||
|
tempFile.delete();
|
||||||
|
throw new SecurityException("SHA-1 checksum mismatch for " + serverJarUrl + "! Expected: " + expectedSha1 + ", got: " + actualSha1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Files.move(tempFile.toPath(), targetFile.toPath(), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
|
||||||
|
LOGGER.info("[PatchBukkit] Successfully downloaded and verified official Mojang server bytecode (" + targetFile.length() / (1024 * 1024) + " MB).");
|
||||||
|
return targetFile;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -23,6 +23,7 @@ public class PatchBukkitBootstrap {
|
|||||||
|
|
||||||
public static boolean bootstrapPlugins(String pluginsDirPath) {
|
public static boolean bootstrapPlugins(String pluginsDirPath) {
|
||||||
try {
|
try {
|
||||||
|
org.patchbukkit.PatchBukkitServer.initServer();
|
||||||
File pluginsDir = new File(pluginsDirPath);
|
File pluginsDir = new File(pluginsDirPath);
|
||||||
if (!pluginsDir.exists() || !pluginsDir.isDirectory()) {
|
if (!pluginsDir.exists() || !pluginsDir.isDirectory()) {
|
||||||
LOGGER.info("[PatchBukkit] Plugins directory does not exist: " + pluginsDirPath);
|
LOGGER.info("[PatchBukkit] Plugins directory does not exist: " + pluginsDirPath);
|
||||||
@ -208,6 +209,7 @@ public class PatchBukkitBootstrap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
org.patchbukkit.PatchBukkitServer.ensureCraftRegistry();
|
||||||
plugin.onLoad();
|
plugin.onLoad();
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
LOGGER.log(Level.SEVERE, "[PatchBukkit] Error during onLoad() for " + holder.name, t);
|
LOGGER.log(Level.SEVERE, "[PatchBukkit] Error during onLoad() for " + holder.name, t);
|
||||||
|
|||||||
@ -0,0 +1,128 @@
|
|||||||
|
package org.patchbukkit.log;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.Level;
|
||||||
|
import org.apache.logging.log4j.core.Core;
|
||||||
|
import org.apache.logging.log4j.core.Filter;
|
||||||
|
import org.apache.logging.log4j.core.Layout;
|
||||||
|
import org.apache.logging.log4j.core.LogEvent;
|
||||||
|
import org.apache.logging.log4j.core.appender.AbstractAppender;
|
||||||
|
import org.apache.logging.log4j.core.config.Property;
|
||||||
|
import org.apache.logging.log4j.core.config.plugins.Plugin;
|
||||||
|
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
|
||||||
|
import org.apache.logging.log4j.core.config.plugins.PluginElement;
|
||||||
|
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
|
||||||
|
import org.patchbukkit.PatchBukkitServer;
|
||||||
|
import patchbukkit.bridge.NativeBridgeFfi;
|
||||||
|
import patchbukkit.log.LogLevel;
|
||||||
|
import patchbukkit.log.SendLogRequest;
|
||||||
|
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
|
||||||
|
@Plugin(
|
||||||
|
name = "NativeBridge",
|
||||||
|
category = Core.CATEGORY_NAME,
|
||||||
|
elementType = org.apache.logging.log4j.core.Appender.ELEMENT_TYPE,
|
||||||
|
printObject = true
|
||||||
|
)
|
||||||
|
public class NativeBridgeLog4jAppender extends AbstractAppender {
|
||||||
|
|
||||||
|
protected NativeBridgeLog4jAppender(
|
||||||
|
String name,
|
||||||
|
Filter filter,
|
||||||
|
Layout<? extends Serializable> layout,
|
||||||
|
boolean ignoreExceptions,
|
||||||
|
Property[] properties
|
||||||
|
) {
|
||||||
|
super(name, filter, layout, ignoreExceptions, properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PluginFactory
|
||||||
|
public static NativeBridgeLog4jAppender createAppender(
|
||||||
|
@PluginAttribute("name") String name,
|
||||||
|
@PluginElement("Filter") Filter filter,
|
||||||
|
@PluginElement("Layout") Layout<? extends Serializable> layout
|
||||||
|
) {
|
||||||
|
if (name == null) {
|
||||||
|
name = "NativeBridge";
|
||||||
|
}
|
||||||
|
return new NativeBridgeLog4jAppender(
|
||||||
|
name,
|
||||||
|
filter,
|
||||||
|
layout,
|
||||||
|
true,
|
||||||
|
Property.EMPTY_ARRAY
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NativeBridgeLog4jAppender create(String name) {
|
||||||
|
return createAppender(name, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void append(LogEvent event) {
|
||||||
|
if (event == null) return;
|
||||||
|
if (PatchBukkitServer.isLogging()) return;
|
||||||
|
PatchBukkitServer.setLogging(true);
|
||||||
|
try {
|
||||||
|
String loggerName = event.getLoggerName();
|
||||||
|
if (loggerName == null) {
|
||||||
|
loggerName = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter internal JDK / system loggers if below WARNING
|
||||||
|
if (loggerName.startsWith("jdk.") || loggerName.startsWith("sun.") ||
|
||||||
|
loggerName.startsWith("java.") || loggerName.startsWith("javax.")) {
|
||||||
|
if (event.getLevel().intLevel() > Level.WARN.intLevel()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LogLevel logLevel = toLogLevel(event.getLevel());
|
||||||
|
String message = event.getMessage() != null ? event.getMessage().getFormattedMessage() : "";
|
||||||
|
Throwable thrown = event.getThrown();
|
||||||
|
if (thrown != null) {
|
||||||
|
StringWriter sw = new StringWriter();
|
||||||
|
PrintWriter pw = new PrintWriter(sw);
|
||||||
|
if (!message.isEmpty()) {
|
||||||
|
pw.println(message);
|
||||||
|
}
|
||||||
|
thrown.printStackTrace(pw);
|
||||||
|
pw.flush();
|
||||||
|
message = sw.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
NativeBridgeFfi.sendLog(
|
||||||
|
SendLogRequest.newBuilder()
|
||||||
|
.setLevel(logLevel)
|
||||||
|
.setMessage(message)
|
||||||
|
.setLoggerName(loggerName)
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
PatchBukkitServer.ORIGINAL_ERR.println("[" + event.getLevel() + "][" + loggerName + "] " + message);
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
PatchBukkitServer.ORIGINAL_ERR.println("[NativeBridgeLog4jAppender Error] Failed to publish record: " + t.getMessage());
|
||||||
|
} finally {
|
||||||
|
PatchBukkitServer.setLogging(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LogLevel toLogLevel(Level level) {
|
||||||
|
if (level == null) return LogLevel.INFO;
|
||||||
|
if (level.isMoreSpecificThan(Level.ERROR)) {
|
||||||
|
return LogLevel.SEVERE;
|
||||||
|
} else if (level.isMoreSpecificThan(Level.WARN)) {
|
||||||
|
return LogLevel.WARNING;
|
||||||
|
} else if (level.isMoreSpecificThan(Level.INFO)) {
|
||||||
|
return LogLevel.INFO;
|
||||||
|
} else if (level.isMoreSpecificThan(Level.DEBUG)) {
|
||||||
|
return LogLevel.CONFIG;
|
||||||
|
} else {
|
||||||
|
return LogLevel.FINE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,302 @@
|
|||||||
|
package org.patchbukkit.network;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.channel.ChannelDuplexHandler;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||||
|
import io.netty.channel.ChannelOutboundHandlerAdapter;
|
||||||
|
import io.netty.channel.ChannelPromise;
|
||||||
|
import io.netty.channel.embedded.EmbeddedChannel;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A virtual Netty channel representing a player's network pipeline.
|
||||||
|
* Fully emulates the vanilla Minecraft Netty pipeline structure, including
|
||||||
|
* timeout, legacy query, splitter, decrypt, decompress, decoder, prepender,
|
||||||
|
* encrypt, compress, encoder, bundle_packer, and packet_handler.
|
||||||
|
*/
|
||||||
|
public class PatchBukkitVirtualChannel extends EmbeddedChannel {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger("PatchBukkitVirtualChannel");
|
||||||
|
|
||||||
|
private final UUID playerUuid;
|
||||||
|
private final SocketAddress localAddress;
|
||||||
|
private final SocketAddress remoteAddress;
|
||||||
|
private int compressionThreshold = 256;
|
||||||
|
private boolean encrypted = false;
|
||||||
|
|
||||||
|
public PatchBukkitVirtualChannel(UUID playerUuid) {
|
||||||
|
super();
|
||||||
|
this.playerUuid = playerUuid;
|
||||||
|
this.localAddress = new InetSocketAddress("127.0.0.1", 25565);
|
||||||
|
this.remoteAddress = new InetSocketAddress("127.0.0.1", 50000 + (Math.abs(playerUuid.hashCode()) % 10000));
|
||||||
|
|
||||||
|
setupPipeline();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupPipeline() {
|
||||||
|
// Full Vanilla Minecraft pipeline structure:
|
||||||
|
// Inbound: timeout -> legacy_query -> splitter -> [decrypt] -> [decompress] -> decoder -> packet_handler
|
||||||
|
// Outbound: timeout -> prepender -> [encrypt] -> [compress] -> encoder -> [bundle_packer] -> packet_handler
|
||||||
|
|
||||||
|
// 1. "timeout" (ReadTimeoutHandler / WriteTimeoutHandler emulation)
|
||||||
|
pipeline().addLast("timeout", new ChannelDuplexHandler());
|
||||||
|
|
||||||
|
// 2. "legacy_query" (handles legacy 1.6- server ping queries)
|
||||||
|
pipeline().addLast("legacy_query", new ChannelInboundHandlerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||||
|
ctx.fireChannelRead(msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. "splitter" (Varint21FrameDecoder)
|
||||||
|
pipeline().addLast("splitter", new ChannelInboundHandlerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||||
|
ctx.fireChannelRead(msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. "decompress" (CompressionDecoder - enabled by default for play phase, threshold 256)
|
||||||
|
pipeline().addLast("decompress", new VirtualCompressionDecoder(compressionThreshold, false));
|
||||||
|
|
||||||
|
// 5. "decoder" (PacketDecoder: ByteBuf -> Packet)
|
||||||
|
pipeline().addLast("decoder", new ChannelInboundHandlerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||||
|
ctx.fireChannelRead(msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 6. "prepender" (Varint21LengthFieldPrepender)
|
||||||
|
pipeline().addLast("prepender", new ChannelOutboundHandlerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
||||||
|
ctx.write(msg, promise);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 7. "compress" (CompressionEncoder - enabled by default for play phase, threshold 256)
|
||||||
|
pipeline().addLast("compress", new VirtualCompressionEncoder(compressionThreshold));
|
||||||
|
|
||||||
|
// 8. "encoder" (PacketEncoder: Packet -> ByteBuf)
|
||||||
|
pipeline().addLast("encoder", new ChannelOutboundHandlerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
||||||
|
ctx.write(msg, promise);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 9. "bundle_packer" (PacketBundlePacker - 1.20+)
|
||||||
|
pipeline().addLast("bundle_packer", new ChannelOutboundHandlerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
||||||
|
ctx.write(msg, promise);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 10. "packet_handler" (Connection / ServerGamePacketListenerImpl terminal handler)
|
||||||
|
pipeline().addLast("packet_handler", new ChannelInboundHandlerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||||
|
// Packet reached the end of inbound pipeline
|
||||||
|
if (msg instanceof ByteBuf buf) {
|
||||||
|
buf.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||||
|
LOGGER.fine("[PatchBukkitVirtualChannel] Exception in pipeline for " + playerUuid + ": " + cause.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates or toggles compression handlers exactly like Vanilla Minecraft Connection.setupCompression().
|
||||||
|
*
|
||||||
|
* @param threshold the compression threshold, or -1 to disable
|
||||||
|
* @param validateDecompressed whether to validate decompressed size
|
||||||
|
*/
|
||||||
|
public void setupCompression(int threshold, boolean validateDecompressed) {
|
||||||
|
this.compressionThreshold = threshold;
|
||||||
|
if (threshold >= 0) {
|
||||||
|
var decompress = pipeline().get("decompress");
|
||||||
|
if (decompress instanceof VirtualCompressionDecoder cd) {
|
||||||
|
cd.setThreshold(threshold, validateDecompressed);
|
||||||
|
} else {
|
||||||
|
if (pipeline().get("decoder") != null) {
|
||||||
|
pipeline().addBefore("decoder", "decompress", new VirtualCompressionDecoder(threshold, validateDecompressed));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var compress = pipeline().get("compress");
|
||||||
|
if (compress instanceof VirtualCompressionEncoder ce) {
|
||||||
|
ce.setThreshold(threshold);
|
||||||
|
} else {
|
||||||
|
if (pipeline().get("encoder") != null) {
|
||||||
|
pipeline().addBefore("encoder", "compress", new VirtualCompressionEncoder(threshold));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (pipeline().get("decompress") != null) {
|
||||||
|
pipeline().remove("decompress");
|
||||||
|
}
|
||||||
|
if (pipeline().get("compress") != null) {
|
||||||
|
pipeline().remove("compress");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setupCompression(int threshold) {
|
||||||
|
setupCompression(threshold, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCompressionThreshold(int threshold) {
|
||||||
|
setupCompression(threshold, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCompressionThreshold() {
|
||||||
|
return compressionThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCompressed() {
|
||||||
|
return compressionThreshold >= 0 && pipeline().get("decompress") != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables or updates encryption handlers in the pipeline matching Vanilla Connection.setEncryptionKey().
|
||||||
|
*/
|
||||||
|
public void setEncryptionKey(Object decryptCipher, Object encryptCipher) {
|
||||||
|
this.encrypted = true;
|
||||||
|
if (pipeline().get("decrypt") == null && pipeline().get("splitter") != null) {
|
||||||
|
pipeline().addBefore("splitter", "decrypt", new VirtualCipherDecoder(decryptCipher));
|
||||||
|
}
|
||||||
|
if (pipeline().get("encrypt") == null && pipeline().get("prepender") != null) {
|
||||||
|
pipeline().addBefore("prepender", "encrypt", new VirtualCipherEncoder(encryptCipher));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEncrypted() {
|
||||||
|
return encrypted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getPlayerUuid() {
|
||||||
|
return playerUuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SocketAddress localAddress() {
|
||||||
|
return localAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SocketAddress remoteAddress() {
|
||||||
|
return remoteAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isActive() {
|
||||||
|
return isOpen();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emulates Vanilla net.minecraft.network.CompressionDecoder.
|
||||||
|
*/
|
||||||
|
public static class VirtualCompressionDecoder extends ChannelInboundHandlerAdapter {
|
||||||
|
public int threshold;
|
||||||
|
public boolean validateDecompressed;
|
||||||
|
|
||||||
|
public VirtualCompressionDecoder(int threshold, boolean validateDecompressed) {
|
||||||
|
this.threshold = threshold;
|
||||||
|
this.validateDecompressed = validateDecompressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getThreshold() {
|
||||||
|
return threshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setThreshold(int threshold) {
|
||||||
|
this.threshold = threshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setThreshold(int threshold, boolean validateDecompressed) {
|
||||||
|
this.threshold = threshold;
|
||||||
|
this.validateDecompressed = validateDecompressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int threshold() {
|
||||||
|
return threshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||||
|
ctx.fireChannelRead(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emulates Vanilla net.minecraft.network.CompressionEncoder.
|
||||||
|
*/
|
||||||
|
public static class VirtualCompressionEncoder extends ChannelOutboundHandlerAdapter {
|
||||||
|
public int threshold;
|
||||||
|
|
||||||
|
public VirtualCompressionEncoder(int threshold) {
|
||||||
|
this.threshold = threshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getThreshold() {
|
||||||
|
return threshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setThreshold(int threshold) {
|
||||||
|
this.threshold = threshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int threshold() {
|
||||||
|
return threshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
||||||
|
ctx.write(msg, promise);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emulates Vanilla net.minecraft.network.CipherDecoder.
|
||||||
|
*/
|
||||||
|
public static class VirtualCipherDecoder extends ChannelInboundHandlerAdapter {
|
||||||
|
public final Object cipher;
|
||||||
|
|
||||||
|
public VirtualCipherDecoder(Object cipher) {
|
||||||
|
this.cipher = cipher;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||||
|
ctx.fireChannelRead(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emulates Vanilla net.minecraft.network.CipherEncoder.
|
||||||
|
*/
|
||||||
|
public static class VirtualCipherEncoder extends ChannelOutboundHandlerAdapter {
|
||||||
|
public final Object cipher;
|
||||||
|
|
||||||
|
public VirtualCipherEncoder(Object cipher) {
|
||||||
|
this.cipher = cipher;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
||||||
|
ctx.write(msg, promise);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,264 @@
|
|||||||
|
package org.patchbukkit.network;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
import org.patchbukkit.entity.PatchBukkitPlayer;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages the lifecycle and I/O of virtual Netty channels for connected players.
|
||||||
|
*/
|
||||||
|
public final class VirtualChannelManager {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger("VirtualChannelManager");
|
||||||
|
private static final VirtualChannelManager INSTANCE = new VirtualChannelManager();
|
||||||
|
|
||||||
|
private final Map<UUID, PatchBukkitVirtualChannel> channels = new ConcurrentHashMap<>();
|
||||||
|
private final Map<UUID, VirtualPlayerHandle> playerHandles = new ConcurrentHashMap<>();
|
||||||
|
private final Map<UUID, net.minecraft.server.level.ServerPlayer> serverPlayers = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private VirtualChannelManager() {}
|
||||||
|
|
||||||
|
public static VirtualChannelManager getInstance() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PatchBukkitVirtualChannel getOrCreateChannel(UUID uuid) {
|
||||||
|
if (uuid == null) return null;
|
||||||
|
return channels.computeIfAbsent(uuid, PatchBukkitVirtualChannel::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
public VirtualPlayerHandle getPlayerHandle(PatchBukkitPlayer player) {
|
||||||
|
if (player == null) return null;
|
||||||
|
return playerHandles.computeIfAbsent(player.getUniqueId(), uuid -> {
|
||||||
|
PatchBukkitVirtualChannel channel = getOrCreateChannel(uuid);
|
||||||
|
VirtualNetworkManager netManager = new VirtualNetworkManager(channel);
|
||||||
|
VirtualPlayerConnection conn = new VirtualPlayerConnection(netManager);
|
||||||
|
return new VirtualPlayerHandle(player, conn);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized net.minecraft.server.level.ServerPlayer getOrCreateServerPlayer(PatchBukkitPlayer player) {
|
||||||
|
if (player == null) return null;
|
||||||
|
UUID uuid = player.getUniqueId();
|
||||||
|
net.minecraft.server.level.ServerPlayer existing = serverPlayers.get(uuid);
|
||||||
|
if (existing != null) return existing;
|
||||||
|
|
||||||
|
try {
|
||||||
|
java.lang.reflect.Field unsafeField = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
|
||||||
|
unsafeField.setAccessible(true);
|
||||||
|
sun.misc.Unsafe unsafe = (sun.misc.Unsafe) unsafeField.get(null);
|
||||||
|
|
||||||
|
PatchBukkitVirtualChannel channel = getOrCreateChannel(uuid);
|
||||||
|
|
||||||
|
net.minecraft.network.Connection connection =
|
||||||
|
(net.minecraft.network.Connection) unsafe.allocateInstance(net.minecraft.network.Connection.class);
|
||||||
|
connection.channel = channel;
|
||||||
|
connection.address = player.getAddress() != null ? player.getAddress() : new java.net.InetSocketAddress("127.0.0.1", 54321);
|
||||||
|
|
||||||
|
for (java.lang.reflect.Field cf : net.minecraft.network.Connection.class.getDeclaredFields()) {
|
||||||
|
if (cf.getType().equals(net.minecraft.network.protocol.PacketFlow.class)) {
|
||||||
|
cf.setAccessible(true);
|
||||||
|
cf.set(connection, net.minecraft.network.protocol.PacketFlow.SERVERBOUND);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
net.minecraft.server.level.ServerPlayer serverPlayer =
|
||||||
|
(net.minecraft.server.level.ServerPlayer) unsafe.allocateInstance(net.minecraft.server.level.ServerPlayer.class);
|
||||||
|
serverPlayer.setId(player.getEntityId());
|
||||||
|
|
||||||
|
java.lang.reflect.Field entUuidField = net.minecraft.world.entity.Entity.class.getDeclaredField("uuid");
|
||||||
|
entUuidField.setAccessible(true);
|
||||||
|
entUuidField.set(serverPlayer, uuid);
|
||||||
|
|
||||||
|
java.lang.reflect.Field entStringUuidField = net.minecraft.world.entity.Entity.class.getDeclaredField("stringUUID");
|
||||||
|
entStringUuidField.setAccessible(true);
|
||||||
|
entStringUuidField.set(serverPlayer, uuid.toString());
|
||||||
|
|
||||||
|
com.mojang.authlib.GameProfile profile =
|
||||||
|
new com.mojang.authlib.GameProfile(uuid, player.getName() != null ? player.getName() : "Player");
|
||||||
|
java.lang.reflect.Field profileField = null;
|
||||||
|
Class<?> pClass = net.minecraft.server.level.ServerPlayer.class;
|
||||||
|
while (pClass != null && profileField == null) {
|
||||||
|
for (java.lang.reflect.Field pf : pClass.getDeclaredFields()) {
|
||||||
|
if (pf.getType().equals(com.mojang.authlib.GameProfile.class)) {
|
||||||
|
profileField = pf;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pClass = pClass.getSuperclass();
|
||||||
|
}
|
||||||
|
if (profileField != null) {
|
||||||
|
profileField.setAccessible(true);
|
||||||
|
profileField.set(serverPlayer, profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
net.minecraft.server.network.ServerGamePacketListenerImpl listener =
|
||||||
|
(net.minecraft.server.network.ServerGamePacketListenerImpl) unsafe.allocateInstance(net.minecraft.server.network.ServerGamePacketListenerImpl.class);
|
||||||
|
listener.player = serverPlayer;
|
||||||
|
|
||||||
|
Class<?> scClass = net.minecraft.server.network.ServerCommonPacketListenerImpl.class;
|
||||||
|
java.lang.reflect.Field serverField = scClass.getDeclaredField("server");
|
||||||
|
serverField.setAccessible(true);
|
||||||
|
serverField.set(listener, org.patchbukkit.PatchBukkitServer.getDedicatedServer());
|
||||||
|
|
||||||
|
java.lang.reflect.Field connField = scClass.getDeclaredField("connection");
|
||||||
|
connField.setAccessible(true);
|
||||||
|
connField.set(listener, connection);
|
||||||
|
|
||||||
|
serverPlayer.connection = listener;
|
||||||
|
|
||||||
|
try {
|
||||||
|
org.bukkit.craftbukkit.entity.CraftPlayer craftPlayer =
|
||||||
|
(org.bukkit.craftbukkit.entity.CraftPlayer) unsafe.allocateInstance(org.bukkit.craftbukkit.entity.CraftPlayer.class);
|
||||||
|
java.lang.reflect.Field entityField = null;
|
||||||
|
Class<?> cpClass = org.bukkit.craftbukkit.entity.CraftPlayer.class;
|
||||||
|
while (cpClass != null && entityField == null) {
|
||||||
|
for (java.lang.reflect.Field ef : cpClass.getDeclaredFields()) {
|
||||||
|
if (ef.getName().equals("entity")) {
|
||||||
|
entityField = ef;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cpClass = cpClass.getSuperclass();
|
||||||
|
}
|
||||||
|
if (entityField != null) {
|
||||||
|
entityField.setAccessible(true);
|
||||||
|
entityField.set(craftPlayer, serverPlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
java.lang.reflect.Field bukkitEntityField = net.minecraft.world.entity.Entity.class.getDeclaredField("bukkitEntity");
|
||||||
|
bukkitEntityField.setAccessible(true);
|
||||||
|
bukkitEntityField.set(serverPlayer, craftPlayer);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
LOGGER.log(Level.FINE, "[VirtualChannelManager] Failed to link CraftPlayer", t);
|
||||||
|
}
|
||||||
|
|
||||||
|
net.minecraft.server.network.ServerConnectionListener scl = org.patchbukkit.PatchBukkitServer.getServerConnection();
|
||||||
|
if (scl != null && scl.getConnections() != null) {
|
||||||
|
scl.getConnections().add(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
net.minecraft.server.dedicated.DedicatedPlayerList pl = org.patchbukkit.PatchBukkitServer.getDedicatedPlayerList();
|
||||||
|
if (pl != null) {
|
||||||
|
if (pl.getPlayers() != null) {
|
||||||
|
pl.getPlayers().add(serverPlayer);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
java.lang.reflect.Field byUuidField = null;
|
||||||
|
Class<?> curPl = pl.getClass();
|
||||||
|
while (curPl != null && byUuidField == null) {
|
||||||
|
for (java.lang.reflect.Field f : curPl.getDeclaredFields()) {
|
||||||
|
if (f.getName().equals("playersByUUID")) {
|
||||||
|
byUuidField = f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
curPl = curPl.getSuperclass();
|
||||||
|
}
|
||||||
|
if (byUuidField != null) {
|
||||||
|
byUuidField.setAccessible(true);
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<UUID, net.minecraft.server.level.ServerPlayer> map =
|
||||||
|
(Map<UUID, net.minecraft.server.level.ServerPlayer>) byUuidField.get(pl);
|
||||||
|
if (map != null) {
|
||||||
|
map.put(uuid, serverPlayer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
serverPlayers.put(uuid, serverPlayer);
|
||||||
|
return serverPlayer;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
LOGGER.log(Level.WARNING, "[VirtualChannelManager] Failed to allocate ServerPlayer for " + uuid, t);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removePlayer(UUID uuid) {
|
||||||
|
if (uuid == null) return;
|
||||||
|
PatchBukkitVirtualChannel channel = channels.remove(uuid);
|
||||||
|
playerHandles.remove(uuid);
|
||||||
|
net.minecraft.server.level.ServerPlayer sp = serverPlayers.remove(uuid);
|
||||||
|
|
||||||
|
if (sp != null) {
|
||||||
|
if (sp.connection != null && sp.connection.connection != null) {
|
||||||
|
net.minecraft.server.network.ServerConnectionListener scl = org.patchbukkit.PatchBukkitServer.getServerConnection();
|
||||||
|
if (scl != null && scl.getConnections() != null) {
|
||||||
|
scl.getConnections().remove(sp.connection.connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
net.minecraft.server.dedicated.DedicatedPlayerList pl = org.patchbukkit.PatchBukkitServer.getDedicatedPlayerList();
|
||||||
|
if (pl != null) {
|
||||||
|
if (pl.getPlayers() != null) {
|
||||||
|
pl.getPlayers().removeIf(p -> {
|
||||||
|
try {
|
||||||
|
return sp == p || uuid.equals(p.getUUID());
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
java.lang.reflect.Field byUuidField = null;
|
||||||
|
Class<?> curPl = pl.getClass();
|
||||||
|
while (curPl != null && byUuidField == null) {
|
||||||
|
for (java.lang.reflect.Field f : curPl.getDeclaredFields()) {
|
||||||
|
if (f.getName().equals("playersByUUID")) {
|
||||||
|
byUuidField = f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
curPl = curPl.getSuperclass();
|
||||||
|
}
|
||||||
|
if (byUuidField != null) {
|
||||||
|
byUuidField.setAccessible(true);
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<UUID, net.minecraft.server.level.ServerPlayer> map =
|
||||||
|
(Map<UUID, net.minecraft.server.level.ServerPlayer>) byUuidField.get(pl);
|
||||||
|
if (map != null) {
|
||||||
|
map.remove(uuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (channel != null && channel.isOpen()) {
|
||||||
|
channel.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handlePacketReceived(UUID uuid, int packetId, byte[] payload) {
|
||||||
|
if (uuid == null || payload == null) return;
|
||||||
|
PatchBukkitVirtualChannel channel = getOrCreateChannel(uuid);
|
||||||
|
if (channel == null || !channel.isActive()) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
ByteBuf buffer = Unpooled.wrappedBuffer(payload);
|
||||||
|
channel.pipeline().fireChannelRead(buffer);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
LOGGER.log(Level.FINE, "[VirtualChannelManager] Error in inbound packet processing for " + uuid, t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handlePacketSent(UUID uuid, int packetId, byte[] payload) {
|
||||||
|
if (uuid == null || payload == null) return;
|
||||||
|
PatchBukkitVirtualChannel channel = getOrCreateChannel(uuid);
|
||||||
|
if (channel == null || !channel.isActive()) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
ByteBuf buffer = Unpooled.wrappedBuffer(payload);
|
||||||
|
channel.write(buffer);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
LOGGER.log(Level.FINE, "[VirtualChannelManager] Error in outbound packet processing for " + uuid, t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,106 @@
|
|||||||
|
package org.patchbukkit.network;
|
||||||
|
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emulates the NMS net.minecraft.network.Connection / NetworkManager object
|
||||||
|
* which protocol injection frameworks and anticheats reflect on.
|
||||||
|
*/
|
||||||
|
public class VirtualNetworkManager {
|
||||||
|
|
||||||
|
public final Channel channel;
|
||||||
|
|
||||||
|
public VirtualNetworkManager(Channel channel) {
|
||||||
|
this.channel = channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Channel getChannel() {
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SocketAddress getRemoteAddress() {
|
||||||
|
return channel != null ? channel.remoteAddress() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SocketAddress getLocalAddress() {
|
||||||
|
return channel != null ? channel.localAddress() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConnected() {
|
||||||
|
return channel != null && channel.isActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMemoryConnection() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEncrypted() {
|
||||||
|
return channel instanceof PatchBukkitVirtualChannel v && v.isEncrypted();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCompressed() {
|
||||||
|
return channel instanceof PatchBukkitVirtualChannel v && v.isCompressed();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCompressionThreshold() {
|
||||||
|
return channel instanceof PatchBukkitVirtualChannel v ? v.getCompressionThreshold() : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setupCompression(int threshold, boolean validateDecompressed) {
|
||||||
|
if (channel instanceof PatchBukkitVirtualChannel v) {
|
||||||
|
v.setupCompression(threshold, validateDecompressed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setupCompression(int threshold) {
|
||||||
|
setupCompression(threshold, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCompressionThreshold(int threshold) {
|
||||||
|
setupCompression(threshold, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEncryptionKey(Object decryptCipher, Object encryptCipher) {
|
||||||
|
if (channel instanceof PatchBukkitVirtualChannel v) {
|
||||||
|
v.setEncryptionKey(decryptCipher, encryptCipher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send(Object packet) {
|
||||||
|
if (channel != null && channel.isActive()) {
|
||||||
|
channel.writeAndFlush(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send(Object packet, Object listener) {
|
||||||
|
send(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendPacket(Object packet) {
|
||||||
|
send(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disconnect(Object reason) {
|
||||||
|
if (channel != null && channel.isOpen()) {
|
||||||
|
channel.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleDisconnection() {
|
||||||
|
disconnect(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReadOnly() {
|
||||||
|
// No-op for read-only pipeline state
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getAverageReceivedPackets() {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getAverageSentPackets() {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,83 @@
|
|||||||
|
package org.patchbukkit.network;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Acts as the NMS ServerGamePacketListenerImpl / PlayerConnection object
|
||||||
|
* which protocol injection frameworks inspect to find the network connection.
|
||||||
|
*/
|
||||||
|
public class VirtualPlayerConnection implements io.papermc.paper.connection.PlayerConnection {
|
||||||
|
|
||||||
|
// Mojang field name: connection
|
||||||
|
public final VirtualNetworkManager connection;
|
||||||
|
|
||||||
|
// Spigot / legacy field name: networkManager
|
||||||
|
public final VirtualNetworkManager networkManager;
|
||||||
|
|
||||||
|
// Direct channel alias for reflection libraries looking for Channel field
|
||||||
|
public final io.netty.channel.Channel channel;
|
||||||
|
|
||||||
|
public VirtualPlayerConnection(VirtualNetworkManager networkManager) {
|
||||||
|
this.connection = networkManager;
|
||||||
|
this.networkManager = networkManager;
|
||||||
|
this.channel = networkManager != null ? networkManager.channel : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VirtualNetworkManager getConnection() {
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VirtualNetworkManager getNetworkManager() {
|
||||||
|
return networkManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public io.netty.channel.Channel getChannel() {
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send(Object packet) {
|
||||||
|
if (connection != null) {
|
||||||
|
connection.send(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendPacket(Object packet) {
|
||||||
|
send(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disconnect(net.kyori.adventure.text.Component reason) {
|
||||||
|
if (connection != null && connection.channel != null) {
|
||||||
|
connection.channel.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConnected() {
|
||||||
|
return connection != null && connection.channel != null && connection.channel.isActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTransferred() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public java.net.SocketAddress getAddress() {
|
||||||
|
return connection != null && connection.channel != null ? connection.channel.remoteAddress() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public java.net.InetSocketAddress getClientAddress() {
|
||||||
|
java.net.SocketAddress addr = getAddress();
|
||||||
|
return addr instanceof java.net.InetSocketAddress ? (java.net.InetSocketAddress) addr : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public java.net.InetSocketAddress getVirtualHost() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public java.net.InetSocketAddress getHAProxyAddress() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
package org.patchbukkit.network;
|
||||||
|
|
||||||
|
import org.patchbukkit.entity.PatchBukkitPlayer;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Acts as the NMS ServerPlayer / EntityPlayer handle returned by Player.getHandle().
|
||||||
|
*/
|
||||||
|
public class VirtualPlayerHandle {
|
||||||
|
|
||||||
|
public final PatchBukkitPlayer player;
|
||||||
|
public final UUID uuid;
|
||||||
|
public final String name;
|
||||||
|
|
||||||
|
// Mojang field name: connection
|
||||||
|
public final VirtualPlayerConnection connection;
|
||||||
|
|
||||||
|
// Spigot / legacy field name: playerConnection
|
||||||
|
public final VirtualPlayerConnection playerConnection;
|
||||||
|
|
||||||
|
public VirtualPlayerHandle(PatchBukkitPlayer player, VirtualPlayerConnection connection) {
|
||||||
|
this.player = player;
|
||||||
|
this.uuid = player.getUniqueId();
|
||||||
|
this.name = player.getName();
|
||||||
|
this.connection = connection;
|
||||||
|
this.playerConnection = connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PatchBukkitPlayer getBukkitEntity() {
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getUUID() {
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VirtualPlayerConnection getConnection() {
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,227 @@
|
|||||||
|
package org.patchbukkit.registry;
|
||||||
|
|
||||||
|
import io.papermc.paper.util.Holderable;
|
||||||
|
import net.kyori.adventure.key.Key;
|
||||||
|
import org.bukkit.NamespacedKey;
|
||||||
|
import org.bukkit.craftbukkit.damage.CraftDamageEffect;
|
||||||
|
import org.bukkit.craftbukkit.damage.CraftDamageType;
|
||||||
|
import org.bukkit.craftbukkit.util.CraftNamespacedKey;
|
||||||
|
import org.bukkit.damage.DamageEffect;
|
||||||
|
import org.bukkit.damage.DamageScaling;
|
||||||
|
import org.bukkit.damage.DamageType;
|
||||||
|
import org.bukkit.damage.DeathMessageType;
|
||||||
|
import org.jspecify.annotations.NonNull;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class PatchBukkitDamageType implements DamageType, Holderable<net.minecraft.world.damagesource.DamageType> {
|
||||||
|
|
||||||
|
private static final Field HOLDER_REF_VALUE_FIELD;
|
||||||
|
|
||||||
|
static {
|
||||||
|
Field field = null;
|
||||||
|
try {
|
||||||
|
field = net.minecraft.core.Holder.Reference.class.getDeclaredField("value");
|
||||||
|
field.setAccessible(true);
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
HOLDER_REF_VALUE_FIELD = field;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final NamespacedKey namespacedKey;
|
||||||
|
private final Key adventureKey;
|
||||||
|
private final String translationKey;
|
||||||
|
private final DamageScaling damageScaling;
|
||||||
|
private final DamageEffect damageEffect;
|
||||||
|
private final DeathMessageType deathMessageType;
|
||||||
|
private final float exhaustion;
|
||||||
|
private final net.minecraft.world.damagesource.DamageType nmsHandle;
|
||||||
|
private final net.minecraft.core.Holder<net.minecraft.world.damagesource.DamageType> holder;
|
||||||
|
|
||||||
|
public PatchBukkitDamageType(@NonNull NamespacedKey key) {
|
||||||
|
this(
|
||||||
|
key,
|
||||||
|
"death.attack." + (key != null ? key.getKey() : "generic"),
|
||||||
|
determineScaling(key),
|
||||||
|
determineEffect(key),
|
||||||
|
determineDeathMessageType(key),
|
||||||
|
determineExhaustion(key)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PatchBukkitDamageType(
|
||||||
|
@NonNull NamespacedKey namespacedKey,
|
||||||
|
@Nullable String translationKey,
|
||||||
|
@Nullable DamageScaling damageScaling,
|
||||||
|
@Nullable DamageEffect damageEffect,
|
||||||
|
@Nullable DeathMessageType deathMessageType,
|
||||||
|
float exhaustion
|
||||||
|
) {
|
||||||
|
if (namespacedKey == null) {
|
||||||
|
namespacedKey = NamespacedKey.minecraft("generic");
|
||||||
|
}
|
||||||
|
this.namespacedKey = namespacedKey;
|
||||||
|
this.adventureKey = Key.key(namespacedKey.namespace(), namespacedKey.value());
|
||||||
|
this.translationKey = translationKey != null ? translationKey : ("death.attack." + namespacedKey.getKey());
|
||||||
|
this.damageScaling = damageScaling != null ? damageScaling : DamageScaling.WHEN_CAUSED_BY_LIVING_NON_PLAYER;
|
||||||
|
this.damageEffect = damageEffect != null ? damageEffect : getFallbackEffect();
|
||||||
|
this.deathMessageType = deathMessageType != null ? deathMessageType : DeathMessageType.DEFAULT;
|
||||||
|
this.exhaustion = exhaustion;
|
||||||
|
|
||||||
|
net.minecraft.world.damagesource.DamageType nms = null;
|
||||||
|
net.minecraft.core.Holder<net.minecraft.world.damagesource.DamageType> h = null;
|
||||||
|
try {
|
||||||
|
var scalingNms = CraftDamageType.damageScalingToNMS(this.damageScaling);
|
||||||
|
var effectsNms = this.damageEffect instanceof CraftDamageEffect craftEffect
|
||||||
|
? craftEffect.getHandle()
|
||||||
|
: net.minecraft.world.damagesource.DamageEffects.HURT;
|
||||||
|
var deathMessageNms = CraftDamageType.deathMessageTypeToNMS(this.deathMessageType);
|
||||||
|
nms = new net.minecraft.world.damagesource.DamageType(
|
||||||
|
this.namespacedKey.getKey(),
|
||||||
|
scalingNms,
|
||||||
|
this.exhaustion,
|
||||||
|
effectsNms,
|
||||||
|
deathMessageNms
|
||||||
|
);
|
||||||
|
|
||||||
|
var nmsKey = net.minecraft.resources.ResourceKey.create(
|
||||||
|
net.minecraft.core.registries.Registries.DAMAGE_TYPE,
|
||||||
|
CraftNamespacedKey.toMinecraft(this.namespacedKey)
|
||||||
|
);
|
||||||
|
var ref = net.minecraft.core.Holder.Reference.createStandAlone(null, nmsKey);
|
||||||
|
if (HOLDER_REF_VALUE_FIELD != null) {
|
||||||
|
HOLDER_REF_VALUE_FIELD.set(ref, nms);
|
||||||
|
}
|
||||||
|
h = ref;
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
|
||||||
|
this.nmsHandle = nms;
|
||||||
|
this.holder = h;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DamageEffect getFallbackEffect() {
|
||||||
|
try {
|
||||||
|
if (DamageEffect.HURT != null) {
|
||||||
|
return DamageEffect.HURT;
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
return () -> org.bukkit.Sound.ENTITY_PLAYER_HURT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DamageScaling determineScaling(@Nullable NamespacedKey key) {
|
||||||
|
if (key == null) return DamageScaling.WHEN_CAUSED_BY_LIVING_NON_PLAYER;
|
||||||
|
String val = key.getKey().toLowerCase(Locale.ROOT);
|
||||||
|
return switch (val) {
|
||||||
|
case "fall", "drown", "starve", "freeze", "in_fire", "dry_out", "in_wall",
|
||||||
|
"cramming", "fly_into_wall", "stalagmite", "out_of_world", "outside_border",
|
||||||
|
"generic_kill" -> DamageScaling.NEVER;
|
||||||
|
case "player_attack", "player_explosion", "explosion" -> DamageScaling.ALWAYS;
|
||||||
|
default -> DamageScaling.WHEN_CAUSED_BY_LIVING_NON_PLAYER;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DamageEffect determineEffect(@Nullable NamespacedKey key) {
|
||||||
|
if (key == null) return getFallbackEffect();
|
||||||
|
String val = key.getKey().toLowerCase(Locale.ROOT);
|
||||||
|
try {
|
||||||
|
return switch (val) {
|
||||||
|
case "in_fire", "on_fire", "lava", "campfire", "hot_floor" ->
|
||||||
|
DamageEffect.BURNING != null ? DamageEffect.BURNING : (() -> org.bukkit.Sound.ENTITY_PLAYER_HURT_ON_FIRE);
|
||||||
|
case "drown" ->
|
||||||
|
DamageEffect.DROWNING != null ? DamageEffect.DROWNING : (() -> org.bukkit.Sound.ENTITY_PLAYER_HURT_DROWN);
|
||||||
|
case "freeze" ->
|
||||||
|
DamageEffect.FREEZING != null ? DamageEffect.FREEZING : (() -> org.bukkit.Sound.ENTITY_PLAYER_HURT_FREEZE);
|
||||||
|
case "cactus", "sweet_berry_bush", "stalagmite" ->
|
||||||
|
DamageEffect.POKING != null ? DamageEffect.POKING : (() -> org.bukkit.Sound.ENTITY_PLAYER_HURT_SWEET_BERRY_BUSH);
|
||||||
|
case "thorns" ->
|
||||||
|
DamageEffect.THORNS != null ? DamageEffect.THORNS : (() -> org.bukkit.Sound.ENCHANT_THORNS_HIT);
|
||||||
|
default -> getFallbackEffect();
|
||||||
|
};
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
return getFallbackEffect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DeathMessageType determineDeathMessageType(@Nullable NamespacedKey key) {
|
||||||
|
if (key == null) return DeathMessageType.DEFAULT;
|
||||||
|
String val = key.getKey().toLowerCase(Locale.ROOT);
|
||||||
|
return switch (val) {
|
||||||
|
case "bad_respawn_point" -> DeathMessageType.INTENTIONAL_GAME_DESIGN;
|
||||||
|
case "fall", "stalagmite" -> DeathMessageType.FALL_VARIANTS;
|
||||||
|
default -> DeathMessageType.DEFAULT;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float determineExhaustion(@Nullable NamespacedKey key) {
|
||||||
|
if (key == null) return 0.1f;
|
||||||
|
String val = key.getKey().toLowerCase(Locale.ROOT);
|
||||||
|
return switch (val) {
|
||||||
|
case "freeze", "starve" -> 0.0f;
|
||||||
|
default -> 0.1f;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public net.minecraft.core.Holder<net.minecraft.world.damagesource.DamageType> getHolder() {
|
||||||
|
return holder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public net.minecraft.world.damagesource.DamageType getHandle() {
|
||||||
|
return nmsHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull NamespacedKey getKey() {
|
||||||
|
return namespacedKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull Key key() {
|
||||||
|
return adventureKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull String getTranslationKey() {
|
||||||
|
return translationKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull DamageScaling getDamageScaling() {
|
||||||
|
return damageScaling;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull DamageEffect getDamageEffect() {
|
||||||
|
return damageEffect;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull DeathMessageType getDeathMessageType() {
|
||||||
|
return deathMessageType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getExhaustion() {
|
||||||
|
return exhaustion;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (!(o instanceof DamageType that)) return false;
|
||||||
|
return Objects.equals(namespacedKey, that.getKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hashCode(namespacedKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PatchBukkitDamageType{" + namespacedKey + "}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -124,6 +124,11 @@ public class PatchBukkitRegistry<P, B extends Keyed> implements Registry<B> {
|
|||||||
}
|
}
|
||||||
} else if (RegistryKey.SOUND_EVENT.equals(registryKey) || "sound_event".equalsIgnoreCase(registryKey.key().value())) {
|
} else if (RegistryKey.SOUND_EVENT.equals(registryKey) || "sound_event".equalsIgnoreCase(registryKey.key().value())) {
|
||||||
// Do not reflectively autoDiscover Sound.class because Sound.<clinit> depends on Registry.SOUNDS!
|
// Do not reflectively autoDiscover Sound.class because Sound.<clinit> depends on Registry.SOUNDS!
|
||||||
|
} else if (RegistryKey.MENU.equals(registryKey) || "menu".equalsIgnoreCase(registryKey.key().value())) {
|
||||||
|
// Do not reflectively autoDiscover MenuType.class because MenuType.<clinit> depends on Registry.MENU!
|
||||||
|
} else if (RegistryKey.DAMAGE_TYPE.equals(registryKey) || "damage_type".equalsIgnoreCase(registryKey.key().value())) {
|
||||||
|
// Do not reflectively autoDiscover DamageType.class because DamageType.<clinit> depends on Registry.DAMAGE_TYPE!
|
||||||
|
populateDefaultDamageTypes();
|
||||||
} else {
|
} else {
|
||||||
Class<B> valueClass = (Class<B>) LegacyRegistryIdentifiers.KEY_TO_CLASS_MAP.get(registryKey);
|
Class<B> valueClass = (Class<B>) LegacyRegistryIdentifiers.KEY_TO_CLASS_MAP.get(registryKey);
|
||||||
if (valueClass != null) {
|
if (valueClass != null) {
|
||||||
@ -134,6 +139,68 @@ public class PatchBukkitRegistry<P, B extends Keyed> implements Registry<B> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final String[] DEFAULT_DAMAGE_TYPES = {
|
||||||
|
"arrow",
|
||||||
|
"bad_respawn_point",
|
||||||
|
"cactus",
|
||||||
|
"campfire",
|
||||||
|
"cramming",
|
||||||
|
"dragon_breath",
|
||||||
|
"drown",
|
||||||
|
"dry_out",
|
||||||
|
"ender_pearl",
|
||||||
|
"explosion",
|
||||||
|
"fall",
|
||||||
|
"falling_anvil",
|
||||||
|
"falling_block",
|
||||||
|
"falling_stalactite",
|
||||||
|
"fireball",
|
||||||
|
"fireworks",
|
||||||
|
"fly_into_wall",
|
||||||
|
"freeze",
|
||||||
|
"generic",
|
||||||
|
"generic_kill",
|
||||||
|
"hot_floor",
|
||||||
|
"in_fire",
|
||||||
|
"in_wall",
|
||||||
|
"indirect_magic",
|
||||||
|
"lava",
|
||||||
|
"lightning_bolt",
|
||||||
|
"mace_smash",
|
||||||
|
"magic",
|
||||||
|
"mob_attack",
|
||||||
|
"mob_attack_no_aggro",
|
||||||
|
"mob_projectile",
|
||||||
|
"on_fire",
|
||||||
|
"out_of_world",
|
||||||
|
"outside_border",
|
||||||
|
"player_attack",
|
||||||
|
"player_explosion",
|
||||||
|
"sonic_boom",
|
||||||
|
"spear",
|
||||||
|
"spit",
|
||||||
|
"stalagmite",
|
||||||
|
"starve",
|
||||||
|
"sting",
|
||||||
|
"sulfur_cube_hot",
|
||||||
|
"sweet_berry_bush",
|
||||||
|
"thorns",
|
||||||
|
"thrown",
|
||||||
|
"trident",
|
||||||
|
"unattributed_fireball",
|
||||||
|
"wind_charge",
|
||||||
|
"wither",
|
||||||
|
"wither_skull"
|
||||||
|
};
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private void populateDefaultDamageTypes() {
|
||||||
|
for (String name : DEFAULT_DAMAGE_TYPES) {
|
||||||
|
NamespacedKey key = NamespacedKey.minecraft(name);
|
||||||
|
entries.put(key, (B) new PatchBukkitDamageType(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private static <B extends Keyed> Map<NamespacedKey, B> autoDiscover(Class<B> clazz) {
|
private static <B extends Keyed> Map<NamespacedKey, B> autoDiscover(Class<B> clazz) {
|
||||||
Map<NamespacedKey, B> map = new LinkedHashMap<>();
|
Map<NamespacedKey, B> map = new LinkedHashMap<>();
|
||||||
@ -222,6 +289,26 @@ public class PatchBukkitRegistry<P, B extends Keyed> implements Registry<B> {
|
|||||||
}
|
}
|
||||||
} catch (Throwable ignored) {}
|
} catch (Throwable ignored) {}
|
||||||
}
|
}
|
||||||
|
// Fallback for MenuType via CraftMenuType
|
||||||
|
if (RegistryKey.MENU.equals(registryKey) || "menu".equalsIgnoreCase(registryKey != null ? registryKey.key().value() : "")) {
|
||||||
|
try {
|
||||||
|
var nmsIdent = org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(key);
|
||||||
|
var holderOpt = net.minecraft.core.registries.BuiltInRegistries.MENU.get(nmsIdent);
|
||||||
|
if (holderOpt != null && holderOpt.isPresent()) {
|
||||||
|
B menuType = (B) new org.bukkit.craftbukkit.inventory.CraftMenuType<>(holderOpt.get());
|
||||||
|
entries.put(key, menuType);
|
||||||
|
return menuType;
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
}
|
||||||
|
// Fallback for DamageType
|
||||||
|
if (RegistryKey.DAMAGE_TYPE.equals(registryKey) || "damage_type".equalsIgnoreCase(registryKey != null ? registryKey.key().value() : "")) {
|
||||||
|
try {
|
||||||
|
PatchBukkitDamageType damageType = new PatchBukkitDamageType(key);
|
||||||
|
entries.put(key, (B) damageType);
|
||||||
|
return (B) damageType;
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
fallbackSet.remove(key);
|
fallbackSet.remove(key);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -44,10 +44,32 @@ public class PatchBukkitRegistryAccess extends io.papermc.paper.registry.PaperRe
|
|||||||
return this.getRegistry(registryKey);
|
return this.getRegistry(registryKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <M> void lockReferenceHolders(final net.minecraft.resources.ResourceKey<? extends net.minecraft.core.Registry<M>> resourceKey) {
|
||||||
|
try {
|
||||||
|
final var entry = io.papermc.paper.registry.PaperRegistries.getEntry(resourceKey);
|
||||||
|
if (entry != null) {
|
||||||
|
Registry<?> reg = super.getRegistry(entry.apiKey());
|
||||||
|
if (reg instanceof org.bukkit.craftbukkit.CraftRegistry<?, ?> craftRegistry) {
|
||||||
|
craftRegistry.lockReferenceHolders();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
public <T extends Keyed> Registry<T> getRegistry(RegistryKey<T> registryKey) {
|
public <T extends Keyed> Registry<T> getRegistry(RegistryKey<T> registryKey) {
|
||||||
if (registryKey == null) return null;
|
if (registryKey == null) return null;
|
||||||
|
|
||||||
|
if (!FACTORIES.containsKey(registryKey) && !RegistryKey.ITEM.equals(registryKey) && !RegistryKey.BLOCK.equals(registryKey) && !RegistryKey.DAMAGE_TYPE.equals(registryKey)) {
|
||||||
|
try {
|
||||||
|
Registry<T> superReg = super.getRegistry(registryKey);
|
||||||
|
if (superReg != null) {
|
||||||
|
return superReg;
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
Registry<?> existing = instances.get(registryKey);
|
Registry<?> existing = instances.get(registryKey);
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
return (Registry<T>) existing;
|
return (Registry<T>) existing;
|
||||||
|
|||||||
@ -0,0 +1,29 @@
|
|||||||
|
package org.patchbukkit.util;
|
||||||
|
|
||||||
|
import net.minecraft.world.level.block.Block;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import net.minecraft.world.item.Item;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
|
||||||
|
public class CraftMagicNumbers {
|
||||||
|
|
||||||
|
public static BlockState getBlock(org.bukkit.material.MaterialData material) {
|
||||||
|
return org.bukkit.craftbukkit.util.CraftMagicNumbers.getBlock(material);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Block getBlock(Material material) {
|
||||||
|
return org.bukkit.craftbukkit.util.CraftMagicNumbers.getBlock(material);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Material getMaterial(Block block) {
|
||||||
|
return org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Item getItem(Material material) {
|
||||||
|
return org.bukkit.craftbukkit.util.CraftMagicNumbers.getItem(material);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Material getMaterial(Item item) {
|
||||||
|
return org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -34,10 +34,13 @@ public final class Versioning {
|
|||||||
} catch (final IOException ex) {
|
} catch (final IOException ex) {
|
||||||
Logger.getLogger(Versioning.class.getName()).log(Level.SEVERE, "Could not get Bukkit version!", ex);
|
Logger.getLogger(Versioning.class.getName()).log(Level.SEVERE, "Could not get Bukkit version!", ex);
|
||||||
}
|
}
|
||||||
BUKKIT_VERSION = bukkitVersion;
|
|
||||||
if (apiVersion == null) {
|
if (apiVersion == null) {
|
||||||
apiVersion = "Unknown-Version";
|
apiVersion = "26.3";
|
||||||
}
|
}
|
||||||
|
if (bukkitVersion == null || bukkitVersion.contains(".build.")) {
|
||||||
|
bukkitVersion = apiVersion + "-R0.1-SNAPSHOT";
|
||||||
|
}
|
||||||
|
BUKKIT_VERSION = bukkitVersion;
|
||||||
API_VERSION = apiVersion;
|
API_VERSION = apiVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -146,6 +146,7 @@ public class PatchBukkitRegionAccessor implements RegionAccessor {
|
|||||||
) {
|
) {
|
||||||
UUID worldUuid = (this instanceof PatchBukkitWorld w) ? w.getUID() : UUID.randomUUID();
|
UUID worldUuid = (this instanceof PatchBukkitWorld w) ? w.getUID() : UUID.randomUUID();
|
||||||
UUID entityUuid = UUID.randomUUID();
|
UUID entityUuid = UUID.randomUUID();
|
||||||
|
int entityId = -1;
|
||||||
try {
|
try {
|
||||||
var res = NativeBridgeFfi.spawnWorldEntity(SpawnWorldEntityRequest.newBuilder()
|
var res = NativeBridgeFfi.spawnWorldEntity(SpawnWorldEntityRequest.newBuilder()
|
||||||
.setWorldUuid(BridgeUtils.convertUuid(worldUuid))
|
.setWorldUuid(BridgeUtils.convertUuid(worldUuid))
|
||||||
@ -156,12 +157,17 @@ public class PatchBukkitRegionAccessor implements RegionAccessor {
|
|||||||
.setYaw(loc.getYaw())
|
.setYaw(loc.getYaw())
|
||||||
.setPitch(loc.getPitch())
|
.setPitch(loc.getPitch())
|
||||||
.build());
|
.build());
|
||||||
if (res != null && res.hasEntityUuid()) {
|
if (res != null) {
|
||||||
entityUuid = UUID.fromString(res.getEntityUuid().getValue());
|
if (res.hasEntityUuid()) {
|
||||||
|
entityUuid = UUID.fromString(res.getEntityUuid().getValue());
|
||||||
|
}
|
||||||
|
if (res.getEntityId() > 0) {
|
||||||
|
entityId = res.getEntityId();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (Throwable ignored) {}
|
} catch (Throwable ignored) {}
|
||||||
|
|
||||||
Entity entity = PatchBukkitEntity.create(entityUuid, type, loc);
|
Entity entity = PatchBukkitEntity.create(entityUuid, type, loc, entityId);
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -129,14 +129,14 @@ public class PatchBukkitWorld extends PatchBukkitRegionAccessor implements World
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private PatchBukkitWorld(UUID uuid) {
|
protected PatchBukkitWorld(UUID uuid) {
|
||||||
this.uuid = uuid;
|
this.uuid = uuid;
|
||||||
initDefaultGameRules();
|
initDefaultGameRules();
|
||||||
syncWorldInfo();
|
syncWorldInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PatchBukkitWorld getOrCreate(UUID uuid) {
|
public static PatchBukkitWorld getOrCreate(UUID uuid) {
|
||||||
return instances.computeIfAbsent(uuid, PatchBukkitWorld::new);
|
return instances.computeIfAbsent(uuid, org.patchbukkit.CraftWorld::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PatchBukkitWorld getOrCreate(String uuid) {
|
public static PatchBukkitWorld getOrCreate(String uuid) {
|
||||||
@ -353,6 +353,7 @@ public class PatchBukkitWorld extends PatchBukkitRegionAccessor implements World
|
|||||||
@Override
|
@Override
|
||||||
public @NotNull Item dropItem(@NotNull Location loc, @NotNull ItemStack item, @Nullable Consumer<? super Item> function) {
|
public @NotNull Item dropItem(@NotNull Location loc, @NotNull ItemStack item, @Nullable Consumer<? super Item> function) {
|
||||||
UUID entityUuid = UUID.randomUUID();
|
UUID entityUuid = UUID.randomUUID();
|
||||||
|
int entityId = -1;
|
||||||
try {
|
try {
|
||||||
var res = NativeBridgeFfi.spawnWorldEntity(SpawnWorldEntityRequest.newBuilder()
|
var res = NativeBridgeFfi.spawnWorldEntity(SpawnWorldEntityRequest.newBuilder()
|
||||||
.setWorldUuid(BridgeUtils.convertUuid(this.uuid))
|
.setWorldUuid(BridgeUtils.convertUuid(this.uuid))
|
||||||
@ -361,12 +362,17 @@ public class PatchBukkitWorld extends PatchBukkitRegionAccessor implements World
|
|||||||
.setY(loc.getY())
|
.setY(loc.getY())
|
||||||
.setZ(loc.getZ())
|
.setZ(loc.getZ())
|
||||||
.build());
|
.build());
|
||||||
if (res != null && res.hasEntityUuid()) {
|
if (res != null) {
|
||||||
entityUuid = UUID.fromString(res.getEntityUuid().getValue());
|
if (res.hasEntityUuid()) {
|
||||||
|
entityUuid = UUID.fromString(res.getEntityUuid().getValue());
|
||||||
|
}
|
||||||
|
if (res.getEntityId() > 0) {
|
||||||
|
entityId = res.getEntityId();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (Throwable ignored) {}
|
} catch (Throwable ignored) {}
|
||||||
|
|
||||||
Item itemEntity = (Item) PatchBukkitEntity.create(entityUuid, EntityType.ITEM, loc);
|
Item itemEntity = (Item) PatchBukkitEntity.create(entityUuid, EntityType.ITEM, loc, entityId);
|
||||||
if (function != null) {
|
if (function != null) {
|
||||||
function.accept(itemEntity);
|
function.accept(itemEntity);
|
||||||
}
|
}
|
||||||
@ -403,6 +409,7 @@ public class PatchBukkitWorld extends PatchBukkitRegionAccessor implements World
|
|||||||
@Override
|
@Override
|
||||||
public @NotNull LightningStrike strikeLightning(@NotNull Location loc) {
|
public @NotNull LightningStrike strikeLightning(@NotNull Location loc) {
|
||||||
UUID entityUuid = UUID.randomUUID();
|
UUID entityUuid = UUID.randomUUID();
|
||||||
|
int entityId = -1;
|
||||||
try {
|
try {
|
||||||
var res = NativeBridgeFfi.spawnWorldEntity(SpawnWorldEntityRequest.newBuilder()
|
var res = NativeBridgeFfi.spawnWorldEntity(SpawnWorldEntityRequest.newBuilder()
|
||||||
.setWorldUuid(BridgeUtils.convertUuid(this.uuid))
|
.setWorldUuid(BridgeUtils.convertUuid(this.uuid))
|
||||||
@ -411,12 +418,17 @@ public class PatchBukkitWorld extends PatchBukkitRegionAccessor implements World
|
|||||||
.setY(loc.getY())
|
.setY(loc.getY())
|
||||||
.setZ(loc.getZ())
|
.setZ(loc.getZ())
|
||||||
.build());
|
.build());
|
||||||
if (res != null && res.hasEntityUuid()) {
|
if (res != null) {
|
||||||
entityUuid = UUID.fromString(res.getEntityUuid().getValue());
|
if (res.hasEntityUuid()) {
|
||||||
|
entityUuid = UUID.fromString(res.getEntityUuid().getValue());
|
||||||
|
}
|
||||||
|
if (res.getEntityId() > 0) {
|
||||||
|
entityId = res.getEntityId();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (Throwable ignored) {}
|
} catch (Throwable ignored) {}
|
||||||
|
|
||||||
LightningStrike bolt = (LightningStrike) PatchBukkitEntity.create(entityUuid, EntityType.LIGHTNING_BOLT, loc);
|
LightningStrike bolt = (LightningStrike) PatchBukkitEntity.create(entityUuid, EntityType.LIGHTNING_BOLT, loc, entityId);
|
||||||
registerEntity(bolt);
|
registerEntity(bolt);
|
||||||
return bolt;
|
return bolt;
|
||||||
}
|
}
|
||||||
@ -458,7 +470,7 @@ public class PatchBukkitWorld extends PatchBukkitRegionAccessor implements World
|
|||||||
type = EntityType.valueOf(summary.getEntityType());
|
type = EntityType.valueOf(summary.getEntityType());
|
||||||
} catch (Throwable ignored) {}
|
} catch (Throwable ignored) {}
|
||||||
Location loc = new Location(this, summary.getX(), summary.getY(), summary.getZ(), summary.getYaw(), summary.getPitch());
|
Location loc = new Location(this, summary.getX(), summary.getY(), summary.getZ(), summary.getYaw(), summary.getPitch());
|
||||||
Entity e = PatchBukkitEntity.create(u, type, loc);
|
Entity e = PatchBukkitEntity.create(u, type, loc, summary.getEntityId());
|
||||||
list.add(e);
|
list.add(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1590,6 +1602,11 @@ public class PatchBukkitWorld extends PatchBukkitRegionAccessor implements World
|
|||||||
spawnParticle(particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, data, force);
|
spawnParticle(particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, data, force);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> void spawnParticle(@NotNull Particle particle, @Nullable List<Player> receivers, @Nullable Player source, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, double xSpread, double ySpread, @Nullable T data, boolean force, @NotNull Particle.RandomizationType randomizationType) {
|
||||||
|
spawnParticle(particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, data, force);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void playSound(@NotNull Entity entity, @NotNull Sound sound, @NotNull SoundCategory category, float volume, float pitch) {
|
public void playSound(@NotNull Entity entity, @NotNull Sound sound, @NotNull SoundCategory category, float volume, float pitch) {
|
||||||
playSound(entity.getLocation(), sound, category, volume, pitch);
|
playSound(entity.getLocation(), sound, category, volume, pitch);
|
||||||
@ -1724,6 +1741,22 @@ public class PatchBukkitWorld extends PatchBukkitRegionAccessor implements World
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public @Nullable Entity getEntity(int entityId) {
|
||||||
|
for (Entity e : getEntities()) {
|
||||||
|
if (e.getEntityId() == entityId) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
var resp = NativeBridgeFfi.getEntityUuid(patchbukkit.entity.GetEntityUuidRequest.newBuilder().setEntityId(entityId).build());
|
||||||
|
if (resp != null && resp.hasUuid()) {
|
||||||
|
UUID u = UUID.fromString(resp.getUuid().getValue());
|
||||||
|
return getEntity(u);
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable RayTraceResult rayTraceBlocks(@NotNull Position start, @NotNull Vector direction, double maxDistance, @NotNull FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks, @Nullable Predicate<? super Block> filter) {
|
public @Nullable RayTraceResult rayTraceBlocks(@NotNull Position start, @NotNull Vector direction, double maxDistance, @NotNull FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks, @Nullable Predicate<? super Block> filter) {
|
||||||
return rayTraceBlocks(new Location(this, start.x(), start.y(), start.z()), direction, maxDistance, fluidCollisionMode, ignorePassableBlocks);
|
return rayTraceBlocks(new Location(this, start.x(), start.y(), start.z()), direction, maxDistance, fluidCollisionMode, ignorePassableBlocks);
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
org.patchbukkit.PatchBukkitInternalAPIBridge
|
|
||||||
@ -1 +0,0 @@
|
|||||||
org.patchbukkit.loader.PatchBukkitPluginLoader
|
|
||||||
@ -0,0 +1,89 @@
|
|||||||
|
package org.patchbukkit.loader;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.patchbukkit.PatchBukkitServer;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.jar.JarFile;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
public class CloudReflectionTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGrimAcCloudReflection() throws Exception {
|
||||||
|
PatchBukkitServer.initServer();
|
||||||
|
|
||||||
|
File grimFile = new File("/home/alex/Documents/Development/Rust/Pumpkin/plugins/data/patchbukkit/patchbukkit-plugins/grimac-bukkit-2.3.74-61caa53.jar");
|
||||||
|
if (!grimFile.exists()) return;
|
||||||
|
|
||||||
|
PatchBukkitPluginClassLoader loader = new PatchBukkitPluginClassLoader(
|
||||||
|
getClass().getClassLoader(),
|
||||||
|
grimFile
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Class<?> vcw = getClass().getClassLoader().loadClass("org.bukkit.craftbukkit.command.VanillaCommandWrapper");
|
||||||
|
System.out.println("VanillaCommandWrapper in AppClassLoader: " + vcw);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
System.out.println("VanillaCommandWrapper NOT in AppClassLoader: " + t);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Class<?> remappedTest = loader.loadClass("org.bukkit.craftbukkitcommand.VanillaCommandWrapper");
|
||||||
|
System.out.println("loader.loadClass with dots: " + remappedTest);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
System.out.println("loader.loadClass with dots FAILED: " + t);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Class<?> remappedSlashTest = loader.loadClass("org/bukkit/craftbukkitcommand/VanillaCommandWrapper");
|
||||||
|
System.out.println("loader.loadClass with slashes: " + remappedSlashTest);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
System.out.println("loader.loadClass with slashes FAILED: " + t);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Class<?> forNameDotTest = Class.forName("org.bukkit.craftbukkitcommand.VanillaCommandWrapper", true, loader);
|
||||||
|
System.out.println("Class.forName with dots: " + forNameDotTest);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
System.out.println("Class.forName with dots FAILED: " + t);
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<?> cbrClass = loader.loadClass("ac.grim.grimac.shaded.incendo.cloud.bukkit.internal.CraftBukkitReflection");
|
||||||
|
Field unsafeField = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
|
||||||
|
unsafeField.setAccessible(true);
|
||||||
|
sun.misc.Unsafe unsafe = (sun.misc.Unsafe) unsafeField.get(null);
|
||||||
|
|
||||||
|
Field cbPkgField2 = cbrClass.getDeclaredField("CB_PKG_VERSION");
|
||||||
|
cbPkgField2.setAccessible(true);
|
||||||
|
unsafe.putObject(unsafe.staticFieldBase(cbPkgField2), unsafe.staticFieldOffset(cbPkgField2), ".");
|
||||||
|
System.out.println("After unsafe putObject, CB_PKG_VERSION: '" + cbPkgField2.get(null) + "'");
|
||||||
|
assertNotNull(cbrClass);
|
||||||
|
|
||||||
|
Field cbPkgField = cbrClass.getDeclaredField("CB_PKG_VERSION");
|
||||||
|
cbPkgField.setAccessible(true);
|
||||||
|
Object version = cbPkgField.get(null);
|
||||||
|
System.out.println("CB_PKG_VERSION: '" + version + "'");
|
||||||
|
|
||||||
|
for (Field f : cbrClass.getDeclaredFields()) {
|
||||||
|
f.setAccessible(true);
|
||||||
|
try {
|
||||||
|
System.out.println("Field " + f.getName() + " = " + f.get(null));
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
Method needObcMethod = cbrClass.getDeclaredMethod("needOBCClass", String.class);
|
||||||
|
needObcMethod.setAccessible(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Object res = needObcMethod.invoke(null, "command.VanillaCommandWrapper");
|
||||||
|
System.out.println("Found OBC Class: " + res);
|
||||||
|
assertNotNull(res);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,91 @@
|
|||||||
|
package org.patchbukkit.loader;
|
||||||
|
|
||||||
|
import io.papermc.paper.configuration.GlobalConfiguration;
|
||||||
|
import io.papermc.paper.configuration.PaperConfigurations;
|
||||||
|
import net.minecraft.SharedConstants;
|
||||||
|
import net.minecraft.core.RegistryAccess;
|
||||||
|
import net.minecraft.server.Bootstrap;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
public class GlobalConfigTest {
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
public static void setUp() {
|
||||||
|
SharedConstants.tryDetectVersion();
|
||||||
|
Bootstrap.bootStrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPaperConfigurationsInit() throws Exception {
|
||||||
|
Path tempDir = Files.createTempDirectory("paper-config-test");
|
||||||
|
PaperConfigurations configs = new PaperConfigurations(tempDir);
|
||||||
|
GlobalConfiguration globalConfig = configs.initializeGlobalConfiguration(RegistryAccess.EMPTY);
|
||||||
|
assertNotNull(globalConfig);
|
||||||
|
assertNotNull(GlobalConfiguration.get());
|
||||||
|
assertNotNull(GlobalConfiguration.get().misc);
|
||||||
|
assertEquals(5, GlobalConfiguration.get().misc.maxJoinsPerTick);
|
||||||
|
|
||||||
|
Class<?> connClass = Class.forName("net.minecraft.network.Connection");
|
||||||
|
assertNotNull(connClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testServerConnectionListener() throws Exception {
|
||||||
|
net.minecraft.server.network.ServerConnectionListener scl =
|
||||||
|
new net.minecraft.server.network.ServerConnectionListener(null);
|
||||||
|
assertNotNull(scl);
|
||||||
|
assertNotNull(scl.getConnections());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDedicatedServerAllocation() throws Exception {
|
||||||
|
Field unsafeField = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
|
||||||
|
unsafeField.setAccessible(true);
|
||||||
|
sun.misc.Unsafe unsafe = (sun.misc.Unsafe) unsafeField.get(null);
|
||||||
|
|
||||||
|
Class<?> dsClass = Class.forName("net.minecraft.server.dedicated.DedicatedServer");
|
||||||
|
Object dedicatedServer = unsafe.allocateInstance(dsClass);
|
||||||
|
assertNotNull(dedicatedServer);
|
||||||
|
|
||||||
|
net.minecraft.server.network.ServerConnectionListener scl =
|
||||||
|
new net.minecraft.server.network.ServerConnectionListener((net.minecraft.server.MinecraftServer) dedicatedServer);
|
||||||
|
|
||||||
|
Field connField = null;
|
||||||
|
for (Field f : net.minecraft.server.MinecraftServer.class.getDeclaredFields()) {
|
||||||
|
if (f.getType().equals(net.minecraft.server.network.ServerConnectionListener.class)) {
|
||||||
|
connField = f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertNotNull(connField);
|
||||||
|
connField.setAccessible(true);
|
||||||
|
connField.set(dedicatedServer, scl);
|
||||||
|
|
||||||
|
assertSame(scl, connField.get(dedicatedServer));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCraftServerAllocation() throws Exception {
|
||||||
|
Field unsafeField = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
|
||||||
|
unsafeField.setAccessible(true);
|
||||||
|
sun.misc.Unsafe unsafe = (sun.misc.Unsafe) unsafeField.get(null);
|
||||||
|
|
||||||
|
Class<?> csClass = Class.forName("org.bukkit.craftbukkit.CraftServer");
|
||||||
|
System.out.println("CraftServer code source: " + csClass.getProtectionDomain().getCodeSource().getLocation());
|
||||||
|
for (java.lang.reflect.Constructor<?> ctor : csClass.getDeclaredConstructors()) {
|
||||||
|
System.out.println("CraftServer ctor: " + ctor);
|
||||||
|
}
|
||||||
|
Object craftServer = unsafe.allocateInstance(csClass);
|
||||||
|
assertNotNull(craftServer);
|
||||||
|
assertTrue(craftServer instanceof org.bukkit.Server);
|
||||||
|
assertEquals("org.bukkit.craftbukkit.CraftServer", craftServer.getClass().getName());
|
||||||
|
assertEquals("org.bukkit.craftbukkit", craftServer.getClass().getPackage().getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
package org.patchbukkit.loader;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
public class MojangServerProviderTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testServerJarProviderLocatesOrDownloads(@TempDir File tempDir) {
|
||||||
|
File serverJar = MojangServerProvider.getOrDownloadServerJar(tempDir, "26.2");
|
||||||
|
assertNotNull(serverJar, "Server jar should be located or downloaded");
|
||||||
|
assertTrue(serverJar.exists(), "Server jar file must exist");
|
||||||
|
assertTrue(serverJar.length() > 0, "Server jar file must not be empty");
|
||||||
|
|
||||||
|
// Second call should return cached file immediately
|
||||||
|
long length = serverJar.length();
|
||||||
|
File cached = MojangServerProvider.getOrDownloadServerJar(tempDir, "26.2");
|
||||||
|
assertNotNull(cached);
|
||||||
|
assertEquals(length, cached.length());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,98 @@
|
|||||||
|
package org.patchbukkit.loader;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.patchbukkit.PatchBukkitServer;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
public class ProtocolPluginsLoadTest {
|
||||||
|
|
||||||
|
private static final String PROTOCOLLIB_URL =
|
||||||
|
"https://repo1.maven.org/maven2/net/dmulloy2/ProtocolLib/5.4.0/ProtocolLib-5.4.0.jar";
|
||||||
|
private static final String PACKETEVENTS_URL =
|
||||||
|
"https://github.com/retrooper/packetevents/releases/download/v2.7.0/packetevents-spigot-2.7.0.jar";
|
||||||
|
|
||||||
|
private static Path testPluginsDir;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
public static void setUp() throws Exception {
|
||||||
|
PatchBukkitServer.initServer();
|
||||||
|
|
||||||
|
testPluginsDir = Path.of("build", "test-plugins");
|
||||||
|
Files.createDirectories(testPluginsDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static File ensurePluginJar(String filename, String url) throws Exception {
|
||||||
|
Path target = testPluginsDir.resolve(filename);
|
||||||
|
if (!Files.exists(target) || Files.size(target) == 0) {
|
||||||
|
try (InputStream in = URI.create(url).toURL().openStream()) {
|
||||||
|
Files.copy(in, target, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return target.toFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoadPacketEvents() throws Exception {
|
||||||
|
File jarFile = ensurePluginJar("PacketEvents.jar", PACKETEVENTS_URL);
|
||||||
|
assertTrue(jarFile.exists(), "PacketEvents jar must exist");
|
||||||
|
|
||||||
|
Plugin plugin = Bukkit.getPluginManager().loadPlugin(jarFile);
|
||||||
|
assertNotNull(plugin, "PacketEvents plugin must load successfully");
|
||||||
|
assertEquals("packetevents", plugin.getName().toLowerCase(), "Plugin name should match");
|
||||||
|
|
||||||
|
// Test enable
|
||||||
|
assertDoesNotThrow(() -> Bukkit.getPluginManager().enablePlugin(plugin));
|
||||||
|
assertTrue(plugin.isEnabled(), "PacketEvents should be enabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoadProtocolLib() throws Exception {
|
||||||
|
File jarFile = ensurePluginJar("ProtocolLib.jar", PROTOCOLLIB_URL);
|
||||||
|
assertTrue(jarFile.exists(), "ProtocolLib jar must exist");
|
||||||
|
|
||||||
|
Plugin plugin = Bukkit.getPluginManager().loadPlugin(jarFile);
|
||||||
|
assertNotNull(plugin, "ProtocolLib plugin must load successfully");
|
||||||
|
assertEquals("protocollib", plugin.getName().toLowerCase(), "Plugin name should match");
|
||||||
|
|
||||||
|
// Test enable (ProtocolLib 5.4.0 disables itself on MC 26.2 without dev builds)
|
||||||
|
try {
|
||||||
|
Bukkit.getPluginManager().enablePlugin(plugin);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
t.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoadGrimAC() throws Exception {
|
||||||
|
File grimFile = new File("/home/alex/Documents/Development/Rust/Pumpkin/plugins/data/patchbukkit/patchbukkit-plugins/grimac-bukkit-2.3.74-61caa53.jar");
|
||||||
|
if (!grimFile.exists()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Plugin plugin = Bukkit.getPluginManager().loadPlugin(grimFile);
|
||||||
|
assertNotNull(plugin, "GrimAC plugin must load successfully");
|
||||||
|
assertEquals("grimac", plugin.getName().toLowerCase());
|
||||||
|
|
||||||
|
assertDoesNotThrow(() -> Bukkit.getPluginManager().enablePlugin(plugin));
|
||||||
|
assertTrue(plugin.isEnabled(), "GrimAC should be enabled");
|
||||||
|
|
||||||
|
org.bukkit.entity.Player player = new org.patchbukkit.entity.CraftPlayer(java.util.UUID.randomUUID(), "GrimTester");
|
||||||
|
if (Bukkit.getServer() instanceof PatchBukkitServer pbs) {
|
||||||
|
pbs.registerPlayer(player);
|
||||||
|
}
|
||||||
|
org.bukkit.event.player.PlayerJoinEvent joinEvent =
|
||||||
|
new org.bukkit.event.player.PlayerJoinEvent(player, net.kyori.adventure.text.Component.empty());
|
||||||
|
assertDoesNotThrow(() -> Bukkit.getPluginManager().callEvent(joinEvent));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,72 @@
|
|||||||
|
package org.patchbukkit.log;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.core.Filter;
|
||||||
|
import org.apache.logging.log4j.core.LogEvent;
|
||||||
|
import org.apache.logging.log4j.core.Logger;
|
||||||
|
import org.apache.logging.log4j.core.filter.AbstractFilter;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.patchbukkit.PatchBukkitServer;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
public class Log4jPipelineTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLog4jPipelineAndFiltering() {
|
||||||
|
// Initialize PatchBukkitServer to trigger configureLogging()
|
||||||
|
PatchBukkitServer.getInstance();
|
||||||
|
|
||||||
|
Logger rootLogger = (Logger) LogManager.getRootLogger();
|
||||||
|
assertNotNull(rootLogger, "Root logger should be an instance of org.apache.logging.log4j.core.Logger");
|
||||||
|
|
||||||
|
List<String> capturedMessages = new CopyOnWriteArrayList<>();
|
||||||
|
List<String> capturedLoggers = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
|
Filter testFilter = new AbstractFilter() {
|
||||||
|
@Override
|
||||||
|
public Result filter(LogEvent event) {
|
||||||
|
if (event != null) {
|
||||||
|
capturedLoggers.add(event.getLoggerName());
|
||||||
|
capturedMessages.add(event.getMessage().getFormattedMessage());
|
||||||
|
}
|
||||||
|
return Result.NEUTRAL;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
testFilter.start();
|
||||||
|
rootLogger.addFilter(testFilter);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. Test direct Log4j logging
|
||||||
|
LogManager.getLogger("MyLog4jPlugin").info("Direct log4j message 123");
|
||||||
|
|
||||||
|
// 2. Test Java Util Logging (JUL) forwarding via ForwardLogHandler
|
||||||
|
java.util.logging.Logger.getLogger("MyJulPlugin").info("JUL message 456");
|
||||||
|
|
||||||
|
// 3. Test SLF4J logging via log4j-slf4j2-impl
|
||||||
|
LoggerFactory.getLogger("MySlf4jPlugin").info("SLF4J message 789");
|
||||||
|
|
||||||
|
// 4. Test Apache Commons Logging via LogFactory (used by HttpClient / Aether / CivModCore)
|
||||||
|
org.apache.commons.logging.LogFactory.getLog("MyJclPlugin").info("JCL message 012");
|
||||||
|
|
||||||
|
// Verify Log4j captured all logging mechanisms
|
||||||
|
assertTrue(capturedLoggers.contains("MyLog4jPlugin"), "Log4j direct logger should be captured");
|
||||||
|
assertTrue(capturedMessages.contains("Direct log4j message 123"), "Log4j direct message should be captured");
|
||||||
|
|
||||||
|
assertTrue(capturedLoggers.contains("MyJulPlugin"), "JUL logger should be forwarded to Log4j");
|
||||||
|
assertTrue(capturedMessages.contains("JUL message 456"), "JUL message should be forwarded to Log4j");
|
||||||
|
|
||||||
|
assertTrue(capturedLoggers.contains("MySlf4jPlugin"), "SLF4J logger should be routed to Log4j");
|
||||||
|
assertTrue(capturedMessages.contains("SLF4J message 789"), "SLF4J message should be routed to Log4j");
|
||||||
|
|
||||||
|
assertTrue(capturedLoggers.contains("MyJclPlugin"), "JCL logger should be routed to Log4j");
|
||||||
|
assertTrue(capturedMessages.contains("JCL message 012"), "JCL message should be routed to Log4j");
|
||||||
|
} finally {
|
||||||
|
testFilter.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,156 @@
|
|||||||
|
package org.patchbukkit.network;
|
||||||
|
|
||||||
|
import com.mojang.authlib.GameProfile;
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import net.minecraft.SharedConstants;
|
||||||
|
import net.minecraft.network.Connection;
|
||||||
|
import net.minecraft.network.protocol.PacketFlow;
|
||||||
|
import net.minecraft.server.Bootstrap;
|
||||||
|
import net.minecraft.server.dedicated.DedicatedServer;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
import net.minecraft.server.network.ServerCommonPacketListenerImpl;
|
||||||
|
import net.minecraft.server.network.ServerGamePacketListenerImpl;
|
||||||
|
import org.bukkit.craftbukkit.CraftServer;
|
||||||
|
import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.patchbukkit.PatchBukkitServer;
|
||||||
|
import org.patchbukkit.entity.PatchBukkitPlayer;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
public class HeadlessServerPlayerTest {
|
||||||
|
|
||||||
|
private static sun.misc.Unsafe unsafe;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
public static void setUp() throws Exception {
|
||||||
|
SharedConstants.tryDetectVersion();
|
||||||
|
Bootstrap.bootStrap();
|
||||||
|
PatchBukkitServer.initServer();
|
||||||
|
|
||||||
|
Field f = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
|
||||||
|
f.setAccessible(true);
|
||||||
|
unsafe = (sun.misc.Unsafe) f.get(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAllocateHeadlessServerPlayerHierarchy() throws Exception {
|
||||||
|
UUID uuid = UUID.randomUUID();
|
||||||
|
String name = "TestBot";
|
||||||
|
PatchBukkitPlayer bukkitPlayer = new PatchBukkitPlayer(uuid, name);
|
||||||
|
|
||||||
|
// 1. Virtual Channel
|
||||||
|
PatchBukkitVirtualChannel channel = new PatchBukkitVirtualChannel(uuid);
|
||||||
|
|
||||||
|
// 2. Net.minecraft.network.Connection
|
||||||
|
Connection connection = (Connection) unsafe.allocateInstance(Connection.class);
|
||||||
|
connection.channel = channel;
|
||||||
|
connection.address = new InetSocketAddress("127.0.0.1", 54321);
|
||||||
|
|
||||||
|
// Set receiving flow field
|
||||||
|
for (Field cf : Connection.class.getDeclaredFields()) {
|
||||||
|
if (cf.getType().equals(PacketFlow.class)) {
|
||||||
|
cf.setAccessible(true);
|
||||||
|
cf.set(connection, PacketFlow.SERVERBOUND);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. ServerPlayer
|
||||||
|
ServerPlayer serverPlayer = (ServerPlayer) unsafe.allocateInstance(ServerPlayer.class);
|
||||||
|
|
||||||
|
// Set Entity UUID
|
||||||
|
Field entUuidField = net.minecraft.world.entity.Entity.class.getDeclaredField("uuid");
|
||||||
|
entUuidField.setAccessible(true);
|
||||||
|
entUuidField.set(serverPlayer, uuid);
|
||||||
|
|
||||||
|
Field entStringUuidField = net.minecraft.world.entity.Entity.class.getDeclaredField("stringUUID");
|
||||||
|
entStringUuidField.setAccessible(true);
|
||||||
|
entStringUuidField.set(serverPlayer, uuid.toString());
|
||||||
|
|
||||||
|
// Set gameProfile
|
||||||
|
GameProfile profile = new GameProfile(uuid, name);
|
||||||
|
Field profileField = null;
|
||||||
|
Class<?> pClass = ServerPlayer.class;
|
||||||
|
while (pClass != null && profileField == null) {
|
||||||
|
for (Field pf : pClass.getDeclaredFields()) {
|
||||||
|
if (pf.getType().equals(GameProfile.class)) {
|
||||||
|
profileField = pf;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pClass = pClass.getSuperclass();
|
||||||
|
}
|
||||||
|
assertNotNull(profileField, "GameProfile field must exist on Player/ServerPlayer");
|
||||||
|
profileField.setAccessible(true);
|
||||||
|
profileField.set(serverPlayer, profile);
|
||||||
|
|
||||||
|
// 4. ServerGamePacketListenerImpl
|
||||||
|
ServerGamePacketListenerImpl listener =
|
||||||
|
(ServerGamePacketListenerImpl) unsafe.allocateInstance(ServerGamePacketListenerImpl.class);
|
||||||
|
listener.player = serverPlayer;
|
||||||
|
|
||||||
|
// Set server and connection in ServerCommonPacketListenerImpl superclass
|
||||||
|
Class<?> scClass = ServerCommonPacketListenerImpl.class;
|
||||||
|
Field serverField = scClass.getDeclaredField("server");
|
||||||
|
serverField.setAccessible(true);
|
||||||
|
serverField.set(listener, PatchBukkitServer.getDedicatedServer());
|
||||||
|
|
||||||
|
Field connField = scClass.getDeclaredField("connection");
|
||||||
|
connField.setAccessible(true);
|
||||||
|
connField.set(listener, connection);
|
||||||
|
|
||||||
|
// Link listener into ServerPlayer
|
||||||
|
serverPlayer.connection = listener;
|
||||||
|
|
||||||
|
// 5. CraftPlayer
|
||||||
|
CraftPlayer craftPlayer = (CraftPlayer) unsafe.allocateInstance(CraftPlayer.class);
|
||||||
|
Field entityField = null;
|
||||||
|
Class<?> cpClass = CraftPlayer.class;
|
||||||
|
while (cpClass != null && entityField == null) {
|
||||||
|
for (Field ef : cpClass.getDeclaredFields()) {
|
||||||
|
if (ef.getName().equals("entity")) {
|
||||||
|
entityField = ef;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cpClass = cpClass.getSuperclass();
|
||||||
|
}
|
||||||
|
assertNotNull(entityField, "entity field must exist on CraftEntity");
|
||||||
|
entityField.setAccessible(true);
|
||||||
|
entityField.set(craftPlayer, serverPlayer);
|
||||||
|
|
||||||
|
// Set bukkitEntity on Entity
|
||||||
|
Field bukkitEntityField = null;
|
||||||
|
Class<?> entClass = net.minecraft.world.entity.Entity.class;
|
||||||
|
for (Field bef : entClass.getDeclaredFields()) {
|
||||||
|
if (bef.getName().equals("bukkitEntity")) {
|
||||||
|
bukkitEntityField = bef;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertNotNull(bukkitEntityField, "bukkitEntity field must exist on Entity");
|
||||||
|
bukkitEntityField.setAccessible(true);
|
||||||
|
bukkitEntityField.set(serverPlayer, craftPlayer);
|
||||||
|
|
||||||
|
// Assertions:
|
||||||
|
// A. Channel retrieval matches
|
||||||
|
assertSame(channel, serverPlayer.connection.connection.channel);
|
||||||
|
assertSame(channel, listener.connection.channel);
|
||||||
|
|
||||||
|
// B. getBukkitEntity() and getHandle() circular reference matches
|
||||||
|
assertSame(craftPlayer, serverPlayer.getBukkitEntity());
|
||||||
|
assertSame(serverPlayer, craftPlayer.getHandle());
|
||||||
|
|
||||||
|
// C. UUID and Name matches
|
||||||
|
assertEquals(uuid, serverPlayer.getUUID());
|
||||||
|
assertEquals(name, serverPlayer.getScoreboardName());
|
||||||
|
assertEquals(uuid, craftPlayer.getUniqueId());
|
||||||
|
assertEquals(name, craftPlayer.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,183 @@
|
|||||||
|
package org.patchbukkit.network;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||||
|
import io.netty.channel.ChannelOutboundHandlerAdapter;
|
||||||
|
import io.netty.channel.ChannelPromise;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.patchbukkit.PatchBukkitServer;
|
||||||
|
import org.patchbukkit.entity.PatchBukkitPlayer;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
public class VirtualChannelPipelineTest {
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
public static void setUp() {
|
||||||
|
PatchBukkitServer.initServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPlayerHandleAndChannelReflection() throws Exception {
|
||||||
|
UUID uuid = UUID.randomUUID();
|
||||||
|
PatchBukkitPlayer player = new PatchBukkitPlayer(uuid, "Steve");
|
||||||
|
|
||||||
|
// 1. PacketEvents reflects on player handle
|
||||||
|
VirtualPlayerHandle handle = VirtualChannelManager.getInstance().getPlayerHandle(player);
|
||||||
|
assertNotNull(handle, "Player handle must not be null");
|
||||||
|
|
||||||
|
// Authentic ServerPlayer
|
||||||
|
net.minecraft.server.level.ServerPlayer nmsPlayer = player.getHandle();
|
||||||
|
assertNotNull(nmsPlayer, "ServerPlayer must not be null");
|
||||||
|
|
||||||
|
// 2. PacketEvents reflects on handle.connection
|
||||||
|
Field connectionField = handle.getClass().getField("connection");
|
||||||
|
assertNotNull(connectionField);
|
||||||
|
Object connection = connectionField.get(handle);
|
||||||
|
assertNotNull(connection, "Handle connection must not be null");
|
||||||
|
|
||||||
|
// 3. PacketEvents reflects on connection.channel (direct)
|
||||||
|
Field channelField = connection.getClass().getField("channel");
|
||||||
|
assertNotNull(channelField);
|
||||||
|
Object channelObj = channelField.get(connection);
|
||||||
|
assertNotNull(channelObj, "Channel must not be null");
|
||||||
|
assertInstanceOf(Channel.class, channelObj);
|
||||||
|
|
||||||
|
// Verify authentic ServerPlayer connection points to exact same channel
|
||||||
|
assertSame(channelObj, nmsPlayer.connection.connection.channel, "NMS ServerPlayer channel must match");
|
||||||
|
|
||||||
|
// Also verify nested connection.connection.channel (Mojang ServerGamePacketListenerImpl -> Connection -> channel)
|
||||||
|
Field nestedConnField = connection.getClass().getField("connection");
|
||||||
|
Object nestedConn = nestedConnField.get(connection);
|
||||||
|
Field nestedChannelField = nestedConn.getClass().getField("channel");
|
||||||
|
assertSame(channelObj, nestedChannelField.get(nestedConn), "Nested connection.channel must match direct channel");
|
||||||
|
|
||||||
|
// Also verify nested connection.networkManager.channel (Spigot ServerGamePacketListenerImpl -> NetworkManager -> channel)
|
||||||
|
Field nestedNmField = connection.getClass().getField("networkManager");
|
||||||
|
Object nestedNm = nestedNmField.get(connection);
|
||||||
|
Field nestedNmChannelField = nestedNm.getClass().getField("channel");
|
||||||
|
assertSame(channelObj, nestedNmChannelField.get(nestedNm), "Nested networkManager.channel must match direct channel");
|
||||||
|
|
||||||
|
Channel channel = (Channel) channelObj;
|
||||||
|
assertTrue(channel.isOpen());
|
||||||
|
assertTrue(channel.isActive());
|
||||||
|
|
||||||
|
// 4. Verify standard Minecraft pipeline handlers exist in exact vanilla order
|
||||||
|
assertNotNull(channel.pipeline().get("timeout"), "Pipeline must contain 'timeout'");
|
||||||
|
assertNotNull(channel.pipeline().get("legacy_query"), "Pipeline must contain 'legacy_query'");
|
||||||
|
assertNotNull(channel.pipeline().get("splitter"), "Pipeline must contain 'splitter'");
|
||||||
|
assertNotNull(channel.pipeline().get("decompress"), "Pipeline must contain 'decompress'");
|
||||||
|
assertNotNull(channel.pipeline().get("decoder"), "Pipeline must contain 'decoder'");
|
||||||
|
assertNotNull(channel.pipeline().get("prepender"), "Pipeline must contain 'prepender'");
|
||||||
|
assertNotNull(channel.pipeline().get("compress"), "Pipeline must contain 'compress'");
|
||||||
|
assertNotNull(channel.pipeline().get("encoder"), "Pipeline must contain 'encoder'");
|
||||||
|
assertNotNull(channel.pipeline().get("bundle_packer"), "Pipeline must contain 'bundle_packer'");
|
||||||
|
assertNotNull(channel.pipeline().get("packet_handler"), "Pipeline must contain 'packet_handler'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCompressionAndEncryptionLifecycle() {
|
||||||
|
UUID uuid = UUID.randomUUID();
|
||||||
|
PatchBukkitPlayer player = new PatchBukkitPlayer(uuid, "CompressPlayer");
|
||||||
|
VirtualPlayerHandle handle = VirtualChannelManager.getInstance().getPlayerHandle(player);
|
||||||
|
PatchBukkitVirtualChannel channel = (PatchBukkitVirtualChannel) handle.connection.connection.channel;
|
||||||
|
VirtualNetworkManager nm = handle.connection.connection;
|
||||||
|
|
||||||
|
// Initially compressed with threshold 256
|
||||||
|
assertTrue(channel.isCompressed());
|
||||||
|
assertTrue(nm.isCompressed());
|
||||||
|
assertEquals(256, channel.getCompressionThreshold());
|
||||||
|
assertEquals(256, nm.getCompressionThreshold());
|
||||||
|
assertFalse(channel.isEncrypted());
|
||||||
|
assertFalse(nm.isEncrypted());
|
||||||
|
|
||||||
|
// Change compression threshold
|
||||||
|
nm.setupCompression(512, true);
|
||||||
|
assertEquals(512, channel.getCompressionThreshold());
|
||||||
|
assertEquals(512, nm.getCompressionThreshold());
|
||||||
|
PatchBukkitVirtualChannel.VirtualCompressionDecoder decoder =
|
||||||
|
(PatchBukkitVirtualChannel.VirtualCompressionDecoder) channel.pipeline().get("decompress");
|
||||||
|
PatchBukkitVirtualChannel.VirtualCompressionEncoder encoder =
|
||||||
|
(PatchBukkitVirtualChannel.VirtualCompressionEncoder) channel.pipeline().get("compress");
|
||||||
|
assertNotNull(decoder);
|
||||||
|
assertNotNull(encoder);
|
||||||
|
assertEquals(512, decoder.threshold());
|
||||||
|
assertTrue(decoder.validateDecompressed);
|
||||||
|
assertEquals(512, encoder.threshold());
|
||||||
|
|
||||||
|
// Disable compression with -1
|
||||||
|
nm.setupCompression(-1);
|
||||||
|
assertFalse(channel.isCompressed());
|
||||||
|
assertFalse(nm.isCompressed());
|
||||||
|
assertNull(channel.pipeline().get("decompress"));
|
||||||
|
assertNull(channel.pipeline().get("compress"));
|
||||||
|
|
||||||
|
// Re-enable compression
|
||||||
|
nm.setupCompression(128);
|
||||||
|
assertTrue(channel.isCompressed());
|
||||||
|
assertTrue(nm.isCompressed());
|
||||||
|
assertEquals(128, channel.getCompressionThreshold());
|
||||||
|
assertNotNull(channel.pipeline().get("decompress"));
|
||||||
|
assertNotNull(channel.pipeline().get("compress"));
|
||||||
|
|
||||||
|
// Enable encryption
|
||||||
|
Object dummyCipher = new Object();
|
||||||
|
nm.setEncryptionKey(dummyCipher, dummyCipher);
|
||||||
|
assertTrue(channel.isEncrypted());
|
||||||
|
assertTrue(nm.isEncrypted());
|
||||||
|
assertNotNull(channel.pipeline().get("decrypt"));
|
||||||
|
assertNotNull(channel.pipeline().get("encrypt"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPacketEventsStylePipelineInjection() {
|
||||||
|
UUID uuid = UUID.randomUUID();
|
||||||
|
PatchBukkitPlayer player = new PatchBukkitPlayer(uuid, "Alex");
|
||||||
|
VirtualPlayerHandle handle = VirtualChannelManager.getInstance().getPlayerHandle(player);
|
||||||
|
Channel channel = handle.connection.connection.channel;
|
||||||
|
|
||||||
|
AtomicBoolean injectedInboundReceived = new AtomicBoolean(false);
|
||||||
|
AtomicInteger receivedBytesCount = new AtomicInteger(0);
|
||||||
|
|
||||||
|
// Simulate PacketEvents injecting its handler before "packet_handler"
|
||||||
|
channel.pipeline().addBefore("packet_handler", "packetevents_inbound_handler", new ChannelInboundHandlerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||||
|
if (msg instanceof ByteBuf buf) {
|
||||||
|
injectedInboundReceived.set(true);
|
||||||
|
receivedBytesCount.set(buf.readableBytes());
|
||||||
|
}
|
||||||
|
ctx.fireChannelRead(msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Simulate Pumpkin receiving packet bytes from the client and forwarding to VirtualChannelManager
|
||||||
|
byte[] testPacket = "hello_minecraft_packet".getBytes(StandardCharsets.UTF_8);
|
||||||
|
VirtualChannelManager.getInstance().handlePacketReceived(uuid, 0x01, testPacket);
|
||||||
|
|
||||||
|
assertTrue(injectedInboundReceived.get(), "Injected PacketEvents handler should have intercepted the inbound packet");
|
||||||
|
assertEquals(testPacket.length, receivedBytesCount.get(), "Injected handler should have received exact byte length");
|
||||||
|
|
||||||
|
// Simulate outbound write
|
||||||
|
AtomicBoolean outboundIntercepted = new AtomicBoolean(false);
|
||||||
|
channel.pipeline().addBefore("encoder", "packetevents_outbound_handler", new ChannelOutboundHandlerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
||||||
|
outboundIntercepted.set(true);
|
||||||
|
ctx.write(msg, promise);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
channel.writeAndFlush("dummy_packet");
|
||||||
|
assertTrue(outboundIntercepted.get(), "Outbound write should have passed through injected outbound handler");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -64,6 +64,31 @@ public class PatchBukkitRegistryAccessTest {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDamageTypeInitialization() {
|
||||||
|
assertDoesNotThrow(() -> {
|
||||||
|
org.bukkit.damage.DamageType arrow = org.bukkit.damage.DamageType.ARROW;
|
||||||
|
assertNotNull(arrow, "DamageType.ARROW must initialize successfully without ExceptionInInitializerError");
|
||||||
|
assertEquals(org.bukkit.NamespacedKey.minecraft("arrow"), arrow.getKey());
|
||||||
|
assertNotNull(arrow.getTranslationKey());
|
||||||
|
assertNotNull(arrow.getDamageScaling());
|
||||||
|
assertNotNull(arrow.getDamageEffect());
|
||||||
|
assertNotNull(arrow.getDeathMessageType());
|
||||||
|
|
||||||
|
org.bukkit.damage.DamageType generic = org.bukkit.damage.DamageType.GENERIC;
|
||||||
|
assertNotNull(generic);
|
||||||
|
assertEquals(org.bukkit.NamespacedKey.minecraft("generic"), generic.getKey());
|
||||||
|
|
||||||
|
Registry<org.bukkit.damage.DamageType> damageTypeReg = Registry.DAMAGE_TYPE;
|
||||||
|
assertNotNull(damageTypeReg);
|
||||||
|
assertSame(arrow, damageTypeReg.get(org.bukkit.NamespacedKey.minecraft("arrow")));
|
||||||
|
|
||||||
|
org.bukkit.damage.DamageSource source = org.bukkit.damage.DamageSource.builder(arrow).build();
|
||||||
|
assertNotNull(source);
|
||||||
|
assertEquals(arrow, source.getDamageType());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInspectBlockTypeAndDataMethods() {
|
public void testInspectBlockTypeAndDataMethods() {
|
||||||
System.out.println("=== BLOCK TYPE METHODS ===");
|
System.out.println("=== BLOCK TYPE METHODS ===");
|
||||||
|
|||||||
@ -22,6 +22,8 @@ service NativeBridge {
|
|||||||
rpc SetAbilities(patchbukkit.abilities.SetAbilitiesRequest) returns (patchbukkit.abilities.SetAbilitiesResponse);
|
rpc SetAbilities(patchbukkit.abilities.SetAbilitiesRequest) returns (patchbukkit.abilities.SetAbilitiesResponse);
|
||||||
|
|
||||||
rpc GetEntityHealth(patchbukkit.common.UUID) returns (patchbukkit.entity.EntityHealthResponse);
|
rpc GetEntityHealth(patchbukkit.common.UUID) returns (patchbukkit.entity.EntityHealthResponse);
|
||||||
|
rpc GetEntityId(patchbukkit.common.UUID) returns (patchbukkit.entity.GetEntityIdResponse);
|
||||||
|
rpc GetEntityUuid(patchbukkit.entity.GetEntityUuidRequest) returns (patchbukkit.entity.GetEntityUuidResponse);
|
||||||
rpc SetEntityHealth(patchbukkit.entity.SetEntityHealthRequest) returns (google.protobuf.Empty);
|
rpc SetEntityHealth(patchbukkit.entity.SetEntityHealthRequest) returns (google.protobuf.Empty);
|
||||||
rpc DamageEntity(patchbukkit.entity.DamageEntityRequest) returns (google.protobuf.Empty);
|
rpc DamageEntity(patchbukkit.entity.DamageEntityRequest) returns (google.protobuf.Empty);
|
||||||
rpc GetEntityVelocity(patchbukkit.common.UUID) returns (patchbukkit.entity.EntityVelocityResponse);
|
rpc GetEntityVelocity(patchbukkit.common.UUID) returns (patchbukkit.entity.EntityVelocityResponse);
|
||||||
|
|||||||
@ -231,6 +231,14 @@ message GetCooldownResponse {
|
|||||||
bool is_on_cooldown = 2;
|
bool is_on_cooldown = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message GetEntityIdResponse {
|
||||||
|
int32 entity_id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetEntityUuidRequest {
|
||||||
|
int32 entity_id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetEntityUuidResponse {
|
||||||
|
patchbukkit.common.UUID uuid = 1;
|
||||||
|
}
|
||||||
|
|||||||
@ -279,6 +279,7 @@ message PlayerItemMendEvent {
|
|||||||
message PlayerJoinEvent {
|
message PlayerJoinEvent {
|
||||||
patchbukkit.common.UUID player_uuid = 1;
|
patchbukkit.common.UUID player_uuid = 1;
|
||||||
string join_message = 2;
|
string join_message = 2;
|
||||||
|
int32 entity_id = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message PlayerKickEvent {
|
message PlayerKickEvent {
|
||||||
|
|||||||
@ -154,6 +154,7 @@ message EntitySummaryProto {
|
|||||||
float pitch = 7;
|
float pitch = 7;
|
||||||
bool is_player = 8;
|
bool is_player = 8;
|
||||||
string custom_name = 9;
|
string custom_name = 9;
|
||||||
|
int32 entity_id = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetWorldEntitiesRequest {
|
message GetWorldEntitiesRequest {
|
||||||
@ -177,6 +178,7 @@ message SpawnWorldEntityRequest {
|
|||||||
message SpawnWorldEntityResponse {
|
message SpawnWorldEntityResponse {
|
||||||
patchbukkit.common.UUID entity_uuid = 1;
|
patchbukkit.common.UUID entity_uuid = 1;
|
||||||
bool success = 2;
|
bool success = 2;
|
||||||
|
int32 entity_id = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message CreateWorldExplosionRequest {
|
message CreateWorldExplosionRequest {
|
||||||
|
|||||||
212
rust/Cargo.lock
generated
212
rust/Cargo.lock
generated
@ -265,9 +265,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-compression"
|
name = "async-compression"
|
||||||
version = "0.4.44"
|
version = "0.4.48"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "515a1f282e33d55983c499d7e9e87082e81cbc32974825bf9032f928392d5844"
|
checksum = "fb61aea1a7def73ee7c350a184f0e70b32c182344e2e75bf70c9b621b83417fd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"compression-codecs",
|
"compression-codecs",
|
||||||
"compression-core",
|
"compression-core",
|
||||||
@ -400,9 +400,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.13.1"
|
version = "2.13.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
|
checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
@ -685,9 +685,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "compression-codecs"
|
name = "compression-codecs"
|
||||||
version = "0.4.39"
|
version = "0.4.43"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2fe67f2944eef52fc7b106b8c9450d243a88701a0c065f7f57235e76abaed7df"
|
checksum = "bef16c47ba2797aa6a909cc37d39911f3a6743811fe7408ac0b0cc0276b656e9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"compression-core",
|
"compression-core",
|
||||||
"flate2",
|
"flate2",
|
||||||
@ -1788,9 +1788,9 @@ checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "granit-parser"
|
name = "granit-parser"
|
||||||
version = "1.2.0"
|
version = "1.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "65ec0d45986cd51c847c75c5b69a00852c4fc84d0e5e79f041173f73437d0cdf"
|
checksum = "e20f99e46474f56bd905c56e817ebddcf377a611f94c53ac4649e4d3fa3c0cd0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arraydeque",
|
"arraydeque",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
@ -2188,7 +2188,7 @@ version = "0.11.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4cc00ea907cab49550b7da656f80ebb97be1b997d931fbcd28d39734e17ce592"
|
checksum = "4cc00ea907cab49550b7da656f80ebb97be1b997d931fbcd28d39734e17ce592"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.1",
|
"bitflags 2.13.2",
|
||||||
"inotify-sys",
|
"inotify-sys",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
@ -2412,7 +2412,7 @@ version = "1.1.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087"
|
checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.1",
|
"bitflags 2.13.2",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -2690,7 +2690,7 @@ version = "0.31.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
|
checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.1",
|
"bitflags 2.13.2",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cfg_aliases",
|
"cfg_aliases",
|
||||||
"libc",
|
"libc",
|
||||||
@ -2719,7 +2719,7 @@ version = "8.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3"
|
checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.1",
|
"bitflags 2.13.2",
|
||||||
"fsevent-sys",
|
"fsevent-sys",
|
||||||
"inotify",
|
"inotify",
|
||||||
"kqueue",
|
"kqueue",
|
||||||
@ -2737,7 +2737,7 @@ version = "2.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a"
|
checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.1",
|
"bitflags 2.13.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2828,7 +2828,7 @@ version = "0.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
|
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.1",
|
"bitflags 2.13.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2972,6 +2972,7 @@ name = "patchbukkit"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"arc-swap",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"glob",
|
"glob",
|
||||||
"jni",
|
"jni",
|
||||||
@ -2990,10 +2991,12 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde-saphyr",
|
"serde-saphyr",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"tempfile",
|
||||||
"time",
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml",
|
"toml",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"tracing-subscriber",
|
||||||
"uuid",
|
"uuid",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
"zip",
|
"zip",
|
||||||
@ -3145,7 +3148,7 @@ version = "0.18.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
|
checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.1",
|
"bitflags 2.13.2",
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
"fdeflate",
|
"fdeflate",
|
||||||
"flate2",
|
"flate2",
|
||||||
@ -3457,15 +3460,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pumpkin"
|
name = "pumpkin"
|
||||||
version = "0.1.0-dev+26.2-26.45"
|
version = "0.1.0+26.2-26.45"
|
||||||
source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#55762a50d21ecaff480c95f1ac4017342b017d02"
|
source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#844e2871e2091c14b9991cb6534e97a6c8e09480"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes 0.9.3",
|
"aes 0.9.3",
|
||||||
"arc-swap",
|
"arc-swap",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"axum",
|
"axum",
|
||||||
"base64 0.23.1",
|
"base64 0.23.1",
|
||||||
"bitflags 2.13.1",
|
"bitflags 2.13.2",
|
||||||
"bytes",
|
"bytes",
|
||||||
"colored",
|
"colored",
|
||||||
"crossbeam",
|
"crossbeam",
|
||||||
@ -3484,10 +3487,12 @@ dependencies = [
|
|||||||
"num-bigint 0.5.1",
|
"num-bigint 0.5.1",
|
||||||
"ordered-float",
|
"ordered-float",
|
||||||
"postcard",
|
"postcard",
|
||||||
"pumpkin-codecs",
|
"pumpkin-auth",
|
||||||
|
"pumpkin-command",
|
||||||
"pumpkin-config",
|
"pumpkin-config",
|
||||||
"pumpkin-data",
|
"pumpkin-data",
|
||||||
"pumpkin-gametest",
|
"pumpkin-gametest",
|
||||||
|
"pumpkin-host-bindings",
|
||||||
"pumpkin-inventory",
|
"pumpkin-inventory",
|
||||||
"pumpkin-macros",
|
"pumpkin-macros",
|
||||||
"pumpkin-nbt",
|
"pumpkin-nbt",
|
||||||
@ -3523,12 +3528,13 @@ dependencies = [
|
|||||||
"wasmtime-wasi",
|
"wasmtime-wasi",
|
||||||
"wasmtime-wasi-http",
|
"wasmtime-wasi-http",
|
||||||
"webrtc",
|
"webrtc",
|
||||||
|
"xxhash-rust",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pumpkin-api-macros"
|
name = "pumpkin-api-macros"
|
||||||
version = "0.1.0-dev+26.2-26.45"
|
version = "0.1.0+26.2-26.45"
|
||||||
source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#55762a50d21ecaff480c95f1ac4017342b017d02"
|
source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#844e2871e2091c14b9991cb6534e97a6c8e09480"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro-error2",
|
"proc-macro-error2",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@ -3536,19 +3542,56 @@ dependencies = [
|
|||||||
"syn 3.0.5",
|
"syn 3.0.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pumpkin-auth"
|
||||||
|
version = "0.1.0+26.2-26.45"
|
||||||
|
source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#844e2871e2091c14b9991cb6534e97a6c8e09480"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.23.1",
|
||||||
|
"crypto-bigint 0.7.5",
|
||||||
|
"ecdsa 0.17.0",
|
||||||
|
"md5",
|
||||||
|
"p384 0.14.0",
|
||||||
|
"reqwest",
|
||||||
|
"rsa",
|
||||||
|
"rustls",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"sha2 0.11.0",
|
||||||
|
"thiserror 2.0.20",
|
||||||
|
"uuid",
|
||||||
|
"webpki-root-certs",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pumpkin-codecs"
|
name = "pumpkin-codecs"
|
||||||
version = "0.1.0-dev+26.2-26.45"
|
version = "0.1.0+26.2-26.45"
|
||||||
source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#55762a50d21ecaff480c95f1ac4017342b017d02"
|
source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#844e2871e2091c14b9991cb6534e97a6c8e09480"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pumpkin-command"
|
||||||
|
version = "0.1.0+26.2-26.45"
|
||||||
|
source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#844e2871e2091c14b9991cb6534e97a6c8e09480"
|
||||||
|
dependencies = [
|
||||||
|
"pumpkin-codecs",
|
||||||
|
"pumpkin-data",
|
||||||
|
"pumpkin-nbt",
|
||||||
|
"pumpkin-protocol",
|
||||||
|
"pumpkin-util",
|
||||||
|
"rustc-hash",
|
||||||
|
"serde_json",
|
||||||
|
"tracing",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pumpkin-config"
|
name = "pumpkin-config"
|
||||||
version = "0.1.0-dev+26.2-26.45"
|
version = "0.1.0+26.2-26.45"
|
||||||
source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#55762a50d21ecaff480c95f1ac4017342b017d02"
|
source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#844e2871e2091c14b9991cb6534e97a6c8e09480"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pumpkin-util",
|
"pumpkin-util",
|
||||||
"serde",
|
"serde",
|
||||||
@ -3559,8 +3602,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pumpkin-data"
|
name = "pumpkin-data"
|
||||||
version = "0.1.0-dev+26.2-26.45"
|
version = "0.1.0+26.2-26.45"
|
||||||
source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#55762a50d21ecaff480c95f1ac4017342b017d02"
|
source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#844e2871e2091c14b9991cb6534e97a6c8e09480"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crc-fast",
|
"crc-fast",
|
||||||
"phf",
|
"phf",
|
||||||
@ -3572,8 +3615,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pumpkin-gametest"
|
name = "pumpkin-gametest"
|
||||||
version = "0.1.0-dev+26.2-26.45"
|
version = "0.1.0+26.2-26.45"
|
||||||
source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#55762a50d21ecaff480c95f1ac4017342b017d02"
|
source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#844e2871e2091c14b9991cb6534e97a6c8e09480"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"pumpkin-data",
|
"pumpkin-data",
|
||||||
@ -3586,16 +3629,23 @@ dependencies = [
|
|||||||
"thiserror 2.0.20",
|
"thiserror 2.0.20",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pumpkin-host-bindings"
|
||||||
|
version = "0.1.0+26.2-26.45"
|
||||||
|
source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#844e2871e2091c14b9991cb6534e97a6c8e09480"
|
||||||
|
dependencies = [
|
||||||
|
"wasmtime",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pumpkin-inventory"
|
name = "pumpkin-inventory"
|
||||||
version = "0.1.0-dev+26.2-26.45"
|
version = "0.1.0+26.2-26.45"
|
||||||
source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#55762a50d21ecaff480c95f1ac4017342b017d02"
|
source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#844e2871e2091c14b9991cb6534e97a6c8e09480"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pumpkin-data",
|
"pumpkin-data",
|
||||||
"pumpkin-nbt",
|
"pumpkin-nbt",
|
||||||
"pumpkin-protocol",
|
"pumpkin-protocol",
|
||||||
"pumpkin-util",
|
"pumpkin-util",
|
||||||
"pumpkin-world",
|
|
||||||
"rand 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rand 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.20",
|
||||||
@ -3604,21 +3654,20 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pumpkin-macros"
|
name = "pumpkin-macros"
|
||||||
version = "0.1.0-dev+26.2-26.45"
|
version = "0.1.0+26.2-26.45"
|
||||||
source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#55762a50d21ecaff480c95f1ac4017342b017d02"
|
source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#844e2871e2091c14b9991cb6534e97a6c8e09480"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro-error2",
|
"proc-macro-error2",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"pumpkin-data",
|
|
||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.5",
|
"syn 3.0.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pumpkin-nbt"
|
name = "pumpkin-nbt"
|
||||||
version = "0.1.0-dev+26.2-26.45"
|
version = "0.1.0+26.2-26.45"
|
||||||
source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#55762a50d21ecaff480c95f1ac4017342b017d02"
|
source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#844e2871e2091c14b9991cb6534e97a6c8e09480"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"cesu8",
|
"cesu8",
|
||||||
@ -3631,8 +3680,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pumpkin-plugin-runtime"
|
name = "pumpkin-plugin-runtime"
|
||||||
version = "0.1.0-dev+26.2-26.45"
|
version = "0.1.0+26.2-26.45"
|
||||||
source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#55762a50d21ecaff480c95f1ac4017342b017d02"
|
source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#844e2871e2091c14b9991cb6534e97a6c8e09480"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures",
|
"futures",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.20",
|
||||||
@ -3643,12 +3692,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pumpkin-protocol"
|
name = "pumpkin-protocol"
|
||||||
version = "0.1.0-dev+26.2-26.45"
|
version = "0.1.0+26.2-26.45"
|
||||||
source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#55762a50d21ecaff480c95f1ac4017342b017d02"
|
source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#844e2871e2091c14b9991cb6534e97a6c8e09480"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes 0.9.3",
|
"aes 0.9.3",
|
||||||
"async-compression",
|
"async-compression",
|
||||||
"bitflags 2.13.1",
|
"base64 0.23.1",
|
||||||
|
"bitflags 2.13.2",
|
||||||
"bytes",
|
"bytes",
|
||||||
"cesu8",
|
"cesu8",
|
||||||
"cfb8",
|
"cfb8",
|
||||||
@ -3658,50 +3708,39 @@ dependencies = [
|
|||||||
"pumpkin-macros",
|
"pumpkin-macros",
|
||||||
"pumpkin-nbt",
|
"pumpkin-nbt",
|
||||||
"pumpkin-util",
|
"pumpkin-util",
|
||||||
"pumpkin-world",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.20",
|
||||||
"tokio",
|
"tokio",
|
||||||
"uuid",
|
"uuid",
|
||||||
"xxhash-rust",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pumpkin-util"
|
name = "pumpkin-util"
|
||||||
version = "0.1.0-dev+26.2-26.45"
|
version = "0.1.0+26.2-26.45"
|
||||||
source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#55762a50d21ecaff480c95f1ac4017342b017d02"
|
source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#844e2871e2091c14b9991cb6534e97a6c8e09480"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.23.1",
|
|
||||||
"bytes",
|
"bytes",
|
||||||
"colored",
|
"colored",
|
||||||
"crypto-bigint 0.7.5",
|
|
||||||
"dashmap",
|
"dashmap",
|
||||||
"ecdsa 0.17.0",
|
|
||||||
"md5",
|
"md5",
|
||||||
"num-derive",
|
"num-derive",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"p384 0.14.0",
|
|
||||||
"pumpkin-codecs",
|
"pumpkin-codecs",
|
||||||
"pumpkin-nbt",
|
"pumpkin-nbt",
|
||||||
"reqwest",
|
|
||||||
"rsa",
|
|
||||||
"rustls",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2 0.11.0",
|
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.20",
|
||||||
"uuid",
|
"uuid",
|
||||||
"webpki-root-certs",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pumpkin-world"
|
name = "pumpkin-world"
|
||||||
version = "0.1.0-dev+26.2-26.45"
|
version = "0.1.0+26.2-26.45"
|
||||||
source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#55762a50d21ecaff480c95f1ac4017342b017d02"
|
source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#844e2871e2091c14b9991cb6534e97a6c8e09480"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arc-swap",
|
"arc-swap",
|
||||||
"bitflags 2.13.1",
|
"bitflags 2.13.2",
|
||||||
"bytes",
|
"bytes",
|
||||||
"crossbeam",
|
"crossbeam",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
@ -3848,7 +3887,7 @@ version = "0.5.18"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.1",
|
"bitflags 2.13.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3928,11 +3967,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.13.4"
|
version = "0.13.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3"
|
checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.23.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"h2",
|
"h2",
|
||||||
@ -4403,7 +4442,7 @@ version = "1.1.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.1",
|
"bitflags 2.13.2",
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
@ -4506,7 +4545,7 @@ version = "18.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "53f6a737db68eb1a8ccff86b584b2fc13eca6a7bb6f78ebc7c529547e3ab9684"
|
checksum = "53f6a737db68eb1a8ccff86b584b2fc13eca6a7bb6f78ebc7c529547e3ab9684"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.1",
|
"bitflags 2.13.2",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"clipboard-win",
|
"clipboard-win",
|
||||||
"libc",
|
"libc",
|
||||||
@ -4589,7 +4628,7 @@ version = "3.7.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
|
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.1",
|
"bitflags 2.13.2",
|
||||||
"core-foundation",
|
"core-foundation",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
"libc",
|
"libc",
|
||||||
@ -4628,9 +4667,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde-saphyr"
|
name = "serde-saphyr"
|
||||||
version = "1.2.0"
|
version = "1.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3afb591f9cdb6223c88ba39269aff895620c7f0716dc42b705b5733d5c7c0823"
|
checksum = "b8050abb251097357e24aff63ba2c52a6309ecb7d23a5474023df960a02694d8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"annotate-snippets",
|
"annotate-snippets",
|
||||||
"base64 0.23.1",
|
"base64 0.23.1",
|
||||||
@ -4997,6 +5036,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fastrand",
|
"fastrand",
|
||||||
|
"getrandom 0.4.3",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
@ -5230,7 +5270,7 @@ version = "0.6.11"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
|
checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.1",
|
"bitflags 2.13.2",
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http",
|
||||||
@ -5283,6 +5323,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
|
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
"valuable",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-log"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"tracing-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -5308,10 +5360,12 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"regex-automata",
|
"regex-automata",
|
||||||
"sharded-slab",
|
"sharded-slab",
|
||||||
|
"smallvec",
|
||||||
"thread_local",
|
"thread_local",
|
||||||
"time",
|
"time",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-core",
|
"tracing-core",
|
||||||
|
"tracing-log",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -5408,9 +5462,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.26.0"
|
version = "1.26.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812"
|
checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.4.3",
|
"getrandom 0.4.3",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
@ -5419,6 +5473,12 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "valuable"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
version = "0.9.5"
|
version = "0.9.5"
|
||||||
@ -5533,7 +5593,7 @@ version = "0.258.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d9a61719f93a87b16d325921e251800c4833f8fab50fa21c7de73aed50086313"
|
checksum = "d9a61719f93a87b16d325921e251800c4833f8fab50fa21c7de73aed50086313"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.1",
|
"bitflags 2.13.2",
|
||||||
"hashbrown 0.17.1",
|
"hashbrown 0.17.1",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"semver",
|
"semver",
|
||||||
@ -5558,7 +5618,7 @@ source = "git+https://github.com/bytecodealliance/wasmtime?rev=f6b3140dbb1d9b436
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"addr2line",
|
"addr2line",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"bitflags 2.13.1",
|
"bitflags 2.13.2",
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"bytes",
|
"bytes",
|
||||||
"cc",
|
"cc",
|
||||||
@ -5755,7 +5815,7 @@ version = "49.0.0-dev"
|
|||||||
source = "git+https://github.com/bytecodealliance/wasmtime?rev=f6b3140dbb1d9b436ef523d14740292c956b0364#f6b3140dbb1d9b436ef523d14740292c956b0364"
|
source = "git+https://github.com/bytecodealliance/wasmtime?rev=f6b3140dbb1d9b436ef523d14740292c956b0364#f6b3140dbb1d9b436ef523d14740292c956b0364"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bitflags 2.13.1",
|
"bitflags 2.13.2",
|
||||||
"heck",
|
"heck",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"wit-component",
|
"wit-component",
|
||||||
@ -5768,7 +5828,7 @@ version = "49.0.0-dev"
|
|||||||
source = "git+https://github.com/bytecodealliance/wasmtime?rev=f6b3140dbb1d9b436ef523d14740292c956b0364#f6b3140dbb1d9b436ef523d14740292c956b0364"
|
source = "git+https://github.com/bytecodealliance/wasmtime?rev=f6b3140dbb1d9b436ef523d14740292c956b0364#f6b3140dbb1d9b436ef523d14740292c956b0364"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"bitflags 2.13.1",
|
"bitflags 2.13.2",
|
||||||
"bytes",
|
"bytes",
|
||||||
"cap-primitives",
|
"cap-primitives",
|
||||||
"futures",
|
"futures",
|
||||||
@ -6185,7 +6245,7 @@ version = "0.36.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d"
|
checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.1",
|
"bitflags 2.13.2",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -6196,7 +6256,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "481b5c47b2ecce0389b5e08a05557d6a190c9cd761773b8880a8017ee04dc7ef"
|
checksum = "481b5c47b2ecce0389b5e08a05557d6a190c9cd761773b8880a8017ee04dc7ef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bitflags 2.13.1",
|
"bitflags 2.13.2",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"log",
|
"log",
|
||||||
"serde",
|
"serde",
|
||||||
|
|||||||
@ -8,7 +8,7 @@ description = "Compatibility layer for running Bukkit, Spigot, and Paper plugins
|
|||||||
build = "build/main.rs"
|
build = "build/main.rs"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
pumpkin = { git = "https://github.com/Pumpkin-MC/Pumpkin.git", branch = "master", package = "pumpkin" }
|
pumpkin = { git = "https://github.com/Pumpkin-MC/Pumpkin.git", branch = "master", package = "pumpkin" }
|
||||||
@ -27,11 +27,11 @@ walkdir = "2"
|
|||||||
zip = "8.6"
|
zip = "8.6"
|
||||||
|
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
uuid = "1.25.0"
|
uuid = "1.26.1"
|
||||||
|
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde-saphyr = "1.1.0"
|
serde-saphyr = "1.3.0"
|
||||||
|
|
||||||
rand = { git = "https://github.com/rust-random/rand"}
|
rand = { git = "https://github.com/rust-random/rand"}
|
||||||
prost = "0.14"
|
prost = "0.14"
|
||||||
@ -39,6 +39,11 @@ time = "0.3"
|
|||||||
|
|
||||||
toml = "1.1"
|
toml = "1.1"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tempfile = "3"
|
||||||
|
arc-swap = "1.9"
|
||||||
|
tracing-subscriber = "0.3"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
env_logger = "0.11.11"
|
env_logger = "0.11.11"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
@ -47,7 +52,8 @@ protoc-bin-vendored = "3"
|
|||||||
glob = "0.3"
|
glob = "0.3"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = false
|
||||||
|
codegen-units = 16
|
||||||
|
|
||||||
[lints.clippy]
|
[lints.clippy]
|
||||||
# Groups
|
# Groups
|
||||||
|
|||||||
@ -5,8 +5,11 @@ pub mod java;
|
|||||||
pub mod protobufs;
|
pub mod protobufs;
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
println!("cargo::rerun-if-changed=rust/src/build.rs");
|
println!("cargo::rerun-if-changed=build/main.rs");
|
||||||
println!("cargo::rerun-if-changed=java/build/libs/");
|
println!("cargo::rerun-if-changed=build/java.rs");
|
||||||
|
println!("cargo::rerun-if-changed=build/protobufs.rs");
|
||||||
|
println!("cargo::rerun-if-changed=../java/patchbukkit/build/libs/patchbukkit.jar");
|
||||||
|
println!("cargo::rerun-if-changed=resources/jassets/");
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
||||||
let base = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
|
let base = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
|
||||||
|
|||||||
@ -3730,6 +3730,7 @@ impl PatchBukkitEvent for pumpkin::plugin::player::player_join::PlayerJoinEvent
|
|||||||
value: self.player.gameprofile.id.to_string(),
|
value: self.player.gameprofile.id.to_string(),
|
||||||
}),
|
}),
|
||||||
join_message: serde_json::to_string(&self.join_message).unwrap_or_default(),
|
join_message: serde_json::to_string(&self.join_message).unwrap_or_default(),
|
||||||
|
entity_id: self.player.living_entity.entity.entity_id,
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
context: EventContext {
|
context: EventContext {
|
||||||
|
|||||||
@ -296,9 +296,17 @@ impl JvmWorker {
|
|||||||
.build()
|
.build()
|
||||||
.map_err(|e| anyhow::anyhow!("Failed to build JVM init args: {e:?}"))?;
|
.map_err(|e| anyhow::anyhow!("Failed to build JVM init args: {e:?}"))?;
|
||||||
|
|
||||||
let jvm =
|
let jvm = match JavaVM::new(jvm_args) {
|
||||||
JavaVM::new(jvm_args).map_err(|e| anyhow::anyhow!("Failed to create JavaVM: {e:?}"))?;
|
Ok(jvm) => Arc::new(jvm),
|
||||||
let jvm = Arc::new(jvm);
|
Err(e) => {
|
||||||
|
if let Ok(existing) = JavaVM::singleton() {
|
||||||
|
tracing::info!("Reusing existing JavaVM in process");
|
||||||
|
Arc::new(existing)
|
||||||
|
} else {
|
||||||
|
return Err(anyhow::anyhow!("Failed to create JavaVM: {e:?}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
jvm.attach_current_thread(|env| -> anyhow::Result<()> {
|
jvm.attach_current_thread(|env| -> anyhow::Result<()> {
|
||||||
initialize_callbacks(env).map_err(|e| {
|
initialize_callbacks(env).map_err(|e| {
|
||||||
|
|||||||
@ -8,6 +8,7 @@ use crate::{
|
|||||||
common::Uuid,
|
common::Uuid,
|
||||||
entity::{
|
entity::{
|
||||||
DamageEntityRequest, EntityHealthResponse, GetCooldownRequest, GetCooldownResponse,
|
DamageEntityRequest, EntityHealthResponse, GetCooldownRequest, GetCooldownResponse,
|
||||||
|
GetEntityIdResponse, GetEntityUuidRequest, GetEntityUuidResponse,
|
||||||
GetExperienceResponse, GetFoodLevelResponse, GetPlayerPoseStateResponse,
|
GetExperienceResponse, GetFoodLevelResponse, GetPlayerPoseStateResponse,
|
||||||
KickPlayerRequest, PlayerConnectionInfoResponse, SendActionBarRequest,
|
KickPlayerRequest, PlayerConnectionInfoResponse, SendActionBarRequest,
|
||||||
SendBlockChangeRequest, SendGameEventRequest, SendResourcePackRequest,
|
SendBlockChangeRequest, SendGameEventRequest, SendResourcePackRequest,
|
||||||
@ -32,6 +33,54 @@ pub fn ffi_native_bridge_get_entity_health_impl(request: Uuid) -> Option<EntityH
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ffi_native_bridge_get_entity_id_impl(request: Uuid) -> Option<GetEntityIdResponse> {
|
||||||
|
if let Some(id) = with_player(Some(&request), |player| {
|
||||||
|
player.living_entity.entity.entity_id
|
||||||
|
}) {
|
||||||
|
return Some(GetEntityIdResponse { entity_id: id });
|
||||||
|
}
|
||||||
|
|
||||||
|
let ctx = CALLBACK_CONTEXT.get()?;
|
||||||
|
let uuid_str = &request.value;
|
||||||
|
let uuid = uuid::Uuid::parse_str(uuid_str).ok()?;
|
||||||
|
|
||||||
|
for world in ctx.plugin_context.server.worlds.load().iter() {
|
||||||
|
if let Some(entity) = world.get_entity_by_uuid(uuid) {
|
||||||
|
return Some(GetEntityIdResponse {
|
||||||
|
entity_id: entity.get_entity().entity_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ffi_native_bridge_get_entity_uuid_impl(
|
||||||
|
request: GetEntityUuidRequest,
|
||||||
|
) -> Option<GetEntityUuidResponse> {
|
||||||
|
let ctx = CALLBACK_CONTEXT.get()?;
|
||||||
|
let id = request.entity_id;
|
||||||
|
|
||||||
|
for world in ctx.plugin_context.server.worlds.load().iter() {
|
||||||
|
if let Some(player) = world.get_player_by_id(id) {
|
||||||
|
return Some(GetEntityUuidResponse {
|
||||||
|
uuid: Some(Uuid {
|
||||||
|
value: player.gameprofile.id.to_string(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if let Some(entity) = world.get_entity_by_id(id) {
|
||||||
|
return Some(GetEntityUuidResponse {
|
||||||
|
uuid: Some(Uuid {
|
||||||
|
value: entity.get_entity().entity_uuid.to_string(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
pub fn ffi_native_bridge_set_entity_health_impl(request: SetEntityHealthRequest) -> Option<()> {
|
pub fn ffi_native_bridge_set_entity_health_impl(request: SetEntityHealthRequest) -> Option<()> {
|
||||||
with_player(request.uuid.as_ref(), |player| {
|
with_player(request.uuid.as_ref(), |player| {
|
||||||
let health = request.health as f32;
|
let health = request.health as f32;
|
||||||
|
|||||||
@ -2253,7 +2253,8 @@ pub fn ffi_native_bridge_register_event_impl(request: RegisterEventRequest) -> O
|
|||||||
request.blocking,
|
request.blocking,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
"org.bukkit.event.player.PlayerPreLoginEvent" => {
|
"org.bukkit.event.player.PlayerPreLoginEvent"
|
||||||
|
| "io.papermc.paper.event.connection.PlayerConnectionValidateLoginEvent" => {
|
||||||
plugin_context
|
plugin_context
|
||||||
.register_event::<
|
.register_event::<
|
||||||
pumpkin::plugin::player::player_pre_login::PlayerPreLoginEvent,
|
pumpkin::plugin::player::player_pre_login::PlayerPreLoginEvent,
|
||||||
@ -3754,6 +3755,16 @@ pub fn ffi_native_bridge_register_event_impl(request: RegisterEventRequest) -> O
|
|||||||
request.plugin_name
|
request.plugin_name
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
name if name.starts_with("io.papermc.paper.event.")
|
||||||
|
|| name.starts_with("com.destroystokyo.paper.event.")
|
||||||
|
|| name.starts_with("org.spigotmc.event.") =>
|
||||||
|
{
|
||||||
|
tracing::info!(
|
||||||
|
"Registered Paper/Spigot event listener '{}' from plugin '{}'",
|
||||||
|
request.event_type,
|
||||||
|
request.plugin_name
|
||||||
|
);
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
"Unknown or unhandled Bukkit event type '{}' from plugin '{}'",
|
"Unknown or unhandled Bukkit event type '{}' from plugin '{}'",
|
||||||
|
|||||||
@ -51,8 +51,8 @@ pub fn ffi_native_bridge_get_server_info_impl(
|
|||||||
Some(ServerInfoResponse {
|
Some(ServerInfoResponse {
|
||||||
server_name: "Pumpkin".to_string(),
|
server_name: "Pumpkin".to_string(),
|
||||||
version: "0.1.0".to_string(),
|
version: "0.1.0".to_string(),
|
||||||
bukkit_version: "1.21.4-R0.1-SNAPSHOT".to_string(),
|
bukkit_version: format!("{}-R0.1-SNAPSHOT", pumpkin_data::packet::CURRENT_MC_VERSION),
|
||||||
minecraft_version: "1.21.4".to_string(),
|
minecraft_version: pumpkin_data::packet::CURRENT_MC_VERSION.to_string(),
|
||||||
motd,
|
motd,
|
||||||
ip,
|
ip,
|
||||||
port,
|
port,
|
||||||
|
|||||||
@ -62,8 +62,8 @@ pub fn ffi_native_bridge_player_play_sound_impl(request: PlayerPlaySoundRequest)
|
|||||||
let category = SoundCategory::from_name(&sound.category.to_lowercase())?;
|
let category = SoundCategory::from_name(&sound.category.to_lowercase())?;
|
||||||
|
|
||||||
let seed = match request.seed {
|
let seed = match request.seed {
|
||||||
Some(seed) => seed as f64,
|
Some(seed) => seed,
|
||||||
None => rng().random::<f64>(),
|
None => rng().random::<i64>(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let position = request.location?.position?;
|
let position = request.location?.position?;
|
||||||
|
|||||||
@ -423,6 +423,7 @@ pub fn ffi_native_bridge_get_world_entities_impl(
|
|||||||
pitch: p.living_entity.entity.pitch.load(),
|
pitch: p.living_entity.entity.pitch.load(),
|
||||||
is_player: true,
|
is_player: true,
|
||||||
custom_name: p.gameprofile.name.clone(),
|
custom_name: p.gameprofile.name.clone(),
|
||||||
|
entity_id: p.living_entity.entity.entity_id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -441,6 +442,7 @@ pub fn ffi_native_bridge_get_world_entities_impl(
|
|||||||
pitch: base.pitch.load(),
|
pitch: base.pitch.load(),
|
||||||
is_player: false,
|
is_player: false,
|
||||||
custom_name: String::new(),
|
custom_name: String::new(),
|
||||||
|
entity_id: base.entity_id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -464,21 +466,23 @@ pub fn ffi_native_bridge_spawn_world_entity_impl(
|
|||||||
let new_uuid = uuid::Uuid::new_v4();
|
let new_uuid = uuid::Uuid::new_v4();
|
||||||
let pos = Vector3::new(request.x, request.y, request.z);
|
let pos = Vector3::new(request.x, request.y, request.z);
|
||||||
|
|
||||||
|
let entity_type: &'static pumpkin_data::entity::EntityType =
|
||||||
|
match request.entity_type.to_uppercase().as_str() {
|
||||||
|
"LIGHTNING_BOLT" | "LIGHTNING" => &pumpkin_data::entity::EntityType::LIGHTNING_BOLT,
|
||||||
|
"ITEM" | "DROPPED_ITEM" => &pumpkin_data::entity::EntityType::ITEM,
|
||||||
|
"ZOMBIE" => &pumpkin_data::entity::EntityType::ZOMBIE,
|
||||||
|
"SKELETON" => &pumpkin_data::entity::EntityType::SKELETON,
|
||||||
|
"CREEPER" => &pumpkin_data::entity::EntityType::CREEPER,
|
||||||
|
"COW" => &pumpkin_data::entity::EntityType::COW,
|
||||||
|
"PIG" => &pumpkin_data::entity::EntityType::PIG,
|
||||||
|
"SHEEP" => &pumpkin_data::entity::EntityType::SHEEP,
|
||||||
|
_ => &pumpkin_data::entity::EntityType::PIG,
|
||||||
|
};
|
||||||
|
let entity = pumpkin::entity::r#type::from_type(entity_type, pos, &world, new_uuid);
|
||||||
|
let entity_id = entity.get_entity().entity_id;
|
||||||
|
|
||||||
let w = world.clone();
|
let w = world.clone();
|
||||||
ctx.runtime.spawn(async move {
|
ctx.runtime.spawn(async move {
|
||||||
let entity_type: &'static pumpkin_data::entity::EntityType =
|
|
||||||
match request.entity_type.to_uppercase().as_str() {
|
|
||||||
"LIGHTNING_BOLT" | "LIGHTNING" => &pumpkin_data::entity::EntityType::LIGHTNING_BOLT,
|
|
||||||
"ITEM" | "DROPPED_ITEM" => &pumpkin_data::entity::EntityType::ITEM,
|
|
||||||
"ZOMBIE" => &pumpkin_data::entity::EntityType::ZOMBIE,
|
|
||||||
"SKELETON" => &pumpkin_data::entity::EntityType::SKELETON,
|
|
||||||
"CREEPER" => &pumpkin_data::entity::EntityType::CREEPER,
|
|
||||||
"COW" => &pumpkin_data::entity::EntityType::COW,
|
|
||||||
"PIG" => &pumpkin_data::entity::EntityType::PIG,
|
|
||||||
"SHEEP" => &pumpkin_data::entity::EntityType::SHEEP,
|
|
||||||
_ => &pumpkin_data::entity::EntityType::PIG,
|
|
||||||
};
|
|
||||||
let entity = pumpkin::entity::r#type::from_type(entity_type, pos, &w, new_uuid);
|
|
||||||
w.spawn_entity(entity);
|
w.spawn_entity(entity);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -487,6 +491,7 @@ pub fn ffi_native_bridge_spawn_world_entity_impl(
|
|||||||
value: new_uuid.to_string(),
|
value: new_uuid.to_string(),
|
||||||
}),
|
}),
|
||||||
success: true,
|
success: true,
|
||||||
|
entity_id,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -77,15 +77,17 @@ impl EventManager {
|
|||||||
|
|
||||||
let uuid_jstr = env.new_string(player.gameprofile.id.to_string())?;
|
let uuid_jstr = env.new_string(player.gameprofile.id.to_string())?;
|
||||||
let name_jstr = env.new_string(&player.gameprofile.name)?;
|
let name_jstr = env.new_string(&player.gameprofile.name)?;
|
||||||
|
let entity_id = player.living_entity.entity.entity_id;
|
||||||
|
|
||||||
env.call_static_method(
|
env.call_static_method(
|
||||||
jni::jni_str!("org/patchbukkit/PatchBukkitServer"),
|
jni::jni_str!("org/patchbukkit/PatchBukkitServer"),
|
||||||
jni::jni_str!("registerPlayer"),
|
jni::jni_str!("registerPlayer"),
|
||||||
jni::jni_sig!("(Ljava/lang/String;Ljava/lang/String;Z)V"),
|
jni::jni_sig!("(Ljava/lang/String;Ljava/lang/String;ZI)V"),
|
||||||
&[
|
&[
|
||||||
(&uuid_jstr).into(),
|
(&uuid_jstr).into(),
|
||||||
(&name_jstr).into(),
|
(&name_jstr).into(),
|
||||||
jni::objects::JValue::Bool(is_op),
|
jni::objects::JValue::Bool(is_op),
|
||||||
|
jni::objects::JValue::Int(entity_id),
|
||||||
],
|
],
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|||||||
@ -23,7 +23,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
async fn on_load_inner(plugin: &PatchBukkitPlugin, server: Arc<Context>) -> Result<(), String> {
|
pub async fn on_load_inner(plugin: &PatchBukkitPlugin, server: Arc<Context>) -> Result<(), String> {
|
||||||
server.init_log();
|
server.init_log();
|
||||||
tracing::info!("Starting PatchBukkit");
|
tracing::info!("Starting PatchBukkit");
|
||||||
|
|
||||||
@ -124,7 +124,10 @@ async fn on_load_inner(plugin: &PatchBukkitPlugin, server: Arc<Context>) -> Resu
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn on_unload_inner(plugin: &PatchBukkitPlugin, _server: Arc<Context>) -> Result<(), String> {
|
pub async fn on_unload_inner(
|
||||||
|
plugin: &PatchBukkitPlugin,
|
||||||
|
_server: Arc<Context>,
|
||||||
|
) -> Result<(), String> {
|
||||||
{
|
{
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
plugin
|
plugin
|
||||||
|
|||||||
231
rust/tests/pumpkin_server_test.rs
Normal file
231
rust/tests/pumpkin_server_test.rs
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
use patchbukkit::{
|
||||||
|
PatchBukkitPlugin, commands::SimpleCommandSender, directories::setup_directories,
|
||||||
|
java::jvm::commands::JvmCommand,
|
||||||
|
};
|
||||||
|
use pumpkin::{PumpkinServer, data::VanillaData, plugin::Context};
|
||||||
|
use pumpkin_config::{AdvancedConfiguration, BasicConfiguration};
|
||||||
|
use std::{fs, sync::Arc};
|
||||||
|
use tokio::sync::oneshot;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_pumpkin_server_with_patchbukkit_and_plugins() {
|
||||||
|
let _ = tracing_subscriber::fmt().try_init();
|
||||||
|
let temp_dir = tempfile::tempdir().expect("Failed to create tempdir");
|
||||||
|
let prev_dir = std::env::current_dir().expect("Failed to get current dir");
|
||||||
|
std::env::set_current_dir(temp_dir.path()).expect("Failed to set current dir");
|
||||||
|
|
||||||
|
let basic_config = BasicConfiguration {
|
||||||
|
default_level_name: "test_world".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut advanced_config = AdvancedConfiguration::default();
|
||||||
|
advanced_config.networking.java.enabled = false;
|
||||||
|
advanced_config.networking.bedrock.enabled = false;
|
||||||
|
advanced_config.networking.rcon.enabled = false;
|
||||||
|
|
||||||
|
let vanilla_data = VanillaData::load();
|
||||||
|
let telemetry_config = pumpkin_config::TelemetryConfig::default();
|
||||||
|
let pumpkin_server = PumpkinServer::new(
|
||||||
|
basic_config,
|
||||||
|
advanced_config,
|
||||||
|
telemetry_config,
|
||||||
|
vanilla_data,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let metadata = pumpkin::plugin::PluginMetadata {
|
||||||
|
name: "patchbukkit".to_string(),
|
||||||
|
version: "0.1.0".to_string(),
|
||||||
|
authors: vec!["PumpkinMC".to_string()],
|
||||||
|
description: "PatchBukkit".to_string(),
|
||||||
|
dependencies: vec![],
|
||||||
|
permissions: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
let handlers = Arc::new(arc_swap::ArcSwap::from_pointee(
|
||||||
|
std::collections::HashMap::new(),
|
||||||
|
));
|
||||||
|
let context = Arc::new(Context::new(
|
||||||
|
metadata,
|
||||||
|
pumpkin_server.server.clone(),
|
||||||
|
handlers,
|
||||||
|
Arc::clone(&pumpkin_server.server.plugin_manager),
|
||||||
|
Arc::clone(&pumpkin::LOGGER_IMPL),
|
||||||
|
));
|
||||||
|
|
||||||
|
// Setup directories
|
||||||
|
let dirs = setup_directories(&context).expect("Failed to setup PatchBukkit directories");
|
||||||
|
|
||||||
|
// Resolve test plugins built in java/patchbukkit/build/test-plugins
|
||||||
|
let test_plugins_dir = if prev_dir.join("java").exists() {
|
||||||
|
prev_dir
|
||||||
|
.join("java")
|
||||||
|
.join("patchbukkit")
|
||||||
|
.join("build")
|
||||||
|
.join("test-plugins")
|
||||||
|
} else if let Some(parent) = prev_dir.parent() {
|
||||||
|
parent
|
||||||
|
.join("java")
|
||||||
|
.join("patchbukkit")
|
||||||
|
.join("build")
|
||||||
|
.join("test-plugins")
|
||||||
|
} else {
|
||||||
|
std::path::PathBuf::from("java/patchbukkit/build/test-plugins")
|
||||||
|
};
|
||||||
|
|
||||||
|
let packetevents_source = test_plugins_dir.join("PacketEvents.jar");
|
||||||
|
if packetevents_source.exists() {
|
||||||
|
fs::copy(&packetevents_source, dirs.plugins.join("PacketEvents.jar"))
|
||||||
|
.expect("Failed to copy PacketEvents.jar to plugins dir");
|
||||||
|
println!(
|
||||||
|
"Copied PacketEvents.jar to plugins directory: {:?}",
|
||||||
|
dirs.plugins
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let protocollib_source = test_plugins_dir.join("ProtocolLib.jar");
|
||||||
|
if protocollib_source.exists() {
|
||||||
|
fs::copy(&protocollib_source, dirs.plugins.join("ProtocolLib.jar"))
|
||||||
|
.expect("Failed to copy ProtocolLib.jar to plugins dir");
|
||||||
|
println!(
|
||||||
|
"Copied ProtocolLib.jar to plugins directory: {:?}",
|
||||||
|
dirs.plugins
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let grimac_system_source = std::path::PathBuf::from(
|
||||||
|
"/home/alex/Documents/Development/Rust/Pumpkin/plugins/data/patchbukkit/patchbukkit-plugins/grimac-bukkit-2.3.74-61caa53.jar",
|
||||||
|
);
|
||||||
|
if grimac_system_source.exists() {
|
||||||
|
let _ = fs::copy(&grimac_system_source, dirs.plugins.join("GrimAC.jar"));
|
||||||
|
println!("Copied GrimAC.jar to plugins directory: {:?}", dirs.plugins);
|
||||||
|
}
|
||||||
|
|
||||||
|
let plugin = PatchBukkitPlugin::new();
|
||||||
|
|
||||||
|
// Call on_load_inner
|
||||||
|
patchbukkit::on_load_inner(&plugin, context.clone())
|
||||||
|
.await
|
||||||
|
.expect("Failed to on_load PatchBukkit");
|
||||||
|
|
||||||
|
// Wait for PatchBukkit JVM initialization and plugin loading to complete
|
||||||
|
let mut initialized = false;
|
||||||
|
for _ in 0..100 {
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
if plugin
|
||||||
|
.command_tx
|
||||||
|
.send(JvmCommand::TriggerCommand {
|
||||||
|
full_command: "version".to_string(),
|
||||||
|
command_sender: SimpleCommandSender::Console,
|
||||||
|
respond_to: tx,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.is_ok()
|
||||||
|
&& let Ok(res) = rx.await
|
||||||
|
&& res.is_ok()
|
||||||
|
{
|
||||||
|
initialized = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert!(
|
||||||
|
initialized,
|
||||||
|
"PatchBukkit JVM should be initialized and ready"
|
||||||
|
);
|
||||||
|
|
||||||
|
if packetevents_source.exists() {
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
plugin
|
||||||
|
.command_tx
|
||||||
|
.send(JvmCommand::TriggerCommand {
|
||||||
|
full_command: "packetevents".to_string(),
|
||||||
|
command_sender: SimpleCommandSender::Console,
|
||||||
|
respond_to: tx,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.expect("Failed to send packetevents TriggerCommand");
|
||||||
|
|
||||||
|
let pe_res = rx.await.expect("Failed to receive packetevents response");
|
||||||
|
assert!(
|
||||||
|
pe_res.is_ok(),
|
||||||
|
"packetevents command should execute successfully: {:?}",
|
||||||
|
pe_res
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if grimac_system_source.exists() {
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
plugin
|
||||||
|
.command_tx
|
||||||
|
.send(JvmCommand::TriggerCommand {
|
||||||
|
full_command: "grim".to_string(),
|
||||||
|
command_sender: SimpleCommandSender::Console,
|
||||||
|
respond_to: tx,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.expect("Failed to send grim TriggerCommand");
|
||||||
|
|
||||||
|
let grim_res = rx.await.expect("Failed to receive grim response");
|
||||||
|
assert!(
|
||||||
|
grim_res.is_ok(),
|
||||||
|
"grim command should execute successfully: {:?}",
|
||||||
|
grim_res
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test PlayerJoinEvent with GrimAC and PacketEvents
|
||||||
|
let player_uuid = uuid::Uuid::new_v4();
|
||||||
|
let (join_tx, join_rx) = oneshot::channel();
|
||||||
|
plugin
|
||||||
|
.command_tx
|
||||||
|
.send(JvmCommand::FireEvent {
|
||||||
|
payload: patchbukkit::events::handler::JvmEventPayload {
|
||||||
|
event: patchbukkit::proto::patchbukkit::events::Event {
|
||||||
|
data: Some(
|
||||||
|
patchbukkit::proto::patchbukkit::events::event::Data::PlayerJoin(
|
||||||
|
patchbukkit::proto::patchbukkit::events::PlayerJoinEvent {
|
||||||
|
player_uuid: Some(
|
||||||
|
patchbukkit::proto::patchbukkit::common::Uuid {
|
||||||
|
value: player_uuid.to_string(),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
join_message: String::new(),
|
||||||
|
entity_id: 1,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
context: patchbukkit::events::handler::EventContext {
|
||||||
|
server: pumpkin_server.server.clone(),
|
||||||
|
player: None,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugin: "GrimAC".to_string(),
|
||||||
|
respond_to: join_tx,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.expect("Failed to send PlayerJoin JvmCommand");
|
||||||
|
|
||||||
|
let join_res = join_rx
|
||||||
|
.await
|
||||||
|
.expect("Failed to receive PlayerJoin response");
|
||||||
|
assert!(
|
||||||
|
!join_res.cancelled,
|
||||||
|
"PlayerJoin should not be cancelled: {:?}",
|
||||||
|
join_res
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call on_unload_inner to gracefully shut down
|
||||||
|
patchbukkit::on_unload_inner(&plugin, context.clone())
|
||||||
|
.await
|
||||||
|
.expect("Failed to on_unload PatchBukkit");
|
||||||
|
|
||||||
|
// Restore working directory
|
||||||
|
let _ = std::env::set_current_dir(prev_dir);
|
||||||
|
|
||||||
|
tokio::task::spawn_blocking(move || drop(plugin))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user