mirror of
https://github.com/GravitLauncher/Launcher
synced 2024-12-22 08:31:07 +03:00
[FEATURE] Support modular start
This commit is contained in:
parent
6cd5a69149
commit
44bc8b0bbc
11 changed files with 165 additions and 24 deletions
|
@ -49,6 +49,7 @@ public void run(String[] args) {
|
|||
String compatClasses = System.getProperty("launcher.runtime.launch.compat", null);
|
||||
String nativesDir = System.getProperty("launcher.runtime.launch.natives", "natives");
|
||||
String launcherOptionsPath = System.getProperty("launcher.runtime.launch.options", null);
|
||||
boolean enableHacks = Boolean.getBoolean("launcher.runtime.launch.enablehacks");
|
||||
ClientPermissions permissions = new ClientPermissions();
|
||||
if(mainClass == null) {
|
||||
throw new NullPointerException("Add `-Dlauncher.runtime.mainclass=YOUR_MAIN_CLASS` to jvmArgs");
|
||||
|
@ -130,6 +131,7 @@ public void run(String[] args) {
|
|||
} else {
|
||||
options = new LaunchOptions();
|
||||
}
|
||||
options.enableHacks = enableHacks;
|
||||
ClassLoaderControl classLoaderControl = launch.init(classpath, nativesDir, options);
|
||||
ClientService.classLoaderControl = classLoaderControl;
|
||||
if(compatClasses != null) {
|
||||
|
|
|
@ -460,7 +460,7 @@ public enum ClassLoaderConfig {
|
|||
}
|
||||
|
||||
public enum CompatibilityFlags {
|
||||
LEGACY_NATIVES_DIR, CLASS_CONTROL_API
|
||||
LEGACY_NATIVES_DIR, CLASS_CONTROL_API, ENABLE_HACKS
|
||||
}
|
||||
|
||||
public static class Version implements Comparable<Version> {
|
||||
|
|
|
@ -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);
|
||||
EnvHelper.checkDangerousParams();
|
||||
JVMHelper.checkStackTrace(ClientLauncherEntryPoint.class);
|
||||
LogHelper.printVersion("Client Launcher");
|
||||
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.loadModule(new ClientLauncherCoreModule());
|
||||
LauncherConfig.initModules(modulesManager); //INIT
|
||||
|
@ -132,6 +140,7 @@ public static void main(String[] args) throws Throwable {
|
|||
LogHelper.debug("Natives dir %s", params.nativesDir);
|
||||
ClientProfile.ClassLoaderConfig classLoaderConfig = profile.getClassLoaderConfig();
|
||||
LaunchOptions options = new LaunchOptions();
|
||||
options.enableHacks = profile.hasFlag(ClientProfile.CompatibilityFlags.ENABLE_HACKS);
|
||||
options.moduleConf = profile.getModuleConf();
|
||||
if (classLoaderConfig == ClientProfile.ClassLoaderConfig.LAUNCHER) {
|
||||
if(JVMHelper.JVM_VERSION <= 11) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package pro.gravit.utils.launch;
|
||||
|
||||
import pro.gravit.utils.helper.HackHelper;
|
||||
import pro.gravit.utils.helper.JVMHelper;
|
||||
|
||||
import java.io.File;
|
||||
|
@ -23,6 +24,7 @@
|
|||
public class BasicLaunch implements Launch {
|
||||
|
||||
private Instrumentation instrumentation;
|
||||
private MethodHandles.Lookup hackLookup;
|
||||
|
||||
public BasicLaunch(Instrumentation instrumentation) {
|
||||
this.instrumentation = instrumentation;
|
||||
|
@ -33,6 +35,9 @@ public BasicLaunch() {
|
|||
|
||||
@Override
|
||||
public ClassLoaderControl init(List<Path> files, String nativePath, LaunchOptions options) {
|
||||
if(options.enableHacks) {
|
||||
hackLookup = HackHelper.createHackLookup(BasicLaunch.class);
|
||||
}
|
||||
return new BasicClassLoaderControl();
|
||||
}
|
||||
|
||||
|
@ -41,7 +46,7 @@ public void launch(String mainClass, String mainModule, Collection<String> args)
|
|||
Class<?> mainClazz = Class.forName(mainClass);
|
||||
MethodHandle mainMethod = MethodHandles.lookup().findStatic(mainClazz, "main", MethodType.methodType(void.class, String[].class)).asFixedArity();
|
||||
JVMHelper.fullGC();
|
||||
mainMethod.invokeWithArguments((Object) args.toArray(new String[0]));
|
||||
mainMethod.asFixedArity().invokeWithArguments((Object) args.toArray(new String[0]));
|
||||
}
|
||||
|
||||
private class BasicClassLoaderControl implements ClassLoaderControl {
|
||||
|
@ -120,5 +125,10 @@ public ClassLoader getClassLoader() {
|
|||
public Object getJava9ModuleController() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodHandles.Lookup getHackLookup() {
|
||||
return hackLookup;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package pro.gravit.utils.launch;
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Path;
|
||||
import java.security.ProtectionDomain;
|
||||
|
@ -16,6 +17,8 @@ public interface ClassLoaderControl {
|
|||
ClassLoader getClassLoader();
|
||||
|
||||
Object getJava9ModuleController();
|
||||
|
||||
MethodHandles.Lookup getHackLookup();
|
||||
interface ClassTransformer {
|
||||
boolean filter(String moduleName, String name);
|
||||
byte[] transform(String moduleName, String name, ProtectionDomain protectionDomain, byte[] data);
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
import java.util.Map;
|
||||
|
||||
public class LaunchOptions {
|
||||
public boolean enableHacks;
|
||||
public ModuleConf moduleConf;
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package pro.gravit.utils.launch;
|
||||
|
||||
import pro.gravit.utils.helper.HackHelper;
|
||||
import pro.gravit.utils.helper.IOHelper;
|
||||
import pro.gravit.utils.helper.JVMHelper;
|
||||
|
||||
|
@ -20,6 +21,7 @@
|
|||
|
||||
public class LegacyLaunch implements Launch {
|
||||
private LegacyClassLoader legacyClassLoader;
|
||||
private MethodHandles.Lookup hackLookup;
|
||||
|
||||
@Override
|
||||
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());
|
||||
legacyClassLoader.nativePath = nativePath;
|
||||
if(options.enableHacks) {
|
||||
hackLookup = HackHelper.createHackLookup(BasicLaunch.class);
|
||||
}
|
||||
return legacyClassLoader.makeControl();
|
||||
}
|
||||
|
||||
|
@ -40,10 +45,10 @@ public void launch(String mainClass, String mainModule, Collection<String> args)
|
|||
Class<?> mainClazz = Class.forName(mainClass, true, legacyClassLoader);
|
||||
MethodHandle mainMethod = MethodHandles.lookup().findStatic(mainClazz, "main", MethodType.methodType(void.class, String[].class)).asFixedArity();
|
||||
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 List<ClassLoaderControl.ClassTransformer> transformers = new ArrayList<>();
|
||||
private final Map<String, Class<?>> classMap = new ConcurrentHashMap<>();
|
||||
|
@ -170,6 +175,11 @@ public ClassLoader getClassLoader() {
|
|||
public Object getJava9ModuleController() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodHandles.Lookup getHackLookup() {
|
||||
return hackLookup;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package pro.gravit.utils.launch;
|
||||
|
||||
import pro.gravit.utils.helper.HackHelper;
|
||||
import pro.gravit.utils.helper.IOHelper;
|
||||
import pro.gravit.utils.helper.JVMHelper;
|
||||
import pro.gravit.utils.helper.LogHelper;
|
||||
|
@ -12,6 +13,7 @@
|
|||
import java.lang.module.Configuration;
|
||||
import java.lang.module.ModuleFinder;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.file.Path;
|
||||
|
@ -28,6 +30,7 @@ public class ModuleLaunch implements Launch {
|
|||
private ModuleLayer.Controller controller;
|
||||
private ModuleFinder moduleFinder;
|
||||
private ModuleLayer layer;
|
||||
private MethodHandles.Lookup hackLookup;
|
||||
@Override
|
||||
public ClassLoaderControl init(List<Path> files, String nativePath, LaunchOptions options) {
|
||||
moduleClassLoader = new ModuleClassLoader(files.stream().map((e) -> {
|
||||
|
@ -39,34 +42,84 @@ public ClassLoaderControl init(List<Path> files, String nativePath, LaunchOption
|
|||
}).toArray(URL[]::new), BasicLaunch.class.getClassLoader());
|
||||
moduleClassLoader.nativePath = nativePath;
|
||||
{
|
||||
if(options.enableHacks) {
|
||||
hackLookup = HackHelper.createHackLookup(ModuleLaunch.class);
|
||||
}
|
||||
if(options.moduleConf != null) {
|
||||
// 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();
|
||||
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()
|
||||
.resolveAndBind(ModuleFinder.of(), moduleFinder, options.moduleConf.modules);
|
||||
.resolveAndBind(moduleFinder, ModuleFinder.of(), options.moduleConf.modules);
|
||||
controller = ModuleLayer.defineModulesWithOneLoader(configuration, List.of(bootLayer), moduleClassLoader);
|
||||
moduleClassLoader.controller = controller;
|
||||
layer = controller.layer();
|
||||
// Configure exports / opens
|
||||
for(var e : options.moduleConf.exports.entrySet()) {
|
||||
String[] split = e.getKey().split("\\\\");
|
||||
Module source = layer.findModule(split[0]).orElseThrow();
|
||||
String[] split = e.getKey().split("/");
|
||||
String moduleName = split[0];
|
||||
String pkg = split[1];
|
||||
Module target = layer.findModule(e.getValue()).orElseThrow();
|
||||
controller.addExports(source, pkg, target);
|
||||
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);
|
||||
}
|
||||
}
|
||||
for(var e : options.moduleConf.opens.entrySet()) {
|
||||
String[] split = e.getKey().split("\\\\");
|
||||
Module source = layer.findModule(split[0]).orElseThrow();
|
||||
String[] split = e.getKey().split("/");
|
||||
String moduleName = split[0];
|
||||
String pkg = split[1];
|
||||
Module target = layer.findModule(e.getValue()).orElseThrow();
|
||||
controller.addOpens(source, pkg, target);
|
||||
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);
|
||||
}
|
||||
}
|
||||
for(var e : options.moduleConf.reads.entrySet()) {
|
||||
Module source = layer.findModule(e.getKey()).orElseThrow();
|
||||
Module target = layer.findModule(e.getValue()).orElseThrow();
|
||||
controller.addReads(source, target);
|
||||
LogHelper.dev("Read module %s to %s", e.getKey(), e.getValue());
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -80,7 +133,7 @@ public void launch(String mainClass, String mainModuleName, Collection<String> a
|
|||
Class<?> mainClazz = Class.forName(mainClass, true, moduleClassLoader);
|
||||
MethodHandle mainMethod = MethodHandles.lookup().findStatic(mainClazz, "main", MethodType.methodType(void.class, String[].class)).asFixedArity();
|
||||
JVMHelper.fullGC();
|
||||
mainMethod.invokeWithArguments((Object) args.toArray(new String[0]));
|
||||
mainMethod.asFixedArity().invokeWithArguments((Object) args.toArray(new String[0]));
|
||||
return;
|
||||
}
|
||||
Module mainModule = layer.findModule(mainModuleName).orElseThrow();
|
||||
|
@ -92,7 +145,7 @@ public void launch(String mainClass, String mainModuleName, Collection<String> a
|
|||
ClassLoader loader = mainModule.getClassLoader();
|
||||
Class<?> mainClazz = Class.forName(mainClass, true, loader);
|
||||
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) {
|
||||
|
@ -103,14 +156,12 @@ private static String getPackageFromClass(String clazz) {
|
|||
return clazz;
|
||||
}
|
||||
|
||||
private static class ModuleClassLoader extends URLClassLoader {
|
||||
private class ModuleClassLoader extends URLClassLoader {
|
||||
private final ClassLoader SYSTEM_CLASS_LOADER = ClassLoader.getSystemClassLoader();
|
||||
private final List<ClassLoaderControl.ClassTransformer> transformers = new ArrayList<>();
|
||||
private final Map<String, Class<?>> classMap = new ConcurrentHashMap<>();
|
||||
private String nativePath;
|
||||
|
||||
private ModuleLayer.Controller controller;
|
||||
|
||||
private final List<String> packages = new ArrayList<>();
|
||||
public ModuleClassLoader(URL[] urls) {
|
||||
super(urls);
|
||||
|
@ -245,6 +296,11 @@ public ClassLoader getClassLoader() {
|
|||
public Object getJava9ModuleController() {
|
||||
return controller;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodHandles.Lookup getHackLookup() {
|
||||
return hackLookup;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -189,6 +189,7 @@ public void run(String... args) throws Throwable {
|
|||
break;
|
||||
}
|
||||
LaunchOptions options = new LaunchOptions();
|
||||
options.enableHacks = config.enableHacks;
|
||||
options.moduleConf = config.moduleConf;
|
||||
classLoaderControl = launch.init(config.classpath.stream().map(Paths::get).collect(Collectors.toCollection(ArrayList::new)), config.nativesDir, options);
|
||||
LogHelper.info("Start Minecraft Server");
|
||||
|
@ -263,6 +264,7 @@ public static final class Config {
|
|||
public byte[] encodedServerRsaPublicKey;
|
||||
|
||||
public byte[] encodedServerEcPublicKey;
|
||||
public boolean enableHacks;
|
||||
|
||||
public Map<String, String> properties;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue