1
0
mirror of https://github.com/SKCraft/Launcher.git synced 2024-11-27 12:46:22 +01:00

Start implementing per-instance settings & better Java runtime selection

This commit is contained in:
Henry Le Grys 2021-06-15 04:44:52 +01:00
parent 5b742bc1a1
commit 2ff147fc10
10 changed files with 293 additions and 76 deletions

View File

@ -7,6 +7,8 @@
package com.skcraft.launcher;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.skcraft.launcher.launch.JavaRuntime;
import com.skcraft.launcher.launch.JavaRuntimeFinder;
import lombok.Data;
/**
@ -22,7 +24,7 @@ import lombok.Data;
public class Configuration {
private boolean offlineEnabled = false;
private String jvmPath;
private JavaRuntime javaRuntime;
private String jvmArgs;
private int minMemory = 1024;
private int maxMemory = 0; // Updated in Launcher
@ -55,4 +57,11 @@ public class Configuration {
public void setWidowHeight(int height) {
this.windowHeight = height;
}
/**
* Backwards compatibility for old configs with jvmPaths
*/
public void setJvmPath(String jvmPath) {
this.javaRuntime = JavaRuntimeFinder.getRuntimeFromPath(jvmPath);
}
}

View File

@ -33,6 +33,7 @@ public class Instance implements Comparable<Instance> {
private Date lastAccessed;
@JsonProperty("launch")
private LaunchModifier launchModifier;
private InstanceSettings settings = new InstanceSettings();
@JsonIgnore private File dir;
@JsonIgnore private URL manifestURL;

View File

@ -0,0 +1,30 @@
package com.skcraft.launcher;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.skcraft.launcher.launch.JavaRuntime;
import com.skcraft.launcher.launch.MemorySettings;
import lombok.Data;
import java.util.Optional;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class InstanceSettings {
private JavaRuntime runtime;
private MemorySettings memorySettings;
private String customJvmArgs;
/**
* @return Empty optional if there is no custom runtime set, present optional if there is.
*/
public Optional<JavaRuntime> getRuntime() {
return Optional.ofNullable(runtime);
}
/**
* @return Empty optional if there are no custom memory settings, present optional if there are.
*/
public Optional<MemorySettings> getMemorySettings() {
return Optional.ofNullable(memorySettings);
}
}

View File

@ -0,0 +1,79 @@
package com.skcraft.launcher.launch;
import lombok.Data;
import java.io.File;
@Data
public class JavaRuntime implements Comparable<JavaRuntime> {
private final File dir;
private final String version;
private final boolean is64Bit;
private boolean isMinecraftBundled = false;
public int getMajorVersion() {
String[] parts = version.split("\\.");
if (parts.length < 2) {
throw new IllegalArgumentException("Invalid Java runtime version: " + version);
}
if (parts[0].equals("1")) {
return Integer.parseInt(parts[1]);
} else {
return Integer.parseInt(parts[0]);
}
}
@Override
public int compareTo(JavaRuntime o) {
if (isMinecraftBundled && !o.isMinecraftBundled) {
return -1;
} else if (!isMinecraftBundled && o.isMinecraftBundled) {
return 1;
}
if (is64Bit && !o.is64Bit) {
return -1;
} else if (!is64Bit && o.is64Bit) {
return 1;
}
String[] a = version.split("[\\._]");
String[] b = o.version.split("[\\._]");
int min = Math.min(a.length, b.length);
for (int i = 0; i < min; i++) {
int first, second;
try {
first = Integer.parseInt(a[i]);
} catch (NumberFormatException e) {
return -1;
}
try {
second = Integer.parseInt(b[i]);
} catch (NumberFormatException e) {
return 1;
}
if (first > second) {
return -1;
} else if (first < second) {
return 1;
}
}
if (a.length == b.length) {
return 0; // Same
}
return a.length > b.length ? -1 : 1;
}
@Override
public String toString() {
return String.format("Java %s (%s) (%s)", version, is64Bit ? "64-bit" : "32-bit", dir);
}
}

View File

@ -6,16 +6,16 @@
package com.skcraft.launcher.launch;
import com.skcraft.launcher.model.minecraft.JavaVersion;
import com.skcraft.launcher.util.Environment;
import com.skcraft.launcher.util.EnvironmentParser;
import com.skcraft.launcher.util.Platform;
import com.skcraft.launcher.util.WinRegistry;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.*;
/**
* Finds the best Java runtime to use.
@ -25,52 +25,110 @@ public final class JavaRuntimeFinder {
private JavaRuntimeFinder() {
}
public static List<JavaRuntime> getAvailableRuntimes() {
Environment env = Environment.getInstance();
List<JavaRuntime> entries = new ArrayList<>();
File launcherDir;
if (env.getPlatform() == Platform.WINDOWS) {
try {
String launcherPath = WinRegistry.readString(WinRegistry.HKEY_CURRENT_USER,
"SOFTWARE\\Mojang\\InstalledProducts\\Minecraft Launcher", "InstallLocation");
launcherDir = new File(launcherPath);
} catch (Throwable ignored) {
launcherDir = new File(System.getenv("APPDATA"), ".minecraft");
}
try {
getEntriesFromRegistry(entries, "SOFTWARE\\JavaSoft\\Java Runtime Environment");
getEntriesFromRegistry(entries, "SOFTWARE\\JavaSoft\\Java Development Kit");
} catch (Throwable ignored) {
}
Collections.sort(entries);
} else if (env.getPlatform() == Platform.LINUX) {
launcherDir = new File(System.getenv("HOME"), ".minecraft");
} else {
return Collections.emptyList();
}
if (!launcherDir.isDirectory()) {
return entries;
}
File runtimes = new File(launcherDir, "runtime");
for (File potential : Objects.requireNonNull(runtimes.listFiles())) {
if (potential.getName().startsWith("jre-x")) {
boolean is64Bit = potential.getName().equals("jre-x64");
entries.add(new JavaRuntime(potential.getAbsoluteFile(), readVersionFromRelease(potential), is64Bit));
} else {
String runtimeName = potential.getName();
String[] children = potential.list();
if (children == null || children.length == 0) continue;
String platformName = children[0];
String[] parts = platformName.split("-");
if (parts.length < 2) continue;
String arch = parts[1];
boolean is64Bit = arch.equals("x64");
File javaDir = new File(potential, String.format("%s/%s", platformName, runtimeName));
entries.add(new JavaRuntime(javaDir.getAbsoluteFile(), readVersionFromRelease(javaDir), is64Bit));
}
}
return entries;
}
/**
* Return the path to the best found JVM location.
*
* @return the JVM location, or null
*/
public static File findBestJavaPath() {
if (Environment.getInstance().getPlatform() != Platform.WINDOWS) {
return null;
}
List<JREEntry> entries = new ArrayList<JREEntry>();
try {
getEntriesFromRegistry(entries, "SOFTWARE\\JavaSoft\\Java Runtime Environment");
getEntriesFromRegistry(entries, "SOFTWARE\\JavaSoft\\Java Development Kit");
} catch (Throwable ignored) {
}
Collections.sort(entries);
List<JavaRuntime> entries = getAvailableRuntimes();
if (entries.size() > 0) {
return new File(entries.get(0).dir, "bin");
return new File(entries.get(0).getDir(), "bin");
}
return null;
}
public static Optional<JavaRuntime> findBestJavaRuntime(JavaVersion targetVersion) {
List<JavaRuntime> entries = getAvailableRuntimes();
return entries.stream().sorted()
.filter(runtime -> runtime.getMajorVersion() == targetVersion.getMajorVersion())
.findFirst();
}
public static JavaRuntime getRuntimeFromPath(String path) {
File target = new File(path);
return new JavaRuntime(target, readVersionFromRelease(target), guessIf64Bit(target));
}
private static void getEntriesFromRegistry(List<JREEntry> entries, String basePath)
private static void getEntriesFromRegistry(List<JavaRuntime> entries, String basePath)
throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
List<String> subKeys = WinRegistry.readStringSubKeys(WinRegistry.HKEY_LOCAL_MACHINE, basePath);
for (String subKey : subKeys) {
JREEntry entry = getEntryFromRegistry(basePath, subKey);
JavaRuntime entry = getEntryFromRegistry(basePath, subKey);
if (entry != null) {
entries.add(entry);
}
}
}
private static JREEntry getEntryFromRegistry(String basePath, String version) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
private static JavaRuntime getEntryFromRegistry(String basePath, String version) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
String regPath = basePath + "\\" + version;
String path = WinRegistry.readString(WinRegistry.HKEY_LOCAL_MACHINE, regPath, "JavaHome");
File dir = new File(path);
if (dir.exists() && new File(dir, "bin/java.exe").exists()) {
JREEntry entry = new JREEntry();
entry.dir = dir;
entry.version = version;
entry.is64Bit = guessIf64Bit(dir);
return entry;
return new JavaRuntime(dir, version, guessIf64Bit(dir));
} else {
return null;
}
@ -84,52 +142,18 @@ public final class JavaRuntimeFinder {
return false;
}
}
private static class JREEntry implements Comparable<JREEntry> {
private File dir;
private String version;
private boolean is64Bit;
@Override
public int compareTo(JREEntry o) {
if (is64Bit && !o.is64Bit) {
return -1;
} else if (!is64Bit && o.is64Bit) {
return 1;
private static String readVersionFromRelease(File javaPath) {
File releaseFile = new File(javaPath, "release");
if (releaseFile.exists()) {
try {
Map<String, String> releaseDetails = EnvironmentParser.parse(releaseFile);
return releaseDetails.get("JAVA_VERSION");
} catch (IOException ignored) {
}
String[] a = version.split("[\\._]");
String[] b = o.version.split("[\\._]");
int min = Math.min(a.length, b.length);
for (int i = 0; i < min; i++) {
int first, second;
try {
first = Integer.parseInt(a[i]);
} catch (NumberFormatException e) {
return -1;
}
try {
second = Integer.parseInt(b[i]);
} catch (NumberFormatException e) {
return 1;
}
if (first > second) {
return -1;
} else if (first < second) {
return 1;
}
}
if (a.length == b.length) {
return 0; // Same
}
return a.length > b.length ? -1 : 1;
}
}
return null;
}
}

View File

@ -0,0 +1,19 @@
package com.skcraft.launcher.launch;
import lombok.Data;
/**
* Settings for launched process memory allocation.
*/
@Data
public class MemorySettings {
/**
* Minimum memory in megabytes.
*/
private int minMemory;
/**
* Maximum memory in megabytes.
*/
private int maxMemory;
}

View File

@ -32,6 +32,7 @@ import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Callable;
import static com.skcraft.launcher.LauncherUtils.checkInterrupted;
@ -212,8 +213,14 @@ public class Runner implements Callable<Process>, ProgressObservable {
* @throws IOException on I/O error
*/
private void addJvmArgs() throws IOException {
int minMemory = config.getMinMemory();
int maxMemory = config.getMaxMemory();
int minMemory = instance.getSettings().getMemorySettings()
.map(MemorySettings::getMinMemory)
.orElse(config.getMinMemory());
int maxMemory = instance.getSettings().getMemorySettings()
.map(MemorySettings::getMaxMemory)
.orElse(config.getMaxMemory());
int permGen = config.getPermGen();
if (minMemory <= 0) {
@ -240,16 +247,25 @@ public class Runner implements Callable<Process>, ProgressObservable {
builder.setMaxMemory(maxMemory);
builder.setPermGen(permGen);
String rawJvmPath = config.getJvmPath();
JavaRuntime selectedRuntime = instance.getSettings().getRuntime()
.orElseGet(() -> Optional.ofNullable(versionManifest.getJavaVersion())
.flatMap(JavaRuntimeFinder::findBestJavaRuntime)
.orElse(config.getJavaRuntime())
);
String rawJvmPath = selectedRuntime.getDir().getAbsolutePath();
if (!Strings.isNullOrEmpty(rawJvmPath)) {
builder.tryJvmPath(new File(rawJvmPath));
}
List<String> flags = builder.getFlags();
String rawJvmArgs = config.getJvmArgs();
if (!Strings.isNullOrEmpty(rawJvmArgs)) {
for (String arg : JavaProcessBuilder.splitArgs(rawJvmArgs)) {
flags.add(arg);
String[] rawJvmArgsList = new String[] {
config.getJvmArgs(),
instance.getSettings().getCustomJvmArgs()
};
for (String rawJvmArgs : rawJvmArgsList) {
if (!Strings.isNullOrEmpty(rawJvmArgs)) {
flags.addAll(JavaProcessBuilder.splitArgs(rawJvmArgs));
}
}

View File

@ -0,0 +1,11 @@
package com.skcraft.launcher.model.minecraft;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class JavaVersion {
private String component;
private int majorVersion;
}

View File

@ -27,6 +27,7 @@ public class VersionManifest {
private String mainClass;
private int minimumLauncherVersion;
private LinkedHashSet<Library> libraries;
private JavaVersion javaVersion;
private Map<String, Artifact> downloads = new HashMap<String, Artifact>();
public String getAssetId() {

View File

@ -0,0 +1,27 @@
package com.skcraft.launcher.util;
import com.google.common.base.Splitter;
import com.google.common.io.CharSource;
import com.google.common.io.Files;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
/**
* Parses dotenv-style files.
*/
public class EnvironmentParser {
public static Map<String, String> parse(File target) throws IOException {
CharSource charSource = Files.asCharSource(target, StandardCharsets.UTF_8);
Map<String, String> values = Splitter.onPattern("\r?\n").withKeyValueSeparator('=')
.split(charSource.read());
// Remove quotes
// TODO do this better. it works fine for the release file, though
values.replaceAll((key, value) -> value.substring(1, value.length() - 1));
return values;
}
}