diff --git a/src/main/java/buttondevteam/discordplugin/DiscordPlugin.java b/src/main/java/buttondevteam/discordplugin/DiscordPlugin.java index 91db049..60e1044 100755 --- a/src/main/java/buttondevteam/discordplugin/DiscordPlugin.java +++ b/src/main/java/buttondevteam/discordplugin/DiscordPlugin.java @@ -35,6 +35,7 @@ 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.*; @@ -101,17 +102,15 @@ 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); - }*/ + getLogger().info("Load complete"); + } @Override public void pluginEnable() { diff --git a/src/main/java/buttondevteam/discordplugin/broadcaster/PlayerListWatcher.java b/src/main/java/buttondevteam/discordplugin/broadcaster/PlayerListWatcher.java index 39f69ce..c8aa8bd 100755 --- a/src/main/java/buttondevteam/discordplugin/broadcaster/PlayerListWatcher.java +++ b/src/main/java/buttondevteam/discordplugin/broadcaster/PlayerListWatcher.java @@ -1,10 +1,12 @@ package buttondevteam.discordplugin.broadcaster; import buttondevteam.discordplugin.mcchat.MCChatUtils; +import buttondevteam.discordplugin.playerfaker.DelegatingMockMaker; import buttondevteam.lib.TBMCCoreAPI; import lombok.val; import org.bukkit.Bukkit; import org.mockito.Mockito; +import org.mockito.internal.creation.bytebuddy.SubclassByteBuddyMockMaker; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -34,6 +36,7 @@ public class PlayerListWatcher { module.logWarn("Player list already mocked!"); return false; } + DelegatingMockMaker.getInstance().setMockMaker(new SubclassByteBuddyMockMaker()); val icbcl = Class.forName(nms + ".IChatBaseComponent"); Method sendMessageTemp; try { diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/DelegatingMockMaker.java b/src/main/java/buttondevteam/discordplugin/playerfaker/DelegatingMockMaker.java new file mode 100644 index 0000000..116cade --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/playerfaker/DelegatingMockMaker.java @@ -0,0 +1,20 @@ +package buttondevteam.discordplugin.playerfaker; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Delegate; +import org.mockito.internal.creation.bytebuddy.SubclassByteBuddyMockMaker; +import org.mockito.plugins.MockMaker; + +public class DelegatingMockMaker implements MockMaker { + @Getter + @Setter + @Delegate + private MockMaker mockMaker = new SubclassByteBuddyMockMaker(); + @Getter + private static DelegatingMockMaker instance; + + public DelegatingMockMaker() { + instance = this; + } +} diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/ServerWatcher.java b/src/main/java/buttondevteam/discordplugin/playerfaker/ServerWatcher.java index 038d58e..c65a113 100644 --- a/src/main/java/buttondevteam/discordplugin/playerfaker/ServerWatcher.java +++ b/src/main/java/buttondevteam/discordplugin/playerfaker/ServerWatcher.java @@ -1,36 +1,24 @@ 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.objenesis.ObjenesisStd; +import org.mockito.Mockito; +import org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker; -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; + //private ByteBuddy byteBuddy; + //private AsmVisitorWrapper mockTransformer; @IgnoreForBinding public void enableDisable(boolean enable) throws Exception { @@ -38,30 +26,62 @@ public class ServerWatcher { serverField.setAccessible(true); if (enable) { var serverClass = Bukkit.getServer().getClass(); - //var mockMaker = new InlineByteBuddyMockMaker(); - //System.setProperty("net.bytebuddy.experimental", "true"); - /*try { - var resources = cl.getResources("mockito-extensions/" + MockMaker.class.getName()); - System.out.println("Found resources: " + resources); - Iterables.toIterable(resources).forEach(resource -> System.out.println("Resource: " + resource)); - } catch (IOException e) { - throw new IllegalStateException("Failed to load " + MockMaker.class, e); - }*/ - 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); + //var impl = MethodDelegation.to(this); + //var names = Arrays.stream(ServerWatcher.class.getMethods()).map(Method::getName).toArray(String[]::new); + /*if (byteBuddy == null) { + byteBuddy = new ByteBuddy() + .with(TypeValidation.DISABLED) + .with(Implementation.Context.Disabled.Factory.INSTANCE) + .with(MethodGraph.Compiler.ForDeclaredMethods.INSTANCE) + .ignore(isSynthetic().and(not(isConstructor())).or(isDefaultFinalizer())); + + mockTransformer=new InlineByteBuddyMockMaker().createMock() + }*/ + DelegatingMockMaker.getInstance().setMockMaker(new InlineByteBuddyMockMaker()); + 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); 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)); @@ -71,61 +91,25 @@ public class ServerWatcher { serverField.set(null, origServer); } - public Player getPlayer(UUID uuid, @SuperCall Callable original) { + /*@Override + public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { + if (classBeingRedefined == null) + return null; 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 byteBuddy + .redefine( + classBeingRedefined, + ClassFileLocator.Simple.of(classBeingRedefined.getName(), classfileBuffer) + ) + //.visit(new InlineBytecodeGenerator.ParameterWritingVisitorWrapper(classBeingRedefined)) + .visit(mockTransformer) + .make() + .getBytes(); + } catch (Throwable throwable) { + TBMCCoreAPI.SendException("Failed to transform class!", throwable); 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 { diff --git a/src/main/resources/mockito-extensions/org.mockito.plugins.MockMaker b/src/main/resources/mockito-extensions/org.mockito.plugins.MockMaker index 7da34da..deb6307 100644 --- a/src/main/resources/mockito-extensions/org.mockito.plugins.MockMaker +++ b/src/main/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -1 +1 @@ -org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker \ No newline at end of file +buttondevteam.discordplugin.playerfaker.DelegatingMockMaker \ No newline at end of file