NorbiPeti has completed the challenge [Bullseye]
Using the custom event to detect player /stop as now restarts can come from there too (btw it defaults to stop unless a command is ran) Added support for advancements in 1.16: - Now each player gets effectively a reference to the player list for advancements, and since I simply call the method on the original object, it will pass that on, instead of my mock - I tried calling the method that sends the reference on the mock in that case, but that just results in Mockito's version being called which means infinite recursion or something like that (I didn't get a StackOverflowError actually but the server didn't respond) - I tried implementing the method myself but actually I never tested it because at that point I was convinced I can call the original the right way - I had an educated guess that the mock is a subclass of the original class, so I just need to call super.method() right? - But how do you call that reflectively? Apparently Method.invoke() will always respect overriden methods; but thanks to StackOverflow I found out about MethodHandles, and it has the perfect thing I need, findSpecial / unreflectSpecial - Only problem is, kind of like how you can only use super.method() inside the class, you can only use the 'special' methods inside the class... So how could I make it run inside the mocked class? I have no idea since I can only supply an Answer object which has no connection to it, but apparently all the lookup() method actually does is call a constructor with the caller's class - so let's call the constructor! Which is, of course private - So now I have a reflection call creating a Lookup object which can get a handle to the method without checking any overrides and then using that handle to call the original method with the 'this' parameter being the mock #128
This commit is contained in:
parent
50500e87b9
commit
6b60135867
4 changed files with 72 additions and 6 deletions
2
pom.xml
2
pom.xml
|
@ -180,7 +180,7 @@
|
|||
<dependency>
|
||||
<groupId>com.discord4j</groupId>
|
||||
<artifactId>discord4j-core</artifactId>
|
||||
<version>3.0.14</version>
|
||||
<version>3.0.15</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-jdk14 -->
|
||||
<dependency>
|
||||
|
|
|
@ -248,7 +248,7 @@ public class DiscordPlugin extends ButtonPlugin {
|
|||
.sanitizeString(Bukkit.getOnlinePlayers().stream()
|
||||
.map(Player::getDisplayName).collect(Collectors.joining(", ")))
|
||||
+ (Bukkit.getOnlinePlayers().size() == 1 ? " was " : " were ")
|
||||
+ "kicked the hell out.") //TODO: Make configurable
|
||||
+ "thrown out") //TODO: Make configurable
|
||||
: ""); //If 'restart' is disabled then this isn't shown even if joinleave is enabled
|
||||
})).subscribe(), ChannelconBroadcast.RESTART, false);
|
||||
timings.printElapsed("Updating player list");
|
||||
|
|
|
@ -3,15 +3,23 @@ package buttondevteam.discordplugin.broadcaster;
|
|||
import buttondevteam.discordplugin.mcchat.MCChatUtils;
|
||||
import buttondevteam.lib.TBMCCoreAPI;
|
||||
import lombok.val;
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.matcher.ElementMatchers;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class PlayerListWatcher {
|
||||
|
@ -98,6 +106,18 @@ 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
|
||||
mock = Mockito.mock(dplc, Mockito.withSettings().defaultAnswer(new Answer<>() { // Cannot call super constructor
|
||||
@Override
|
||||
public Object answer(InvocationOnMock invocation) throws Throwable {
|
||||
|
@ -107,10 +127,25 @@ public class PlayerListWatcher {
|
|||
sendAll(invocation.getArgument(0));
|
||||
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());
|
||||
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()
|
||||
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."));
|
||||
|
@ -142,8 +177,6 @@ public class PlayerListWatcher {
|
|||
var comp = fixComponent.invoke(null, chatComponent);
|
||||
var packet = ppocC.getParameterCount() == 3
|
||||
? ppocC.newInstance(comp, chatmessagetype, sysb.get(null))
|
||||
|
||||
|
||||
: ppocC.newInstance(comp, chatmessagetype);
|
||||
this.sendAll(packet);
|
||||
// CraftBukkit end
|
||||
|
@ -155,7 +188,14 @@ 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))));
|
||||
|
@ -164,6 +204,31 @@ 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<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());
|
||||
plist = currentPL;
|
||||
for (var plc = dplc; plc != null; plc = plc.getSuperclass()) { //Set all fields
|
||||
|
|
|
@ -3,6 +3,7 @@ package buttondevteam.discordplugin.listeners;
|
|||
import buttondevteam.discordplugin.DiscordPlayer;
|
||||
import buttondevteam.discordplugin.DiscordPlugin;
|
||||
import buttondevteam.discordplugin.commands.ConnectCommand;
|
||||
import buttondevteam.lib.TBMCCommandPreprocessEvent;
|
||||
import buttondevteam.lib.player.TBMCPlayerGetInfoEvent;
|
||||
import buttondevteam.lib.player.TBMCPlayerJoinEvent;
|
||||
import discord4j.core.object.entity.Member;
|
||||
|
@ -52,7 +53,7 @@ public class MCListener implements Listener {
|
|||
}
|
||||
|
||||
@EventHandler
|
||||
public void onServerCommand(ServerCommandEvent e) {
|
||||
DiscordPlugin.Restart = !e.getCommand().equalsIgnoreCase("stop"); // The variable is always true except if stopped
|
||||
public void onCommandPreprocess(TBMCCommandPreprocessEvent e){
|
||||
DiscordPlugin.Restart = !e.getMessage().equalsIgnoreCase("/stop"); // The variable is always true except if stopped
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue