Skip to content

[Bug] Java 版本告警分配完全倒置:AUTO 模式(HMCL 决策)零提示,手动模式(用户决策)才弹窗;且上界检测近乎只覆盖 Forge #6799

Description

@Chen-Mengze

问题描述 | Bug Description

环境

  • HMCL 版本:3.16.3
  • 系统:Windows 11 25H2 10.0.26200.9278,x86-64
  • 已安装 Java:仅 1 份 —— JDK 25.0.4.1 (BellSoft Liberica)
  • 游戏版本:1.20.1 + Forge 整合包

一、核心问题:告警分配完全倒置

这是本报告最重要的一点。当前 HMCL 的 Java 告警分布是反的

AUTO / VERSION DETECTED / CUSTOM(手动)
谁做的决策 HMCL 用户
用户是否知情 否 —— 主动交出选择权 是 —— 明确指定了路径或版本
是否有告警 没有 ✅ 有
能否自动修正 ✅ 能(切换/下载 Java) ❌ 不能(不能覆盖用户显式指定)

为什么这是错的

选择 AUTO 的用户,恰恰是最不了解、也最不想操心 Java 版本的那批人。"自动"二字的语义就是"由你替我决定"。HMCL 作为决策方,却对这批用户零提示。

反之,手动指定 Java 是有意识的行为——用户知道自己装了什么、选了什么、为什么选。给他们弹窗,是在向唯一已经做出知情决策的人做风险告知。

手动选择本身即隐含前提:用户知晓自己选择的是什么版本。若因此出现问题,那是用户自主选择应承担的风险,而非启动器的责任。

真正需要被提示的,是那些把选择权交给 HMCL、因而无从知晓风险的用户。

这个倒置会自我强化

手动分支唯一有告警的路径,其修复动作恰恰是把用户踢回 AUTOLauncherHelper.java:544-547):

Controllers.confirm(i18n("launch.advice.java.auto"), ..., () -> {
    setting.setJavaAutoSelected();     // ← 弹完窗,切成 AUTO
    future.complete(suggestedJava);
}, breakAction);

完整链条:手动指定 → HMCL 告警 → 用户接受建议 → 设置被改为 AUTO → 从此再无告警。

系统主动把用户从"唯一有告警的模式"引导到"唯一没有告警的模式"。

关于 UI 上的版本显示

AUTO 模式下设置页会显示 HMCL 将使用的 Java 版本,但这不构成有效告知:

  • 被动信息 ≠ 主动告知。静态显示需要用户主动去读,且不携带"此版本可能有风险"的判断。
  • 决策方的义务是在决策发生的时刻主动推送风险,而非把信息摆在角落等待被发现。
  • 选了 AUTO 的用户不会去看那个显示——如果愿意看,他早就手动选了。

技术层面的补充

AUTO 是唯一具备自动修正能力的模式。 手动模式下即使告警,HMCL 也只能提示,不能擅自覆盖用户显式指定的路径;而 AUTO 模式下 HMCL 本就拥有完整处置权——可切换到另一个已安装的 Java,或触发下载。

因此告警放在 AUTO 才具备实际价值:既能告知,又能当场给出修正动作(复用 downloadJava)。放在手动模式只能告知、无法修正,是低效的。


二、实测复现(日志证据)

日志关键片段

Java 检测 —— 只有一份 Java:

[01:49:38] Finished Java lookup, found 1
- JDK 25.0.4.1 (x86-64, BellSoft): C:\Program Files\BellSoft\LibericaJDK-25-Full\bin\java.exe

游戏版本(mod 目录):

|-> mods
|  |-> alcocraftplus-1.20.1-forge-2.0.2.jar
|  |-> architectury-9.2.14-forge.jar
|  |-> alexscaves-2.0.1.jar

启动流程 —— checkGameState 同秒完成,无弹窗:

[01:49:48] Launching game version: Closing Song
[01:49:48] Executing task: ...LauncherHelper.checkGameState(LauncherHelper.java:393)
[01:49:48] Task finished: ...LauncherHelper.checkGameState(LauncherHelper.java:393)
[01:49:48] Executing task: ...GameLibrariesTask
[01:49:48] Executing task: ...GameAssetDownloadTask

全程唯一对话框是 TaskExecutorDialogPane(启动进度框),无任何告警弹窗记录

期望行为

启动前提示:当前 Java 25 高于该环境推荐版本(Java 17),部分 mod 可能不兼容,并给出一键切换/下载 Java 17 的入口。

实际行为

零提示,直接用 Java 25 启动。

精确推演(源码对照,main 分支)

约束 1.20.1 Forge + Java 25 结果
VANILLA appliesToVersionImpl 要求 version.javaVersion() == null;1.20.1 json 含 javaVersion(17) 不命中
GAME_JSON mandatory,atLeast("17") Java 25 通过
MODDED_JAVA_17 between("17","17.999")isMandatory=false 违规(suggested 级)

唯一违规项 MODDED_JAVA_17 属建议级,被 AUTO 分支丢弃(机制见第四节)。

