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 3821c8ea0..abd2f452b 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java @@ -45,6 +45,7 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.*; import java.util.function.Supplier; @@ -68,7 +69,7 @@ public class DefaultLauncher extends Launcher { super(repository, version, authInfo, options, listener, daemon); } - private CommandBuilder generateCommandLine(File nativeFolder) throws IOException { + private Command generateCommandLine(File nativeFolder) throws IOException { CommandBuilder res = new CommandBuilder(); switch (options.getProcessPriority()) { @@ -208,10 +209,19 @@ public class DefaultLauncher extends Launcher { Path gameAssets = repository.getActualAssetDirectory(version.getId(), version.getAssetIndex().getId()); Map configuration = getConfigurations(); configuration.put("${classpath}", String.join(OperatingSystem.PATH_SEPARATOR, classpath)); - configuration.put("${natives_directory}", nativeFolder.getAbsolutePath()); configuration.put("${game_assets}", gameAssets.toAbsolutePath().toString()); configuration.put("${assets_root}", gameAssets.toAbsolutePath().toString()); + + String nativeFolderPath = nativeFolder.getAbsolutePath(); + Path tempNativeFolder = null; + if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX + && !StringUtils.isASCII(nativeFolderPath)) { + tempNativeFolder = Paths.get("/", "tmp", "hmcl-natives-" + UUID.randomUUID()); + nativeFolderPath = tempNativeFolder + File.pathSeparator + nativeFolderPath; + } + configuration.put("${natives_directory}", nativeFolderPath); + res.addAll(Arguments.parseArguments(version.getArguments().map(Arguments::getJvm).orElseGet(this::getDefaultJVMArguments), configuration)); if (authInfo.getArguments() != null && authInfo.getArguments().getJvm() != null && !authInfo.getArguments().getJvm().isEmpty()) res.addAll(Arguments.parseArguments(authInfo.getArguments().getJvm(), configuration)); @@ -258,7 +268,7 @@ public class DefaultLauncher extends Launcher { res.addAllWithoutParsing(Arguments.parseStringArguments(options.getGameArguments(), configuration)); res.removeIf(it -> getForbiddens().containsKey(it) && getForbiddens().get(it).get()); - return res; + return new Command(res, tempNativeFolder); } public Map getFeatures() { @@ -363,8 +373,10 @@ public class DefaultLauncher extends Launcher { nativeFolder = new File(options.getNativesDir()); } + final Command command = generateCommandLine(nativeFolder); + // To guarantee that when failed to generate launch command line, we will not call pre-launch command - List rawCommandLine = generateCommandLine(nativeFolder).asMutableList(); + List rawCommandLine = command.commandLine.asMutableList(); // Pass classpath using the environment variable, to reduce the command length String classpath = null; @@ -374,6 +386,12 @@ public class DefaultLauncher extends Launcher { classpath = rawCommandLine.remove(cpIndex); } + if (command.tempNativeFolder != null) { + Files.deleteIfExists(command.tempNativeFolder); + Files.createSymbolicLink(command.tempNativeFolder, nativeFolder.toPath().toAbsolutePath()); + command.tempNativeFolder.toFile().deleteOnExit(); + } + if (rawCommandLine.stream().anyMatch(StringUtils::isBlank)) { throw new IllegalStateException("Illegal command line " + rawCommandLine); } @@ -464,8 +482,8 @@ public class DefaultLauncher extends Launcher { if (!FileUtils.makeFile(scriptFile)) throw new IOException("Script file: " + scriptFile + " cannot be created."); - final CommandBuilder commandLine = generateCommandLine(nativeFolder); - final String command = usePowerShell ? null : commandLine.toString(); + final Command commandLine = generateCommandLine(nativeFolder); + final String command = usePowerShell ? null : commandLine.commandLine.toString(); if (!usePowerShell && isWindows) { if (command.length() > 8192) { // maximum length of the command in cmd @@ -509,7 +527,7 @@ public class DefaultLauncher extends Launcher { writer.newLine(); writer.write('&'); - for (String rawCommand : commandLine.asList()) { + for (String rawCommand : commandLine.commandLine.asList()) { writer.write(' '); writer.write(CommandBuilder.pwshString(rawCommand)); } @@ -533,6 +551,10 @@ public class DefaultLauncher extends Launcher { writer.write("export " + entry.getKey() + "=" + entry.getValue()); writer.newLine(); } + if (commandLine.tempNativeFolder != null) { + writer.write(new CommandBuilder().add("ln", "-s", nativeFolder.getAbsolutePath(), commandLine.tempNativeFolder.toString()).toString()); + writer.newLine(); + } writer.write(new CommandBuilder().add("cd", repository.getRunDirectory(version.getId()).getAbsolutePath()).toString()); } writer.newLine(); @@ -551,6 +573,10 @@ public class DefaultLauncher extends Launcher { writer.write("pause"); writer.newLine(); } + if (commandLine.tempNativeFolder != null) { + writer.write(new CommandBuilder().add("rm", commandLine.tempNativeFolder.toString()).toString()); + writer.newLine(); + } } } if (!scriptFile.setExecutable(true)) @@ -574,4 +600,14 @@ public class DefaultLauncher extends Launcher { managedProcess.addRelatedThread(stderr); managedProcess.addRelatedThread(Lang.thread(new ExitWaiter(managedProcess, Arrays.asList(stdout, stderr), processListener::onExit), "exit-waiter", isDaemon)); } + + private static final class Command { + final CommandBuilder commandLine; + final Path tempNativeFolder; + + Command(CommandBuilder commandBuilder, Path tempNativeFolder) { + this.commandLine = commandBuilder; + this.tempNativeFolder = tempNativeFolder; + } + } } 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 f2add13fb..80af57207 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java @@ -22,6 +22,8 @@ import org.jackhuang.hmcl.util.platform.OperatingSystem; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.io.UnsupportedEncodingException; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.StandardCharsets; import java.util.*; import java.util.regex.Pattern; @@ -258,6 +260,10 @@ public final class StringUtils { return Optional.of(str.substring(0, halfLength) + " ... " + str.substring(str.length() - halfLength)); } + public static boolean isASCII(CharSequence cs) { + return US_ASCII_ENCODER.canEncode(cs); + } + /** * Class for computing the longest common subsequence between strings. */ @@ -295,4 +301,5 @@ public final class StringUtils { public static final Pattern CHINESE_PATTERN = Pattern.compile("[\\u4e00-\\u9fa5]"); + public static final CharsetEncoder US_ASCII_ENCODER = StandardCharsets.US_ASCII.newEncoder(); }