mirror of https://github.com/Minestom/Minestom.git
remove code modification, bootstrap + MinestomRootClassLoader still required for now
This commit is contained in:
parent
91a8607710
commit
18ecefd97a
|
@ -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'
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]);
|
||||
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
// }
|
||||
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -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!!!");
|
||||
// }
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue