[FEATURE] Support modular start

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

View file

@ -49,6 +49,7 @@ public void run(String[] args) {
String compatClasses = System.getProperty("launcher.runtime.launch.compat", null);
String 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) {

View file

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

View file

@ -62,12 +62,20 @@ private static ClientParams readParams(SocketAddress address) throws IOException
}
}
public static void main(String[] args) throws Throwable {
public static void main(String[] args) {
JVMHelper.verifySystemProperties(ClientLauncherEntryPoint.class, true);
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) {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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