diff --git a/pom.xml b/pom.xml
index 4b4d005..309105f 100755
--- a/pom.xml
+++ b/pom.xml
@@ -215,7 +215,7 @@
org.mockito
mockito-core
- 3.5.10
+ 3.5.13
org.mockito
diff --git a/src/main/java/buttondevteam/discordplugin/DiscordPlugin.java b/src/main/java/buttondevteam/discordplugin/DiscordPlugin.java
index 7d60a22..91db049 100755
--- a/src/main/java/buttondevteam/discordplugin/DiscordPlugin.java
+++ b/src/main/java/buttondevteam/discordplugin/DiscordPlugin.java
@@ -35,7 +35,6 @@ import lombok.val;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
-import org.mockito.internal.util.MockUtil;
import reactor.core.publisher.Mono;
import java.awt.*;
@@ -102,16 +101,17 @@ public class DiscordPlugin extends ButtonPlugin {
return getIConfig().getData("inviteLink", "");
}
- @Override
+ /*@Override
public void onLoad() { //Needed by ServerWatcher
var thread = Thread.currentThread();
getLogger().info("Setting context class loader for " + thread);
var cl = thread.getContextClassLoader();
thread.setContextClassLoader(getClassLoader());
MockUtil.isMock(null); //Load MockUtil to load Mockito plugins
+ //new ByteBuddy().rebase(Player.class)
getLogger().info("Restoring context class loader");
thread.setContextClassLoader(cl);
- }
+ }*/
@Override
public void pluginEnable() {
diff --git a/src/main/java/buttondevteam/discordplugin/broadcaster/GeneralEventBroadcasterModule.java b/src/main/java/buttondevteam/discordplugin/broadcaster/GeneralEventBroadcasterModule.java
index 44492eb..8dd9b52 100644
--- a/src/main/java/buttondevteam/discordplugin/broadcaster/GeneralEventBroadcasterModule.java
+++ b/src/main/java/buttondevteam/discordplugin/broadcaster/GeneralEventBroadcasterModule.java
@@ -15,7 +15,7 @@ public class GeneralEventBroadcasterModule extends Component {
@Override
protected void enable() {
try {
- PlayerListWatcher.hookUpDown(true);
+ PlayerListWatcher.hookUpDown(true, this);
log("Finished hooking into the player list");
hooked = true;
} catch (Exception e) {
@@ -30,7 +30,7 @@ public class GeneralEventBroadcasterModule extends Component {
protected void disable() {
try {
if (!hooked) return;
- if (PlayerListWatcher.hookUpDown(false))
+ if (PlayerListWatcher.hookUpDown(false, this))
log("Finished unhooking the player list!");
else
log("Didn't have the player list hooked.");
diff --git a/src/main/java/buttondevteam/discordplugin/broadcaster/PlayerListWatcher.java b/src/main/java/buttondevteam/discordplugin/broadcaster/PlayerListWatcher.java
index 396df84..39f69ce 100755
--- a/src/main/java/buttondevteam/discordplugin/broadcaster/PlayerListWatcher.java
+++ b/src/main/java/buttondevteam/discordplugin/broadcaster/PlayerListWatcher.java
@@ -21,7 +21,7 @@ public class PlayerListWatcher {
private static Object mock;
private static MethodHandle fHandle; //Handle for PlayerList.f(EntityPlayer) - Only needed for 1.16
- static boolean hookUpDown(boolean up) throws Exception {
+ static boolean hookUpDown(boolean up, GeneralEventBroadcasterModule module) throws Exception {
val csc = Bukkit.getServer().getClass();
Field conf = csc.getDeclaredField("console");
conf.setAccessible(true);
@@ -30,6 +30,10 @@ public class PlayerListWatcher {
val dplc = Class.forName(nms + ".DedicatedPlayerList");
val currentPL = server.getClass().getMethod("getPlayerList").invoke(server);
if (up) {
+ if (currentPL == mock) {
+ module.logWarn("Player list already mocked!");
+ return false;
+ }
val icbcl = Class.forName(nms + ".IChatBaseComponent");
Method sendMessageTemp;
try {
@@ -84,7 +88,11 @@ public class PlayerListWatcher {
if (fHandle == null) {
assert lookupConstructor != null;
var lookup = lookupConstructor.newInstance(mock.getClass());
+ //var mcl = method.getDeclaringClass();
fHandle = lookup.unreflectSpecial(method, mock.getClass()); //Special: super.method()
+ /*if (mcl.getSimpleName().contains("Mock")) //inline mock
+ lookup.findSpecial(mcl, method.getName(), )
+ fHandle.type()*/
}
return fHandle.invoke(mock, invocation.getArgument(0)); //Invoke with our instance, so it passes that to advancement data, we have the fields as well
}
diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/ServerWatcher.java b/src/main/java/buttondevteam/discordplugin/playerfaker/ServerWatcher.java
index bb039f7..038d58e 100644
--- a/src/main/java/buttondevteam/discordplugin/playerfaker/ServerWatcher.java
+++ b/src/main/java/buttondevteam/discordplugin/playerfaker/ServerWatcher.java
@@ -1,21 +1,38 @@
package buttondevteam.discordplugin.playerfaker;
import buttondevteam.discordplugin.mcchat.MCChatUtils;
+import buttondevteam.lib.TBMCCoreAPI;
import com.destroystokyo.paper.profile.CraftPlayerProfile;
import lombok.RequiredArgsConstructor;
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.agent.ByteBuddyAgent;
+import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
+import net.bytebuddy.dynamic.scaffold.MethodGraph;
+import net.bytebuddy.dynamic.scaffold.TypeValidation;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.MethodCall;
+import net.bytebuddy.implementation.MethodDelegation;
+import net.bytebuddy.implementation.bind.annotation.IgnoreForBinding;
+import net.bytebuddy.implementation.bind.annotation.RuntimeType;
+import net.bytebuddy.implementation.bind.annotation.SuperCall;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.entity.Player;
-import org.mockito.Mockito;
+import org.objenesis.ObjenesisStd;
+import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
+import java.util.concurrent.Callable;
+
+import static net.bytebuddy.matcher.ElementMatchers.*;
public class ServerWatcher {
private List playerList;
private final List fakePlayers = new ArrayList<>();
private Server origServer;
+ @IgnoreForBinding
public void enableDisable(boolean enable) throws Exception {
var serverField = Bukkit.class.getDeclaredField("server");
serverField.setAccessible(true);
@@ -30,50 +47,21 @@ public class ServerWatcher {
} catch (IOException e) {
throw new IllegalStateException("Failed to load " + MockMaker.class, e);
}*/
- var settings = Mockito.withSettings().stubOnly()
- .defaultAnswer(invocation -> {
- var method = invocation.getMethod();
- int pc = method.getParameterCount();
- Player player = null;
- switch (method.getName()) {
- case "getPlayer":
- if (pc == 1 && method.getParameterTypes()[0] == UUID.class)
- player = MCChatUtils.LoggedInPlayers.get(invocation.getArgument(0));
- break;
- case "getPlayerExact":
- if (pc == 1) {
- final String argument = invocation.getArgument(0);
- player = MCChatUtils.LoggedInPlayers.values().stream()
- .filter(dcp -> dcp.getName().equalsIgnoreCase(argument)).findAny().orElse(null);
- }
- break;
- case "getOnlinePlayers":
- if (playerList == null) {
- @SuppressWarnings("unchecked") var list = (List) method.invoke(origServer, invocation.getArguments());
- playerList = new AppendListView<>(list, fakePlayers);
- }
- return playerList;
- case "createProfile": //Paper's method, casts the player to a CraftPlayer
- if (pc == 2) {
- UUID uuid = invocation.getArgument(0);
- String name = invocation.getArgument(1);
- player = uuid != null ? MCChatUtils.LoggedInPlayers.get(uuid) : null;
- if (player == null && name != null)
- player = MCChatUtils.LoggedInPlayers.values().stream()
- .filter(dcp -> dcp.getName().equalsIgnoreCase(name)).findAny().orElse(null);
- if (player != null)
- return new CraftPlayerProfile(player.getUniqueId(), player.getName());
- }
- break;
- }
- if (player != null)
- return player;
- return method.invoke(origServer, invocation.getArguments());
- });
- //var mock = mockMaker.createMock(settings, MockHandlerFactory.createMockHandler(settings));
- //thread.setContextClassLoader(cl);
- var mock = Mockito.mock(serverClass, settings);
+ ByteBuddyAgent.install();
var originalServer = serverField.get(null);
+ var impl = MethodDelegation.to(this);
+ var names = Arrays.stream(ServerWatcher.class.getMethods()).map(Method::getName).toArray(String[]::new);
+ var utype = new ByteBuddy() //InlineBytecodeGenerator
+ .with(TypeValidation.DISABLED)
+ .with(Implementation.Context.Disabled.Factory.INSTANCE)
+ .with(MethodGraph.Compiler.ForDeclaredMethods.INSTANCE)
+ .ignore(isSynthetic().and(not(isConstructor())).or(isDefaultFinalizer()))
+ .redefine(serverClass)
+ .method(target -> !target.isStatic()).intercept(MethodCall.invokeSelf().on(originalServer).withAllArguments())
+ .method(target -> Arrays.stream(names).anyMatch(target.getActualName()::equalsIgnoreCase)).intercept(impl).make();
+ var ltype = utype.load(serverClass.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
+ var type = ltype.getLoaded();
+ var mock = new ObjenesisStd().newInstance(type);
for (var field : serverClass.getFields()) //Copy public fields, private fields aren't accessible directly anyways
if (!Modifier.isFinal(field.getModifiers()) && !Modifier.isStatic(field.getModifiers()))
field.set(mock, field.get(originalServer));
@@ -83,6 +71,62 @@ public class ServerWatcher {
serverField.set(null, origServer);
}
+ public Player getPlayer(UUID uuid, @SuperCall Callable original) {
+ try {
+ var player = MCChatUtils.LoggedInPlayers.get(uuid);
+ if (player == null) return original.call();
+ return player;
+ } catch (Exception e) {
+ TBMCCoreAPI.SendException("Failed to handle getPlayer!", e);
+ return null;
+ }
+ }
+
+ public Player getPlayerExact(String name, @SuperCall Callable original) {
+ try {
+ var player = MCChatUtils.LoggedInPlayers.values().stream()
+ .filter(dcp -> dcp.getName().equalsIgnoreCase(name)).findAny().orElse(null);
+ if (player == null) return original.call();
+ return player;
+ } catch (Exception e) {
+ TBMCCoreAPI.SendException("Failed to handle getPlayerExact!", e);
+ return null;
+ }
+ }
+
+ @RuntimeType
+ //public List getOnlinePlayers(@SuperCall Callable> original) {
+ public List getOnlinePlayers() {
+ try {
+ if (playerList == null) {
+ //var list = original.call();
+ var list = new ArrayList();
+ playerList = new AppendListView<>(list, fakePlayers);
+ }
+ return playerList;
+ } catch (
+ Exception e) {
+ TBMCCoreAPI.SendException("Failed to handle getOnlinePlayers!", e);
+ return null;
+ }
+ }
+
+ @RuntimeType
+ public Object createProfile(UUID uuid, String name) { //Paper's method, casts the player to a CraftPlayer
+ try {
+ var player = uuid != null ? MCChatUtils.LoggedInPlayers.get(uuid) : null;
+ if (player == null && name != null)
+ player = MCChatUtils.LoggedInPlayers.values().stream()
+ .filter(dcp -> dcp.getName().equalsIgnoreCase(name)).findAny().orElse(null);
+ if (player != null)
+ return new CraftPlayerProfile(player.getUniqueId(), player.getName());
+ return null;
+ } catch (Exception e) {
+ TBMCCoreAPI.SendException("Failed to handle createProfile!", e);
+ return null;
+ }
+ }
+
@RequiredArgsConstructor
public static class AppendListView extends AbstractSequentialList {
private final List originalList;