Modrinth/packages/app-lib/library/MinecraftLaunch.java
Josiah Glosson f10e0f2bf1
Pass system properties into Minecraft (+ some launch code cleanup) (#3822)
* Create get_resource_file macro to get an embedded resource

If the tauri feature is enabled, the resource will be loaded from Tauri resources.
If the tauri feature is disabled, the resource will be extracted to a temp directory.

* Wrap process execution to inject system properties through stdin

* Pass the time values as ISO 8601 datetimes

* Remove entirely internal modrinth.process.uuid

* Redo Java version checking somewhat and fix a few bugs with it

* Fix game launch with early access versions of Java

* Format Java code

* Fix modrinth.profile.modified being the same as modrinth.profile.created

* Revert to manually extracting class files
2025-06-26 13:23:14 +00:00

119 lines
3.8 KiB
Java

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
public final class MinecraftLaunch {
public static void main(String[] args) throws IOException, ReflectiveOperationException {
final String mainClass = args[0];
final String[] gameArgs = Arrays.copyOfRange(args, 1, args.length);
System.setProperty("modrinth.process.args", String.join("\u001f", gameArgs));
parseInput();
relaunch(mainClass, gameArgs);
}
private static void parseInput() throws IOException {
final ByteArrayOutputStream line = new ByteArrayOutputStream();
while (true) {
final int b = System.in.read();
if (b < 0) {
throw new IllegalStateException("Stdin terminated while parsing");
}
if (b != '\n') {
line.write(b);
continue;
}
if (handleLine(line.toString("UTF-8"))) {
break;
}
line.reset();
}
}
private static boolean handleLine(String line) {
final String[] parts = line.split("\t", 2);
switch (parts[0]) {
case "property": {
final String[] keyValue = parts[1].split("\t", 2);
System.setProperty(keyValue[0], keyValue[1]);
return false;
}
case "launch":
return true;
}
System.err.println("Unknown input line " + line);
return false;
}
private static void relaunch(String mainClassName, String[] args) throws ReflectiveOperationException {
final int javaVersion = getJavaVersion();
final Class<?> mainClass = Class.forName(mainClassName);
if (javaVersion >= 25) {
Method mainMethod;
try {
mainMethod = findMainMethodJep512(mainClass);
} catch (ReflectiveOperationException e) {
System.err
.println("[MODRINTH] Unable to call JDK findMainMethod. Falling back to pre-Java 25 main method finding.");
// If the above fails due to JDK implementation details changing
try {
mainMethod = findSimpleMainMethod(mainClass);
} catch (ReflectiveOperationException innerE) {
e.addSuppressed(innerE);
throw e;
}
}
if (mainMethod == null) {
throw new IllegalArgumentException("Could not find main() method");
}
Object thisObject = null;
if (!Modifier.isStatic(mainMethod.getModifiers())) {
thisObject = mainClass.getDeclaredConstructor().newInstance();
}
final Object[] parameters = mainMethod.getParameterCount() > 0
? new Object[] { args }
: new Object[] {};
mainMethod.invoke(thisObject, parameters);
} else {
findSimpleMainMethod(mainClass).invoke(null, new Object[] { args });
}
}
private static int getJavaVersion() {
String javaVersion = System.getProperty("java.version");
final int dotIndex = javaVersion.indexOf('.');
if (dotIndex != -1) {
javaVersion = javaVersion.substring(0, dotIndex);
}
final int dashIndex = javaVersion.indexOf('-');
if (dashIndex != -1) {
javaVersion = javaVersion.substring(0, dashIndex);
}
return Integer.parseInt(javaVersion);
}
private static Method findMainMethodJep512(Class<?> mainClass) throws ReflectiveOperationException {
// BEWARE BELOW: This code may break if JDK internals to find the main method
// change.
final Class<?> methodFinderClass = Class.forName("jdk.internal.misc.MethodFinder");
final Method methodFinderMethod = methodFinderClass.getDeclaredMethod("findMainMethod", Class.class);
final Object result = methodFinderMethod.invoke(null, mainClass);
return (Method) result;
}
private static Method findSimpleMainMethod(Class<?> mainClass) throws NoSuchMethodException {
return mainClass.getMethod("main", String[].class);
}
}