From 44bc8b0bbc734e8410c9bcec4fa929de524fde6d Mon Sep 17 00:00:00 2001 From: Gravita <12893402+gravit0@users.noreply.github.com> Date: Wed, 1 Nov 2023 04:05:14 +0700 Subject: [PATCH] [FEATURE] Support modular start --- .../launcher/debug/ClientRuntimeProvider.java | 2 + .../launcher/profiles/ClientProfile.java | 2 +- .../client/ClientLauncherEntryPoint.java | 11 ++- .../pro/gravit/utils/helper/HackHelper.java | 33 +++++++ .../pro/gravit/utils/launch/BasicLaunch.java | 12 ++- .../utils/launch/ClassLoaderControl.java | 3 + .../gravit/utils/launch/LaunchOptions.java | 1 + .../pro/gravit/utils/launch/LegacyLaunch.java | 14 ++- .../pro/gravit/utils/launch/ModuleHacks.java | 15 +++ .../pro/gravit/utils/launch/ModuleLaunch.java | 94 +++++++++++++++---- .../gravit/launcher/server/ServerWrapper.java | 2 + 11 files changed, 165 insertions(+), 24 deletions(-) create mode 100644 LauncherCore/src/main/java/pro/gravit/utils/helper/HackHelper.java create mode 100644 LauncherCore/src/main/java11/pro/gravit/utils/launch/ModuleHacks.java diff --git a/Launcher/src/main/java/pro/gravit/launcher/debug/ClientRuntimeProvider.java b/Launcher/src/main/java/pro/gravit/launcher/debug/ClientRuntimeProvider.java index 6c270e76..82bd51e2 100644 --- a/Launcher/src/main/java/pro/gravit/launcher/debug/ClientRuntimeProvider.java +++ b/Launcher/src/main/java/pro/gravit/launcher/debug/ClientRuntimeProvider.java @@ -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) { diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/profiles/ClientProfile.java b/LauncherAPI/src/main/java/pro/gravit/launcher/profiles/ClientProfile.java index e484f551..70d9dd2a 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/profiles/ClientProfile.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/profiles/ClientProfile.java @@ -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 { diff --git a/LauncherClient/src/main/java/pro/gravit/launcher/client/ClientLauncherEntryPoint.java b/LauncherClient/src/main/java/pro/gravit/launcher/client/ClientLauncherEntryPoint.java index 9fd2a165..70c7c739 100644 --- a/LauncherClient/src/main/java/pro/gravit/launcher/client/ClientLauncherEntryPoint.java +++ b/LauncherClient/src/main/java/pro/gravit/launcher/client/ClientLauncherEntryPoint.java @@ -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) { diff --git a/LauncherCore/src/main/java/pro/gravit/utils/helper/HackHelper.java b/LauncherCore/src/main/java/pro/gravit/utils/helper/HackHelper.java new file mode 100644 index 00000000..87d95ddc --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/utils/helper/HackHelper.java @@ -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 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); + } +} diff --git a/LauncherCore/src/main/java/pro/gravit/utils/launch/BasicLaunch.java b/LauncherCore/src/main/java/pro/gravit/utils/launch/BasicLaunch.java index 740553e3..1cb326a4 100644 --- a/LauncherCore/src/main/java/pro/gravit/utils/launch/BasicLaunch.java +++ b/LauncherCore/src/main/java/pro/gravit/utils/launch/BasicLaunch.java @@ -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 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 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; + } } } diff --git a/LauncherCore/src/main/java/pro/gravit/utils/launch/ClassLoaderControl.java b/LauncherCore/src/main/java/pro/gravit/utils/launch/ClassLoaderControl.java index c8b857b4..08e44065 100644 --- a/LauncherCore/src/main/java/pro/gravit/utils/launch/ClassLoaderControl.java +++ b/LauncherCore/src/main/java/pro/gravit/utils/launch/ClassLoaderControl.java @@ -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); diff --git a/LauncherCore/src/main/java/pro/gravit/utils/launch/LaunchOptions.java b/LauncherCore/src/main/java/pro/gravit/utils/launch/LaunchOptions.java index 59312f30..f5232739 100644 --- a/LauncherCore/src/main/java/pro/gravit/utils/launch/LaunchOptions.java +++ b/LauncherCore/src/main/java/pro/gravit/utils/launch/LaunchOptions.java @@ -6,6 +6,7 @@ import java.util.Map; public class LaunchOptions { + public boolean enableHacks; public ModuleConf moduleConf; diff --git a/LauncherCore/src/main/java/pro/gravit/utils/launch/LegacyLaunch.java b/LauncherCore/src/main/java/pro/gravit/utils/launch/LegacyLaunch.java index aca88038..08f65795 100644 --- a/LauncherCore/src/main/java/pro/gravit/utils/launch/LegacyLaunch.java +++ b/LauncherCore/src/main/java/pro/gravit/utils/launch/LegacyLaunch.java @@ -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 files, String nativePath, LaunchOptions options) { @@ -31,6 +33,9 @@ public ClassLoaderControl init(List 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 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 transformers = new ArrayList<>(); private final Map> classMap = new ConcurrentHashMap<>(); @@ -170,6 +175,11 @@ public ClassLoader getClassLoader() { public Object getJava9ModuleController() { return null; } + + @Override + public MethodHandles.Lookup getHackLookup() { + return hackLookup; + } } } } diff --git a/LauncherCore/src/main/java11/pro/gravit/utils/launch/ModuleHacks.java b/LauncherCore/src/main/java11/pro/gravit/utils/launch/ModuleHacks.java new file mode 100644 index 00000000..de357291 --- /dev/null +++ b/LauncherCore/src/main/java11/pro/gravit/utils/launch/ModuleHacks.java @@ -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); + } + } +} diff --git a/LauncherCore/src/main/java11/pro/gravit/utils/launch/ModuleLaunch.java b/LauncherCore/src/main/java11/pro/gravit/utils/launch/ModuleLaunch.java index bef68378..c1582557 100644 --- a/LauncherCore/src/main/java11/pro/gravit/utils/launch/ModuleLaunch.java +++ b/LauncherCore/src/main/java11/pro/gravit/utils/launch/ModuleLaunch.java @@ -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 files, String nativePath, LaunchOptions options) { moduleClassLoader = new ModuleClassLoader(files.stream().map((e) -> { @@ -39,34 +42,84 @@ public ClassLoaderControl init(List 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 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 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 transformers = new ArrayList<>(); private final Map> classMap = new ConcurrentHashMap<>(); private String nativePath; - private ModuleLayer.Controller controller; - private final List 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; + } } } } diff --git a/ServerWrapper/src/main/java/pro/gravit/launcher/server/ServerWrapper.java b/ServerWrapper/src/main/java/pro/gravit/launcher/server/ServerWrapper.java index 42f2aa47..6884ef9b 100644 --- a/ServerWrapper/src/main/java/pro/gravit/launcher/server/ServerWrapper.java +++ b/ServerWrapper/src/main/java/pro/gravit/launcher/server/ServerWrapper.java @@ -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 properties; }