Compare commits

...
66 Commits
Author SHA1 Message Date
Zkitefly c16d6e8d5f 添加 NeoForge 的 BMCLAPI 代理支持 (#3605) 2025-02-15 16:38:30 +08:00
Glavo 6df73aeb68 修复语言设置中英语选项名称错误的问题 (#3612) 2025-02-15 12:19:09 +08:00
3gf8jv4dv 23d11eca81 fix(locale): adjust time formats for various languages (#3613)
- Change to a standard and commonly used format.
- Rename i18n key as it is used in more than one place.
2025-02-15 12:16:42 +08:00
Glavo 0e307f3b71 简化 AdvancedListBox (#3611) 2025-02-12 21:53:10 +08:00
Glavo 33b791f2b0 更新日期时间格式 (#3610)
* Revert "improv(i18n): refine time formats on ui (#3608)"

This reverts commit 741f65d37c.

* update
2025-02-12 19:37:05 +08:00
3gf8jv4dv de01946ab2 fix(locale): fix a typo in zh-TW language (#3606) 2025-02-12 17:35:03 +08:00
3gf8jv4dv 5526bac70e fix(locale): change Socks to SOCKS (#3607)
ref: https://en.wikipedia.org/wiki/SOCKS
2025-02-12 17:33:31 +08:00
3gf8jv4dv 741f65d37c improv(i18n): refine time formats on ui (#3608)
- unify time formats using DateTimeFormatter, deprecate `world.time`
- change FormatStyle to make the time detail appropriate
- fix incorrect time format in English locale
2025-02-12 17:32:36 +08:00
Haowei Wen ecafc9d6f0 Fix height computation in ComponentListCell 2025-02-11 23:51:50 +08:00
Glavo 8795d522b0 支持临时隐藏预览版提示 (#3603) 2025-02-11 19:00:03 +08:00
Glavo d8e7e0e97e 更新 mod 数据 (#3601)
* 更新 mod 数据

* update

* update

* update
2025-02-10 22:04:48 +08:00
Glavo 8236dfc7e5 Fix #3595: patchNative may throw NPE when gameVersion is null (#3602) 2025-02-10 19:59:25 +08:00
Glavo 8a15b7d274 修复使用自定义字体时未正确设置字重的问题 (#3598) 2025-02-10 00:41:31 +08:00
3gf8jv4dv fce4c13515 fix(locale): fix zh-tw locale for game crash window buttons (#3596) 2025-02-09 09:28:00 +08:00
Glavo 2f36506099 Fix #3508: Java 下载对话框中切换包类型时不应重置版本字段 (#3594)
* Fix #3508: Java 下载对话框中切换包类型时不应重置版本字段

* fix checkstyle
2025-02-08 18:02:46 +08:00
Glavo 5e60eaf595 修复 macOS 平台 HMCLauncher.sh 不会显示 Java 下载页面的问题 (#3549) 2025-01-29 07:35:19 +08:00
Glavo f70fb4ae08 Update README (#3548) 2025-01-29 07:27:17 +08:00
3gf8jv4dv 38adb48051 Fix qq badge in readme (#3544)
ref: https://shields.io/blog/simple-icons-14
2025-01-25 16:38:55 +08:00
Glavo f972c49762 Fix #3539: 安装 MultiMC 整合包后应当设置图标 (#3541) 2025-01-24 09:11:22 +08:00
Glavo 5afeb3d019 优化 HMCLauncher.sh (#3538) 2025-01-23 18:21:29 +08:00
Burning_TNT 38897fbd2a Fix #3063 修复本地模组管理界面水平滚动条显示异常的问题 (#3463) 2025-01-23 13:51:55 +08:00
ZkiteflyandGlavo 0562042218 Update HMCLauncher.sh (#3265)
* Update HMCLauncher.sh

* update

* update

---------

Co-authored-by: Glavo <zjx001202@gmail.com>
2025-01-23 13:17:33 +08:00
Glavo c5e7ee8aef 优化对 Linux RISC-V 64 平台的支持 (#3536) 2025-01-23 10:23:27 +08:00
Glavo 35b288b800 Fix #3529: 修复在 FreeBSD x86-64 平台未正确替换 LWJGL 本地库的问题 (#3531)
* Fix #3529: 修复在 FreeBSD x86-64 平台未正确替换 LWJGL 本地库的问题

* update

* update

* update
2025-01-20 14:03:11 +08:00
Zkitefly a609d32081 更新版权年份至2025 (#3527) 2025-01-16 11:26:31 +08:00
Haowei Wen 72d8b75643 模组列表页中按下 Esc 时取消选中 (#3523)
* 模组列表页中按下 Esc 时取消选中

目前在模组列表页中选中模组后,按下 Esc 是不会有任何反应的,因为 [ListView](https://github.com/openjdk/jfx/blob/5b074c4c2b4f539301813322a6589cec28121553/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/ListViewBehavior.java#L108) 会处理这个事件,认为是取消编辑,而不论是否处于编辑模式中。

这个 PR 添加了 `FXUtils.ignoreEvent()` 函数,用于忽略掉对特定事件的处理。然后,我们就可以让 ListView 不处理 Esc 按下事件,然后在模组页添加 Esc 处理逻辑(必须让 ListView 忽略掉这个事件,不然就会被它优先处理)。

理论上所有用了 ListView / JFXListView 的地方都会有这个问题,即当焦点在 ListView 中时,按下 Esc 不会触发正常的事件处理(如返回上一页、关闭对话框)。比如在游戏下载页面中,对某个版本按下右键后,焦点就会转移到其上,此时再按 Esc 就毫无反应。

* 修复 Esc 在版本下载页/模组下载页/数据包页不工作
2025-01-16 11:15:29 +08:00
3gf8jv4dvandyushijinhun 01f9a452f1 Update ISSUE_TEMPLATE [skip ci] (#3446)
* Update ISSUE_TEMPLATE [skip ci]

* Apply suggestions from yushijinhun

Co-authored-by: yushijinhun <yushijinhun@gmail.com>

---------

Co-authored-by: yushijinhun <yushijinhun@gmail.com>
2025-01-06 03:40:49 +08:00
3gf8jv4dvandyushijinhun f2edc8001c Revise Spanish & English (#3465)
* Revise Spanish & English

Overhaul for Spanish. I cannot guarantee it is 100% correct, but I
promise to proofread it. During the overhaul some more mistakes in the
English language were discovered, so let's fix it.

All translations have been revised except for the `game.crash.reason`
series.

* Apply suggestions from yushijinhun

Co-authored-by: yushijinhun <yushijinhun@gmail.com>

---------

Co-authored-by: yushijinhun <yushijinhun@gmail.com>
2025-01-06 03:37:19 +08:00
3gf8jv4dvandyushijinhun d2df515325 Revise README [skip ci] (#3447)
* Revise README [skip ci]

* Apply suggestions from yushijinhun

Co-authored-by: yushijinhun <yushijinhun@gmail.com>

---------

Co-authored-by: yushijinhun <yushijinhun@gmail.com>
2025-01-06 03:32:42 +08:00
3gf8jv4dv 95e39fe87a gha: revise release description [skip ci] (#3474)
unify text style
2025-01-06 03:12:13 +08:00
lijiaxuanand3gf8jv4dv 662bf1a9d4 Update I18N.properties English Translations (#3477)
* Update I18N.properties English Translations

更新了英文语言翻译文件,修复了一些语法错误并且让一些句子更加通顺

* Update I18N.properties

Co-authored-by: 3gf8jv4dv <3gf8jv4dv@gmail.com>

* Update I18N.properties

Co-authored-by: 3gf8jv4dv <3gf8jv4dv@gmail.com>

* Update I18N.properties

Co-authored-by: 3gf8jv4dv <3gf8jv4dv@gmail.com>

* Update I18N.properties

Co-authored-by: 3gf8jv4dv <3gf8jv4dv@gmail.com>

* Update HMCL/src/main/resources/assets/lang/I18N.properties

Co-authored-by: 3gf8jv4dv <3gf8jv4dv@gmail.com>

* Update I18N.properties

* Update HMCL/src/main/resources/assets/lang/I18N.properties

Co-authored-by: 3gf8jv4dv <3gf8jv4dv@gmail.com>

* Update HMCL/src/main/resources/assets/lang/I18N.properties

Co-authored-by: 3gf8jv4dv <3gf8jv4dv@gmail.com>

---------

Co-authored-by: 3gf8jv4dv <3gf8jv4dv@gmail.com>
2025-01-06 02:15:58 +08:00
Zkiteflyand3gf8jv4dv bfda297838 检查安装时的非ASCII字符名称 (#3244)
* 检查安装时的非ASCII字符名称

* i18n

* 添加导出模组列表信息功能

* Revert "添加导出模组列表信息功能"

This reverts commit 6505d1c284.

* Update HMCL/src/main/resources/assets/lang/I18N.properties

Co-authored-by: 3gf8jv4dv <158490944+3gf8jv4dv@users.noreply.github.com>

* Update HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties

Co-authored-by: 3gf8jv4dv <158490944+3gf8jv4dv@users.noreply.github.com>

* Update HMCL/src/main/resources/assets/lang/I18N_zh.properties

Co-authored-by: 3gf8jv4dv <158490944+3gf8jv4dv@users.noreply.github.com>

---------

Co-authored-by: 3gf8jv4dv <158490944+3gf8jv4dv@users.noreply.github.com>
2025-01-05 01:31:44 +08:00
ZkiteflyandGlavo d80f61e2e7 update getAssetIndex() (#3252)
* update getAssetIndex()

* update

---------

Co-authored-by: Glavo <zjx001202@gmail.com>
2025-01-04 21:33:48 +08:00
Zkitefly 1f7b156423 Press ESC in search box to exit search (#3519) 2025-01-04 01:38:11 +08:00
Haowei Wen 56d20a5d08 Fix problems with window resizing in Linux 2025-01-04 01:35:36 +08:00
Haowei Wen b5cea83a8a Workaround for JDK-8344372
https://bugs.openjdk.org/browse/JDK-8344372
2025-01-03 20:15:03 +08:00
Haowei Wen 397edaec5d Press ESC in search box to exit search 2025-01-03 17:08:19 +08:00
Zkitefly 39b8de4824 fix 2025-01-03 17:08:19 +08:00
Zkitefly 5197157a97 添加搜索功能 2025-01-03 17:08:19 +08:00
Glavo d61826ba4c 优化 Release Note (#3516) 2025-01-01 05:37:30 +08:00
Glavo 85886da770 Bump Mesa to 24.3.2 (#3515)
* Bump Mesa to 24.3.2

* update
2025-01-01 05:14:14 +08:00
Haowei Wen 6f53da159a Refine DownloadPage
* Fix cursor is not hand when hovering on ModItem
* Fix clicking on the ModItem in ModVersion dialog would open a new dialog again
* Fix ESC doesn't work in ModVersion dialog
2024-12-31 19:56:38 +08:00
Haowei Wen 78e15d130b Update memory status every 1s, fix #3440
This commit also fixes Lang.getTimer() returns a non-daemon Timer,
preventing Java from exiting after closing the launcher.
2024-12-30 20:14:04 +08:00
Haowei Wen 7d12ef6b09 Fix memory slider indicator is covered by progress bar 2024-12-30 19:39:42 +08:00
Haowei Wen 260f4b6c5d Fix ESC not working in input dialog 2024-12-30 00:17:00 +08:00
Haowei Wen 104534d12f Fix style 2024-12-30 00:07:43 +08:00
Haowei Wen 81b3911dc9 Fix value in text field might not be saved when pressing ESC
https://github.com/HMCL-dev/HMCL/pull/1869#issuecomment-2335027530
2024-12-30 00:02:32 +08:00
Haowei Wen c5b8aabc57 Refactor VersionSetting.clone() & .addListener(), fix javaVersionType is not saved 2024-12-29 22:36:40 +08:00
Haowei Wen f0a064afdd Fix sometimes version specific setting is lost 2024-12-29 22:18:15 +08:00
Haowei Wen 8a4f6af193 Fix NPE due to the order of attaching specificSettingsListener 2024-12-29 21:40:09 +08:00
Haowei Wen e63ee49eaa Remove VersionSetting.global property as it's unused 2024-12-29 20:59:47 +08:00
Haowei Wen d5408c38b3 Copy global settings when specialize version setting 2024-12-29 20:18:58 +08:00
Haowei Wen 5fd52bb808 Move skin cache directory to hmcl dir, close #2970 2024-12-29 18:28:23 +08:00
白铭骢 (Mingcong Bai) cb2b3525b8 fix(DefaultLauncher): also set LIBGL_KOPPER_DRI2=1 for Zink mode (#3503)
The amdgpu DDX is missing support for modifiers, causing Zink to fail.
Disable DRI3 to workaround this issue.

Link: https://gitlab.freedesktop.org/mesa/mesa/-/issues/10093

Signed-off-by: Mingcong Bai <jeffbai@aosc.io>
2024-12-25 19:55:42 +08:00
Glavo 4c6fdf49de Bump JetBrains Annotations to 26.0.1 (#3493) 2024-12-13 00:29:34 +08:00
Burning_TNT 981b55a10e Fix #3456 在更新预览界面没法拖动启动器窗口 (#3464) 2024-12-10 16:20:02 +08:00
Glavo 8460df7d5d 修复 OAuth 登录时轮询过于频繁的问题 (#3490)
* 修复 OAuth 登录时轮询过于频繁的问题

* update
2024-12-09 17:47:42 +08:00
Glavo 727e6746d2 Update versions.txt (#3484)
* Update versions.txt

* update
2024-12-04 12:29:17 +08:00
Glavo 8e0f0e2a38 使用 ONE_PASS_BOX 阴影代替高斯模糊 (#3458)
* 使用 ONE_PASS_BOX 代替高斯模糊

* update

* update

* update
2024-12-02 21:24:58 +08:00
Glavo 3df52b0e8f Fix typo in check-update.yml (#3479) 2024-11-29 20:06:08 +08:00
Glavo 6f0e133bb0 添加 banshanjdk-8 下载地址 (#3476)
* 添加 banshanjdk-8 下载地址

* update

* fix i18n

* fix typo
2024-11-28 20:34:12 +08:00
Glavo 36da64f796 支持加载 WebP 格式的背景图片 (#3457)
* 支持加载 WebP 格式的背景图片

* update

* update

* update

* update
2024-11-28 00:35:08 +08:00
Glavo 4c242d0c80 不再生成 MD5 checksum (#3473) 2024-11-24 14:10:58 +08:00
Glavo 0a230ef6b5 优化 Release Note (#3472) 2024-11-23 19:48:46 +08:00
Burning_TNT c12ae9fdf8 Fix #3460 无法识别奇异的 Forge (Like) 模组 Metadata (#3470) 2024-11-21 21:22:20 +08:00
Glavo 773ef4282c Bump action-gh-release to v2 (#3469) 2024-11-17 22:09:29 +08:00
77 changed files with 7683 additions and 2525 deletions

No files matched your search

+25 -19
View File
@@ -1,6 +1,6 @@
name: Bug 反馈 | Bug Feedback
name: Bug 反馈 | Bug Report
description:
反馈一个 HMCL 错误。| Bug Feedback.
反馈一个 HMCL 错误。| File a bug report for HMCL.
title: "[Bug] "
labels: bug
body:
@@ -8,29 +8,35 @@ body:
attributes:
value: |
提交前请确认:
* 该问题确实是 **HMCL 的错误**,而**不是 Minecraft 非正常退出**,如果你的 Minecraft 非正常退出,请前往 [QQ 群](https://docs.hmcl.net/groups.html)/[Discord 频道](https://discord.gg/jVvC7HfM6U) 中获取帮助。
* 你的启动器版本是**最新的快照版本**,可以点击 [此处](https://zkitefly.github.io/HMCL-Snapshot-Update/) 下载最新快照版本。
如果你的问题并不属于上述两类,你可以选取另一种 Issue 类型,或者直接前往 [QQ 群](https://docs.hmcl.net/groups.html)/[Discord 频道](https://discord.gg/jVvC7HfM6U) 中获取帮助。
* 该问题确实是 **HMCL 的错误**,而**不是 Minecraft 非正常退出**,如果你的 Minecraft 非正常退出,请前往 [QQ 群](https://docs.hmcl.net/groups.html)/[Discord 服务器](https://discord.gg/jVvC7HfM6U) 获取帮助。
* 你的启动器版本是**最新的预览版本**,可以点击 [此处](https://zkitefly.github.io/HMCL-Snapshot-Update/) 下载最新预览版本。
如果你的问题并不属于上述两类,你可以选取另一种 Issue 类型,或者直接前往 [QQ 群](https://docs.hmcl.net/groups.html)/[Discord 服务器](https://discord.gg/jVvC7HfM6U) 获取帮助。
Before submitting, please confirm:
* The issue is indeed **an error of HMCL**, not **Minecraft abnormal exit**. If your Minecraft exits abnormally, please go to the [QQ group](https://docs.hmcl.net/groups.html) or [Discord channel](https://discord.gg/jVvC7HfM6U) for help.
* Your launcher version is **the latest snapshot version**. You can click [here](https://github.com/burningtnt/HMCL-Snapshot-Update/raw/master/datas/HMCL-dev.jar) to download the latest snapshot version.
If your issue does not fall into the above two categories, you can choose another type of issue or directly go to the [QQ group](https://docs.hmcl.net/groups.html) or [Discord channel](https://discord.gg/jVvC7HfM6U) for help.
* The issue is indeed **a bug of HMCL**, not **Minecraft abnormal exit**. If your Minecraft exits abnormally, please go to the [QQ group](https://docs.hmcl.net/groups.html) or [Discord server](https://discord.gg/jVvC7HfM6U) for help.
* Your launcher is the **latest nightly build**. You can click [here](https://zkitefly.github.io/HMCL-Snapshot-Update/en) to download the latest nightly build.
If your issue does not fall into the above two categories, you can choose another type of issue or directly go to the [QQ group](https://docs.hmcl.net/groups.html) or [Discord server](https://discord.gg/jVvC7HfM6U) for help.
- type: input
id: platform
attributes:
label: 平台 | Platform
description: 请输入您遇到 BUG 的平台。 Please enter the platform on which you encountered the bug.
description: |
请输入你遇到问题的平台。
Please enter the platform on which you encountered the bug.
placeholder: e.g. Windows 11
validations:
required: true
- type: textarea
id: bug-report
attributes:
label: 问题描述 | Problem Description
description: 请尽可能地详细描述你所遇到的问题,并描述如何重新触发这个问题。 Please describe the problem you met in as much detail as possible. Besides, please describe how to trigger this problem again.
label: 问题描述 | Bug Description
description: |
请尽可能地详细描述你所遇到的问题,并描述如何重新触发这个问题。
Please describe the bug you met in as much detail as possible. Additionally, describe the steps to reproduce this bug.
placeholder: |
1. 点击 HMCL 上的某个按钮 | Click a button named ...
2. 向下翻页 | Scroll down
@@ -42,12 +48,12 @@ body:
attributes:
label: 启动器崩溃报告 / 启动器日志文件 | Launcher Crash Report / Launcher Log File
description: |
如果您的启动器崩溃了,请将崩溃报告填入(或将文件拖入)下方。
如果您的启动器没有崩溃,请在遇到问题后不要退出启动器,在启动器的【设置】>【通用】>【调试】一栏中点击“导出启动器日志”,并将导出的日志拖到下方的输入栏中。
如果你的启动器崩溃了,请将崩溃报告填入(或将文件拖入)下方。
如果你的启动器没有崩溃,请在遇到问题后**不要退出启动器**,在启动器的 “设置 → 通用 → 调试” 一栏中点击 “导出启动器日志”,并将导出的日志拖到下方的输入栏中。
**请注意:启动器崩溃报告或日志文件是诊断问题的重要依据,请务必上传!**
If your launcher crashes, please fill it in (or drag the file in) below.
If your launcher doesn't crash, please DO NOT EXIT your launcher, click "Export Launcher Log" in the [Settings] > [General] > [Debug] column of the launcher, and drag the exported log to the input field below.
**Attention: The crash report or the log file is the key to solve the problem. Please update it!**
If your launcher crashes, please fill in (or drag the file in) the following input field with the crash report.
If your launcher does not crash, please DO NOT EXIT your launcher, click "Export Launcher Logs" in the "Settings → General → Debug" of the launcher, and drag the exported log to the following input field.
**ATTENTION: The crash report or log file is the key to resolving the bug. Please upload them!**
validations:
required: true
+4 -4
View File
@@ -1,11 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: QQ 群 | QQ Groups
- name: QQ 群 | QQ Group
url: https://docs.hmcl.net/groups.html
about: Hello Minecraft! Launcher 的官方 QQ 交流群。| The official QQ groups of Hello Minecraft! Laucher.
- name: Discord 频道 | Discord Channel
about: Hello Minecraft! Launcher 的官方 QQ 交流群。| The official QQ group of Hello Minecraft! Launcher.
- name: Discord 服务器 | Discord Server
url: https://discord.gg/jVvC7HfM6U
about: Hello Minecraft! Launcher 的官方 Discord 频道。| The official Discord channel of Hello Minecraft! Launcher.
about: Hello Minecraft! Launcher 的官方 Discord 服务器。| The official Discord server of Hello Minecraft! Launcher.
- name: 其他反馈 | Others
url: https://github.com/HMCL-dev/HMCL/discussions/new/choose
about: 通过 Discussions 反馈其他问题。| Report other problems in Discussions.
+10 -4
View File
@@ -1,22 +1,28 @@
name: 新功能 | Feature Request
description: 为 HMCL 提出新功能 | A new feature for HMCL
description: 为 HMCL 提出新功能。| Suggest a new feature or enhancement for HMCL.
title: "[Feature] "
labels: enhancement
body:
- type: markdown
attributes:
value: 请确认 Issues 列表无重复的项目。| Please make sure that no duplicated issues has already been delivered.
value: |
请确认 Issues 列表无重复的项目。
Please make sure that no duplicate issues have already been submitted.
- type: textarea
id: description
attributes:
label: 描述 | Description
description: 请详细描述你想加入的新功能。| Please describe the new feature detaily.
description: |
请详细描述你想加入的新功能。
Please describe the new feature in detail.
validations:
required: true
- type: textarea
id: reason
attributes:
label: 原因 | Reason
description: 请描述该功能带来的好处及原因。| Please describe why you want to add the feature into HMCL.
description: |
请描述该功能带来的好处及原因。
Please describe why you want to add the feature or enhancement to HMCL.
validations:
required: true
+16 -11
View File
@@ -50,16 +50,19 @@ jobs:
if: ${{ env.continue == 'true' }}
run: |
echo "The full changelogs can be found on the website: https://docs.hmcl.net/changelog/dev.html" >> RELEASE_NOTE
echo "Notice: changelogs are written in Chinese." >> RELEASE_NOTE
echo "" >> RELEASE_NOTE
echo "*Notice: changelogs are written in Chinese.*" >> RELEASE_NOTE
echo "" >> RELEASE_NOTE
echo "| File Name | SHA-256 Checksum |" >> RELEASE_NOTE
echo "| --- | --- |" >> RELEASE_NOTE
echo "| HMCL-$HMCL_VERSION.exe | $(cat HMCL-$HMCL_VERSION.exe.sha256) |" >> RELEASE_NOTE
echo "| HMCL-$HMCL_VERSION.jar | $(cat HMCL-$HMCL_VERSION.jar.sha256) |" >> RELEASE_NOTE
echo "| HMCL-$HMCL_VERSION.sh | $(cat HMCL-$HMCL_VERSION.sh.sha256) |" >> RELEASE_NOTE
echo "| [HMCL-$HMCL_VERSION.exe]($GH_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.exe) | \`$(cat HMCL-$HMCL_VERSION.exe.sha256)\` |" >> RELEASE_NOTE
echo "| [HMCL-$HMCL_VERSION.jar]($GH_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.jar) | \`$(cat HMCL-$HMCL_VERSION.jar.sha256)\` |" >> RELEASE_NOTE
echo "| [HMCL-$HMCL_VERSION.sh]($GH_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.sh) | \`$(cat HMCL-$HMCL_VERSION.sh.sha256)\` |" >> RELEASE_NOTE
env:
GH_DOWNLOAD_BASE_URL: https://github.com/HMCL-dev/HMCL/releases/download
- name: Create release
if: ${{ env.continue == 'true' }}
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
with:
body_path: RELEASE_NOTE
files: |
@@ -69,7 +72,7 @@ jobs:
target_commitish: ${{ env.HMCL_COMMIT_SHA }}
name: ${{ env.HMCL_TAG_NAME }}
tag_name: ${{ env.HMCL_TAG_NAME }}
prerelease: ture
prerelease: true
stable-check-update:
if: ${{ github.repository_owner == 'HMCL-dev' }}
needs: dev-check-update
@@ -115,16 +118,18 @@ jobs:
echo "" >> RELEASE_NOTE
echo "The full changelogs can be found on the website: https://docs.hmcl.net/changelog/stable.html" >> RELEASE_NOTE
echo "" >> RELEASE_NOTE
echo "Notice: changelogs are written in Chinese." >> RELEASE_NOTE
echo "*Notice: changelogs are written in Chinese.*" >> RELEASE_NOTE
echo "" >> RELEASE_NOTE
echo "| File Name | SHA-256 Checksum |" >> RELEASE_NOTE
echo "| --- | --- |" >> RELEASE_NOTE
echo "| HMCL-$HMCL_VERSION.exe | $(cat HMCL-$HMCL_VERSION.exe.sha256) |" >> RELEASE_NOTE
echo "| HMCL-$HMCL_VERSION.jar | $(cat HMCL-$HMCL_VERSION.jar.sha256) |" >> RELEASE_NOTE
echo "| HMCL-$HMCL_VERSION.sh | $(cat HMCL-$HMCL_VERSION.sh.sha256) |" >> RELEASE_NOTE
echo "| [HMCL-$HMCL_VERSION.exe]($GH_DOWNLOAD_BASE_URL/release-$HMCL_VERSION/HMCL-$HMCL_VERSION.exe) | \`$(cat HMCL-$HMCL_VERSION.exe.sha256)\` |" >> RELEASE_NOTE
echo "| [HMCL-$HMCL_VERSION.jar]($GH_DOWNLOAD_BASE_URL/release-$HMCL_VERSION/HMCL-$HMCL_VERSION.jar) | \`$(cat HMCL-$HMCL_VERSION.jar.sha256)\` |" >> RELEASE_NOTE
echo "| [HMCL-$HMCL_VERSION.sh]($GH_DOWNLOAD_BASE_URL/release-$HMCL_VERSION/HMCL-$HMCL_VERSION.sh) | \`$(cat HMCL-$HMCL_VERSION.sh.sha256)\` |" >> RELEASE_NOTE
env:
GH_DOWNLOAD_BASE_URL: https://github.com/HMCL-dev/HMCL/releases/download
- name: Create release
if: ${{ env.continue == 'true' }}
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
with:
body_path: RELEASE_NOTE
files: |
+1
View File
@@ -20,6 +20,7 @@ minecraft-exported-crash-info*
/HMCLCore/build/
/HMCLTransformerDiscoveryService/build/
/minecraft/libraries/HMCLTransformerDiscoveryService/build/
/buildSrc/build/
# idea
.idea
+3 -1
View File
@@ -1,2 +1,4 @@
/data.csv
/data.json
/data.json
/mod.json
/modpack.json
+18 -2
View File
@@ -1,3 +1,4 @@
import org.jackhuang.hmcl.gradle.mod.ParseModDataTask
import java.net.URI
import java.nio.file.FileSystems
import java.nio.file.Files
@@ -37,13 +38,13 @@ version = "$versionRoot.$buildNumber"
dependencies {
implementation(project(":HMCLCore"))
implementation("libs:JFoenix")
implementation("com.twelvemonkeys.imageio:imageio-webp:3.12.0")
}
fun digest(algorithm: String, bytes: ByteArray): ByteArray = MessageDigest.getInstance(algorithm).digest(bytes)
fun createChecksum(file: File) {
val algorithms = linkedMapOf(
"MD5" to "md5",
"SHA-1" to "sha1",
"SHA-256" to "sha256",
"SHA-512" to "sha512"
@@ -111,6 +112,9 @@ tasks.getByName<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("sha
exclude("**/package-info.class")
exclude("META-INF/maven/**")
exclude("META-INF/services/javax.imageio.spi.ImageReaderSpi")
exclude("META-INF/services/javax.imageio.spi.ImageInputStreamSpi")
minimize {
exclude(dependency("com.google.code.gson:.*:.*"))
exclude(dependency("libs:JFoenix:.*"))
@@ -118,7 +122,7 @@ tasks.getByName<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("sha
manifest {
attributes(
"Created-By" to "Copyright(c) 2013-2024 huangyuhui.",
"Created-By" to "Copyright(c) 2013-2025 huangyuhui.",
"Main-Class" to "org.jackhuang.hmcl.Main",
"Multi-Release" to "true",
"Implementation-Version" to project.version,
@@ -250,3 +254,15 @@ tasks.create<JavaExec>("run") {
logger.quiet("HMCL_JAVA_OPTS: $vmOptions")
}
}
// mcmod data
tasks.register<ParseModDataTask>("parseModData") {
inputFile.set(layout.projectDirectory.file("mod.json"))
outputFile.set(layout.projectDirectory.file("src/main/resources/assets/mod_data.txt"))
}
tasks.register<ParseModDataTask>("parseModPackData") {
inputFile.set(layout.projectDirectory.file("modpack.json"))
outputFile.set(layout.projectDirectory.file("src/main/resources/assets/modpack_data.txt"))
}
-123
View File
@@ -1,123 +0,0 @@
#
# Hello Minecraft! Launcher
# Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
#
import json
import codecs
import re
import sys
from urllib.parse import urlparse, parse_qs
S = ';'
MOD_SEPARATOR = ','
CURSEFORGE_PATTERN1 = re.compile(
r'^/(minecraft|Minecraft|minecraft-bedrock)/(mc-mods|modpacks|customization|mc-addons|texture-packs|customization/configuration|addons)/+(?P<modid>[\w-]+)(/(.*?))?$')
CURSEFORGE_PATTERN2 = re.compile(
r'^/projects/(?P<modid>[\w-]+)(/(.*?))?$')
CURSEFORGE_PATTERN3 = re.compile(
r'^/mc-mods/minecraft/(?P<modid>[\w-]+)(/(.*?))?$')
CURSEFORGE_PATTERN4 = re.compile(
r'^/legacy/mc-mods/minecraft/(\d+)-(?P<modid>[\w-]+)'
)
def parseCurseforge(url):
res = urlparse(url)
if res.scheme not in ['http', 'https']:
return ''
for pattern in [CURSEFORGE_PATTERN1, CURSEFORGE_PATTERN2, CURSEFORGE_PATTERN3, CURSEFORGE_PATTERN4]:
match = pattern.match(res.path)
if match:
return match.group('modid')
return ''
MCMOD_PATTERN = re.compile(
r'^http(s)?://www\.mcmod\.cn/(class|modpack)/(?P<modid>\d+)\.html$')
def parseMcmod(url):
match = MCMOD_PATTERN.match(url)
if match:
return match.group('modid')
return ''
skip = [
'Minecraft',
'The Building Game'
]
if __name__ == '__main__':
json_name = sys.argv[1] or 'data.json'
with codecs.open(json_name, mode='r', encoding='utf-8-sig') as jsonfile, codecs.open('data.csv', mode='w', encoding='utf-8') as outfile:
data = json.load(jsonfile)
for mod in data:
chinese_name = mod['name']['main']
sub_name = mod['name']['sub']
abbr = mod['name']['abbr']
if sub_name in skip:
continue
if S in chinese_name:
print('Error! ' + chinese_name)
exit(1)
if S in sub_name:
print('Error! ' + chinese_name)
exit(1)
black_lists = [
'Wood Converter'
]
curseforge_id = ''
mcmod_id = ''
links = mod['links']['list']
if 'curseforge' in links and links['curseforge'] and sub_name not in black_lists and chinese_name not in black_lists:
for link in links['curseforge']:
curseforge_id = parseCurseforge(link['url'])
if curseforge_id != '':
break
if curseforge_id == '':
print('Error curseforge ' + chinese_name)
exit(1)
if 'mcmod' in links and links['mcmod']:
mcmod_id = parseMcmod(links['mcmod'][0]['url'])
if mcmod_id == '':
print('Error mcmod ' + chinese_name)
exit(1)
mod_id = []
if 'modid' in mod and 'list' in mod['modid']:
for id in mod['modid']['list']:
if MOD_SEPARATOR in id:
print('Error mod id!' + id)
exit(1)
mod_id.append(id)
mod_ids = MOD_SEPARATOR.join([str(id) for id in mod_id])
outfile.write(
f'{curseforge_id}{S}{mcmod_id}{S}{mod_ids}{S}{chinese_name}{S}{sub_name}{S}{abbr}\n')
print('Success!')
@@ -33,19 +33,19 @@ import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.ProxyManager;
import org.jackhuang.hmcl.setting.VersionIconType;
import org.jackhuang.hmcl.setting.VersionSetting;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.javafx.PropertyUtils;
import org.jackhuang.hmcl.java.JavaRuntime;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
@@ -218,7 +218,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
private VersionSetting initLocalVersionSetting(String id, VersionSetting vs) {
localVersionSettings.put(id, vs);
vs.addPropertyChangedListener(a -> saveVersionSetting(id));
vs.addListener(a -> saveVersionSetting(id));
return vs;
}
@@ -251,7 +251,6 @@ public class HMCLGameRepository extends DefaultGameRepository {
public VersionSetting getVersionSetting(String id) {
VersionSetting vs = getLocalVersionSetting(id);
if (vs == null || vs.isUsesGlobal()) {
profile.getGlobal().setGlobal(true); // always keep global.isGlobal = true
profile.getGlobal().setUsesGlobal(true);
return profile.getGlobal();
} else
@@ -261,24 +260,11 @@ public class HMCLGameRepository extends DefaultGameRepository {
public Optional<File> getVersionIconFile(String id) {
File root = getVersionRoot(id);
File iconFile = new File(root, "icon.png");
if (iconFile.exists()) {
return Optional.of(iconFile);
}
iconFile = new File(root, "icon.jpg");
if (iconFile.exists()) {
return Optional.of(iconFile);
}
iconFile = new File(root, "icon.bmp");
if (iconFile.exists()) {
return Optional.of(iconFile);
}
iconFile = new File(root, "icon.gif");
if (iconFile.exists()) {
return Optional.of(iconFile);
for (String extension : FXUtils.IMAGE_EXTENSIONS) {
File file = new File(root, "icon." + extension);
if (file.exists()) {
return Optional.of(file);
}
}
return Optional.empty();
@@ -286,7 +272,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
public void setVersionIconFile(String id, File iconFile) throws IOException {
String ext = FileUtils.getExtension(iconFile).toLowerCase(Locale.ROOT);
if (!ext.equals("png") && !ext.equals("jpg") && !ext.equals("bmp") && !ext.equals("gif")) {
if (!FXUtils.IMAGE_EXTENSIONS.contains(ext)) {
throw new IllegalArgumentException("Unsupported icon file: " + ext);
}
@@ -297,11 +283,9 @@ public class HMCLGameRepository extends DefaultGameRepository {
public void deleteIconFile(String id) {
File root = getVersionRoot(id);
new File(root, "icon.png").delete();
new File(root, "icon.jpg").delete();
new File(root, "icon.bmp").delete();
new File(root, "icon.gif").delete();
for (String extension : FXUtils.IMAGE_EXTENSIONS) {
new File(root, "icon." + extension).delete();
}
}
public Image getVersionIconImage(String id) {
@@ -315,9 +299,9 @@ public class HMCLGameRepository extends DefaultGameRepository {
Version version = getVersion(id).resolve(this);
Optional<File> iconFile = getVersionIconFile(id);
if (iconFile.isPresent()) {
try (InputStream inputStream = new FileInputStream(iconFile.get())) {
return new Image(inputStream);
} catch (IOException e) {
try {
return FXUtils.loadImage(iconFile.get().toPath());
} catch (Exception e) {
LOG.warning("Failed to load version icon of " + id, e);
}
}
@@ -353,6 +337,8 @@ public class HMCLGameRepository extends DefaultGameRepository {
if (!FileUtils.makeDirectory(file.getAbsoluteFile().getParentFile()))
return false;
LOG.info("Saving version setting: " + id);
try {
FileUtils.writeText(file, GSON.toJson(localVersionSettings.get(id)));
return true;
@@ -373,7 +359,12 @@ public class HMCLGameRepository extends DefaultGameRepository {
vs = createLocalVersionSetting(id);
if (vs == null)
return null;
vs.setUsesGlobal(false);
VersionIconType versionIcon = vs.getVersionIcon();
if (vs.isUsesGlobal()) {
PropertyUtils.copyProperties(profile.getGlobal(), vs);
vs.setUsesGlobal(false);
}
vs.setVersionIcon(versionIcon); // versionIcon is preserved
return vs;
}
@@ -87,7 +87,7 @@ public final class TexturesLoader {
}
private static final ThreadPoolExecutor POOL = threadPool("TexturesDownload", true, 2, 10, TimeUnit.SECONDS);
private static final Path TEXTURES_DIR = Metadata.MINECRAFT_DIRECTORY.resolve("assets").resolve("skins");
private static final Path TEXTURES_DIR = Metadata.HMCL_DIRECTORY.resolve("skins");
private static Path getTexturePath(Texture texture) {
String url = texture.getUrl();
@@ -47,7 +47,7 @@ import java.net.Proxy;
import java.util.Map;
import java.util.TreeMap;
public final class Config implements Cloneable, Observable {
public final class Config implements Observable {
public static final int CURRENT_UI_VERSION = 0;
@@ -223,11 +223,6 @@ public final class Config implements Cloneable, Observable {
return CONFIG_GSON.toJson(this);
}
@Override
public Config clone() {
return fromJson(this.toJson());
}
// Getters & Setters & Properties
public String getSelectedProfile() {
return selectedProfile.get();
@@ -197,7 +197,7 @@ public final class ConfigHolder {
}
}
static void markConfigDirty() {
private static void markConfigDirty() {
configWriter.accept(configInstance.toJson());
}
@@ -241,11 +241,7 @@ public final class ConfigHolder {
}
}
static void markGlobalConfigDirty() {
private static void markGlobalConfigDirty() {
globalConfigWriter.accept(globalConfigInstance.toJson());
}
private static void saveGlobalConfigSync() throws IOException {
writeToConfig(globalConfigInstance.toJson());
}
}
@@ -33,7 +33,7 @@ import java.lang.reflect.Type;
import java.util.*;
@JsonAdapter(GlobalConfig.Serializer.class)
public final class GlobalConfig implements Cloneable, Observable {
public final class GlobalConfig implements Observable {
@Nullable
public static GlobalConfig fromJson(String json) throws JsonParseException {
@@ -79,11 +79,6 @@ public final class GlobalConfig implements Cloneable, Observable {
return Config.CONFIG_GSON.toJson(this);
}
@Override
public GlobalConfig clone() {
return fromJson(this.toJson());
}
public int getAgreementVersion() {
return agreementVersion.get();
}
@@ -188,7 +188,7 @@ public final class Profile implements Observable {
global.addListener(listener);
gameDir.addListener(listener);
useRelativePath.addListener(listener);
global.get().addPropertyChangedListener(listener);
global.get().addListener(listener);
selectedVersion.addListener(listener);
}
@@ -25,6 +25,7 @@ import javafx.beans.binding.ObjectBinding;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.util.Pair;
import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.File;
@@ -123,6 +124,45 @@ public class Theme {
opacity);
}
private static Pair<String, String> parseFontStyle(String style) {
if (style == null) {
return null;
}
style = style.toLowerCase(Locale.ROOT);
String weight;
String posture;
if (style.contains("thin")) {
weight = "100";
} else if (style.contains("extralight") || style.contains("extra light") || style.contains("ultralight") | style.contains("ultra light")) {
weight = "200";
} else if (style.contains("medium")) {
weight = "500";
} else if (style.contains("semibold") || style.contains("semi bold") || style.contains("demibold") || style.contains("demi bold")) {
weight = "600";
} else if (style.contains("extrabold") || style.contains("extra bold") || style.contains("ultrabold") || style.contains("ultra bold")) {
weight = "800";
} else if (style.contains("black") || style.contains("heavy")) {
weight = "900";
} else if (style.contains("light")) {
weight = "lighter";
} else if (style.contains("bold")) {
weight = "bold";
} else {
weight = null;
}
if (style.contains("italic") || style.contains("oblique")) {
posture = "italic";
} else {
posture = null;
}
return Pair.pair(weight, posture);
}
public String[] getStylesheets(String overrideFontFamily) {
String css = "/assets/css/blue.css";
@@ -130,12 +170,12 @@ public class Theme {
? System.getProperty("hmcl.font.override", System.getenv("HMCL_FONT"))
: overrideFontFamily;
String fontStyle = null;
Pair<String, String> fontStyle = null;
if (fontFamily == null) {
Optional<Font> font = tryLoadFont();
if (font.isPresent()) {
fontFamily = font.get().getFamily();
fontStyle = font.get().getStyle();
fontStyle = parseFontStyle(font.get().getStyle());
}
}
@@ -159,8 +199,13 @@ public class Theme {
else
themeBuilder.append("-fx-font-family:\"").append(fontFamily).append("\";");
if (fontStyle != null && !fontStyle.isEmpty())
themeBuilder.append("-fx-font-style:\"").append(fontStyle).append("\";");
if (fontStyle != null) {
if (fontStyle.getKey() != null)
themeBuilder.append("-fx-font-weight:").append(fontStyle.getKey()).append(";");
if (fontStyle.getValue() != null)
themeBuilder.append("-fx-font-style:").append(fontStyle.getValue()).append(";");
}
themeBuilder.append('}');
@@ -20,11 +20,14 @@ package org.jackhuang.hmcl.setting;
import com.google.gson.*;
import com.google.gson.annotations.JsonAdapter;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.*;
import org.jackhuang.hmcl.game.*;
import org.jackhuang.hmcl.java.JavaManager;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.javafx.ObservableHelper;
import org.jackhuang.hmcl.util.javafx.PropertyUtils;
import org.jackhuang.hmcl.java.JavaRuntime;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
@@ -42,16 +45,12 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
* @author huangyuhui
*/
@JsonAdapter(VersionSetting.Serializer.class)
public final class VersionSetting implements Cloneable {
public final class VersionSetting implements Cloneable, Observable {
private boolean global = false;
private transient ObservableHelper helper = new ObservableHelper(this);
public boolean isGlobal() {
return global;
}
public void setGlobal(boolean global) {
this.global = global;
public VersionSetting() {
PropertyUtils.attachListener(this, helper);
}
private final BooleanProperty usesGlobalProperty = new SimpleBooleanProperty(this, "usesGlobal", true);
@@ -708,79 +707,21 @@ public final class VersionSetting implements Cloneable {
}
}
public void addPropertyChangedListener(InvalidationListener listener) {
usesGlobalProperty.addListener(listener);
javaVersionProperty.addListener(listener);
javaDirProperty.addListener(listener);
wrapperProperty.addListener(listener);
permSizeProperty.addListener(listener);
maxMemoryProperty.addListener(listener);
minMemoryProperty.addListener(listener);
autoMemory.addListener(listener);
preLaunchCommandProperty.addListener(listener);
postExitCommand.addListener(listener);
javaArgsProperty.addListener(listener);
minecraftArgsProperty.addListener(listener);
environmentVariablesProperty.addListener(listener);
noJVMArgsProperty.addListener(listener);
notCheckGameProperty.addListener(listener);
notCheckJVMProperty.addListener(listener);
notPatchNativesProperty.addListener(listener);
showLogsProperty.addListener(listener);
serverIpProperty.addListener(listener);
fullscreenProperty.addListener(listener);
widthProperty.addListener(listener);
heightProperty.addListener(listener);
gameDirTypeProperty.addListener(listener);
gameDirProperty.addListener(listener);
processPriorityProperty.addListener(listener);
rendererProperty.addListener(listener);
useNativeGLFW.addListener(listener);
useNativeOpenAL.addListener(listener);
launcherVisibilityProperty.addListener(listener);
defaultJavaPathProperty.addListener(listener);
nativesDirProperty.addListener(listener);
nativesDirTypeProperty.addListener(listener);
versionIcon.addListener(listener);
@Override
public void addListener(InvalidationListener listener) {
helper.addListener(listener);
}
@Override
public void removeListener(InvalidationListener listener) {
helper.removeListener(listener);
}
@Override
public VersionSetting clone() {
VersionSetting versionSetting = new VersionSetting();
versionSetting.setUsesGlobal(isUsesGlobal());
versionSetting.setJavaVersionType(getJavaVersionType());
versionSetting.setJavaVersion(getJavaVersion());
versionSetting.setDefaultJavaPath(getDefaultJavaPath());
versionSetting.setJavaDir(getJavaDir());
versionSetting.setWrapper(getWrapper());
versionSetting.setPermSize(getPermSize());
versionSetting.setMaxMemory(getMaxMemory());
versionSetting.setMinMemory(getMinMemory());
versionSetting.setAutoMemory(isAutoMemory());
versionSetting.setPreLaunchCommand(getPreLaunchCommand());
versionSetting.setPostExitCommand(getPostExitCommand());
versionSetting.setJavaArgs(getJavaArgs());
versionSetting.setMinecraftArgs(getMinecraftArgs());
versionSetting.setEnvironmentVariables(getEnvironmentVariables());
versionSetting.setNoJVMArgs(isNoJVMArgs());
versionSetting.setNotCheckGame(isNotCheckGame());
versionSetting.setNotCheckJVM(isNotCheckJVM());
versionSetting.setNotPatchNatives(isNotPatchNatives());
versionSetting.setShowLogs(isShowLogs());
versionSetting.setServerIp(getServerIp());
versionSetting.setFullscreen(isFullscreen());
versionSetting.setWidth(getWidth());
versionSetting.setHeight(getHeight());
versionSetting.setGameDirType(getGameDirType());
versionSetting.setGameDir(getGameDir());
versionSetting.setProcessPriority(getProcessPriority());
versionSetting.setRenderer(getRenderer());
versionSetting.setUseNativeGLFW(isUseNativeGLFW());
versionSetting.setUseNativeOpenAL(isUseNativeOpenAL());
versionSetting.setLauncherVisibility(getLauncherVisibility());
versionSetting.setNativesDir(getNativesDir());
versionSetting.setVersionIcon(getVersionIcon());
return versionSetting;
VersionSetting cloned = new VersionSetting();
PropertyUtils.copyProperties(this, cloned);
return cloned;
}
public static class Serializer implements JsonSerializer<VersionSetting>, JsonDeserializer<VersionSetting> {
@@ -18,6 +18,7 @@
package org.jackhuang.hmcl.ui;
import com.jfoenix.controls.*;
import com.twelvemonkeys.imageio.plugins.webp.WebPImageReaderSpi;
import javafx.animation.*;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
@@ -26,10 +27,14 @@ import javafx.beans.WeakInvalidationListener;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.Property;
import javafx.beans.value.*;
import javafx.event.Event;
import javafx.event.EventDispatcher;
import javafx.event.EventType;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.*;
@@ -44,6 +49,7 @@ import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import javafx.util.Callback;
import javafx.util.Duration;
@@ -53,11 +59,9 @@ import org.glavo.png.PNGWriter;
import org.glavo.png.javafx.PNGJavaFXUtils;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
import org.jackhuang.hmcl.util.Holder;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.ResourceNotFoundError;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.*;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import org.jackhuang.hmcl.util.javafx.ExtendedProperties;
import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
@@ -68,13 +72,14 @@ import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.*;
import java.lang.ref.WeakReference;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -84,6 +89,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -113,6 +119,10 @@ public final class FXUtils {
public static final String DEFAULT_MONOSPACE_FONT = OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS ? "Consolas" : "Monospace";
public static final List<String> IMAGE_EXTENSIONS = Lang.immutableListOf(
"png", "jpg", "jpeg", "bmp", "gif", "webp"
);
private static final Map<String, Image> builtinImageCache = new ConcurrentHashMap<>();
private static final Map<String, Path> remoteImageCache = new ConcurrentHashMap<>();
@@ -228,6 +238,21 @@ public final class FXUtils {
});
}
@SuppressWarnings("unchecked")
public static <T extends Event> void ignoreEvent(Node node, EventType<T> type, Predicate<? super T> filter) {
EventDispatcher oldDispatcher = node.getEventDispatcher();
node.setEventDispatcher((event, tail) -> {
EventType<?> t = event.getEventType();
while (t != null && t != type)
t = t.getSuperType();
if (t == type && filter.test((T) event)) {
return tail.dispatchEvent(event);
} else {
return oldDispatcher.dispatchEvent(event, tail);
}
});
}
public static <K, T> void setupCellValueFactory(JFXTreeTableColumn<K, T> column, Function<K, ObservableValue<T>> mapper) {
column.setCellValueFactory(param -> {
if (column.validateValue(param))
@@ -515,10 +540,12 @@ public final class FXUtils {
}
public static <T> void bind(JFXTextField textField, Property<T> property, StringConverter<T> converter) {
textField.setText(converter == null ? (String) property.getValue() : converter.toString(property.getValue()));
TextFieldBindingListener<T> listener = new TextFieldBindingListener<>(textField, property, converter);
textField.focusedProperty().addListener((ChangeListener<Boolean>) listener);
property.addListener(listener);
TextFieldBinding<T> binding = new TextFieldBinding<>(textField, property, converter);
binding.updateTextField();
textField.getProperties().put("FXUtils.bind.binding", binding);
textField.focusedProperty().addListener(binding.focusedListener);
textField.sceneProperty().addListener(binding.sceneListener);
property.addListener(binding.propertyListener);
}
public static void bindInt(JFXTextField textField, Property<Number> property) {
@@ -530,68 +557,66 @@ public final class FXUtils {
}
public static void unbind(JFXTextField textField, Property<?> property) {
TextFieldBindingListener<?> listener = new TextFieldBindingListener<>(textField, property, null);
textField.focusedProperty().removeListener((ChangeListener<Boolean>) listener);
property.removeListener(listener);
TextFieldBinding<?> binding = (TextFieldBinding<?>) textField.getProperties().remove("FXUtils.bind.binding");
if (binding != null) {
textField.focusedProperty().removeListener(binding.focusedListener);
textField.sceneProperty().removeListener(binding.sceneListener);
property.removeListener(binding.propertyListener);
}
}
private static final class TextFieldBindingListener<T> implements ChangeListener<Boolean>, InvalidationListener {
private final int hashCode;
private final WeakReference<JFXTextField> textFieldRef;
private final WeakReference<Property<T>> propertyRef;
private static final class TextFieldBinding<T> {
private final JFXTextField textField;
private final Property<T> property;
private final StringConverter<T> converter;
TextFieldBindingListener(JFXTextField textField, Property<T> property, StringConverter<T> converter) {
this.textFieldRef = new WeakReference<>(textField);
this.propertyRef = new WeakReference<>(property);
public final ChangeListener<Boolean> focusedListener;
public final ChangeListener<Scene> sceneListener;
public final InvalidationListener propertyListener;
public TextFieldBinding(JFXTextField textField, Property<T> property, StringConverter<T> converter) {
this.textField = textField;
this.property = property;
this.converter = converter;
this.hashCode = System.identityHashCode(textField) ^ System.identityHashCode(property);
}
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean focused) { // On TextField changed
JFXTextField textField = textFieldRef.get();
Property<T> property = this.propertyRef.get();
if (textField != null && property != null && oldValue == Boolean.TRUE && focused == Boolean.FALSE) {
if (textField.validate()) {
String newText = textField.getText();
@SuppressWarnings("unchecked")
T newValue = converter == null ? (T) newText : converter.fromString(newText);
if (!Objects.equals(newValue, property.getValue()))
property.setValue(newValue);
} else {
// Rollback to old value
invalidated(null);
focusedListener = (observable, oldFocused, newFocused) -> {
if (oldFocused && !newFocused) {
if (textField.validate()) {
uppdateProperty();
} else {
// Rollback to old value
updateTextField();
}
}
};
sceneListener = (observable, oldScene, newScene) -> {
if (oldScene != null && newScene == null) {
// Component is being removed from scene
if (textField.validate()) {
uppdateProperty();
}
}
};
propertyListener = observable -> {
updateTextField();
};
}
public void uppdateProperty() {
String newText = textField.getText();
@SuppressWarnings("unchecked")
T newValue = converter == null ? (T) newText : converter.fromString(newText);
if (!Objects.equals(newValue, property.getValue())) {
property.setValue(newValue);
}
}
@Override
public void invalidated(Observable observable) { // On property change
JFXTextField textField = textFieldRef.get();
Property<T> property = this.propertyRef.get();
if (textField != null && property != null) {
T value = property.getValue();
textField.setText(converter == null ? (String) value : converter.toString(value));
}
}
@Override
public int hashCode() {
return hashCode;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof TextFieldBindingListener))
return false;
TextFieldBindingListener<?> other = (TextFieldBindingListener<?>) obj;
return this.hashCode == other.hashCode
&& this.textFieldRef.get() == other.textFieldRef.get()
&& this.propertyRef.get() == other.propertyRef.get();
public void updateTextField() {
T value = property.getValue();
textField.setText(converter == null ? (String) value : converter.toString(value));
}
}
@@ -713,6 +738,50 @@ public final class FXUtils {
stage.getIcons().add(newBuiltinImage(icon));
}
private static Image loadWebPImage(InputStream input) throws IOException {
WebPImageReaderSpi spi = new WebPImageReaderSpi();
ImageReader reader = spi.createReaderInstance(null);
try (ImageInputStream imageInput = ImageIO.createImageInputStream(input)) {
reader.setInput(imageInput, true, true);
return SwingFXUtils.toFXImage(reader.read(0, reader.getDefaultReadParam()), null);
} finally {
reader.dispose();
}
}
public static Image loadImage(Path path) throws Exception {
try (InputStream input = Files.newInputStream(path)) {
if ("webp".equalsIgnoreCase(FileUtils.getExtension(path)))
return loadWebPImage(input);
else {
Image image = new Image(input);
if (image.isError())
throw image.getException();
return image;
}
}
}
public static Image loadImage(URL url) throws Exception {
URLConnection connection = NetworkUtils.createConnection(url);
if (connection instanceof HttpURLConnection) {
connection = NetworkUtils.resolveConnection((HttpURLConnection) connection);
}
try (InputStream input = connection.getInputStream()) {
String path = url.getPath();
if (path != null && "webp".equalsIgnoreCase(StringUtils.substringAfterLast(path, '.')))
return loadWebPImage(input);
else {
Image image = new Image(input);
if (image.isError())
throw image.getException();
return image;
}
}
}
/**
* Suppress IllegalArgumentException since the url is supposed to be correct definitely.
*
@@ -1034,4 +1103,9 @@ public final class FXUtils {
return String.format("#%02x%02x%02x", r, g, b);
}
public static FileChooser.ExtensionFilter getImageExtensionFilter() {
return new FileChooser.ExtensionFilter(i18n("extension.png"),
IMAGE_EXTENSIONS.stream().map(ext -> "*." + ext).toArray(String[]::new));
}
}
@@ -41,6 +41,9 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
public final class UpgradeDialog extends JFXDialogLayout {
public UpgradeDialog(RemoteVersion remoteVersion, Runnable updateRunnable) {
maxWidthProperty().bind(Controllers.getScene().widthProperty().multiply(0.7));
maxHeightProperty().bind(Controllers.getScene().heightProperty().multiply(0.7));
setHeading(new Label(i18n("update.changelog")));
setBody(new ProgressIndicator());
@@ -24,6 +24,8 @@ import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.versions.VersionPage;
import java.util.function.Consumer;
@@ -54,11 +56,36 @@ public class AdvancedListBox extends ScrollPane {
return this;
}
public AdvancedListBox addNavigationDrawerItem(Consumer<AdvancedListItem> fn) {
private AdvancedListItem createNavigationDrawerItem(String title, SVG leftGraphic) {
AdvancedListItem item = new AdvancedListItem();
item.getStyleClass().add("navigation-drawer-item");
item.setActionButtonVisible(false);
fn.accept(item);
item.setTitle(title);
if (leftGraphic != null) {
item.setLeftGraphic(VersionPage.wrap(leftGraphic));
}
return item;
}
public AdvancedListBox addNavigationDrawerItem(String title, SVG leftGraphic, Runnable onAction) {
return addNavigationDrawerItem(title, leftGraphic, onAction, null);
}
public AdvancedListBox addNavigationDrawerItem(String title, SVG leftGraphic, Runnable onAction, Consumer<AdvancedListItem> initializer) {
AdvancedListItem item = createNavigationDrawerItem(title, leftGraphic);
if (onAction != null) {
item.setOnAction(e -> onAction.run());
}
if (initializer != null) {
initializer.accept(item);
}
return add(item);
}
public AdvancedListBox addNavigationDrawerTab(TabHeader tabHeader, TabControl.Tab<?> tab, String title, SVG leftGraphic) {
AdvancedListItem item = createNavigationDrawerItem(title, leftGraphic);
item.activeProperty().bind(tabHeader.getSelectionModel().selectedItemProperty().isEqualTo(tab));
item.setOnAction(e -> tabHeader.select(tab));
return add(item);
}
@@ -1,52 +0,0 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.construct;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.TextFlow;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
public final class AnnouncementCard extends VBox {
public AnnouncementCard(String title, String content, Runnable onClose) {
TextFlow body = FXUtils.segmentToTextFlow(content, Controllers::onHyperlinkAction);
body.setLineSpacing(4);
BorderPane titleBar = new BorderPane();
titleBar.getStyleClass().add("title");
titleBar.setLeft(new Label(title));
if (onClose != null) {
Node hideNode = SVG.CLOSE.createIcon(Theme.blackFill(), 20, 20);
hideNode.setOnMouseClicked(e -> onClose.run());
hideNode.setCursor(Cursor.HAND);
titleBar.setRight(hideNode);
}
getChildren().setAll(titleBar, body);
setSpacing(16);
getStyleClass().addAll("card", "announcement");
}
}
@@ -149,8 +149,8 @@ final class ComponentListCell extends StackPane {
}
Platform.runLater(() -> {
double newAnimatedHeight = (list.prefHeight(-1) + 8 + 10) * (expanded ? 1 : -1);
double newHeight = expanded ? getHeight() + newAnimatedHeight : prefHeight(-1);
double newAnimatedHeight = (list.prefHeight(list.getWidth()) + 8 + 10) * (expanded ? 1 : -1);
double newHeight = expanded ? getHeight() + newAnimatedHeight : prefHeight(list.getWidth());
double contentHeight = expanded ? newAnimatedHeight : 0;
if (expanded) {
@@ -27,6 +27,7 @@ import org.jackhuang.hmcl.util.FutureCallback;
import java.util.concurrent.CompletableFuture;
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class InputDialogPane extends JFXDialogLayout {
@@ -68,6 +69,8 @@ public class InputDialogPane extends JFXDialogLayout {
lblCreationWarning.setText(msg);
});
});
onEscPressed(this, cancelButton::fire);
}
public CompletableFuture<String> getCompletableFuture() {
@@ -18,6 +18,7 @@
package org.jackhuang.hmcl.ui.construct;
import com.jfoenix.controls.JFXListView;
import javafx.beans.binding.DoubleBinding;
import javafx.css.PseudoClass;
import javafx.scene.control.ListCell;
import javafx.scene.layout.Region;
@@ -45,9 +46,11 @@ public abstract class MDListCell<T> extends ListCell<T> {
Region clippedContainer = (Region) listView.lookup(".clipped-container");
setPrefWidth(0);
if (clippedContainer != null) {
maxWidthProperty().bind(clippedContainer.widthProperty());
prefWidthProperty().bind(clippedContainer.widthProperty());
minWidthProperty().bind(clippedContainer.widthProperty());
DoubleBinding converted = clippedContainer.widthProperty().subtract(1);
maxWidthProperty().bind(converted);
prefWidthProperty().bind(converted);
minWidthProperty().bind(converted);
}
}
@@ -21,7 +21,6 @@ import com.jfoenix.controls.JFXRadioButton;
import com.jfoenix.controls.JFXTextField;
import com.jfoenix.validation.base.ValidatorBase;
import javafx.beans.property.*;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
@@ -114,6 +113,7 @@ public final class MultiFileItem<T> extends VBox {
public static class Option<T> {
protected final String title;
protected String subtitle;
protected String tooltip;
protected final T data;
protected final BooleanProperty selected = new SimpleBooleanProperty();
protected final JFXRadioButton left = new JFXRadioButton();
@@ -140,6 +140,11 @@ public final class MultiFileItem<T> extends VBox {
return this;
}
public Option<T> setTooltip(String tooltip) {
this.tooltip = tooltip;
return this;
}
public boolean isSelected() {
return left.isSelected();
}
@@ -161,6 +166,8 @@ public final class MultiFileItem<T> extends VBox {
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
left.setToggleGroup(group);
left.setUserData(data);
if (StringUtils.isNotBlank(tooltip))
FXUtils.installFastTooltip(left, tooltip);
pane.setLeft(left);
if (StringUtils.isNotBlank(subtitle)) {
@@ -268,8 +275,9 @@ public final class MultiFileItem<T> extends VBox {
return this;
}
public ObservableList<FileChooser.ExtensionFilter> getExtensionFilters() {
return selector.getExtensionFilters();
public FileOption<T> addExtensionFilter(FileChooser.ExtensionFilter filter) {
selector.getExtensionFilters().add(filter);
return this;
}
@Override
@@ -49,13 +49,10 @@ import org.jackhuang.hmcl.ui.construct.Navigator;
import org.jackhuang.hmcl.ui.construct.StackContainerPane;
import org.jackhuang.hmcl.ui.wizard.Refreshable;
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -180,23 +177,13 @@ public class DecoratorController {
case CUSTOM:
String backgroundImage = config().getBackgroundImage();
if (backgroundImage != null)
image = tryLoadImage(Paths.get(backgroundImage)).orElse(null);
image = tryLoadImage(Paths.get(backgroundImage));
break;
case NETWORK:
String backgroundImageUrl = config().getBackgroundImageUrl();
if (backgroundImageUrl != null) {
try {
URLConnection connection = NetworkUtils.createConnection(new URL(backgroundImageUrl));
if (connection instanceof HttpURLConnection) {
connection = NetworkUtils.resolveConnection((HttpURLConnection) connection);
}
try (InputStream input = connection.getInputStream()) {
image = new Image(input);
if (image.isError()) {
throw image.getException();
}
}
image = FXUtils.loadImage(new URL(backgroundImageUrl));
} catch (Exception e) {
LOG.warning("Couldn't load background image", e);
}
@@ -218,73 +205,57 @@ public class DecoratorController {
* Load background image from bg/, background.png, background.jpg, background.gif
*/
private Image loadDefaultBackgroundImage() {
Optional<Image> image = randomImageIn(Paths.get("bg"));
if (!image.isPresent()) {
image = tryLoadImage(Paths.get("background.png"));
Image image = randomImageIn(Paths.get("bg"));
if (image != null)
return image;
for (String extension : FXUtils.IMAGE_EXTENSIONS) {
image = tryLoadImage(Paths.get("background." + extension));
if (image != null)
return image;
}
if (!image.isPresent()) {
image = tryLoadImage(Paths.get("background.jpg"));
}
if (!image.isPresent()) {
image = tryLoadImage(Paths.get("background.gif"));
}
return image.orElseGet(() -> newBuiltinImage("/assets/img/background.jpg"));
return newBuiltinImage("/assets/img/background.jpg");
}
private Optional<Image> randomImageIn(Path imageDir) {
private @Nullable Image randomImageIn(Path imageDir) {
if (!Files.isDirectory(imageDir)) {
return Optional.empty();
return null;
}
List<Path> candidates;
try (Stream<Path> stream = Files.list(imageDir)) {
candidates = stream
.filter(it -> FXUtils.IMAGE_EXTENSIONS.contains(getExtension(it).toLowerCase(Locale.ROOT)))
.filter(Files::isReadable)
.filter(it -> {
String ext = getExtension(it).toLowerCase(Locale.ROOT);
return ext.equals("png") || ext.equals("jpg") || ext.equals("gif");
})
.collect(toList());
} catch (IOException e) {
LOG.warning("Failed to list files in ./bg", e);
return Optional.empty();
return null;
}
Random rnd = new Random();
while (candidates.size() > 0) {
while (!candidates.isEmpty()) {
int selected = rnd.nextInt(candidates.size());
Optional<Image> loaded = tryLoadImage(candidates.get(selected));
if (loaded.isPresent()) {
Image loaded = tryLoadImage(candidates.get(selected));
if (loaded != null)
return loaded;
} else {
else
candidates.remove(selected);
}
}
return Optional.empty();
return null;
}
private Optional<Image> tryLoadImage(Path path) {
private @Nullable Image tryLoadImage(Path path) {
if (!Files.isReadable(path))
return Optional.empty();
return null;
return tryLoadImage(path.toAbsolutePath().toUri().toString());
}
private Optional<Image> tryLoadImage(String url) {
Image img;
try {
img = new Image(url);
} catch (IllegalArgumentException e) {
return FXUtils.loadImage(path);
} catch (Exception e) {
LOG.warning("Couldn't load background image", e);
return Optional.empty();
return null;
}
if (img.getException() != null) {
LOG.warning("Couldn't load background image", img.getException());
return Optional.empty();
}
return Optional.of(img);
}
// ==== Navigation ====
@@ -27,11 +27,10 @@ import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.SkinBase;
import javafx.scene.effect.BlurType;
import javafx.scene.effect.DropShadow;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
@@ -49,8 +48,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
private final Stage primaryStage;
private final TransitionPane navBarPane;
private double xOffset, yOffset, newX, newY, initX, initY;
private boolean titleBarTransparent = true;
private double mouseInitX, mouseInitY, stageInitX, stageInitY, stageInitWidth, stageInitHeight;
/**
* Constructor for all SkinBase instances.
@@ -68,6 +66,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
StackPane shadowContainer = new StackPane();
shadowContainer.getStyleClass().add("body");
shadowContainer.setEffect(new DropShadow(BlurType.ONE_PASS_BOX, Color.rgb(0, 0, 0, 0.4), 10, 0.3, 0.0, 0.0));
parent = new StackPane();
Rectangle clip = new Rectangle();
@@ -299,13 +298,6 @@ public class DecoratorSkin extends SkinBase<Decorator> {
return navBar;
}
private void updateInitMouseValues(MouseEvent mouseEvent) {
initX = mouseEvent.getScreenX();
initY = mouseEvent.getScreenY();
xOffset = mouseEvent.getSceneX();
yOffset = mouseEvent.getSceneY();
}
private boolean isRightEdge(double x, double y, Bounds boundsInParent) {
return x < root.getWidth() && x >= root.getWidth() - root.snappedLeftInset();
}
@@ -322,146 +314,139 @@ public class DecoratorSkin extends SkinBase<Decorator> {
return x >= 0 && x <= root.snappedLeftInset();
}
private boolean setStageWidth(double width) {
if (width >= primaryStage.getMinWidth() && width >= titleContainer.getMinWidth()) {
primaryStage.setWidth(width);
initX = newX;
return true;
} else {
if (width >= primaryStage.getMinWidth() && width <= titleContainer.getMinWidth())
primaryStage.setWidth(titleContainer.getMinWidth());
private void resizeStage(double newWidth, double newHeight) {
if (newWidth < 0)
newWidth = primaryStage.getWidth();
if (newWidth < primaryStage.getMinWidth())
newWidth = primaryStage.getMinWidth();
if (newWidth < titleContainer.getMinWidth())
newWidth = titleContainer.getMinWidth();
return false;
}
if (newHeight < 0)
newHeight = primaryStage.getHeight();
if (newHeight < primaryStage.getMinHeight())
newHeight = primaryStage.getMinHeight();
if (newHeight < titleContainer.getMinHeight())
newHeight = titleContainer.getMinHeight();
// Width and height must be set simultaneously to avoid JDK-8344372 (https://github.com/openjdk/jfx/pull/1654)
primaryStage.setWidth(newWidth);
primaryStage.setHeight(newHeight);
}
private boolean setStageHeight(double height) {
if (height >= primaryStage.getMinHeight() && height >= titleContainer.getHeight()) {
primaryStage.setHeight(height);
initY = newY;
return true;
} else {
if (height >= primaryStage.getMinHeight() && height <= titleContainer.getHeight())
primaryStage.setHeight(titleContainer.getHeight());
return false;
}
}
// ====
protected void onMouseMoved(MouseEvent mouseEvent) {
if (!primaryStage.isFullScreen()) {
updateInitMouseValues(mouseEvent);
if (primaryStage.isResizable()) {
double x = mouseEvent.getX(), y = mouseEvent.getY();
Bounds boundsInParent = root.getBoundsInParent();
double diagonalSize = root.snappedLeftInset() + 10;
if (this.isRightEdge(x, y, boundsInParent)) {
if (y < diagonalSize) {
root.setCursor(Cursor.NE_RESIZE);
} else if (y > root.getHeight() - diagonalSize) {
root.setCursor(Cursor.SE_RESIZE);
} else {
root.setCursor(Cursor.E_RESIZE);
}
} else if (this.isLeftEdge(x, y, boundsInParent)) {
if (y < diagonalSize) {
root.setCursor(Cursor.NW_RESIZE);
} else if (y > root.getHeight() - diagonalSize) {
root.setCursor(Cursor.SW_RESIZE);
} else {
root.setCursor(Cursor.W_RESIZE);
}
} else if (this.isTopEdge(x, y, boundsInParent)) {
if (x < diagonalSize) {
root.setCursor(Cursor.NW_RESIZE);
} else if (x > root.getWidth() - diagonalSize) {
root.setCursor(Cursor.NE_RESIZE);
} else {
root.setCursor(Cursor.N_RESIZE);
}
} else if (this.isBottomEdge(x, y, boundsInParent)) {
if (x < diagonalSize) {
root.setCursor(Cursor.SW_RESIZE);
} else if (x > root.getWidth() - diagonalSize) {
root.setCursor(Cursor.SE_RESIZE);
} else {
root.setCursor(Cursor.S_RESIZE);
}
private void onMouseMoved(MouseEvent mouseEvent) {
if (!primaryStage.isFullScreen() && primaryStage.isResizable()) {
double x = mouseEvent.getX(), y = mouseEvent.getY();
Bounds boundsInParent = root.getBoundsInParent();
double diagonalSize = root.snappedLeftInset() + 10;
if (this.isRightEdge(x, y, boundsInParent)) {
if (y < diagonalSize) {
root.setCursor(Cursor.NE_RESIZE);
} else if (y > root.getHeight() - diagonalSize) {
root.setCursor(Cursor.SE_RESIZE);
} else {
root.setCursor(Cursor.DEFAULT);
root.setCursor(Cursor.E_RESIZE);
}
} else if (this.isLeftEdge(x, y, boundsInParent)) {
if (y < diagonalSize) {
root.setCursor(Cursor.NW_RESIZE);
} else if (y > root.getHeight() - diagonalSize) {
root.setCursor(Cursor.SW_RESIZE);
} else {
root.setCursor(Cursor.W_RESIZE);
}
} else if (this.isTopEdge(x, y, boundsInParent)) {
if (x < diagonalSize) {
root.setCursor(Cursor.NW_RESIZE);
} else if (x > root.getWidth() - diagonalSize) {
root.setCursor(Cursor.NE_RESIZE);
} else {
root.setCursor(Cursor.N_RESIZE);
}
} else if (this.isBottomEdge(x, y, boundsInParent)) {
if (x < diagonalSize) {
root.setCursor(Cursor.SW_RESIZE);
} else if (x > root.getWidth() - diagonalSize) {
root.setCursor(Cursor.SE_RESIZE);
} else {
root.setCursor(Cursor.S_RESIZE);
}
} else {
root.setCursor(Cursor.DEFAULT);
}
} else {
root.setCursor(Cursor.DEFAULT);
}
}
protected void onMouseReleased(MouseEvent mouseEvent) {
private void onMouseReleased(MouseEvent mouseEvent) {
getSkinnable().setDragging(false);
}
protected void onMouseDragged(MouseEvent mouseEvent) {
getSkinnable().setDragging(true);
if (mouseEvent.isPrimaryButtonDown() && (this.xOffset != -1.0 || this.yOffset != -1.0)) {
if (!this.primaryStage.isFullScreen() && !mouseEvent.isStillSincePress()) {
this.newX = mouseEvent.getScreenX();
this.newY = mouseEvent.getScreenY();
double deltaX = this.newX - this.initX;
double deltaY = this.newY - this.initY;
Cursor cursor = root.getCursor();
if (Cursor.E_RESIZE == cursor) {
this.setStageWidth(this.primaryStage.getWidth() + deltaX);
mouseEvent.consume();
} else if (Cursor.NE_RESIZE == cursor) {
if (this.setStageHeight(this.primaryStage.getHeight() - deltaY)) {
this.primaryStage.setY(this.primaryStage.getY() + deltaY);
}
private void onMouseDragged(MouseEvent mouseEvent) {
if (!getSkinnable().isDragging()) {
getSkinnable().setDragging(true);
mouseInitX = mouseEvent.getScreenX();
mouseInitY = mouseEvent.getScreenY();
stageInitX = primaryStage.getX();
stageInitY = primaryStage.getY();
stageInitWidth = primaryStage.getWidth();
stageInitHeight = primaryStage.getHeight();
}
this.setStageWidth(this.primaryStage.getWidth() + deltaX);
mouseEvent.consume();
} else if (Cursor.SE_RESIZE == cursor) {
this.setStageWidth(this.primaryStage.getWidth() + deltaX);
this.setStageHeight(this.primaryStage.getHeight() + deltaY);
mouseEvent.consume();
} else if (Cursor.S_RESIZE == cursor) {
this.setStageHeight(this.primaryStage.getHeight() + deltaY);
mouseEvent.consume();
} else if (Cursor.W_RESIZE == cursor) {
if (this.setStageWidth(this.primaryStage.getWidth() - deltaX)) {
this.primaryStage.setX(this.primaryStage.getX() + deltaX);
}
if (primaryStage.isFullScreen() || !mouseEvent.isPrimaryButtonDown() || mouseEvent.isStillSincePress())
return;
mouseEvent.consume();
} else if (Cursor.SW_RESIZE == cursor) {
if (this.setStageWidth(this.primaryStage.getWidth() - deltaX)) {
this.primaryStage.setX(this.primaryStage.getX() + deltaX);
}
double dx = mouseEvent.getScreenX() - mouseInitX;
double dy = mouseEvent.getScreenY() - mouseInitY;
this.setStageHeight(this.primaryStage.getHeight() + deltaY);
mouseEvent.consume();
} else if (Cursor.NW_RESIZE == cursor) {
if (this.setStageWidth(this.primaryStage.getWidth() - deltaX)) {
this.primaryStage.setX(this.primaryStage.getX() + deltaX);
}
Cursor cursor = root.getCursor();
if (getSkinnable().isAllowMove()) {
if (cursor == Cursor.DEFAULT) {
primaryStage.setX(stageInitX + dx);
primaryStage.setY(stageInitY + dy);
mouseEvent.consume();
}
}
if (this.setStageHeight(this.primaryStage.getHeight() - deltaY)) {
this.primaryStage.setY(this.primaryStage.getY() + deltaY);
}
if (getSkinnable().isResizable()) {
if (cursor == Cursor.E_RESIZE) {
resizeStage(stageInitWidth + dx, -1);
mouseEvent.consume();
mouseEvent.consume();
} else if (Cursor.N_RESIZE == cursor) {
if (this.setStageHeight(this.primaryStage.getHeight() - deltaY)) {
this.primaryStage.setY(this.primaryStage.getY() + deltaY);
}
} else if (cursor == Cursor.S_RESIZE) {
resizeStage(-1, stageInitHeight + dy);
mouseEvent.consume();
mouseEvent.consume();
} else if (getSkinnable().isAllowMove()) {
this.primaryStage.setX(mouseEvent.getScreenX() - this.xOffset);
this.primaryStage.setY(mouseEvent.getScreenY() - this.yOffset);
mouseEvent.consume();
}
} else if (cursor == Cursor.W_RESIZE) {
resizeStage(stageInitWidth - dx, -1);
primaryStage.setX(stageInitX + stageInitWidth - primaryStage.getWidth());
mouseEvent.consume();
} else if (cursor == Cursor.N_RESIZE) {
resizeStage(-1, stageInitHeight - dy);
primaryStage.setY(stageInitY + stageInitHeight - primaryStage.getHeight());
mouseEvent.consume();
} else if (cursor == Cursor.SE_RESIZE) {
resizeStage(stageInitWidth + dx, stageInitHeight + dy);
mouseEvent.consume();
} else if (cursor == Cursor.SW_RESIZE) {
resizeStage(stageInitWidth - dx, stageInitHeight + dy);
primaryStage.setX(stageInitX + stageInitWidth - primaryStage.getWidth());
mouseEvent.consume();
} else if (cursor == Cursor.NW_RESIZE) {
resizeStage(stageInitWidth - dx, stageInitHeight - dy);
primaryStage.setX(stageInitX + stageInitWidth - primaryStage.getWidth());
primaryStage.setY(stageInitY + stageInitHeight - primaryStage.getHeight());
mouseEvent.consume();
} else if (cursor == Cursor.NE_RESIZE) {
resizeStage(stageInitWidth + dx, stageInitHeight - dy);
primaryStage.setY(stageInitY + stageInitHeight - primaryStage.getHeight());
mouseEvent.consume();
}
}
}
@@ -60,7 +60,6 @@ import java.util.concurrent.CancellationException;
import java.util.function.Supplier;
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage {
@@ -100,43 +99,16 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE);
});
{
AdvancedListBox sideBar = new AdvancedListBox()
.startCategory(i18n("download.game").toUpperCase(Locale.ROOT))
.addNavigationDrawerItem(item -> {
item.setTitle(i18n("game"));
item.setLeftGraphic(wrap(SVG.GAMEPAD));
item.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(newGameTab));
item.setOnAction(e -> tab.select(newGameTab));
})
.addNavigationDrawerItem(settingsItem -> {
settingsItem.setTitle(i18n("modpack"));
settingsItem.setLeftGraphic(wrap(SVG.PACK));
settingsItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(modpackTab));
settingsItem.setOnAction(e -> tab.select(modpackTab));
})
.startCategory(i18n("download.content").toUpperCase(Locale.ROOT))
.addNavigationDrawerItem(item -> {
item.setTitle(i18n("mods"));
item.setLeftGraphic(wrap(SVG.PUZZLE));
item.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(modTab));
item.setOnAction(e -> tab.select(modTab));
})
.addNavigationDrawerItem(item -> {
item.setTitle(i18n("resourcepack"));
item.setLeftGraphic(wrap(SVG.TEXTURE_BOX));
item.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(resourcePackTab));
item.setOnAction(e -> tab.select(resourcePackTab));
})
.addNavigationDrawerItem(item -> {
item.setTitle(i18n("world"));
item.setLeftGraphic(wrap(SVG.EARTH));
item.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(worldTab));
item.setOnAction(e -> selectTabIfCurseForgeAvailable(worldTab));
});
FXUtils.setLimitWidth(sideBar, 200);
setLeft(sideBar);
}
AdvancedListBox sideBar = new AdvancedListBox()
.startCategory(i18n("download.game").toUpperCase(Locale.ROOT))
.addNavigationDrawerTab(tab, newGameTab, i18n("game"), SVG.GAMEPAD)
.addNavigationDrawerTab(tab, modpackTab, i18n("modpack"), SVG.PACK)
.startCategory(i18n("download.content").toUpperCase(Locale.ROOT))
.addNavigationDrawerTab(tab, modTab, i18n("mods"), SVG.PUZZLE)
.addNavigationDrawerTab(tab, resourcePackTab, i18n("resourcepack"), SVG.TEXTURE_BOX)
.addNavigationDrawerTab(tab, worldTab, i18n("world"), SVG.EARTH);
FXUtils.setLimitWidth(sideBar, 200);
setLeft(sideBar);
setCenter(transitionPane);
}
@@ -40,6 +40,7 @@ import org.jackhuang.hmcl.ui.construct.Validator;
import org.jackhuang.hmcl.ui.wizard.WizardController;
import org.jackhuang.hmcl.ui.wizard.WizardPage;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import static javafx.beans.binding.Bindings.createBooleanBinding;
@@ -121,8 +122,25 @@ public class InstallersPage extends Control implements WizardPage {
}
protected void onInstall() {
controller.getSettings().put("name", txtName.getText());
controller.onFinish();
String name = txtName.getText();
// Check for non-ASCII characters.
if (!StandardCharsets.US_ASCII.newEncoder().canEncode(name)) {
Controllers.dialog(new MessageDialogPane.Builder(
i18n("install.name.invalid"),
i18n("message.warning"),
MessageDialogPane.MessageType.QUESTION)
.yesOrNo(() -> {
controller.getSettings().put("name", name);
controller.onFinish();
}, () -> {
// The user selects Cancel and does nothing.
})
.build());
} else {
controller.getSettings().put("name", name);
controller.onFinish();
}
}
@Override
@@ -42,6 +42,7 @@ import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.File;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Optional;
@@ -149,10 +150,27 @@ public final class LocalModpackPage extends ModpackPage {
}
protected void onInstall() {
if (!txtModpackName.validate()) return;
controller.getSettings().put(MODPACK_NAME, txtModpackName.getText());
controller.getSettings().put(MODPACK_CHARSET, charset);
controller.onFinish();
String name = txtModpackName.getText();
// Check for non-ASCII characters.
if (!StandardCharsets.US_ASCII.newEncoder().canEncode(name)) {
Controllers.dialog(new MessageDialogPane.Builder(
i18n("install.name.invalid"),
i18n("message.warning"),
MessageDialogPane.MessageType.QUESTION)
.yesOrNo(() -> {
controller.getSettings().put(MODPACK_NAME, name);
controller.getSettings().put(MODPACK_CHARSET, charset);
controller.onFinish();
}, () -> {
// The user selects Cancel and does nothing.
})
.build());
} else {
controller.getSettings().put(MODPACK_NAME, name);
controller.getSettings().put(MODPACK_CHARSET, charset);
controller.onFinish();
}
}
protected void onDescribe() {
@@ -21,12 +21,18 @@ import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXCheckBox;
import com.jfoenix.controls.JFXListView;
import com.jfoenix.controls.JFXSpinner;
import com.jfoenix.controls.JFXTextField;
import javafx.animation.PauseTransition;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.*;
import javafx.util.Duration;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.download.VersionList;
@@ -55,14 +61,20 @@ import org.jackhuang.hmcl.ui.wizard.WizardPage;
import org.jackhuang.hmcl.util.Holder;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static org.jackhuang.hmcl.ui.FXUtils.ignoreEvent;
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.wrap;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.formatDateTime;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.StringUtils.isBlank;
public final class VersionsPage extends BorderPane implements WizardPage, Refreshable {
private final String gameVersion;
@@ -86,6 +98,11 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
private final VersionList<?> versionList;
private CompletableFuture<?> executor;
private final TransitionPane toolbarPane;
private final HBox searchBar;
private final JFXTextField searchField;
private boolean isSearching = false;
public VersionsPage(Navigation navigation, String title, String gameVersion, DownloadProvider downloadProvider, String libraryId, Runnable callback) {
this.title = title;
this.gameVersion = gameVersion;
@@ -136,6 +153,9 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
list.getStyleClass().add("jfx-list-view-float");
VBox.setVgrow(list, Priority.ALWAYS);
// ListViewBehavior would consume ESC pressed event, preventing us from handling it, so we ignore it here
ignoreEvent(list, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE);
centrePane.getContent().setAll(checkPane, list);
}
@@ -163,7 +183,15 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
this.setCenter(root);
versionList = downloadProvider.getVersionListById(libraryId);
if (versionList.hasType()) {
boolean hasType = versionList.hasType();
chkRelease.setManaged(hasType);
chkRelease.setVisible(hasType);
chkSnapshot.setManaged(hasType);
chkSnapshot.setVisible(hasType);
chkOld.setManaged(hasType);
chkOld.setVisible(hasType);
if (hasType) {
centrePane.getContent().setAll(checkPane, list);
} else {
centrePane.getContent().setAll(list);
@@ -187,6 +215,50 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
callback.run();
});
{
toolbarPane = new TransitionPane();
searchBar = new HBox();
searchBar.setAlignment(Pos.CENTER);
searchBar.setPadding(new Insets(0, 5, 0, 5));
searchField = new JFXTextField();
searchField.setPromptText(i18n("search"));
HBox.setHgrow(searchField, Priority.ALWAYS);
JFXButton closeSearchBar = new JFXButton();
closeSearchBar.getStyleClass().add("jfx-tool-bar-button");
closeSearchBar.setGraphic(wrap(SVG.CLOSE.createIcon(Theme.blackFill(), -1, -1)));
closeSearchBar.setOnAction(e -> {
toolbarPane.setContent(checkPane, ContainerAnimations.FADE);
isSearching = false;
searchField.clear();
list.getItems().setAll(loadVersions());
});
onEscPressed(searchField, closeSearchBar::fire);
searchBar.getChildren().setAll(searchField, closeSearchBar);
JFXButton searchButton = new JFXButton(i18n("search"));
searchButton.getStyleClass().add("jfx-tool-bar-button");
searchButton.setGraphic(wrap(SVG.MAGNIFY.createIcon(Theme.blackFill(), -1, -1)));
searchButton.setOnAction(e -> {
toolbarPane.setContent(searchBar, ContainerAnimations.FADE);
searchField.requestFocus();
});
checkPane.getChildren().add(checkPane.getChildren().size() - 1, searchButton);
centrePane.getContent().remove(checkPane);
toolbarPane.setContent(checkPane, ContainerAnimations.FADE);
centrePane.getContent().add(0, toolbarPane);
PauseTransition pause = new PauseTransition(Duration.millis(100));
pause.setOnFinished(e -> search());
searchField.textProperty().addListener((observable, oldValue, newValue) -> {
pause.setRate(1);
pause.playFromStart();
});
}
refresh();
}
@@ -265,6 +337,37 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
FXUtils.openLink("https://bmclapidoc.bangbang93.com");
}
private void search() {
isSearching = true;
String queryString = searchField.getText();
if (isBlank(queryString)) {
list.getItems().setAll(loadVersions());
} else {
list.getItems().clear();
Predicate<String> predicate;
if (queryString.startsWith("regex:")) {
try {
Pattern pattern = Pattern.compile(queryString.substring("regex:".length()));
predicate = s -> pattern.matcher(s).find();
} catch (Throwable e) {
LOG.warning("Illegal regular expression", e);
return;
}
} else {
String lowerQueryString = queryString.toLowerCase(Locale.ROOT);
predicate = s -> s.toLowerCase(Locale.ROOT).contains(lowerQueryString);
}
for (RemoteVersion version : loadVersions()) {
if (predicate.test(version.getSelfVersion())) {
list.getItems().add(version);
}
}
}
}
private static class RemoteVersionListCell extends ListCell<RemoteVersion> {
final IconedTwoLineListItem content = new IconedTwoLineListItem();
final RipplerContainer ripplerContainer = new RipplerContainer(content);
@@ -29,6 +29,7 @@ import org.jackhuang.hmcl.setting.ConfigHolder;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.VersionSetting;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.wizard.WizardController;
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
import org.jackhuang.hmcl.util.Lang;
@@ -129,17 +130,13 @@ public final class ExportWizardProvider implements WizardProvider {
if (bg.isDirectory())
zip.putDirectory(bg.toPath(), "bg");
File background_png = new File("background.png").getAbsoluteFile();
if (background_png.isFile())
zip.putFile(background_png, "background.png");
for (String extension : FXUtils.IMAGE_EXTENSIONS) {
String fileName = "background." + extension;
File background_jpg = new File("background.jpg").getAbsoluteFile();
if (background_jpg.isFile())
zip.putFile(background_jpg, "background.jpg");
File background_gif = new File("background.gif").getAbsoluteFile();
if (background_gif.isFile())
zip.putFile(background_gif, "background.gif");
File background = new File(fileName).getAbsoluteFile();
if (background.isFile())
zip.putFile(background, "background.png");
}
zip.putFile(launcherJar, launcherJar.getFileName().toString());
}
@@ -195,7 +192,8 @@ public final class ExportWizardProvider implements WizardProvider {
/* overrideJavaArgs */ true,
/* overrideConsole */ true,
/* overrideCommands */ true,
/* overrideWindow */ true
/* overrideWindow */ true,
/* iconKey */ null // TODO
), modpackFile);
}
@@ -48,6 +48,8 @@ import org.jackhuang.hmcl.ui.wizard.SinglePageWizardProvider;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.TaskCancellationAction;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.platform.Architecture;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import org.jackhuang.hmcl.util.platform.Platform;
import java.io.File;
@@ -214,6 +216,7 @@ public final class JavaDownloadDialog extends StackPane {
distributionBox.setItems(FXCollections.observableList(new ArrayList<>(distributions)));
FXUtils.onChange(packageTypeBox.getSelectionModel().selectedItemProperty(), packageType -> {
ObservableList<DiscoJavaRemoteVersion> versions;
if (packageType == null
|| currentJavaVersionList.get() == null
@@ -222,8 +225,26 @@ public final class JavaDownloadDialog extends StackPane {
return;
}
DiscoJavaRemoteVersion oldVersion = remoteVersionBox.getSelectionModel().getSelectedItem();
remoteVersionBox.setItems(versions);
if (oldVersion != null) {
for (int i = 0; i < versions.size(); i++) {
DiscoJavaRemoteVersion version = versions.get(i);
if (Objects.equals(version.getDistributionVersion(), oldVersion.getDistributionVersion())) {
remoteVersionBox.getSelectionModel().select(i);
return;
}
}
for (int i = 0; i < versions.size(); i++) {
DiscoJavaRemoteVersion version = versions.get(i);
if (version.getJdkVersion() == oldVersion.getJdkVersion()) {
remoteVersionBox.getSelectionModel().select(i);
return;
}
}
}
for (int i = 0; i < versions.size(); i++) {
DiscoJavaRemoteVersion version = versions.get(i);
if (version.getJdkVersion() == GameJavaVersion.LATEST.getMajorVersion()) {
@@ -289,6 +310,11 @@ public final class JavaDownloadDialog extends StackPane {
setHeading(new Label(i18n("java.download")));
setBody(body);
setActions(warningLabel, downloadButtonPane, cancelButton);
if (platform.getOperatingSystem() == OperatingSystem.LINUX && platform.getArchitecture() == Architecture.RISCV64) {
JFXHyperlink hyperlink = new JFXHyperlink(i18n("java.download.banshanjdk-8"));
hyperlink.setExternalLink("https://www.zthread.cn/#product");
getActions().add(0, hyperlink);
}
}
private void onDownload() {
@@ -35,7 +35,6 @@ import org.jackhuang.hmcl.ui.versions.VersionSettingsPage;
import java.util.Locale;
import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class LauncherSettingsPage extends DecoratorAnimatedPage implements DecoratorPage, PageAware {
@@ -69,61 +68,19 @@ public class LauncherSettingsPage extends DecoratorAnimatedPage implements Decor
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE);
});
{
AdvancedListBox sideBar = new AdvancedListBox()
.addNavigationDrawerItem(settingsItem -> {
settingsItem.setTitle(i18n("settings.type.global.manage"));
settingsItem.setLeftGraphic(wrap(SVG.GAMEPAD));
settingsItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(gameTab));
settingsItem.setOnAction(e -> tab.select(gameTab));
})
.addNavigationDrawerItem(javaItem -> {
javaItem.setTitle(i18n("java.management"));
javaItem.setLeftGraphic(wrap(SVG.WRENCH_OUTLINE));
javaItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(javaManagementTab));
javaItem.setOnAction(e -> tab.select(javaManagementTab));
})
.startCategory(i18n("launcher").toUpperCase(Locale.ROOT))
.addNavigationDrawerItem(settingsItem -> {
settingsItem.setTitle(i18n("settings.launcher.general"));
settingsItem.setLeftGraphic(wrap(SVG.APPLICATION_OUTLINE));
settingsItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(settingsTab));
settingsItem.setOnAction(e -> tab.select(settingsTab));
})
.addNavigationDrawerItem(personalizationItem -> {
personalizationItem.setTitle(i18n("settings.launcher.appearance"));
personalizationItem.setLeftGraphic(wrap(SVG.STYLE_OUTLINE));
personalizationItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(personalizationTab));
personalizationItem.setOnAction(e -> tab.select(personalizationTab));
})
.addNavigationDrawerItem(downloadItem -> {
downloadItem.setTitle(i18n("download"));
downloadItem.setLeftGraphic(wrap(SVG.DOWNLOAD_OUTLINE));
downloadItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(downloadTab));
downloadItem.setOnAction(e -> tab.select(downloadTab));
})
.startCategory(i18n("help").toUpperCase(Locale.ROOT))
.addNavigationDrawerItem(helpItem -> {
helpItem.setTitle(i18n("help"));
helpItem.setLeftGraphic(wrap(SVG.HELP_CIRCLE_OUTLINE));
helpItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(helpTab));
helpItem.setOnAction(e -> tab.select(helpTab));
})
.addNavigationDrawerItem(feedbackItem -> {
feedbackItem.setTitle(i18n("feedback"));
feedbackItem.setLeftGraphic(wrap(SVG.MESSAGE_ALERT_OUTLINE));
feedbackItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(feedbackTab));
feedbackItem.setOnAction(e -> tab.select(feedbackTab));
})
.addNavigationDrawerItem(aboutItem -> {
aboutItem.setTitle(i18n("about"));
aboutItem.setLeftGraphic(wrap(SVG.INFORMATION_OUTLINE));
aboutItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(aboutTab));
aboutItem.setOnAction(e -> tab.select(aboutTab));
});
FXUtils.setLimitWidth(sideBar, 200);
setLeft(sideBar);
}
AdvancedListBox sideBar = new AdvancedListBox()
.addNavigationDrawerTab(tab, gameTab, i18n("settings.type.global.manage"), SVG.GAMEPAD)
.addNavigationDrawerTab(tab, javaManagementTab, i18n("java.management"), SVG.WRENCH_OUTLINE)
.startCategory(i18n("launcher").toUpperCase(Locale.ROOT))
.addNavigationDrawerTab(tab, settingsTab, i18n("settings.launcher.general"), SVG.APPLICATION_OUTLINE)
.addNavigationDrawerTab(tab, personalizationTab, i18n("settings.launcher.appearance"), SVG.STYLE_OUTLINE)
.addNavigationDrawerTab(tab, downloadTab, i18n("download"), SVG.DOWNLOAD_OUTLINE)
.startCategory(i18n("help").toUpperCase(Locale.ROOT))
.addNavigationDrawerTab(tab, helpTab, i18n("help"), SVG.HELP_CIRCLE_OUTLINE)
.addNavigationDrawerTab(tab, feedbackTab, i18n("feedback"), SVG.MESSAGE_ALERT_OUTLINE)
.addNavigationDrawerTab(tab, aboutTab, i18n("about"), SVG.INFORMATION_OUTLINE);
FXUtils.setLimitWidth(sideBar, 200);
setLeft(sideBar);
setCenter(transitionPane);
}
@@ -28,13 +28,16 @@ import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.TextFlow;
import javafx.util.Duration;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.game.Version;
@@ -47,7 +50,6 @@ import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
import org.jackhuang.hmcl.ui.animation.TransitionPane;
import org.jackhuang.hmcl.ui.construct.AnnouncementCard;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
import org.jackhuang.hmcl.ui.construct.PopupMenu;
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
@@ -102,23 +104,49 @@ public final class MainPage extends StackPane implements DecoratorPage {
setPadding(new Insets(20));
if (Metadata.isNightly() || (Metadata.isDev() && !Objects.equals(Metadata.VERSION, config().getShownTips().get(ANNOUNCEMENT)))) {
AnnouncementCard announcementCard = null;
String title;
String content;
if (Metadata.isNightly()) {
announcementCard = new AnnouncementCard(i18n("update.channel.nightly.title"), i18n("update.channel.nightly.hint"), null);
} else if (Metadata.isDev()) {
announcementCard = new AnnouncementCard(i18n("update.channel.dev.title"), i18n("update.channel.dev.hint"), this::hideAnnouncementPane);
title = i18n("update.channel.nightly.title");
content = i18n("update.channel.nightly.hint");
} else {
title = i18n("update.channel.dev.title");
content = i18n("update.channel.dev.hint");
}
if (announcementCard != null) {
VBox announcementBox = new VBox(16);
announcementBox.getChildren().add(announcementCard);
VBox announcementCard = new VBox();
announcementPane = new TransitionPane();
announcementPane.setContent(announcementBox, ContainerAnimations.NONE);
BorderPane titleBar = new BorderPane();
titleBar.getStyleClass().add("title");
titleBar.setLeft(new Label(title));
getChildren().add(announcementPane);
}
Node hideNode = SVG.CLOSE.createIcon(Theme.blackFill(), 20, 20);
hideNode.setCursor(Cursor.HAND);
titleBar.setRight(hideNode);
FXUtils.onClicked(hideNode, () -> {
if (announcementPane != null) {
if (Metadata.isDev()) {
config().getShownTips().put(ANNOUNCEMENT, Metadata.VERSION);
}
announcementPane.setContent(new StackPane(), ContainerAnimations.FADE);
}
});
TextFlow body = FXUtils.segmentToTextFlow(content, Controllers::onHyperlinkAction);
body.setLineSpacing(4);
announcementCard.getChildren().setAll(titleBar, body);
announcementCard.setSpacing(16);
announcementCard.getStyleClass().addAll("card", "announcement");
VBox announcementBox = new VBox(16);
announcementBox.getChildren().add(announcementCard);
announcementPane = new TransitionPane();
announcementPane.setContent(announcementBox, ContainerAnimations.NONE);
getChildren().add(announcementPane);
}
updatePane = new StackPane();
@@ -297,13 +325,6 @@ public final class MainPage extends StackPane implements DecoratorPage {
showUpdate.set(false);
}
public void hideAnnouncementPane() {
if (announcementPane != null) {
config().getShownTips().put(ANNOUNCEMENT, Metadata.VERSION);
announcementPane.setContent(new StackPane(), ContainerAnimations.FADE);
}
}
@Override
public ReadOnlyObjectWrapper<State> stateProperty() {
return state;
@@ -106,11 +106,13 @@ public class PersonalizationPage extends StackPane {
backgroundSublist.setHasSubtitle(true);
backgroundItem.loadChildren(Arrays.asList(
new MultiFileItem.Option<>(i18n("launcher.background.default"), EnumBackgroundImage.DEFAULT),
new MultiFileItem.Option<>(i18n("launcher.background.default"), EnumBackgroundImage.DEFAULT)
.setTooltip(i18n("launcher.background.default.tooltip")),
new MultiFileItem.Option<>(i18n("launcher.background.classic"), EnumBackgroundImage.CLASSIC),
new MultiFileItem.Option<>(i18n("launcher.background.translucent"), EnumBackgroundImage.TRANSLUCENT),
new MultiFileItem.FileOption<>(i18n("settings.custom"), EnumBackgroundImage.CUSTOM)
.setChooserTitle(i18n("launcher.background.choose"))
.addExtensionFilter(FXUtils.getImageExtensionFilter())
.bindBidirectional(config().backgroundImageProperty()),
new MultiFileItem.StringOption<>(i18n("launcher.background.network"), EnumBackgroundImage.NETWORK)
.setValidators(new URLValidator(true))
@@ -27,6 +27,8 @@ import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.SkinBase;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
@@ -38,6 +40,7 @@ import org.jackhuang.hmcl.ui.construct.SpinnerPane;
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
import org.jackhuang.hmcl.util.StringUtils;
import static org.jackhuang.hmcl.ui.FXUtils.ignoreEvent;
import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
@@ -110,6 +113,9 @@ class DatapackListPageSkin extends SkinBase<DatapackListPage> {
listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
Bindings.bindContent(listView.getItems(), skinnable.getItems());
// ListViewBehavior would consume ESC pressed event, preventing us from handling it, so we ignore it here
ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE);
center.setContent(listView);
root.setCenter(center);
}
@@ -36,6 +36,8 @@ import javafx.scene.control.Label;
import javafx.scene.control.Skin;
import javafx.scene.control.SkinBase;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.*;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.mod.RemoteMod;
@@ -64,6 +66,7 @@ import java.util.Locale;
import java.util.Optional;
import java.util.stream.Collectors;
import static org.jackhuang.hmcl.ui.FXUtils.ignoreEvent;
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.selectedItemPropertyFor;
@@ -493,6 +496,9 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
RemoteMod selectedItem = listView.getSelectionModel().getSelectedItem();
Controllers.navigate(new DownloadPage(getSkinnable(), selectedItem, getSkinnable().getProfileVersion(), getSkinnable().callback));
});
// ListViewBehavior would consume ESC pressed event, preventing us from handling it, so we ignore it here
ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE);
listView.setCellFactory(x -> new FloatListCell<RemoteMod>(listView) {
TwoLineListItem content = new TwoLineListItem();
ImageView imageView = new ImageView();
@@ -55,13 +55,11 @@ import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class DownloadPage extends Control implements DecoratorPage {
@@ -380,13 +378,14 @@ public class DownloadPage extends Control implements DecoratorPage {
HBox descPane = new HBox(8);
descPane.setPadding(new Insets(0, 8, 0, 8));
descPane.setAlignment(Pos.CENTER_LEFT);
descPane.setMouseTransparent(true);
{
StackPane graphicPane = new StackPane();
TwoLineListItem content = new TwoLineListItem();
HBox.setHgrow(content, Priority.ALWAYS);
content.setTitle(dataItem.getName());
content.setSubtitle(FORMATTER.format(dataItem.getDatePublished()));
content.setSubtitle(I18n.formatDateTime(dataItem.getDatePublished()));
switch (dataItem.getVersionType()) {
case Alpha:
@@ -445,7 +444,7 @@ public class DownloadPage extends Control implements DecoratorPage {
VBox box = new VBox(8);
box.setPadding(new Insets(8));
ModItem modItem = new ModItem(version, selfPage);
FXUtils.onClicked(modItem, () -> fireEvent(new DialogCloseEvent()));
modItem.setMouseTransparent(true); // Item is displayed for info, clicking shouldn't open the dialog again
box.getChildren().setAll(modItem);
SpinnerPane spinnerPane = new SpinnerPane();
ScrollPane scrollPane = new ScrollPane();
@@ -488,6 +487,8 @@ public class DownloadPage extends Control implements DecoratorPage {
this.prefWidthProperty().bind(BindingMapping.of(Controllers.getStage().widthProperty()).map(w -> w.doubleValue() * 0.7));
this.prefHeightProperty().bind(BindingMapping.of(Controllers.getStage().heightProperty()).map(w -> w.doubleValue() * 0.7));
onEscPressed(this, cancelButton::fire);
}
private void loadDependencies(RemoteMod.Version version, DownloadPage selfPage, SpinnerPane spinnerPane, ComponentList dependenciesList) {
@@ -524,8 +525,6 @@ public class DownloadPage extends Control implements DecoratorPage {
}
}
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL).withLocale(Locale.getDefault()).withZone(ZoneId.systemDefault());
public interface DownloadCallback {
void download(Profile profile, @Nullable String version, RemoteMod.Version file);
}
@@ -87,26 +87,10 @@ public class GameListPage extends DecoratorAnimatedPage implements DecoratorPage
}
AdvancedListBox bottomLeftCornerList = new AdvancedListBox()
.addNavigationDrawerItem(installNewGameItem -> {
installNewGameItem.setTitle(i18n("install.new_game"));
installNewGameItem.setLeftGraphic(VersionPage.wrap(SVG.PLUS_CIRCLE_OUTLINE));
installNewGameItem.setOnAction(e -> Versions.addNewGame());
})
.addNavigationDrawerItem(installModpackItem -> {
installModpackItem.setTitle(i18n("install.modpack"));
installModpackItem.setLeftGraphic(VersionPage.wrap(SVG.PACK));
installModpackItem.setOnAction(e -> Versions.importModpack());
})
.addNavigationDrawerItem(refreshItem -> {
refreshItem.setTitle(i18n("button.refresh"));
refreshItem.setLeftGraphic(VersionPage.wrap(SVG.REFRESH));
refreshItem.setOnAction(e -> gameList.refreshList());
})
.addNavigationDrawerItem(globalManageItem -> {
globalManageItem.setTitle(i18n("settings.type.global.manage"));
globalManageItem.setLeftGraphic(VersionPage.wrap(SVG.GEAR_OUTLINE));
globalManageItem.setOnAction(e -> modifyGlobalGameSettings());
});
.addNavigationDrawerItem(i18n("install.new_game"), SVG.PLUS_CIRCLE_OUTLINE, Versions::addNewGame)
.addNavigationDrawerItem(i18n("install.modpack"), SVG.PACK, Versions::importModpack)
.addNavigationDrawerItem(i18n("button.refresh"), SVG.REFRESH, gameList::refreshList)
.addNavigationDrawerItem(i18n("settings.type.global.manage"), SVG.GEAR_OUTLINE, this::modifyGlobalGameSettings);
FXUtils.setLimitHeight(bottomLeftCornerList, 40 * 4 + 12 * 2);
setLeft(pane, bottomLeftCornerList);
}
@@ -31,6 +31,8 @@ import javafx.scene.control.SelectionMode;
import javafx.scene.control.SkinBase;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
@@ -70,6 +72,7 @@ import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static org.jackhuang.hmcl.ui.FXUtils.ignoreEvent;
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2;
import static org.jackhuang.hmcl.util.Lang.mapOf;
@@ -131,6 +134,8 @@ class ModListPageSkin extends SkinBase<ModListPage> {
Bindings.bindContent(listView.getItems(), getSkinnable().getItems());
});
onEscPressed(searchField, closeSearchBar::fire);
searchBar.getChildren().setAll(searchField, closeSearchBar);
// Toolbar Normal
@@ -168,6 +173,16 @@ class ModListPageSkin extends SkinBase<ModListPage> {
changeToolbar(toolbarSelecting);
});
root.getContent().add(toolbarPane);
// Clear selection when pressing ESC
root.addEventHandler(KeyEvent.KEY_PRESSED, e -> {
if (e.getCode() == KeyCode.ESCAPE) {
if (listView.getSelectionModel().getSelectedItem() != null) {
listView.getSelectionModel().clearSelection();
e.consume();
}
}
});
}
{
@@ -194,6 +209,10 @@ class ModListPageSkin extends SkinBase<ModListPage> {
}
});
// ListViewBehavior would consume ESC pressed event, preventing us from handling it
// So we ignore it here
ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE);
center.setContent(listView);
root.getContent().add(center);
}
@@ -72,7 +72,7 @@ public class VersionIconDialog extends DialogPane {
private void exploreIcon() {
FileChooser chooser = new FileChooser();
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("extension.png"), "*.png", "*.jpg", "*.bmp", "*.gif"));
chooser.getExtensionFilters().add(FXUtils.getImageExtensionFilter());
File selectedFile = chooser.showOpenDialog(Controllers.getStage());
if (selectedFile != null) {
try {
@@ -308,25 +308,14 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
);
AdvancedListBox toolbar = new AdvancedListBox()
.addNavigationDrawerItem(upgradeItem -> {
upgradeItem.setTitle(i18n("version.update"));
upgradeItem.setLeftGraphic(wrap(SVG.UPDATE));
.addNavigationDrawerItem(i18n("version.update"), SVG.UPDATE, control::updateGame, upgradeItem -> {
upgradeItem.visibleProperty().bind(control.currentVersionUpgradable);
upgradeItem.setOnAction(e -> control.updateGame());
})
.addNavigationDrawerItem(testGameItem -> {
testGameItem.setTitle(i18n("version.launch.test"));
testGameItem.setLeftGraphic(wrap(SVG.ROCKET_LAUNCH_OUTLINE));
testGameItem.setOnAction(e -> control.testGame());
})
.addNavigationDrawerItem(browseMenuItem -> {
browseMenuItem.setTitle(i18n("settings.game.exploration"));
browseMenuItem.setLeftGraphic(wrap(SVG.FOLDER_OUTLINE));
.addNavigationDrawerItem(i18n("version.launch.test"), SVG.ROCKET_LAUNCH_OUTLINE, control::testGame)
.addNavigationDrawerItem(i18n("settings.game.exploration"), SVG.FOLDER_OUTLINE, null, browseMenuItem -> {
browseMenuItem.setOnAction(e -> browsePopup.show(browseMenuItem, JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.LEFT, browseMenuItem.getWidth(), 0));
})
.addNavigationDrawerItem(managementItem -> {
managementItem.setTitle(i18n("settings.game.management"));
managementItem.setLeftGraphic(wrap(SVG.WRENCH_OUTLINE));
.addNavigationDrawerItem(i18n("settings.game.management"), SVG.WRENCH_OUTLINE, null, managementItem -> {
managementItem.setOnAction(e -> managementPopup.show(managementItem, JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.LEFT, managementItem.getWidth(), 0));
});
toolbar.getStyleClass().add("advanced-list-box-clear-padding");
@@ -57,10 +57,27 @@ import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
import static org.jackhuang.hmcl.util.Lang.getTimer;
import static org.jackhuang.hmcl.util.Pair.pair;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public final class VersionSettingsPage extends StackPane implements DecoratorPage, VersionPage.VersionLoadable, PageAware {
private static final ObjectProperty<OperatingSystem.PhysicalMemoryStatus> memoryStatus = new SimpleObjectProperty<>(OperatingSystem.PhysicalMemoryStatus.INVALID);
private static TimerTask memoryStatusUpdateTask;
private static void initMemoryStatusUpdateTask() {
FXUtils.checkFxUserThread();
if (memoryStatusUpdateTask != null)
return;
memoryStatusUpdateTask = new TimerTask() {
@Override
public void run() {
Platform.runLater(() -> memoryStatus.set(OperatingSystem.getPhysicalMemoryStatus()));
}
};
getTimer().scheduleAtFixedRate(memoryStatusUpdateTask, 0, 1000);
}
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(new State("", null, false, false, false));
private AdvancedVersionSettingPage advancedVersionSettingPage;
@@ -92,7 +109,8 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
private final ImagePickerItem iconPickerItem;
private final ChangeListener<Collection<JavaRuntime>> javaListChangeListener;
private final InvalidationListener specificSettingsListener;
private final InvalidationListener usesGlobalListener;
private final ChangeListener<Boolean> specificSettingsListener;
private final InvalidationListener javaListener = any -> initJavaSubtitle();
private boolean updatingJavaSetting = false;
private boolean updatingSelectedJava = false;
@@ -101,7 +119,6 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
private final BooleanProperty navigateToSpecificSettings = new SimpleBooleanProperty(false);
private final BooleanProperty enableSpecificSettings = new SimpleBooleanProperty(false);
private final IntegerProperty maxMemory = new SimpleIntegerProperty();
private final ObjectProperty<OperatingSystem.PhysicalMemoryStatus> memoryStatus = new SimpleObjectProperty<>(OperatingSystem.PhysicalMemoryStatus.INVALID);
private final BooleanProperty modpack = new SimpleBooleanProperty();
public VersionSettingsPage(boolean globalSetting) {
@@ -185,7 +202,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
javaCustomOption = new MultiFileItem.FileOption<Pair<JavaVersionType, JavaRuntime>>(i18n("settings.custom"), pair(JavaVersionType.CUSTOM, null))
.setChooserTitle(i18n("settings.game.java_directory.choose"));
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS)
javaCustomOption.getExtensionFilters().add(new FileChooser.ExtensionFilter("Java", "java.exe"));
javaCustomOption.addExtensionFilter(new FileChooser.ExtensionFilter("Java", "java.exe"));
javaListChangeListener = FXUtils.onWeakChangeAndOperate(JavaManager.getAllJavaProperty(), allJava -> {
List<MultiFileItem.Option<Pair<JavaVersionType, JavaRuntime>>> options = new ArrayList<>();
@@ -235,6 +252,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
VBox.setMargin(chkAutoAllocate, new Insets(0, 0, 8, 5));
HBox lowerBoundPane = new HBox(8);
lowerBoundPane.setStyle("-fx-view-order: -1;"); // prevent the indicator from being covered by the progress bar
lowerBoundPane.setAlignment(Pos.CENTER);
VBox.setMargin(lowerBoundPane, new Insets(0, 0, 0, 16));
{
@@ -443,22 +461,8 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
rootPane.getChildren().add(componentList);
initialize();
specificSettingsListener = any -> enableSpecificSettings.set(!lastVersionSetting.isUsesGlobal());
addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onDecoratorPageNavigating);
cboLauncherVisibility.getItems().setAll(LauncherVisibility.values());
cboLauncherVisibility.setConverter(stringConverter(e -> i18n("settings.advanced.launcher_visibility." + e.name().toLowerCase(Locale.ROOT))));
cboProcessPriority.getItems().setAll(ProcessPriority.values());
cboProcessPriority.setConverter(stringConverter(e -> i18n("settings.advanced.process_priority." + e.name().toLowerCase(Locale.ROOT))));
}
private void initialize() {
memoryStatus.set(OperatingSystem.getPhysicalMemoryStatus());
enableSpecificSettings.addListener((a, b, newValue) -> {
usesGlobalListener = any -> enableSpecificSettings.set(!lastVersionSetting.isUsesGlobal());
specificSettingsListener = (a, b, newValue) -> {
if (versionId == null) return;
// do not call versionSettings.setUsesGlobal(true/false)
@@ -470,9 +474,20 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
profile.getRepository().globalizeVersionSetting(versionId);
Platform.runLater(() -> loadVersion(profile, versionId));
});
};
addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onDecoratorPageNavigating);
cboLauncherVisibility.getItems().setAll(LauncherVisibility.values());
cboLauncherVisibility.setConverter(stringConverter(e -> i18n("settings.advanced.launcher_visibility." + e.name().toLowerCase(Locale.ROOT))));
cboProcessPriority.getItems().setAll(ProcessPriority.values());
cboProcessPriority.setConverter(stringConverter(e -> i18n("settings.advanced.process_priority." + e.name().toLowerCase(Locale.ROOT))));
memoryStatus.set(OperatingSystem.getPhysicalMemoryStatus());
componentList.disableProperty().bind(enableSpecificSettings.not());
initMemoryStatusUpdateTask();
}
@Override
@@ -519,7 +534,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
FXUtils.unbindEnum(cboLauncherVisibility);
FXUtils.unbindEnum(cboProcessPriority);
lastVersionSetting.usesGlobalProperty().removeListener(specificSettingsListener);
lastVersionSetting.usesGlobalProperty().removeListener(usesGlobalListener);
lastVersionSetting.javaVersionTypeProperty().removeListener(javaListener);
lastVersionSetting.javaDirProperty().removeListener(javaListener);
lastVersionSetting.defaultJavaPathPropertyProperty().removeListener(javaListener);
@@ -528,6 +543,8 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
gameDirItem.selectedDataProperty().unbindBidirectional(lastVersionSetting.gameDirTypeProperty());
gameDirSublist.subtitleProperty().unbind();
enableSpecificSettings.removeListener(specificSettingsListener);
if (advancedVersionSettingPage != null) {
advancedVersionSettingPage.unbindProperties();
advancedVersionSettingPage = null;
@@ -552,9 +569,10 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
FXUtils.bindEnum(cboLauncherVisibility, versionSetting.launcherVisibilityProperty());
FXUtils.bindEnum(cboProcessPriority, versionSetting.processPriorityProperty());
versionSetting.usesGlobalProperty().addListener(specificSettingsListener);
if (versionId != null)
enableSpecificSettings.set(!versionSetting.isUsesGlobal());
versionSetting.usesGlobalProperty().addListener(usesGlobalListener);
enableSpecificSettings.addListener(specificSettingsListener);
javaItem.setToggleSelectedListener(newValue -> {
if (javaItem.getSelectedData() == null || updatingSelectedJava)
@@ -83,7 +83,7 @@ public final class NativePatcher {
final boolean useNativeOpenAL = settings.isUseNativeOpenAL();
if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() && (useNativeGLFW || useNativeOpenAL)
&& GameVersionNumber.compare(gameVersion, "1.19") >= 0) {
&& gameVersion != null && GameVersionNumber.compare(gameVersion, "1.19") >= 0) {
version = version.setLibraries(version.getLibraries().stream()
.filter(library -> {
@@ -153,6 +153,6 @@ public final class NativePatcher {
}
public static Library getMesaLoader(JavaRuntime javaVersion, Renderer renderer) {
return getNatives(javaVersion.getPlatform()).get(renderer == Renderer.LLVMPIPE ? "software-renderer-loader" : "mesa-loader");
return getNatives(javaVersion.getPlatform()).get("mesa-loader");
}
}
@@ -0,0 +1,124 @@
// Copy from javafx.swing
/*
* Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.jackhuang.hmcl.util;
import javafx.scene.image.*;
import javafx.scene.image.Image;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.awt.image.SampleModel;
import java.awt.image.SinglePixelPackedSampleModel;
import java.nio.IntBuffer;
/**
* This class provides utility methods for converting data types between
* Swing/AWT and JavaFX formats.
*
* @since JavaFX 2.2
*/
public final class SwingFXUtils {
private SwingFXUtils() {
}
/**
* Snapshots the specified {@link BufferedImage} and stores a copy of
* its pixels into a JavaFX {@link Image} object, creating a new
* object if needed.
* The returned {@code Image} will be a static snapshot of the state
* of the pixels in the {@code BufferedImage} at the time the method
* completes. Further changes to the {@code BufferedImage} will not
* be reflected in the {@code Image}.
* <p>
* The optional JavaFX {@link WritableImage} parameter may be reused
* to store the copy of the pixels.
* A new {@code Image} will be created if the supplied object is null,
* is too small or of a type which the image pixels cannot be easily
* converted into.
*
* @param bimg the {@code BufferedImage} object to be converted
* @param wimg an optional {@code WritableImage} object that can be
* used to store the returned pixel data
* @return an {@code Image} object representing a snapshot of the
* current pixels in the {@code BufferedImage}.
* @since JavaFX 2.2
*/
public static WritableImage toFXImage(BufferedImage bimg, WritableImage wimg) {
int bw = bimg.getWidth();
int bh = bimg.getHeight();
switch (bimg.getType()) {
case BufferedImage.TYPE_INT_ARGB:
case BufferedImage.TYPE_INT_ARGB_PRE:
break;
default:
BufferedImage converted =
new BufferedImage(bw, bh, BufferedImage.TYPE_INT_ARGB_PRE);
Graphics2D g2d = converted.createGraphics();
g2d.drawImage(bimg, 0, 0, null);
g2d.dispose();
bimg = converted;
break;
}
// assert(bimg.getType == TYPE_INT_ARGB[_PRE]);
if (wimg != null) {
int iw = (int) wimg.getWidth();
int ih = (int) wimg.getHeight();
if (iw < bw || ih < bh) {
wimg = null;
} else if (bw < iw || bh < ih) {
int[] empty = new int[iw];
PixelWriter pw = wimg.getPixelWriter();
PixelFormat<IntBuffer> pf = PixelFormat.getIntArgbPreInstance();
if (bw < iw) {
pw.setPixels(bw, 0, iw - bw, bh, pf, empty, 0, 0);
}
if (bh < ih) {
pw.setPixels(0, bh, iw, ih - bh, pf, empty, 0, 0);
}
}
}
if (wimg == null) {
wimg = new WritableImage(bw, bh);
}
PixelWriter pw = wimg.getPixelWriter();
DataBufferInt db = (DataBufferInt) bimg.getRaster().getDataBuffer();
int[] data = db.getData();
int offset = bimg.getRaster().getDataBuffer().getOffset();
int scan = 0;
SampleModel sm = bimg.getRaster().getSampleModel();
if (sm instanceof SinglePixelPackedSampleModel) {
scan = ((SinglePixelPackedSampleModel) sm).getScanlineStride();
}
PixelFormat<IntBuffer> pf = (bimg.isAlphaPremultiplied() ?
PixelFormat.getIntArgbPreInstance() :
PixelFormat.getIntArgbInstance());
pw.setPixels(0, 0, bw, bh, pf, data, offset, scan);
return wimg;
}
}
@@ -77,7 +77,7 @@ public final class I18n {
public static String formatDateTime(Instant instant) {
DateTimeFormatter formatter = dateTimeFormatter;
if (formatter == null) {
formatter = dateTimeFormatter = DateTimeFormatter.ofPattern(getResourceBundle().getString("world.time")).withZone(ZoneId.systemDefault());
formatter = dateTimeFormatter = DateTimeFormatter.ofPattern(getResourceBundle().getString("datetime.format")).withZone(ZoneId.systemDefault());
}
return formatter.format(instant);
+66 -30
View File
@@ -33,6 +33,8 @@ case "$(uname -m)" in
_HMCL_ARCH="arm64";;
arm|arm32|aarch32|armv7*)
_HMCL_ARCH="arm32";;
riscv64)
_HMCL_ARCH="riscv64";;
loongarch64)
_HMCL_ARCH="loongarch64";;
*)
@@ -56,17 +58,42 @@ else
_HMCL_VM_OPTIONS="-XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=15"
fi
function show_warning_console() {
echo -e "\033[1;31m$1\033[0m" >&2
}
function show_warning_dialog() {
if [ -n "$3" ]; then
if [ -n "$(command -v xdg-open)" ]; then
if [ -n "$(command -v zenity)" ]; then
zenity --question --title="$1" --text="$2" && xdg-open "$3"
elif [ -n "$(command -v kdialog)" ]; then
kdialog --title "$1" --warningyesno "$2" && xdg-open "$3"
fi
fi
else
if [ -n "$(command -v zenity)" ]; then
zenity --info --title="$1" --text="$2"
elif [ -n "$(command -v kdialog)" ]; then
kdialog --title "$1" --msgbox "$2"
fi
fi
}
function show_warning() {
show_warning_console "$1: $2"
show_warning_dialog "$1" "$2"
}
# First, find Java in HMCL_JAVA_HOME
if [ -n "${HMCL_JAVA_HOME+x}" ]; then
if [ -x "$HMCL_JAVA_HOME/bin/$_HMCL_JAVA_EXE_NAME" ]; then
exec "$HMCL_JAVA_HOME/bin/$_HMCL_JAVA_EXE_NAME" $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH"
else
if [ "$_HMCL_USE_CHINESE" == true ]; then
echo "环境变量 HMCL_JAVA_HOME 的值无效,请设置为合法的 Java 路径。" 1>&2
echo "你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。" 1>&2
show_warning "错误" "环境变量 HMCL_JAVA_HOME 的值无效,请设置为合法的 Java 路径。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。"
else
echo "The value of the environment variable HMCL_JAVA_HOME is invalid, please set it to a valid Java path." 1>&2
echo "You can visit the https://docs.hmcl.net/help.html page for help." 1>&2
show_warning "Error" "The value of the environment variable HMCL_JAVA_HOME is invalid, please set it to a valid Java path.\nYou can visit the https://docs.hmcl.net/help.html page for help."
fi
exit 1
fi
@@ -97,6 +124,11 @@ case "$_HMCL_ARCH" in
exec "$_HMCL_DIR/jre-arm32/bin/$_HMCL_JAVA_EXE_NAME" $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH"
fi
;;
riscv64)
if [ -x "$_HMCL_DIR/jre-riscv64/bin/$_HMCL_JAVA_EXE_NAME" ]; then
exec "$_HMCL_DIR/jre-riscv64/bin/$_HMCL_JAVA_EXE_NAME" $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH"
fi
;;
loongarch64)
if [ -x "$_HMCL_DIR/jre-loongarch64/bin/$_HMCL_JAVA_EXE_NAME" ]; then
exec "$_HMCL_DIR/jre-loongarch64/bin/$_HMCL_JAVA_EXE_NAME" $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH"
@@ -114,37 +146,41 @@ if [ -x "$(command -v $_HMCL_JAVA_EXE_NAME)" ]; then
exec $_HMCL_JAVA_EXE_NAME $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH"
fi
# Java not found
if [ "$_HMCL_OS" == "osx" ]; then
_HMCL_DOWNLOAD_PAGE_OS="macos"
else
_HMCL_DOWNLOAD_PAGE_OS="$_HMCL_OS"
if [ "$_HMCL_OS" == "freebsd" ] && [ -x "$(command -v javavm)" ]; then
exec javavm $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH"
fi
# Java not found
case "$_HMCL_OS-$_HMCL_ARCH" in
windows-x86|windows-x86_64|windows-arm64|linux-x86|linux-x86_64|linux-arm32|linux-arm64|linux-loongarch64|macos-x86_64|macos-arm64)
if [ "$_HMCL_USE_CHINESE" == true ]; then
echo "运行 HMCL 需要 Java 运行时环境,请安装 Java 并设置环境变量后重试。" 1>&2
echo "https://docs.hmcl.net/downloads/$_HMCL_DOWNLOAD_PAGE_OS/$_HMCL_HMCL_ARCH.html" 1>&2
echo "你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。" 1>&2
else
echo "The Java runtime environment is required to run HMCL. " 1>&2
echo "Please install Java and set the environment variables and try again." 1>&2
echo "https://docs.hmcl.net/downloads/$_HMCL_DOWNLOAD_PAGE_OS/$_HMCL_HMCL_ARCH.html" 1>&2
echo "You can visit the https://docs.hmcl.net/help.html page for help." 1>&2
fi
windows-x86|windows-x86_64|windows-arm64)
_HMCL_JAVA_DOWNLOAD_PAGE="https://docs.hmcl.net/downloads/windows/$_HMCL_ARCH.html"
;;
*)
if [ "$_HMCL_USE_CHINESE" == true ]; then
echo "运行 HMCL 需要 Java 运行时环境,请安装 Java 并设置环境变量后重试。" 1>&2
echo "你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。" 1>&2
else
echo "The Java runtime environment is required to run HMCL. " 1>&2
echo "Please install Java and set the environment variables and try again." 1>&2
echo "You can visit the https://docs.hmcl.net/help.html page for help." 1>&2
fi
linux-x86|linux-x86_64|linux-arm64|linux-arm32|linux-riscv64|linux-loongarch64)
_HMCL_JAVA_DOWNLOAD_PAGE="https://docs.hmcl.net/downloads/linux/$_HMCL_ARCH.html"
;;
osx-x86_64|osx-arm64)
_HMCL_JAVA_DOWNLOAD_PAGE="https://docs.hmcl.net/downloads/macos/$_HMCL_ARCH.html"
;;
esac
if [ "$_HMCL_USE_CHINESE" == true ]; then
_HMCL_WARNING_MESSAGE="运行 HMCL 需要 Java 运行时环境,请安装 Java 并设置环境变量后重试。"
if [ -n "$_HMCL_JAVA_DOWNLOAD_PAGE" ]; then
show_warning_console "错误: $_HMCL_WARNING_MESSAGE\n你可以前往此处下载:\n$_HMCL_JAVA_DOWNLOAD_PAGE"
show_warning_dialog "错误" "$_HMCL_WARNING_MESSAGE\n\n是否要前往 Java 下载页面?" "$_HMCL_JAVA_DOWNLOAD_PAGE"
else
show_warning "错误" "$_HMCL_WARNING_MESSAGE\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。"
fi
else
_HMCL_WARNING_MESSAGE="The Java runtime environment is required to run HMCL.\nPlease install Java and set the environment variables and try again."
if [ -n "$_HMCL_JAVA_DOWNLOAD_PAGE" ]; then
show_warning_console "Error: $_HMCL_WARNING_MESSAGE\nYou can download it from here:\n$_HMCL_JAVA_DOWNLOAD_PAGE"
show_warning_dialog "Error" "$_HMCL_WARNING_MESSAGE\n\nDo you want to go to the Java download page?" "$_HMCL_JAVA_DOWNLOAD_PAGE"
else
show_warning "Error" "$_HMCL_WARNING_MESSAGE\nYou can visit the https://docs.hmcl.net/help.html page for help."
fi
fi
exit 1
@@ -1307,11 +1307,6 @@
-fx-padding: 8;
}
.body {
-fx-border-radius: 5;
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.4), 10, 0.3, 0.0, 0.0);
}
.debug-border {
-fx-border-color: red;
-fx-border-width: 1;
@@ -1,6 +1,6 @@
#
# Hello Minecraft! Launcher
# Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> 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
@@ -21,7 +21,7 @@
about=About
about.copyright=Copyright
about.copyright.statement=Copyright © 2024 huangyuhui
about.copyright.statement=Copyright © 2025 huangyuhui
about.author=Author
about.author.statement=bilibili @huanghongxun
about.claim=EULA
@@ -66,17 +66,17 @@ account.failed.injector_download_failure=Failed to download the authlib-injector
account.failed.invalid_credentials=Incorrect password or rate limited. Please try again later.
account.failed.invalid_password=Invalid password.
account.failed.invalid_token=Please try logging in again.
account.failed.migration=Your account needs to be migrated to a Microsoft account. If you already did, you should log in to your migrated Microsoft account again.
account.failed.migration=Your account needs to be migrated to a Microsoft account. If you already did, you should log in using your migrated Microsoft account again.
account.failed.no_character=There are no characters linked to this account.
account.failed.server_disconnected=Failed to connect to the authentication server. You can log in using offline mode or try logging in again.\n\
If you try several times and still fail, please try logging in to the account again.
account.failed.server_response_malformed=Invalid server response. The authentication server may not be working.
If the issue persists after multiple attempts, please try logging in to the account again.
account.failed.server_response_malformed=Invalid server response. The authentication server may be down.
account.failed.ssl=An SSL error occurred while connecting to the server. Please try updating your Java.
account.failed.wrong_account=You have logged in to the wrong account.
account.hmcl.hint=You need to click "Login" and complete the process in the opened browser window.
account.hmcl.hint=You need to click "Log in" and complete the process in the opened browser window.
account.injector.add=New Auth Server
account.injector.empty=None (You can click "+" to add one)
account.injector.http=Warning: This server uses the unsafe HTTP protocol. Anyone between your connections will be able to see your credentials in cleartext.
account.injector.http=Warning: This server uses the unsafe HTTP protocol. Anyone intercepting your connection would be able to see your credentials in plaintext.
account.injector.link.homepage=Homepage
account.injector.link.register=Register
account.injector.server=Authentication Server
@@ -95,16 +95,16 @@ account.copy_uuid=Copy UUID of the Account
account.methods=Login Type
account.methods.authlib_injector=authlib-injector
account.methods.microsoft=Microsoft
account.methods.microsoft.birth=How to Change Your Account Birthday
account.methods.microsoft.birth=How to Change Your Account Birth Date
account.methods.microsoft.close_page=Microsoft account authorization is now completed.\n\
\n\
There are some extra works for us, but you can safely close this tab for now.
account.methods.microsoft.deauthorize=Deauthorize
account.methods.microsoft.error.add_family=An adult must add you to a family in order for you to play Minecraft because you are not yet 18 years old.
account.methods.microsoft.error.add_family_probably=Please check if the age indicated in your account settings is at least 18 years old. If not and you believe this is an error, you can click "How to Change Your Account Birthday" to learn how to change it.
account.methods.microsoft.error.add_family_probably=Please check if the age indicated in your account settings is at least 18 years old. If not and you believe this is an error, you can click "How to Change Your Account Birth Date" to learn how to change it.
account.methods.microsoft.error.country_unavailable=Xbox Live is not available in your current country/region.
account.methods.microsoft.error.missing_xbox_account=Your Microsoft account does not have a linked Xbox account yet. Please click "Create Profile / Edit Profile Name" to create one before continuing.
account.methods.microsoft.error.no_character=Your account does not own the Minecraft Java Edition.\nThe game profile may not have been created\nplease click "Create Profile / Edit Profile Name" to create it.
account.methods.microsoft.error.no_character=Please ensure you have purchased Minecraft: Java Edition.\nIf you have already purchased the game, a profile may not have been created yet.\nClick "Create Profile / Edit Profile Name" to create it.
account.methods.microsoft.error.unknown=Failed to log in, error code: %d.
account.methods.microsoft.error.wrong_verify_method=Please log in using your password on the Microsoft account login page, and do not use a verification code to log in.
account.methods.microsoft.logging_in=Logging in...
@@ -113,26 +113,26 @@ account.methods.microsoft.hint=Please click "Log in" and copy the code displayed
If the token used to log in to the Microsoft account is leaked, you can click "Deauthorize" to deauthorize it.
account.methods.microsoft.manual=Your device code is <b>%1$s</b>. Please click here to copy.\n\
\n\
After clicking "Log in", you should complete the login process in the opened browser window. If it did not show, you can navigate to %2$s manually.\n\
After clicking "Log in", you should complete the login process in the opened browser window. If that does not show up, you can navigate to %2$s manually.\n\
\n\
If the token used to log in to the Microsoft account is leaked, you can click "Deauthorize" to deauthorize it.
account.methods.microsoft.makegameidsettings=Create Profile / Edit Profile Name
account.methods.microsoft.profile=Account Profile
account.methods.microsoft.purchase=Buy Minecraft
account.methods.microsoft.snapshot=You are using an unofficial build of HMCL. Please download the official build for login.
account.methods.microsoft.snapshot=You are using an unofficial build of HMCL. Please download the official build to log in.
account.methods.microsoft.snapshot.website=Official Website
account.methods.offline=Offline
account.methods.offline.name.special_characters=Recommended to use English letters, numbers, and underscores
account.methods.offline.name.invalid=Generally, game usernames only allow English letters, numbers, and underscores and cannot exceed 16 characters in length.\n\
account.methods.offline.name.special_characters=Using only English letters, numbers, and underscores is recommended
account.methods.offline.name.invalid=Generally, game usernames are limited to English letters, numbers, and underscores and cannot exceed 16 characters in length.\n\
\n\
\ · Some legitimate usernames: HuangYu, huang_Yu, Huang_Yu_123;\n\
\ · Some illegal usernames: Huang Yu, Huang-Yu_%%%, Huang_Yu_hello_world_hello_world.\n\
\ · Valid usernames: HuangYu, huang_Yu, Huang_Yu_123;\n\
\ · Invalid usernames: Huang Yu, Huang-Yu_%%%, Huang_Yu_hello_world_hello_world.\n\
\n\
If you believe that there is a corresponding mod or plugin on the server side to remove this restriction, you can ignore this warning.
If you believe there is a corresponding mod or plugin on the server side to remove this restriction, you can ignore this warning.
account.methods.offline.uuid=UUID
account.methods.offline.uuid.hint=UUID is a unique identifier for Minecraft players, and each launcher may generate UUIDs differently. Editing it to the one generated by other launchers allows you to keep your items in your offline account inventory.\n\
account.methods.offline.uuid.hint=UUID is a unique identifier for Minecraft players, and each launcher may generate UUIDs differently. Changing it to the one generated by other launchers allows you to keep your items in your offline account inventory.\n\
\n\
This option is for advanced users only. We do not recommend editing this option unless you know what you are doing.
This option is for advanced users only. We do not recommend changing this option unless you know what you are doing.
account.methods.offline.uuid.malformed=Invalid format.
account.methods.forgot_password=Forgot Password
account.missing=No Accounts
@@ -165,7 +165,7 @@ archive.version=Version
assets.download=Downloading Assets
assets.download_all=Validating Assets Integrity
assets.index.malformed=Index files of downloaded assets were corrupted. You can resolve this problem by clicking "Manage → Update Game Assets" on the "Edit Instance" page.
assets.index.malformed=Index files of downloaded assets are corrupted. You can resolve this problem by clicking "Manage → Update Game Assets" on the "Edit Instance" page.
button.cancel=Cancel
button.change_source=Change Download Source
@@ -193,7 +193,7 @@ color.recent=Recommended
color.custom=Custom Color
crash.NoClassDefFound=Please verify the integrity of this software, or try updating your Java.
crash.user_fault=The launcher crashed because of corrupted Java or system environment. Please make sure your Java or OS is installed properly.
crash.user_fault=The launcher crashed due to corrupted Java or system environment. Please make sure your Java or OS is installed properly.
curse.category.0=All
@@ -319,9 +319,11 @@ curse.sort.name=Name
curse.sort.popularity=Popularity
curse.sort.total_downloads=Total Downloads
datetime.format=MMM d, yyyy, h\:mm\:ss a
download=Download
download.hint=Install games and modpacks or download mods, resource packs, and worlds.
download.code.404=File not found on the remote server: %s
download.code.404=File "%s" not found on the remote server.
download.content=Addons
download.curseforge.unavailable=HMCL Nightly channel build does not support access to CurseForge. Please use Release or Beta channel builds to download.
download.existing=The file cannot be saved because it already exists. You can click "Save As" to save the file elsewhere.
@@ -348,10 +350,10 @@ download.javafx.prepare=Preparing to download
exception.access_denied=HMCL is unable to access the file "%s". It may be locked by another process.\n\
\n\
For Windows users, you can open "Resource Monitor" to check if another process is currently using it. If so, you can try again after closing that process.\n\
If not, please check if your account has enough permissions to access it.
For Windows users, you can open "Resource Monitor" to check if another process is currently using it. If so, you can try again after terminating that process.\n\
If not, please check if your user account has adequate permissions to access it.
exception.artifact_malformed=Cannot verify the integrity of the downloaded files.
exception.ssl_handshake=Failed to establish an SSL connection because the SSL certificate is missing from the current Java installation. You can try opening HMCL with another Java and try again.
exception.ssl_handshake=Failed to establish SSL connection because the SSL certificate is missing from the current Java installation. You can try opening HMCL with another Java installation and try again.
extension.bat=Windows Batch File (.bat)
extension.mod=Mod File
@@ -365,7 +367,7 @@ fatal.javafx.incompatible=Missing JavaFX environment.\n\
fatal.javafx.incomplete=The JavaFX environment is incomplete.\n\
Please try replacing your Java or reinstalling OpenJFX.
fatal.javafx.missing=Missing JavaFX environment. Please open Hello Minecraft! Launcher with Java, which includes OpenJFX.
fatal.config_change_owner_root=You are using the root account to open Hello Minecraft! Launcher. This may cause you to fail to open HMCL with another account in the future.\n\
fatal.config_change_owner_root=You are using the root account to open Hello Minecraft! Launcher. This may prevent you from opening HMCL with another account in the future.\n\
Do you still want to continue?
fatal.config_in_temp_dir=You are opening Hello Minecraft! Launcher in a temporary directory. Your settings and game data may be lost.\n\
It is recommended to move HMCL to another location and reopen it.\n\
@@ -375,7 +377,7 @@ fatal.config_loading_failure=Cannot load configuration files.\n\
For macOS, try putting HMCL somewhere with permissions other than "Desktop", "Downloads", and "Documents" and try again.
fatal.config_loading_failure.unix=Hello Minecraft! Launcher could not load the configuration file because it was created by user "%1$s".\n\
Please open HMCL as root user (not recommended), or execute the following command in the terminal to change the ownership of the configuration file to the current user:\n%2$s
fatal.mac_app_translocation=Hello Minecraft! Launcher is isolated to a temporary directory by the OS because of macOS security mechanisms.\n\
fatal.mac_app_translocation=Hello Minecraft! Launcher is isolated to a temporary directory by the OS due to macOS security mechanisms.\n\
Please move HMCL to a different directory before attempting to open. Otherwise, your settings and game data may be lost after restarting.\n\
Do you still want to continue?
fatal.migration_requires_manual_reboot=Hello Minecraft! Launcher has been upgraded. Please restart the launcher.
@@ -388,13 +390,13 @@ fatal.apply_update_need_win7=Hello Minecraft! Launcher cannot automatically upda
You can update manually by downloading a newer launcher version from %s.
fatal.samba=If you opened Hello Minecraft! Launcher from a Samba network drive, some features might not be working. Please try updating your Java or moving the launcher to another directory.
fatal.illegal_char=Your user path contains an illegal character "=". You will not be able to use authlib-injector or change the skin of your offline account.
fatal.unsupported_platform=Minecraft is not yet fully supported for your platform, so you may experience missing functionality or even be unable to launch the game.\n\
fatal.unsupported_platform=Minecraft is not fully supported on your platform yet, so you may experience missing features or even be unable to launch the game.\n\
\n\
If you cannot launch Minecraft 1.17 and later, you can try switching the "Renderer" to "Software" in "Global/Instance-specific Settings → Advanced Settings" to use CPU rendering for better compatibility.
fatal.unsupported_platform.loongarch=Hello Minecraft! Launcher has provided support for the Loongson platform.\n\
If you encounter problems when playing a game, you can visit https://docs.hmcl.net/groups.html for help.
fatal.unsupported_platform.osx_arm64=Hello Minecraft! Launcher has provided support for the Apple silicon platform, using native ARM Java to launch games to get a smoother game experience.\n\
If you encounter problems when playing a game, launching the game with Java based on x86-64 architecture may have better compatibility.
fatal.unsupported_platform.osx_arm64=Hello Minecraft! Launcher has provided support for the Apple silicon platform, using native ARM Java to launch games to get a smoother gaming experience.\n\
If you encounter problems when playing a game, launching the game with Java based on x86-64 architecture may offer better compatibility.
fatal.unsupported_platform.windows_arm64=Hello Minecraft! Launcher has provided native support for the Windows on Arm platform. If you encounter problems when playing a game, please try launching the game with Java based on x86 architecture.\n\
\n\
If you are using the <b>Qualcomm</b> platform, you may need to install the <a href="ms-windows-store://pdp/?productid=9NQPSL29BFFF">OpenGL Compatibility Pack</a> before playing games.\n\
@@ -421,7 +423,7 @@ folder.saves=Saves
folder.screenshots=Screenshots
game=Games
game.crash.feedback=<b>Please do not share screenshots of this interface with others!</b> If you ask for help from others, please click <b>"Export Crash Logs"</b> and send the exported file to others for analysis.
game.crash.feedback=<b>Please do not share screenshots of this window with others!</b> If you ask for help from others, please click <b>"Export Crash Logs"</b> and send the exported file to others for analysis.
game.crash.info=Crash Info
game.crash.reason=Crash Cause
game.crash.reason.analyzing=Analyzing...
@@ -476,14 +478,14 @@ game.crash.reason.need_jdk11=The game crashed because of an inappropriate Java V
You need to download and install Java 11, and set it in "Global/Instance-specific Settings → Java".
game.crash.reason.mod_name=The game crashed because of mod filename problems.\n\
\n\
Mod file names should use only English letters (Aa~Zz), numbers (0~9), hyphens (-), underscores (_), and dots (.) in half-width.\n\
Mod file names should use only English letters (A~Z, a~z), numbers (0~9), hyphens (-), underscores (_), and dots (.) in half-width.\n\
\n\
Please navigate to the mod directory and add all non-compliant mod file names with one of the above compliant characters.
Please navigate to the mod directory and change all non-compliant mod file names using the compliant characters above.
game.crash.reason.incomplete_forge_installation=The game cannot continue to run because of an incomplete Forge/NeoForge installation.\n\
\n\
Please reinstall Forge/NeoForge in "Edit Instance → Loaders".
game.crash.reason.fabric_version_0_12=Fabric Loader 0.12 or later is incompatible with currently installed mods. You need to downgrade it to 0.11.7.
game.crash.reason.fabric_warnings=The Fabric warned:\n\
game.crash.reason.fabric_warnings=The Fabric Loader warned:\n\
\n\
%1$s
game.crash.reason.file_already_exists=The game crashed because the file "%1$s" already exists.\n\
@@ -499,9 +501,9 @@ game.crash.reason.graphics_driver=The game crashed because of a problem with you
\n\
Please try again after updating your graphics driver to the latest version.\n\
\n\
If your computer has a discrete graphics card, you need to check whether the game uses integrated/core graphics. If so, please open the launcher using your discrete graphics card. If the problem persists, you probably should consider using a new graphics card or a new computer.\n\
If your computer has a dedicated graphics card, you need to check whether the game uses integrated/core graphics. If so, please open the launcher using your dedicated graphics card. If the problem persists, you probably should consider using a new graphics card or a new computer.\n\
\n\
If you are using your integrated graphics card, please notice that Minecraft 1.16.5 or previous requires <a href="https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html">Java 1.8.0_51 or previous</a> for Intel(R) Core(TM) 3000 processor series or previous.
If you are using your integrated graphics card, please notice that Minecraft 1.16.5 or earlier requires <a href="https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html">Java 1.8.0_51 or earlier</a> for Intel(R) Core(TM) 3000 processor series or earlier.
game.crash.reason.macos_failed_to_find_service_port_for_display=The game cannot continue because the OpenGL window on the Apple silicon platform failed to initialize.\n\
\n\
For this problem, HMCL does not have direct solutions at the moment. Please try opening any browser and going fullscreen, then return to HMCL, launch the game, and <b>quickly return to the browser page</b> before the game window pops up, wait for the game window to appear, and then switch back to the game window.
@@ -520,8 +522,8 @@ game.crash.reason.mod_files_are_decompressed=The game crashed because the mod fi
\n\
Please put the entire mod file directly into the mod directory!\n\
\n\
If extract will cause errors in the game, please delete the extracted mod in the mod directory and then launch the game.
game.crash.reason.shaders_mod=The game crashed because of both OptiFine and Shaders mod installed at the same time.\n\
If extraction causes errors in the game, please delete the extracted mod in the mod directory and then launch the game.
game.crash.reason.shaders_mod=The game crashed because of both OptiFine and Shaders mod are installed at the same time.\n\
\n\
Just remove the Shader mod because OptiFine has built-in support for shaders.
game.crash.reason.rtss_forest_sodium=The game crashed because the RivaTuner Statistical Server (RTSS) is not compatible with Sodium.\n\
@@ -617,15 +619,15 @@ game.crash.reason.no_such_method_error=The game cannot continue to run because o
You may need to reinstall the game and all mods or ask someone for help.
game.crash.reason.opengl_not_supported=The game crashed because your graphics driver does not support OpenGL.\n\
\n\
If you are streaming the game over the Internet or using a remote desktop environment, please play the game on your local one.\n\
If you are streaming the game over the Internet or using a remote desktop environment, please play the game on your local machine.\n\
\n\
Or, you can try updating your driver to the latest version and then try again.\n\
Or, you can update your graphic driver to the latest version and then try again.\n\
\n\
If your computer has a discrete graphics card, please make sure the game is actually using it for rendering. If the problem persists, please consider getting a new graphics card or a new computer.
game.crash.reason.openj9=The game is unable to run on an OpenJ9 VM. Please switch to a Java that uses the Hotspot VM in "Global/Instance-specific Settings → Java" and relaunch the game. If you do not have one, you can download one online.
If your computer has a dedicated graphics card, please make sure the game is indeed using it for rendering. If the problem persists, please consider getting a new graphics card or a new computer.
game.crash.reason.openj9=The game is unable to run on an OpenJ9 VM. Please switch to a Java that uses the Hotspot VM in "Global/Instance-specific Settings → Java" and relaunch the game. If you do not have one, you can download one.
game.crash.reason.out_of_memory=The game crashed because the computer ran out of memory.\n\
\n\
Maybe because there is not enough memory available or too many mods installed. You can try resolving it by increasing the allocated memory in "Global/Instance-specific Settings → Memory".\n\
Maybe there is not enough memory available or too many mods installed. You can try resolving it by increasing the allocated memory in "Global/Instance-specific Settings → Memory".\n\
\n\
If you still encounter these problems, you may need a better computer.
game.crash.reason.resolution_too_high=The game crashed because the resource pack resolution is too high.\n\
@@ -644,7 +646,7 @@ game.crash.reason.unsatisfied_link_error=Failed to launch Minecraft because of m
\n\
If you have edited the native library path, please make sure these libraries do exist. Or, please try launching again after reverting it to default.\n\
\n\
If you did not, please check if you have missing dependency mods.\n\
If you have not, please check if you have missing dependency mods.\n\
\n\
Otherwise, if you believe this is caused by HMCL, please provide feedback to us.
game.crash.title=Game Crashed
@@ -677,7 +679,7 @@ install.installer.choose=Choose Your %s Version
install.installer.depend=Requires %s
install.installer.fabric=Fabric
install.installer.fabric-api=Fabric API
install.installer.fabric-api.warning=Warning: Fabric API is a mod and will be installed into the mod directory of the game instance. Please do not change the working directory of the game, or the Fabric API will not work. If you do want to change these settings, you should reinstall it.
install.installer.fabric-api.warning=Warning: Fabric API is a mod and will be installed into the mod directory of the game instance. Please do not change the working directory of the game, or the Fabric API won't function. If you do want to change the directory, you should reinstall it.
install.installer.forge=Forge
install.installer.neoforge=NeoForge
install.installer.game=Minecraft
@@ -696,6 +698,7 @@ install.installer.quilt-api=QSL/QFAPI
install.installer.version=%s
install.installer.external_version=%s (Installed by external process, which cannot be configured)
install.modpack=Install Modpack
install.name.invalid=The name contains non-ASCII characters (such as emoji or CJK characters).\nIt is recommended to change the name to include only English letters, numbers, and underscores to avoid potential issues when launching the game.\nDo you want to proceed with the installation?
install.new_game=Install Instance
install.new_game.already_exists=This instance name already exists. Please use another name.
install.new_game.current_game_version=Current Instance Version
@@ -711,6 +714,7 @@ java.disabled.management=Disabled Java
java.disabled.management.remove=Remove this Java from the list
java.disabled.management.restore=Re-enable this Java
java.download=Download Java
java.download.banshanjdk-8=Download Banshan JDK 8
java.download.load_list.failed=Failed to load version list
java.download.more=More Java distributions
java.download.prompt=Please choose the Java version you want to download:
@@ -735,32 +739,33 @@ java.uninstall.confirm=Are you sure you want to uninstall this Java? This action
lang=English
lang.default=Use System Locales
launch.advice=%s Do you still want to continue to launch?
launch.advice.multi=The following problems were detected:\n\n%s\n\nThese problems may cause you to be unable to launch the game or affect the game experience. \nDo you still want to continue to launch?
launch.advice.multi=The following problems were detected:\n\n%s\n\nThese problems may prevent you from launching the game or affect gaming experience.\nDo you still want to continue to launch?
launch.advice.java.auto=The current Java VM version is not compatible with the instance.\n\nClick "Yes" to automatically choose the most compatible Java VM version. Or, you can navigate to "Global/Instance-specific Settings → Java" to choose one yourself.
launch.advice.java.modded_java_7=Minecraft 1.7.2 and previous versions require Java 7 or previous.
launch.advice.corrected=We have resolved the Java VM problem. If you still want to use your choice of Java version, you can disable the "Do not check Java VM compatibility" in "Global/Instance-specific Settings → Advanced Settings".
launch.advice.uncorrected=If you still want to use your choice of Java version, you can disable the "Do not check Java VM compatibility" in "Global/Instance-specific Settings → Advanced Settings".
launch.advice.java.modded_java_7=Minecraft 1.7.2 and previous versions require Java 7 or earlier.
launch.advice.corrected=We have resolved the Java VM problem. If you still want to use your choice of Java version, you can disable "Do not check Java VM compatibility" in "Global/Instance-specific Settings → Advanced Settings".
launch.advice.uncorrected=If you still want to use your choice of Java version, you can disable "Do not check Java VM compatibility" in "Global/Instance-specific Settings → Advanced Settings".
launch.advice.different_platform=The 64-bit Java version is recommended for your device, but you have installed a 32-bit one.
launch.advice.forge2760_liteloader=Forge version 2760 is not compatible with LiteLoader. Please consider upgrading Forge to version 2773 or later.
launch.advice.forge28_2_2_optifine=Forge version 28.2.2 or later is not compatible with OptiFine. Please consider downgrading Forge to version 28.2.1 or previous.
launch.advice.forge37_0_60=Forge versions previous to 37.0.60 are not compatible with Java 17. Please update Forge to 37.0.60 or later, or launch the game with Java 16.
launch.advice.forge28_2_2_optifine=Forge version 28.2.2 or later is not compatible with OptiFine. Please consider downgrading Forge to version 28.2.1 or earlier.
launch.advice.forge37_0_60=Forge versions prior to 37.0.60 are not compatible with Java 17. Please update Forge to 37.0.60 or later, or launch the game with Java 16.
launch.advice.java8_1_13=Minecraft 1.13 and later can only be run on Java 8 or later. Please use Java 8 or later versions.
launch.advice.java8_51_1_13=Minecraft 1.13 may crash on Java 8 versions previous to 1.8.0_51. Please install the latest Java 8 version.
launch.advice.java9=You cannot launch Minecraft 1.12 or previous with Java 9 or later. Please use Java 8 instead.
launch.advice.java8_51_1_13=Minecraft 1.13 may crash on Java 8 versions prior to 1.8.0_51. Please install the latest Java 8 version.
launch.advice.java9=You cannot launch Minecraft 1.12 or earlier with Java 9 or later. Please use Java 8 instead.
launch.advice.modded_java=Some mods may not be compatible with newer Java versions. It is recommended to use Java %s to launch Minecraft %s.
launch.advice.modlauncher8=The Forge version you are using is not compatible with the current Java version. Please try updating Forge.
launch.advice.newer_java=You are using the old Java to launch the game. It is recommended to update to Java 8, otherwise some mods may cause the game to crash.
launch.advice.newer_java=You are using an older Java version to launch the game. It is recommended to update to Java 8, otherwise some mods may cause the game to crash.
launch.advice.not_enough_space=You have allocated a memory size larger than the actual %d MB of memory installed on your computer. You may experience degraded performance or even be unable to launch the game.
launch.advice.require_newer_java_version=The current game version requires Java %s, but we could not find one. Do you want to download one now?
launch.advice.too_large_memory_for_32bit=You have allocated a memory size larger than the memory limitation of the 32-bit Java installation. You may be unable to launch the game.
launch.advice.vanilla_linux_java_8=Minecraft 1.12.2 or previous only supports Java 8 for the Linux x86-64 platform because the later versions cannot load 32-bit native libraries like liblwjgl.so\n\nPlease download it from java.com or install OpenJDK 8.
launch.advice.vanilla_x86.translation=Minecraft is not fully supported for your platform, so you may experience missing functionality or even be unable to launch the game.\nYou can download Java for the <b>x86-64</b> architecture <a href="https://learn.microsoft.com/java/openjdk/download">here</a> for a full gaming experience.
launch.advice.unknown=The game cannot be launched for the following reasons:
launch.advice.vanilla_linux_java_8=Minecraft 1.12.2 or earlier only supports Java 8 for the Linux x86-64 platform because the later versions cannot load 32-bit native libraries like liblwjgl.so\n\nPlease download it from java.com or install OpenJDK 8.
launch.advice.vanilla_x86.translation=Minecraft is not fully supported on your platform, so you may experience missing features or even be unable to launch the game.\nYou can download Java for the <b>x86-64</b> architecture <a href="https://learn.microsoft.com/java/openjdk/download">here</a> for a full gaming experience.
launch.advice.unknown=The game cannot be launched due to the following reasons:
launch.failed=Failed to launch
launch.failed.cannot_create_jvm=We are unable to create a Java VM. It may be caused by incorrect Java VM arguments. You can try resolving it by removing all arguments you added in "Global/Instance-specific Settings → Advanced Settings → Java VM Options".
launch.failed.creating_process=We are unable to create a new process. Please check your Java path.\n
launch.failed.command_too_long=The command length exceeds the maximum length of a bat script. Please try exporting it as a PowerShell script.
launch.failed.command_too_long=The command length exceeds the maximum length of a batch script. Please try exporting it as a PowerShell script.
launch.failed.decompressing_natives=Failed to extract native libraries.\n
launch.failed.download_library=Failed to download libraries "%s".
launch.failed.executable_permission=Failed to make the launch script executable.
@@ -787,7 +792,8 @@ launcher.agreement.hint=You must agree to the EULA to use this software.
launcher.background=Background Image
launcher.background.choose=Choose background image
launcher.background.classic=Classic
launcher.background.default=Default (Or "background.png/.jpg/.gif" and the images in the "bg" directory)
launcher.background.default=Default
launcher.background.default.tooltip=Or "background.png/.jpg/.gif/.webp" and the images in the "bg" directory
launcher.background.network=From URL
launcher.background.translucent=Translucent
launcher.cache_directory=Cache Directory
@@ -863,7 +869,7 @@ modpack.install=Install Modpack %s
modpack.installing=Installing Modpack
modpack.introduction=Curse, Modrinth, MultiMC, and MCBBS modpacks are currently supported.
modpack.invalid=Invalid modpack, you can try downloading it again.
modpack.mismatched_type=Modpack type mismatched, the current instance is a(an) %s type, but the provided one is %s type.
modpack.mismatched_type=Modpack type mismatched, the current instance is a(n) %s type, but the provided one is %s type.
modpack.name=Modpack Name
modpack.not_a_valid_name=Invalid modpack name.
modpack.origin=Source
@@ -883,7 +889,7 @@ modpack.type.modrinth=Modrinth
modpack.type.multimc=MultiMC
modpack.type.multimc.export=Can be imported by Hello Minecraft! Launcher and MultiMC
modpack.type.server=Auto-Update Modpack from Server
modpack.type.server.export=Allows server owner to update the game instance remotely
modpack.type.server.export=Allows server owner to remotely update the game instance
modpack.type.server.malformed=Invalid modpack manifest. Please contact the modpack maker to resolve this problem.
modpack.unsupported=Unsupported modpack format
modpack.update=Updating modpack
@@ -898,10 +904,10 @@ modpack.wizard.step.initialization.exported_version=Game version to export
modpack.wizard.step.initialization.force_update=Force updating the modpack to the latest version (you will need a file-hosting server)
modpack.wizard.step.initialization.include_launcher=Include the launcher
modpack.wizard.step.initialization.save=Export to...
modpack.wizard.step.initialization.warning=Before making a modpack, please make sure the game launches normally and Minecraft is a release version instead of a snapshot. The launcher will save your download settings.\n\
modpack.wizard.step.initialization.warning=Before making a modpack, please make sure the game can be launched normally and Minecraft is a release version instead of a snapshot. The launcher will save your download settings.\n\
\n\
Keep in mind that you are not allowed to add mods and resource packs that are explicitly said not to be distributed or put in a modpack.
modpack.wizard.step.initialization.server=Click here for more tutorials for making a server modpack that can be automatically updated.
Keep in mind that you are not allowed to add mods and resource packs that explicitly state they could not to be distributed or put in a modpack.
modpack.wizard.step.initialization.server=Click here for more information on how to make a server modpack that can be automatically updated.
modrinth.category.adventure=Adventure
modrinth.category.audio=Audio
@@ -977,7 +983,7 @@ mods.add=Add Mod
mods.add.failed=Failed to add mod %s.
mods.add.success=%s was successfully added.
mods.broken_dependency.title=Broken dependency
mods.broken_dependency.desc=This dependency existed before, but it does not exist now. Try using another download source.
mods.broken_dependency.desc=This dependency existed before, but it does not exist anymore. Try using another download source.
mods.category=Category
mods.channel.alpha=Alpha
mods.channel.beta=Beta
@@ -993,16 +999,16 @@ mods.check_updates.target_version=Target Version
mods.check_updates.update=Update
mods.choose_mod=Choose mod
mods.curseforge=CurseForge
mods.dependency.embedded=Built-in Pre-mod (Already packaged in the mod file by the author. No need to download separately)
mods.dependency.optional=Optional Pre-mod (If missing, the game will run normally, but the mod features may be missing)
mods.dependency.required=Required Pre-mod (Must be downloaded separately. Missing may cause the game to fail to launch)
mods.dependency.tool=Required Library (Must be downloaded separately. Missing may cause the game to fail to launch)
mods.dependency.include=Built-in Pre-mod (Already packaged in the mod file by the author, no need to download separately)
mods.dependency.incompatible=Incompatible Mod (Installing these mods at the same time will cause the game to fail to launch)
mods.dependency.broken=Broken Pre-mod (This mod existed before, but it does not exist now. Try using another download source.)
mods.dependency.embedded=Built-in Dependencies (Already packaged in the mod file by the author. No need to download separately)
mods.dependency.optional=Optional Dependencies (If missing, the game will run normally, but the mod features may be missing)
mods.dependency.required=Required Dependencies (Must be downloaded separately. Missing may prevent the game from launching)
mods.dependency.tool=Required Dependencies (Must be downloaded separately. Missing may prevent the game from launching)
mods.dependency.include=Built-in Dependencies (Already packaged in the mod file by the author. No need to download separately)
mods.dependency.incompatible=Incompatible Mods (Installing these mods at the same time will prevent the game from launching)
mods.dependency.broken=Broken Dependencies (This mod existed before, but it does not exist anymore. Try using another download source.)
mods.disable=Disable
mods.download=Mod Download
mods.download.title=Mod Download - %1s
mods.download=Download Mod
mods.download.title=Download Mod - %1s
mods.download.recommend=Recommended Mod Version - Minecraft %1s
mods.enable=Enable
mods.manage=Mods
@@ -1084,7 +1090,6 @@ world.name=World Name
world.name.enter=Enter the world name
world.reveal=Reveal in Explorer
world.show_all=Show All
world.time=h\:mm\:ss a, EEE, MMM d yyyy
profile=Game Directories
profile.already_exists=This name already exists. Please use a different name.
@@ -1168,7 +1173,7 @@ settings.advanced.natives_directory=Native Library Path
settings.advanced.natives_directory.choose=Choose the location of the desired native library
settings.advanced.natives_directory.custom=Custom
settings.advanced.natives_directory.default=Default
settings.advanced.natives_directory.hint=This option is intended only for users of Apple M1 or other not officially supported platforms. Please do not edit this option unless you know what you are doing.\n\
settings.advanced.natives_directory.hint=This option is intended only for users of Apple silicon or other not officially supported platforms. Please do not edit this option unless you know what you are doing.\n\
\n\
Before proceeding, please make sure all libraries (e.g. lwjgl.dll, libopenal.so) are provided in your desired directory.\n\
Note: It is recommended to use a fully English-letters path for the specified local library file. Otherwise it may lead to game launch failure.
@@ -1249,7 +1254,7 @@ settings.launcher.proxy.http=HTTP
settings.launcher.proxy.none=No Proxy
settings.launcher.proxy.password=Password
settings.launcher.proxy.port=Port
settings.launcher.proxy.socks=Socks
settings.launcher.proxy.socks=SOCKS
settings.launcher.proxy.username=Username
settings.launcher.theme=Theme
settings.launcher.title_transparent=Transparent Titlebar
@@ -1290,12 +1295,12 @@ update=Update
update.accept=Update
update.changelog=Changelog
update.channel.dev=Beta
update.channel.dev.hint=You are currently using a Beta channel build of the launcher, which may include some extra features but is also sometimes more unstable than the Release channel.\n\
update.channel.dev.hint=You are currently using a Beta channel build of the launcher. While it may include some extra features, it is also sometimes less stable than the Release channel builds.\n\
\n\
If you encounter any bugs or problems, please navigate to <a href="hmcl://settings/feedback">Settings → Feedback</a> page to submit your feedback.
update.channel.dev.title=Beta Channel Notice
update.channel.nightly=Nightly
update.channel.nightly.hint=You are currently using a Nightly channel build of the launcher, which may include some extra features but is also always more unstable than the other channels.\n\
update.channel.nightly.hint=You are currently using a Nightly channel build of the launcher. While it may include some extra features, it is also always less stable than the other channel builds.\n\
\n\
If you encounter any bugs or problems, please navigate to <a href="hmcl://settings/feedback">Settings → Feedback</a> page to submit your feedback.
update.channel.nightly.title=Nightly Channel Notice
@@ -1348,7 +1353,7 @@ version.manage.remove_assets=Delete All Assets
version.manage.remove_libraries=Delete All Libraries
version.manage.rename=Rename Instance
version.manage.rename.message=Enter New Instance Name
version.manage.rename.fail=Failed to rename the instance, some files might be in use, or the name contains an invalid character.
version.manage.rename.fail=Failed to rename the instance. Some files might be in use, or the name contains an invalid character.
version.settings=Settings
version.update=Update Modpack
File diff suppressed because it is too large. Load diff
@@ -1,6 +1,6 @@
#
# Hello Minecraft! Launcher
# Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> 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
@@ -20,7 +20,7 @@
about=について
about.copyright=著作権
about.copyright.statement=Copyright © 2024 huangyuhui
about.copyright.statement=Copyright © 2025 huangyuhui
about.author=著者
about.author.statement=bilibili @huanghongxun
about.claim=EULA
@@ -283,6 +283,8 @@ curse.sort.name=名前
curse.sort.popularity=人気
curse.sort.total_downloads=合計ダウンロード数
datetime.format=yyyy/MM/dd H:mm:ss
download=ダウンロード
download.hint=ゲームや modpack をインストールするか、mod、リソース パック、マップをダウンロードします
download.code.404=リモートサーバーにファイルが見つかりません:%s
@@ -535,7 +537,8 @@ launcher.agreement.hint=このソフトウェアを使用するには、EULAに
launcher.background=背景画像
launcher.background.classic=クラシック
launcher.background.choose=背景画像ファイルを選択してください
launcher.background.default=標準(ランチャーと同じディレクトリにある background.png/jpg/gif と bg フォルダから自動的に画像を取得します。)
launcher.background.default=標準
launcher.background.default.tooltip=ランチャーと同じディレクトリにある background.png/.jpg/.gif/.webp と bg フォルダから自動的に画像を取得します。
launcher.background.network=ネットワーク
launcher.background.translucent=半透明
launcher.cache_directory=キャッシュ用のディレクトリ
@@ -716,7 +719,6 @@ world.name=マップ名
world.name.enter=マップ名を入力してください
world.reveal=フォルダを表示
world.show_all=すべて表示
world.time=EEE、MMM d、yyyy HH:mm:ss
profile=ゲームディレクトリ
profile.already_exists=この名前はすでに存在します。別の名前を使用してください。
@@ -856,7 +858,7 @@ settings.launcher.proxy.http=HTTP
settings.launcher.proxy.none=プロキシなし
settings.launcher.proxy.password=パスワード
settings.launcher.proxy.port=ポート
settings.launcher.proxy.socks=Socks
settings.launcher.proxy.socks=SOCKS
settings.launcher.proxy.username=アカウント
settings.launcher.theme=テーマ
settings.launcher.title_transparent=タイトルの透明性
@@ -1,6 +1,6 @@
#
# Hello Minecraft! Launcher
# Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> 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
@@ -20,7 +20,7 @@
about=О лаунчере
about.copyright=Авторские права
about.copyright.statement=Авторские права © 2024 huangyuhui.
about.copyright.statement=Авторские права © 2025 huangyuhui.
about.author=Автор
about.author.statement=bilibili @huanghongxun
about.claim=EULA
@@ -295,6 +295,8 @@ curse.sort.name=Название
curse.sort.popularity=Популярность
curse.sort.total_downloads=Скачиваний
datetime.format=d MMM yyyy г., HH\:mm\:ss
download=Скачать
download.hint=Установите игры и пакеты модов или загрузите моды, пакеты ресурсов и карты
download.code.404=Файл не найден на удалённом сервере: %s
@@ -557,7 +559,8 @@ launcher.agreement.hint=Нужно принять пользовательско
launcher.background=Фоновое изображение
launcher.background.choose=Выберите фоновое изображение
launcher.background.classic=Классическое
launcher.background.default=По умолчанию (или background.png/jpg/gif, или папку с изображениями)
launcher.background.default=По умолчанию
launcher.background.default.tooltip=или background.png/.jpg/.gif/.webp, или папку с изображениями
launcher.background.network=По ссылке
launcher.background.translucent=Полупрозрачный
launcher.cache_directory=Папка с кэшем
@@ -775,7 +778,6 @@ world.name=Название мира
world.name.enter=Введите название мира
world.reveal=Показать в проводнике
world.show_all=Показать все
world.time=EEE, MMM d, yyyy HH\:mm\:ss
profile=Папки с играми
profile.already_exists=Имя уже существует, используйте другое имя.
@@ -1,6 +1,6 @@
#
# Hello Minecraft! Launcher
# Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> 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
@@ -20,7 +20,7 @@
about=關於
about.copyright=著作權
about.copyright.statement=著作權所有 © 2024 huangyuhui
about.copyright.statement=著作權所有 © 2025 huangyuhui
about.author=作者
about.author.statement=bilibili @huanghongxun
about.claim=使用者協議
@@ -319,6 +319,8 @@ curse.sort.name=名稱
curse.sort.popularity=熱度
curse.sort.total_downloads=下載量
datetime.format=yyyy 年 MM 月 dd 日 HH:mm:ss
download=下載
download.hint=安裝遊戲和模組包或下載模組、資源包和地圖
download.code.404=遠端伺服器沒有需要下載的檔案:%s
@@ -510,6 +512,7 @@ install.installer.quilt-api=QSL/QFAPI
install.installer.version=%s
install.installer.external_version=%s [由外部安裝的版本,無法解除安裝或更換]
install.modpack=安裝模組包
install.name.invalid=名稱中包含非 ASCII 字元(如 Emoji 表情或中文字元)。\n建議修改名稱,名稱建議僅包含英文字母、數字和底線,以防啟動遊戲時出現問題。是否繼續安裝?
install.new_game=安裝新實例
install.new_game.already_exists=此實例已經存在,請重新命名
install.new_game.current_game_version=目前遊戲版本
@@ -525,6 +528,7 @@ java.disabled.management=管理已禁用的 Java
java.disabled.management.remove=從清單中移除此 Java
java.disabled.management.restore=重新啟用此 Java
java.download=下載 Java
java.download.banshanjdk-8=下載 Banshan JDK 8
java.download.load_list.failed=載入版本清單失敗
java.download.more=更多發行版
java.download.prompt=請選擇你要下載的 Java 版本:
@@ -602,7 +606,8 @@ launcher.agreement.hint=同意本軟體的使用者協議與免責宣告以使
launcher.background=背景圖片
launcher.background.choose=選取背景圖片
launcher.background.classic=經典
launcher.background.default=預設 (自動尋找啟動器同目錄下的「background.png/.jpg/.gif」及「bg」目錄內的圖片)
launcher.background.default=預設
launcher.background.default.tooltip=自動尋找啟動器同目錄下的「background.png/.jpg/.gif/.webp」及「bg」目錄內的圖片
launcher.background.network=網路
launcher.background.translucent=半透明
launcher.cache_directory=檔案下載快取目錄
@@ -625,7 +630,7 @@ logwindow.terminate_game=結束遊戲處理程式
logwindow.title=日誌
logwindow.help=你可以前往 HMCL 社群,向他人尋求幫助
logwindow.autoscroll=自動滾動
logwindow.export_game_crash_logs=匯出遊戲崩潰訊息
logwindow.export_game_crash_logs=匯出遊戲崩潰資訊
logwindow.export_dump=匯出遊戲執行堆疊
logwindow.export_dump.no_dependency=你的 Java 不包含用於建立遊戲執行堆疊的相依元件。請前往 Discord 或 HMCL QQ 群尋求幫助。
@@ -889,7 +894,6 @@ world.name=世界名稱
world.name.enter=輸入世界名稱
world.reveal=開啟目錄
world.show_all=全部顯示
world.time=yyyy 年 M 月 d 日, HH:mm:ss
profile=遊戲目錄
profile.already_exists=該名稱已存在
@@ -973,7 +977,7 @@ settings.advanced.natives_directory.hint=本選項提供給 Apple M1 等未受
settings.advanced.no_jvm_args=不加入預設的 JVM 參數
settings.advanced.precall_command=遊戲啟動前執行指令
settings.advanced.precall_command.prompt=將在遊戲啟動前呼叫使用
settings.advanced.process_priority=處理程序優先度
settings.advanced.process_priority=處理程式優先度
settings.advanced.process_priority.low=低 (節省遊戲占用資源,可能會造成遊戲卡頓)
settings.advanced.process_priority.below_normal=較低 (節省遊戲占用資源,可能會造成遊戲卡頓)
settings.advanced.process_priority.normal=中 (平衡)
@@ -1045,7 +1049,7 @@ settings.launcher.proxy.http=HTTP
settings.launcher.proxy.none=不使用代理
settings.launcher.proxy.password=密碼
settings.launcher.proxy.port=連線埠
settings.launcher.proxy.socks=Socks
settings.launcher.proxy.socks=SOCKS
settings.launcher.proxy.username=帳戶
settings.launcher.theme=主題
settings.launcher.title_transparent=標題欄透明
@@ -1,6 +1,6 @@
#
# Hello Minecraft! Launcher
# Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> 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
@@ -20,7 +20,7 @@
about=关于
about.copyright=版权
about.copyright.statement=版权所有 © 2024 huangyuhui
about.copyright.statement=版权所有 © 2025 huangyuhui
about.author=作者
about.author.statement=bilibili @huanghongxun
about.claim=用户协议
@@ -328,6 +328,8 @@ curse.sort.name=名称
curse.sort.popularity=热度
curse.sort.total_downloads=下载量
datetime.format=yyyy 年 MM 月 dd 日 HH:mm:ss
download=下载
download.hint=安装游戏和整合包或下载模组、资源包和地图
download.code.404=远程服务器不包含需要下载的文件: %s\n你可以点击右上角帮助按钮进行求助。
@@ -521,6 +523,7 @@ install.installer.quilt-api=QSL/QFAPI
install.installer.version=%s
install.installer.external_version=%s (由外部安装的版本,无法卸载或更换)
install.modpack=安装整合包
install.name.invalid=名称中包含非 ASCII 字符(如 Emoji 表情或中文字符)。\n建议修改名称,名称建议仅包含英文字母、数字和下划线,以防启动游戏时出现问题。是否继续安装?
install.new_game=安装新游戏
install.new_game.already_exists=此版本已经存在,请换一个名字
install.new_game.current_game_version=当前游戏版本
@@ -536,6 +539,7 @@ java.disabled.management=管理已禁用的 Java
java.disabled.management.remove=从列表中移除此 Java
java.disabled.management.restore=重新启用此 Java
java.download=下载 Java
java.download.banshanjdk-8=下载 Banshan JDK 8
java.download.load_list.failed=加载版本列表失败
java.download.more=更多发行版
java.download.prompt=请选择你要下载的 Java 版本:
@@ -613,7 +617,8 @@ launcher.agreement.hint=同意本软件的用户协议与免责声明以使用
launcher.background=背景图片
launcher.background.choose=选择背景图片
launcher.background.classic=经典
launcher.background.default=默认 (自动检索启动器同文件夹下的“background.png/.jpg/.gif”及“bg”文件夹内的图片)
launcher.background.default=默认
launcher.background.default.tooltip=自动检索启动器同文件夹下的“background.png/.jpg/.gif/.webp”及“bg”文件夹内的图片
launcher.background.network=网络
launcher.background.translucent=半透明
launcher.cache_directory=文件下载缓存文件夹
@@ -900,7 +905,6 @@ world.name=世界名称
world.name.enter=输入世界名称
world.reveal=打开文件夹
world.show_all=显示全部
world.time=yyyy 年 MM 月 dd 日, HH:mm:ss
profile=游戏文件夹
profile.already_exists=该名称已存在
@@ -1056,7 +1060,7 @@ settings.launcher.proxy.http=HTTP
settings.launcher.proxy.none=不使用代理
settings.launcher.proxy.password=密码
settings.launcher.proxy.port=端口
settings.launcher.proxy.socks=Socks
settings.launcher.proxy.socks=SOCKS
settings.launcher.proxy.username=账户
settings.launcher.theme=主题
settings.launcher.title_transparent=标题栏透明
File diff suppressed because it is too large. Load diff
+337 -57
View File
@@ -1,9 +1,9 @@
#
# Hello Minecraft! Launcher
# Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
#
# mcmod.cn
# Copyright (C) 2024. All Rights Reserved.
# Copyright (C) 2025. All Rights Reserved.
#
gt-new-horizons;1;;格雷科技:新视野;GT: New Horizons;GTNH
enigtech-2;2;;玄理2;EnigTech 2;ET2
@@ -39,7 +39,7 @@ modern-skyblock-3-departed;31;;现代空岛3;Modern Skyblock 3: Departed;
gregblock;32;;格雷空岛;Gregblock;
sevtech-ages;33;;赛文科技;SevTech: Ages;STA
;34;;伊卡洛斯号迫降;Icarus Landing;IL
greedycraft;35;;贪婪整合包;GreedyCraft;
greedycraft;35;;贪婪整合包;GreedyCraft;GC
stoneblock;36;;StoneBlock;;
ftb-presents-skyfactory-3;37;;天空工厂3;FTB Presents SkyFactory 3;SF3
space-astronomy;38;;Space Astronomy;;
@@ -142,7 +142,7 @@ awakening-sky-of-diamonds;135;;Awakening - Sky of Diamonds;;
ragnamod-v;136;;Ragnamod V;;
all-the-mods-3-expert;137;;All the Mods 3 专家版;All the Mods 3 Expert;ATM3E
seablock-rustic-waters;138;;乡村水域;Seablock: Rustic Waters;
engineers-life;139;;Engineer's Life;;
engineers-life;139;;Engineer's Life;;EL
star-factory;140;;Star Factory;;
lunar-laboratory;141;;Lunar Laboratory;;
glacial-awakening;142;;Glacial Awakening;;
@@ -170,7 +170,7 @@ all-the-mods-6;164;;All the Mods 6;;ATM6
garden-of-glass-questbook-edition;165;;Garden of Glass (Questbook Edition);;
;166;;圣剑传奇;Sword Legendary;
;167;;FTB Presents Direwolf20 1.16;;
team-rustic;168;;Team Rustic;;TR
team-rustic;168;;Rusty Sink 1.20;;
enigmatica2expertskyblock;169;;Enigmatica 2: Expert Skyblock;;E2ES
;170;;休闲-生存-2021版;Leisure Survival-2021;ls21
hexxit-updated;171;;Hexxit Updated;;
@@ -217,7 +217,7 @@ airaka-tech;211;;艾瑞卡科技;Airaka Tech;
originssmp;213;;起源SMP;OriginsSMP;
create-live-2;214;;Create Live 2;;
origin-smp-copy;215;;Origin - SMP;;
;216;;逆迫降;INUERSE FORCED LANDING;
;216;;逆迫降;INVERSE FORCED LANDING;
avatar-the-four-elements;217;;Avatar: The Four Elements;;
seaopolis;218;;Seaopolis;;
zombie-apocalypse-remastered;219;;僵尸启示录:重制;Zombie Apocalypse Remastered;ZAR
@@ -253,9 +253,9 @@ evolution-reset;250;;进化:重启;Evolution: Reset;ER
the-nuclear-wasteland;251;;核荒原;The Nuclear Wasteland;
blessed-or-cursed-expedition-to-infinite-force;252;;祝福或诅咒:无限力量之征;Blessed or Cursed: Expedition to Infinite Force;BOC
;253;;边界生存;Border Survival;BSV
medieval-mc-fabric;255;;Medieval Minecraft;;
medieval-mc-fabric;255;;Medieval Minecraft;;MMC
heavens-of-sorcery;256;;巫术天堂;Heavens of Sorcery;
herodotus;257;;希罗多德;Herodotus;HDS
herodotus;257;;希罗多德;Herodotus-Authentic Edition:Official;HDS
;258;;重生世界;Rebirth world;RW
permafrost-eots;259;;永久冻土:风暴之眼;Permafrost - Eyes of the Storm;
ultimate-alchemy;260;;终极炼金;Ultimate Alchemy;
@@ -269,7 +269,7 @@ the-kingdom-of-daldar-forge-labs;267;;The Kingdom of Daldar - Forge Labs;;
demon-slayers-unleashed;268;;Demon Slayers Unleashed;;
valhelsia-enhanced-vanilla;269;;Valhelsia: Enhanced Vanilla;;
;270;;FTB University 1.16;;
thaumic-renaissance-kedition;271;;神秘复兴;Thaumic Renaissance Kedition;
thaumic-renaissance-kedition;271;;神秘复兴;Thaumic Renaissance / Thaumic Renaissance - Kedition;
the-winter-rescue;273;;冬季救援;The Winter Rescue;TWR
;274;;飞翔之路;Flying Road;
forever-stranded-lost-souls;275;;Forever Stranded Lost Souls;;
@@ -281,11 +281,11 @@ manufactio;281;;Manufactio;;
world-of-dragons-ii;282;;World of Dragons II;;
tensura-unleashed;283;;Tensura Unleashed;;
skyopolis-4;284;;Skyopolis 4;;
;286;;FTB OceanBlock;;
ftb-oceanblock;286;;FTB OceanBlock;;
tolkiencraft-iii-return-to-middle-earth;287;;TolkienCraft III - Return to Middle-earth;;
delivery-inc;288;;Delivery Inc.;;
poetica;289;;Poetica;;
divine-journey-2;290;;Divine Journey 2;;DJ2
divine-journey-2;290;;神圣之旅2;Divine Journey 2;DJ2
all-the-mods-7;291;;All the Mods 7;;ATM7
skyfactory-one;292;;SkyFactory One;;
feed-the-singularity;293;;喂养奇点生存;Feed the Singularity;
@@ -374,12 +374,11 @@ jurassic-world-reborn;376;;Jurassic World Reborn;;
;377;;FTB Omnia;;
fantazy-tech-expert-mode;378;;幻想科技;Fantazy Tech;
crainer-craft-2;379;;Crainer Craft 2;;
;380;;All The Forge 8;;
all-the-forge-8;380;;All The Forge 8;;
colony-space-modpack;381;;KN: Colony New Worlds;;
skyopolis-3;382;;Skyopolis 3;;
aboe-3;383;;A Bit of Everything 3;;
aboe-3;383;;A Bit of Everything 3;;ABOE3
sky-alchemist;384;;天空炼金术士;Sky Alchemist;
epicadventure;385;;奥德赛;OdysseyCraft;OC
;386;;字字落实;;
nuclear-earth-reirradiated;387;;核地球:再辐照;Nuclear Earth: Reirradiated;
god-block;388;;God Block;;
@@ -392,7 +391,7 @@ gregtech-community-pack;393;;GregTech Community Pack;;GCP
create-yourself;395;;Create Yourself;;
starrynight-pray-stp;397;;星夜祈临;StarryNight Pray;StP
rustic-waters-ii;398;;乡村水域2;Rustic Waters II;
enigmatica6expert;399;;Enigmatica 6 Expert;;E6E
enigmatica6expert;399;;Enigmatica 6: Expert;;E6E
celestial-journey;400;;Celestial Journey;;
;401;;天空舞台;Skyblock Create Show;
project-architect;402;;Project Architect;;
@@ -406,10 +405,10 @@ kimetsu-no-yaiba-demon-slayer-mod-pack;409;;鬼灭之刃;Kimetsu no Yaiba;
;410;;七曜之际;FOR A WEEK;
not-too-complicated;411;;Not Too Complicated;;NTC
fiefdom;412;;列土封疆;Fiefdom;
reinforce-reality;413;;死亡工艺&重生;Deathcraft & Rebirth;
reinforce-reality;413;;死亡工艺&重生;Deathcraft & Rebirth / Reinforce Reality;
afterlife;414;;The Afterlife;;
create-skyblock;415;;CREATE Skyblock;;
lightcraftproject;416;;Light Craft;;
lightcraftprojectlite;416;;Light Craft;;
epoch-runner-formerly-nuclear-beyond-modpack;417;;时代领跑人;Epoch Runner;
;418;;星海征途;The Journey of Star Sea;TJoSS
roguelike-adventures-and-dungeons-2;419;;冒险与地牢2;Roguelike Adventures and Dungeons 2;RAD2
@@ -422,7 +421,7 @@ magnificia;423;;Magnificia;;
chroma-sky-2;426;;色度空岛2;Chroma Sky 2;
infinity-fabric;427;;无限;Infinity;IF
enigmatica-2-expert-extended;428;;Enigmatica 2: Expert - Extended;;E2E-E
;429;;水星迫降重制版;Mercury landing;
mercury-landing;429;;水星迫降重制版;Mercury landing;
magic-sky;430;;Magic Sky;;
;431;;科技飞升;Rise of Tech;RoT
not-too-complicated-2;432;;Not Too Complicated 2;;
@@ -448,7 +447,7 @@ lcw;453;;失落文明:野原;Lost_Civilization:Wilds;LCW
promo-cinematic;454;;Minecraft trailer recreation | Promo-Cinematic Modpack;;
the-last-one;455;;最后一人;The Last One;TLO
;456;;帕斯特之梦;PasterDream;PD
;457;;失落的米德加尔特;Lost Midgard;LM
lost-midgard;457;;失落的米德加尔特;Lost Midgard;LM
;458;;真实空岛;Real Island;RI
abyss-hunter;459;;深渊猎人;Abyss Hunter;AH
blackstone-block;460;;Blackstone Block;;
@@ -458,14 +457,14 @@ mcdoom-modpack;463;;MCDoom Modpack;;
poko;464;;Poko;;
;465;;永无止境:重缝;Cabricality;CABF
aperomods;466;;Aperomods;;APM
;467;;XPlus 2.0 基础整合;XPlus Modpack;X+
;467;;XPlus 2.0 基础整合;XPlus 2.0 Modpack;X+
crazy-crave-5;468;;Crazy Crave 5;;
sky-research;469;;Sky Research;;
;470;;魔力做功;Work Done by Mana;WDM
;471;;Beyond Reality;;
slcp4;472;;微笑草坪4;SmileLawn 4;SLCP4
crazy-craft-updated;473;;Crazy Craft Updated;;
planetary;474;;Planetary;;
planetary;474;;行星;Planetary;
slcp5;475;;微笑草坪5;SmileLawn 5;SLCP5
;476;;寂静传说;tales of silent;TOS
Wightcraft;477;;Wightcraft;;
@@ -481,7 +480,7 @@ jetpack-cat;482;;喷气背包猫;Jetpack Cat;
mineshafts-monsters-lite;487;;Mineshafts & Monsters - LITE;;
;488;;AELife;;AEL
mineshafts-monsters-adventure;489;;Mineshafts & Monsters - Adventure;;
dimensionhopper;490;;DimensionHopper;;
dimensionhopper;490;;Dimension Hopper:The Fall;;
technology-empty-island;491;;科技空岛;RxanGrben;
greed-the-resource-4-infinity;492;;Greed the Resource 4 - Infinity;;
caveopolis;493;;Caveopolis;;
@@ -500,8 +499,8 @@ other-body;504;;Other Body;;
;506;;莱姆星云;Lime Nebula;LN
void-genesis;507;;Void Genesis;;
create-flavored;508;;Create Flavored;;
terrafirmagreg;509;;TerraFirmaGreg;;TFG
create-astral;510;;Create: Astral;;
terrafirmagreg-vintage;509;;TerraFirmaGreg;;TFG
create-astral;510;;机械动力:星辰;Create: Astral;
;511;;起源创造;CreateOrigin;CO
random-item-skyblock-recondite-i;512;;Random Item Island - Recondite I;;
infinity-foundation;513;;Infinity Foundation;;
@@ -517,7 +516,7 @@ tankoptimization;521;;坦坦优化;Tankoptimization;
pick-the-stars;523;;摘星;Pick The Stars;PTS
earth-technology;524;;Earth Technology;;
;525;;核乐不为;;ZMC
;526;;FTB StoneBlock 3;;
ftb-stoneblock-3;526;;FTB StoneBlock 3;;
create-mod-plus;527;;Create +;;
multiplayer-optimization;528;;RawDiamondMC的优化包;RawDiamondMC's optimization pack;MPO
chroma-endless;529;;Chroma Endless;;
@@ -532,12 +531,12 @@ fos-community-edition;534;;奇点工厂1:社区版;Factory of Singularity:Comm
;538;;跃迁计划;CT TransitionPlan;CTTP
astropolis;539;;Astropolis;;
;540;;筑梦者;Builder Dream;
;541;;尘埃传说;dusttale;
;541;;尘埃传说;;
magiculture-2;542;;Magiculture 2;;
;543;;交错维度2;Among Dimensions 2;AD
hexteria-skies;544;;Hexteria Skies;;
;545;;Omniworld;;
a-l;546;;冒险生活;Adventure Life;
al-legacy;546;;冒险生活-L;Adventure Life - Legacy;ALL
skymachina;547;;SkyMachina;;
all-of-fabric-6;548;;All of Fabric 6;;AOF6
;549;;机械动力:锄与锤;;CHH
@@ -546,11 +545,11 @@ technologicaljourney;552;;Technological Journey;;TJ
;553;;末世工匠;End-Time Craftsmen;EdTC
;554;;光芒消逝之日;;VL
gregic-skies;555;;Gregic Skies;;
;556;;勇者之章;Chapter of Yuusha;
;556;;勇者之章;Chapter of Yuusha;CY
;557;;齿轮颂歌:暮光;Gear Carol;
pluma;558;;Pluma, a Journey to the Future!;;Pluma
all-the-mods-7-to-the-sky;559;;All the Mods 7 - To the Sky;;ATM7S
;560;;月见苍穹传说:大地演化;TskimiCanopyCraft:Terra Evolution;TSCC:TE
;560;;月见苍穹传说:大地演化;TskimiCanopyCraft: Terra Evolution;TSCC:TE
;561;;最后的战役:劫后余生;The Last Stand:Aftermath;LSA/TLS
;562;;小猪配平;Peppa Pig's Burning Broom;2P2B
zerblands-remastered;563;;Zerblands-Remastered;;
@@ -565,7 +564,7 @@ provefrom;569;;忘却之刻;Provefrom;PvF
darkrpg-forge;572;;DarkRPG Forge;;
expert-of-magic-art-world;573;;魔艺世界;Expert of Magic Art World;EOMAW
;574;;龙境;Dragoncraft;DGC
nuclear-radiation-2;576;;核污染二;;NR2
nuclear-radiation-2;576;;核污染二;nuclear radiation 2;NR2
;577;;白狐铃的奇思妙想;Arctic Fox Ling Fantasy;AFLF
;578;;MineSpace;;MS
;579;;史诗战斗:兵团战争;EPIC Fight Corps War;EFC
@@ -579,22 +578,22 @@ multiblock-madness-2;583;;MultiBlock Madness 2;;MBM2
e2eu;587;;Enigmatica 2 Expert Unofficial;;E2Eu
e2eus;588;;Enigmatica 2 Expert Unofficial Skybound;;E2EuS
;589;;龙之冒险;;
;590;;格雷:量子跃迁;GregTech:QuantumTransition;GTQT
gregtech-quantum-transition;590;;格雷:量子跃迁;GregTech:QuantumTransition;GTQT
the-winter-frontier;591;;冬境边域;The Winter Frontier;TWF
;592;;林深不知处;NO, is killed by... Twlight Forest?;NON
;593;;太阳机械厂;Create Sun;CS
dawn-craft;594;;破晓之界;DawnCraft;
dawn-craft;594;;破晓之界;DawnCraft: Echoes of Legends;DC
aof-presents-skylore;595;;AOF: Skylore;;
;596;;可乐兔的格雷整合包;GtMagic;
;597;;FTB Skies;;
keletus-gregpack;596;;可乐兔的格雷整合包;Keletu's GregPack;
ftb-skies;597;;FTB Skies;;
tnp-limitless-5;598;;TNP Limitless 5;;
create-back-on-track;599;;Create: Back on Track;;
;600;;重构:炼金术;Refactoring: Alchemy;RA
;601;;地心;Geocentric;GOC
;602;;巫师·古老恶意;;
;602;;巫师·古老恶意;Wizard against wicked;WaW
;603;;进化太阳生存;Evolutionary Sun Survival;ESS
changfeng;604;;长峰漫路;Long peak Long road;LPR
techopolis-2;605;;Techopolis 2;;
techopolis-2;605;;科技城2;Techopolis 2;
;606;;Arcomua 原版整合;Arcomua Modpack;ARC
;607;;UnityCraft;;UC
;608;;灵犀;;
@@ -606,7 +605,7 @@ create-beyond-earth;612;;Create: Beyond Earth;;
;614;;Additive;;
;615;;无人荒漠-新世界;;
new-game-modpack-release;616;;New Game - An Improved Vanilla Experience;;
prodigium-reforged;617;;Prodigium Reforged;;
prodigium-reforged;617;;Prodigium Reforged (Terraria Pack);;
;618;;FTB Presents Integration by Parts DX;;
striving-standoff;619;;抗争之际;Striving Standoff;SS
gun-fire-survival;620;;枪火生存;Gun fire survival;
@@ -628,10 +627,10 @@ cobblemon-forge;635;;Cobblemon Official Modpack;;
sbeevs-industrial-revolution;636;;Sbeev's Industrial Revolution;;
;637;;Gensokyo Reimagined QOL;;
statech-industry;638;;StaTech Industry;;
vault-hunters-1-18-2;639;;Vault Hunters 3rd Edition;;
vault-hunters-1-18-2;639;;宝藏猎人3;Vault Hunters 3rd Edition;
survival-hard-rock;640;;生存硬摇滚;Survival Hard Rock;
aged;641;;Aged;;
;642;;The Lost Era;;
the-lost-era-modpack;642;;The Lost Era;;
vault-hunters-official-modpack;643;;Vault Hunters 2nd Edition;;
;644;;Sodium Plus;;
;645;;霜叶表达法;Wither Expression;WE
@@ -640,7 +639,7 @@ create-live_4;646;;Create Live 4;;
valhelsia-5;648;;Valhelsia 5;;
caves-cliffs-plus;649;;洞穴与山崖:增强版;Caves & Cliffs: Plus;CC+
enigmatica9expert;650;;Enigmatica 9: Expert;;E9E
;651;;虚无之主;The Lord of Ascension;TLoA
the-lord-of-ascension;651;;虚无之主;The Lord of Ascension;TLoA
;652;;寄生科技;;PTY
rise-of-tech-2;653;;科技飞升2;Rise of Tech 2;RoT2
;654;;虚空居者;Void Dweller;
@@ -653,7 +652,7 @@ valhelsia-6;660;;Valhelsia 6;;
all-the-mods-volcanoblock;661;;All the Mods: Volcano Block;;ATMVB
gregtronix-revoluton;662;;Gregtronix Revolution;;GR
;663;;空岛物语;Empty Island RPG;EIRPG
prominence-fabric;664;;Prominence I [FABRIC];;
prominence-fabric;664;;卓越1 [FABRIC];Prominence I;
multiskyblock-2;665;;模块化空岛2;MultiSkyBlock 2;MSB2
;666;;The Minehattan Project;;
;667;;万象天工;Millions of Heavens industries;MOHI
@@ -677,7 +676,7 @@ musketeer;683;;Musketeer - One for All;;
;685;;我的骑士:怪物大乱斗;;
;686;;塔可努的原生优化整合包;TechNoob's Vanilla Boost Pack;TNVBP
;687;;灵茗屿空岛;LMY SKY;
;688;;绿色版红石生电优化;;
;688;;红石生电优化;Redstone Survival Optimization;RSO
;689;;恐惧深渊;Abyss of Fear;AoF
;690;;德雷戈拉旅人日记;Diary of Dregora's Traveller;DoDT
nwtcraft;691;;近西之旅;NwtCraft;Nwtc
@@ -685,26 +684,26 @@ nwtcraft;691;;近西之旅;NwtCraft;Nwtc
aeropolis;693;;Aeropolis;;
project-sacrifice;694;;Project Sacrifice;;
chosens-modded-adventure;695;;Chosen's Modded Adventure;;
ciscos-fantasy-medieval-adventure-rpg;696;;Cisco's Fantasy Medieval Adventure RPG;;
ciscos-fantasy-medieval-adventure-rpg;696;;Cisco's Fantasy Medieval RPG [Lite];;
;697;;命运之轮;Wheel of Fate;
all-the-mods-gravitas2;698;;All The Mods - Gravitas²;;ATMG2
enigmatica9;699;;Enigmatica 9;;E9
;700;;死亡突围;Deadout;DT
create-arcane-engineering;701;;机械动力:奥术工程;Create: Arcane Engineering;CAE
chroma-endless-2;702;;Chroma Endless 2;;
techrevolution;703;;TechRevolution;;TECHR
techrevolution;703;;TechRevolution;;
cabin;704;;CABIN: Create Above & Beyond In Newer;;CABIN
aoc;705;;All of Create;;Aofc
aoc;705;;All of Create;;
;706;;交错维度3;Among Dimensions 3;AD
gregfactory-sky;707;;GregFactory Sky;;GFS
;708;;飞翔之路4;Flying Road 4;
;709;;星月传说;Celestial Legend;CL
ctc-create-the-creation;710;;Create:the Creation;;CTC
create-perfect-world;711;;Create: Perfect World;;
new-simple-mods;712;;New Simple Mods;;
;713;;灾变录;Apocalypse of Catastrophe;ACC
new-simple-mods;712;;New Simple Mods - Easy to Understand;;
;713;;灾变录;Catastrophe Record;CR
sky-greg;714;;Sky Greg;;
deceasedcraft;715;;DeceasedCraft;;
deceasedcraft;715;;亡者世界;Deceased Craft;
;716;;生欲:求生;Vivere The Zombie Apocalypse;VZA
create-planetary;717;;Create Planetary;;
master-mabaoguo;718;;Master mabaoguo;;
@@ -717,41 +716,322 @@ the-backrooms-survival;724;;The Backrooms Survival;;
;725;;13年前的梦;Dreams 13 years ago;
cookiecrafter;726;;CookieCrafter;;
;727;;理想国:科克肖特;Utopia:Cockshott;UCST
bigchadguys-plus;728;;BigChadGuys Plus;;BCG+
bcg;728;;BigChadGuys Plus;;BCG+
;729;;异世界探险;ISEKAIBOUKEN;YSB
create-explore-c-e;730;;Create & Explore;;C&E
twistercraft;731;;TwisterCraft;;
all-the-horror;732;;恐怖生存;All The Horror;
the-backrooms-liminal-spaces;733;;The Backrooms;;
;734;;伊甸空天;Eden Aerospace;EAS
;735;;亚特兰深渊;AtlanAbyss;
atlanabyss;735;;亚特兰深渊;AtlanAbyss;
;736;;龙境II;Dragon Odyssey;DO
;737;;龙之冒险:新征程;;
all-the-mods-9-no-frills;738;;All the Mods 9 - No Frills;;ATM9NF
toms-cobblemon;739;;Tom's Cobblemon;;
life-in-the-village-3;740;;Life in the village 3;;LitV3
;741;;联缘;NEXUS;
;741;;联缘:NEXUS;;
;742;;Create: Cinematic;;
;743;;神秘纪元;Thaumic Epoch;ThE
;745;;FTB Genesis;;
cook-in-skyblock;746;;天空厨房;Cook in skyland;CiS
;747;;被支配的恐惧;Dominated by fear;DF
;748;;新的原版!;New Vanilla;NVA
;747;;被恐惧支配 1;Dominated by Fear 1;DoF
new-vanilla-modpack;748;;新的原版;New Vanilla;NVA
;749;;群峦救援非官方版;Terrafirma Rescue Unofficial;TFRU
stoneopolis;750;;Stoneopolis;;
;751;;FTB Skies Expert;;
ftb-skies-expert;751;;FTB Skies Expert;;
wmshd-pack-what-mojang-should-have-done;752;;What Mojang Should Have Done;;WMSHD
;753;;无限:重生;Infinity-Reborn;IF2
;754;;随心之旅;spontaneous journey;
oxmodpack;755;;OxMODPACK 8 +Online;;
gregtech-community-pack-modern;756;;GregTech Community Pack Modern;;GCPM
;757;;锻造大师2;Master Blacksmith2;MB2
;757;;锻造大师3;Master Blacksmith3;MB3
;758;;科技用于探索世界;Technology Exploration World;
;759;;幻梦闲笔;Aetherial Voyage;AV
;759;;幻梦闲笔;Aetherial Voyage;
fear-the-mist;760;;雾中的恐惧;Fear The Mist;
tekkit-resurrection;761;;Tekkit The Resurrection;;
;762;;Cup Pineapple UBLU;;CPU
;763;;FTB Arcanum Institute;;
ftb-arcanum-institute;763;;FTB Arcanum Institute;;
create-haven-adventures;764;;Create: Haven Adventures;;
modecube;765;;ModéCube;;
electrictechnocraft;766;;ElectricTechnoCraft;;
create-2-mekanism;767;;Create 2 Mekanism;;C2M
randblock;768;;随机空岛;RandBlock;RD
;769;;格雷科技休闲版;GregTech Leisure;GTL
frozenopolis;770;;Frozenopolis;;
infernal-rpg;771;;Infernal Origins;;
;772;;天狼星;Sirius;
;773;;悠沙海;Ethereal Sand Sea;ESS
;774;;AIR for JAVA;;AFJ
;775;;Create Extra;;
frs-optimisation-for-survival;776;;Fr的生存优化;Fr's Optimisation for Survival;FROS
;777;;Exile Magic Constructor;;EMC
;778;;Landscapes Reimagined Genesis;;
al-nl;779;;冒险生活 - 新生活;Adventure Life - New Life;AL-NL
harvista;780;;拾穗;Harvista;
;781;;时间之书 : 星之旅行;BookofTime StarTravel;BKSL
create-applied-energevation;783;;机械动力:应用能源时代;Create: Applied Energevation;CAEV
;784;;新星工程:世界;Nova Engineering - World;NEW
create-chronicles-bosses-and-beyond;785;;Create Chronicles: Bosses and Beyond;;
;786;;Adrenaline;;
forget-me-not;787;;勿忘我;Forget Me Not;FMN
modpack-essentials;788;;整合优选;Modpack Essentials;ME
desertopolis;789;;Desertopolis;;
omniopolis;790;;Omniopolis;;
;791;;战斗的法则;The Law of Battle;
factory-of-singularity-ii-colorfulness;792;;奇点工厂II:绚彩;Factory of Singularity II: Colorfulness;FoS2
prey-beta;793;;Prey;;
;794;;魔法之旅;Magic journey;MJ
;795;;轻松冒险;Easy adventure;EAT
;796;;魔幻大陆2;Enchanted Lands 2;
;797;;艾利克斯的世界;Alexcraft;AC
terrafirmacraft-wiph-walden-lagu;798;;瓦尔登湖畔;Wiph Walden Lagu;WWL
aocfabric;799;;All of Create Fabric;;
craftingcraft;800;;合成工艺;CraftingCraft;
;801;;感染者手册;Handbook for Infected People;HIP
;802;;居无定所—中世旷野;;
;803;;宾果难题;Bingo Madness;
chemillas-reborn;804;;化学:重生;Chemilla's Reborn;CR
;805;;群峦传说·异世界行纪;Terra Firma Craft:Travel in Another World;TFC:TAW
milkyway;806;;Create: Milkyway;;
farming-crossing-4;807;;Farming Crossing 4;;
hardrock-terrafirmacraft-4-realistic-extreme;808;;HardRock TerraFirmaCraft;;TFCH/HTFC
terrafirma-rebirth;809;;群峦:重生;TerraFirma: Rebirth;TFCR
create-live-5-skyblock;810;;Create Live 5;;
;811;;万象征程;Cosmos of Endless Allegory;CEA
;812;;远古之遗;;
;813;;奈の奇妙冒险;Kotona's Mystery Adventure;KSMA
;814;;战争魔匠;war craftman;WC
;815;;命定IV;unique destiny IV;
;816;;Wither Storm Enhanced;;
;817;;朝花夕拾;Dawn Blossoms Plucked at Dusk;
la;818;;胶兽大冒险;Latex Advanture;LA
mystics-monstrosity;819;;Mystic's Monstrosity;;
;820;;更完美的MC;More Perfect Minecraft;MPMC
;821;;动画世界:烂柯人;AnimateCraft: Lankeren;
;822;;New Create;;NC
land-of-memories;823;;回忆之地;;LOM
;824;;异世界冒险2;;
ragnamod-vii;825;;Ragnamod VII;;
path-of-truth;826;;真理之路;Path of Truth;PoT
;827;;TFC生活质量更新;;
all-the-mods-9-to-the-sky;828;;All The Mod 9 - To The Sky;;ATM9S
;829;;魔金:探秘;ManaMetalMod Exploration;M3E
fallenpetals;830;;落英;FallenPetals;FP
dungeon-heroes;831;;Dungeon Heroes (RPG Series);;
play-as-dragon-gothic-edition;832;;Play as Dragon: Gothic Edition;;
skyblock-burgeria;833;;空岛汉堡店;;
crafting-craft-2;834;;合成工艺2;Crafting Craft 2;
;835;;僵尸入侵100天;Zombie Invade 100 Days;ZI100D
compact-world-expert;836;;Compact World Expert;;CWE
a-fragment-of-peace;837;;宁然一隅;A Fragment of Peace;AFoP
easy-create-modpack;838;;Easy Create;;
;839;;流线鱼概念;Fish Spi Rally;FSRMP
meatballcraft;840;;肉丸工艺,次元提升;MeatballCraft, Dimensional Ascension;
seaopolis-submerged;841;;海之城: 潜没水中;Seaopolis: Submerged;
;842;;空中厕所;PoopSky;
compact-sky;843;;收缩空岛;Compact Sky;
back-to-back;844;;背靠背;Back to Back;BTB
adventure-exploration-part2;845;;冒险与探索;Adventure & Exploration;AnE
;846;;复兴;Renaissance;
live-like-a-castaway;847;;木筏求生2;Raftcraft2;RC2
mermaid-legend;848;;人鱼传说;Mermaid legend;
;849;;文明时代;;Aoc
create-lost-and-renaissance;850;;机械动力:失落与复兴;Create Lost and Renaissance;CLR
all-the-mods-10;851;;All the Mods 10;;ATM10
;852;;格雷伊甸:建造;GregEden:Building;GEB
;853;;迷失时代;LOST ERA;LE
create-alchemy-plan;854;;机械动力:炼金计划;Create:Alchemy Plan;CAP
deus-ex-machina;855;;Deus Ex Machina;;
create-prepare-to-dye;856;;Create Prepare to Dye;;
;857;;All The Fabric 5;;
create-leisurely-delight;858;;机械动力:悠然乐事;Create Delight;CD
;859;;蔚蓝悠悠;Azure Leisurely;AL
infinityevolved-reloaded;860;;Infinity Evolved: Reloaded;;
;861;;空中厕所:生产线;Poopsky:Assembly Line;PAL
;862;;佛罗伦萨迷思;Fantasy in Florence;FIF
craftoria;863;;Craftoria;;
mechanic-craft;864;;MechanicCraft;;
;865;;无用之人-群星;Good For Nothing:Stellaris;GFN
;866;;打金无限;;
ciscos-adventure-rpg-ultimate;867;;Cisco's Fantasy Medieval RPG [Ultimate];;
jour-avancee-daylight;868;;Jour avancée/DayLight;;DL
;869;;勇者之章 Ⅱ;Chapter of Yuusha II;CY2
;870;;Beta 1.7.4;;B1.7.4
;871;;神不在的星期天;God Without Create;GWC
createa-colony;872;;Create'a Colony;;
;873;;黑暗深处;Deep Dark;
;874;;自在极境;Free Extreme Realm;
all-of-fabric-7;875;;All of Fabric 7;;AOF7
ftb-neotech;876;;FTB NeoTech;;
;877;;Minecraft+;Minecraft plus;MCP
;878;;龙珠Super;Dragon Ball Super;
create-vanilla-exploration;879;;Vanilla Create Exploration;;
progress;880;;Progress: Create;;
aeon-automata;881;;Aeon Automata;;AA
;882;;被恐惧支配2:收容失效;Dominated by Fear 2 Containment Breach;DoF2
;883;;剑拔弩张之时;;TC
project-architect-2;884;;Project Architect 2;;
;885;;路网通途 / 更好的我的世界铁路;Railway Network Access;RNA
create-mekanized;886;;Create: Mekanized;;
create-perfect-world-2;887;;Create: Perfect World 2;;
create-stellar;888;;Create Stellar;;
monifactory;889;;Monifactory;;
create-factory-builder;890;;Create: Factory Builder;;
;891;;群峦传说:宝宝快乐版;TerraFirma: Baby Happy Edition;TFBH
;892;;阶段: 残骸;Phase: the Debris;
;893;;AE管道;AE Pipes;APE
skyt-2;894;;SkyT 2;;
stagecraft;895;;StageCraft;;
;896;;我的世界从来不简单;Minecraft Is Never Easy;MINE
ctnh;897;;机械动力:新视野;Create: New Horizon;CTNH
agora-interregnum;898;;Agora Interregnum;;
evolving-technology;899;;Evolving Technology;;
enlightened-6-expert;900;;Enlightened 6 Expert;;En6E
;901;;历久尤新;Old But New;OBN
;902;;继承;Inherit;IN
lucky-world-invasion;903;;幸运世界入侵;Lucky World Invasion;LWI
ftb-interactions-remastered;904;;FTB Interactions Remastered;;FTBIR
;905;;崩坏-新生;Broken-New Life;BNL
;906;;持之永恒重生;Stay With Eternity Rebirth;SWER
shadow-awakening;907;;暗影觉醒;Shadow Awakening;SAG
;908;;理想箱庭物语;Second Eden;SE
finality-genesis-1-20-1;909;;Finality Genesis;;
;910;;死亡突围 · 重临死境;DeadoutRe;DTR
soa3;911;;Slow Start AoA3;;SoA3
new-vanilla-2;912;;新的原版 2;New Vanilla 2;NVA2
prominence-2-rpg;913;;卓越 II RPG;Prominence II RPG;
;914;;伟大冒险者:世界之眼;Great Adventurer:the Eye of the World;GAEW
;915;;破碎;Fractures Rainimator;Rain
;916;;更好的原版;Better Vanilla;BV
strictly-medieval-3-forge;917;;Strictly Medival 3;;
;918;;潘神的迷宫;Pan's Labyrinth;PL
;919;;持之永恒-天空挑战者;Stay With Eternity Sky Challenger;SWESC
easy-to-ascension;920;;易如飞升;Easy to Ascension;
;921;;幽匿末日;The Last Day of Sculk;TLDS
;922;;交错空岛;Crossisland;CrI
;923;;机械动力:魔法乐事;Create Magic's Delight;CRMD
minetech-evolution;924;;科技进化;Minetech Evolution;MTE
gregified-sky-edition;925;;Gregified: Sky Edition;;
;926;;原版扩展;Additonal Vanilla;AV
create-on-a-potato;927;;Create on a Potato PC: Ultimate;;CPU
;928;;现实主义;realism;RLM
;929;;晋升之路;path of advance;POA
;930;;永无止境:超越常新;Above and Beyond : Refresh;CABR
cosmic-frontiers;931;;Cosmic Frontiers;;
integrated-minecraft;932;;Integrated MC;;
;933;;Rising Legends;;
;934;;城乡之旅;Travel Township;TT
;935;;龙鸣牢大;FarmerKobeBryant;FKB
setgamedifficulty-reika;936;;SetGameDifficulty Reika;;
epic-journey-gtceu;937;;Epic Journey GTCEu;;
;938;;飞渡群峦;TerraFirmaCraft:Leap The Top;TFC:LTT
gregtech-skybound;939;;Gregtech: Skybound;;
simple-tfc;940;;简单群峦;SimpleTFC;
ftb-presents-direwolf20-s14;941;;FTB Presents Direwolf20 1.21;;
final-judgment-save-the-world;942;;Final Judgment:Save the world;;
;943;;极端世界:重置;Extreme World Reset;EWR
explore-everything;944;;空岛:探索;Explore Everything;SEE
epic-journey-gtceu-conquerors-edition;945;;Epic Journey GTCEu - Conqueror's Edition;;
the-undead-blooms;946;;亡灵绽放 | 真菌孢子启示录 |;The Undead Blooms | Fungal Spore Apocalypse |;
;947;;星地征程;Starland Journey;SJ
;948;;希望与绝望-惊变100天;Hope and Despair;had
;949;;梦篡光阴;Dreams Distort Overture;DDO
;950;;最后的光芒;The Last Light;TLL
endil-expert-edition;951;;Endil's Expert Edition;;EEE
peace-of-mind;952;;Peace of Mind;;
distantly-optimized;953;;Distantly Optimized;;DO
farming-experience;954;;Farming Experience;;
hungry-for-tech-2;955;;Hungry For Tech 2;;
remain;957;;Remain;;
;958;;末日狂徒;;
gregtania6-skyblock;959;;Gregtania6 Skyblock;;
harmonious-engineering;960;;Harmonious Engineering;;
valley-of-farming-and-ranching;961;;农牧物语;Valley of Farming and Ranching;
;962;;机械动力:火车;Create Train;
;963;;理想国冒险之旅;;AOIC
;964;;生欲:求生2;Vivere The Zombie Apocalypse-II;VZA2
;965;;起源-世界;OriginCraft Pulses;
;966;;Legacies;;
the-legend-of-2900;967;;海上机械师;The Legend Of 2900;TLOU
mirasites-100-days;968;;畸变:蛊之奇境;Mirasites-BrokenMemories;MBM
;969;;末日余晖:独自一人;;MRYH
;970;;幸存者与僵尸;Survivors And Zombies;SAZ
;971;;海岛寿司店;Sushi Island;
infinite-alchemy;972;;Infinite Alchemy;;IA
;973;;探索的时光;Adventuring Time;AT
;974;;饕餮飨食;;
;975;;丰富的MC;RichMC;RM
;976;;刀剑异闻录;Sword Strange Tales;SST
etbw;977;;探索光明的世界;Explore the Bright World;EtBW
hardship-era;978;;苦难年代;Hardship Era;HE
rlcraft-dregora;979;;RLCraft Dregora;;
;980;;Farm The Ingots;;FTI
venoms-craft;981;;Venom's Craft;;
;982;;慢生活冒险;;
;983;;多元工艺:绘镇曲;Painted Town Melody;PTM
;984;;遗忘之海;Leisurely Life;
;985;;幻想国度;F_Land;
;986;;群峦传说:重制;TerraFirmaCraft Remake;TFCRe
;987;;未来;;
;988;;起源生物—崛起;;
;989;;工匠:技艺革新;Construct:Technological Innovation;CTI
erstwhile-journey;990;;往昔之旅;Erstwhile Journey;EJ
;991;;Exploria;;
ftb-evolution;992;;FTB Evolution;;
;994;;云程发轫;cloud journey begins;CJB
forever-factory;995;;永恒工厂;Forever Factory;
;996;;FTB University 1.19;;
symbolica;997;;Symbolica;;
aelstar;998;;Aelstar;;
srp-qz;999;;SRP:隔离区;SRP: Quarantine Zone;
industrial-machines;1000;;Industrial Machines;;
auto-terrafirmacraft-tfc-create;1001;;Auto-TerraFirmaCraft (TFC + Create);;ATFC
;1002;;时间之书:星的日记;The Book of Time: A Star's Journal;TASL
;1003;;特摄世界;Tokusatsu World;TW
journey-of-reincarnation;1004;;转世之旅;Journey of Reincarnation;
;1005;;冒险与休闲;Adventure and Leisure;AL
enigmatica10;1006;;Enigmatica 10;;E10
mc-chocolate-edition;1007;;The Chocolate Edition;;
civilization-l;1008;;Civilization-L;;
;1009;;群峦:降临;TerraFirma:Advent;TFA
valhelsia-7;1010;;Valhelsia 7;;
mechanical-mastery-plus;1011;;Mechanical Mastery Plus;;
ashes-to-ashes;1012;;尘归尘;Ashes To Ashes;ATA
;1013;;格雷乐事;GT So Easy;GTse
create-silence-and-prosperity;1014;;机械动力:寂静与繁荣;Create Silence and Prosperity;CSP
mif;1015;;MI:Foundation;;MIF
c2mse2;1016;;Create 2 Mekanism: Sky Edition 2;;C2MSE2
;1017;;Tsuri No Gatsu 5 - FDCraft Version;;TSNG5-FDC
;1018;;无限战争4;Infinity War4;INF4
;1019;;群峦传说:工业长路;;
;1020;;自然冒险 初生;Nature Adventure Newborn;NAB
;1021;;羽舲的基础优化;Hanekmio's Basis Optimization;
create-star-valley;1022;;Create: Gears of History;;
;1023;;阿尔法计划;AlphaPlan;AP
spell-dimension;1024;;咒次元;Spell Dimension;SD
skyfactory-5;1025;;天空工厂 5;SkyFactory 5;SF5
;1026;;破劫之刃;Break Disaster;
reminiscent-create;1027;;Reminiscent Create;;
techsert;1028;;Techsert;;TH
prehistoric-world-dinosaurs-adventure;1029;;Prehistoric World - Dinosaurs & Adventure;;
terminal;1030;;终末;Terminal;
;1031;;异次元水晶 3;Isolated Crystal 3;
;1032;;机械动力:无限总是小于零;Create:Infinity Always Smaller Than Zero;CIASZ
;1033;;踪灭I-开端;;
;1034;;踪灭II;;
rabbits-dawn-craft;1035;;某兔子的破晓之界;A Certain Rabbit's DawnCraft - Dawn of the Deep Soul;RDC
;1036;;铁砧天空;;ASKY
grand-architect-journey;1037;;伟大工程巡礼;Grand Architecture Journey;GAJ
the-adventure-of-valkyriens;1038;;瓦尔基里大冒险;The Adventure of Valkyriens;TAOV
;1039;;方块宝可梦:奥瑞星光;Cobblemon Ori Starlight;COS
;1040;;群峦传说:天才版;;
compression;1041;;Compression;;
compact-sky-easy;1042;;Compact Sky: Easy;;CSE
;1043;;丰收与探索;;
;1044;;脆骨症:黯光;No Flesh Within Chest-DIM;NFWC-DIM
;1045;;小雷的优化冒险;;
cobblemon-eternal-edition;1046;;Cobblemon Eternal;;
;1047;;Tinkerer's Quilt;;
gregtech-expert;1048;;GregTech Expert;;
;1049;;Beta Horizons;;
minecraft-legendary-edition;1050;;Legendary Edition;;
+212 -36
View File
@@ -3986,38 +3986,27 @@
"com.mojang:text2speech:1.13.9:natives-linux": null
},
"windows-x86_64": {
"software-renderer-loader": {
"name": "org.glavo:llvmpipe-loader:1.0",
"downloads": {
"artifact": {
"path": "org/glavo/llvmpipe-loader/1.0/llvmpipe-loader-1.0.jar",
"url": "https://repo1.maven.org/maven2/org/glavo/llvmpipe-loader/1.0/llvmpipe-loader-1.0.jar",
"sha1": "ff255415e5c4b2a18970da0a8e552b557ca013ae",
"size": 12964773
}
}
},
"mesa-loader": {
"name": "org.glavo:mesa-loader-windows:0.2.0:x64",
"name": "org.glavo:mesa-loader-windows:0.3.0:x64",
"downloads": {
"artifact": {
"path": "org/glavo/mesa-loader-windows/0.2.0/mesa-loader-windows-0.2.0-x64.jar",
"url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/0.2.0/mesa-loader-windows-0.2.0-x64.jar",
"sha1": "adb4a16ecb95c8944a016829b05d70f934ea1a29",
"size": 22823052
"path": "org/glavo/mesa-loader-windows/0.3.0/mesa-loader-windows-0.3.0-x64.jar",
"url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/0.3.0/mesa-loader-windows-0.3.0-x64.jar",
"sha1": "629fca32417d6ec489cef8b2cbd0827131ec6801",
"size": 27174940
}
}
}
},
"windows-x86": {
"mesa-loader": {
"name": "org.glavo:mesa-loader-windows:0.2.0:x86",
"name": "org.glavo:mesa-loader-windows:0.3.0:x86",
"downloads": {
"artifact": {
"path": "org/glavo/mesa-loader-windows/0.2.0/mesa-loader-windows-0.2.0-x86.jar",
"url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/0.2.0/mesa-loader-windows-0.2.0-x86.jar",
"sha1": "93c0b9b7382d984e713d3aa299ceb337fde2897b",
"size": 18273286
"path": "org/glavo/mesa-loader-windows/0.3.0/mesa-loader-windows-0.3.0-x86.jar",
"url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/0.3.0/mesa-loader-windows-0.3.0-x86.jar",
"sha1": "d25e0cdf5c5eb182acc9b93f700e0e6d8de36283",
"size": 22528549
}
}
}
@@ -4398,7 +4387,18 @@
"com.mojang:text2speech:1.10.3:natives": null,
"com.mojang:text2speech:1.11.3:natives": null,
"com.mojang:text2speech:1.12.4:natives": null,
"com.mojang:text2speech:1.13.9:natives-windows": null
"com.mojang:text2speech:1.13.9:natives-windows": null,
"mesa-loader": {
"name": "org.glavo:mesa-loader-windows:0.3.0:arm64",
"downloads": {
"artifact": {
"path": "org/glavo/mesa-loader-windows/0.3.0/mesa-loader-windows-0.3.0-arm64.jar",
"url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/0.3.0/mesa-loader-windows-0.3.0-arm64.jar",
"sha1": "1986490c6fbe950e64018c2fb62c8ecf77a247ed",
"size": 24082103
}
}
}
},
"osx-arm64": {
"org.lwjgl.lwjgl:lwjgl-platform:2.9.1-nightly-20130708-debug3:natives": {
@@ -5274,7 +5274,7 @@
}
}
},
"org.lwjgl:lwjgl:3.3.1:natives": {
"org.lwjgl:lwjgl:3.3.1:natives-linux": {
"name": "org.lwjgl:lwjgl:3.3.4:natives-freebsd",
"downloads": {
"artifact": {
@@ -5296,7 +5296,7 @@
}
}
},
"org.lwjgl:lwjgl-jemalloc:3.3.1:natives": {
"org.lwjgl:lwjgl-jemalloc:3.3.1:natives-linux": {
"name": "org.lwjgl:lwjgl-jemalloc:3.3.4:natives-freebsd",
"downloads": {
"artifact": {
@@ -5318,7 +5318,7 @@
}
}
},
"org.lwjgl:lwjgl-openal:3.3.1:natives": {
"org.lwjgl:lwjgl-openal:3.3.1:natives-linux": {
"name": "org.lwjgl:lwjgl-openal:3.3.4:natives-freebsd",
"downloads": {
"artifact": {
@@ -5340,7 +5340,7 @@
}
}
},
"org.lwjgl:lwjgl-opengl:3.3.1:natives": {
"org.lwjgl:lwjgl-opengl:3.3.1:natives-linux": {
"name": "org.lwjgl:lwjgl-opengl:3.3.4:natives-freebsd",
"downloads": {
"artifact": {
@@ -5362,7 +5362,7 @@
}
}
},
"org.lwjgl:lwjgl-glfw:3.3.1:natives": {
"org.lwjgl:lwjgl-glfw:3.3.1:natives-linux": {
"name": "org.lwjgl:lwjgl-glfw:3.3.4:natives-freebsd",
"downloads": {
"artifact": {
@@ -5384,7 +5384,7 @@
}
}
},
"org.lwjgl:lwjgl-stb:3.3.1:natives": {
"org.lwjgl:lwjgl-stb:3.3.1:natives-linux": {
"name": "org.lwjgl:lwjgl-stb:3.3.4:natives-freebsd",
"downloads": {
"artifact": {
@@ -5406,7 +5406,7 @@
}
}
},
"org.lwjgl:lwjgl-tinyfd:3.3.1:natives": {
"org.lwjgl:lwjgl-tinyfd:3.3.1:natives-linux": {
"name": "org.lwjgl:lwjgl-tinyfd:3.3.4:natives-freebsd",
"downloads": {
"artifact": {
@@ -5428,7 +5428,7 @@
}
}
},
"org.lwjgl:lwjgl:3.3.2:natives": {
"org.lwjgl:lwjgl:3.3.2:natives-linux": {
"name": "org.lwjgl:lwjgl:3.3.4:natives-freebsd",
"downloads": {
"artifact": {
@@ -5450,7 +5450,7 @@
}
}
},
"org.lwjgl:lwjgl-jemalloc:3.3.2:natives": {
"org.lwjgl:lwjgl-jemalloc:3.3.2:natives-linux": {
"name": "org.lwjgl:lwjgl-jemalloc:3.3.4:natives-freebsd",
"downloads": {
"artifact": {
@@ -5472,7 +5472,7 @@
}
}
},
"org.lwjgl:lwjgl-openal:3.3.2:natives": {
"org.lwjgl:lwjgl-openal:3.3.2:natives-linux": {
"name": "org.lwjgl:lwjgl-openal:3.3.4:natives-freebsd",
"downloads": {
"artifact": {
@@ -5494,7 +5494,7 @@
}
}
},
"org.lwjgl:lwjgl-opengl:3.3.2:natives": {
"org.lwjgl:lwjgl-opengl:3.3.2:natives-linux": {
"name": "org.lwjgl:lwjgl-opengl:3.3.4:natives-freebsd",
"downloads": {
"artifact": {
@@ -5516,7 +5516,7 @@
}
}
},
"org.lwjgl:lwjgl-glfw:3.3.2:natives": {
"org.lwjgl:lwjgl-glfw:3.3.2:natives-linux": {
"name": "org.lwjgl:lwjgl-glfw:3.3.4:natives-freebsd",
"downloads": {
"artifact": {
@@ -5538,7 +5538,7 @@
}
}
},
"org.lwjgl:lwjgl-stb:3.3.2:natives": {
"org.lwjgl:lwjgl-stb:3.3.2:natives-linux": {
"name": "org.lwjgl:lwjgl-stb:3.3.4:natives-freebsd",
"downloads": {
"artifact": {
@@ -5560,7 +5560,7 @@
}
}
},
"org.lwjgl:lwjgl-tinyfd:3.3.2:natives": {
"org.lwjgl:lwjgl-tinyfd:3.3.2:natives-linux": {
"name": "org.lwjgl:lwjgl-tinyfd:3.3.4:natives-freebsd",
"downloads": {
"artifact": {
@@ -5571,6 +5571,182 @@
}
}
},
"org.lwjgl:lwjgl:3.3.3": {
"name": "org.lwjgl:lwjgl:3.3.4",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar",
"sha1": "b86c3e4832426e8a6b466013b7cb34b40e9ce956",
"size": 800127
}
}
},
"org.lwjgl:lwjgl:3.3.3:natives-linux": {
"name": "org.lwjgl:lwjgl:3.3.4:natives-freebsd",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-freebsd.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-freebsd.jar",
"sha1": "610d14530e637564d97d74af7cb98a737e70b77b",
"size": 96209
}
}
},
"org.lwjgl:lwjgl-jemalloc:3.3.3": {
"name": "org.lwjgl:lwjgl-jemalloc:3.3.4",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar",
"sha1": "e3f5dcb8e13f3a5ed3f740fd30a114cee2a80bc4",
"size": 46430
}
}
},
"org.lwjgl:lwjgl-jemalloc:3.3.3:natives-linux": {
"name": "org.lwjgl:lwjgl-jemalloc:3.3.4:natives-freebsd",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-freebsd.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-freebsd.jar",
"sha1": "5ee27f3bad4715067cef0630682da4bb5a1b88ac",
"size": 157297
}
}
},
"org.lwjgl:lwjgl-openal:3.3.3": {
"name": "org.lwjgl:lwjgl-openal:3.3.4",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar",
"sha1": "9b74d3ea380c83353d42af43ad9659e04dabe84a",
"size": 113103
}
}
},
"org.lwjgl:lwjgl-openal:3.3.3:natives-linux": {
"name": "org.lwjgl:lwjgl-openal:3.3.4:natives-freebsd",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-freebsd.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-freebsd.jar",
"sha1": "3863f8268f5515c27f1364257f8a018f0c6afa79",
"size": 597486
}
}
},
"org.lwjgl:lwjgl-opengl:3.3.3": {
"name": "org.lwjgl:lwjgl-opengl:3.3.4",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar",
"sha1": "2852ac7d9f6fc71349f1ce28e2708ff1977f18af",
"size": 931960
}
}
},
"org.lwjgl:lwjgl-opengl:3.3.3:natives-linux": {
"name": "org.lwjgl:lwjgl-opengl:3.3.4:natives-freebsd",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-freebsd.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-freebsd.jar",
"sha1": "579071d2a3714f5662522f7d3edf58e941580587",
"size": 81028
}
}
},
"org.lwjgl:lwjgl-glfw:3.3.3": {
"name": "org.lwjgl:lwjgl-glfw:3.3.4",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar",
"sha1": "7e46ecdec85db8738053cfde1414352cd62dab74",
"size": 147044
}
}
},
"org.lwjgl:lwjgl-glfw:3.3.3:natives-linux": {
"name": "org.lwjgl:lwjgl-glfw:3.3.4:natives-freebsd",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-freebsd.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-freebsd.jar",
"sha1": "f67b9b6c29451d8fea66db17aaba2f65e908c7e9",
"size": 104415
}
}
},
"org.lwjgl:lwjgl-stb:3.3.3": {
"name": "org.lwjgl:lwjgl-stb:3.3.4",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar",
"sha1": "5821735d5ef23f6da8542887344e57eb181b7cac",
"size": 143112
}
}
},
"org.lwjgl:lwjgl-stb:3.3.3:natives-linux": {
"name": "org.lwjgl:lwjgl-stb:3.3.4:natives-freebsd",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-freebsd.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-freebsd.jar",
"sha1": "f5551338a1e2035ff747053f0e985dc93db1235c",
"size": 226093
}
}
},
"org.lwjgl:lwjgl-tinyfd:3.3.3": {
"name": "org.lwjgl:lwjgl-tinyfd:3.3.4",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar",
"sha1": "2d73789ffd8962b38d9d599cc38b2383ce818c7a",
"size": 15928
}
}
},
"org.lwjgl:lwjgl-tinyfd:3.3.3:natives-linux": {
"name": "org.lwjgl:lwjgl-tinyfd:3.3.4:natives-freebsd",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-freebsd.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-freebsd.jar",
"sha1": "acd5e1b9b9b99ce4d21867058ee468ee45a859e5",
"size": 40104
}
}
},
"org.lwjgl:lwjgl-freetype:3.3.3": {
"name": "org.lwjgl:lwjgl-freetype:3.3.4",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-freetype/3.3.4/lwjgl-freetype-3.3.4.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-freetype/3.3.4/lwjgl-freetype-3.3.4.jar",
"sha1": "23f7bf165068ef2ca80ae1b79fd905af20498600",
"size": 453489
}
}
},
"org.lwjgl:lwjgl-freetype:3.3.3:natives-linux": {
"name": "org.lwjgl:lwjgl-freetype:3.3.4:natives-freebsd",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-freetype/3.3.4/lwjgl-freetype-3.3.4-natives-freebsd.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-freetype/3.3.4/lwjgl-freetype-3.3.4-natives-freebsd.jar",
"sha1": "67d6775292771087cb3d5ba1239bf9bf42fb3bd7",
"size": 1176759
}
}
},
"net.java.jinput:jinput-platform:2.0.5:natives": null,
"com.mojang:text2speech:1.10.3:natives": null,
"com.mojang:text2speech:1.11.3:natives": null,
+1 -1
View File
@@ -13,7 +13,7 @@ dependencies {
api("org.nanohttpd:nanohttpd:2.3.1")
api("org.apache.commons:commons-compress:1.25.0")
api("org.jsoup:jsoup:1.18.1")
compileOnlyApi("org.jetbrains:annotations:24.1.0")
compileOnlyApi("org.jetbrains:annotations:26.0.1")
if (JavaVersion.current().isJava8) {
org.gradle.internal.jvm.Jvm.current().toolsJar?.let {
@@ -109,7 +109,7 @@ public class OAuth {
options.callback.openBrowser(deviceTokenResponse.verificationURI);
long startTime = System.nanoTime();
int interval = deviceTokenResponse.interval;
long interval = TimeUnit.MILLISECONDS.convert(deviceTokenResponse.interval, TimeUnit.SECONDS);
while (true) {
Thread.sleep(Math.max(interval, 1));
@@ -138,7 +138,7 @@ public class OAuth {
}
if ("slow_down".equals(tokenResponse.error)) {
interval += 5;
interval += 5000;
continue;
}
@@ -72,6 +72,7 @@ public final class BMCLAPIDownloadProvider implements DownloadProvider {
pair("https://files.minecraftforge.net/maven", apiRoot + "/maven"),
pair("https://maven.minecraftforge.net", apiRoot + "/maven"),
pair("https://maven.neoforged.net/releases/net/neoforged/forge", apiRoot + "/maven/net/neoforged/forge"),
pair("https://maven.neoforged.net/releases/net/neoforged/neoforge", apiRoot + "/maven/net/neoforged/neoforge"),
pair("http://dl.liteloader.com/versions/versions.json", apiRoot + "/maven/com/mumfrey/liteloader/versions.json"),
pair("http://dl.liteloader.com/versions", apiRoot + "/maven"),
pair("https://meta.fabricmc.net", apiRoot + "/fabric-meta"),
@@ -18,25 +18,13 @@
package org.jackhuang.hmcl.game;
import com.google.gson.JsonParseException;
import org.jackhuang.hmcl.util.Constants;
import org.jackhuang.hmcl.util.Immutable;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.ToStringBuilder;
import org.jackhuang.hmcl.util.*;
import org.jackhuang.hmcl.util.gson.JsonMap;
import org.jackhuang.hmcl.util.gson.Validation;
import org.jetbrains.annotations.Nullable;
import java.time.Instant;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.*;
import java.util.stream.Collectors;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
@@ -229,7 +217,42 @@ public class Version implements Comparable<Version>, Validation {
public AssetIndexInfo getAssetIndex() {
String assetsId = assets == null ? "legacy" : assets;
return assetIndex == null ? new AssetIndexInfo(assetsId, Constants.DEFAULT_INDEX_URL + assetsId + ".json") : assetIndex;
if (assetIndex == null) {
String hash;
switch (assetsId) {
case "1.8":
hash = "f6ad102bcaa53b1a58358f16e376d548d44933ec";
break;
case "14w31a":
hash = "10a2a0e75b03cfb5a7196abbdf43b54f7fa61deb";
break;
case "14w25a":
hash = "32ff354a3be1c4dd83027111e6d79ee4d701d2c0";
break;
case "1.7.4":
hash = "545510a60f526b9aa8a38f9c0bc7a74235d21675";
break;
case "1.7.10":
hash = "1863782e33ce7b584fc45b037325a1964e095d3e";
break;
case "1.7.3":
hash = "f6cf726f4747128d13887010c2cbc44ba83504d9";
break;
case "pre-1.6":
hash = "3d8e55480977e32acd9844e545177e69a52f594b";
break;
case "legacy":
default:
assetsId = "legacy";
hash = "770572e819335b6c0a053f8378ad88eda189fc14";
}
String url = Constants.DEFAULT_INDEX_URL + hash + "/" + assetsId + ".json";
return new AssetIndexInfo(assetsId, url);
} else {
return assetIndex;
}
}
public boolean appliesToCurrentEnvironment() {
@@ -496,6 +496,13 @@ public class DefaultLauncher extends Launcher {
break;
case ZINK:
env.put("MESA_LOADER_DRIVER_OVERRIDE", "zink");
/**
* The amdgpu DDX is missing support for modifiers, causing Zink to fail.
* Disable DRI3 to workaround this issue.
*
* Link: https://gitlab.freedesktop.org/mesa/mesa/-/issues/10093
*/
env.put("LIBGL_KOPPER_DRI2", "1");
break;
}
}
@@ -160,12 +160,17 @@ public final class ForgeNewModMetadata {
private static ModLoaderType analyzeLoader(Toml toml, String modID, int loaderACC, ModLoaderType defaultLoader) throws IOException {
List<HashMap<String, Object>> dependencies = toml.getList("dependencies." + modID);
if (dependencies != null) {
for (HashMap<String, Object> dependency : dependencies) {
switch ((String) dependency.get("modId")) {
case "forge": return checkLoaderACC(loaderACC, ACC_FORGE, ModLoaderType.FORGE);
case "neoforge": return checkLoaderACC(loaderACC, ACC_NEO_FORGED, ModLoaderType.NEO_FORGED);
}
if (dependencies == null) {
dependencies = toml.getList("dependencies"); // ??? I have no idea why some of the Forge mods use [[dependencies]]
if (dependencies == null) {
return defaultLoader;
}
}
for (HashMap<String, Object> dependency : dependencies) {
switch ((String) dependency.get("modId")) {
case "forge": return checkLoaderACC(loaderACC, ACC_FORGE, ModLoaderType.FORGE);
case "neoforge": return checkLoaderACC(loaderACC, ACC_NEO_FORGED, ModLoaderType.NEO_FORGED);
}
}
@@ -58,6 +58,7 @@ public final class MultiMCInstanceConfiguration implements ModpackManifest {
private final boolean overrideConsole; // OverrideConsole
private final boolean overrideCommands; // OverrideCommands
private final boolean overrideWindow; // OverrideWindow
private final String iconKey;
private final MultiMCManifest mmcPack;
@@ -94,6 +95,7 @@ public final class MultiMCInstanceConfiguration implements ModpackManifest {
wrapperCommand = readValue(p, "WrapperCommand");
name = defaultName;
notes = Optional.ofNullable(readValue(p, "notes")).orElse("");
iconKey = readValue(p, "iconKey");
}
/**
@@ -112,7 +114,7 @@ public final class MultiMCInstanceConfiguration implements ModpackManifest {
return value;
}
public MultiMCInstanceConfiguration(String instanceType, String name, String gameVersion, Integer permGen, String wrapperCommand, String preLaunchCommand, String postExitCommand, String notes, String javaPath, String jvmArgs, boolean fullscreen, Integer width, Integer height, Integer maxMemory, Integer minMemory, boolean showConsole, boolean showConsoleOnError, boolean autoCloseConsole, boolean overrideMemory, boolean overrideJavaLocation, boolean overrideJavaArgs, boolean overrideConsole, boolean overrideCommands, boolean overrideWindow) {
public MultiMCInstanceConfiguration(String instanceType, String name, String gameVersion, Integer permGen, String wrapperCommand, String preLaunchCommand, String postExitCommand, String notes, String javaPath, String jvmArgs, boolean fullscreen, Integer width, Integer height, Integer maxMemory, Integer minMemory, boolean showConsole, boolean showConsoleOnError, boolean autoCloseConsole, boolean overrideMemory, boolean overrideJavaLocation, boolean overrideJavaArgs, boolean overrideConsole, boolean overrideCommands, boolean overrideWindow, String iconKey) {
this.instanceType = instanceType;
this.name = name;
this.gameVersion = gameVersion;
@@ -138,6 +140,7 @@ public final class MultiMCInstanceConfiguration implements ModpackManifest {
this.overrideCommands = overrideCommands;
this.overrideWindow = overrideWindow;
this.mmcPack = null;
this.iconKey = iconKey;
}
public String getInstanceType() {
@@ -310,6 +313,10 @@ public final class MultiMCInstanceConfiguration implements ModpackManifest {
return overrideWindow;
}
public String getIconKey() {
return iconKey;
}
public Properties toProperties() {
Properties p = new Properties();
if (instanceType != null) p.setProperty("InstanceType", instanceType);
@@ -336,6 +343,7 @@ public final class MultiMCInstanceConfiguration implements ModpackManifest {
if (wrapperCommand != null) p.setProperty("WrapperCommand", wrapperCommand);
if (name != null) p.setProperty("name", name);
if (notes != null) p.setProperty("notes", notes);
if (iconKey != null) p.setProperty("iconKey", iconKey);
return p;
}
@@ -199,6 +199,14 @@ public final class MultiMCModpackInstallTask extends Task<Void> {
Path jarmods = root.resolve("jarmods");
if (Files.exists(jarmods))
FileUtils.copyDirectory(jarmods, repository.getVersionRoot(name).toPath().resolve("jarmods"));
String iconKey = this.manifest.getIconKey();
if (iconKey != null) {
Path iconFile = root.resolve(iconKey + ".png");
if (Files.exists(iconFile)) {
FileUtils.copyFile(iconFile, repository.getVersionRoot(name).toPath().resolve("icon.png"));
}
}
}
dependencies.add(repository.saveAsync(version));
@@ -29,5 +29,5 @@ public final class Constants {
public static final String DEFAULT_LIBRARY_URL = "https://libraries.minecraft.net/";
public static final String DEFAULT_VERSION_DOWNLOAD_URL = "https://bmclapi2.bangbang93.com/versions/";
public static final String DEFAULT_INDEX_URL = "https://launchermeta.mojang.com/mc-staging/assets/legacy/c0fd82e8ce9fbc93119e40d96d5a4e62cfa3f729/";
public static final String DEFAULT_INDEX_URL = "https://launchermeta.mojang.com/v1/packages/";
}
@@ -389,7 +389,7 @@ public final class Lang {
public static synchronized Timer getTimer() {
if (timer == null) {
timer = new Timer();
timer = new Timer(true);
}
return timer;
}
@@ -786,4 +786,14 @@
1.21.2-rc1
1.21.2-rc2
1.21.2
1.21.3
1.21.3
24w44a
24w45a
24w46a
1.21.4-pre1
1.21.4-pre2
1.21.4-pre3
1.21.4-rc1
1.21.4-rc2
1.21.4-rc3
1.21.4
+12 -12
View File
@@ -2,18 +2,18 @@
**English** | [简体中文](PLATFORM_cn.md) | [繁體中文](PLATFORM_tw.md)
| | Windows | Linux | macOS | FreeBSD |
| -------------------------- | :----------------------------------------------- | :------------------------ | :--------------------------------------------------------------------- | :------------------------ |
| x86-64 | ✅️ | ✅️ | ✅️ | 👌 (Minecraft 1.13~1.21.1) |
| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / |
| ARM64 | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21) | 👌 (Minecraft 1.6~1.18.2)<br/>✅ (Minecraft 1.19+)<br/>✅ (use Rosetta 2) | ❔ |
| ARM32 | /️ | 👌 (Minecraft 1.8~1.20.1) | / | / |
| MIPS64el | / | 👌 (Minecraft 1.8~1.20.1) | / | / |
| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.1) | / | / |
| LoongArch64 | / | 👌 (Minecraft 1.6~1.21.1) | / | / |
| LoongArch64 (Old World) | / | 👌 (Minecraft 1.6~1.20.1) | / | / |
| PowerPC-64 (Little-Endian) | / | ❔ | / | / |
| S390x | / | ❔ | / | / |
| | Windows | Linux | macOS | FreeBSD |
|----------------------------|:--------------------------------------------------|:---------------------------|:------------------------------------------------------------------------|:---------------------------|
| x86-64 | ✅️ | ✅️ | ✅️ | 👌 (Minecraft 1.13~1.21.4) |
| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / |
| ARM64 | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21.4) | 👌 (Minecraft 1.6~1.18.2)<br/>✅ (Minecraft 1.19+)<br/>✅ (use Rosetta 2) | ❔ |
| ARM32 | /️ | 👌 (Minecraft 1.8~1.20.1) | / | / |
| MIPS64el | / | 👌 (Minecraft 1.8~1.20.1) | / | / |
| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.4) | / | / |
| LoongArch64 | / | 👌 (Minecraft 1.6~1.21.4) | / | / |
| LoongArch64 (Old World) | / | 👌 (Minecraft 1.6~1.20.1) | / | / |
| PowerPC-64 (Little-Endian) | / | ❔ | / | / |
| S390x | / | ❔ | / | / |
Legend:
+12 -12
View File
@@ -2,18 +2,18 @@
[English](PLATFORM.md) | **简体中文** | [繁體中文](PLATFORM_tw.md)
| | Windows | Linux | macOS | FreeBSD |
| -------------------------- | :----------------------------------------------- | :------------------------ | :---------------------------------------------------------------------- | :----------------------- |
| x86-64 | ✅️ | ✅️ | ✅️ | 👌(Minecraft 1.13~1.21.1) |
| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / |
| ARM64 | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21) | 👌 (Minecraft 1.6~1.18.2)<br/>✅ (Minecraft 1.19+)<br/>✅ (使用 Rosetta 2) | ❔ |
| ARM32 | /️ | 👌 (Minecraft 1.8~1.20.1) | / | / |
| MIPS64el | / | 👌 (Minecraft 1.8~1.20.1) | / | / |
| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.1) | / | / |
| LoongArch64 | / | 👌 (Minecraft 1.6~1.21.1) | / | / |
| LoongArch64 (旧世界) | / | 👌 (Minecraft 1.6~1.20.1) | / | / |
| PowerPC-64 (Little-Endian) | / | ❔ | / | / |
| S390x | / | ❔ | / | / |
| | Windows | Linux | macOS | FreeBSD |
|----------------------------|:--------------------------------------------------|:---------------------------|:-----------------------------------------------------------------------|:--------------------------|
| x86-64 | ✅️ | ✅️ | ✅️ | 👌(Minecraft 1.13~1.21.4) |
| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / |
| ARM64 | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21.4) | 👌 (Minecraft 1.6~1.18.2)<br/>✅ (Minecraft 1.19+)<br/>✅ (使用 Rosetta 2) | ❔ |
| ARM32 | /️ | 👌 (Minecraft 1.8~1.20.1) | / | / |
| MIPS64el | / | 👌 (Minecraft 1.8~1.20.1) | / | / |
| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.4) | / | / |
| LoongArch64 | / | 👌 (Minecraft 1.6~1.21.4) | / | / |
| LoongArch64 (旧世界) | / | 👌 (Minecraft 1.6~1.20.1) | / | / |
| PowerPC-64 (Little-Endian) | / | ❔ | / | / |
| S390x | / | ❔ | / | / |
图例:
+12 -12
View File
@@ -2,18 +2,18 @@
[English](PLATFORM.md) | [简体中文](PLATFORM_cn.md) | **繁體中文**
| | Windows | Linux | macOS | FreeBSD |
| -------------------------- | :----------------------------------------------- | :------------------------ | :---------------------------------------------------------------------- | :----------------------- |
| x86-64 | ✅️ | ✅️ | ✅️ | 👌(Minecraft 1.13~1.21.1) |
| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / |
| ARM64 | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21) | 👌 (Minecraft 1.6~1.18.2)<br/>✅ (Minecraft 1.19+)<br/>✅ (使用 Rosetta 2) | ❔ |
| ARM32 | /️ | 👌 (Minecraft 1.8~1.20.1) | / | / |
| MIPS64el | / | 👌 (Minecraft 1.8~1.20.1) | / | / |
| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.1) | / | / |
| LoongArch64 | / | 👌 (Minecraft 1.6~1.21.1) | / | / |
| LoongArch64 (舊世界) | / | 👌 (Minecraft 1.6~1.20.1) | / | / |
| PowerPC-64 (Little-Endian) | / | ❔ | / | / |
| S390x | / | ❔ | / | / |
| | Windows | Linux | macOS | FreeBSD |
|----------------------------|:--------------------------------------------------|:---------------------------|:-----------------------------------------------------------------------|:--------------------------|
| x86-64 | ✅️ | ✅️ | ✅️ | 👌(Minecraft 1.13~1.21.4) |
| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / |
| ARM64 | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21.4) | 👌 (Minecraft 1.6~1.18.2)<br/>✅ (Minecraft 1.19+)<br/>✅ (使用 Rosetta 2) | ❔ |
| ARM32 | /️ | 👌 (Minecraft 1.8~1.20.1) | / | / |
| MIPS64el | / | 👌 (Minecraft 1.8~1.20.1) | / | / |
| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.4) | / | / |
| LoongArch64 | / | 👌 (Minecraft 1.6~1.21.4) | / | / |
| LoongArch64 (舊世界) | / | 👌 (Minecraft 1.6~1.20.1) | / | / |
| PowerPC-64 (Little-Endian) | / | ❔ | / | / |
| S390x | / | ❔ | / | / |
圖例:
+4 -4
View File
@@ -4,7 +4,7 @@
![Downloads](https://img.shields.io/github/downloads/HMCL-dev/HMCL/total?style=flat)
![Stars](https://img.shields.io/github/stars/HMCL-dev/HMCL?style=flat)
[![Discord](https://img.shields.io/discord/995291757799538688.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/jVvC7HfM6U)
[![QQ Group](https://img.shields.io/badge/QQ-HMCL-bright?label=&logo=tencentqq&logoColor=ffffff&color=1EBAFC&labelColor=1DB0EF&logoSize=auto)](https://docs.hmcl.net/groups.html)
[![QQ Group](https://img.shields.io/badge/QQ-HMCL-bright?label=&logo=qq&logoColor=ffffff&color=1EBAFC&labelColor=1DB0EF&logoSize=auto)](https://docs.hmcl.net/groups.html)
**English** | [简体中文](README_cn.md) | [繁體中文](README_tw.md)
@@ -12,13 +12,13 @@
HMCL is an open-source, cross-platform Minecraft launcher that supports Mod Management, Game Customizing, ModLoader Installing (Forge, NeoForge, Fabric, Quilt, LiteLoader, and OptiFine), Modpack Creating, UI Customization, and more.
HMCL has amazing cross-platform capabilities. Not only does it run on different operating systems like Windows, Linux, and macOS, but it also supports various CPU architectures such as x86, ARM, MIPS, and LoongArch. You can easily enjoy Minecraft across different platforms through HMCL.
HMCL has amazing cross-platform capabilities. Not only does it run on different operating systems like Windows, Linux, and macOS, but it also supports various CPU architectures such as x86, ARM, RISC-V, MIPS, and LoongArch. You can easily enjoy Minecraft across different platforms through HMCL.
For systems and CPU architectures supported by HMCL, see [this table](PLATFORM.md).
For systems and CPU architectures supported by HMCL, please refer to [this table](PLATFORM.md).
## Download
Download the latest version from [the official website](https://hmcl.huangyuhui.net/download).
Download the latest version from the [official website](https://hmcl.huangyuhui.net/download).
You can also find the latest version of HMCL in [GitHub Releases](https://github.com/HMCL-dev/HMCL/releases).
+3 -3
View File
@@ -4,7 +4,7 @@
![Downloads](https://img.shields.io/github/downloads/HMCL-dev/HMCL/total?style=flat)
![Stars](https://img.shields.io/github/stars/HMCL-dev/HMCL?style=flat)
[![Discord](https://img.shields.io/discord/995291757799538688.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/jVvC7HfM6U)
[![QQ Group](https://img.shields.io/badge/QQ-HMCL-bright?label=&logo=tencentqq&logoColor=ffffff&color=1EBAFC&labelColor=1DB0EF&logoSize=auto)](https://docs.hmcl.net/groups.html)
[![QQ Group](https://img.shields.io/badge/QQ-HMCL-bright?label=&logo=qq&logoColor=ffffff&color=1EBAFC&labelColor=1DB0EF&logoSize=auto)](https://docs.hmcl.net/groups.html)
[English](README.md) | **简体中文** | [繁體中文](README_tw.md)
@@ -12,7 +12,7 @@
HMCL 是一款开源、跨平台的 Minecraft 启动器,支持模组管理、游戏自定义、游戏自动安装 (Forge、NeoForge、Fabric、Quilt、LiteLoader 和 OptiFine)、整合包创建、界面自定义等功能。
HMCL 有着强大的跨平台能力。它不仅支持 Windows、Linux、macOS 等常见的操作系统,同时也支持 x86、ARM、MIPS 和 LoongArch 等不同的 CPU 架构。你可以使用 HMCL 在不同平台上轻松地游玩 Minecraft。
HMCL 有着强大的跨平台能力。它不仅支持 Windows、Linux、macOS 等常见的操作系统,同时也支持 x86、ARM、RISC-V、MIPS、LoongArch 等不同的 CPU 架构。你可以使用 HMCL 在不同平台上轻松地游玩 Minecraft。
如果你想要了解 HMCL 对不同平台的支持程度,请参见 [此表格](PLATFORM_cn.md)。
@@ -59,7 +59,7 @@ HMCL 有着强大的跨平台能力。它不仅支持 Windows、Linux、macOS
| 参数 | 简介 |
| -------------------------------------------- | -------------------------------------------------------------------- |
| `-Dhmcl.home=<path>` | 覆盖 HMCL 数据文件夹 |
| `-Dhmcl.self_integrity_check.disable=true` | 检查更新时绕过本体完整性检查 |
| `-Dhmcl.self_integrity_check.disable=true` | 检查更新时不检查本体完整性 |
| `-Dhmcl.bmclapi.override=<url>` | 覆盖 BMCLAPI 的 API Root,默认值为 `https://bmclapi2.bangbang93.com` |
| `-Dhmcl.font.override=<font family>` | 覆盖字族 |
| `-Dhmcl.version.override=<version>` | 覆盖版本号 |
+4 -4
View File
@@ -4,15 +4,15 @@
![Downloads](https://img.shields.io/github/downloads/HMCL-dev/HMCL/total?style=flat)
![Stars](https://img.shields.io/github/stars/HMCL-dev/HMCL?style=flat)
[![Discord](https://img.shields.io/discord/995291757799538688.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/jVvC7HfM6U)
[![QQ Group](https://img.shields.io/badge/QQ-HMCL-bright?label=&logo=tencentqq&logoColor=ffffff&color=1EBAFC&labelColor=1DB0EF&logoSize=auto)](https://docs.hmcl.net/groups.html)
[![QQ Group](https://img.shields.io/badge/QQ-HMCL-bright?label=&logo=qq&logoColor=ffffff&color=1EBAFC&labelColor=1DB0EF&logoSize=auto)](https://docs.hmcl.net/groups.html)
[English](README.md) | [简体中文](README_cn.md) | **繁體中文**
## 簡介
HMCL 是一款開源、跨平臺的 Minecraft 啟動器,支援模組管理、遊戲自訂、遊戲自動安裝 (Forge、NeoForge、Fabric、Quilt、LiteLoader 和 OptiFine)、模組包建立、介面自訂等功能。
HMCL 是一款開源、跨平臺的 Minecraft 啟動器,支援模組管理、遊戲客製化、遊戲自動安裝 (Forge、NeoForge、Fabric、Quilt、LiteLoader 和 OptiFine)、模組包建立、介面客製化等功能。
HMCL 有著強大的跨平臺能力。它不僅支援 Windows、Linux、macOS 等常見的作業系統,同時也支援 x86、ARM、MIPS 和 LoongArch 等不同的 CPU 架構。你可以使用 HMCL 在不同平臺上輕鬆地遊玩 Minecraft。
HMCL 有著強大的跨平臺能力。它不僅支援 Windows、Linux、macOS 等常見的作業系統,同時也支援 x86、ARM、RISC-V、MIPS、LoongArch 等不同的 CPU 架構。你可以使用 HMCL 在不同平臺上輕鬆地遊玩 Minecraft。
如果你想要了解 HMCL 對不同平臺的支援程度,請參見 [此表格](PLATFORM_tw.md)。
@@ -59,7 +59,7 @@ HMCL 有著強大的跨平臺能力。它不僅支援 Windows、Linux、macOS
| 參數 | 簡介 |
| -------------------------------------------- | -------------------------------------------------------------------- |
| `-Dhmcl.home=<path>` | 覆蓋 HMCL 使用者目錄 |
| `-Dhmcl.self_integrity_check.disable=true` | 檢查更新時繞過本體完整性檢查 |
| `-Dhmcl.self_integrity_check.disable=true` | 檢查更新時不檢查本體完整性 |
| `-Dhmcl.bmclapi.override=<url>` | 覆蓋 BMCLAPI 的 API Root,預設值為 `https://bmclapi2.bangbang93.com` |
| `-Dhmcl.font.override=<font family>` | 覆蓋字族 |
| `-Dhmcl.version.override=<version>` | 覆蓋版本號 |
+12
View File
@@ -0,0 +1,12 @@
repositories {
mavenCentral()
}
dependencies {
implementation("com.google.code.gson:gson:2.11.0")
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
@@ -0,0 +1,218 @@
package org.jackhuang.hmcl.gradle.mod;
import com.google.gson.*;
import com.google.gson.annotations.JsonAdapter;
import com.google.gson.reflect.TypeToken;
import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.lang.reflect.Type;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public abstract class ParseModDataTask extends DefaultTask {
@InputFile
public abstract RegularFileProperty getInputFile();
@OutputFile
public abstract RegularFileProperty getOutputFile();
// ---
private static final Logger LOGGER = Logging.getLogger(ParseModDataTask.class);
private static final String S = ";";
private static final String MOD_SEPARATOR = ",";
private static final Pattern[] CURSEFORGE_PATTERNS = {
Pattern.compile("^/(minecraft|Minecraft|minecraft-bedrock)/(mc-mods|data-packs|modpacks|customization|mc-addons|texture-packs|customization/configuration|addons)/+(?<modid>[\\w-]+)(/(.*?))?$"),
Pattern.compile("^/projects/(?<modid>[\\w-]+)(/(.*?))?$"),
Pattern.compile("^/mc-mods/minecraft/(?<modid>[\\w-]+)(/(.*?))?$"),
Pattern.compile("^/legacy/mc-mods/minecraft/(\\d+)-(?<modid>[\\w-]+)"),
Pattern.compile("^//minecraft/mc-mods/(?<modid>eyes-mod)$") // Workaround for Eye Blocks
};
private static String parseCurseforge(String url) {
URI res = URI.create(url);
if (!"http".equals(res.getScheme())
&& !"https".equals(res.getScheme())
&& !"www.curseforge.com".equals(res.getHost())) { // Workaround for DakerACG
return "";
}
for (Pattern pattern : CURSEFORGE_PATTERNS) {
Matcher matcher = pattern.matcher(res.getPath());
if (matcher.matches()) {
return matcher.group("modid");
}
}
return "";
}
private static final Pattern MCMOD_PATTERN =
Pattern.compile("^https://www\\.mcmod\\.cn/(class|modpack)/(?<modid>\\d+)\\.html$");
private static String parseMcMod(String url) {
Matcher matcher = MCMOD_PATTERN.matcher(url);
if (matcher.matches()) {
return matcher.group("modid");
}
return "";
}
private static final Set<String> skip = new HashSet<>(Arrays.asList(
"Minecraft",
"The Building Game"
));
@TaskAction
public void run() throws IOException {
Path inputFile = getInputFile().get().getAsFile().toPath().toAbsolutePath();
Path outputFile = getOutputFile().get().getAsFile().toPath().toAbsolutePath();
Files.createDirectories(outputFile.getParent());
List<ModData> modDatas;
try (BufferedReader reader = Files.newBufferedReader(inputFile)) {
modDatas = new Gson().fromJson(reader, TypeToken.getParameterized(List.class, ModData.class).getType());
}
try (BufferedWriter writer = Files.newBufferedWriter(outputFile, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE)) {
writer.write("#\n" +
"# Hello Minecraft! Launcher\n" +
"# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors\n" +
"#\n" +
"# mcmod.cn\n" +
"# Copyright (C) 2025. All Rights Reserved.\n" +
"#\n");
for (ModData mod : modDatas) {
String chineseName = mod.name.main;
String subName = mod.name.sub;
String abbr = mod.name.abbr;
if (chineseName == null)
chineseName = "";
if (subName == null)
subName = "";
if (abbr == null)
abbr = "";
if (skip.contains(subName)) {
continue;
}
if (chineseName.contains(S) || subName.contains(S)) {
throw new GradleException("Error: " + chineseName);
}
String curseforgeId = "";
String mcmodId = "";
Map<String, List<ModData.Link>> links = mod.links.list;
List<ModData.Link> curseforgeLinks = links.get("curseforge");
List<ModData.Link> mcmodLinks = links.get("mcmod");
if (curseforgeLinks != null && !curseforgeLinks.isEmpty()) {
for (ModData.Link link : curseforgeLinks) {
curseforgeId = parseCurseforge(link.url);
if (!curseforgeId.isEmpty()) {
break;
}
}
if (curseforgeId.isEmpty()) {
LOGGER.warn("Error curseforge: {}", chineseName);
}
}
if (mcmodLinks != null && !mcmodLinks.isEmpty()) {
mcmodId = parseMcMod(mcmodLinks.get(0).url);
if (mcmodId.isEmpty()) {
throw new GradleException("Error mcmod: " + chineseName);
}
}
List<String> modId = new ArrayList<>();
if (mod.modid != null) {
for (String id : mod.modid) {
if (id.contains(MOD_SEPARATOR)) {
throw new GradleException("Error modid: " + id);
}
modId.add(id);
}
}
String modIds = String.join(MOD_SEPARATOR, modId);
writer.write(curseforgeId + S + mcmodId + S + modIds + S + chineseName + S + subName + S + abbr + "\n");
}
}
}
public static final class ModData {
public Name name;
@JsonAdapter(ModIdDeserializer.class)
public List<String> modid;
public Links links;
public static final class Name {
public String main;
public String sub;
public String abbr;
}
public static final class Link {
public String url;
public String content;
}
public static final class Links {
public Map<String, List<Link>> list;
}
public static final class ModIdDeserializer implements JsonDeserializer<List<String>> {
private static final Type STRING_LIST = TypeToken.getParameterized(List.class, String.class).getType();
@Override
public List<String> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
if (json.isJsonNull()) {
return null;
}
if (json.isJsonArray()) {
return context.deserialize(json, STRING_LIST);
} else {
JsonObject jsonObject = json.getAsJsonObject();
JsonElement list = jsonObject.get("list");
if (list == null) {
return null;
} else {
return context.deserialize(list, STRING_LIST);
}
}
}
}
}
}
-10
View File
@@ -1,13 +1,3 @@
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("com.google.code.gson:gson:2.11.0")
}
}
val jfxVersion = "17.0.13"
val oldJfxVersion = "19.0.2.1"