diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java index ee8ea5593c..7745691953 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java @@ -37,10 +37,7 @@ import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.construct.*; import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType; -import org.jackhuang.hmcl.upgrade.RemoteVersion; -import org.jackhuang.hmcl.upgrade.UpdateChannel; -import org.jackhuang.hmcl.upgrade.UpdateChecker; -import org.jackhuang.hmcl.upgrade.UpdateHandler; +import org.jackhuang.hmcl.upgrade.*; import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.i18n.I18n; @@ -121,10 +118,18 @@ protected int getTrailingTextIndex() { updateButton.setManaged(outdated); updatePane.pseudoClassStateChanged(PseudoClass.getPseudoClass("active"), outdated); - if (UpdateChecker.isOutdated()) { + if (outdated) { lblUpdateSubProperty.set(i18n("update.newest_version", UpdateChecker.getLatestVersion().version())); } else if (UpdateChecker.isCheckingUpdate()) { lblUpdateSubProperty.set(i18n("update.checking")); + } else if (UpdateChecker.errorProperty().get() != null) { + Throwable t = UpdateChecker.errorProperty().get(); + if (t instanceof SelfVerificationException) { + lblUpdateSubProperty.set(i18n("update.unverified")); + } else { + String msg = t.getClass().getSimpleName() + ": " + t.getLocalizedMessage(); + lblUpdateSubProperty.set(i18n("update.check_failed", msg)); + } } else { lblUpdateSubProperty.set(i18n("update.latest")); } @@ -132,6 +137,7 @@ protected int getTrailingTextIndex() { UpdateChecker.latestVersionProperty().addListener(new WeakInvalidationListener(updateListener)); UpdateChecker.outdatedProperty().addListener(new WeakInvalidationListener(updateListener)); UpdateChecker.checkingUpdateProperty().addListener(new WeakInvalidationListener(updateListener)); + UpdateChecker.errorProperty().addListener(new WeakInvalidationListener(updateListener)); updateListener.invalidated(null); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/SelfVerificationException.java b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/SelfVerificationException.java new file mode 100644 index 0000000000..1986329dd1 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/SelfVerificationException.java @@ -0,0 +1,28 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2026 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.upgrade; + +import java.io.IOException; + +public class SelfVerificationException extends IOException { + + public SelfVerificationException() { + super("Self verification failed"); + } + +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateChecker.java b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateChecker.java index 3db5160696..81f9c17bce 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateChecker.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateChecker.java @@ -25,6 +25,7 @@ import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.util.io.NetworkUtils; import org.jackhuang.hmcl.util.versioning.VersionNumber; +import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.util.LinkedHashMap; @@ -54,6 +55,7 @@ private UpdateChecker() { }, latestVersion); private static final ReadOnlyBooleanWrapper checkingUpdate = new ReadOnlyBooleanWrapper(false); + private static final ReadOnlyObjectWrapper<@Nullable Throwable> error = new ReadOnlyObjectWrapper<>(); public static void init() { requestCheckUpdate(UpdateChannel.getChannel(), settings().acceptPreviewUpdateProperty().get()); @@ -83,9 +85,13 @@ public static ReadOnlyBooleanProperty checkingUpdateProperty() { return checkingUpdate.getReadOnlyProperty(); } + public static ReadOnlyObjectProperty<@Nullable Throwable> errorProperty() { + return error.getReadOnlyProperty(); + } + private static RemoteVersion checkUpdate(UpdateChannel channel, boolean preview) throws IOException { if (!IntegrityChecker.DISABLE_SELF_INTEGRITY_CHECK && !IntegrityChecker.isSelfVerified()) { - throw new IOException("Self verification failed"); + throw new SelfVerificationException(); } var query = new LinkedHashMap(); @@ -109,16 +115,23 @@ public static void requestCheckUpdate(UpdateChannel channel, boolean preview) { thread(() -> { RemoteVersion result = null; + Throwable t = null; try { result = checkUpdate(channel, preview); LOG.info("Latest version (" + channel + ", preview=" + preview + ") is " + result); } catch (Throwable e) { + t = e; LOG.warning("Failed to check for update", e); } + Throwable throwable = t; RemoteVersion finalResult = result; Platform.runLater(() -> { - if (finalResult != null) { + if (throwable != null) { + latestVersion.set(null); + error.set(throwable); + } else { + error.set(null); latestVersion.set(finalResult); } checkingUpdate.set(false); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateHandler.java b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateHandler.java index 4688264a03..bbf859b4b6 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateHandler.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateHandler.java @@ -174,7 +174,7 @@ private static void applyUpdate(Path target) throws IOException { Path self = getCurrentLocation(); if (!IntegrityChecker.DISABLE_SELF_INTEGRITY_CHECK && !IntegrityChecker.isSelfVerified()) { - throw new IOException("Self verification failed"); + throw new SelfVerificationException(); } ExecutableHeaderHelper.copyWithHeader(self, target); diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 6ee719bc05..6c286125be 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -1846,6 +1846,7 @@ update.channel.nightly.hint=You are currently using a Nightly channel build of t update.channel.nightly.title=Nightly Channel Notice update.channel.stable=Stable update.checking=Checking for updates +update.check_failed=Failed to check for update. %s update.disable_auto_show_update_dialog=Do not show update dialog automatically update.disable_auto_show_update_dialog.subtitle=Enable this option to prevent HMCL from automatically showing the update dialog. update.failed=Failed to update @@ -1860,6 +1861,7 @@ update.no_browser=Cannot open in system browser. But we copied the link to your update.tooltip=Update update.preview=Preview HMCL releases early update.preview.subtitle=Enable this option to receive new versions of HMCL early for testing before their official release. +update.unverified=Update unavailable due to this launcher instance not verified warning.java_interpreted_mode=HMCL is running in an interpreted Java environment, which will greatly affect performance.\n\ \n\ diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index f4021b9b3b..733b5e0506 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -1643,6 +1643,7 @@ update.channel.nightly.hint=你正在使用 HMCL 預覽版。預覽版更新較 update.channel.nightly.title=預覽版提示 update.channel.stable=穩定版 update.checking=正在檢查更新 +update.check_failed=檢查更新失敗 %s update.disable_auto_show_update_dialog=不自動顯示更新彈窗 update.disable_auto_show_update_dialog.subtitle=啟用此選項,HMCL 將不會自動彈出更新彈窗。 update.failed=更新失敗 @@ -1657,6 +1658,7 @@ update.no_browser=無法開啟瀏覽器。網址已經複製到剪貼簿了, update.tooltip=更新 update.preview=提前測試 HMCL 預覽版本 update.preview.subtitle=啟用此選項,你將可以提前取得 HMCL 的新版本,以便在正式發布前進行測試。 +update.unverified=更新不可用,因為無法驗證此啟動器例項完整性 warning.java_interpreted_mode=HMCL 正在執行於直譯器模式的 Java 環境中,效能將會受到很大影響。\n我們建議你使用支援 JIT 的 Java 開啟 HMCL 以獲得最佳體驗。 warning.software_rendering=HMCL 正在使用軟體繪製,效能將會受到很大影響。 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 848051d09f..4e1d6ef5a8 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -1643,6 +1643,7 @@ update.channel.nightly.hint=你正在使用 HMCL 预览版。预览版更新较 update.channel.nightly.title=预览版提示 update.channel.stable=稳定版 update.checking=正在检查更新 +update.check_failed=检查更新失败 %s update.disable_auto_show_update_dialog=不自动显示更新弹窗 update.disable_auto_show_update_dialog.subtitle=启用此选项,HMCL 将不会自动弹出更新弹窗。 update.failed=更新失败 @@ -1657,6 +1658,7 @@ update.no_browser=无法打开浏览器。网址已经复制到剪贴板,你 update.tooltip=更新 update.preview=提前预览 HMCL 版本 update.preview.subtitle=启用此选项,你将可以提前获取 HMCL 的新版本,以便在正式发布前进行测试。 +update.unverified=更新不可用,因为无法验证此启动器实例完整性 warning.java_interpreted_mode=HMCL 正在运行在解释器模式的 Java 环境中,性能将会受到很大影响。\n我们建议你使用支持 JIT 的 Java 启动 HMCL 以获取最佳体验。 warning.software_rendering=HMCL 正在使用软件渲染,性能将会受到很大影响。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。