233 lines
12 KiB
Java
233 lines
12 KiB
Java
package io.gitlab.jfronny.inceptum.launcher.system.launch;
|
|
|
|
import io.gitlab.jfronny.commons.OSUtils;
|
|
import io.gitlab.jfronny.commons.ref.R;
|
|
import io.gitlab.jfronny.inceptum.common.*;
|
|
import io.gitlab.jfronny.inceptum.launcher.LauncherEnv;
|
|
import io.gitlab.jfronny.inceptum.launcher.api.FabricMetaApi;
|
|
import io.gitlab.jfronny.inceptum.launcher.api.McApi;
|
|
import io.gitlab.jfronny.inceptum.launcher.api.account.AccountManager;
|
|
import io.gitlab.jfronny.inceptum.launcher.api.account.AuthInfo;
|
|
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.ArtifactInfo;
|
|
import io.gitlab.jfronny.inceptum.launcher.model.mojang.*;
|
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance;
|
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.Mod;
|
|
import io.gitlab.jfronny.inceptum.launcher.system.setup.steps.DownloadLibrariesStep;
|
|
import io.gitlab.jfronny.inceptum.launcher.util.ProcessState;
|
|
import io.gitlab.jfronny.inceptum.launcher.util.VersionInfoLibraryResolver;
|
|
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.net.URISyntaxException;
|
|
import java.nio.file.*;
|
|
import java.util.*;
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
import java.util.concurrent.atomic.AtomicReference;
|
|
|
|
public class InstanceLauncher {
|
|
public static void launchClient(Instance instance) {
|
|
if (AccountManager.accountMissing() && InceptumConfig.enforceAccount) {
|
|
LauncherEnv.showError("You have not set up an account.\nDoing so is required to play Minecraft", "Not authenticated");
|
|
return;
|
|
}
|
|
AuthInfo authInfo = AccountManager.selectedAccount;
|
|
if (authInfo.equals(AccountManager.NULL_AUTH)) {
|
|
try {
|
|
String sysUser = System.getProperty("user.name");
|
|
if (InceptumConfig.offlineAccountLastName == null)
|
|
InceptumConfig.offlineAccountLastName = sysUser;
|
|
LauncherEnv.getInput("User name", "Please enter the username to use for this session", InceptumConfig.offlineAccountLastName, name -> {
|
|
InceptumConfig.offlineAccountLastName = name.equals(sysUser) ? null : name;
|
|
InceptumConfig.saveConfig();
|
|
AuthInfo infoNew = new AuthInfo(name, authInfo.uuid, authInfo.accessToken, authInfo.userType);
|
|
launchClient(instance, infoNew);
|
|
}, R::nop);
|
|
} catch (IOException e) {
|
|
LauncherEnv.showError("Failed to request input", e);
|
|
}
|
|
} else launchClient(instance, authInfo);
|
|
}
|
|
|
|
private static void launchClient(Instance instance, AuthInfo authInfo) {
|
|
try {
|
|
launch(instance, LaunchType.Client, false, authInfo);
|
|
} catch (LaunchException | IOException e) {
|
|
LauncherEnv.showError("Could not launch client", e);
|
|
}
|
|
}
|
|
|
|
public static void launch(Instance instance, LaunchType launchType, boolean restart, AuthInfo authInfo) throws LaunchException, IOException {
|
|
if (authInfo == null) throw new LaunchException("authInfo is null");
|
|
VersionsListInfo versionDataSimple = getVersion(instance.gameVersion);
|
|
VersionInfo versionInfo = McApi.getVersionInfo(versionDataSimple);
|
|
// Add fabric metadata if using fabric
|
|
if (instance.isFabric) {
|
|
versionInfo = FabricMetaApi.addFabric(versionInfo, instance.loaderVersion, launchType.fabricMetaType);
|
|
}
|
|
// Ensure libs/assets are present
|
|
DownloadLibrariesStep.execute(versionInfo, new AtomicBoolean(false), new ProcessState());
|
|
// Prepare arguments
|
|
List<String> args = new LinkedList<>();
|
|
// JVM path
|
|
{
|
|
final VersionInfo lambdaVersionInfo = versionInfo;
|
|
args.add(Objects.requireNonNullElseGet(instance.meta.java, () ->
|
|
OSUtils.getJvmBinary(MetaHolder.NATIVES_DIR
|
|
.resolve(lambdaVersionInfo.javaVersion.component)
|
|
.resolve(Integer.toString(lambdaVersionInfo.javaVersion.majorVersion)))
|
|
.toAbsolutePath().toString()));
|
|
}
|
|
// Java classpath
|
|
StringBuilder classPath = new StringBuilder();
|
|
for (ArtifactInfo artifact : VersionInfoLibraryResolver.getRelevant(versionInfo)) {
|
|
classPath.append(MetaHolder.LIBRARIES_DIR.resolve(artifact.path));
|
|
classPath.append(File.pathSeparatorChar);
|
|
}
|
|
Path gameJar = MetaHolder.LIBRARIES_DIR.resolve("net/minecraft/" + launchType.name).resolve(versionDataSimple.id + ".jar");
|
|
classPath.append(gameJar);
|
|
classPath.append(File.pathSeparatorChar);
|
|
classPath.append(DownloadLibrariesStep.launchWrapperArtifact.localPath);
|
|
// JVM arguments
|
|
if (instance.meta.arguments != null && instance.meta.arguments.jvm != null)
|
|
args.addAll(instance.meta.arguments.jvm);
|
|
if (launchType == LaunchType.Client && versionInfo.arguments != null)
|
|
args.addAll(parse(versionInfo.arguments.jvm, versionInfo, instance, classPath.toString(), authInfo));
|
|
if (instance.meta.minMem != null) args.add("-Xms" + instance.meta.minMem);
|
|
if (instance.meta.maxMem != null) args.add("-Xmx" + instance.meta.maxMem);
|
|
// Forceload natives
|
|
if (Files.exists(MetaHolder.FORCE_LOAD_PATH)) {
|
|
args.add("-Dinceptum.forceloadNatives=" + MetaHolder.FORCE_LOAD_PATH);
|
|
}
|
|
// Fabric imods
|
|
if (instance.isFabric) {
|
|
StringBuilder fabricAddMods = new StringBuilder("-Dfabric.addMods=");
|
|
for (Mod mod : instance.mods) {
|
|
if (mod.isEnabled && mod.needsInject) {
|
|
fabricAddMods.append(mod.jarPath);
|
|
fabricAddMods.append(File.pathSeparatorChar);
|
|
}
|
|
}
|
|
args.add(fabricAddMods.substring(0, fabricAddMods.length() - 1));
|
|
}
|
|
// Wrapper class (launched by vm, launches main class)
|
|
args.add("io.gitlab.jfronny.inceptum.launchwrapper.Main");
|
|
// Main class
|
|
args.add(resolveMainClass(instance, versionInfo, gameJar, launchType));
|
|
// Game arguments
|
|
if (launchType == LaunchType.Client) {
|
|
if (versionInfo.arguments != null)
|
|
args.addAll(parse(versionInfo.arguments.game, versionInfo, instance, classPath.toString(), authInfo));
|
|
else if (versionInfo.minecraftArguments != null) {
|
|
for (String s : versionInfo.minecraftArguments.split(" ")) {
|
|
args.add(expandArg(s, versionInfo, instance, classPath.toString(), authInfo));
|
|
}
|
|
} else throw new LaunchException("Could not launch: No valid source for client arguments found");
|
|
}
|
|
if (instance.meta().arguments != null) {
|
|
switch (launchType) {
|
|
case Client -> {
|
|
if (instance.meta().arguments.client != null)
|
|
args.addAll(instance.meta.arguments.client);
|
|
}
|
|
case Server -> {
|
|
if (instance.meta().arguments.server != null)
|
|
args.addAll(instance.meta.arguments.server);
|
|
}
|
|
}
|
|
}
|
|
// Write launch time
|
|
instance.meta.lastLaunched = System.currentTimeMillis() / 1000L;
|
|
instance.writeMeta();
|
|
// Log command used to start
|
|
Utils.LOGGER.info(String.join(" ", args));
|
|
// Create process
|
|
ProcessBuilder pb = new ProcessBuilder(args.toArray(new String[0]));
|
|
pb.directory(instance.path.toFile());
|
|
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
|
|
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
|
|
AtomicReference<Process> proc = new AtomicReference<>();
|
|
Runnable starterRunner = () -> {
|
|
try {
|
|
proc.set(pb.start());
|
|
instance.setRunningLock(proc.get().pid());
|
|
} catch (IOException e) {
|
|
Utils.LOGGER.error("Could not start " + launchType.name, e);
|
|
}
|
|
};
|
|
starterRunner.run();
|
|
if (restart) {
|
|
new Thread(() -> {
|
|
while (true) {
|
|
try {
|
|
proc.get().waitFor();
|
|
} catch (InterruptedException e) {
|
|
Utils.LOGGER.error("Could not wait for server to finish", e);
|
|
}
|
|
Utils.LOGGER.info("Restarting server");
|
|
starterRunner.run();
|
|
if (!proc.get().isAlive) {
|
|
Utils.LOGGER.error("Could not restart server");
|
|
return;
|
|
}
|
|
}
|
|
}).start();
|
|
}
|
|
}
|
|
|
|
private static String resolveMainClass(Instance instance, VersionInfo versionInfo, Path gameJar, LaunchType launchType) throws LaunchException {
|
|
if (launchType == LaunchType.Client || instance.isFabric) return versionInfo.mainClass;
|
|
// Identify main class using MANIFEST.MF
|
|
final String linePrefix = "Main-Class: ";
|
|
try (FileSystem fs = Utils.openZipFile(gameJar, false)) {
|
|
for (String line : Files.readAllLines(fs.getPath("META-INF/MANIFEST.MF"))) {
|
|
if (line.startsWith(linePrefix)) {
|
|
return line.substring(linePrefix.length());
|
|
}
|
|
}
|
|
} catch (IOException | URISyntaxException e) {
|
|
throw new LaunchException("IO Exception while trying to identify entrypoint", e);
|
|
}
|
|
throw new LaunchException("Could not identify entrypoint");
|
|
}
|
|
|
|
private static VersionsListInfo getVersion(String minecraftVersion) throws LaunchException {
|
|
for (VersionsListInfo version : McApi.getVersions().versions) {
|
|
if (version.id.equals(minecraftVersion))
|
|
return version;
|
|
}
|
|
throw new LaunchException("Could not find data for minecraft version: " + minecraftVersion);
|
|
}
|
|
|
|
private static List<String> parse(List<MinecraftArgument> arguments, VersionInfo info, Instance instance, String classPath, AuthInfo authInfo) {
|
|
List<String> res = new ArrayList<>();
|
|
for (MinecraftArgument argument : arguments) {
|
|
for (String s : argument.arg()) {
|
|
res.add(expandArg(s, info, instance, classPath, authInfo));
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
private static String expandArg(String arg, VersionInfo info, Instance instance, String classPath, AuthInfo authInfo) {
|
|
return arg
|
|
// game args
|
|
.replace("${auth_player_name}", authInfo.name)
|
|
.replace("${version_name}", "Inceptum")
|
|
.replace("${game_directory}", instance.path.toString())
|
|
.replace("${assets_root}", MetaHolder.ASSETS_DIR.toString())
|
|
.replace("${assets_index_name}", info.assets)
|
|
.replace("${auth_uuid}", authInfo.uuid)
|
|
.replace("${auth_access_token}", authInfo.accessToken)
|
|
.replace("${user_type}", authInfo.userType)
|
|
.replace("${version_type}", info.type)
|
|
.replace("${resolution_width}", "1920") //TODO has_custom_resolution
|
|
.replace("${resolution_height}", "1080") //TODO has_custom_resolution
|
|
// jvm args
|
|
.replace("${natives_directory}", MetaHolder.NATIVES_DIR.resolve(instance.gameVersion).toString())
|
|
.replace("${launcher_name}", "Inceptum")
|
|
.replace("${launcher_version}", BuildMetadata.VERSION)
|
|
.replace("${classpath}", classPath)
|
|
.replace("${user_properties}", "{}");
|
|
}
|
|
}
|