diff --git a/src/main/java/net/tfminecraft/activitytf/gui/ActivityGui.java b/src/main/java/net/tfminecraft/activitytf/gui/ActivityGui.java index 98b98f1..bafa0dd 100644 --- a/src/main/java/net/tfminecraft/activitytf/gui/ActivityGui.java +++ b/src/main/java/net/tfminecraft/activitytf/gui/ActivityGui.java @@ -328,7 +328,7 @@ private ItemStack item(ItemStack stack, String name, List lore) { @EventHandler public void onClick(InventoryClickEvent event) { - if (!(event.getView().getTopInventory().getHolder() instanceof Marker)) { + if (!(event.getView().getTopInventory().getHolder(false) instanceof Marker)) { return; } event.setCancelled(true); @@ -475,7 +475,7 @@ private void claim(Player player, InventoryClickEvent event) { @EventHandler public void onDrag(InventoryDragEvent event) { - if (!(event.getView().getTopInventory().getHolder() instanceof Marker)) { + if (!(event.getView().getTopInventory().getHolder(false) instanceof Marker)) { return; } for (int rawSlot : event.getRawSlots()) { diff --git a/src/main/java/net/tfminecraft/activitytf/gui/ReferralGui.java b/src/main/java/net/tfminecraft/activitytf/gui/ReferralGui.java index 76a1b5a..c84cbf1 100644 --- a/src/main/java/net/tfminecraft/activitytf/gui/ReferralGui.java +++ b/src/main/java/net/tfminecraft/activitytf/gui/ReferralGui.java @@ -602,7 +602,7 @@ public void confirmTyped(Player player, String name) { @EventHandler public void onClick(InventoryClickEvent event) { - if (!(event.getView().getTopInventory().getHolder() instanceof Marker marker)) { + if (!(event.getView().getTopInventory().getHolder(false) instanceof Marker marker)) { return; } event.setCancelled(true); @@ -764,7 +764,7 @@ static String linkKey(Link link) { @EventHandler public void onDrag(InventoryDragEvent event) { - if (!(event.getView().getTopInventory().getHolder() instanceof Marker)) { + if (!(event.getView().getTopInventory().getHolder(false) instanceof Marker)) { return; } int size = event.getView().getTopInventory().getSize(); diff --git a/src/main/java/net/tfminecraft/activitytf/managers/ActivityManager.java b/src/main/java/net/tfminecraft/activitytf/managers/ActivityManager.java index fb8e14f..89f90ca 100644 --- a/src/main/java/net/tfminecraft/activitytf/managers/ActivityManager.java +++ b/src/main/java/net/tfminecraft/activitytf/managers/ActivityManager.java @@ -788,7 +788,7 @@ public boolean runClickCommands(Player player, int slot) { void runClickCommandsNow(Player player, List commands, String id, Predicate console) { InventoryView open = player.getOpenInventory(); - if (open != null && open.getTopInventory().getHolder() instanceof ActivityGui.Marker) { + if (open != null && open.getTopInventory().getHolder(false) instanceof ActivityGui.Marker) { player.closeInventory(); } diff --git a/src/test/java/net/tfminecraft/activitytf/testsupport/HolderLookupPatch.java b/src/test/java/net/tfminecraft/activitytf/testsupport/HolderLookupPatch.java new file mode 100644 index 0000000..4b7ac68 --- /dev/null +++ b/src/test/java/net/tfminecraft/activitytf/testsupport/HolderLookupPatch.java @@ -0,0 +1,91 @@ +package net.tfminecraft.activitytf.testsupport; + +import static net.bytebuddy.matcher.ElementMatchers.is; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import java.util.concurrent.atomic.AtomicBoolean; + +import net.bytebuddy.agent.ByteBuddyAgent; +import net.bytebuddy.agent.builder.AgentBuilder; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.dynamic.DynamicType; +import net.bytebuddy.utility.JavaModule; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.junit.platform.launcher.LauncherSession; +import org.junit.platform.launcher.LauncherSessionListener; +import org.mockbukkit.mockbukkit.inventory.InventoryMock; + +/** + * MockBukkit throws on Paper's {@code getHolder(false)}. Its inventories hold no block snapshot, + * so the patch answers with the plain holder, which is what Paper returns for plugin GUIs. + */ +public final class HolderLookupPatch implements LauncherSessionListener { + private static boolean installed; + + @Override + public void launcherSessionOpened(LauncherSession session) { + install(); + } + + /** Patches {@link InventoryMock#getHolder(boolean)} once per test JVM. */ + public static synchronized void install() { + if (installed) { + return; + } + AtomicBoolean patched = new AtomicBoolean(); + AtomicBoolean rejected = new AtomicBoolean(); + new AgentBuilder.Default() + .disableClassFormatChanges() + .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION) + .with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE) + .with(AgentBuilder.TypeStrategy.Default.REDEFINE) + .with(AgentBuilder.Listener.StreamWriting.toSystemError().withErrorsOnly()) + .with(new AgentBuilder.Listener.Adapter() { + @Override + public void onTransformation(TypeDescription type, ClassLoader loader, JavaModule module, + boolean loaded, DynamicType dynamicType) { + if (type.getName().equals(InventoryMock.class.getName())) { + patched.set(true); + } + } + + // The JVM can still reject the new bytes after onTransformation. + @Override + public void onError(String typeName, ClassLoader loader, JavaModule module, boolean loaded, + Throwable throwable) { + if (typeName.equals(InventoryMock.class.getName())) { + rejected.set(true); + } + } + }) + .type(is(InventoryMock.class)) + .transform((builder, type, loader, module, domain) -> builder.visit(Advice.to(PlainHolder.class) + .on(named("getHolder").and(takesArguments(boolean.class))))) + .installOn(ByteBuddyAgent.install()); + // A failed retransformation only reaches the listeners, so check it took before marking it done. + if (!patched.get() || rejected.get()) { + throw new IllegalStateException("Could not patch InventoryMock#getHolder(boolean)"); + } + installed = true; + } + + /** Skips the throwing body and returns {@code getHolder()} instead. */ + static final class PlainHolder { + private PlainHolder() { + } + + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + static boolean skip() { + return true; + } + + @Advice.OnMethodExit + static void exit(@Advice.This Inventory inventory, + @Advice.Return(readOnly = false) InventoryHolder holder) { + holder = inventory.getHolder(); + } + } +} diff --git a/src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener b/src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener index 7a2b3e9..a9f92f8 100644 --- a/src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener +++ b/src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener @@ -1 +1,2 @@ net.tfminecraft.activitytf.testsupport.RegistryBootstrap +net.tfminecraft.activitytf.testsupport.HolderLookupPatch