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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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