Skip to content

Commit

Permalink
Experimenting with an in memory launcher
Browse files Browse the repository at this point in the history
  • Loading branch information
3arthqu4ke committed Apr 13, 2024
1 parent 3972adb commit 9203c54
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,6 @@ public interface LauncherProperties extends HmcProperties {
Property<Boolean> ASSETS_BACKOFF = bool("hmc.assets.backoff");

Property<Boolean> SET_LIBRARY_DIR = bool("hmc.set.library.dir");
Property<Boolean> IN_MEMORY = bool("hmc.in.memory");

}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public Account login(Config config) throws AuthException {
return this.login(email, password);
}

if (offlineChecker.isOffline()) {
if (true) {
return new Account("Offline", OFFLINE_UUID, "", "", "", "");
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package me.earth.headlessmc.launcher.java;

import lombok.Data;
import lombok.var;

@Data
public class Java implements Comparable<Java> {
Expand All @@ -12,4 +13,22 @@ public int compareTo(Java o) {
return Integer.compare(this.getVersion(), o.getVersion());
}

public static Java current() {
return new Java("java", parseSystemProperty(System.getProperty("java.version")));
}

private static int parseSystemProperty(String versionIn) {
var version = versionIn;
if (version.startsWith("1.")) {
version = version.substring(2, 3);
} else {
int dot = version.indexOf(".");
if (dot != -1) {
version = version.substring(0, dot);
}
}

return Integer.parseInt(version);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ public class JavaService extends Service<Java> {

@Override
protected List<Java> update() {
if (cfg.getConfig().get(LauncherProperties.IN_MEMORY, true)) {
return new ArrayList<>(0);
}

val systemDefaultJavaHome = Optional.ofNullable(System.getenv("JAVA_HOME"));
val currentJavaHome = Optional.ofNullable(System.getProperty("java.home"));
val foundJavaHomes = Stream.of(systemDefaultJavaHome, currentJavaHome)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
package me.earth.headlessmc.launcher.launch;

import lombok.Builder;
import lombok.CustomLog;
import lombok.val;
import lombok.var;
import lombok.*;
import me.earth.headlessmc.config.HmcProperties;
import me.earth.headlessmc.launcher.Launcher;
import me.earth.headlessmc.launcher.LauncherProperties;
import me.earth.headlessmc.launcher.auth.AuthException;
import me.earth.headlessmc.launcher.java.Java;
import me.earth.headlessmc.launcher.os.OS;
import me.earth.headlessmc.launcher.version.Features;
import me.earth.headlessmc.launcher.version.Version;
Expand All @@ -18,6 +16,7 @@
import java.util.List;

@Builder
@Getter
@CustomLog
class Command {
private static final String RT_MAIN = "me.earth.headlessmc.runtime.Main";
Expand All @@ -33,12 +32,14 @@ class Command {

public List<String> build() throws LaunchException, AuthException {
val config = launcher.getConfig();
val java = launcher.getJavaService().findBestVersion(version.getJava());
if (java == null) {
var java = launcher.getJavaService().findBestVersion(version.getJava());
if (java == null && !launcher.getConfig().get(LauncherProperties.IN_MEMORY, true)) {
throw new LaunchException("Couldn't find Java version for "
+ version.getName()
+ ", requires Java "
+ version.getJava());
} else {
java = Java.current();
}

val result = new ArrayList<String>();
Expand Down Expand Up @@ -69,20 +70,24 @@ public List<String> build() throws LaunchException, AuthException {

val adapter = ArgumentAdapterHelper.create(launcher, version, natives);
result.addAll(adapter.build(os, Features.EMPTY, "jvm"));
getActualMainClass(result);
result.addAll(adapter.build(os, Features.EMPTY, "game"));
result.addAll(Arrays.asList(config.get(LauncherProperties.GAME_ARGS,
new String[0])));
return result;
}

public String getActualMainClass(List<String> result) {
var mainClass = version.getMainClass();
if (runtime) {
result.add("-D" + HmcProperties.MAIN.getName() + "="
+ version.getMainClass());
mainClass = RT_MAIN;
}

result.add(config.get(LauncherProperties.CUSTOM_MAIN_CLASS, mainClass));

result.addAll(adapter.build(os, Features.EMPTY, "game"));
result.addAll(Arrays.asList(config.get(LauncherProperties.GAME_ARGS,
new String[0])));
return result;
mainClass = launcher.getConfig().get(LauncherProperties.CUSTOM_MAIN_CLASS, mainClass);
result.add(mainClass);
return mainClass;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package me.earth.headlessmc.launcher.launch;

import lombok.CustomLog;
import lombok.RequiredArgsConstructor;
import me.earth.headlessmc.api.config.HasConfig;
import me.earth.headlessmc.launcher.Main;
import me.earth.headlessmc.launcher.auth.AuthException;
import me.earth.headlessmc.launcher.files.FileManager;
import me.earth.headlessmc.launcher.os.OS;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

@CustomLog
@RequiredArgsConstructor
public class InMemoryLauncher {
private final FileManager files;
private final HasConfig config;
private final OS os;
private final LaunchOptions options;
private final Command command;

public void launch() throws IOException, LaunchException, AuthException {
String mainClass = command.getActualMainClass(new ArrayList<>());
URL[] classpathUrls = new URL[command.getClasspath().size()];
for (int i = 0; i < command.getClasspath().size(); i++) {
log.info(command.getClasspath().get(i));
classpathUrls[i] = Paths.get(command.getClasspath().get(i)).toUri().toURL();
}

List<String> actualCommand = command.build();
List<String> gameArgs = new ArrayList<>();
boolean hasPassedMainClass = false;
for (String arg : actualCommand) {
if (arg.startsWith("-D")) {
String[] split = arg.split("-D|=");
if (split.length > 2) {
log.info("SystemProperty: " + split[1] + " : " + split[2]);
System.setProperty(split[1], split[2]);
}
}

if (hasPassedMainClass) {
gameArgs.add(arg);
}

if (arg.equals(mainClass)) {
hasPassedMainClass = true;
}
}

try (URLClassLoader urlClassLoader = new URLClassLoader(classpathUrls)) {
try {
Class<?> mainClassClass = Class.forName(mainClass, false, urlClassLoader);
Method main = mainClassClass.getDeclaredMethod("main", String[].class);
main.setAccessible(true);
main.invoke(null, (Object) gameArgs.toArray(new String[0]));
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (Exception e) {
throw new LaunchException("Failed to launch game", e);
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ public LaunchOptionsBuilder parseFlags(
Launcher ctx, boolean quit, String... args) {
boolean lwjgl = flag(ctx, "-lwjgl", INVERT_LWJGL_FLAG, args);
// if offline only allow launching with the lwjgl flag!
if (!lwjgl && launcher.getAccountManager().getOfflineChecker().isOffline()) {
log.warning("You are offline, game will start in headless mode!");
lwjgl = true;
}
//if (!lwjgl && launcher.getAccountManager().getOfflineChecker().isOffline()) {
// log.warning("You are offline, game will start in headless mode!");
// lwjgl = true;
//}

return this
.runtime(CommandUtil.hasFlag("-commands", args))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public Process run(LaunchOptions options, Instrumentation instrumentation)
val dlls = options.getFiles().createRelative("extracted");
val targets = processLibraries(version, dlls);
addGameJar(version, targets);
val command = Command.builder()
val commandBuilder = Command.builder()
.classpath(instrumentation.instrument(targets))
.os(os)
.jvmArgs(options.getAdditionalJvmArgs())
Expand All @@ -64,16 +64,19 @@ public Process run(LaunchOptions options, Instrumentation instrumentation)
.version(version)
.launcher(launcher)
.lwjgl(options.isLwjgl())
.build()
.build();

val command = commandBuilder.build();
downloadAssets(files, version);
log.debug(command.toString());
val dir = new File(launcher.getConfig().get(
LauncherProperties.GAME_DIR, launcher.getMcFiles().getPath()));
log.info("Game will run in " + dir);
//noinspection ResultOfMethodCallIgnored
dir.mkdirs();
if (options.getLauncher().getConfig().get(LauncherProperties.IN_MEMORY, true)) {
new InMemoryLauncher(files, config, os, options, commandBuilder).launch();
}

return this.run(new ProcessBuilder()
.command(command)
.directory(dir)
Expand Down

0 comments on commit 9203c54

Please sign in to comment.