[FEATURE] Support modular start

This commit is contained in:
Gravita 2023-11-01 04:05:14 +07:00
parent 6cd5a69149
commit 44bc8b0bbc
11 changed files with 165 additions and 24 deletions

View file

@ -49,6 +49,7 @@ public void run(String[] args) {
String compatClasses = System.getProperty("launcher.runtime.launch.compat", null); String compatClasses = System.getProperty("launcher.runtime.launch.compat", null);
String nativesDir = System.getProperty("launcher.runtime.launch.natives", "natives"); String nativesDir = System.getProperty("launcher.runtime.launch.natives", "natives");
String launcherOptionsPath = System.getProperty("launcher.runtime.launch.options", null); String launcherOptionsPath = System.getProperty("launcher.runtime.launch.options", null);
boolean enableHacks = Boolean.getBoolean("launcher.runtime.launch.enablehacks");
ClientPermissions permissions = new ClientPermissions(); ClientPermissions permissions = new ClientPermissions();
if(mainClass == null) { if(mainClass == null) {
throw new NullPointerException("Add `-Dlauncher.runtime.mainclass=YOUR_MAIN_CLASS` to jvmArgs"); throw new NullPointerException("Add `-Dlauncher.runtime.mainclass=YOUR_MAIN_CLASS` to jvmArgs");
@ -130,6 +131,7 @@ public void run(String[] args) {
} else { } else {
options = new LaunchOptions(); options = new LaunchOptions();
} }
options.enableHacks = enableHacks;
ClassLoaderControl classLoaderControl = launch.init(classpath, nativesDir, options); ClassLoaderControl classLoaderControl = launch.init(classpath, nativesDir, options);
ClientService.classLoaderControl = classLoaderControl; ClientService.classLoaderControl = classLoaderControl;
if(compatClasses != null) { if(compatClasses != null) {

View file

@ -460,7 +460,7 @@ public enum ClassLoaderConfig {
} }
public enum CompatibilityFlags { public enum CompatibilityFlags {
LEGACY_NATIVES_DIR, CLASS_CONTROL_API LEGACY_NATIVES_DIR, CLASS_CONTROL_API, ENABLE_HACKS
} }
public static class Version implements Comparable<Version> { public static class Version implements Comparable<Version> {

View file

@ -62,12 +62,20 @@ private static ClientParams readParams(SocketAddress address) throws IOException
} }
} }
public static void main(String[] args) throws Throwable { public static void main(String[] args) {
JVMHelper.verifySystemProperties(ClientLauncherEntryPoint.class, true); JVMHelper.verifySystemProperties(ClientLauncherEntryPoint.class, true);
EnvHelper.checkDangerousParams(); EnvHelper.checkDangerousParams();
JVMHelper.checkStackTrace(ClientLauncherEntryPoint.class); JVMHelper.checkStackTrace(ClientLauncherEntryPoint.class);
LogHelper.printVersion("Client Launcher"); LogHelper.printVersion("Client Launcher");
ClientLauncherMethods.checkClass(ClientLauncherEntryPoint.class); ClientLauncherMethods.checkClass(ClientLauncherEntryPoint.class);
try {
realMain(args);
} catch (Throwable e) {
LogHelper.error(e);
}
}
private static void realMain(String[] args) throws Throwable {
modulesManager = new ClientModuleManager(); modulesManager = new ClientModuleManager();
modulesManager.loadModule(new ClientLauncherCoreModule()); modulesManager.loadModule(new ClientLauncherCoreModule());
LauncherConfig.initModules(modulesManager); //INIT LauncherConfig.initModules(modulesManager); //INIT
@ -132,6 +140,7 @@ public static void main(String[] args) throws Throwable {
LogHelper.debug("Natives dir %s", params.nativesDir); LogHelper.debug("Natives dir %s", params.nativesDir);
ClientProfile.ClassLoaderConfig classLoaderConfig = profile.getClassLoaderConfig(); ClientProfile.ClassLoaderConfig classLoaderConfig = profile.getClassLoaderConfig();
LaunchOptions options = new LaunchOptions(); LaunchOptions options = new LaunchOptions();
options.enableHacks = profile.hasFlag(ClientProfile.CompatibilityFlags.ENABLE_HACKS);
options.moduleConf = profile.getModuleConf(); options.moduleConf = profile.getModuleConf();
if (classLoaderConfig == ClientProfile.ClassLoaderConfig.LAUNCHER) { if (classLoaderConfig == ClientProfile.ClassLoaderConfig.LAUNCHER) {
if(JVMHelper.JVM_VERSION <= 11) { if(JVMHelper.JVM_VERSION <= 11) {

View file

@ -0,0 +1,33 @@
package pro.gravit.utils.helper;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.function.Consumer;
public class HackHelper {
private static MethodHandles.Lookup createHackLookupImpl(Class<?> lookupClass) {
try {
Field trusted = MethodHandles.Lookup.class.getDeclaredField("TRUSTED");
trusted.setAccessible(true);
int value = (int) trusted.get(null);
Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, Class.class,int.class);
constructor.setAccessible(true);
return constructor.newInstance(lookupClass, null, value);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
public static MethodHandles.Lookup createHackLookup(Class<?> lookupClass) {
Exception e = new Exception();
StackTraceElement[] elements = e.getStackTrace();
String className = elements[elements.length-1].getClassName();
if(!className.startsWith("pro.gravit.launcher.") && !className.startsWith("pro.gravit.utils.")) {
throw new SecurityException(String.format("Untrusted class %s", className));
}
return createHackLookupImpl(lookupClass);
}
}

View file

@ -1,5 +1,6 @@
package pro.gravit.utils.launch; package pro.gravit.utils.launch;
import pro.gravit.utils.helper.HackHelper;
import pro.gravit.utils.helper.JVMHelper; import pro.gravit.utils.helper.JVMHelper;
import java.io.File; import java.io.File;
@ -23,6 +24,7 @@
public class BasicLaunch implements Launch { public class BasicLaunch implements Launch {
private Instrumentation instrumentation; private Instrumentation instrumentation;
private MethodHandles.Lookup hackLookup;
public BasicLaunch(Instrumentation instrumentation) { public BasicLaunch(Instrumentation instrumentation) {
this.instrumentation = instrumentation; this.instrumentation = instrumentation;
@ -33,6 +35,9 @@ public BasicLaunch() {
@Override @Override
public ClassLoaderControl init(List<Path> files, String nativePath, LaunchOptions options) { public ClassLoaderControl init(List<Path> files, String nativePath, LaunchOptions options) {
if(options.enableHacks) {
hackLookup = HackHelper.createHackLookup(BasicLaunch.class);
}
return new BasicClassLoaderControl(); return new BasicClassLoaderControl();
} }
@ -41,7 +46,7 @@ public void launch(String mainClass, String mainModule, Collection<String> args)
Class<?> mainClazz = Class.forName(mainClass); Class<?> mainClazz = Class.forName(mainClass);
MethodHandle mainMethod = MethodHandles.lookup().findStatic(mainClazz, "main", MethodType.methodType(void.class, String[].class)).asFixedArity(); MethodHandle mainMethod = MethodHandles.lookup().findStatic(mainClazz, "main", MethodType.methodType(void.class, String[].class)).asFixedArity();
JVMHelper.fullGC(); JVMHelper.fullGC();
mainMethod.invokeWithArguments((Object) args.toArray(new String[0])); mainMethod.asFixedArity().invokeWithArguments((Object) args.toArray(new String[0]));
} }
private class BasicClassLoaderControl implements ClassLoaderControl { private class BasicClassLoaderControl implements ClassLoaderControl {
@ -120,5 +125,10 @@ public ClassLoader getClassLoader() {
public Object getJava9ModuleController() { public Object getJava9ModuleController() {
return null; return null;
} }
@Override
public MethodHandles.Lookup getHackLookup() {
return hackLookup;
}
} }
} }

View file

@ -1,5 +1,6 @@
package pro.gravit.utils.launch; package pro.gravit.utils.launch;
import java.lang.invoke.MethodHandles;
import java.net.URL; import java.net.URL;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.ProtectionDomain; import java.security.ProtectionDomain;
@ -16,6 +17,8 @@ public interface ClassLoaderControl {
ClassLoader getClassLoader(); ClassLoader getClassLoader();
Object getJava9ModuleController(); Object getJava9ModuleController();
MethodHandles.Lookup getHackLookup();
interface ClassTransformer { interface ClassTransformer {
boolean filter(String moduleName, String name); boolean filter(String moduleName, String name);
byte[] transform(String moduleName, String name, ProtectionDomain protectionDomain, byte[] data); byte[] transform(String moduleName, String name, ProtectionDomain protectionDomain, byte[] data);

View file

@ -6,6 +6,7 @@
import java.util.Map; import java.util.Map;
public class LaunchOptions { public class LaunchOptions {
public boolean enableHacks;
public ModuleConf moduleConf; public ModuleConf moduleConf;

View file

@ -1,5 +1,6 @@
package pro.gravit.utils.launch; package pro.gravit.utils.launch;
import pro.gravit.utils.helper.HackHelper;
import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.JVMHelper; import pro.gravit.utils.helper.JVMHelper;
@ -20,6 +21,7 @@
public class LegacyLaunch implements Launch { public class LegacyLaunch implements Launch {
private LegacyClassLoader legacyClassLoader; private LegacyClassLoader legacyClassLoader;
private MethodHandles.Lookup hackLookup;
@Override @Override
public ClassLoaderControl init(List<Path> files, String nativePath, LaunchOptions options) { public ClassLoaderControl init(List<Path> files, String nativePath, LaunchOptions options) {
@ -31,6 +33,9 @@ public ClassLoaderControl init(List<Path> files, String nativePath, LaunchOption
} }
}).toArray(URL[]::new), BasicLaunch.class.getClassLoader()); }).toArray(URL[]::new), BasicLaunch.class.getClassLoader());
legacyClassLoader.nativePath = nativePath; legacyClassLoader.nativePath = nativePath;
if(options.enableHacks) {
hackLookup = HackHelper.createHackLookup(BasicLaunch.class);
}
return legacyClassLoader.makeControl(); return legacyClassLoader.makeControl();
} }
@ -40,10 +45,10 @@ public void launch(String mainClass, String mainModule, Collection<String> args)
Class<?> mainClazz = Class.forName(mainClass, true, legacyClassLoader); Class<?> mainClazz = Class.forName(mainClass, true, legacyClassLoader);
MethodHandle mainMethod = MethodHandles.lookup().findStatic(mainClazz, "main", MethodType.methodType(void.class, String[].class)).asFixedArity(); MethodHandle mainMethod = MethodHandles.lookup().findStatic(mainClazz, "main", MethodType.methodType(void.class, String[].class)).asFixedArity();
JVMHelper.fullGC(); JVMHelper.fullGC();
mainMethod.invokeWithArguments((Object) args.toArray(new String[0])); mainMethod.asFixedArity().invokeWithArguments((Object) args.toArray(new String[0]));
} }
private static class LegacyClassLoader extends URLClassLoader { private class LegacyClassLoader extends URLClassLoader {
private final ClassLoader SYSTEM_CLASS_LOADER = ClassLoader.getSystemClassLoader(); private final ClassLoader SYSTEM_CLASS_LOADER = ClassLoader.getSystemClassLoader();
private final List<ClassLoaderControl.ClassTransformer> transformers = new ArrayList<>(); private final List<ClassLoaderControl.ClassTransformer> transformers = new ArrayList<>();
private final Map<String, Class<?>> classMap = new ConcurrentHashMap<>(); private final Map<String, Class<?>> classMap = new ConcurrentHashMap<>();
@ -170,6 +175,11 @@ public ClassLoader getClassLoader() {
public Object getJava9ModuleController() { public Object getJava9ModuleController() {
return null; return null;
} }
@Override
public MethodHandles.Lookup getHackLookup() {
return hackLookup;
}
} }
} }
} }

View file

@ -0,0 +1,15 @@
package pro.gravit.utils.launch;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
public class ModuleHacks {
public static ModuleLayer.Controller createController(MethodHandles.Lookup lookup, ModuleLayer layer) {
try {
return (ModuleLayer.Controller) lookup.findConstructor(ModuleLayer.Controller.class, MethodType.methodType(void.class, ModuleLayer.class)).invoke(layer);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
}

View file

@ -1,5 +1,6 @@
package pro.gravit.utils.launch; package pro.gravit.utils.launch;
import pro.gravit.utils.helper.HackHelper;
import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.JVMHelper; import pro.gravit.utils.helper.JVMHelper;
import pro.gravit.utils.helper.LogHelper; import pro.gravit.utils.helper.LogHelper;
@ -12,6 +13,7 @@
import java.lang.module.Configuration; import java.lang.module.Configuration;
import java.lang.module.ModuleFinder; import java.lang.module.ModuleFinder;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.nio.file.Path; import java.nio.file.Path;
@ -28,6 +30,7 @@ public class ModuleLaunch implements Launch {
private ModuleLayer.Controller controller; private ModuleLayer.Controller controller;
private ModuleFinder moduleFinder; private ModuleFinder moduleFinder;
private ModuleLayer layer; private ModuleLayer layer;
private MethodHandles.Lookup hackLookup;
@Override @Override
public ClassLoaderControl init(List<Path> files, String nativePath, LaunchOptions options) { public ClassLoaderControl init(List<Path> files, String nativePath, LaunchOptions options) {
moduleClassLoader = new ModuleClassLoader(files.stream().map((e) -> { moduleClassLoader = new ModuleClassLoader(files.stream().map((e) -> {
@ -39,37 +42,87 @@ public ClassLoaderControl init(List<Path> files, String nativePath, LaunchOption
}).toArray(URL[]::new), BasicLaunch.class.getClassLoader()); }).toArray(URL[]::new), BasicLaunch.class.getClassLoader());
moduleClassLoader.nativePath = nativePath; moduleClassLoader.nativePath = nativePath;
{ {
if(options.enableHacks) {
hackLookup = HackHelper.createHackLookup(ModuleLaunch.class);
}
if(options.moduleConf != null) { if(options.moduleConf != null) {
// Create Module Layer // Create Module Layer
moduleFinder = ModuleFinder.of(options.moduleConf.modulePath.stream().map(Paths::get).toArray(Path[]::new)); moduleFinder = ModuleFinder.of(options.moduleConf.modulePath.stream().map(Paths::get).map(Path::toAbsolutePath).toArray(Path[]::new));
ModuleLayer bootLayer = ModuleLayer.boot(); ModuleLayer bootLayer = ModuleLayer.boot();
if(options.moduleConf.modules.contains("ALL-MODULE-PATH")) {
var set = moduleFinder.findAll();
if(LogHelper.isDevEnabled()) {
for(var m : set) {
LogHelper.dev("Found module %s in %s", m.descriptor().name(), m.location().map(URI::toString).orElse("unknown"));
}
LogHelper.dev("Found %d modules", set.size());
}
for(var m : set) {
options.moduleConf.modules.add(m.descriptor().name());
}
options.moduleConf.modules.remove("ALL-MODULE-PATH");
}
configuration = bootLayer.configuration() configuration = bootLayer.configuration()
.resolveAndBind(ModuleFinder.of(), moduleFinder, options.moduleConf.modules); .resolveAndBind(moduleFinder, ModuleFinder.of(), options.moduleConf.modules);
controller = ModuleLayer.defineModulesWithOneLoader(configuration, List.of(bootLayer), moduleClassLoader); controller = ModuleLayer.defineModulesWithOneLoader(configuration, List.of(bootLayer), moduleClassLoader);
moduleClassLoader.controller = controller;
layer = controller.layer(); layer = controller.layer();
// Configure exports / opens // Configure exports / opens
for(var e : options.moduleConf.exports.entrySet()) { for(var e : options.moduleConf.exports.entrySet()) {
String[] split = e.getKey().split("\\\\"); String[] split = e.getKey().split("/");
Module source = layer.findModule(split[0]).orElseThrow(); String moduleName = split[0];
String pkg = split[1]; String pkg = split[1];
Module target = layer.findModule(e.getValue()).orElseThrow(); LogHelper.dev("Export module: %s package: %s to %s", moduleName, pkg, e.getValue());
Module source = layer.findModule(split[0]).orElse(null);
if(source == null) {
throw new RuntimeException(String.format("Module %s not found", moduleName));
}
Module target = layer.findModule(e.getValue()).orElse(null);
if(target == null) {
throw new RuntimeException(String.format("Module %s not found", e.getValue()));
}
if(options.enableHacks && source.getLayer() != layer) {
ModuleHacks.createController(hackLookup, source.getLayer()).addExports(source, pkg, target);
} else {
controller.addExports(source, pkg, target); controller.addExports(source, pkg, target);
} }
}
for(var e : options.moduleConf.opens.entrySet()) { for(var e : options.moduleConf.opens.entrySet()) {
String[] split = e.getKey().split("\\\\"); String[] split = e.getKey().split("/");
Module source = layer.findModule(split[0]).orElseThrow(); String moduleName = split[0];
String pkg = split[1]; String pkg = split[1];
Module target = layer.findModule(e.getValue()).orElseThrow(); LogHelper.dev("Open module: %s package: %s to %s", moduleName, pkg, e.getValue());
Module source = layer.findModule(split[0]).orElse(null);
if(source == null) {
throw new RuntimeException(String.format("Module %s not found", moduleName));
}
Module target = layer.findModule(e.getValue()).orElse(null);
if(target == null) {
throw new RuntimeException(String.format("Module %s not found", e.getValue()));
}
if(options.enableHacks && source.getLayer() != layer) {
ModuleHacks.createController(hackLookup, source.getLayer()).addOpens(source, pkg, target);
} else {
controller.addOpens(source, pkg, target); controller.addOpens(source, pkg, target);
} }
}
for(var e : options.moduleConf.reads.entrySet()) { for(var e : options.moduleConf.reads.entrySet()) {
Module source = layer.findModule(e.getKey()).orElseThrow(); LogHelper.dev("Read module %s to %s", e.getKey(), e.getValue());
Module target = layer.findModule(e.getValue()).orElseThrow(); Module source = layer.findModule(e.getKey()).orElse(null);
if(source == null) {
throw new RuntimeException(String.format("Module %s not found", e.getKey()));
}
Module target = layer.findModule(e.getValue()).orElse(null);
if(target == null) {
throw new RuntimeException(String.format("Module %s not found", e.getValue()));
}
if(options.enableHacks && source.getLayer() != layer) {
ModuleHacks.createController(hackLookup, source.getLayer()).addReads(source, target);
} else {
controller.addReads(source, target); controller.addReads(source, target);
} }
} }
} }
}
return moduleClassLoader.makeControl(); return moduleClassLoader.makeControl();
} }
@ -80,7 +133,7 @@ public void launch(String mainClass, String mainModuleName, Collection<String> a
Class<?> mainClazz = Class.forName(mainClass, true, moduleClassLoader); Class<?> mainClazz = Class.forName(mainClass, true, moduleClassLoader);
MethodHandle mainMethod = MethodHandles.lookup().findStatic(mainClazz, "main", MethodType.methodType(void.class, String[].class)).asFixedArity(); MethodHandle mainMethod = MethodHandles.lookup().findStatic(mainClazz, "main", MethodType.methodType(void.class, String[].class)).asFixedArity();
JVMHelper.fullGC(); JVMHelper.fullGC();
mainMethod.invokeWithArguments((Object) args.toArray(new String[0])); mainMethod.asFixedArity().invokeWithArguments((Object) args.toArray(new String[0]));
return; return;
} }
Module mainModule = layer.findModule(mainModuleName).orElseThrow(); Module mainModule = layer.findModule(mainModuleName).orElseThrow();
@ -92,7 +145,7 @@ public void launch(String mainClass, String mainModuleName, Collection<String> a
ClassLoader loader = mainModule.getClassLoader(); ClassLoader loader = mainModule.getClassLoader();
Class<?> mainClazz = Class.forName(mainClass, true, loader); Class<?> mainClazz = Class.forName(mainClass, true, loader);
MethodHandle mainMethod = MethodHandles.lookup().findStatic(mainClazz, "main", MethodType.methodType(void.class, String[].class)); MethodHandle mainMethod = MethodHandles.lookup().findStatic(mainClazz, "main", MethodType.methodType(void.class, String[].class));
mainMethod.invoke((Object) args.toArray(new String[0])); mainMethod.asFixedArity().invokeWithArguments((Object) args.toArray(new String[0]));
} }
private static String getPackageFromClass(String clazz) { private static String getPackageFromClass(String clazz) {
@ -103,14 +156,12 @@ private static String getPackageFromClass(String clazz) {
return clazz; return clazz;
} }
private static class ModuleClassLoader extends URLClassLoader { private class ModuleClassLoader extends URLClassLoader {
private final ClassLoader SYSTEM_CLASS_LOADER = ClassLoader.getSystemClassLoader(); private final ClassLoader SYSTEM_CLASS_LOADER = ClassLoader.getSystemClassLoader();
private final List<ClassLoaderControl.ClassTransformer> transformers = new ArrayList<>(); private final List<ClassLoaderControl.ClassTransformer> transformers = new ArrayList<>();
private final Map<String, Class<?>> classMap = new ConcurrentHashMap<>(); private final Map<String, Class<?>> classMap = new ConcurrentHashMap<>();
private String nativePath; private String nativePath;
private ModuleLayer.Controller controller;
private final List<String> packages = new ArrayList<>(); private final List<String> packages = new ArrayList<>();
public ModuleClassLoader(URL[] urls) { public ModuleClassLoader(URL[] urls) {
super(urls); super(urls);
@ -245,6 +296,11 @@ public ClassLoader getClassLoader() {
public Object getJava9ModuleController() { public Object getJava9ModuleController() {
return controller; return controller;
} }
@Override
public MethodHandles.Lookup getHackLookup() {
return hackLookup;
}
} }
} }
} }

View file

@ -189,6 +189,7 @@ public void run(String... args) throws Throwable {
break; break;
} }
LaunchOptions options = new LaunchOptions(); LaunchOptions options = new LaunchOptions();
options.enableHacks = config.enableHacks;
options.moduleConf = config.moduleConf; options.moduleConf = config.moduleConf;
classLoaderControl = launch.init(config.classpath.stream().map(Paths::get).collect(Collectors.toCollection(ArrayList::new)), config.nativesDir, options); classLoaderControl = launch.init(config.classpath.stream().map(Paths::get).collect(Collectors.toCollection(ArrayList::new)), config.nativesDir, options);
LogHelper.info("Start Minecraft Server"); LogHelper.info("Start Minecraft Server");
@ -263,6 +264,7 @@ public static final class Config {
public byte[] encodedServerRsaPublicKey; public byte[] encodedServerRsaPublicKey;
public byte[] encodedServerEcPublicKey; public byte[] encodedServerEcPublicKey;
public boolean enableHacks;
public Map<String, String> properties; public Map<String, String> properties;
} }