diff --git a/src/main/java/buttondevteam/discordplugin/DiscordPlayerSender.java b/src/main/java/buttondevteam/discordplugin/DiscordPlayerSender.java index 083a141..4bff6f0 100755 --- a/src/main/java/buttondevteam/discordplugin/DiscordPlayerSender.java +++ b/src/main/java/buttondevteam/discordplugin/DiscordPlayerSender.java @@ -13,7 +13,7 @@ import java.lang.reflect.Modifier; public abstract class DiscordPlayerSender extends DiscordSenderBase implements IMCPlayer { protected Player player; - private @Getter VCMDWrapper vanillaCmdListener; + private @Getter final VCMDWrapper vanillaCmdListener; public DiscordPlayerSender(User user, MessageChannel channel, Player player, MinecraftChatModule module) { super(user, channel); diff --git a/src/main/java/buttondevteam/discordplugin/broadcaster/PlayerListWatcher.java b/src/main/java/buttondevteam/discordplugin/broadcaster/PlayerListWatcher.java index 18e70ff..f1a46c9 100755 --- a/src/main/java/buttondevteam/discordplugin/broadcaster/PlayerListWatcher.java +++ b/src/main/java/buttondevteam/discordplugin/broadcaster/PlayerListWatcher.java @@ -12,6 +12,7 @@ import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import java.io.File; +import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.reflect.Constructor; @@ -25,6 +26,7 @@ import java.util.UUID; public class PlayerListWatcher { private static Object plist; private static Object mock; + private static MethodHandle fHandle; //Handle for PlayerList.f(EntityPlayer) - Only needed for 1.16 /*public PlayerListWatcher(DedicatedServer minecraftserver) { super(minecraftserver); // <-- Does some init stuff and calls Bukkit.setServer() so we have to use Objenesis @@ -107,17 +109,12 @@ public class PlayerListWatcher { val toPlainText = tpt; val sysb = Class.forName(nms + ".SystemUtils").getField("b"); - //TODO: 1.16 only - //String cbs = nms.replace("net.minecraft.server", "org.bukkit.craftbukkit"); - val epcl = Class.forName(nms + ".EntityPlayer"); - /*val getUUID = epcl.getMethod("getUniqueID"); - val getPlayer = Class.forName(cbs + ".CraftPlayer").getConstructors()[0]; - val getDataFixer = server.getClass().getMethod("getDataFixer"); - val getAdvancementData = server.getClass().getMethod("getAdvancementData"); - val adpcl = Class.forName(nms + ".AdvancementDataPlayer").getConstructors()[0]; - val setEPlayer = getAdvancementData.getReturnType().getMethod("a", epcl);*/ - val adpcl = Class.forName(nms + ".AdvancementDataPlayer"); //Find the original method without overrides + Constructor lookupConstructor; + if (nms.contains("1_16")) { + lookupConstructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class); + lookupConstructor.setAccessible(true); //Create lookup with a given class instead of caller + } else lookupConstructor = null; mock = Mockito.mock(dplc, Mockito.withSettings().defaultAnswer(new Answer<>() { // Cannot call super constructor @Override public Object answer(InvocationOnMock invocation) throws Throwable { @@ -128,24 +125,19 @@ public class PlayerListWatcher { return null; } //In 1.16 it passes a reference to the player list to advancement data for each player - if (method.getName().equals("f") && method.getParameterCount() > 0 && method.getParameterTypes()[0].getSimpleName().equals("EntityPlayer")) { - //val fHandle = MethodHandles.lookup().findSpecial(dplc, "f", MethodType.methodType(adpcl, epcl), mock.getClass()); - System.out.println("Declaring class: " + method.getDeclaringClass()); + if (nms.contains("1_16") && method.getName().equals("f") && method.getParameterCount() > 0 && method.getParameterTypes()[0].getSimpleName().equals("EntityPlayer")) { method.setAccessible(true); - //new ByteBuddy().subclass(dplc).method(ElementMatchers.named("f")) - var lookupConstructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class); - lookupConstructor.setAccessible(true); //Create lookup with a given class instead of caller - var lookup = lookupConstructor.newInstance(mock.getClass()); - val fHandle = lookup.unreflectSpecial(method, mock.getClass()); //Special: super.method() + if (fHandle == null) { + assert lookupConstructor != null; + var lookup = lookupConstructor.newInstance(mock.getClass()); + fHandle = lookup.unreflectSpecial(method, mock.getClass()); //Special: super.method() + } return fHandle.invoke(mock, invocation.getArgument(0)); //Invoke with our instance, so it passes that to advancement data, we have the fields as well } return method.invoke(plist, invocation.getArguments()); } val args = invocation.getArguments(); val params = method.getParameterTypes(); - System.out.println("Method: " + method.getName()); - System.out.println("args: " + Arrays.toString(args)); - System.out.println("params: " + Arrays.toString(params)); if (params.length == 0) { TBMCCoreAPI.SendException("Found a strange method", new Exception("Found a sendMessage() method without arguments.")); @@ -188,14 +180,7 @@ public class PlayerListWatcher { private void sendAll(Object packet) { try { // Some messages get sent by directly constructing a packet sendAll.invoke(plist, packet); - /*if(packet.getClass().getName().contains("Chat")) - System.out.println("Chat packet: "+packet); - if(packet.getClass().getName().contains("Advancement")) - System.out.println("Advancement packet: "+packet);*/ - if (!packet.getClass().getName().contains("KeepAlive")) - System.out.println("Packet: " + packet); if (packet.getClass() == ppoc) { - //System.out.println("Indeed a chat packet"); Field msgf = ppoc.getDeclaredField("a"); msgf.setAccessible(true); MCChatUtils.forAllMCChat(MCChatUtils.send((String) toPlainText.invoke(msgf.get(packet)))); @@ -204,31 +189,6 @@ public class PlayerListWatcher { TBMCCoreAPI.SendException("Failed to broadcast message sent to all players - hacking failed.", e); } } - - /*public Object f(Object entityplayer) { //Returns advancement data - try { - UUID uuid = (UUID) getUUID.invoke(entityplayer); - @SuppressWarnings("unchecked") Map map = (Map) dplc.getField("p").get(plist); - var advancementdataplayer = map.get(uuid); - - if (advancementdataplayer == null) { - var player = (Player) getPlayer.newInstance(Bukkit.getServer(), entityplayer); - var file = new File(player.getWorld().getWorldFolder(), "advancements"); - File file1 = new File(file, uuid + ".json"); - - var dataFixer = getDataFixer.invoke(server); - var advData = getAdvancementData.invoke(server); - advancementdataplayer = adpcl.newInstance(dataFixer, this, advData, file1, entityplayer); - //advancementdataplayer = new AdvancementDataPlayer(this.server.getDataFixer(), this, this.server.getAdvancementData(), file1, entityplayer); - map.put(uuid, advancementdataplayer); - } - - setEPlayer.invoke(advancementdataplayer, entityplayer); - return advancementdataplayer; - } catch (Exception e) { - TBMCCoreAPI.SendException("An error occurred while getting advancement data!", e); - } - }*/ }).stubOnly()); plist = currentPL; for (var plc = dplc; plc != null; plc = plc.getSuperclass()) { //Set all fields diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/VCMDWrapper.java b/src/main/java/buttondevteam/discordplugin/playerfaker/VCMDWrapper.java index ae3fd82..16dfcf6 100644 --- a/src/main/java/buttondevteam/discordplugin/playerfaker/VCMDWrapper.java +++ b/src/main/java/buttondevteam/discordplugin/playerfaker/VCMDWrapper.java @@ -9,9 +9,12 @@ import lombok.RequiredArgsConstructor; import org.bukkit.Bukkit; import org.bukkit.entity.Player; +import javax.annotation.Nullable; + @RequiredArgsConstructor public class VCMDWrapper { @Getter //Needed to mock the player + @Nullable private final Object listener; /** @@ -38,7 +41,7 @@ public class VCMDWrapper { ret = new VanillaCommandListener<>(player, bukkitplayer); else if (mcpackage.contains("1_14")) ret = new VanillaCommandListener14<>(player, bukkitplayer); - else if (mcpackage.contains("1_15") || mcpackage.contains("1.16")) + else if (mcpackage.contains("1_15") || mcpackage.contains("1_16")) ret = VanillaCommandListener15.create(player, bukkitplayer); //bukkitplayer may be null but that's fine else ret = null; @@ -55,4 +58,9 @@ public class VCMDWrapper { private static void compatWarning(MinecraftChatModule module) { module.logWarn("Vanilla commands won't be available from Discord due to a compatibility error. Disable vanilla command support to remove this message."); } + + static boolean compatResponse(DiscordSenderBase dsender) { + dsender.sendMessage("Vanilla commands are not supported on this Minecraft version."); + return true; + } } diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener.java b/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener.java index 6a6dafd..18b3618 100755 --- a/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener.java +++ b/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener.java @@ -85,6 +85,8 @@ public class VanillaCommandListener> return true; ICommandListener icommandlistener = (ICommandListener) sender.getVanillaCmdListener().getListener(); + if (icommandlistener == null) + return VCMDWrapper.compatResponse(dsender); String[] args = cmdstr.split(" "); args = Arrays.copyOfRange(args, 1, args.length); try { diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener14.java b/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener14.java index b40364d..7a6df61 100644 --- a/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener14.java +++ b/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener14.java @@ -87,6 +87,8 @@ public class VanillaCommandListener14 val world = ((CraftWorld) Bukkit.getWorlds().get(0)).getHandle(); ICommandListener icommandlistener = (ICommandListener) sender.getVanillaCmdListener().getListener(); + if (icommandlistener == null) + return VCMDWrapper.compatResponse(dsender); val wrapper = new CommandListenerWrapper(icommandlistener, new Vec3D(0, 0, 0), new Vec2F(0, 0), world, 0, sender.getName(), new ChatComponentText(sender.getName()), world.getMinecraftServer(), null); diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener15.java b/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener15.java index 31829e9..e0ea99b 100644 --- a/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener15.java +++ b/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener15.java @@ -79,7 +79,7 @@ public class VanillaCommandListener15 var server = Bukkit.getServer(); var cmap = (SimpleCommandMap) server.getClass().getMethod("getCommandMap").invoke(server); val cmd = cmap.getCommand(cmdstr.split(" ")[0].toLowerCase()); - if (!(dsender instanceof Player) || !vcwcl.isAssignableFrom(dsender.getClass())) + if (!(dsender instanceof Player) || !vcwcl.isAssignableFrom(cmd.getClass())) return Bukkit.dispatchCommand(dsender, cmdstr); // Unconnected users are treated well in vanilla cmds if (!(dsender instanceof IMCPlayer)) @@ -94,7 +94,8 @@ public class VanillaCommandListener15 var cworld = Bukkit.getWorlds().get(0); val world = cworld.getClass().getMethod("getHandle").invoke(cworld); var icommandlistener = sender.getVanillaCmdListener().getListener(); - var nms = icommandlistener.getClass().getPackage().getName(); + if (icommandlistener == null) + return VCMDWrapper.compatResponse(dsender); var clwcl = Class.forName(nms + ".CommandListenerWrapper"); var v3dcl = Class.forName(nms + ".Vec3D"); var v2fcl = Class.forName(nms + ".Vec2F"); @@ -102,7 +103,8 @@ public class VanillaCommandListener15 var mcscl = Class.forName(nms + ".MinecraftServer"); var ecl = Class.forName(nms + ".Entity"); var cctcl = Class.forName(nms + ".ChatComponentText"); - Object wrapper = clwcl.getConstructor(icommandlistener.getClass(), v3dcl, v2fcl, world.getClass(), int.class, String.class, icbcl, mcscl, ecl) + var iclcl = Class.forName(nms + ".ICommandListener"); + Object wrapper = clwcl.getConstructor(iclcl, v3dcl, v2fcl, world.getClass(), int.class, String.class, icbcl, mcscl, ecl) .newInstance(icommandlistener, v3dcl.getConstructor(double.class, double.class, double.class).newInstance(0, 0, 0), v2fcl.getConstructor(float.class, float.class).newInstance(0, 0), @@ -112,7 +114,7 @@ public class VanillaCommandListener15 new Vec2F(0, 0), world, 0, sender.getName(), new ChatComponentText(sender.getName()), world.getMinecraftServer(), null);*/ var pncscl = Class.forName(vcwcl.getPackage().getName() + ".ProxiedNativeCommandSender"); - Object pncs = pncscl.getConstructor(clwcl, sender.getClass(), sender.getClass()) + Object pncs = pncscl.getConstructor(clwcl, CommandSender.class, CommandSender.class) .newInstance(wrapper, sender, sender); String[] args = cmdstr.split(" "); args = Arrays.copyOfRange(args, 1, args.length);