Compare commits

...
Author SHA1 Message Date
Glavo 578aaafb0a 使用 DDE 桌面时启用窗口动画 (#3682) 2025-03-02 00:28:44 +08:00
3gf8jv4dv 60deea64c8 chore(locale): improve russian translation (mods & nbt) (#3678)
- add `mods.check_updates.empty`
- add nbt translations
2025-03-01 21:05:08 +08:00
3gf8jv4dv 7e1cd4319b chore(locale): improve russian translation (modpack) (#3679)
- Revise some translations based on English
- Change some terminology to make sure to keep up with the game and wiki
- Add some missing translations
2025-03-01 21:04:54 +08:00
Glavo 318da840f9 修复对话框关闭动画缺失的问题 (#3676) 2025-03-01 21:01:51 +08:00
Glavo 0c7046d764 优化 TransitionPane (#3680)
* update

* update

* update

* update

* update

* update

* update

* update

* update

* Fix checkstyle
2025-03-01 20:50:31 +08:00
Glavo bdf4886445 禁止标题栏中返回键获取焦点 (#3677) 2025-03-01 10:20:39 +08:00
Glavo 4a758d045d 使更多标签支持双击复制 (#3673) 2025-03-01 01:47:56 +08:00
Glavo 1059a4b3d4 支持关闭鼠标进入 RipplerContainer 时的渐变动画 (#3670)
* update

* fix checkstyle
2025-02-28 23:56:42 +08:00
Glavo a25eb14da4 优化 TransitionPane (#3668) 2025-02-28 23:41:40 +08:00
Glavo 349b663c83 禁止标题栏按钮获取焦点 (#3666) 2025-02-28 01:54:53 +08:00
Glavo 04781da3a7 清理 FXUtils (#3667) 2025-02-28 01:53:31 +08:00
Glavo 96e1ddff79 Close #3664: 支持双击复制世界信息 (#3665)
* Close #3664: 支持双击复制世界信息

* update

* update
2025-02-27 23:58:20 +08:00
3gf8jv4dv e8502890a4 chore(locale): improve russian translation (game setting / management) (#3661)
- Re-translate something from English
- Fix some inconsistent terminology
- Revise text to fit UI
- Change some terminology to make sure to keep up with the game and wiki
- Add some missing translations
2025-02-27 21:19:31 +08:00
3gf8jv4dv 4d0619a9df chore(locale): improve russian translation (javafx dialog / search) (#3662) 2025-02-27 21:18:57 +08:00
3gf8jv4dv fba14737f9 chore(locale): revise unofficial hint for russian (#3660)
- add `unofficial.hint`
- fix typo in `account.methods.microsoft.snapshot`
2025-02-27 20:56:59 +08:00
3gf8jv4dv 5de98399c5 chore(locale): add wiki translation for russian (#3659) 2025-02-27 20:56:41 +08:00
Glavo 7adfa0e3d1 启动时尝试从 HMCL_DIRECTORY 中加载字体 (#3663)
* 启动时尝试从 HMCL_DIRECTORY 中加载字体

* update

* update
2025-02-27 15:05:51 +08:00
Glavo a45b636b1c 启用高帧率动画 (#3655)
* 启用高帧率动画

* update
2025-02-27 09:51:06 +08:00
Glavo 50c22e1f43 添加最小化和还原动画 (#3657)
* update

* update

* update

* update

* update

* update

* update

* update

* update

* fix checkstyle
2025-02-26 20:39:49 +08:00
Glavo 3b38c22398 修复标题过长时窗口异常的问题 (#3654) 2025-02-26 02:00:27 +08:00
Glavo 618243350e 优化 NBT 查看器 (#3653)
* update

* update

* update
2025-02-26 01:40:36 +08:00
Glavo ff4940e79f 添加关闭启动器时的淡出动画 (#3652)
* 添加关闭启动器时的淡出动画

* update
2025-02-26 01:04:12 +08:00
Glavo 1c163934ee Fix #2048: 当微软账户 Token 过期时应当刷新账户 (#3651) 2025-02-25 20:50:49 +08:00
Glavo c513d00c50 添加启动动画 (#3442)
* update

* update

* update
2025-02-25 18:20:44 +08:00
3gf8jv4dv 2c9549b36b fix(ui): limit width of FileSelector in offline skin pane (#3623)
make the text around the selector not be covered.

resolves https://github.com/HMCL-dev/HMCL/issues/3296
2025-02-25 17:58:31 +08:00
Glavo 3389e0a934 Close #3649: 点击账户卡片空白处时切换账户 (#3650)
* Close #3649: 点击账户卡片空白处时切换账户

* update
2025-02-25 17:36:51 +08:00
3gf8jv4dv 78149449a9 chore(locale): improve russian translation (modrinth category) (#3647)
- Add missing translations
- Fix existing translations
2025-02-25 17:33:07 +08:00
3gf8jv4dv b792b23ad4 chore(locale): improve russian translation (web) (#3648) 2025-02-25 17:32:27 +08:00
3gf8jv4dv 881b0e9127 chore(locale): improve russian translation (account part) (#3642)
- Re-translate some translations based on English
- Fix some inconsistent terminology
  - Such as Account, учетная запись > аккаунт
- Add some missing translations
- Reorder some keys to ensure they match with English
- Adjust terminology related to LittleSkin to ensure consistency with
the terminology used on the site
2025-02-23 10:48:45 +08:00
3gf8jv4dv 77e06c8820 chore(locale): adjust english translation of account.portable (#3643)
Match the style of the account type shown for the account item
on account list page
2025-02-23 10:48:03 +08:00
Glavo bc2405cf12 修复在 macOS 平台无法找到通过 DMG 安装的 Java 的问题 (#3645) 2025-02-23 10:47:25 +08:00
Glavo 72b5428f8b 修复 NeoForge 版本识别错误的问题 (#3638) 2025-02-22 17:28:25 +08:00
Glavo e7e8d45497 禁止整合包复制全局游戏设置 (#3639)
* 禁止整合包复制全局游戏设置

* update
2025-02-22 14:35:54 +08:00
3gf8jv4dv 82dbe18d78 fix(i18n): fix a missing i18n for LittleSkin term (#3637)
The word "LittleSkin" appears twice in OfflineAccountSkinPane, but one
of them is hardcoded. Add i18n support for the hardcoded one.
2025-02-22 08:36:34 +08:00
3gf8jv4dv ecb7ef0867 fix(locale): remove accidental japanese characters from russian (#3636)
This bug first appeared in https://github.com/HMCL-dev/HMCL/pull/2412
2025-02-22 00:50:59 +08:00
Glavo d40742a624 Bump jsoup to 1.18.3 (#3632) 2025-02-19 16:28:46 +08:00
Glavo 6f8002a0cf Fix typo in FXUtils (#3631) 2025-02-19 15:34:08 +08:00
3gf8jv4dv 3d3896c5c4 fix(i18n): add i18n support for steve & alex option in offline skin pane (#3630)
Some Slavic Minecraft communities have translated Steve and Alex,
such as Russian and Ukrainian. They need a translation.

ref:
- https://ru.minecraft.wiki/w/%D0%A1%D0%BA%D0%B8%D0%BD
- https://uk.minecraft.wiki/w/%D0%A1%D0%BA%D1%96%D0%BD
2025-02-19 15:30:34 +08:00
Glavoand3gf8jv4dv a24f8e2ec0 在游戏设置中添加“复制全局游戏设置”选项 (#3628)
* update

* update

* Fix checkstyle

* update

* fix checkstyle

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

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

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

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

* Fix FXUtils.bindEnum

---------

Co-authored-by: 3gf8jv4dv <3gf8jv4dv@gmail.com>
2025-02-19 15:29:50 +08:00
Glavo fce08a6923 删除全局游戏设置页面的离线账户更换皮肤提示 (#3629) 2025-02-19 13:38:51 +08:00
Glavo 5fa01c8838 修复在非官方支持的平台上 Minecraft 1.21 与 Sodium 不兼容的问题 (#3622)
* update

* update

* update
2025-02-18 23:58:16 +08:00
Glavo 15a3fb5f59 优化 VersionsPage 搜索框 (#3625)
* update

* update

* update

* update
2025-02-18 23:51:28 +08:00
Glavo d5ab14f476 Fix #3295: 修复离线账户选择 Steve/Alex 皮肤时模型错误的问题 (#3626)
* Fix #3295: 修复离线账户选择 Steve/Alex 皮肤时模型错误的问题

* update
2025-02-18 22:46:52 +08:00
Glavo 554ecfd6d5 Fix #3445: 点击安装整合包时无条件关闭整合包下载对话框 (#3621) 2025-02-17 23:35:58 +08:00
Glavoand3gf8jv4dv c2abe755f4 优化整合包下载对话框 (#3620)
* update

* 优化整合包下载对话框

* update

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

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

---------

Co-authored-by: 3gf8jv4dv <3gf8jv4dv@gmail.com>
2025-02-17 22:44:25 +08:00
Glavo c0410c46ff 在日志中记录 PROCESSOR_IDENTIFIER (#3619) 2025-02-16 21:10:58 +08:00
Glavo da93b4c12d 修复无法识别 Intel64 架构的问题 (#3618) 2025-02-16 17:23:14 +08:00
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
Burning_TNTandYELANDAOKONG 9edfe29659 实现 #2969: 启动前日志列出mods目录的jar文件 (#3125)
* Support #2969: 启动前日志列出mods目录的jar文件

* Support #2969: 启动前日志列出mods目录jar文件

* Support #2969: 启动前日志中使用字符画列出mods目录模组文件

* Support #2969: 启动前日志中使用字符画列出mods目录模组文件

* Support #2969: 启动前日志中使用字符画列出mods目录模组文件

* Support #2969: 日志列出mods目录时不再过滤文件后缀

* Support #2969: 启动时日志列出mods目录(代码优化)

* Support #2969: 启动时日志列出mods目录(不再列出Zip内容)

* Support #2969

* Fix: checkstyle

---------

Co-authored-by: YELANDAOKONG <yelandaokong@yldk.xyz>
2024-11-16 23:29:53 +08:00
Burning_TNT 5591d92e25 Fix #2991: 导入 PrismLauncher 格式整合包时 $.jvmArgs 参数未正确移除两侧引号 (#3030) 2024-11-15 22:49:30 +08:00
Glavo 65bdb29e71 在 macOS 上使用 JavaFX 19.0.2.1 (#3461) 2024-11-15 18:02:59 +08:00
Glavo 355df211db 在游戏下载列表中添加 Minecraft Wiki 链接 (#3435) 2024-11-14 17:49:27 +08:00
3gf8jv4dv fa66549f53 Revise English (#3453)
- Adjust some translation styles again
- Fix some missed mistakes
2024-11-14 09:59:15 +08:00
3gf8jv4dv 6e48dbd6c6 Fix dialog title when modpack download fails (#3451)
* Fix dialog title when modpack download fails

* Update
2024-11-14 09:58:14 +08:00
Zkitefly e3c7278759 Update check-update.yml (#3452) 2024-11-14 09:42:43 +08:00
3gf8jv4dv dd1829d8ca Check word 其它 → 其他 when checking translation (#3455)
There is some controversy over the use of this word.
In order to ensure consistency in formal texts, we
should unify them to more tried-and-true and commonly
used usages.

By checking at build time, it lets the translator
or proofreader correct the word on his/her own without
any special attention from the reviewer.
2024-11-14 09:27:45 +08:00
3gf8jv4dv 60ac16a3ea Revise Chinese (#3454)
* Revise Chinese

- Simplified Chinese only
- Minor revision

* oops...
2024-11-13 22:14:55 +08:00
Glavo b400a9a3ef 使最小化按钮居中 (#3450)
* 修复最小化按钮未居中的问题

* update
2024-11-12 14:24:15 +08:00
3gf8jv4dv c7cc2d0733 Fix missing i18n when checking for updates fails (#3444)
* Fix missing i18n when checking for updates fails

* Revise translation
2024-11-11 19:20:28 +08:00
Glavo 861223e909 清理 GameJavaVersion (#3441)
* 清理 GameJavaVersion

* update
2024-11-11 02:30:41 +08:00
Glavo 70ecbc734e Fix #3438: 修复 Minecraft 1.20.2+Forge 默认图标错误的问题 (#3439) 2024-11-10 23:19:14 +08:00
ZkiteflyandGlavo 2ca7e7c830 Fix #3200: 启动时不应补全 Modrinth 整合包中已禁用的 mod (#3308)
* Update ModrinthCompletionTask.java

* Update ModrinthCompletionTask.java

* Update ModrinthCompletionTask.java

* Update ModrinthCompletionTask.java

* Update ModrinthCompletionTask.java

* update

* update

* update

* update

* update

* update

* update

---------

Co-authored-by: Glavo <zjx001202@gmail.com>
2024-11-10 21:27:36 +08:00
Wulian 9a0db56ac3 修复 Copyright © 显示为“漏”的bug (#3437)
Fixes #3436

Specify UTF-8 charset when reading `deps.json` file in `AboutPage.java`.

---

For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/HMCL-dev/HMCL/issues/3436?shareId=XXXX-XXXX-XXXX-XXXX).
2024-11-10 19:02:59 +08:00
Glavo c1ae5875ba 将 VERSION_ROOT 更新至 3.6 (#3431) 2024-11-10 13:01:50 +08:00
Glavo 1d0ae427bf Update JavaVersionConstraint: 1.20.5 requires Java 21 (#3434) 2024-11-09 19:39:09 +08:00
3gf8jv4dv 3dde2d19e2 Fix extra "=" in zh_CN launch.advice translation (#3430)
Closes #3429
2024-11-08 05:59:02 +08:00
119 changed files with 8943 additions and 3113 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 -10
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,6 +72,7 @@ jobs:
target_commitish: ${{ env.HMCL_COMMIT_SHA }}
name: ${{ env.HMCL_TAG_NAME }}
tag_name: ${{ env.HMCL_TAG_NAME }}
prerelease: true
stable-check-update:
if: ${{ github.repository_owner == 'HMCL-dev' }}
needs: dev-check-update
@@ -114,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
+19 -3
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
@@ -25,7 +26,7 @@ val buildNumber = System.getenv("BUILD_NUMBER")?.toInt().let { number ->
if (!shortCommit.isNullOrEmpty()) "$prefix-$shortCommit" else "SNAPSHOT"
}
}
val versionRoot = System.getenv("VERSION_ROOT") ?: "3.5"
val versionRoot = System.getenv("VERSION_ROOT") ?: "3.6"
val versionType = System.getenv("VERSION_TYPE") ?: if (isOfficial) "nightly" else "unofficial"
val microsoftAuthId = System.getenv("MICROSOFT_AUTH_ID") ?: ""
@@ -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!')
@@ -225,8 +225,11 @@ public final class Launcher extends Application {
LOG.info("Operating System: " + (OperatingSystem.OS_RELEASE_PRETTY_NAME == null
? OperatingSystem.SYSTEM_NAME + ' ' + OperatingSystem.SYSTEM_VERSION
: OperatingSystem.OS_RELEASE_PRETTY_NAME + " (" + OperatingSystem.SYSTEM_NAME + ' ' + OperatingSystem.SYSTEM_VERSION + ')'));
LOG.info("System Architecture: " + Architecture.SYSTEM_ARCH_NAME);
LOG.info("Java Architecture: " + Architecture.CURRENT_ARCH_NAME);
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
LOG.info("Processor Identifier: " + System.getenv("PROCESSOR_IDENTIFIER"));
}
LOG.info("System Architecture: " + Architecture.SYSTEM_ARCH.getDisplayName());
LOG.info("Java Architecture: " + Architecture.CURRENT_ARCH.getDisplayName());
LOG.info("Java Version: " + System.getProperty("java.version") + ", " + System.getProperty("java.vendor"));
LOG.info("Java VM Version: " + System.getProperty("java.vm.name") + " (" + System.getProperty("java.vm.info") + "), " + System.getProperty("java.vm.vendor"));
LOG.info("Java Home: " + System.getProperty("java.home"));
@@ -245,7 +248,6 @@ public final class Launcher extends Application {
LOG.info("XDG Session Type: " + System.getenv("XDG_SESSION_TYPE"));
LOG.info("XDG Current Desktop: " + System.getenv("XDG_CURRENT_DESKTOP"));
}
launch(Launcher.class, args);
} catch (Throwable e) { // Fucking JavaFX will suppress the exception and will break our crash reporter.
CRASH_REPORTER.uncaughtException(Thread.currentThread(), e);
@@ -54,6 +54,7 @@ public final class Main {
public static void main(String[] args) {
System.getProperties().putIfAbsent("java.net.useSystemProxies", "true");
System.getProperties().putIfAbsent("javafx.autoproxy.disable", "true");
System.getProperties().putIfAbsent("javafx.animation.pulse", "120");
System.getProperties().putIfAbsent("http.agent", "HMCL/" + Metadata.VERSION);
LOG.start(Metadata.HMCL_DIRECTORY.resolve("logs"));
@@ -43,8 +43,8 @@ public class CrashReport {
stackTrace + "\n\n" +
"-- System Details --\n" +
" Operating System: " + OperatingSystem.SYSTEM_NAME + ' ' + OperatingSystem.SYSTEM_VERSION + "\n" +
" System Architecture: " + Architecture.SYSTEM_ARCH_NAME + "\n" +
" Java Architecture: " + Architecture.CURRENT_ARCH_NAME + "\n" +
" System Architecture: " + Architecture.SYSTEM_ARCH.getDisplayName() + "\n" +
" Java Architecture: " + Architecture.CURRENT_ARCH.getDisplayName() + "\n" +
" Java Version: " + System.getProperty("java.version") + ", " + System.getProperty("java.vendor") + "\n" +
" Java VM Version: " + System.getProperty("java.vm.name") + " (" + System.getProperty("java.vm.info") + "), " + System.getProperty("java.vm.vendor") + "\n" +
" JVM Max Memory: " + Runtime.getRuntime().maxMemory() + "\n" +
@@ -33,6 +33,7 @@ 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;
@@ -43,9 +44,7 @@ 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 +217,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 +250,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 +259,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 +271,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 +282,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 +298,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 +336,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 +358,9 @@ public class HMCLGameRepository extends DefaultGameRepository {
vs = createLocalVersionSetting(id);
if (vs == null)
return null;
vs.setUsesGlobal(false);
if (vs.isUsesGlobal()) {
vs.setUsesGlobal(false);
}
return vs;
}
@@ -383,7 +370,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
vs.setUsesGlobal(true);
}
public LaunchOptions getLaunchOptions(String version, JavaRuntime javaVersion, File gameDir, List<String> javaAgents, boolean makeLaunchScript) {
public LaunchOptions getLaunchOptions(String version, JavaRuntime javaVersion, File gameDir, List<String> javaAgents, List<String> javaArguments, boolean makeLaunchScript) {
VersionSetting vs = getVersionSetting(version);
LaunchOptions.Builder builder = new LaunchOptions.Builder()
@@ -426,7 +413,8 @@ public class HMCLGameRepository extends DefaultGameRepository {
.setUseNativeGLFW(vs.isUseNativeGLFW())
.setUseNativeOpenAL(vs.isUseNativeOpenAL())
.setDaemon(!makeLaunchScript && vs.getLauncherVisibility().isDaemon())
.setJavaAgents(javaAgents);
.setJavaAgents(javaAgents)
.setJavaArguments(javaArguments);
if (config().hasProxy()) {
builder.setProxy(ProxyManager.getProxy());
if (config().hasProxyAuth()) {
@@ -36,10 +36,14 @@ import org.jackhuang.hmcl.mod.ModpackProvider;
import org.jackhuang.hmcl.setting.*;
import org.jackhuang.hmcl.task.*;
import org.jackhuang.hmcl.ui.*;
import org.jackhuang.hmcl.ui.construct.*;
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
import org.jackhuang.hmcl.ui.construct.PromptDialogPane;
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
import org.jackhuang.hmcl.util.*;
import org.jackhuang.hmcl.util.i18n.I18n;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.ResponseCodeException;
import org.jackhuang.hmcl.util.platform.*;
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
@@ -60,9 +64,10 @@ import static javafx.application.Platform.runLater;
import static javafx.application.Platform.setImplicitExit;
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
import static org.jackhuang.hmcl.util.Lang.resolveException;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.platform.Platform.*;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.platform.Platform.SYSTEM_PLATFORM;
import static org.jackhuang.hmcl.util.platform.Platform.isCompatibleWithX86Java;
public final class LauncherHelper {
@@ -126,13 +131,14 @@ public final class LauncherHelper {
boolean integrityCheck = repository.unmarkVersionLaunchedAbnormally(selectedVersion);
CountDownLatch launchingLatch = new CountDownLatch(1);
List<String> javaAgents = new ArrayList<>(0);
List<String> javaArguments = new ArrayList<>(0);
AtomicReference<JavaRuntime> javaVersionRef = new AtomicReference<>();
TaskExecutor executor = checkGameState(profile, setting, version.get())
.thenComposeAsync(java -> {
javaVersionRef.set(Objects.requireNonNull(java));
version.set(NativePatcher.patchNative(version.get(), gameVersion.orElse(null), java, setting));
version.set(NativePatcher.patchNative(repository, version.get(), gameVersion.orElse(null), java, setting, javaArguments));
if (setting.isNotCheckGame())
return null;
return Task.allOf(
@@ -150,7 +156,7 @@ public final class LauncherHelper {
Task.composeAsync(() -> {
Renderer renderer = setting.getRenderer();
if (renderer != Renderer.DEFAULT && OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
Library lib = NativePatcher.getMesaLoader(java, renderer);
Library lib = NativePatcher.getMesaLoader(java);
if (lib == null)
return null;
File file = dependencyManager.getGameRepository().getLibraryFile(version.get(), lib);
@@ -177,7 +183,11 @@ public final class LauncherHelper {
.thenComposeAsync(() -> gameVersion.map(s -> new GameVerificationFixTask(dependencyManager, s, version.get())).orElse(null))
.thenComposeAsync(() -> logIn(account).withStage("launch.state.logging_in"))
.thenComposeAsync(authInfo -> Task.supplyAsync(() -> {
LaunchOptions launchOptions = repository.getLaunchOptions(selectedVersion, javaVersionRef.get(), profile.getGameDir(), javaAgents, scriptFile != null);
LaunchOptions launchOptions = repository.getLaunchOptions(
selectedVersion, javaVersionRef.get(), profile.getGameDir(), javaAgents, javaArguments, scriptFile != null);
LOG.info("Here's the structure of game mod directory:\n" + FileUtils.printFileStructure(repository.getModManager(selectedVersion).getModsDirectory(), 10));
return new HMCLGameLauncher(
repository,
version.get(),
@@ -522,6 +532,9 @@ public final class LauncherHelper {
case MODDED_JAVA_17:
suggestions.add(i18n("launch.advice.modded_java", 17, gameVersion));
break;
case MODDED_JAVA_21:
suggestions.add(i18n("launch.advice.modded_java", 21, gameVersion));
break;
case VANILLA_JAVA_8_51:
suggestions.add(i18n("launch.advice.java8_51_1_13"));
break;
@@ -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();
@@ -376,8 +376,8 @@ public final class JavaManager {
searchAllJavaInDirectory(javaRuntimes, Paths.get(System.getProperty("user.home"), "/.sdkman/candidates/java")); // SDKMAN!
break;
case OSX:
tryAddJavaHome(javaRuntimes, Paths.get("/Library/Java/JavaVirtualMachines/Contents/Home"));
tryAddJavaHome(javaRuntimes, Paths.get(System.getProperty("user.home"), "/Library/Java/JavaVirtualMachines/Contents/Home"));
searchJavaInMacJavaVirtualMachines(javaRuntimes, Paths.get("/Library/Java/JavaVirtualMachines"));
searchJavaInMacJavaVirtualMachines(javaRuntimes, Paths.get(System.getProperty("user.home"), "/Library/Java/JavaVirtualMachines"));
tryAddJavaExecutable(javaRuntimes, Paths.get("/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java"));
tryAddJavaExecutable(javaRuntimes, Paths.get("/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/MacOS/itms/java/bin/java"));
// Homebrew
@@ -631,6 +631,20 @@ public final class JavaManager {
}
}
private static void searchJavaInMacJavaVirtualMachines(Map<Path, JavaRuntime> javaRuntimes, Path directory) {
if (!Files.isDirectory(directory)) {
return;
}
try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory)) {
for (Path subDir : stream) {
tryAddJavaHome(javaRuntimes, subDir.resolve("Contents/Home"));
}
} catch (IOException e) {
LOG.warning("Failed to find Java in " + directory, e);
}
}
// ==== Windows Registry Support ====
private static void queryJavaInRegistryKey(Map<Path, JavaRuntime> javaRuntimes, String location) {
for (String java : querySubFolders(location)) {
@@ -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);
}
@@ -24,7 +24,10 @@ import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.util.Lazy;
import org.jackhuang.hmcl.util.Pair;
import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.File;
@@ -56,29 +59,41 @@ public class Theme {
Color.web("#B71C1C") // red
};
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private static Optional<Font> font;
private static final Lazy<Font> FONT = new Lazy<>(() -> {
String[] fileNames = {"font.ttf", "font.otf", "font.woff"};
private static Optional<Font> tryLoadFont() {
//noinspection OptionalAssignedToNull
if (font != null) {
return font;
}
for (String fileName : fileNames) {
Path path = Paths.get(fileName).toAbsolutePath();
if (Files.isRegularFile(path)) {
try {
Font font = Font.loadFont(path.toUri().toURL().toExternalForm(), 0);
if (font != null) {
return font;
}
} catch (MalformedURLException ignored) {
}
Path path = Paths.get("font.ttf");
if (!Files.isRegularFile(path)) {
path = Paths.get("font.otf");
}
if (Files.isRegularFile(path)) {
try {
return font = Optional.ofNullable(Font.loadFont(path.toAbsolutePath().toUri().toURL().toExternalForm(), 0));
} catch (MalformedURLException ignored) {
LOG.warning("Failed to load font " + path);
}
}
return font = Optional.empty();
}
for (String fileName : fileNames) {
Path path = Metadata.HMCL_DIRECTORY.resolve(fileName);
if (Files.isRegularFile(path)) {
try {
Font font = Font.loadFont(path.toUri().toURL().toExternalForm(), 0);
if (font != null) {
return font;
}
} catch (MalformedURLException ignored) {
}
LOG.warning("Failed to load font " + path);
}
}
return null;
});
public static Theme getTheme() {
Theme theme = config().getTheme();
@@ -123,6 +138,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 +184,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();
Font font = FONT.get();
if (font != null) {
fontFamily = font.getFamily();
fontStyle = parseFontStyle(font.getStyle());
}
}
@@ -159,8 +213,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> {
@@ -19,6 +19,9 @@ package org.jackhuang.hmcl.ui;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXDialogLayout;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
import javafx.beans.property.DoubleProperty;
@@ -34,6 +37,7 @@ import javafx.scene.paint.Color;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.util.Duration;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.game.ModpackHelper;
@@ -42,6 +46,7 @@ import org.jackhuang.hmcl.setting.*;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.ui.account.AccountListPage;
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
import org.jackhuang.hmcl.ui.construct.*;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
import org.jackhuang.hmcl.ui.decorator.DecoratorController;
@@ -267,6 +272,24 @@ public final class Controllers {
stage.initStyle(StageStyle.TRANSPARENT);
stage.setScene(scene);
if (AnimationUtils.playWindowAnimation()) {
Timeline timeline = new Timeline(
new KeyFrame(Duration.millis(0),
new KeyValue(decorator.getDecorator().opacityProperty(), 0, FXUtils.EASE),
new KeyValue(decorator.getDecorator().scaleXProperty(), 0.8, FXUtils.EASE),
new KeyValue(decorator.getDecorator().scaleYProperty(), 0.8, FXUtils.EASE),
new KeyValue(decorator.getDecorator().scaleZProperty(), 0.8, FXUtils.EASE)
),
new KeyFrame(Duration.millis(600),
new KeyValue(decorator.getDecorator().opacityProperty(), 1, FXUtils.EASE),
new KeyValue(decorator.getDecorator().scaleXProperty(), 1, FXUtils.EASE),
new KeyValue(decorator.getDecorator().scaleYProperty(), 1, FXUtils.EASE),
new KeyValue(decorator.getDecorator().scaleZProperty(), 1, FXUtils.EASE)
)
);
timeline.play();
}
if (!Architecture.SYSTEM_ARCH.isX86() && globalConfig().getPlatformPromptVersion() < 1) {
Runnable continueAction = () -> globalConfig().setPlatformPromptVersion(1);
@@ -18,18 +18,23 @@
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;
import javafx.beans.Observable;
import javafx.beans.WeakInvalidationListener;
import javafx.beans.WeakListener;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.Property;
import javafx.beans.value.*;
import javafx.geometry.Insets;
import javafx.event.Event;
import javafx.event.EventDispatcher;
import javafx.event.EventType;
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,15 @@ 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 +90,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 +120,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,20 +239,21 @@ public final class FXUtils {
});
}
public static <K, T> void setupCellValueFactory(JFXTreeTableColumn<K, T> column, Function<K, ObservableValue<T>> mapper) {
column.setCellValueFactory(param -> {
if (column.validateValue(param))
return mapper.apply(param.getValue().getValue());
else
return column.getComputedValue(param);
@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 Node wrapMargin(Node node, Insets insets) {
StackPane.setMargin(node, insets);
return new StackPane(node);
}
public static void setValidateWhileTextChanged(Node field, boolean validate) {
if (field instanceof JFXTextField) {
if (validate) {
@@ -515,10 +527,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,53 +544,116 @@ 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);
focusedListener = (observable, oldFocused, newFocused) -> {
if (oldFocused && !newFocused) {
if (textField.validate()) {
updateProperty();
} else {
// Rollback to old value
updateTextField();
}
}
};
sceneListener = (observable, oldScene, newScene) -> {
if (oldScene != null && newScene == null) {
// Component is being removed from scene
if (textField.validate()) {
updateProperty();
}
}
};
propertyListener = observable -> {
updateTextField();
};
}
public void updateProperty() {
String newText = textField.getText();
@SuppressWarnings("unchecked")
T newValue = converter == null ? (T) newText : converter.fromString(newText);
if (!Objects.equals(newValue, property.getValue())) {
property.setValue(newValue);
}
}
public void updateTextField() {
T value = property.getValue();
textField.setText(converter == null ? (String) value : converter.toString(value));
}
}
private static final class EnumBidirectionalBinding<E extends Enum<E>> implements InvalidationListener, WeakListener {
private final WeakReference<JFXComboBox<E>> comboBoxRef;
private final WeakReference<Property<E>> propertyRef;
private final int hashCode;
private boolean updating = false;
private EnumBidirectionalBinding(JFXComboBox<E> comboBox, Property<E> property) {
this.comboBoxRef = new WeakReference<>(comboBox);
this.propertyRef = new WeakReference<>(property);
this.hashCode = System.identityHashCode(comboBox) ^ 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();
public void invalidated(Observable sourceProperty) {
if (!updating) {
final JFXComboBox<E> comboBox = comboBoxRef.get();
final Property<E> property = 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 (comboBox == null || property == null) {
if (comboBox != null) {
comboBox.getSelectionModel().selectedItemProperty().removeListener(this);
}
if (!Objects.equals(newValue, property.getValue()))
property.setValue(newValue);
if (property != null) {
property.removeListener(this);
}
} else {
// Rollback to old value
invalidated(null);
updating = true;
try {
if (property == sourceProperty) {
E newValue = property.getValue();
comboBox.getSelectionModel().select(newValue);
} else {
E newValue = comboBox.getSelectionModel().getSelectedItem();
property.setValue(newValue);
}
} finally {
updating = false;
}
}
}
}
@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));
}
public boolean wasGarbageCollected() {
return comboBoxRef.get() == null || propertyRef.get() == null;
}
@Override
@@ -585,52 +662,45 @@ public final class FXUtils {
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof TextFieldBindingListener))
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof EnumBidirectionalBinding))
return false;
TextFieldBindingListener<?> other = (TextFieldBindingListener<?>) obj;
return this.hashCode == other.hashCode
&& this.textFieldRef.get() == other.textFieldRef.get()
&& this.propertyRef.get() == other.propertyRef.get();
EnumBidirectionalBinding<?> that = (EnumBidirectionalBinding<?>) o;
final JFXComboBox<E> comboBox = this.comboBoxRef.get();
final Property<E> property = this.propertyRef.get();
final JFXComboBox<?> thatComboBox = that.comboBoxRef.get();
final Property<?> thatProperty = that.propertyRef.get();
if (comboBox == null || property == null || thatComboBox == null || thatProperty == null)
return false;
return comboBox == thatComboBox && property == thatProperty;
}
}
public static void bindBoolean(JFXToggleButton toggleButton, Property<Boolean> property) {
toggleButton.selectedProperty().bindBidirectional(property);
}
public static void unbindBoolean(JFXToggleButton toggleButton, Property<Boolean> property) {
toggleButton.selectedProperty().unbindBidirectional(property);
}
public static void bindBoolean(JFXCheckBox checkBox, Property<Boolean> property) {
checkBox.selectedProperty().bindBidirectional(property);
}
public static void unbindBoolean(JFXCheckBox checkBox, Property<Boolean> property) {
checkBox.selectedProperty().unbindBidirectional(property);
}
/**
* Bind combo box selection with given enum property bidirectionally.
* You should <b>only and always</b> use {@code bindEnum} as well as {@code unbindEnum} at the same time.
*
* @param comboBox the combo box being bound with {@code property}.
* @param property the property being bound with {@code combo box}.
* @see #unbindEnum(JFXComboBox)
* @see #unbindEnum(JFXComboBox, Property)
* @see ExtendedProperties#selectedItemPropertyFor(ComboBox)
*/
public static <T extends Enum<T>> void bindEnum(JFXComboBox<T> comboBox, Property<T> property) {
unbindEnum(comboBox);
EnumBidirectionalBinding<T> binding = new EnumBidirectionalBinding<>(comboBox, property);
T currentValue = property.getValue();
@SuppressWarnings("unchecked")
T[] enumConstants = (T[]) currentValue.getClass().getEnumConstants();
ChangeListener<Number> listener = (a, b, newValue) -> property.setValue(enumConstants[newValue.intValue()]);
comboBox.getSelectionModel().selectedItemProperty().removeListener(binding);
property.removeListener(binding);
comboBox.getSelectionModel().select(currentValue.ordinal());
comboBox.getProperties().put("FXUtils.bindEnum.listener", listener);
comboBox.getSelectionModel().selectedIndexProperty().addListener(listener);
comboBox.getSelectionModel().select(property.getValue());
comboBox.getSelectionModel().selectedItemProperty().addListener(binding);
property.addListener(binding);
}
/**
@@ -641,11 +711,10 @@ public final class FXUtils {
* @see #bindEnum(JFXComboBox, Property)
* @see ExtendedProperties#selectedItemPropertyFor(ComboBox)
*/
public static void unbindEnum(JFXComboBox<? extends Enum<?>> comboBox) {
@SuppressWarnings("unchecked")
ChangeListener<Number> listener = (ChangeListener<Number>) comboBox.getProperties().remove("FXUtils.bindEnum.listener");
if (listener != null)
comboBox.getSelectionModel().selectedIndexProperty().removeListener(listener);
public static <T extends Enum<T>> void unbindEnum(JFXComboBox<T> comboBox, Property<T> property) {
EnumBidirectionalBinding<T> binding = new EnumBidirectionalBinding<>(comboBox, property);
comboBox.getSelectionModel().selectedItemProperty().removeListener(binding);
property.removeListener(binding);
}
public static void bindAllEnabled(BooleanProperty allEnabled, BooleanProperty... children) {
@@ -713,6 +782,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.
*
@@ -941,6 +1054,8 @@ public final class FXUtils {
}
};
public static final Interpolator EASE = Interpolator.SPLINE(0.25, 0.1, 0.25, 1);
public static void onEscPressed(Node node, Runnable action) {
node.addEventHandler(KeyEvent.KEY_PRESSED, e -> {
if (e.getCode() == KeyCode.ESCAPE) {
@@ -959,6 +1074,18 @@ public final class FXUtils {
});
}
public static void copyOnDoubleClick(Labeled label) {
label.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> {
if (e.getButton() == MouseButton.PRIMARY && e.getClickCount() == 2) {
String text = label.getText();
if (text != null && !text.isEmpty()) {
copyText(label.getText());
e.consume();
}
}
});
}
public static void copyText(String text) {
ClipboardContent content = new ClipboardContent();
content.putString(text);
@@ -1034,4 +1161,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));
}
}
@@ -59,6 +59,7 @@ public enum SVG {
ALERT_OUTLINE("M12,2L1,21H23M12,6L19.53,19H4.47M11,10V14H13V10M11,16V18H13V16"),
PLUS("M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z"),
PLUS_CIRCLE_OUTLINE("M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M13,7H11V11H7V13H11V17H13V13H17V11H13V7Z"),
MINUS("M19 13H5V11H19V13Z"),
IMPORT_ICON("M14,12L10,8V11H2V13H10V16M20,18V6C20,4.89 19.1,4 18,4H6A2,2 0 0,0 4,6V9H6V6H18V18H6V15H4V18A2,2 0 0,0 6,20H18A2,2 0 0,0 20,18Z"),
EXPORT("M23,12L19,8V11H10V13H19V16M1,18V6C1,4.89 1.9,4 3,4H15A2,2 0 0,1 17,6V9H15V6H3V18H15V15H17V18A2,2 0 0,1 15,20H3A2,2 0 0,1 1,18Z"),
OPEN_IN_NEW("M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z"),
@@ -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());
@@ -21,6 +21,7 @@ import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXRadioButton;
import com.jfoenix.effects.JFXDepthManager;
import javafx.geometry.Pos;
import javafx.scene.Cursor;
import javafx.scene.canvas.Canvas;
import javafx.scene.control.Label;
import javafx.scene.control.SkinBase;
@@ -50,13 +51,11 @@ public final class AccountListItemSkin extends SkinBase<AccountListItem> {
super(skinnable);
BorderPane root = new BorderPane();
root.setCursor(Cursor.HAND);
FXUtils.onClicked(root, skinnable::fire);
JFXRadioButton chkSelected = new JFXRadioButton() {
@Override
public void fire() {
skinnable.fire();
}
};
JFXRadioButton chkSelected = new JFXRadioButton();
chkSelected.setMouseTransparent(true);
BorderPane.setAlignment(chkSelected, Pos.CENTER);
chkSelected.selectedProperty().bind(skinnable.selectedProperty());
root.setLeft(chkSelected);
@@ -101,6 +101,9 @@ public class OfflineAccountSkinPane extends StackPane {
VBox optionPane = new VBox(skinItem, skinOptionPane);
pane.setRight(optionPane);
skinSelector.maxWidthProperty().bind(skinOptionPane.maxWidthProperty().multiply(0.7));
capeSelector.maxWidthProperty().bind(skinOptionPane.maxWidthProperty().multiply(0.7));
layout.setBody(pane);
cslApiField.setPromptText(i18n("account.skin.type.csl_api.location.hint"));
@@ -108,10 +111,10 @@ public class OfflineAccountSkinPane extends StackPane {
skinItem.loadChildren(Arrays.asList(
new MultiFileItem.Option<>(i18n("message.default"), Skin.Type.DEFAULT),
new MultiFileItem.Option<>("Steve", Skin.Type.STEVE),
new MultiFileItem.Option<>("Alex", Skin.Type.ALEX),
new MultiFileItem.Option<>(i18n("account.skin.type.steve"), Skin.Type.STEVE),
new MultiFileItem.Option<>(i18n("account.skin.type.alex"), Skin.Type.ALEX),
new MultiFileItem.Option<>(i18n("account.skin.type.local_file"), Skin.Type.LOCAL_FILE),
new MultiFileItem.Option<>("LittleSkin", Skin.Type.LITTLE_SKIN),
new MultiFileItem.Option<>(i18n("account.skin.type.little_skin"), Skin.Type.LITTLE_SKIN),
new MultiFileItem.Option<>(i18n("account.skin.type.csl_api"), Skin.Type.CUSTOM_SKIN_LOADER_API)
));
@@ -201,6 +204,12 @@ public class OfflineAccountSkinPane extends StackPane {
}
private Skin getSkin() {
return new Skin(skinItem.getSelectedData(), cslApiField.getText(), modelCombobox.getValue(), skinSelector.getValue(), capeSelector.getValue());
Skin.Type type = skinItem.getSelectedData();
if (type == Skin.Type.LOCAL_FILE) {
return new Skin(type, cslApiField.getText(), modelCombobox.getValue(), skinSelector.getValue(), capeSelector.getValue());
} else {
String cslApi = type == Skin.Type.CUSTOM_SKIN_LOADER_API ? cslApiField.getText() : null;
return new Skin(type, cslApi, null, null, null);
}
}
}
@@ -1,7 +1,28 @@
/*
* Hello Minecraft! Launcher
* 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
* 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.animation;
import org.jackhuang.hmcl.setting.ConfigHolder;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
/**
* @author Glavo
*/
public final class AnimationUtils {
private AnimationUtils() {
@@ -15,9 +36,15 @@ public final class AnimationUtils {
public static void init() {
}
private static final boolean enabled = !ConfigHolder.config().isAnimationDisabled();
private static final boolean ENABLED = !ConfigHolder.config().isAnimationDisabled();
private static final boolean PLAY_WINDOW_ANIMATION = ENABLED
&& (!OperatingSystem.CURRENT_OS.isLinuxOrBSD() || "DDE".equals(System.getenv("XDG_CURRENT_DESKTOP")));
public static boolean isAnimationEnabled() {
return enabled;
return ENABLED;
}
public static boolean playWindowAnimation() {
return PLAY_WINDOW_ANIMATION;
}
}
@@ -17,7 +17,6 @@
*/
package org.jackhuang.hmcl.ui.animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.scene.Node;
@@ -26,6 +25,8 @@ import javafx.util.Duration;
import org.jackhuang.hmcl.ui.FXUtils;
public class TransitionPane extends StackPane implements AnimationHandler {
private static final Duration DEFAULT_DURATION = Duration.millis(200);
private Duration duration;
private Node previousNode, currentNode;
@@ -55,7 +56,7 @@ public class TransitionPane extends StackPane implements AnimationHandler {
}
public void setContent(Node newView, AnimationProducer transition) {
setContent(newView, transition, Duration.millis(160));
setContent(newView, transition, DEFAULT_DURATION);
}
public void setContent(Node newView, AnimationProducer transition, Duration duration) {
@@ -64,26 +65,25 @@ public class TransitionPane extends StackPane implements AnimationHandler {
updateContent(newView);
if (previousNode == EMPTY_PANE) {
setMouseTransparent(false);
getChildren().setAll(newView);
return;
}
if (AnimationUtils.isAnimationEnabled()) {
if (AnimationUtils.isAnimationEnabled() && transition != ContainerAnimations.NONE) {
setMouseTransparent(true);
transition.init(this);
// runLater or "init" will not work
Platform.runLater(() -> {
Timeline newAnimation = new Timeline();
newAnimation.getKeyFrames().addAll(transition.animate(this));
newAnimation.getKeyFrames().add(new KeyFrame(duration, e -> {
newAnimation.getKeyFrames().setAll(transition.animate(this));
newAnimation.setOnFinished(e -> {
setMouseTransparent(false);
getChildren().remove(previousNode);
}));
});
FXUtils.playAnimation(this, "transition_pane", newAnimation);
});
} else {
setMouseTransparent(false);
getChildren().remove(previousNode);
}
}
@@ -99,8 +99,6 @@ public class TransitionPane extends StackPane implements AnimationHandler {
if (previousNode == newView)
previousNode = EMPTY_PANE;
setMouseTransparent(true);
currentNode = newView;
getChildren().setAll(previousNode, currentNode);
@@ -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) {
@@ -17,7 +17,6 @@ import javafx.scene.layout.Priority;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.util.Lazy;
import org.jackhuang.hmcl.util.StringUtils;
public class IconedTwoLineListItem extends HBox {
@@ -29,13 +28,8 @@ public class IconedTwoLineListItem extends HBox {
private final ImageView imageView = new ImageView();
private final TwoLineListItem twoLineListItem = new TwoLineListItem();
private final Lazy<JFXButton> externalLinkButton = new Lazy<>(() -> {
JFXButton button = new JFXButton();
button.getStyleClass().add("toggle-icon4");
button.setGraphic(SVG.OPEN_IN_NEW.createIcon(Theme.blackFill(), -1, -1));
button.setOnAction(e -> FXUtils.openLink(externalLink.get()));
return button;
});
private JFXButton externalLinkButton;
@SuppressWarnings("FieldCanBeLocal")
private final InvalidationListener observer;
@@ -52,7 +46,7 @@ public class IconedTwoLineListItem extends HBox {
getChildren().clear();
if (image.get() != null) getChildren().add(imageView);
getChildren().add(twoLineListItem);
if (StringUtils.isNotBlank(externalLink.get())) getChildren().add(externalLinkButton.get());
if (StringUtils.isNotBlank(externalLink.get())) getChildren().add(getExternalLinkButton());
}, image, externalLink);
}
@@ -111,4 +105,14 @@ public class IconedTwoLineListItem extends HBox {
public ImageView getImageView() {
return imageView;
}
public JFXButton getExternalLinkButton() {
if (externalLinkButton == null) {
externalLinkButton = new JFXButton();
externalLinkButton.getStyleClass().add("toggle-icon4");
externalLinkButton.setGraphic(SVG.OPEN_IN_NEW.createIcon(Theme.blackFill(), -1, -1));
externalLinkButton.setOnAction(e -> FXUtils.openLink(externalLink.get()));
}
return externalLinkButton;
}
}
@@ -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() {
@@ -20,24 +20,28 @@ package org.jackhuang.hmcl.ui.construct;
import javafx.scene.Node;
import javafx.scene.layout.StackPane;
import java.util.ArrayList;
import java.util.Optional;
import java.util.Stack;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
public class StackContainerPane extends StackPane {
private final Stack<Node> stack = new Stack<>();
public class JFXDialogPane extends StackPane {
private final ArrayList<Node> stack = new ArrayList<>();
public int size() {
return stack.size();
}
public Optional<Node> peek() {
if (stack.isEmpty()) {
return Optional.empty();
} else {
return Optional.of(stack.peek());
return Optional.of(stack.get(stack.size() - 1));
}
}
public void push(Node node) {
stack.push(node);
stack.add(node);
getChildren().setAll(node);
LOG.info(this + " " + stack);
@@ -48,7 +52,7 @@ public class StackContainerPane extends StackPane {
if (stack.isEmpty())
getChildren().setAll();
else
getChildren().setAll(stack.peek());
getChildren().setAll(stack.get(stack.size() - 1));
LOG.info(this + " " + stack + ", removed: " + flag + ", object: " + node);
}
@@ -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
@@ -39,6 +39,7 @@ import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Rectangle;
import javafx.util.Duration;
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
import org.jackhuang.hmcl.util.Lang;
import java.util.List;
@@ -46,6 +47,7 @@ import java.util.List;
@DefaultProperty("container")
public class RipplerContainer extends StackPane {
private static final String DEFAULT_STYLE_CLASS = "rippler-container";
private static final Duration DURATION = Duration.millis(200);
private final ObjectProperty<Node> container = new SimpleObjectProperty<>(this, "container", null);
private final StyleableObjectProperty<Paint> ripplerFill = new SimpleStyleableObjectProperty<>(StyleableProperties.RIPPLER_FILL,this, "ripplerFill", null);
@@ -103,11 +105,10 @@ public class RipplerContainer extends StackPane {
rectangle.heightProperty().bind(heightProperty());
}));
setOnMouseEntered(e -> {
new Transition() {
if (AnimationUtils.isAnimationEnabled()) {
setOnMouseEntered(e -> new Transition() {
{
setCycleCount(1);
setCycleDuration(Duration.millis(200));
setCycleDuration(DURATION);
setInterpolator(Interpolator.EASE_IN);
}
@@ -115,14 +116,11 @@ public class RipplerContainer extends StackPane {
protected void interpolate(double frac) {
interpolateBackground(frac);
}
}.play();
});
}.play());
setOnMouseExited(e -> {
new Transition() {
setOnMouseExited(e -> new Transition() {
{
setCycleCount(1);
setCycleDuration(Duration.millis(200));
setCycleDuration(DURATION);
setInterpolator(Interpolator.EASE_OUT);
}
@@ -130,8 +128,11 @@ public class RipplerContainer extends StackPane {
protected void interpolate(double frac) {
interpolateBackground(1 - frac);
}
}.play();
});
}.play());
} else {
setOnMouseEntered(e -> interpolateBackground(1));
setOnMouseExited(e -> interpolateBackground(0));
}
}
private void interpolateBackground(double frac) {
@@ -157,23 +157,5 @@ public class SpinnerPane extends Control {
}
}
public interface State {}
public static class LoadedState implements State {}
public static class LoadingState implements State {}
public static class FailedState implements State {
private final String reason;
public FailedState(String reason) {
this.reason = reason;
}
public String getReason() {
return reason;
}
}
public static final EventType<Event> FAILED_ACTION = new EventType<>(Event.ANY, "FAILED_ACTION");
}
@@ -18,6 +18,9 @@
package org.jackhuang.hmcl.ui.decorator;
import com.jfoenix.controls.JFXSnackbar;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
@@ -35,6 +38,9 @@ import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.util.Duration;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
import org.jackhuang.hmcl.ui.wizard.Navigation;
public class Decorator extends Control {
@@ -61,12 +67,40 @@ public class Decorator extends Control {
private final ReadOnlyBooleanWrapper allowMove = new ReadOnlyBooleanWrapper();
private final ReadOnlyBooleanWrapper dragging = new ReadOnlyBooleanWrapper();
private boolean playRestoreMinimizeAnimation = false;
public Decorator(Stage primaryStage) {
this.primaryStage = primaryStage;
setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY)));
primaryStage.initStyle(StageStyle.UNDECORATED);
if (AnimationUtils.playWindowAnimation()) {
FXUtils.onChange(primaryStage.iconifiedProperty(), iconified -> {
if (playRestoreMinimizeAnimation && !iconified) {
playRestoreMinimizeAnimation = false;
Timeline timeline = new Timeline(
new KeyFrame(Duration.millis(0),
new KeyValue(this.opacityProperty(), 0, FXUtils.EASE),
new KeyValue(this.translateYProperty(), 200, FXUtils.EASE),
new KeyValue(this.scaleXProperty(), 0.4, FXUtils.EASE),
new KeyValue(this.scaleYProperty(), 0.4, FXUtils.EASE),
new KeyValue(this.scaleZProperty(), 0.4, FXUtils.EASE)
),
new KeyFrame(Duration.millis(200),
new KeyValue(this.opacityProperty(), 1, FXUtils.EASE),
new KeyValue(this.translateYProperty(), 0, FXUtils.EASE),
new KeyValue(this.scaleXProperty(), 1, FXUtils.EASE),
new KeyValue(this.scaleYProperty(), 1, FXUtils.EASE),
new KeyValue(this.scaleZProperty(), 1, FXUtils.EASE)
)
);
timeline.play();
}
});
}
}
public Stage getPrimaryStage() {
@@ -239,7 +273,29 @@ public class Decorator extends Control {
}
public void minimize() {
primaryStage.setIconified(true);
if (AnimationUtils.playWindowAnimation()) {
playRestoreMinimizeAnimation = true;
Timeline timeline = new Timeline(
new KeyFrame(Duration.millis(0),
new KeyValue(this.opacityProperty(), 1, FXUtils.EASE),
new KeyValue(this.translateYProperty(), 0, FXUtils.EASE),
new KeyValue(this.scaleXProperty(), 1, FXUtils.EASE),
new KeyValue(this.scaleYProperty(), 1, FXUtils.EASE),
new KeyValue(this.scaleZProperty(), 1, FXUtils.EASE)
),
new KeyFrame(Duration.millis(200),
new KeyValue(this.opacityProperty(), 0, FXUtils.EASE),
new KeyValue(this.translateYProperty(), 200, FXUtils.EASE),
new KeyValue(this.scaleXProperty(), 0.4, FXUtils.EASE),
new KeyValue(this.scaleYProperty(), 0.4, FXUtils.EASE),
new KeyValue(this.scaleZProperty(), 0.4, FXUtils.EASE)
)
);
timeline.setOnFinished(event -> primaryStage.setIconified(true));
timeline.play();
} else {
primaryStage.setIconified(true);
}
}
public void close() {
@@ -31,7 +31,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class DecoratorAnimationProducer implements AnimationProducer {
public final class DecoratorAnimationProducer implements AnimationProducer {
@Override
public void init(AnimationHandler handler) {
}
@@ -19,6 +19,9 @@ package org.jackhuang.hmcl.ui.decorator;
import com.jfoenix.controls.JFXDialog;
import com.jfoenix.controls.JFXSnackbar;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
@@ -35,6 +38,7 @@ import javafx.scene.input.MouseEvent;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDnD;
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
@@ -42,20 +46,18 @@ import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.account.AddAuthlibInjectorServerPane;
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
import org.jackhuang.hmcl.ui.construct.DialogAware;
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.ui.construct.Navigator;
import org.jackhuang.hmcl.ui.construct.StackContainerPane;
import org.jackhuang.hmcl.ui.construct.JFXDialogPane;
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;
@@ -80,11 +82,32 @@ public class DecoratorController {
private final Navigator navigator;
private JFXDialog dialog;
private StackContainerPane dialogPane;
private JFXDialogPane dialogPane;
public DecoratorController(Stage stage, Node mainPage) {
decorator = new Decorator(stage);
decorator.setOnCloseButtonAction(Launcher::stopApplication);
decorator.setOnCloseButtonAction(() -> {
if (AnimationUtils.playWindowAnimation()) {
Timeline timeline = new Timeline(
new KeyFrame(Duration.millis(0),
new KeyValue(decorator.opacityProperty(), 1, FXUtils.EASE),
new KeyValue(decorator.scaleXProperty(), 1, FXUtils.EASE),
new KeyValue(decorator.scaleYProperty(), 1, FXUtils.EASE),
new KeyValue(decorator.scaleZProperty(), 0.3, FXUtils.EASE)
),
new KeyFrame(Duration.millis(200),
new KeyValue(decorator.opacityProperty(), 0, FXUtils.EASE),
new KeyValue(decorator.scaleXProperty(), 0.8, FXUtils.EASE),
new KeyValue(decorator.scaleYProperty(), 0.8, FXUtils.EASE),
new KeyValue(decorator.scaleZProperty(), 0.8, FXUtils.EASE)
)
);
timeline.setOnFinished(event -> Launcher.stopApplication());
timeline.play();
} else {
Launcher.stopApplication();
}
});
decorator.titleTransparentProperty().bind(config().titleTransparentProperty());
navigator = new Navigator();
@@ -180,23 +203,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 +231,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 ====
@@ -383,7 +380,7 @@ public class DecoratorController {
return;
}
dialog = new JFXDialog();
dialogPane = new StackContainerPane();
dialogPane = new JFXDialogPane();
dialog.setContent(dialogPane);
decorator.capableDraggingWindow(dialog);
@@ -426,18 +423,21 @@ public class DecoratorController {
.ifPresent(handler -> node.removeEventHandler(DialogCloseEvent.CLOSE, (EventHandler<DialogCloseEvent>) handler));
if (dialog != null) {
dialogPane.pop(node);
JFXDialogPane pane = dialogPane;
if (node instanceof DialogAware) {
((DialogAware) node).onDialogClosed();
}
if (dialogPane.getChildren().isEmpty()) {
if (pane.size() == 1 && pane.peek().orElse(null) == node) {
dialog.setOnDialogClosed(e -> pane.pop(node));
dialog.close();
dialog = null;
dialogPane = null;
navigator.setDisable(false);
} else {
pane.pop(node);
}
if (node instanceof DialogAware) {
((DialogAware) node).onDialogClosed();
}
}
}
@@ -18,10 +18,8 @@
package org.jackhuang.hmcl.ui.decorator;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.svg.SVGGlyph;
import javafx.beans.binding.Bindings;
import javafx.collections.ListChangeListener;
import javafx.css.PseudoClass;
import javafx.geometry.Bounds;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
@@ -29,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;
@@ -44,20 +41,14 @@ import org.jackhuang.hmcl.ui.animation.AnimationProducer;
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
import org.jackhuang.hmcl.ui.animation.TransitionPane;
import org.jackhuang.hmcl.ui.wizard.Navigation;
import org.jackhuang.hmcl.util.Lang;
public class DecoratorSkin extends SkinBase<Decorator> {
private static final PseudoClass TRANSPARENT = PseudoClass.getPseudoClass("transparent");
private static final SVGGlyph minus = Lang.apply(new SVGGlyph(0, "MINUS", "M804.571 420.571v109.714q0 22.857-16 38.857t-38.857 16h-694.857q-22.857 0-38.857-16t-16-38.857v-109.714q0-22.857 16-38.857t38.857-16h694.857q22.857 0 38.857 16t16 38.857z", Color.WHITE),
glyph -> { glyph.setSize(12, 2); glyph.setTranslateY(4); });
private final StackPane root, parent;
private final StackPane titleContainer;
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.
@@ -69,14 +60,13 @@ public class DecoratorSkin extends SkinBase<Decorator> {
primaryStage = control.getPrimaryStage();
minus.fillProperty().bind(Theme.foregroundFillBinding());
Decorator skinnable = getSkinnable();
root = new StackPane();
root.getStyleClass().add("window");
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();
@@ -197,18 +187,19 @@ public class DecoratorSkin extends SkinBase<Decorator> {
buttonsContainer.setMaxHeight(40);
{
JFXButton btnHelp = new JFXButton();
btnHelp.setFocusTraversable(false);
btnHelp.setGraphic(SVG.HELP_CIRCLE_OUTLINE.createIcon(Theme.foregroundFillBinding(), -1, -1));
btnHelp.getStyleClass().add("jfx-decorator-button");
btnHelp.setOnAction(e -> FXUtils.openLink("https://docs.hmcl.net/help.html"));
JFXButton btnMin = new JFXButton();
StackPane pane = new StackPane(minus);
pane.setAlignment(Pos.CENTER);
btnMin.setGraphic(pane);
btnMin.setFocusTraversable(false);
btnMin.setGraphic(SVG.MINUS.createIcon(Theme.foregroundFillBinding(), -1, -1));
btnMin.getStyleClass().add("jfx-decorator-button");
btnMin.setOnAction(e -> skinnable.minimize());
JFXButton btnClose = new JFXButton();
btnClose.setFocusTraversable(false);
btnClose.setGraphic(SVG.CLOSE.createIcon(Theme.foregroundFillBinding(), -1, -1));
btnClose.getStyleClass().add("jfx-decorator-button");
btnClose.setOnAction(e -> skinnable.close());
@@ -236,6 +227,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
if (canBack) {
JFXButton backNavButton = new JFXButton();
backNavButton.setFocusTraversable(false);
backNavButton.setGraphic(SVG.BACK.createIcon(Theme.foregroundFillBinding(), -1, -1));
backNavButton.getStyleClass().add("jfx-decorator-button");
backNavButton.ripplerFillProperty().set(Theme.whiteFill());
@@ -247,6 +239,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
if (canClose) {
JFXButton closeNavButton = new JFXButton();
closeNavButton.setFocusTraversable(false);
closeNavButton.setGraphic(SVG.CLOSE.createIcon(Theme.foregroundFillBinding(), -1, -1));
closeNavButton.getStyleClass().add("jfx-decorator-button");
closeNavButton.ripplerFillProperty().set(Theme.whiteFill());
@@ -270,7 +263,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
titleLabel.getStyleClass().add("jfx-decorator-title");
if (titleNode == null) {
titleLabel.maxWidthProperty().bind(Bindings.createDoubleBinding(
() -> skinnable.getWidth() - 100 - navLeft.getWidth(),
() -> skinnable.getWidth() - 150 - navLeft.getWidth(),
skinnable.widthProperty(), navLeft.widthProperty()));
} else {
titleLabel.prefWidthProperty().bind(Bindings.createDoubleBinding(() -> {
@@ -310,13 +303,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();
}
@@ -333,146 +319,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,20 @@ 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.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
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;
@@ -53,15 +61,21 @@ import org.jackhuang.hmcl.ui.wizard.Navigation;
import org.jackhuang.hmcl.ui.wizard.Refreshable;
import org.jackhuang.hmcl.ui.wizard.WizardPage;
import org.jackhuang.hmcl.util.Holder;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.i18n.I18n;
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;
public final class VersionsPage extends BorderPane implements WizardPage, Refreshable {
@@ -78,14 +92,15 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
private final JFXCheckBox chkRelease;
private final JFXCheckBox chkSnapshot;
private final JFXCheckBox chkOld;
private final JFXButton btnRefresh;
private final HBox checkPane;
private final ComponentList centrePane;
private final StackPane center;
private final VersionList<?> versionList;
private CompletableFuture<?> executor;
private final HBox searchBar;
private final StringProperty queryString = new SimpleStringProperty();
public VersionsPage(Navigation navigation, String title, String gameVersion, DownloadProvider downloadProvider, String libraryId, Runnable callback) {
this.title = title;
this.gameVersion = gameVersion;
@@ -100,6 +115,8 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
this.setTop(hintPane);
root = new TransitionPane();
BorderPane toolbarPane = new BorderPane();
JFXButton btnRefresh;
{
spinner = new JFXSpinner();
@@ -109,7 +126,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
centrePane = new ComponentList();
centrePane.getStyleClass().add("no-padding");
{
checkPane = new HBox();
HBox checkPane = new HBox();
checkPane.setSpacing(10);
{
chkRelease = new JFXCheckBox(i18n("version.game.releases"));
@@ -122,21 +139,69 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
chkOld = new JFXCheckBox(i18n("version.game.old"));
HBox.setMargin(chkOld, new Insets(10, 0, 10, 0));
HBox pane = new HBox();
HBox.setHgrow(pane, Priority.ALWAYS);
btnRefresh = new JFXButton(i18n("button.refresh"));
btnRefresh.getStyleClass().add("jfx-tool-bar-button");
btnRefresh.setOnAction(e -> onRefresh());
checkPane.getChildren().setAll(chkRelease, chkSnapshot, chkOld, pane, btnRefresh);
checkPane.getChildren().setAll(chkRelease, chkSnapshot, chkOld);
}
list = new JFXListView<>();
list.getStyleClass().add("jfx-list-view-float");
VBox.setVgrow(list, Priority.ALWAYS);
centrePane.getContent().setAll(checkPane, list);
TransitionPane rightToolbarPane = new TransitionPane();
{
HBox refreshPane = new HBox();
refreshPane.setAlignment(Pos.CENTER_RIGHT);
btnRefresh = new JFXButton(i18n("button.refresh"));
btnRefresh.getStyleClass().add("jfx-tool-bar-button");
btnRefresh.setOnAction(e -> onRefresh());
JFXButton btnSearch = new JFXButton(i18n("search"));
btnSearch.getStyleClass().add("jfx-tool-bar-button");
btnSearch.setGraphic(wrap(SVG.MAGNIFY.createIcon(Theme.blackFill(), -1, -1)));
searchBar = new HBox();
{
searchBar.setAlignment(Pos.CENTER);
searchBar.setPadding(new Insets(0, 5, 0, 0));
JFXTextField 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 -> {
searchField.clear();
rightToolbarPane.setContent(refreshPane, ContainerAnimations.FADE);
});
onEscPressed(searchField, closeSearchBar::fire);
PauseTransition pause = new PauseTransition(Duration.millis(100));
pause.setOnFinished(e -> queryString.set(searchField.getText()));
searchField.textProperty().addListener((observable, oldValue, newValue) -> {
pause.setRate(1);
pause.playFromStart();
});
searchBar.getChildren().setAll(searchField, closeSearchBar);
btnSearch.setOnAction(e -> {
rightToolbarPane.setContent(searchBar, ContainerAnimations.FADE);
searchField.requestFocus();
});
}
refreshPane.getChildren().setAll(new HBox(btnSearch, btnRefresh));
rightToolbarPane.setContent(refreshPane, ContainerAnimations.NONE);
}
// 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);
toolbarPane.setLeft(checkPane);
toolbarPane.setRight(rightToolbarPane);
centrePane.getContent().setAll(toolbarPane, list);
}
center.getChildren().setAll(centrePane);
@@ -163,22 +228,53 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
this.setCenter(root);
versionList = downloadProvider.getVersionListById(libraryId);
if (versionList.hasType()) {
centrePane.getContent().setAll(checkPane, list);
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(toolbarPane, list);
} else {
centrePane.getContent().setAll(list);
}
ComponentList.setVgrow(list, Priority.ALWAYS);
InvalidationListener listener = o -> list.getItems().setAll(loadVersions());
InvalidationListener listener = o -> {
List<RemoteVersion> versions = loadVersions();
String query = queryString.get();
if (!StringUtils.isBlank(query)) {
Predicate<RemoteVersion> predicate;
if (query.startsWith("regex:")) {
try {
Pattern pattern = Pattern.compile(query.substring("regex:".length()));
predicate = it -> pattern.matcher(it.getSelfVersion()).find();
} catch (Throwable e) {
LOG.warning("Illegal regular expression", e);
return;
}
} else {
String lowerQueryString = query.toLowerCase(Locale.ROOT);
predicate = it -> it.getSelfVersion().toLowerCase(Locale.ROOT).contains(lowerQueryString);
}
versions = versions.stream().filter(predicate).collect(Collectors.toList());
}
list.getItems().setAll(versions);
};
chkRelease.selectedProperty().addListener(listener);
chkSnapshot.selectedProperty().addListener(listener);
chkOld.selectedProperty().addListener(listener);
queryString.addListener(listener);
btnRefresh.setGraphic(wrap(SVG.REFRESH.createIcon(Theme.blackFill(), -1, -1)));
Holder<RemoteVersionListCell> lastCell = new Holder<>();
list.setCellFactory(listView -> new RemoteVersionListCell(lastCell));
list.setCellFactory(listView -> new RemoteVersionListCell(lastCell, libraryId));
FXUtils.onClicked(list, () -> {
if (list.getSelectionModel().getSelectedIndex() < 0)
@@ -259,7 +355,9 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
refresh();
}
private void onBack() { navigation.onPrev(true); }
private void onBack() {
navigation.onPrev(true);
}
private void onSponsor() {
FXUtils.openLink("https://bmclapidoc.bangbang93.com");
@@ -272,8 +370,12 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
private final Holder<RemoteVersionListCell> lastCell;
RemoteVersionListCell(Holder<RemoteVersionListCell> lastCell) {
RemoteVersionListCell(Holder<RemoteVersionListCell> lastCell, String libraryId) {
this.lastCell = lastCell;
if ("game".equals(libraryId)) {
content.getExternalLinkButton().setGraphic(SVG.EARTH.createIcon(Theme.blackFill(), -1, -1));
FXUtils.installFastTooltip(content.getExternalLinkButton(), i18n("wiki.tooltip"));
}
pane.getStyleClass().add("md-list-cell");
StackPane.setMargin(content, new Insets(10, 16, 10, 16));
@@ -297,7 +399,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
content.setTitle(remoteVersion.getSelfVersion());
if (remoteVersion.getReleaseDate() != null) {
content.setSubtitle(formatDateTime(remoteVersion.getReleaseDate()));
content.setSubtitle(I18n.formatDateTime(remoteVersion.getReleaseDate()));
} else {
content.setSubtitle(null);
}
@@ -307,14 +409,19 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
case RELEASE:
content.getTags().setAll(i18n("version.game.release"));
content.setImage(VersionIconType.GRASS.getIcon());
content.setExternalLink(i18n("wiki.version.game.release", remoteVersion.getGameVersion()));
break;
case SNAPSHOT:
content.getTags().setAll(i18n("version.game.snapshot"));
content.setImage(VersionIconType.COMMAND.getIcon());
content.setExternalLink(i18n("wiki.version.game.snapshot", remoteVersion.getGameVersion()));
break;
default:
content.getTags().setAll(i18n("version.game.old"));
content.setImage(VersionIconType.CRAFT_TABLE.getIcon());
content.setExternalLink(null);
break;
}
} else {
@@ -339,6 +446,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
content.setSubtitle(remoteVersion.getGameVersion());
else
content.getTags().setAll(remoteVersion.getGameVersion());
content.setExternalLink(null);
}
}
}
@@ -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);
}
@@ -36,6 +36,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
@@ -116,7 +117,7 @@ public final class AboutPage extends StackPane {
return componentList;
}
try (Reader reader = new InputStreamReader(input)) {
try (Reader reader = new InputStreamReader(input, StandardCharsets.UTF_8)) {
JsonArray array = JsonUtils.GSON.fromJson(reader, JsonArray.class);
for (JsonElement element : array) {
@@ -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() {
@@ -181,6 +181,7 @@ public final class JavaInstallPage extends WizardSinglePage {
pane.setLeft(new Label(name));
Label valueLabel = FXUtils.truncatedLabel(value, 60);
FXUtils.copyOnDoubleClick(valueLabel);
BorderPane.setAlignment(valueLabel, Pos.CENTER_RIGHT);
pane.setCenter(valueLabel);
@@ -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 {
@@ -63,67 +62,25 @@ public class LauncherSettingsPage extends DecoratorAnimatedPage implements Decor
tab = new TabHeader(gameTab, javaManagementTab, settingsTab, personalizationTab, downloadTab, helpTab, feedbackTab, aboutTab);
tab.select(gameTab);
gameTab.initializeIfNeeded();
gameTab.getNode().loadVersion(Profiles.getSelectedProfile(), null);
FXUtils.onChangeAndOperate(tab.getSelectionModel().selectedItemProperty(), newValue -> {
transitionPane.setContent(gameTab.getNode(), ContainerAnimations.NONE);
FXUtils.onChange(tab.getSelectionModel().selectedItemProperty(), newValue -> {
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))
@@ -39,7 +39,7 @@ import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
import org.jackhuang.hmcl.ui.nbt.NBTEditorPage;
import org.jackhuang.hmcl.ui.nbt.NBTHelper;
import org.jackhuang.hmcl.ui.nbt.NBTFileType;
import org.jackhuang.hmcl.ui.versions.GameAdvancedListItem;
import org.jackhuang.hmcl.ui.versions.Versions;
import org.jackhuang.hmcl.upgrade.UpdateChecker;
@@ -90,16 +90,16 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
if (mainPage == null) {
MainPage mainPage = new MainPage();
FXUtils.applyDragListener(mainPage,
file -> ModpackHelper.isFileModpackByExtension(file) || NBTHelper.isNBTFileByExtension(file),
file -> ModpackHelper.isFileModpackByExtension(file) || NBTFileType.isNBTFileByExtension(file.toPath()),
modpacks -> {
File file = modpacks.get(0);
if (ModpackHelper.isFileModpackByExtension(file)) {
Controllers.getDecorator().startWizard(
new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), file),
i18n("install.modpack"));
} else if (NBTHelper.isNBTFileByExtension(file)) {
} else if (NBTFileType.isNBTFileByExtension(file.toPath())) {
try {
Controllers.navigate(new NBTEditorPage(file));
Controllers.navigate(new NBTEditorPage(file.toPath()));
} catch (Throwable e) {
LOG.warning("Fail to open nbt file", e);
Controllers.dialog(i18n("nbt.open.failed") + "\n\n" + StringUtils.getStackTrace(e),
@@ -1,3 +1,20 @@
/*
* Hello Minecraft! Launcher
* 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
* 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.nbt;
import com.jfoenix.controls.JFXButton;
@@ -5,33 +22,38 @@ import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
import org.jackhuang.hmcl.ui.construct.PageCloseEvent;
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.StringUtils;
import java.io.*;
import java.util.concurrent.CompletableFuture;
import java.nio.file.Path;
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class NBTEditorPage extends BorderPane implements DecoratorPage {
/**
* @author Glavo
*/
public final class NBTEditorPage extends SpinnerPane implements DecoratorPage {
private final ReadOnlyObjectWrapper<State> state;
private final File file;
private final Path file;
private final NBTFileType type;
public NBTEditorPage(File file) throws IOException {
private final BorderPane root = new BorderPane();
public NBTEditorPage(Path file) throws IOException {
getStyleClass().add("gray-background");
this.state = new ReadOnlyObjectWrapper<>(DecoratorPage.State.fromTitle(i18n("nbt.title", file.getAbsolutePath())));
this.state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("nbt.title", file.toString())));
this.file = file;
this.type = NBTFileType.ofFile(file);
@@ -39,7 +61,8 @@ public class NBTEditorPage extends BorderPane implements DecoratorPage {
throw new IOException("Unknown type of file " + file);
}
setCenter(new ProgressIndicator());
setContent(root);
setLoading(true);
HBox actions = new HBox(8);
actions.setPadding(new Insets(8));
@@ -65,18 +88,16 @@ public class NBTEditorPage extends BorderPane implements DecoratorPage {
actions.getChildren().setAll(saveButton, cancelButton);
CompletableFuture.supplyAsync(Lang.wrap(() -> type.readAsTree(file)))
.thenAcceptAsync(tree -> {
setCenter(new NBTTreeView(tree));
// setBottom(actions);
}, Schedulers.javafx())
.handleAsync((result, e) -> {
if (e != null) {
LOG.warning("Fail to open nbt file", e);
Controllers.dialog(i18n("nbt.open.failed") + "\n\n" + StringUtils.getStackTrace(e), null, MessageDialogPane.MessageType.WARNING, cancelButton::fire);
Task.supplyAsync(() -> type.readAsTree(file))
.whenComplete(Schedulers.javafx(), (result, exception) -> {
if (exception == null) {
setLoading(false);
root.setCenter(new NBTTreeView(result));
} else {
LOG.warning("Fail to open nbt file", exception);
Controllers.dialog(i18n("nbt.open.failed") + "\n\n" + StringUtils.getStackTrace(exception), null, MessageDialogPane.MessageType.WARNING, cancelButton::fire);
}
return null;
}, Schedulers.javafx());
}).start();
}
public void save() throws IOException {
@@ -1,3 +1,20 @@
/*
* Hello Minecraft! Launcher
* 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
* 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.nbt;
import com.github.steveice10.opennbt.NBTIO;
@@ -9,16 +26,21 @@ import org.apache.commons.compress.utils.BoundedInputStream;
import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.zip.GZIPInputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
/**
* @author Glavo
*/
public enum NBTFileType {
COMPRESSED("dat", "dat_old") {
@Override
public Tag read(File file) throws IOException {
try (BufferedInputStream fileInputStream = new BufferedInputStream(new FileInputStream(file))) {
public Tag read(Path file) throws IOException {
try (BufferedInputStream fileInputStream = new BufferedInputStream(Files.newInputStream(file))) {
fileInputStream.mark(3);
byte[] header = new byte[3];
if (fileInputStream.read(header) < 3) {
@@ -42,25 +64,28 @@ public enum NBTFileType {
},
ANVIL("mca") {
@Override
public Tag read(File file) throws IOException {
public Tag read(Path file) throws IOException {
return REGION.read(file);
}
@Override
public NBTTreeView.Item readAsTree(File file) throws IOException {
public NBTTreeView.Item readAsTree(Path file) throws IOException {
return REGION.readAsTree(file);
}
},
REGION("mcr") {
@Override
public Tag read(File file) throws IOException {
try (RandomAccessFile r = new RandomAccessFile(file, "r")) {
public Tag read(Path file) throws IOException {
try (RandomAccessFile r = new RandomAccessFile(file.toFile(), "r")) {
ListTag tag = new ListTag(file.getFileName().toString(), CompoundTag.class);
if (r.length() == 0) {
return tag;
}
byte[] header = new byte[4096];
byte[] buffer = new byte[1 * 1024 * 1024]; // The maximum size of each chunk is 1MiB
Inflater inflater = new Inflater();
ListTag tag = new ListTag(file.getName(), CompoundTag.class);
r.readFully(header);
for (int i = 0; i < 4096; i += 4) {
int offset = ((header[i] & 0xff) << 16) + ((header[i + 1] & 0xff) << 8) + (header[i + 2] & 0xff);
@@ -109,7 +134,7 @@ public enum NBTFileType {
}
@Override
public NBTTreeView.Item readAsTree(File file) throws IOException {
public NBTTreeView.Item readAsTree(Path file) throws IOException {
NBTTreeView.Item item = new NBTTreeView.Item(read(file));
for (Tag tag : ((ListTag) item.getValue())) {
@@ -135,7 +160,11 @@ public enum NBTFileType {
static final NBTFileType[] types = values();
public static NBTFileType ofFile(File file) {
public static boolean isNBTFileByExtension(Path file) {
return NBTFileType.ofFile(file) != null;
}
public static NBTFileType ofFile(Path file) {
String ext = FileUtils.getExtension(file);
for (NBTFileType type : types) {
for (String extension : type.extensions) {
@@ -153,11 +182,11 @@ public enum NBTFileType {
this.extensions = extensions;
}
public abstract Tag read(File file) throws IOException;
public abstract Tag read(Path file) throws IOException;
public NBTTreeView.Item readAsTree(File file) throws IOException {
public NBTTreeView.Item readAsTree(Path file) throws IOException {
NBTTreeView.Item root = NBTTreeView.buildTree(read(file));
root.setName(file.getName());
root.setName(file.getFileName().toString());
return root;
}
}
@@ -1,12 +0,0 @@
package org.jackhuang.hmcl.ui.nbt;
import java.io.File;
public final class NBTHelper {
private NBTHelper() {
}
public static boolean isNBTFileByExtension(File file) {
return NBTFileType.ofFile(file) != null;
}
}
@@ -1,3 +1,20 @@
/*
* Hello Minecraft! Launcher
* 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
* 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.nbt;
import com.github.steveice10.opennbt.tag.builtin.Tag;
@@ -6,6 +23,9 @@ import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
/**
* @author Glavo
*/
public enum NBTTagType {
BYTE, SHORT, INT, LONG, FLOAT, DOUBLE,
BYTE_ARRAY, INT_ARRAY, LONG_ARRAY,
@@ -1,3 +1,20 @@
/*
* Hello Minecraft! Launcher
* 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
* 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.nbt;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
@@ -14,7 +31,10 @@ import java.util.EnumMap;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class NBTTreeView extends TreeView<Tag> {
/**
* @author Glavo
*/
public final class NBTTreeView extends TreeView<Tag> {
public NBTTreeView(NBTTreeView.Item tree) {
this.setRoot(tree);
@@ -240,7 +240,7 @@ public final class AdvancedVersionSettingPage extends StackPane implements Decor
FXUtils.unbind(txtWrapper, versionSetting.wrapperProperty());
FXUtils.unbind(txtPreLaunchCommand, versionSetting.preLaunchCommandProperty());
FXUtils.unbind(txtPostExitCommand, versionSetting.postExitCommandProperty());
FXUtils.unbindEnum(cboRenderer);
FXUtils.unbindEnum(cboRenderer, versionSetting.rendererProperty());
noGameCheckPane.selectedProperty().unbindBidirectional(versionSetting.notCheckGameProperty());
noJVMCheckPane.selectedProperty().unbindBidirectional(versionSetting.notCheckJVMProperty());
noJVMArgsPane.selectedProperty().unbindBidirectional(versionSetting.noJVMArgsProperty());
@@ -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;
@@ -92,11 +95,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
private Runnable retrySearch;
public DownloadListPage(RemoteModRepository repository) {
this(repository, null);
}
public DownloadListPage(RemoteModRepository repository, DownloadPage.DownloadCallback callback) {
this(repository, callback, false);
this(repository, null, false);
}
public DownloadListPage(RemoteModRepository repository, DownloadPage.DownloadCallback callback, boolean versionSelection) {
@@ -493,6 +492,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 {
@@ -307,7 +305,7 @@ public class DownloadPage extends Control implements DecoratorPage {
ComponentList sublist = new ComponentList(() -> {
ArrayList<ModItem> items = new ArrayList<>(versions.size());
for (RemoteMod.Version v: versions) {
for (RemoteMod.Version v : versions) {
items.add(new ModItem(v, control));
}
return items;
@@ -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:
@@ -440,12 +439,14 @@ public class DownloadPage extends Control implements DecoratorPage {
private static final class ModVersion extends JFXDialogLayout {
public ModVersion(RemoteMod.Version version, DownloadPage selfPage) {
this.setHeading(new HBox(new Label(i18n("mods.download.title", version.getName()))));
boolean isModpack = selfPage.repository.getType() == RemoteModRepository.Type.MODPACK;
this.setHeading(new HBox(new Label(i18n(isModpack ? "modpack.download.title" : "mods.download.title", version.getName()))));
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();
@@ -462,10 +463,10 @@ public class DownloadPage extends Control implements DecoratorPage {
this.setBody(box);
JFXButton downloadButton = new JFXButton(i18n("mods.install"));
JFXButton downloadButton = new JFXButton(isModpack ? i18n("install.modpack") : i18n("mods.install"));
downloadButton.getStyleClass().add("dialog-accept");
downloadButton.setOnAction(e -> {
if (!spinnerPane.isLoading() && spinnerPane.getFailedReason() == null) {
if (isModpack || !spinnerPane.isLoading() && spinnerPane.getFailedReason() == null) {
fireEvent(new DialogCloseEvent());
}
selfPage.download(version);
@@ -488,6 +489,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 +527,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);
}
@@ -192,7 +192,7 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
})
.whenComplete(Schedulers.javafx(), (result, exception) -> {
if (exception != null || result == null) {
Controllers.dialog("Failed to check updates", "failed", MessageDialogPane.MessageType.ERROR);
Controllers.dialog(i18n("mods.check_updates.failed_check"), i18n("message.failed"), MessageDialogPane.MessageType.ERROR);
} else if (result.isEmpty()) {
Controllers.dialog(i18n("mods.check_updates.empty"));
} else {
@@ -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);
}
@@ -379,6 +398,7 @@ class ModListPageSkin extends SkinBase<ModListPage> {
setHeading(titleContainer);
Label description = new Label(modInfo.getModInfo().getDescription().toString());
FXUtils.copyOnDoubleClick(description);
setBody(description);
if (StringUtils.isNotBlank(modInfo.getModInfo().getId())) {
@@ -139,7 +139,7 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
task.whenComplete(Schedulers.javafx(), exception -> {
fireEvent(new PageCloseEvent());
if (!task.getFailedMods().isEmpty()) {
Controllers.dialog(i18n("mods.check_updates.failed") + "\n" +
Controllers.dialog(i18n("mods.check_updates.failed_download") + "\n" +
task.getFailedMods().stream().map(LocalModFile::getFileName).collect(Collectors.joining("\n")),
i18n("install.failed"),
MessageDialogPane.MessageType.ERROR);
@@ -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 {
@@ -66,7 +66,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
private String preferredVersionName = null;
{
public VersionPage() {
versionSettingsTab.setNodeSupplier(loadVersionFor(() -> new VersionSettingsPage(false)));
installerListTab.setNodeSupplier(loadVersionFor(InstallerListPage::new));
modListTab.setNodeSupplier(loadVersionFor(ModListPage::new));
@@ -77,7 +77,8 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onNavigated);
tab.select(versionSettingsTab);
FXUtils.onChangeAndOperate(tab.getSelectionModel().selectedItemProperty(), newValue -> {
transitionPane.setContent(versionSettingsTab.getNode(), ContainerAnimations.NONE);
FXUtils.onChange(tab.getSelectionModel().selectedItemProperty(), newValue -> {
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE);
});
@@ -308,25 +309,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");
@@ -46,6 +46,7 @@ import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.Pair;
import org.jackhuang.hmcl.util.javafx.BindingMapping;
import org.jackhuang.hmcl.util.javafx.PropertyUtils;
import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
import org.jackhuang.hmcl.util.platform.Architecture;
import org.jackhuang.hmcl.java.JavaRuntime;
@@ -57,10 +58,28 @@ 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 +111,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 +121,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) {
@@ -118,10 +137,6 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
rootPane.getStyleClass().add("card-list");
if (globalSetting) {
HintPane skinHint = new HintPane(MessageDialogPane.MessageType.INFO);
skinHint.setText(i18n("settings.skin"));
rootPane.getChildren().add(skinHint);
HintPane specificSettingsHint = new HintPane(MessageDialogPane.MessageType.WARNING);
Text text = new Text();
text.textProperty().bind(BindingMapping.of(selectedVersion)
@@ -173,6 +188,31 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
componentList = new ComponentList();
componentList.setDepth(1);
if (!globalSetting) {
BorderPane copyGlobalPane = new BorderPane();
{
Label label = new Label(i18n("settings.game.copy_global"));
copyGlobalPane.setLeft(label);
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
JFXButton copyAll = new JFXButton(i18n("settings.game.copy_global.copy_all"));
copyAll.disableProperty().bind(modpack);
copyGlobalPane.setRight(copyAll);
copyAll.setOnAction(e -> Controllers.confirm(i18n("settings.game.copy_global.copy_all.confirm"), null, () -> {
Set<String> ignored = new HashSet<>(Arrays.asList(
"usesGlobal",
"versionIcon"
));
PropertyUtils.copyProperties(profile.getGlobal(), lastVersionSetting, name -> !ignored.contains(name));
}, null));
copyAll.getStyleClass().add("jfx-button-border");
BorderPane.setAlignment(copyAll, Pos.CENTER_RIGHT);
}
componentList.getContent().add(copyGlobalPane);
}
javaItem = new MultiFileItem<>();
javaSublist = new ComponentSublist();
javaSublist.getContent().add(javaItem);
@@ -185,7 +225,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 +275,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));
{
@@ -428,7 +469,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
showAdvancedSettingPane.setRight(button);
}
componentList.getContent().setAll(
componentList.getContent().addAll(
javaSublist,
gameDirSublist,
maxMemoryPane,
@@ -443,22 +484,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 +497,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
@@ -513,13 +551,13 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
javaCustomOption.valueProperty().unbindBidirectional(lastVersionSetting.javaDirProperty());
gameDirCustomOption.valueProperty().unbindBidirectional(lastVersionSetting.gameDirProperty());
FXUtils.unbind(txtServerIP, lastVersionSetting.serverIpProperty());
FXUtils.unbindBoolean(chkAutoAllocate, lastVersionSetting.autoMemoryProperty());
FXUtils.unbindBoolean(chkFullscreen, lastVersionSetting.fullscreenProperty());
chkAutoAllocate.selectedProperty().unbindBidirectional(lastVersionSetting.autoMemoryProperty());
chkFullscreen.selectedProperty().unbindBidirectional(lastVersionSetting.fullscreenProperty());
showLogsPane.selectedProperty().unbindBidirectional(lastVersionSetting.showLogsProperty());
FXUtils.unbindEnum(cboLauncherVisibility);
FXUtils.unbindEnum(cboProcessPriority);
FXUtils.unbindEnum(cboLauncherVisibility, lastVersionSetting.launcherVisibilityProperty());
FXUtils.unbindEnum(cboProcessPriority, lastVersionSetting.processPriorityProperty());
lastVersionSetting.usesGlobalProperty().removeListener(specificSettingsListener);
lastVersionSetting.usesGlobalProperty().removeListener(usesGlobalListener);
lastVersionSetting.javaVersionTypeProperty().removeListener(javaListener);
lastVersionSetting.javaDirProperty().removeListener(javaListener);
lastVersionSetting.defaultJavaPathPropertyProperty().removeListener(javaListener);
@@ -528,6 +566,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;
@@ -546,15 +586,16 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
javaCustomOption.bindBidirectional(versionSetting.javaDirProperty());
gameDirCustomOption.bindBidirectional(versionSetting.gameDirProperty());
FXUtils.bindString(txtServerIP, versionSetting.serverIpProperty());
FXUtils.bindBoolean(chkAutoAllocate, versionSetting.autoMemoryProperty());
FXUtils.bindBoolean(chkFullscreen, versionSetting.fullscreenProperty());
chkAutoAllocate.selectedProperty().bindBidirectional(versionSetting.autoMemoryProperty());
chkFullscreen.selectedProperty().bindBidirectional(versionSetting.fullscreenProperty());
showLogsPane.selectedProperty().bindBidirectional(versionSetting.showLogsProperty());
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 Versions {
} catch (IOException e) {
Controllers.dialog(
i18n("install.failed.downloading.detail", file.getFile().getUrl()) + "\n" + StringUtils.getStackTrace(e),
i18n("download.failed.no_code", file.getFile().getUrl()), MessageDialogPane.MessageType.ERROR);
i18n("download.failed.no_code"), MessageDialogPane.MessageType.ERROR);
return;
}
Controllers.taskDialog(
@@ -96,7 +96,7 @@ public final class Versions {
} else {
Controllers.dialog(
i18n("install.failed.downloading.detail", file.getFile().getUrl()) + "\n" + StringUtils.getStackTrace(e),
i18n("download.failed.no_code", file.getFile().getUrl()), MessageDialogPane.MessageType.ERROR);
i18n("download.failed.no_code"), MessageDialogPane.MessageType.ERROR);
}
}).executor(true),
i18n("message.downloading"),
@@ -103,6 +103,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
Label worldNameLabel = new Label();
FXUtils.copyOnDoubleClick(worldNameLabel);
worldNameLabel.setText(world.getWorldName());
BorderPane.setAlignment(worldNameLabel, Pos.CENTER_RIGHT);
worldNamePane.setRight(worldNameLabel);
@@ -115,6 +116,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
gameVersionPane.setLeft(label);
Label gameVersionLabel = new Label();
FXUtils.copyOnDoubleClick(gameVersionLabel);
gameVersionLabel.setText(world.getGameVersion());
BorderPane.setAlignment(gameVersionLabel, Pos.CENTER_RIGHT);
gameVersionPane.setRight(gameVersionLabel);
@@ -127,6 +129,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
randomSeedPane.setLeft(label);
Label randomSeedLabel = new Label();
FXUtils.copyOnDoubleClick(randomSeedLabel);
BorderPane.setAlignment(randomSeedLabel, Pos.CENTER_RIGHT);
randomSeedPane.setRight(randomSeedLabel);
@@ -143,6 +146,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
lastPlayedPane.setLeft(label);
Label lastPlayedLabel = new Label();
FXUtils.copyOnDoubleClick(lastPlayedLabel);
lastPlayedLabel.setText(formatDateTime(Instant.ofEpochMilli(world.getLastPlayed())));
BorderPane.setAlignment(lastPlayedLabel, Pos.CENTER_RIGHT);
lastPlayedPane.setRight(lastPlayedLabel);
@@ -155,6 +159,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
timePane.setLeft(label);
Label timeLabel = new Label();
FXUtils.copyOnDoubleClick(timeLabel);
BorderPane.setAlignment(timeLabel, Pos.CENTER_RIGHT);
timePane.setRight(timeLabel);
@@ -258,6 +263,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
locationPane.setLeft(label);
Label locationLabel = new Label();
FXUtils.copyOnDoubleClick(locationLabel);
BorderPane.setAlignment(locationLabel, Pos.CENTER_RIGHT);
locationPane.setRight(locationLabel);
@@ -276,6 +282,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
lastDeathLocationPane.setLeft(label);
Label lastDeathLocationLabel = new Label();
FXUtils.copyOnDoubleClick(lastDeathLocationLabel);
BorderPane.setAlignment(lastDeathLocationLabel, Pos.CENTER_RIGHT);
lastDeathLocationPane.setRight(lastDeathLocationLabel);
@@ -297,6 +304,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
spawnPane.setLeft(label);
Label spawnLabel = new Label();
FXUtils.copyOnDoubleClick(spawnLabel);
BorderPane.setAlignment(spawnLabel, Pos.CENTER_RIGHT);
spawnPane.setRight(spawnLabel);
@@ -34,8 +34,8 @@ public final class Lazy<T> {
}
public T get() {
if (value == null) {
value = Objects.requireNonNull(supplier.get());
if (supplier != null) {
value = supplier.get();
supplier = null;
}
return value;
@@ -18,6 +18,8 @@
package org.jackhuang.hmcl.util;
import org.jackhuang.hmcl.game.*;
import org.jackhuang.hmcl.mod.LocalModFile;
import org.jackhuang.hmcl.mod.ModManager;
import org.jackhuang.hmcl.setting.VersionSetting;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.platform.Architecture;
@@ -60,7 +62,11 @@ public final class NativePatcher {
});
}
public static Version patchNative(Version version, String gameVersion, JavaRuntime javaVersion, VersionSetting settings) {
public static Version patchNative(DefaultGameRepository repository,
Version version, String gameVersion,
JavaRuntime javaVersion,
VersionSetting settings,
List<String> javaArguments) {
if (settings.getNativesDirType() == NativesDirectoryType.CUSTOM) {
if (gameVersion != null && GameVersionNumber.compare(gameVersion, "1.19") < 0)
return version;
@@ -83,7 +89,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 -> {
@@ -124,6 +130,7 @@ public final class NativePatcher {
return version;
}
boolean lwjglVersionChanged = false;
ArrayList<Library> newLibraries = new ArrayList<>();
for (Library library : version.getLibraries()) {
if (!library.appliesToCurrentEnvironment())
@@ -145,14 +152,33 @@ public final class NativePatcher {
} else if (replacement != null) {
LOG.info("Replace " + library.getName() + " with " + replacement.getName());
newLibraries.add(replacement);
if ("org.lwjgl:lwjgl".equals(library.getName()) && !Objects.equals(library.getVersion(), replacement.getVersion())) {
lwjglVersionChanged = true;
}
}
}
}
if (lwjglVersionChanged) {
ModManager modManager = repository.getModManager(version.getId());
try {
for (LocalModFile mod : modManager.getMods()) {
if ("sodium".equals(mod.getId())) {
// https://github.com/CaffeineMC/sodium/issues/2561
javaArguments.add("-Dsodium.checks.issue2561=false");
break;
}
}
} catch (Throwable e) {
LOG.warning("Failed to get mods", e);
}
}
return version.setLibraries(newLibraries);
}
public static Library getMesaLoader(JavaRuntime javaVersion, Renderer renderer) {
return getNatives(javaVersion.getPlatform()).get(renderer == Renderer.LLVMPIPE ? "software-renderer-loader" : "mesa-loader");
public static Library getMesaLoader(JavaRuntime javaVersion) {
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
@@ -141,18 +141,20 @@ account.move_to_global=Convert to Global Account\nThe account information will b
account.move_to_portable=Convert to Portable Account\nThe account information will be saved in a config file in the same directory as HMCL.
account.not_logged_in=Not Logged in
account.password=Password
account.portable=Portable Account
account.portable=Portable
account.skin=Skin
account.skin.file=Skin File
account.skin.model=Model
account.skin.model.default=Classic
account.skin.model.slim=Slim
account.skin.type.alex=Alex
account.skin.type.csl_api=Blessing Skin
account.skin.type.csl_api.location=Address
account.skin.type.csl_api.location.hint=CustomSkinAPI URL
account.skin.type.little_skin=LittleSkin
account.skin.type.little_skin.hint=You need to create a player with the same player name as your offline account on your skin provider site. Your skin will now be set to the skin assigned to your player on the skin provider site.
account.skin.type.local_file=Local Skin File
account.skin.type.steve=Steve
account.skin.upload=Upload/Edit Skin
account.skin.upload.failed=Failed to upload skin.
account.skin.invalid_skin=Invalid skin file.
@@ -165,7 +167,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 +195,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 due to a 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,16 +321,18 @@ 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.
download.external_link=Visit Download Website
download.failed=Failed to download "%1$s", response code: %2$d.
download.failed.empty=No versions are available. Please click here to go back.
download.failed.no_code=Failed to download "%s".
download.failed.no_code=Failed to download
download.failed.refresh=Failed to fetch version list. Please click here to retry.
download.game=New Game
download.provider.bmclapi=BMCLAPI (bangbang93, https://bmclapi2.bangbang93.com/)
@@ -348,10 +352,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 due to the lack of an SSL certificate in the current Java installation. You can try opening HMCL in 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 +369,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 +379,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=Due to macOS security mechanisms, Hello Minecraft! Launcher is isolated to a temporary directory by the OS.\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 +392,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,39 +425,39 @@ 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...
game.crash.reason.multiple=Several reasons detected:\n\n
game.crash.reason.block=The game crashed due to a block in the world.\n\
game.crash.reason.block=The game crashed because of a block in the world.\n\
\n\
You can try removing this block using MCEdit or deleting the mod that added it.\n\
\n\
Block Type: %1$s\n\
Location: %2$s
game.crash.reason.bootstrap_failed=The game crashed due to mod "%1$s".\n\
game.crash.reason.bootstrap_failed=The game crashed because of the "%1$s" mod.\n\
\n\
You can try deleting or updating it.
game.crash.reason.config=The game crashed due to a mod "%1$s" cannot parse its config file "%2$s".
game.crash.reason.debug_crash=The game crashed due to you triggering it manually. So you probably know why.
game.crash.reason.duplicated_mod=The game cannot continue to run due to duplicate mods "%1$s".\n\
game.crash.reason.config=The game crashed because the mod "%1$s" could not parse its config file "%2$s".
game.crash.reason.debug_crash=The game crashed because you triggered it manually. So you probably know why :)
game.crash.reason.duplicated_mod=The game cannot continue to run because of duplicate mods "%1$s".\n\
\n\
%2$s\n\
\n\
Each mod can only be installed once. Please delete the duplicate mod and try again.
game.crash.reason.entity=The game crashed due to an entity in the world.\n\
game.crash.reason.entity=The game crashed because of an entity in the world.\n\
\n\
You can try removing the entity using MCEdit or deleting the mod that added it.\n\
\n\
Entity Type: %1$s\n\
Location: %2$s
game.crash.reason.modmixin_failure=The game crashed due to some mod injection failure.\n\
game.crash.reason.modmixin_failure=The game crashed because some mods failed to inject.\n\
\n\
This generally means that the mod has a bug or is incompatible with the current environment.\n\
\n\
You can check the log to find the error mod.
game.crash.reason.mod_repeat_installation=The game crashed due to duplicate mods.\n\
game.crash.reason.mod_repeat_installation=The game crashed because of duplicate mods.\n\
\n\
Each mod can only be installed once. Please delete the duplicate mod and then relaunch the game.
game.crash.reason.forge_error=Forge/NeoForge may have provided error information.\n\
@@ -462,140 +466,140 @@ game.crash.reason.forge_error=Forge/NeoForge may have provided error information
\n\
If you do not see the error message, you can view the error report to understand how the error occurred.\n\
%1$s
game.crash.reason.mod_resolution0=The game crashed due to some mod problems. You can check the log to find the error mod(s).
game.crash.reason.mixin_apply_mod_failed=The game crashed due to the mixin could not be applied to the "%1$s" mod.\n\
game.crash.reason.mod_resolution0=The game crashed because of some mod problems. You can check the logs to find the faulty mod(s).
game.crash.reason.mixin_apply_mod_failed=The game crashed because the mixin could not be applied to the mod "%1$s".\n\
\n\
You can try deleting or updating the mod to resolve the problem.
game.crash.reason.java_version_is_too_high=The game crashed due to the Java version being too high to continue running.\n\
game.crash.reason.java_version_is_too_high=The game crashed because the Java version is too new to continue running.\n\
\n\
Please use an earlier Java version in "Global/Instance-specific Settings → Java" and then launch the game.\n\
Please use the previous major Java version in "Global/Instance-specific Settings → Java" and then launch the game.\n\
\n\
If not, you can download it from <a href="https://www.java.com/download/">java.com (Java 8)</a> or <a href="https://bell-sw.com/pages/downloads/#downloads">BellSoft Liberica Full JRE (Java 17)</a> and other distributions to download and install one (restart the launcher after installation).
game.crash.reason.need_jdk11=The game crashed due to an inappropriate Java VM version.\n\
game.crash.reason.need_jdk11=The game crashed because of an inappropriate Java VM version.\n\
\n\
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 due to mod file name problems.\n\
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.
game.crash.reason.incomplete_forge_installation=The game cannot continue to run due to an incomplete installation of Forge/NeoForge.\n\
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 due to file "%1$s" already exists.\n\
game.crash.reason.file_already_exists=The game crashed because the file "%1$s" already exists.\n\
\n\
You can try backing up and deleting that file, then relaunching the game.
game.crash.reason.file_changed=The game crashed due to file "%1$s" already exists.\n\
game.crash.reason.file_changed=The game crashed because the file "%1$s" already exists.\n\
\n\
You can try backing up and deleting that file, then relaunching the game.
game.crash.reason.gl_operation_failure=The game crashed due to some mods, shaders, or resource packs.\n\
game.crash.reason.gl_operation_failure=The game crashed because of some mods, shaders, or resource packs.\n\
\n\
Please disable the mods, shaders, or resource packs you are using and then try again.
game.crash.reason.graphics_driver=The game crashed due to a problem with your graphics driver.\n\
game.crash.reason.graphics_driver=The game crashed because of a problem with your graphics driver.\n\
\n\
Please try again after updating your graphics driver to the latest version.\n\
\n\
If your PC 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 PC.\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 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 due to a failure to initialize the OpenGL window on the Apple silicon platform.\n\
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.
game.crash.reason.illegal_access_error=The game crashed due to some mod(s).\n\
game.crash.reason.illegal_access_error=The game crashed because of some mod(s).\n\
\n\
If you know this: "%1$s", you can update or delete the mod(s) and then try again.
game.crash.reason.install_mixinbootstrap=The game crashed due to missing MixinBootstrap.\n\
game.crash.reason.install_mixinbootstrap=The game crashed because of the missing MixinBootstrap.\n\
\n\
You can try installing <a href="https://www.curseforge.com/minecraft/mc-mods/mixinbootstrap">MixinBootstrap</a> to resolve the problem. If it crashes after installation, try adding an exclamation mark (!) in front of the file name of this mod to try to resolve the problem.
game.crash.reason.optifine_is_not_compatible_with_forge=The game crashes due to OptiFine being incompatible with the currently installed Forge.\n\
game.crash.reason.optifine_is_not_compatible_with_forge=The game crashed because OptiFine is not compatible with the current Forge installation.\n\
\n\
Please navigate to<a href="https://optifine.net/downloads">the official website of OptiFine</a>, check whether the Forge version is compatible with OptiFine, and reinstall the instance in strict accordance with the corresponding version, or change the OptiFine version in "Edit Instance → Loaders".\n\
\n\
After testing, we believe that too high or too low OptiFine versions may cause crashes.
game.crash.reason.mod_files_are_decompressed=The game crashed due to the mod file having been extracted.\n\
game.crash.reason.mod_files_are_decompressed=The game crashed because the mod file was extracted.\n\
\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 due to both OptiFine and Shaders mods being installed.\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 Shaders mod due to OptiFine having built-in support for shaders.
game.crash.reason.rtss_forest_sodium=The game crashed due to RivaTuner Statistics Server (RTSS) being incompatible with Sodium.\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\
\n\
Click <a href="https://github.com/CaffeineMC/sodium-fabric/wiki/Known-Issues#rtss-incompatible">here</a> for more details.
game.crash.reason.too_many_mods_lead_to_exceeding_the_id_limit=The game crashed due to you having installed too many mods, which exceeds the ID limit of the game.\n\
game.crash.reason.too_many_mods_lead_to_exceeding_the_id_limit=The game crashed because you installed too many mods and exceeded the game ID limit.\n\
\n\
Please try installing<a href="https://www.curseforge.com/minecraft/mc-mods/jeid">JEID</a> or deleting some large mods.
game.crash.reason.night_config_fixes=The game crashed due to some problems with Night Config.\n\
game.crash.reason.night_config_fixes=The game crashed because of some problems with Night Config.\n\
\n\
You can try installing the <a href="https://www.curseforge.com/minecraft/mc-mods/night-config-fixes">Night Config Fixes</a> mod, which may help you with this problem.\n\
\n\
For more information, visit the <a href="https://github.com/Fuzss/nightconfigfixes">GitHub repository</a> of this mod.
game.crash.reason.optifine_causes_the_world_to_fail_to_load=The game may not continue to run due to OptiFine.\n\
game.crash.reason.optifine_causes_the_world_to_fail_to_load=The game may not continue to run because of the OptiFine.\n\
\n\
This problem only occurs in a specific OptiFine version. You can try changing the OptiFine version in "Edit Instance → Loaders".
game.crash.reason.jdk_9=The game crashed due to the Java version being too new for this instance.\n\
game.crash.reason.jdk_9=The game crashed because the Java version is too new for this instance.\n\
\n\
You need to download and install Java 8 and choose it in "Global/Instance-specific Settings → Java".
game.crash.reason.jvm_32bit=The game crashed due to the current memory allocation exceeding the limit of the 32-bit Java VM.\n\
game.crash.reason.jvm_32bit=The game crashed because the current memory allocation exceeded the 32-bit Java VM limit.\n\
\n\
If your OS is 64-bit, please install and use a 64-bit Java version. Otherwise, you may need to reinstall a 64-bit OS or get a moderner PC.\n\
If your OS is 64-bit, please install and use a 64-bit Java version. Otherwise, you may need to reinstall a 64-bit OS or get a moderner computer.\n\
\n\
Or, you can disable the "Automatically Allocate" option in "Global/Instance-specific Settings → Memory" and set the maximum memory allocation size to 1024 MB or below.
game.crash.reason.loading_crashed_forge=The game crashed due to mod "%1$s" (%2$s).\n\
game.crash.reason.loading_crashed_forge=The game crashed because of the mod "%1$s" (%2$s).\n\
\n\
You can try deleting or updating it.
game.crash.reason.loading_crashed_fabric=The game crashed due to mod "%1$s".\n\
game.crash.reason.loading_crashed_fabric=The game crashed because of the mod "%1$s".\n\
\n\
You can try deleting or updating it.
game.crash.reason.mac_jdk_8u261=The game crashed due to your current Forge or OptiFine version not being compatible with your Java installation.\n\
game.crash.reason.mac_jdk_8u261=The game crashed because your current Forge or OptiFine version is incompatible with your Java installation.\n\
\n\
Please try updating Forge and OptiFine, or try using Java 8u251 or earlier versions.
game.crash.reason.forge_repeat_installation=The game crashed due to a duplicate installation of Forge. <a href="https://github.com/HMCL-dev/HMCL/issues/1880">This is a known problem</a>\n\
Please try updating Forge and OptiFine, or try using Java 8u251 or previous versions.
game.crash.reason.forge_repeat_installation=The game crashed because of a duplicate Forge installation. <a href="https://github.com/HMCL-dev/HMCL/issues/1880">This is a known problem</a>\n\
\n\
It is recommended to upload the log feedback to GitHub so that we can find more clues and resolve this problem.\n\
\n\
Currently you can uninstall Forge and reinstall it in "Edit Instance → Loaders".
game.crash.reason.optifine_repeat_installation=The game crashed due to repeated installation of OptiFine.\n\
game.crash.reason.optifine_repeat_installation=The game crashed because of a duplicate OptiFine installation.\n\
\n\
Please delete OptiFine in the mod directory or uninstall it in "Edit Instance → Loaders".
game.crash.reason.memory_exceeded=The game crashed due to too much memory allocated for a small page file.\n\
game.crash.reason.memory_exceeded=The game crashed because too much memory was allocated for a small page file.\n\
\n\
You can try disabling the "Automatically Allocate" option in "Global/Instance-specific Settings → Memory" and adjust the value till the game launches.\n\
\n\
You can also try increasing the page file size in system settings.
game.crash.reason.mod=The game crashed due to the mod "%1$s".\n\
game.crash.reason.mod=The game crashed because of the mod "%1$s".\n\
\n\
You may update or delete the mod and then try again.
game.crash.reason.mod_resolution=The game cannot continue to run due to mod dependency problems.\n\
game.crash.reason.mod_resolution=The game cannot continue to run because of mod dependency problems.\n\
\n\
Fabric provided the following details:\n\
\n\
%1$s
game.crash.reason.forgemod_resolution=The game cannot continue to run due to mod dependency problems.\n\
game.crash.reason.forgemod_resolution=The game cannot continue to run because of mod dependency problems.\n\
\n\
Forge/NeoForge provided the following details:\n\
%1$s
game.crash.reason.forge_found_duplicate_mods=The game crashed due to a duplicate mods problem. Forge/NeoForge provides the following information:\n\
game.crash.reason.forge_found_duplicate_mods=The game crashed because of a duplicate mod problem. Forge/NeoForge provided the following information:\n\
%1$s
game.crash.reason.mod_resolution_collection=The game crashed because the mod version is not compatible.\n\
\n\
"%1$s" requires "%2$s".\n\
\n\
You need to upgrade or downgrade %3$s before continuing.
game.crash.reason.mod_resolution_conflict=The game crashed due to conflicting mods.\n\
game.crash.reason.mod_resolution_conflict=The game crashed because of conflicting mods.\n\
\n\
"%1$s" is incompatible with "%2$s".
game.crash.reason.mod_resolution_missing=The game crashed due to some dependency mods not being installed.\n\
game.crash.reason.mod_resolution_missing=The game crashed because some dependent mods were not installed.\n\
\n\
"%1$s" requires mod "%2$s".\n\
\n\
This means that you have to download and install "%2$s" first to continue playing.
game.crash.reason.mod_resolution_missing_minecraft=The game crashed due to a mod being incompatible with the current Minecraft version.\n\
game.crash.reason.mod_resolution_missing_minecraft=The game crashed because a mod is incompatible with the current Minecraft version.\n\
\n\
"%1$s" requires Minecraft version %2$s.\n\
\n\
@@ -604,31 +608,31 @@ game.crash.reason.mod_resolution_missing_minecraft=The game crashed due to a mod
Otherwise, you should install a version that is compatible with this Minecraft version.
game.crash.reason.mod_resolution_mod_version=%1$s (Version: %2$s)
game.crash.reason.mod_resolution_mod_version.any=%1$s (Any Version)
game.crash.reason.modlauncher_8=The game crashed due to your current Forge version not being compatible with your Java installation. Please try updating Forge.
game.crash.reason.no_class_def_found_error=The game cannot continue to run due to incomplete code.\n\
game.crash.reason.modlauncher_8=The game crashed because your current Forge version is not compatible with your Java installation. Please try updating Forge.
game.crash.reason.no_class_def_found_error=The game cannot continue to run because of incomplete code.\n\
\n\
Your game instance is missing %1$s. This might be due to a mod missing, an incompatible mod installed, or some files being corrupted.\n\
Your game instance is missing %1$s. This might be because a mod is missing, an incompatible mod is installed, or some files are corrupted.\n\
\n\
You may need to reinstall the game and all mods or ask someone for help.
game.crash.reason.no_such_method_error=The game cannot continue to run due to incomplete code.\n\
game.crash.reason.no_such_method_error=The game cannot continue to run because of incomplete code.\n\
\n\
Your game instance might be missing a mod, installed an incompatible mod, or some files might be corrupted.\n\
\n\
You may need to reinstall the game and all mods or ask someone for help.
game.crash.reason.opengl_not_supported=The game crashed due to OpenGL not being supported by your graphics driver.\n\
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 PC 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 PC.
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.
game.crash.reason.out_of_memory=The game crashed due to it running out of memory.\n\
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 PC.
game.crash.reason.resolution_too_high=The game crashed due to the resource pack resolution being too high.\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\
\n\
You should switch to a resource pack with lower resolution or consider buying a better graphics card with more VRAM.
game.crash.reason.stacktrace=The crash reason is unknown. You can view its details by clicking "Logs".\n\
@@ -636,15 +640,15 @@ game.crash.reason.stacktrace=The crash reason is unknown. You can view its detai
There are some keywords that might contain some mod names. You can search them online to figure out the problem yourself.\n\
\n\
%s
game.crash.reason.too_old_java=The game crashed due to you using an outdated Java VM version.\n\
game.crash.reason.too_old_java=The game crashed because you are using an outdated Java VM version.\n\
\n\
You need to switch to a newer version (%1$s) of Java in "Global/Instance-specific Settings → Java" and then relaunch the game. You can download Java from <a href="https://learn.microsoft.com/java/openjdk/download">here</a>.
You need to switch to a newer Java version (%1$s) in "Global/Instance-specific Settings → Java" and then relaunch the game. You can download Java from <a href="https://learn.microsoft.com/java/openjdk/download">here</a>.
game.crash.reason.unknown=We are not able to figure out why the game crashed. Please refer to the game logs.
game.crash.reason.unsatisfied_link_error=Failed to launch Minecraft due to missing libraries: %1$s.\n\
game.crash.reason.unsatisfied_link_error=Failed to launch Minecraft because of missing libraries: %1$s.\n\
\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
@@ -664,7 +668,7 @@ install=New Instance
install.change_version=Change Version
install.change_version.confirm=Are you sure you want to switch %s from version %s to %s?
install.failed=Failed to install
install.failed.downloading=We are unable to download some required files.
install.failed.downloading=Failed to download some required files.
install.failed.downloading.detail=Failed to download file: %s
install.failed.downloading.timeout=Download timeout when fetching: %s
install.failed.install_online=Failed to identify the provided file. If you are installing a mod, navigate to the "Mods" page.
@@ -677,7 +681,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,12 +700,13 @@ 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
install.new_game.malformed=Invalid name.
install.select=Choose operation
install.success=Installed successfully.
install.success=Successfully installed.
java.add=Add Java
java.add.failed=This Java is invalid or incompatible with the current platform.
@@ -711,6 +716,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 +741,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 earlier 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 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 earlier.
launch.advice.forge37_0_60=Forge versions earlier than 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.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 earlier than 1.8.0_51. Please install the latest Java 8 version.
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 versions of Java. It is recommended to use Java %s to launch Minecraft %s.
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.not_enough_space=You have allocated a memory size larger than the actual %d MB of memory installed on your PC. You may experience degraded performance or even be unable to launch the game.
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 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 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.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 +794,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
@@ -798,7 +806,7 @@ launcher.cache_directory.disabled=Disabled
launcher.cache_directory.invalid=Failed to create a cache directory, falling back to default.
launcher.contact=Contact Us
launcher.crash=Hello Minecraft! Launcher has encountered a fatal error! Please copy the following log and ask for help on our Discord, QQ group, GitHub, or other Minecraft forum.
launcher.crash.java_internal_error=Hello Minecraft! Launcher has encountered a fatal error due to your Java being broken. Please uninstall your Java and download a suitable Java <a href="https://bell-sw.com/pages/downloads/#downloads">here</a>.
launcher.crash.java_internal_error=Hello Minecraft! Launcher has encountered a fatal error because your Java is corrupted. Please uninstall your Java and download a suitable Java <a href="https://bell-sw.com/pages/downloads/#downloads">here</a>.
launcher.crash.hmcl_out_dated=Hello Minecraft! Launcher has encountered a fatal error! Your launcher is outdated. Please update your launcher!
launcher.update_java=Please update your Java version.
@@ -816,7 +824,7 @@ logwindow.export_dump.no_dependency=Your Java does not contain the dependencies
main_page=Home
message.cancelled=Operation canceled
message.cancelled=Operation Canceled
message.confirm=Confirm
message.copied=Copied to clipboard
message.default=Default
@@ -825,7 +833,7 @@ message.downloading=Downloading
message.error=Error
message.failed=Operation Failed
message.info=Information
message.success=Operation completed successfully
message.success=Operation successfully completed
message.unknown=Unknown
message.warning=Warning
@@ -842,6 +850,7 @@ modpack.completion=Downloading dependencies
modpack.desc=Describe your modpack, including an introduction and probably some changelog. Markdown and images from URL are currently supported.
modpack.description=Modpack Description
modpack.download=Download Modpacks
modpack.download.title=Download Modpack - %1s
modpack.enter_name=Enter a name for this modpack.
modpack.export=Export as Modpack
modpack.export.as=Export Modpack As...
@@ -863,7 +872,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,8 +892,8 @@ 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.malformed=Invalid modpack manifest. Please refer to the modpack maker to resolve this problem.
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
modpack.wizard=Modpack Export Guide
@@ -898,10 +907,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
@@ -975,9 +984,9 @@ modrinth.category.512x+=512x+
mods=Mods
mods.add=Add Mod
mods.add.failed=Failed to add mod %s.
mods.add.success=%s was added successfully.
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
@@ -985,23 +994,24 @@ mods.channel.release=Release
mods.check_updates=Check for Updates
mods.check_updates.current_version=Current Version
mods.check_updates.empty=All mods are up-to-date
mods.check_updates.failed=Failed to download some files.
mods.check_updates.failed_check=Failed to check for updates.
mods.check_updates.failed_download=Failed to download some files.
mods.check_updates.file=File
mods.check_updates.source=Source
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
@@ -1011,7 +1021,7 @@ mods.mcmod.page=MCMod Page
mods.mcmod.search=Search in MCMod
mods.modrinth=Modrinth
mods.name=Name
mods.not_modded=You must install a mod loader (Forge, NeoForge, Fabric, Quilt, or LiteLoader) first to manage your mods!
mods.not_modded=You must install a modloader (Forge, NeoForge, Fabric, Quilt, or LiteLoader) first to manage your mods!
mods.restore=Restore
mods.url=Official Page
mods.update_modpack_mod.warning=Updating mods in a modpack can lead to irreparable results, possibly corrupting the modpack so that it cannot launch. Are you sure you want to update?
@@ -1083,7 +1093,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.
@@ -1167,7 +1176,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.
@@ -1199,6 +1208,9 @@ settings.advanced.wrapper_launcher.prompt=Allows launching using an extra wrappe
settings.custom=Custom
settings.game=Settings
settings.game.copy_global=Copy from Global Settings
settings.game.copy_global.copy_all=Copy All
settings.game.copy_global.copy_all.confirm=Are you sure you want to overwrite the current instance settings? This action cannot be undone!
settings.game.current=Game
settings.game.dimension=Resolution
settings.game.exploration=Explore
@@ -1248,7 +1260,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
@@ -1265,7 +1277,6 @@ settings.memory.lower_bound=Minimum Memory
settings.memory.used_per_total=%1$.1f GB Used / %2$.1f GB Total
settings.physical_memory=Physical Memory Size
settings.show_log=Show Logs
settings.skin=Skins for offline accounts are now supported. You can navigate to your account list to change your skin and cape, but other players cannot see it in multiplayer.
settings.tabs.installers=Loaders
settings.take_effect_after_restart=Applies After Restart
settings.type=Settings Type of Instance
@@ -1289,12 +1300,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
@@ -1347,10 +1358,14 @@ 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
wiki.tooltip=Minecraft Wiki Page
wiki.version.game.release=https://minecraft.wiki/w/Java_Edition_%s
wiki.version.game.snapshot=https://minecraft.wiki/w/Java_Edition_%s
wizard.prev=< Prev
wizard.failed=Failed
wizard.finish=Finish
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=キャッシュ用のディレクトリ
@@ -665,7 +668,8 @@ mods.add.success=mods %s が正常に追加されました。
mods.category=Category
mods.check_updates=更新を確認
mods.check_updates.current_version=Current
mods.check_updates.failed=一部のファイルのダウンロードに失敗しました
mods.check_updates.failed_check=更新のチェックに失敗しました
mods.check_updates.failed_download=一部のファイルのダウンロードに失敗しました
mods.check_updates.file=ファイル
mods.check_updates.source=Source
mods.check_updates.target_version=Target
@@ -715,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=この名前はすでに存在します。別の名前を使用してください。
@@ -855,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=タイトルの透明性
@@ -871,7 +874,6 @@ settings.memory.lower_bound=最小メモリ
settings.memory.used_per_total=%1$.1f GB使用済み/%2$.1fGB合計
settings.physical_memory=物理メモリサイズ
settings.show_log=ログを表示
settings.skin=オフラインアカウントのスキンの変更がサポートされるようになりました!アカウントページに移動して、今すぐ肌を変えてください!
settings.tabs.installers=Installers
settings.take_effect_after_restart=再起動後に有効になります
settings.type=バージョン設定タイプ
@@ -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
@@ -50,33 +50,35 @@ about.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL)
account=Аккаунты
account.cape=Плащ
account.character=Персонаж
account.choose=Выберите персонажа
account.character=Игрок
account.choose=Выберите игрока
account.create=Добавить аккаунт
account.create.microsoft=Добавить аккаунт Microsoft
account.create.offline=Добавить автономный аккаунт
account.create.authlibInjector=Создайте аккаунт authlib-injector
account.create.offline=Добавить аккаунт в режиме офлайн
account.create.authlibInjector=Добавить аккаунт authlib-injector
account.email=Почта
account.failed=Не удалось проверить аккаунт
account.failed.character_deleted=Персонаж уже был удалён.
account.failed=Не удалось обновить аккаунт.
account.failed.character_deleted=Игрок уже был удален.
account.failed.connect_authentication_server=Не удаётся связаться с серверами авторизации, проверьте свой интернет.
account.failed.connect_injector_server=Не удаётся подключиться к серверу авторизации. Проверьте свой интернет и убедитесь, что вы ввели правильный URL-адрес.
account.failed.injector_download_failure=Не удаётся скачать authlib-injector. Проверьте свой интернет или попробуйте переключиться на другое зеркало скачивания.
account.failed.invalid_credentials=Неверный пароль или ограничена скорость, повторите попытку позже.
account.failed.invalid_password=Неверный пароль
account.failed.invalid_token=Попробуйте выйти из аккаунта и повторно войти введя пароль
account.failed.invalid_token=Попробуйте выйти из аккаунта и повторно войти.
account.failed.migration=Ваш аккаунт должен быть перенесён в Microsoft. Если вы уже сделали это, вам следует войти в свой перенесенный аккаунт Microsoft.
account.failed.no_character=Аккаунт не имеет персонажей.
account.failed.server_disconnected=Невозможно получить доступ к серверу авторизации, не удалось проверить информацию о аккаунте. \n\
Вы можете выбрать «Повторить проверку аккаунта», чтобы попытаться снова. \n\
Также можно выбрать «Пропустить проверку аккаунта», чтобы продолжить запуск игры, \n\
но информация об аккаунте может быть не актуальной. Если срок действия информации \nо аккаунте истечёт, будет невозможно войти на сервер, который требует подтверждения \nаккаунта.
account.failed.server_disconnected=Невозможно получить доступ к серверу авторизации, не удалось проверить информацию о аккаунте.\n\
Вы можете выбрать «Повторить проверку аккаунта», чтобы попытаться снова.\n\
Также можно выбрать «Пропустить проверку аккаунта», чтобы продолжить запуск игры,\n\
но информация об аккаунте может быть не актуальной. Если срок действия информации\n\
о аккаунте истечёт, будет невозможно войти на сервер, который требует подтверждения\n\
аккаунта.
account.failed.server_response_malformed=Неверный ответ сервера, видимо сервер авторизации не работает.
account.failed.ssl=При подключении к серверу произошла ошибка SSL. Пожалуйста, попробуйте обновить Java.
account.failed.wrong_account=Вы вошли в неверный аккаунт.
account.hmcl.hint=Необходимо нажать на «Войти» и завершить процесс в открывшейся вкладке вашего браузера.
# avoid too long sequence.
account.injector.add=Новый сервер авторизации
account.injector.empty=Ничего (Вы можете нажать на кнопку с плюсом справа, чтобы добавить)
account.injector.empty=Ничего (Вы можете нажать кнопку «плюс», чтобы добавить еще)
account.injector.http=Предупреждение\: Этот сервер использует небезопасный протокол HTTP, и любой игрок, находящийся между вашими соединениями, сможет увидеть ваши учётные данные в открытом виде.
account.injector.link.homepage=Домашняя страница
account.injector.link.register=Зарегистрироваться
@@ -85,53 +87,78 @@ account.injector.server_url=URL-адрес сервера
account.injector.server_name=Имя сервера
account.login=Войти
account.login.hint=Мы не храним ваш пароль.
account.login.refresh=Повторный вход
account.login.refresh.microsoft.hint=Поскольку авторизация учетной записи недействительна, вам необходимо повторно добавить свою учетную запись Microsoft
account.login.skip=Войти в режиме офлайн
account.login.retry=Повторить снова
account.login.refresh=Войти снова
account.login.refresh.microsoft.hint=Вам необходимо снова войти в аккаунт Microsoft, поскольку авторизация аккаунта недействительна.
account.logout=Выйти
account.register=Зарегестрироваться
account.manage=Список аккаунтов
account.copy_uuid=Копирование UUID
account.methods=Способ входа
account.methods.authlib_injector=authlib-injector
account.methods.microsoft=Аккаунт Microsoft
account.methods.microsoft.birth=Как изменить настройки дня рождения...
account.methods.microsoft.deauthorize=Отменить авторизацию аккаунта
account.methods.microsoft=Microsoft
account.methods.microsoft.birth=Как изменить настройки даты рождения...
account.methods.microsoft.close_page=Авторизация аккаунта Microsoft завершена. Осталось выполнить некоторые шаги, которые необходимо завершить позже. Вы можете закрыть эту страницу.
account.methods.microsoft.deauthorize=Отменить авторизацию аккаунта
account.methods.microsoft.error.add_family=Поскольку тебе ещё нет 18 лет, взрослый должен добавить тебя в семейный доступ, чтобы ты мог играть в Minecraft.
account.methods.microsoft.error.add_family_probably=Проверьте в настройках своего аккаунта, исполнилось ли вам уже 18 лет или нет. Если нет, необходимо добавить семейный аккаунт.
account.methods.microsoft.error.add_family_probably=Проверьте, что возраст, указанный в настройках вашего аккаунта, составляет не менее 18 лет. Если нет и вы считаете, что это ошибка, вы можете нажать «Как изменить настройки даты рождения...», чтобы узнать, как ее изменить.
account.methods.microsoft.error.country_unavailable=Xbox Live недоступен в вашей стране/регионе.
account.methods.microsoft.error.missing_xbox_account=Ваш аккаунт Microsoft не подключена к Xbox. Создайте его, прежде чем продолжить.
account.methods.microsoft.error.no_character=На аккаунте отсутствует профиль Minecraft Java.\nФайл игры не может быть создан, пожалуйста,\nнажмите на ссылку выше, чтобы создать его
account.methods.microsoft.error.no_character=Убедитесь, что вы приобрели Minecraft: Java Edition.\n\
Если вы уже приобрели игру, профиль может быть еще не создан.\n\
Нажмите «Создать профиль / Редактировать имя профиля», чтобы создать его.
account.methods.microsoft.error.unknown=Не удалось войти. Microsoft отвечает с кодом ошибки %d.
account.methods.microsoft.error.wrong_verify_method=Пожалуйста, войдите в систему, используя свой пароль на странице входа в аккаунт Microsoft, и не используйте проверочный код для авторизации.
account.methods.microsoft.logging_in=Авторизация...
account.methods.microsoft.makegameidsettings=Создать архив / изменение имени профиля
account.methods.microsoft.hint=Вам необходимо нажать кнопку «Войти» и продолжить процесс авторизации во вновь открывшемся окне браузера.\nЕсли токен, используемый для входа в аккаунт Microsoft случайно утёк, вы можете нажать «Отменить авторизацию аккаунта» ниже, чтобы отменить авторизацию.\nЕсли у вас возникнут какие-либо проблемы, вы можете нажать кнопку справки в правом верхнем углу для получения помощи.
account.methods.microsoft.manual=После нажатия кнопки «Войти», вы должны завершить авторизацию во вновь открывшемся окне браузера. Если окно браузера не открылось, вы можете щёлкнуть здесь, чтобы скопировать ссылку и вручную открыть её в браузере.\nЕсли токен, используемый для входа в аккаунт Microsoft случайно утёк, вы можете нажать «Отменить авторизацию аккаунта» ниже, чтобы отменить авторизацию.\n問題が発生した場合は、右上隅にあるヘルプ ボタンをクリックするとヘルプが表示されます。
account.methods.microsoft.hint=Вам необходимо нажать кнопку «Войти» и продолжить процесс авторизации во вновь открывшемся окне браузера.\n\
\n\
Если токен, используемый для входа в аккаунт Microsoft случайно утёк, вы можете нажать «Отменить авторизацию аккаунта» ниже, чтобы отменить авторизацию.
account.methods.microsoft.manual=Ваш код устройства - <b>%1$s</b>. Нажмите здесь, чтобы скопировать.\n\
\n\
После нажатия кнопки «Войти», вы должны завершить авторизацию во вновь открывшемся окне браузера. Если окно браузера не открылось, вы можете щёлкнуть здесь, чтобы скопировать ссылку и вручную открыть её в браузере.\n\
\n\
Если токен, используемый для входа в аккаунт Microsoft случайно утёк, вы можете нажать «Отменить авторизацию аккаунта» ниже, чтобы отменить авторизацию.
account.methods.microsoft.makegameidsettings=Создать / редактировать профиль
account.methods.microsoft.profile=Профиль аккаунта...
account.methods.microsoft.purchase=Купить Minecraft
account.methods.forgot_password=ЗАБЫЛ ПАРОЛЬ
account.methods.microsoft.snapshot=Вы используете неофициальное построение HMCL, загрузите официальное построение для входа в Microsoft.
account.methods.microsoft.snapshot=Вы используете неофициальную версию HMCL, загрузите официальную версию для входа в аккаунт Microsoft.
account.methods.microsoft.snapshot.website=Официальный сайт
account.methods.offline=Офлайн
account.methods.offline.name.special_characters=Рекомендуется использовать только английские буквы, цифры и символы подчёркивания.
account.methods.offline.name.invalid=Как правило, игровые имена пользователя ограничиваются английскими буквами, цифрами и символами подчёркивания и не могут превышать 16 символов.\n\
\n\
\ · Действительный: HuangYu, huang_Yu, Huang_Yu_123;\n\
\ · Недействительно: Huang Yu, Huang-Yu_%%%, Huang_Yu_hello_world_hello_world.\n\
\n\
Если вы считаете, что на стороне сервера есть соответствующий мод или плагин, снимающий это ограничение, вы можете проигнорировать это предупреждение.
account.methods.offline.uuid=UUID
account.methods.offline.uuid.hint=UUID - это уникальный идентификатор игрового персонажа в Minecraft. Способ генерации UUID различается в разных лаунчерах игр. Изменение UUID на тот, который генерируется другим лаунчером гарантирует, что игровые блоки/предметы в рюкзаке вашего офлайн аккаунта останутся. Этот параметр предназначен для экспертов. Если вы не знаете что делаете, мы не советуем вам изменять этот параметр.\nЭта опция не требуется для присоединения к серверам.
account.methods.offline.uuid.malformed=Malformed
account.methods.offline.uuid.hint=UUID - это уникальный идентификатор игрового персонажа в Minecraft. Способ генерации UUID различается в разных лаунчерах игр. Изменение UUID на тот, который генерируется другим лаунчером гарантирует, что игровые блоки/предметы в рюкзаке вашего офлайн аккаунта останутся. Этот параметр предназначен для экспертов. Если вы не знаете что делаете, мы не советуем вам изменять этот параметр. Эта опция не требуется для присоединения к серверам.
account.methods.offline.uuid.malformed=Недопустимый формат.
account.methods.forgot_password=Забыли пароль
account.missing=Нет аккаунтов
account.missing.add=Нажмите здесь, чтобы добавить.
account.move_to_global=Конвертировать в глобальный аккаунт\nИнформация об аккаунте будет сохранена в файле конфигурации в каталоге текущего пользователя системы.
account.move_to_portable=Конвертировать в портативный аккаунт\nИнформация об аккаунте будет сохранена в файле конфигурации в той же папке, что и HMCL.
account.not_logged_in=Вход не произведён
account.password=Пароль
account.portable=Портативный
account.skin=Скин
account.skin.file=Файл скина
account.skin.model=Модель
account.skin.model.default=Классическая
account.skin.model.slim=Стройная
account.skin.type.alex=Алекс
account.skin.type.csl_api=Blessing Skin
account.skin.type.csl_api.location=Адрес
account.skin.type.csl_api.location.hint=URL-адрес CustomSkinAPI
account.skin.type.little_skin=LittleSkin
account.skin.type.little_skin.hint=Необходимо создать персонажа с тем же именем, что и на автономном аккаунте на сайте поставщика скинов. Затем ваш скин будет вашим загруженным скином на этот сайт.
account.skin.type.little_skin.hint=Вам нужно создать игрока с тем же именем, что и у вашего офлайн-аукнта на сайте поставщика скинов. Теперь ваш скин будет установлен на скин, назначенный вашему игроку на сайте поставщика скинов.
account.skin.type.local_file=Локальный файл скина
account.skin.type.steve=Стив
account.skin.upload=Загрузить скин
account.skin.upload.failed=Не удаётся загрузить скин
account.skin.invalid_skin=Недопустимый файл скина
account.skin.upload.failed=Не удаётся загрузить скин.
account.skin.invalid_skin=Недопустимый файл скина.
account.username=Имя пользователя
archive.author=Автор(ы)
@@ -295,6 +322,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
@@ -471,7 +500,7 @@ install.installer.quilt=Quilt
install.installer.quilt-api=QSL/QFAPI
install.installer.version=%s
install.modpack=Установить модпак
install.new_game=Добавить новый экземпляр
install.new_game=Установить сборку
install.new_game.already_exists=Экземпляр уже существует. Используйте другое имя.
install.new_game.current_game_version=Текущая версия экземпляра
install.new_game.malformed=Недопустимое имя
@@ -557,7 +586,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=Папка с кэшем
@@ -596,17 +626,20 @@ message.success=Операция успешно завершена
message.unknown=Неизвестно
message.warning=Предупреждение
modpack=Модпак
modpack=Модпаки
modpack.choose=Выберите модпак
modpack.choose.local=Импорт из файла
modpack.choose.local.detail=Можно перетащить файл модпака сюда
modpack.choose.remote=Скачать
modpack.choose.remote.detail=Необходима прямая ссылка на скачивание модпака
modpack.choose.remote.tooltip=Введите URL-адрес модпака
modpack.choose.repository=Скачать модпак с CurseForge или Modrinth
modpack.choose.repository.detail=Не забудьте вернуться на эту страницу и перетащить файл модпака сюда после того, как модпак будет скачан.
modpack.completion=Скачивание зависимостей
modpack.desc=Опишите модпак, включая введение и журнал изменений. Поддерживаются Markdown и изображения по URL-адресу.
modpack.description=Описание
modpack.download=Скачать модпак
modpack.download.title=Скачать модпак - %1s
modpack.enter_name=Введите имя модпака.
modpack.export=Экспортировать модпак
modpack.export.as=Экспортировать модпак как...
@@ -615,20 +648,20 @@ modpack.files.blueprints=Чертежи BuildCraft
modpack.files.config=Конфигурации модов
modpack.files.dumps=Выходной файл отладки NEI
modpack.files.hmclversion_cfg=Файл конфигурации лаунчера
modpack.files.liteconfig=Файл конфигурации мода
modpack.files.liteconfig=Связанные файлы LiteLoader
modpack.files.mods=Моды
modpack.files.mods.voxelmods=Параметры VoxelMods
modpack.files.options_txt=Файл настроек Minecraft
modpack.files.optionsshaders_txt=Файл настроек шейдера
modpack.files.resourcepacks=Текстурпаки
modpack.files.saves=Сохранения игры
modpack.files.resourcepacks=Пакеты ресурсов/текстур
modpack.files.saves=миры
modpack.files.scripts=Файл конфигурации MineTweaker
modpack.files.servers_dat=Файл списка серверов
modpack.install=Установить модпак %s
modpack.installing=Установка модпака
modpack.introduction=Поддерживаются модпаки CurseForge, Modrinth, MultiMC и MCBBS.
modpack.invalid=Неверный модпак, попробуйте скачать его заново.
modpack.mismatched_type=Несоответствие типа модпака, текущий экземпляр имеет тип %s, но предоставленный экземпляр имеет тип %s.
modpack.mismatched_type=Несоответствие типа модпака, текущий сборка имеет тип %s, но предоставленный сборка имеет тип %s.
modpack.name=Имя модпака
modpack.not_a_valid_name=Неверное имя модпака
modpack.origin=Источник
@@ -642,94 +675,168 @@ modpack.type.curse=Curse
modpack.type.curse.error=Невозможно установить этот модпак. Повторите ещё раз.
modpack.type.curse.not_found=Некоторые из необходимых ресурсов отсутствуют и поэтому не могут быть загружены. Скачайте последнюю версию или другие модпаки.
modpack.type.manual.warning=Возможно, вам нужно напрямую распаковать файл этого модпака, а не импортировать его. И запустите игру с помощью прилагаемого лаунчера. Этот модпак создаётся вручную путем сжатия директории .minecraft, а не экспортируется лаунчером. HMCL может попытаться импортировать этот модпак, продолжать?
modpack.type.mcbbs=Стандарт MCBBS
modpack.type.mcbbs=MCBBS
modpack.type.mcbbs.export=Может быть импортирован Hello Minecraft! Launcher
modpack.type.modrinth=Modrinth
modpack.type.multimc=MultiMC
modpack.type.multimc.export=Может быть импортирован Hello Minecraft! Launcher и MultiMC
modpack.type.server=Сервер автообновления модпака
modpack.type.server.export=Разрешить менеджеру сервера удаленно обновлять клиент игры
modpack.type.server.malformed=Некорректный манифест модпака сервера
modpack.unsupported=Hello Minecraft! Launcher не поддерживает этот формат пакетов
modpack.update=Обновить игру
modpack.update=Обновление модпака
modpack.wizard=Мастер экспорта модпака
modpack.wizard.step.1=Основные параметры
modpack.wizard.step.1.title=Установка основных параметров для модпака.
modpack.wizard.step.1=Основные настройки
modpack.wizard.step.1.title=Некоторые основные сведения о модпаке.
modpack.wizard.step.2=Выбрать файлы
modpack.wizard.step.2.title=Выберите файлы, которые вы хотите добавить в модпак.
modpack.wizard.step.3=Тип модпака
modpack.wizard.step.3.title=Выберите тип модпака, который вы хотите экспортировать.
modpack.wizard.step.initialization.exported_version=Версия игры для экспорта
modpack.wizard.step.initialization.force_update=Принудительное обновление модпака до последней версии (вам понадобится служба хостинга файлов)
modpack.wizard.step.initialization.include_launcher=Включать лаунчер
modpack.wizard.step.initialization.save=Экспортировать в...
modpack.wizard.step.initialization.warning=Перед созданием модпака, убедитесь что игра запускается, и это Release, а не Snapshot версия. Лаунчер сохранит настройки скачивания.\n\
\n\
Имейте в виду, что не разрешается добавлять моды и текстурпаки, в которых явно сказано, что они не должны распространяться или помещаться в модпаки.
modpack.wizard.step.initialization.warning=Перед созданием модпака убедитесь, что игра запускается нормально и Minecraft является релизной версией, а не снапшотом. Пусковая установка сохранит ваши настройки скачивания.\n\
\n\
Помните, что вам не разрешается добавлять моды и пакет ресурсов, в которых явно указано, что они не подлежат распространению или помещению в модпак.
modpack.wizard.step.initialization.server=Нажмите здесь для получения дополнительных руководств по созданию модпака на сервере, который может автообновляться.
modrinth.category.adventure=Adventure
modrinth.category.adventure=Приключение
modrinth.category.audio=Аудио
modrinth.category.blocks=Блоки
modrinth.category.bukkit=Bukkit
modrinth.category.bungeecord=BungeeCord
modrinth.category.challenging=Challenging
modrinth.category.combat=Combat
modrinth.category.challenging=Вызов
modrinth.category.core-shaders=Ядро шейдеров
modrinth.category.combat=Бой
modrinth.category.cursed=Cursed
modrinth.category.decoration=Decoration
modrinth.category.economy=Economy
modrinth.category.equipment=Equipment
modrinth.category.decoration=Украшения
modrinth.category.economy=Экономика
modrinth.category.entities=Сущности
modrinth.category.environment=Окружающая среда
modrinth.category.equipment=Оборудование
modrinth.category.fabric=Fabric
modrinth.category.fonts=Шрифты
modrinth.category.food=Еда
modrinth.category.forge=Forge
modrinth.category.game-mechanics=Игровая механика
modrinth.category.gui=ГИП
modrinth.category.items=Предметы
modrinth.category.kitchen-sink=Kitchen-Sink
modrinth.category.library=Библиотека
modrinth.category.lightweight=Легкий
modrinth.category.liteloader=LiteLoader
modrinth.category.locale=Язык
modrinth.category.magic=Магия
modrinth.category.management=Управление
modrinth.category.minecraft=Minecraft
modrinth.category.minigame=Мини-игра
modrinth.category.misc=Разное
modrinth.category.mobs=Мобы
modrinth.category.modded=Модифицированный
modrinth.category.models=Модели
modrinth.category.modloader=Мод-загрузчик
modrinth.category.multiplayer=Сетевая игра
modrinth.category.neoforge=NeoForge
modrinth.category.optimization=Оптимизация
modrinth.category.paper=Paper
modrinth.category.purpur=Purpur
modrinth.category.quests=Квесты
modrinth.category.quilt=Quilt
modrinth.category.realistic=Реалистичный
modrinth.category.rift=Rift
modrinth.category.simplistic=Упрощенный
modrinth.category.social=Социальная
modrinth.category.spigot=Spigot
modrinth.category.sponge=Sponge
modrinth.category.storage=Хранилище
modrinth.category.technology=Технология
modrinth.category.themed=Тематический
modrinth.category.transportation=Транспорт
modrinth.category.tweaks=Усовершенствование
modrinth.category.utility=Утилиты
modrinth.category.vanilla-like=Ванильное
modrinth.category.velocity=Velocity
modrinth.category.waterfall=Waterfall
modrinth.category.worldgen=Генерация мира
modrinth.category.datapack=Datapack
modrinth.category.datapack=Набор данных
modrinth.category.folia=Folia
modrinth.category.8x-=8x-
modrinth.category.16x=16x
modrinth.category.32x=32x
modrinth.category.48x=48x
modrinth.category.64x=64x
modrinth.category.128x=128x
modrinth.category.256x=256x
modrinth.category.512x+=512x+
mods=Моды
mods.add=Добавить моды
mods.add.failed=Не удаётся установить мод %s.
mods.add.success=%s был успешно установлен.
mods.add=Добавить мод
mods.add.failed=Не удалось установить мод %s.
mods.add.success=%s был успешно добавлен.
mods.broken_dependency.title=Сломанная зависимость
mods.broken_dependency.desc=Эта зависимость существовала раньше, но теперь ее нет. Попробуйте использовать другой источник скачивания.
mods.category=Категория
mods.channel.alpha=Альфа
mods.channel.beta=Бета
mods.channel.release=Релиз
mods.check_updates=Проверить обновления
mods.check_updates.current_version=Текущая версия
mods.check_updates.failed=Не удаётся скачать некоторые файлы.
mods.check_updates.empty=Все моды новейшие
mods.check_updates.failed_check=Не удалось проверить обновления.
mods.check_updates.failed_download=Не удалось скачать некоторые файлы.
mods.check_updates.file=Файл
mods.check_updates.source=Источник
mods.check_updates.target_version=Целевая версия
mods.check_updates.update=Обновить
mods.choose_mod=Выберите мод
mods.curseforge=CurseForge
mods.dependency.embedded=Встроенные зависимости (Уже упакован в файл мода автором. Нет необходимости скачивать отдельно.)
mods.dependency.optional=Необязательные зависимости (Если он отсутствует, игра будет работать нормально, но функции мода могут отсутствовать.)
mods.dependency.required=Необходимые зависимости (Необходимо скачивать отдельно, отсутствие может помешать запуску игры.)
mods.dependency.tool=Необходимые зависимости (Необходимо скачивать отдельно, отсутствие может помешать запуску игры.)
mods.dependency.include=Встроенные зависимости (Уже упакован в файл мода автором. Нет необходимости скачивать отдельно.)
mods.dependency.incompatible=Несовместимые моды (Одновременная установка этих модов не позволит запустить игру.)
mods.dependency.broken=Сломанные зависимости (Этот мод существовал раньше, но теперь его нет. Попробуйте использовать другой источник скачивания.)
mods.disable=Отключить
mods.download=Скачать мод
mods.download.title=Скачать мод - %1s
mods.download.recommend=Рекомендуемая версия мода - Minecraft %1s
mods.enable=Включить
mods.manage=Управление модами
mods.manage=Моды
mods.mcbbs=MCBBS
mods.mcmod=MCMOD
mods.mcmod.page=Страница MCMOD
mods.mcmod.search=Искать на MCMOD
mods.mcmod=MCMod
mods.mcmod.page=Страница MCMod
mods.mcmod.search=Искать на MCMod
mods.modrinth=Modrinth
mods.name=Имя
mods.not_modded=Для управления модами необходимо сначала установить загрузчик (Fabric, Forge или LiteLoader).\!
mods.restore=Откат
mods.name=Название
mods.not_modded=Для управления модами необходимо сначала установить загрузчик (Forge, NeoForge, Fabric, Quilt или LiteLoader)!
mods.restore=Восстановить
mods.url=Официальная страница
mods.update_modpack_mod.warning=Обновление модов в пакете модов может привести к непоправимым результатам, возможно, повредить пакет модов так, что он не сможет запуститься. Вы уверены, что хотите обновить?
mods.install=Установить
mods.save_as=Сохранить как
datapack=Датапаки
nbt.entries=%s записи
nbt.open.failed=Не удалось открыть файл
nbt.save.failed=Не удалось сохранить файл
nbt.title=Смотреть файл - %s
datapack=Наборы данных
datapack.add=Установить
datapack.choose_datapack=Выберите датапак для импорта
datapack.extension=Датапак
datapack.title=Датапаки мира %s
datapack.choose_datapack=Выберите набор данных для импорта
datapack.extension=Набор данных
datapack.title=Мир [%s] - Наборы данных
web.failed=Не удалось загрузить страницу
web.open_in_browser=Хотите ли вы открыть этот адрес в браузере?\n\n%s
web.view_in_browser=Смотреть в браузере
world=Миры
world.add=Добавить мир
world.datapack=Управление датапаками
world.datapack.1_13=Только Minecraft 1.13 или новее поддерживает датапаки.
world.description=%s. Последний запуск игры %s. Версия игры\: %s.
world.datapack=Управлять наборами данных
world.datapack.1_13=Только Minecraft 1.13 или новее поддерживает наборы данных.
world.description=%s. | Последний запуск игры %s. | Версия игры\: %s.
world.download=Скачать мир
world.export=Экспорт мира
world.export.title=Выберите папку для экспорта мира
@@ -738,51 +845,51 @@ world.export.wizard=Экспорт мира %s
world.extension=Архив мира
world.game_version=Версия игры
world.import.already_exists=Мир уже существует.
world.import.choose=Выберите архив сохранения, который хотите импортировать
world.import.failed=Не удаётся импортировать этот мир\: %s
world.import.invalid=Не удаётся разобрать сохранение.
world.info.title=Сведения о мире %s
world.import.choose=Выберите архив мира, который хотите импортировать
world.import.failed=Не удалось импортировать этот мир\: %s
world.import.invalid=Не удалось разобрать мир.
world.info.title=Мир [%s] - Сведения
world.info.basic=Основные сведения
world.info.allow_cheats=Разрешить команды/читы
world.info.dimension.the_nether=Нижний мир
world.info.dimension.the_end=Край
world.info.difficulty=Сложность
world.info.difficulty.peaceful=Мирно
world.info.difficulty.easy=Легко
world.info.difficulty.normal=Обычно
world.info.difficulty.hard=Тяжело
world.info.difficulty.peaceful=Мирная
world.info.difficulty.easy=Лёгкая
world.info.difficulty.normal=Нормальная
world.info.difficulty.hard=Сложная
world.info.failed=Не удалось прочитать сведения о мире
world.info.game_version=Версия игры
world.info.last_played=Последний запуск игры
world.info.generate_features=Генерировать структуры
world.info.generate_features=Генерация строений
world.info.player=Сведения о игроке
world.info.player.food_level=Уровень голода
world.info.player.game_type=Режим игры
world.info.player.game_type.adventure=Приключение
world.info.player.game_type.creative=Творческий
world.info.player.game_type.spectator=Наблюдатель
world.info.player.game_type.creative=Творчество
world.info.player.game_type.spectator=Наблюдение
world.info.player.game_type.survival=Выживание
world.info.player.health=Здоровье
world.info.player.last_death_location=Место последней смерти
world.info.player.location=Расположение
world.info.player.spawn=Место появления
world.info.player.spawn=Точка возрождения
world.info.player.xp_level=Уровень опыта
world.info.random_seed=Сид
world.info.random_seed=Ключ генератора мира
world.info.time=Время игры
world.info.time.format=%s дн.
world.manage=Миры и датапаки
world.manage=Миры
world.name=Название мира
world.name.enter=Введите название мира
world.reveal=Показать в проводнике
world.reveal=Показать в диспетчере файлов
world.show_all=Показать все
world.time=EEE, MMM d, yyyy HH\:mm\:ss
profile=Папки с играми
profile.already_exists=Имя уже существует, используйте другое имя.
profile.default=Текущая папка
profile.home=По умолчанию
profile.instance_directory=Папка с экземплярами
profile.instance_directory.choose=Выберите папку для экземпляра
profile.manage=Список папок с экземплярами
profile.home=Лаунчер Minecraft
profile.instance_directory=Папка с игрой
profile.instance_directory.choose=Выберите папку с игрой
profile.manage=Список папок для сборки
profile.name=Имя
profile.new=Новая папка
profile.title=Папки с играми
@@ -792,68 +899,77 @@ profile.use_relative_path=По возможности используйте о
repositories.custom=Пользовательский репозиторий Maven (%s)
repositories.maven_central=Универсальный (Maven Central)
repositories.tencentcloud_mirror=Зеркало Mainland China (Репозиторий Tencent Cloud Maven)
repositories.chooser=Для работы лаунчера требуется JavaFX.\n\
\n\
Нажмите «ОК», чтобы скачать JavaFX из указанного репозитория, или нажмите «Отмена» чтобы выйти.\n\
\n\
Репозитории\:
repositories.chooser=Для работы HMCL требуется JavaFX.\n\
\n\
Нажмите «ОК», чтобы скачать JavaFX из указанного репозитория, или нажмите «Отмена» чтобы выйти.\n\
\n\
Репозитории:
repositories.chooser.title=Выберите зеркало для скачивания JavaFX
resourcepack=Текстурпаки
resourcepack=Пакеты ресурсов
search=Поиск
search.hint.chinese=Поиск на китайском и английском языках
search.hint.english=Поиск только на английском языке
search.enter=Вводите здесь
search.sort=Сортировать
search.sort=Сортировка
search.first_page=Первый
search.previous_page=Пред.
search.next_page=След.
search.last_page=Последний
search.page_n=%d / %s
selector.choose=Выбрать
selector.choose=Выберите
selector.choose_file=Выберите файл
selector.custom=Свой
settings=Настройки
settings.advanced=Расширенные настройки
settings.advanced.modify=Изменить расширенные настройки
settings.advanced.title=Расширенные настройки - %s
settings.advanced.custom_commands=Пользовательские команды
settings.advanced.custom_commands.hint=Пользовательские команды выполняются со следующими переменными окружения:\n\
\ - $INST_NAME: название версии\n\
\ - $INST_ID: название версии\n\
\ - $INST_DIR: абсолютный путь к версии\n\
\ - $INST_MC_DIR: абсолютный путь к minecraft\n\
\ - $INST_JAVA: двоичный файл java, используемый для запуска\n\
\ - $INST_FORGE: устанавливается, если установлен Forge\n\
\ - $INST_NEOFORGE: устанавливается, если установлен NeoForge\n\
\ - $INST_LITELOADER: устанавливается, если установлен LiteLoader\n\
\ - $INST_OPTIFINE: устанавливается, если установлен OptiFine\n\
\ - $INST_FABRIC: устанавливается, если установлена Fabric\n\
\ - $INST_QUILT: устанавливается, если установлена Quilt
settings.advanced.dont_check_game_completeness=Не сканировать игровые файлы
settings.advanced.dont_check_jvm_validity=Не проверять, может ли JVM запустить игру или нет
settings.advanced.game_dir.default=По умолчанию (.minecraft/)
settings.advanced.game_dir.independent=Раздельно (.minecraft/versions/<имя экземпляра>/, кроме assets и библиотек)
settings.advanced.java_permanent_generation_space=Постоянное пространство
\ · $INST_NAME: название сборки.\n\
\ · $INST_ID: название сборки.\n\
\ · $INST_DIR: абсолютный путь к рабочей папке сборки.\n\
\ · $INST_MC_DIR: абсолютный путь к текущей папке игры.\n\
\ · $INST_JAVA: двоичный файл java, используемый для запуска.\n\
\ · $INST_FORGE: устанавливается, если установлен Forge.\n\
\ · $INST_NEOFORGE: устанавливается, если установлен NeoForge.\n\
\ · $INST_LITELOADER: устанавливается, если установлен LiteLoader.\n\
\ · $INST_OPTIFINE: устанавливается, если установлен OptiFine.\n\
\ · $INST_FABRIC: устанавливается, если установлен Fabric.\n\
\ · $INST_QUILT: устанавливается, если установлен Quilt.
settings.advanced.dont_check_game_completeness=Не проверять целостность игры
settings.advanced.dont_check_jvm_validity=Не проверять совместимость Java VM
settings.advanced.dont_patch_natives=Не пытайтесь автоматически заменить нативные библиотеки
settings.advanced.environment_variables=Переменные среды
settings.advanced.game_dir.default=По умолчанию («.minecraft/»)
settings.advanced.game_dir.independent=Раздельно («.minecraft/versions/<имя сборки>/», кроме assets и библиотек)
settings.advanced.java_permanent_generation_space=Пространство PermGen
settings.advanced.java_permanent_generation_space.prompt=в МБ
settings.advanced.jvm=Параметры Java Virtual Machine
settings.advanced.jvm=Настройки Java VM
settings.advanced.jvm_args=Аргументы Java VM
settings.advanced.jvm_args.prompt=- Если параметр, введённый в «Параметры Java Virtual Machine», совпадает\nс параметром по умолчанию, он не будет добавлен.\n\
- Введите любые параметры GC в «Параметры Java Virtual Machine»,\nпараметр G1 по умолчанию будет отключен.\n\
- Нажмите «Не добавлять аргументы JVM по умолчанию» ниже,\nчтобы запустить игру без добавления аргументов по умолчанию.
settings.advanced.launcher_visibility.close=Закрывать после запуска игры.
settings.advanced.launcher_visibility.hide=Сворачивать после запуска игры.
settings.advanced.launcher_visibility.hide_and_reopen=Сворачивать и снова открывать, после закрытия игры.
settings.advanced.launcher_visibility.keep=Оставлять видимым.
settings.advanced.jvm_args.prompt=\ · Если параметр, введённый в «Аргументы Java VM», совпадает с параметром по умолчанию, он не будет добавлен.\n\
\ · Введите любые параметры GC в «Аргументы Java VM», параметр G1 по умолчанию будет отключен.\n\
\ · Нажмите «Не добавлять аргументы Java VM по умолчанию» ниже, чтобы запустить игру без добавления аргументов по умолчанию.
settings.advanced.launcher_visibility.close=Закрывать после запуска игры
settings.advanced.launcher_visibility.hide=Сворачивать после запуска игры
settings.advanced.launcher_visibility.hide_and_reopen=Сворачивать и снова открывать, после закрытия игры
settings.advanced.launcher_visibility.keep=Оставлять видимым
settings.advanced.launcher_visible=Видимость лаунчера
settings.advanced.minecraft_arguments=Аргументы запуска
settings.advanced.minecraft_arguments.prompt=По умолчанию
settings.advanced.natives_directory=Папка с нативными библиотеками
settings.advanced.natives_directory.choose=Выберите расоложение нужных нативных библиотек
settings.advanced.natives_directory.custom=Своя
settings.advanced.natives_directory.custom=Пользовательское
settings.advanced.natives_directory.default=По умолчанию
settings.advanced.natives_directory.hint=Параметр предназначен только для пользователей Apple M1 или других официально не поддерживаемых платформ. Не изменяйте, если не знаете что делаете.\n\
\n\
Прежде чем продолжить, убедитесь что все библиотеки (например, lwjgl.dll, libopenal.so) находятся в нужной папке.
settings.advanced.no_jvm_args=Не добавлять аргументы JVM по умолчанию
settings.advanced.natives_directory.hint=Параметр предназначен только для пользователей Apple silicon или других официально не поддерживаемых платформ. Не изменяйте, если не знаете что делаете.\n\
\n\
Прежде чем продолжить, убедитесь что все библиотеки (например, lwjgl.dll, libopenal.so) находятся в нужной папке.\n\
Примечание: Рекомендуется использовать полностью английские пути для указанных файлов локальных библиотек, иначе игра может не запуститься.
settings.advanced.no_jvm_args=Не добавлять аргументы Java VM по умолчанию
settings.advanced.precall_command=Команда перед запуском
settings.advanced.precall_command.prompt=Команды, которые необходимо выполнить перед запуском игры
settings.advanced.process_priority=Приоритет процесса
@@ -864,6 +980,11 @@ settings.advanced.process_priority.above_normal=Выше обычного
settings.advanced.process_priority.high=Высокий
settings.advanced.post_exit_command=Команда после выхода
settings.advanced.post_exit_command.prompt=Команды, которые необходимо выполнить после выхода из игры
settings.advanced.renderer=Рендерер
settings.advanced.renderer.default=OpenGL (По умолчанию)
settings.advanced.renderer.d3d12=DirectX 12 (Низкая производительность и совместимость)
settings.advanced.renderer.llvmpipe=ПО (Низкая производительность, лучшая совместимость)
settings.advanced.renderer.zink=Vulkan (Лучшая производительность, низкая совместимость)
settings.advanced.server_ip=Адрес сервера
settings.advanced.server_ip.prompt=Присоединяться к серверу при запуске игры
settings.advanced.use_native_glfw=[Только для Linux/FreeBSD] Использовать системный GLFW
@@ -873,27 +994,30 @@ settings.advanced.workaround.warning=Варианты обхода предна
settings.advanced.wrapper_launcher=Команда-оболочка
settings.advanced.wrapper_launcher.prompt=Позволяет запускать с помощью дополнительной программы-оболочки, такой как «optirun» в Linux.
settings.custom=Своё
settings.custom=Пользовательское
settings.game=Игровые настройки
settings.game=Настройки
settings.game.copy_global=Копировать из глобальные настройки
settings.game.copy_global.copy_all=Копировать все
settings.game.copy_global.copy_all.confirm=Вы уверены, что хотите переписать текущие настройки сборки? Это действие нельзя отменить!
settings.game.current=Игра
settings.game.dimension=Разрешение
settings.game.exploration=Расположение
settings.game.fullscreen=Полноэкранный режим
settings.game.java_directory=Путь Java
settings.game.java_directory=Java
settings.game.java_directory.auto=Автовыбор
settings.game.java_directory.auto.not_found=Совместимая версия Java не была установлена.
settings.game.java_directory.bit=%s-разрядная
settings.game.java_directory.choose=Выбрать папку с Java.
settings.game.java_directory.invalid=Неверныая папка Java.
settings.game.java_directory.choose=Выбрать папку с Java
settings.game.java_directory.invalid=Неверныая папка Java
settings.game.java_directory.version=Укажите версию Java
settings.game.java_directory.template=%s (%s)
settings.game.management=Инструменты
settings.game.working_directory=Расположение папки
settings.game.working_directory.choose=Выбор расположения
settings.game.working_directory.hint=Включите параметр «Раздельно» в разделе «Расположение папки», чтобы позволить текущему экземпляру хранить свои настройки, сохранения и моды в отдельной папке.\n\
\n\
Рекомендуется включить этот параметр, чтобы избежать конфликтов с модами, но нужно будет переместить свои сохранения вручную.
\n\
Рекомендуется включить этот параметр, чтобы избежать конфликтов с модами, но нужно будет переместить свои сохранения вручную.
settings.icon=Значок
@@ -912,7 +1036,7 @@ settings.launcher.font=Шрифт
settings.launcher.general=Общие
settings.launcher.language=Язык
settings.launcher.launcher_log.export=Экспорт логов лаунчера
settings.launcher.launcher_log.export.failed=Не удаётся экспортировать логи
settings.launcher.launcher_log.export.failed=Не удалось экспортировать логи
settings.launcher.launcher_log.export.success=Логи экспортированы в %s
settings.launcher.log=Запись логов
settings.launcher.log.font=Шрифт
@@ -940,16 +1064,15 @@ settings.memory.lower_bound=Мин. объём памяти
settings.memory.used_per_total=Использовано %1$.1f ГБ / Всего %2$.1f ГБ
settings.physical_memory=Размер физической памяти
settings.show_log=Показать логи
settings.skin=Теперь поддерживаются скины для автономных аккаунтов. Можно зайти в настройки аккаунта, чтобы изменить свой скин и плащ, но другие игроки не смогут увидеть их в мультиплеере.
settings.tabs.installers=Загрузчик модов/OptiFine
settings.tabs.installers=Погрузчики
settings.take_effect_after_restart=Применится после перезапуска
settings.type=Тип настроек экземпляра
settings.type.global=Общие настройки экземпляра (совместно используется экземплярами)
settings.type.global.manage=Общие настройки игры
settings.type.global.edit=Изменить глобальные настройки экземпляра
settings.type.special.enable=Включить раздельные настройки для экземпляров
settings.type.special.edit=Изменить настройки текущего экземпляра
settings.type.special.edit.hint=В текущем экземпляре [%s] включены раздельные настройки для экземпляров, параметры на этой странице НЕ повлияют на этот экземпляр. Нажмите здесь, чтобы изменить раздельные параметры.
settings.type.global=Глобальные настройки (совместно используется экземплярами)
settings.type.global.manage=Глобальные настройки
settings.type.global.edit=Изменить глобальные настройки
settings.type.special.enable=Включить раздельные настройки для сборки
settings.type.special.edit=Изменить текущие настройки сборки
settings.type.special.edit.hint=В текущей сборке «%s» включен параметр «Специфические для сборки настройки». Все настройки на этой странице НЕ влияют на эту сборку. Нажмите здесь, чтобы изменить его настройки.
sponsor=Спонсоры
sponsor.bmclapi=Загрузки для материкового Китая предоставляются BMCLAPI. Нажмите здесь для получения подробностей.
@@ -958,6 +1081,8 @@ sponsor.hmcl=Hello Minecraft! Launcher — лаунчер Minecraft с откр
system.architecture=Архитектура
system.operating_system=Операционная система
unofficial.hint=Вы используете неофициальную версию HMCL. Мы не можем гарантировать безопасность.
update=Обновление
update.accept=Обновить
update.changelog=Изменения
@@ -980,49 +1105,54 @@ update.bubble.title=Доступно обновление: %s
update.bubble.subtitle=Кликните здесь для обновления
update.note=Предупреждение: Бета-версии могут иметь больше функциональных возможностей и исправлений ошибок, но также и больше возможных ошибок.
update.latest=Это последняя версия.
update.no_browser=Не удаётся открыть браузер. Ссылка была скопирована в буфер обмена. Вставьте его в адресную строку браузера для обновления.
update.no_browser=Не удалось открыть браузер. Ссылка была скопирована в буфер обмена. Вставьте его в адресную строку браузера для обновления.
update.tooltip=Обновить
version=Игры
version.name=Название сборки
version.cannot_read=Невозможно найти версию игры. Невозможно продолжить автоматическую установку.
version.empty=Нет игр
version.empty.add=Установить новую версию
version.empty=Нет сборок
version.empty.add=Установить новую сборок
version.empty.launch=Нет версии для запуска, установите версию через страницу скачивания.
version.empty.hint=Нет установленной версии игры. Кликните здесь, чтобы скачать новую игру.
version.game.old=Старые
version.game.old=Исторические
version.game.release=Релиз
version.game.releases=Релизы
version.game.snapshot=Снапшот
version.game.snapshots=Снапшоты
version.launch=Играть
version.launch.test=Тестовая игра
version.switch=Сменить версию
version.switch=Сменить сборку
version.launch_script=Создать сценарий запуска
version.launch_script.failed=Невозможно выполнить сценарий запуска.
version.launch_script.failed=Невозможно создать сценарий запуска.
version.launch_script.save=Сохранить сценарий запуска
version.launch_script.success=Создан сценарий %s.
version.manage=Все версии
version.manage.clean=Очистить каталог игры
version.manage.clean.tooltip=Очистить логи, отчёты о сбоях
version.manage.duplicate=Скопировать экземпляр игры
version.manage.duplicate.duplicate_save=Скопировать сохранения
version.manage.duplicate.prompt=Введите название новой версии
version.manage=Все сборки
version.manage.clean=Очистить журнал игры
version.manage.clean.tooltip=Очистить файлы в папках «logs» и «crash-reports».
version.manage.duplicate=Скопировать экземпляр сборки
version.manage.duplicate.duplicate_save=Скопировать миры
version.manage.duplicate.prompt=Введите новое название для этой сборки
version.manage.duplicate.confirm=Новая версия-копия, будет заблокирована: принудительный независимый режим, независимые игровые настройки.
version.manage.manage=Управление версией
version.manage.manage.title=Управление версией - %1s
version.manage.manage=Изменить сборку
version.manage.manage.title=Изменить сборку - %1s
version.manage.redownload_assets_index=Обновить файлы игровых активов
version.manage.remove=Удалить
version.manage.remove.confirm=Удалить %s? Вы не сможете восстановить эту версию снова!
version.manage.remove.confirm.trash=Удалить %s? Вы можете восстановить эту версию с именем %s из корзины системы.
version.manage.remove.confirm.independent=Поскольку эта версия находится в независимом режиме, удаление этой версии также приведёт к удалению всех сохранённых миров, принадлежащих этой версии. Удалить %s?
version.manage.remove.confirm=Удалить %s? Вы не сможете восстановить эту сборку снова!
version.manage.remove.confirm.trash=Удалить %s? Вы можете восстановить эту сборку с именем %s из корзины системы.
version.manage.remove.confirm.independent=Поскольку эта сборка находится в режиме разделения, удаление этой сборки приведет к удалению всех миров, принадлежащих этой сборке. Удалить %s?
version.manage.remove_assets=Удалить файлы игровых активов
version.manage.remove_libraries=Удалить файлы библиотек
version.manage.rename=Переименовать
version.manage.rename.message=Введите новое название для этой версии
version.manage.rename.fail=Не удалось переименовать версию.
version.manage.rename.message=Введите новое название для этой сборки
version.manage.rename.fail=Не удалось переименовать сборку. Возможно, некоторые файлы уже используются или в их имени содержится недопустимый символ.
version.settings=Настройки
version.update=Обновить модпак
wiki.tooltip=Страница Minecraft Wiki
wiki.version.game.release=https://ru.minecraft.wiki/w/%s_(Java_Edition)
wiki.version.game.snapshot=https://ru.minecraft.wiki/w/%s_(Java_Edition)
wizard.prev=< Пред.
wizard.failed=Не удалось
wizard.finish=Завершено
@@ -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=使用者協議
@@ -148,12 +148,14 @@ account.skin.file=外觀圖片檔案
account.skin.model=模型
account.skin.model.default=寬型
account.skin.model.slim=纖細
account.skin.type.alex=Alex
account.skin.type.csl_api=Blessing Skin 伺服器
account.skin.type.csl_api.location=伺服器位址
account.skin.type.csl_api.location.hint=CustomSkinAPI 位址
account.skin.type.little_skin=LittleSkin 皮膚站
account.skin.type.little_skin.hint=你需要在皮膚站中加入並使用和該離線帳戶同名角色,此時離線帳戶外觀將為皮膚站上對應角色所設定的外觀。
account.skin.type.local_file=本機外觀圖片檔案
account.skin.type.steve=Steve
account.skin.upload=上傳/編輯外觀
account.skin.upload.failed=外觀上傳失敗
account.skin.invalid_skin=無法識別的外觀檔案
@@ -319,6 +321,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
@@ -328,7 +332,7 @@ download.existing=檔案已存在,無法儲存。你可以將檔案儲存至
download.external_link=打開下載網站
download.failed=下載失敗:%1$s\n錯誤碼:%2$d
download.failed.empty=沒有可供安裝的版本,按一下此處返回。
download.failed.no_code=下載失敗:%s
download.failed.no_code=下載失敗
download.failed.refresh=載入版本清單失敗,按一下此處重試。
download.game=新遊戲
download.provider.bmclapi=BMCLAPI (bangbang93, https://bmclapi2.bangbang93.com/)
@@ -510,6 +514,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 +530,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 +608,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 +632,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 群尋求幫助。
@@ -657,6 +664,7 @@ modpack.completion=下載模組包相關檔案
modpack.desc=描述你要製作的模組包,比如模組包注意事項和更新紀錄,支援 Markdown (圖片請上傳至網路)。
modpack.description=模組包描述
modpack.download=下載模組包
modpack.download.title=模組包下載 - %1s
modpack.enter_name=給遊戲取個你喜歡的名稱
modpack.export=匯出模組包
modpack.export.as=請選取模組包類型。若你無法決定,請選取 MCBBS 類型。
@@ -790,7 +798,8 @@ mods.channel.release=Release
mods.check_updates=檢查模組更新
mods.check_updates.current_version=目前版本
mods.check_updates.empty=沒有需要更新的模組
mods.check_updates.failed=部分檔案下載失敗
mods.check_updates.failed_check=檢查更新失敗
mods.check_updates.failed_download=部分檔案下載失敗
mods.check_updates.file=檔案
mods.check_updates.source=來源
mods.check_updates.target_version=目標版本
@@ -888,7 +897,6 @@ world.name=世界名稱
world.name.enter=輸入世界名稱
world.reveal=開啟目錄
world.show_all=全部顯示
world.time=yyyy 年 M 月 d 日, HH:mm:ss
profile=遊戲目錄
profile.already_exists=該名稱已存在
@@ -972,7 +980,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=中 (平衡)
@@ -997,6 +1005,9 @@ settings.advanced.wrapper_launcher.prompt=如填寫「optirun」後,啟動指
settings.custom=自訂
settings.game=遊戲設定
settings.game.copy_global=複製全域遊戲設定
settings.game.copy_global.copy_all=複製全部
settings.game.copy_global.copy_all.confirm=你確定要覆寫目前實例特定遊戲設定嗎?該操作無法復原!
settings.game.current=遊戲
settings.game.dimension=遊戲介面解析度大小
settings.game.exploration=瀏覽
@@ -1044,7 +1055,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=標題欄透明
@@ -1061,7 +1072,6 @@ settings.memory.lower_bound=最低分配
settings.memory.used_per_total=已使用 %1$.1f GB / 總記憶體 %2$.1f GB
settings.physical_memory=實體記憶體大小
settings.show_log=查看日誌
settings.skin=現已支援離線帳戶更換外觀,你可以到帳戶頁面更改離線帳戶的外觀和披風 (多人遊戲下其他玩家無法看到你的外觀)。
settings.tabs.installers=自動安裝
settings.take_effect_after_restart=重啟後生效
settings.type=實例遊戲設定類型
@@ -1147,6 +1157,10 @@ version.manage.rename.fail=重新命名版本失敗,可能檔案被佔用或
version.settings=遊戲設定
version.update=更新模組包
wiki.tooltip=Minecraft Wiki 頁面
wiki.version.game.release=https://zh.minecraft.wiki/w/Java版%s?variant=zh-tw
wiki.version.game.snapshot=https://zh.minecraft.wiki/w/%s?variant=zh-tw
wizard.prev=< 上一步
wizard.failed=失敗
wizard.finish=完成
@@ -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=用户协议
@@ -157,12 +157,14 @@ account.skin.file=皮肤图片文件
account.skin.model=模型
account.skin.model.default=宽型
account.skin.model.slim=纤细
account.skin.type.alex=Alex
account.skin.type.csl_api=Blessing Skin 服务器
account.skin.type.csl_api.location=服务器地址
account.skin.type.csl_api.location.hint=CustomSkinAPI 地址
account.skin.type.little_skin=LittleSkin 皮肤站
account.skin.type.little_skin.hint=你需要在皮肤站中创建并使用和该离线账户同名的角色,此时离线账户皮肤将显示为皮肤站上对应角色所设置的皮肤。\n你可以点击右上角帮助按钮进行求助。
account.skin.type.local_file=本地皮肤图片文件
account.skin.type.steve=Steve
account.skin.upload=上传/编辑皮肤
account.skin.upload.failed=皮肤上传失败
account.skin.invalid_skin=无法识别的皮肤文件
@@ -328,6 +330,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你可以点击右上角帮助按钮进行求助。
@@ -337,7 +341,7 @@ download.existing=文件已存在,无法保存。你可以将文件保存至
download.external_link=打开下载网站
download.failed=下载失败: %1$s,\n错误码:%2$d\n你可以点击右上角帮助按钮进行求助。
download.failed.empty=[没有可供安装的版本,点击此处返回]\n(你可以点击右上角帮助按钮进行求助)
download.failed.no_code=下载失败: %s
download.failed.no_code=下载失败
download.failed.refresh=[加载版本列表失败,点击此处重试]\n(你可以点击右上角帮助按钮进行求助)
download.game=新游戏
download.provider.bmclapi=BMCLAPI (bangbang93, https://bmclapi2.bangbang93.com)
@@ -434,7 +438,7 @@ game.crash.reason.jdk_9=当前游戏由于 Java 版本过高,无法继续运
game.crash.reason.jvm_32bit=当前游戏由于内存分配过大,超过了 32 位 Java 内存限制,无法继续运行。\n如果你的电脑是 64 位系统,请下载安装并更换 64 位 Java。<a href="https://bell-sw.com/pages/downloads/#downloads">下载 Java</a>\n如果你的电脑是 32 位系统,你或许可以重新安装 64 位系统,或换一台新电脑。\n或者,你可以在“(全局/版本特定) 游戏设置 → 游戏内存”中关闭“自动分配内存”,并且把内存限制调节为 1024 MB 或以下。
game.crash.reason.loading_crashed_forge=当前游戏由于模组“%1$s (%2$s)”错误,无法继续运行。\n你可以尝试删除或更新该模组以解决问题。
game.crash.reason.loading_crashed_fabric=当前游戏由于模组“%1$s”错误,无法继续运行。\n你可以尝试删除或更新该模组以解决问题。
game.crash.reason.memory_exceeded=当前游戏由于分配的内存过大,无法继续运行。\n该问题是由于系统页面文件太小导致的。\n你需要在全局(特定)游戏设置中关闭游戏内存的自动分配,并将游戏内存调低至游戏能正常启动为止。\n你还可以尝试将 虚拟内存 设置调整至「自动管理所有驱动器分页文件大小」,<a href="https://docs.hmcl.net/assets/img/hmcl/自动管理所有驱动器分页文件大小.webp">详情</a>。
game.crash.reason.memory_exceeded=当前游戏由于分配的内存过大,无法继续运行。\n该问题是由于系统页面文件太小导致的。\n你需要在全局(特定)游戏设置中关闭游戏内存的自动分配,并将游戏内存调低至游戏能正常启动为止。\n你还可以尝试将虚拟内存设置调整为“自动管理所有驱动器分页文件大小”,<a href="https://docs.hmcl.net/assets/img/hmcl/自动管理所有驱动器分页文件大小.webp">详情</a>。
game.crash.reason.mac_jdk_8u261=当前游戏由于你所使用的 Forge/OptiFine 与 Java 冲突而崩溃。\n请尝试更新 Forge/OptiFine,或使用 Java 8u251 及更早版本启动。
game.crash.reason.mod=当前游戏由于“%1$s”的问题,无法继续运行。\n你可以更新或删除已经安装的“%1$s”再试。
game.crash.reason.mod_resolution=当前游戏由于模组依赖问题,无法继续运行。Fabric 提供了如下信息:\n%1$s
@@ -521,6 +525,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 +541,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 版本:
@@ -561,7 +567,7 @@ java.uninstall.confirm=你确定要卸载此 Java 吗?此操作无法撤销!
lang=简体中文
lang.default=跟随系统语言
launch.advice==%s 是否继续启动?
launch.advice=%s 是否继续启动?
launch.advice.multi=检测到以下问题:\n\n%s\n\n这些问题可能导致游戏无法正常启动或影响游戏体验,是否继续启动?\n你可以点击右上角帮助按钮进行求助。
launch.advice.java.auto=当前选择的 Java 虚拟机版本不满足游戏要求。\n点击“是”即可由 HMCL 来自动选取合适的 Java 虚拟机版本。\n或者你可以在“(全局/版本特定) 游戏设置 → 游戏 Java”中选择一个合适的 Java 虚拟机版本。
launch.advice.java.modded_java_7=Minecraft 1.7.2 及更低版本需要 Java 7 及更低版本。
@@ -613,7 +619,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=文件下载缓存文件夹
@@ -668,6 +675,7 @@ modpack.completion=下载整合包相关文件
modpack.desc=描述你要制作的整合包,比如整合包注意事项和更新记录,支持 Markdown (图片请用网络链接)
modpack.description=整合包描述
modpack.download=下载整合包
modpack.download.title=整合包下载 - %1s
modpack.enter_name=给游戏起个你喜欢的名字
modpack.export=导出整合包
modpack.export.as=请选择整合包类型 (若无法决定,请选择“我的世界中文论坛整合包标准”)
@@ -801,7 +809,8 @@ mods.channel.release=稳定版本
mods.check_updates=检查模组更新
mods.check_updates.current_version=当前版本
mods.check_updates.empty=没有需要更新的模组
mods.check_updates.failed=部分文件下载失败
mods.check_updates.failed_check=检查更新失败
mods.check_updates.failed_download=部分文件下载失败
mods.check_updates.file=文件
mods.check_updates.source=来源
mods.check_updates.target_version=目标版本
@@ -899,7 +908,6 @@ world.name=世界名称
world.name.enter=输入世界名称
world.reveal=打开文件夹
world.show_all=显示全部
world.time=yyyy 年 MM 月 dd 日, HH:mm:ss
profile=游戏文件夹
profile.already_exists=该名称已存在
@@ -1008,6 +1016,9 @@ settings.advanced.wrapper_launcher.prompt=如填写“optirun”后,启动命
settings.custom=自定义
settings.game=游戏设置
settings.game.copy_global=复制全局游戏设置
settings.game.copy_global.copy_all=复制全部
settings.game.copy_global.copy_all.confirm=你确定要覆盖当前版本特定游戏设置吗?此操作无法撤销!
settings.game.current=游戏
settings.game.dimension=游戏窗口分辨率
settings.game.exploration=浏览
@@ -1055,7 +1066,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=标题栏透明
@@ -1072,7 +1083,6 @@ settings.memory.lower_bound=最低内存分配
settings.memory.used_per_total=设备中已使用 %1$.1f GB / 设备总内存 %2$.1f GB
settings.physical_memory=物理内存大小
settings.show_log=查看日志
settings.skin=现已支持离线账户更换皮肤,你可以到账户页面更改离线账户的皮肤和披风 (多人游戏下其他玩家无法看到你的皮肤)。
settings.tabs.installers=自动安装
settings.take_effect_after_restart=重启后生效
settings.type=版本游戏设置类型
@@ -1158,6 +1168,10 @@ version.manage.rename.fail=重命名版本失败,可能文件被占用或者
version.settings=游戏设置
version.update=更新整合包
wiki.tooltip=Minecraft Wiki 页面
wiki.version.game.release=https://zh.minecraft.wiki/w/Java版%s?variant=zh-cn
wiki.version.game.snapshot=https://zh.minecraft.wiki/w/%s?variant=zh-cn
wizard.prev=< 上一步
wizard.failed=失败
wizard.finish=完成
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,
@@ -82,25 +82,25 @@
"module": "javafx.base",
"groupId": "org.openjfx",
"artifactId": "javafx-base",
"version": "17.0.13",
"version": "19.0.2.1",
"classifier": "mac",
"sha1": "e1744403a6691aaa6b65a7e9b4ff9aad98831bb1"
"sha1": "73e422d8426aaa23e8c712b9f4d9bebf70d3bfb9"
},
{
"module": "javafx.graphics",
"groupId": "org.openjfx",
"artifactId": "javafx-graphics",
"version": "17.0.13",
"version": "19.0.2.1",
"classifier": "mac",
"sha1": "dbe733470094abee56560067d0fea3ad24991d2b"
"sha1": "6ab6f3e23421fcfa04e692d9d26a8f55a830c114"
},
{
"module": "javafx.controls",
"groupId": "org.openjfx",
"artifactId": "javafx-controls",
"version": "17.0.13",
"version": "19.0.2.1",
"classifier": "mac",
"sha1": "0d43a776d6c9e2622e3581ed04501e2cadcd6e65"
"sha1": "b7786b1b63e741c0e234829825fae5fef9d96c31"
}
],
"osx-arm64": [
@@ -108,25 +108,25 @@
"module": "javafx.base",
"groupId": "org.openjfx",
"artifactId": "javafx-base",
"version": "17.0.13",
"version": "19.0.2.1",
"classifier": "mac-aarch64",
"sha1": "d4dc7d2864c07b56a27e39fa5691856ddccf0ba4"
"sha1": "1d0d887c492330ed527b0614d115a4f32d2d05a4"
},
{
"module": "javafx.graphics",
"groupId": "org.openjfx",
"artifactId": "javafx-graphics",
"version": "17.0.13",
"version": "19.0.2.1",
"classifier": "mac-aarch64",
"sha1": "d6a49f19e90db56c134757d1bbab4935de075d45"
"sha1": "64db28799e61e0f8f51e471c732599b2a6961803"
},
{
"module": "javafx.controls",
"groupId": "org.openjfx",
"artifactId": "javafx-controls",
"version": "17.0.13",
"version": "19.0.2.1",
"classifier": "mac-aarch64",
"sha1": "ad564497edc49f7a6135c3b52d360962e19a2539"
"sha1": "3a14bd5f3ebe45d344c1f2bade0fe074e6ea2a83"
}
],
"linux-x86_64": [
+2 -2
View File
@@ -12,8 +12,8 @@ dependencies {
api("com.github.steveice10:opennbt:1.5")
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")
api("org.jsoup:jsoup:1.18.3")
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;
}
@@ -84,7 +84,7 @@ public final class MicrosoftAccount extends OAuthAccount {
@Override
public AuthInfo logIn() throws AuthenticationException {
if (!authenticated) {
if (!authenticated || System.currentTimeMillis() > session.getNotAfter()) {
if (service.validate(session.getNotAfter(), session.getTokenType(), session.getAccessToken())) {
authenticated = true;
} else {
@@ -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"),
@@ -170,6 +170,7 @@ public final class LibraryAnalyzer implements Iterable<LibraryAnalyzer.LibraryMa
Version resolvedVersion = version.resolve(provider);
String mainClass = resolvedVersion.getMainClass();
return mainClass != null && (LAUNCH_WRAPPER_MAIN.equals(mainClass)
|| mainClass.startsWith("net.minecraftforge")
|| mainClass.startsWith("net.fabricmc")
|| mainClass.startsWith("org.quiltmc")
|| mainClass.startsWith("cpw.mods"));
@@ -215,11 +216,6 @@ public final class LibraryAnalyzer implements Iterable<LibraryAnalyzer.LibraryMa
@Override
protected String patchVersion(Version gameVersion, String libraryVersion) {
Matcher matcher = NEO_FORGE_VERSION_MATCHER.matcher(libraryVersion);
if (matcher.find()) {
return matcher.group("forge");
}
String res = scanVersion(gameVersion);
if (res != null) {
return res;
@@ -232,6 +228,11 @@ public final class LibraryAnalyzer implements Iterable<LibraryAnalyzer.LibraryMa
}
}
Matcher matcher = NEO_FORGE_VERSION_MATCHER.matcher(libraryVersion);
if (matcher.find()) {
return matcher.group("forge");
}
return super.patchVersion(gameVersion, libraryVersion);
}
@@ -247,12 +248,15 @@ public final class LibraryAnalyzer implements Iterable<LibraryAnalyzer.LibraryMa
for (int i = 0; i < gameArguments.size() - 1; i++) {
Argument argument = gameArguments.get(i);
if (argument instanceof StringArgument && "--fml.neoForgeVersion".equals(((StringArgument) argument).getArgument())) {
Argument next = gameArguments.get(i + 1);
if (next instanceof StringArgument) {
return ((StringArgument) next).getArgument();
if (argument instanceof StringArgument) {
String argumentValue = ((StringArgument) argument).getArgument();
if ("--fml.neoForgeVersion".equals(argumentValue) || "--fml.forgeVersion".equals(argumentValue)) {
Argument next = gameArguments.get(i + 1);
if (next instanceof StringArgument) {
return ((StringArgument) next).getArgument();
}
return null; // Normally, there should not be two --fml.neoForgeVersion argument.
}
return null; // Normally, there should not be two --fml.neoForgeVersion argument.
}
}
return null;
@@ -33,7 +33,7 @@ public final class GameJavaVersion {
public static final GameJavaVersion LATEST = JAVA_21;
public static GameJavaVersion getMinimumJavaVersion(GameVersionNumber gameVersion) {
if (gameVersion.compareTo("1.21") >= 0)
if (gameVersion.compareTo("1.20.5") >= 0)
return JAVA_21;
if (gameVersion.compareTo("1.18") >= 0)
return JAVA_17;
@@ -59,21 +59,6 @@ public final class GameJavaVersion {
}
}
public static boolean isSupportedPlatform(Platform platform) {
OperatingSystem os = platform.getOperatingSystem();
Architecture arch = platform.getArchitecture();
switch (arch) {
case X86:
return os == OperatingSystem.WINDOWS || os == OperatingSystem.LINUX;
case X86_64:
return os == OperatingSystem.WINDOWS || os == OperatingSystem.LINUX || os == OperatingSystem.OSX;
case ARM64:
return os == OperatingSystem.WINDOWS || os == OperatingSystem.OSX;
default:
return false;
}
}
public static List<GameJavaVersion> getSupportedVersions(Platform platform) {
OperatingSystem operatingSystem = platform.getOperatingSystem();
Architecture architecture = platform.getArchitecture();
@@ -62,7 +62,14 @@ public enum JavaVersionConstraint {
return analyzer != null && analyzer.has(LibraryAnalyzer.LibraryType.FORGE);
}
},
MODDED_JAVA_17(false, GameVersionNumber.atLeast("1.18"), VersionNumber.between("17", "17.999")) {
MODDED_JAVA_17(false, GameVersionNumber.between("1.18", "1.20.4"), VersionNumber.between("17", "17.999")) {
@Override
protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,
@Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) {
return analyzer != null && analyzer.has(LibraryAnalyzer.LibraryType.FORGE);
}
},
MODDED_JAVA_21(false, GameVersionNumber.atLeast("1.20.5"), VersionNumber.between("21", "21.999")) {
@Override
protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,
@Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) {
@@ -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() {
@@ -50,6 +50,8 @@ import static org.jackhuang.hmcl.util.Pair.pair;
*/
public class DefaultLauncher extends Launcher {
private final LibraryAnalyzer analyzer;
public DefaultLauncher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options) {
this(repository, version, authInfo, options, null);
}
@@ -60,6 +62,8 @@ public class DefaultLauncher extends Launcher {
public DefaultLauncher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options, ProcessListener listener, boolean daemon) {
super(repository, version, authInfo, options, listener, daemon);
this.analyzer = LibraryAnalyzer.analyze(version, repository.getGameVersion(version).orElse(null));
}
private Command generateCommandLine(File nativeFolder) throws IOException {
@@ -496,12 +500,18 @@ 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;
}
}
}
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version, repository.getGameVersion(version).orElse(null));
if (analyzer.has(LibraryAnalyzer.LibraryType.FORGE)) {
env.put("INST_FORGE", "1");
}
@@ -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);
}
}
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.mod.modrinth;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.game.DefaultGameRepository;
import org.jackhuang.hmcl.mod.ModManager;
import org.jackhuang.hmcl.mod.ModpackCompletionException;
import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.task.Task;
@@ -27,6 +28,7 @@ import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
@@ -41,6 +43,7 @@ public class ModrinthCompletionTask extends Task<Void> {
private final DefaultDependencyManager dependency;
private final DefaultGameRepository repository;
private final ModManager modManager;
private final String version;
private ModrinthManifest manifest;
private final List<Task<?>> dependencies = new ArrayList<>();
@@ -69,6 +72,7 @@ public class ModrinthCompletionTask extends Task<Void> {
public ModrinthCompletionTask(DefaultDependencyManager dependencyManager, String version, ModrinthManifest manifest) {
this.dependency = dependencyManager;
this.repository = dependencyManager.getGameRepository();
this.modManager = repository.getModManager(version);
this.version = version;
this.manifest = manifest;
@@ -99,18 +103,28 @@ public class ModrinthCompletionTask extends Task<Void> {
if (manifest == null)
return;
Path runDirectory = repository.getRunDirectory(version).toPath();
Path runDirectory = repository.getRunDirectory(version).toPath().toAbsolutePath().normalize();
Path modsDirectory = runDirectory.resolve("mods");
for (ModrinthManifest.File file : manifest.getFiles()) {
if (file.getEnv() != null && file.getEnv().getOrDefault("client", "required").equals("unsupported"))
continue;
Path filePath = runDirectory.resolve(file.getPath());
if (!Files.exists(filePath) && !file.getDownloads().isEmpty()) {
FileDownloadTask task = new FileDownloadTask(file.getDownloads().get(0), filePath.toFile());
task.setCacheRepository(dependency.getCacheRepository());
task.setCaching(true);
dependencies.add(task.withCounter("hmcl.modpack.download"));
}
if (file.getDownloads().isEmpty())
continue;
Path filePath = runDirectory.resolve(file.getPath()).toAbsolutePath().normalize();
if (!filePath.startsWith(runDirectory))
throw new IOException("Unsecure path: " + file.getPath());
if (Files.exists(filePath))
continue;
if (modsDirectory.equals(filePath.getParent()) && this.modManager.hasSimpleMod(FileUtils.getName(filePath)))
continue;
FileDownloadTask task = new FileDownloadTask(file.getDownloads(), filePath.toFile());
task.setCacheRepository(dependency.getCacheRepository());
task.setCaching(true);
dependencies.add(task.withCounter("hmcl.modpack.download"));
}
if (!dependencies.isEmpty()) {
Loaded 100 of 119 files, more files were not shown because too many files have changed in this diff. Show more