-
Notifications
You must be signed in to change notification settings - Fork 20
/
Copy pathLayeredBootstrapLauncher.java
163 lines (142 loc) · 7.33 KB
/
LayeredBootstrapLauncher.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
package me.earth.headlessmc.modlauncher;
import dev.xdark.deencapsulation.Deencapsulation;
import java.io.IOException;
import java.lang.module.Configuration;
import java.lang.module.ModuleFinder;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.spi.FileSystemProvider;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* cpw's BootstrapLauncher turns a classpath environment into a modularized one.
* But this requires that the BootstrapLauncher and all libraries are loaded from the boot layer.
* When launching in memory we cannot launch from boot layer because we do not know which version to launch.
* This launcher introduces a layer between boot layer and MC-BOOTSTRAP layer,
* which creates and contains the modules from the ignoreList,
* which cpw's bootstraplauncher would usually leave in the boot layer.
* @see <a href="https://github.com/McModLauncher/bootstraplauncher">https://github.com/McModLauncher/bootstraplauncher</a>
*/
@SuppressWarnings({"unused", "Since15"}) // called via Reflection from me.earth.headlessmc.launcher.launch.InMemoryLauncher
public class LayeredBootstrapLauncher {
private static final Logger LOGGER = Logger.getLogger(LayeredBootstrapLauncher.class.getSimpleName());
private final List<Path> classpath;
private final URL[] classpathUrls;
private final String mainClass;
/**
* Constructs a new LayeredBootstrapLauncher.
*
* @param classpath the classpath to load.
* @param classpathUrls the classpath, just mapped to URLs.
* @param mainClass the name of the mainClass to call.
*/
public LayeredBootstrapLauncher(List<Path> classpath, URL[] classpathUrls, String mainClass) {
this.classpath = classpath;
this.classpathUrls = classpathUrls;
this.mainClass = mainClass;
}
/**
* Launches the game on the with the configured classPath and mainClass for the given arguments.
*
* @param args the arguments to launch the game with.
* @throws IOException if something goes wrong.
*/
public void launch(String[] args) throws IOException {
try (ModuleURLClassLoader classLoader = new ModuleURLClassLoader(classpathUrls, ClassLoader.getSystemClassLoader())) {
try {
String ignoreList = System.getProperty("ignoreList", "asm,securejarhandler");
String[] ignores = ignoreList.split(",");
Set<Path> validPaths = findValidPaths(ignores);
LOGGER.fine("Valid paths: " + validPaths);
ModuleFinder finder = ModuleFinder.of(validPaths.toArray(new Path[0]));
Set<String> moduleNames = new HashSet<>();
finder.findAll().forEach(moduleReference -> moduleNames.add(moduleReference.descriptor().name()));
LOGGER.fine("Modules in paths: " + moduleNames);
ModuleLayer bootLayer = ModuleLayer.boot(); // should be LayeredBootstrapLauncher.class.getModule.getLayer()?
Configuration configuration = bootLayer.configuration().resolve(finder, ModuleFinder.of(), moduleNames);
List<ModuleLayer> parents = new ArrayList<>(1);
parents.add(bootLayer);
ModuleLayer newLayer = ModuleLayer.defineModules(configuration, parents, ignored -> classLoader).layer();
LOGGER.fine("Binding Classloader");
ModuleURLClassLoader.bindToLayer(classLoader, newLayer);
classLoader.setConfiguration(configuration);
Thread.currentThread().setContextClassLoader(classLoader);
LOGGER.fine("Hacking in ForgeFileSystemProviders");
hackInForgeFileSystemProviders(classLoader);
LOGGER.fine("Looking for main class " + mainClass);
String mostPromisingModule = moduleNames.stream().filter(mainClass::startsWith).findFirst().orElse(null);
Class<?> mainClassClass;
if (mostPromisingModule == null) {
LOGGER.info("Looking for main class " + mainClass + " via findClass");
mainClassClass = classLoader.findClass(mainClass);
} else {
LOGGER.fine("Looking for main class " + mainClass + " in module " + mostPromisingModule);
mainClassClass = classLoader.findClass(mostPromisingModule, mainClass);
}
if (mainClassClass == null) {
throw new IllegalStateException("Failed to find main class " + mainClass);
}
Method main = mainClassClass.getMethod("main", String[].class);
main.invoke(null, (Object) args);
} catch (Exception e) {
throw new IllegalStateException("Failed to launch game", e);
}
}
}
private Set<Path> findValidPaths(String... ignores) {
Set<Path> validPaths = new HashSet<>(ignores.length * 2);
for (Path path : classpath) {
boolean isIgnored = false;
String fileName = path.getFileName().toString();
for (String ignore : ignores) {
if (fileName.startsWith(ignore)) {
isIgnored = true;
break;
}
}
// We want the opposite of the BootstrapLauncher,
// we want the Modules it usually ignores and build a layer from them.
if (!isIgnored) {
continue;
}
// this is kinda ugly and we should check if we can remove this
// i noticed that ModuleFinders cannot find modules for some jars
// so we verify first which jars actually contain modules
ModuleFinder finder = ModuleFinder.of(path);
try {
finder.findAll(); // will throw an exception if it cant find a module
validPaths.add(path);
} catch (Exception e) {
LOGGER.log(Level.INFO, "Failed to find module for path " + path, e);
}
}
return validPaths;
}
/**
* A terrible hack!
* Forge provides some own {@link java.nio.file.spi.FileSystemProvider}s.
* These providers usually get loaded from the BootLayer on the SystemClassloader.
* But when launching from memory we do not have these FileSystemProviders yet,
* so we need to reload all FileSystemProvider from our Classloader and add them.
*
* @param classLoader the classloader to load the FileSystemProviders from.
*/
private void hackInForgeFileSystemProviders(ClassLoader classLoader) {
try {
Deencapsulation.deencapsulate(FileSystemProvider.class);
Field installedProviders = FileSystemProvider.class.getDeclaredField("installedProviders");
installedProviders.setAccessible(true);
List<FileSystemProvider> providers = new ArrayList<>();
ServiceLoader.load(FileSystemProvider.class, classLoader).forEach(provider -> {
LOGGER.fine("Found FileSystemProvider: " + provider);
providers.add(provider);
});
installedProviders.set(null, providers);
} catch (Throwable throwable) {
LOGGER.log(Level.SEVERE, "Failed to hack in Forge FileSystemProviders", throwable);
}
}
}