mirror of
https://github.com/Minestom/Minestom.git
synced 2025-03-02 11:21:15 +01:00
(WIP) Support for runtime code modification
This commit is contained in:
parent
b8c30d9b58
commit
164719090a
11
src/main/java/fr/themode/demo/MainWrapper.java
Normal file
11
src/main/java/fr/themode/demo/MainWrapper.java
Normal file
@ -0,0 +1,11 @@
|
||||
package fr.themode.demo;
|
||||
|
||||
import net.minestom.server.Bootstrap;
|
||||
|
||||
public class MainWrapper {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Bootstrap.bootstrap("fr.themode.demo.Main", args);
|
||||
}
|
||||
|
||||
}
|
23
src/main/java/net/minestom/server/Bootstrap.java
Normal file
23
src/main/java/net/minestom/server/Bootstrap.java
Normal file
@ -0,0 +1,23 @@
|
||||
package net.minestom.server;
|
||||
|
||||
import net.minestom.server.extras.selfmodification.MinestomOverwriteClassLoader;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Used to launch Minestom with the {@link MinestomOverwriteClassLoader} to allow for self-modifications
|
||||
*/
|
||||
public class Bootstrap {
|
||||
|
||||
public static void bootstrap(String mainClassFullName, String[] args) {
|
||||
try {
|
||||
ClassLoader classLoader = new MinestomOverwriteClassLoader(Bootstrap.class.getClassLoader());
|
||||
Class<?> mainClass = classLoader.loadClass(mainClassFullName);
|
||||
Method main = mainClass.getDeclaredMethod("main", String[].class);
|
||||
main.invoke(null, new Object[] { args });
|
||||
} catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException | ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
@ -145,6 +145,9 @@ public class MinecraftServer {
|
||||
public static MinecraftServer init() {
|
||||
if (minecraftServer != null) // don't init twice
|
||||
return minecraftServer;
|
||||
extensionManager = new ExtensionManager();
|
||||
extensionManager.loadExtensions();
|
||||
|
||||
// warmup/force-init registries
|
||||
// without this line, registry types that are not loaded explicitly will have an internal empty registry in Registries
|
||||
// That can happen with PotionType for instance, if no code tries to access a PotionType field
|
||||
@ -180,8 +183,6 @@ public class MinecraftServer {
|
||||
|
||||
updateManager = new UpdateManager();
|
||||
|
||||
extensionManager = new ExtensionManager();
|
||||
|
||||
lootTableManager = new LootTableManager();
|
||||
tagManager = new TagManager();
|
||||
|
||||
@ -452,7 +453,6 @@ public class MinecraftServer {
|
||||
updateManager.start();
|
||||
nettyServer.start(address, port);
|
||||
long t1 = -System.nanoTime();
|
||||
extensionManager.loadExtensionJARs();
|
||||
// Init extensions
|
||||
// TODO: Extensions should handle depending on each other and have a load-order.
|
||||
extensionManager.getExtensions().forEach(Extension::preInitialize);
|
||||
|
@ -1,13 +1,16 @@
|
||||
package net.minestom.server.extensions;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.minestom.server.extras.selfmodification.MinestomOverwriteClassLoader;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.Constructor;
|
||||
@ -16,10 +19,8 @@ import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
@Slf4j
|
||||
public class ExtensionManager {
|
||||
@ -30,7 +31,7 @@ public class ExtensionManager {
|
||||
public ExtensionManager() {
|
||||
}
|
||||
|
||||
public void loadExtensionJARs() {
|
||||
public void loadExtensions() {
|
||||
if (!extensionFolder.exists()) {
|
||||
if (!extensionFolder.mkdirs()) {
|
||||
log.error("Could not find or create the extension folder, extensions will not be loaded!");
|
||||
@ -38,14 +39,12 @@ public class ExtensionManager {
|
||||
}
|
||||
}
|
||||
|
||||
for (File file : extensionFolder.listFiles()) {
|
||||
if (file.isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
if (!file.getName().endsWith(".jar")) {
|
||||
continue;
|
||||
}
|
||||
List<DiscoveredExtension> discoveredExtensions = discoverExtensions();
|
||||
setupCodeModifiers(discoveredExtensions);
|
||||
|
||||
for (DiscoveredExtension extension : discoveredExtensions) {
|
||||
URLClassLoader loader;
|
||||
File file = extension.jarFile;
|
||||
try {
|
||||
URL url = file.toURI().toURL();
|
||||
loader = loadJar(url);
|
||||
@ -137,6 +136,30 @@ public class ExtensionManager {
|
||||
}
|
||||
}
|
||||
|
||||
private List<DiscoveredExtension> discoverExtensions() {
|
||||
Gson gson = new Gson();
|
||||
List<DiscoveredExtension> extensions = new LinkedList<>();
|
||||
for (File file : extensionFolder.listFiles()) {
|
||||
if (file.isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
if (!file.getName().endsWith(".jar")) {
|
||||
continue;
|
||||
}
|
||||
try(ZipFile f = new ZipFile(file);
|
||||
InputStreamReader reader = new InputStreamReader(f.getInputStream(f.getEntry("extension.json")))) {
|
||||
|
||||
DiscoveredExtension extension = new DiscoveredExtension();
|
||||
extension.jarFile = file;
|
||||
extension.description = gson.fromJson(reader, JsonObject.class);
|
||||
extensions.add(extension);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return extensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a URL into the classpath.
|
||||
*
|
||||
@ -166,4 +189,35 @@ public class ExtensionManager {
|
||||
public Map<URL, URLClassLoader> getExtensionLoaders() {
|
||||
return new HashMap<>(extensionLoaders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extensions are allowed to apply Mixin transformers, the magic happens here
|
||||
*/
|
||||
private void setupCodeModifiers(List<DiscoveredExtension> extensions) {
|
||||
ClassLoader cl = getClass().getClassLoader();
|
||||
if(!(cl instanceof MinestomOverwriteClassLoader)) {
|
||||
log.warning("Current class loader is not a MinestomOverwriteClassLoader, but "+cl+". This disables code modifiers (Mixin support is therefore disabled)");
|
||||
return;
|
||||
}
|
||||
MinestomOverwriteClassLoader modifiableClassLoader = (MinestomOverwriteClassLoader)cl;
|
||||
log.info("Start loading code modifiers...");
|
||||
for(DiscoveredExtension extension : extensions) {
|
||||
try {
|
||||
if(extension.description.has("codeModifier")) {
|
||||
String codeModifierClass = extension.description.get("codeModifier").getAsString();
|
||||
modifiableClassLoader.loadModifier(extension.jarFile, codeModifierClass);
|
||||
}
|
||||
// TODO: special support for mixins
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
log.error("Failed to load code modifier for extension "+extension.jarFile, e);
|
||||
}
|
||||
}
|
||||
log.info("Done loading code modifiers.");
|
||||
}
|
||||
|
||||
private class DiscoveredExtension {
|
||||
private File jarFile;
|
||||
private JsonObject description;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,72 @@
|
||||
package net.minestom.server.extras.selfmodification;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
|
||||
// TODO: register code modifiers
|
||||
public class MinestomOverwriteClassLoader extends URLClassLoader {
|
||||
|
||||
public MinestomOverwriteClassLoader(ClassLoader parent) {
|
||||
super("Minestom ClassLoader", loadURLs(), parent);
|
||||
}
|
||||
|
||||
private static URL[] loadURLs() {
|
||||
String classpath = System.getProperty("java.class.path");
|
||||
String[] parts = classpath.split(";");
|
||||
URL[] urls = new URL[parts.length];
|
||||
for (int i = 0; i < urls.length; i++) {
|
||||
try {
|
||||
String part = parts[i];
|
||||
String protocol;
|
||||
if(part.contains("!")) {
|
||||
protocol = "jar://";
|
||||
} else {
|
||||
protocol = "file://";
|
||||
}
|
||||
urls[i] = new URL(protocol+part);
|
||||
} catch (MalformedURLException e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
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);
|
||||
if(loadedClass != null)
|
||||
return loadedClass;
|
||||
|
||||
try {
|
||||
Class<?> systemClass = ClassLoader.getPlatformClassLoader().loadClass(name);
|
||||
return systemClass;
|
||||
} catch (ClassNotFoundException e) {
|
||||
try {
|
||||
String path = name.replace(".", "/") + ".class";
|
||||
byte[] bytes = getResourceAsStream(path).readAllBytes();
|
||||
Class<?> defined = defineClass(name, bytes, 0, bytes.length);
|
||||
if(resolve) {
|
||||
resolveClass(defined);
|
||||
}
|
||||
return defined;
|
||||
} catch (Exception ioException) {
|
||||
// fail to load class, let parent load
|
||||
// this forbids code modification, but at least it will load
|
||||
return super.loadClass(name, resolve);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void loadModifier(File originFile, String codeModifierClass) {
|
||||
throw new UnsupportedOperationException("TODO");
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user