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..66ad9fb32ad 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 @@ -36,7 +36,8 @@ import org.jetbrains.annotations.Unmodifiable; import java.io.IOException; -import java.nio.charset.StandardCharsets; +import java.io.UncheckedIOException; +import java.nio.charset.Charset; import java.nio.file.*; import java.util.*; import java.util.stream.Stream; @@ -224,33 +225,40 @@ public ResourcePackManager(GameRepository repository, String id) { this.optionsFile = repository.getRunDirectory(id).resolve("options.txt"); } + @Nullable + private Charset optionsFileCharset; + @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; } + optionsFileCharset = StringUtils.detectMaybeNativeTextEncoding(bytes); + new String(bytes, optionsFileCharset).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(), optionsFileCharset); } catch (IOException e) { LOG.warning("Failed to save instance options file", e); } 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 533aaaab189..45df1dee6f8 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java @@ -17,12 +17,16 @@ */ package org.jackhuang.hmcl.util; +import org.glavo.chardet.UniversalDetector; import org.jackhuang.hmcl.util.gson.JsonUtils; +import org.jackhuang.hmcl.util.platform.OperatingSystem; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Nullable; import java.io.PrintWriter; import java.io.StringWriter; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.*; import java.util.function.Predicate; import java.util.regex.Matcher; @@ -630,6 +634,17 @@ public static List deserializeStringList(String json) { return JsonUtils.fromNonNullJson(json, JsonUtils.listTypeOf(String.class)); } + public static Charset detectMaybeNativeTextEncoding(byte[] data) { + var detector = new UniversalDetector(); + detector.handleData(data); + detector.dataEnd(); + var detected = detector.getDetectedCharset(); + if (detected == null || !detected.isSupported()) return OperatingSystem.NATIVE_CHARSET; + var charset = detected.getCharset(); + if (charset == StandardCharsets.UTF_8 || charset == StandardCharsets.US_ASCII) return StandardCharsets.UTF_8; + return OperatingSystem.NATIVE_CHARSET; + } + public static class LevCalculator { private int[][] lev; 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 c583860bcc8..1fb1a0efc08 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 @@ -27,6 +27,7 @@ import org.jetbrains.annotations.Nullable; import java.io.*; +import java.nio.charset.Charset; import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.PosixFileAttributeView; @@ -507,13 +508,17 @@ public static Path tmpSaveFile(Path file) { } public static void saveSafely(Path file, String content) throws IOException { + saveSafely(file, content, 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 : UTF_8, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE)) { writer.write(content); }