diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackManager.java index d571f6422cf..5799909e92f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackManager.java @@ -18,6 +18,7 @@ package org.jackhuang.hmcl.addon.resourcepack; import com.google.gson.annotations.SerializedName; +import kala.encdet.EncodingDetector; import org.jackhuang.hmcl.game.GameRepository; import org.jackhuang.hmcl.addon.LocalAddonManager; import org.jackhuang.hmcl.addon.meta.PackMcMeta; @@ -36,6 +37,8 @@ import org.jetbrains.annotations.Unmodifiable; import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.*; import java.util.*; @@ -224,33 +227,46 @@ public ResourcePackManager(GameRepository repository, String id) { this.optionsFile = repository.getRunDirectory(id).resolve("options.txt"); } + private @Nullable Charset optionsFileEncoding; + @NotNull private Map loadOptions() { getMinecraftVersion(); Map options = new LinkedHashMap<>(); if (!Files.isRegularFile(optionsFile)) return options; - try (var stream = Files.lines(optionsFile, StandardCharsets.UTF_8)) { - stream.forEach(s -> { - if (StringUtils.isNotBlank(s)) { - var entry = s.split(":", 2); - if (entry.length == 2) { - options.put(entry[0], entry[1]); - } - } - }); - } catch (IOException e) { + byte[] bytes; + try { + bytes = Files.readAllBytes(optionsFile); + } catch (IOException | UncheckedIOException e) { LOG.warning("Failed to read instance options file", e); + return options; } + + EncodingDetector.Encoding bestEncoding = EncodingDetector.MODERN_WEB.detect(bytes).bestEncoding(); + if (bestEncoding == EncodingDetector.Encoding.ASCII) + bestEncoding = EncodingDetector.Encoding.UTF_8; + + optionsFileEncoding = bestEncoding != null && bestEncoding.approximateCharset() != null + ? bestEncoding.approximateCharset() : StandardCharsets.UTF_8; + //noinspection DataFlowIssue + new String(bytes, optionsFileEncoding).lines().forEach(s -> { + if (StringUtils.isNotBlank(s)) { + var entry = s.split(":", 2); + if (entry.length == 2) { + options.put(entry[0], entry[1]); + } + } + }); return options; } private void saveOptions(@NotNull Map options) { + StringBuilder sb = new StringBuilder(); + for (var entry : options.entrySet()) { + sb.append(entry.getKey()).append(":").append(entry.getValue()).append(System.lineSeparator()); + } try { - StringBuilder sb = new StringBuilder(); - for (var entry : options.entrySet()) { - sb.append(entry.getKey()).append(":").append(entry.getValue()).append(System.lineSeparator()); - } - FileUtils.saveSafely(optionsFile, sb.toString()); + FileUtils.saveSafely(optionsFile, sb.toString(), optionsFileEncoding); } catch (IOException e) { LOG.warning("Failed to save instance options file", e); } @@ -277,7 +293,8 @@ public PackMcMeta.PackVersion getRequiredVersion() { if (requiredVersion == null) { lock.lock(); try { - if (requiredVersion == null) requiredVersion = getPackVersion(getMinecraftVersion(), repository.getVersionJar(id)); + if (requiredVersion == null) + requiredVersion = getPackVersion(getMinecraftVersion(), repository.getVersionJar(id)); } finally { lock.unlock(); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/FileUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/FileUtils.java index b7df7c80946..c90680b57c3 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/FileUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/FileUtils.java @@ -25,6 +25,8 @@ import org.jetbrains.annotations.Nullable; import java.io.*; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.PosixFileAttributeView; @@ -486,13 +488,17 @@ public static Path tmpSaveFile(Path file) { } public static void saveSafely(Path file, String content) throws IOException { + saveSafely(file, content, StandardCharsets.UTF_8); + } + + public static void saveSafely(Path file, String content, @Nullable Charset charset) throws IOException { Path parent = file.toAbsolutePath().getParent(); if (parent != null) { Files.createDirectories(parent); } Path tmpFile = tmpSaveFile(file); - try (BufferedWriter writer = Files.newBufferedWriter(tmpFile, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE)) { + try (BufferedWriter writer = Files.newBufferedWriter(tmpFile, charset != null ? charset : StandardCharsets.UTF_8, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE)) { writer.write(content); }