Fixes, fix vanilla command handling on 1.16

And on unsupported versions too
This commit is contained in:
Norbi Peti 2020-07-31 01:43:02 +02:00
parent 6b60135867
commit 6bf91afab9
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
6 changed files with 33 additions and 59 deletions

View file

@ -13,7 +13,7 @@ import java.lang.reflect.Modifier;
public abstract class DiscordPlayerSender extends DiscordSenderBase implements IMCPlayer<DiscordPlayerSender> { public abstract class DiscordPlayerSender extends DiscordSenderBase implements IMCPlayer<DiscordPlayerSender> {
protected Player player; protected Player player;
private @Getter VCMDWrapper vanillaCmdListener; private @Getter final VCMDWrapper vanillaCmdListener;
public DiscordPlayerSender(User user, MessageChannel channel, Player player, MinecraftChatModule module) { public DiscordPlayerSender(User user, MessageChannel channel, Player player, MinecraftChatModule module) {
super(user, channel); super(user, channel);

View file

@ -12,6 +12,7 @@ import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer;
import java.io.File; import java.io.File;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType; import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
@ -25,6 +26,7 @@ import java.util.UUID;
public class PlayerListWatcher { public class PlayerListWatcher {
private static Object plist; private static Object plist;
private static Object mock; private static Object mock;
private static MethodHandle fHandle; //Handle for PlayerList.f(EntityPlayer) - Only needed for 1.16
/*public PlayerListWatcher(DedicatedServer minecraftserver) { /*public PlayerListWatcher(DedicatedServer minecraftserver) {
super(minecraftserver); // <-- Does some init stuff and calls Bukkit.setServer() so we have to use Objenesis 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 toPlainText = tpt;
val sysb = Class.forName(nms + ".SystemUtils").getField("b"); 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 //Find the original method without overrides
Constructor<MethodHandles.Lookup> 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 mock = Mockito.mock(dplc, Mockito.withSettings().defaultAnswer(new Answer<>() { // Cannot call super constructor
@Override @Override
public Object answer(InvocationOnMock invocation) throws Throwable { public Object answer(InvocationOnMock invocation) throws Throwable {
@ -128,24 +125,19 @@ public class PlayerListWatcher {
return null; return null;
} }
//In 1.16 it passes a reference to the player list to advancement data for each player //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")) { if (nms.contains("1_16") && 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());
method.setAccessible(true); method.setAccessible(true);
//new ByteBuddy().subclass(dplc).method(ElementMatchers.named("f")) if (fHandle == null) {
var lookupConstructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class); assert lookupConstructor != null;
lookupConstructor.setAccessible(true); //Create lookup with a given class instead of caller
var lookup = lookupConstructor.newInstance(mock.getClass()); var lookup = lookupConstructor.newInstance(mock.getClass());
val fHandle = lookup.unreflectSpecial(method, mock.getClass()); //Special: super.method() 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 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()); return method.invoke(plist, invocation.getArguments());
} }
val args = invocation.getArguments(); val args = invocation.getArguments();
val params = method.getParameterTypes(); 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) { if (params.length == 0) {
TBMCCoreAPI.SendException("Found a strange method", TBMCCoreAPI.SendException("Found a strange method",
new Exception("Found a sendMessage() method without arguments.")); new Exception("Found a sendMessage() method without arguments."));
@ -188,14 +180,7 @@ public class PlayerListWatcher {
private void sendAll(Object packet) { private void sendAll(Object packet) {
try { // Some messages get sent by directly constructing a packet try { // Some messages get sent by directly constructing a packet
sendAll.invoke(plist, 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) { if (packet.getClass() == ppoc) {
//System.out.println("Indeed a chat packet");
Field msgf = ppoc.getDeclaredField("a"); Field msgf = ppoc.getDeclaredField("a");
msgf.setAccessible(true); msgf.setAccessible(true);
MCChatUtils.forAllMCChat(MCChatUtils.send((String) toPlainText.invoke(msgf.get(packet)))); 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); 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<UUID, Object> map = (Map<UUID, Object>) 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()); }).stubOnly());
plist = currentPL; plist = currentPL;
for (var plc = dplc; plc != null; plc = plc.getSuperclass()) { //Set all fields for (var plc = dplc; plc != null; plc = plc.getSuperclass()) { //Set all fields

View file

@ -9,9 +9,12 @@ import lombok.RequiredArgsConstructor;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import javax.annotation.Nullable;
@RequiredArgsConstructor @RequiredArgsConstructor
public class VCMDWrapper { public class VCMDWrapper {
@Getter //Needed to mock the player @Getter //Needed to mock the player
@Nullable
private final Object listener; private final Object listener;
/** /**
@ -38,7 +41,7 @@ public class VCMDWrapper {
ret = new VanillaCommandListener<>(player, bukkitplayer); ret = new VanillaCommandListener<>(player, bukkitplayer);
else if (mcpackage.contains("1_14")) else if (mcpackage.contains("1_14"))
ret = new VanillaCommandListener14<>(player, bukkitplayer); 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 ret = VanillaCommandListener15.create(player, bukkitplayer); //bukkitplayer may be null but that's fine
else else
ret = null; ret = null;
@ -55,4 +58,9 @@ public class VCMDWrapper {
private static void compatWarning(MinecraftChatModule module) { 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."); 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;
}
} }

View file

@ -85,6 +85,8 @@ public class VanillaCommandListener<T extends DiscordSenderBase & IMCPlayer<T>>
return true; return true;
ICommandListener icommandlistener = (ICommandListener) sender.getVanillaCmdListener().getListener(); ICommandListener icommandlistener = (ICommandListener) sender.getVanillaCmdListener().getListener();
if (icommandlistener == null)
return VCMDWrapper.compatResponse(dsender);
String[] args = cmdstr.split(" "); String[] args = cmdstr.split(" ");
args = Arrays.copyOfRange(args, 1, args.length); args = Arrays.copyOfRange(args, 1, args.length);
try { try {

View file

@ -87,6 +87,8 @@ public class VanillaCommandListener14<T extends DiscordSenderBase & IMCPlayer<T>
val world = ((CraftWorld) Bukkit.getWorlds().get(0)).getHandle(); val world = ((CraftWorld) Bukkit.getWorlds().get(0)).getHandle();
ICommandListener icommandlistener = (ICommandListener) sender.getVanillaCmdListener().getListener(); ICommandListener icommandlistener = (ICommandListener) sender.getVanillaCmdListener().getListener();
if (icommandlistener == null)
return VCMDWrapper.compatResponse(dsender);
val wrapper = new CommandListenerWrapper(icommandlistener, new Vec3D(0, 0, 0), val wrapper = new CommandListenerWrapper(icommandlistener, new Vec3D(0, 0, 0),
new Vec2F(0, 0), world, 0, sender.getName(), new Vec2F(0, 0), world, 0, sender.getName(),
new ChatComponentText(sender.getName()), world.getMinecraftServer(), null); new ChatComponentText(sender.getName()), world.getMinecraftServer(), null);

View file

@ -79,7 +79,7 @@ public class VanillaCommandListener15<T extends DiscordSenderBase & IMCPlayer<T>
var server = Bukkit.getServer(); var server = Bukkit.getServer();
var cmap = (SimpleCommandMap) server.getClass().getMethod("getCommandMap").invoke(server); var cmap = (SimpleCommandMap) server.getClass().getMethod("getCommandMap").invoke(server);
val cmd = cmap.getCommand(cmdstr.split(" ")[0].toLowerCase()); 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 return Bukkit.dispatchCommand(dsender, cmdstr); // Unconnected users are treated well in vanilla cmds
if (!(dsender instanceof IMCPlayer)) if (!(dsender instanceof IMCPlayer))
@ -94,7 +94,8 @@ public class VanillaCommandListener15<T extends DiscordSenderBase & IMCPlayer<T>
var cworld = Bukkit.getWorlds().get(0); var cworld = Bukkit.getWorlds().get(0);
val world = cworld.getClass().getMethod("getHandle").invoke(cworld); val world = cworld.getClass().getMethod("getHandle").invoke(cworld);
var icommandlistener = sender.getVanillaCmdListener().getListener(); 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 clwcl = Class.forName(nms + ".CommandListenerWrapper");
var v3dcl = Class.forName(nms + ".Vec3D"); var v3dcl = Class.forName(nms + ".Vec3D");
var v2fcl = Class.forName(nms + ".Vec2F"); var v2fcl = Class.forName(nms + ".Vec2F");
@ -102,7 +103,8 @@ public class VanillaCommandListener15<T extends DiscordSenderBase & IMCPlayer<T>
var mcscl = Class.forName(nms + ".MinecraftServer"); var mcscl = Class.forName(nms + ".MinecraftServer");
var ecl = Class.forName(nms + ".Entity"); var ecl = Class.forName(nms + ".Entity");
var cctcl = Class.forName(nms + ".ChatComponentText"); 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, .newInstance(icommandlistener,
v3dcl.getConstructor(double.class, double.class, double.class).newInstance(0, 0, 0), v3dcl.getConstructor(double.class, double.class, double.class).newInstance(0, 0, 0),
v2fcl.getConstructor(float.class, float.class).newInstance(0, 0), v2fcl.getConstructor(float.class, float.class).newInstance(0, 0),
@ -112,7 +114,7 @@ public class VanillaCommandListener15<T extends DiscordSenderBase & IMCPlayer<T>
new Vec2F(0, 0), world, 0, sender.getName(), new Vec2F(0, 0), world, 0, sender.getName(),
new ChatComponentText(sender.getName()), world.getMinecraftServer(), null);*/ new ChatComponentText(sender.getName()), world.getMinecraftServer(), null);*/
var pncscl = Class.forName(vcwcl.getPackage().getName() + ".ProxiedNativeCommandSender"); 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); .newInstance(wrapper, sender, sender);
String[] args = cmdstr.split(" "); String[] args = cmdstr.split(" ");
args = Arrays.copyOfRange(args, 1, args.length); args = Arrays.copyOfRange(args, 1, args.length);