这份日志的意义:1.20.1 Forge 是 HMCL 中上界覆盖最完善的组合(6 次 FORGE 引用、档位齐全)。连它都静默放行,零覆盖的 Fabric / Quilt / NeoForge 只会更糟——它们的 violatedSuggestedConstraints 本身是空集,连"被吞掉"这个动作都不需要发生。


三、检测能力单边:只能可靠检测「Java 太旧」

下界(atLeast)覆盖充分VANILLA / GAME_JSON / CLEANROOM 从 version.json 推出最低版本,跨加载器通用。

上界覆盖面极窄

能检测"Java 太新"的约束 覆盖范围
LAUNCH_WRAPPER ≤1.12.999 且 launchwrapper < 1.13
VANILLA_LINUX_JAVA_8 Linux x64 + ≤1.12.999
MODDED_JAVA_7/8/16/17/21 仅 FORGE(且全部 isMandatory=false
MODLAUNCHER_8 仅 FORGE,1.16.3–1.17.1 特定版本段

对各 GameComponentType 的引用计数:

类型 引用次数
FORGE 6
CLEANROOM 4
NEO_FORGE / FABRIC / QUILT / LEGACY_FABRIC / LITELOADER / OPTIFINE 0

FABRIC / NEO_FORGE / QUILT / LEGACY_FABRIC / LITELOADERGameComponentType.java:43-222 均为带 ModLoaderType 的正式枚举常量,无任何约束引用。且 FORGENEO_FORGE 设计上互斥(GameComponentType.java:96-105,检测到 NeoForge 即 return false),NeoForge 永远不可能命中 MODDED_JAVA_*

≥1.13 的任何非 Forge 环境(含纯原版)都不存在 Java 上界。

为什么不可忽略

1. HMCL 自身制造了"高版本 Java 是常态"的环境

// Metadata.java:47-49
MINIMUM_REQUIRED_JAVA_VERSION   = 17;
MINIMUM_SUPPORTED_JAVA_VERSION  = 17;
RECOMMENDED_JAVA_VERSION        = 21;
// GameJavaVersion.java:40
LATEST = JAVA_25;
// GameJavaVersion.java:43-44 — MC 26.1+ 直接要求 Java 25

HMCL 要运行就必须有 Java 17+,引导下载的是最新 LTS。用户机器上的 Java 只会被往上推。 "Java 太新"不是边缘场景,而是默认场景

2. 这是概率性问题,所以正确解法是提醒,不是拦截

1.16.5 + Fabric 用 Java 25 并不崩溃;是否崩溃取决于具体 mod 组合调用了哪些 API,HMCL 无法静态判定

  • ❌ 不应设 isMandatory=true —— 会误伤大量本可正常运行的组合;
  • 必须给出 warning —— 让用户知情并保留"仍要启动"的选择权。

这正是 isMandatory=false 的设计意图。问题在于它既没覆盖到 Fabric / Quilt / NeoForge,也不被 AUTO 分支消费。


四、机制细节:告警为何在 AUTO 下被丢弃

JavaManager.java:355-363 降级时只交出 JavaRuntime,丢掉 violationSuggested

if (!violationMandatory) {
    mandatory = chooseJava(mandatory, java);
    if (!violationSuggested)
        suggested = chooseJava(suggested, java);
}
}
return suggested != null ? suggested : mandatory;   // ← 降级兜底

LauncherHelper.java:445-449 AUTO / VERSION 分支拿到非空结果即放行:

if (java != null) {
    return Task.completed(java);      // ← 零校验、零提示
}

DETECTED / CUSTOM 分支(main 分支 LauncherHelper.java:517-726)有完整的 suggestions 生成与弹窗(630-723 行),整段只存在于 else 分支

影响矩阵(机器上只有 Java 25)

场景 检测到违规? AUTO 手动指定
1.12.2- + Forge(LaunchWrapper) mandatory 违规 ✅ 弹窗 ✅ 弹窗
1.20.1 + Forge(本日志实测) suggested 违规 静默 ⚠️ 弹窗
1.16.5 + Fabric 零违规 ❌ 静默 ❌ 无弹窗
1.20.4 + Fabric / Quilt 零违规 ❌ 静默 ❌ 无弹窗
1.20.2–1.20.4 + NeoForge 零违规 ❌ 静默 ❌ 无弹窗
1.16.5 原版 零违规 ❌ 静默 ❌ 无弹窗

仅修复"AUTO 不消费告警"不够:Fabric / Quilt / NeoForge 的 violatedSuggestedConstraints 是空集,手动指定也不会提示。


补充:GameSettings.java:966+ 的 DETECTED 模式在 pathHash 匹配失败后也会 fallback 到 findSuitableJava(),同样静默降级,需一并覆盖。

启动器崩溃报告 / 启动器日志文件 | Launcher Crash Report / Launcher Log File

这里采用了手动禁用适合版本java的方法进行测试

2026-09-01T01-49-37.log

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions