diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java index 90643edea9..47ea469b3c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java @@ -17,7 +17,7 @@ */ package org.jackhuang.hmcl.game; -import kala.encdet.EncodingDetector; +import org.jackhuang.hmcl.util.io.IOUtils; import org.jackhuang.hmcl.util.io.Zipper; import org.jackhuang.hmcl.util.logging.Logger; import org.jackhuang.hmcl.util.platform.OperatingSystem; @@ -90,7 +90,7 @@ public final class LogExporter { for (Path file : stream) { if (Files.isRegularFile(file)) { if (logMatcher == null || logMatcher.matches(file)) { - try (BufferedReader reader = EncodingDetector.MODERN_WEB.newBufferedReader(file)) { + try (BufferedReader reader = IOUtils.newBufferedReaderMaybeNativeEncoding(file)) { zipper.putLines(reader.lines().map(Logger::filterForbiddenToken), file.getFileName().toString()); } catch (IOException e) { LOG.warning("Failed to read log file: " + file, e); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/GameCrashWindow.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/GameCrashWindow.java index 4d1a23cf11..5037480bfe 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/GameCrashWindow.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/GameCrashWindow.java @@ -33,7 +33,6 @@ import javafx.scene.layout.VBox; import javafx.scene.text.Text; import javafx.scene.text.TextFlow; import javafx.stage.Stage; -import kala.encdet.EncodingDetector; import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.download.LibraryAnalyzer; import org.jackhuang.hmcl.game.*; @@ -49,6 +48,7 @@ import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.Log4jLevel; import org.jackhuang.hmcl.util.Pair; import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.logging.Logger; import org.jackhuang.hmcl.util.platform.*; @@ -149,7 +149,7 @@ public class GameCrashWindow extends Stage { String log; try { - log = EncodingDetector.MODERN_WEB.readString(latestLog); + log = FileUtils.readTextMaybeNativeEncoding(latestLog); } catch (IOException e) { LOG.warning("Failed to read logs/latest.log", e); return pair(new HashSet(), new HashSet()); 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 c90680b57c..c6cf0ee08e 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 @@ -18,6 +18,7 @@ package org.jackhuang.hmcl.util.io; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import kala.encdet.EncodingDetector; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.function.ExceptionalConsumer; import org.jackhuang.hmcl.util.platform.OperatingSystem; @@ -38,6 +39,8 @@ import java.util.*; import java.util.function.Predicate; import java.util.stream.Stream; +import static java.nio.charset.StandardCharsets.US_ASCII; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.jackhuang.hmcl.util.logging.Logger.LOG; /** @@ -235,6 +238,22 @@ public final class FileUtils { } } + public static String readTextMaybeNativeEncoding(Path file) throws IOException { + byte[] bytes = Files.readAllBytes(file); + + if (OperatingSystem.NATIVE_CHARSET == UTF_8) + return new String(bytes, UTF_8); + + EncodingDetector detector = EncodingDetector.MODERN_WEB; + EncodingDetector.@Nullable Encoding bestEncoding = detector.detect(bytes).bestEncoding(); + @Nullable Charset detectedCharset = bestEncoding != null ? bestEncoding.approximateCharset() : null; + + if (detectedCharset != null && (detectedCharset == UTF_8 || detectedCharset == US_ASCII)) + return new String(bytes, UTF_8); + else + return new String(bytes, OperatingSystem.NATIVE_CHARSET); + } + public static void deleteDirectory(Path directory) throws IOException { if (!Files.exists(directory)) return; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/IOUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/IOUtils.java index aa0ac20ba9..efc3a25d13 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/IOUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/IOUtils.java @@ -17,11 +17,20 @@ */ package org.jackhuang.hmcl.util.io; +import kala.encdet.EncodingDetector; +import org.jetbrains.annotations.Nullable; + import java.io.*; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.zip.GZIPInputStream; import static java.nio.charset.StandardCharsets.*; +import static org.jackhuang.hmcl.util.platform.OperatingSystem.NATIVE_CHARSET; /** * This utility class consists of some util methods operating on InputStream/OutputStream. @@ -35,6 +44,44 @@ public final class IOUtils { public static final int DEFAULT_BUFFER_SIZE = 32 * 1024; + public static BufferedReader newBufferedReaderMaybeNativeEncoding(Path file) throws IOException { + if (NATIVE_CHARSET == UTF_8) + return Files.newBufferedReader(file); + + FileChannel channel = FileChannel.open(file); + try { + long oldPosition = channel.position(); + long size = channel.size(); + + EncodingDetector detector = EncodingDetector.MODERN_WEB; + + int bufferSize = (int) Math.max(Math.min(size - oldPosition, detector.maxBytes()), 8192L); + ByteBuffer buffer = ByteBuffer.allocate(bufferSize); + + //noinspection StatementWithEmptyBody + while (buffer.hasRemaining() && channel.read(buffer) > 0) { + // do nothing + } + + buffer.flip(); + Charset charset; + if (buffer.remaining() == 0) { + charset = UTF_8; + } else { + EncodingDetector.@Nullable Encoding encoding = detector.detect(buffer).bestEncoding(); + Charset detectedCharset = encoding != null ? encoding.approximateCharset() : null; + charset = detectedCharset != null && (detectedCharset == UTF_8 || detectedCharset == US_ASCII) + ? UTF_8 + : NATIVE_CHARSET; + } + channel.position(oldPosition); + return new BufferedReader(new InputStreamReader(Channels.newInputStream(channel), charset)); + } catch (Throwable e) { + closeQuietly(channel, e); + throw e; + } + } + public static byte[] readFully(InputStream stream) throws IOException { try (stream) { return stream.readAllBytes();