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:
parent
5b742bc1a1
commit
2ff147fc10
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
@ -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() {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user