From 26b8ad125e7dc441e30cba474ba6a2e5bc06f71a Mon Sep 17 00:00:00 2001 From: jglrxavpok Date: Sun, 23 Aug 2020 22:38:27 +0200 Subject: [PATCH] Very basic Mixin support --- build.gradle | 13 +- gradle.properties | 2 + .../java/net/minestom/server/Bootstrap.java | 30 ++++- .../MinestomOverwriteClassLoader.java | 123 +++++++++++++----- .../mixins/GlobalPropertyServiceMinestom.java | 68 ++++++++++ .../mixins/MinestomBytecodeProvider.java | 38 ++++++ .../mixins/MinestomClassProvider.java | 34 +++++ .../mixins/MinestomTransformerProvider.java | 75 +++++++++++ .../mixins/MixinCodeModifier.java | 59 +++++++++ .../mixins/MixinPlatformAgentMinestom.java | 29 +++++ .../mixins/MixinServiceMinestom.java | 86 ++++++++++++ .../mixins/MixinServiceMinestomBootstrap.java | 20 +++ ...powered.asm.service.IGlobalPropertyService | 1 + ...rg.spongepowered.asm.service.IMixinService | 1 + ...powered.asm.service.IMixinServiceBootstrap | 1 + src/main/resources/log4j2.xml | 7 +- .../testextension/TestExtensionLauncher.java | 4 + .../TestExtensionLauncherArgs.java | 17 +++ src/test/java/testextension/TestModifier.java | 2 +- .../mixins/InstanceContainerMixin.java | 18 +++ src/test/resources/mixins.testextension.json | 10 ++ 21 files changed, 599 insertions(+), 39 deletions(-) create mode 100644 gradle.properties create mode 100644 src/main/java/net/minestom/server/extras/selfmodification/mixins/GlobalPropertyServiceMinestom.java create mode 100644 src/main/java/net/minestom/server/extras/selfmodification/mixins/MinestomBytecodeProvider.java create mode 100644 src/main/java/net/minestom/server/extras/selfmodification/mixins/MinestomClassProvider.java create mode 100644 src/main/java/net/minestom/server/extras/selfmodification/mixins/MinestomTransformerProvider.java create mode 100644 src/main/java/net/minestom/server/extras/selfmodification/mixins/MixinCodeModifier.java create mode 100644 src/main/java/net/minestom/server/extras/selfmodification/mixins/MixinPlatformAgentMinestom.java create mode 100644 src/main/java/net/minestom/server/extras/selfmodification/mixins/MixinServiceMinestom.java create mode 100644 src/main/java/net/minestom/server/extras/selfmodification/mixins/MixinServiceMinestomBootstrap.java create mode 100644 src/main/resources/META-INF/services/org.spongepowered.asm.service.IGlobalPropertyService create mode 100644 src/main/resources/META-INF/services/org.spongepowered.asm.service.IMixinService create mode 100644 src/main/resources/META-INF/services/org.spongepowered.asm.service.IMixinServiceBootstrap create mode 100644 src/test/java/testextension/TestExtensionLauncherArgs.java create mode 100644 src/test/java/testextension/mixins/InstanceContainerMixin.java create mode 100644 src/test/resources/mixins.testextension.json diff --git a/build.gradle b/build.gradle index 0e5dbc815..50e28e17e 100644 --- a/build.gradle +++ b/build.gradle @@ -30,6 +30,10 @@ allprojects { mavenCentral() maven { url 'https://libraries.minecraft.net' } maven { url 'https://jitpack.io' } + maven { + name 'sponge' + url 'http://repo.spongepowered.org/maven' + } } } @@ -112,8 +116,13 @@ dependencies { annotationProcessor 'org.projectlombok:lombok:1.18.12' // Code modification - api "org.ow2.asm:asm:6.2.1" - api "org.ow2.asm:asm-tree:6.2.1" + api "org.ow2.asm:asm:${asmVersion}" + api "org.ow2.asm:asm-tree:${asmVersion}" + api "org.ow2.asm:asm-analysis:${asmVersion}" + api "org.ow2.asm:asm-util:${asmVersion}" + api "org.ow2.asm:asm-commons:${asmVersion}" + implementation 'com.google.guava:guava:21.0' + api "org.spongepowered:mixin:${mixinVersion}" // Path finding api 'com.github.MadMartian:hydrazine-path-finding:1.3.1' diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 000000000..13d2e376f --- /dev/null +++ b/gradle.properties @@ -0,0 +1,2 @@ +asmVersion=8.0.1 +mixinVersion=0.8 \ No newline at end of file diff --git a/src/main/java/net/minestom/server/Bootstrap.java b/src/main/java/net/minestom/server/Bootstrap.java index ff48514bd..0c0f20e43 100644 --- a/src/main/java/net/minestom/server/Bootstrap.java +++ b/src/main/java/net/minestom/server/Bootstrap.java @@ -1,9 +1,16 @@ package net.minestom.server; import net.minestom.server.extras.selfmodification.MinestomOverwriteClassLoader; +import net.minestom.server.extras.selfmodification.mixins.MixinCodeModifier; +import org.spongepowered.asm.launch.MixinBootstrap; +import org.spongepowered.asm.launch.platform.CommandLineOptions; +import org.spongepowered.asm.launch.platform.MixinPlatformManager; +import org.spongepowered.asm.mixin.MixinEnvironment; +import org.spongepowered.asm.mixin.Mixins; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.Arrays; /** * Used to launch Minestom with the {@link MinestomOverwriteClassLoader} to allow for self-modifications @@ -12,13 +19,17 @@ public class Bootstrap { public static void bootstrap(String mainClassFullName, String[] args) { try { - ClassLoader classLoader = new MinestomOverwriteClassLoader(Bootstrap.class.getClassLoader()); + ClassLoader classLoader = MinestomOverwriteClassLoader.getInstance(); + startMixin(args); + MixinEnvironment.init(MixinEnvironment.Phase.INIT); + MinestomOverwriteClassLoader.getInstance().addCodeModifier(new MixinCodeModifier()); // ensure extensions are loaded when starting the server Class serverClass = classLoader.loadClass("net.minestom.server.MinecraftServer"); Method init = serverClass.getMethod("init"); init.invoke(null); + Class mainClass = classLoader.loadClass(mainClassFullName); Method main = mainClass.getDeclaredMethod("main", String[].class); main.invoke(null, new Object[] { args }); @@ -26,4 +37,21 @@ public class Bootstrap { e.printStackTrace(); } } + + private static void startMixin(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + Method start = MixinBootstrap.class.getDeclaredMethod("start"); + start.setAccessible(true); + if (! ((boolean)start.invoke(null)) ) { + return; + } + + Method doInit = MixinBootstrap.class.getDeclaredMethod("doInit", CommandLineOptions.class); + doInit.setAccessible(true); + doInit.invoke(null, CommandLineOptions.ofArgs(Arrays.asList(args))); + + MixinBootstrap.getPlatform().inject(); + Mixins.getConfigs().forEach(c -> { + MinestomOverwriteClassLoader.getInstance().protectedPackages.add(c.getConfig().getMixinPackage()); + }); + } } diff --git a/src/main/java/net/minestom/server/extras/selfmodification/MinestomOverwriteClassLoader.java b/src/main/java/net/minestom/server/extras/selfmodification/MinestomOverwriteClassLoader.java index 80bded808..8690db615 100644 --- a/src/main/java/net/minestom/server/extras/selfmodification/MinestomOverwriteClassLoader.java +++ b/src/main/java/net/minestom/server/extras/selfmodification/MinestomOverwriteClassLoader.java @@ -7,11 +7,13 @@ import org.objectweb.asm.ClassWriter; import org.objectweb.asm.tree.ClassNode; import java.io.File; +import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; @@ -19,14 +21,25 @@ import java.util.Set; @Slf4j public class MinestomOverwriteClassLoader extends URLClassLoader { + private static MinestomOverwriteClassLoader INSTANCE; + /** * Classes that cannot be loaded/modified by this classloader. * Will go through parent class loader */ - private static final Set protectedClasses = Set.of( - "net.minestom.server.extras.selfmodification.CodeModifier", - "net.minestom.server.extras.selfmodification.MinestomOverwriteClassLoader" - ); + public final Set protectedClasses = new HashSet<>() { + { + add("net.minestom.server.extras.selfmodification.CodeModifier"); + add("net.minestom.server.extras.selfmodification.MinestomOverwriteClassLoader"); + } + }; + public final Set protectedPackages = new HashSet<>() { + { + add("com.google"); + add("com.mojang"); + add("org.objectweb.asm"); + } + }; private final URLClassLoader asmClassLoader; // TODO: replace by tree to optimize lookup times. We can use the fact that package names are split with '.' to allow for fast lookup @@ -38,7 +51,7 @@ public class MinestomOverwriteClassLoader extends URLClassLoader { private final Method findParentLoadedClass; private final Class loadedCodeModifier; - public MinestomOverwriteClassLoader(ClassLoader parent) { + private MinestomOverwriteClassLoader(ClassLoader parent) { super("Minestom ClassLoader", loadURLs(), parent); try { findParentLoadedClass = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class); @@ -58,6 +71,17 @@ public class MinestomOverwriteClassLoader extends URLClassLoader { asmClassLoader = newChild(new URL[0]); } + public static MinestomOverwriteClassLoader getInstance() { + if(INSTANCE == null) { + synchronized (MinestomOverwriteClassLoader.class) { + if(INSTANCE == null) { + INSTANCE = new MinestomOverwriteClassLoader(MinestomOverwriteClassLoader.class.getClassLoader()); + } + } + } + return INSTANCE; + } + private static URL[] loadURLs() { String classpath = System.getProperty("java.class.path"); String[] parts = classpath.split(";"); @@ -110,9 +134,7 @@ public class MinestomOverwriteClassLoader extends URLClassLoader { return super.loadClass(name, resolve); } - String path = name.replace(".", "/") + ".class"; - byte[] bytes = getResourceAsStream(path).readAllBytes(); - return transformAndLoad(name, bytes, resolve); + return define(name, loadBytes(name, true), resolve); } catch (Exception ex) { log.trace("Fail to load class, resorting to parent loader: "+name, ex); // fail to load class, let parent load @@ -123,32 +145,17 @@ public class MinestomOverwriteClassLoader extends URLClassLoader { } private boolean isProtected(String name) { - return protectedClasses.contains(name); + if(!protectedClasses.contains(name)) { + for(String start : protectedPackages) { + if(name.startsWith(start)) + return true; + } + return false; + } + return true; } - private Class transformAndLoad(String name, byte[] bytes, boolean resolve) throws ClassNotFoundException { - ClassReader reader = new ClassReader(bytes); - ClassNode node = new ClassNode(); - reader.accept(node, 0); - boolean modified = false; - synchronized (modifiers) { - for(CodeModifier modifier : modifiers) { - boolean shouldModify = modifier.getNamespace() == null || name.startsWith(modifier.getNamespace()); - if(shouldModify) { - modified |= modifier.transform(node); - } - } - } - if(modified) { - ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES) { - @Override - protected ClassLoader getClassLoader() { - return asmClassLoader; - } - }; - node.accept(writer); - bytes = writer.toByteArray(); - } + private Class define(String name, byte[] bytes, boolean resolve) throws ClassNotFoundException { Class defined = defineClass(name, bytes, 0, bytes.length); log.trace("Loaded with code modifiers: "+name); if(resolve) { @@ -157,6 +164,46 @@ public class MinestomOverwriteClassLoader extends URLClassLoader { return defined; } + public byte[] loadBytes(String name, boolean transform) throws IOException { + String path = name.replace(".", "/") + ".class"; + byte[] bytes = getResourceAsStream(path).readAllBytes(); + if(transform && !isProtected(name)) { + ClassReader reader = new ClassReader(bytes); + ClassNode node = new ClassNode(); + reader.accept(node, 0); + boolean modified = false; + synchronized (modifiers) { + for(CodeModifier modifier : modifiers) { + boolean shouldModify = modifier.getNamespace() == null || name.startsWith(modifier.getNamespace()); + if(shouldModify) { + modified |= modifier.transform(node); + } + } + } + if(modified) { + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES) { + @Override + protected ClassLoader getClassLoader() { + return asmClassLoader; + } + }; + node.accept(writer); + bytes = writer.toByteArray(); + log.trace("Modified "+name); + } + } + return bytes; + } + + @Override + public Class findClass(String name) throws ClassNotFoundException { + return super.findClass(name); + } + + public void resolve(Class clazz) { + resolveClass(clazz); + } + @NotNull public URLClassLoader newChild(@NotNull URL[] urls) { return URLClassLoader.newInstance(urls, this); @@ -174,11 +221,21 @@ public class MinestomOverwriteClassLoader extends URLClassLoader { CodeModifier modifier = (CodeModifier) modifierClass.getDeclaredConstructor().newInstance(); synchronized (modifiers) { log.warn("Added Code modifier: "+modifier); - modifiers.add(modifier); + addCodeModifier(modifier); } } } catch (MalformedURLException | ClassNotFoundException | InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e) { e.printStackTrace(); } } + + public void addCodeModifier(CodeModifier modifier) { + synchronized (modifiers) { + modifiers.add(modifier); + } + } + + public List getModifiers() { + return modifiers; + } } diff --git a/src/main/java/net/minestom/server/extras/selfmodification/mixins/GlobalPropertyServiceMinestom.java b/src/main/java/net/minestom/server/extras/selfmodification/mixins/GlobalPropertyServiceMinestom.java new file mode 100644 index 000000000..a33cc14b8 --- /dev/null +++ b/src/main/java/net/minestom/server/extras/selfmodification/mixins/GlobalPropertyServiceMinestom.java @@ -0,0 +1,68 @@ +package net.minestom.server.extras.selfmodification.mixins; + +import org.spongepowered.asm.service.IGlobalPropertyService; +import org.spongepowered.asm.service.IPropertyKey; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public class GlobalPropertyServiceMinestom implements IGlobalPropertyService { + + private class BasicProperty implements IPropertyKey { + + private final String name; + + public BasicProperty(String name) { + this.name = name; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BasicProperty that = (BasicProperty) o; + return Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } + + @Override + public String toString() { + return "BasicProperty{" + + "name='" + name + '\'' + + '}'; + } + } + + private final Map keys = new HashMap<>(); + private final Map values = new HashMap<>(); + + @Override + public IPropertyKey resolveKey(String name) { + return keys.computeIfAbsent(name, k -> new BasicProperty(k)); + } + + @Override + public T getProperty(IPropertyKey key) { + return (T) values.get(key); + } + + @Override + public void setProperty(IPropertyKey key, Object value) { + values.put(key, value); + } + + @Override + public T getProperty(IPropertyKey key, T defaultValue) { + return (T) values.getOrDefault(key, defaultValue); + } + + @Override + public String getPropertyString(IPropertyKey key, String defaultValue) { + return (String) values.getOrDefault(key, defaultValue); + } +} diff --git a/src/main/java/net/minestom/server/extras/selfmodification/mixins/MinestomBytecodeProvider.java b/src/main/java/net/minestom/server/extras/selfmodification/mixins/MinestomBytecodeProvider.java new file mode 100644 index 000000000..e303a6bcc --- /dev/null +++ b/src/main/java/net/minestom/server/extras/selfmodification/mixins/MinestomBytecodeProvider.java @@ -0,0 +1,38 @@ +package net.minestom.server.extras.selfmodification.mixins; + +import net.minestom.server.extras.selfmodification.MinestomOverwriteClassLoader; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.tree.ClassNode; +import org.spongepowered.asm.service.IClassBytecodeProvider; + +import java.io.IOException; + +public class MinestomBytecodeProvider implements IClassBytecodeProvider { + private final MinestomOverwriteClassLoader classLoader; + + public MinestomBytecodeProvider(MinestomOverwriteClassLoader classLoader) { + this.classLoader = classLoader; + } + + @Override + public ClassNode getClassNode(String name) throws ClassNotFoundException, IOException { + return getClassNode(name, false); + } + + private ClassNode loadNode(String name, boolean transform) throws ClassNotFoundException { + ClassNode node = new ClassNode(); + ClassReader reader; + try { + reader = new ClassReader(classLoader.loadBytes(name, transform)); + } catch (IOException e) { + throw new ClassNotFoundException("Could not load ClassNode with name "+name, e); + } + reader.accept(node, 0); + return node; + } + + @Override + public ClassNode getClassNode(String name, boolean runTransformers) throws ClassNotFoundException, IOException { + return loadNode(name, runTransformers); + } +} diff --git a/src/main/java/net/minestom/server/extras/selfmodification/mixins/MinestomClassProvider.java b/src/main/java/net/minestom/server/extras/selfmodification/mixins/MinestomClassProvider.java new file mode 100644 index 000000000..7ee9ce2c1 --- /dev/null +++ b/src/main/java/net/minestom/server/extras/selfmodification/mixins/MinestomClassProvider.java @@ -0,0 +1,34 @@ +package net.minestom.server.extras.selfmodification.mixins; + +import net.minestom.server.extras.selfmodification.MinestomOverwriteClassLoader; +import org.spongepowered.asm.service.IClassProvider; + +import java.net.URL; + +public class MinestomClassProvider implements IClassProvider { + private final MinestomOverwriteClassLoader classLoader; + + public MinestomClassProvider(MinestomOverwriteClassLoader classLoader) { + this.classLoader = classLoader; + } + + @Override + public URL[] getClassPath() { + return classLoader.getURLs(); + } + + @Override + public Class findClass(String name) throws ClassNotFoundException { + return classLoader.findClass(name); + } + + @Override + public Class findClass(String name, boolean initialize) throws ClassNotFoundException { + return Class.forName(name, initialize, Thread.currentThread().getContextClassLoader()); + } + + @Override + public Class findAgentClass(String name, boolean initialize) throws ClassNotFoundException { + return Class.forName(name, initialize, classLoader); + } +} diff --git a/src/main/java/net/minestom/server/extras/selfmodification/mixins/MinestomTransformerProvider.java b/src/main/java/net/minestom/server/extras/selfmodification/mixins/MinestomTransformerProvider.java new file mode 100644 index 000000000..43bbac597 --- /dev/null +++ b/src/main/java/net/minestom/server/extras/selfmodification/mixins/MinestomTransformerProvider.java @@ -0,0 +1,75 @@ +package net.minestom.server.extras.selfmodification.mixins; + +import net.minestom.server.extras.selfmodification.CodeModifier; +import net.minestom.server.extras.selfmodification.MinestomOverwriteClassLoader; +import org.objectweb.asm.tree.ClassNode; +import org.spongepowered.asm.service.ITransformer; +import org.spongepowered.asm.service.ITransformerProvider; +import org.spongepowered.asm.service.ITreeClassTransformer; + +import java.lang.reflect.InvocationTargetException; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +public class MinestomTransformerProvider implements ITransformerProvider { + private final MinestomOverwriteClassLoader classLoader; + private List transformers; + + public MinestomTransformerProvider(MinestomOverwriteClassLoader classLoader) { + this.classLoader = classLoader; + } + + @Override + public void addTransformerExclusion(String name) { + classLoader.protectedClasses.add(name); + } + + @Override + public Collection getTransformers() { + return getDelegatedTransformers(); + } + + @Override + public Collection getDelegatedTransformers() { + if(transformers == null) { + transformers = buildTransformerList(); + } + return transformers; + } + + private List buildTransformerList() { + List result = new LinkedList<>(); + for(CodeModifier modifier : classLoader.getModifiers()) { + result.add(toMixin(modifier)); + } + + try { + Class clazz = classLoader.loadClass("org.spongepowered.asm.mixin.transformer.MixingTransformer"); + ITransformer mixinTransformer = (ITransformer) clazz.getDeclaredConstructor().newInstance(); + result.add(mixinTransformer); + } catch (ClassNotFoundException | InstantiationException | InvocationTargetException | NoSuchMethodException | IllegalAccessException e) { + e.printStackTrace(); + } + return result; + } + + private ITransformer toMixin(CodeModifier modifier) { + return new ITreeClassTransformer() { + @Override + public boolean transformClassNode(String name, String transformedName, ClassNode classNode) { + return modifier.transform(classNode); + } + + @Override + public String getName() { + return modifier.getClass().getName(); + } + + @Override + public boolean isDelegationExcluded() { + return false; + } + }; + } +} diff --git a/src/main/java/net/minestom/server/extras/selfmodification/mixins/MixinCodeModifier.java b/src/main/java/net/minestom/server/extras/selfmodification/mixins/MixinCodeModifier.java new file mode 100644 index 000000000..7c5dc4123 --- /dev/null +++ b/src/main/java/net/minestom/server/extras/selfmodification/mixins/MixinCodeModifier.java @@ -0,0 +1,59 @@ +package net.minestom.server.extras.selfmodification.mixins; + +import net.minestom.server.extras.selfmodification.CodeModifier; +import org.objectweb.asm.tree.ClassNode; +import org.spongepowered.asm.mixin.MixinEnvironment; +import org.spongepowered.asm.mixin.transformer.MixinProcessor; +import org.spongepowered.asm.mixin.transformer.ext.Extensions; +import org.spongepowered.asm.mixin.transformer.ext.IHotSwap; +import org.spongepowered.asm.service.ISyntheticClassInfo; +import org.spongepowered.asm.service.ISyntheticClassRegistry; +import org.spongepowered.asm.transformers.TreeTransformer; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public class MixinCodeModifier extends CodeModifier { + + private Method transformClassMethod; + private TreeTransformer processor; + + public MixinCodeModifier() { + try { + Class mixinTransformerClass = Class.forName("org.spongepowered.asm.mixin.transformer.MixinTransformer"); + Constructor ctor = mixinTransformerClass.getDeclaredConstructor(); + ctor.setAccessible(true); + this.processor = (TreeTransformer) ctor.newInstance(); + transformClassMethod = mixinTransformerClass.getDeclaredMethod("transformClass", MixinEnvironment.class, String.class, ClassNode.class); + transformClassMethod.setAccessible(true); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InstantiationException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + } + + @Override + public boolean transform(ClassNode source) { + try { + return (boolean) transformClassMethod.invoke(processor, MixinEnvironment.getEnvironment(MixinEnvironment.Phase.DEFAULT), source.name.replace("/", "."), source); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + return false; + } + + @Override + public String getNamespace() { + return null; + } +} diff --git a/src/main/java/net/minestom/server/extras/selfmodification/mixins/MixinPlatformAgentMinestom.java b/src/main/java/net/minestom/server/extras/selfmodification/mixins/MixinPlatformAgentMinestom.java new file mode 100644 index 000000000..c35bf2534 --- /dev/null +++ b/src/main/java/net/minestom/server/extras/selfmodification/mixins/MixinPlatformAgentMinestom.java @@ -0,0 +1,29 @@ +package net.minestom.server.extras.selfmodification.mixins; + +import org.spongepowered.asm.launch.platform.IMixinPlatformServiceAgent; +import org.spongepowered.asm.launch.platform.MixinPlatformAgentAbstract; +import org.spongepowered.asm.launch.platform.MixinPlatformManager; +import org.spongepowered.asm.launch.platform.container.IContainerHandle; +import org.spongepowered.asm.util.Constants; + +import java.util.Collection; + +public class MixinPlatformAgentMinestom extends MixinPlatformAgentAbstract implements IMixinPlatformServiceAgent { + @Override + public void init() { } + + @Override + public String getSideName() { + return Constants.SIDE_SERVER; + } + + @Override + public AcceptResult accept(MixinPlatformManager manager, IContainerHandle handle) { + return AcceptResult.ACCEPTED; + } + + @Override + public Collection getMixinContainers() { + return null; + } +} diff --git a/src/main/java/net/minestom/server/extras/selfmodification/mixins/MixinServiceMinestom.java b/src/main/java/net/minestom/server/extras/selfmodification/mixins/MixinServiceMinestom.java new file mode 100644 index 000000000..878aac9e8 --- /dev/null +++ b/src/main/java/net/minestom/server/extras/selfmodification/mixins/MixinServiceMinestom.java @@ -0,0 +1,86 @@ +package net.minestom.server.extras.selfmodification.mixins; + +import net.minestom.server.extras.selfmodification.MinestomOverwriteClassLoader; +import org.spongepowered.asm.launch.platform.container.ContainerHandleVirtual; +import org.spongepowered.asm.launch.platform.container.IContainerHandle; +import org.spongepowered.asm.mixin.MixinEnvironment; +import org.spongepowered.asm.service.*; +import org.spongepowered.asm.util.IConsumer; + +import java.io.InputStream; +import java.util.Collection; +import java.util.Collections; + +public class MixinServiceMinestom extends MixinServiceAbstract { + + private final MinestomOverwriteClassLoader classLoader; + private final MinestomClassProvider classProvider; + private final MinestomBytecodeProvider bytecodeProvider; + private final MinestomTransformerProvider transformerProvider; + + public MixinServiceMinestom() { + this.classLoader = MinestomOverwriteClassLoader.getInstance(); + classProvider = new MinestomClassProvider(classLoader); + bytecodeProvider = new MinestomBytecodeProvider(classLoader); + transformerProvider = new MinestomTransformerProvider(classLoader); + } + + @Override + public String getName() { + return "Minestom"; + } + + @Override + public boolean isValid() { + return true; + } + + @Override + public IClassProvider getClassProvider() { + return classProvider; + } + + @Override + public IClassBytecodeProvider getBytecodeProvider() { + return bytecodeProvider; + } + + @Override + public ITransformerProvider getTransformerProvider() { + return transformerProvider; + } + + @Override + public Collection getPlatformAgents() { + return Collections.singletonList("net.minestom.server.extras.selfmodification.mixins.MixinPlatformAgentMinestom"); + } + + @Override + public IContainerHandle getPrimaryContainer() { + return new ContainerHandleVirtual("Minestom"); + } + + @Override + public InputStream getResourceAsStream(String name) { + return classLoader.getResourceAsStream(name); + } + + // TODO: everything below + + @Override + public IClassTracker getClassTracker() { + return null; + } + + @Override + public IMixinAuditTrail getAuditTrail() { + return null; + } + + @Override + public void wire(MixinEnvironment.Phase phase, IConsumer phaseConsumer) { + super.wire(phase, phaseConsumer); + phaseConsumer.accept(MixinEnvironment.Phase.PREINIT); + phaseConsumer.accept(MixinEnvironment.Phase.INIT); + } +} diff --git a/src/main/java/net/minestom/server/extras/selfmodification/mixins/MixinServiceMinestomBootstrap.java b/src/main/java/net/minestom/server/extras/selfmodification/mixins/MixinServiceMinestomBootstrap.java new file mode 100644 index 000000000..8d514d417 --- /dev/null +++ b/src/main/java/net/minestom/server/extras/selfmodification/mixins/MixinServiceMinestomBootstrap.java @@ -0,0 +1,20 @@ +package net.minestom.server.extras.selfmodification.mixins; + +import org.spongepowered.asm.service.IMixinServiceBootstrap; + +public class MixinServiceMinestomBootstrap implements IMixinServiceBootstrap { + @Override + public String getName() { + return "MinestomBootstrap"; + } + + @Override + public String getServiceClassName() { + return "net.minestom.server.extras.selfmodification.mixins.MixinServiceMinestom"; + } + + @Override + public void bootstrap() { + + } +} diff --git a/src/main/resources/META-INF/services/org.spongepowered.asm.service.IGlobalPropertyService b/src/main/resources/META-INF/services/org.spongepowered.asm.service.IGlobalPropertyService new file mode 100644 index 000000000..74a63c27d --- /dev/null +++ b/src/main/resources/META-INF/services/org.spongepowered.asm.service.IGlobalPropertyService @@ -0,0 +1 @@ +net.minestom.server.extras.selfmodification.mixins.GlobalPropertyServiceMinestom \ No newline at end of file diff --git a/src/main/resources/META-INF/services/org.spongepowered.asm.service.IMixinService b/src/main/resources/META-INF/services/org.spongepowered.asm.service.IMixinService new file mode 100644 index 000000000..113a8c7ca --- /dev/null +++ b/src/main/resources/META-INF/services/org.spongepowered.asm.service.IMixinService @@ -0,0 +1 @@ +net.minestom.server.extras.selfmodification.mixins.MixinServiceMinestom \ No newline at end of file diff --git a/src/main/resources/META-INF/services/org.spongepowered.asm.service.IMixinServiceBootstrap b/src/main/resources/META-INF/services/org.spongepowered.asm.service.IMixinServiceBootstrap new file mode 100644 index 000000000..d357615c7 --- /dev/null +++ b/src/main/resources/META-INF/services/org.spongepowered.asm.service.IMixinServiceBootstrap @@ -0,0 +1 @@ +net.minestom.server.extras.selfmodification.mixins.MixinServiceMinestomBootstrap \ No newline at end of file diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index f8c195373..ea0bfe2c8 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -2,13 +2,16 @@ - + + + + - + \ No newline at end of file diff --git a/src/test/java/testextension/TestExtensionLauncher.java b/src/test/java/testextension/TestExtensionLauncher.java index a7529725a..6479b71af 100644 --- a/src/test/java/testextension/TestExtensionLauncher.java +++ b/src/test/java/testextension/TestExtensionLauncher.java @@ -1,12 +1,16 @@ package testextension; import net.minestom.server.Bootstrap; +import org.spongepowered.asm.launch.MixinBootstrap; +import org.spongepowered.asm.mixin.Mixins; // To launch with VM arguments: // -Dminestom.extension.indevfolder.classes=build/classes/java/test/ -Dminestom.extension.indevfolder.resources=build/resources/test/ public class TestExtensionLauncher { public static void main(String[] args) { + MixinBootstrap.init(); + Mixins.addConfiguration("mixins.testextension.json"); Bootstrap.bootstrap("fr.themode.demo.MainDemo", args); } diff --git a/src/test/java/testextension/TestExtensionLauncherArgs.java b/src/test/java/testextension/TestExtensionLauncherArgs.java new file mode 100644 index 000000000..f9130f23f --- /dev/null +++ b/src/test/java/testextension/TestExtensionLauncherArgs.java @@ -0,0 +1,17 @@ +package testextension; + +import net.minestom.server.Bootstrap; + +// To launch with VM arguments: +// -Dminestom.extension.indevfolder.classes=build/classes/java/test/ -Dminestom.extension.indevfolder.resources=build/resources/test/ +public class TestExtensionLauncherArgs { + + public static void main(String[] args) { + String[] argsWithMixins = new String[args.length+2]; + System.arraycopy(args, 0, argsWithMixins, 0, args.length); + argsWithMixins[argsWithMixins.length-2] = "--mixin"; + argsWithMixins[argsWithMixins.length-1] = "mixins.testextension.json"; + Bootstrap.bootstrap("fr.themode.demo.MainDemo", argsWithMixins); + } + +} diff --git a/src/test/java/testextension/TestModifier.java b/src/test/java/testextension/TestModifier.java index fccc05940..602ba13f6 100644 --- a/src/test/java/testextension/TestModifier.java +++ b/src/test/java/testextension/TestModifier.java @@ -10,7 +10,7 @@ public class TestModifier extends CodeModifier implements Opcodes { @Override public boolean transform(ClassNode source) { if(source.name.equals("net/minestom/server/instance/InstanceContainer")) { - System.out.println("Modifying code source of "+source.name); + System.out.println("Modifying code of "+source.name); MethodNode constructor = findConstructor(source.methods); constructor.instructions.insert(constructor.instructions.getFirst(), buildInjectionCode()); return true; diff --git a/src/test/java/testextension/mixins/InstanceContainerMixin.java b/src/test/java/testextension/mixins/InstanceContainerMixin.java new file mode 100644 index 000000000..972ac6c51 --- /dev/null +++ b/src/test/java/testextension/mixins/InstanceContainerMixin.java @@ -0,0 +1,18 @@ +package testextension.mixins; + +import net.minestom.server.instance.InstanceContainer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(InstanceContainer.class) +public class InstanceContainerMixin { + + @Inject(method = "", at = @At("RETURN")) + private void onRunHead(CallbackInfo ci) { + System.out.println("Hello from Mixin!!!"); + } + + +} diff --git a/src/test/resources/mixins.testextension.json b/src/test/resources/mixins.testextension.json new file mode 100644 index 000000000..42df826a5 --- /dev/null +++ b/src/test/resources/mixins.testextension.json @@ -0,0 +1,10 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "testextension.mixins", + "target": "@env(DEFAULT)", + "compatibilityLevel": "JAVA_11", + "mixins": [ + "InstanceContainerMixin" + ] +} \ No newline at end of file