diff --git a/bukkit/build.gradle.kts b/bukkit/build.gradle.kts index f41c76ccc..ac2d03e6a 100644 --- a/bukkit/build.gradle.kts +++ b/bukkit/build.gradle.kts @@ -39,6 +39,7 @@ dependencies { compileOnly("io.netty:netty-all:${rootProject.properties["netty_version"]}") // ByteBuddy compileOnly("net.bytebuddy:byte-buddy:${rootProject.properties["byte_buddy_version"]}") + compileOnly("net.bytebuddy:byte-buddy-agent:${rootProject.properties["byte_buddy_version"]}") // Command compileOnly("org.incendo:cloud-core:${rootProject.properties["cloud_core_version"]}") compileOnly("org.incendo:cloud-minecraft-extras:${rootProject.properties["cloud_minecraft_extras_version"]}") diff --git a/bukkit/paper-loader/src/main/java/net/momirealms/craftengine/bukkit/plugin/PaperCraftEngineBootstrap.java b/bukkit/paper-loader/src/main/java/net/momirealms/craftengine/bukkit/plugin/PaperCraftEngineBootstrap.java index df011e5ca..1af1956bf 100644 --- a/bukkit/paper-loader/src/main/java/net/momirealms/craftengine/bukkit/plugin/PaperCraftEngineBootstrap.java +++ b/bukkit/paper-loader/src/main/java/net/momirealms/craftengine/bukkit/plugin/PaperCraftEngineBootstrap.java @@ -4,6 +4,7 @@ import io.papermc.paper.plugin.bootstrap.BootstrapContext; import io.papermc.paper.plugin.bootstrap.PluginBootstrap; import io.papermc.paper.plugin.bootstrap.PluginProviderContext; import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; +import net.momirealms.craftengine.bukkit.plugin.agent.RuntimePatcher; import net.momirealms.craftengine.bukkit.plugin.classpath.PaperClassPathAppender; import net.momirealms.craftengine.core.plugin.logger.PluginLogger; import net.momirealms.craftengine.core.plugin.logger.Slf4jPluginLogger; @@ -46,13 +47,13 @@ public class PaperCraftEngineBootstrap implements PluginBootstrap { this.plugin.applyDependencies(); this.plugin.setUpConfig(); if (VersionHelper.isOrAbove1_21_4()) { - context.getLifecycleManager().registerEventHandler(LifecycleEvents.DATAPACK_DISCOVERY, (e) -> { - try { - this.plugin.injectRegistries(); - } catch (Throwable ex) { - logger.warn("Failed to inject registries", ex); - } - }); + new ModernEventHandler(context, this.plugin).register(); + } else { + try { + RuntimePatcher.patch(this.plugin); + } catch (Exception e) { + throw new RuntimeException("Failed to patch server", e); + } } } @@ -60,4 +61,24 @@ public class PaperCraftEngineBootstrap implements PluginBootstrap { public @NotNull JavaPlugin createPlugin(@NotNull PluginProviderContext context) { return new PaperCraftEnginePlugin(this); } + + public static class ModernEventHandler { + private final BootstrapContext context; + private final BukkitCraftEngine plugin; + + public ModernEventHandler(BootstrapContext context, BukkitCraftEngine plugin) { + this.context = context; + this.plugin = plugin; + } + + public void register() { + this.context.getLifecycleManager().registerEventHandler(LifecycleEvents.DATAPACK_DISCOVERY, (e) -> { + try { + this.plugin.injectRegistries(); + } catch (Throwable ex) { + this.plugin.logger().warn("Failed to inject registries", ex); + } + }); + } + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java index e9c7964fc..cbc46af16 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java @@ -84,6 +84,8 @@ public class BukkitBlockManager extends AbstractBlockManager { // cached tag packet protected Object cachedUpdateTagsPacket; + private final List> blocksToDeceive = new ArrayList<>(); + public BukkitBlockManager(BukkitCraftEngine plugin) { super(plugin); instance = this; @@ -100,6 +102,7 @@ public class BukkitBlockManager extends AbstractBlockManager { @Override public void init() { this.initMirrorRegistry(); + this.deceiveBukkit(); boolean enableNoteBlocks = this.blockAppearanceArranger.containsKey(BlockKeys.NOTE_BLOCK); this.blockEventListener = new BlockEventListener(plugin, this, enableNoteBlocks); if (enableNoteBlocks) { @@ -754,7 +757,7 @@ public class BukkitBlockManager extends AbstractBlockManager { builder2.put(stateId, blockHolder); stateIds.add(stateId); - deceiveBukkit(newRealBlock, clientSideBlockType, isNoteBlock); + this.blocksToDeceive.add(Tuple.of(newRealBlock, clientSideBlockType, isNoteBlock)); order.add(realBlockKey); counter++; } @@ -795,9 +798,20 @@ public class BukkitBlockManager extends AbstractBlockManager { } @SuppressWarnings("unchecked") - private void deceiveBukkit(Object newBlock, Key replacedBlock, boolean isNoteBlock) throws IllegalAccessException { - Map magicMap = (Map) CraftBukkitReflections.field$CraftMagicNumbers$BLOCK_MATERIAL.get(null); - Map factories = (Map) CraftBukkitReflections.field$CraftBlockStates$FACTORIES.get(null); + private void deceiveBukkit() { + try { + Map magicMap = (Map) CraftBukkitReflections.field$CraftMagicNumbers$BLOCK_MATERIAL.get(null); + Map factories = (Map) CraftBukkitReflections.field$CraftBlockStates$FACTORIES.get(null); + for (Tuple tuple : this.blocksToDeceive) { + deceiveBukkit(tuple.left(), tuple.mid(), tuple.right(), magicMap, factories); + } + this.blocksToDeceive.clear(); + } catch (ReflectiveOperationException e) { + this.plugin.logger().warn("Failed to deceive bukkit", e); + } + } + + private void deceiveBukkit(Object newBlock, Key replacedBlock, boolean isNoteBlock, Map magicMap, Map factories) { if (isNoteBlock) { magicMap.put(newBlock, Material.STONE); } else { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitCraftEngine.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitCraftEngine.java index 98fee0cc8..b441b5ba1 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitCraftEngine.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitCraftEngine.java @@ -117,7 +117,8 @@ public class BukkitCraftEngine extends CraftEngine { this.config = new Config(this); } - protected void injectRegistries() { + public void injectRegistries() { + if (super.blockManager != null) return; try { BlockGenerator.init(); super.blockManager = new BukkitBlockManager(this); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/agent/BlocksAgent.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/agent/BlocksAgent.java new file mode 100644 index 000000000..cac4429ba --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/agent/BlocksAgent.java @@ -0,0 +1,41 @@ +package net.momirealms.craftengine.bukkit.plugin.agent; + +import net.bytebuddy.agent.builder.AgentBuilder; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.matcher.ElementMatchers; + +import java.lang.instrument.Instrumentation; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public final class BlocksAgent { + + public static void agentmain(String args, Instrumentation instrumentation) { + new AgentBuilder.Default() + .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION) + .with(AgentBuilder.RedefinitionStrategy.REDEFINITION) + .type(ElementMatchers.named("net.minecraft.server.Bootstrap") + .or(ElementMatchers.named("net.minecraft.server.DispenserRegistry"))) + .transform((builder, typeDescription, classLoader, module, protectionDomain) -> + builder.visit(Advice.to(BlocksAdvice.class) + .on(ElementMatchers.named("validate") + .or(ElementMatchers.named("c"))))) + .installOn(instrumentation); + } + + public static class BlocksAdvice { + + @Advice.OnMethodExit + public static void onExit() { + try { + Class holder = Class.forName("net.momirealms.craftengine.bukkit.plugin.agent.PluginHolder"); + Field field = holder.getField("plugin"); + Object plugin = field.get(null); + Method injectRegistries = plugin.getClass().getMethod("injectRegistries"); + injectRegistries.invoke(plugin); + } catch (Exception e) { + throw new RuntimeException("Failed to inject registries", e); + } + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/agent/RuntimePatcher.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/agent/RuntimePatcher.java new file mode 100644 index 000000000..dbb2d5d85 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/agent/RuntimePatcher.java @@ -0,0 +1,54 @@ +package net.momirealms.craftengine.bukkit.plugin.agent; + +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.agent.ByteBuddyAgent; +import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; +import net.bytebuddy.dynamic.scaffold.InstrumentedType; +import net.bytebuddy.implementation.Implementation; +import net.bytebuddy.implementation.bytecode.ByteCodeAppender; +import net.bytebuddy.jar.asm.Opcodes; +import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; +import org.bukkit.Bukkit; +import org.jetbrains.annotations.NotNull; + +import java.lang.instrument.Instrumentation; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +public class RuntimePatcher { + + public static void patch(BukkitCraftEngine plugin) throws ReflectiveOperationException { + Class holderClass = new ByteBuddy() + .subclass(Object.class) + .name("net.momirealms.craftengine.bukkit.plugin.agent.PluginHolder") + .defineField("plugin", Object.class, Modifier.PUBLIC | Modifier.STATIC) + .defineMethod("setPlugin", void.class, Modifier.PUBLIC | Modifier.STATIC) + .withParameters(Object.class) + .intercept(new Implementation() { + @Override + public @NotNull InstrumentedType prepare(@NotNull InstrumentedType instrumentedType) { + return instrumentedType; + } + + @Override + public @NotNull ByteCodeAppender appender(@NotNull Target implementationTarget) { + return (methodVisitor, implementationContext, instrumentedMethod) -> { + methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); + methodVisitor.visitFieldInsn(Opcodes.PUTSTATIC, + "net/momirealms/craftengine/bukkit/plugin/agent/PluginHolder", + "plugin", + "Ljava/lang/Object;"); + methodVisitor.visitInsn(Opcodes.RETURN); + return new ByteCodeAppender.Size(1, 1); + }; + } + }) + .make() + .load(Bukkit.class.getClassLoader(), ClassLoadingStrategy.Default.INJECTION) + .getLoaded(); + Method setPlugin = holderClass.getMethod("setPlugin", Object.class); + setPlugin.invoke(null, plugin); + Instrumentation inst = ByteBuddyAgent.install(); + BlocksAgent.agentmain(null, inst); + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/BukkitReflectionUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/BukkitReflectionUtils.java index bbaf192a2..2d3a32de5 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/BukkitReflectionUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/BukkitReflectionUtils.java @@ -14,52 +14,27 @@ public final class BukkitReflectionUtils { private static final String PREFIX_CRAFTBUKKIT = "org.bukkit.craftbukkit"; private static final String CRAFT_SERVER = "CraftServer"; private static final String CB_PKG_VERSION; - public static final int MAJOR_REVISION; private BukkitReflectionUtils() {} static { - final Class serverClass; - if (Bukkit.getServer() == null) { - // Paper plugin Bootstrapper 1.20.6+ - serverClass = Objects.requireNonNull(ReflectionUtils.getClazz("org.bukkit.craftbukkit.CraftServer")); + if (VersionHelper.isMojmap()) { + CB_PKG_VERSION = "."; } else { - serverClass = Bukkit.getServer().getClass(); - } - final String pkg = serverClass.getPackage().getName(); - final String nmsVersion = pkg.substring(pkg.lastIndexOf(".") + 1); - if (!nmsVersion.contains("_")) { - int fallbackVersion = -1; - if (Bukkit.getServer() != null) { - try { - final Method getMinecraftVersion = serverClass.getDeclaredMethod("getMinecraftVersion"); - fallbackVersion = Integer.parseInt(getMinecraftVersion.invoke(Bukkit.getServer()).toString().split("\\.")[1]); - } catch (final Exception ignored) { - } - } else { - // Paper plugin bootstrapper 1.20.6+ - try { - final Class sharedConstants = Objects.requireNonNull(ReflectionUtils.getClazz("net.minecraft.SharedConstants")); - final Method getCurrentVersion = sharedConstants.getDeclaredMethod("getCurrentVersion"); - final Object currentVersion = getCurrentVersion.invoke(null); - final Method getName = currentVersion.getClass().getDeclaredMethod("getName"); - final String versionName = (String) getName.invoke(currentVersion); + String name; + label: { + for (int i = 0; i <= VersionHelper.minorVersion(); i++) { try { - fallbackVersion = Integer.parseInt(versionName.split("\\.")[1]); - } catch (final Exception ignored) { + name = ".v1_" + VersionHelper.majorVersion() + "_R" + i + "."; + Class.forName(PREFIX_CRAFTBUKKIT + name + CRAFT_SERVER); + break label; + } catch (ClassNotFoundException ignored) { } - } catch (final ReflectiveOperationException e) { - throw new RuntimeException(e); } + throw new RuntimeException("Could not find CraftServer version"); } - MAJOR_REVISION = fallbackVersion; - } else { - MAJOR_REVISION = Integer.parseInt(nmsVersion.split("_")[1]); + CB_PKG_VERSION = name; } - String name = serverClass.getName(); - name = name.substring(PREFIX_CRAFTBUKKIT.length()); - name = name.substring(0, name.length() - CRAFT_SERVER.length()); - CB_PKG_VERSION = name; } public static String assembleCBClass(String className) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java index 6a93b1c75..39ede4ecb 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java @@ -304,7 +304,7 @@ public abstract class CraftEngine implements Plugin { Dependencies.GSON, Dependencies.COMMONS_IO, Dependencies.COMMONS_LANG3, Dependencies.COMMONS_IMAGING, Dependencies.ZSTD, - Dependencies.BYTE_BUDDY, + Dependencies.BYTE_BUDDY, Dependencies.BYTE_BUDDY_AGENT, Dependencies.SNAKE_YAML, Dependencies.BOOSTED_YAML, Dependencies.OPTION, diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java index d952ba1c1..2eb14be4f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java @@ -199,6 +199,18 @@ public class Dependencies { List.of(Relocation.of("bytebuddy", "net{}bytebuddy")) ); + public static final Dependency BYTE_BUDDY_AGENT = new Dependency( + "byte-buddy-agent", + "net{}bytebuddy", + "byte-buddy-agent", + List.of(Relocation.of("bytebuddy", "net{}bytebuddy")) + ) { + @Override + public String getVersion() { + return BYTE_BUDDY.getVersion(); + } + }; + public static final Dependency SNAKE_YAML = new Dependency( "snake-yaml", "org{}yaml", diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/VersionHelper.java b/core/src/main/java/net/momirealms/craftengine/core/util/VersionHelper.java index e02d0ff53..e01f720b3 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/VersionHelper.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/VersionHelper.java @@ -15,7 +15,9 @@ public class VersionHelper { public static final Field field$DetectedVersion$name = requireNonNull( ReflectionUtils.getDeclaredField(clazz$DetectedVersion, String.class, 1)); - private static final float version; + private static final int version; + private static final int majorVersion; + private static final int minorVersion; private static final boolean mojmap; private static final boolean folia; private static final boolean paper; @@ -39,31 +41,47 @@ public class VersionHelper { Object detectedVersion = field$DetectedVersion$BUILT_IN.get(null); String name = (String) field$DetectedVersion$name.get(detectedVersion); String[] split = name.split("\\."); - version = Float.parseFloat(split[1] + "." + (split.length == 3 ? split[2] : "0")); + int major = Integer.parseInt(split[1]); + int minor = split.length == 3 ? Integer.parseInt(split[2]) : 0; + + // 2001 = 1.20.1 + // 2104 = 1.21.4 + version = major * 100 + minor; + + v1_20 = version >= 2000; + v1_20_1 = version >= 2001; + v1_20_2 = version >= 2002; + v1_20_3 = version >= 2003; + v1_20_4 = version >= 2004; + v1_20_5 = version >= 2005; + v1_20_6 = version >= 2006; + v1_21 = version >= 2100; + v1_21_1 = version >= 2101; + v1_21_2 = version >= 2102; + v1_21_3 = version >= 2103; + v1_21_4 = version >= 2104; + v1_21_5 = version >= 2105; + + majorVersion = major; + minorVersion = minor; + mojmap = checkMojMap(); folia = checkFolia(); paper = checkPaper(); - v1_20 = version >= 20f; - v1_20_1 = version >= 20.1f; - v1_20_2 = version >= 20.2f; - v1_20_3 = version >= 20.3f; - v1_20_4 = version >= 20.4f; - v1_20_5 = version >= 20.5f; - v1_20_6 = version >= 20.6f; - v1_21 = version >= 21f; - v1_21_1 = version >= 21.1f; - v1_21_2 = version >= 21.2f; - v1_21_3 = version >= 21.3f; - v1_21_4 = version >= 21.4f; - v1_21_5 = version >= 21.5f; } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); + throw new RuntimeException("Failed to init VersionHelper", e); } } - public static void init() {} + public static int majorVersion() { + return majorVersion; + } - public static float version() { + public static int minorVersion() { + return minorVersion; + } + + public static int version() { return version; }