Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<String, String> loadOptions() {
getMinecraftVersion();
Map<String, String> 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<String, String> 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);
}
Expand Down
15 changes: 15 additions & 0 deletions HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -630,6 +634,17 @@ public static List<String> 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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the detected non-UTF-8 charset

When options.txt is encoded in a supported non-UTF-8 charset that differs from the current JVM native charset, such as an instance copied from a GBK Windows machine to a UTF-8 Linux/macOS install or a UTF-16 file with a BOM, this branch discards the charset UniversalDetector found and forces loadOptions() to decode with OperatingSystem.NATIVE_CHARSET. That still produces mojibake before the resource pack lists are parsed and later rewritten, so the non-UTF-8 fix only works when the file happens to match the local native charset; return the detected charset for supported non-ASCII encodings instead of falling back here.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

无需担心,这种情况MC也读取不了,就不管了(

Comment thread
ToobLac marked this conversation as resolved.
}

public static class LevCalculator {
private int[][] lev;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

Expand Down