From 52af83d11224efd89e40be08c85a4cf8582e8c8d Mon Sep 17 00:00:00 2001 From: Wulian233 <1055917385@qq.com> Date: Sat, 4 Jul 2026 13:49:57 +0800 Subject: [PATCH 1/3] =?UTF-8?q?Fix=20#4240:=20MC=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E4=B8=BA1.6.4=E6=97=B6=EF=BC=8C=E6=97=A5=E5=BF=97=E6=8A=8A?= =?UTF-8?q?=E6=AF=8F=E4=B8=80=E6=9D=A1=E9=83=BD=E7=BB=99=E7=AE=97=E6=88=90?= =?UTF-8?q?error?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jackhuang/hmcl/game/LauncherHelper.java | 22 +++++++++++++-- .../org/jackhuang/hmcl/util/Log4jLevel.java | 28 +++++++++++-------- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java index bade3ba46fb..262fbadaddc 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java @@ -145,6 +145,7 @@ private void launch0() { DefaultDependencyManager dependencyManager = repository.getDependency(); AtomicReference version = new AtomicReference<>(MaintainTask.maintain(repository, repository.getResolvedVersion(selectedVersion))); Optional gameVersion = repository.getGameVersion(version.get()); + boolean legacyForgeStderrLog = isLegacyForgeStderrLog(version.get(), gameVersion); boolean integrityCheck = repository.unmarkVersionLaunchedAbnormally(selectedVersion); CountDownLatch launchingLatch = new CountDownLatch(1); List javaAgents = new ArrayList<>(0); @@ -242,7 +243,7 @@ private void launch0() { launchOptions, launcherVisibility == LauncherVisibility.CLOSE ? null // Unnecessary to start listening to game process output when close launcher immediately after game launched. - : new HMCLProcessListener(repository, version.get(), authInfo, launchOptions, launchingLatch, gameVersion.isPresent()) + : new HMCLProcessListener(repository, version.get(), authInfo, launchOptions, launchingLatch, gameVersion.isPresent(), legacyForgeStderrLog) ); }).thenComposeAsync(launcher -> { // launcher is prev task's result if (scriptFile == null) { @@ -684,6 +685,19 @@ else if (violatedMandatoryConstraints.contains(JavaVersionConstraint.VANILLA)) return task.withStage("launch.state.java"); } + // https://github.com/HMCL-dev/HMCL/issues/4240 + private static boolean isLegacyForgeStderrLog(Version version, Optional gameVersion) { + LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version, gameVersion.orElse(null)); + Optional minecraftVersion = analyzer.getVersion(LibraryAnalyzer.LibraryType.MINECRAFT); + return analyzer.has(LibraryAnalyzer.LibraryType.FORGE) + && (minecraftVersion.filter(LauncherHelper::isMinecraft164).isPresent() + || isMinecraft164(version.getId())); + } + + private static boolean isMinecraft164(String version) { + return version.equals("1.6.4"); + } + private static CompletableFuture downloadJava(GameJavaVersion javaVersion, HMCLGameRepository repository) { CompletableFuture future = new CompletableFuture<>(); Controllers.dialog(new MessageDialogPane.Builder( @@ -802,16 +816,18 @@ private final class HMCLProcessListener implements ProcessListener { private final CircularArrayList logs; private final CountDownLatch launchingLatch; private final String forbiddenAccessToken; + private final boolean legacyForgeStderrLog; private Thread submitLogThread; private LinkedBlockingQueue logBuffer; - public HMCLProcessListener(HMCLGameRepository repository, Version version, AuthInfo authInfo, LaunchOptions launchOptions, CountDownLatch launchingLatch, boolean detectWindow) { + public HMCLProcessListener(HMCLGameRepository repository, Version version, AuthInfo authInfo, LaunchOptions launchOptions, CountDownLatch launchingLatch, boolean detectWindow, boolean legacyForgeStderrLog) { this.repository = repository; this.version = version; this.launchOptions = launchOptions; this.launchingLatch = launchingLatch; this.detectWindow = detectWindow; this.forbiddenAccessToken = authInfo != null ? authInfo.getAccessToken() : null; + this.legacyForgeStderrLog = legacyForgeStderrLog; this.logs = new CircularArrayList<>(Log.getLogLines() + 1); } @@ -929,7 +945,7 @@ public void onLog(String log, boolean isErrorStream) { if (forbiddenAccessToken != null) log = log.replace(forbiddenAccessToken, ""); - Log4jLevel level = isErrorStream && !log.startsWith("[authlib-injector]") ? Log4jLevel.ERROR : null; + Log4jLevel level = isErrorStream && !log.startsWith("[authlib-injector]") && !legacyForgeStderrLog ? Log4jLevel.ERROR : null; if (showLogs) { if (level == null) level = Lang.requireNonNullElse(Log4jLevel.guessLevel(log), Log4jLevel.INFO); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Log4jLevel.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Log4jLevel.java index f3abac83eb9..3a44f3526b8 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Log4jLevel.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Log4jLevel.java @@ -19,6 +19,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.logging.Level; /** * @@ -84,26 +85,24 @@ public static Log4jLevel guessLevel(String line) { if (m2.find()) { String level2Str = m2.group("category"); if (null != level2Str) - switch (level2Str) { - case "STDOUT": - level = INFO; - break; - case "STDERR": - level = ERROR; - break; - } + level = switch (level2Str) { + case "STDOUT" -> INFO; + case "STDERR" -> ERROR; + default -> level; + }; } if (line.contains("STDERR]") || line.contains("[STDERR/]")) { level = ERROR; } } else { - if (line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") - || line.contains("[FINER]") || line.contains("[FINEST]")) + if (containsLevelMarker(line, Level.INFO) || containsLevelMarker(line, Level.CONFIG) + || containsLevelMarker(line, Level.FINE) || containsLevelMarker(line, Level.FINER) + || containsLevelMarker(line, Level.FINEST)) level = INFO; - if (line.contains("[SEVERE]") || line.contains("[STDERR]")) + if (containsLevelMarker(line, Level.SEVERE) || line.contains("[STDERR]")) level = ERROR; - if (line.contains("[WARNING]")) + if (containsLevelMarker(line, Level.WARNING) || line.contains("[WARN]")) level = WARN; if (line.contains("[DEBUG]")) level = DEBUG; @@ -120,6 +119,11 @@ public static Log4jLevel guessLevel(String line) { return level; } + private static boolean containsLevelMarker(String line, Level level) { + return line.contains("[" + level.getName() + "]") + || line.contains("[" + level.getLocalizedName() + "]"); + } + public static boolean isError(Log4jLevel a) { return a != null && a.lessOrEqual(Log4jLevel.ERROR); } From 2105650119cd621661d0b91dd296174c3e70c834 Mon Sep 17 00:00:00 2001 From: Wulian233 <1055917385@qq.com> Date: Sun, 5 Jul 2026 19:57:19 +0800 Subject: [PATCH 2/3] update --- .../jackhuang/hmcl/game/LauncherHelper.java | 24 ++---- .../org/jackhuang/hmcl/util/Log4jLevel.java | 73 ++++++++----------- 2 files changed, 37 insertions(+), 60 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java index 262fbadaddc..c91c0ff7e94 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java @@ -145,7 +145,6 @@ private void launch0() { DefaultDependencyManager dependencyManager = repository.getDependency(); AtomicReference version = new AtomicReference<>(MaintainTask.maintain(repository, repository.getResolvedVersion(selectedVersion))); Optional gameVersion = repository.getGameVersion(version.get()); - boolean legacyForgeStderrLog = isLegacyForgeStderrLog(version.get(), gameVersion); boolean integrityCheck = repository.unmarkVersionLaunchedAbnormally(selectedVersion); CountDownLatch launchingLatch = new CountDownLatch(1); List javaAgents = new ArrayList<>(0); @@ -243,7 +242,7 @@ private void launch0() { launchOptions, launcherVisibility == LauncherVisibility.CLOSE ? null // Unnecessary to start listening to game process output when close launcher immediately after game launched. - : new HMCLProcessListener(repository, version.get(), authInfo, launchOptions, launchingLatch, gameVersion.isPresent(), legacyForgeStderrLog) + : new HMCLProcessListener(repository, version.get(), authInfo, launchOptions, launchingLatch, gameVersion.isPresent()) ); }).thenComposeAsync(launcher -> { // launcher is prev task's result if (scriptFile == null) { @@ -685,19 +684,6 @@ else if (violatedMandatoryConstraints.contains(JavaVersionConstraint.VANILLA)) return task.withStage("launch.state.java"); } - // https://github.com/HMCL-dev/HMCL/issues/4240 - private static boolean isLegacyForgeStderrLog(Version version, Optional gameVersion) { - LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version, gameVersion.orElse(null)); - Optional minecraftVersion = analyzer.getVersion(LibraryAnalyzer.LibraryType.MINECRAFT); - return analyzer.has(LibraryAnalyzer.LibraryType.FORGE) - && (minecraftVersion.filter(LauncherHelper::isMinecraft164).isPresent() - || isMinecraft164(version.getId())); - } - - private static boolean isMinecraft164(String version) { - return version.equals("1.6.4"); - } - private static CompletableFuture downloadJava(GameJavaVersion javaVersion, HMCLGameRepository repository) { CompletableFuture future = new CompletableFuture<>(); Controllers.dialog(new MessageDialogPane.Builder( @@ -816,18 +802,16 @@ private final class HMCLProcessListener implements ProcessListener { private final CircularArrayList logs; private final CountDownLatch launchingLatch; private final String forbiddenAccessToken; - private final boolean legacyForgeStderrLog; private Thread submitLogThread; private LinkedBlockingQueue logBuffer; - public HMCLProcessListener(HMCLGameRepository repository, Version version, AuthInfo authInfo, LaunchOptions launchOptions, CountDownLatch launchingLatch, boolean detectWindow, boolean legacyForgeStderrLog) { + public HMCLProcessListener(HMCLGameRepository repository, Version version, AuthInfo authInfo, LaunchOptions launchOptions, CountDownLatch launchingLatch, boolean detectWindow) { this.repository = repository; this.version = version; this.launchOptions = launchOptions; this.launchingLatch = launchingLatch; this.detectWindow = detectWindow; this.forbiddenAccessToken = authInfo != null ? authInfo.getAccessToken() : null; - this.legacyForgeStderrLog = legacyForgeStderrLog; this.logs = new CircularArrayList<>(Log.getLogLines() + 1); } @@ -945,7 +929,9 @@ public void onLog(String log, boolean isErrorStream) { if (forbiddenAccessToken != null) log = log.replace(forbiddenAccessToken, ""); - Log4jLevel level = isErrorStream && !log.startsWith("[authlib-injector]") && !legacyForgeStderrLog ? Log4jLevel.ERROR : null; + Log4jLevel level = log.startsWith("[authlib-injector]") + ? Log4jLevel.guessLevel(log) + : Log4jLevel.guessLevel(log, isErrorStream); if (showLogs) { if (level == null) level = Lang.requireNonNullElse(Log4jLevel.guessLevel(log), Log4jLevel.INFO); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Log4jLevel.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Log4jLevel.java index 3a44f3526b8..d1a0ea0c408 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Log4jLevel.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Log4jLevel.java @@ -51,58 +51,33 @@ public boolean lessOrEqual(Log4jLevel level) { public static final Pattern MINECRAFT_LOGGER = Pattern.compile("\\[(?[0-9:]+)] \\[[^/]+/(?[^]]+)]"); public static final Pattern MINECRAFT_LOGGER_CATEGORY = Pattern.compile("\\[(?[0-9:]+)] \\[[^/]+/(?[^]]+)] \\[(?[^]]+)]"); public static final String JAVA_SYMBOL = "([a-zA-Z_$][a-zA-Z\\d_$]*\\.)+[a-zA-Z_$][a-zA-Z\\d_$]*"; + private static final String WRAPPED_PRINT_STREAM = "[java.lang.Throwable$WrappedPrintStream:println"; public static Log4jLevel guessLevel(String line) { Log4jLevel level = null; Matcher m = MINECRAFT_LOGGER.matcher(line); if (m.find()) { - // New style logs from log4j - String levelStr = m.group("level"); - if (null != levelStr) - switch (levelStr) { - case "INFO": - level = INFO; - break; - case "WARN": - level = WARN; - break; - case "ERROR": - level = ERROR; - break; - case "FATAL": - level = FATAL; - break; - case "TRACE": - level = TRACE; - break; - case "DEBUG": - level = DEBUG; - break; - default: - break; - } + level = parseLevel(m.group("level")); Matcher m2 = MINECRAFT_LOGGER_CATEGORY.matcher(line); if (m2.find()) { String level2Str = m2.group("category"); if (null != level2Str) level = switch (level2Str) { case "STDOUT" -> INFO; - case "STDERR" -> ERROR; + case "STDERR" -> guessStderrLevel(line, level); default -> level; }; - } - - if (line.contains("STDERR]") || line.contains("[STDERR/]")) { - level = ERROR; + } else if (line.contains("STDERR]") || line.contains("[STDERR/]")) { + level = guessStderrLevel(line, level); } } else { if (containsLevelMarker(line, Level.INFO) || containsLevelMarker(line, Level.CONFIG) || containsLevelMarker(line, Level.FINE) || containsLevelMarker(line, Level.FINER) || containsLevelMarker(line, Level.FINEST)) level = INFO; - if (containsLevelMarker(line, Level.SEVERE) || line.contains("[STDERR]")) + if (containsLevelMarker(line, Level.SEVERE)) level = ERROR; - if (containsLevelMarker(line, Level.WARNING) || line.contains("[WARN]")) + if (containsLevelMarker(line, Level.WARNING)) level = WARN; if (line.contains("[DEBUG]")) level = DEBUG; @@ -119,6 +94,31 @@ public static Log4jLevel guessLevel(String line) { return level; } + public static Log4jLevel guessLevel(String line, boolean isErrorStream) { + Log4jLevel level = guessLevel(line); + return level != null || !isErrorStream ? level : ERROR; + } + + private static Log4jLevel parseLevel(String level) { + return switch (level) { + case "FATAL" -> FATAL; + case "ERROR" -> ERROR; + case "WARN" -> WARN; + case "INFO" -> INFO; + case "DEBUG" -> DEBUG; + case "TRACE" -> TRACE; + case "ALL" -> ALL; + default -> null; + }; + } + + private static Log4jLevel guessStderrLevel(String line, Log4jLevel fallback) { + if (line.contains(WRAPPED_PRINT_STREAM) && fallback != null) { + return fallback; + } + return ERROR; + } + private static boolean containsLevelMarker(String line, Level level) { return line.contains("[" + level.getName() + "]") || line.contains("[" + level.getLocalizedName() + "]"); @@ -128,15 +128,6 @@ public static boolean isError(Log4jLevel a) { return a != null && a.lessOrEqual(Log4jLevel.ERROR); } - public static Log4jLevel mergeLevel(Log4jLevel a, Log4jLevel b) { - if (a == null) - return b; - else if (b == null) - return a; - else - return a.level < b.level ? a : b; - } - public static boolean guessLogLineError(String log) { return isError(guessLevel(log)); } From c3984023ff6196b98688d6adc9262e0079603f9a Mon Sep 17 00:00:00 2001 From: Wulian233 <1055917385@qq.com> Date: Sun, 5 Jul 2026 20:28:35 +0800 Subject: [PATCH 3/3] update --- .../org/jackhuang/hmcl/util/Log4jLevel.java | 53 ++++++++++++++----- 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Log4jLevel.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Log4jLevel.java index d1a0ea0c408..29fb944a220 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Log4jLevel.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Log4jLevel.java @@ -17,9 +17,9 @@ */ package org.jackhuang.hmcl.util; +import java.util.logging.Level; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.logging.Level; /** * @@ -53,6 +53,16 @@ public boolean lessOrEqual(Log4jLevel level) { public static final String JAVA_SYMBOL = "([a-zA-Z_$][a-zA-Z\\d_$]*\\.)+[a-zA-Z_$][a-zA-Z\\d_$]*"; private static final String WRAPPED_PRINT_STREAM = "[java.lang.Throwable$WrappedPrintStream:println"; + private static final String[] INFO_MARKERS = markers( + Level.INFO, + Level.CONFIG, + Level.FINE, + Level.FINER, + Level.FINEST + ); + private static final String[] ERROR_MARKERS = markers(Level.SEVERE); + private static final String[] WARN_MARKERS = markers(Level.WARNING); + public static Log4jLevel guessLevel(String line) { Log4jLevel level = null; Matcher m = MINECRAFT_LOGGER.matcher(line); @@ -61,29 +71,34 @@ public static Log4jLevel guessLevel(String line) { Matcher m2 = MINECRAFT_LOGGER_CATEGORY.matcher(line); if (m2.find()) { String level2Str = m2.group("category"); - if (null != level2Str) + if (level2Str != null) { level = switch (level2Str) { case "STDOUT" -> INFO; case "STDERR" -> guessStderrLevel(line, level); default -> level; }; + } } else if (line.contains("STDERR]") || line.contains("[STDERR/]")) { level = guessStderrLevel(line, level); } } else { - if (containsLevelMarker(line, Level.INFO) || containsLevelMarker(line, Level.CONFIG) - || containsLevelMarker(line, Level.FINE) || containsLevelMarker(line, Level.FINER) - || containsLevelMarker(line, Level.FINEST)) + if (containsAny(line, INFO_MARKERS)) { level = INFO; - if (containsLevelMarker(line, Level.SEVERE)) + } + if (containsAny(line, ERROR_MARKERS)) { level = ERROR; - if (containsLevelMarker(line, Level.WARNING)) + } + if (containsAny(line, WARN_MARKERS)) { level = WARN; - if (line.contains("[DEBUG]")) + } + if (line.contains("[DEBUG]")) { level = DEBUG; + } } - if (line.contains("overwriting existing")) + + if (line.contains("overwriting existing")) { level = FATAL; + } /*if (line.contains("Exception in thread") || line.matches("\\s+at " + JAVA_SYMBOL) @@ -119,9 +134,23 @@ private static Log4jLevel guessStderrLevel(String line, Log4jLevel fallback) { return ERROR; } - private static boolean containsLevelMarker(String line, Level level) { - return line.contains("[" + level.getName() + "]") - || line.contains("[" + level.getLocalizedName() + "]"); + private static String[] markers(Level... levels) { + String[] markers = new String[levels.length * 2]; + int i = 0; + for (Level level : levels) { + markers[i++] = '[' + level.getName() + ']'; + markers[i++] = '[' + level.getLocalizedName() + ']'; + } + return markers; + } + + private static boolean containsAny(String line, String[] markers) { + for (String marker : markers) { + if (line.contains(marker)) { + return true; + } + } + return false; } public static boolean isError(Log4jLevel a) {