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 3fad11ca..369258e6 100644 --- a/Launcher/src/main/java/pro/gravit/launcher/debug/ClientRuntimeProvider.java +++ b/Launcher/src/main/java/pro/gravit/launcher/debug/ClientRuntimeProvider.java @@ -1,6 +1,7 @@ package pro.gravit.launcher.debug; import pro.gravit.launcher.ClientPermissions; +import pro.gravit.launcher.Launcher; import pro.gravit.launcher.LauncherEngine; import pro.gravit.launcher.api.AuthService; import pro.gravit.launcher.events.request.AuthRequestEvent; @@ -10,10 +11,17 @@ import pro.gravit.launcher.request.Request; import pro.gravit.launcher.request.auth.AuthRequest; import pro.gravit.launcher.request.update.ProfilesRequest; +import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.LogHelper; +import pro.gravit.utils.launch.*; +import java.io.File; +import java.io.Reader; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.UUID; public class ClientRuntimeProvider implements RuntimeProvider { @@ -32,6 +40,11 @@ public void run(String[] args) { long expire = Long.parseLong(System.getProperty("launcher.runtime.auth.expire", "0")); String profileUUID = System.getProperty("launcher.runtime.profileuuid", null); String mainClass = System.getProperty("launcher.runtime.mainclass", null); + String mainModule = System.getProperty("launcher.runtime.mainmodule", null); + String launchMode = System.getProperty("launcher.runtime.launch", "basic"); + 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); ClientPermissions permissions = new ClientPermissions(); if(mainClass == null) { throw new NullPointerException("Add `-Dlauncher.runtime.mainclass=YOUR_MAIN_CLASS` to jvmArgs"); @@ -79,8 +92,42 @@ public void run(String[] args) { AuthService.uuid = UUID.fromString(uuid); AuthService.username = username; AuthService.permissions = permissions; - Class mainClazz = Class.forName(mainClass); - mainClazz.getMethod("main", String[].class).invoke(null, (Object) newArgs.toArray(new String[0])); + Launch launch; + switch (launchMode) { + case "basic": { + launch = new BasicLaunch(); + break; + } + case "legacy": { + launch = new LegacyLaunch(); + break; + } + case "module": { + launch = new ModuleLaunch(); + break; + } + default: { + throw new UnsupportedOperationException(String.format("Unknown launch mode: '%s'", launchMode)); + } + } + List classpath = new ArrayList<>(); + try { + for(var c : System.getProperty("java.class.path").split(File.pathSeparator)) { + classpath.add(Paths.get(c)); + } + } catch (Throwable e) { + LogHelper.error(e); + } + LaunchOptions options; + if(launcherOptionsPath != null) { + try(Reader reader = IOHelper.newReader(Paths.get(launcherOptionsPath))) { + options = Launcher.gsonManager.gson.fromJson(reader, LaunchOptions.class); + } + } else { + options = new LaunchOptions(); + } + launch.init(classpath, nativesDir, options); + launch.launch(mainClass, mainModule, Arrays.asList(args)); } catch (Throwable e) { LogHelper.error(e); LauncherEngine.exitLauncher(-15); 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 36c533ee..e484f551 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/profiles/ClientProfile.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/profiles/ClientProfile.java @@ -8,6 +8,7 @@ import pro.gravit.launcher.profiles.optional.triggers.OptionalTrigger; import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.VerifyHelper; +import pro.gravit.utils.launch.LaunchOptions; import java.lang.reflect.Type; import java.net.InetSocketAddress; @@ -80,6 +81,10 @@ public final class ClientProfile implements Comparable { // Client launcher @LauncherNetworkAPI private String mainClass; + @LauncherNetworkAPI + private String mainModule; + @LauncherNetworkAPI + private LaunchOptions.ModuleConf moduleConf; public ClientProfile() { update = new ArrayList<>(); @@ -211,6 +216,14 @@ public String getMainClass() { return mainClass; } + public String getMainModule() { + return mainModule; + } + + public LaunchOptions.ModuleConf getModuleConf() { + return moduleConf; + } + public List getServers() { return servers; } @@ -447,7 +460,7 @@ public enum ClassLoaderConfig { } public enum CompatibilityFlags { - LEGACY_NATIVES_DIR + LEGACY_NATIVES_DIR, CLASS_CONTROL_API } public static class Version implements Comparable { diff --git a/LauncherClient/src/main/java/pro/gravit/launcher/api/ClientService.java b/LauncherClient/src/main/java/pro/gravit/launcher/api/ClientService.java index 20e29eb6..a065319d 100644 --- a/LauncherClient/src/main/java/pro/gravit/launcher/api/ClientService.java +++ b/LauncherClient/src/main/java/pro/gravit/launcher/api/ClientService.java @@ -1,21 +1,20 @@ package pro.gravit.launcher.api; import pro.gravit.launcher.utils.ApiBridgeService; +import pro.gravit.utils.helper.IOHelper; +import pro.gravit.utils.helper.JVMHelper; +import pro.gravit.utils.launch.ClassLoaderControl; import java.lang.instrument.Instrumentation; import java.net.URL; public class ClientService { public static Instrumentation instrumentation; - public static ClassLoader classLoader; + public static ClassLoaderControl classLoaderControl; public static String nativePath; public static URL[] baseURLs; - public static ClassLoader getClassLoader() { - return classLoader; - } - public static String findLibrary(String name) { - return ApiBridgeService.findLibrary(classLoader, name); + return nativePath.concat(IOHelper.PLATFORM_SEPARATOR).concat(JVMHelper.NATIVE_PREFIX).concat(name).concat(JVMHelper.NATIVE_EXTENSION); } } diff --git a/LauncherClient/src/main/java/pro/gravit/launcher/client/ClientClassLoader.java b/LauncherClient/src/main/java/pro/gravit/launcher/client/ClientClassLoader.java deleted file mode 100644 index 624ed0d1..00000000 --- a/LauncherClient/src/main/java/pro/gravit/launcher/client/ClientClassLoader.java +++ /dev/null @@ -1,92 +0,0 @@ -package pro.gravit.launcher.client; - -import pro.gravit.utils.helper.IOHelper; -import pro.gravit.utils.helper.JVMHelper; - -import java.net.URL; -import java.net.URLClassLoader; -import java.util.ArrayList; -import java.util.List; - -public class ClientClassLoader extends URLClassLoader { - private static final ClassLoader SYSTEM_CLASS_LOADER = ClassLoader.getSystemClassLoader(); - public String nativePath; - - private final List packages = new ArrayList<>(); - - /** - * Constructs a new URLClassLoader for the specified URLs using the - * default delegation parent {@code ClassLoader}. The URLs will - * be searched in the order specified for classes and resources after - * first searching in the parent class loader. Any URL that ends with - * a '/' is assumed to refer to a directory. Otherwise, the URL is - * assumed to refer to a JAR file which will be downloaded and opened - * as needed. - * - *

If there is a security manager, this method first - * calls the security manager's {@code checkCreateClassLoader} method - * to ensure creation of a class loader is allowed. - * - * @param urls the URLs from which to load classes and resources - * @throws SecurityException if a security manager exists and its - * {@code checkCreateClassLoader} method doesn't allow - * creation of a class loader. - * @throws NullPointerException if {@code urls} is {@code null}. - */ - public ClientClassLoader(URL[] urls) { - super(urls); - packages.add("pro.gravit.launcher."); - packages.add("pro.gravit.utils."); - } - - /** - * Constructs a new URLClassLoader for the given URLs. The URLs will be - * searched in the order specified for classes and resources after first - * searching in the specified parent class loader. Any {@code jar:} - * scheme URL is assumed to refer to a JAR file. Any {@code file:} scheme - * URL that ends with a '/' is assumed to refer to a directory. Otherwise, - * the URL is assumed to refer to a JAR file which will be downloaded and - * opened as needed. - * - *

If there is a security manager, this method first - * calls the security manager's {@code checkCreateClassLoader} method - * to ensure creation of a class loader is allowed. - * - * @param urls the URLs from which to load classes and resources - * @param parent the parent class loader for delegation - * @throws SecurityException if a security manager exists and its - * {@code checkCreateClassLoader} method doesn't allow - * creation of a class loader. - * @throws NullPointerException if {@code urls} is {@code null}. - */ - public ClientClassLoader(URL[] urls, ClassLoader parent) { - super(urls, parent); - } - - @Override - protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { - if(name != null) { - for(String pkg : packages) { - if(name.startsWith(pkg)) { - return SYSTEM_CLASS_LOADER.loadClass(name); - } - } - } - return super.loadClass(name, resolve); - } - - @Override - public String findLibrary(String name) { - return nativePath.concat(IOHelper.PLATFORM_SEPARATOR).concat(JVMHelper.NATIVE_PREFIX).concat(name).concat(JVMHelper.NATIVE_EXTENSION); - } - - public void addAllowedPackage(String pkg) { - packages.add(pkg); - } - - @Override - public void addURL(URL url) { - super.addURL(url); - } -} - 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 41bc3ab7..9fd2a165 100644 --- a/LauncherClient/src/main/java/pro/gravit/launcher/client/ClientLauncherEntryPoint.java +++ b/LauncherClient/src/main/java/pro/gravit/launcher/client/ClientLauncherEntryPoint.java @@ -21,6 +21,7 @@ import pro.gravit.launcher.serialize.HInput; import pro.gravit.launcher.utils.DirWatcher; import pro.gravit.utils.helper.*; +import pro.gravit.utils.launch.*; import java.io.File; import java.io.IOException; @@ -39,10 +40,12 @@ import java.util.stream.Stream; public class ClientLauncherEntryPoint { - private static ClassLoader classLoader; public static ClientModuleManager modulesManager; public static ClientParams clientParams; + private static Launch launch; + private static ClassLoaderControl classLoaderControl; + private static ClientParams readParams(SocketAddress address) throws IOException { try (Socket socket = IOHelper.newSocket()) { socket.connect(address); @@ -102,7 +105,7 @@ public static void main(String[] args) throws Throwable { LogHelper.debug("Verifying ClientLauncher sign and classpath"); List classpath = resolveClassPath(clientDir, params.actions, params.profile) .filter(x -> !profile.getModulePath().contains(clientDir.relativize(x).toString())) - .collect(Collectors.toList()); + .collect(Collectors.toCollection(ArrayList::new)); List classpathURLs = classpath.stream().map(IOHelper::toURL).collect(Collectors.toList()); // Start client with WatchService monitoring RequestService service; @@ -128,34 +131,39 @@ 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.moduleConf = profile.getModuleConf(); if (classLoaderConfig == ClientProfile.ClassLoaderConfig.LAUNCHER) { - ClientClassLoader classLoader = new ClientClassLoader(classpathURLs.toArray(new URL[0]), ClientLauncherEntryPoint.class.getClassLoader()); + if(JVMHelper.JVM_VERSION <= 11) { + launch = new LegacyLaunch(); + } else { + launch = new ModuleLaunch(); + } + classLoaderControl = launch.init(classpath, params.nativesDir, options); System.setProperty("java.class.path", classpath.stream().map(Path::toString).collect(Collectors.joining(File.pathSeparator))); - ClientLauncherEntryPoint.classLoader = classLoader; - Thread.currentThread().setContextClassLoader(classLoader); - classLoader.nativePath = params.nativesDir; - modulesManager.invokeEvent(new ClientProcessClassLoaderEvent(classLoader, profile)); - ClientService.classLoader = classLoader; - ClientService.nativePath = classLoader.nativePath; - classLoader.addURL(IOHelper.getCodeSource(ClientLauncherEntryPoint.class).toUri().toURL()); - ClientService.baseURLs = classLoader.getURLs(); + modulesManager.invokeEvent(new ClientProcessClassLoaderEvent(launch, classLoaderControl, profile)); + ClientService.nativePath = params.nativesDir; + ClientService.baseURLs = classLoaderControl.getURLs(); } else if (classLoaderConfig == ClientProfile.ClassLoaderConfig.AGENT) { - ClientLauncherEntryPoint.classLoader = ClassLoader.getSystemClassLoader(); + launch = new BasicLaunch(LauncherAgent.inst); classpathURLs.add(IOHelper.getCodeSource(ClientLauncherEntryPoint.class).toUri().toURL()); + classLoaderControl = launch.init(classpath, params.nativesDir, options); for (URL url : classpathURLs) { LauncherAgent.addJVMClassPath(Paths.get(url.toURI())); } ClientService.instrumentation = LauncherAgent.inst; ClientService.nativePath = params.nativesDir; - modulesManager.invokeEvent(new ClientProcessClassLoaderEvent(classLoader, profile)); - ClientService.classLoader = classLoader; + modulesManager.invokeEvent(new ClientProcessClassLoaderEvent(launch, null, profile)); ClientService.baseURLs = classpathURLs.toArray(new URL[0]); } else if (classLoaderConfig == ClientProfile.ClassLoaderConfig.SYSTEM_ARGS) { - ClientLauncherEntryPoint.classLoader = ClassLoader.getSystemClassLoader(); - ClientService.classLoader = ClassLoader.getSystemClassLoader(); + launch = new BasicLaunch(); + classLoaderControl = launch.init(classpath, params.nativesDir, options); ClientService.baseURLs = classpathURLs.toArray(new URL[0]); ClientService.nativePath = params.nativesDir; } + if(profile.hasFlag(ClientProfile.CompatibilityFlags.CLASS_CONTROL_API)) { + ClientService.classLoaderControl = classLoaderControl; + } AuthService.username = params.playerProfile.username; AuthService.uuid = params.playerProfile.uuid; KeyService.serverRsaPublicKey = Launcher.getConfig().rsaPublicKey; @@ -264,27 +272,20 @@ private static void launch(ClientProfile profile, ClientParams params) throws Th } LogHelper.debug("Args: " + copy); // Resolve main class and method - Class mainClass = classLoader.loadClass(profile.getMainClass()); - if (LogHelper.isDevEnabled() && classLoader instanceof URLClassLoader) { - for (URL u : ((URLClassLoader) classLoader).getURLs()) { - LogHelper.dev("ClassLoader URL: %s", u.toString()); - } - } modulesManager.invokeEvent(new ClientProcessPreInvokeMainClassEvent(params, profile, args)); // Invoke main method try { { List compatClasses = profile.getCompatClasses(); for (String e : compatClasses) { - Class clazz = classLoader.loadClass(e); - MethodHandle runMethod = MethodHandles.lookup().findStatic(clazz, "run", MethodType.methodType(void.class)); - runMethod.invoke(); + Class clazz = classLoaderControl.getClass(e); + MethodHandle runMethod = MethodHandles.lookup().findStatic(clazz, "run", MethodType.methodType(void.class, ClassLoaderControl.class)); + runMethod.invoke(classLoaderControl); } } - MethodHandle mainMethod = MethodHandles.lookup().findStatic(mainClass, "main", MethodType.methodType(void.class, String[].class)).asFixedArity(); Launcher.LAUNCHED.set(true); JVMHelper.fullGC(); - mainMethod.invokeWithArguments((Object) args.toArray(new String[0])); + launch.launch(params.profile.getMainClass(), params.profile.getMainModule(), args); LogHelper.debug("Main exit successful"); } catch (Throwable e) { LogHelper.error(e); diff --git a/LauncherClient/src/main/java/pro/gravit/launcher/client/events/client/ClientProcessClassLoaderEvent.java b/LauncherClient/src/main/java/pro/gravit/launcher/client/events/client/ClientProcessClassLoaderEvent.java index 9a97c1e7..e42c17a9 100644 --- a/LauncherClient/src/main/java/pro/gravit/launcher/client/events/client/ClientProcessClassLoaderEvent.java +++ b/LauncherClient/src/main/java/pro/gravit/launcher/client/events/client/ClientProcessClassLoaderEvent.java @@ -2,13 +2,17 @@ import pro.gravit.launcher.modules.LauncherModule; import pro.gravit.launcher.profiles.ClientProfile; +import pro.gravit.utils.launch.ClassLoaderControl; +import pro.gravit.utils.launch.Launch; public class ClientProcessClassLoaderEvent extends LauncherModule.Event { - public final ClassLoader clientClassLoader; + public final Launch launch; + public final ClassLoaderControl classLoaderControl; public final ClientProfile profile; - public ClientProcessClassLoaderEvent(ClassLoader clientClassLoader, ClientProfile profile) { - this.clientClassLoader = clientClassLoader; + public ClientProcessClassLoaderEvent(Launch launch, ClassLoaderControl classLoaderControl, ClientProfile profile) { + this.launch = launch; + this.classLoaderControl = classLoaderControl; this.profile = profile; } } diff --git a/LauncherClient/src/main/java/pro/gravit/launcher/utils/ApiBridgeService.java b/LauncherClient/src/main/java/pro/gravit/launcher/utils/ApiBridgeService.java index 76cb77d9..8e0dcab9 100644 --- a/LauncherClient/src/main/java/pro/gravit/launcher/utils/ApiBridgeService.java +++ b/LauncherClient/src/main/java/pro/gravit/launcher/utils/ApiBridgeService.java @@ -2,7 +2,6 @@ import pro.gravit.launcher.Launcher; import pro.gravit.launcher.LauncherTrustManager; -import pro.gravit.launcher.client.ClientClassLoader; import java.security.cert.X509Certificate; @@ -16,12 +15,4 @@ public static void checkCertificatesSuccess(X509Certificate[] certs) throws Exce LauncherTrustManager trustManager = Launcher.getConfig().trustManager; trustManager.checkCertificatesSuccess(certs, trustManager::stdCertificateChecker); } - - public static String findLibrary(ClassLoader classLoader, String library) { - if (classLoader instanceof ClientClassLoader) { - ClientClassLoader clientClassLoader = (ClientClassLoader) classLoader; - return clientClassLoader.findLibrary(library); - } - return null; - } } diff --git a/LauncherCore/src/main/java/pro/gravit/utils/launch/BasicLaunch.java b/LauncherCore/src/main/java/pro/gravit/utils/launch/BasicLaunch.java new file mode 100644 index 00000000..740553e3 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/utils/launch/BasicLaunch.java @@ -0,0 +1,124 @@ +package pro.gravit.utils.launch; + +import pro.gravit.utils.helper.JVMHelper; + +import java.io.File; +import java.io.IOException; +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.IllegalClassFormatException; +import java.lang.instrument.Instrumentation; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.ProtectionDomain; +import java.util.Collection; +import java.util.List; +import java.util.jar.JarFile; + +public class BasicLaunch implements Launch { + + private Instrumentation instrumentation; + + public BasicLaunch(Instrumentation instrumentation) { + this.instrumentation = instrumentation; + } + + public BasicLaunch() { + } + + @Override + public ClassLoaderControl init(List files, String nativePath, LaunchOptions options) { + return new BasicClassLoaderControl(); + } + + @Override + public void launch(String mainClass, String mainModule, Collection args) throws Throwable { + 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])); + } + + private class BasicClassLoaderControl implements ClassLoaderControl { + + @Override + public void addLauncherPackage(String prefix) { + throw new UnsupportedOperationException(); + } + + @Override + public void addTransformer(ClassTransformer transformer) { + if (instrumentation == null) { + throw new UnsupportedOperationException(); + } + instrumentation.addTransformer(new ClassFileTransformer() { + @Override + public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { + if(transformer.filter(null, className)) { + return transformer.transform(null, className, protectionDomain, classfileBuffer); + } + return classfileBuffer; + } + }); + } + + @Override + public void addURL(URL url) { + if (instrumentation == null) { + throw new UnsupportedOperationException(); + } + try { + instrumentation.appendToSystemClassLoaderSearch(new JarFile(new File(url.toURI()))); + } catch (URISyntaxException | IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void addJar(Path path) { + if (instrumentation == null) { + throw new UnsupportedOperationException(); + } + try { + instrumentation.appendToSystemClassLoaderSearch(new JarFile(path.toFile())); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public URL[] getURLs() { + String classpath = System.getProperty("java.class.path"); + String[] split = classpath.split(File.pathSeparator); + URL[] urls = new URL[split.length]; + try { + for(int i=0;i getClass(String name) throws ClassNotFoundException { + return Class.forName(name); + } + + @Override + public ClassLoader getClassLoader() { + return BasicLaunch.class.getClassLoader(); + } + + @Override + public Object getJava9ModuleController() { + return null; + } + } +} diff --git a/LauncherCore/src/main/java/pro/gravit/utils/launch/ClassLoaderControl.java b/LauncherCore/src/main/java/pro/gravit/utils/launch/ClassLoaderControl.java new file mode 100644 index 00000000..c8b857b4 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/utils/launch/ClassLoaderControl.java @@ -0,0 +1,23 @@ +package pro.gravit.utils.launch; + +import java.net.URL; +import java.nio.file.Path; +import java.security.ProtectionDomain; + +public interface ClassLoaderControl { + void addLauncherPackage(String prefix); + void addTransformer(ClassTransformer transformer); + void addURL(URL url); + void addJar(Path path); + URL[] getURLs(); + + Class getClass(String name) throws ClassNotFoundException; + + ClassLoader getClassLoader(); + + Object getJava9ModuleController(); + 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/Launch.java b/LauncherCore/src/main/java/pro/gravit/utils/launch/Launch.java new file mode 100644 index 00000000..67e89af6 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/utils/launch/Launch.java @@ -0,0 +1,10 @@ +package pro.gravit.utils.launch; + +import java.nio.file.Path; +import java.util.Collection; +import java.util.List; + +public interface Launch { + ClassLoaderControl init(List files, String nativePath, LaunchOptions options); + void launch(String mainClass, String mainModule, Collection args) throws Throwable; +} diff --git a/LauncherCore/src/main/java/pro/gravit/utils/launch/LaunchOptions.java b/LauncherCore/src/main/java/pro/gravit/utils/launch/LaunchOptions.java new file mode 100644 index 00000000..59312f30 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/utils/launch/LaunchOptions.java @@ -0,0 +1,19 @@ +package pro.gravit.utils.launch; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class LaunchOptions { + public ModuleConf moduleConf; + + + public static final class ModuleConf { + public List modules = new ArrayList<>(); + public List modulePath = new ArrayList<>(); + public Map exports = new HashMap<>(); + public Map opens = new HashMap<>(); + public Map reads = new HashMap<>(); + } +} diff --git a/LauncherCore/src/main/java/pro/gravit/utils/launch/LegacyLaunch.java b/LauncherCore/src/main/java/pro/gravit/utils/launch/LegacyLaunch.java new file mode 100644 index 00000000..aca88038 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/utils/launch/LegacyLaunch.java @@ -0,0 +1,175 @@ +package pro.gravit.utils.launch; + +import pro.gravit.utils.helper.IOHelper; +import pro.gravit.utils.helper.JVMHelper; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class LegacyLaunch implements Launch { + private LegacyClassLoader legacyClassLoader; + + @Override + public ClassLoaderControl init(List files, String nativePath, LaunchOptions options) { + legacyClassLoader = new LegacyClassLoader(files.stream().map((e) -> { + try { + return e.toUri().toURL(); + } catch (MalformedURLException ex) { + throw new RuntimeException(ex); + } + }).toArray(URL[]::new), BasicLaunch.class.getClassLoader()); + legacyClassLoader.nativePath = nativePath; + return legacyClassLoader.makeControl(); + } + + @Override + public void launch(String mainClass, String mainModule, Collection args) throws Throwable { + Thread.currentThread().setContextClassLoader(legacyClassLoader); + 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])); + } + + private static class LegacyClassLoader 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 final List packages = new ArrayList<>(); + public LegacyClassLoader(URL[] urls) { + super(urls); + packages.add("pro.gravit.launcher."); + packages.add("pro.gravit.utils."); + } + public LegacyClassLoader(URL[] urls, ClassLoader parent) { + super(urls, parent); + } + + @Override + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + if(name != null) { + for(String pkg : packages) { + if(name.startsWith(pkg)) { + return SYSTEM_CLASS_LOADER.loadClass(name); + } + } + } + return super.loadClass(name, resolve); + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + Class clazz; + { + clazz = classMap.get(name); + if(clazz != null) { + return clazz; + } + } + if(name != null && !transformers.isEmpty()) { + boolean needTransform = false; + for(ClassLoaderControl.ClassTransformer t : transformers) { + if(t.filter(null, name)) { + needTransform = true; + break; + } + } + if(needTransform) { + String rawClassName = name.replace(".", "/").concat(".class"); + try(InputStream input = getResourceAsStream(rawClassName)) { + byte[] bytes = IOHelper.read(input); + for(ClassLoaderControl.ClassTransformer t : transformers) { + bytes = t.transform(null, name, null, bytes); + } + clazz = defineClass(name, bytes, 0, bytes.length); + } catch (IOException e) { + throw new ClassNotFoundException(name, e); + } + } + } + if(clazz == null) { + clazz = super.findClass(name); + } + if(clazz != null) { + classMap.put(name, clazz); + return clazz; + } else { + throw new ClassNotFoundException(name); + } + } + + @Override + public String findLibrary(String name) { + return nativePath.concat(IOHelper.PLATFORM_SEPARATOR).concat(JVMHelper.NATIVE_PREFIX).concat(name).concat(JVMHelper.NATIVE_EXTENSION); + } + + public void addAllowedPackage(String pkg) { + packages.add(pkg); + } + + private LegacyClassLoaderControl makeControl() { + return new LegacyClassLoaderControl(); + } + + public class LegacyClassLoaderControl implements ClassLoaderControl { + + @Override + public void addLauncherPackage(String prefix) { + addAllowedPackage(prefix); + } + + @Override + public void addTransformer(ClassTransformer transformer) { + transformers.add(transformer); + } + + @Override + public void addURL(URL url) { + LegacyClassLoader.this.addURL(url); + } + + @Override + public void addJar(Path path) { + try { + LegacyClassLoader.this.addURL(path.toUri().toURL()); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + + @Override + public URL[] getURLs() { + return LegacyClassLoader.this.getURLs(); + } + + @Override + public Class getClass(String name) throws ClassNotFoundException { + return Class.forName(name, false, LegacyClassLoader.this); + } + + @Override + public ClassLoader getClassLoader() { + return LegacyClassLoader.this; + } + + @Override + public Object getJava9ModuleController() { + return null; + } + } + } +} diff --git a/LauncherCore/src/main/java/pro/gravit/utils/launch/ModuleLaunch.java b/LauncherCore/src/main/java/pro/gravit/utils/launch/ModuleLaunch.java new file mode 100644 index 00000000..0301ad02 --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/utils/launch/ModuleLaunch.java @@ -0,0 +1,17 @@ +package pro.gravit.utils.launch; + +import java.nio.file.Path; +import java.util.Collection; +import java.util.List; + +public class ModuleLaunch implements Launch { + @Override + public ClassLoaderControl init(List files, String nativePath, LaunchOptions options) { + throw new UnsupportedOperationException("Please use Multi-Release JAR"); + } + + @Override + public void launch(String mainClass, String mainModule, Collection args) throws Throwable { + throw new UnsupportedOperationException("Please use Multi-Release JAR"); + } +} diff --git a/LauncherCore/src/main/java11/pro/gravit/utils/launch/ModuleLaunch.java b/LauncherCore/src/main/java11/pro/gravit/utils/launch/ModuleLaunch.java new file mode 100644 index 00000000..45286e79 --- /dev/null +++ b/LauncherCore/src/main/java11/pro/gravit/utils/launch/ModuleLaunch.java @@ -0,0 +1,237 @@ +package pro.gravit.utils.launch; + +import pro.gravit.utils.helper.IOHelper; +import pro.gravit.utils.helper.JVMHelper; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.module.Configuration; +import java.lang.module.ModuleFinder; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class ModuleLaunch implements Launch { + private ModuleClassLoader moduleClassLoader; + private Configuration configuration; + private ModuleLayer.Controller controller; + private ModuleFinder moduleFinder; + private ModuleLayer layer; + @Override + public ClassLoaderControl init(List files, String nativePath, LaunchOptions options) { + moduleClassLoader = new ModuleClassLoader(files.stream().map((e) -> { + try { + return e.toUri().toURL(); + } catch (MalformedURLException ex) { + throw new RuntimeException(ex); + } + }).toArray(URL[]::new), BasicLaunch.class.getClassLoader()); + moduleClassLoader.nativePath = nativePath; + { + if(options.moduleConf != null) { + // Create Module Layer + moduleFinder = ModuleFinder.of(options.moduleConf.modulePath.stream().map(Paths::get).toArray(Path[]::new)); + ModuleLayer bootLayer = ModuleLayer.boot(); + configuration = bootLayer.configuration() + .resolveAndBind(ModuleFinder.of(), moduleFinder, 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 pkg = split[1]; + Module target = layer.findModule(e.getValue()).orElseThrow(); + 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 pkg = split[1]; + Module target = layer.findModule(e.getValue()).orElseThrow(); + 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); + } + } + } + return moduleClassLoader.makeControl(); + } + + @Override + public void launch(String mainClass, String mainModuleName, Collection args) throws Throwable { + Thread.currentThread().setContextClassLoader(moduleClassLoader); + if(mainModuleName == null) { + 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])); + return; + } + Module mainModule = layer.findModule(mainModuleName).orElseThrow(); + Module unnamed = ModuleLaunch.class.getClassLoader().getUnnamedModule(); + if(unnamed != null) { + controller.addOpens(mainModule, getPackageFromClass(mainClass), unnamed); + } + // Start main class + 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])); + } + + private static String getPackageFromClass(String clazz) { + int index = clazz.lastIndexOf("."); + if(index >= 0) { + return clazz.substring(0, index); + } + return clazz; + } + + private static 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); + packages.add("pro.gravit.launcher."); + packages.add("pro.gravit.utils."); + } + public ModuleClassLoader(URL[] urls, ClassLoader parent) { + super(urls, parent); + } + + @Override + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + if(name != null) { + for(String pkg : packages) { + if(name.startsWith(pkg)) { + return SYSTEM_CLASS_LOADER.loadClass(name); + } + } + } + return super.loadClass(name, resolve); + } + + @Override + protected Class findClass(String moduleName, String name) { + + Class clazz; + { + clazz = classMap.get(name); + if(clazz != null) { + return clazz; + } + } + if(name != null && !transformers.isEmpty()) { + boolean needTransform = false; + for(ClassLoaderControl.ClassTransformer t : transformers) { + if(t.filter(moduleName, name)) { + needTransform = true; + break; + } + } + if(needTransform) { + String rawClassName = name.replace(".", "/").concat(".class"); + try(InputStream input = getResourceAsStream(rawClassName)) { + byte[] bytes = IOHelper.read(input); + for(ClassLoaderControl.ClassTransformer t : transformers) { + bytes = t.transform(moduleName, name, null, bytes); + } + clazz = defineClass(name, bytes, 0, bytes.length); + } catch (IOException e) { + return null; + } + } + } + if(clazz == null) { + clazz = super.findClass(moduleName, name); + } + if(clazz != null) { + classMap.put(name, clazz); + return clazz; + } else { + return null; + } + } + + @Override + public String findLibrary(String name) { + return nativePath.concat(IOHelper.PLATFORM_SEPARATOR).concat(JVMHelper.NATIVE_PREFIX).concat(name).concat(JVMHelper.NATIVE_EXTENSION); + } + + public void addAllowedPackage(String pkg) { + packages.add(pkg); + } + + private ModuleClassLoaderControl makeControl() { + return new ModuleClassLoaderControl(); + } + + private class ModuleClassLoaderControl implements ClassLoaderControl { + + @Override + public void addLauncherPackage(String prefix) { + addAllowedPackage(prefix); + } + + @Override + public void addTransformer(ClassTransformer transformer) { + transformers.add(transformer); + } + + @Override + public void addURL(URL url) { + ModuleClassLoader.this.addURL(url); + } + + @Override + public void addJar(Path path) { + try { + ModuleClassLoader.this.addURL(path.toUri().toURL()); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + + @Override + public URL[] getURLs() { + return ModuleClassLoader.this.getURLs(); + } + + @Override + public Class getClass(String name) throws ClassNotFoundException { + return Class.forName(name, false, ModuleClassLoader.this); + } + + @Override + public ClassLoader getClassLoader() { + return ModuleClassLoader.this; + } + + @Override + public Object getJava9ModuleController() { + return controller; + } + } + } +} 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 0e07b07d..266edc6a 100644 --- a/ServerWrapper/src/main/java/pro/gravit/launcher/server/ServerWrapper.java +++ b/ServerWrapper/src/main/java/pro/gravit/launcher/server/ServerWrapper.java @@ -17,25 +17,24 @@ import pro.gravit.launcher.request.update.ProfilesRequest; import pro.gravit.launcher.request.websockets.StdWebSocketService; import pro.gravit.launcher.server.authlib.InstallAuthlib; -import pro.gravit.launcher.server.launch.ClasspathLaunch; -import pro.gravit.launcher.server.launch.Launch; -import pro.gravit.launcher.server.launch.ModuleLaunch; -import pro.gravit.launcher.server.launch.SimpleLaunch; import pro.gravit.launcher.server.setup.ServerWrapperSetup; import pro.gravit.utils.PublicURLClassLoader; import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.LogHelper; import pro.gravit.utils.helper.SecurityHelper; +import pro.gravit.utils.launch.*; import java.lang.reflect.Type; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; +import java.util.stream.Collectors; public class ServerWrapper extends JsonConfigurable { public static final Path configFile = Paths.get(System.getProperty("serverwrapper.configFile", "ServerWrapperConfig.json")); public static final boolean disableSetup = Boolean.parseBoolean(System.getProperty("serverwrapper.disableSetup", "false")); public static ServerWrapper wrapper; + public static ClassLoaderControl classLoaderControl; public Config config; public PublicURLClassLoader ucp; public ClassLoader loader; @@ -173,19 +172,26 @@ public void run(String... args) throws Throwable { Launch launch; switch (config.classLoaderConfig) { case LAUNCHER: - launch = new ClasspathLaunch(); + launch = new LegacyLaunch(); break; case MODULE: launch = new ModuleLaunch(); break; default: - launch = new SimpleLaunch(); + if(ServerAgent.isAgentStarted()) { + launch = new BasicLaunch(ServerAgent.inst); + } else { + launch = new BasicLaunch(); + } break; } + LaunchOptions options = new LaunchOptions(); + 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"); LogHelper.debug("Invoke main method %s with %s", classname, launch.getClass().getName()); try { - launch.run(classname, config, real_args); + launch.launch(config.mainclass, config.mainmodule, Arrays.asList(real_args)); } catch (Throwable e) { LogHelper.error(e); System.exit(-1); @@ -233,13 +239,15 @@ public static final class Config { public ClientProfile.ClassLoaderConfig classLoaderConfig; public String librariesDir; public String mainclass; + public String mainmodule; + public String nativesDir = "natives"; public List args; public String authId; public AuthRequestEvent.OAuthRequestEvent oauth; public long oauthExpireTime; public Map extendedTokens; public LauncherConfig.LauncherEnvironment env; - public ModuleConf moduleConf = new ModuleConf(); + public LaunchOptions.ModuleConf moduleConf = new LaunchOptions.ModuleConf(); public byte[] encodedServerRsaPublicKey; @@ -247,13 +255,4 @@ public static final class Config { public Map properties; } - - public static final class ModuleConf { - public List modules = new ArrayList<>(); - public List modulePath = new ArrayList<>(); - public String mainModule = ""; - public Map exports = new HashMap<>(); - public Map opens = new HashMap<>(); - public Map reads = new HashMap<>(); - } } diff --git a/ServerWrapper/src/main/java/pro/gravit/launcher/server/launch/ClasspathLaunch.java b/ServerWrapper/src/main/java/pro/gravit/launcher/server/launch/ClasspathLaunch.java deleted file mode 100644 index 554ca32f..00000000 --- a/ServerWrapper/src/main/java/pro/gravit/launcher/server/launch/ClasspathLaunch.java +++ /dev/null @@ -1,23 +0,0 @@ -package pro.gravit.launcher.server.launch; - -import pro.gravit.launcher.server.ServerWrapper; -import pro.gravit.utils.PublicURLClassLoader; -import pro.gravit.utils.helper.IOHelper; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import java.net.URL; -import java.nio.file.Paths; - -public class ClasspathLaunch implements Launch { - @Override - @SuppressWarnings("ConfusingArgumentToVarargsMethod") - public void run(String mainclass, ServerWrapper.Config config, String[] args) throws Throwable { - URL[] urls = config.classpath.stream().map(Paths::get).map(IOHelper::toURL).toArray(URL[]::new); - ClassLoader ucl = new PublicURLClassLoader(urls); - Class mainClass = Class.forName(mainclass, true, ucl); - MethodHandle mainMethod = MethodHandles.lookup().findStatic(mainClass, "main", MethodType.methodType(void.class, String[].class)); - mainMethod.invoke(args); - } -} diff --git a/ServerWrapper/src/main/java/pro/gravit/launcher/server/launch/Launch.java b/ServerWrapper/src/main/java/pro/gravit/launcher/server/launch/Launch.java deleted file mode 100644 index f4e915e8..00000000 --- a/ServerWrapper/src/main/java/pro/gravit/launcher/server/launch/Launch.java +++ /dev/null @@ -1,7 +0,0 @@ -package pro.gravit.launcher.server.launch; - -import pro.gravit.launcher.server.ServerWrapper; - -public interface Launch { - void run(String mainclass, ServerWrapper.Config config, String[] args) throws Throwable; -} diff --git a/ServerWrapper/src/main/java/pro/gravit/launcher/server/launch/ModuleLaunch.java b/ServerWrapper/src/main/java/pro/gravit/launcher/server/launch/ModuleLaunch.java deleted file mode 100644 index b0e8102e..00000000 --- a/ServerWrapper/src/main/java/pro/gravit/launcher/server/launch/ModuleLaunch.java +++ /dev/null @@ -1,10 +0,0 @@ -package pro.gravit.launcher.server.launch; - -import pro.gravit.launcher.server.ServerWrapper; - -public class ModuleLaunch implements Launch { - @Override - public void run(String mainclass, ServerWrapper.Config config, String[] args) { - throw new UnsupportedOperationException("Module system not supported"); - } -} diff --git a/ServerWrapper/src/main/java/pro/gravit/launcher/server/launch/SimpleLaunch.java b/ServerWrapper/src/main/java/pro/gravit/launcher/server/launch/SimpleLaunch.java deleted file mode 100644 index 8b2b6df7..00000000 --- a/ServerWrapper/src/main/java/pro/gravit/launcher/server/launch/SimpleLaunch.java +++ /dev/null @@ -1,17 +0,0 @@ -package pro.gravit.launcher.server.launch; - -import pro.gravit.launcher.server.ServerWrapper; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; - -public class SimpleLaunch implements Launch { - @Override - @SuppressWarnings("ConfusingArgumentToVarargsMethod") - public void run(String mainclass, ServerWrapper.Config config, String[] args) throws Throwable { - Class mainClass = Class.forName(mainclass); - MethodHandle mainMethod = MethodHandles.lookup().findStatic(mainClass, "main", MethodType.methodType(void.class, String[].class)); - mainMethod.invoke(args); - } -} diff --git a/ServerWrapper/src/main/java11/pro/gravit/launcher/server/launch/ModuleLaunch.java b/ServerWrapper/src/main/java11/pro/gravit/launcher/server/launch/ModuleLaunch.java deleted file mode 100644 index 53cbfa7a..00000000 --- a/ServerWrapper/src/main/java11/pro/gravit/launcher/server/launch/ModuleLaunch.java +++ /dev/null @@ -1,69 +0,0 @@ -package pro.gravit.launcher.server.launch; - -import pro.gravit.launcher.server.ServerWrapper; -import pro.gravit.utils.PublicURLClassLoader; -import pro.gravit.utils.helper.IOHelper; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import java.lang.module.Configuration; -import java.lang.module.ModuleFinder; -import java.net.URL; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; - -public class ModuleLaunch implements Launch { - @Override - @SuppressWarnings("ConfusingArgumentToVarargsMethod") - public void run(String mainclass, ServerWrapper.Config config, String[] args) throws Throwable { - URL[] urls = config.classpath.stream().map(Paths::get).map(IOHelper::toURL).toArray(URL[]::new); - ClassLoader ucl = new PublicURLClassLoader(urls); - // Create Module Layer - ModuleFinder finder = ModuleFinder.of(config.moduleConf.modulePath.stream().map(Paths::get).toArray(Path[]::new)); - ModuleLayer bootLayer = ModuleLayer.boot(); - Configuration configuration = bootLayer.configuration() - .resolveAndBind(ModuleFinder.of(), finder, config.moduleConf.modules); - ModuleLayer.Controller controller = ModuleLayer.defineModulesWithOneLoader(configuration, List.of(bootLayer), ucl); - ModuleLayer layer = controller.layer(); - // Configure exports / opens - for(var e : config.moduleConf.exports.entrySet()) { - String[] split = e.getKey().split("\\\\"); - Module source = layer.findModule(split[0]).orElseThrow(); - String pkg = split[1]; - Module target = layer.findModule(e.getValue()).orElseThrow(); - controller.addExports(source, pkg, target); - } - for(var e : config.moduleConf.opens.entrySet()) { - String[] split = e.getKey().split("\\\\"); - Module source = layer.findModule(split[0]).orElseThrow(); - String pkg = split[1]; - Module target = layer.findModule(e.getValue()).orElseThrow(); - controller.addOpens(source, pkg, target); - } - for(var e : config.moduleConf.reads.entrySet()) { - Module source = layer.findModule(e.getKey()).orElseThrow(); - Module target = layer.findModule(e.getValue()).orElseThrow(); - controller.addReads(source, target); - } - Module mainModule = layer.findModule(config.moduleConf.mainModule).orElseThrow(); - Module unnamed = ModuleLaunch.class.getClassLoader().getUnnamedModule(); - if(unnamed != null) { - controller.addOpens(mainModule, getPackageFromClass(config.mainclass), unnamed); - } - // Start main class - ClassLoader loader = mainModule.getClassLoader(); - Class mainClass = Class.forName(mainclass, true, loader); - MethodHandle mainMethod = MethodHandles.lookup().findStatic(mainClass, "main", MethodType.methodType(void.class, String[].class)); - mainMethod.invoke(args); - } - - private static String getPackageFromClass(String clazz) { - int index = clazz.lastIndexOf("."); - if(index >= 0) { - return clazz.substring(0, index); - } - return clazz; - } -} diff --git a/build.gradle b/build.gradle index 8ec0579d..fd75f711 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ id 'org.openjfx.javafxplugin' version '0.0.10' apply false } group = 'pro.gravit.launcher' -version = '5.5.0' +version = '5.5.1-SNAPSHOT' apply from: 'props.gradle'