diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java index 6bef2d069..5b793b618 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java @@ -21,8 +21,10 @@ import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.auth.AuthInfo; import org.jackhuang.hmcl.launch.DefaultLauncher; import org.jackhuang.hmcl.launch.ProcessListener; +import org.jackhuang.hmcl.util.CommandBuilder; import java.util.List; +import java.util.Map; /** * @author huangyuhui @@ -42,10 +44,10 @@ public final class HMCLGameLauncher extends DefaultLauncher { } @Override - protected void appendJvmArgs(List result) { - super.appendJvmArgs(result); - - result.add("-Dminecraft.launcher.version=" + Launcher.VERSION); - result.add("-Dminecraft.launcher.brand=" + Launcher.NAME); + protected Map getConfigurations() { + Map res = super.getConfigurations(); + res.put("${launcher_name}", Launcher.NAME); + res.put("${launcher_version}", Launcher.VERSION); + return res; } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java index c6efa3008..629412a05 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java @@ -434,7 +434,7 @@ public final class LauncherHelper { logWindow.show(); logWindow.onDone.register(() -> { - logWindow.logLine("Commands: " + StringUtils.makeCommand(process.getCommands()), Log4jLevel.INFO); + logWindow.logLine("Command: " + new CommandBuilder().addAll(process.getCommands()).toString(), Log4jLevel.INFO); for (Map.Entry entry : logs) logWindow.logLine(entry.getKey(), entry.getValue()); }); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MaintainTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MaintainTask.java index c74d7b515..20d2fb7cb 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MaintainTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MaintainTask.java @@ -20,6 +20,7 @@ package org.jackhuang.hmcl.download; import org.jackhuang.hmcl.game.Library; import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.task.TaskResult; +import org.jackhuang.hmcl.util.CommandBuilder; import org.jackhuang.hmcl.util.StringUtils; import java.util.ArrayList; @@ -93,7 +94,7 @@ public class MaintainTask extends TaskResult { args.add("optifine.OptiFineForgeTweaker"); } - return version.setMinecraftArguments(StringUtils.makeCommand(args)); + return version.setMinecraftArguments(new CommandBuilder().addAll(args).toString()); } @Override diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java index 2b241ba79..8d764f534 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java @@ -25,7 +25,6 @@ import java.io.*; import java.nio.file.Files; import java.util.*; import java.util.function.Supplier; -import java.util.stream.Collectors; import static org.jackhuang.hmcl.util.Lang.mapOf; import static org.jackhuang.hmcl.util.Pair.pair; @@ -48,8 +47,8 @@ public class DefaultLauncher extends Launcher { super(repository, versionId, authInfo, options, listener, daemon); } - private List generateCommandLine(File nativeFolder) throws IOException { - List res = new LinkedList<>(); + private CommandBuilder generateCommandLine(File nativeFolder, boolean enableLoggingInfo) throws IOException { + CommandBuilder res = new CommandBuilder(); // Executable if (StringUtils.isNotBlank(options.getWrapper())) @@ -58,7 +57,7 @@ public class DefaultLauncher extends Launcher { res.add(options.getJava().getBinary().toString()); if (StringUtils.isNotBlank(options.getJavaArgs())) - res.addAll(StringUtils.tokenize(options.getJavaArgs())); + res.addAllWithoutParsing(StringUtils.tokenize(options.getJavaArgs())); // JVM Args if (!options.isNoGeneratedJVMArgs()) { @@ -72,7 +71,7 @@ public class DefaultLauncher extends Launcher { } Map logging = version.getLogging(); - if (logging != null) { + if (logging != null && enableLoggingInfo) { LoggingInfo loggingInfo = logging.get(DownloadType.CLIENT); if (loggingInfo != null) { File loggingFile = repository.getLoggingObject(version.getId(), version.getAssetIndex().getId(), loggingInfo); @@ -187,11 +186,10 @@ public class DefaultLauncher extends Launcher { } if (StringUtils.isNotBlank(options.getMinecraftArgs())) - res.addAll(StringUtils.tokenize(options.getMinecraftArgs())); + res.addAllWithoutParsing(StringUtils.tokenize(options.getMinecraftArgs())); - return res.stream() - .filter(it -> !getForbiddens().containsKey(it) || !getForbiddens().get(it).get()) - .collect(Collectors.toList()); + res.removeIf(it -> getForbiddens().containsKey(it) && getForbiddens().get(it).get()); + return res; } public Map getFeatures() { @@ -224,7 +222,7 @@ public class DefaultLauncher extends Launcher { * -Dminecraft.launcher.brand=<Your launcher version> * -Dlog4j.configurationFile=<Your custom log4j configuration> */ - protected void appendJvmArgs(List result) { + protected void appendJvmArgs(CommandBuilder result) { } public void decompressNatives(File destination) throws NotDecompressingNativesException { @@ -262,18 +260,20 @@ public class DefaultLauncher extends Launcher { File nativeFolder = Files.createTempDirectory("minecraft").toFile(); // To guarantee that when failed to generate launch command line, we will not call pre-launch command - List rawCommandLine = generateCommandLine(nativeFolder); + List rawCommandLine = generateCommandLine(nativeFolder, true).asList(); decompressNatives(nativeFolder); + File runDirectory = repository.getRunDirectory(version.getId()); + if (StringUtils.isNotBlank(options.getPreLaunchCommand())) - Runtime.getRuntime().exec(options.getPreLaunchCommand()).waitFor(); + new ProcessBuilder(options.getPreLaunchCommand()) + .directory(runDirectory).start().waitFor(); Process process; try { - ProcessBuilder builder = new ProcessBuilder(rawCommandLine); - builder.directory(repository.getRunDirectory(version.getId())) - .environment().put("APPDATA", options.getGameDir().getAbsoluteFile().getParent()); + ProcessBuilder builder = new ProcessBuilder(rawCommandLine).directory(runDirectory); + builder.environment().put("APPDATA", options.getGameDir().getAbsoluteFile().getParent()); process = builder.start(); } catch (IOException e) { throw new ProcessCreationException(e); @@ -305,14 +305,14 @@ public class DefaultLauncher extends Launcher { writer.newLine(); writer.write("set APPDATA=" + options.getGameDir().getAbsoluteFile().getParent()); writer.newLine(); - writer.write("cd /D %APPDATA%"); + writer.write(new CommandBuilder().add("cd", "/D", repository.getRunDirectory(version.getId()).getAbsolutePath()).toString()); writer.newLine(); } if (StringUtils.isNotBlank(options.getPreLaunchCommand())) { writer.write(options.getPreLaunchCommand()); writer.newLine(); } - writer.write(StringUtils.makeCommand(generateCommandLine(nativeFolder))); + writer.write(generateCommandLine(nativeFolder, false).toString()); } if (!scriptFile.setExecutable(true)) throw new PermissionException(); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/CommandBuilder.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/CommandBuilder.java new file mode 100644 index 000000000..93cba34ce --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/CommandBuilder.java @@ -0,0 +1,123 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util; + +import java.util.*; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +public final class CommandBuilder { + private final OperatingSystem os; + private List raw = new LinkedList<>(); + + public CommandBuilder() { + this(OperatingSystem.CURRENT_OS); + } + + public CommandBuilder(OperatingSystem os) { + this.os = os; + } + + private String parse(String s) { + if (OperatingSystem.WINDOWS == os) { + return parseWindows(s); + } else { + return parseBash(s); + } + } + + public CommandBuilder add(String... args) { + for (String s : args) + raw.add(new Item(s, true)); + return this; + } + + public CommandBuilder addAll(Collection args) { + for (String s : args) + raw.add(new Item(s, true)); + return this; + } + + public CommandBuilder addWithoutParsing(String... args) { + for (String s : args) + raw.add(new Item(s, false)); + return this; + } + + public CommandBuilder addAllWithoutParsing(Collection args) { + for (String s : args) + raw.add(new Item(s, false)); + return this; + } + + public boolean removeIf(Predicate pred) { + return raw.removeIf(i -> pred.test(i.arg)); + } + + @Override + public String toString() { + return String.join(" ", raw.stream().map(i -> i.parse ? parse(i.arg) : i.arg).collect(Collectors.toList())); + } + + public List asList() { + return raw.stream().map(i -> i.arg).collect(Collectors.toList()); + } + + private static class Item { + String arg; + boolean parse; + + Item(String arg, boolean parse) { + this.arg = arg; + this.parse = parse; + } + } + + private static String parseWindows(String s) { + if (s.indexOf(' ') >= 0 || s.indexOf('\t') >= 0) + if (s.charAt(0) != '"') { + // The argument has not been quoted, add quotes. + return '"' + s.replace("\\", "\\\\").replace("\"", "\"\"") + '"'; + } else if (s.endsWith("\"")) { + // The argument has already been quoted. + return s; + } else { + // Unmatched quote for the argument. + throw new IllegalArgumentException(); + } + else { + return s; + } + } + + private static String parseBash(String s) { + if (s.indexOf(' ') >= 0 || s.indexOf('\t') >= 0) + if (s.charAt(0) != '"') { + // The argument has not been quoted, add quotes. + return '"' + s.replace("\"", "\\\"") + '"'; + } else if (s.endsWith("\"")) { + // The argument has already been quoted. + return s; + } else { + // Unmatched quote for the argument. + throw new IllegalArgumentException(); + } + else + return s; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java index bc1a17cda..134321500 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java @@ -33,36 +33,6 @@ public final class StringUtils { private StringUtils() { } - public static String makeCommand(List cmd) { - StringBuilder cmdbuf = new StringBuilder(120); - for (int i = 0; i < cmd.size(); i++) { - if (i > 0) - cmdbuf.append(' '); - String s = cmd.get(i); - if (s.indexOf(' ') >= 0 || s.indexOf('\t') >= 0) - if (s.charAt(0) != '"') { - cmdbuf.append('"'); - cmdbuf.append(s); - if (s.endsWith("\\")) - cmdbuf.append("\\"); - cmdbuf.append('"'); - } else if (s.endsWith("\"")) - /* - * The argument has already been quoted. - */ - cmdbuf.append(s); - else - /* - * Unmatched quote for the argument. - */ - throw new IllegalArgumentException(); - else - cmdbuf.append(s); - } - - return cmdbuf.toString(); - } - public static String getStackTrace(Throwable throwable) { ByteArrayOutputStream stream = new ByteArrayOutputStream(); throwable.printStackTrace(new PrintStream(stream));