remove code modification, bootstrap + MinestomRootClassLoader still required for now

This commit is contained in:
Matt Worzala 2021-08-23 10:04:12 -04:00 committed by TheMode
parent 91a8607710
commit 18ecefd97a
23 changed files with 70 additions and 784 deletions

View File

@ -136,14 +136,6 @@ dependencies {
// Guava 21.0+ required for Mixin
api 'com.google.guava:guava:31.0.1-jre'
// Code modification
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}"
api "org.spongepowered:mixin:${mixinVersion}"
// Path finding
api 'com.github.MadMartian:hydrazine-path-finding:1.6.0'

View File

@ -1,8 +1,6 @@
# Update this version with every release. It is purely used for the code generator and data dependency.
mcVersion = 1.17
asmVersion=9.2
mixinVersion=0.8.4
hephaistosVersion=2.3.2
hephaistosVersion=v2.3.2
kotlinVersion=1.5.31
adventureVersion=4.9.3

View File

@ -1,17 +1,9 @@
package net.minestom.server;
import net.minestom.server.extensions.ExtensionManager;
import net.minestom.server.extras.selfmodification.MinestomRootClassLoader;
import net.minestom.server.extras.selfmodification.mixins.MixinCodeModifier;
import net.minestom.server.extras.selfmodification.mixins.MixinServiceMinestom;
import org.spongepowered.asm.launch.MixinBootstrap;
import org.spongepowered.asm.launch.platform.CommandLineOptions;
import org.spongepowered.asm.mixin.Mixins;
import org.spongepowered.asm.service.ServiceNotAvailableError;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* Used to launch Minestom with the {@link MinestomRootClassLoader} to allow for self-modifications
@ -21,24 +13,6 @@ public final class Bootstrap {
public static void bootstrap(String mainClassFullName, String[] args) {
try {
ClassLoader classLoader = MinestomRootClassLoader.getInstance();
startMixin(args);
try {
MinestomRootClassLoader.getInstance().addCodeModifier(new MixinCodeModifier());
} catch (RuntimeException e) {
e.printStackTrace();
System.err.println("Failed to add MixinCodeModifier, mixins will not be injected. Check the log entries above to debug.");
}
ExtensionManager.loadCodeModifiersEarly();
MixinServiceMinestom.gotoPreinitPhase();
// ensure extensions are loaded when starting the server
Class<?> serverClass = classLoader.loadClass("net.minestom.server.MinecraftServer");
Method init = serverClass.getMethod("init");
init.invoke(null);
MixinServiceMinestom.gotoInitPhase();
MixinServiceMinestom.gotoDefaultPhase();
Class<?> mainClass = classLoader.loadClass(mainClassFullName);
Method main = mainClass.getDeclaredMethod("main", String[].class);
@ -48,29 +22,4 @@ public final class Bootstrap {
e.printStackTrace();
}
}
private static void startMixin(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
// hacks required to pass custom arguments
Method start = MixinBootstrap.class.getDeclaredMethod("start");
start.setAccessible(true);
try {
if (!((boolean) start.invoke(null))) {
return;
}
} catch (ServiceNotAvailableError e) {
e.printStackTrace();
System.err.println("Failed to load Mixin, see error above.");
System.err.println("It is possible you simply have two files with identical names inside your server jar. " +
"Check your META-INF/services directory inside your Minestom implementation and merge files with identical names inside META-INF/services.");
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 -> MinestomRootClassLoader.getInstance().protectedPackages.add(c.getConfig().getMixinPackage()));
}
}

View File

@ -37,27 +37,15 @@ public final class DiscoveredExtension {
/** Version of this extension, highly reccomended to set it. */
private String version;
/** Points to sponge mixin config in resources folder. */
private String mixinConfig;
/** People who have made this extension. */
private String[] authors;
/** All code modifiers (the classes they point to) */
private String[] codeModifiers;
/** List of extension names that this depends on. */
private String[] dependencies;
/** List of Repositories and URLs that this depends on. */
private ExternalDependencies externalDependencies;
/** A list of any missing code modifiers to be used for logging. */
private final List<String> missingCodeModifiers = new LinkedList<>();
/** If this extension couldn't load its mixin configuration. */
private boolean failedToLoadMixin = false;
/**
* Extra meta on the object.
* Do NOT use as configuration:
@ -96,24 +84,11 @@ public final class DiscoveredExtension {
return version;
}
@NotNull
public String getMixinConfig() {
return mixinConfig;
}
@NotNull
public String[] getAuthors() {
return authors;
}
@NotNull
public String[] getCodeModifiers() {
if (codeModifiers == null) {
codeModifiers = new String[0];
}
return codeModifiers;
}
@NotNull
public String[] getDependencies() {
return dependencies;
@ -204,18 +179,10 @@ public final class DiscoveredExtension {
extension.version = "Unspecified";
}
if (extension.mixinConfig == null) {
extension.mixinConfig = "";
}
if (extension.authors == null) {
extension.authors = new String[0];
}
if (extension.codeModifiers == null) {
extension.codeModifiers = new String[0];
}
// No dependencies were specified
if (extension.dependencies == null) {
extension.dependencies = new String[0];
@ -238,22 +205,6 @@ public final class DiscoveredExtension {
return meta;
}
public void addMissingCodeModifier(String codeModifierClass) {
missingCodeModifiers.add(codeModifierClass);
}
public void setFailedToLoadMixinFlag() {
failedToLoadMixin = true;
}
public List<String> getMissingCodeModifiers() {
return missingCodeModifiers;
}
public boolean hasFailedToLoadMixin() {
return failedToLoadMixin;
}
public MinestomExtensionClassLoader makeClassLoader() {
final URL[] urls = this.files.toArray(new URL[0]);

View File

@ -233,13 +233,6 @@ public abstract class Extension {
}
}
/**
* If this extension registers code modifiers and/or mixins, are they loaded correctly?
*/
public boolean areCodeModifiersAllLoadedCorrectly() {
return !getOrigin().hasFailedToLoadMixin() && getOrigin().getMissingCodeModifiers().isEmpty();
}
/**
* Removes all expired reference to observers
*

View File

@ -16,10 +16,6 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongepowered.asm.mixin.Mixins;
import org.spongepowered.asm.mixin.throwables.MixinError;
import org.spongepowered.asm.mixin.throwables.MixinException;
import org.spongepowered.asm.service.ServiceNotAvailableError;
import java.io.*;
import java.lang.reflect.Constructor;
@ -179,7 +175,6 @@ public class ExtensionManager {
// remove invalid extensions
discoveredExtensions.removeIf(ext -> ext.loadStatus != DiscoveredExtension.LoadStatus.LOAD_SUCCESS);
setupCodeModifiers(discoveredExtensions);
// Load the extensions
for (DiscoveredExtension discoveredExtension : discoveredExtensions) {
@ -616,61 +611,6 @@ public class ExtensionManager {
return extensions.containsKey(name);
}
/**
* Extensions are allowed to apply Mixin transformers, the magic happens here.
*/
private void setupCodeModifiers(@NotNull List<DiscoveredExtension> extensions) {
final ClassLoader cl = getClass().getClassLoader();
if (!(cl instanceof MinestomRootClassLoader modifiableClassLoader)) {
LOGGER.warn("Current class loader is not a MinestomOverwriteClassLoader, but {}. " +
"This disables code modifiers (Mixin support is therefore disabled). " +
"This can be fixed by starting your server using Bootstrap#bootstrap (optional).", cl);
return;
}
setupCodeModifiers(extensions, modifiableClassLoader);
}
private void setupCodeModifiers(@NotNull List<DiscoveredExtension> extensions, MinestomRootClassLoader modifiableClassLoader) {
LOGGER.info("Start loading code modifiers...");
for (DiscoveredExtension extension : extensions) {
try {
for (String codeModifierClass : extension.getCodeModifiers()) {
boolean loaded = modifiableClassLoader.loadModifier(extension.files.toArray(new URL[0]), codeModifierClass);
if (!loaded) {
extension.addMissingCodeModifier(codeModifierClass);
}
}
if (!extension.getMixinConfig().isEmpty()) {
final String mixinConfigFile = extension.getMixinConfig();
try {
Mixins.addConfiguration(mixinConfigFile);
LOGGER.info("Found mixin in extension {}: {}", extension.getName(), mixinConfigFile);
} catch (ServiceNotAvailableError | MixinError | MixinException e) {
if (MinecraftServer.getExceptionManager() != null) {
MinecraftServer.getExceptionManager().handleException(e);
} else {
e.printStackTrace();
}
LOGGER.error("Could not load Mixin configuration: " + mixinConfigFile);
extension.setFailedToLoadMixinFlag();
}
}
} catch (Exception e) {
if (MinecraftServer.getExceptionManager() != null) {
MinecraftServer.getExceptionManager().handleException(e);
} else {
e.printStackTrace();
}
LOGGER.error("Failed to load code modifier for extension in files: " +
extension.files
.stream()
.map(URL::toExternalForm)
.collect(Collectors.joining(", ")), e);
}
}
LOGGER.info("Done loading code modifiers.");
}
private void unload(@NotNull Extension ext) {
ext.preTerminate();
ext.terminate();
@ -784,10 +724,6 @@ public class ExtensionManager {
toReload.setMinestomExtensionClassLoader(toReload.makeClassLoader());
}
// setup code modifiers for these extensions
// TODO: it is possible the new modifiers cannot be applied (because the targeted classes are already loaded), should we issue a warning?
setupCodeModifiers(extensionsToLoad);
List<Extension> newExtensions = new LinkedList<>();
for (DiscoveredExtension toReload : extensionsToLoad) {
// reload extensions
@ -837,40 +773,12 @@ public class ExtensionManager {
* Shutdowns all the extensions by unloading them.
*/
public void shutdown() {
//todo(mattw) what is different here from the method below?
for (Extension extension : getExtensions()) {
extension.unload();
}
}
/**
* Loads code modifiers early, that is before <code>MinecraftServer.init()</code> is called.
*/
public static void loadCodeModifiersEarly() {
// allow users to disable early code modifier load
if ("true".equalsIgnoreCase(System.getProperty(DISABLE_EARLY_LOAD_SYSTEM_KEY))) {
return;
}
LOGGER.info("Early load of code modifiers from extensions.");
ExtensionManager manager = new ExtensionManager();
// discover extensions that are present
List<DiscoveredExtension> discovered = manager.discoverExtensions();
// setup extension class loaders, so that Mixin can load the json configuration file correctly
for (DiscoveredExtension e : discovered) {
e.setMinestomExtensionClassLoader(e.makeClassLoader());
}
// setup code modifiers and mixins
manager.setupCodeModifiers(discovered, MinestomRootClassLoader.getInstance());
// setup is done, remove all extension classloaders
for (Extension extension : manager.getExtensions()) {
MinestomRootClassLoader.getInstance().removeChildInHierarchy(extension.getOrigin().getMinestomExtensionClassLoader());
}
LOGGER.info("Early load of code modifiers from extensions done!");
}
/**
* Unloads all extensions
*/

View File

@ -1,22 +0,0 @@
package net.minestom.server.extras.selfmodification;
import org.objectweb.asm.tree.ClassNode;
/**
* Will be called by {@link MinestomRootClassLoader} to transform classes at load-time
*/
public abstract class CodeModifier {
/**
* Must return true iif the class node has been modified
* @param source
* @return
*/
public abstract boolean transform(ClassNode source);
/**
* Beginning of the class names to transform.
* 'null' is allowed to transform any class, but not recommended
* @return
*/
public abstract String getNamespace();
}

View File

@ -76,7 +76,6 @@ public class MinestomExtensionClassLoader extends HierarchyClassLoader {
}
try (in) {
byte[] bytes = in.readAllBytes();
bytes = root.transformBytes(bytes, name);
Class<?> clazz = defineClass(name, bytes, 0, bytes.length);
if (resolve) {
resolveClass(clazz);

View File

@ -4,9 +4,6 @@ import net.minestom.server.MinecraftServer;
import net.minestom.server.extensions.ExtensionManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.ClassNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -67,9 +64,6 @@ public class MinestomRootClassLoader extends HierarchyClassLoader {
// TODO: eg. Node("java", Node("lang"), Node("io")). Loading "java.nio.Channel" would apply modifiers from "java", but not "java.io" or "java.lang".
// TODO: that's an example, please don't modify standard library classes. And this classloader should not let you do it because it first asks the platform classloader
// TODO: priorities?
private final List<CodeModifier> modifiers = new LinkedList<>();
/**
* Whether Minestom detected that it is running in a dev environment.
* Determined by the existence of the system property {@link ExtensionManager#INDEV_CLASSES_FOLDER}
@ -216,54 +210,16 @@ public class MinestomRootClassLoader extends HierarchyClassLoader {
if (input == null) {
throw new ClassNotFoundException("Could not find resource " + path);
}
byte[] originalBytes = input.readAllBytes();
if (transform) {
return transformBytes(originalBytes, name);
}
return originalBytes;
return input.readAllBytes();
}
public byte[] loadBytesWithChildren(@NotNull String name, boolean transform) throws IOException, ClassNotFoundException {
String path = name.replace(".", "/") + ".class";
InputStream input = getResourceAsStreamWithChildren(path);
if (input == null) {
throw new ClassNotFoundException("Could not find resource " + path);
}
byte[] originalBytes = input.readAllBytes();
if (transform) {
return transformBytes(originalBytes, name);
}
return originalBytes;
}
byte[] transformBytes(byte[] classBytecode, String name) {
if (!isProtected(name)) {
ClassReader reader = new ClassReader(classBytecode);
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);
classBytecode = writer.toByteArray();
LOGGER.trace("Modified {}", name);
}
}
return classBytecode;
return input.readAllBytes();
}
// overriden to increase access (from protected to public)
@ -277,53 +233,11 @@ public class MinestomRootClassLoader extends HierarchyClassLoader {
return URLClassLoader.newInstance(urls, this);
}
/**
* Loads a code modifier.
* @param urls
* @param codeModifierClass
* @return whether the modifier has been loaded. Returns 'true' even if the code modifier is already loaded before calling this method
*/
public boolean loadModifier(URL[] urls, String codeModifierClass) {
if(alreadyLoadedCodeModifiers.contains(codeModifierClass)) {
return true;
}
try {
URLClassLoader loader = newChild(urls);
Class<?> modifierClass = loader.loadClass(codeModifierClass);
if (CodeModifier.class.isAssignableFrom(modifierClass)) {
CodeModifier modifier = (CodeModifier) modifierClass.getDeclaredConstructor().newInstance();
synchronized (modifiers) {
LOGGER.warn("Added Code modifier: {}", modifier);
addCodeModifier(modifier);
alreadyLoadedCodeModifiers.add(codeModifierClass);
}
}
return true;
} catch (ClassNotFoundException | InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e) {
if (MinecraftServer.getExceptionManager() != null) {
MinecraftServer.getExceptionManager().handleException(e);
} else {
e.printStackTrace();
}
}
return false;
}
public void addCodeModifier(CodeModifier modifier) {
synchronized (modifiers) {
modifiers.add(modifier);
}
}
@Override
public void addURL(URL url) {
super.addURL(url);
}
public List<CodeModifier> getModifiers() {
return modifiers;
}
/**
* Tries to know which extension created this object, based on the classloader of the object. This can only check that the class of the object has been loaded
* by an extension.

View File

@ -1,71 +0,0 @@
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;
/**
* Global properties service for Mixin
*/
public class GlobalPropertyServiceMinestom implements IGlobalPropertyService {
private static 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<String, IPropertyKey> keys = new HashMap<>();
private final Map<IPropertyKey, Object> values = new HashMap<>();
@Override
public IPropertyKey resolveKey(String name) {
return keys.computeIfAbsent(name, BasicProperty::new);
}
@Override
public <T> T getProperty(IPropertyKey key) {
return (T) values.get(key);
}
@Override
public void setProperty(IPropertyKey key, Object value) {
values.put(key, value);
}
@Override
public <T> 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);
}
}

View File

@ -1,41 +0,0 @@
package net.minestom.server.extras.selfmodification.mixins;
import net.minestom.server.extras.selfmodification.MinestomRootClassLoader;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.tree.ClassNode;
import org.spongepowered.asm.service.IClassBytecodeProvider;
import java.io.IOException;
/**
* Provides class bytecode for Mixin
*/
public class MinestomBytecodeProvider implements IClassBytecodeProvider {
private final MinestomRootClassLoader classLoader;
public MinestomBytecodeProvider(MinestomRootClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public ClassNode getClassNode(String name) throws ClassNotFoundException {
return getClassNode(name, false);
}
private ClassNode loadNode(String name, boolean transform) throws ClassNotFoundException {
ClassNode node = new ClassNode();
ClassReader reader;
try {
reader = new ClassReader(classLoader.loadBytesWithChildren(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 {
return loadNode(name, runTransformers);
}
}

View File

@ -1,37 +0,0 @@
package net.minestom.server.extras.selfmodification.mixins;
import net.minestom.server.extras.selfmodification.MinestomRootClassLoader;
import org.spongepowered.asm.service.IClassProvider;
import java.net.URL;
/**
* Provides classes for Mixin
*/
public class MinestomClassProvider implements IClassProvider {
private final MinestomRootClassLoader classLoader;
public MinestomClassProvider(MinestomRootClassLoader 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);
}
}

View File

@ -1,29 +0,0 @@
package net.minestom.server.extras.selfmodification.mixins;
import net.minestom.server.MinecraftServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongepowered.asm.service.IMixinAuditTrail;
/**
* Takes care of logging mixin operations
*/
public class MixinAuditTrailMinestom implements IMixinAuditTrail {
public final static Logger LOGGER = LoggerFactory.getLogger(MinecraftServer.class);
@Override
public void onApply(String className, String mixinName) {
LOGGER.trace("Applied mixin {} to class {}", mixinName, className);
}
@Override
public void onPostProcess(String className) {
LOGGER.trace("Post processing {}", className);
}
@Override
public void onGenerate(String className, String generatorName) {
LOGGER.trace("Generating class {} via generator {}", className, generatorName);
}
}

View File

@ -1,53 +0,0 @@
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.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 {
/**
* 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.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 | IllegalAccessException | InstantiationException | InvocationTargetException | ClassNotFoundException e) {
throw new RuntimeException("Failed to initialize MixinCodeModifier", e);
}
}
@Override
public boolean transform(ClassNode source) {
try {
return (boolean) transformClassMethod.invoke(transformer, MixinEnvironment.getEnvironment(MixinEnvironment.Phase.DEFAULT), source.name.replace("/", "."), source);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
return false;
}
@Override
public String getNamespace() {
return null;
}
}

View File

@ -1,29 +0,0 @@
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<IContainerHandle> getMixinContainers() {
return null;
}
}

View File

@ -1,109 +0,0 @@
package net.minestom.server.extras.selfmodification.mixins;
import net.minestom.server.extras.selfmodification.MinestomRootClassLoader;
import org.jetbrains.annotations.NotNull;
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 MinestomRootClassLoader classLoader;
private final MinestomClassProvider classProvider;
private final MinestomBytecodeProvider bytecodeProvider;
private final MixinAuditTrailMinestom auditTrail;
private static MixinServiceMinestom INSTANCE = null;
private IConsumer<MixinEnvironment.Phase> phaseConsumer;
public MixinServiceMinestom() {
INSTANCE = this;
this.classLoader = MinestomRootClassLoader.getInstance();
classProvider = new MinestomClassProvider(classLoader);
bytecodeProvider = new MinestomBytecodeProvider(classLoader);
auditTrail = new MixinAuditTrailMinestom();
}
@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 null;
}
@Override
public Collection<String> getPlatformAgents() {
return Collections.singletonList("net.minestom.server.extras.selfmodification.mixins.MixinPlatformAgentMinestom");
}
@Override
public IContainerHandle getPrimaryContainer() {
return new ContainerHandleVirtual("Minestom");
}
@Override
public InputStream getResourceAsStream(@NotNull String name) {
return classLoader.getResourceAsStreamWithChildren(name);
}
@Override
public IClassTracker getClassTracker() {
return null;
}
@Override
public IMixinAuditTrail getAuditTrail() {
return auditTrail;
}
@Override
public void wire(MixinEnvironment.Phase phase, IConsumer<MixinEnvironment.Phase> phaseConsumer) {
super.wire(phase, phaseConsumer);
this.phaseConsumer = phaseConsumer;
}
private void gotoPhase(MixinEnvironment.Phase phase) {
phaseConsumer.accept(phase);
}
public static void gotoPreinitPhase() {
if(INSTANCE != null) {
INSTANCE.gotoPhase(MixinEnvironment.Phase.PREINIT);
}
}
public static void gotoInitPhase() {
if(INSTANCE != null) {
INSTANCE.gotoPhase(MixinEnvironment.Phase.INIT);
}
}
public static void gotoDefaultPhase() {
if(INSTANCE != null) {
INSTANCE.gotoPhase(MixinEnvironment.Phase.DEFAULT);
}
}
}

View File

@ -1,20 +0,0 @@
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() {
}
}

View File

@ -21,12 +21,12 @@ public class MixinIntoMinestomCore extends Extension {
// force load of InstanceContainer class
InstanceContainer c = new InstanceContainer(UUID.randomUUID(), DimensionType.OVERWORLD);
System.out.println(c.toString());
try {
Assertions.assertTrue(success, "InstanceContainer must have been mixed in with improveextensions.InstanceContainerMixin");
Assertions.assertEquals(1, MinecraftServer.getExtensionManager().getExtensions().stream().map(extension -> extension.getOrigin().getMinestomExtensionClassLoader()).toArray().length, "Only one extension classloader (this extension's) must be active.");
} catch (AssertionFailedError e) {
e.printStackTrace();
}
// try {
// Assertions.assertTrue(success, "InstanceContainer must have been mixed in with improveextensions.InstanceContainerMixin");
// Assertions.assertEquals(1, MinecraftServer.getExtensionManager().getExtensions().stream().map(extension -> extension.getOrigin().getMinestomExtensionClassLoader()).toArray().length, "Only one extension classloader (this extension's) must be active.");
// } catch (AssertionFailedError e) {
// e.printStackTrace();
// }
MinecraftServer.stopCleanly();
}

View File

@ -11,10 +11,10 @@ public class MissingCodeModifiersExtension extends Extension {
public void initialize() {
// force load of InstanceContainer class
try {
Assertions.assertFalse(areCodeModifiersAllLoadedCorrectly(), "Mixin configuration could not be loaded and code modifiers are unavailable, the failure should be reported");
Assertions.assertTrue(getOrigin().hasFailedToLoadMixin(), "Mixin configuration does not exist and should not be loaded");
Assertions.assertEquals(1, getOrigin().getMissingCodeModifiers().size(), "Code modifier does not exist, it should be reported as missing");
Assertions.assertEquals("InvalidCodeModifierClass", getOrigin().getMissingCodeModifiers().get(0));
// Assertions.assertFalse(areCodeModifiersAllLoadedCorrectly(), "Mixin configuration could not be loaded and code modifiers are unavailable, the failure should be reported");
// Assertions.assertTrue(getOrigin().hasFailedToLoadMixin(), "Mixin configuration does not exist and should not be loaded");
// Assertions.assertEquals(1, getOrigin().getMissingCodeModifiers().size(), "Code modifier does not exist, it should be reported as missing");
// Assertions.assertEquals("InvalidCodeModifierClass", getOrigin().getMissingCodeModifiers().get(0));
System.out.println("All tests passed.");
} catch (AssertionFailedError e) {
e.printStackTrace();

View File

@ -1,19 +1,12 @@
package improveextensions.mixins;
import improveextensions.MixinIntoMinestomCore;
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)
//@Mixin(InstanceContainer.class)
public class InstanceContainerMixin {
@Inject(method = "<init>", at = @At("RETURN"), require = 1)
private void constructorHead(CallbackInfo ci) {
System.out.println("Mixin into InstanceContainerMixin");
MixinIntoMinestomCore.success = true;
}
// @Inject(method = "<init>", at = @At("RETURN"), require = 1)
// private void constructorHead(CallbackInfo ci) {
// System.out.println("Mixin into InstanceContainerMixin");
// MixinIntoMinestomCore.success = true;
// }
}

View File

@ -1,37 +1,37 @@
package testextension;
import net.minestom.server.extras.selfmodification.CodeModifier;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.*;
//import net.minestom.server.extras.selfmodification.CodeModifier;
//import org.objectweb.asm.Opcodes;
//import org.objectweb.asm.tree.*;
import java.util.List;
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 of "+source.name);
MethodNode constructor = findConstructor(source.methods);
constructor.instructions.insert(constructor.instructions.getFirst(), buildInjectionCode());
return true;
}
return false;
}
private InsnList buildInjectionCode() {
InsnList list = new InsnList();
list.add(new FieldInsnNode(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"));
list.add(new LdcInsnNode("Hello from modified code!!"));
list.add(new MethodInsnNode(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false));
return list;
}
private MethodNode findConstructor(List<MethodNode> methods) {
return methods.stream().filter(m -> m.name.equals("<init>")).findFirst().orElseThrow();
}
@Override
public String getNamespace() {
return "net.minestom.server";
}
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 of "+source.name);
// MethodNode constructor = findConstructor(source.methods);
// constructor.instructions.insert(constructor.instructions.getFirst(), buildInjectionCode());
// return true;
// }
// return false;
// }
//
// private InsnList buildInjectionCode() {
// InsnList list = new InsnList();
// list.add(new FieldInsnNode(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"));
// list.add(new LdcInsnNode("Hello from modified code!!"));
// list.add(new MethodInsnNode(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false));
// return list;
// }
//
// private MethodNode findConstructor(List<MethodNode> methods) {
// return methods.stream().filter(m -> m.name.equals("<init>")).findFirst().orElseThrow();
// }
//
// @Override
// public String getNamespace() {
// return "net.minestom.server";
// }
}

View File

@ -2,17 +2,17 @@ package testextension.mixins;
import net.minestom.server.instance.DynamicChunk;
import net.minestom.server.instance.block.Block;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
//import org.spongepowered.asm.mixin.Mixin;
//import org.spongepowered.asm.mixin.injection.At;
//import org.spongepowered.asm.mixin.injection.ModifyVariable;
@Mixin(DynamicChunk.class)
//@Mixin(DynamicChunk.class)
public class DynamicChunkMixin {
@ModifyVariable(method = "setBlock", at = @At("HEAD"), index = 4, require = 1, argsOnly = true, remap = false)
public int oopsAllTnt(short blockStateId) {
if(blockStateId != 0)
return Block.TNT.id();
return 0;
}
// @ModifyVariable(method = "setBlock", at = @At("HEAD"), index = 4, require = 1, argsOnly = true, remap = false)
// public int oopsAllTnt(short blockStateId) {
// if(blockStateId != 0)
// return Block.TNT.id();
// return 0;
// }
}

View File

@ -1,17 +1,17 @@
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;
//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)
//@Mixin(InstanceContainer.class)
public class InstanceContainerMixin {
@Inject(method = "<init>", at = @At("RETURN"))
private void onRunHead(CallbackInfo ci) {
System.out.println("Hello from Mixin!!!");
}
// @Inject(method = "<init>", at = @At("RETURN"))
// private void onRunHead(CallbackInfo ci) {
// System.out.println("Hello from Mixin!!!");
// }
}