mirror of
https://github.com/Minestom/Minestom.git
synced 2024-09-26 13:43:01 +02:00
Loading mixins from extensions
This commit is contained in:
parent
26b8ad125e
commit
c3c2b0a34c
@ -21,15 +21,14 @@ public class Bootstrap {
|
||||
try {
|
||||
ClassLoader classLoader = MinestomOverwriteClassLoader.getInstance();
|
||||
startMixin(args);
|
||||
MixinEnvironment.init(MixinEnvironment.Phase.INIT);
|
||||
MinestomOverwriteClassLoader.getInstance().addCodeModifier(new MixinCodeModifier());
|
||||
MixinEnvironment.init(MixinEnvironment.Phase.DEFAULT);
|
||||
|
||||
// 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 });
|
||||
|
@ -6,6 +6,7 @@ import net.minestom.server.extras.selfmodification.MinestomOverwriteClassLoader;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.spongepowered.asm.mixin.Mixins;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Constructor;
|
||||
@ -232,7 +233,11 @@ public class ExtensionManager {
|
||||
modifiableClassLoader.loadModifier(extension.files, elem.getAsString());
|
||||
}
|
||||
}
|
||||
// TODO: special support for mixins
|
||||
if(extension.description.has("mixinConfig")) {
|
||||
String mixinConfigFile = extension.description.get("mixinConfig").getAsString();
|
||||
Mixins.addConfiguration(mixinConfigFile);
|
||||
log.info("Found mixin in extension "+extension.description.get("name").getAsString()+": "+mixinConfigFile);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
log.error("Failed to load code modifier for extension in files: "+Arrays.toString(extension.files), e);
|
||||
|
@ -9,7 +9,6 @@ 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;
|
||||
@ -18,6 +17,9 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Class Loader that can modify class bytecode when they are loaded
|
||||
*/
|
||||
@Slf4j
|
||||
public class MinestomOverwriteClassLoader extends URLClassLoader {
|
||||
|
||||
@ -38,8 +40,16 @@ public class MinestomOverwriteClassLoader extends URLClassLoader {
|
||||
add("com.google");
|
||||
add("com.mojang");
|
||||
add("org.objectweb.asm");
|
||||
add("org.slf4j");
|
||||
add("org.apache");
|
||||
add("org.spongepowered");
|
||||
add("net.minestom.server.extras.selfmodification");
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Used to let ASM find out common super types, without actually commiting to loading them
|
||||
* Otherwise ASM would accidentally load classes we might want to modify
|
||||
*/
|
||||
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
|
||||
@ -48,26 +58,9 @@ public class MinestomOverwriteClassLoader extends URLClassLoader {
|
||||
|
||||
// TODO: priorities?
|
||||
private List<CodeModifier> modifiers = new LinkedList<>();
|
||||
private final Method findParentLoadedClass;
|
||||
private final Class<?> loadedCodeModifier;
|
||||
|
||||
private MinestomOverwriteClassLoader(ClassLoader parent) {
|
||||
super("Minestom ClassLoader", loadURLs(), parent);
|
||||
try {
|
||||
findParentLoadedClass = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class);
|
||||
findParentLoadedClass.setAccessible(true);
|
||||
} catch (NoSuchMethodException e) {
|
||||
e.printStackTrace();
|
||||
throw new Error("Failed to access ClassLoader#findLoadedClass", e);
|
||||
}
|
||||
|
||||
try {
|
||||
loadedCodeModifier = loadClass("net.minestom.server.extras.selfmodification.CodeModifier");
|
||||
} catch (ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
throw new Error("Failed to access CodeModifier class.");
|
||||
}
|
||||
|
||||
super("Minestom ClassLoader", extractURLsFromClasspath(), parent);
|
||||
asmClassLoader = newChild(new URL[0]);
|
||||
}
|
||||
|
||||
@ -82,7 +75,7 @@ public class MinestomOverwriteClassLoader extends URLClassLoader {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
private static URL[] loadURLs() {
|
||||
private static URL[] extractURLsFromClasspath() {
|
||||
String classpath = System.getProperty("java.class.path");
|
||||
String[] parts = classpath.split(";");
|
||||
URL[] urls = new URL[parts.length];
|
||||
@ -103,13 +96,6 @@ public class MinestomOverwriteClassLoader extends URLClassLoader {
|
||||
return urls;
|
||||
}
|
||||
|
||||
private static URL[] fromParent(ClassLoader parent) {
|
||||
if(parent instanceof URLClassLoader) {
|
||||
return ((URLClassLoader) parent).getURLs();
|
||||
}
|
||||
return new URL[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
|
||||
Class<?> loadedClass = findLoadedClass(name);
|
||||
@ -117,18 +103,12 @@ public class MinestomOverwriteClassLoader extends URLClassLoader {
|
||||
return loadedClass;
|
||||
|
||||
try {
|
||||
// we do not load system classes by ourselves
|
||||
Class<?> systemClass = ClassLoader.getPlatformClassLoader().loadClass(name);
|
||||
log.trace("System class: "+systemClass);
|
||||
return systemClass;
|
||||
} catch (ClassNotFoundException e) {
|
||||
try {
|
||||
// check if parent already loaded the class
|
||||
Class<?> loadedByParent = (Class<?>) findParentLoadedClass.invoke(getParent(), name);
|
||||
if(loadedByParent != null) {
|
||||
log.trace("Already found in parent: "+loadedByParent);
|
||||
return super.loadClass(name, resolve);
|
||||
}
|
||||
|
||||
if(isProtected(name)) {
|
||||
log.trace("Protected: "+name);
|
||||
return super.loadClass(name, resolve);
|
||||
@ -164,7 +144,17 @@ public class MinestomOverwriteClassLoader extends URLClassLoader {
|
||||
return defined;
|
||||
}
|
||||
|
||||
public byte[] loadBytes(String name, boolean transform) throws IOException {
|
||||
/**
|
||||
* Loads and possibly transforms class bytecode corresponding to the given binary name.
|
||||
* @param name
|
||||
* @param transform
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public byte[] loadBytes(String name, boolean transform) throws IOException, ClassNotFoundException {
|
||||
if(name == null)
|
||||
throw new ClassNotFoundException();
|
||||
String path = name.replace(".", "/") + ".class";
|
||||
byte[] bytes = getResourceAsStream(path).readAllBytes();
|
||||
if(transform && !isProtected(name)) {
|
||||
@ -195,15 +185,12 @@ public class MinestomOverwriteClassLoader extends URLClassLoader {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// overriden to increase access (from protected to public)
|
||||
@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);
|
||||
|
@ -7,6 +7,9 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Global properties service for Mixin
|
||||
*/
|
||||
public class GlobalPropertyServiceMinestom implements IGlobalPropertyService {
|
||||
|
||||
private class BasicProperty implements IPropertyKey {
|
||||
|
@ -7,6 +7,9 @@ import org.spongepowered.asm.service.IClassBytecodeProvider;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Provides class bytecode for Mixin
|
||||
*/
|
||||
public class MinestomBytecodeProvider implements IClassBytecodeProvider {
|
||||
private final MinestomOverwriteClassLoader classLoader;
|
||||
|
||||
|
@ -5,6 +5,9 @@ import org.spongepowered.asm.service.IClassProvider;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Provides classes for Mixin
|
||||
*/
|
||||
public class MinestomClassProvider implements IClassProvider {
|
||||
private final MinestomOverwriteClassLoader classLoader;
|
||||
|
||||
|
@ -1,75 +0,0 @@
|
||||
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<ITransformer> transformers;
|
||||
|
||||
public MinestomTransformerProvider(MinestomOverwriteClassLoader classLoader) {
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTransformerExclusion(String name) {
|
||||
classLoader.protectedClasses.add(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ITransformer> getTransformers() {
|
||||
return getDelegatedTransformers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ITransformer> getDelegatedTransformers() {
|
||||
if(transformers == null) {
|
||||
transformers = buildTransformerList();
|
||||
}
|
||||
return transformers;
|
||||
}
|
||||
|
||||
private List<ITransformer> buildTransformerList() {
|
||||
List<ITransformer> 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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package net.minestom.server.extras.selfmodification.mixins;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.spongepowered.asm.service.IMixinAuditTrail;
|
||||
|
||||
/**
|
||||
* Takes care of logging mixin operations
|
||||
*/
|
||||
@Slf4j
|
||||
public class MixinAuditTrailMinestom implements IMixinAuditTrail {
|
||||
@Override
|
||||
public void onApply(String className, String mixinName) {
|
||||
log.trace("Applied mixin "+mixinName+" to class "+className);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostProcess(String className) {
|
||||
log.trace("Post processing "+className);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGenerate(String className, String generatorName) {
|
||||
log.trace("Generating class "+className+" via generator "+generatorName);
|
||||
}
|
||||
}
|
@ -3,50 +3,44 @@ 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;
|
||||
|
||||
/**
|
||||
* CodeModifier responsible for applying Mixins during class load
|
||||
*/
|
||||
public class MixinCodeModifier extends CodeModifier {
|
||||
|
||||
private Method transformClassMethod;
|
||||
private TreeTransformer processor;
|
||||
/**
|
||||
* Call MixinTransformer's transformClass
|
||||
*/
|
||||
private final Method transformClassMethod;
|
||||
private final TreeTransformer transformer;
|
||||
|
||||
public MixinCodeModifier() {
|
||||
try {
|
||||
// MixinTransformer is package-protected, so we have to force to gain access
|
||||
Class<?> mixinTransformerClass = Class.forName("org.spongepowered.asm.mixin.transformer.MixinTransformer");
|
||||
Constructor<?> ctor = mixinTransformerClass.getDeclaredConstructor();
|
||||
ctor.setAccessible(true);
|
||||
this.processor = (TreeTransformer) ctor.newInstance();
|
||||
this.transformer = (TreeTransformer) ctor.newInstance();
|
||||
|
||||
// we can't access the MixinTransformer type here, so we use reflection to access the method
|
||||
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();
|
||||
} catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException | ClassNotFoundException e) {
|
||||
throw new RuntimeException("Failed to initialize MixinCodeModifier", e);
|
||||
}
|
||||
}
|
||||
|
||||
@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) {
|
||||
return (boolean) transformClassMethod.invoke(transformer, MixinEnvironment.getEnvironment(MixinEnvironment.Phase.DEFAULT), source.name.replace("/", "."), source);
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
|
@ -16,13 +16,13 @@ public class MixinServiceMinestom extends MixinServiceAbstract {
|
||||
private final MinestomOverwriteClassLoader classLoader;
|
||||
private final MinestomClassProvider classProvider;
|
||||
private final MinestomBytecodeProvider bytecodeProvider;
|
||||
private final MinestomTransformerProvider transformerProvider;
|
||||
private final MixinAuditTrailMinestom auditTrail;
|
||||
|
||||
public MixinServiceMinestom() {
|
||||
this.classLoader = MinestomOverwriteClassLoader.getInstance();
|
||||
classProvider = new MinestomClassProvider(classLoader);
|
||||
bytecodeProvider = new MinestomBytecodeProvider(classLoader);
|
||||
transformerProvider = new MinestomTransformerProvider(classLoader);
|
||||
auditTrail = new MixinAuditTrailMinestom();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -47,7 +47,7 @@ public class MixinServiceMinestom extends MixinServiceAbstract {
|
||||
|
||||
@Override
|
||||
public ITransformerProvider getTransformerProvider() {
|
||||
return transformerProvider;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -65,8 +65,6 @@ public class MixinServiceMinestom extends MixinServiceAbstract {
|
||||
return classLoader.getResourceAsStream(name);
|
||||
}
|
||||
|
||||
// TODO: everything below
|
||||
|
||||
@Override
|
||||
public IClassTracker getClassTracker() {
|
||||
return null;
|
||||
@ -74,12 +72,13 @@ public class MixinServiceMinestom extends MixinServiceAbstract {
|
||||
|
||||
@Override
|
||||
public IMixinAuditTrail getAuditTrail() {
|
||||
return null;
|
||||
return auditTrail;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void wire(MixinEnvironment.Phase phase, IConsumer<MixinEnvironment.Phase> phaseConsumer) {
|
||||
super.wire(phase, phaseConsumer);
|
||||
// TODO: hook into Minestom initialization process
|
||||
phaseConsumer.accept(MixinEnvironment.Phase.PREINIT);
|
||||
phaseConsumer.accept(MixinEnvironment.Phase.INIT);
|
||||
}
|
||||
|
@ -6,11 +6,9 @@ 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 class TestExtensionLauncherNoSetup {
|
||||
|
||||
public static void main(String[] args) {
|
||||
MixinBootstrap.init();
|
||||
Mixins.addConfiguration("mixins.testextension.json");
|
||||
Bootstrap.bootstrap("fr.themode.demo.MainDemo", args);
|
||||
}
|
||||
|
@ -14,5 +14,4 @@ public class InstanceContainerMixin {
|
||||
System.out.println("Hello from Mixin!!!");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -3,5 +3,6 @@
|
||||
"name": "Test extension",
|
||||
"codeModifiers": [
|
||||
"testextension.TestModifier"
|
||||
]
|
||||
],
|
||||
"mixinConfig": "mixins.testextension.json"
|
||||
}
|
Loading…
Reference in New Issue
Block a user