[FEATURE] Unified launch

This commit is contained in:
Gravita 2023-10-31 22:18:10 +07:00
parent 429c7a45c4
commit c1df548258
21 changed files with 725 additions and 284 deletions

View file

@ -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<Path> 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);

View file

@ -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<ClientProfile> {
// 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<ServerProfile> 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<Version> {

View file

@ -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);
}
}

View file

@ -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<String> 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.
*
* <p>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.
*
* <p>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);
}
}

View file

@ -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<Path> 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<URL> 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<String> 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);

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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<Path> files, String nativePath, LaunchOptions options) {
return new BasicClassLoaderControl();
}
@Override
public void launch(String mainClass, String mainModule, Collection<String> 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<split.length;i++) {
urls[i] = Paths.get(split[i]).toAbsolutePath().toUri().toURL();
}
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
return urls;
}
@Override
public Class<?> getClass(String name) throws ClassNotFoundException {
return Class.forName(name);
}
@Override
public ClassLoader getClassLoader() {
return BasicLaunch.class.getClassLoader();
}
@Override
public Object getJava9ModuleController() {
return null;
}
}
}

View file

@ -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);
}
}

View file

@ -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<Path> files, String nativePath, LaunchOptions options);
void launch(String mainClass, String mainModule, Collection<String> args) throws Throwable;
}

View file

@ -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<String> modules = new ArrayList<>();
public List<String> modulePath = new ArrayList<>();
public Map<String, String> exports = new HashMap<>();
public Map<String, String> opens = new HashMap<>();
public Map<String, String> reads = new HashMap<>();
}
}

View file

@ -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<Path> 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<String> 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<ClassLoaderControl.ClassTransformer> transformers = new ArrayList<>();
private final Map<String, Class<?>> classMap = new ConcurrentHashMap<>();
private String nativePath;
private final List<String> 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;
}
}
}
}

View file

@ -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<Path> files, String nativePath, LaunchOptions options) {
throw new UnsupportedOperationException("Please use Multi-Release JAR");
}
@Override
public void launch(String mainClass, String mainModule, Collection<String> args) throws Throwable {
throw new UnsupportedOperationException("Please use Multi-Release JAR");
}
}

View file

@ -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<Path> 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<String> 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<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);
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;
}
}
}
}

View file

@ -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<ServerWrapper.Config> {
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<String> args;
public String authId;
public AuthRequestEvent.OAuthRequestEvent oauth;
public long oauthExpireTime;
public Map<String, Request.ExtendedToken> 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<String, String> properties;
}
public static final class ModuleConf {
public List<String> modules = new ArrayList<>();
public List<String> modulePath = new ArrayList<>();
public String mainModule = "";
public Map<String, String> exports = new HashMap<>();
public Map<String, String> opens = new HashMap<>();
public Map<String, String> reads = new HashMap<>();
}
}

View file

@ -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);
}
}

View file

@ -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;
}

View file

@ -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");
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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'