diff --git a/launcher/src/main/java/com/skcraft/launcher/Configuration.java b/launcher/src/main/java/com/skcraft/launcher/Configuration.java index 5d47891..934a69b 100644 --- a/launcher/src/main/java/com/skcraft/launcher/Configuration.java +++ b/launcher/src/main/java/com/skcraft/launcher/Configuration.java @@ -62,6 +62,8 @@ public class Configuration { * Backwards compatibility for old configs with jvmPaths */ public void setJvmPath(String jvmPath) { - this.javaRuntime = JavaRuntimeFinder.getRuntimeFromPath(jvmPath); + if (jvmPath != null) { + this.javaRuntime = JavaRuntimeFinder.getRuntimeFromPath(jvmPath); + } } } diff --git a/launcher/src/main/java/com/skcraft/launcher/dialog/ConfigurationDialog.java b/launcher/src/main/java/com/skcraft/launcher/dialog/ConfigurationDialog.java index 70aecd6..9081920 100644 --- a/launcher/src/main/java/com/skcraft/launcher/dialog/ConfigurationDialog.java +++ b/launcher/src/main/java/com/skcraft/launcher/dialog/ConfigurationDialog.java @@ -8,6 +8,9 @@ package com.skcraft.launcher.dialog; import com.skcraft.launcher.Configuration; import com.skcraft.launcher.Launcher; +import com.skcraft.launcher.dialog.component.BetterComboBox; +import com.skcraft.launcher.launch.JavaRuntime; +import com.skcraft.launcher.launch.JavaRuntimeFinder; import com.skcraft.launcher.persistence.Persistence; import com.skcraft.launcher.swing.*; import com.skcraft.launcher.util.SharedLocale; @@ -29,7 +32,7 @@ public class ConfigurationDialog extends JDialog { private final JPanel tabContainer = new JPanel(new BorderLayout()); private final JTabbedPane tabbedPane = new JTabbedPane(); private final FormPanel javaSettingsPanel = new FormPanel(); - private final JTextField jvmPathText = new JTextField(); + private final JComboBox jvmRuntime = new BetterComboBox<>(); private final JTextField jvmArgsText = new JTextField(); private final JSpinner minMemorySpinner = new JSpinner(); private final JSpinner maxMemorySpinner = new JSpinner(); @@ -70,7 +73,10 @@ public class ConfigurationDialog extends JDialog { setResizable(false); setLocationRelativeTo(owner); - mapper.map(jvmPathText, "jvmPath"); + JavaRuntime[] javaRuntimes = JavaRuntimeFinder.getAvailableRuntimes().toArray(new JavaRuntime[0]); + jvmRuntime.setModel(new DefaultComboBoxModel<>(javaRuntimes)); + jvmRuntime.setSelectedItem(config.getJavaRuntime()); + mapper.map(jvmArgsText, "jvmArgs"); mapper.map(minMemorySpinner, "minMemory"); mapper.map(maxMemorySpinner, "maxMemory"); @@ -88,7 +94,7 @@ public class ConfigurationDialog extends JDialog { } private void initComponents() { - javaSettingsPanel.addRow(new JLabel(SharedLocale.tr("options.jvmPath")), jvmPathText); + javaSettingsPanel.addRow(new JLabel(SharedLocale.tr("options.jvmPath")), jvmRuntime); javaSettingsPanel.addRow(new JLabel(SharedLocale.tr("options.jvmArguments")), jvmArgsText); javaSettingsPanel.addRow(Box.createVerticalStrut(15)); javaSettingsPanel.addRow(new JLabel(SharedLocale.tr("options.64BitJavaWarning"))); @@ -157,6 +163,8 @@ public class ConfigurationDialog extends JDialog { */ public void save() { mapper.copyFromSwing(); + config.setJavaRuntime((JavaRuntime) jvmRuntime.getSelectedItem()); + Persistence.commitAndForget(config); dispose(); } diff --git a/launcher/src/main/java/com/skcraft/launcher/dialog/component/BetterComboBox.java b/launcher/src/main/java/com/skcraft/launcher/dialog/component/BetterComboBox.java new file mode 100644 index 0000000..b9bb22d --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/dialog/component/BetterComboBox.java @@ -0,0 +1,28 @@ +package com.skcraft.launcher.dialog.component; + +import javax.swing.*; +import javax.swing.plaf.basic.BasicComboBoxUI; +import javax.swing.plaf.basic.BasicComboPopup; +import javax.swing.plaf.basic.ComboPopup; +import java.awt.*; + +public class BetterComboBox extends JComboBox { + public BetterComboBox() { + setUI(new BetterComboBoxUI()); + } + + private static class BetterComboBoxUI extends BasicComboBoxUI { + @Override + protected ComboPopup createPopup() { + BasicComboPopup popup = new BasicComboPopup(comboBox) { + @Override + protected Rectangle computePopupBounds(int px, int py, int pw, int ph) { + return super.computePopupBounds(px, py, Math.max(comboBox.getPreferredSize().width, pw), ph); + } + }; + + popup.getAccessibleContext().setAccessibleParent(comboBox); + return popup; + } + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/launch/JavaRuntime.java b/launcher/src/main/java/com/skcraft/launcher/launch/JavaRuntime.java index fd60404..f651e84 100644 --- a/launcher/src/main/java/com/skcraft/launcher/launch/JavaRuntime.java +++ b/launcher/src/main/java/com/skcraft/launcher/launch/JavaRuntime.java @@ -12,6 +12,10 @@ public class JavaRuntime implements Comparable { private boolean isMinecraftBundled = false; public int getMajorVersion() { + if (version == null) { + return 0; // + } + String[] parts = version.split("\\."); if (parts.length < 2) { @@ -39,6 +43,12 @@ public class JavaRuntime implements Comparable { return 1; } + if (version == null) { + return 1; + } else if (o.version == null) { + return -1; + } + String[] a = version.split("[\\._]"); String[] b = o.version.split("[\\._]"); int min = Math.min(a.length, b.length); diff --git a/launcher/src/main/java/com/skcraft/launcher/launch/JavaRuntimeFinder.java b/launcher/src/main/java/com/skcraft/launcher/launch/JavaRuntimeFinder.java index 3aadfee..a81171c 100644 --- a/launcher/src/main/java/com/skcraft/launcher/launch/JavaRuntimeFinder.java +++ b/launcher/src/main/java/com/skcraft/launcher/launch/JavaRuntimeFinder.java @@ -48,6 +48,22 @@ public final class JavaRuntimeFinder { Collections.sort(entries); } else if (env.getPlatform() == Platform.LINUX) { launcherDir = new File(System.getenv("HOME"), ".minecraft"); + + String javaHome = System.getenv("JAVA_HOME"); + if (javaHome != null) { + entries.add(getRuntimeFromPath(javaHome)); + } + + File[] runtimesList = new File("/usr/lib/jvm").listFiles(); + if (runtimesList != null) { + Arrays.stream(runtimesList).map(file -> { + try { + return file.getCanonicalFile(); + } catch (IOException exception) { + return file; + } + }).distinct().forEach(file -> entries.add(getRuntimeFromPath(file.getAbsolutePath()))); + } } else { return Collections.emptyList(); } @@ -57,7 +73,12 @@ public final class JavaRuntimeFinder { } File runtimes = new File(launcherDir, "runtime"); - for (File potential : Objects.requireNonNull(runtimes.listFiles())) { + File[] runtimeList = runtimes.listFiles(); + if (runtimeList == null) { + return entries; + } + + for (File potential : runtimeList) { if (potential.getName().startsWith("jre-x")) { boolean is64Bit = potential.getName().equals("jre-x64"); @@ -108,6 +129,12 @@ public final class JavaRuntimeFinder { public static JavaRuntime getRuntimeFromPath(String path) { File target = new File(path); + { + File jre = new File(target, "jre/release"); + if (jre.isFile()) { + target = jre.getParentFile(); + } + } return new JavaRuntime(target, readVersionFromRelease(target), guessIf64Bit(target)); } @@ -150,7 +177,8 @@ public final class JavaRuntimeFinder { Map releaseDetails = EnvironmentParser.parse(releaseFile); return releaseDetails.get("JAVA_VERSION"); - } catch (IOException ignored) { + } catch (IOException e) { + throw new RuntimeException("Failed to read release file", e); } } diff --git a/launcher/src/main/java/com/skcraft/launcher/util/EnvironmentParser.java b/launcher/src/main/java/com/skcraft/launcher/util/EnvironmentParser.java index 02302bf..227afea 100644 --- a/launcher/src/main/java/com/skcraft/launcher/util/EnvironmentParser.java +++ b/launcher/src/main/java/com/skcraft/launcher/util/EnvironmentParser.java @@ -1,27 +1,139 @@ package com.skcraft.launcher.util; -import com.google.common.base.Splitter; import com.google.common.io.CharSource; import com.google.common.io.Files; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import java.io.BufferedReader; +import java.io.EOFException; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.HashMap; import java.util.Map; /** * Parses dotenv-style files. */ +@RequiredArgsConstructor public class EnvironmentParser { + private final BufferedReader reader; + + private char read() throws IOException { + int c = reader.read(); + + if (c == -1) { + throw new EOFException("End of stream reached unexpectedly!"); + } + + return (char) c; + } + + public Map parse() throws IOException { + HashMap result = new HashMap<>(); + + while (reader.ready()) { + KeyValue entry = parseLine(); + + result.put(entry.getKey(), entry.getValue()); + } + + return result; + } + + public KeyValue parseLine() throws IOException { + String key = parseKey(); + String value = parseValue(); + + try { + reader.mark(1); + char newline = read(); + if (newline == '\r') { + reader.mark(1); + if (read() != '\n') { + throw new IOException("Expected CRLF but only got CR"); + } + reader.reset(); + } else if (newline != '\n') { + reader.reset(); + } + } catch (EOFException ignored) { + } + + return new KeyValue(key, value); + } + + private String parseKey() throws IOException { + StringBuilder buffer = new StringBuilder(); + + // Very lenient key parsing. + while (true) { + char c = read(); + + switch (c) { + case '=': + case '\r': + case '\n': + return buffer.toString(); + default: + buffer.append(c); + } + } + } + + private String parseValue() throws IOException { + StringBuffer buffer = new StringBuffer(); + + while (true) { + char c = read(); + + switch (c) { + case '\r': + case '\n': + return buffer.toString(); + case '"': + buffer.append(parseQuotedPhrase()); + break; + case '\\': + char next = read(); + buffer.append(next); + break; + default: + buffer.append(c); + } + } + } + + private String parseQuotedPhrase() throws IOException { + StringBuilder buffer = new StringBuilder(); + + while (true) { + char c = read(); + + switch (c) { + case '"': + return buffer.toString(); + case '\\': + char next = read(); + buffer.append(next); + break; + default: + buffer.append(c); + } + } + } + public static Map parse(File target) throws IOException { CharSource charSource = Files.asCharSource(target, StandardCharsets.UTF_8); - Map 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)); + EnvironmentParser parser = new EnvironmentParser(charSource.openBufferedStream()); + return parser.parse(); + } - return values; + @Data + private static class KeyValue { + private final String key; + private final String value; } } diff --git a/launcher/src/main/resources/com/skcraft/launcher/lang/Launcher.properties b/launcher/src/main/resources/com/skcraft/launcher/lang/Launcher.properties index f4d52a7..9a09ec1 100644 --- a/launcher/src/main/resources/com/skcraft/launcher/lang/Launcher.properties +++ b/launcher/src/main/resources/com/skcraft/launcher/lang/Launcher.properties @@ -25,7 +25,7 @@ button.ok=OK options.title = Options options.useProxyCheck = Use following proxy in Minecraft -options.jvmPath=JVM path\: +options.jvmPath=Java Runtime\: options.jvmArguments=JVM arguments\: options.64BitJavaWarning=Make sure to have 64-bit Java installed if you are planning to set the memory limits higher. options.minMemory=Minimum memory (MB)\: