Compare commits
281
Commits
v3.4.210
...
v3.5.3.222
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19b6633f4a | ||
|
|
2ad3e7a291 | ||
|
|
1fa63a528d | ||
|
|
b80c787b60 | ||
|
|
c8652012c5 | ||
|
|
e70b43be26 | ||
|
|
2d182807b2 | ||
|
|
675af3a5aa | ||
|
|
4ba4c3e749 | ||
|
|
11e2277c9e | ||
|
|
66642fe621 | ||
|
|
3189a22f26 | ||
|
|
366e359d83 | ||
|
|
f7d8a473aa | ||
|
|
8276bf5d8c | ||
|
|
371150dfa8 | ||
|
|
38d71fd6bb | ||
|
|
c627ca96c9 | ||
|
|
09167bb302 | ||
|
|
a3c24b1b02 | ||
|
|
9f4a3cf8a8 | ||
|
|
702d27393e | ||
|
|
698d3a311d | ||
|
|
c92f8e3b51 | ||
|
|
7c502ecd01 | ||
|
|
a237a31d23 | ||
|
|
5f4fc77258 | ||
|
|
8bd4df68f5 | ||
|
|
29cff02f3c | ||
|
|
d1bc41e1f1 | ||
|
|
2fd1bf4c29 | ||
|
|
edd89fb040 | ||
|
|
08d6b58c8c | ||
|
|
e3fc9f5116 | ||
|
|
6dcd808cc8 | ||
|
|
df6230fc01 | ||
|
|
9f502ae6ae | ||
|
|
2a979d2b15 | ||
|
|
ae484e3b67 | ||
|
|
7206832edb | ||
|
|
288b32ed79 | ||
|
|
53bc3b354f | ||
|
|
30565c2b50 | ||
|
|
c400c7be9d | ||
|
|
7acd9d2491 | ||
|
|
dc26dba1d0 | ||
|
|
66cbb23a38 | ||
|
|
663fc176d8 | ||
|
|
b9ec22677e | ||
|
|
7d55c7f37f | ||
|
|
f12882a389 | ||
|
|
8cfde9d5c6 | ||
|
|
10180a74b6 | ||
|
|
abd30c397d | ||
|
|
a230027974 | ||
|
|
59f6837914 | ||
|
|
7546af9067 | ||
|
|
1aa3860706 | ||
|
|
fd098a65c5 | ||
|
|
77f7a56df9 | ||
|
|
4fe879085f | ||
|
|
96914ec4f6 | ||
|
|
a1c6b6439c | ||
|
|
b91dba3141 | ||
|
|
718c0a668a | ||
|
|
c62963f455 | ||
|
|
cc39a37beb | ||
|
|
1bc8450110 | ||
|
|
bec0ca6dea | ||
|
|
7c7296a57d | ||
|
|
9684ce4a1b | ||
|
|
c0df55ee62 | ||
|
|
b598e2613e | ||
|
|
99e578b1bc | ||
|
|
3766491c28 | ||
|
|
71f13b24cb | ||
|
|
4a3d20576e | ||
|
|
ecbc243c6b | ||
|
|
c012ca1183 | ||
|
|
820acd90e7 | ||
|
|
e904a5b42d | ||
|
|
cb051c033f | ||
|
|
565fc1f7b6 | ||
|
|
f6b8fae903 | ||
|
|
5819af5e7c | ||
|
|
f4057fb8f8 | ||
|
|
783a36be84 | ||
|
|
091d6e00b1 | ||
|
|
d721d00139 | ||
|
|
f7797d4b6f | ||
|
|
f810373227 | ||
|
|
c808225f78 | ||
|
|
0b06bf82d8 | ||
|
|
6969107c68 | ||
|
|
6347de62f0 | ||
|
|
1f09c122de | ||
|
|
f3de209f99 | ||
|
|
d29c833866 | ||
|
|
edac522d06 | ||
|
|
3b1a890423 | ||
|
|
4d4864819a | ||
|
|
151e7e04eb | ||
|
|
03c634658b | ||
|
|
10c54509aa | ||
|
|
8c74a559c3 | ||
|
|
d3ae8045c0 | ||
|
|
b9753beeac | ||
|
|
6a602adc4d | ||
|
|
c6835ac4f9 | ||
|
|
7af4332d04 | ||
|
|
af5b5e0f67 | ||
|
|
e6368f5355 | ||
|
|
1fe9826871 | ||
|
|
01ea047521 | ||
|
|
f24a73412a | ||
|
|
4def209c62 | ||
|
|
a4a7a1e507 | ||
|
|
560725431d | ||
|
|
ffde4987e9 | ||
|
|
3334371ae3 | ||
|
|
8b1808fa2e | ||
|
|
7d86d31844 | ||
|
|
0cbfa62731 | ||
|
|
db735b58ec | ||
|
|
17fe11e8f5 | ||
|
|
fc65d8a535 | ||
|
|
6ade056bf8 | ||
|
|
a5ebf08a8c | ||
|
|
ca2eccc762 | ||
|
|
efd76a1407 | ||
|
|
48785e93f0 | ||
|
|
20ddf60c48 | ||
|
|
1b9b0226f2 | ||
|
|
a1f8ff7463 | ||
|
|
aafe7f44e7 | ||
|
|
8909224a13 | ||
|
|
f5d01bef93 | ||
|
|
f7de97c844 | ||
|
|
2136fddec5 | ||
|
|
312152de78 | ||
|
|
4fe7d10872 | ||
|
|
9ec10dc5dc | ||
|
|
2672459287 | ||
|
|
8a8918c049 | ||
|
|
98e0a92745 | ||
|
|
2b21f5f48f | ||
|
|
69ddc939b3 | ||
|
|
17ad6362c3 | ||
|
|
0f22c9e31a | ||
|
|
07f9b30ed5 | ||
|
|
7756becb35 | ||
|
|
cf2ac010bb | ||
|
|
76c8e98c02 | ||
|
|
fe66b44b10 | ||
|
|
97695e4b5a | ||
|
|
59653f4030 | ||
|
|
530b632c46 | ||
|
|
c8f1a10b8b | ||
|
|
84570c5024 | ||
|
|
624d21c4dc | ||
|
|
8db54d015a | ||
|
|
9204c720a4 | ||
|
|
daf81b5d78 | ||
|
|
c2c3f55925 | ||
|
|
ba520f1d90 | ||
|
|
81d1dbdcf0 | ||
|
|
23cb2655d1 | ||
|
|
9c4f94238d | ||
|
|
d28723916d | ||
|
|
65d4c9c8ee | ||
|
|
3a1f7a13a1 | ||
|
|
69d97a508c | ||
|
|
e7dc23ae26 | ||
|
|
4cffe30910 | ||
|
|
6f0dfba4aa | ||
|
|
6d617216c7 | ||
|
|
13d5e5e6af | ||
|
|
c697413554 | ||
|
|
994ae3e2f5 | ||
|
|
69ab292394 | ||
|
|
1985d31a07 | ||
|
|
b813185dc3 | ||
|
|
6daf04c9fc | ||
|
|
fd49ec4f97 | ||
|
|
84a4921629 | ||
|
|
b4aeaff783 | ||
|
|
caf73b89ea | ||
|
|
1c406557a4 | ||
|
|
d39eac9512 | ||
|
|
2530d31d58 | ||
|
|
c6894a1e95 | ||
|
|
05fdea3267 | ||
|
|
e0f7d9cd32 | ||
|
|
91b9fe23cc | ||
|
|
4e4e2907d9 | ||
|
|
b94f54f56d | ||
|
|
12932a1d33 | ||
|
|
7beef30d20 | ||
|
|
f1e4e3c753 | ||
|
|
d1ba758c6f | ||
|
|
c0472056ac | ||
|
|
1780c479d5 | ||
|
|
59f8465ece | ||
|
|
256f72dd08 | ||
|
|
836c7769f6 | ||
|
|
ebe880bd39 | ||
|
|
10fea79a35 | ||
|
|
451600448e | ||
|
|
7fccd6261e | ||
|
|
2436dab797 | ||
|
|
56538623a5 | ||
|
|
42a667c0ef | ||
|
|
5df95ad0fa | ||
|
|
345598016a | ||
|
|
6da38e537b | ||
|
|
e65f664821 | ||
|
|
cfa1eedae7 | ||
|
|
130c8d0e89 | ||
|
|
c4a2695b69 | ||
|
|
246069cc48 | ||
|
|
36a7d4a510 | ||
|
|
1b4a53f572 | ||
|
|
61a9d80e2c | ||
|
|
31568a5806 | ||
|
|
9aa73f5a5c | ||
|
|
35943feb08 | ||
|
|
73de21f936 | ||
|
|
4f1af87ba0 | ||
|
|
d129bd3e6b | ||
|
|
c2c52e8747 | ||
|
|
7e2ab49f89 | ||
|
|
3192d41fe3 | ||
|
|
ad5b260460 | ||
|
|
e57cb0ce51 | ||
|
|
9514357391 | ||
|
|
6fd0a40cc6 | ||
|
|
350d63231f | ||
|
|
cc28f1d5b5 | ||
|
|
4b73af9250 | ||
|
|
6e0efe521d | ||
|
|
041ab30778 | ||
|
|
678be4b92f | ||
|
|
74d759a45b | ||
|
|
295499c17b | ||
|
|
043d1c88c8 | ||
|
|
55e9b93b1c | ||
|
|
aa5e3e9697 | ||
|
|
fbf7ff6ebd | ||
|
|
3a5e8a827c | ||
|
|
6176dfaaf7 | ||
|
|
76c361e9bc | ||
|
|
ccb8981634 | ||
|
|
99b55d03c1 | ||
|
|
651fa6e2d1 | ||
|
|
4b258da0a0 | ||
|
|
9a66605616 | ||
|
|
da13895b72 | ||
|
|
c657a1d2cc | ||
|
|
eac8896f3e | ||
|
|
113f4f948b | ||
|
|
1b8dc1f75e | ||
|
|
001236e8e6 | ||
|
|
8025b8e24b | ||
|
|
e48bf012a6 | ||
|
|
94e32694b2 | ||
|
|
cbcf033b15 | ||
|
|
7da2671ec5 | ||
|
|
b6ea4c7003 | ||
|
|
49378061bf | ||
|
|
38013a9298 | ||
|
|
5ffbd16800 | ||
|
|
bcb29ef792 | ||
|
|
d20adb20d3 | ||
|
|
4f28cdd58f | ||
|
|
4b6b5c2f0e | ||
|
|
c31269a358 | ||
|
|
55092326a4 | ||
|
|
10dbcf63b7 | ||
|
|
32fad77104 | ||
|
|
05d2fbbbb9 | ||
|
|
916733a1e8 |
No files matched your search
+1117
File diff suppressed because it is too large.
Load diff
@@ -1,68 +1,69 @@
|
||||
name: Bug 反馈
|
||||
description: 反馈一个 Bug。为了提高反馈效率,请优先在开黑啦或者或 Discord 频道中反馈。
|
||||
name: Bug Feedback
|
||||
description:
|
||||
反馈一个 Bug。为了提高反馈效率,请优先在 KOOK 或者或 Discord 频道中反馈。若确认是 HMCL 问题,请发布 issue。
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
提交前请确保你的启动器版本是**最新的开发版**,可以在启动器设置中更换更新通道至开发版获取更新。
|
||||
|
||||
|
||||
我们发现,对于不经常检查邮箱的人来说,GitHub Issues 的反馈效率太低,时隔几个小时甚至一天的情况很多。
|
||||
为了改善反馈效率,并将积极为 HMCL 提供问题反馈和建议的人聚集起来,我们希望在这里反馈的人可以在 [开黑啦频道](https://kaihei.co/Kx7n3t)
|
||||
为了改善反馈效率,并将积极为 HMCL 提供问题反馈和建议的人聚集起来,我们希望在这里反馈的人可以在 [KOOK 频道](https://kook.top/Kx7n3t)
|
||||
或 [Discord 频道](https://discord.gg/jVvC7HfM6U)中反馈信息,**而不在 GitHub Issues 中反馈问题**。
|
||||
|
||||
如果你希望在 QQ 群中反馈问题,请[赞助 HMCL](https://afdian.net/@huanghongxun) 后申请加入群聊,也请将下面的信息表填好直接发在群中,加快我们的沟通速度。
|
||||
如果你希望在 QQ 群中反馈问题,请[赞助 HMCL](https://hmcl.huangyuhui.net/api/redirect/sponsor) 后申请加入群聊,也请将下面的信息表填好直接发在群中,加快我们的沟通速度。
|
||||
- type: input
|
||||
id: hmcl-version
|
||||
attributes:
|
||||
label: 启动器版本
|
||||
description: 请输入您所使用的的 HMCL 启动器版本。
|
||||
placeholder: 例如:3.3.188
|
||||
label: 启动器版本 Launcher Version
|
||||
description: 请输入您所使用的的 HMCL 启动器版本。 Please enter the HMCL launcher version you are using.
|
||||
placeholder: e.g. 3.5.3.211
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: platform
|
||||
attributes:
|
||||
label: 平台
|
||||
description: 请输入您遇到 BUG 的平台。
|
||||
placeholder: 例如:Windows 11
|
||||
label: 平台 Platform
|
||||
description: 请输入您遇到 BUG 的平台。 Please enter the platform on which you encountered the bug.
|
||||
placeholder: e.g. Windows 11
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: java-version
|
||||
attributes:
|
||||
label: Java
|
||||
description: 请输入您所使用的 Java 的名称与版本。
|
||||
placeholder: 例如:LibericaJDK Full JDK 17.0.1
|
||||
description: 请输入您所使用的 Java 的名称与版本。 Please enter the name and version of Java you are using.
|
||||
placeholder: e.g. LibericaJDK Full JDK 17.0.1
|
||||
- type: input
|
||||
id: game-version
|
||||
attributes:
|
||||
label: 游戏版本
|
||||
description: 如果您是在启动通过 HMCL 启动器自动下载的游戏时遇到了问题,请输入您所启动的游戏版本。
|
||||
placeholder: 例如:1.17.1
|
||||
label: 游戏版本 Game Version
|
||||
description: 如果您是在启动通过 HMCL 启动器自动下载的游戏时遇到了问题,请输入您所启动的游戏版本。If you are having trouble launching a game that was automatically downloaded through the HMCL launcher, please enter the version of the game you are launching.
|
||||
placeholder: e.g. 1.19
|
||||
- type: input
|
||||
id: game-modify
|
||||
attributes:
|
||||
label: 对游戏所做的修改
|
||||
description: 如果您为游戏安装了 OptiFine、Forge,或者是其他的 mod,请将您所做的修改填入下方。
|
||||
placeholder: 例如:OptiFine HD_U_G9, Rift
|
||||
label: 对游戏所做的修改 Modifications Made to the Game
|
||||
description: 如果您为游戏安装了 OptiFine、Forge,或者是其他的 mod,请将您所做的修改填入下方。 If you have OptiFine, Forge, or other mods installed for the game, please enter your modifications below.
|
||||
placeholder: e.g. OptiFine HD_U_G9, Rift
|
||||
- type: textarea
|
||||
id: game-crash-report
|
||||
attributes:
|
||||
label: 游戏崩溃报告
|
||||
description: 如果有游戏崩溃报告的话,请填入(或将文件拖入)下方。
|
||||
label: 游戏崩溃报告 Game Crash Report
|
||||
description: 如果有游戏崩溃报告的话,请填入(或将文件拖入)下方。 If there is a game crash report, please fill in (or drag the file into) below.
|
||||
- type: textarea
|
||||
id: hmcl-crash-report
|
||||
attributes:
|
||||
label: 启动器崩溃报告
|
||||
description: 如果有启动器崩溃报告的话,请填入(或将文件拖入)下方。
|
||||
label: 启动器崩溃报告 Launcher Crash Report
|
||||
description: 如果有启动器崩溃报告的话,请填入(或将文件拖入)下方。 If there is a launcher crash report, please fill it in (or drag the file in) below.
|
||||
- type: textarea
|
||||
id: hmcl-logs
|
||||
attributes:
|
||||
label: 启动器日志文件
|
||||
description: 遇到问题后,请在启动器的【设置】>【通用】>【调试】一栏中点击“导出启动器日志”,并将导出的日志拖到下方的输入栏中。
|
||||
label: 启动器日志文件 Launcher Log File
|
||||
description: 遇到问题后,请在启动器的【设置】>【通用】>【调试】一栏中点击“导出启动器日志”,并将导出的日志拖到下方的输入栏中。 After encountering a problem, please click "Export Launcher Log" in the [Settings] > [General] > [Debug] column of the launcher, and drag the exported log to the input field below.
|
||||
- type: textarea
|
||||
id: bug-report
|
||||
attributes:
|
||||
label: 问题描述
|
||||
description: 请尽可能地详细描述问题,特别是您是怎么遇到这个 BUG 的。
|
||||
label: 问题描述 Problem Description
|
||||
description: 请尽可能地详细描述问题,特别是您是怎么遇到这个 BUG 的。 Please describe the problem in as much detail as possible, especially how you encountered this bug.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: 开黑啦频道
|
||||
url: https://kaihei.co/Kx7n3t
|
||||
about: Hello Minecraft! Launcher 的官方开黑啦频道。
|
||||
- name: KOOK 频道
|
||||
url: https://kook.top/Kx7n3t
|
||||
about: Hello Minecraft! Launcher 的官方 KOOK 频道。
|
||||
- name: Discord 频道
|
||||
url: https://discord.gg/jVvC7HfM6U
|
||||
about: Hello Minecraft! Launcher 的官方 Discord 频道。
|
||||
@@ -11,4 +11,4 @@ contact_links:
|
||||
about: 前往爱发电赞助 HMCL。赞助后可以申请加入官方 QQ 群反馈问题。
|
||||
- name: 其他反馈
|
||||
url: https://github.com/huanghongxun/HMCL/issues/new
|
||||
about: 通过 Issue 反馈其他问题。
|
||||
about: 通过 Issue 反馈其他问题。
|
||||
@@ -1,16 +1,14 @@
|
||||
name: Check Update
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- javafx
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '30 * * * *'
|
||||
|
||||
jobs:
|
||||
check-update:
|
||||
if: ${{ github.repository_owner == 'huanghongxun' }}
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
HMCL_BASE_VERSION: '3.4'
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Fetch tags
|
||||
@@ -20,15 +18,15 @@ jobs:
|
||||
- name: Fetch last version
|
||||
run: |
|
||||
wget -O ci.json https://ci.huangyuhui.net/job/HMCL/lastSuccessfulBuild/api/json
|
||||
|
||||
export HMCL_BUILD_NUMBER=`cat ci.json | jq -M -r '.id'`
|
||||
export HMCL_VERSION="$HMCL_BASE_VERSION.$HMCL_BUILD_NUMBER"
|
||||
|
||||
export HMCL_EXE_FILE_NAME=`cat ci.json | jq -M -r '.artifacts[] | select(.fileName | endswith(".exe")) | .fileName'`
|
||||
if [ -z `echo $HMCL_EXE_FILE_NAME | grep -E "^HMCL-[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?\.exe\$"` ]; then exit 1; fi
|
||||
|
||||
export HMCL_VERSION=`echo "${HMCL_EXE_FILE_NAME%.exe}" | tail -c +6`
|
||||
export HMCL_COMMIT_SHA=`cat ci.json | jq -M -r '.actions[] | select(._class == "hudson.plugins.git.util.BuildData") | .lastBuiltRevision.SHA1'`
|
||||
|
||||
if [ "${#HMCL_BUILD_NUMBER}" != 3 ]; then exit 1; fi
|
||||
if [ "${#HMCL_COMMIT_SHA}" != 40 ]; then exit 1; fi
|
||||
|
||||
echo "HMCL_BUILD_NUMBER=$HMCL_BUILD_NUMBER" >> $GITHUB_ENV
|
||||
echo "HMCL_VERSION=$HMCL_VERSION" >> $GITHUB_ENV
|
||||
echo "HMCL_COMMIT_SHA=$HMCL_COMMIT_SHA" >> $GITHUB_ENV
|
||||
echo "HMCL_TAG_NAME=v$HMCL_VERSION" >> $GITHUB_ENV
|
||||
@@ -42,7 +40,7 @@ jobs:
|
||||
wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.jar"
|
||||
wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.jar.sha1"
|
||||
env:
|
||||
DOWNLOAD_BASE_URL: https://ci.huangyuhui.net/job/HMCL/${{ env.HMCL_BUILD_NUMBER }}/artifact/HMCL/build/libs
|
||||
DOWNLOAD_BASE_URL: https://ci.huangyuhui.net/job/HMCL/lastSuccessfulBuild/artifact/HMCL/build/libs
|
||||
- name: Generate release note
|
||||
if: ${{ env.continue == 'true' }}
|
||||
run: |
|
||||
@@ -63,5 +61,3 @@ jobs:
|
||||
target_commitish: ${{ env.HMCL_COMMIT_SHA }}
|
||||
name: ${{ env.HMCL_TAG_NAME }}
|
||||
tag_name: ${{ env.HMCL_TAG_NAME }}
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
name: Sync to Gitee
|
||||
|
||||
on:
|
||||
push
|
||||
|
||||
jobs:
|
||||
run:
|
||||
if: ${{ github.repository_owner == 'huanghongxun' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Mirror Github to Gitee
|
||||
uses: Yikun/[email protected]
|
||||
with:
|
||||
src: github/huanghongxun
|
||||
dst: gitee/huanghongxun
|
||||
static_list: 'HMCL'
|
||||
force_update: true
|
||||
debug: true
|
||||
dst_key: ${{ secrets.GITEE_SYNC_BOT_PRIVATE_KEY }}
|
||||
dst_token: ${{ secrets.GITEE_SYNC_BOT_TOKEN }}
|
||||
cache_path: /github/workspace/hub-mirror-cache
|
||||
@@ -1,44 +0,0 @@
|
||||
name: Create Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check and set HMCL version
|
||||
run: |
|
||||
export HMCL_VERSION=`echo ${{ github.ref }} | awk '{print substr($0,12)}'`
|
||||
echo "HMCL_VERSION=$HMCL_VERSION" >> $GITHUB_ENV
|
||||
|
||||
if [ -z `echo $HMCL_VERSION | grep "^[0-9]\.[0-9]\.[0-9]\{3\}\$"` ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "HMCL_BUILD_NUMBER=`echo $HMCL_VERSION | awk '{print substr($0,5)}'`" >> $GITHUB_ENV
|
||||
- name: Download artifacts
|
||||
run: |
|
||||
wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.exe"
|
||||
wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.exe.sha1"
|
||||
wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.jar"
|
||||
wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.jar.sha1"
|
||||
env:
|
||||
DOWNLOAD_BASE_URL: https://ci.huangyuhui.net/job/HMCL/${{ env.HMCL_BUILD_NUMBER }}/artifact/HMCL/build/libs
|
||||
- name: Generate release note
|
||||
run: |
|
||||
echo "HMCL v$HMCL_VERSION" >> RELEASE_NOTE
|
||||
echo "" >> RELEASE_NOTE
|
||||
echo "The full changelogs can be found on the website: https://hmcl.huangyuhui.net/changelog/dev.html" >> RELEASE_NOTE
|
||||
echo "Notice: changelogs are written in Chinese." >> RELEASE_NOTE
|
||||
- name: Create release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
body_path: RELEASE_NOTE
|
||||
files: |
|
||||
HMCL-${{ env.HMCL_VERSION }}.exe
|
||||
HMCL-${{ env.HMCL_VERSION }}.exe.sha1
|
||||
HMCL-${{ env.HMCL_VERSION }}.jar
|
||||
HMCL-${{ env.HMCL_VERSION }}.jar.sha1
|
||||
+6
-2
@@ -18,19 +18,23 @@ NVIDIA
|
||||
/HMCL/build/
|
||||
/HMCLCore/build/
|
||||
/HMCLTransformerDiscoveryService/build/
|
||||
/minecraft/libraries/HMCLTransformerDiscoveryService/build/
|
||||
/minecraft/libraries/log4j-patch/build/
|
||||
|
||||
# idea
|
||||
.idea
|
||||
/out/
|
||||
/HMCL/out/
|
||||
/HMCLCore/out/
|
||||
/HMCLTransformerDiscoveryService/out/
|
||||
/minecraft/libraries/HMCLTransformerDiscoveryService/out/
|
||||
/minecraft/libraries/log4j-patch/out/
|
||||
|
||||
# eclipse
|
||||
/bin/
|
||||
/HMCL/bin/
|
||||
/HMCLCore/bin/
|
||||
/HMCLTransformerDiscoveryService/bin/
|
||||
/minecraft/libraries/HMCLTransformerDiscoveryService/bin/
|
||||
/minecraft/libraries/log4j-patch/bin/
|
||||
.classpath
|
||||
.project
|
||||
.settings
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
[submodule "JSTUN"]
|
||||
path = JSTUN
|
||||
url = https://github.com/huanghongxun/JSTUN
|
||||
@@ -1,236 +0,0 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
maven { url 'https://jitpack.io' }
|
||||
}
|
||||
dependencies {
|
||||
classpath 'org.tukaani:xz:1.8'
|
||||
classpath 'org.glavo:pack200:0.3.0'
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'application'
|
||||
id 'com.github.johnrengelman.shadow' version '7.0.0'
|
||||
}
|
||||
|
||||
import java.nio.file.FileSystems
|
||||
import java.security.KeyFactory
|
||||
import java.security.MessageDigest
|
||||
import java.security.Signature
|
||||
import java.security.spec.PKCS8EncodedKeySpec
|
||||
import java.util.jar.JarFile
|
||||
import java.util.jar.JarOutputStream
|
||||
import java.util.zip.GZIPOutputStream
|
||||
import java.util.zip.ZipFile
|
||||
import java.nio.file.Files
|
||||
|
||||
import org.tukaani.xz.LZMA2Options
|
||||
import org.tukaani.xz.XZOutputStream
|
||||
import org.glavo.pack200.Pack200
|
||||
|
||||
def dev = null
|
||||
def shortcommit = System.getenv("GITHUB_SHA")?.toLowerCase()?.substring(0, 7) ?: null
|
||||
if (shortcommit != null && !shortcommit.isEmpty()) dev = "dev-" + shortcommit
|
||||
def buildnumber = System.getenv("BUILD_NUMBER") ?: dev ?: "SNAPSHOT"
|
||||
if (System.getenv("BUILD_NUMBER") != null && System.getenv("BUILD_NUMBER_OFFSET") != null)
|
||||
buildnumber = (Integer.parseInt(System.getenv("BUILD_NUMBER")) - Integer.parseInt(System.getenv("BUILD_NUMBER_OFFSET"))).toString()
|
||||
def versionroot = System.getenv("VERSION_ROOT") ?: "3.4"
|
||||
def microsoftAuthId = System.getenv("MICROSOFT_AUTH_ID") ?: ""
|
||||
def microsoftAuthSecret = System.getenv("MICROSOFT_AUTH_SECRET") ?: ""
|
||||
def versionType = System.getenv("VERSION_TYPE") ?: "nightly"
|
||||
version = versionroot + '.' + buildnumber
|
||||
|
||||
mainClassName = 'org.jackhuang.hmcl.Main'
|
||||
|
||||
dependencies {
|
||||
implementation project(":HMCLCore")
|
||||
implementation 'libs:JFoenix'
|
||||
|
||||
implementation group: 'de.javawi.jstun', name: 'jstun', version: '0.7.4'
|
||||
}
|
||||
|
||||
def digest(String algorithm, byte[] bytes) {
|
||||
return MessageDigest.getInstance(algorithm).digest(bytes)
|
||||
}
|
||||
|
||||
def createChecksum(File file) {
|
||||
def algorithm = "SHA-1"
|
||||
def suffix = "sha1"
|
||||
new File(file.parentFile, file.name + "." + suffix).text = digest(algorithm, file.bytes).encodeHex().toString() + "\n"
|
||||
}
|
||||
|
||||
def attachSignature(File jar) {
|
||||
def keyLocation = System.getenv("HMCL_SIGNATURE_KEY");
|
||||
if (keyLocation == null)
|
||||
return
|
||||
def privatekey = KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(new File(keyLocation).bytes))
|
||||
|
||||
def signer = Signature.getInstance("SHA512withRSA")
|
||||
signer.initSign(privatekey)
|
||||
new ZipFile(jar).withCloseable { zip ->
|
||||
zip.stream()
|
||||
.sorted(Comparator.comparing { it.name })
|
||||
.filter { it.name != "META-INF/hmcl_signature" }
|
||||
.forEach {
|
||||
signer.update(digest("SHA-512", it.name.getBytes("UTF-8")))
|
||||
signer.update(digest("SHA-512", zip.getInputStream(it).bytes))
|
||||
}
|
||||
}
|
||||
def signature = signer.sign()
|
||||
|
||||
FileSystems.newFileSystem(URI.create("jar:" + jar.toURI()), [:]).withCloseable { zipfs ->
|
||||
Files.newOutputStream(zipfs.getPath("META-INF/hmcl_signature")).withCloseable { it << signature }
|
||||
}
|
||||
}
|
||||
|
||||
ext.packer = Pack200.newPacker()
|
||||
packer.properties()["pack.effort"] = "9"
|
||||
ext.unpacker = Pack200.newUnpacker()
|
||||
|
||||
// Pack200 does not guarantee that unpacked .class file is bit-wise same as the .class file before packing
|
||||
// because of shrinking. So we should pack .class files and unpack it to make sure that after unpacking
|
||||
// .class files remain the same.
|
||||
def repack(File file) {
|
||||
def packed = new ByteArrayOutputStream()
|
||||
new JarFile(file).withCloseable { packer.pack(it, packed) }
|
||||
new JarOutputStream(file.newOutputStream()).withCloseable { unpacker.unpack(new ByteArrayInputStream(packed.toByteArray()), it) }
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
java11 {
|
||||
java {
|
||||
srcDirs = ['src/main/java11']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compileJava11Java {
|
||||
if(JavaVersion.current() < JavaVersion.VERSION_11) {
|
||||
javaCompiler = javaToolchains.compilerFor {
|
||||
languageVersion = JavaLanguageVersion.of(11)
|
||||
}
|
||||
}
|
||||
options.compilerArgs.add('--add-exports=java.base/jdk.internal.loader=ALL-UNNAMED')
|
||||
sourceCompatibility = 11
|
||||
targetCompatibility = 11
|
||||
}
|
||||
|
||||
jar {
|
||||
enabled = false
|
||||
dependsOn shadowJar
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
classifier = null
|
||||
|
||||
|
||||
minimize {
|
||||
exclude(dependency('com.google.code.gson:.*:.*'))
|
||||
exclude(dependency('com.github.steveice10:.*:.*'))
|
||||
exclude(dependency('libs:JFoenix:.*'))
|
||||
exclude(project(":HMCLTransformerDiscoveryService"))
|
||||
}
|
||||
|
||||
manifest {
|
||||
attributes 'Created-By': 'Copyright(c) 2013-2021 huangyuhui.',
|
||||
'Main-Class': mainClassName,
|
||||
'Multi-Release': 'true',
|
||||
'Implementation-Version': project.version,
|
||||
'Microsoft-Auth-Id': microsoftAuthId,
|
||||
'Microsoft-Auth-Secret': microsoftAuthSecret,
|
||||
'Build-Channel': versionType,
|
||||
'Class-Path': 'pack200.jar',
|
||||
'Add-Opens': [
|
||||
'java.base/java.lang',
|
||||
'java.base/java.lang.reflect',
|
||||
'javafx.graphics/javafx.css',
|
||||
'javafx.base/com.sun.javafx.runtime',
|
||||
'javafx.controls/com.sun.javafx.scene.control.behavior',
|
||||
'javafx.controls/javafx.scene.control.skin',
|
||||
'javafx.controls/com.sun.javafx.scene.control',
|
||||
'javafx.base/com.sun.javafx.binding',
|
||||
'javafx.base/com.sun.javafx.event',
|
||||
'javafx.graphics/com.sun.javafx.stage'
|
||||
].join(" "),
|
||||
'Add-Exports': [
|
||||
'java.base/jdk.internal.loader',
|
||||
'javafx.controls/com.sun.javafx.scene.control.behavior',
|
||||
'javafx.controls/javafx.scene.control.skin',
|
||||
'javafx.controls/com.sun.javafx.scene.control',
|
||||
'javafx.base/com.sun.javafx.binding',
|
||||
'javafx.graphics/com.sun.javafx.stage',
|
||||
'javafx.base/com.sun.javafx.event'
|
||||
].join(" ")
|
||||
}
|
||||
|
||||
doLast {
|
||||
repack(jar.archivePath) // see repack()
|
||||
attachSignature(jar.archivePath)
|
||||
createChecksum(jar.archivePath)
|
||||
}
|
||||
}
|
||||
|
||||
def createExecutable(String suffix, String header) {
|
||||
def output = new File(jar.archivePath.parentFile, jar.archivePath.name[0..-4] + suffix)
|
||||
output.bytes = new File(project.projectDir, header).bytes
|
||||
output << jar.archivePath.bytes
|
||||
createChecksum(output)
|
||||
}
|
||||
|
||||
processResources {
|
||||
ext.convertToBSS = { String resource ->
|
||||
// exclude resource
|
||||
doFirst {
|
||||
def cssFile = new File(this.projectDir, "src/main/resources/" + resource)
|
||||
def bssFile = new File(this.projectDir, "build/compiled-resources/" + resource[0..-4] + "bss")
|
||||
bssFile.parentFile.mkdirs()
|
||||
javaexec {
|
||||
classpath = sourceSets.main.compileClasspath
|
||||
mainClass = "com.sun.javafx.css.parser.Css2Bin"
|
||||
args = [cssFile, bssFile]
|
||||
}
|
||||
}
|
||||
}
|
||||
from "build/compiled-resources"
|
||||
|
||||
convertToBSS "assets/css/root.css"
|
||||
convertToBSS "assets/css/blue.css"
|
||||
|
||||
into('META-INF/versions/11') {
|
||||
from sourceSets.java11.output
|
||||
}
|
||||
dependsOn java11Classes
|
||||
}
|
||||
|
||||
task makePack(dependsOn: jar) {
|
||||
ext.outputPath = new File(jar.archivePath.parentFile, jar.archivePath.name[0..-4] + "pack")
|
||||
doLast {
|
||||
outputPath.newOutputStream().withCloseable { out ->
|
||||
new JarFile(jar.archivePath).withCloseable { jarFile -> packer.pack(jarFile, out) }
|
||||
}
|
||||
createChecksum(outputPath)
|
||||
}
|
||||
}
|
||||
|
||||
task makePackXz(dependsOn: makePack) doLast {
|
||||
def packXz = new File(makePack.outputPath.parentFile, makePack.outputPath.name + ".xz")
|
||||
// Our CI server does not have enough memory space to compress file at highest level.
|
||||
new XZOutputStream(packXz.newOutputStream(), new LZMA2Options(5)).withCloseable { it << makePack.outputPath.bytes }
|
||||
createChecksum(packXz)
|
||||
}
|
||||
|
||||
task makePackGz(dependsOn: makePack) doLast {
|
||||
def packGz = new File(makePack.outputPath.parentFile, makePack.outputPath.name + ".gz")
|
||||
new GZIPOutputStream(packGz.newOutputStream()).withCloseable { it << makePack.outputPath.bytes }
|
||||
createChecksum(packGz)
|
||||
}
|
||||
|
||||
|
||||
task makeExecutables(dependsOn: jar) doLast {
|
||||
createExecutable("exe", "src/main/resources/assets/HMCLauncher.exe")
|
||||
}
|
||||
|
||||
build.dependsOn makePackXz
|
||||
build.dependsOn makePackGz
|
||||
build.dependsOn makeExecutables
|
||||
@@ -0,0 +1,284 @@
|
||||
import java.nio.file.FileSystems
|
||||
import java.nio.file.Files
|
||||
import java.security.KeyFactory
|
||||
import java.security.MessageDigest
|
||||
import java.security.Signature
|
||||
import java.security.spec.PKCS8EncodedKeySpec
|
||||
import java.util.jar.JarFile
|
||||
import java.util.jar.JarOutputStream
|
||||
import java.util.zip.ZipFile
|
||||
import java.util.zip.GZIPOutputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.net.URI
|
||||
|
||||
import org.glavo.pack200.Pack200
|
||||
import org.tukaani.xz.LZMA2Options
|
||||
import org.tukaani.xz.XZOutputStream
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
maven(url = "https://jitpack.io")
|
||||
}
|
||||
dependencies {
|
||||
classpath("org.tukaani:xz:1.8")
|
||||
classpath("org.glavo:pack200:0.3.0")
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id("com.github.johnrengelman.shadow") version "7.0.0"
|
||||
}
|
||||
|
||||
val buildNumber = System.getenv("BUILD_NUMBER")?.toInt().let { number ->
|
||||
val offset = System.getenv("BUILD_NUMBER_OFFSET")?.toInt() ?: 0
|
||||
if (number != null) {
|
||||
(number - offset).toString()
|
||||
} else {
|
||||
val shortCommit = System.getenv("GITHUB_SHA")?.toLowerCase()?.substring(0, 7)
|
||||
if (!shortCommit.isNullOrEmpty()) "dev-$shortCommit" else "SNAPSHOT"
|
||||
}
|
||||
}
|
||||
val versionRoot = System.getenv("VERSION_ROOT") ?: "3.5"
|
||||
val versionType = System.getenv("VERSION_TYPE") ?: "nightly"
|
||||
|
||||
val microsoftAuthId = System.getenv("MICROSOFT_AUTH_ID") ?: ""
|
||||
val microsoftAuthSecret = System.getenv("MICROSOFT_AUTH_SECRET") ?: ""
|
||||
val curseForgeApiKey = System.getenv("CURSEFORGE_API_KEY") ?: ""
|
||||
|
||||
version = "$versionRoot.$buildNumber"
|
||||
|
||||
dependencies {
|
||||
implementation(project(":HMCLCore"))
|
||||
implementation("libs:JFoenix")
|
||||
|
||||
implementation("de.javawi.jstun:jstun:0.7.4")
|
||||
}
|
||||
|
||||
fun digest(algorithm: String, bytes: 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"
|
||||
)
|
||||
|
||||
algorithms.forEach { (algorithm, ext) ->
|
||||
File(file.parentFile, "${file.name}.$ext").writeText(
|
||||
digest(algorithm, file.readBytes()).joinToString(separator = "", postfix = "\n") { "%02x".format(it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun attachSignature(jar: File) {
|
||||
val keyLocation = System.getenv("HMCL_SIGNATURE_KEY") ?: return
|
||||
val privatekey = KeyFactory.getInstance("RSA").generatePrivate(PKCS8EncodedKeySpec(File(keyLocation).readBytes()))
|
||||
val signer = Signature.getInstance("SHA512withRSA")
|
||||
signer.initSign(privatekey)
|
||||
ZipFile(jar).use { zip ->
|
||||
zip.stream()
|
||||
.sorted(Comparator.comparing { it.name })
|
||||
.filter { it.name != "META-INF/hmcl_signature" }
|
||||
.forEach {
|
||||
signer.update(digest("SHA-512", it.name.toByteArray()))
|
||||
signer.update(digest("SHA-512", zip.getInputStream(it).readBytes()))
|
||||
}
|
||||
}
|
||||
val signature = signer.sign()
|
||||
FileSystems.newFileSystem(URI.create("jar:" + jar.toURI()), emptyMap<String, Any>()).use { zipfs ->
|
||||
Files.newOutputStream(zipfs.getPath("META-INF/hmcl_signature")).use { it.write(signature) }
|
||||
}
|
||||
}
|
||||
|
||||
val packer = Pack200.newPacker().apply {
|
||||
properties()["pack.effort"] = "9"
|
||||
}
|
||||
|
||||
val unpacker = Pack200.newUnpacker()
|
||||
|
||||
// Pack200 does not guarantee that unpacked .class file is bit-wise same as the .class file before packing
|
||||
// because of shrinking. So we should pack .class files and unpack it to make sure that after unpacking
|
||||
// .class files remain the same.
|
||||
fun repack(file: File) {
|
||||
val packed = ByteArrayOutputStream()
|
||||
JarFile(file).use { packer.pack(it, packed) }
|
||||
JarOutputStream(file.outputStream()).use { unpacker.unpack(ByteArrayInputStream(packed.toByteArray()), it) }
|
||||
}
|
||||
|
||||
val java11 = sourceSets.create("java11") {
|
||||
java {
|
||||
srcDir("src/main/java11")
|
||||
}
|
||||
}
|
||||
|
||||
tasks.getByName<JavaCompile>(java11.compileJavaTaskName) {
|
||||
if (JavaVersion.current() < JavaVersion.VERSION_11) {
|
||||
javaCompiler.set(javaToolchains.compilerFor {
|
||||
languageVersion.set(JavaLanguageVersion.of(11))
|
||||
})
|
||||
}
|
||||
options.compilerArgs.add("--add-exports=java.base/jdk.internal.loader=ALL-UNNAMED")
|
||||
sourceCompatibility = "11"
|
||||
targetCompatibility = "11"
|
||||
}
|
||||
|
||||
tasks.jar {
|
||||
enabled = false
|
||||
dependsOn(tasks["shadowJar"])
|
||||
}
|
||||
|
||||
val jarPath = tasks.jar.get().archiveFile.get().asFile
|
||||
|
||||
tasks.getByName<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("shadowJar") {
|
||||
archiveClassifier.set(null as String?)
|
||||
|
||||
minimize {
|
||||
exclude(dependency("com.google.code.gson:.*:.*"))
|
||||
exclude(dependency("com.github.steveice10:.*:.*"))
|
||||
exclude(dependency("libs:JFoenix:.*"))
|
||||
}
|
||||
|
||||
manifest {
|
||||
attributes(
|
||||
"Created-By" to "Copyright(c) 2013-2021 huangyuhui.",
|
||||
"Main-Class" to "org.jackhuang.hmcl.Main",
|
||||
"Multi-Release" to "true",
|
||||
"Implementation-Version" to project.version,
|
||||
"Microsoft-Auth-Id" to microsoftAuthId,
|
||||
"Microsoft-Auth-Secret" to microsoftAuthSecret,
|
||||
"CurseForge-Api-Key" to curseForgeApiKey,
|
||||
"Build-Channel" to versionType,
|
||||
"Class-Path" to "pack200.jar",
|
||||
"Add-Opens" to listOf(
|
||||
"java.base/java.lang",
|
||||
"java.base/java.lang.reflect",
|
||||
"javafx.graphics/javafx.css",
|
||||
"javafx.base/com.sun.javafx.runtime",
|
||||
"javafx.controls/com.sun.javafx.scene.control.behavior",
|
||||
"javafx.controls/javafx.scene.control.skin",
|
||||
"javafx.controls/com.sun.javafx.scene.control",
|
||||
"javafx.base/com.sun.javafx.binding",
|
||||
"javafx.base/com.sun.javafx.event",
|
||||
"javafx.graphics/com.sun.javafx.stage"
|
||||
).joinToString(" "),
|
||||
"Add-Exports" to listOf(
|
||||
"java.base/jdk.internal.loader",
|
||||
"javafx.controls/com.sun.javafx.scene.control.behavior",
|
||||
"javafx.controls/javafx.scene.control.skin",
|
||||
"javafx.controls/com.sun.javafx.scene.control",
|
||||
"javafx.base/com.sun.javafx.binding",
|
||||
"javafx.graphics/com.sun.javafx.stage",
|
||||
"javafx.base/com.sun.javafx.event"
|
||||
).joinToString(" ")
|
||||
)
|
||||
}
|
||||
|
||||
doLast {
|
||||
repack(jarPath) // see repack()
|
||||
attachSignature(jarPath)
|
||||
createChecksum(jarPath)
|
||||
}
|
||||
}
|
||||
|
||||
fun createExecutable(suffix: String, header: String) {
|
||||
val output = File(jarPath.parentFile, jarPath.nameWithoutExtension + '.' + suffix)
|
||||
|
||||
output.outputStream().use {
|
||||
it.write(File(project.projectDir, header).readBytes())
|
||||
it.write(jarPath.readBytes())
|
||||
}
|
||||
|
||||
createChecksum(output)
|
||||
}
|
||||
|
||||
tasks.processResources {
|
||||
fun convertToBSS(resource: String) {
|
||||
doFirst {
|
||||
val cssFile = File(projectDir, "src/main/resources/$resource")
|
||||
val bssFile = File(projectDir, "build/compiled-resources/${resource.substring(0, resource.length - 4)}.bss")
|
||||
bssFile.parentFile.mkdirs()
|
||||
javaexec {
|
||||
classpath = sourceSets["main"].compileClasspath
|
||||
mainClass.set("com.sun.javafx.css.parser.Css2Bin")
|
||||
args(cssFile, bssFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
from("build/compiled-resources")
|
||||
|
||||
convertToBSS("assets/css/root.css")
|
||||
convertToBSS("assets/css/blue.css")
|
||||
|
||||
into("META-INF/versions/11") {
|
||||
from(sourceSets["java11"].output)
|
||||
}
|
||||
dependsOn(tasks["java11Classes"])
|
||||
|
||||
into("assets") {
|
||||
from(project.buildDir.resolve("openjfx-dependencies.json"))
|
||||
}
|
||||
dependsOn(rootProject.tasks["generateOpenJFXDependencies"])
|
||||
}
|
||||
|
||||
val packFile = File(jarPath.parentFile, jarPath.nameWithoutExtension + ".pack")
|
||||
|
||||
val makePack = tasks.create("makePack") {
|
||||
dependsOn(tasks.jar)
|
||||
|
||||
doLast {
|
||||
packFile.outputStream().use { out ->
|
||||
JarFile(jarPath).use { jarFile -> packer.pack(jarFile, out) }
|
||||
}
|
||||
createChecksum(packFile)
|
||||
}
|
||||
}
|
||||
|
||||
val makePackXz = tasks.create("makePackXz") {
|
||||
dependsOn(makePack)
|
||||
|
||||
val packXz = File(packFile.parentFile, packFile.name + ".xz")
|
||||
|
||||
doLast {
|
||||
// Our CI server does not have enough memory space to compress file at highest level.
|
||||
XZOutputStream(packXz.outputStream(), LZMA2Options(5))
|
||||
.use { it.write(packFile.readBytes()) }
|
||||
createChecksum(packXz)
|
||||
}
|
||||
}
|
||||
|
||||
val makePackGz = tasks.create("makePackGz") {
|
||||
dependsOn(makePack)
|
||||
|
||||
val packGz = File(packFile.parentFile, packFile.name + ".gz")
|
||||
|
||||
doLast {
|
||||
GZIPOutputStream(packGz.outputStream()).use { it.write(packFile.readBytes()) }
|
||||
createChecksum(packGz)
|
||||
}
|
||||
}
|
||||
|
||||
val makeExecutables = tasks.create("makeExecutables") {
|
||||
dependsOn(makePack)
|
||||
|
||||
doLast {
|
||||
createExecutable("exe", "src/main/resources/assets/HMCLauncher.exe")
|
||||
createExecutable("sh", "src/main/resources/assets/HMCLauncher.sh")
|
||||
}
|
||||
}
|
||||
|
||||
tasks.build {
|
||||
dependsOn(makePackXz, makePackGz, makeExecutables)
|
||||
}
|
||||
|
||||
tasks.create<JavaExec>("run") {
|
||||
dependsOn(tasks.jar)
|
||||
|
||||
group = "application"
|
||||
|
||||
classpath = files(jarPath)
|
||||
workingDir = rootProject.rootDir
|
||||
}
|
||||
@@ -19,6 +19,7 @@
|
||||
import json
|
||||
import codecs
|
||||
import re
|
||||
import sys
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
|
||||
S = ';'
|
||||
@@ -26,18 +27,21 @@ S = ';'
|
||||
MOD_SEPARATOR = ','
|
||||
|
||||
CURSEFORGE_PATTERN1 = re.compile(
|
||||
r'^/minecraft/(mc-mods|customization|mc-addons|customization/configuration)/(?P<modid>[\w-]+)(/(.*?))?$')
|
||||
r'^/minecraft/(mc-mods|modpacks|customization|mc-addons|customization/configuration)/+(?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]:
|
||||
for pattern in [CURSEFORGE_PATTERN1, CURSEFORGE_PATTERN2, CURSEFORGE_PATTERN3, CURSEFORGE_PATTERN4]:
|
||||
match = pattern.match(res.path)
|
||||
if match:
|
||||
return match.group('modid')
|
||||
@@ -45,7 +49,7 @@ def parseCurseforge(url):
|
||||
|
||||
|
||||
MCMOD_PATTERN = re.compile(
|
||||
r'^http(s)?://www\.mcmod\.cn/class/(?P<modid>\d+)\.html$')
|
||||
r'^http(s)?://www\.mcmod\.cn/(class|modpack)/(?P<modid>\d+)\.html$')
|
||||
|
||||
|
||||
def parseMcmod(url):
|
||||
@@ -55,7 +59,7 @@ def parseMcmod(url):
|
||||
return ''
|
||||
|
||||
|
||||
MCBBS_HTML_PATTERN = re.compile(r'/thread-(?P<modid>\d+)-(\d+)-(\d+).html')
|
||||
MCBBS_HTML_PATTERN = re.compile(r'/+thread-(?P<modid>\d+)-(\d+)-(\d+).html')
|
||||
MCBBS_PHP_PATTERN = re.compile(r'')
|
||||
|
||||
|
||||
@@ -78,8 +82,16 @@ def parseMcbbs(url):
|
||||
return ''
|
||||
|
||||
|
||||
skip = [
|
||||
'Minecraft',
|
||||
'The Building Game'
|
||||
]
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
with codecs.open('data.json', mode='r', encoding='utf-8-sig') as jsonfile, codecs.open('data.csv', mode='w', encoding='utf-8') as outfile:
|
||||
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:
|
||||
@@ -87,6 +99,9 @@ if __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)
|
||||
@@ -123,7 +138,7 @@ if __name__ == '__main__':
|
||||
exit(1)
|
||||
|
||||
mod_id = []
|
||||
if 'list' in mod['modid']:
|
||||
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)
|
||||
|
||||
@@ -3,8 +3,8 @@ package moe.mickey.minecraft.skin.fx;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class SkinAnimation {
|
||||
@@ -12,9 +12,8 @@ public class SkinAnimation {
|
||||
protected int weight, left;
|
||||
protected List<SkinTransition> transitions;
|
||||
|
||||
@Deprecated
|
||||
public SkinAnimation() {
|
||||
this.transitions = new LinkedList<>();
|
||||
this.transitions = new ArrayList<>();
|
||||
}
|
||||
|
||||
public SkinAnimation(int weight, SkinTransition... transitions) {
|
||||
|
||||
@@ -2,14 +2,15 @@ package moe.mickey.minecraft.skin.fx;
|
||||
|
||||
import javafx.animation.AnimationTimer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class SkinAnimationPlayer {
|
||||
|
||||
protected final Random random = new Random();
|
||||
protected LinkedList<SkinAnimation> animations = new LinkedList<>();
|
||||
protected List<SkinAnimation> animations = new ArrayList<>();
|
||||
protected SkinAnimation playing;
|
||||
protected boolean running;
|
||||
protected int weightedSum = 0;
|
||||
@@ -28,7 +29,7 @@ public class SkinAnimationPlayer {
|
||||
}
|
||||
playing = tmp;
|
||||
if (playing == null && animations.size() > 0)
|
||||
playing = animations.getLast();
|
||||
playing = animations.get(animations.size() - 1);
|
||||
if (playing != null) {
|
||||
playing.playFromStart();
|
||||
lastPlayTime = now;
|
||||
|
||||
@@ -4,6 +4,7 @@ import javafx.scene.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.input.ScrollEvent;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Material;
|
||||
import javafx.scene.paint.PhongMaterial;
|
||||
import javafx.scene.shape.Shape3D;
|
||||
@@ -11,6 +12,8 @@ import javafx.scene.transform.Rotate;
|
||||
import javafx.scene.transform.Scale;
|
||||
import javafx.scene.transform.Translate;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class SkinCanvas extends Group {
|
||||
|
||||
public static final Image ALEX = new Image(SkinCanvas.class.getResourceAsStream("/assets/img/alex.png"));
|
||||
@@ -22,7 +25,7 @@ public class SkinCanvas extends Group {
|
||||
public static final SkinCube STEVEN_LARM = new SkinCube(4, 12, 4, 16F / 64F, 16F / 64F, 32F / 64F, 48F / 64F, 0F, false);
|
||||
public static final SkinCube STEVEN_RARM = new SkinCube(4, 12, 4, 16F / 64F, 16F / 64F, 40F / 64F, 16F / 64F, 0F, false);
|
||||
|
||||
protected Image srcSkin, skin;
|
||||
protected Image srcSkin, skin, srcCape, cape;
|
||||
protected boolean isSlim;
|
||||
|
||||
protected double preW, preH;
|
||||
@@ -45,6 +48,8 @@ public class SkinCanvas extends Group {
|
||||
public final SkinCube llegInside = new SkinCube(4, 12, 4, 16F / 64F, 16F / 64F, 16F / 64F, 48F / 64F, 0F, false);
|
||||
public final SkinCube rlegInside = new SkinCube(4, 12, 4, 16F / 64F, 16F / 64F, 0F, 16F / 64F, 0F, false);
|
||||
|
||||
public final SkinCube capeCube = new SkinCube(10, 16, 1, 22F / 64F, 17F / 32F, 0F, 0F, 0F, false);
|
||||
|
||||
public final SkinGroup head = new SkinGroup(
|
||||
new Rotate(0, 0, headInside.getHeight() / 2, 0, Rotate.X_AXIS),
|
||||
new Rotate(0, Rotate.Y_AXIS),
|
||||
@@ -82,6 +87,13 @@ public class SkinCanvas extends Group {
|
||||
rlegOuter, rlegInside
|
||||
);
|
||||
|
||||
public final SkinGroup capeGroup = new SkinGroup(
|
||||
new Rotate(0, 0, -capeCube.getHeight() / 2, 0, Rotate.X_AXIS),
|
||||
new Rotate(0, Rotate.Y_AXIS),
|
||||
new Rotate(0, Rotate.Z_AXIS),
|
||||
capeCube
|
||||
);
|
||||
|
||||
protected PerspectiveCamera camera = new PerspectiveCamera(true);
|
||||
|
||||
protected Rotate xRotate = new Rotate(0, Rotate.X_AXIS);
|
||||
@@ -90,10 +102,10 @@ public class SkinCanvas extends Group {
|
||||
protected Translate translate = new Translate(0, 0, -80);
|
||||
protected Scale scale = new Scale(1, 1);
|
||||
|
||||
protected SkinAnimationPlayer animationplayer = new SkinAnimationPlayer();
|
||||
protected SkinAnimationPlayer animationPlayer = new SkinAnimationPlayer();
|
||||
|
||||
public SkinAnimationPlayer getAnimationplayer() {
|
||||
return animationplayer;
|
||||
public SkinAnimationPlayer getAnimationPlayer() {
|
||||
return animationPlayer;
|
||||
}
|
||||
|
||||
public Image getSrcSkin() {
|
||||
@@ -104,20 +116,21 @@ public class SkinCanvas extends Group {
|
||||
return skin;
|
||||
}
|
||||
|
||||
public void updateSkin(Image skin, boolean isSlim) {
|
||||
public void updateSkin(Image skin, boolean isSlim, final @Nullable Image cape) {
|
||||
if (SkinHelper.isNoRequest(skin) && SkinHelper.isSkin(skin)) {
|
||||
this.srcSkin = skin;
|
||||
this.skin = SkinHelper.x32Tox64(skin);
|
||||
this.srcCape = cape;
|
||||
this.cape = cape == null ? null : cape.getWidth() < 256 ? SkinHelper.enlarge(cape, 4, 8) : cape;
|
||||
int multiple = Math.max((int) (1024 / skin.getWidth()), 1);
|
||||
if (multiple > 1)
|
||||
this.skin = SkinHelper.enlarge(this.skin, multiple);
|
||||
if (this.isSlim != isSlim)
|
||||
updateSkinModel(isSlim);
|
||||
this.skin = SkinHelper.enlarge(this.skin, multiple, multiple);
|
||||
updateSkinModel(isSlim, cape != null);
|
||||
bindMaterial(root);
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateSkinModel(boolean isSlim) {
|
||||
protected void updateSkinModel(boolean isSlim, boolean hasCape) {
|
||||
this.isSlim = isSlim;
|
||||
FunctionHelper.alwaysB(SkinMultipleCubes::setWidth, isSlim ? 3 : 4, larmOuter, rarmOuter);
|
||||
FunctionHelper.alwaysB(SkinCube::setWidth, isSlim ? 3D : 4D, larmInside, rarmInside);
|
||||
@@ -134,6 +147,8 @@ public class SkinCanvas extends Group {
|
||||
|
||||
larm.getZRotate().setPivotX(-larmInside.getWidth() / 2);
|
||||
rarm.getZRotate().setPivotX(+rarmInside.getWidth() / 2);
|
||||
|
||||
capeGroup.setVisible(hasCape);
|
||||
}
|
||||
|
||||
public SkinCanvas(double preW, double preH) {
|
||||
@@ -149,17 +164,17 @@ public class SkinCanvas extends Group {
|
||||
init();
|
||||
}
|
||||
|
||||
protected Material createMaterial() {
|
||||
protected Material createMaterial(final Image image) {
|
||||
PhongMaterial material = new PhongMaterial();
|
||||
material.setDiffuseMap(skin);
|
||||
material.setDiffuseMap(image);
|
||||
return material;
|
||||
}
|
||||
|
||||
protected void bindMaterial(Group group) {
|
||||
Material material = createMaterial();
|
||||
Material material = createMaterial(skin);
|
||||
for (Node node : group.getChildren())
|
||||
if (node instanceof Shape3D)
|
||||
((Shape3D) node).setMaterial(material);
|
||||
((Shape3D) node).setMaterial(node == capeCube ? createMaterial(cape) : material);
|
||||
else if (node instanceof SkinMultipleCubes)
|
||||
((SkinMultipleCubes) node).updateSkin(skin);
|
||||
else if (node instanceof Group)
|
||||
@@ -178,6 +193,11 @@ public class SkinCanvas extends Group {
|
||||
lleg.setTranslateY(+(bodyInside.getHeight() + llegInside.getHeight()) / 2);
|
||||
rleg.setTranslateY(+(bodyInside.getHeight() + rlegInside.getHeight()) / 2);
|
||||
|
||||
capeGroup.setTranslateY(+(capeCube.getHeight() - bodyOuter.getHeight()) / 2);
|
||||
capeGroup.setTranslateZ(-(bodyInside.getDepth() + bodyOuter.getDepth()) / 2);
|
||||
|
||||
capeGroup.getTransforms().addAll(new Rotate(180, Rotate.Y_AXIS), new Rotate(10, Rotate.X_AXIS));
|
||||
|
||||
root.getTransforms().addAll(xRotate);
|
||||
|
||||
root.getChildren().addAll(
|
||||
@@ -186,15 +206,20 @@ public class SkinCanvas extends Group {
|
||||
larm,
|
||||
rarm,
|
||||
lleg,
|
||||
rleg
|
||||
rleg,
|
||||
capeGroup
|
||||
);
|
||||
updateSkin(skin, false);
|
||||
updateSkin(skin, false, null);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
protected SubScene createSubScene() {
|
||||
Group group = new Group();
|
||||
|
||||
AmbientLight light = new AmbientLight(Color.WHITE);
|
||||
group.getChildren().add(light);
|
||||
|
||||
group.getChildren().add(createPlayerModel());
|
||||
group.getTransforms().add(zRotate);
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package moe.mickey.minecraft.skin.fx;
|
||||
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.shape.Mesh;
|
||||
import javafx.scene.shape.MeshView;
|
||||
import javafx.scene.shape.TriangleMesh;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
public class SkinCube extends MeshView {
|
||||
@@ -20,61 +20,60 @@ public class SkinCube extends MeshView {
|
||||
width /= 2F;
|
||||
height /= 2F;
|
||||
depth /= 2F;
|
||||
|
||||
return new float[]{
|
||||
-width, -height, depth, // P0
|
||||
width, -height, depth, // P1
|
||||
-width, height, depth, // P2
|
||||
width, height, depth, // P3
|
||||
-width, -height, -depth, // P4
|
||||
width, -height, -depth, // P5
|
||||
-width, height, -depth, // P6
|
||||
width, height, -depth // P7
|
||||
-width, -height, +depth, // P0
|
||||
+width, -height, +depth, // P1
|
||||
-width, +height, +depth, // P2
|
||||
+width, +height, +depth, // P3
|
||||
-width, -height, -depth, // P4
|
||||
+width, -height, -depth, // P5
|
||||
-width, +height, -depth, // P6
|
||||
+width, +height, -depth // P7
|
||||
};
|
||||
}
|
||||
|
||||
public static float[] createTexCoords(float width, float height, float depth, float scaleX, float scaleY,
|
||||
float startX, float startY, boolean isSlim) {
|
||||
float startX, float startY, boolean isSlim) {
|
||||
float x = (width + depth) * 2, y = height + depth, half_width = width / x * scaleX, half_depth = depth / x * scaleX,
|
||||
top_x = depth / x * scaleX + startX, top_y = startY, arm4 = isSlim ? half_depth : half_width,
|
||||
bottom_x = startX, middle_y = depth / y * scaleY + top_y, bottom_y = scaleY + top_y;
|
||||
return new float[]{
|
||||
top_x, top_y, // T0 ---
|
||||
top_x + half_width, top_y, // T1 |
|
||||
top_x + half_width * 2, top_y, // T2 ---
|
||||
bottom_x, middle_y, // T3 ---
|
||||
bottom_x + half_depth, middle_y, // T4 |
|
||||
top_x + half_width, top_y, // T1 |
|
||||
top_x + half_width * 2, top_y, // T2 ---
|
||||
bottom_x, middle_y, // T3 ---
|
||||
bottom_x + half_depth, middle_y, // T4 |
|
||||
bottom_x + half_depth + half_width, middle_y, // T5 |
|
||||
bottom_x + scaleX - arm4, middle_y, // T6 |
|
||||
bottom_x + scaleX, middle_y, // T7 ---
|
||||
bottom_x, bottom_y, // T8 ---
|
||||
bottom_x + half_depth, bottom_y, // T9 |
|
||||
bottom_x + scaleX - arm4, middle_y, // T6 |
|
||||
bottom_x + scaleX, middle_y, // T7 ---
|
||||
bottom_x, bottom_y, // T8 ---
|
||||
bottom_x + half_depth, bottom_y, // T9 |
|
||||
bottom_x + half_depth + half_width, bottom_y, // T10 |
|
||||
bottom_x + scaleX - arm4, bottom_y, // T11 |
|
||||
bottom_x + scaleX, bottom_y // T12 ---
|
||||
bottom_x + scaleX - arm4, bottom_y, // T11 |
|
||||
bottom_x + scaleX, bottom_y // T12 ---
|
||||
};
|
||||
}
|
||||
|
||||
public static int[] createFaces() {
|
||||
int[] faces = new int[]{
|
||||
int[] faces = {
|
||||
// TOP
|
||||
5, 0, 4, 1, 0, 5, //P5,T0, P4,T1, P0,T5
|
||||
5, 0, 0, 5, 1, 4, //P5,T0, P0,T5, P1,T4
|
||||
// RIGHT
|
||||
0, 5, 4, 6, 6, 11, //P0,T4 ,P4,T3, P6,T8
|
||||
0, 5, 6, 11, 2, 10, //P0,T4 ,P6,T8, P2,T9
|
||||
0, 5, 4, 6, 6, 11, //P0,T4 ,P4,T3, P6,T8
|
||||
0, 5, 6, 11, 2, 10, //P0,T4 ,P6,T8, P2,T9
|
||||
// FRONT
|
||||
1, 4, 0, 5, 2, 10, //P1,T5, P0,T4, P2,T9
|
||||
1, 4, 2, 10, 3, 9, //P1,T5, P2,T9, P3,T10
|
||||
1, 4, 0, 5, 2, 10, //P1,T5, P0,T4, P2,T9
|
||||
1, 4, 2, 10, 3, 9, //P1,T5, P2,T9, P3,T10
|
||||
// LEFT
|
||||
5, 3, 1, 4, 3, 9, //P5,T6, P1,T5, P3,T10
|
||||
5, 3, 3, 9, 7, 8, //P5,T6, P3,T10,P7,T11
|
||||
// BACK
|
||||
4, 6, 5, 7, 7, 12, //P4,T6, P5,T7, P7,T12
|
||||
4, 6, 7, 12, 6, 11, //P4,T6, P7,T12,P6,T11
|
||||
4, 6, 5, 7, 7, 12, //P4,T6, P5,T7, P7,T12
|
||||
4, 6, 7, 12, 6, 11, //P4,T6, P7,T12,P6,T11
|
||||
// BOTTOM
|
||||
3, 5, 2, 6, 6, 2, //P3,T2, P2,T1, P6,T5
|
||||
3, 5, 6, 2, 7, 1 //P3,T2, P6,T5, P7,T6
|
||||
3, 5, 6, 2, 7, 1 //P3,T2, P6,T5, P7,T6
|
||||
};
|
||||
|
||||
int[] copy = faces.clone();
|
||||
@@ -91,7 +90,6 @@ public class SkinCube extends MeshView {
|
||||
|
||||
private double width, height, depth;
|
||||
private boolean isSlim;
|
||||
private Image skin;
|
||||
private Mesh model;
|
||||
|
||||
public SkinCube(float width, float height, float depth, float scaleX, float scaleY, float startX, float startY, float enlarge, boolean isSlim) {
|
||||
|
||||
@@ -10,12 +10,12 @@ public final class SkinHelper {
|
||||
private SkinHelper() {
|
||||
}
|
||||
|
||||
public static class PixelCopyer {
|
||||
public static class PixelCopier {
|
||||
|
||||
protected Image srcImage;
|
||||
protected WritableImage newImage;
|
||||
|
||||
public PixelCopyer(Image srcImage, WritableImage newImage) {
|
||||
public PixelCopier(Image srcImage, WritableImage newImage) {
|
||||
this.srcImage = srcImage;
|
||||
this.newImage = newImage;
|
||||
}
|
||||
@@ -70,53 +70,53 @@ public final class SkinHelper {
|
||||
}
|
||||
|
||||
public static Image x32Tox64(Image srcSkin) {
|
||||
if (srcSkin.getHeight() == 64)
|
||||
if (srcSkin.getHeight() == srcSkin.getWidth())
|
||||
return srcSkin;
|
||||
|
||||
WritableImage newSkin = new WritableImage((int) srcSkin.getWidth(), (int) srcSkin.getHeight() * 2);
|
||||
PixelCopyer copyer = new PixelCopyer(srcSkin, newSkin);
|
||||
PixelCopier copier = new PixelCopier(srcSkin, newSkin);
|
||||
// HEAD & HAT
|
||||
copyer.copy(0 / 64F, 0 / 32F, 0 / 64F, 0 / 64F, 64 / 64F, 16 / 32F);
|
||||
copier.copy(0 / 64F, 0 / 32F, 0 / 64F, 0 / 64F, 64 / 64F, 16 / 32F);
|
||||
// LEFT-LEG
|
||||
x32Tox64(copyer, 0 / 64F, 16 / 32F, 16 / 64F, 48 / 64F, 4 / 64F, 12 / 32F, 4 / 64F);
|
||||
x32Tox64(copier, 0 / 64F, 16 / 32F, 16 / 64F, 48 / 64F, 4 / 64F, 12 / 32F, 4 / 64F);
|
||||
// RIGHT-LEG
|
||||
copyer.copy(0 / 64F, 16 / 32F, 0 / 64F, 16 / 64F, 16 / 64F, 16 / 32F);
|
||||
copier.copy(0 / 64F, 16 / 32F, 0 / 64F, 16 / 64F, 16 / 64F, 16 / 32F);
|
||||
// BODY
|
||||
copyer.copy(16 / 64F, 16 / 32F, 16 / 64F, 16 / 64F, 24 / 64F, 16 / 32F);
|
||||
copier.copy(16 / 64F, 16 / 32F, 16 / 64F, 16 / 64F, 24 / 64F, 16 / 32F);
|
||||
// LEFT-ARM
|
||||
x32Tox64(copyer, 40 / 64F, 16 / 32F, 32 / 64F, 48 / 64F, 4 / 64F, 12 / 32F, 4 / 64F);
|
||||
x32Tox64(copier, 40 / 64F, 16 / 32F, 32 / 64F, 48 / 64F, 4 / 64F, 12 / 32F, 4 / 64F);
|
||||
// RIGHT-ARM
|
||||
copyer.copy(40 / 64F, 16 / 32F, 40 / 64F, 16 / 64F, 16 / 64F, 16 / 32F);
|
||||
copier.copy(40 / 64F, 16 / 32F, 40 / 64F, 16 / 64F, 16 / 64F, 16 / 32F);
|
||||
|
||||
return newSkin;
|
||||
}
|
||||
|
||||
static void x32Tox64(PixelCopyer copyer, float srcX, float srcY, float toX, float toY, float width, float height, float depth) {
|
||||
static void x32Tox64(PixelCopier copier, float srcX, float srcY, float toX, float toY, float width, float height, float depth) {
|
||||
// TOP
|
||||
copyer.copy(srcX + depth, srcY, toX + depth, toY, width, depth * 2, true, false);
|
||||
copier.copy(srcX + depth, srcY, toX + depth, toY, width, depth * 2, true, false);
|
||||
// BOTTOM
|
||||
copyer.copy(srcX + depth + width, srcY, toX + depth + width, toY, width, depth * 2, true, false);
|
||||
copier.copy(srcX + depth + width, srcY, toX + depth + width, toY, width, depth * 2, true, false);
|
||||
// INS
|
||||
copyer.copy(srcX, srcY + depth * 2, toX + width + depth, toY + depth, depth, height, true, false);
|
||||
copier.copy(srcX, srcY + depth * 2, toX + width + depth, toY + depth, depth, height, true, false);
|
||||
// OUTS
|
||||
copyer.copy(srcX + width + depth, srcY + depth * 2, toX, toY + depth, depth, height, true, false);
|
||||
copier.copy(srcX + width + depth, srcY + depth * 2, toX, toY + depth, depth, height, true, false);
|
||||
// FRONT
|
||||
copyer.copy(srcX + depth, srcY + depth * 2, toX + depth, toY + depth, width, height, true, false);
|
||||
copier.copy(srcX + depth, srcY + depth * 2, toX + depth, toY + depth, width, height, true, false);
|
||||
// BACK
|
||||
copyer.copy(srcX + width + depth * 2, srcY + depth * 2, toX + width + depth * 2, toY + depth, width, height, true, false);
|
||||
copier.copy(srcX + width + depth * 2, srcY + depth * 2, toX + width + depth * 2, toY + depth, width, height, true, false);
|
||||
}
|
||||
|
||||
public static Image enlarge(Image srcSkin, int multiple) {
|
||||
WritableImage newSkin = new WritableImage((int) srcSkin.getWidth() * multiple, (int) srcSkin.getHeight() * multiple);
|
||||
public static Image enlarge(Image srcSkin, int multipleX, int multipleY) {
|
||||
WritableImage newSkin = new WritableImage((int) srcSkin.getWidth() * multipleX, (int) srcSkin.getHeight() * multipleY);
|
||||
PixelReader reader = srcSkin.getPixelReader();
|
||||
PixelWriter writer = newSkin.getPixelWriter();
|
||||
|
||||
for (int x = 0, lenX = (int) srcSkin.getWidth(); x < lenX; x++)
|
||||
for (int y = 0, lenY = (int) srcSkin.getHeight(); y < lenY; y++)
|
||||
for (int mx = 0; mx < multiple; mx++)
|
||||
for (int my = 0; my < multiple; my++) {
|
||||
for (int mx = 0; mx < multipleX; mx++)
|
||||
for (int my = 0; my < multipleY; my++) {
|
||||
int argb = reader.getArgb(x, y);
|
||||
writer.setArgb(x * multiple + mx, y * multiple + my, argb);
|
||||
writer.setArgb(x * multipleX + mx, y * multipleY + my, argb);
|
||||
}
|
||||
|
||||
return newSkin;
|
||||
|
||||
@@ -31,19 +31,14 @@ import org.jackhuang.hmcl.upgrade.UpdateHandler;
|
||||
import org.jackhuang.hmcl.util.CrashReporter;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.net.*;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
@@ -70,6 +65,16 @@ public final class Launcher extends Application {
|
||||
Main.showErrorAndExit(i18n("fatal.config_loading_failure", Paths.get("").toAbsolutePath().normalize()));
|
||||
}
|
||||
|
||||
if (Metadata.HMCL_DIRECTORY.toAbsolutePath().toString().indexOf('=') >= 0) {
|
||||
Main.showWarningAndContinue(i18n("fatal.illegal_char"));
|
||||
}
|
||||
|
||||
if (!Architecture.CURRENT_ARCH.isX86()
|
||||
&& !(OperatingSystem.CURRENT_OS == OperatingSystem.OSX && Architecture.CURRENT_ARCH == Architecture.ARM64)
|
||||
&& ConfigHolder.globalConfig().getAgreementVersion() < 1) {
|
||||
Main.showWarningAndContinue(i18n("fatal.unsupported_platform"));
|
||||
}
|
||||
|
||||
// runLater to ensure ConfigHolder.init() finished initialization
|
||||
Platform.runLater(() -> {
|
||||
// When launcher visibility is set to "hide and reopen" without Platform.implicitExit = false,
|
||||
@@ -154,34 +159,5 @@ public final class Launcher extends Application {
|
||||
});
|
||||
}
|
||||
|
||||
public static List<File> getCurrentJarFiles() {
|
||||
List<File> result = new LinkedList<>();
|
||||
if (Launcher.class.getClassLoader() instanceof URLClassLoader) {
|
||||
URL[] urls = ((URLClassLoader) Launcher.class.getClassLoader()).getURLs();
|
||||
for (URL u : urls)
|
||||
try {
|
||||
File f = new File(u.toURI());
|
||||
if (f.isFile() && (f.getName().endsWith(".exe") || f.getName().endsWith(".jar")))
|
||||
result.add(f);
|
||||
} catch (URISyntaxException e) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
File jarFile = new File(URLDecoder.decode(Launcher.class.getProtectionDomain().getCodeSource().getLocation().getPath(), "UTF-8"));
|
||||
String ext = FileUtils.getExtension(jarFile);
|
||||
if ("jar".equals(ext) || "exe".equals(ext))
|
||||
result.add(jarFile);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
LOG.log(Level.WARNING, "Failed to decode jar path", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (result.isEmpty())
|
||||
return null;
|
||||
else
|
||||
return result;
|
||||
}
|
||||
|
||||
public static final CrashReporter CRASH_REPORTER = new CrashReporter(true);
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl;
|
||||
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.SelfDependencyPatcher;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
@@ -97,7 +98,12 @@ public final class Main {
|
||||
showErrorAndExit(i18n("fatal.javafx.missing"));
|
||||
} catch (SelfDependencyPatcher.IncompatibleVersionException e) {
|
||||
LOG.log(Level.SEVERE, "unable to patch JVM", e);
|
||||
showErrorAndExit(i18n("fatal.javafx.incompatible"));
|
||||
if (Architecture.CURRENT_ARCH == Architecture.MIPS64EL
|
||||
|| Architecture.CURRENT_ARCH == Architecture.LOONGARCH64
|
||||
|| Architecture.CURRENT_ARCH == Architecture.LOONGARCH64_OW)
|
||||
showErrorAndExit(i18n("fatal.javafx.incompatible.loongson"));
|
||||
else
|
||||
showErrorAndExit(i18n("fatal.javafx.incompatible"));
|
||||
} catch (CancellationException e) {
|
||||
LOG.log(Level.SEVERE, "User cancels downloading JavaFX", e);
|
||||
System.exit(0);
|
||||
|
||||
@@ -38,7 +38,7 @@ public final class Metadata {
|
||||
public static final String FULL_TITLE = FULL_NAME + " v" + VERSION;
|
||||
|
||||
public static final String UPDATE_URL = System.getProperty("hmcl.update_source.override", "https://hmcl.huangyuhui.net/api/update_link");
|
||||
public static final String CONTACT_URL = "https://hmcl.huangyuhui.net/contact";
|
||||
public static final String CONTACT_URL = "https://github.com/huanghongxun/HMCL/issues";
|
||||
public static final String HELP_URL = "https://hmcl.huangyuhui.net/help";
|
||||
public static final String CHANGELOG_URL = "https://hmcl.huangyuhui.net/changelog/";
|
||||
public static final String PUBLISH_URL = "http://www.mcbbs.net/thread-142335-1-1.html";
|
||||
|
||||
@@ -20,7 +20,6 @@ package org.jackhuang.hmcl.game;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import javafx.scene.image.Image;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
@@ -28,8 +27,7 @@ import org.jackhuang.hmcl.event.Event;
|
||||
import org.jackhuang.hmcl.event.EventManager;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.mod.ModpackConfiguration;
|
||||
import org.jackhuang.hmcl.mod.mcbbs.McbbsModpackLocalInstallTask;
|
||||
import org.jackhuang.hmcl.mod.mcbbs.McbbsModpackManifest;
|
||||
import org.jackhuang.hmcl.mod.ModpackProvider;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.ProxyManager;
|
||||
import org.jackhuang.hmcl.setting.VersionIconType;
|
||||
@@ -328,7 +326,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
vs.setUsesGlobal(true);
|
||||
}
|
||||
|
||||
public LaunchOptions getLaunchOptions(String version, JavaVersion javaVersion, File gameDir) {
|
||||
public LaunchOptions getLaunchOptions(String version, JavaVersion javaVersion, File gameDir, List<String> javaAgents, boolean makeLaunchScript) {
|
||||
VersionSetting vs = getVersionSetting(version);
|
||||
|
||||
LaunchOptions.Builder builder = new LaunchOptions.Builder()
|
||||
@@ -357,8 +355,11 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
.setNativesDirType(vs.getNativesDirType())
|
||||
.setNativesDir(vs.getNativesDir())
|
||||
.setProcessPriority(vs.getProcessPriority())
|
||||
.setUseSoftwareRenderer(vs.isUseSoftwareRenderer())
|
||||
.setUseNativeGLFW(vs.isUseNativeGLFW())
|
||||
.setUseNativeOpenAL(vs.isUseNativeOpenAL());
|
||||
.setUseNativeOpenAL(vs.isUseNativeOpenAL())
|
||||
.setDaemon(!makeLaunchScript && vs.getLauncherVisibility().isDaemon())
|
||||
.setJavaAgents(javaAgents);
|
||||
if (config().hasProxy()) {
|
||||
builder.setProxy(ProxyManager.getProxy());
|
||||
if (config().hasProxyAuth()) {
|
||||
@@ -372,11 +373,8 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
try {
|
||||
String jsonText = FileUtils.readText(json);
|
||||
ModpackConfiguration<?> modpackConfiguration = JsonUtils.GSON.fromJson(jsonText, ModpackConfiguration.class);
|
||||
if (McbbsModpackLocalInstallTask.MODPACK_TYPE.equals(modpackConfiguration.getType())) {
|
||||
ModpackConfiguration<McbbsModpackManifest> config = JsonUtils.GSON.fromJson(FileUtils.readText(json), new TypeToken<ModpackConfiguration<McbbsModpackManifest>>() {
|
||||
}.getType());
|
||||
config.getManifest().injectLaunchOptions(builder);
|
||||
}
|
||||
ModpackProvider provider = ModpackHelper.getProviderByType(modpackConfiguration.getType());
|
||||
if (provider != null) provider.injectLaunchOptions(jsonText, builder);
|
||||
} catch (IOException | JsonParseException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@@ -452,6 +450,20 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
}
|
||||
|
||||
public static long getAllocatedMemory(long minimum, long available, boolean auto) {
|
||||
return auto ? Math.max(minimum, (long) (available * 0.8)) : minimum;
|
||||
if (auto) {
|
||||
available -= 256 * 1024 * 1024; // Reserve 256MB memory for off-heap memory and HMCL itself
|
||||
if (available <= 0) {
|
||||
return minimum;
|
||||
}
|
||||
|
||||
final long threshold = 8L * 1024 * 1024 * 1024;
|
||||
final long suggested = Math.min(available <= threshold
|
||||
? (long) (available * 0.8)
|
||||
: (long) (threshold * 0.8 + (available - threshold) * 0.2),
|
||||
32736L * 1024 * 1024); // Limit the maximum suggested memory to ensure that compressed oops are available
|
||||
return Math.max(minimum, suggested);
|
||||
} else {
|
||||
return minimum;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,8 @@ import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public final class HMCLModpackInstallTask extends Task<Void> {
|
||||
@@ -42,8 +43,8 @@ public final class HMCLModpackInstallTask extends Task<Void> {
|
||||
private final HMCLGameRepository repository;
|
||||
private final DefaultDependencyManager dependency;
|
||||
private final Modpack modpack;
|
||||
private final List<Task<?>> dependencies = new LinkedList<>();
|
||||
private final List<Task<?>> dependents = new LinkedList<>();
|
||||
private final List<Task<?>> dependencies = new ArrayList<>(1);
|
||||
private final List<Task<?>> dependents = new ArrayList<>(4);
|
||||
|
||||
public HMCLModpackInstallTask(Profile profile, File zipFile, Modpack modpack, String name) {
|
||||
dependency = profile.getDependency();
|
||||
@@ -69,13 +70,13 @@ public final class HMCLModpackInstallTask extends Task<Void> {
|
||||
config = JsonUtils.GSON.fromJson(FileUtils.readText(json), new TypeToken<ModpackConfiguration<Modpack>>() {
|
||||
}.getType());
|
||||
|
||||
if (!MODPACK_TYPE.equals(config.getType()))
|
||||
if (!HMCLModpackProvider.INSTANCE.getName().equals(config.getType()))
|
||||
throw new IllegalArgumentException("Version " + name + " is not a HMCL modpack. Cannot update this version.");
|
||||
}
|
||||
} catch (JsonParseException | IOException ignore) {
|
||||
}
|
||||
dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), "/minecraft", it -> !"pack.json".equals(it), config));
|
||||
dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), "/minecraft", modpack, MODPACK_TYPE, modpack.getName(), modpack.getVersion(), repository.getModpackConfiguration(name)).withStage("hmcl.modpack"));
|
||||
dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), Collections.singletonList("/minecraft"), it -> !"pack.json".equals(it), config));
|
||||
dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), Collections.singletonList("/minecraft"), modpack, HMCLModpackProvider.INSTANCE, modpack.getName(), modpack.getVersion(), repository.getModpackConfiguration(name)).withStage("hmcl.modpack"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -104,6 +105,4 @@ public final class HMCLModpackInstallTask extends Task<Void> {
|
||||
|
||||
dependencies.add(libraryTask.thenComposeAsync(repository::saveAsync));
|
||||
}
|
||||
|
||||
public static final String MODPACK_TYPE = "HMCL";
|
||||
}
|
||||
@@ -17,8 +17,16 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
public final class HMCLModpackManifest {
|
||||
import org.jackhuang.hmcl.mod.ModpackManifest;
|
||||
import org.jackhuang.hmcl.mod.ModpackProvider;
|
||||
|
||||
public final class HMCLModpackManifest implements ModpackManifest {
|
||||
public static final HMCLModpackManifest INSTANCE = new HMCLModpackManifest();
|
||||
|
||||
private HMCLModpackManifest() {}
|
||||
|
||||
@Override
|
||||
public ModpackProvider getProvider() {
|
||||
return HMCLModpackProvider.INSTANCE;
|
||||
}
|
||||
}
|
||||
+37
-18
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 huangyuhui <[email protected]> and contributors
|
||||
* Copyright (C) 2022 huangyuhui <[email protected]> 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
|
||||
@@ -18,8 +18,13 @@
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import org.apache.commons.compress.archivers.zip.ZipFile;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.mod.MismatchedModpackTypeException;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.mod.ModpackProvider;
|
||||
import org.jackhuang.hmcl.mod.ModpackUpdateTask;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
@@ -30,26 +35,39 @@ import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class HMCLModpackManager {
|
||||
private HMCLModpackManager() {
|
||||
public final class HMCLModpackProvider implements ModpackProvider {
|
||||
public static final HMCLModpackProvider INSTANCE = new HMCLModpackProvider();
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "HMCL";
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the manifest in a HMCL modpack.
|
||||
*
|
||||
* @param file a HMCL modpack file.
|
||||
* @param encoding encoding of modpack zip file.
|
||||
* @return the manifest of HMCL modpack.
|
||||
* @throws IOException if the file is not a valid zip file.
|
||||
* @throws JsonParseException if the manifest.json is missing or malformed.
|
||||
*/
|
||||
public static Modpack readHMCLModpackManifest(Path file, Charset encoding) throws IOException, JsonParseException {
|
||||
String manifestJson = CompressingUtils.readTextZipEntry(file, "modpack.json", encoding);
|
||||
@Override
|
||||
public Task<?> createCompletionTask(DefaultDependencyManager dependencyManager, String version) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<?> createUpdateTask(DefaultDependencyManager dependencyManager, String name, File zipFile, Modpack modpack) throws MismatchedModpackTypeException {
|
||||
if (!(modpack.getManifest() instanceof HMCLModpackManifest))
|
||||
throw new MismatchedModpackTypeException(getName(), modpack.getManifest().getProvider().getName());
|
||||
|
||||
if (!(dependencyManager.getGameRepository() instanceof HMCLGameRepository)) {
|
||||
throw new IllegalArgumentException("HMCLModpackProvider requires HMCLGameRepository");
|
||||
}
|
||||
|
||||
HMCLGameRepository repository = (HMCLGameRepository) dependencyManager.getGameRepository();
|
||||
Profile profile = repository.getProfile();
|
||||
|
||||
return new ModpackUpdateTask(dependencyManager.getGameRepository(), name, new HMCLModpackInstallTask(profile, zipFile, modpack, name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Modpack readManifest(ZipFile file, Path path, Charset encoding) throws IOException, JsonParseException {
|
||||
String manifestJson = CompressingUtils.readTextZipEntry(file, "modpack.json");
|
||||
Modpack manifest = JsonUtils.fromNonNullJson(manifestJson, HMCLModpack.class).setEncoding(encoding);
|
||||
String gameJson = CompressingUtils.readTextZipEntry(file, "minecraft/pack.json", encoding);
|
||||
String gameJson = CompressingUtils.readTextZipEntry(file, "minecraft/pack.json");
|
||||
Version game = JsonUtils.fromNonNullJson(gameJson, Version.class);
|
||||
if (game.getJar() == null)
|
||||
if (StringUtils.isBlank(manifest.getVersion()))
|
||||
@@ -67,4 +85,5 @@ public final class HMCLModpackManager {
|
||||
return new HMCLModpackInstallTask(((HMCLGameRepository) dependencyManager.getGameRepository()).getProfile(), zipFile, this, name);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,28 +20,19 @@ package org.jackhuang.hmcl.game;
|
||||
import javafx.application.Platform;
|
||||
import javafx.stage.Stage;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.auth.*;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDownloadException;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.MaintainTask;
|
||||
import org.jackhuang.hmcl.download.game.GameAssetIndexDownloadTask;
|
||||
import org.jackhuang.hmcl.download.game.GameVerificationFixTask;
|
||||
import org.jackhuang.hmcl.download.game.LibraryDownloadException;
|
||||
import org.jackhuang.hmcl.download.game.*;
|
||||
import org.jackhuang.hmcl.download.java.JavaRepository;
|
||||
import org.jackhuang.hmcl.launch.*;
|
||||
import org.jackhuang.hmcl.mod.ModpackCompletionException;
|
||||
import org.jackhuang.hmcl.mod.ModpackConfiguration;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseCompletionException;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseCompletionTask;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseInstallTask;
|
||||
import org.jackhuang.hmcl.mod.mcbbs.McbbsModpackCompletionTask;
|
||||
import org.jackhuang.hmcl.mod.mcbbs.McbbsModpackLocalInstallTask;
|
||||
import org.jackhuang.hmcl.mod.server.ServerModpackCompletionTask;
|
||||
import org.jackhuang.hmcl.mod.server.ServerModpackLocalInstallTask;
|
||||
import org.jackhuang.hmcl.setting.DownloadProviders;
|
||||
import org.jackhuang.hmcl.setting.LauncherVisibility;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
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.*;
|
||||
@@ -55,16 +46,22 @@ import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.AccessDeniedException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Lang.resolveException;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
@@ -90,8 +87,7 @@ public final class LauncherHelper {
|
||||
this.launchingStepsPane.setTitle(i18n("version.launch"));
|
||||
}
|
||||
|
||||
private final TaskExecutorDialogPane launchingStepsPane = new TaskExecutorDialogPane(it -> {
|
||||
});
|
||||
private final TaskExecutorDialogPane launchingStepsPane = new TaskExecutorDialogPane(TaskCancellationAction.NORMAL);
|
||||
|
||||
public void setTestMode() {
|
||||
launcherVisibility = LauncherVisibility.KEEP;
|
||||
@@ -120,45 +116,60 @@ public final class LauncherHelper {
|
||||
private void launch0() {
|
||||
HMCLGameRepository repository = profile.getRepository();
|
||||
DefaultDependencyManager dependencyManager = profile.getDependency();
|
||||
Version version = MaintainTask.maintain(repository, repository.getResolvedVersion(selectedVersion));
|
||||
Optional<String> gameVersion = repository.getGameVersion(version);
|
||||
AtomicReference<Version> version = new AtomicReference<>(MaintainTask.maintain(repository, repository.getResolvedVersion(selectedVersion)));
|
||||
Optional<String> gameVersion = repository.getGameVersion(version.get());
|
||||
boolean integrityCheck = repository.unmarkVersionLaunchedAbnormally(selectedVersion);
|
||||
CountDownLatch launchingLatch = new CountDownLatch(1);
|
||||
List<String> javaAgents = new ArrayList<>(0);
|
||||
|
||||
AtomicReference<JavaVersion> javaVersionRef = new AtomicReference<>();
|
||||
|
||||
TaskExecutor executor = checkGameState(profile, setting, version)
|
||||
getLog4jPatch(version.get()).ifPresent(javaAgents::add);
|
||||
|
||||
TaskExecutor executor = checkGameState(profile, setting, version.get())
|
||||
.thenComposeAsync(javaVersion -> {
|
||||
javaVersionRef.set(Objects.requireNonNull(javaVersion));
|
||||
return dependencyManager.checkPatchCompletionAsync(repository.getVersion(selectedVersion), integrityCheck);
|
||||
})
|
||||
.thenComposeAsync(Task.allOf(
|
||||
Task.composeAsync(() -> {
|
||||
if (setting.isNotCheckGame())
|
||||
return null;
|
||||
else
|
||||
return dependencyManager.checkGameCompletionAsync(version, integrityCheck);
|
||||
}), Task.composeAsync(() -> {
|
||||
if (setting.isNotCheckGame()) {
|
||||
return null;
|
||||
} else {
|
||||
version.set(NativePatcher.patchNative(version.get(), gameVersion.orElse(null), javaVersion, setting));
|
||||
if (setting.isNotCheckGame())
|
||||
return null;
|
||||
return Task.allOf(
|
||||
dependencyManager.checkGameCompletionAsync(version.get(), integrityCheck),
|
||||
Task.composeAsync(() -> {
|
||||
try {
|
||||
ModpackConfiguration<?> configuration = ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(selectedVersion));
|
||||
if (CurseInstallTask.MODPACK_TYPE.equals(configuration.getType()))
|
||||
return new CurseCompletionTask(dependencyManager, selectedVersion);
|
||||
else if (ServerModpackLocalInstallTask.MODPACK_TYPE.equals(configuration.getType()))
|
||||
return new ServerModpackCompletionTask(dependencyManager, selectedVersion);
|
||||
else if (McbbsModpackLocalInstallTask.MODPACK_TYPE.equals(configuration.getType()))
|
||||
return new McbbsModpackCompletionTask(dependencyManager, selectedVersion);
|
||||
else
|
||||
return null;
|
||||
ModpackProvider provider = ModpackHelper.getProviderByType(configuration.getType());
|
||||
if (provider == null) return null;
|
||||
else return provider.createCompletionTask(dependencyManager, selectedVersion);
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}))).withStage("launch.state.dependencies")
|
||||
}),
|
||||
Task.composeAsync(() -> {
|
||||
if (setting.isUseSoftwareRenderer() && OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
Library lib = NativePatcher.getSoftwareRendererLoader(javaVersion);
|
||||
if (lib == null)
|
||||
return null;
|
||||
File file = dependencyManager.getGameRepository().getLibraryFile(version.get(), lib);
|
||||
if (file.getAbsolutePath().indexOf('=') >= 0) {
|
||||
LOG.warning("Invalid character '=' in the libraries directory path, unable to attach software renderer loader");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (GameLibrariesTask.shouldDownloadLibrary(repository, version.get(), lib, integrityCheck)) {
|
||||
return new LibraryDownloadTask(dependencyManager, file, lib)
|
||||
.thenRunAsync(() -> javaAgents.add(file.getAbsolutePath()));
|
||||
} else {
|
||||
javaAgents.add(file.getAbsolutePath());
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
);
|
||||
}).withStage("launch.state.dependencies")
|
||||
.thenComposeAsync(() -> {
|
||||
return gameVersion.map(s -> new GameVerificationFixTask(dependencyManager, s, version)).orElse(null);
|
||||
return gameVersion.map(s -> new GameVerificationFixTask(dependencyManager, s, version.get())).orElse(null);
|
||||
})
|
||||
.thenComposeAsync(Task.supplyAsync(() -> {
|
||||
try {
|
||||
@@ -172,15 +183,15 @@ public final class LauncherHelper {
|
||||
}
|
||||
}).withStage("launch.state.logging_in"))
|
||||
.thenComposeAsync(authInfo -> Task.supplyAsync(() -> {
|
||||
LaunchOptions launchOptions = repository.getLaunchOptions(selectedVersion, javaVersionRef.get(), profile.getGameDir());
|
||||
LaunchOptions launchOptions = repository.getLaunchOptions(selectedVersion, javaVersionRef.get(), profile.getGameDir(), javaAgents, scriptFile != null);
|
||||
return new HMCLGameLauncher(
|
||||
repository,
|
||||
version,
|
||||
version.get(),
|
||||
authInfo,
|
||||
launchOptions,
|
||||
launcherVisibility == LauncherVisibility.CLOSE
|
||||
? null // Unnecessary to start listening to game process output when close launcher immediately after game launched.
|
||||
: new HMCLProcessListener(repository, version, authInfo, launchOptions, launchingLatch, gameVersion.isPresent())
|
||||
: new HMCLProcessListener(repository, version.get(), authInfo, launchOptions, launchingLatch, gameVersion.isPresent())
|
||||
);
|
||||
}).thenComposeAsync(launcher -> { // launcher is prev task's result
|
||||
if (scriptFile == null) {
|
||||
@@ -197,10 +208,10 @@ public final class LauncherHelper {
|
||||
if (launcherVisibility == LauncherVisibility.CLOSE)
|
||||
Launcher.stopApplication();
|
||||
else
|
||||
launchingStepsPane.setCancel(it -> {
|
||||
launchingStepsPane.setCancel(new TaskCancellationAction(it -> {
|
||||
process.stop();
|
||||
it.fireEvent(new DialogCloseEvent());
|
||||
});
|
||||
}));
|
||||
} else {
|
||||
Platform.runLater(() -> {
|
||||
launchingStepsPane.fireEvent(new DialogCloseEvent());
|
||||
@@ -230,7 +241,7 @@ public final class LauncherHelper {
|
||||
Exception ex = executor.getException();
|
||||
if (!(ex instanceof CancellationException)) {
|
||||
String message;
|
||||
if (ex instanceof CurseCompletionException) {
|
||||
if (ex instanceof ModpackCompletionException) {
|
||||
if (ex.getCause() instanceof FileNotFoundException)
|
||||
message = i18n("modpack.type.curse.not_found");
|
||||
else
|
||||
@@ -336,15 +347,32 @@ public final class LauncherHelper {
|
||||
Runnable continueAction = () -> future.complete(JavaVersion.fromCurrentEnvironment());
|
||||
|
||||
if (setting.isJavaAutoSelected()) {
|
||||
JavaVersionConstraint.VersionRanges range = JavaVersionConstraint.findSuitableJavaVersionRange(gameVersion, version);
|
||||
GameJavaVersion targetJavaVersion;
|
||||
GameJavaVersion targetJavaVersion = null;
|
||||
|
||||
if (range.getMandatory().contains(VersionNumber.asVersion("16"))) {
|
||||
targetJavaVersion = GameJavaVersion.JAVA_16;
|
||||
} else if (range.getMandatory().contains(VersionNumber.asVersion("1.8.0_51"))) {
|
||||
targetJavaVersion = GameJavaVersion.JAVA_8;
|
||||
} else {
|
||||
targetJavaVersion = null;
|
||||
if (org.jackhuang.hmcl.util.platform.Platform.isCompatibleWithX86Java()) {
|
||||
JavaVersionConstraint.VersionRanges range = JavaVersionConstraint.findSuitableJavaVersionRange(gameVersion, version);
|
||||
if (range.getMandatory().contains(VersionNumber.asVersion("17.0.1"))) {
|
||||
targetJavaVersion = GameJavaVersion.JAVA_17;
|
||||
} else if (range.getMandatory().contains(VersionNumber.asVersion("16.0.1"))) {
|
||||
targetJavaVersion = GameJavaVersion.JAVA_16;
|
||||
} else {
|
||||
String java8Version;
|
||||
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
java8Version = "1.8.0_51";
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX) {
|
||||
java8Version = "1.8.0_202";
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX) {
|
||||
java8Version = "1.8.0_74";
|
||||
} else {
|
||||
java8Version = null;
|
||||
}
|
||||
|
||||
if (java8Version != null && range.getMandatory().contains(VersionNumber.asVersion(java8Version)))
|
||||
targetJavaVersion = GameJavaVersion.JAVA_8;
|
||||
else
|
||||
targetJavaVersion = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (targetJavaVersion != null) {
|
||||
@@ -354,7 +382,9 @@ public final class LauncherHelper {
|
||||
})
|
||||
.exceptionally(throwable -> {
|
||||
LOG.log(Level.WARNING, "Failed to download java", throwable);
|
||||
Controllers.dialog(i18n("launch.failed.no_accepted_java"), i18n("message.warning"), MessageType.WARNING, continueAction);
|
||||
Controllers.confirm(i18n("launch.failed.no_accepted_java"), i18n("message.warning"), MessageType.WARNING, continueAction, () -> {
|
||||
future.completeExceptionally(new CancellationException("No accepted java"));
|
||||
});
|
||||
return null;
|
||||
});
|
||||
}
|
||||
@@ -413,51 +443,35 @@ public final class LauncherHelper {
|
||||
.thenAcceptAsync(downloadedJavaVersion -> {
|
||||
setting.setJavaVersion(downloadedJavaVersion);
|
||||
future.complete(downloadedJavaVersion);
|
||||
})
|
||||
.exceptionally(throwable -> {
|
||||
}, Schedulers.javafx())
|
||||
.whenCompleteAsync((result, throwable) -> {
|
||||
LOG.log(Level.WARNING, "Failed to download java", throwable);
|
||||
breakAction.run();
|
||||
return null;
|
||||
});
|
||||
}, Schedulers.javafx());
|
||||
return Task.fromCompletableFuture(future);
|
||||
case VANILLA_JAVA_16:
|
||||
Controllers.confirm(i18n("launch.advice.require_newer_java_version", gameVersion.toString(), 16), i18n("message.warning"), () -> {
|
||||
FXUtils.openLink("https://adoptium.net/?variant=openjdk17");
|
||||
FXUtils.openLink(OPENJDK_DOWNLOAD_LINK);
|
||||
}, breakAction);
|
||||
return null;
|
||||
return Task.fromCompletableFuture(future);
|
||||
case VANILLA_JAVA_17:
|
||||
Controllers.confirm(i18n("launch.advice.require_newer_java_version", gameVersion.toString(), 17), i18n("message.warning"), () -> {
|
||||
FXUtils.openLink("https://adoptium.net/?variant=openjdk17");
|
||||
FXUtils.openLink(OPENJDK_DOWNLOAD_LINK);
|
||||
}, breakAction);
|
||||
return null;
|
||||
return Task.fromCompletableFuture(future);
|
||||
case VANILLA_JAVA_8:
|
||||
Controllers.dialog(i18n("launch.advice.java8_1_13"), i18n("message.error"), MessageType.ERROR, breakAction);
|
||||
return null;
|
||||
return Task.fromCompletableFuture(future);
|
||||
case VANILLA_LINUX_JAVA_8:
|
||||
if (setting.getNativesDirType() == NativesDirectoryType.VERSION_FOLDER) {
|
||||
Controllers.dialog(i18n("launch.advice.vanilla_linux_java_8"), i18n("message.error"), MessageType.ERROR, breakAction);
|
||||
return null;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
case VANILLA_X86:
|
||||
if (setting.getNativesDirType() == NativesDirectoryType.VERSION_FOLDER) {
|
||||
if (Architecture.SYSTEM_ARCH == Architecture.ARM64) {
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX
|
||||
// Windows on ARM introduced translation support for x86-64 after 10.0.21277.
|
||||
|| (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS && OperatingSystem.SYSTEM_BUILD_NUMBER >= 21277)) {
|
||||
Controllers.dialog(i18n("launch.advice.vanilla_x86.translation"), i18n("message.error"), MessageType.ERROR, breakAction);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Controllers.dialog(i18n("launch.advice.vanilla_x86"), i18n("message.error"), MessageType.ERROR, breakAction);
|
||||
return null;
|
||||
return Task.fromCompletableFuture(future);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
case LAUNCH_WRAPPER:
|
||||
Controllers.dialog(i18n("launch.advice.java9") + "\n" + i18n("launch.advice.uncorrected"), i18n("message.error"), MessageType.ERROR, breakAction);
|
||||
return null;
|
||||
return Task.fromCompletableFuture(future);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -482,19 +496,33 @@ public final class LauncherHelper {
|
||||
switch (violatedSuggestedConstraint) {
|
||||
case MODDED_JAVA_7:
|
||||
Controllers.dialog(i18n("launch.advice.java.modded_java_7"), i18n("message.warning"), MessageType.WARNING, continueAction);
|
||||
return null;
|
||||
break;
|
||||
case MODDED_JAVA_8:
|
||||
Controllers.dialog(i18n("launch.advice.newer_java"), i18n("message.warning"), MessageType.WARNING, continueAction);
|
||||
break;
|
||||
case MODDED_JAVA_16:
|
||||
Controllers.dialog(i18n("launch.advice.forge37_0_60"), i18n("message.warning"), MessageType.WARNING, continueAction);
|
||||
break;
|
||||
case VANILLA_JAVA_8_51:
|
||||
Controllers.dialog(i18n("launch.advice.java8_51_1_13"), i18n("message.warning"), MessageType.WARNING, continueAction);
|
||||
break;
|
||||
case MODLAUNCHER_8:
|
||||
Controllers.dialog(i18n("launch.advice.modlauncher8"), i18n("message.warning"), MessageType.WARNING, continueAction);
|
||||
break;
|
||||
case VANILLA_X86:
|
||||
if (setting.getNativesDirType() == NativesDirectoryType.VERSION_FOLDER
|
||||
&& org.jackhuang.hmcl.util.platform.Platform.isCompatibleWithX86Java()) {
|
||||
Controllers.dialog(i18n("launch.advice.vanilla_x86.translation"), i18n("message.warning"), MessageType.WARNING, continueAction);
|
||||
} else {
|
||||
continueAction.run();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Cannot allocate too much memory exceeding free space.
|
||||
if (!suggested && OperatingSystem.TOTAL_MEMORY > 0 && OperatingSystem.TOTAL_MEMORY < setting.getMaxMemory()) {
|
||||
Controllers.confirm(i18n("launch.advice.not_enough_space", OperatingSystem.TOTAL_MEMORY), i18n("message.error"), continueAction, null);
|
||||
Controllers.confirm(i18n("launch.advice.not_enough_space", OperatingSystem.TOTAL_MEMORY), i18n("message.error"), continueAction, breakAction);
|
||||
suggested = true;
|
||||
}
|
||||
|
||||
@@ -506,7 +534,7 @@ public final class LauncherHelper {
|
||||
VersionNumber.VERSION_COMPARATOR.compare(it.getVersion(), "1.12.2-14.23.5.2773") < 0);
|
||||
boolean hasLiteLoader = version.getLibraries().stream().anyMatch(it -> it.is("com.mumfrey", "liteloader"));
|
||||
if (hasForge2760 && hasLiteLoader && gameVersion.compareTo(VersionNumber.asVersion("1.12.2")) == 0) {
|
||||
Controllers.confirm(i18n("launch.advice.forge2760_liteloader"), i18n("message.error"), continueAction, null);
|
||||
Controllers.confirm(i18n("launch.advice.forge2760_liteloader"), i18n("message.error"), continueAction, breakAction);
|
||||
suggested = true;
|
||||
}
|
||||
}
|
||||
@@ -518,12 +546,12 @@ public final class LauncherHelper {
|
||||
VersionNumber.VERSION_COMPARATOR.compare("1.14.4-28.2.2", it.getVersion()) <= 0);
|
||||
boolean hasOptiFine = version.getLibraries().stream().anyMatch(it -> it.is("optifine", "OptiFine"));
|
||||
if (hasForge28_2_2 && hasOptiFine && gameVersion.compareTo(VersionNumber.asVersion("1.14.4")) == 0) {
|
||||
Controllers.confirm(i18n("launch.advice.forge28_2_2_optifine"), i18n("message.error"), continueAction, null);
|
||||
Controllers.confirm(i18n("launch.advice.forge28_2_2_optifine"), i18n("message.error"), continueAction, breakAction);
|
||||
suggested = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!suggested) {
|
||||
if (!suggested && !future.isDone()) {
|
||||
future.complete(javaVersion);
|
||||
}
|
||||
|
||||
@@ -536,7 +564,7 @@ public final class LauncherHelper {
|
||||
|
||||
JFXHyperlink link = new JFXHyperlink(i18n("download.external_link"));
|
||||
link.setOnAction(e -> {
|
||||
FXUtils.openLink("https://docs.microsoft.com/zh-cn/java/openjdk/download");
|
||||
FXUtils.openLink(OPENJDK_DOWNLOAD_LINK);
|
||||
future.completeExceptionally(new CancellationException());
|
||||
});
|
||||
|
||||
@@ -578,26 +606,59 @@ public final class LauncherHelper {
|
||||
private static CompletableFuture<JavaVersion> downloadJavaImpl(GameJavaVersion javaVersion, DownloadProvider downloadProvider) {
|
||||
CompletableFuture<JavaVersion> future = new CompletableFuture<>();
|
||||
|
||||
TaskExecutorDialogPane javaDownloadingPane = new TaskExecutorDialogPane(it -> {
|
||||
});
|
||||
|
||||
TaskExecutor executor = JavaRepository.downloadJava(javaVersion, downloadProvider)
|
||||
Controllers.taskDialog(JavaRepository.downloadJava(javaVersion, downloadProvider)
|
||||
.whenComplete(Schedulers.javafx(), (downloadedJava, exception) -> {
|
||||
if (exception != null) {
|
||||
future.completeExceptionally(exception);
|
||||
} else {
|
||||
future.complete(downloadedJava);
|
||||
}
|
||||
})
|
||||
.executor(false);
|
||||
|
||||
javaDownloadingPane.setExecutor(executor, true);
|
||||
Controllers.dialog(javaDownloadingPane);
|
||||
executor.start();
|
||||
}), i18n("download.java"), TaskCancellationAction.NORMAL);
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
private static Optional<String> getLog4jPatch(Version version) {
|
||||
Optional<String> log4jVersion = version.getLibraries().stream()
|
||||
.filter(it -> it.is("org.apache.logging.log4j", "log4j-core")
|
||||
&& (VersionNumber.VERSION_COMPARATOR.compare(it.getVersion(), "2.17") < 0 || "2.0-beta9".equals(it.getVersion())))
|
||||
.map(Library::getVersion)
|
||||
.findFirst();
|
||||
|
||||
if (log4jVersion.isPresent()) {
|
||||
final String agentFileName = "log4j-patch-agent-1.0.jar";
|
||||
|
||||
Path agentFile = Metadata.HMCL_DIRECTORY.resolve(agentFileName).toAbsolutePath();
|
||||
String agentFilePath = agentFile.toString();
|
||||
if (agentFilePath.indexOf('=') >= 0) {
|
||||
LOG.warning("Invalid character '=' in the HMCL directory path, unable to attach log4j-patch");
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
if (Files.notExists(agentFile)) {
|
||||
try (InputStream input = DefaultLauncher.class.getResourceAsStream("/assets/game/" + agentFileName)) {
|
||||
LOG.info("Extract log4j patch to " + agentFilePath);
|
||||
Files.createDirectories(agentFile.getParent());
|
||||
Files.copy(input, agentFile, StandardCopyOption.REPLACE_EXISTING);
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to extract log4j patch");
|
||||
try {
|
||||
Files.deleteIfExists(agentFile);
|
||||
} catch (IOException ex) {
|
||||
LOG.log(Level.WARNING, "Failed to delete incomplete log4j patch", ex);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
boolean isBeta = log4jVersion.get().startsWith("2.0-beta");
|
||||
return Optional.of(agentFilePath + "=" + isBeta);
|
||||
} else {
|
||||
LOG.info("No log4j with security vulnerabilities found");
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private void checkExit() {
|
||||
switch (launcherVisibility) {
|
||||
case HIDE_AND_REOPEN:
|
||||
@@ -631,7 +692,6 @@ public final class LauncherHelper {
|
||||
|
||||
private final HMCLGameRepository repository;
|
||||
private final Version version;
|
||||
private final Map<String, String> forbiddenTokens;
|
||||
private final LaunchOptions launchOptions;
|
||||
private ManagedProcess process;
|
||||
private boolean lwjgl;
|
||||
@@ -648,13 +708,6 @@ public final class LauncherHelper {
|
||||
this.launchingLatch = launchingLatch;
|
||||
this.detectWindow = detectWindow;
|
||||
|
||||
if (authInfo == null)
|
||||
forbiddenTokens = Collections.emptyMap();
|
||||
else
|
||||
forbiddenTokens = mapOf(
|
||||
pair(authInfo.getAccessToken(), "<access token>")
|
||||
);
|
||||
|
||||
logs = new LinkedList<>();
|
||||
}
|
||||
|
||||
@@ -663,8 +716,6 @@ public final class LauncherHelper {
|
||||
this.process = process;
|
||||
|
||||
String command = new CommandBuilder().addAll(process.getCommands()).toString();
|
||||
for (Map.Entry<String, String> entry : forbiddenTokens.entrySet())
|
||||
command = command.replace(entry.getKey(), entry.getValue());
|
||||
|
||||
LOG.info("Launched process: " + command);
|
||||
|
||||
@@ -716,10 +767,7 @@ public final class LauncherHelper {
|
||||
|
||||
@Override
|
||||
public synchronized void onLog(String log, Log4jLevel level) {
|
||||
String newLog = log;
|
||||
for (Map.Entry<String, String> entry : forbiddenTokens.entrySet())
|
||||
newLog = newLog.replace(entry.getKey(), entry.getValue());
|
||||
String filteredLog = newLog;
|
||||
String filteredLog = Logging.filterForbiddenToken(log);
|
||||
|
||||
if (level.lessOrEqual(Log4jLevel.ERROR))
|
||||
System.err.println(filteredLog);
|
||||
@@ -767,6 +815,8 @@ public final class LauncherHelper {
|
||||
|
||||
}
|
||||
|
||||
private static final String OPENJDK_DOWNLOAD_LINK = "https://docs.microsoft.com/java/openjdk/download";
|
||||
|
||||
public static final Queue<ManagedProcess> PROCESSES = new ConcurrentLinkedQueue<>();
|
||||
|
||||
public static void stopManagedProcesses() {
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2022 huangyuhui <[email protected]> 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.game;
|
||||
|
||||
import org.jackhuang.hmcl.mod.LocalModFile;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
import org.jackhuang.hmcl.mod.RemoteModRepository;
|
||||
import org.jackhuang.hmcl.ui.versions.ModTranslations;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public abstract class LocalizedRemoteModRepository implements RemoteModRepository {
|
||||
|
||||
protected abstract RemoteModRepository getBackedRemoteModRepository();
|
||||
|
||||
@Override
|
||||
public Stream<RemoteMod> search(String gameVersion, Category category, int pageOffset, int pageSize, String searchFilter, SortType sort, SortOrder sortOrder) throws IOException {
|
||||
String newSearchFilter;
|
||||
if (StringUtils.CHINESE_PATTERN.matcher(searchFilter).find()) {
|
||||
ModTranslations modTranslations = ModTranslations.getTranslationsByRepositoryType(getType());
|
||||
List<ModTranslations.Mod> mods = modTranslations.searchMod(searchFilter);
|
||||
List<String> searchFilters = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (ModTranslations.Mod mod : mods) {
|
||||
String englishName = mod.getName();
|
||||
if (StringUtils.isNotBlank(mod.getSubname())) {
|
||||
englishName = mod.getSubname();
|
||||
}
|
||||
|
||||
searchFilters.add(englishName);
|
||||
|
||||
count++;
|
||||
if (count >= 3) break;
|
||||
}
|
||||
newSearchFilter = String.join(" ", searchFilters);
|
||||
} else {
|
||||
newSearchFilter = searchFilter;
|
||||
}
|
||||
|
||||
return getBackedRemoteModRepository().search(gameVersion, category, pageOffset, pageSize, newSearchFilter, sort, sortOrder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Category> getCategories() throws IOException {
|
||||
return getBackedRemoteModRepository().getCategories();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<RemoteMod.Version> getRemoteVersionByLocalFile(LocalModFile localModFile, Path file) throws IOException {
|
||||
return getBackedRemoteModRepository().getRemoteVersionByLocalFile(localModFile, file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoteMod getModById(String id) throws IOException {
|
||||
return getBackedRemoteModRepository().getModById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoteMod.File getModFile(String modId, String fileId) throws IOException {
|
||||
return getBackedRemoteModRepository().getModFile(modId, fileId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<RemoteMod.Version> getRemoteVersionsById(String id) throws IOException {
|
||||
return getBackedRemoteModRepository().getRemoteVersionsById(id);
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.Zipper;
|
||||
@@ -66,7 +65,7 @@ public final class LogExporter {
|
||||
}
|
||||
zipper.putTextFile(Logging.getLogs(), "hmcl.log");
|
||||
zipper.putTextFile(logs, "minecraft.log");
|
||||
zipper.putTextFile(launchScript, OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS ? "launch.bat" : "launch.sh");
|
||||
zipper.putTextFile(Logging.filterForbiddenToken(launchScript), OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS ? "launch.bat" : "launch.sh");
|
||||
|
||||
for (String id : versions) {
|
||||
Path versionJson = baseDirectory.resolve("versions").resolve(id).resolve(id + ".json");
|
||||
@@ -77,6 +76,6 @@ public final class LogExporter {
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}, Schedulers.io());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -19,16 +19,16 @@ package org.jackhuang.hmcl.game;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.apache.commons.compress.archivers.zip.ZipFile;
|
||||
import org.jackhuang.hmcl.mod.*;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseCompletionException;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseInstallTask;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseManifest;
|
||||
import org.jackhuang.hmcl.mod.mcbbs.McbbsModpackLocalInstallTask;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseModpackProvider;
|
||||
import org.jackhuang.hmcl.mod.mcbbs.McbbsModpackManifest;
|
||||
import org.jackhuang.hmcl.mod.mcbbs.McbbsModpackProvider;
|
||||
import org.jackhuang.hmcl.mod.modrinth.ModrinthModpackProvider;
|
||||
import org.jackhuang.hmcl.mod.multimc.MultiMCInstanceConfiguration;
|
||||
import org.jackhuang.hmcl.mod.multimc.MultiMCModpackInstallTask;
|
||||
import org.jackhuang.hmcl.mod.server.ServerModpackLocalInstallTask;
|
||||
import org.jackhuang.hmcl.mod.multimc.MultiMCModpackProvider;
|
||||
import org.jackhuang.hmcl.mod.server.ServerModpackManifest;
|
||||
import org.jackhuang.hmcl.mod.server.ServerModpackProvider;
|
||||
import org.jackhuang.hmcl.mod.server.ServerModpackRemoteInstallTask;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
@@ -41,6 +41,7 @@ import org.jackhuang.hmcl.util.function.ExceptionalRunnable;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
@@ -51,44 +52,54 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Lang.toIterable;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
public final class ModpackHelper {
|
||||
private ModpackHelper() {}
|
||||
|
||||
private static final Map<String, ModpackProvider> providers = mapOf(
|
||||
pair(CurseModpackProvider.INSTANCE.getName(), CurseModpackProvider.INSTANCE),
|
||||
pair(McbbsModpackProvider.INSTANCE.getName(), McbbsModpackProvider.INSTANCE),
|
||||
pair(ModrinthModpackProvider.INSTANCE.getName(), ModrinthModpackProvider.INSTANCE),
|
||||
pair(MultiMCModpackProvider.INSTANCE.getName(), MultiMCModpackProvider.INSTANCE),
|
||||
pair(ServerModpackProvider.INSTANCE.getName(), ServerModpackProvider.INSTANCE),
|
||||
pair(HMCLModpackProvider.INSTANCE.getName(), HMCLModpackProvider.INSTANCE)
|
||||
);
|
||||
|
||||
@Nullable
|
||||
public static ModpackProvider getProviderByType(String type) {
|
||||
return providers.get(type);
|
||||
}
|
||||
|
||||
public static boolean isFileModpackByExtension(File file) {
|
||||
String ext = FileUtils.getExtension(file);
|
||||
return "zip".equals(ext) || "mrpack".equals(ext);
|
||||
}
|
||||
|
||||
public static Modpack readModpackManifest(Path file, Charset charset) throws UnsupportedModpackException, ManuallyCreatedModpackException {
|
||||
try {
|
||||
return McbbsModpackManifest.readManifest(file, charset);
|
||||
} catch (Exception ignored) {
|
||||
// ignore it, not a valid MCBBS modpack.
|
||||
}
|
||||
|
||||
try {
|
||||
return CurseManifest.readCurseForgeModpackManifest(file, charset);
|
||||
} catch (Exception e) {
|
||||
// ignore it, not a valid CurseForge modpack.
|
||||
}
|
||||
|
||||
try {
|
||||
return HMCLModpackManager.readHMCLModpackManifest(file, charset);
|
||||
} catch (Exception e) {
|
||||
// ignore it, not a valid HMCL modpack.
|
||||
}
|
||||
|
||||
try {
|
||||
return MultiMCInstanceConfiguration.readMultiMCModpackManifest(file, charset);
|
||||
} catch (Exception e) {
|
||||
// ignore it, not a valid MultiMC modpack.
|
||||
}
|
||||
|
||||
try {
|
||||
return ServerModpackManifest.readManifest(file, charset);
|
||||
} catch (Exception e) {
|
||||
// ignore it, not a valid Server modpack.
|
||||
try (ZipFile zipFile = CompressingUtils.openZipFile(file, charset)) {
|
||||
// Order for trying detecting manifest is necessary here.
|
||||
// Do not change to iterating providers.
|
||||
for (ModpackProvider provider : new ModpackProvider[]{
|
||||
McbbsModpackProvider.INSTANCE,
|
||||
CurseModpackProvider.INSTANCE,
|
||||
ModrinthModpackProvider.INSTANCE,
|
||||
HMCLModpackProvider.INSTANCE,
|
||||
MultiMCModpackProvider.INSTANCE,
|
||||
ServerModpackProvider.INSTANCE}) {
|
||||
try {
|
||||
return provider.readManifest(zipFile, file, charset);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
|
||||
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(file, charset)) {
|
||||
@@ -137,17 +148,6 @@ public final class ModpackHelper {
|
||||
}
|
||||
}
|
||||
|
||||
private static String getManifestType(Object manifest) throws UnsupportedModpackException {
|
||||
if (manifest instanceof HMCLModpackManifest)
|
||||
return HMCLModpackInstallTask.MODPACK_TYPE;
|
||||
else if (manifest instanceof MultiMCInstanceConfiguration)
|
||||
return MultiMCModpackInstallTask.MODPACK_TYPE;
|
||||
else if (manifest instanceof CurseManifest)
|
||||
return CurseInstallTask.MODPACK_TYPE;
|
||||
else
|
||||
throw new UnsupportedModpackException();
|
||||
}
|
||||
|
||||
public static Task<?> getInstallTask(Profile profile, ServerModpackManifest manifest, String name, Modpack modpack) {
|
||||
profile.getRepository().markVersionAsModpack(name);
|
||||
|
||||
@@ -161,7 +161,7 @@ public final class ModpackHelper {
|
||||
};
|
||||
|
||||
ExceptionalConsumer<Exception, ?> failure = ex -> {
|
||||
if (ex instanceof CurseCompletionException && !(ex.getCause() instanceof FileNotFoundException)) {
|
||||
if (ex instanceof ModpackCompletionException && !(ex.getCause() instanceof FileNotFoundException)) {
|
||||
success.run();
|
||||
// This is tolerable and we will not delete the game
|
||||
}
|
||||
@@ -203,7 +203,7 @@ public final class ModpackHelper {
|
||||
};
|
||||
|
||||
ExceptionalConsumer<Exception, ?> failure = ex -> {
|
||||
if (ex instanceof CurseCompletionException && !(ex.getCause() instanceof FileNotFoundException)) {
|
||||
if (ex instanceof ModpackCompletionException && !(ex.getCause() instanceof FileNotFoundException)) {
|
||||
success.run();
|
||||
// This is tolerable and we will not delete the game
|
||||
}
|
||||
@@ -232,38 +232,13 @@ public final class ModpackHelper {
|
||||
}
|
||||
}
|
||||
|
||||
public static Task<Void> getUpdateTask(Profile profile, File zipFile, Charset charset, String name, ModpackConfiguration<?> configuration) throws UnsupportedModpackException, ManuallyCreatedModpackException, MismatchedModpackTypeException {
|
||||
public static Task<?> getUpdateTask(Profile profile, File zipFile, Charset charset, String name, ModpackConfiguration<?> configuration) throws UnsupportedModpackException, ManuallyCreatedModpackException, MismatchedModpackTypeException {
|
||||
Modpack modpack = ModpackHelper.readModpackManifest(zipFile.toPath(), charset);
|
||||
|
||||
switch (configuration.getType()) {
|
||||
case CurseInstallTask.MODPACK_TYPE:
|
||||
if (!(modpack.getManifest() instanceof CurseManifest))
|
||||
throw new MismatchedModpackTypeException(CurseInstallTask.MODPACK_TYPE, getManifestType(modpack.getManifest()));
|
||||
|
||||
return new ModpackUpdateTask(profile.getRepository(), name, new CurseInstallTask(profile.getDependency(), zipFile, modpack, (CurseManifest) modpack.getManifest(), name));
|
||||
case MultiMCModpackInstallTask.MODPACK_TYPE:
|
||||
if (!(modpack.getManifest() instanceof MultiMCInstanceConfiguration))
|
||||
throw new MismatchedModpackTypeException(MultiMCModpackInstallTask.MODPACK_TYPE, getManifestType(modpack.getManifest()));
|
||||
|
||||
return new ModpackUpdateTask(profile.getRepository(), name, new MultiMCModpackInstallTask(profile.getDependency(), zipFile, modpack, (MultiMCInstanceConfiguration) modpack.getManifest(), name));
|
||||
case HMCLModpackInstallTask.MODPACK_TYPE:
|
||||
if (!(modpack.getManifest() instanceof HMCLModpackManifest))
|
||||
throw new MismatchedModpackTypeException(HMCLModpackInstallTask.MODPACK_TYPE, getManifestType(modpack.getManifest()));
|
||||
|
||||
return new ModpackUpdateTask(profile.getRepository(), name, new HMCLModpackInstallTask(profile, zipFile, modpack, name));
|
||||
case McbbsModpackLocalInstallTask.MODPACK_TYPE:
|
||||
if (!(modpack.getManifest() instanceof McbbsModpackManifest))
|
||||
throw new MismatchedModpackTypeException(McbbsModpackLocalInstallTask.MODPACK_TYPE, getManifestType(modpack.getManifest()));
|
||||
|
||||
return new ModpackUpdateTask(profile.getRepository(), name, new McbbsModpackLocalInstallTask(profile.getDependency(), zipFile, modpack, (McbbsModpackManifest) modpack.getManifest(), name));
|
||||
case ServerModpackLocalInstallTask.MODPACK_TYPE:
|
||||
if (!(modpack.getManifest() instanceof ServerModpackManifest))
|
||||
throw new MismatchedModpackTypeException(ServerModpackLocalInstallTask.MODPACK_TYPE, getManifestType(modpack.getManifest()));
|
||||
|
||||
return new ModpackUpdateTask(profile.getRepository(), name, new ServerModpackLocalInstallTask(profile.getDependency(), zipFile, modpack, (ServerModpackManifest) modpack.getManifest(), name));
|
||||
default:
|
||||
throw new UnsupportedModpackException();
|
||||
ModpackProvider provider = getProviderByType(configuration.getType());
|
||||
if (provider == null) {
|
||||
throw new UnsupportedModpackException();
|
||||
}
|
||||
return provider.createUpdateTask(profile.getDependency(), name, zipFile, modpack);
|
||||
}
|
||||
|
||||
public static void toVersionSetting(MultiMCInstanceConfiguration c, VersionSetting vs) {
|
||||
|
||||
@@ -81,6 +81,7 @@ public final class Accounts {
|
||||
public static final YggdrasilAccountFactory FACTORY_MOJANG = YggdrasilAccountFactory.MOJANG;
|
||||
public static final AuthlibInjectorAccountFactory FACTORY_AUTHLIB_INJECTOR = new AuthlibInjectorAccountFactory(AUTHLIB_INJECTOR_DOWNLOADER, Accounts::getOrCreateAuthlibInjectorServer);
|
||||
public static final MicrosoftAccountFactory FACTORY_MICROSOFT = new MicrosoftAccountFactory(new MicrosoftService(OAUTH_CALLBACK));
|
||||
public static final BoundAuthlibInjectorAccountFactory FACTORY_LITTLE_SKIN = getAccountFactoryByAuthlibInjectorServer(new AuthlibInjectorServer("https://littleskin.cn/api/yggdrasil/"));
|
||||
public static final List<AccountFactory<?>> FACTORIES = immutableListOf(FACTORY_OFFLINE, FACTORY_MOJANG, FACTORY_MICROSOFT, FACTORY_AUTHLIB_INJECTOR);
|
||||
|
||||
// ==== login type / account factory mapping ====
|
||||
@@ -96,14 +97,24 @@ public final class Accounts {
|
||||
}
|
||||
|
||||
public static String getLoginType(AccountFactory<?> factory) {
|
||||
return Optional.ofNullable(factory2type.get(factory))
|
||||
.orElseThrow(() -> new IllegalArgumentException("Unrecognized account factory"));
|
||||
String type = factory2type.get(factory);
|
||||
if (type != null) return type;
|
||||
|
||||
if (factory instanceof BoundAuthlibInjectorAccountFactory) {
|
||||
return factory2type.get(FACTORY_AUTHLIB_INJECTOR);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Unrecognized account factory");
|
||||
}
|
||||
|
||||
public static AccountFactory<?> getAccountFactory(String loginType) {
|
||||
return Optional.ofNullable(type2factory.get(loginType))
|
||||
.orElseThrow(() -> new IllegalArgumentException("Unrecognized login type"));
|
||||
}
|
||||
|
||||
public static BoundAuthlibInjectorAccountFactory getAccountFactoryByAuthlibInjectorServer(AuthlibInjectorServer server) {
|
||||
return new BoundAuthlibInjectorAccountFactory(AUTHLIB_INJECTOR_DOWNLOADER, server);
|
||||
}
|
||||
// ====
|
||||
|
||||
public static AccountFactory<?> getAccountFactory(Account account) {
|
||||
@@ -119,10 +130,10 @@ public final class Accounts {
|
||||
throw new IllegalArgumentException("Failed to determine account type: " + account);
|
||||
}
|
||||
|
||||
private static ObservableList<Account> accounts = observableArrayList(account -> new Observable[] { account });
|
||||
private static ReadOnlyListWrapper<Account> accountsWrapper = new ReadOnlyListWrapper<>(Accounts.class, "accounts", accounts);
|
||||
private static final ObservableList<Account> accounts = observableArrayList(account -> new Observable[] { account });
|
||||
private static final ReadOnlyListWrapper<Account> accountsWrapper = new ReadOnlyListWrapper<>(Accounts.class, "accounts", accounts);
|
||||
|
||||
private static ObjectProperty<Account> selectedAccount = new SimpleObjectProperty<Account>(Accounts.class, "selectedAccount") {
|
||||
private static final ObjectProperty<Account> selectedAccount = new SimpleObjectProperty<Account>(Accounts.class, "selectedAccount") {
|
||||
{
|
||||
accounts.addListener(onInvalidating(this::invalidated));
|
||||
}
|
||||
@@ -231,6 +242,14 @@ public final class Accounts {
|
||||
triggerAuthlibInjectorUpdateCheck();
|
||||
}
|
||||
|
||||
Schedulers.io().execute(() -> {
|
||||
try {
|
||||
FACTORY_LITTLE_SKIN.getServer().fetchMetadataResponse();
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to fetch authlib-injector server metdata: " + FACTORY_LITTLE_SKIN.getServer(), e);
|
||||
}
|
||||
});
|
||||
|
||||
for (AuthlibInjectorServer server : config().getAuthlibInjectorServers()) {
|
||||
if (selected instanceof AuthlibInjectorAccount && ((AuthlibInjectorAccount) selected).getServer() == server)
|
||||
continue;
|
||||
@@ -313,7 +332,7 @@ public final class Accounts {
|
||||
// ====
|
||||
|
||||
// ==== Login type name i18n ===
|
||||
private static Map<AccountFactory<?>, String> unlocalizedLoginTypeNames = mapOf(
|
||||
private static final Map<AccountFactory<?>, String> unlocalizedLoginTypeNames = mapOf(
|
||||
pair(Accounts.FACTORY_OFFLINE, "account.methods.offline"),
|
||||
pair(Accounts.FACTORY_MOJANG, "account.methods.yggdrasil"),
|
||||
pair(Accounts.FACTORY_AUTHLIB_INJECTOR, "account.methods.authlib_injector"),
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.jackhuang.hmcl.util.i18n.Locales;
|
||||
import org.jackhuang.hmcl.util.i18n.Locales.SupportedLocale;
|
||||
import org.jackhuang.hmcl.util.javafx.ObservableHelper;
|
||||
import org.jackhuang.hmcl.util.javafx.PropertyUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
@@ -146,7 +147,7 @@ public final class Config implements Cloneable, Observable {
|
||||
private ObservableList<Map<Object, Object>> accountStorages = FXCollections.observableArrayList();
|
||||
|
||||
@SerializedName("fontFamily")
|
||||
private StringProperty fontFamily = new SimpleStringProperty("Consolas");
|
||||
private StringProperty fontFamily = new SimpleStringProperty(OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS ? "Consolas" : "Monospace");
|
||||
|
||||
@SerializedName("fontSize")
|
||||
private DoubleProperty fontSize = new SimpleDoubleProperty(12);
|
||||
|
||||
@@ -30,7 +30,6 @@ import java.nio.file.*;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
public final class ConfigHolder {
|
||||
@@ -175,7 +174,7 @@ public final class ConfigHolder {
|
||||
private static void writeToConfig(String content) throws IOException {
|
||||
LOG.info("Saving config");
|
||||
synchronized (configLocation) {
|
||||
Files.write(configLocation, content.getBytes(UTF_8));
|
||||
FileUtils.saveSafely(configLocation, content);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +218,7 @@ public final class ConfigHolder {
|
||||
private static void writeToGlobalConfig(String content) throws IOException {
|
||||
LOG.info("Saving global config");
|
||||
synchronized (GLOBAL_CONFIG_PATH) {
|
||||
Files.write(GLOBAL_CONFIG_PATH, content.getBytes(UTF_8));
|
||||
FileUtils.saveSafely(GLOBAL_CONFIG_PATH, content);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -153,6 +153,7 @@ public class GlobalConfig implements Cloneable, Observable {
|
||||
private static final Set<String> knownFields = new HashSet<>(Arrays.asList(
|
||||
"agreementVersion",
|
||||
"multiplayerToken",
|
||||
"multiplayerRelay",
|
||||
"multiplayerAgreementVersion"
|
||||
));
|
||||
|
||||
|
||||
@@ -43,5 +43,9 @@ public enum LauncherVisibility {
|
||||
/**
|
||||
* Hide the launcher and reopen it when game closes.
|
||||
*/
|
||||
HIDE_AND_REOPEN
|
||||
HIDE_AND_REOPEN;
|
||||
|
||||
public boolean isDaemon() {
|
||||
return this != CLOSE;
|
||||
}
|
||||
}
|
||||
@@ -26,8 +26,8 @@ import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -204,7 +204,7 @@ public final class Profiles {
|
||||
return selectedVersion.get();
|
||||
}
|
||||
|
||||
private static final List<Consumer<Profile>> versionsListeners = new LinkedList<>();
|
||||
private static final List<Consumer<Profile>> versionsListeners = new ArrayList<>(4);
|
||||
|
||||
public static void registerVersionsListener(Consumer<Profile> listener) {
|
||||
Profile profile = getSelectedProfile();
|
||||
|
||||
@@ -83,14 +83,7 @@ public class Theme {
|
||||
public String[] getStylesheets(String overrideFontFamily) {
|
||||
Color textFill = getForegroundColor();
|
||||
|
||||
String fontFamily;
|
||||
if (System.getProperty("hmcl.font.override") != null) {
|
||||
fontFamily = System.getProperty("hmcl.font.override");
|
||||
} else if (overrideFontFamily != null) {
|
||||
fontFamily = overrideFontFamily;
|
||||
} else {
|
||||
fontFamily = null;
|
||||
}
|
||||
String fontFamily = System.getProperty("hmcl.font.override", overrideFontFamily);
|
||||
|
||||
String css;
|
||||
try {
|
||||
@@ -104,6 +97,7 @@ public class Theme {
|
||||
.replace("%disabled-font-color%", String.format("rgba(%d, %d, %d, 0.7)", (int) Math.ceil(textFill.getRed() * 256), (int) Math.ceil(textFill.getGreen() * 256), (int) Math.ceil(textFill.getBlue() * 256)))
|
||||
.replace("%font-color%", getColorDisplayName(getForegroundColor()))
|
||||
.replace("%font%", Optional.ofNullable(fontFamily).map(f -> "-fx-font-family: \"" + f + "\";").orElse("")));
|
||||
temp.deleteOnExit();
|
||||
css = temp.toURI().toString();
|
||||
} catch (IOException | NullPointerException e) {
|
||||
Logging.LOG.log(Level.SEVERE, "Unable to create theme stylesheet. Fallback to blue theme.", e);
|
||||
|
||||
@@ -60,7 +60,7 @@ public final class VersionSetting implements Cloneable {
|
||||
this.global = global;
|
||||
}
|
||||
|
||||
private final BooleanProperty usesGlobalProperty = new SimpleBooleanProperty(this, "usesGlobal", false);
|
||||
private final BooleanProperty usesGlobalProperty = new SimpleBooleanProperty(this, "usesGlobal", true);
|
||||
|
||||
public BooleanProperty usesGlobalProperty() {
|
||||
return usesGlobalProperty;
|
||||
@@ -392,6 +392,20 @@ public final class VersionSetting implements Cloneable {
|
||||
notCheckGameProperty.set(notCheckGame);
|
||||
}
|
||||
|
||||
private final BooleanProperty notPatchNativesProperty = new SimpleBooleanProperty(this, "notPatchNatives", false);
|
||||
|
||||
public BooleanProperty notPatchNativesProperty() {
|
||||
return notPatchNativesProperty;
|
||||
}
|
||||
|
||||
public boolean isNotPatchNatives() {
|
||||
return notPatchNativesProperty.get();
|
||||
}
|
||||
|
||||
public void setNotPatchNatives(boolean notPatchNatives) {
|
||||
notPatchNativesProperty.set(notPatchNatives);
|
||||
}
|
||||
|
||||
private final BooleanProperty showLogsProperty = new SimpleBooleanProperty(this, "showLogs", false);
|
||||
|
||||
public BooleanProperty showLogsProperty() {
|
||||
@@ -540,6 +554,20 @@ public final class VersionSetting implements Cloneable {
|
||||
processPriorityProperty.set(processPriority);
|
||||
}
|
||||
|
||||
private final BooleanProperty useSoftwareRenderer = new SimpleBooleanProperty(this, "softwareRenderer", false);
|
||||
|
||||
public boolean isUseSoftwareRenderer() {
|
||||
return useSoftwareRenderer.get();
|
||||
}
|
||||
|
||||
public BooleanProperty useSoftwareRendererProperty() {
|
||||
return useSoftwareRenderer;
|
||||
}
|
||||
|
||||
public void setUseSoftwareRenderer(boolean useSoftwareRenderer) {
|
||||
this.useSoftwareRenderer.set(useSoftwareRenderer);
|
||||
}
|
||||
|
||||
private final BooleanProperty useNativeGLFW = new SimpleBooleanProperty(this, "nativeGLFW", false);
|
||||
|
||||
public boolean isUseNativeGLFW() {
|
||||
@@ -610,7 +638,7 @@ public final class VersionSetting implements Cloneable {
|
||||
public Task<JavaVersion> getJavaVersion(VersionNumber gameVersion, Version version, boolean checkJava) {
|
||||
return Task.runAsync(Schedulers.javafx(), () -> {
|
||||
if (StringUtils.isBlank(getJava())) {
|
||||
setJava(StringUtils.isBlank(getJavaDir()) ? "Default" : "Custom");
|
||||
setJava(StringUtils.isBlank(getJavaDir()) ? "Auto" : "Custom");
|
||||
}
|
||||
}).thenSupplyAsync(() -> {
|
||||
try {
|
||||
@@ -633,7 +661,7 @@ public final class VersionSetting implements Cloneable {
|
||||
.collect(Collectors.toList());
|
||||
if (matchedJava.isEmpty()) {
|
||||
runInFX(() -> {
|
||||
setJava("Default");
|
||||
setJava("Auto");
|
||||
});
|
||||
return JavaVersion.fromCurrentEnvironment();
|
||||
} else {
|
||||
@@ -670,6 +698,7 @@ public final class VersionSetting implements Cloneable {
|
||||
noJVMArgsProperty.addListener(listener);
|
||||
notCheckGameProperty.addListener(listener);
|
||||
notCheckJVMProperty.addListener(listener);
|
||||
notPatchNativesProperty.addListener(listener);
|
||||
showLogsProperty.addListener(listener);
|
||||
serverIpProperty.addListener(listener);
|
||||
fullscreenProperty.addListener(listener);
|
||||
@@ -678,6 +707,7 @@ public final class VersionSetting implements Cloneable {
|
||||
gameDirTypeProperty.addListener(listener);
|
||||
gameDirProperty.addListener(listener);
|
||||
processPriorityProperty.addListener(listener);
|
||||
useSoftwareRenderer.addListener(listener);
|
||||
useNativeGLFW.addListener(listener);
|
||||
useNativeOpenAL.addListener(listener);
|
||||
launcherVisibilityProperty.addListener(listener);
|
||||
@@ -706,6 +736,7 @@ public final class VersionSetting implements Cloneable {
|
||||
versionSetting.setNoJVMArgs(isNoJVMArgs());
|
||||
versionSetting.setNotCheckGame(isNotCheckGame());
|
||||
versionSetting.setNotCheckJVM(isNotCheckJVM());
|
||||
versionSetting.setNotPatchNatives(isNotPatchNatives());
|
||||
versionSetting.setShowLogs(isShowLogs());
|
||||
versionSetting.setServerIp(getServerIp());
|
||||
versionSetting.setFullscreen(isFullscreen());
|
||||
@@ -714,6 +745,7 @@ public final class VersionSetting implements Cloneable {
|
||||
versionSetting.setGameDirType(getGameDirType());
|
||||
versionSetting.setGameDir(getGameDir());
|
||||
versionSetting.setProcessPriority(getProcessPriority());
|
||||
versionSetting.setUseSoftwareRenderer(isUseSoftwareRenderer());
|
||||
versionSetting.setUseNativeGLFW(isUseNativeGLFW());
|
||||
versionSetting.setUseNativeOpenAL(isUseNativeOpenAL());
|
||||
versionSetting.setLauncherVisibility(getLauncherVisibility());
|
||||
@@ -747,10 +779,12 @@ public final class VersionSetting implements Cloneable {
|
||||
obj.addProperty("noJVMArgs", src.isNoJVMArgs());
|
||||
obj.addProperty("notCheckGame", src.isNotCheckGame());
|
||||
obj.addProperty("notCheckJVM", src.isNotCheckJVM());
|
||||
obj.addProperty("notPatchNatives", src.isNotPatchNatives());
|
||||
obj.addProperty("showLogs", src.isShowLogs());
|
||||
obj.addProperty("gameDir", src.getGameDir());
|
||||
obj.addProperty("launcherVisibility", src.getLauncherVisibility().ordinal());
|
||||
obj.addProperty("processPriority", src.getProcessPriority().ordinal());
|
||||
obj.addProperty("useSoftwareRenderer", src.isUseSoftwareRenderer());
|
||||
obj.addProperty("useNativeGLFW", src.isUseNativeGLFW());
|
||||
obj.addProperty("useNativeOpenAL", src.isUseNativeOpenAL());
|
||||
obj.addProperty("gameDirType", src.getGameDirType().ordinal());
|
||||
@@ -793,9 +827,11 @@ public final class VersionSetting implements Cloneable {
|
||||
vs.setNoJVMArgs(Optional.ofNullable(obj.get("noJVMArgs")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setNotCheckGame(Optional.ofNullable(obj.get("notCheckGame")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setNotCheckJVM(Optional.ofNullable(obj.get("notCheckJVM")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setNotPatchNatives(Optional.ofNullable(obj.get("notPatchNatives")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setShowLogs(Optional.ofNullable(obj.get("showLogs")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setLauncherVisibility(LauncherVisibility.values()[Optional.ofNullable(obj.get("launcherVisibility")).map(JsonElement::getAsInt).orElse(LauncherVisibility.HIDE.ordinal())]);
|
||||
vs.setProcessPriority(ProcessPriority.values()[Optional.ofNullable(obj.get("processPriority")).map(JsonElement::getAsInt).orElse(ProcessPriority.NORMAL.ordinal())]);
|
||||
vs.setUseSoftwareRenderer(Optional.ofNullable(obj.get("useSoftwareRenderer")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setUseNativeGLFW(Optional.ofNullable(obj.get("useNativeGLFW")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setUseNativeOpenAL(Optional.ofNullable(obj.get("useNativeOpenAL")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setGameDirType(GameDirectoryType.values()[Optional.ofNullable(obj.get("gameDirType")).map(JsonElement::getAsInt).orElse(GameDirectoryType.ROOT_FOLDER.ordinal())]);
|
||||
|
||||
@@ -31,7 +31,7 @@ import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.download.java.JavaRepository;
|
||||
import org.jackhuang.hmcl.game.ModpackHelper;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.EnumCommonDirectory;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
@@ -51,13 +51,13 @@ import org.jackhuang.hmcl.ui.versions.VersionPage;
|
||||
import org.jackhuang.hmcl.util.FutureCallback;
|
||||
import org.jackhuang.hmcl.util.Lazy;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.globalConfig;
|
||||
@@ -75,7 +75,7 @@ public final class Controllers {
|
||||
GameListPage gameListPage = new GameListPage();
|
||||
gameListPage.selectedProfileProperty().bindBidirectional(Profiles.selectedProfileProperty());
|
||||
gameListPage.profilesProperty().bindContent(Profiles.profilesProperty());
|
||||
FXUtils.applyDragListener(gameListPage, it -> "zip".equals(FileUtils.getExtension(it)), modpacks -> {
|
||||
FXUtils.applyDragListener(gameListPage, ModpackHelper::isFileModpackByExtension, modpacks -> {
|
||||
File modpack = modpacks.get(0);
|
||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), modpack), i18n("install.modpack"));
|
||||
});
|
||||
@@ -172,7 +172,7 @@ public final class Controllers {
|
||||
dialog(i18n("launcher.cache_directory.invalid"));
|
||||
}
|
||||
|
||||
Task.runAsync(JavaVersion::initialize).thenRunAsync(JavaRepository::initialize).start();
|
||||
Task.runAsync(JavaVersion::initialize).start();
|
||||
|
||||
scene = new Scene(decorator.getDecorator());
|
||||
scene.setFill(Color.TRANSPARENT);
|
||||
@@ -262,11 +262,7 @@ public final class Controllers {
|
||||
return pane.getCompletableFuture();
|
||||
}
|
||||
|
||||
public static TaskExecutorDialogPane taskDialog(TaskExecutor executor, String title) {
|
||||
return taskDialog(executor, title, null);
|
||||
}
|
||||
|
||||
public static TaskExecutorDialogPane taskDialog(TaskExecutor executor, String title, Consumer<Region> onCancel) {
|
||||
public static TaskExecutorDialogPane taskDialog(TaskExecutor executor, String title, TaskCancellationAction onCancel) {
|
||||
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(onCancel);
|
||||
pane.setTitle(title);
|
||||
pane.setExecutor(executor);
|
||||
@@ -274,7 +270,7 @@ public final class Controllers {
|
||||
return pane;
|
||||
}
|
||||
|
||||
public static TaskExecutorDialogPane taskDialog(Task<?> task, String title, Consumer<Region> onCancel) {
|
||||
public static TaskExecutorDialogPane taskDialog(Task<?> task, String title, TaskCancellationAction onCancel) {
|
||||
TaskExecutor executor = task.executor();
|
||||
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(onCancel);
|
||||
pane.setTitle(title);
|
||||
|
||||
@@ -390,11 +390,19 @@ public final class FXUtils {
|
||||
try {
|
||||
Runtime.getRuntime().exec(new String[]{"explorer.exe", "/select,", file.toAbsolutePath().toString()});
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.log(Level.SEVERE, "Unable to open " + file + " by executing explorer /select", e);
|
||||
Logging.LOG.log(Level.SEVERE, "Unable to open " + file + " by executing explorer /select," + file, e);
|
||||
}
|
||||
break;
|
||||
case OSX:
|
||||
try {
|
||||
Runtime.getRuntime().exec(new String[]{"/usr/bin/open", "-R", file.toAbsolutePath().toString()});
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.log(Level.SEVERE, "Unable to open " + file + " by executing /usr/bin/open -R " + file, e);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Currently unsupported.
|
||||
// We do not have an universal method to show file in file manager.
|
||||
openFolder(file.getParent().toFile());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import javafx.stage.Stage;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.launch.ProcessListener;
|
||||
@@ -221,7 +222,7 @@ public class GameCrashWindow extends Stage {
|
||||
LogWindow logWindow = new LogWindow();
|
||||
|
||||
logWindow.logLine("Command: " + new CommandBuilder().addAll(managedProcess.getCommands()).toString(), Log4jLevel.INFO);
|
||||
logWindow.logLine("ClassPath: " + managedProcess.getClasspath(), Log4jLevel.INFO);
|
||||
if (managedProcess.getClasspath() != null) logWindow.logLine("ClassPath: " + managedProcess.getClasspath(), Log4jLevel.INFO);
|
||||
for (Map.Entry<String, Log4jLevel> entry : logs)
|
||||
logWindow.logLine(entry.getKey(), entry.getValue());
|
||||
|
||||
@@ -281,6 +282,11 @@ public class GameCrashWindow extends Stage {
|
||||
infoPane.setPadding(new Insets(8));
|
||||
infoPane.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
TwoLineListItem launcher = new TwoLineListItem();
|
||||
launcher.getStyleClass().setAll("two-line-item-second-large");
|
||||
launcher.setTitle(i18n("launcher"));
|
||||
launcher.setSubtitle(Metadata.VERSION);
|
||||
|
||||
TwoLineListItem version = new TwoLineListItem();
|
||||
version.getStyleClass().setAll("two-line-item-second-large");
|
||||
version.setTitle(i18n("archive.game_version"));
|
||||
@@ -306,7 +312,7 @@ public class GameCrashWindow extends Stage {
|
||||
arch.setTitle(i18n("system.architecture"));
|
||||
arch.subtitleProperty().bind(GameCrashWindow.this.arch);
|
||||
|
||||
infoPane.getChildren().setAll(version, memory, java, os, arch);
|
||||
infoPane.getChildren().setAll(launcher, version, memory, java, os, arch);
|
||||
}
|
||||
|
||||
HBox moddedPane = new HBox(8);
|
||||
@@ -315,13 +321,15 @@ public class GameCrashWindow extends Stage {
|
||||
moddedPane.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
for (LibraryAnalyzer.LibraryType type : LibraryAnalyzer.LibraryType.values()) {
|
||||
analyzer.getVersion(type).ifPresent(ver -> {
|
||||
TwoLineListItem item = new TwoLineListItem();
|
||||
item.getStyleClass().setAll("two-line-item-second-large");
|
||||
item.setTitle(i18n("install.installer." + type.getPatchId()));
|
||||
item.setSubtitle(ver);
|
||||
moddedPane.getChildren().add(item);
|
||||
});
|
||||
if (!type.getPatchId().isEmpty()) {
|
||||
analyzer.getVersion(type).ifPresent(ver -> {
|
||||
TwoLineListItem item = new TwoLineListItem();
|
||||
item.getStyleClass().setAll("two-line-item-second-large");
|
||||
item.setTitle(i18n("install.installer." + type.getPatchId()));
|
||||
item.setSubtitle(ver);
|
||||
moddedPane.getChildren().add(item);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.ui;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
@@ -32,9 +33,7 @@ import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.*;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
|
||||
@@ -59,6 +58,13 @@ public class InstallerItem extends Control {
|
||||
public final ObjectProperty<EventHandler<? super MouseEvent>> removeAction = new SimpleObjectProperty<>();
|
||||
public final ObjectProperty<EventHandler<? super MouseEvent>> action = new SimpleObjectProperty<>();
|
||||
|
||||
private Style style = Style.LIST_ITEM;
|
||||
|
||||
public enum Style {
|
||||
LIST_ITEM,
|
||||
CARD,
|
||||
}
|
||||
|
||||
public InstallerItem(LibraryAnalyzer.LibraryType id) {
|
||||
this(id.getPatchId());
|
||||
}
|
||||
@@ -83,12 +89,20 @@ public class InstallerItem extends Control {
|
||||
case "optifine":
|
||||
imageUrl = "/assets/img/command.png";
|
||||
break;
|
||||
case "quilt":
|
||||
case "quilt-api":
|
||||
imageUrl = "/assets/img/quilt.png";
|
||||
break;
|
||||
default:
|
||||
imageUrl = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void setStyleMode(Style style) {
|
||||
this.style = style;
|
||||
}
|
||||
|
||||
public void setState(String libraryVersion, boolean incompatibleWithGame, boolean removable) {
|
||||
this.libraryVersion.set(libraryVersion);
|
||||
this.incompatibleWithGame.set(incompatibleWithGame);
|
||||
@@ -111,76 +125,112 @@ public class InstallerItem extends Control {
|
||||
public final InstallerItem forge = new InstallerItem(FORGE);
|
||||
public final InstallerItem liteLoader = new InstallerItem(LITELOADER);
|
||||
public final InstallerItem optiFine = new InstallerItem(OPTIFINE);
|
||||
public final InstallerItem quilt = new InstallerItem(QUILT);
|
||||
public final InstallerItem quiltApi = new InstallerItem(QUILT_API);
|
||||
|
||||
public InstallerItemGroup() {
|
||||
forge.incompatibleLibraryName.bind(Bindings.createStringBinding(() -> {
|
||||
if (fabric.libraryVersion.get() != null) return FABRIC.getPatchId();
|
||||
if (quilt.libraryVersion.get() != null) return QUILT.getPatchId();
|
||||
return null;
|
||||
}, fabric.libraryVersion));
|
||||
}, fabric.libraryVersion, quilt.libraryVersion));
|
||||
|
||||
liteLoader.incompatibleLibraryName.bind(Bindings.createStringBinding(() -> {
|
||||
if (fabric.libraryVersion.get() != null) return FABRIC.getPatchId();
|
||||
if (quilt.libraryVersion.get() != null) return QUILT.getPatchId();
|
||||
return null;
|
||||
}, fabric.libraryVersion));
|
||||
}, fabric.libraryVersion, quilt.libraryVersion));
|
||||
|
||||
optiFine.incompatibleLibraryName.bind(Bindings.createStringBinding(() -> {
|
||||
if (fabric.libraryVersion.get() != null) return FABRIC.getPatchId();
|
||||
if (quilt.libraryVersion.get() != null) return QUILT.getPatchId();
|
||||
return null;
|
||||
}, fabric.libraryVersion));
|
||||
}, fabric.libraryVersion, quilt.libraryVersion));
|
||||
|
||||
for (InstallerItem fabric : new InstallerItem[]{fabric, fabricApi}) {
|
||||
fabric.incompatibleLibraryName.bind(Bindings.createStringBinding(() -> {
|
||||
if (forge.libraryVersion.get() != null) return FORGE.getPatchId();
|
||||
if (liteLoader.libraryVersion.get() != null) return LITELOADER.getPatchId();
|
||||
if (optiFine.libraryVersion.get() != null) return OPTIFINE.getPatchId();
|
||||
if (forge.libraryVersion.get() != null) return FORGE.getPatchId();
|
||||
if (quilt.libraryVersion.get() != null) return QUILT.getPatchId();
|
||||
if (quiltApi.libraryVersion.get() != null) return QUILT_API.getPatchId();
|
||||
return null;
|
||||
}, optiFine.libraryVersion, forge.libraryVersion));
|
||||
}, forge.libraryVersion, liteLoader.libraryVersion, optiFine.libraryVersion, quilt.libraryVersion, quiltApi.libraryVersion));
|
||||
}
|
||||
|
||||
fabricApi.dependencyName.bind(Bindings.createStringBinding(() -> {
|
||||
if (fabric.libraryVersion.get() == null) return FABRIC.getPatchId();
|
||||
else return null;
|
||||
}, fabric.libraryVersion));
|
||||
|
||||
for (InstallerItem quilt : new InstallerItem[]{quilt, quiltApi}) {
|
||||
quilt.incompatibleLibraryName.bind(Bindings.createStringBinding(() -> {
|
||||
if (fabric.libraryVersion.get() != null) return FABRIC.getPatchId();
|
||||
if (fabricApi.libraryVersion.get() != null) return FABRIC_API.getPatchId();
|
||||
if (forge.libraryVersion.get() != null) return FORGE.getPatchId();
|
||||
if (liteLoader.libraryVersion.get() != null) return LITELOADER.getPatchId();
|
||||
if (optiFine.libraryVersion.get() != null) return OPTIFINE.getPatchId();
|
||||
return null;
|
||||
}, fabric.libraryVersion, fabricApi.libraryVersion, forge.libraryVersion, liteLoader.libraryVersion, optiFine.libraryVersion));
|
||||
}
|
||||
|
||||
quiltApi.dependencyName.bind(Bindings.createStringBinding(() -> {
|
||||
if (quilt.libraryVersion.get() == null) return QUILT.getPatchId();
|
||||
else return null;
|
||||
}, quilt.libraryVersion));
|
||||
}
|
||||
|
||||
public InstallerItem[] getLibraries() {
|
||||
return new InstallerItem[]{game, forge, liteLoader, optiFine, fabric, fabricApi};
|
||||
return new InstallerItem[]{game, forge, liteLoader, optiFine, fabric, fabricApi, quilt, quiltApi};
|
||||
}
|
||||
}
|
||||
|
||||
public static class InstallerItemSkin extends SkinBase<InstallerItem> {
|
||||
|
||||
private static final PseudoClass LIST_ITEM = PseudoClass.getPseudoClass("list-item");
|
||||
private static final PseudoClass CARD = PseudoClass.getPseudoClass("card");
|
||||
|
||||
InstallerItemSkin(InstallerItem control) {
|
||||
super(control);
|
||||
|
||||
HBox hbox = new HBox();
|
||||
hbox.getStyleClass().add("md-list-cell");
|
||||
hbox.setPadding(new Insets(8));
|
||||
RipplerContainer container = new RipplerContainer(hbox);
|
||||
Pane pane;
|
||||
if (control.style == Style.CARD) {
|
||||
pane = new VBox();
|
||||
} else {
|
||||
pane = new HBox();
|
||||
}
|
||||
pane.getStyleClass().add("installer-item");
|
||||
RipplerContainer container = new RipplerContainer(pane);
|
||||
getChildren().setAll(container);
|
||||
|
||||
hbox.setAlignment(Pos.CENTER_LEFT);
|
||||
pane.pseudoClassStateChanged(LIST_ITEM, control.style == Style.LIST_ITEM);
|
||||
pane.pseudoClassStateChanged(CARD, control.style == Style.CARD);
|
||||
|
||||
if (control.imageUrl != null) {
|
||||
ImageView view = new ImageView(new Image(control.imageUrl, 32, 32, true, true));
|
||||
Node node = FXUtils.limitingSize(view, 32, 32);
|
||||
node.setMouseTransparent(true);
|
||||
hbox.getChildren().add(node);
|
||||
node.getStyleClass().add("installer-item-image");
|
||||
pane.getChildren().add(node);
|
||||
|
||||
if (control.style == Style.CARD) {
|
||||
VBox.setMargin(node, new Insets(8, 0, 16, 0));
|
||||
}
|
||||
}
|
||||
|
||||
Label nameLabel = new Label();
|
||||
nameLabel.getStyleClass().add("installer-item-name");
|
||||
nameLabel.setMouseTransparent(true);
|
||||
hbox.getChildren().add(nameLabel);
|
||||
nameLabel.setPrefWidth(80);
|
||||
pane.getChildren().add(nameLabel);
|
||||
nameLabel.textProperty().set(I18n.hasKey("install.installer." + control.id) ? i18n("install.installer." + control.id) : control.id);
|
||||
HBox.setMargin(nameLabel, new Insets(0, 4, 0, 4));
|
||||
|
||||
Label label = new Label();
|
||||
label.setMouseTransparent(true);
|
||||
hbox.getChildren().add(label);
|
||||
label.setMaxWidth(Double.MAX_VALUE);
|
||||
HBox.setHgrow(label, Priority.ALWAYS);
|
||||
label.textProperty().bind(Bindings.createStringBinding(() -> {
|
||||
Label statusLabel = new Label();
|
||||
statusLabel.getStyleClass().add("installer-item-status");
|
||||
statusLabel.setMouseTransparent(true);
|
||||
pane.getChildren().add(statusLabel);
|
||||
HBox.setHgrow(statusLabel, Priority.ALWAYS);
|
||||
statusLabel.textProperty().bind(Bindings.createStringBinding(() -> {
|
||||
String incompatibleWith = control.incompatibleLibraryName.get();
|
||||
String version = control.libraryVersion.get();
|
||||
if (control.incompatibleWithGame.get()) {
|
||||
@@ -193,8 +243,13 @@ public class InstallerItem extends Control {
|
||||
return i18n("install.installer.version", version);
|
||||
}
|
||||
}, control.incompatibleLibraryName, control.incompatibleWithGame, control.libraryVersion));
|
||||
BorderPane.setMargin(label, new Insets(0, 0, 0, 8));
|
||||
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
|
||||
BorderPane.setMargin(statusLabel, new Insets(0, 0, 0, 8));
|
||||
BorderPane.setAlignment(statusLabel, Pos.CENTER_LEFT);
|
||||
|
||||
HBox buttonsContainer = new HBox();
|
||||
buttonsContainer.setSpacing(8);
|
||||
buttonsContainer.setAlignment(Pos.CENTER);
|
||||
pane.getChildren().add(buttonsContainer);
|
||||
|
||||
JFXButton closeButton = new JFXButton();
|
||||
closeButton.setGraphic(SVG.close(Theme.blackFillBinding(), -1, -1));
|
||||
@@ -202,7 +257,7 @@ public class InstallerItem extends Control {
|
||||
closeButton.visibleProperty().bind(control.removable);
|
||||
closeButton.managedProperty().bind(closeButton.visibleProperty());
|
||||
closeButton.onMouseClickedProperty().bind(control.removeAction);
|
||||
hbox.getChildren().add(closeButton);
|
||||
buttonsContainer.getChildren().add(closeButton);
|
||||
|
||||
JFXButton arrowButton = new JFXButton();
|
||||
arrowButton.graphicProperty().bind(Bindings.createObjectBinding(() -> control.upgradable.get()
|
||||
@@ -215,16 +270,16 @@ public class InstallerItem extends Control {
|
||||
control.installable, control.incompatibleLibraryName));
|
||||
arrowButton.managedProperty().bind(arrowButton.visibleProperty());
|
||||
arrowButton.onMouseClickedProperty().bind(control.action);
|
||||
hbox.getChildren().add(arrowButton);
|
||||
buttonsContainer.getChildren().add(arrowButton);
|
||||
|
||||
FXUtils.onChangeAndOperate(arrowButton.visibleProperty(), clickable -> {
|
||||
if (clickable) {
|
||||
container.onMouseClickedProperty().bind(control.action);
|
||||
hbox.setCursor(Cursor.HAND);
|
||||
pane.setCursor(Cursor.HAND);
|
||||
} else {
|
||||
container.onMouseClickedProperty().unbind();
|
||||
container.onMouseClickedProperty().set(null);
|
||||
hbox.setCursor(Cursor.DEFAULT);
|
||||
pane.setCursor(Cursor.DEFAULT);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXDialogLayout;
|
||||
import javafx.concurrent.Worker;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.web.WebEngine;
|
||||
import javafx.scene.web.WebView;
|
||||
@@ -41,23 +42,23 @@ public class UpgradeDialog extends JFXDialogLayout {
|
||||
|
||||
{
|
||||
String url = CHANGELOG_URL + remoteVersion.getChannel().channelName + ".html";
|
||||
WebView webView = new WebView();
|
||||
webView.getEngine().setUserDataDirectory(Metadata.HMCL_DIRECTORY.toFile());
|
||||
try {
|
||||
WebView webView = new WebView();
|
||||
webView.getEngine().setUserDataDirectory(Metadata.HMCL_DIRECTORY.toFile());
|
||||
WebEngine engine = webView.getEngine();
|
||||
engine.load(CHANGELOG_URL + remoteVersion.getChannel().channelName);
|
||||
engine.load(url);
|
||||
engine.getLoadWorker().stateProperty().addListener((observable, oldValue, newValue) -> {
|
||||
String viewURL = engine.getLoadWorker().getMessage().trim();
|
||||
if (!viewURL.startsWith(CHANGELOG_URL)) {
|
||||
engine.getLoadWorker().cancel();
|
||||
FXUtils.openLink(viewURL);
|
||||
if (newValue == Worker.State.FAILED) {
|
||||
LOG.warning("Failed to load update log, trying to open it in browser");
|
||||
FXUtils.openLink(url);
|
||||
setBody();
|
||||
}
|
||||
});
|
||||
setBody(webView);
|
||||
} catch (NoClassDefFoundError | UnsatisfiedLinkError e) {
|
||||
LOG.log(Level.WARNING, "WebView is missing or initialization failed", e);
|
||||
FXUtils.openLink(url);
|
||||
}
|
||||
setBody(webView);
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.jackhuang.hmcl.auth.AuthenticationException;
|
||||
import org.jackhuang.hmcl.auth.CredentialExpiredException;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.auth.microsoft.MicrosoftAccount;
|
||||
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.CompleteGameProfile;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.TextureType;
|
||||
@@ -44,6 +45,7 @@ import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.DialogController;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.util.skin.InvalidSkinException;
|
||||
import org.jackhuang.hmcl.util.skin.NormalizedSkin;
|
||||
@@ -137,7 +139,7 @@ public class AccountListItem extends RadioButton {
|
||||
} else {
|
||||
return createBooleanBinding(() -> true);
|
||||
}
|
||||
} else if (account instanceof OfflineAccount) {
|
||||
} else if (account instanceof OfflineAccount || account instanceof MicrosoftAccount) {
|
||||
return createBooleanBinding(() -> true);
|
||||
} else {
|
||||
return createBooleanBinding(() -> false);
|
||||
@@ -153,6 +155,10 @@ public class AccountListItem extends RadioButton {
|
||||
Controllers.dialog(new OfflineAccountSkinPane((OfflineAccount) account));
|
||||
return null;
|
||||
}
|
||||
if (account instanceof MicrosoftAccount) {
|
||||
FXUtils.openLink("https://www.minecraft.net/msaprofile/mygames/editskin");
|
||||
return null;
|
||||
}
|
||||
if (!(account instanceof YggdrasilAccount)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -122,6 +122,14 @@ public class AccountListPage extends DecoratorAnimatedPage implements DecoratorP
|
||||
microsoftItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_MICROSOFT)));
|
||||
boxMethods.getChildren().add(microsoftItem);
|
||||
|
||||
AdvancedListItem littleSkinItem = new AdvancedListItem();
|
||||
littleSkinItem.getStyleClass().add("navigation-drawer-item");
|
||||
littleSkinItem.setActionButtonVisible(false);
|
||||
littleSkinItem.setTitle(i18n("account.methods.little_skin"));
|
||||
littleSkinItem.setLeftGraphic(wrap(SVG::server));
|
||||
littleSkinItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_LITTLE_SKIN)));
|
||||
boxMethods.getChildren().add(littleSkinItem);
|
||||
|
||||
VBox boxAuthServers = new VBox();
|
||||
authServerItems = MappedObservableList.create(skinnable.authServersProperty(), server -> {
|
||||
AdvancedListItem item = new AdvancedListItem();
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.jackhuang.hmcl.auth.CharacterSelector;
|
||||
import org.jackhuang.hmcl.auth.NoSelectedCharacterException;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccountFactory;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.BoundAuthlibInjectorAccountFactory;
|
||||
import org.jackhuang.hmcl.auth.microsoft.MicrosoftAccountFactory;
|
||||
import org.jackhuang.hmcl.auth.offline.OfflineAccountFactory;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.GameProfile;
|
||||
@@ -194,9 +195,8 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
setPrefWidth(560);
|
||||
}
|
||||
|
||||
public CreateAccountPane(AuthlibInjectorServer authserver) {
|
||||
this(Accounts.FACTORY_AUTHLIB_INJECTOR);
|
||||
((AccountDetailsInputPane) detailsPane).selectAuthServer(authserver);
|
||||
public CreateAccountPane(AuthlibInjectorServer authServer) {
|
||||
this(Accounts.getAccountFactoryByAuthlibInjectorServer(authServer));
|
||||
}
|
||||
|
||||
private void onAccept() {
|
||||
@@ -267,39 +267,67 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
}
|
||||
if (factory == Accounts.FACTORY_MICROSOFT) {
|
||||
VBox vbox = new VBox(8);
|
||||
HintPane hintPane = new HintPane(MessageDialogPane.MessageType.INFO);
|
||||
FXUtils.onChangeAndOperate(deviceCode, deviceCode -> {
|
||||
if (deviceCode != null) {
|
||||
FXUtils.copyText(deviceCode.getUserCode());
|
||||
hintPane.setSegment(i18n("account.methods.microsoft.manual", deviceCode.getUserCode(), deviceCode.getVerificationUri()));
|
||||
} else {
|
||||
hintPane.setSegment(i18n("account.methods.microsoft.hint"));
|
||||
}
|
||||
});
|
||||
hintPane.setOnMouseClicked(e -> {
|
||||
if (deviceCode.get() != null) {
|
||||
FXUtils.copyText(deviceCode.get().getUserCode());
|
||||
}
|
||||
});
|
||||
if (!Accounts.OAUTH_CALLBACK.getClientId().isEmpty()) {
|
||||
HintPane hintPane = new HintPane(MessageDialogPane.MessageType.INFO);
|
||||
FXUtils.onChangeAndOperate(deviceCode, deviceCode -> {
|
||||
if (deviceCode != null) {
|
||||
FXUtils.copyText(deviceCode.getUserCode());
|
||||
hintPane.setSegment(i18n("account.methods.microsoft.manual", deviceCode.getUserCode(), deviceCode.getVerificationUri()));
|
||||
} else {
|
||||
hintPane.setSegment(i18n("account.methods.microsoft.hint"));
|
||||
}
|
||||
});
|
||||
hintPane.setOnMouseClicked(e -> {
|
||||
if (deviceCode.get() != null) {
|
||||
FXUtils.copyText(deviceCode.get().getUserCode());
|
||||
}
|
||||
});
|
||||
|
||||
holder.add(Accounts.OAUTH_CALLBACK.onGrantDeviceCode.registerWeak(value -> {
|
||||
runInFX(() -> deviceCode.set(value));
|
||||
}));
|
||||
holder.add(Accounts.OAUTH_CALLBACK.onGrantDeviceCode.registerWeak(value -> {
|
||||
runInFX(() -> deviceCode.set(value));
|
||||
}));
|
||||
HBox box = new HBox(8);
|
||||
JFXHyperlink birthLink = new JFXHyperlink(i18n("account.methods.microsoft.birth"));
|
||||
birthLink.setOnAction(e -> FXUtils.openLink("https://support.microsoft.com/account-billing/837badbc-999e-54d2-2617-d19206b9540a"));
|
||||
JFXHyperlink profileLink = new JFXHyperlink(i18n("account.methods.microsoft.profile"));
|
||||
profileLink.setOnAction(e -> FXUtils.openLink("https://account.live.com/editprof.aspx"));
|
||||
JFXHyperlink purchaseLink = new JFXHyperlink(i18n("account.methods.yggdrasil.purchase"));
|
||||
JFXHyperlink deauthorizeLink = new JFXHyperlink(i18n("account.methods.microsoft.deauthorize"));
|
||||
deauthorizeLink.setOnAction(e -> FXUtils.openLink("https://account.live.com/consent/Edit?client_id=000000004C794E0A"));
|
||||
purchaseLink.setOnAction(e -> FXUtils.openLink(YggdrasilService.PURCHASE_URL));
|
||||
box.getChildren().setAll(profileLink, birthLink, purchaseLink, deauthorizeLink);
|
||||
GridPane.setColumnSpan(box, 2);
|
||||
|
||||
HBox box = new HBox(8);
|
||||
JFXHyperlink birthLink = new JFXHyperlink(i18n("account.methods.microsoft.birth"));
|
||||
birthLink.setOnAction(e -> FXUtils.openLink("https://support.microsoft.com/zh-cn/account-billing/如何更改-microsoft-帐户上的出生日期-837badbc-999e-54d2-2617-d19206b9540a"));
|
||||
JFXHyperlink profileLink = new JFXHyperlink(i18n("account.methods.microsoft.profile"));
|
||||
profileLink.setOnAction(e -> FXUtils.openLink("https://account.live.com/editprof.aspx"));
|
||||
JFXHyperlink purchaseLink = new JFXHyperlink(i18n("account.methods.yggdrasil.purchase"));
|
||||
purchaseLink.setOnAction(e -> FXUtils.openLink(YggdrasilService.PURCHASE_URL));
|
||||
box.getChildren().setAll(profileLink, birthLink, purchaseLink);
|
||||
GridPane.setColumnSpan(box, 2);
|
||||
vbox.getChildren().setAll(hintPane, box);
|
||||
|
||||
vbox.getChildren().setAll(hintPane, box);
|
||||
btnAccept.setDisable(false);
|
||||
} else {
|
||||
HintPane hintPane = new HintPane(MessageDialogPane.MessageType.WARNING);
|
||||
hintPane.setText(i18n("account.methods.microsoft.snapshot"));
|
||||
|
||||
vbox.getChildren().setAll(hintPane);
|
||||
btnAccept.setDisable(true);
|
||||
}
|
||||
|
||||
detailsPane = vbox;
|
||||
btnAccept.setDisable(false);
|
||||
} else if (factory == Accounts.FACTORY_MOJANG) {
|
||||
VBox vbox = new VBox(8);
|
||||
HintPane hintPane = new HintPane(MessageDialogPane.MessageType.WARNING);
|
||||
hintPane.setText(i18n("account.methods.yggdrasil.migration.hint"));
|
||||
|
||||
HBox linkPane = new HBox(8);
|
||||
|
||||
JFXHyperlink migrationLink = new JFXHyperlink(i18n("account.methods.yggdrasil.migration"));
|
||||
migrationLink.setOnAction(e -> FXUtils.openLink(YggdrasilService.PROFILE_URL));
|
||||
|
||||
JFXHyperlink migrationHowLink = new JFXHyperlink(i18n("account.methods.yggdrasil.migration.how"));
|
||||
migrationHowLink.setOnAction(e -> FXUtils.openLink(YggdrasilService.MIGRATION_FAQ_URL));
|
||||
|
||||
linkPane.getChildren().setAll(migrationLink, migrationHowLink);
|
||||
|
||||
vbox.getChildren().setAll(hintPane, linkPane);
|
||||
detailsPane = vbox;
|
||||
btnAccept.setDisable(true);
|
||||
} else {
|
||||
detailsPane = new AccountDetailsInputPane(factory, btnAccept::fire);
|
||||
btnAccept.disableProperty().bind(((AccountDetailsInputPane) detailsPane).validProperty().not());
|
||||
@@ -310,7 +338,7 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
private static class AccountDetailsInputPane extends GridPane {
|
||||
|
||||
// ==== authlib-injector hyperlinks ====
|
||||
private static final String[] ALLOWED_LINKS = { "register" };
|
||||
private static final String[] ALLOWED_LINKS = { "homepage", "register" };
|
||||
|
||||
private static List<Hyperlink> createHyperlinks(AuthlibInjectorServer server) {
|
||||
if (server == null) {
|
||||
@@ -332,12 +360,13 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
}
|
||||
// =====
|
||||
|
||||
private AccountFactory<?> factory;
|
||||
private final AccountFactory<?> factory;
|
||||
private @Nullable AuthlibInjectorServer server;
|
||||
private @Nullable JFXComboBox<AuthlibInjectorServer> cboServers;
|
||||
private @Nullable JFXTextField txtUsername;
|
||||
private @Nullable JFXPasswordField txtPassword;
|
||||
private @Nullable JFXTextField txtUUID;
|
||||
private BooleanBinding valid;
|
||||
private final BooleanBinding valid;
|
||||
|
||||
public AccountDetailsInputPane(AccountFactory<?> factory, Runnable onAction) {
|
||||
this.factory = factory;
|
||||
@@ -355,7 +384,28 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
|
||||
int rowIndex = 0;
|
||||
|
||||
if (factory instanceof AuthlibInjectorAccountFactory) {
|
||||
if (factory instanceof BoundAuthlibInjectorAccountFactory) {
|
||||
this.server = ((BoundAuthlibInjectorAccountFactory) factory).getServer();
|
||||
|
||||
Label lblServers = new Label(i18n("account.injector.server"));
|
||||
setHalignment(lblServers, HPos.LEFT);
|
||||
add(lblServers, 0, rowIndex);
|
||||
|
||||
Label lblServerName = new Label(this.server.getName());
|
||||
lblServerName.setMaxWidth(Double.MAX_VALUE);
|
||||
HBox.setHgrow(lblServerName, Priority.ALWAYS);
|
||||
|
||||
HBox linksContainer = new HBox();
|
||||
linksContainer.setAlignment(Pos.CENTER);
|
||||
linksContainer.getChildren().setAll(createHyperlinks(this.server));
|
||||
linksContainer.setMinWidth(USE_PREF_SIZE);
|
||||
|
||||
HBox boxServers = new HBox(lblServerName, linksContainer);
|
||||
boxServers.setAlignment(Pos.CENTER_LEFT);
|
||||
add(boxServers, 1, rowIndex);
|
||||
|
||||
rowIndex++;
|
||||
} else if (factory instanceof AuthlibInjectorAccountFactory) {
|
||||
Label lblServers = new Label(i18n("account.injector.server"));
|
||||
setHalignment(lblServers, HPos.LEFT);
|
||||
add(lblServers, 0, rowIndex);
|
||||
@@ -378,7 +428,10 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
|
||||
HBox linksContainer = new HBox();
|
||||
linksContainer.setAlignment(Pos.CENTER);
|
||||
onChangeAndOperate(cboServers.valueProperty(), server -> linksContainer.getChildren().setAll(createHyperlinks(server)));
|
||||
onChangeAndOperate(cboServers.valueProperty(), server -> {
|
||||
this.server = server;
|
||||
linksContainer.getChildren().setAll(createHyperlinks(server));
|
||||
});
|
||||
linksContainer.setMinWidth(USE_PREF_SIZE);
|
||||
|
||||
JFXButton btnAddServer = new JFXButton();
|
||||
@@ -435,10 +488,10 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
GridPane.setColumnSpan(box, 2);
|
||||
|
||||
JFXHyperlink migrationLink = new JFXHyperlink(i18n("account.methods.yggdrasil.migration"));
|
||||
migrationLink.setOnAction(e -> FXUtils.openLink(YggdrasilService.PROFILE_URL));
|
||||
migrationLink.setOnAction(e -> FXUtils.openLink("https://aka.ms/MinecraftMigration"));
|
||||
|
||||
JFXHyperlink migrationHowLink = new JFXHyperlink(i18n("account.methods.yggdrasil.migration.how"));
|
||||
migrationHowLink.setOnAction(e -> FXUtils.openLink(YggdrasilService.MIGRATION_FAQ_URL));
|
||||
migrationHowLink.setOnAction(e -> FXUtils.openLink("https://help.minecraft.net/hc/articles/4411173197709"));
|
||||
|
||||
JFXHyperlink purchaseLink = new JFXHyperlink(i18n("account.methods.yggdrasil.purchase"));
|
||||
purchaseLink.setOnAction(e -> FXUtils.openLink(YggdrasilService.PURCHASE_URL));
|
||||
@@ -523,11 +576,8 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
private boolean requiresEmailAsUsername() {
|
||||
if (factory instanceof YggdrasilAccountFactory) {
|
||||
return true;
|
||||
} else if ((factory instanceof AuthlibInjectorAccountFactory) && cboServers != null) {
|
||||
AuthlibInjectorServer server = cboServers.getValue();
|
||||
if (server != null && !server.isNonEmailLogin()) {
|
||||
return true;
|
||||
}
|
||||
} else if ((factory instanceof AuthlibInjectorAccountFactory) && this.server != null) {
|
||||
return !server.isNonEmailLogin();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -544,7 +594,7 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
}
|
||||
|
||||
public @Nullable AuthlibInjectorServer getAuthServer() {
|
||||
return cboServers == null ? null : cboServers.getValue();
|
||||
return this.server;
|
||||
}
|
||||
|
||||
public @Nullable String getUsername() {
|
||||
@@ -559,10 +609,6 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
return valid;
|
||||
}
|
||||
|
||||
public void selectAuthServer(AuthlibInjectorServer authserver) {
|
||||
cboServers.getSelectionModel().select(authserver);
|
||||
}
|
||||
|
||||
public void focus() {
|
||||
if (txtUsername != null) {
|
||||
txtUsername.requestFocus();
|
||||
@@ -671,5 +717,5 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
}
|
||||
}
|
||||
|
||||
private static final String MICROSOFT_ACCOUNT_EDIT_PROFILE_URL = "https://support.microsoft.com/zh-cn/account-billing/%E5%A6%82%E4%BD%95%E6%9B%B4%E6%94%B9-microsoft-%E5%B8%90%E6%88%B7%E4%B8%8A%E7%9A%84%E5%87%BA%E7%94%9F%E6%97%A5%E6%9C%9F-837badbc-999e-54d2-2617-d19206b9540a";
|
||||
private static final String MICROSOFT_ACCOUNT_EDIT_PROFILE_URL = "https://support.microsoft.com/account-billing/837badbc-999e-54d2-2617-d19206b9540a";
|
||||
}
|
||||
@@ -61,7 +61,7 @@ public class OAuthAccountLoginDialog extends DialogPane {
|
||||
|
||||
HBox box = new HBox(8);
|
||||
JFXHyperlink birthLink = new JFXHyperlink(i18n("account.methods.microsoft.birth"));
|
||||
birthLink.setOnAction(e -> FXUtils.openLink("https://support.microsoft.com/zh-cn/account-billing/如何更改-microsoft-帐户上的出生日期-837badbc-999e-54d2-2617-d19206b9540a"));
|
||||
birthLink.setOnAction(e -> FXUtils.openLink("https://support.microsoft.com/account-billing/how-to-change-a-birth-date-on-a-microsoft-account-837badbc-999e-54d2-2617-d19206b9540a"));
|
||||
JFXHyperlink profileLink = new JFXHyperlink(i18n("account.methods.microsoft.profile"));
|
||||
profileLink.setOnAction(e -> FXUtils.openLink("https://account.live.com/editprof.aspx"));
|
||||
JFXHyperlink purchaseLink = new JFXHyperlink(i18n("account.methods.yggdrasil.purchase"));
|
||||
|
||||
@@ -76,7 +76,7 @@ public class OfflineAccountSkinPane extends StackPane {
|
||||
canvasPane.setPrefWidth(300);
|
||||
canvasPane.setPrefHeight(300);
|
||||
pane.setCenter(canvas);
|
||||
canvas.getAnimationplayer().addSkinAnimation(new SkinAniWavingArms(100, 2000, 7.5, canvas), new SkinAniRunning(100, 100, 30, canvas));
|
||||
canvas.getAnimationPlayer().addSkinAnimation(new SkinAniWavingArms(100, 2000, 7.5, canvas), new SkinAniRunning(100, 100, 30, canvas));
|
||||
canvas.enableRotation(.5);
|
||||
|
||||
canvas.addEventHandler(DragEvent.DRAG_OVER, e -> {
|
||||
@@ -136,11 +136,14 @@ public class OfflineAccountSkinPane extends StackPane {
|
||||
LOG.log(Level.WARNING, "Failed to load skin", exception);
|
||||
Controllers.showToast(i18n("message.failed"));
|
||||
} else {
|
||||
if (result == null || result.getSkin() == null) {
|
||||
canvas.updateSkin(getDefaultTexture(), isDefaultSlim());
|
||||
if (result == null || result.getSkin() == null && result.getCape() == null) {
|
||||
canvas.updateSkin(getDefaultTexture(), isDefaultSlim(), null);
|
||||
return;
|
||||
}
|
||||
canvas.updateSkin(new Image(result.getSkin().getInputStream()), result.getModel() == TextureModel.ALEX);
|
||||
canvas.updateSkin(
|
||||
result.getSkin() != null ? new Image(result.getSkin().getInputStream()) : getDefaultTexture(),
|
||||
result.getModel() == TextureModel.ALEX,
|
||||
result.getCape() != null ? new Image(result.getCape().getInputStream()) : null);
|
||||
}
|
||||
}).start();
|
||||
}, skinItem.selectedDataProperty(), cslApiField.textProperty(), skinSelector.valueProperty(), capeSelector.valueProperty());
|
||||
@@ -183,7 +186,7 @@ public class OfflineAccountSkinPane extends StackPane {
|
||||
});
|
||||
|
||||
JFXHyperlink littleSkinLink = new JFXHyperlink(i18n("account.skin.type.little_skin"));
|
||||
littleSkinLink.setOnAction(e -> FXUtils.openLink("https://mcskin.littleservice.cn/"));
|
||||
littleSkinLink.setOnAction(e -> FXUtils.openLink("https://littleskin.cn/"));
|
||||
JFXButton cancelButton = new JFXButton(i18n("button.cancel"));
|
||||
cancelButton.getStyleClass().add("dialog-cancel");
|
||||
cancelButton.setOnAction(e -> fireEvent(new DialogCloseEvent()));
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 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 com.jfoenix.validation.base.ValidatorBase;
|
||||
import javafx.beans.NamedArg;
|
||||
import javafx.scene.control.TextInputControl;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
public class DoubleValidator extends ValidatorBase {
|
||||
private final boolean nullable;
|
||||
|
||||
public DoubleValidator() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
public DoubleValidator(@NamedArg("nullable") boolean nullable) {
|
||||
this.nullable = nullable;
|
||||
}
|
||||
|
||||
public DoubleValidator(@NamedArg("message") String message, @NamedArg("nullable") boolean nullable) {
|
||||
super(message);
|
||||
this.nullable = nullable;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void eval() {
|
||||
if (srcControl.get() instanceof TextInputControl) {
|
||||
evalTextInputField();
|
||||
}
|
||||
}
|
||||
|
||||
private void evalTextInputField() {
|
||||
TextInputControl textField = ((TextInputControl) srcControl.get());
|
||||
|
||||
if (StringUtils.isBlank(textField.getText()))
|
||||
hasErrors.set(!nullable);
|
||||
else
|
||||
hasErrors.set(Lang.toDoubleOrNull(textField.getText()) == null);
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
@@ -55,7 +56,7 @@ public final class MessageDialogPane extends StackPane {
|
||||
}
|
||||
|
||||
@FXML
|
||||
private Label content;
|
||||
private StackPane content;
|
||||
@FXML
|
||||
private Label graphic;
|
||||
@FXML
|
||||
@@ -68,7 +69,7 @@ public final class MessageDialogPane extends StackPane {
|
||||
public MessageDialogPane(@NotNull String text, @Nullable String title, @NotNull MessageType type) {
|
||||
FXUtils.loadFXML(this, "/assets/fxml/message-dialog.fxml");
|
||||
|
||||
content.setText(text);
|
||||
content.getChildren().setAll(FXUtils.segmentToTextFlow(text, Controllers::onHyperlinkAction));
|
||||
|
||||
if (title != null)
|
||||
this.title.setText(title);
|
||||
|
||||
@@ -22,12 +22,13 @@ import javafx.application.Platform;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.task.TaskListener;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
@@ -37,7 +38,7 @@ import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
|
||||
public class TaskExecutorDialogPane extends StackPane {
|
||||
private TaskExecutor executor;
|
||||
private Consumer<Region> onCancel;
|
||||
private TaskCancellationAction onCancel;
|
||||
private final Consumer<FileDownloadTask.SpeedEvent> speedEventHandler;
|
||||
|
||||
@FXML
|
||||
@@ -49,14 +50,14 @@ public class TaskExecutorDialogPane extends StackPane {
|
||||
@FXML
|
||||
private TaskListPane taskListPane;
|
||||
|
||||
public TaskExecutorDialogPane(Consumer<Region> cancel) {
|
||||
public TaskExecutorDialogPane(@NotNull TaskCancellationAction cancel) {
|
||||
FXUtils.loadFXML(this, "/assets/fxml/task-dialog.fxml");
|
||||
|
||||
setCancel(cancel);
|
||||
|
||||
btnCancel.setOnAction(e -> {
|
||||
Optional.ofNullable(executor).ifPresent(TaskExecutor::cancel);
|
||||
onCancel.accept(this);
|
||||
onCancel.getCancellationAction().accept(this);
|
||||
});
|
||||
|
||||
speedEventHandler = speedEvent -> {
|
||||
@@ -113,7 +114,7 @@ public class TaskExecutorDialogPane extends StackPane {
|
||||
lblTitle.setText(currentState);
|
||||
}
|
||||
|
||||
public void setCancel(Consumer<Region> onCancel) {
|
||||
public void setCancel(TaskCancellationAction onCancel) {
|
||||
this.onCancel = onCancel;
|
||||
|
||||
runInFX(() -> btnCancel.setDisable(onCancel == null));
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.jackhuang.hmcl.download.forge.ForgeNewInstallTask;
|
||||
import org.jackhuang.hmcl.download.forge.ForgeOldInstallTask;
|
||||
import org.jackhuang.hmcl.download.game.GameAssetDownloadTask;
|
||||
import org.jackhuang.hmcl.download.game.GameInstallTask;
|
||||
import org.jackhuang.hmcl.download.java.JavaDownloadTask;
|
||||
import org.jackhuang.hmcl.download.liteloader.LiteLoaderInstallTask;
|
||||
import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask;
|
||||
import org.jackhuang.hmcl.game.HMCLModpackInstallTask;
|
||||
@@ -42,10 +43,15 @@ import org.jackhuang.hmcl.mod.ModpackInstallTask;
|
||||
import org.jackhuang.hmcl.mod.ModpackUpdateTask;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseCompletionTask;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseInstallTask;
|
||||
import org.jackhuang.hmcl.mod.mcbbs.McbbsModpackCompletionTask;
|
||||
import org.jackhuang.hmcl.mod.mcbbs.McbbsModpackExportTask;
|
||||
import org.jackhuang.hmcl.mod.modrinth.ModrinthCompletionTask;
|
||||
import org.jackhuang.hmcl.mod.modrinth.ModrinthInstallTask;
|
||||
import org.jackhuang.hmcl.mod.multimc.MultiMCModpackExportTask;
|
||||
import org.jackhuang.hmcl.mod.multimc.MultiMCModpackInstallTask;
|
||||
import org.jackhuang.hmcl.mod.server.ServerModpackCompletionTask;
|
||||
import org.jackhuang.hmcl.mod.server.ServerModpackExportTask;
|
||||
import org.jackhuang.hmcl.mod.server.ServerModpackLocalInstallTask;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
@@ -123,8 +129,8 @@ public final class TaskListPane extends StackPane {
|
||||
task.setName(i18n("install.installer.install", i18n("install.installer.fabric")));
|
||||
} else if (task instanceof FabricAPIInstallTask) {
|
||||
task.setName(i18n("install.installer.install", i18n("install.installer.fabric-api")));
|
||||
} else if (task instanceof CurseCompletionTask) {
|
||||
task.setName(i18n("modpack.type.curse.completion"));
|
||||
} else if (task instanceof CurseCompletionTask || task instanceof ModrinthCompletionTask || task instanceof ServerModpackCompletionTask || task instanceof McbbsModpackCompletionTask) {
|
||||
task.setName(i18n("modpack.completion"));
|
||||
} else if (task instanceof ModpackInstallTask) {
|
||||
task.setName(i18n("modpack.installing"));
|
||||
} else if (task instanceof ModpackUpdateTask) {
|
||||
@@ -133,12 +139,18 @@ public final class TaskListPane extends StackPane {
|
||||
task.setName(i18n("modpack.install", i18n("modpack.type.curse")));
|
||||
} else if (task instanceof MultiMCModpackInstallTask) {
|
||||
task.setName(i18n("modpack.install", i18n("modpack.type.multimc")));
|
||||
} else if (task instanceof ModrinthInstallTask) {
|
||||
task.setName(i18n("modpack.install", i18n("modpack.type.modrinth")));
|
||||
} else if (task instanceof ServerModpackLocalInstallTask) {
|
||||
task.setName(i18n("modpack.install", i18n("modpack.type.server")));
|
||||
} else if (task instanceof HMCLModpackInstallTask) {
|
||||
task.setName(i18n("modpack.install", i18n("modpack.type.hmcl")));
|
||||
} else if (task instanceof McbbsModpackExportTask || task instanceof MultiMCModpackExportTask || task instanceof ServerModpackExportTask) {
|
||||
task.setName(i18n("modpack.export"));
|
||||
} else if (task instanceof MinecraftInstanceTask) {
|
||||
task.setName(i18n("modpack.scan"));
|
||||
} else if (task instanceof JavaDownloadTask) {
|
||||
task.setName(i18n("download.java"));
|
||||
}
|
||||
|
||||
Platform.runLater(() -> {
|
||||
@@ -186,7 +198,7 @@ public final class TaskListPane extends StackPane {
|
||||
if (task instanceof Task.CountTask) {
|
||||
runInFX(() -> {
|
||||
stageNodes.stream()
|
||||
.filter(x -> x.stage.equals(((Task.CountTask) task).getCountStage()))
|
||||
.filter(x -> x.stage.equals(((Task<?>.CountTask) task).getCountStage()))
|
||||
.findAny()
|
||||
.ifPresent(StageNode::count);
|
||||
});
|
||||
@@ -234,6 +246,7 @@ public final class TaskListPane extends StackPane {
|
||||
case "hmcl.install.optifine": message = i18n("install.installer.install", i18n("install.installer.optifine") + " " + stageValue); break;
|
||||
case "hmcl.install.fabric": message = i18n("install.installer.install", i18n("install.installer.fabric") + " " + stageValue); break;
|
||||
case "hmcl.install.fabric-api": message = i18n("install.installer.install", i18n("install.installer.fabric-api") + " " + stageValue); break;
|
||||
case "hmcl.install.quilt": message = i18n("install.installer.install", i18n("install.installer.quilt") + " " + stageValue); break;
|
||||
default: message = i18n(stageKey); break;
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
@@ -31,7 +31,6 @@ import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
@@ -41,16 +40,13 @@ import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.TabHeader;
|
||||
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.ui.versions.DownloadListPage;
|
||||
import org.jackhuang.hmcl.ui.versions.ModDownloadListPage;
|
||||
import org.jackhuang.hmcl.ui.versions.VersionPage;
|
||||
import org.jackhuang.hmcl.ui.versions.Versions;
|
||||
import org.jackhuang.hmcl.ui.versions.*;
|
||||
import org.jackhuang.hmcl.ui.wizard.Navigation;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -83,7 +79,7 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
newGameTab.setNodeSupplier(loadVersionFor(() -> new VersionsPage(versionPageNavigator, i18n("install.installer.choose", i18n("install.installer.game")), "", DownloadProviders.getDownloadProvider(),
|
||||
"game", versionPageNavigator::onGameSelected)));
|
||||
modpackTab.setNodeSupplier(loadVersionFor(() -> {
|
||||
DownloadListPage page = new DownloadListPage(CurseForgeRemoteModRepository.MODPACKS, Versions::downloadModpackImpl);
|
||||
ModpackDownloadListPage page = new ModpackDownloadListPage(Versions::downloadModpackImpl, false);
|
||||
|
||||
JFXButton installLocalModpackButton = new JFXButton(i18n("install.modpack"));
|
||||
installLocalModpackButton.setButtonType(JFXButton.ButtonType.RAISED);
|
||||
@@ -175,10 +171,7 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
}
|
||||
Path dest = runDirectory.resolve(subdirectoryName).resolve(result);
|
||||
|
||||
TaskExecutorDialogPane downloadingPane = new TaskExecutorDialogPane(it -> {
|
||||
});
|
||||
|
||||
TaskExecutor executor = Task.composeAsync(() -> {
|
||||
Controllers.taskDialog(Task.composeAsync(() -> {
|
||||
FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(file.getFile().getUrl()), dest.toFile());
|
||||
task.setName(file.getName());
|
||||
return task;
|
||||
@@ -192,11 +185,7 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
} else {
|
||||
Controllers.showToast(i18n("install.success"));
|
||||
}
|
||||
}).executor(false);
|
||||
|
||||
downloadingPane.setExecutor(executor, true);
|
||||
Controllers.dialog(downloadingPane);
|
||||
executor.start();
|
||||
}), i18n("message.downloading"), TaskCancellationAction.NORMAL);
|
||||
|
||||
resolve.run();
|
||||
}, file.getFile().getFilename());
|
||||
|
||||
@@ -29,14 +29,13 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.FlowPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.InstallerItem;
|
||||
import org.jackhuang.hmcl.ui.construct.ComponentList;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.RequiredValidator;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
@@ -67,6 +66,10 @@ public class InstallersPage extends Control implements WizardPage {
|
||||
|
||||
group.game.installable.setValue(false);
|
||||
|
||||
for (InstallerItem item : group.getLibraries()) {
|
||||
item.setStyleMode(InstallerItem.Style.CARD);
|
||||
}
|
||||
|
||||
for (InstallerItem library : group.getLibraries()) {
|
||||
String libraryId = library.getLibraryId();
|
||||
if (libraryId.equals("game")) continue;
|
||||
@@ -140,22 +143,23 @@ public class InstallersPage extends Control implements WizardPage {
|
||||
BorderPane root = new BorderPane();
|
||||
root.setPadding(new Insets(16));
|
||||
|
||||
ComponentList list = new ComponentList();
|
||||
list.getStyleClass().add("no-padding");
|
||||
root.setCenter(list);
|
||||
{
|
||||
HBox versionNamePane = new HBox(8);
|
||||
versionNamePane.getStyleClass().add("card-non-transparent");
|
||||
versionNamePane.setStyle("-fx-padding: 20 8 20 16");
|
||||
versionNamePane.setAlignment(Pos.CENTER_LEFT);
|
||||
versionNamePane.setPadding(new Insets(20, 8, 20, 16));
|
||||
|
||||
control.txtName.setMaxWidth(300);
|
||||
versionNamePane.getChildren().setAll(new Label(i18n("archive.name")), control.txtName);
|
||||
list.getContent().add(versionNamePane);
|
||||
root.setTop(versionNamePane);
|
||||
}
|
||||
|
||||
{
|
||||
VBox libraryPane = new VBox(control.group.getLibraries());
|
||||
list.getContent().add(libraryPane);
|
||||
FlowPane libraryPane = new FlowPane(control.group.getLibraries());
|
||||
BorderPane.setMargin(libraryPane, new Insets(16, 0, 16, 0));
|
||||
libraryPane.setVgap(16);
|
||||
libraryPane.setHgap(16);
|
||||
root.setCenter(libraryPane);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ package org.jackhuang.hmcl.ui.download;
|
||||
import javafx.scene.Node;
|
||||
import org.jackhuang.hmcl.game.ModpackHelper;
|
||||
import org.jackhuang.hmcl.game.ManuallyCreatedModpackException;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseCompletionException;
|
||||
import org.jackhuang.hmcl.mod.ModpackCompletionException;
|
||||
import org.jackhuang.hmcl.mod.MismatchedModpackTypeException;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.mod.UnsupportedModpackException;
|
||||
@@ -125,7 +125,7 @@ public class ModpackInstallWizardProvider implements WizardProvider {
|
||||
settings.put("failure_callback", new FailureCallback() {
|
||||
@Override
|
||||
public void onFail(Map<String, Object> settings, Exception exception, Runnable next) {
|
||||
if (exception instanceof CurseCompletionException) {
|
||||
if (exception instanceof ModpackCompletionException) {
|
||||
if (exception.getCause() instanceof FileNotFoundException) {
|
||||
Controllers.dialog(i18n("modpack.type.curse.not_found"), i18n("install.failed"), MessageType.ERROR, next);
|
||||
} else {
|
||||
|
||||
@@ -23,6 +23,7 @@ import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.game.ModpackHelper;
|
||||
import org.jackhuang.hmcl.mod.server.ServerModpackManifest;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.GetTask;
|
||||
@@ -31,8 +32,8 @@ import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -66,7 +67,7 @@ public final class ModpackSelectionPage extends StackPane implements WizardPage
|
||||
Platform.runLater(controller::onNext);
|
||||
}
|
||||
|
||||
FXUtils.applyDragListener(this, it -> "zip".equals(FileUtils.getExtension(it)), modpacks -> {
|
||||
FXUtils.applyDragListener(this, ModpackHelper::isFileModpackByExtension, modpacks -> {
|
||||
File modpack = modpacks.get(0);
|
||||
controller.getSettings().put(MODPACK_FILE, modpack);
|
||||
controller.onNext();
|
||||
@@ -77,7 +78,7 @@ public final class ModpackSelectionPage extends StackPane implements WizardPage
|
||||
private void onChooseLocalFile() {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle(i18n("modpack.choose"));
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("modpack"), "*.zip"));
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("modpack"), "*.zip", "*.mrpack"));
|
||||
File selectedFile = chooser.showOpenDialog(Controllers.getStage());
|
||||
if (selectedFile == null) {
|
||||
Platform.runLater(controller::onEnd);
|
||||
@@ -106,7 +107,7 @@ public final class ModpackSelectionPage extends StackPane implements WizardPage
|
||||
} else {
|
||||
reject.accept(e.getMessage());
|
||||
}
|
||||
}).executor(true), i18n("message.downloading"));
|
||||
}).executor(true), i18n("message.downloading"), TaskCancellationAction.NORMAL);
|
||||
} else {
|
||||
// otherwise we still consider the file as modpack zip file
|
||||
// since casually the url may not ends with ".zip"
|
||||
@@ -124,7 +125,8 @@ public final class ModpackSelectionPage extends StackPane implements WizardPage
|
||||
reject.accept(e.getMessage());
|
||||
}
|
||||
}).executor(true),
|
||||
i18n("message.downloading")
|
||||
i18n("message.downloading"),
|
||||
TaskCancellationAction.NORMAL
|
||||
);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
|
||||
@@ -41,6 +41,7 @@ import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.zip.ZipException;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
@@ -173,6 +174,8 @@ public final class UpdateInstallerWizardProvider implements WizardProvider {
|
||||
} else if (exception instanceof VersionMismatchException) {
|
||||
VersionMismatchException e = ((VersionMismatchException) exception);
|
||||
Controllers.dialog(i18n("install.failed.version_mismatch", e.getExpect(), e.getActual()), i18n("install.failed"), MessageDialogPane.MessageType.ERROR, next);
|
||||
} else if (exception instanceof CancellationException) {
|
||||
// Ignore cancel
|
||||
} else {
|
||||
Controllers.dialog(StringUtils.getStackTrace(exception), i18n("install.failed"), MessageDialogPane.MessageType.ERROR, next);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.download;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXCheckBox;
|
||||
import com.jfoenix.controls.JFXListView;
|
||||
import com.jfoenix.controls.JFXSpinner;
|
||||
@@ -39,7 +40,9 @@ import org.jackhuang.hmcl.download.forge.ForgeRemoteVersion;
|
||||
import org.jackhuang.hmcl.download.game.GameRemoteVersion;
|
||||
import org.jackhuang.hmcl.download.liteloader.LiteLoaderRemoteVersion;
|
||||
import org.jackhuang.hmcl.download.optifine.OptiFineRemoteVersion;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.ui.construct.ComponentList;
|
||||
@@ -58,6 +61,7 @@ import java.util.concurrent.CompletableFuture;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.wrap;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
@@ -84,13 +88,15 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
@FXML
|
||||
private JFXCheckBox chkOld;
|
||||
@FXML
|
||||
private JFXButton btnRefresh;
|
||||
@FXML
|
||||
private HBox checkPane;
|
||||
@FXML
|
||||
private ComponentList centrePane;
|
||||
@FXML
|
||||
private StackPane center;
|
||||
|
||||
private VersionList<?> versionList;
|
||||
private final VersionList<?> versionList;
|
||||
private CompletableFuture<?> executor;
|
||||
|
||||
public VersionsPage(Navigation navigation, String title, String gameVersion, DownloadProvider downloadProvider, String libraryId, Runnable callback) {
|
||||
@@ -114,6 +120,8 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
chkSnapshot.selectedProperty().addListener(listener);
|
||||
chkOld.selectedProperty().addListener(listener);
|
||||
|
||||
btnRefresh.setGraphic(wrap(SVG.refresh(Theme.blackFillBinding(), -1, -1)));
|
||||
|
||||
list.setCellFactory(listView -> new ListCell<RemoteVersion>() {
|
||||
IconedTwoLineListItem content = new IconedTwoLineListItem();
|
||||
RipplerContainer ripplerContainer = new RipplerContainer(content);
|
||||
@@ -136,7 +144,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
|
||||
content.setTitle(remoteVersion.getSelfVersion());
|
||||
if (remoteVersion.getReleaseDate() != null) {
|
||||
content.setSubtitle(Locales.DATE_TIME_FORMATTER.get().format(remoteVersion.getReleaseDate()));
|
||||
content.setSubtitle(Locales.DATE_TIME_FORMATTER.get().format(remoteVersion.getReleaseDate().toInstant()));
|
||||
} else {
|
||||
content.setSubtitle("");
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package org.jackhuang.hmcl.ui.export;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.mod.ModAdviser;
|
||||
import org.jackhuang.hmcl.mod.ModpackExportInfo;
|
||||
import org.jackhuang.hmcl.mod.mcbbs.McbbsModpackExportTask;
|
||||
@@ -33,14 +32,13 @@ import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.io.JarUtils;
|
||||
import org.jackhuang.hmcl.util.io.Zipper;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
@@ -70,8 +68,8 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
}
|
||||
|
||||
private Task<?> exportWithLauncher(String modpackType, ModpackExportInfo exportInfo, File modpackFile) {
|
||||
List<File> launcherJar = Launcher.getCurrentJarFiles();
|
||||
boolean packWithLauncher = exportInfo.isPackWithLauncher() && launcherJar != null;
|
||||
Optional<Path> launcherJar = JarUtils.thisJar();
|
||||
boolean packWithLauncher = exportInfo.isPackWithLauncher() && launcherJar.isPresent();
|
||||
return new Task<Object>() {
|
||||
File tempModpack;
|
||||
Task<?> exportTask;
|
||||
@@ -139,8 +137,7 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
if (background_jpg.isFile())
|
||||
zip.putFile(background_jpg, "background.jpg");
|
||||
|
||||
for (File jar : launcherJar)
|
||||
zip.putFile(jar, jar.getName());
|
||||
zip.putFile(launcherJar.get(), launcherJar.get().getFileName().toString());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -35,7 +35,7 @@ import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.LinkedList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@@ -147,7 +147,7 @@ public final class ModpackFileSelectionPage extends StackPane implements WizardP
|
||||
|
||||
@FXML
|
||||
private void onNext() {
|
||||
LinkedList<String> list = new LinkedList<>();
|
||||
ArrayList<String> list = new ArrayList<>();
|
||||
getFilesNeeded(rootNode, "minecraft", list);
|
||||
controller.getSettings().put(MODPACK_FILE_SELECTION, list);
|
||||
controller.onFinish();
|
||||
|
||||
@@ -29,7 +29,6 @@ import javafx.scene.control.*;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
@@ -43,6 +42,7 @@ import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.JarUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import java.io.File;
|
||||
@@ -96,8 +96,7 @@ public final class ModpackInfoPage extends Control implements WizardPage {
|
||||
launchArguments.set(versionSetting.getMinecraftArgs());
|
||||
javaArguments.set(versionSetting.getJavaArgs());
|
||||
|
||||
List<File> launcherJar = Launcher.getCurrentJarFiles();
|
||||
canIncludeLauncher = launcherJar != null;
|
||||
canIncludeLauncher = JarUtils.thisJar().isPresent();
|
||||
|
||||
next.set(e -> onNext());
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
* Copyright (C) 2022 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
|
||||
@@ -62,7 +62,13 @@ public class AboutPage extends StackPane {
|
||||
bangbang93.setTitle("bangbang93");
|
||||
bangbang93.setSubtitle(i18n("about.thanks_to.bangbang93.statement"));
|
||||
bangbang93.setExternalLink("https://bmclapi2.bangbang93.com/");
|
||||
|
||||
|
||||
IconedTwoLineListItem glavo = new IconedTwoLineListItem();
|
||||
glavo.setImage(new Image("/assets/img/glavo.jpg", 32, 32, false, true));
|
||||
glavo.setTitle("Glavo");
|
||||
glavo.setSubtitle(i18n("about.thanks_to.glavo.statement"));
|
||||
glavo.setExternalLink("https://github.com/Glavo");
|
||||
|
||||
IconedTwoLineListItem gamerteam = new IconedTwoLineListItem();
|
||||
gamerteam.setTitle("gamerteam");
|
||||
gamerteam.setImage(new Image("/assets/img/gamerteam.jpg", 32, 32, false, true));
|
||||
@@ -90,7 +96,7 @@ public class AboutPage extends StackPane {
|
||||
noin.setImage(new Image("/assets/img/noin.png", 32, 32, false, true));
|
||||
noin.setTitle(i18n("about.thanks_to.noin"));
|
||||
noin.setSubtitle(i18n("about.thanks_to.noin.statement"));
|
||||
noin.setExternalLink("https://noin.cn/");
|
||||
noin.setExternalLink("https://mcer.cn/cato");
|
||||
|
||||
IconedTwoLineListItem contributors = new IconedTwoLineListItem();
|
||||
contributors.setImage(new Image("/assets/img/github.png", 32, 32, false, true));
|
||||
@@ -98,7 +104,7 @@ public class AboutPage extends StackPane {
|
||||
contributors.setSubtitle(i18n("about.thanks_to.contributors.statement"));
|
||||
contributors.setExternalLink("https://github.com/huanghongxun/HMCL/graphs/contributors");
|
||||
|
||||
thanks.getContent().setAll(yushijinhun, bangbang93, mcbbs, mcmod, noin, gamerteam, redLnn, contributors);
|
||||
thanks.getContent().setAll(yushijinhun, bangbang93, glavo, mcbbs, mcmod, noin, gamerteam, redLnn, contributors);
|
||||
}
|
||||
|
||||
ComponentList community = new ComponentList();
|
||||
@@ -115,13 +121,13 @@ public class AboutPage extends StackPane {
|
||||
discord.setSubtitle(i18n("about.community.discord.statement"));
|
||||
discord.setExternalLink("https://discord.gg/jVvC7HfM6U");
|
||||
|
||||
IconedTwoLineListItem kaiheila = new IconedTwoLineListItem();
|
||||
kaiheila.setImage(new Image("/assets/img/kaiheila.jpeg", 32, 32, false, true));
|
||||
kaiheila.setTitle(i18n("about.community.kaiheila"));
|
||||
kaiheila.setSubtitle(i18n("about.community.kaiheila.statement"));
|
||||
kaiheila.setExternalLink("https://kaihei.co/Kx7n3t");
|
||||
IconedTwoLineListItem kookapp = new IconedTwoLineListItem();
|
||||
kookapp.setImage(new Image("/assets/img/kookapp.png", 32, 32, false, true));
|
||||
kookapp.setTitle(i18n("about.community.kookapp"));
|
||||
kookapp.setSubtitle(i18n("about.community.kookapp.statement"));
|
||||
kookapp.setExternalLink("https://kook.top/Kx7n3t");
|
||||
|
||||
community.getContent().setAll(users, discord, kaiheila);
|
||||
community.getContent().setAll(users, discord, kookapp);
|
||||
}
|
||||
|
||||
ComponentList dep = new ComponentList();
|
||||
@@ -167,9 +173,9 @@ public class AboutPage extends StackPane {
|
||||
jstun.setExternalLink("https://github.com/tking/JSTUN");
|
||||
|
||||
IconedTwoLineListItem minecraftJFXSkin = new IconedTwoLineListItem();
|
||||
jstun.setTitle("minecraft-jfx-skin");
|
||||
jstun.setSubtitle("Copyright (c) 2016 InfinityStudio.\nLicensed under the GPL 3.");
|
||||
jstun.setExternalLink("https://github.com/InfinityStudio/minecraft-jfx-skin");
|
||||
minecraftJFXSkin.setTitle("minecraft-jfx-skin");
|
||||
minecraftJFXSkin.setSubtitle("Copyright (c) 2016 InfinityStudio.\nLicensed under the GPL 3.");
|
||||
minecraftJFXSkin.setExternalLink("https://github.com/InfinityStudio/minecraft-jfx-skin");
|
||||
|
||||
dep.getContent().setAll(javafx, jfoenix, gson, xz, fxgson, constantPoolScanner, openNBT, jstun, minecraftJFXSkin);
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ public class FeedbackPage extends VBox implements PageAware {
|
||||
container.setPickOnBounds(false);
|
||||
container.setAlignment(Pos.CENTER_LEFT);
|
||||
HBox.setHgrow(content, Priority.ALWAYS);
|
||||
content.setMouseTransparent(true);
|
||||
content.setMouseTransparent(false);
|
||||
setSelectable();
|
||||
|
||||
likeButton.getStyleClass().add("toggle-icon4");
|
||||
@@ -152,7 +152,7 @@ public class FeedbackPage extends VBox implements PageAware {
|
||||
"#" + feedback.getId(),
|
||||
i18n("feedback.state." + feedback.getState().name().toLowerCase(Locale.US)),
|
||||
i18n("feedback.type." + feedback.getType().name().toLowerCase(Locale.US)));
|
||||
container.setOnMouseClicked(e -> {
|
||||
content.setOnMouseClicked(e -> {
|
||||
getFeedback(feedback.getId())
|
||||
.thenAcceptAsync(Schedulers.javafx(), f -> {
|
||||
Controllers.dialog(new ViewFeedbackDialog(f));
|
||||
@@ -243,8 +243,8 @@ public class FeedbackPage extends VBox implements PageAware {
|
||||
HintPane hintPane = new HintPane(MessageDialogPane.MessageType.INFO);
|
||||
hintPane.textProperty().bind(BindingMapping.of(logging).map(logging ->
|
||||
logging
|
||||
? i18n("account.methods.microsoft.manual")
|
||||
: i18n("account.methods.microsoft.hint")));
|
||||
? i18n("account.hmcl.hint")
|
||||
: i18n("account.hmcl.hint")));
|
||||
hintPane.setOnMouseClicked(e -> {
|
||||
if (logging.get() && OAuthServer.lastlyOpenedURL != null) {
|
||||
FXUtils.copyText(OAuthServer.lastlyOpenedURL);
|
||||
|
||||
@@ -40,8 +40,8 @@ import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
|
||||
import org.jackhuang.hmcl.ui.versions.GameAdvancedListItem;
|
||||
import org.jackhuang.hmcl.ui.versions.Versions;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.io.File;
|
||||
@@ -82,7 +82,7 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
private MainPage getMainPage() {
|
||||
if (mainPage == null) {
|
||||
MainPage mainPage = new MainPage();
|
||||
FXUtils.applyDragListener(mainPage, it -> "zip".equals(FileUtils.getExtension(it)), modpacks -> {
|
||||
FXUtils.applyDragListener(mainPage, ModpackHelper::isFileModpackByExtension, modpacks -> {
|
||||
File modpack = modpacks.get(0);
|
||||
Controllers.getDecorator().startWizard(
|
||||
new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), modpack),
|
||||
@@ -199,7 +199,7 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
modpack)
|
||||
.executor())
|
||||
.thenAcceptAsync(Schedulers.javafx(), executor -> {
|
||||
Controllers.taskDialog(executor, i18n("modpack.installing"));
|
||||
Controllers.taskDialog(executor, i18n("modpack.installing"), TaskCancellationAction.NO_CANCEL);
|
||||
executor.start();
|
||||
}).start();
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
* Copyright (C) 2022 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
|
||||
@@ -82,8 +82,8 @@ public class CreateMultiplayerRoomDialog extends DialogPane implements DialogAwa
|
||||
setValid(false);
|
||||
|
||||
JFXHyperlink noinLink = new JFXHyperlink();
|
||||
noinLink.setText("noin.cn");
|
||||
noinLink.setOnAction(e -> FXUtils.openLink("https://noin.cn"));
|
||||
noinLink.setText("mcer.cn");
|
||||
noinLink.setOnAction(e -> FXUtils.openLink("https://mcer.cn/circle/cato"));
|
||||
|
||||
setActions(warningLabel, noinLink, acceptPane, cancelButton);
|
||||
|
||||
|
||||
@@ -54,11 +54,11 @@ public class MultiplayerClient extends Thread {
|
||||
setDaemon(true);
|
||||
}
|
||||
|
||||
public void setGamePort(int gamePort) {
|
||||
public synchronized void setGamePort(int gamePort) {
|
||||
this.gamePort = gamePort;
|
||||
}
|
||||
|
||||
public int getGamePort() {
|
||||
public synchronized int getGamePort() {
|
||||
return gamePort;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,15 +19,17 @@ package org.jackhuang.hmcl.ui.multiplayer;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import javafx.application.Platform;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.event.Event;
|
||||
import org.jackhuang.hmcl.event.EventManager;
|
||||
import org.jackhuang.hmcl.launch.StreamPump;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.ChecksumMismatchException;
|
||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.CommandBuilder;
|
||||
@@ -50,65 +52,59 @@ import java.util.logging.Level;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Lang.wrap;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
/**
|
||||
* Cato Management.
|
||||
*/
|
||||
public final class MultiplayerManager {
|
||||
static final String CATO_VERSION = "1.1.1-3f36a56";
|
||||
// private static final String CATO_DOWNLOAD_URL = "https://files.huangyuhui.net/maven/cato/cato/" + MultiplayerManager.CATO_VERSION;
|
||||
private static final String CATO_DOWNLOAD_URL = "https://codechina.csdn.net/huanghongxun1/ioi_bin/-/raw/3f36a567d9381e5fa404b96bcecd6d6a555f235c/client/";
|
||||
static final String CATO_VERSION = "1.2.2";
|
||||
private static final String CATO_DOWNLOAD_URL = "https://gitcode.net/to/cato/-/raw/master/client/";
|
||||
private static final String CATO_HASH_URL = CATO_DOWNLOAD_URL + "cato-all-files.sha1";
|
||||
private static final String CATO_PATH = getCatoPath();
|
||||
public static final int CATO_AGREEMENT_VERSION = 2;
|
||||
private static final String REMOTE_ADDRESS = "127.0.0.1";
|
||||
private static final String LOCAL_ADDRESS = "0.0.0.0";
|
||||
|
||||
private static final Map<String, String> HASH = mapOf(
|
||||
pair("cato-client-darwin-amd64", "e733fa4b72be24b99026249c31c4ae8b52ad3906"),
|
||||
pair("cato-client-darwin-arm64", "19ab41b7f78263cad81009ae1a391b0d0367d95f"),
|
||||
pair("cato-client-freebsd-amd64", "6de8439ab9267e444d45bb9f02394023ce0bfe5e"),
|
||||
pair("cato-client-freebsd-arm64", "5cd8930e7cd17d72386b085950e63467e4ce72ef"),
|
||||
pair("cato-client-freebsd-arm7", "d0e54a4a1b0f8c2f769d74a6a600eec4a9c4e0da"),
|
||||
pair("cato-client-freebsd-i386", "d9d22faf85e01e77b18f28f1609e87623ac9b1a4"),
|
||||
pair("cato-client-js.wasm", "acb366a4d6a3c6cd464ef5919ce7d2d58a52964e"),
|
||||
pair("cato-client-linux-amd64", "04e94a3adff1e4d865dead2c6c5da584b74bff0a"),
|
||||
pair("cato-client-linux-arm64", "ba662bc461995dae5e78d21b9fea35c2db81b652"),
|
||||
pair("cato-client-linux-arm7", "28021eff56e1e273c051853808e70e7ab3af80d9"),
|
||||
pair("cato-client-linux-i386", "4f524a9cfbcea560bdf07d3468b883f946c431ed"),
|
||||
pair("cato-client-linux-mips", "91c3108b046511c2aafbe88f82b38a06c03d88e1"),
|
||||
pair("cato-client-linux-mips64", "04d1b66494dbdb0443eaae85bda6b61408e300ed"),
|
||||
pair("cato-client-linux-mips64le", "d838ac71052f7873b28f4b4a2f23470b918ef85d"),
|
||||
pair("cato-client-linux-mipsle", "54918ed9cdbe48f7adf11680947281f5fc7c5a7d"),
|
||||
pair("cato-client-linux-ppc64", "f2679259516b0db0422f85afcce35bfa6f7b3192"),
|
||||
pair("cato-client-linux-ppc64le", "463d9b86316dfc83e668ddfae96e51181a9e3ffa"),
|
||||
pair("cato-client-openbsd-amd64", "b776155e955340485faf62c3b2aad3a788c57e2b"),
|
||||
pair("cato-client-openbsd-arm64", "fd6ec21248fd9c8be3d9d2876faf65b95c63b3a9"),
|
||||
pair("cato-client-openbsd-arm7", "19eabe71530bb4eac7a32f6b8aeef72f6f9382e9"),
|
||||
pair("cato-client-openbsd-i386", "3e755798628a379997dbc43dfd71765d62263ebe"),
|
||||
pair("cato-client-windows-amd64.exe", "21a862f455c909c9fb786cd308e969629eb3c8d9"),
|
||||
pair("cato-client-windows-arm64.exe", "7f2fe5d054ebc3265edb046badafdd6ac94bf7eb"),
|
||||
pair("cato-client-windows-i386.exe", "4e03e640c6cabfd7e1ddf60bba1726353bc3afee")
|
||||
);
|
||||
private static CompletableFuture<Map<String, String>> HASH;
|
||||
|
||||
private MultiplayerManager() {
|
||||
}
|
||||
|
||||
private static CompletableFuture<Map<String, String>> getCatoHash() {
|
||||
FXUtils.checkFxUserThread();
|
||||
if (HASH == null) {
|
||||
HASH = CompletableFuture.supplyAsync(wrap(() -> {
|
||||
String hashList = HttpRequest.GET(CATO_HASH_URL).getString();
|
||||
Map<String, String> hashes = new HashMap<>();
|
||||
for (String line : hashList.split("\n")) {
|
||||
String[] items = line.trim().split(" ");
|
||||
if (items.length == 2 && items[0].length() == 40) {
|
||||
hashes.put(items[1], items[0]);
|
||||
} else {
|
||||
LOG.warning("Failed to parse cato hash file, hash line " + line);
|
||||
}
|
||||
}
|
||||
return hashes;
|
||||
}));
|
||||
}
|
||||
return HASH;
|
||||
}
|
||||
|
||||
public static Task<Void> downloadCato() {
|
||||
return new FileDownloadTask(
|
||||
NetworkUtils.toURL(CATO_DOWNLOAD_URL + getCatoFileName()),
|
||||
getCatoExecutable().toFile(),
|
||||
new FileDownloadTask.IntegrityCheck("SHA-1", HASH.get(getCatoFileName()))
|
||||
).thenRunAsync(() -> {
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX || OperatingSystem.CURRENT_OS == OperatingSystem.OSX) {
|
||||
Set<PosixFilePermission> perm = Files.getPosixFilePermissions(getCatoExecutable());
|
||||
perm.add(PosixFilePermission.OWNER_EXECUTE);
|
||||
Files.setPosixFilePermissions(getCatoExecutable(), perm);
|
||||
}
|
||||
});
|
||||
return Task.fromCompletableFuture(getCatoHash()).thenComposeAsync(catoHashes ->
|
||||
new FileDownloadTask(
|
||||
NetworkUtils.toURL(CATO_DOWNLOAD_URL + getCatoFileName()),
|
||||
getCatoExecutable().toFile(),
|
||||
catoHashes.get(getCatoFileName()) == null ? null : new FileDownloadTask.IntegrityCheck("SHA-1", catoHashes.get(getCatoFileName()))
|
||||
).thenRunAsync(() -> {
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX || OperatingSystem.CURRENT_OS == OperatingSystem.OSX) {
|
||||
Set<PosixFilePermission> perm = Files.getPosixFilePermissions(getCatoExecutable());
|
||||
perm.add(PosixFilePermission.OWNER_EXECUTE);
|
||||
Files.setPosixFilePermissions(getCatoExecutable(), perm);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public static Path getCatoExecutable() {
|
||||
@@ -116,7 +112,7 @@ public final class MultiplayerManager {
|
||||
}
|
||||
|
||||
private static CompletableFuture<CatoSession> startCato(String token, State state) {
|
||||
return CompletableFuture.completedFuture(null).thenApplyAsync(wrap(unused -> {
|
||||
return getCatoHash().thenApplyAsync(wrap(catoHashes -> {
|
||||
Path exe = getCatoExecutable();
|
||||
if (!Files.isRegularFile(exe)) {
|
||||
throw new CatoNotExistsException(exe);
|
||||
@@ -127,13 +123,18 @@ public final class MultiplayerManager {
|
||||
}
|
||||
|
||||
try {
|
||||
ChecksumMismatchException.verifyChecksum(exe, "SHA-1", HASH.get(getCatoFileName()));
|
||||
String hash = catoHashes.get(getCatoFileName());
|
||||
if (hash != null) {
|
||||
ChecksumMismatchException.verifyChecksum(exe, "SHA-1", hash);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Files.deleteIfExists(exe);
|
||||
throw e;
|
||||
}
|
||||
|
||||
String[] commands = new String[]{exe.toString(), "--token", StringUtils.isBlank(token) ? "new" : token};
|
||||
String[] commands = StringUtils.isBlank(token)
|
||||
? new String[]{exe.toString()}
|
||||
: new String[]{exe.toString(), "-auth.token", token};
|
||||
Process process = new ProcessBuilder()
|
||||
.command(commands)
|
||||
.start();
|
||||
@@ -176,7 +177,7 @@ public final class MultiplayerManager {
|
||||
session.setClient(client);
|
||||
|
||||
TimerTask task = Lang.setTimeout(() -> {
|
||||
future.completeExceptionally(new JoinRequestTimeoutException());
|
||||
Platform.runLater(() -> future.completeExceptionally(new JoinRequestTimeoutException()));
|
||||
session.stop();
|
||||
}, 30 * 1000);
|
||||
|
||||
@@ -187,24 +188,30 @@ public final class MultiplayerManager {
|
||||
session.addRelatedThread(Lang.thread(new LocalServerBroadcaster(port, session), "LocalServerBroadcaster", true));
|
||||
session.setName(connectedEvent.getSessionName());
|
||||
client.setGamePort(port);
|
||||
session.onExit.unregister(onExit);
|
||||
future.complete(session);
|
||||
Platform.runLater(() -> {
|
||||
session.onExit.unregister(onExit);
|
||||
future.complete(session);
|
||||
});
|
||||
} catch (IOException e) {
|
||||
future.completeExceptionally(e);
|
||||
session.stop();
|
||||
Platform.runLater(() -> future.completeExceptionally(e));
|
||||
}
|
||||
task.cancel();
|
||||
});
|
||||
client.onKicked().register(kickedEvent -> {
|
||||
future.completeExceptionally(new KickedException(kickedEvent.getReason()));
|
||||
session.stop();
|
||||
task.cancel();
|
||||
Platform.runLater(() -> {
|
||||
future.completeExceptionally(new KickedException(kickedEvent.getReason()));
|
||||
});
|
||||
});
|
||||
client.onDisconnected().register(disconnectedEvent -> {
|
||||
if (!client.isConnected()) {
|
||||
// We fail to establish connection with server
|
||||
future.completeExceptionally(new ConnectionErrorException());
|
||||
}
|
||||
Platform.runLater(() -> {
|
||||
if (!client.isConnected()) {
|
||||
// We fail to establish connection with server
|
||||
future.completeExceptionally(new ConnectionErrorException());
|
||||
}
|
||||
});
|
||||
});
|
||||
client.onHandshake().register(handshakeEvent -> {
|
||||
if (handler != null) {
|
||||
@@ -254,9 +261,11 @@ public final class MultiplayerManager {
|
||||
}, 15 * 1000);
|
||||
|
||||
session.onPeerConnected.register(event -> {
|
||||
session.onExit.unregister(onExit);
|
||||
future.complete(session);
|
||||
peerConnectionTimeoutTask.cancel();
|
||||
Platform.runLater(() -> {
|
||||
session.onExit.unregister(onExit);
|
||||
future.complete(session);
|
||||
});
|
||||
});
|
||||
|
||||
return future;
|
||||
@@ -348,17 +357,17 @@ public final class MultiplayerManager {
|
||||
|
||||
this.type = type;
|
||||
addRelatedThread(Lang.thread(this::waitFor, "CatoExitWaiter", true));
|
||||
addRelatedThread(Lang.thread(new StreamPump(process.getInputStream(), this::checkCatoLog), "CatoInputStreamPump", true));
|
||||
addRelatedThread(Lang.thread(new StreamPump(process.getErrorStream(), this::checkCatoLog), "CatoErrorStreamPump", true));
|
||||
pumpInputStream(this::checkCatoLog);
|
||||
pumpErrorStream(this::checkCatoLog);
|
||||
|
||||
writer = new BufferedWriter(new OutputStreamWriter(process.getOutputStream(), StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
public MultiplayerClient getClient() {
|
||||
public synchronized MultiplayerClient getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public CatoSession setClient(MultiplayerClient client) {
|
||||
public synchronized CatoSession setClient(MultiplayerClient client) {
|
||||
this.client = client;
|
||||
return this;
|
||||
}
|
||||
@@ -400,7 +409,8 @@ public final class MultiplayerManager {
|
||||
onExit.fireEvent(new CatoExitEvent(this, CatoExitEvent.EXIT_CODE_INTERRUPTED));
|
||||
} finally {
|
||||
try {
|
||||
writer.close();
|
||||
if (writer != null)
|
||||
writer.close();
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to close cato stdin writer", e);
|
||||
}
|
||||
@@ -412,11 +422,11 @@ public final class MultiplayerManager {
|
||||
return id != null;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
public synchronized String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
public synchronized void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.HMCLService;
|
||||
import org.jackhuang.hmcl.util.Result;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.io.ChecksumMismatchException;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -134,7 +135,7 @@ public class MultiplayerPage extends DecoratorAnimatedPage implements DecoratorP
|
||||
|
||||
private void testNAT() {
|
||||
Task.supplyAsync(() -> {
|
||||
DiscoveryTest tester = new DiscoveryTest(null, 0, "stun.stunprotocol.org", 3478);
|
||||
DiscoveryTest tester = new DiscoveryTest(null, 0, "stun.miwifi.com", 3478);
|
||||
return tester.test();
|
||||
}).whenComplete(Schedulers.javafx(), (info, exception) -> {
|
||||
if (exception == null) {
|
||||
@@ -196,7 +197,7 @@ public class MultiplayerPage extends DecoratorAnimatedPage implements DecoratorP
|
||||
Controllers.showToast(i18n("multiplayer.download.success"));
|
||||
}
|
||||
}).executor();
|
||||
Controllers.taskDialog(executor, i18n("multiplayer.download"));
|
||||
Controllers.taskDialog(executor, i18n("multiplayer.download"), TaskCancellationAction.NORMAL);
|
||||
executor.start();
|
||||
} else {
|
||||
setDisabled(false);
|
||||
@@ -373,10 +374,11 @@ public class MultiplayerPage extends DecoratorAnimatedPage implements DecoratorP
|
||||
}
|
||||
} else if (e instanceof MultiplayerManager.CatoExitException) {
|
||||
LOG.info("Cato exited accidentally");
|
||||
int exitCode = ((MultiplayerManager.CatoExitException) e).getExitCode();
|
||||
if (!((MultiplayerManager.CatoExitException) e).isReady()) {
|
||||
return i18n("multiplayer.exit.before_ready");
|
||||
return i18n("multiplayer.exit.before_ready", exitCode);
|
||||
} else {
|
||||
return i18n("multiplayer.exit.after_ready");
|
||||
return i18n("multiplayer.exit.after_ready", exitCode);
|
||||
}
|
||||
} else if (e instanceof ChecksumMismatchException) {
|
||||
return i18n("exception.artifact_malformed");
|
||||
|
||||
@@ -128,8 +128,8 @@ public class MultiplayerPageSkin extends DecoratorAnimatedPage.DecoratorAnimated
|
||||
.startCategory(i18n("help"))
|
||||
.addNavigationDrawerItem(item -> {
|
||||
item.setTitle(i18n("help"));
|
||||
item.setLeftGraphic(wrap(SVG::gamepad));
|
||||
item.setOnAction(e -> FXUtils.openLink("https://hmcl.huangyuhui.net/help/launcher/multiplayer.html"));
|
||||
item.setLeftGraphic(wrap(SVG::helpCircleOutline));
|
||||
item.setOnAction(e -> FXUtils.openLink("https://docs.hmcl.net/multiplayer"));
|
||||
})
|
||||
.addNavigationDrawerItem(report -> {
|
||||
report.setTitle(i18n("feedback"));
|
||||
@@ -278,7 +278,7 @@ public class MultiplayerPageSkin extends DecoratorAnimatedPage.DecoratorAnimated
|
||||
HBox.setHgrow(placeholder, Priority.ALWAYS);
|
||||
|
||||
pane.getChildren().setAll(
|
||||
new Label("cato " + MultiplayerManager.CATO_VERSION),
|
||||
new Label("Based on Cato"),
|
||||
aboutLink,
|
||||
placeholder,
|
||||
FXUtils.segmentToTextFlow(i18n("multiplayer.powered_by"), Controllers::onHyperlinkAction));
|
||||
|
||||
@@ -82,7 +82,6 @@ class DatapackListPageSkin extends SkinBase<DatapackListPage> {
|
||||
|
||||
{
|
||||
Region clippedContainer = (Region)listView.lookup(".clipped-container");
|
||||
setPrefWidth(0);
|
||||
HBox container = new HBox(8);
|
||||
container.setPadding(new Insets(0, 0, 0, 6));
|
||||
container.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
@@ -62,6 +62,7 @@ import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -88,6 +89,8 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
private TaskExecutor executor;
|
||||
protected RemoteModRepository repository;
|
||||
|
||||
private Runnable retrySearch;
|
||||
|
||||
public DownloadListPage(RemoteModRepository repository) {
|
||||
this(repository, null);
|
||||
}
|
||||
@@ -115,7 +118,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
|
||||
if (!searchInitialized) {
|
||||
searchInitialized = true;
|
||||
search("", null, 0, "", 0);
|
||||
search("", null, 0, "", RemoteModRepository.SortType.DATE_CREATED);
|
||||
}
|
||||
|
||||
if (versionSelection) {
|
||||
@@ -150,7 +153,8 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
this.loading.set(loading);
|
||||
}
|
||||
|
||||
public void search(String userGameVersion, RemoteModRepository.Category category, int pageOffset, String searchFilter, int sort) {
|
||||
public void search(String userGameVersion, RemoteModRepository.Category category, int pageOffset, String searchFilter, RemoteModRepository.SortType sort) {
|
||||
retrySearch = null;
|
||||
setLoading(true);
|
||||
setFailed(false);
|
||||
File versionJar = StringUtils.isNotBlank(version.get().getVersion())
|
||||
@@ -169,7 +173,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
}
|
||||
return gameVersion;
|
||||
}).thenApplyAsync(gameVersion -> {
|
||||
return repository.search(gameVersion, category, pageOffset, 50, searchFilter, sort);
|
||||
return repository.search(gameVersion, category, pageOffset, 50, searchFilter, sort, RemoteModRepository.SortOrder.DESC);
|
||||
}).whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
setLoading(false);
|
||||
if (exception == null) {
|
||||
@@ -177,6 +181,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
failed.set(false);
|
||||
} else {
|
||||
failed.set(true);
|
||||
retrySearch = () -> search(userGameVersion, category, pageOffset, searchFilter, sort);
|
||||
}
|
||||
}).executor(true);
|
||||
}
|
||||
@@ -186,7 +191,10 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
}
|
||||
|
||||
protected String getLocalizedCategoryIndent(ModDownloadListPageSkin.CategoryIndented category) {
|
||||
return StringUtils.repeats(' ', category.indent * 4) + getLocalizedCategory(category.getCategory() == null ? "0" : category.getCategory().getId());
|
||||
return StringUtils.repeats(' ', category.indent * 4) +
|
||||
(category.getCategory() == null
|
||||
? i18n("curse.category.0")
|
||||
: getLocalizedCategory(category.getCategory().getId()));
|
||||
}
|
||||
|
||||
protected String getLocalizedOfficialPage() {
|
||||
@@ -238,13 +246,19 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
{
|
||||
int rowIndex = 0;
|
||||
|
||||
if (control.versionSelection) {
|
||||
JFXComboBox<String> versionsComboBox = new JFXComboBox<>();
|
||||
versionsComboBox.setMaxWidth(Double.MAX_VALUE);
|
||||
Bindings.bindContent(versionsComboBox.getItems(), control.versions);
|
||||
selectedItemPropertyFor(versionsComboBox).bindBidirectional(control.selectedVersion);
|
||||
if (control.versionSelection || !control.downloadSources.isEmpty()) {
|
||||
searchPane.addRow(rowIndex);
|
||||
int columns = 0;
|
||||
Node lastNode = null;
|
||||
if (control.versionSelection) {
|
||||
JFXComboBox<String> versionsComboBox = new JFXComboBox<>();
|
||||
versionsComboBox.setMaxWidth(Double.MAX_VALUE);
|
||||
Bindings.bindContent(versionsComboBox.getItems(), control.versions);
|
||||
selectedItemPropertyFor(versionsComboBox).bindBidirectional(control.selectedVersion);
|
||||
|
||||
searchPane.addRow(rowIndex, new Label(i18n("version")), versionsComboBox);
|
||||
searchPane.add(new Label(i18n("version")), columns++, rowIndex);
|
||||
searchPane.add(lastNode = versionsComboBox, columns++, rowIndex);
|
||||
}
|
||||
|
||||
if (control.downloadSources.getSize() > 1) {
|
||||
JFXComboBox<String> downloadSourceComboBox = new JFXComboBox<>();
|
||||
@@ -253,10 +267,12 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
downloadSourceComboBox.setConverter(stringConverter(I18n::i18n));
|
||||
selectedItemPropertyFor(downloadSourceComboBox).bindBidirectional(control.downloadSource);
|
||||
|
||||
searchPane.add(new Label(i18n("settings.launcher.download_source")), 2, rowIndex);
|
||||
searchPane.add(downloadSourceComboBox, 3, rowIndex);
|
||||
} else {
|
||||
GridPane.setColumnSpan(versionsComboBox, 3);
|
||||
searchPane.add(new Label(i18n("settings.launcher.download_source")), columns++, rowIndex);
|
||||
searchPane.add(lastNode = downloadSourceComboBox, columns++, rowIndex);
|
||||
}
|
||||
|
||||
if (columns == 2) {
|
||||
GridPane.setColumnSpan(lastNode, 3);
|
||||
}
|
||||
|
||||
rowIndex++;
|
||||
@@ -296,28 +312,25 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
categoryComboBox.setPromptText(i18n("mods.category"));
|
||||
categoryComboBox.getSelectionModel().select(0);
|
||||
categoryComboBox.setConverter(stringConverter(getSkinnable()::getLocalizedCategoryIndent));
|
||||
Task.supplyAsync(() -> getSkinnable().repository.getCategories())
|
||||
.thenAcceptAsync(Schedulers.javafx(), categories -> {
|
||||
List<CategoryIndented> result = new ArrayList<>();
|
||||
result.add(new CategoryIndented(0, null));
|
||||
for (RemoteModRepository.Category category : Lang.toIterable(categories)) {
|
||||
resolveCategory(category, 0, result);
|
||||
}
|
||||
categoryComboBox.getItems().setAll(result);
|
||||
}).start();
|
||||
FXUtils.onChangeAndOperate(getSkinnable().downloadSource, downloadSource -> {
|
||||
Task.supplyAsync(() -> getSkinnable().repository.getCategories())
|
||||
.thenAcceptAsync(Schedulers.javafx(), categories -> {
|
||||
List<CategoryIndented> result = new ArrayList<>();
|
||||
result.add(new CategoryIndented(0, null));
|
||||
for (RemoteModRepository.Category category : Lang.toIterable(categories)) {
|
||||
resolveCategory(category, 0, result);
|
||||
}
|
||||
categoryComboBox.getItems().setAll(result);
|
||||
}).start();
|
||||
});
|
||||
|
||||
StackPane sortStackPane = new StackPane();
|
||||
JFXComboBox<String> sortComboBox = new JFXComboBox<>();
|
||||
JFXComboBox<RemoteModRepository.SortType> sortComboBox = new JFXComboBox<>();
|
||||
sortStackPane.getChildren().setAll(sortComboBox);
|
||||
sortComboBox.prefWidthProperty().bind(sortStackPane.widthProperty());
|
||||
sortComboBox.getStyleClass().add("fit-width");
|
||||
sortComboBox.getItems().setAll(
|
||||
i18n("curse.sort.date_created"),
|
||||
i18n("curse.sort.popularity"),
|
||||
i18n("curse.sort.last_updated"),
|
||||
i18n("curse.sort.name"),
|
||||
i18n("curse.sort.author"),
|
||||
i18n("curse.sort.total_downloads"));
|
||||
sortComboBox.setConverter(stringConverter(sortType -> i18n("curse.sort." + sortType.name().toLowerCase(Locale.ROOT))));
|
||||
sortComboBox.getItems().setAll(RemoteModRepository.SortType.values());
|
||||
sortComboBox.getSelectionModel().select(0);
|
||||
searchPane.addRow(rowIndex++, new Label(i18n("mods.category")), categoryStackPane, new Label(i18n("search.sort")), sortStackPane);
|
||||
|
||||
@@ -341,7 +354,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
.orElse(null),
|
||||
0,
|
||||
nameField.getText(),
|
||||
sortComboBox.getSelectionModel().getSelectedIndex());
|
||||
sortComboBox.getSelectionModel().getSelectedItem());
|
||||
searchButton.setOnAction(searchAction);
|
||||
nameField.setOnAction(searchAction);
|
||||
gameVersionField.setOnAction(searchAction);
|
||||
@@ -360,6 +373,11 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
return null;
|
||||
}
|
||||
}, getSkinnable().failedProperty()));
|
||||
spinnerPane.setOnFailedAction(e -> {
|
||||
if (getSkinnable().retrySearch != null) {
|
||||
getSkinnable().retrySearch.run();
|
||||
}
|
||||
});
|
||||
|
||||
JFXListView<RemoteMod> listView = new JFXListView<>();
|
||||
spinnerPane.setContent(listView);
|
||||
@@ -385,8 +403,8 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
@Override
|
||||
protected void updateControl(RemoteMod dataItem, boolean empty) {
|
||||
if (empty) return;
|
||||
ModTranslations.Mod mod = ModTranslations.getModByCurseForgeId(dataItem.getSlug());
|
||||
content.setTitle(mod != null ? mod.getDisplayName() : dataItem.getTitle());
|
||||
ModTranslations.Mod mod = ModTranslations.getTranslationsByRepositoryType(getSkinnable().repository.getType()).getModByCurseForgeId(dataItem.getSlug());
|
||||
content.setTitle(mod != null && I18n.getCurrentLocale().getLocale() == Locale.CHINA ? mod.getDisplayName() : dataItem.getTitle());
|
||||
content.setSubtitle(dataItem.getDescription());
|
||||
content.getTags().setAll(dataItem.getCategories().stream()
|
||||
.map(category -> getSkinnable().getLocalizedCategory(category))
|
||||
|
||||
@@ -38,6 +38,7 @@ import javafx.scene.layout.*;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.mod.ModManager;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
import org.jackhuang.hmcl.mod.RemoteModRepository;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
@@ -50,6 +51,8 @@ import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.SimpleMultimap;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -69,6 +72,8 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
private final BooleanProperty loaded = new SimpleBooleanProperty(false);
|
||||
private final BooleanProperty loading = new SimpleBooleanProperty(false);
|
||||
private final BooleanProperty failed = new SimpleBooleanProperty(false);
|
||||
private final RemoteModRepository repository;
|
||||
private final ModTranslations translations;
|
||||
private final RemoteMod addon;
|
||||
private final ModTranslations.Mod mod;
|
||||
private final Profile.ProfileVersion version;
|
||||
@@ -80,11 +85,18 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
|
||||
public DownloadPage(DownloadListPage page, RemoteMod addon, Profile.ProfileVersion version, @Nullable DownloadCallback callback) {
|
||||
this.page = page;
|
||||
this.repository = page.repository;
|
||||
this.addon = addon;
|
||||
this.mod = ModTranslations.getModByCurseForgeId(addon.getSlug());
|
||||
this.translations = ModTranslations.getTranslationsByRepositoryType(repository.getType());
|
||||
this.mod = translations.getModByCurseForgeId(addon.getSlug());
|
||||
this.version = version;
|
||||
this.callback = callback;
|
||||
loadModVersions();
|
||||
|
||||
this.state.set(State.fromTitle(addon.getTitle()));
|
||||
}
|
||||
|
||||
private void loadModVersions() {
|
||||
File versionJar = StringUtils.isNotBlank(version.getVersion())
|
||||
? version.getProfile().getRepository().getVersionJar(version.getVersion())
|
||||
: null;
|
||||
@@ -93,9 +105,9 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
setFailed(false);
|
||||
|
||||
Task.allOf(
|
||||
Task.supplyAsync(() -> addon.getData().loadDependencies()),
|
||||
Task.supplyAsync(() -> {
|
||||
Stream<RemoteMod.Version> versions = addon.getData().loadVersions();
|
||||
Task.supplyAsync(() -> addon.getData().loadDependencies(repository)),
|
||||
Task.supplyAsync(() -> {
|
||||
Stream<RemoteMod.Version> versions = addon.getData().loadVersions(repository);
|
||||
// if (StringUtils.isNotBlank(version.getVersion())) {
|
||||
// Optional<String> gameVersion = GameVersion.minecraftVersion(versionJar);
|
||||
// if (gameVersion.isPresent()) {
|
||||
@@ -103,8 +115,8 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
// .filter(file -> file.getGameVersions().contains(gameVersion.get())));
|
||||
// }
|
||||
// }
|
||||
return sortVersions(versions);
|
||||
}))
|
||||
return sortVersions(versions);
|
||||
}))
|
||||
.whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
if (exception == null) {
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -122,8 +134,6 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
}
|
||||
setLoading(false);
|
||||
}).start();
|
||||
|
||||
this.state.set(State.fromTitle(addon.getTitle()));
|
||||
}
|
||||
|
||||
private SimpleMultimap<String, RemoteMod.Version> sortVersions(Stream<RemoteMod.Version> versions) {
|
||||
@@ -195,9 +205,13 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
}
|
||||
|
||||
Controllers.taskDialog(
|
||||
new FileDownloadTask(NetworkUtils.toURL(file.getFile().getUrl()), dest).executor(true),
|
||||
i18n("message.downloading")
|
||||
);
|
||||
Task.composeAsync(() -> {
|
||||
FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(file.getFile().getUrl()), dest, file.getFile().getIntegrityCheck());
|
||||
task.setName(file.getName());
|
||||
return task;
|
||||
}),
|
||||
i18n("message.downloading"),
|
||||
TaskCancellationAction.NORMAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -237,8 +251,8 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
|
||||
TwoLineListItem content = new TwoLineListItem();
|
||||
HBox.setHgrow(content, Priority.ALWAYS);
|
||||
ModTranslations.Mod mod = ModTranslations.getModByCurseForgeId(getSkinnable().addon.getSlug());
|
||||
content.setTitle(mod != null ? mod.getDisplayName() : getSkinnable().addon.getTitle());
|
||||
ModTranslations.Mod mod = getSkinnable().translations.getModByCurseForgeId(getSkinnable().addon.getSlug());
|
||||
content.setTitle(mod != null && I18n.getCurrentLocale().getLocale() == Locale.CHINA ? mod.getDisplayName() : getSkinnable().addon.getTitle());
|
||||
content.setSubtitle(getSkinnable().addon.getDescription());
|
||||
content.getTags().setAll(getSkinnable().addon.getCategories().stream()
|
||||
.map(category -> getSkinnable().page.getLocalizedCategory(category))
|
||||
@@ -247,7 +261,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
|
||||
if (getSkinnable().mod != null) {
|
||||
JFXHyperlink openMcmodButton = new JFXHyperlink(i18n("mods.mcmod"));
|
||||
openMcmodButton.setOnAction(e -> FXUtils.openLink(ModManager.getMcmodUrl(getSkinnable().mod.getMcmod())));
|
||||
openMcmodButton.setOnAction(e -> FXUtils.openLink(getSkinnable().translations.getMcmodUrl(getSkinnable().mod)));
|
||||
descriptionPane.getChildren().add(openMcmodButton);
|
||||
|
||||
if (StringUtils.isNotBlank(getSkinnable().mod.getMcbbs())) {
|
||||
@@ -276,7 +290,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
|
||||
Node title = ComponentList.createComponentListTitle(i18n("mods.dependencies"));
|
||||
|
||||
BooleanBinding show = Bindings.createBooleanBinding(() -> !control.dependencies.isEmpty(), control.loaded);
|
||||
BooleanBinding show = Bindings.createBooleanBinding(() -> control.loaded.get() && !control.dependencies.isEmpty(), control.loaded);
|
||||
title.managedProperty().bind(show);
|
||||
title.visibleProperty().bind(show);
|
||||
dependencyPane.managedProperty().bind(show);
|
||||
@@ -297,6 +311,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
return null;
|
||||
}
|
||||
}, getSkinnable().failedProperty()));
|
||||
spinnerPane.setOnFailedAction(e -> getSkinnable().loadModVersions());
|
||||
|
||||
ComponentList list = new ComponentList();
|
||||
StackPane.setAlignment(list, Pos.TOP_CENTER);
|
||||
@@ -341,8 +356,8 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
container.setOnMouseClicked(e -> Controllers.navigate(new DownloadPage(page, addon, version, callback)));
|
||||
getChildren().setAll(container);
|
||||
|
||||
ModTranslations.Mod mod = ModTranslations.getModByCurseForgeId(addon.getSlug());
|
||||
content.setTitle(mod != null ? mod.getDisplayName() : addon.getTitle());
|
||||
ModTranslations.Mod mod = ModTranslations.getTranslationsByRepositoryType(page.repository.getType()).getModByCurseForgeId(addon.getSlug());
|
||||
content.setTitle(mod != null && I18n.getCurrentLocale().getLocale() == Locale.CHINA ? mod.getDisplayName() : addon.getTitle());
|
||||
content.setSubtitle(addon.getDescription());
|
||||
content.getTags().setAll(addon.getCategories().stream()
|
||||
.map(page::getLocalizedCategory)
|
||||
@@ -376,7 +391,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
pane.getChildren().setAll(graphicPane, content, saveAsButton);
|
||||
|
||||
content.setTitle(dataItem.getName());
|
||||
content.setSubtitle(FORMATTER.format(dataItem.getDatePublished()));
|
||||
content.setSubtitle(FORMATTER.format(dataItem.getDatePublished().toInstant()));
|
||||
saveAsButton.setOnMouseClicked(e -> selfPage.saveAs(dataItem));
|
||||
|
||||
switch (dataItem.getVersionType()) {
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.jackhuang.hmcl.task.TaskListener;
|
||||
import org.jackhuang.hmcl.ui.*;
|
||||
import org.jackhuang.hmcl.ui.download.UpdateInstallerWizardProvider;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
@@ -161,7 +162,7 @@ public class InstallerListPage extends ListPageBase<InstallerItem> implements Ve
|
||||
});
|
||||
}
|
||||
});
|
||||
Controllers.taskDialog(executor, i18n("install.installer.install_offline"));
|
||||
Controllers.taskDialog(executor, i18n("install.installer.install_offline"), TaskCancellationAction.NO_CANCEL);
|
||||
executor.start();
|
||||
}
|
||||
|
||||
|
||||
@@ -17,19 +17,10 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import org.jackhuang.hmcl.mod.LocalModFile;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
import org.jackhuang.hmcl.game.LocalizedRemoteModRepository;
|
||||
import org.jackhuang.hmcl.mod.RemoteModRepository;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseForgeRemoteModRepository;
|
||||
import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
@@ -39,66 +30,25 @@ public class ModDownloadListPage extends DownloadListPage {
|
||||
|
||||
repository = new Repository();
|
||||
|
||||
|
||||
supportChinese.set(true);
|
||||
downloadSources.get().setAll("mods.curseforge", "mods.modrinth");
|
||||
downloadSource.set("mods.curseforge");
|
||||
}
|
||||
|
||||
private class Repository implements RemoteModRepository {
|
||||
private class Repository extends LocalizedRemoteModRepository {
|
||||
|
||||
@Override
|
||||
public Stream<RemoteMod> search(String gameVersion, Category category, int pageOffset, int pageSize, String searchFilter, int sort) throws IOException {
|
||||
String newSearchFilter;
|
||||
if (StringUtils.CHINESE_PATTERN.matcher(searchFilter).find()) {
|
||||
List<ModTranslations.Mod> mods = ModTranslations.searchMod(searchFilter);
|
||||
List<String> searchFilters = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (ModTranslations.Mod mod : mods) {
|
||||
String englishName = mod.getName();
|
||||
if (StringUtils.isNotBlank(mod.getSubname())) {
|
||||
englishName = mod.getSubname();
|
||||
}
|
||||
|
||||
searchFilters.add(englishName);
|
||||
|
||||
count++;
|
||||
if (count >= 3) break;
|
||||
}
|
||||
newSearchFilter = String.join(" ", searchFilters);
|
||||
} else {
|
||||
newSearchFilter = searchFilter;
|
||||
}
|
||||
|
||||
protected RemoteModRepository getBackedRemoteModRepository() {
|
||||
if ("mods.modrinth".equals(downloadSource.get())) {
|
||||
return ModrinthRemoteModRepository.INSTANCE.search(gameVersion, category, pageOffset, pageSize, newSearchFilter, sort);
|
||||
return ModrinthRemoteModRepository.MODS;
|
||||
} else {
|
||||
return CurseForgeRemoteModRepository.MODS.search(gameVersion, category, pageOffset, pageSize, newSearchFilter, sort);
|
||||
return CurseForgeRemoteModRepository.MODS;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Category> getCategories() throws IOException {
|
||||
if ("mods.modrinth".equals(downloadSource.get())) {
|
||||
return ModrinthRemoteModRepository.INSTANCE.getCategories();
|
||||
} else {
|
||||
return CurseForgeRemoteModRepository.MODS.getCategories();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<RemoteMod.Version> getRemoteVersionByLocalFile(LocalModFile localModFile, Path file) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoteMod getModById(String id) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<RemoteMod.Version> getRemoteVersionsById(String id) throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
public Type getType() {
|
||||
return Type.MOD;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ import org.jackhuang.hmcl.ui.ListPageBase;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.PageAware;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
@@ -95,7 +96,7 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
synchronized (ModListPage.this) {
|
||||
runInFX(() -> loadingProperty().set(true));
|
||||
modManager.refreshMods();
|
||||
return new LinkedList<>(modManager.getMods());
|
||||
return new ArrayList<>(modManager.getMods());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
@@ -118,10 +119,12 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
chooser.getExtensionFilters().setAll(new FileChooser.ExtensionFilter(i18n("extension.mod"), "*.jar", "*.zip", "*.litemod"));
|
||||
List<File> res = chooser.showOpenMultipleDialog(Controllers.getStage());
|
||||
|
||||
// It's guaranteed that succeeded and failed are thread safe here.
|
||||
List<String> succeeded = new LinkedList<>();
|
||||
List<String> failed = new LinkedList<>();
|
||||
if (res == null) return;
|
||||
|
||||
// It's guaranteed that succeeded and failed are thread safe here.
|
||||
List<String> succeeded = new ArrayList<>(res.size());
|
||||
List<String> failed = new ArrayList<>();
|
||||
|
||||
Task.runAsync(() -> {
|
||||
for (File file : res) {
|
||||
try {
|
||||
@@ -135,7 +138,7 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
}
|
||||
}
|
||||
}).withRunAsync(Schedulers.javafx(), () -> {
|
||||
List<String> prompt = new LinkedList<>();
|
||||
List<String> prompt = new ArrayList<>(1);
|
||||
if (!succeeded.isEmpty())
|
||||
prompt.add(i18n("mods.add.success", String.join(", ", succeeded)));
|
||||
if (!failed.isEmpty())
|
||||
@@ -192,8 +195,7 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
}
|
||||
})
|
||||
.withStagesHint(Collections.singletonList("mods.check_updates")),
|
||||
i18n("update.checking"), pane -> {
|
||||
});
|
||||
i18n("update.checking"), TaskCancellationAction.NORMAL);
|
||||
}
|
||||
|
||||
public void download() {
|
||||
|
||||
@@ -158,7 +158,7 @@ class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
if (isNotBlank(localModFile.getAuthors()))
|
||||
message.append(", ").append(i18n("archive.author")).append(": ").append(localModFile.getAuthors());
|
||||
this.message = message.toString();
|
||||
this.mod = ModTranslations.getModById(localModFile.getId());
|
||||
this.mod = ModTranslations.MOD.getModById(localModFile.getId());
|
||||
}
|
||||
|
||||
String getTitle() {
|
||||
@@ -261,7 +261,7 @@ class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
mcmodButton.setText(i18n("mods.mcmod.page"));
|
||||
mcmodButton.setOnAction(e -> {
|
||||
fireEvent(new DialogCloseEvent());
|
||||
FXUtils.openLink(ModManager.getMcmodUrl(modInfo.getMod().getMcmod()));
|
||||
FXUtils.openLink(ModTranslations.MOD.getMcmodUrl(modInfo.getMod()));
|
||||
});
|
||||
getActions().add(mcmodButton);
|
||||
}
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import org.jackhuang.hmcl.mod.RemoteModRepository;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
@@ -34,28 +36,65 @@ import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
*
|
||||
* @see <a href="https://www.mcmod.cn">mcmod.cn</a>
|
||||
*/
|
||||
public final class ModTranslations {
|
||||
private static List<Mod> mods;
|
||||
private static Map<String, Mod> modIdMap; // mod id -> mod
|
||||
private static Map<String, Mod> curseForgeMap; // curseforge id -> mod
|
||||
private static List<Pair<String, Mod>> keywords;
|
||||
private static int maxKeywordLength = -1;
|
||||
public enum ModTranslations {
|
||||
MOD("/assets/mod_data.txt") {
|
||||
@Override
|
||||
public String getMcmodUrl(Mod mod) {
|
||||
return String.format("https://www.mcmod.cn/class/%s.html", mod.getMcmod());
|
||||
}
|
||||
},
|
||||
MODPACK("/assets/modpack_data.txt") {
|
||||
@Override
|
||||
public String getMcmodUrl(Mod mod) {
|
||||
return String.format("https://www.mcmod.cn/modpack/%s.html", mod.getMcmod());
|
||||
}
|
||||
},
|
||||
EMPTY("") {
|
||||
@Override
|
||||
public String getMcmodUrl(Mod mod) {
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
private ModTranslations(){}
|
||||
public static ModTranslations getTranslationsByRepositoryType(RemoteModRepository.Type type) {
|
||||
switch (type) {
|
||||
case MOD:
|
||||
return MOD;
|
||||
case MODPACK:
|
||||
return MODPACK;
|
||||
default:
|
||||
return EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
public static Mod getModByCurseForgeId(String id) {
|
||||
private final String resourceName;
|
||||
private List<Mod> mods;
|
||||
private Map<String, Mod> modIdMap; // mod id -> mod
|
||||
private Map<String, Mod> curseForgeMap; // curseforge id -> mod
|
||||
private List<Pair<String, Mod>> keywords;
|
||||
private int maxKeywordLength = -1;
|
||||
|
||||
ModTranslations(String resourceName) {
|
||||
this.resourceName = resourceName;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Mod getModByCurseForgeId(String id) {
|
||||
if (StringUtils.isBlank(id) || !loadCurseForgeMap()) return null;
|
||||
|
||||
return curseForgeMap.get(id);
|
||||
}
|
||||
|
||||
public static Mod getModById(String id) {
|
||||
@Nullable
|
||||
public Mod getModById(String id) {
|
||||
if (StringUtils.isBlank(id) || !loadModIdMap()) return null;
|
||||
|
||||
return modIdMap.get(id);
|
||||
}
|
||||
|
||||
public static List<Mod> searchMod(String query) {
|
||||
public abstract String getMcmodUrl(Mod mod);
|
||||
|
||||
public List<Mod> searchMod(String query) {
|
||||
if (!loadKeywords()) return Collections.emptyList();
|
||||
|
||||
StringBuilder newQuery = query.chars()
|
||||
@@ -77,19 +116,23 @@ public final class ModTranslations {
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static boolean loadFromResource() {
|
||||
private boolean loadFromResource() {
|
||||
if (mods != null) return true;
|
||||
if (StringUtils.isBlank(resourceName)) {
|
||||
mods = Collections.emptyList();
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
String modData = IOUtils.readFullyAsString(ModTranslations.class.getResourceAsStream("/assets/mod_data.txt"), StandardCharsets.UTF_8);
|
||||
String modData = IOUtils.readFullyAsString(ModTranslations.class.getResourceAsStream(resourceName), StandardCharsets.UTF_8);
|
||||
mods = Arrays.stream(modData.split("\n")).filter(line -> !line.startsWith("#")).map(Mod::new).collect(Collectors.toList());
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
LOG.log(Level.WARNING, "Failed to load /assets/mod_data.txt", e);
|
||||
LOG.log(Level.WARNING, "Failed to load " + resourceName, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean loadCurseForgeMap() {
|
||||
private boolean loadCurseForgeMap() {
|
||||
if (curseForgeMap != null) {
|
||||
return true;
|
||||
}
|
||||
@@ -107,7 +150,7 @@ public final class ModTranslations {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean loadModIdMap() {
|
||||
private boolean loadModIdMap() {
|
||||
if (modIdMap != null) {
|
||||
return true;
|
||||
}
|
||||
@@ -127,7 +170,7 @@ public final class ModTranslations {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean loadKeywords() {
|
||||
private boolean loadKeywords() {
|
||||
if (keywords != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -39,11 +39,14 @@ import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -144,8 +147,7 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
|
||||
}
|
||||
}),
|
||||
i18n("mods.check_updates.update"),
|
||||
t -> {
|
||||
});
|
||||
TaskCancellationAction.NORMAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -165,14 +167,14 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
|
||||
@Override
|
||||
protected void updateControl(LocalModFile.ModUpdate item, boolean empty) {
|
||||
if (empty) return;
|
||||
ModTranslations.Mod mod = ModTranslations.getModById(item.getLocalMod().getId());
|
||||
content.setTitle(mod != null ? mod.getDisplayName() : item.getCurrentVersion().getName());
|
||||
ModTranslations.Mod mod = ModTranslations.MOD.getModById(item.getLocalMod().getId());
|
||||
content.setTitle(mod != null && I18n.getCurrentLocale().getLocale() == Locale.CHINA ? mod.getDisplayName() : item.getCurrentVersion().getName());
|
||||
content.setSubtitle(item.getLocalMod().getFileName());
|
||||
content.getTags().setAll();
|
||||
|
||||
if (item.getCurrentVersion().getSelf() instanceof CurseAddon.LatestFile) {
|
||||
content.getTags().add("Curseforge");
|
||||
} else if (item.getCurrentVersion().getSelf() instanceof ModrinthRemoteModRepository.ModVersion) {
|
||||
} else if (item.getCurrentVersion().getSelf() instanceof ModrinthRemoteModRepository.ProjectVersion) {
|
||||
content.getTags().add("Modrinth");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2022 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.versions;
|
||||
|
||||
import org.jackhuang.hmcl.game.LocalizedRemoteModRepository;
|
||||
import org.jackhuang.hmcl.mod.RemoteModRepository;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseForgeRemoteModRepository;
|
||||
import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class ModpackDownloadListPage extends DownloadListPage {
|
||||
public ModpackDownloadListPage(DownloadPage.DownloadCallback callback, boolean versionSelection) {
|
||||
super(null, callback, versionSelection);
|
||||
|
||||
repository = new Repository();
|
||||
|
||||
supportChinese.set(true);
|
||||
downloadSources.get().setAll("mods.curseforge", "mods.modrinth");
|
||||
downloadSource.set("mods.curseforge");
|
||||
}
|
||||
|
||||
private class Repository extends LocalizedRemoteModRepository {
|
||||
|
||||
@Override
|
||||
protected RemoteModRepository getBackedRemoteModRepository() {
|
||||
if ("mods.modrinth".equals(downloadSource.get())) {
|
||||
return ModrinthRemoteModRepository.MODPACKS;
|
||||
} else {
|
||||
return CurseForgeRemoteModRepository.MODPACKS;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return Type.MODPACK;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLocalizedCategory(String category) {
|
||||
if ("mods.modrinth".equals(downloadSource.get())) {
|
||||
return i18n("modrinth.category." + category);
|
||||
} else {
|
||||
return i18n("curse.category." + category);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLocalizedOfficialPage() {
|
||||
if ("mods.modrinth".equals(downloadSource.get())) {
|
||||
return i18n("mods.modrinth");
|
||||
} else {
|
||||
return i18n("mods.curseforge");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,6 +90,8 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
private final OptionToggleButton noJVMArgsPane;
|
||||
private final OptionToggleButton noGameCheckPane;
|
||||
private final OptionToggleButton noJVMCheckPane;
|
||||
private final OptionToggleButton noNativesPatchPane;
|
||||
private final OptionToggleButton useSoftwareRenderer;
|
||||
private final OptionToggleButton useNativeGLFWPane;
|
||||
private final OptionToggleButton useNativeOpenALPane;
|
||||
private final ComponentSublist javaSublist;
|
||||
@@ -112,7 +114,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
|
||||
private final StringProperty selectedVersion = new SimpleStringProperty();
|
||||
private final BooleanProperty navigateToSpecificSettings = new SimpleBooleanProperty(false);
|
||||
private final BooleanProperty enableSpecificSettings = new SimpleBooleanProperty(true);
|
||||
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();
|
||||
@@ -154,6 +156,10 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
}
|
||||
|
||||
if (!globalSetting) {
|
||||
HintPane gameDirHint = new HintPane(MessageDialogPane.MessageType.INFO);
|
||||
gameDirHint.setText(i18n("settings.game.working_directory.hint"));
|
||||
rootPane.getChildren().add(gameDirHint);
|
||||
|
||||
ComponentList iconPickerItemWrapper = new ComponentList();
|
||||
rootPane.getChildren().add(iconPickerItemWrapper);
|
||||
|
||||
@@ -458,10 +464,14 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
pane.getColumnConstraints().setAll(title, value);
|
||||
|
||||
txtJVMArgs = new JFXTextField();
|
||||
txtJVMArgs.setPromptText(i18n("settings.advanced.jvm_args.prompt"));
|
||||
txtJVMArgs.getStyleClass().add("fit-width");
|
||||
pane.addRow(0, new Label(i18n("settings.advanced.jvm_args")), txtJVMArgs);
|
||||
|
||||
HintPane hintPane = new HintPane();
|
||||
hintPane.setText(i18n("settings.advanced.jvm_args.prompt"));
|
||||
GridPane.setColumnSpan(hintPane, 2);
|
||||
pane.addRow(4, hintPane);
|
||||
|
||||
txtMetaspace = new JFXTextField();
|
||||
txtMetaspace.setPromptText(i18n("settings.advanced.java_permanent_generation_space.prompt"));
|
||||
txtMetaspace.getStyleClass().add("fit-width");
|
||||
@@ -504,13 +514,22 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
noJVMCheckPane = new OptionToggleButton();
|
||||
noJVMCheckPane.setTitle(i18n("settings.advanced.dont_check_jvm_validity"));
|
||||
|
||||
noNativesPatchPane = new OptionToggleButton();
|
||||
noNativesPatchPane.setTitle(i18n("settings.advanced.dont_patch_natives"));
|
||||
|
||||
useSoftwareRenderer = new OptionToggleButton();
|
||||
useSoftwareRenderer.setTitle(i18n("settings.advanced.use_software_renderer"));
|
||||
|
||||
useNativeGLFWPane = new OptionToggleButton();
|
||||
useNativeGLFWPane.setTitle(i18n("settings.advanced.use_native_glfw"));
|
||||
|
||||
useNativeOpenALPane = new OptionToggleButton();
|
||||
useNativeOpenALPane.setTitle(i18n("settings.advanced.use_native_openal"));
|
||||
|
||||
workaroundPane.getContent().setAll(nativesDirSublist, noJVMArgsPane, noGameCheckPane, noJVMCheckPane, useNativeGLFWPane, useNativeOpenALPane);
|
||||
workaroundPane.getContent().setAll(
|
||||
nativesDirSublist,
|
||||
noJVMArgsPane, noGameCheckPane, noJVMCheckPane, noNativesPatchPane,
|
||||
useSoftwareRenderer, useNativeGLFWPane, useNativeOpenALPane);
|
||||
}
|
||||
|
||||
rootPane.getChildren().addAll(componentList,
|
||||
@@ -627,7 +646,9 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
noGameCheckPane.selectedProperty().unbindBidirectional(lastVersionSetting.notCheckGameProperty());
|
||||
noJVMCheckPane.selectedProperty().unbindBidirectional(lastVersionSetting.notCheckJVMProperty());
|
||||
noJVMArgsPane.selectedProperty().unbindBidirectional(lastVersionSetting.noJVMArgsProperty());
|
||||
noNativesPatchPane.selectedProperty().unbindBidirectional(lastVersionSetting.notPatchNativesProperty());
|
||||
showLogsPane.selectedProperty().unbindBidirectional(lastVersionSetting.showLogsProperty());
|
||||
useSoftwareRenderer.selectedProperty().unbindBidirectional(lastVersionSetting.useSoftwareRendererProperty());
|
||||
useNativeGLFWPane.selectedProperty().unbindBidirectional(lastVersionSetting.useNativeGLFWProperty());
|
||||
useNativeOpenALPane.selectedProperty().unbindBidirectional(lastVersionSetting.useNativeOpenALProperty());
|
||||
FXUtils.unbindEnum(cboLauncherVisibility);
|
||||
@@ -666,7 +687,9 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
noGameCheckPane.selectedProperty().bindBidirectional(versionSetting.notCheckGameProperty());
|
||||
noJVMCheckPane.selectedProperty().bindBidirectional(versionSetting.notCheckJVMProperty());
|
||||
noJVMArgsPane.selectedProperty().bindBidirectional(versionSetting.noJVMArgsProperty());
|
||||
noNativesPatchPane.selectedProperty().bindBidirectional(versionSetting.notPatchNativesProperty());
|
||||
showLogsPane.selectedProperty().bindBidirectional(versionSetting.showLogsProperty());
|
||||
useSoftwareRenderer.selectedProperty().bindBidirectional(versionSetting.useSoftwareRendererProperty());
|
||||
useNativeGLFWPane.selectedProperty().bindBidirectional(versionSetting.useNativeGLFWProperty());
|
||||
useNativeOpenALPane.selectedProperty().bindBidirectional(versionSetting.useNativeOpenALProperty());
|
||||
FXUtils.bindEnum(cboLauncherVisibility, versionSetting.launcherVisibilityProperty());
|
||||
|
||||
@@ -44,6 +44,7 @@ import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
|
||||
import org.jackhuang.hmcl.ui.export.ExportWizardProvider;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
@@ -52,6 +53,7 @@ import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.Level;
|
||||
@@ -91,13 +93,16 @@ public final class Versions {
|
||||
.whenComplete(Schedulers.javafx(), e -> {
|
||||
if (e == null) {
|
||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(profile, modpack.toFile()));
|
||||
} else if (e instanceof CancellationException) {
|
||||
Controllers.showToast(i18n("message.cancelled"));
|
||||
} else {
|
||||
Controllers.dialog(
|
||||
i18n("install.failed.downloading.detail", file.getFile().getUrl()) + "\n" + StringUtils.getStackTrace(e),
|
||||
i18n("download.failed"), MessageDialogPane.MessageType.ERROR);
|
||||
}
|
||||
}).executor(true),
|
||||
i18n("message.downloading")
|
||||
i18n("message.downloading"),
|
||||
TaskCancellationAction.NORMAL
|
||||
);
|
||||
}
|
||||
|
||||
@@ -123,7 +128,12 @@ public final class Versions {
|
||||
}
|
||||
if (profile.getRepository().renameVersion(version, newName)) {
|
||||
resolve.run();
|
||||
profile.getRepository().refreshVersionsAsync().start();
|
||||
profile.getRepository().refreshVersionsAsync()
|
||||
.thenRunAsync(Schedulers.javafx(), () -> {
|
||||
if (profile.getRepository().hasVersion(newName)) {
|
||||
profile.setSelectedVersion(newName);
|
||||
}
|
||||
}).start();
|
||||
} else {
|
||||
reject.accept(i18n("version.manage.rename.fail"));
|
||||
}
|
||||
@@ -167,7 +177,7 @@ public final class Versions {
|
||||
public static void updateGameAssets(Profile profile, String version) {
|
||||
TaskExecutor executor = new GameAssetDownloadTask(profile.getDependency(), profile.getRepository().getVersion(version), GameAssetDownloadTask.DOWNLOAD_INDEX_FORCIBLY, true)
|
||||
.executor();
|
||||
Controllers.taskDialog(executor, i18n("version.manage.redownload_assets_index"));
|
||||
Controllers.taskDialog(executor, i18n("version.manage.redownload_assets_index"), TaskCancellationAction.NO_CANCEL);
|
||||
executor.start();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,553 @@
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.github.steveice10.opennbt.tag.builtin.*;
|
||||
import com.jfoenix.controls.JFXComboBox;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.game.World;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.ComponentList;
|
||||
import org.jackhuang.hmcl.ui.construct.DoubleValidator;
|
||||
import org.jackhuang.hmcl.ui.construct.NumberValidator;
|
||||
import org.jackhuang.hmcl.ui.construct.OptionToggleButton;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.i18n.Locales;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class WorldInfoPage extends StackPane implements DecoratorPage {
|
||||
private final World world;
|
||||
private final CompoundTag levelDat;
|
||||
private final CompoundTag dataTag;
|
||||
|
||||
private final ObjectProperty<State> stateProperty = new SimpleObjectProperty<>();
|
||||
|
||||
public WorldInfoPage(World world) throws IOException {
|
||||
this.world = world;
|
||||
this.levelDat = world.readLevelDat();
|
||||
this.dataTag = levelDat.get("Data");
|
||||
|
||||
CompoundTag worldGenSettings = dataTag.get("WorldGenSettings");
|
||||
|
||||
stateProperty.set(State.fromTitle(i18n("world.info.title", world.getWorldName())));
|
||||
|
||||
ScrollPane scrollPane = new ScrollPane();
|
||||
scrollPane.setFitToHeight(true);
|
||||
scrollPane.setFitToWidth(true);
|
||||
scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
|
||||
getChildren().setAll(scrollPane);
|
||||
|
||||
VBox rootPane = new VBox();
|
||||
rootPane.setFillWidth(true);
|
||||
scrollPane.setContent(rootPane);
|
||||
FXUtils.smoothScrolling(scrollPane);
|
||||
rootPane.getStyleClass().add("card-list");
|
||||
|
||||
ComponentList basicInfo = new ComponentList();
|
||||
{
|
||||
BorderPane worldNamePane = new BorderPane();
|
||||
{
|
||||
Label label = new Label(i18n("world.name"));
|
||||
worldNamePane.setLeft(label);
|
||||
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
|
||||
|
||||
Label worldNameLabel = new Label();
|
||||
worldNameLabel.setText(world.getWorldName());
|
||||
BorderPane.setAlignment(worldNameLabel, Pos.CENTER_RIGHT);
|
||||
worldNamePane.setRight(worldNameLabel);
|
||||
}
|
||||
|
||||
BorderPane gameVersionPane = new BorderPane();
|
||||
{
|
||||
Label label = new Label(i18n("world.info.game_version"));
|
||||
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
|
||||
gameVersionPane.setLeft(label);
|
||||
|
||||
Label gameVersionLabel = new Label();
|
||||
gameVersionLabel.setText(world.getGameVersion());
|
||||
BorderPane.setAlignment(gameVersionLabel, Pos.CENTER_RIGHT);
|
||||
gameVersionPane.setRight(gameVersionLabel);
|
||||
}
|
||||
|
||||
BorderPane randomSeedPane = new BorderPane();
|
||||
{
|
||||
Label label = new Label(i18n("world.info.random_seed"));
|
||||
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
|
||||
randomSeedPane.setLeft(label);
|
||||
|
||||
Label randomSeedLabel = new Label();
|
||||
BorderPane.setAlignment(randomSeedLabel, Pos.CENTER_RIGHT);
|
||||
randomSeedPane.setRight(randomSeedLabel);
|
||||
|
||||
Tag tag = worldGenSettings != null ? worldGenSettings.get("seed") : dataTag.get("RandomSeed");
|
||||
if (tag instanceof LongTag) {
|
||||
randomSeedLabel.setText(tag.getValue().toString());
|
||||
}
|
||||
}
|
||||
|
||||
BorderPane lastPlayedPane = new BorderPane();
|
||||
{
|
||||
Label label = new Label(i18n("world.info.last_played"));
|
||||
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
|
||||
lastPlayedPane.setLeft(label);
|
||||
|
||||
Label lastPlayedLabel = new Label();
|
||||
lastPlayedLabel.setText(Locales.SIMPLE_DATE_FORMAT.get().format(new Date(world.getLastPlayed())));
|
||||
BorderPane.setAlignment(lastPlayedLabel, Pos.CENTER_RIGHT);
|
||||
lastPlayedPane.setRight(lastPlayedLabel);
|
||||
}
|
||||
|
||||
BorderPane timePane = new BorderPane();
|
||||
{
|
||||
Label label = new Label(i18n("world.info.time"));
|
||||
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
|
||||
timePane.setLeft(label);
|
||||
|
||||
Label timeLabel = new Label();
|
||||
BorderPane.setAlignment(timeLabel, Pos.CENTER_RIGHT);
|
||||
timePane.setRight(timeLabel);
|
||||
|
||||
Tag tag = dataTag.get("Time");
|
||||
if (tag instanceof LongTag) {
|
||||
long days = ((LongTag) tag).getValue() / 24000;
|
||||
timeLabel.setText(i18n("world.info.time.format", days));
|
||||
}
|
||||
}
|
||||
|
||||
OptionToggleButton allowCheatsButton = new OptionToggleButton();
|
||||
{
|
||||
allowCheatsButton.setTitle(i18n("world.info.allow_cheats"));
|
||||
Tag tag = dataTag.get("allowCommands");
|
||||
|
||||
if (tag instanceof ByteTag) {
|
||||
ByteTag byteTag = (ByteTag) tag;
|
||||
byte value = byteTag.getValue();
|
||||
if (value == 0 || value == 1) {
|
||||
allowCheatsButton.setSelected(value == 1);
|
||||
allowCheatsButton.selectedProperty().addListener((o, oldValue, newValue) -> {
|
||||
byteTag.setValue(newValue ? (byte) 1 : (byte) 0);
|
||||
saveLevelDat();
|
||||
});
|
||||
} else {
|
||||
allowCheatsButton.setDisable(true);
|
||||
}
|
||||
} else {
|
||||
allowCheatsButton.setDisable(true);
|
||||
}
|
||||
}
|
||||
|
||||
OptionToggleButton generateFeaturesButton = new OptionToggleButton();
|
||||
{
|
||||
generateFeaturesButton.setTitle(i18n("world.info.generate_features"));
|
||||
Tag tag = worldGenSettings != null ? worldGenSettings.get("generate_features") : dataTag.get("MapFeatures");
|
||||
|
||||
if (tag instanceof ByteTag) {
|
||||
ByteTag byteTag = (ByteTag) tag;
|
||||
byte value = byteTag.getValue();
|
||||
if (value == 0 || value == 1) {
|
||||
generateFeaturesButton.setSelected(value == 1);
|
||||
generateFeaturesButton.selectedProperty().addListener((o, oldValue, newValue) -> {
|
||||
byteTag.setValue(newValue ? (byte) 1 : (byte) 0);
|
||||
saveLevelDat();
|
||||
});
|
||||
} else {
|
||||
generateFeaturesButton.setDisable(true);
|
||||
}
|
||||
} else {
|
||||
generateFeaturesButton.setDisable(true);
|
||||
}
|
||||
}
|
||||
|
||||
BorderPane difficultyPane = new BorderPane();
|
||||
{
|
||||
Label label = new Label(i18n("world.info.difficulty"));
|
||||
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
|
||||
difficultyPane.setLeft(label);
|
||||
|
||||
JFXComboBox<Difficulty> difficultyBox = new JFXComboBox<>(Difficulty.items);
|
||||
BorderPane.setAlignment(difficultyBox, Pos.CENTER_RIGHT);
|
||||
difficultyPane.setRight(difficultyBox);
|
||||
|
||||
Tag tag = dataTag.get("Difficulty");
|
||||
if (tag instanceof ByteTag) {
|
||||
ByteTag byteTag = (ByteTag) tag;
|
||||
Difficulty difficulty = Difficulty.of(byteTag.getValue());
|
||||
if (difficulty != null) {
|
||||
difficultyBox.setValue(difficulty);
|
||||
difficultyBox.valueProperty().addListener((o, oldValue, newValue) -> {
|
||||
if (newValue != null) {
|
||||
byteTag.setValue((byte) newValue.ordinal());
|
||||
saveLevelDat();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
difficultyBox.setDisable(true);
|
||||
}
|
||||
} else {
|
||||
difficultyBox.setDisable(true);
|
||||
}
|
||||
}
|
||||
|
||||
basicInfo.getContent().setAll(
|
||||
worldNamePane, gameVersionPane, randomSeedPane, lastPlayedPane, timePane,
|
||||
allowCheatsButton, generateFeaturesButton, difficultyPane);
|
||||
|
||||
rootPane.getChildren().addAll(ComponentList.createComponentListTitle(i18n("world.info.basic")), basicInfo);
|
||||
}
|
||||
|
||||
Tag playerTag = dataTag.get("Player");
|
||||
if (playerTag instanceof CompoundTag) {
|
||||
CompoundTag player = (CompoundTag) playerTag;
|
||||
ComponentList playerInfo = new ComponentList();
|
||||
|
||||
BorderPane locationPane = new BorderPane();
|
||||
{
|
||||
Label label = new Label(i18n("world.info.player.location"));
|
||||
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
|
||||
locationPane.setLeft(label);
|
||||
|
||||
Label locationLabel = new Label();
|
||||
BorderPane.setAlignment(locationLabel, Pos.CENTER_RIGHT);
|
||||
locationPane.setRight(locationLabel);
|
||||
|
||||
Dimension dim = Dimension.of(player.get("Dimension"));
|
||||
if (dim != null) {
|
||||
String posString = dim.formatPosition(player.get("Pos"));
|
||||
if (posString != null)
|
||||
locationLabel.setText(posString);
|
||||
}
|
||||
}
|
||||
|
||||
BorderPane lastDeathLocationPane = new BorderPane();
|
||||
{
|
||||
Label label = new Label(i18n("world.info.player.last_death_location"));
|
||||
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
|
||||
lastDeathLocationPane.setLeft(label);
|
||||
|
||||
Label lastDeathLocationLabel = new Label();
|
||||
BorderPane.setAlignment(lastDeathLocationLabel, Pos.CENTER_RIGHT);
|
||||
lastDeathLocationPane.setRight(lastDeathLocationLabel);
|
||||
|
||||
Tag tag = player.get("LastDeathLocation");
|
||||
if (tag instanceof CompoundTag) {
|
||||
Dimension dim = Dimension.of(((CompoundTag) tag).get("dimension"));
|
||||
if (dim != null) {
|
||||
String posString = dim.formatPosition(((CompoundTag) tag).get("pos"));
|
||||
if (posString != null)
|
||||
lastDeathLocationLabel.setText(posString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BorderPane spawnPane = new BorderPane();
|
||||
{
|
||||
Label label = new Label(i18n("world.info.player.spawn"));
|
||||
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
|
||||
spawnPane.setLeft(label);
|
||||
|
||||
Label spawnLabel = new Label();
|
||||
BorderPane.setAlignment(spawnLabel, Pos.CENTER_RIGHT);
|
||||
spawnPane.setRight(spawnLabel);
|
||||
|
||||
Dimension dim = Dimension.of(player.get("SpawnDimension"));
|
||||
if (dim != null) {
|
||||
Tag x = player.get("SpawnX");
|
||||
Tag y = player.get("SpawnY");
|
||||
Tag z = player.get("SpawnZ");
|
||||
|
||||
if (x instanceof IntTag && y instanceof IntTag && z instanceof IntTag)
|
||||
spawnLabel.setText(dim.formatPosition(((IntTag) x).getValue(), ((IntTag) y).getValue(), ((IntTag) z).getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
BorderPane playerGameTypePane = new BorderPane();
|
||||
{
|
||||
Label label = new Label(i18n("world.info.player.game_type"));
|
||||
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
|
||||
playerGameTypePane.setLeft(label);
|
||||
|
||||
JFXComboBox<GameType> gameTypeBox = new JFXComboBox<>(GameType.items);
|
||||
BorderPane.setAlignment(gameTypeBox, Pos.CENTER_RIGHT);
|
||||
playerGameTypePane.setRight(gameTypeBox);
|
||||
|
||||
Tag tag = player.get("playerGameType");
|
||||
if (tag instanceof IntTag) {
|
||||
IntTag intTag = (IntTag) tag;
|
||||
GameType gameType = GameType.of(intTag.getValue());
|
||||
if (gameType != null) {
|
||||
gameTypeBox.setValue(gameType);
|
||||
gameTypeBox.valueProperty().addListener((o, oldValue, newValue) -> {
|
||||
if (newValue != null) {
|
||||
intTag.setValue(newValue.ordinal());
|
||||
saveLevelDat();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
gameTypeBox.setDisable(true);
|
||||
}
|
||||
} else {
|
||||
gameTypeBox.setDisable(true);
|
||||
}
|
||||
}
|
||||
|
||||
BorderPane healthPane = new BorderPane();
|
||||
{
|
||||
Label label = new Label(i18n("world.info.player.health"));
|
||||
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
|
||||
healthPane.setLeft(label);
|
||||
|
||||
JFXTextField healthField = new JFXTextField();
|
||||
healthField.setPrefWidth(50);
|
||||
healthField.setAlignment(Pos.CENTER_RIGHT);
|
||||
BorderPane.setAlignment(healthField, Pos.CENTER_RIGHT);
|
||||
healthPane.setRight(healthField);
|
||||
|
||||
Tag tag = player.get("Health");
|
||||
if (tag instanceof FloatTag) {
|
||||
FloatTag floatTag = (FloatTag) tag;
|
||||
healthField.setText(new DecimalFormat("#").format(floatTag.getValue().floatValue()));
|
||||
|
||||
healthField.textProperty().addListener((o, oldValue, newValue) -> {
|
||||
if (newValue != null) {
|
||||
try {
|
||||
floatTag.setValue(Float.parseFloat(newValue));
|
||||
saveLevelDat();
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
});
|
||||
FXUtils.setValidateWhileTextChanged(healthField, true);
|
||||
healthField.setValidators(new DoubleValidator(i18n("input.number"), true));
|
||||
} else {
|
||||
healthField.setDisable(true);
|
||||
}
|
||||
}
|
||||
|
||||
BorderPane foodLevelPane = new BorderPane();
|
||||
{
|
||||
Label label = new Label(i18n("world.info.player.food_level"));
|
||||
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
|
||||
foodLevelPane.setLeft(label);
|
||||
|
||||
JFXTextField foodLevelField = new JFXTextField();
|
||||
foodLevelField.setPrefWidth(50);
|
||||
foodLevelField.setAlignment(Pos.CENTER_RIGHT);
|
||||
BorderPane.setAlignment(foodLevelField, Pos.CENTER_RIGHT);
|
||||
foodLevelPane.setRight(foodLevelField);
|
||||
|
||||
Tag tag = player.get("foodLevel");
|
||||
if (tag instanceof IntTag) {
|
||||
IntTag intTag = (IntTag) tag;
|
||||
foodLevelField.setText(String.valueOf(intTag.getValue()));
|
||||
|
||||
foodLevelField.textProperty().addListener((o, oldValue, newValue) -> {
|
||||
if (newValue != null) {
|
||||
try {
|
||||
intTag.setValue(Integer.parseInt(newValue));
|
||||
saveLevelDat();
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
});
|
||||
FXUtils.setValidateWhileTextChanged(foodLevelField, true);
|
||||
foodLevelField.setValidators(new NumberValidator(i18n("input.number"), true));
|
||||
} else {
|
||||
foodLevelField.setDisable(true);
|
||||
}
|
||||
}
|
||||
|
||||
BorderPane xpLevelPane = new BorderPane();
|
||||
{
|
||||
Label label = new Label(i18n("world.info.player.xp_level"));
|
||||
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
|
||||
xpLevelPane.setLeft(label);
|
||||
|
||||
JFXTextField xpLevelField = new JFXTextField();
|
||||
xpLevelField.setPrefWidth(50);
|
||||
xpLevelField.setAlignment(Pos.CENTER_RIGHT);
|
||||
BorderPane.setAlignment(xpLevelField, Pos.CENTER_RIGHT);
|
||||
xpLevelPane.setRight(xpLevelField);
|
||||
|
||||
Tag tag = player.get("XpLevel");
|
||||
if (tag instanceof IntTag) {
|
||||
IntTag intTag = (IntTag) tag;
|
||||
xpLevelField.setText(String.valueOf(intTag.getValue()));
|
||||
|
||||
xpLevelField.textProperty().addListener((o, oldValue, newValue) -> {
|
||||
if (newValue != null) {
|
||||
try {
|
||||
intTag.setValue(Integer.parseInt(newValue));
|
||||
saveLevelDat();
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
});
|
||||
FXUtils.setValidateWhileTextChanged(xpLevelField, true);
|
||||
xpLevelField.setValidators(new NumberValidator(i18n("input.number"), true));
|
||||
} else {
|
||||
xpLevelField.setDisable(true);
|
||||
}
|
||||
}
|
||||
|
||||
playerInfo.getContent().setAll(
|
||||
locationPane, lastDeathLocationPane, spawnPane,
|
||||
playerGameTypePane, healthPane, foodLevelPane, xpLevelPane
|
||||
);
|
||||
|
||||
rootPane.getChildren().addAll(ComponentList.createComponentListTitle(i18n("world.info.player")), playerInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveLevelDat() {
|
||||
LOG.info("Saving level.dat of world " + world.getWorldName());
|
||||
try {
|
||||
this.world.writeLevelDat(levelDat);
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to save level.dat of world " + world.getWorldName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadOnlyObjectProperty<State> stateProperty() {
|
||||
return stateProperty;
|
||||
}
|
||||
|
||||
private static final class Dimension {
|
||||
static final Dimension OVERWORLD = new Dimension(null);
|
||||
static final Dimension THE_NETHER = new Dimension(i18n("world.info.dimension.the_nether"));
|
||||
static final Dimension THE_END = new Dimension(i18n("world.info.dimension.the_end"));
|
||||
|
||||
final String name;
|
||||
|
||||
static Dimension of(Tag tag) {
|
||||
if (tag instanceof IntTag) {
|
||||
switch (((IntTag) tag).getValue()) {
|
||||
case 0:
|
||||
return OVERWORLD;
|
||||
case 1:
|
||||
return THE_NETHER;
|
||||
case 2:
|
||||
return THE_END;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
} else if (tag instanceof StringTag) {
|
||||
String id = ((StringTag) tag).getValue();
|
||||
switch (id) {
|
||||
case "overworld":
|
||||
case "minecraft:overworld":
|
||||
return OVERWORLD;
|
||||
case "the_nether":
|
||||
case "minecraft:the_nether":
|
||||
return THE_NETHER;
|
||||
case "the_end":
|
||||
case "minecraft:the_end":
|
||||
return THE_END;
|
||||
default:
|
||||
return new Dimension(id);
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Dimension(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
String formatPosition(Tag tag) {
|
||||
if (tag instanceof ListTag) {
|
||||
ListTag listTag = (ListTag) tag;
|
||||
if (listTag.size() != 3)
|
||||
return null;
|
||||
|
||||
Tag x = listTag.get(0);
|
||||
Tag y = listTag.get(1);
|
||||
Tag z = listTag.get(2);
|
||||
|
||||
if (x instanceof DoubleTag && y instanceof DoubleTag && z instanceof DoubleTag) {
|
||||
//noinspection MalformedFormatString
|
||||
return this == OVERWORLD
|
||||
? String.format("(%.2f, %.2f, %.2f)", x.getValue(), y.getValue(), z.getValue())
|
||||
: String.format("%s (%.2f, %.2f, %.2f)", name, x.getValue(), y.getValue(), z.getValue());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (tag instanceof IntArrayTag) {
|
||||
IntArrayTag intArrayTag = (IntArrayTag) tag;
|
||||
|
||||
int x = intArrayTag.getValue(0);
|
||||
int y = intArrayTag.getValue(1);
|
||||
int z = intArrayTag.getValue(2);
|
||||
|
||||
return this == OVERWORLD
|
||||
? String.format("(%d, %d, %d)", x, y, z)
|
||||
: String.format("%s (%d, %d, %d)", name, x, y, z);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
String formatPosition(int x, int y, int z) {
|
||||
return this == OVERWORLD
|
||||
? String.format("(%d, %d, %d)", x, y, z)
|
||||
: String.format("%s (%d, %d, %d)", name, x, y, z);
|
||||
}
|
||||
|
||||
String formatPosition(double x, double y, double z) {
|
||||
return this == OVERWORLD
|
||||
? String.format("(%.2f, %.2f, %.2f)", x, y, z)
|
||||
: String.format("%s (%.2f, %.2f, %.2f)", name, x, y, z);
|
||||
}
|
||||
}
|
||||
|
||||
private enum Difficulty {
|
||||
PEACEFUL, EASY, NORMAL, HARD;
|
||||
|
||||
static final ObservableList<Difficulty> items = FXCollections.observableList(Arrays.asList(values()));
|
||||
|
||||
static Difficulty of(int d) {
|
||||
return d >= 0 && d <= items.size() ? items.get(d) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return i18n("world.info.difficulty." + name().toLowerCase(Locale.ROOT));
|
||||
}
|
||||
}
|
||||
|
||||
private enum GameType {
|
||||
SURVIVAL, CREATIVE, ADVENTURE, SPECTATOR;
|
||||
|
||||
static final ObservableList<GameType> items = FXCollections.observableList(Arrays.asList(values()));
|
||||
|
||||
static GameType of(int d) {
|
||||
return d >= 0 && d <= items.size() ? items.get(d) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return i18n("world.info.player.game_type." + name().toLowerCase(Locale.ROOT));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,8 @@ public class WorldListItem extends Control {
|
||||
title.set(parseColorEscapes(world.getWorldName()));
|
||||
|
||||
subtitle.set(i18n("world.description", world.getFileName(), Locales.SIMPLE_DATE_FORMAT.get().format(new Date(world.getLastPlayed())), world.getGameVersion() == null ? i18n("message.unknown") : world.getGameVersion()));
|
||||
|
||||
setOnMouseClicked(event -> showInfo());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -99,4 +101,12 @@ public class WorldListItem extends Control {
|
||||
}
|
||||
Controllers.navigate(new DatapackListPage(world.getWorldName(), world.getFile()));
|
||||
}
|
||||
|
||||
public void showInfo() {
|
||||
try {
|
||||
Controllers.navigate(new WorldInfoPage(world));
|
||||
} catch (Exception e) {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -26,6 +26,7 @@ import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
@@ -41,10 +42,10 @@ public abstract class TaskExecutorDialogWizardDisplayer extends AbstractWizardDi
|
||||
|
||||
@Override
|
||||
public void handleTask(Map<String, Object> settings, Task<?> task) {
|
||||
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(it -> {
|
||||
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(new TaskCancellationAction(it -> {
|
||||
it.fireEvent(new DialogCloseEvent());
|
||||
onEnd();
|
||||
});
|
||||
}));
|
||||
|
||||
pane.setTitle(i18n("message.doing"));
|
||||
if (settings.containsKey("title")) {
|
||||
|
||||
@@ -43,7 +43,9 @@ final class ExecutableHeaderHelper {
|
||||
private ExecutableHeaderHelper() {}
|
||||
|
||||
private static Map<String, String> suffix2header = mapOf(
|
||||
pair("exe", "assets/HMCLauncher.exe"));
|
||||
pair("exe", "assets/HMCLauncher.exe"),
|
||||
pair("sh", "assets/HMCLauncher.sh")
|
||||
);
|
||||
|
||||
private static Optional<String> getSuffix(Path file) {
|
||||
String filename = file.getFileName().toString();
|
||||
|
||||
@@ -29,6 +29,7 @@ import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.UpgradeDialog;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.JarUtils;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
@@ -39,6 +40,7 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -100,8 +102,9 @@ public final class UpdateHandler {
|
||||
|
||||
Task<?> task = new HMCLDownloadTask(version, downloaded);
|
||||
|
||||
TaskExecutor executor = task.executor();
|
||||
Controllers.taskDialog(executor, i18n("message.downloading"));
|
||||
TaskExecutor executor = task.executor(false);
|
||||
Controllers.taskDialog(executor, i18n("message.downloading"), TaskCancellationAction.NORMAL);
|
||||
executor.start();
|
||||
thread(() -> {
|
||||
boolean success = executor.test();
|
||||
|
||||
@@ -121,7 +124,11 @@ public final class UpdateHandler {
|
||||
} else {
|
||||
Exception e = executor.getException();
|
||||
LOG.log(Level.WARNING, "Failed to update to " + version, e);
|
||||
Platform.runLater(() -> Controllers.dialog(e.toString(), i18n("update.failed"), MessageType.ERROR));
|
||||
if (e instanceof CancellationException) {
|
||||
Platform.runLater(() -> Controllers.showToast(i18n("message.cancelled")));
|
||||
} else {
|
||||
Platform.runLater(() -> Controllers.dialog(e.toString(), i18n("update.failed"), MessageType.ERROR));
|
||||
}
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
@@ -47,6 +47,7 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
pair("javafx.fxml.LoadException", i18n("crash.NoClassDefFound")),
|
||||
pair("Location is not set", i18n("crash.NoClassDefFound")),
|
||||
pair("UnsatisfiedLinkError", i18n("crash.user_fault")),
|
||||
pair("java.time.zone.ZoneRulesException: Unable to load TZDB time-zone rules", i18n("crash.user_fault")),
|
||||
pair("java.lang.NoClassDefFoundError", i18n("crash.NoClassDefFound")),
|
||||
pair("org.jackhuang.hmcl.util.ResourceNotFoundError", i18n("crash.NoClassDefFound")),
|
||||
pair("java.lang.VerifyError", i18n("crash.NoClassDefFound")),
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.NativesDirectoryType;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
public final class NativePatcher {
|
||||
private NativePatcher() {
|
||||
}
|
||||
|
||||
private static final Library NONEXISTENT_LIBRARY = new Library(null);
|
||||
|
||||
public static Version patchNative(Version version, String gameVersion, JavaVersion javaVersion, VersionSetting settings) {
|
||||
if (settings.isNotPatchNatives())
|
||||
return version;
|
||||
|
||||
if (settings.getNativesDirType() == NativesDirectoryType.CUSTOM) {
|
||||
if (gameVersion != null && VersionNumber.VERSION_COMPARATOR.compare(gameVersion, "1.19") < 0)
|
||||
return version;
|
||||
|
||||
ArrayList<Library> newLibraries = new ArrayList<>();
|
||||
for (Library library : version.getLibraries()) {
|
||||
if (!library.appliesToCurrentEnvironment())
|
||||
continue;
|
||||
|
||||
if (library.getClassifier() == null
|
||||
|| !library.getArtifactId().startsWith("lwjgl")
|
||||
|| !library.getClassifier().startsWith("natives")) {
|
||||
newLibraries.add(library);
|
||||
}
|
||||
}
|
||||
return version.setLibraries(newLibraries);
|
||||
}
|
||||
|
||||
if (javaVersion.getArchitecture().isX86())
|
||||
return version;
|
||||
|
||||
if (javaVersion.getPlatform().getOperatingSystem() == OperatingSystem.OSX
|
||||
&& javaVersion.getArchitecture() == Architecture.ARM64
|
||||
&& gameVersion != null
|
||||
&& VersionNumber.VERSION_COMPARATOR.compare(gameVersion, "1.19") >= 0)
|
||||
return version;
|
||||
|
||||
Map<String, Library> replacements = Hole.nativeReplacement.get(javaVersion.getPlatform().toString());
|
||||
if (replacements == null) {
|
||||
LOG.warning("No alternative native library provided for platform " + javaVersion.getPlatform());
|
||||
return version;
|
||||
}
|
||||
|
||||
ArrayList<Library> newLibraries = new ArrayList<>();
|
||||
for (Library library : version.getLibraries()) {
|
||||
if (!library.appliesToCurrentEnvironment())
|
||||
continue;
|
||||
|
||||
if (library.isNative()) {
|
||||
Library replacement = replacements.getOrDefault(library.getName() + ":natives", NONEXISTENT_LIBRARY);
|
||||
if (replacement == NONEXISTENT_LIBRARY) {
|
||||
if (!(settings.isUseNativeGLFW() && library.getArtifactId().contains("glfw"))
|
||||
&& !(settings.isUseNativeOpenAL() && library.getArtifactId().contains("openal"))) {
|
||||
LOG.warning("No alternative native library " + library.getName() + " provided for platform " + javaVersion.getPlatform());
|
||||
return version;
|
||||
}
|
||||
newLibraries.add(library);
|
||||
} else if (replacement != null) {
|
||||
newLibraries.add(replacement);
|
||||
}
|
||||
} else {
|
||||
Library replacement = replacements.getOrDefault(library.getName(), NONEXISTENT_LIBRARY);
|
||||
if (replacement == NONEXISTENT_LIBRARY) {
|
||||
newLibraries.add(library);
|
||||
} else if (replacement != null) {
|
||||
newLibraries.add(replacement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return version.setLibraries(newLibraries);
|
||||
}
|
||||
|
||||
public static Library getSoftwareRendererLoader(JavaVersion javaVersion) {
|
||||
Map<String, Library> map = Hole.nativeReplacement.get(javaVersion.getPlatform().toString());
|
||||
return map != null ? map.get("software-renderer-loader") : null;
|
||||
}
|
||||
|
||||
private static final class Hole {
|
||||
static Map<String, Map<String, Library>> nativeReplacement;
|
||||
|
||||
static {
|
||||
//noinspection ConstantConditions
|
||||
try (Reader reader = new InputStreamReader(NativePatcher.class.getResourceAsStream("/assets/natives.json"))) {
|
||||
nativeReplacement = JsonUtils.GSON.fromJson(reader, new TypeToken<Map<String, Map<String, Library>>>() {
|
||||
}.getType());
|
||||
} catch (IOException e) {
|
||||
nativeReplacement = Collections.emptyMap();
|
||||
LOG.log(Level.WARNING, "Failed to load native library list", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,17 +45,13 @@ import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.jackhuang.hmcl.util.io.ChecksumMismatchException;
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.Platform;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -68,117 +64,74 @@ import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
import static org.jackhuang.hmcl.Metadata.HMCL_DIRECTORY;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.SelfDependencyPatcher.DependencyDescriptor.JFX_DEPENDENCIES;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.platform.JavaVersion.CURRENT_JAVA;
|
||||
|
||||
// From: https://github.com/Col-E/Recaf/blob/7378b397cee664ae81b7963b0355ef8ff013c3a7/src/main/java/me/coley/recaf/util/self/SelfDependencyPatcher.java
|
||||
public final class SelfDependencyPatcher {
|
||||
private SelfDependencyPatcher() {
|
||||
private final List<DependencyDescriptor> dependencies = DependencyDescriptor.readDependencies();
|
||||
private final List<Repository> repositories;
|
||||
private final Repository defaultRepository;
|
||||
|
||||
private SelfDependencyPatcher() throws IncompatibleVersionException {
|
||||
// We can only self-patch JavaFX on specific platform.
|
||||
if (dependencies == null) {
|
||||
throw new IncompatibleVersionException();
|
||||
}
|
||||
|
||||
final String customUrl = System.getProperty("hmcl.openjfx.repo");
|
||||
if (customUrl == null) {
|
||||
if (System.getProperty("user.country", "").equalsIgnoreCase("CN")) {
|
||||
defaultRepository = Repository.ALIYUN_MIRROR;
|
||||
} else {
|
||||
defaultRepository = Repository.MAVEN_CENTRAL;
|
||||
}
|
||||
repositories = Collections.unmodifiableList(Arrays.asList(Repository.MAVEN_CENTRAL, Repository.ALIYUN_MIRROR));
|
||||
} else {
|
||||
defaultRepository = new Repository(String.format(i18n("repositories.custom"), customUrl), customUrl);
|
||||
repositories = Collections.unmodifiableList(Arrays.asList(Repository.MAVEN_CENTRAL, Repository.ALIYUN_MIRROR, defaultRepository));
|
||||
}
|
||||
}
|
||||
|
||||
static class DependencyDescriptor {
|
||||
private static final class DependencyDescriptor {
|
||||
private static final String DEPENDENCIES_LIST_FILE = "/assets/openjfx-dependencies.json";
|
||||
private static final Path DEPENDENCIES_DIR_PATH = HMCL_DIRECTORY.resolve("dependencies").resolve(Platform.getPlatform().toString()).resolve("openjfx");
|
||||
|
||||
private static final Path DEPENDENCIES_DIR_PATH = HMCL_DIRECTORY.resolve("dependencies");
|
||||
public static final String CURRENT_ARCH_CLASSIFIER = currentArchClassifier();
|
||||
public static final List<DependencyDescriptor> JFX_DEPENDENCIES = readDependencies();
|
||||
|
||||
private static List<DependencyDescriptor> readDependencies() {
|
||||
String content;
|
||||
try (InputStream in = SelfDependencyPatcher.class.getResourceAsStream(DEPENDENCIES_LIST_FILE)) {
|
||||
content = IOUtils.readFullyAsString(in, UTF_8);
|
||||
static List<DependencyDescriptor> readDependencies() {
|
||||
//noinspection ConstantConditions
|
||||
try (Reader reader = new InputStreamReader(SelfDependencyPatcher.class.getResourceAsStream(DEPENDENCIES_LIST_FILE), UTF_8)) {
|
||||
Map<String, List<DependencyDescriptor>> allDependencies =
|
||||
new Gson().fromJson(reader, new TypeToken<Map<String, List<DependencyDescriptor>>>(){}.getType());
|
||||
return allDependencies.get(Platform.getPlatform().toString());
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
return new Gson().fromJson(content, TypeToken.getParameterized(List.class, DependencyDescriptor.class).getType());
|
||||
}
|
||||
|
||||
private static String currentArchClassifier() {
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX) {
|
||||
switch (Architecture.CURRENT_ARCH) {
|
||||
case X86_64:
|
||||
return "linux";
|
||||
case ARM32:
|
||||
return "linux-arm32-monocle";
|
||||
case ARM64:
|
||||
return "linux-aarch64";
|
||||
}
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX) {
|
||||
switch (Architecture.CURRENT_ARCH) {
|
||||
case X86_64:
|
||||
return "mac";
|
||||
case ARM64:
|
||||
return "mac-aarch64";
|
||||
}
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
switch (Architecture.CURRENT_ARCH) {
|
||||
case X86_64:
|
||||
return "win";
|
||||
case X86:
|
||||
return "win-x86";
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String module;
|
||||
public String groupId;
|
||||
public String artifactId;
|
||||
public String version;
|
||||
public Map<String, String> sha1;
|
||||
public String classifier;
|
||||
public String sha1;
|
||||
|
||||
public String filename() {
|
||||
if (CURRENT_ARCH_CLASSIFIER == null) {
|
||||
return null;
|
||||
}
|
||||
return artifactId + "-" + version + "-" + CURRENT_ARCH_CLASSIFIER + ".jar";
|
||||
return artifactId + "-" + version + "-" + classifier + ".jar";
|
||||
}
|
||||
|
||||
public String sha1() {
|
||||
if (CURRENT_ARCH_CLASSIFIER == null) {
|
||||
return null;
|
||||
}
|
||||
return sha1.get(CURRENT_ARCH_CLASSIFIER);
|
||||
return sha1;
|
||||
}
|
||||
|
||||
public Path localPath() {
|
||||
if (CURRENT_ARCH_CLASSIFIER == null) {
|
||||
return null;
|
||||
}
|
||||
return DEPENDENCIES_DIR_PATH.resolve(filename());
|
||||
}
|
||||
|
||||
public boolean isSupported() {
|
||||
return CURRENT_ARCH_CLASSIFIER != null && sha1.containsKey(CURRENT_ARCH_CLASSIFIER);
|
||||
}
|
||||
}
|
||||
|
||||
static final class Repository {
|
||||
public static final List<Repository> REPOSITORIES;
|
||||
|
||||
public static final Repository CUSTOM;
|
||||
private static final class Repository {
|
||||
public static final Repository MAVEN_CENTRAL = new Repository(i18n("repositories.maven_central"), "https://repo1.maven.org/maven2");
|
||||
public static final Repository ALIYUN_MIRROR = new Repository(i18n("repositories.aliyun_mirror"), "https://maven.aliyun.com/repository/central");
|
||||
|
||||
public static final Repository DEFAULT;
|
||||
|
||||
static {
|
||||
final String customUrl = System.getProperty("hmcl.openjfx.repo");
|
||||
if (customUrl == null) {
|
||||
CUSTOM = null;
|
||||
if (System.getProperty("user.country", "").equalsIgnoreCase("CN")) {
|
||||
DEFAULT = Repository.ALIYUN_MIRROR;
|
||||
} else {
|
||||
DEFAULT = Repository.MAVEN_CENTRAL;
|
||||
}
|
||||
REPOSITORIES = Collections.unmodifiableList(Arrays.asList(MAVEN_CENTRAL, ALIYUN_MIRROR));
|
||||
} else {
|
||||
CUSTOM = new Repository(String.format(i18n("repositories.custom"), customUrl), customUrl);
|
||||
DEFAULT = CUSTOM;
|
||||
REPOSITORIES = Collections.unmodifiableList(Arrays.asList(MAVEN_CENTRAL, ALIYUN_MIRROR, CUSTOM));
|
||||
}
|
||||
}
|
||||
|
||||
private final String name;
|
||||
private final String url;
|
||||
|
||||
@@ -196,8 +149,6 @@ public final class SelfDependencyPatcher {
|
||||
}
|
||||
}
|
||||
|
||||
private static final String DEPENDENCIES_LIST_FILE = "/assets/openjfx-dependencies.json";
|
||||
|
||||
/**
|
||||
* Patch in any missing dependencies, if any.
|
||||
*/
|
||||
@@ -221,19 +172,16 @@ public final class SelfDependencyPatcher {
|
||||
throw new IncompatibleVersionException();
|
||||
}
|
||||
|
||||
// We can only self-patch JavaFX on specific platform.
|
||||
if (DependencyDescriptor.CURRENT_ARCH_CLASSIFIER == null) {
|
||||
throw new IncompatibleVersionException();
|
||||
}
|
||||
SelfDependencyPatcher patcher = new SelfDependencyPatcher();
|
||||
|
||||
// Otherwise we're free to download in Java 11+
|
||||
LOG.info("Missing JavaFX dependencies, attempting to patch in missing classes");
|
||||
|
||||
// Download missing dependencies
|
||||
List<DependencyDescriptor> missingDependencies = checkMissingDependencies();
|
||||
List<DependencyDescriptor> missingDependencies = patcher.checkMissingDependencies();
|
||||
if (!missingDependencies.isEmpty()) {
|
||||
try {
|
||||
fetchDependencies(missingDependencies);
|
||||
patcher.fetchDependencies(missingDependencies);
|
||||
} catch (IOException e) {
|
||||
throw new PatchException("Failed to download dependencies", e);
|
||||
}
|
||||
@@ -241,7 +189,7 @@ public final class SelfDependencyPatcher {
|
||||
|
||||
// Add the dependencies
|
||||
try {
|
||||
loadFromCache();
|
||||
patcher.loadFromCache();
|
||||
} catch (IOException ex) {
|
||||
throw new PatchException("Failed to load JavaFX cache", ex);
|
||||
} catch (ReflectiveOperationException | NoClassDefFoundError ex) {
|
||||
@@ -250,7 +198,7 @@ public final class SelfDependencyPatcher {
|
||||
LOG.info(" - Done!");
|
||||
}
|
||||
|
||||
private static Repository showChooseRepositoryDialog() {
|
||||
private Repository showChooseRepositoryDialog() {
|
||||
final JPanel panel = new JPanel();
|
||||
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
|
||||
|
||||
@@ -260,12 +208,12 @@ public final class SelfDependencyPatcher {
|
||||
|
||||
final ButtonGroup buttonGroup = new ButtonGroup();
|
||||
|
||||
for (Repository repository : Repository.REPOSITORIES) {
|
||||
for (Repository repository : repositories) {
|
||||
final JRadioButton button = new JRadioButton(repository.name);
|
||||
button.putClientProperty("repository", repository);
|
||||
buttonGroup.add(button);
|
||||
panel.add(button);
|
||||
if (repository == Repository.DEFAULT) {
|
||||
if (repository == defaultRepository) {
|
||||
button.setSelected(true);
|
||||
}
|
||||
}
|
||||
@@ -293,16 +241,14 @@ public final class SelfDependencyPatcher {
|
||||
* @throws IOException When the locally cached dependency urls cannot be resolved.
|
||||
* @throws ReflectiveOperationException When the call to add these urls to the system classpath failed.
|
||||
*/
|
||||
private static void loadFromCache() throws IOException, ReflectiveOperationException {
|
||||
private void loadFromCache() throws IOException, ReflectiveOperationException {
|
||||
LOG.info(" - Loading dependencies...");
|
||||
|
||||
Set<String> modules = JFX_DEPENDENCIES.stream()
|
||||
.filter(DependencyDescriptor::isSupported)
|
||||
Set<String> modules = dependencies.stream()
|
||||
.map(it -> it.module)
|
||||
.collect(toSet());
|
||||
|
||||
Path[] jars = JFX_DEPENDENCIES.stream()
|
||||
.filter(DependencyDescriptor::isSupported)
|
||||
Path[] jars = dependencies.stream()
|
||||
.map(DependencyDescriptor::localPath)
|
||||
.toArray(Path[]::new);
|
||||
|
||||
@@ -314,11 +260,11 @@ public final class SelfDependencyPatcher {
|
||||
*
|
||||
* @throws IOException When the files cannot be fetched or saved.
|
||||
*/
|
||||
private static void fetchDependencies(List<DependencyDescriptor> dependencies) throws IOException {
|
||||
private void fetchDependencies(List<DependencyDescriptor> dependencies) throws IOException {
|
||||
boolean isFirstTime = true;
|
||||
|
||||
byte[] buffer = new byte[IOUtils.DEFAULT_BUFFER_SIZE];
|
||||
Repository repository = Repository.DEFAULT;
|
||||
Repository repository = defaultRepository;
|
||||
|
||||
int count = 0;
|
||||
while (true) {
|
||||
@@ -392,13 +338,10 @@ public final class SelfDependencyPatcher {
|
||||
}
|
||||
}
|
||||
|
||||
private static List<DependencyDescriptor> checkMissingDependencies() {
|
||||
private List<DependencyDescriptor> checkMissingDependencies() {
|
||||
List<DependencyDescriptor> missing = new ArrayList<>();
|
||||
|
||||
for (DependencyDescriptor dependency : JFX_DEPENDENCIES) {
|
||||
if (!dependency.isSupported()) {
|
||||
continue;
|
||||
}
|
||||
for (DependencyDescriptor dependency : dependencies) {
|
||||
if (!Files.exists(dependency.localPath())) {
|
||||
missing.add(dependency);
|
||||
continue;
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2022 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.util;
|
||||
|
||||
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class TaskCancellationAction {
|
||||
public static TaskCancellationAction NO_CANCEL = new TaskCancellationAction((Consumer<TaskExecutorDialogPane>) null);
|
||||
public static TaskCancellationAction NORMAL = new TaskCancellationAction(() -> {
|
||||
});
|
||||
|
||||
private final Consumer<TaskExecutorDialogPane> cancellationAction;
|
||||
|
||||
public TaskCancellationAction(Runnable cancellationAction) {
|
||||
this.cancellationAction = it -> cancellationAction.run();
|
||||
}
|
||||
|
||||
public TaskCancellationAction(Consumer<TaskExecutorDialogPane> cancellationAction) {
|
||||
this.cancellationAction = cancellationAction;
|
||||
}
|
||||
|
||||
public Consumer<TaskExecutorDialogPane> getCancellationAction() {
|
||||
return cancellationAction;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
# Switch message language
|
||||
if [ -z "${LANG##zh_*}" ]; then
|
||||
_HMCL_USE_CHINESE=true
|
||||
else
|
||||
_HMCL_USE_CHINESE=false
|
||||
fi
|
||||
|
||||
# _HMCL_OS
|
||||
case "$OSTYPE" in
|
||||
linux*)
|
||||
_HMCL_OS="linux";;
|
||||
darwin*)
|
||||
_HMCL_OS="osx";;
|
||||
msys*|cygwin*)
|
||||
_HMCL_OS="windows";;
|
||||
*)
|
||||
_HMCL_OS="unknown";;
|
||||
esac
|
||||
|
||||
# Normalize _HMCL_ARCH
|
||||
case "$(uname -m)" in
|
||||
x86_64|x86-64|amd64|em64t|x64)
|
||||
_HMCL_ARCH="x86_64";;
|
||||
x86_32|x86-32|x86|ia32|i386|i486|i586|i686|i86pc|x32)
|
||||
_HMCL_ARCH="x86";;
|
||||
arm64|aarch64|armv8*|armv9*)
|
||||
_HMCL_ARCH="arm64";;
|
||||
arm|arm32|aarch32|armv7*)
|
||||
_HMCL_ARCH="arm32";;
|
||||
loongarch64)
|
||||
_HMCL_ARCH="loongarch64";;
|
||||
*)
|
||||
_HMCL_ARCH="unknown";;
|
||||
esac
|
||||
|
||||
# Self path
|
||||
_HMCL_PATH="${BASH_SOURCE[0]}"
|
||||
_HMCL_DIR=$(dirname "$_HMCL_PATH")
|
||||
|
||||
if [ "$_HMCL_OS" == "windows" ]; then
|
||||
_HMCL_JAVA_EXE_NAME="java.exe"
|
||||
else
|
||||
_HMCL_JAVA_EXE_NAME="java"
|
||||
fi
|
||||
|
||||
# _HMCL_VM_OPTIONS
|
||||
if [ -n "${HMCL_JAVA_OPTS+x}" ]; then
|
||||
_HMCL_VM_OPTIONS=${HMCL_JAVA_OPTS}
|
||||
else
|
||||
_HMCL_VM_OPTIONS="-XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=15"
|
||||
fi
|
||||
|
||||
# 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
|
||||
"$HMCL_JAVA_HOME/bin/$_HMCL_JAVA_EXE_NAME" $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH"
|
||||
exit 0
|
||||
else
|
||||
if [ "$_HMCL_USE_CHINESE" == true ]; then
|
||||
echo "环境变量 HMCL_JAVA_HOME 的值无效,请设置为合法的 Java 路径。" 1>&2
|
||||
else
|
||||
echo "The value of the environment variable HMCL_JAVA_HOME is invalid, please set it to a valid Java path." 1>&2
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Find Java in HMCL_DIR
|
||||
case "$_HMCL_ARCH" in
|
||||
x86_64)
|
||||
if [ -x "$_HMCL_DIR/jre-x64/bin/$_HMCL_JAVA_EXE_NAME" ]; then
|
||||
"$_HMCL_DIR/jre-x64/bin/$_HMCL_JAVA_EXE_NAME" $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH"
|
||||
exit 0
|
||||
fi
|
||||
if [ -x "$_HMCL_DIR/jre-x86/bin/$_HMCL_JAVA_EXE_NAME" ]; then
|
||||
"$_HMCL_DIR/jre-x86/bin/$_HMCL_JAVA_EXE_NAME" $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH"
|
||||
exit 0
|
||||
fi
|
||||
;;
|
||||
x86)
|
||||
if [ -x "$_HMCL_DIR/jre-x86/bin/$_HMCL_JAVA_EXE_NAME" ]; then
|
||||
"$_HMCL_DIR/jre-x86/bin/$_HMCL_JAVA_EXE_NAME" $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH"
|
||||
exit 0
|
||||
fi
|
||||
;;
|
||||
arm64)
|
||||
if [ -x "$_HMCL_DIR/jre-arm64/bin/$_HMCL_JAVA_EXE_NAME" ]; then
|
||||
"$_HMCL_DIR/jre-arm64/bin/$_HMCL_JAVA_EXE_NAME" $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH"
|
||||
exit 0
|
||||
fi
|
||||
;;
|
||||
arm32)
|
||||
if [ -x "$_HMCL_DIR/jre-arm32/bin/$_HMCL_JAVA_EXE_NAME" ]; then
|
||||
"$_HMCL_DIR/jre-arm32/bin/$_HMCL_JAVA_EXE_NAME" $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH"
|
||||
exit 0
|
||||
fi
|
||||
;;
|
||||
loongarch64)
|
||||
if [ -x "$_HMCL_DIR/jre-loongarch64/bin/$_HMCL_JAVA_EXE_NAME" ]; then
|
||||
"$_HMCL_DIR/jre-loongarch64/bin/$_HMCL_JAVA_EXE_NAME" $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH"
|
||||
exit 0
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
# Find Java in JAVA_HOME
|
||||
if [ -f "$JAVA_HOME/bin/$_HMCL_JAVA_EXE_NAME" ]; then
|
||||
"$JAVA_HOME/bin/$_HMCL_JAVA_EXE_NAME" $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Find Java in PATH
|
||||
if [ -x "$(command -v $_HMCL_JAVA_EXE_NAME)" ]; then
|
||||
$_HMCL_JAVA_EXE_NAME $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Java not found
|
||||
|
||||
if [[ "$_HMCL_OS" == "unknown" || "$_HMCL_ARCH" == "unknown" ]]; then
|
||||
if [ "$_HMCL_USE_CHINESE" == true ]; then
|
||||
echo "运行 HMCL 需要 Java 运行时环境,请安装 Java 并设置环境变量后重试。" 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
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$_HMCL_ARCH" == "loongarch64" ]]; then
|
||||
if [ "$_HMCL_USE_CHINESE" == true ]; then
|
||||
echo "运行 HMCL 需要 Java 运行时环境,请安装龙芯 JDK8 (http://www.loongnix.cn/zh/api/java/downloads-jdk8/index.html) 并设置环境变量后重试。" 1>&2
|
||||
else
|
||||
echo "The Java runtime environment is required to run HMCL." 1>&2
|
||||
echo "Please install Loongson JDK8 (http://www.loongnix.cn/zh/api/java/downloads-jdk8/index.html) and set the environment variables, then try again." 1>&2
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
case "$_HMCL_OS" in
|
||||
linux)
|
||||
_HMCL_DOWNLOAD_PAGE_OS="&os=Linux";;
|
||||
osx)
|
||||
_HMCL_DOWNLOAD_PAGE_OS="&os=macOS";;
|
||||
windows)
|
||||
_HMCL_DOWNLOAD_PAGE_OS="&os=Windows";;
|
||||
*)
|
||||
echo "Unknown os: $_HMCL_OS" 1>&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$_HMCL_ARCH" in
|
||||
arm64)
|
||||
_HMCL_DOWNLOAD_PAGE_ARCH="&bitness=64-bit&architecture=ARM";;
|
||||
arm32)
|
||||
_HMCL_DOWNLOAD_PAGE_ARCH="&bitness=32-bit&architecture=ARM";;
|
||||
x86_64)
|
||||
_HMCL_DOWNLOAD_PAGE_ARCH="&bitness=64-bit&architecture=x86";;
|
||||
x86)
|
||||
_HMCL_DOWNLOAD_PAGE_ARCH="&bitness=32-bit&architecture=x86";;
|
||||
*)
|
||||
echo "Unknown architecture: $_HMCL_ARCH" 1>&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
_HMCL_DOWNLOAD_PAGE="https://bell-sw.com/pages/downloads/?version=java-17-lts$_HMCL_DOWNLOAD_PAGE_OS$_HMCL_DOWNLOAD_PAGE_ARCH&package=jdk-full"
|
||||
|
||||
if [ "$_HMCL_USE_CHINESE" == true ]; then
|
||||
echo "运行 HMCL 需要 Java 运行时环境,请安装 Java 并设置环境变量后重试。" 1>&2
|
||||
echo "$_HMCL_DOWNLOAD_PAGE" 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 "$_HMCL_DOWNLOAD_PAGE" 1>&2
|
||||
fi
|
||||
exit 1
|
||||
@@ -119,7 +119,7 @@
|
||||
}
|
||||
|
||||
.radio-button-title-label {
|
||||
-fx-font-size: 16.0px;
|
||||
-fx-font-size: 16px;
|
||||
-fx-padding: 14.0 0.0 -20.0 0.0;
|
||||
-fx-text-fill: rgba(0.0, 0.0, 0.0, 0.87);
|
||||
}
|
||||
@@ -315,6 +315,33 @@
|
||||
-fx-cursor: hand;
|
||||
}
|
||||
|
||||
.installer-item {
|
||||
-fx-padding: 8px;
|
||||
}
|
||||
|
||||
.installer-item:list-item {
|
||||
-fx-border-color: #e0e0e0;
|
||||
-fx-border-width: 0 0 1 0;
|
||||
-fx-alignment: center-left;
|
||||
}
|
||||
|
||||
.installer-item:list-item > .installer-item-name {
|
||||
-fx-pref-width: 80px;
|
||||
}
|
||||
|
||||
.installer-item:list-item > .installer-item-status {
|
||||
-fx-max-width: infinity;
|
||||
}
|
||||
|
||||
.installer-item:card {
|
||||
-fx-background-color: white;
|
||||
-fx-background-radius: 4;
|
||||
-fx-alignment: center;
|
||||
-fx-pref-width: 180px;
|
||||
|
||||
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 10, 0.12, -1, 2);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Main Page *
|
||||
|
||||
@@ -23,10 +23,26 @@
|
||||
<JFXSpinner fx:id="spinner" styleClass="first-spinner" />
|
||||
<StackPane fx:id="center" style="-fx-padding: 10;">
|
||||
<ComponentList fx:id="centrePane" styleClass="no-padding">
|
||||
<HBox fx:id="checkPane" spacing="10" style="-fx-padding: 10;">
|
||||
<JFXCheckBox fx:id="chkRelease" text="%version.game.releases" selected="true" />
|
||||
<JFXCheckBox fx:id="chkSnapshot" text="%version.game.snapshots" />
|
||||
<JFXCheckBox fx:id="chkOld" text="%version.game.old" />
|
||||
<HBox fx:id="checkPane" spacing="10">
|
||||
<JFXCheckBox fx:id="chkRelease" text="%version.game.releases" selected="true">
|
||||
<HBox.margin>
|
||||
<Insets top="10" bottom="10" />
|
||||
</HBox.margin>
|
||||
</JFXCheckBox>
|
||||
<JFXCheckBox fx:id="chkSnapshot" text="%version.game.snapshots">
|
||||
<HBox.margin>
|
||||
<Insets top="10" bottom="10" />
|
||||
</HBox.margin>
|
||||
</JFXCheckBox>
|
||||
<JFXCheckBox fx:id="chkOld" text="%version.game.old">
|
||||
<HBox.margin>
|
||||
<Insets top="10" bottom="10" />
|
||||
</HBox.margin>
|
||||
</JFXCheckBox>
|
||||
|
||||
<HBox HBox.hgrow="ALWAYS" />
|
||||
|
||||
<JFXButton fx:id="btnRefresh" styleClass="jfx-tool-bar-button" onMouseClicked="#onRefresh" text="%button.refresh" />
|
||||
</HBox>
|
||||
<JFXListView fx:id="list" styleClass="jfx-list-view-float" VBox.vgrow="ALWAYS">
|
||||
</JFXListView>
|
||||
|
||||
@@ -17,8 +17,7 @@
|
||||
</styleClass>
|
||||
<Label fx:id="title" text="%message.info" />
|
||||
</StackPane>
|
||||
<StackPane styleClass="jfx-layout-body">
|
||||
<Label fx:id="content" wrapText="true" />
|
||||
<StackPane fx:id="content" styleClass="jfx-layout-body">
|
||||
</StackPane>
|
||||
<HBox fx:id="actions" styleClass="jfx-layout-actions" />
|
||||
</VBox>
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 9.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2021 huangyuhui <[email protected]> and contributors
|
||||
# Copyright (C) 2022 huangyuhui <[email protected]> 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
|
||||
@@ -27,8 +27,8 @@ about.claim.statement=テキスト全体のリンクをクリックします。
|
||||
about.community=コミュニティ
|
||||
about.community.discord=Discord
|
||||
about.community.discord.statement=チャットに参加しよう!
|
||||
about.community.kaiheila=KaiHeiLa
|
||||
about.community.kaiheila.statement=チャットに参加しよう!
|
||||
about.community.kookapp=KOOK
|
||||
about.community.kookapp.statement=チャットに参加しよう!
|
||||
about.dependency=Dependencies
|
||||
about.legal=法的承認
|
||||
about.thanks_to=Thanks to
|
||||
@@ -36,11 +36,12 @@ about.thanks_to.bangbang93.statement=BMCLAPIダウンロードソースプロバ
|
||||
about.thanks_to.contributors=問題、プルリクエストなどを介してこのプロジェクトに参加したすべての貢献者。
|
||||
about.thanks_to.contributors.statement=Hello Minecraftをサポートしてくれたオープンソースコミュニティに感謝します!ランチャー
|
||||
about.thanks_to.gamerteam.statement=デフォルトの背景画像プロバイダー。
|
||||
about.thanks_to.glavo.statement=HMCLに多くのテクニカルサポートを提供
|
||||
about.thanks_to.mcbbs=MCBBS
|
||||
about.thanks_to.mcbbs.statement=MCBBSダウンロードソースプロバイダー。
|
||||
about.thanks_to.mcmod=mcmod.cn
|
||||
about.thanks_to.mcmod.statement=Mod中国語の名前翻訳プロバイダー。
|
||||
about.thanks_to.noin=noin.cn
|
||||
about.thanks_to.noin=mcer.cn
|
||||
about.thanks_to.noin.statement=マルチプレイヤーサービスプロバイダー。
|
||||
about.thanks_to.red_lnn.statement=デフォルトの背景画像プロバイダー。
|
||||
about.thanks_to.users=HMCLユーザーグループのメンバー
|
||||
@@ -71,6 +72,7 @@ account.failed.migration=アカウントをMicrosoftアカウントに移行す
|
||||
account.failed.no_character=このアカウントに文字がありません。
|
||||
account.failed.server_response_malformed=無効なサーバー応答。認証サーバーでエラーが発生している可能性があります。
|
||||
account.failed.wrong_account=不一致のアカウントでログインしました。
|
||||
account.hmcl.hint=「ログイン」をクリックし、開いているページでログインを完了する必要があります。
|
||||
account.injector.add=認証サーバーを追加します
|
||||
account.injector.empty=Empty(追加するには右側のプラスボタンをクリックしてください)
|
||||
account.injector.http=警告:このサーバーはHTTPを使用するため、パスワードはクリアテキストで送信されます。
|
||||
@@ -86,18 +88,20 @@ account.register=登録
|
||||
account.manage=アカウントリスト
|
||||
account.methods=ログインタイプ
|
||||
account.methods.authlib_injector=authlib-インジェクター
|
||||
account.methods.little_skin=Little Skin
|
||||
account.methods.microsoft=Microsoft Account
|
||||
account.methods.microsoft.birth=誕生日の設定を編集する方法...
|
||||
account.methods.microsoft.deauthorize=アカウントのバインドを解除
|
||||
account.methods.microsoft.close_page=Microsoftアカウントの認証が終了しました。後で終了するログイン手順がいくつか残っています。このページを今すぐ閉じることができます。
|
||||
account.methods.microsoft.error.add_family=まだ18歳ではないため、Minecraftをプレイするには、大人があなたを家族に追加する必要があります。
|
||||
account.methods.microsoft.error.add_family_probably=すでに18歳であるかどうかにかかわらず、アカウント設定を確認してください。そうでない場合は、アカウントを家族に追加する必要があります。
|
||||
account.methods.microsoft.error.country_unavailable=XBoxLiveはお住まいの国/地域ではご利用いただけません。
|
||||
account.methods.microsoft.error.missing_xbox_account=MicrosoftアカウントがXboxアカウントに接続されていません。続行する前に作成してください。
|
||||
account.methods.microsoft.error.no_character=アカウントにMinecraftJavaプロファイルがありません。 Microsoftアカウントは有効ですが、ゲームを所有していません。
|
||||
account.methods.microsoft.error.unknown=ログインに失敗しました。Microsoftはエラーコード%dで応答します。
|
||||
account.methods.microsoft.error.unknown=ログインに失敗しました。Microsoftはエラーコード %d で応答します。
|
||||
account.methods.microsoft.logging_in=ログイン...
|
||||
account.methods.microsoft.hint=「ログイン」ボタンをクリックして、新しく開いたブラウザウィンドウでログインプロセスを続行する必要があります。
|
||||
account.methods.microsoft.manual=「ログイン」ボタンをクリックした後、新しく開いたブラウザウィンドウで認証を完了する必要があります。ブラウザウィンドウが表示されない場合は、ここをクリックしてURLをコピーし、ブラウザで手動で開くことができます。
|
||||
account.methods.microsoft.hint=「ログイン」ボタンをクリックして、新しく開いたブラウザウィンドウでログインプロセスを続行する必要があります。\nMicrosoftアカウントへのログインに使用されたトークンが誤って漏洩した場合は、下の[アカウントのバインドを解除]をクリックして、ログイン認証をキャンセルできます。
|
||||
account.methods.microsoft.manual=「ログイン」ボタンをクリックした後、新しく開いたブラウザウィンドウで認証を完了する必要があります。ブラウザウィンドウが表示されない場合は、ここをクリックしてURLをコピーし、ブラウザで手動で開くことができます。\nMicrosoftアカウントへのログインに使用されたトークンが誤って漏洩した場合は、下の[アカウントのバインドを解除]をクリックして、ログイン認証をキャンセルできます。
|
||||
account.methods.microsoft.profile=アカウントプロファイル..
|
||||
account.methods.microsoft.snapshot=HMCLスナップショットバージョンはMicrosoftログインをサポートしていません。
|
||||
account.methods.microsoft.waiting_browser=開いているブラウザウィンドウで認証を待機しています...
|
||||
@@ -148,7 +152,7 @@ button.install=インストール
|
||||
button.export=エクスポート
|
||||
button.no=いいえ
|
||||
button.ok=OK
|
||||
button.refresh=リフレッシュ
|
||||
button.refresh=更新
|
||||
button.remove=削除
|
||||
button.remove.confirm=削除してもよろしいですか?この操作をロールバックすることはできません!
|
||||
button.save=保存
|
||||
@@ -182,6 +186,11 @@ curse.category.4472=Tech
|
||||
curse.category.4473=魔法
|
||||
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/6
|
||||
curse.category.5129=Vanilla+
|
||||
curse.category.5189=Utility & QOL
|
||||
curse.category.5190=QoL
|
||||
curse.category.5191=Utility & QoL
|
||||
curse.category.5192=FancyMenu
|
||||
curse.category.423=地図と情報
|
||||
curse.category.426=Addons
|
||||
curse.category.434=武器、道具、武器
|
||||
@@ -224,6 +233,7 @@ curse.category.4843=自動化
|
||||
curse.category.4906=MCreator
|
||||
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/6
|
||||
curse.category.5193=データパッケージ
|
||||
curse.category.399=スチームパンク
|
||||
curse.category.396=128x
|
||||
curse.category.398=512x以上
|
||||
@@ -274,7 +284,7 @@ download.content=ゲームコンテンツ
|
||||
download.curseforge.customization=光と影、およびゲームのカスタマイズ
|
||||
download.existing=ファイルは既に存在するため、保存できません。 「名前を付けて保存」を選択して、ファイルを別の場所に保存できます。
|
||||
download.external_link=ダウンロードサイトを開く
|
||||
download.failed=%1$sのダウンロードに失敗しました、応答コード:%2 $ d
|
||||
download.failed=%1$s のダウンロードに失敗しました、応答コード:%2$d
|
||||
download.failed.empty=候補者なし。戻るにはここをクリックしてください。
|
||||
download.failed.refresh=バージョンリストをダウンロードできません。ここをクリックして再試行してください。
|
||||
download.game=ゲームのダウンロード
|
||||
@@ -286,9 +296,15 @@ download.provider.balanced=mcbbsのソースからロード
|
||||
download.provider.mirror=ミラーソースからロード
|
||||
download.javafx=必要なランタイムコンポーネントのダウンロード
|
||||
download.javafx.notes=ネットワークを介したHMCLに必要なコンポーネントのダウンロード。\n [ダウンロードソースの変更]ボタンをクリックして詳細を表示し、ダウンロードソースを選択するか、[キャンセル]ボタンをクリックして停止して終了します。\n注:ダウンロード速度が遅すぎます。ダウンロードソースを変更してみてください。
|
||||
download.javafx.component=モジュール%sをダウンロードしています
|
||||
download.javafx.component=ダウンロード中のモジュール %s
|
||||
download.javafx.prepare=ダウンロードする準備ができました
|
||||
|
||||
exception.access_denied=ファイル %s にアクセスできないので、HMCL がファイルにアクセスできないか、ファイルが他のプログラムによって開かれています。\n\
|
||||
例えば、管理者でないユーザーは、他のアカウントの個人フォルダーにあるファイルにアクセスできない場合があります。\n\
|
||||
Windowsユーザーの場合、リソースモニターでプログラムがファイルを占有しているかどうかを確認し、もしそうなら、このファイルを占有している関連プログラムを閉じるか、コンピュータを再起動して、もう一度試してみることも可能です。
|
||||
exception.artifact_malformed=ダウンロードしたファイルがチェックサムを通過していない。
|
||||
exception.ssl_handshake=現在のJava仮想マシンに該当するSSL証明書がないため、SSL接続を確立できませんでした。 別のJava仮想マシンでHMCLを起動して、もう一度試してみてください。
|
||||
|
||||
extension.bat=WindowsBatファイル
|
||||
extension.mod=Modファイル
|
||||
extension.png=画像ファイル
|
||||
@@ -300,7 +316,9 @@ fatal.javafx.missing=JavaFXがありません。\nJava11以降を使用してい
|
||||
fatal.missing_dst_root_ca_x3=現在のJavaプラットフォームにDSTルートCAX3証明書がありません。\n引き続きHelloMinecraftを使用できます。ランチャーですが、一部のサイト(Let's Encryptによって発行された証明書を使用するサイトなど)に接続できず、ランチャーが正しく機能しない可能性があります。\n問題を解決するには、Javaランタイムを8u101以降に更新してください。
|
||||
fatal.config_loading_failure=構成にアクセスできません。\nHelloMinecraftを確認してください。 Launcherには、「%s」とその中のファイルへの読み取りおよび書き込みアクセス権があります。
|
||||
fatal.migration_requires_manual_reboot=更新が完了しました。 Hello Minecraftを再開してください!ランチャー。
|
||||
fatal.apply_update_failure=ごめんなさい、こんにちはMinecraft!何か問題が発生したため、ランチャーは更新を完了できませんでした。\nただし、Hello Minecraftをダウンロードすることで、手動で更新を終了できます。 %sからのランチャー。\nこの問題を報告することを検討してください。
|
||||
fatal.apply_update_failure=ごめんなさい、Hello Minecraft! Launcher 何か問題が発生したため、ランチャーは更新を完了できませんでした。\nただし、Hello Minecraftをダウンロードすることで、手動で更新を終了できます。 %s からのランチャー。\nこの問題を報告することを検討してください。
|
||||
fatal.samba=If you are trying to run HMCL in a shared folder by Samba, HMCL may not working, please try updating your Java or running HMCL in a local folder.
|
||||
fatal.illegal_char=ユーザーフォルダーのパスに不正な文字'='が含まれています。hmclは動作しますが、一部の機能が正しく動作しません。 \nログインアカウントやオフラインログインではスキンの変更ができなくなり、多人数参加型ゲームをプレイする際のセキュリティリスクが高くなる可能性があります。
|
||||
|
||||
feedback=フィードバック
|
||||
Feedback.add=フィードバックを追加
|
||||
@@ -343,33 +361,37 @@ game.crash.feedback=問題は非常に一般的ですが、理由がわかりま
|
||||
game.crash.info=ゲームステータス
|
||||
game.crash.reason=クラッシュアナライザー
|
||||
game.crash.reason.analyzing=分析中..
|
||||
game.crash.reason.block=ブロックが原因でゲームを実行できません。\nMCEditでこのブロックを削除するか、関連するModを直接削除できます。\nブロックタイプ:%1$s \nブロックの場所:%2 $ s
|
||||
game.crash.reason.bootstrap_failed=mod%1$sがクラッシュしたため、ゲームを実行できません。\n削除または更新を試みることができます。
|
||||
game.crash.reason.config=modが構成ファイルを解析できないため、ゲームを実行できません。\nMod%1$sは構成ファイル%2 $ sを解析できません。
|
||||
game.crash.reason.block=ブロックが原因でゲームを実行できません。\nMCEditでこのブロックを削除するか、関連するModを直接削除できます。\nブロックタイプ:%1$s \nブロックの場所:%2$s
|
||||
game.crash.reason.bootstrap_failed=mod %1$s がクラッシュしたため、ゲームを実行できません。\n削除または更新を試みることができます。
|
||||
game.crash.reason.config=modが構成ファイルを解析できないため、ゲームを実行できません。\nMod %1$s は構成ファイル %2$s を解析できません。
|
||||
game.crash.reason.debug_crash=クラッシュが手動でトリガーされるため、ゲームを実行できません。\n実際、ゲームは無実であり、すべてあなたの責任です。
|
||||
game.crash.reason.duplicated_mod=modが重複しているためゲームを実行できません:%s。\n%s \n各Modは1つしかインストールできません。重複したmodを削除して、再試行してください。
|
||||
game.crash.reason.entity=エンティティが原因でゲームを実行できません。\nMCEditでこのエンティティを削除するか、関連するModを直接削除できます。\nエンティティタイプ:%1$s \nエンティティの場所:%2 $ s
|
||||
game.crash.reason.duplicated_mod=Mod %1$s が重複してインストールされているため、現在のゲームを継続して実行できません。\n%2$s\n各Modは1つしかインストールできませんので、余分なModを削除して再度お試しください。
|
||||
game.crash.reason.entity=エンティティが原因でゲームを実行できません。\nMCEditでこのエンティティを削除するか、関連するModを直接削除できます。\nエンティティタイプ:%1$s \nエンティティの場所:%2$s
|
||||
game.crash.reason.fabric_version_0_12=Fabric 0.12以降は、現在インストールされているmodと互換性がありません。ファブリックを0.11.7にダウングレードする必要があります。
|
||||
game.crash.reason.fabric_warnings=Fabricはいくつかの警告を出します:\n%1$s
|
||||
game.crash.reason.file_already_exists=ファイル%1$sが既に存在するため、ゲームを実行できません。\n削除できる場合は、バックアップして削除し、再試行してください。
|
||||
game.crash.reason.file_changed=ファイルの検証に失敗したため、ゲームを実行できません。\nMinecraftプライマリjarを変更した場合は、変更をロールバックするか、ゲームを再ダウンロードする必要があります。
|
||||
game.crash.reason.file_already_exists=ファイル %1$s が既に存在するので、現在のゲームは続行できません。 \このファイルを削除してもよいと思われる場合は、このファイルをバックアップして、ゲームを再起動してから削除してみてください。
|
||||
game.crash.reason.file_changed=ファイルのチェックサムに失敗したため、現在のゲームを続行できません。 \nMinecraft.jar ファイルを手動で変更した場合、変更をロールバックするか、ゲームを再度ダウンロードする必要があります。
|
||||
game.crash.reason.gl_operation_failure=一部のモッド、シェーダーパック、テクスチャパックが原因でゲームがクラッシュしました。\n使用しているモッド/シェーダーパック/テクスチャパックを無効にして、再試行してください。
|
||||
game.crash.reason.graphics_driver=グラフィックスドライバーの問題によりゲームがクラッシュしました。\nグラフィックスドライバーを最新バージョンに更新してください。\nコンピューターにディスクリートグラフィックスカードがある場合は、ゲームでitegrated /を使用しているかどうかを確認する必要があります。コアグラフィックス。その場合は、ディスクリートグラフィックカードを使用してHMCLとゲームを実行してください。それでもこれらの問題が発生する場合は、新しいグラフィックカードまたは新しいコンピュータの入手を検討してください。
|
||||
game.crash.reason.graphics_driver=現在、お使いのグラフィックカードのドライバに問題があるため、ゲームがクラッシュしています。\n\
|
||||
ゲームを起動する前に、グラフィックカードのドライバーを最新バージョンにアップグレードしてみてください。\n\
|
||||
ディスクリートグラフィックカードを搭載している場合、統合型/コアグラフィックカードを使用してゲームが起動するかどうかを確認する必要があります。 その場合、ディスクリートグラフィックカードを使用したゲームでHMCLを起動してみてください。 それでも問題がある場合は、新しいグラフィックカードや新しいコンピュータの購入を検討する必要があるかもしれません。 \n\
|
||||
コアグラフィックカードが必要な場合は、お使いのパソコンのCPUがIntel(R) Core(TM) 3000シリーズ以上であることをご確認ください。 その場合、Minecraft 1.16.5以前の場合は、ゲームに使用しているJavaバージョンを1.8.0_51以下にダウングレードするか、ディスクリートグラフィックカードを交換するか、または、新しい コンピュータを使用します。 \n\
|
||||
詳しくは、<a href="https://hmcl.huangyuhui.net/help/game/graphics-card">こちら</a>をクリックしてご覧ください。
|
||||
game.crash.reason.illegal_access_error=一部のmodが原因でゲームがクラッシュしました。\n認識している場合:%1$s、modを更新または削除して、再試行してください。
|
||||
game.crash.reason.jdk_9=Javaのバージョンが高すぎるため、ゲームを実行できません。\nJava 8をダウンロードしてインストールし、ゲーム設定で新しくインストールしたJavaを選択する必要があります。
|
||||
game.crash.reason.jvm_32bit=メモリ割り当てが32ビットJavaVMの制限を超えたため、ゲームがクラッシュしました。\nOSが64ビットの場合は、64ビットJavaをインストールして使用してください。 OSが32ビットの場合は、64ビットOSを再インストールするか、新しいコンピュータを変更できます。\nまたは、自動メモリ割り当てを無効にして、最大メモリサイズを1024MB以下に設定できます。
|
||||
game.crash.reason.loading_crashed_forge=mod%1$s(%2 $ s)がクラッシュしたため、ゲームがクラッシュしました。\n削除または更新を試みることができます。
|
||||
game.crash.reason.loading_crashed_fabric=mod%1$sがクラッシュしたため、ゲームがクラッシュしました。\n削除または更新を試みることができます。
|
||||
game.crash.reason.loading_crashed_forge=mod %1$s(%2$s)がクラッシュしたため、ゲームがクラッシュしました。\n削除または更新を試みることができます。
|
||||
game.crash.reason.loading_crashed_fabric=mod %1$s がクラッシュしたため、ゲームがクラッシュしました。\n削除または更新を試みることができます。
|
||||
game.crash.reason.memory_exceeded=JVMが割り当てるのに十分なメモリがないため、ゲームがクラッシュしました。\nこの問題は、ページサイズが小さすぎることが原因です。\nゲーム設定で自動メモリ割り当てをオフにし、メモリ割り当てを次のように調整する必要があります。システムが処理できる値。\nシステムのページサイズを十分な大きさに調整することもできます。
|
||||
game.crash.reason.mod=modが原因でゲームがクラッシュしました:%1$s。\nmodを更新または削除して、再試行できます。
|
||||
game.crash.reason.mod_resolution=modの解決に失敗したため、ゲームがクラッシュしました。\nFabricは次の情報を提供します:\n%s
|
||||
game.crash.reason.mod_resolution_collection=必要なmodバージョンに互換性がないため、ゲームがクラッシュしました。\n%1$sには%2 $ sが必要です。\nこれは、%2 $ sをアップグレードまたはダウングレードする必要があることを意味します。 %3 $ sをダウンロードする必要があります。
|
||||
game.crash.reason.mod_resolution_conflict=modが競合しているため、ゲームがクラッシュしました。\n%1$sが%2 $ sと競合しています。
|
||||
game.crash.reason.mod_resolution_missing=一部のmodが依存するmodがインストールされていないため、ゲームがクラッシュしました。\n%1$sではmodをインストールする必要があります:%2 $ s。\nこれは、%2 $をダウンロードする必要があることを意味します。 ■ダウンロードページまたはオンラインWebサイトから。
|
||||
game.crash.reason.mod_resolution_missing_minecraft=modが現在のMinecraftバージョンと互換性がないため、ゲームがクラッシュしました。\n%1$sにはMinecraftバージョン%2 $ sが必要です。\nインストールされているバージョンのmodを保持する場合は、変更する必要があります。ゲームバージョン。\n現在のゲームバージョンを引き続き使用する場合は、適切なmodを再インストールする必要があります。
|
||||
game.crash.reason.mod_resolution_mod_version=%1$s(バージョン:%2 $ s)
|
||||
game.crash.reason.mod_resolution_collection=改造前のバージョンと一致しないため、現在のゲームを続行できない。\n%1$s Mod: %2$s が必要です。をクリックすると実行を継続します。\nつまり、フロントエンドをアップデートするか、ダウングレードする必要があるのです。 ダウンロードページからMODをダウンロードするか、ウェブから %3$s をダウンロードすることができます。
|
||||
game.crash.reason.mod_resolution_conflict=modが競合しているため、ゲームがクラッシュしました。\n%1$s が %2$s と競合しています。
|
||||
game.crash.reason.mod_resolution_missing=Modプレフィックスがないため、現在のゲームを続けることができません。 \続行するにはMod: %2$s が必要です。 \これはMODがインストールされていないか、そのMODのバージョンが足りないことを意味します。 ダウンロードページからMODをダウンロードするか、ウェブから %3$s をダウンロードすることができます。
|
||||
game.crash.reason.mod_resolution_missing_minecraft=modが現在のMinecraftバージョンと互換性がないため、ゲームがクラッシュしました。\n%1$s にはMinecraftバージョン %2$s が必要です。\nインストールされているバージョンのmodを保持する場合は、変更する必要があります。ゲームバージョン。\n現在のゲームバージョンを引き続き使用する場合は、適切なmodを再インストールする必要があります。
|
||||
game.crash.reason.mod_resolution_mod_version=%1$s(バージョン:%2$s)
|
||||
game.crash.reason.mod_resolution_mod_version.any=%1$s(任意のバージョン)
|
||||
game.crash.reason.no_class_def_found_error=コードが不完全なためゲームを実行できません。\nゲームにmodがないか、一部のmodファイルが不完全であるか、一部のmodが現在のゲームと互換性がない可能性があります。\nゲームを再インストールする必要がある場合があります。およびmod、またはヘルプを要求します。\n%1$sがありません
|
||||
game.crash.reason.no_class_def_found_error=コードが不完全なためゲームを実行できません。\nゲームにmodがないか、一部のmodファイルが不完全であるか、一部のmodが現在のゲームと互換性がない可能性があります。\nゲームを再インストールする必要がある場合があります。およびmod、またはヘルプを要求します。\n%1$s がありません
|
||||
game.crash.reason.no_such_method_error=コードが不完全なためゲームを実行できません。\nゲームにmodがないか、一部のmodファイルが不完全であるか、一部のmodが現在のゲームと互換性がない可能性があります。\nゲームを再インストールする必要がある場合があります。と改造、または助けを求める。
|
||||
game.crash.reason.opengl_not_supported=グラフィックスドライバに問題があるため、ゲームを実行できません。\nOpenGLがサポートされていないため、リモートデスクトップモードですか、それともストリーミングモードですか。はいの場合は、元のコンピューターを使用してゲームを開始してください。\nまたは、グラフィックスドライバーを最新バージョンに更新して、ゲームを再起動してください。コンピューターに個別のグラフィックがある場合は、ゲームが統合/コアグラフィックを使用しているかどうかを確認する必要があります。その場合は、ディスクリートグラフィックカードを使用してHMCLとゲームを実行してみてください。それでもこれらの問題が発生する場合は、新しいグラフィックカードまたは新しいコンピュータの入手を検討してください。
|
||||
game.crash.reason.openj9=ゲームはOpenJ9仮想マシンでは実行できません。ゲーム設定でHotspotJava仮想マシンに切り替えて、ゲームを再起動してください。そうでない場合は、オンラインでダウンロードできます。
|
||||
@@ -392,29 +414,28 @@ input.number=数値である必要があります。
|
||||
input.not_empty=必須フィールド
|
||||
input.url=有効なURLである必要があります。
|
||||
|
||||
install=新しい構成を作成
|
||||
install=新規作成
|
||||
install.change_version=バージョンの変更
|
||||
install.change_version.confirm=%sをバージョン%sから%sに更新してもよろしいですか?
|
||||
install.change_version.confirm=%s をバージョン %s から %s に更新してもよろしいですか?
|
||||
install.failed=バージョンのインストールに失敗しました
|
||||
install.failed.downloading=一部のファイルが正常にダウンロードされなかったため、インストールに失敗しました
|
||||
install.failed.downloading.access_denied=ファイルのダウンロードに失敗しました:%1$s。これは、オペレーティングシステムによってファイル%2 $ sへのアクセスが拒否されているためです。このファイルにアクセスする権限がないか、このファイルが他のプログラムによって既に開かれている可能性があります。関連するプログラムを閉じるか、コンピュータを再起動してみてください。
|
||||
install.failed.downloading.detail=ファイルのダウンロードに失敗しました:%s
|
||||
install.failed.downloading.timeout=ファイルのダウンロード中にタイムアウトしました:%s
|
||||
install.failed.install_online=提供されたインストーラーファイルを認識できません。 modをインストールする場合は、「Mods」ページに移動します。
|
||||
install.failed.malformed=少し前にダウンロードしたファイルの形式が正しくありません。この問題を解決するには、他のダウンロードプロバイダーに切り替えることができます。
|
||||
install.failed.optifine_conflict=Fabric、OptiFine、ForgeがMinecraft1.13に同時にインストールされます
|
||||
install.failed.optifine_forge_1.17=Minecraft 1.17.1の場合、OptiFine H1 Pre2以降のバージョンのみがForgeと互換性があり、スナップショットバージョンでインストールできます。
|
||||
install.failed.version_mismatch=ライブラリにはゲームバージョン%sが必要ですが、実際のバージョンは%sです。
|
||||
install.installer.change_version=%s、このバージョンは現在のゲームバージョンと互換性がありません。別のものを選択するには、ここをクリックしてください。
|
||||
install.installer.choose=%sバージョンを選択してください
|
||||
install.installer.depend=%sに依存
|
||||
install.failed.version_mismatch=ライブラリにはゲームバージョン %s が必要ですが、実際のバージョンは %s です。
|
||||
install.installer.change_version=%s 、このバージョンは現在のゲームバージョンと互換性がありません。別のものを選択するには、ここをクリックしてください。
|
||||
install.installer.choose=%s バージョンを選択してください
|
||||
install.installer.depend=%s に依存
|
||||
install.installer.fabric=Fabric
|
||||
install.installer.fabric-api=Fabric API
|
||||
install.installer.fabric-api.warning=警告:Fabric APIはmodであり、新しいゲームのmodディレクトリにインストールされます。新しいゲームの実行ディレクトリを変更しないでください。変更すると、FabricAPIのインストールが失われます。その設定を変更したい場合は、FabricAPIを再インストールする必要があります。
|
||||
install.installer.forge=Forge
|
||||
install.installer.game=Minecraft
|
||||
install.installer.incompatible=%sと互換性がありません
|
||||
install.installer.install=インストール%s
|
||||
install.installer.incompatible=%s と互換性がありません
|
||||
install.installer.install=インストール %s
|
||||
install.installer.install_offline=ファイルからのインストール/更新
|
||||
install.installer.install_offline.extension=Forge / OptiFineインストーラー
|
||||
install.installer.install_offline.tooltip=Forge / OptiFineインストールファイルのインポートをサポートします
|
||||
@@ -423,9 +444,11 @@ install.installer.install_online.tooltip=Fabric、Forge、OptiFine、LiteLoader
|
||||
install.installer.liteloader=LiteLoader
|
||||
install.installer.not_installed=インストールされていません
|
||||
install.installer.optifine=OptiFine
|
||||
install.installer.quilt=Quilt
|
||||
install.installer.quilt-api=Quilt API
|
||||
install.installer.version=%s
|
||||
install.modpack=modpackを導入
|
||||
install.new_game=新しいゲームをインストールする
|
||||
install.new_game=新規作成
|
||||
install.new_game.already_exists=このバージョンはすでに存在します。
|
||||
install.new_game.current_game_version=現在のゲームバージョン
|
||||
install.new_game.malformed=無効な名前
|
||||
@@ -447,17 +470,16 @@ launch.advice.java8_51_1_13=Minecraft 1.13は、1.8.0_51より前のJava8でク
|
||||
launch.advice.java9=Java9以降のバージョンのJavaでMinecraft1.12以前を起動することはできません。
|
||||
ゲームを高速化するには、launch.advice.newer_java=Java8をお勧めします。多くのMinecraft1.12以降、およびほとんどのModには、Java8が必要です。
|
||||
launch.advice.not_enough_space=割り当てたメモリが多すぎます。物理メモリサイズが%dMBであるため、ゲームがクラッシュする可能性があります。打ち上げを続けましょうか。
|
||||
launch.advice.require_newer_java_version=Minecraft%1$sにはJava%2 $ s以降が必要です。今すぐダウンロードしますか?
|
||||
launch.advice.require_newer_java_version=Minecraft %1$s にはJava %2$s 以降が必要です。今すぐダウンロードしますか?
|
||||
launch.advice.too_large_memory_for_32bit=32ビットJavaランタイム環境が原因で、割り当てたメモリが多すぎるため、ゲームがクラッシュする可能性があります。 32ビットシステムの最大メモリ容量は1024MBです。打ち上げを続けましょうか。
|
||||
launch.advice.vanilla_linux_java_8=Linux x86-64の場合、Minecraft1.12.2以下はJava8でのみ実行できます。\nJava9以降のバージョンでは、liblwjgl.soなどの32ビットネイティブライブラリをロードできません。
|
||||
launch.advice.vanilla_x86=Minecraftは現在、x86およびx86-64以外のアーキテクチャの公式サポートを提供していません。\nプラットフォームの対応するネイティブライブラリをダウンロードして、その配置パスを指定してみてください。
|
||||
launch.advice.vanilla_x86.translation=Minecraftは現在、x86およびx86-64以外のアーキテクチャの公式サポートを提供していません。\nJava for x86-64を使用して、トランスレータを介してminecraftを実行するか、プラットフォームの対応するネイティブライブラリをダウンロードして指定してくださいその配置パス。
|
||||
launch.failed=起動できません
|
||||
launch.failed.cannot_create_jvm=Java仮想マシンを作成できませんでした。 Java引数は問題を引き起こす可能性があります。 JVM引数なしで再起動してください。
|
||||
launch.failed.creating_process=プロセスの作成に失敗しました。 Javaパスを確認してください。
|
||||
launch.failed.command_too_long=コマンドの長さが制限を超えているため、batスクリプトを作成できません。 PowerShellスクリプトとしてエクスポートしてください。
|
||||
launch.failed.decompressing_natives=ネイティブライブラリを解凍できません。
|
||||
launch.failed.download_library=ライブラリ%sをダウンロードできません。
|
||||
launch.failed.download_library=ライブラリ %s をダウンロードできません。
|
||||
launch.failed.executable_permission=起動スクリプトにアクセス許可を追加できません。
|
||||
launch.failed.execution_policy=実行ポリシーを設定します
|
||||
launch.failed.execution_policy.failed_to_set=実行ポリシーの設定に失敗しました
|
||||
@@ -480,13 +502,14 @@ launcher.agreement.hint=このソフトウェアを使用するには、EULAに
|
||||
launcher.background=背景画像
|
||||
launcher.background.classic=クラシック
|
||||
launcher.background.choose=背景画像ファイルを選択してください
|
||||
launcher.background.default=標準
|
||||
launcher.background.default=標準(ランチャーと同じディレクトリにある background.png/jpg と bg フォルダから自動的に画像を取得します。)
|
||||
launcher.background.network=ネットワーク
|
||||
launcher.background.translucent=半透明
|
||||
launcher.cache_directory=キャッシュ用のディレクトリ
|
||||
launcher.cache_directory.clean=Clear
|
||||
launcher.cache_directory.clean=クリアランス
|
||||
launcher.cache_directory.choose=キャッシュするディレクトリを選択します
|
||||
launcher.cache_directory.default=標準
|
||||
launcher.cache_directory.disabled=無効
|
||||
launcher.cache_directory.default=標準(%AppData%/.minecraft または ~/.minecraft)
|
||||
launcher.cache_directory.disabled=無効(常にゲームパスを使用する)
|
||||
launcher.cache_directory.invalid=無効なディレクトリ。デフォルト設定の復元。
|
||||
launcher.contact=お問い合わせ
|
||||
launcher.crash=Hello Minecraft!ランチャーがクラッシュしました!次のコンテンツをコピーして、MCBBS、Baidu Tieba、GitHub、またはMinecraftForumを介してフィードバックを送信してください。
|
||||
@@ -544,11 +567,11 @@ modpack.files.resourcepacks=リソース/テクスチャパック
|
||||
modpack.files.saves=保存されたマップ
|
||||
modpack.files.scripts=MineTweaker構成
|
||||
modpack.files.servers_dat=サーバーリスト
|
||||
modpack.install=%smodpackをインストールします
|
||||
modpack.install=%s modpackをインストールします
|
||||
modpack.installing=modpackのインストール
|
||||
modpack.introduction=Curse、MultiMC、HMCLmodpackがサポートされています。
|
||||
modpack.invalid=無効なmodpackファイル。
|
||||
modpack.mismatched_type=不適切なmodpackタイプ、現在のゲームは%s modpackですが、更新ファイルは%smodpackです。
|
||||
modpack.mismatched_type=不適切なmodpackタイプ、現在のゲームは %s modpackですが、更新ファイルは %s modpackです。
|
||||
modpack.name=Modpack名
|
||||
modpack.not_a_valid_name=無効なmodpack名
|
||||
modpack.origin=Origin
|
||||
@@ -561,8 +584,8 @@ modpack.task.install.error=このmodpackファイルは認識できません。
|
||||
modpack.task.install.will=modpackをインストールします:
|
||||
modpack.type.curse=Curse
|
||||
modpack.type.curse.completion=Cursemodpackに関連するファイルをインストールします
|
||||
modpack.type.curse.tolerable_error=このCursemodpackのすべてのファイルのダウンロードを完了できません。対応するゲームバージョンを開始するときに、ダウンロードを再試行できます。ネットワークの問題により、数回再試行する場合があります。
|
||||
modpack.type.curse.error=このCursemodpackをインストールできません。再試行してください。
|
||||
modpack.type.curse.tolerable_error=このmodpackのすべてのファイルのダウンロードを完了できません。対応するゲームバージョンを開始するときに、ダウンロードを再試行できます。ネットワークの問題により、数回再試行する場合があります。
|
||||
modpack.type.curse.error=このmodpackをインストールできません。再試行してください。
|
||||
modpack.type.curse.not_found=必要なリソースの一部が欠落しているため、ダウンロードできませんでした。最新バージョンまたは他のmodpackを検討してください。
|
||||
modpack.type.manual.warning=このmodpackをインポートする代わりに、おそらくこのmodpackファイルを直接解凍する必要があります。そして、バンドルされたランチャーを使用してゲームを起動します。このmodpackは、ランチャーによってエクスポートされるのではなく、.minecraftディレクトリを圧縮することによって手動で作成されます。 HMCLはこのmodpackのインポートを試みることができます、続行しますか?
|
||||
modpack.type.mcbbs=MCBBS標準
|
||||
@@ -604,8 +627,8 @@ modrinth.category.worldgen=Worldgen
|
||||
|
||||
mods=Mods
|
||||
mods.add=modを追加
|
||||
mods.add.failed=mods%sの追加に失敗しました。
|
||||
mods.add.success=mods%sが正常に追加されました。
|
||||
mods.add.failed=mods %s の追加に失敗しました。
|
||||
mods.add.success=mods %s が正常に追加されました。
|
||||
mods.category=Category
|
||||
mods.check_updates=更新を確認
|
||||
mods.check_updates.current_version=Current
|
||||
@@ -619,7 +642,7 @@ mods.curseforge=CurseForge
|
||||
mods.dependencies=Dependencies
|
||||
mods.disable=無効にする
|
||||
mods.download=Modのダウンロード
|
||||
mods.download.title=Modダウンロード-%1s
|
||||
mods.download.title=Modダウンロード- %1s
|
||||
mods.enable=有効にする
|
||||
mods.manage=Mods
|
||||
mods.mcbbs=MCBBS
|
||||
@@ -638,11 +661,13 @@ multiplayer.download=マルチプレイヤーの依存関係をダウンロー
|
||||
multiplayer.download.failed=マルチプレイヤーの初期化に失敗しました。一部のファイルはダウンロードできません
|
||||
multiplayer.download.success=依存関係の初期化に成功しました
|
||||
multiplayer.download.unsupported=現在のオペレーティングシステムまたはアーキテクチャはサポートされていません。
|
||||
multiplayer.exit.after_ready=マルチプレイヤーセッションが壊れています。 cato exitcode%d
|
||||
multiplayer.exit.before_ready=マルチプレイヤーセッションの作成に失敗しました。 cato exitcode%d
|
||||
multiplayer.exit.after_ready=マルチプレイヤーセッションが壊れています。 cato exitcode %d
|
||||
multiplayer.exit.before_ready=マルチプレイヤーセッションの作成に失敗しました。 cato exitcode %d
|
||||
multiplayer.exit.timeout=マルチプレイヤーサーバーへの接続に失敗しました。
|
||||
multiplayer.exit.timeout.dynamic_token=マルチプレイヤーサーバーへの接続に失敗しました。
|
||||
multiplayer.hint=マルチプレイヤー機能は実験的なものです。フィードバックをお願いします。
|
||||
multiplayer.nat=ネットワークタイプの検出
|
||||
multiplayer.nat.failed=ネットワークタイプの検出に失敗しました。
|
||||
multiplayer.nat.hint=ネットワークタイプの検出により、ネットワークがマルチプレイヤーモードの要件を満たしているかどうかが明確になります。
|
||||
multiplayer.nat.latency=レイテンシー
|
||||
multiplayer.nat.not_yet_tested=まだテストされていません
|
||||
@@ -657,13 +682,13 @@ multiplayer.nat.type.restricted_cone=Medium(制限付きコーン)
|
||||
multiplayer.nat.type.symmetric=Bad(対称)
|
||||
multiplayer.nat.type.symmetric_udp_firewall=Bad(UDPファイアウォールと対称)
|
||||
multiplayer.nat.type.unknown=不明`
|
||||
multiplayer.powered_by=マルチプレイヤーサービスは<a href="https://noin.cn">noin.cn</a>によって提供されます。 <a href="https://noin.cn/agreement">EULA</a>
|
||||
multiplayer.powered_by=マルチプレイヤーサービスは<a href="https://mcer.cn">mcer.cn</a>によって提供されます。 <a href="https://hmcl.huangyuhui.net/api/redirect/multiplayer-agreement">EULA</a>
|
||||
multiplayer.report=レポート
|
||||
multiplayer.relay=リレーモード
|
||||
multiplayer.relay.hint=静的トークンを持ち、テストされたNATタイプが不良対称であるユーザーの場合
|
||||
multiplayer.session=セッション
|
||||
multiplayer.session.name.format=%1$sの部屋
|
||||
multiplayer.session.name.motd=HMCLマルチプレイヤーセッション-%s
|
||||
multiplayer.session.name.format=%1$s の部屋
|
||||
multiplayer.session.name.motd=HMCLマルチプレイヤーセッション - %s
|
||||
multiplayer.session.close=部屋を閉じる
|
||||
multiplayer.session.close.warning=部屋を閉じた後、部屋に参加したすべてのプレイヤーは接続を失います。継続する?
|
||||
multiplayer.session.copy_room_code=招待コードをコピーする
|
||||
@@ -673,7 +698,7 @@ multiplayer.session.create.error.static_token=マルチプレイヤーセッシ
|
||||
multiplayer.session.create.hint=マルチプレイヤーセッションを作成する前に、実行中のゲームで[LANサーバーを開く]をクリックし、ゲームに表示されているポートを下の空白に入力する必要があります。
|
||||
multiplayer.session.create.join=接続リクエスト
|
||||
multiplayer.session.create.join.allow=すべての参加リクエストを受け入れます(無効にした場合、攻撃を防ぐために、参加リクエストを手動で受け入れる必要があります)。
|
||||
multiplayer.session.create.join.prompt=Player%sがマルチプレイヤーセッションに参加したいと考えています。承認?
|
||||
multiplayer.session.create.join.prompt=Player %s がマルチプレイヤーセッションに参加したいと考えています。承認?
|
||||
multiplayer.session.create.members=メンバー
|
||||
multiplayer.session.create.members.kick=キック
|
||||
multiplayer.session.create.members.kick.prompt=このプレーヤーをキックした後、このプレーヤーとの確立された接続は切断されます。
|
||||
@@ -682,34 +707,34 @@ multiplayer.session.create.port=ポート
|
||||
multiplayer.session.create.port.error=ゲームポートを検出できません。マルチプレイヤー機能を有効にするには、ゲームで[LANサーバーを開く]をクリックする必要があります。
|
||||
multiplayer.session.create.token=トークン
|
||||
multiplayer.session.create.token.apply=静的トークンを申請する
|
||||
multiplayer.session.create.token.prompt=デフォルトはランダム化されています。 noin.cn(中国のウェブサイト)で自分のトークンを申請することができます。
|
||||
multiplayer.session.create.token.prompt=デフォルトはランダム化されています。 mcer.cn で自分のトークンを申請することができます。
|
||||
multiplayer.session.error.already_started=Catoサービスはすでにローカルで開始されています。他のマルチプレイヤーサービスが提供されているかどうかを確認してください。または、タスクマネージャーでcatoプロセスを強制終了できます。
|
||||
multiplayer.session.error.file_not_found=cato実行可能ファイルが見つかりません。このページに入るときに、このファイルをダウンロードする必要があります。ウイルス対策ソフトウェアがファイルを分離したかどうかを確認してください。 trueの場合、ロールバックしてください。
|
||||
multiplayer.session.expired=マルチプレイヤーセッションの有効期限が切れています。続行するには、部屋を再作成または再参加する必要があります。
|
||||
multiplayer.session.hint=マルチプレイヤー機能を有効にするには、ゲームで[LANサーバーを開く]をクリックする必要があります。
|
||||
multiplayer.session.join=セッションに参加する
|
||||
multiplayer.session.join.error=マルチプレイヤーセッションへの参加に失敗しました
|
||||
multiplayer.session.join.error.connection=マルチプレイヤーセッションへの参加に失敗しました。接続を確立できません。
|
||||
multiplayer.session.join.hint=マルチプレイヤーセッションをすでに作成しているゲーマーから招待コードを取得する必要があります。
|
||||
multiplayer.session.join.invitation_code=招待コード
|
||||
multiplayer.session.join.invitation_code.error=不正な招待コード。マルチプレイヤーセッションを作成したプレイヤーから招待コードを入手してください。
|
||||
multiplayer.session.join.invitation_code.version=マルチプレイヤー機能のバージョンはあなたの間で同じではありません。
|
||||
multiplayer.session.join.kicked=セッションホルダーによってキックされました。マルチプレイヤーセッションとの接続が失われます。
|
||||
multiplayer.session.join.kicked.join_acceptance_timeout=相手方が即時加入の要求に応じない場合
|
||||
multiplayer.session.join.kicked.kicked=オーナーに部屋から追い出される。
|
||||
multiplayer.session.join.kicked.version_not_matched=マルチプレイヤーオンライン機能のバージョン番号が異なっているため、接続しているマルチプレイヤーオンライン機能のバージョン番号が同じであることを確認してください。
|
||||
multiplayer.session.join.lost_connection=マルチプレイヤーセッションとの接続が失われました。セッションが作成者によって破棄されたか、このセッションとの接続を確立できない可能性があります。
|
||||
multiplayer.session.join.port.error=リスニングに使用できるローカルネットワークポートが見つかりません。 HMCLがポートでリッスンする権限を持っていることを確認してください。
|
||||
multiplayer.session.join.rejected=接続はセッション所有者によって拒否されます。
|
||||
multiplayer.session.join.wait=受け入れを待っています。
|
||||
multiplayer.session.join.wait_timeout=セッション所有者は時間内にあなたの参加リクエストを受け入れません。
|
||||
multiplayer.session.members=ルームメンバー
|
||||
multiplayer.session.quit=Quit Room
|
||||
multiplayer.session.quit=この部屋から出る
|
||||
multiplayer.session.quit.warning=部屋を終了した後、サーバーとの接続が失われます。継続する?
|
||||
multiplayer.session.username=ユーザー名
|
||||
multiplayer.state.connecting=接続中
|
||||
multiplayer.state.disconnected=マルチプレイヤーセッションが作成/開始されていません
|
||||
multiplayer.state.disconnected.hint=誰かがマルチプレイヤーセッションを作成し、他の人がセッションに参加して一緒にゲームをプレイする必要があります。
|
||||
multiplayer.state.master=作成された部屋:%1$s、ポート:%2 $ d
|
||||
multiplayer.state.master=作成された部屋:%1$s、ポート:%2$d
|
||||
multiplayer.state.master.hint=マルチプレイヤールームを作成したら、[招待コードをコピー]ボタンをクリックして、招待したいプレイヤーに招待コードを送信します。これらのプレーヤーは、会議室に参加するときに招待コードを貼り付けることができます。
|
||||
multiplayer.state.slave=参加ルーム:%1$s、アドレス:%2 $ s
|
||||
multiplayer.state.slave=参加ルーム:%1$s、アドレス:%2$s
|
||||
multiplayer.state.slave.copy=コピーアドレス
|
||||
multiplayer.state.slave.hint=マルチプレイヤールームに参加した後、Minecraftのマルチプレイヤーページにアクセスして「HMCLマルチプレイヤーセッション」サーバーに接続するか、以下に示すアドレスのサーバーを手動で追加する必要があります。
|
||||
|
||||
@@ -717,18 +742,18 @@ datapack=Datapacks
|
||||
datapack.add=データパックをインストールします
|
||||
datapack.choose_datapack=インポートするデータパックのzipを選択してください
|
||||
datapack.extension=Datapack
|
||||
datapack.title=World%s-データパック
|
||||
datapack.title=World %s -データパック
|
||||
|
||||
world=マップ
|
||||
world.add=マップを追加(.zip)
|
||||
world.datapack=データパックの管理
|
||||
world.datapack.1_13=Minecraft1.13以降のバージョンのみがデータパックをサポートします。
|
||||
world.description=%s。最終ゲーム時刻:%s。ゲームバージョン:%s。
|
||||
world.description=%s。 最終ゲーム時刻:%s。 ゲームバージョン:%s。
|
||||
world.download=ダウンロード
|
||||
world.export=このマップをエクスポートする
|
||||
world.export.title=保存する場所を選択してください
|
||||
world.export.location=エクスポート先
|
||||
world.export.wizard=エクスポートワールド%s
|
||||
world.export.wizard=エクスポートワールド: %s
|
||||
world.extension=World zip
|
||||
world.game_version=ゲームバージョン
|
||||
world.import.already_exists=このマップはすでに存在しています。
|
||||
@@ -750,7 +775,7 @@ profile.instance_directory=ゲームディレクトリ
|
||||
profile.instance_directory.choose=ゲームディレクトリを選択してください
|
||||
profile.manage=ゲームディレクトリリスト
|
||||
profile.name=名前
|
||||
profile.new=新しい構成
|
||||
profile.new=新規作成
|
||||
profile.title=ゲームディレクトリ
|
||||
profile.selected=選択済み
|
||||
profile.use_relative_path=可能であれば、ゲームディレクトリの相対パスを使用します
|
||||
@@ -794,12 +819,14 @@ settings.advanced.java_permanent_generation_space=PermGen Space / MB
|
||||
settings.advanced.java_permanent_generation_space.prompt=Java 8以降のメタスペース、MB
|
||||
settings.advanced.jvm=Java仮想マシンの設定
|
||||
settings.advanced.jvm_args=JavaVM引数
|
||||
settings.advanced.jvm_args.prompt=ここに入力して、デフォルト設定を上書きします
|
||||
settings.advanced.jvm_args.prompt=- 入力パラメータがデフォルトパラメータと同じである場合、追加されません\n\
|
||||
- GCパラメータを入力すると、デフォルトパラメータのG1パラメータが無効になります\n\
|
||||
- 追加時にデフォルトの引数を追加しない場合は、下の[デフォルトのJVM引数を追加しない]をクリックしてください
|
||||
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.launcher_visible=ランチャーの設定
|
||||
settings.advanced.minecraft_arguments=Minecraftの引数
|
||||
settings.advanced.minecraft_arguments.prompt=デフォルト
|
||||
settings.advanced.natives_directory=ネイティブライブラリパス(例: LWJGL)
|
||||
@@ -822,10 +849,10 @@ settings.advanced.server_ip=サーバーアドレス
|
||||
settings.advanced.server_ip.prompt=ゲームの起動時にサーバーに参加する
|
||||
settings.advanced.use_native_glfw=[Linux]システムGLFWを使用する
|
||||
settings.advanced.use_native_openal=[Linux]システムOpenALを使用する
|
||||
settings.advanced.workaround=Workarounds
|
||||
settings.advanced.workaround.warning=Workaround settings are only for professional users. Modifying workaround settings may result in game crash. Unless you know what you are doing, please do not modify these options.
|
||||
settings.advanced.wrapper_launcher=Wrapper command
|
||||
settings.advanced.wrapper_launcher.prompt=Allows launching using an extra wrapper program like 'optirun' on Linux.
|
||||
settings.advanced.workaround=デバッグ用オプション
|
||||
settings.advanced.workaround.warning=デバッグオプションはプロフェッショナルのみ使用可能です。 デバッグオプションにより、ゲームが起動しない場合があります。 これらのオプションは、ご自分が何をしているのかが分からない限り、変更しないでください。
|
||||
settings.advanced.wrapper_launcher=パッキングオーダー
|
||||
settings.advanced.wrapper_launcher.prompt=optirunが記入されている場合、起動コマンドが "java ... "から変更されます。を「optirun java ...」に変更しました。
|
||||
|
||||
settings.custom=カスタム
|
||||
|
||||
@@ -836,8 +863,10 @@ settings.game.exploration=フォルダ
|
||||
settings.game.fullscreen=フルスクリーン
|
||||
settings.game.java_directory=Javaディレクトリ
|
||||
settings.game.java_directory.auto=自動的に選択されます
|
||||
settings.game.java_directory.bit=%s-ビット
|
||||
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.template=%s(%s)
|
||||
settings.game.management=管理
|
||||
settings.game.working_directory=作業ディレクトリ
|
||||
@@ -861,7 +890,7 @@ settings.launcher.general=全般的
|
||||
settings.launcher.language=言語
|
||||
settings.launcher.launcher_log.export=ランチャーログのエクスポート
|
||||
settings.launcher.launcher_log.export.failed=ログのエクスポートに失敗しました
|
||||
settings.launcher.launcher_log.export.success=ログが%sにエクスポートされました
|
||||
settings.launcher.launcher_log.export.success=ログが %s にエクスポートされました
|
||||
settings.launcher.log=ログ
|
||||
settings.launcher.log.font=ログフォント
|
||||
settings.launcher.proxy=プロキシ
|
||||
@@ -879,13 +908,13 @@ settings.launcher.title_transparent=タイトルの透明性
|
||||
settings.launcher.version_list_source=バージョンリストソース
|
||||
|
||||
settings.memory=ゲームメモリ
|
||||
settings.memory.allocate.auto=最小%1 $ .1f GB /実際の%2 $ .1f GB
|
||||
settings.memory.allocate.auto.exceeded=最小%1 $ .1f GB /実際の%2 $ .1f GB(%3 $ .1f GB使用可能)
|
||||
settings.memory.allocate.manual=割り当て%1 $ .1f GB
|
||||
settings.memory.allocate.manual.exceeded=割り当て%1 $ .1f GB(%3 $ .1f GB使用可能)
|
||||
settings.memory.auto_allocate=メモリサイズは自動的に決定されます
|
||||
settings.memory.allocate.auto=最小 %1$.1f GB /実際の %2$.1f GB
|
||||
settings.memory.allocate.auto.exceeded=最小 %1$.1f GB /実際の %2$.1f GB(%3$.1f GB使用可能)
|
||||
settings.memory.allocate.manual=割り当て %1$.1f GB
|
||||
settings.memory.allocate.manual.exceeded=割り当て %1$.1f GB(%3$.1f GB使用可能)
|
||||
settings.memory.auto_allocate=メモリサイズの自動割り当て
|
||||
settings.memory.lower_bound=最小メモリ
|
||||
settings.memory.used_per_total=%1 $ .1f GB使用済み/%2 $ .1fGB合計
|
||||
settings.memory.used_per_total=%1$.1f GB使用済み/%2$.1fGB合計
|
||||
settings.physical_memory=物理メモリサイズ
|
||||
settings.show_log=ログを表示
|
||||
settings.skin=オフラインアカウントのスキンの変更がサポートされるようになりました!アカウントページに移動して、今すぐ肌を変えてください!
|
||||
@@ -897,7 +926,7 @@ settings.type.global.manage=グローバルゲーム設定
|
||||
settings.type.global.edit=グローバルゲーム設定を構成します
|
||||
settings.type.special.enable=このゲームの特殊な設定を有効にします
|
||||
settings.type.special.edit=現在のゲーム設定を構成します
|
||||
settings.type.special.edit.hint=現在のゲームバージョン%sで特殊な設定が有効になっているため、現在のページのオプションはそのゲームバージョンに適用されません。リンクをクリックして、現在のゲーム設定を構成します。
|
||||
settings.type.special.edit.hint=現在のゲームバージョン %s で特殊な設定が有効になっているため、現在のページのオプションはそのゲームバージョンに適用されません。リンクをクリックして、現在のゲーム設定を構成します。
|
||||
|
||||
sponsor=寄付
|
||||
sponsor.bmclapi=ダウンロードサービスはBMCLAPIとMCBBSによって提供されます。詳細については、ここをクリックしてください。
|
||||
@@ -912,13 +941,13 @@ update.changelog=変更
|
||||
update.channel.dev=ベータ
|
||||
update.channel.dev.hint=ベータ版を使用しています。ベータ版には、リリースバージョンと比較していくつかの追加機能が含まれている可能性があり、テストにのみ使用されます。\n \
|
||||
ベータ版は不安定なはずです!\n \
|
||||
問題が発生した場合は、<a href="hmcl://settings/feedback">フィードバックページ</a>にアクセスして報告するか、チャット<a href="https://discord.gg/jVvC7HfM6U">Discord</a>または<a href="https://kaihei.co/Kx7n3t">KaiHeiLa</a>で問題を報告します。\n \n \
|
||||
問題が発生した場合は、<a href="hmcl://settings/feedback">フィードバックページ</a>にアクセスして報告するか、チャット<a href="https://discord.gg/jVvC7HfM6U">Discord</a>または<a href="https://kook.top/Kx7n3t">KOOK</a>で問題を報告します。\n \n \
|
||||
このヒントを非表示にするには、<a href="https://hmcl.huangyuhui.net/api/redirect/sponsor">寄付</a>するか、フィードバックに貢献をする必要があります。
|
||||
update.channel.dev.title=ベータ版のヒント
|
||||
update.channel.nightly=アルファ
|
||||
update.channel.nightly.hint=アルファ版を使用しています。これには、ベータ版およびリリース版と比較していくつかの追加機能が含まれている可能性があり、テストにのみ使用されます。\n \
|
||||
アルファ版は不安定です!\n \
|
||||
問題が発生した場合は、<a href="hmcl://settings/feedback">フィードバックページ</a>にアクセスして報告するか、チャット<a href="https://discord.gg/jVvC7HfM6U">Discord</a>または<a href="https://kaihei.co/Kx7n3t">KaiHeiLa</a>で問題を報告します。\n \n \
|
||||
問題が発生した場合は、<a href="hmcl://settings/feedback">フィードバックページ</a>にアクセスして報告するか、チャット<a href="https://discord.gg/jVvC7HfM6U">Discord</a>または<a href="https://kook.top/Kx7n3t">KOOK</a>で問題を報告します。\n \n \
|
||||
このヒントを非表示にするには、<a href="https://hmcl.huangyuhui.net/api/redirect/sponsor">寄付</a>するか、フィードバックに印象的な貢献をする必要があります。
|
||||
update.channel.nightly.title=アルファ版のヒント
|
||||
update.channel.stable=リリース
|
||||
@@ -958,20 +987,20 @@ version.manage.duplicate.duplicate_save=コピー保存
|
||||
version.manage.duplicate.prompt=新しいバージョン名を入力してください
|
||||
version.manage.duplicate.confirm=
|
||||
version.manage.manage=バージョンの管理
|
||||
version.manage.manage.title=バージョンの管理-%1s
|
||||
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_libraries=ライブラリファイルを削除します
|
||||
version.manage.rename=このバージョンの名前を変更します
|
||||
version.manage.rename.message=このバージョンの新しい名前を入力してください
|
||||
version.manage.rename.fail=バージョンの名前を変更できませんでした。
|
||||
version.settings=設定
|
||||
version.update=modpackを更新します
|
||||
version.update=modpackを更新する
|
||||
|
||||
wizard.prev=<前へ
|
||||
wizard.prev=< 前へ
|
||||
wizard.failed=失敗
|
||||
wizard.finish=終了
|
||||
wizard.next=次へ>
|
||||
wizard.next=次へ >
|
||||
@@ -1,11 +1,11 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Авторские права (C) 2021 huangyuhui <[email protected]> и соавторы
|
||||
# Авторские права (C) 2021 huangyuhui <[email protected]> и участников
|
||||
#
|
||||
# Эта программа является бесплатным программным обеспечением:
|
||||
# вы можете распространять и/или изменять её на условиях GNU
|
||||
# General Public License, опубликованной Free Software Foundation,
|
||||
# либо 3й версией лицензии, либо (по вашему выбору) любой более поздней версии.
|
||||
# либо 3й версией лицензии, либо (по вашему выбору) любой версией выше.
|
||||
#
|
||||
# Эта программа распространяется в надежде, что она окажется полезной,
|
||||
# но БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ; даже без подразумеваемых гарантий
|
||||
@@ -17,62 +17,64 @@
|
||||
#
|
||||
|
||||
# Соавторы: vanja-san
|
||||
about=О программе
|
||||
about=О лаунчере
|
||||
about.copyright=Авторские права
|
||||
about.copyright.statement=Авторские права (c) 2021 huangyuhui.
|
||||
about.copyright.statement=Авторские права (С) 2021 huangyuhui.
|
||||
about.author=Автор
|
||||
about.author.statement=Bilibili: @huanghongxun
|
||||
about.claim=EULA
|
||||
about.claim.statement=Кликните на ссылку, чтобы увидеть весь текст.
|
||||
about.community=Сообщество
|
||||
about.community.discord=Discord
|
||||
about.community.discord.statement=Присоединиться к чату!
|
||||
about.community.kaiheila=KaiHeiLa
|
||||
about.community.kaiheila.statement=Присоединиться к чату!
|
||||
about.community.discord.statement=Присоединиться!
|
||||
about.community.kookapp=KOOK
|
||||
about.community.kookapp.statement=Присоединиться!
|
||||
about.dependency=Зависимости
|
||||
about.legal=Юридическое подтверждение
|
||||
about.thanks_to=Отдельная благодарность
|
||||
about.thanks_to.bangbang93.statement=BMCLAPI - поставщик источников скачивания.
|
||||
about.thanks_to.contributors=Всем соавторам, которые участвовали в этом проекте через проблемы, запросы и т.д.
|
||||
about.thanks_to.contributors=Всем соавторам, которые участвовали в этом проекте через issues, запросы и т.д.
|
||||
about.thanks_to.contributors.statement=Спасибо сообществу с открытым исходным кодом за поддержку Hello Minecraft! Launcher
|
||||
about.thanks_to.gamerteam.statement=Поставщику фонового изображения по умолчанию.
|
||||
about.thanks_to.glavo.statement=Оказал большую техническую поддержку HMCL.
|
||||
about.thanks_to.mcbbs=MCBBS
|
||||
about.thanks_to.mcbbs.statement=MCBBS - поставщик источников скачивания.
|
||||
about.thanks_to.mcbbs.statement=MCBBS - поставщик источника скачивания
|
||||
about.thanks_to.mcmod=mcmod.cn
|
||||
about.thanks_to.mcmod.statement=Модифицированный перевод китайских имён.
|
||||
about.thanks_to.noin=noin.cn
|
||||
about.thanks_to.noin.statement=Поставщик многопользовательских услуг.
|
||||
about.thanks_to.red_lnn.statement=Поставщику фонового изображения по умолчанию.
|
||||
about.thanks_to.users=Членам группы пользователей HMCL
|
||||
about.thanks_to.users.statement=Благодаря пожертвованиям, сообщениям об ошибках и так далее.
|
||||
about.thanks_to.mcmod.statement=Модифицированный перевод китайских имён
|
||||
about.thanks_to.noin=mcer.cn
|
||||
about.thanks_to.noin.statement=Поставщик многопользовательских услуг
|
||||
about.thanks_to.red_lnn.statement=Поставщику фонового изображения по умолчанию
|
||||
about.thanks_to.users=Группе участников
|
||||
about.thanks_to.users.statement=Благодаря пожертвованиям, сообщениям об ошибках и так далее
|
||||
about.thanks_to.yushijinhun.statement=Поддержке authlib-injector
|
||||
about.open_source=Открытый исходный код
|
||||
about.open_source.statement=GPL v3 (https://github.com/huanghongxun/HMCL/)
|
||||
|
||||
account=Учётные записи
|
||||
account=Аккаунты
|
||||
account.cape=Плащ
|
||||
account.character=Персонаж
|
||||
account.choose=Выберите персонажа
|
||||
account.create=Создайте новую учетную запись
|
||||
account.create.microsoft=Создать учётную запись Microsoft
|
||||
account.create.yggdrasil=Создать учётную запись Mojang
|
||||
account.create.offline=Создать учётную запись без пароля
|
||||
account.create.authlibInjector=Создать учётную запись authlib-injector
|
||||
account.create=Добавьте новый аккаунт
|
||||
account.create.microsoft=Добавление аккаунта Microsoft
|
||||
account.create.yggdrasil=Добавление аккаунта Mojang
|
||||
account.create.offline=Добавление офлайн аккаунта
|
||||
account.create.authlibInjector=Добавление аккаунта authlib-injector
|
||||
account.email=Электронная почта
|
||||
account.failed=Код ошибки: %d
|
||||
account.failed.character_deleted=Персонаж был удалён.
|
||||
account.failed.connect_authentication_server=Невозможно подключиться к серверу аутентификации. Проверьте сетевое подключение.
|
||||
account.failed.connect_injector_server=Невозможно подключиться к серверу аутентификации. Проверьте свою сеть и убедитесь, что URL-адрес указан правильно.
|
||||
account.failed.injector_download_failure=Не удалось скачать authlib-injector. Проверьте свою сеть и попробуйте переключиться на другой источник скачивания.
|
||||
account.failed.connect_authentication_server=Невозможно подключиться к серверу авторизации. Проверьте сетевое подключение.
|
||||
account.failed.connect_injector_server=Невозможно подключиться к серверу авторизации. Проверьте сетевое подключение и убедитесь, что ссылка указана правильно.
|
||||
account.failed.injector_download_failure=Не удалось скачать authlib-injector. Проверьте сетевое подключение или попробуйте переключиться на другой источник скачивания.
|
||||
account.failed.invalid_credentials=Неверный пароль, или вам временно запрещено входить в систему.
|
||||
account.failed.invalid_password=Неверный пароль
|
||||
account.failed.invalid_token=Выйдите из системы и повторно введите пароль для входа.
|
||||
account.failed.migration=Ваша учётная запись должна быть перенесена в учётную запись Microsoft. Если перенос уже выполнен, следует входить через учётную запись Microsoft.
|
||||
account.failed.no_character=На этой учётной записи нет персонажей.
|
||||
account.failed.server_response_malformed=Неверный ответ сервера. На сервере аутентификации могла произойти ошибка.
|
||||
account.failed.wrong_account=Вход в систему с несоответствующей учётной записи.
|
||||
account.injector.add=Добавьте сервер аутентификации
|
||||
account.injector.empty=Пусто (Кликните кнопку с плюсом справа, чтобы добавить)
|
||||
account.failed.migration=Ваш аккаунт должен быть перенесён в Microsoft. Если перенос уже выполнен, следует войти через аккаунт Microsoft.
|
||||
account.failed.no_character=На этом аккаунте нет персонажей.
|
||||
account.failed.server_response_malformed=Неверный ответ сервера. На сервере авторизации могла произойти ошибка.
|
||||
account.failed.wrong_account=Вход в систему с несоответствующего аккаунта.
|
||||
account.hmcl.hint=Вам необходимо щёлкнуть на «Войти» и завершить вход на открытом сайте
|
||||
account.injector.add=Добавить сервер
|
||||
account.injector.empty=Пусто. Кликните кнопку с плюсом справа, чтобы добавить
|
||||
account.injector.http=Предупреждение: Этот сервер использует HTTP, поэтому ваш пароль будет передан чистым текстом.
|
||||
account.injector.link.register=Зарегестрироваться
|
||||
account.injector.server=Сервер авторизации
|
||||
@@ -83,35 +85,38 @@ account.login.hint=Мы не храним ваш пароль.
|
||||
account.login.refresh=Повторный вход
|
||||
account.logout=Выйти
|
||||
account.register=Регистрация
|
||||
account.manage=Список учётных записей
|
||||
account.methods=Тип входа в систему
|
||||
account.manage=Список аккаунтов
|
||||
account.methods=Метод авторизации
|
||||
account.methods.authlib_injector=authlib-injector
|
||||
account.methods.microsoft=Учётная запись Microsoft
|
||||
account.methods.little_skin=Little Skin
|
||||
account.methods.microsoft=Аккаунт Microsoft
|
||||
account.methods.microsoft.birth=Как изменить настройки дня рождения...
|
||||
account.methods.microsoft.close_page=Авторизация учётной записи Microsoft завершена. Осталось выполнить некоторые шаги по входу в систему, которые необходимо завершить позже. Вы можете закрыть эту страницу.
|
||||
account.methods.microsoft.error.add_family=Поскольку тебе ещё нет 18 лет, взрослый должен добавить тебя в семью, чтобы ты мог играть в Minecraft.
|
||||
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. Хотя учётная запись Microsoft действительна, на ней не приобретено игры.
|
||||
account.methods.microsoft.deauthorize=Отменить авторизацию аккаунта
|
||||
account.methods.microsoft.close_page=Авторизация аккаунта Microsoft завершена. Осталось выполнить некоторые шаги, которые необходимо завершить позже. Вы можете закрыть эту страницу.
|
||||
account.methods.microsoft.error.add_family=Поскольку тебе ещё нет 18 лет, взрослый должен добавить тебя в семейный доступ, чтобы ты мог играть в Minecraft.
|
||||
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. Хотя аккаунт Microsoft действителен, на ней не приобретено игры.
|
||||
account.methods.microsoft.error.unknown=Не удалось войти. Microsoft отвечает с кодом ошибки %d.
|
||||
account.methods.microsoft.logging_in=Вход...
|
||||
account.methods.microsoft.hint=Вам необходимо нажать кнопку «Войти» и продолжить процесс входа во вновь открывшемся окне браузера.
|
||||
account.methods.microsoft.manual=После нажатия кнопки «Войти», вы должны завершить авторизацию во вновь открывшемся окне браузера. Если окно браузера не открылось, вы можете щёлкнуть здесь, чтобы скопировать ссылку и вручную открыть её в браузере.
|
||||
account.methods.microsoft.profile=Профиль учётной записи...
|
||||
account.methods.microsoft.snapshot=Версия HMCL Snapshot не поддерживает вход через систему Microsoft.
|
||||
account.methods.microsoft.logging_in=Авторизация...
|
||||
account.methods.microsoft.hint=Вам необходимо нажать кнопку «Войти» и продолжить процесс авторизации во вновь открывшемся окне браузера.\nЕсли токен, используемый для входа в аккаунт Microsoft случайно утёк, вы можете нажать «Отменить авторизацию аккаунта» ниже, чтобы отменить авторизацию.
|
||||
account.methods.microsoft.manual=После нажатия кнопки «Войти», вы должны завершить авторизацию во вновь открывшемся окне браузера. Если окно браузера не открылось, вы можете щёлкнуть здесь, чтобы скопировать ссылку и вручную открыть её в браузере.\nЕсли токен, используемый для входа в аккаунт Microsoft случайно утёк, вы можете нажать «Отменить авторизацию аккаунта» ниже, чтобы отменить авторизацию.
|
||||
account.methods.microsoft.profile=Профиль аккаунта...
|
||||
account.methods.microsoft.snapshot=Лаунчер версии снапшот не поддерживает авторизацию через систему Microsoft.
|
||||
account.methods.microsoft.waiting_browser=Ожидание авторизации в открытом окне браузера...
|
||||
account.methods.offline=Без пароля
|
||||
account.methods.offline=Офлайн
|
||||
account.methods.offline.uuid=UUID
|
||||
account.methods.offline.uuid.hint=UUID - это уникальный идентификатор игрового персонажа в Minecraft. Способ генерации UUID различается в разных программах запуска игр. Изменение UUID на тот, который генерируется другой программой запуска, гарантирует, что игровые блоки/предметы в рюкзаке вашей учётной записи без пароля останутся. Этот параметр предназначен для экспертов. Если вы не знаете, что делаете, мы не советуем вам изменять этот параметр.
|
||||
account.methods.offline.uuid.hint=UUID - это уникальный идентификатор игрового персонажа в Minecraft. Способ генерации UUID различается в разных лаунчерах игр. Изменение UUID на тот, который генерируется другим лаунчером гарантирует, что игровые блоки/предметы в рюкзаке вашего офлайн аккаунта останутся. Этот параметр предназначен для экспертов. Если вы не знаете что делаете, мы не советуем вам изменять этот параметр.
|
||||
account.methods.offline.uuid.malformed=Malformed
|
||||
account.methods.yggdrasil=Mojang
|
||||
account.methods.yggdrasil.migration=Переход на учётную запись Microsoft
|
||||
account.methods.yggdrasil.migration.how=Как перейти на учётную запись Microsoft?
|
||||
account.methods.yggdrasil.purchase=Приобрести Minecraft
|
||||
account.missing=Нет учётной записи
|
||||
account.methods.yggdrasil.migration=Перейти на Microsoft
|
||||
account.methods.yggdrasil.migration.how=Как перейти на Microsoft?
|
||||
account.methods.yggdrasil.migration.hint=Начиная с 10 марта 2022 года, аккаунты Mojang больше не поддерживаются, и для входа в Minecraft необходимо перейти на аккаунт Microsoft.\nСсылка ниже поможет вам перенести свой аккаунт Mojang в Microsoft.
|
||||
account.methods.yggdrasil.purchase=Приобрести игру
|
||||
account.missing=Нет аккаунта
|
||||
account.missing.add=Чтобы добавить, кликните здесь
|
||||
account.not_logged_in=Не вошёл в систему
|
||||
account.not_logged_in=Авторизация не выполнена
|
||||
account.password=Пароль
|
||||
account.skin=Скин
|
||||
account.skin.file=Файл изображения скина
|
||||
@@ -122,17 +127,17 @@ 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.little_skin.hint=Вам необходимо создать персонажа на сайте скина. Скин этого офлайн аккаунта будет привязан к персонажу на сайте скинов с тем же именем.
|
||||
account.skin.type.local_file=Локальное изображение скина
|
||||
account.skin.upload=Загрузить скин
|
||||
account.skin.upload.failed=Не удалось загрузить скин
|
||||
account.skin.invalid_skin=Нераспознанный файл скина
|
||||
account.username=Название
|
||||
account.skin.invalid_skin=Недействительный файл скина
|
||||
account.username=Имя пользователя
|
||||
|
||||
archive.author=Авторы
|
||||
archive.date=Дата публикации
|
||||
archive.game_version=Игра
|
||||
archive.name=Псевдоним
|
||||
archive.name=Название
|
||||
archive.version=Версия
|
||||
|
||||
assets.download=Скачивание активов
|
||||
@@ -150,17 +155,17 @@ button.no=Нет
|
||||
button.ok=ОК
|
||||
button.refresh=Обновить
|
||||
button.remove=Убрать
|
||||
button.remove.confirm=Вы уверены, что хотите удалить? Откат этой операции невозможен!
|
||||
button.remove.confirm=Удалить? Откат этой операции невозможен!
|
||||
button.save=Сохранить
|
||||
button.save_as=Сохранить как
|
||||
button.select_all=Выбрать все
|
||||
button.yes=Да
|
||||
|
||||
color.recent=Рекомендуемый
|
||||
color.recent=Недавний
|
||||
color.custom=Пользовательский цвет
|
||||
|
||||
crash.NoClassDefFound=Проверьте, что в окне "Hello Minecraft! Launcher" программное обеспечение не повреждено.
|
||||
crash.user_fault=Ваша ОС или среда Java может быть установлена неправильно, что может привести к сбою, проверьте Java Runtime Environment или свой компьютер!
|
||||
crash.user_fault=Ваша ОС или Java может быть установлена неправильно, что может привести к сбою, проверьте Java Runtime Environment или свой компьютер!
|
||||
|
||||
curse.category.0=Все
|
||||
|
||||
@@ -182,6 +187,11 @@ curse.category.4472=Tech
|
||||
curse.category.4473=Magic
|
||||
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/6
|
||||
curse.category.5129=Vanilla+
|
||||
curse.category.5189=Utility & QOL
|
||||
curse.category.5190=QoL
|
||||
curse.category.5191=Utility & QoL
|
||||
curse.category.5192=FancyMenu
|
||||
curse.category.423=Map and Information
|
||||
curse.category.426=Addons
|
||||
curse.category.434=Armor, Tools, and Weapons
|
||||
@@ -224,42 +234,43 @@ curse.category.4843=Automation
|
||||
curse.category.4906=MCreator
|
||||
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/6
|
||||
curse.category.399=Steampunk
|
||||
curse.category.5193=Датапаки
|
||||
curse.category.399=Стимпанк
|
||||
curse.category.396=128x
|
||||
curse.category.398=512x and Higher
|
||||
curse.category.398=512x и выше
|
||||
curse.category.397=256x
|
||||
curse.category.405=Miscellaneous
|
||||
curse.category.405=Разное
|
||||
curse.category.395=64x
|
||||
curse.category.400=Photo Realistic
|
||||
curse.category.400=Фотореалистичные
|
||||
curse.category.393=16x
|
||||
curse.category.403=Traditional
|
||||
curse.category.403=Традиционные
|
||||
curse.category.394=32x
|
||||
curse.category.404=Animated
|
||||
curse.category.4465=Mod Support
|
||||
curse.category.402=Medieval
|
||||
curse.category.401=Modern
|
||||
curse.category.404=Анимированные
|
||||
curse.category.4465=Поддержка модов
|
||||
curse.category.402=Средневековье
|
||||
curse.category.401=Современные
|
||||
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/17
|
||||
curse.category.4464=Modded World
|
||||
curse.category.250=Game Map
|
||||
curse.category.249=Creation
|
||||
curse.category.251=Parkour
|
||||
curse.category.253=Survival
|
||||
curse.category.248=Adventure
|
||||
curse.category.252=Puzzle
|
||||
curse.category.4464=Модифицированный мир
|
||||
curse.category.250=Игровая карта
|
||||
curse.category.249=Создание
|
||||
curse.category.251=Паркур
|
||||
curse.category.253=Выживание
|
||||
curse.category.248=Приключение
|
||||
curse.category.252=Головоломка
|
||||
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/4546
|
||||
curse.category.4551=Hardcore Questing Mode
|
||||
curse.category.4548=Lucky Blocks
|
||||
curse.category.4556=Progression
|
||||
curse.category.4752=Building Gadgets
|
||||
curse.category.4551=Режим хардкорных квестов
|
||||
curse.category.4548=Блоки удачи
|
||||
curse.category.4556=Прогрессия
|
||||
curse.category.4752=Строительные гаджеты
|
||||
curse.category.4553=CraftTweaker
|
||||
curse.category.4554=Recipes
|
||||
curse.category.4549=Guidebook
|
||||
curse.category.4547=Configuration
|
||||
curse.category.4550=Quests
|
||||
curse.category.4555=World Gen
|
||||
curse.category.4552=Scripts
|
||||
curse.category.4554=Рецепты
|
||||
curse.category.4549=Путеводители
|
||||
curse.category.4547=Конфигурация
|
||||
curse.category.4550=Квесты
|
||||
curse.category.4555=Генерация мира
|
||||
curse.category.4552=Скрипты
|
||||
|
||||
curse.sort.author=Автор
|
||||
curse.sort.date_created=Дата создания
|
||||
@@ -270,59 +281,61 @@ curse.sort.total_downloads=Всего скачиваний
|
||||
|
||||
download=Скачать
|
||||
download.code.404=Файл не найден на удалённом сервере: %s
|
||||
download.content=Содержание игры
|
||||
download.content=Игровой контент
|
||||
download.curseforge.customization=Свет и тень, а также настройка игры
|
||||
download.existing=Файл не может быть сохранён, поскольку он уже существует. Вы можете выбрать «Сохранить как», чтобы сохранить файл в другом месте.
|
||||
download.existing=Файл не может быть сохранён, поскольку он уже существует. Вы можете выбрать «Сохранить как», чтобы сохранить файл в другое место.
|
||||
download.external_link=Открыть сайт скачивания
|
||||
download.failed=Не удалось скачать %1$s, код ответа: %2$d
|
||||
download.failed.empty=Кандидатов нет. Кликните здесь, чтобы вернуться.
|
||||
download.failed.refresh=Невозможно скачать список версий. Кликните здесь, чтобы повторить попытку.
|
||||
download.failed.refresh=Невозможно обновить список версий. Кликните здесь, чтобы повторить попытку.
|
||||
download.game=Скачивание игр
|
||||
download.provider.mcbbs=MCBBS (https://www.mcbbs.net/)
|
||||
download.provider.bmclapi=BMCLAPI (bangbang93, https://bmclapi2.bangbang93.com/)
|
||||
download.provider.mojang=Mojang (OptiFine download service is provided by BMCLAPI)
|
||||
download.provider.official=Попробовать загрузить из официального источника
|
||||
download.provider.balanced=Загрузить из самого быстрого источника
|
||||
download.provider.mojang=Mojang (Скачивание OptiFine предоставляется BMCLAPI)
|
||||
download.provider.official=Скачивать с официального источника
|
||||
download.provider.balanced=Скачивать с самого быстрого источника
|
||||
download.provider.mirror=Попробовать загрузить из зеркального источника
|
||||
download.javafx=Скачивание необходимых компонентов среды выполнения
|
||||
download.javafx.notes=Скачивание необходимых компонентов для HMCL через сеть.\nНажмите кнопку «Изменить источник скачивания», чтобы просмотреть более подробную информацию и выбрать источник скачивания, или нажмите кнопку «Отмена», чтобы прекратить и выйти.\nПримечание: Если скорость скачивания слишком низкая, попробуйте изменить источник скачивания.
|
||||
download.javafx.component=Скачивание модуля %s
|
||||
download.javafx.notes=Скачивание необходимых компонентов для лаунчера.\nНажмите кнопку «Изменить источник скачивания», чтобы просмотреть более подробную информацию и выбрать источник скачивания, или нажмите кнопку «Отмена», чтобы прекратить и выйти.\nПримечание: Если скорость скачивания слишком низкая, попробуйте изменить источник скачивания.
|
||||
download.javafx.component=Скачивание компонента %s
|
||||
download.javafx.prepare=Готово к скачиванию
|
||||
|
||||
exception.access_denied=Операционная система запретила доступ к файлу %s. Возможно, у нас нет разрешения на доступ к этому файлу, или этот файл уже был открыт другой программой.\n\
|
||||
Проверьте, есть ли у текущего пользователя операционной системы разрешение на доступ к этому файлу.\n\
|
||||
exception.access_denied=Операционная система запретила доступ к файлу %s. Возможно у нас нет разрешения на доступ к этому файлу, или этот файл уже открыт другой программой.\n\
|
||||
Проверьте есть ли у текущего пользователя операционной системы разрешение на доступ к этому файлу.\n\
|
||||
Для пользователей Windows можно также попробовать воспользоваться «Мониторингом ресурсов», выяснить, не использует ли файл какая-либо программа, попытаться закрыть связанную с ней программу или перезагрузить компьютер.
|
||||
exception.artifact_malformed=Файл не может пройти проверку.
|
||||
exception.ssl_handshake=Невозможно установить SSL-соединение из-за отсутствия SSL-сертификата в текущей установке Java. Попробуйте запустить HMCL с другой Java.
|
||||
|
||||
extension.bat=Файл Windows Bat
|
||||
extension.mod=Мод-файл
|
||||
extension.mod=Файл мода
|
||||
extension.png=Файл изображения
|
||||
extension.ps1=Сценарий PowerShell
|
||||
extension.sh=Сценарий Bash
|
||||
|
||||
fatal.javafx.incompatible=Приложение не может установить JavaFX на текущую среду Java ниже 11.\nЗапустите это приложение, используя JDK 11 или выше или JDK с JavaFX в комплекте.
|
||||
fatal.javafx.missing=JavaFX отсутствует.\nЕсли вы используете Java 11 или более позднюю версию, перейдите на Oracle JRE 8 или установите BellSoft Liberica Full JRE.\nЕсли вы используете другие сборки OpenJDK, убедитесь, что OpenJFX включен.
|
||||
fatal.missing_dst_root_ca_x3=Сертификат DST Root CA X3 отсутствует на текущей платформе Java.\nВы все еще можете использовать Hello Minecraft! Launcher, но он не сможет подключиться к некоторым сайтам (например, к сайтам, использующим сертификаты, выданные Let's Encrypt), что может привести к неправильной работе лаунчера.\nОбновите Java Runtime до версии 8u101 или более поздней, чтобы решить проблему.
|
||||
fatal.config_loading_failure=Конфигурация недоступна.\nПредоставьте Hello Minecraft! Launcher разрешение на чтение и запись к "%s" и файлам в нём.
|
||||
fatal.migration_requires_manual_reboot=Обновление завершено. Перезапустите Hello Minecraft! Launcher.
|
||||
fatal.apply_update_failure=Мы сожалеем, Hello Minecraft! Launcher не смог завершить обновление, потому что что-то пошло не так.\nНо вы все еще можете вручную завершить обновление, загрузив Hello Minecraft! Launcher с %s.\nПодумайте о том, чтобы сообщить нам об этой проблеме.
|
||||
fatal.samba=Если вы пытаетесь запустить HMCL в общей папке Samba, HMCL может не работать, попробуйте обновить Java или запустить HMCL в локальной папке.
|
||||
fatal.missing_dst_root_ca_x3=Сертификат DST Root CA X3 отсутствует на текущей платформе Java.\nВы всё ещё можете использовать лаунчер, но он не сможет подключиться к некоторым сайтам (например к сайтам, использующим сертификаты, выданные Let's Encrypt), что может привести к неправильной работе лаунчера.\nОбновите Java Runtime до версии 8u101 или более поздней, чтобы решить эту проблему.
|
||||
fatal.config_loading_failure=Конфигурация недоступна.\nПредоставьте лаунчеру разрешение на чтение и запись "%s" и файлам в нём.
|
||||
fatal.migration_requires_manual_reboot=Обновление завершено. Перезапустите лаунчер.
|
||||
fatal.apply_update_failure=Мы сожалеем, лаунчер не смог завершить обновление, потому что что-то пошло не так.\nНо вы все ещё можете вручную завершить обновление, загрузив Hлаунчер с %s.\nВы можете сообщить нам об этой проблеме.
|
||||
fatal.samba=Если вы пытаетесь запустить лаунчер в общей папке Samba, он может не работать, попробуйте обновить Java или запустить лаунчер из локальной папки.
|
||||
fatal.illegal_char=Недопустимый символ '=' в пути к папке пользователя. Лаунчер может работать, но некоторые функции не будут работать.\nВы не сможете использовать аккаунт authlib-injector или изменить офлайн скин. Также в сетевой игре могут возникнуть большие риски для безопасности.
|
||||
|
||||
feedback=Обратная связь
|
||||
feedback.add=Добавить отзыв
|
||||
feedback.add=Добавить отзыв (на английском или китайском)
|
||||
feedback.add.hint.search_before_add=Прежде чем добавлять новый отзыв, следует провести поиск по ключевому слову, чтобы выяснить, сообщалось ли уже о том, о чем вы хотите сообщить, или нет. Если есть, вы можете проголосовать за него, чтобы повысить его приоритет.
|
||||
feedback.add.hint.title=Заголовок отзыва должен кратко выражать ваши потребности. Такие заголовки, как "У меня проблема", "У меня есть идея", "Игра не открывается" и т.д. которые не позволяют другим увидеть общую проблему с первого взгляда, неприемлемы.
|
||||
feedback.add.hint.content=Содержание отзыва должно полностью и кратко выражать ваши потребности. Если вы столкнулись с проблемой, необходимо подробно описать путь повторения, например, какая кнопка нажимается после открытия программы запуска, и какая проблема возникает после выполнения какой операции. Если вы хотите добавить новые функции, вам нужно всё продумать: почему игрокам нужна эта функция, какие проблемы она может решить и как её можно реализовать.
|
||||
feedback.add.login=Вам необходимо войти/зарегистрировать учётную запись HMCL, чтобы получить разрешение на обратную связь.
|
||||
feedback.add.permission=Вам необходимо получить разрешение на добавление отзывов, чтобы добавлять новые отзывы.
|
||||
feedback.add.login=Чтобы получить разрешение на обратную связь, необходимо войти/зарегистрировать аккаунт HMCL.
|
||||
feedback.add.permission=Чтобы добавлять отзывы, необходимо получить разрешение.
|
||||
feedback.author=Автор
|
||||
feedback.content=Контент
|
||||
feedback.empty=Нет элементов, удовлетворяющих условиям.
|
||||
feedback.failed=Не удалось загрузить
|
||||
feedback.failed.permission=Поиск по ключевым словам возможен только при входе в учётную запись с разрешением отправки отзывов.
|
||||
feedback.failed.permission=Поиск по ключевым словам возможен только при входе в аккаунт с разрешением отправки отзывов.
|
||||
feedback.failed.too_frequently=Слишком часто. Повторите попытку позже.
|
||||
feedback.like=Нравится
|
||||
feedback.login=Войти в учётную запись HMCL
|
||||
feedback.login=Войти в аккаунт HMCL
|
||||
feedback.response=Ответ
|
||||
feedback.response.empty=Не ответили
|
||||
feedback.state.accepted=Принято
|
||||
@@ -330,16 +343,16 @@ feedback.state.open=В ожидании
|
||||
feedback.state.rejected=Отклонено
|
||||
feedback.title=Заголовок
|
||||
feedback.type=Тип
|
||||
feedback.type.bug=Сообщение об ошибке
|
||||
feedback.type.bug=Сообщить об ошибке
|
||||
feedback.type.feature=Запрос функции
|
||||
feedback.unlike=Не нравится
|
||||
feedback.version=Версия лаунчера
|
||||
|
||||
file=Файл
|
||||
|
||||
folder.config=Конфиги
|
||||
folder.game=Каталог игры
|
||||
folder.mod=Мод
|
||||
folder.config=Папка конфигов
|
||||
folder.game=Папка игры
|
||||
folder.mod=Папка модов
|
||||
folder.resourcepacks=Наборы текстур
|
||||
folder.saves=Сохранения
|
||||
folder.screenshots=Снимки экрана
|
||||
@@ -349,47 +362,48 @@ game.crash.feedback=Проблема настолько распростране
|
||||
game.crash.info=Статус игры
|
||||
game.crash.reason=Анализатор сбоев
|
||||
game.crash.reason.analyzing=Анализирование...
|
||||
game.crash.reason.block=Игра не может запуститься из-за блока.\nУдалите этот блок с помощью MCEdit или напрямую удалите связанный с ним мод.\nТип блока: %1$s\nРасположение блока: %2$s
|
||||
game.crash.reason.bootstrap_failed=Игра невозможна из-за сбоя мода %1$s.\nПопробуйте удалить или обновить его.
|
||||
game.crash.reason.config=Игра не запускается, потому что мод не может разобрать её файл конфигурации.\nМод %1$s не может разобрать свой файл конфигурации %2$s.
|
||||
game.crash.reason.debug_crash=Игра не может запуститься, потому что сбой вызван вручную.\nТо есть, игра невиновна, во всём виноваты вы.
|
||||
game.crash.reason.duplicated_mod=Игра не запускается из-за дублирования модов: %s.\n%s\nКаждый мод может быть установлен только один, удалите дублирующий мод и попробуйте снова.
|
||||
game.crash.reason.entity=Игра не может быть запущена из-за существа.\nВы можете удалить это существо с помощью MCEdit или напрямую удалить связанный с ним мод.\nТип существа: %1$s\nРасположение существа: %2$s
|
||||
game.crash.reason.fabric_version_0_12=Fabric 0.12 и выше несовместим с установленными в данный момент модами. Следует понизить версию fabric до 0.11.7.
|
||||
game.crash.reason.fabric_warnings=Fabric даёт несколько предупреждений:\n%1$s
|
||||
game.crash.reason.file_already_exists=Игра не может быть запущена, потому что файл %1$s уже существует.\nЕсли его можно удалить, создайте резервную копию, удалите его и попробуйте снова.
|
||||
game.crash.reason.file_changed=Игра не может быть запущена из-за сбоя проверки файла.\nЕсли вы изменили первичный jar Minecraft, вам нужно откатить изменения или перекачать игру.
|
||||
game.crash.reason.gl_operation_failure=Игра рухнула из-за некоторых модов, шейдер-паков, текстур-паков.\nОтключите используемые моды или наборы шейдеров/текстур и попробуйте снова.
|
||||
game.crash.reason.graphics_driver=Игра рухнула из-за проблемы с графическим драйвером.\n\
|
||||
game.crash.reason.block=Невозможно запустить игру из-за блока.\nУдалите этот блок с помощью MCEdit или удалите мод связанный с ним напрямую.\nТип блока: %1$s\nРасположение блока: %2$s
|
||||
game.crash.reason.bootstrap_failed=Невозможно запустить игру из-за сбоя мода %1$s.\nПопробуйте удалить или обновить его.
|
||||
game.crash.reason.config=Невозможно запустить игру из-за того, что мод не может разобрать файл конфигурации.\nМод %1$s не может разобрать свой файл конфигурации %2$s.
|
||||
game.crash.reason.debug_crash=Невозможно запустить игру из-за того, что сбой вызван вручную.\nТо есть игра невиновна, во всём виноваты вы.
|
||||
game.crash.reason.duplicated_mod=Невозможно запустить игру из-за дублирования модов: %s.\n%s\nКаждый мод должен быть установлен только один, удалите дублирующий мод и попробуйте снова.
|
||||
game.crash.reason.entity=Невозможно запустить игру из-за существа.\nУдалите это существо с помощью MCEdit или удалите мод связанный с ним напрямую.\nТип существа: %1$s\nРасположение существа: %2$s
|
||||
game.crash.reason.fabric_version_0_12=Fabric 0.12 (и выше) несовместим с установленными модами. Следует понизить версию fabric до 0.11.7.
|
||||
game.crash.reason.fabric_warnings=Предупреждения от Fabric:\n%1$s
|
||||
game.crash.reason.file_already_exists=Невозможно запустить игру из-за того, что файл %1$s уже существует.\nЕсли его можно удалить, создайте резервную копию и удалите его, затем попробуйте снова.
|
||||
game.crash.reason.file_changed=Невозможно запустить игру из-за сбоя проверки файла.\nЕсли вы изменили первичный jar Minecraft, вам нужно откатить изменения или скачать игру заново.
|
||||
game.crash.reason.gl_operation_failure=Игра вылетела из-за некоторых модов, шейдер-паков, текстур-паков.\nОтключите используемые моды или наборы шейдеров/текстур и попробуйте снова.
|
||||
game.crash.reason.graphics_driver=Игра вылетела из-за проблемы с графическим драйвером.\n\
|
||||
Обновите графический драйвер до последней версии.\n\
|
||||
Если в вашем компьютере установлена дискретная видеокарта, необходимо проверить, использует ли игра интегрированную/ядер графику. Если это так, запустите HMCL и игру с использованием дискретной видеокарты. Если у вас по-прежнему возникают подобные проблемы, вы можете подумать о приобретении новой видеокарты или нового компьютера.\n\
|
||||
Если же вы хотите продолжать использовать графическое ядро, проверьте, используете ли вы процессор Intel(R) Core(TM) 3000 series или более старый. Если это так, то для Minecraft 1.16.5 или более старой версии вам необходимо понизить версию Java до 1.8.0_51 или более старой версии, либо приобрести специальную видеокарту или новый компьютер.\n\
|
||||
Кликните <a href="https://hmcl.huangyuhui.net/help/game/graphics-card">тут (Chinese website)</a>, чтобы узнать подробности.
|
||||
game.crash.reason.illegal_access_error=Игра рухнула из-за некоторых модов.\nЕсли вы узнаёте: %1$s, вы можете обновить или удалить мод и повторить попытку.
|
||||
game.crash.reason.jdk_9=Игра не запускается, потому что версия Java слишком высокая.\nВам необходимо скачать и установить Java 8 и выбрать вновь установленную Java в настройках игры.
|
||||
game.crash.reason.jvm_32bit=Игра рухнула из-за того, что выделение памяти превысило лимит 32-битной Java VM.\nЕсли ваша ОС 64-битная, пожалуйста, установите и используйте 64-битную Java. Если ваша ОС 32-битная, вы можете переустановить 64-битную ОС или сменить компьютер.\nИли вы можете отключить автоматическое выделение памяти и установить максимальный размер памяти на 1024 МБ или ниже.
|
||||
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=Игра рухнула, потому что JVM не хватило памяти для выделения.\nЭта проблема вызвана слишком маленьким размером страницы.\nВам необходимо отключить автоматическое выделение памяти в настройках игры и уменьшить выделение памяти до значения, с которым может справиться ваша система.\nВы также можете настроить размер системной страницы на достаточно большое значение.
|
||||
game.crash.reason.mod=Игра рухнула из-за мода: %1$s.\nВы можете обновить или удалить мод и повторить попытку.
|
||||
game.crash.reason.mod_resolution=Игра рухнула из-за сбоя разрешения мода.\nFabric даёт некоторую информацию:\n%s
|
||||
game.crash.reason.mod_resolution_collection=Игра рухнула из-за несовместимости требуемой версии мода.\n%1$s требует %2$s.\nЭто означает, что вам необходимо обновить или понизить версию %2$s. Вам необходимо скачать %3$s.
|
||||
game.crash.reason.mod_resolution_conflict=Игра рухнула из-за конфликтующих модов.\n%1$s конфликтуют с %2$s.
|
||||
game.crash.reason.mod_resolution_missing=Игра рухнула, потому что мод, от которого зависит другой мод, не установлен.\n%1$s требует установки мода: %2$s.\nЭто означает, что вам необходимо скачать %2$s со страницы скачивания или через сайт.
|
||||
game.crash.reason.mod_resolution_missing_minecraft=Игра рухнула из-за несовместимости мода с текущей версией Minecraft.\n%1$s требует Minecraft версии %2$s.\nЕсли вы хотите сохранить установленную версию мода, вам следует изменить версию игры.\nЕсли вы хотите продолжать использовать текущую версию игры, вам следует переустановить необходимый мод.
|
||||
game.crash.reason.illegal_access_error=Игра вылетела из-за некоторых модов.\nЕсли вы узнаёте: %1$s, вы можете обновить или удалить мод и повторить попытку.
|
||||
game.crash.reason.jdk_9=Невозможно запустить игру из-за того, что версия Java слишком высокая.\nНеобходимо скачать и установить Java 8 и вновь выбрать установленную Java в настройках игры.
|
||||
game.crash.reason.jvm_32bit=Игра вылетела из-за того, что выделение памяти превысило лимит 32-битной Java VM.\nЕсли ваша ОС 64-битная, установите и используйте 64-битную Java. Если ваша ОС 32-битная, вы можете переустановить 64-битную ОС или сменить компьютер.\nИли вы можете отключить автоматическое выделение памяти и установить максимальный размер памяти на 1024 МБ или ниже.
|
||||
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=Игра вылетела, потому что JVM не хватило памяти для выделения.\nЭта проблема вызвана слишком маленьким размером страницы.\nВам необходимо отключить автоматическое выделение памяти в настройках игры и уменьшить выделение памяти до значения, с которым может справиться ваша система.\nВы также можете настроить размер системной страницы на достаточно большое значение.
|
||||
game.crash.reason.mod=Игра вылетела из-за мода: %1$s.\nВы можете обновить или удалить мод и повторить попытку.
|
||||
game.crash.reason.mod_resolution=Игра вылетела из-за сбоя разрешения мода.\nFabric даёт некоторую информацию:\n%s
|
||||
game.crash.reason.mod_resolution_collection=Игра вылетела из-за несовместимости требуемой версии мода.\n%1$s требует %2$s.\nЭто означает, что вам необходимо обновить или понизить версию %2$s. Вам необходимо скачать %3$s.
|
||||
game.crash.reason.mod_resolution_conflict=Игра вылетела из-за конфликтующих модов.\n%1$s конфликтуют с %2$s.
|
||||
game.crash.reason.mod_resolution_missing=Игра вылетела, потому что мод, от которого зависит другой мод, не установлен.\n%1$s требует установки мода: %2$s.\nЭто означает, что вам необходимо скачать %2$s со страницы скачивания или через сайт.
|
||||
game.crash.reason.mod_resolution_missing_minecraft=Игра вылетела из-за несовместимости мода с текущей версией Minecraft.\n%1$s требует Minecraft версии %2$s.\nЕсли вы хотите сохранить установленную версию мода, вам следует изменить версию игры.\nЕсли вы хотите продолжать использовать текущую версию игры, вам следует переустановить необходимый мод.
|
||||
game.crash.reason.mod_resolution_mod_version=%1$s (Версия: %2$s)
|
||||
game.crash.reason.mod_resolution_mod_version.any=%1$s (Любая версия)
|
||||
game.crash.reason.no_class_def_found_error=Игра не запускается из-за неполного кода.\nВ вашей игре может отсутствовать мод, или некоторые файлы модов могут быть неполными, или некоторые моды несовместимы с текущей игрой.\nВозможно, вам придётся переустановить игру и моды или обратиться за помощью.\n%1$s отсутствует
|
||||
game.crash.reason.no_such_method_error=Игра не может быть запущена из-за неполного кода.\nВозможно, в вашей игре отсутствует мод, или некоторые файлы модов могут быть неполными, или некоторые моды несовместимы с текущей игрой.\nВозможно, вам придётся переустановить игру и моды или обратиться за помощью.
|
||||
game.crash.reason.opengl_not_supported=Игра не может быть запущена из-за проблемы с графическим драйвером.\nПоскольку OpenGL не поддерживается, вы находитесь в режиме удаленного рабочего стола или потоковой передачи? Если это так, используйте оригинальный компьютер для запуска игры.\nИли попробуйте обновить графический драйвер до последней версии и перезапустить игру. Если ваш компьютер имеет дискретную графику, вам нужно проверить, использует ли игра интегрированную/основную графику. Если это так, попробуйте запустить HMCL и игру с использованием дискретной видеокарты. Если у вас по-прежнему возникают подобные проблемы, вы можете подумать о приобретении новой видеокарты или нового компьютера.
|
||||
game.crash.reason.openj9=Игра не запускается на виртуальной машине OpenJ9, переключитесь на виртуальную машину Hotspot Java в настройках игры и перезапустите игру. Если нет, вы можете скачать его в интернете.
|
||||
game.crash.reason.out_of_memory=Игра не запускается из-за нехватки памяти.\nЭто может быть вызвано слишком малым количеством выделенной памяти или слишком большим количеством модов.\nВы можете увеличить выделение игровой памяти в настройках игры, чтобы игра могла работать на большем объёме памяти.\nЕсли вы все ещё испытываете эти проблемы, возможно, вам нужно сменить компьютер на более качественный.
|
||||
game.crash.reason.resolution_too_high=Игра не запускается, потому что вы используете набор текстур, разрешение которых слишком большое.\nВам следует использовать набор текстур с меньшим разрешением или приобрести видеокарту с большим объемом памяти.
|
||||
game.crash.reason.modlauncher_8=Игра вылетела из-за того, что используемая вами версия Forge несовместима с текущей версией Java. Попробуйте обновить Forge или начните с Java 8u312/11.0.13/17.0.1 или более ранней версии.
|
||||
game.crash.reason.no_class_def_found_error=Невозможно запустить игру из-за неполного кода.\nВ вашей игре может отсутствовать мод, или некоторые файлы модов могут быть неполными, или некоторые моды несовместимы с текущей игрой.\nВозможно, вам придётся переустановить игру и моды или обратиться за помощью.\n%1$s отсутствует
|
||||
game.crash.reason.no_such_method_error=Невозможно запустить игру из-за неполного кода.\nВозможно, в вашей игре отсутствует мод, или некоторые файлы модов могут быть неполными, или некоторые моды несовместимы с текущей игрой.\nВозможно, вам придётся переустановить игру и моды или обратиться за помощью.
|
||||
game.crash.reason.opengl_not_supported=Невозможно запустить игру из-за проблемы с графическим драйвером.\nПоскольку OpenGL не поддерживается, вы находитесь в режиме удаленного рабочего стола или потоковой передачи? Если это так, используйте оригинальный компьютер для запуска игры.\nИли попробуйте обновить графический драйвер до последней версии и перезапустить игру. Если ваш компьютер имеет дискретную графику, вам нужно проверить, использует ли игра интегрированную/основную графику. Если это так, попробуйте запустить HMCL и игру с использованием дискретной видеокарты. Если у вас по-прежнему возникают подобные проблемы, вы можете подумать о приобретении новой видеокарты или нового компьютера.
|
||||
game.crash.reason.openj9=Невозможно запустить игру на виртуальной машине OpenJ9, переключитесь на виртуальную машину Hotspot Java в настройках игры и перезапустите игру. Если его нет, можно скачать его в интернете.
|
||||
game.crash.reason.out_of_memory=Невозможно запустить игру из-за нехватки памяти.\nЭто может быть вызвано слишком малым количеством выделенной памяти или слишком большим количеством модов.\nВы можете увеличить выделение игровой памяти в настройках игры, чтобы игра могла работать на большем объёме памяти.\nЕсли вы все ещё испытываете эти проблемы, возможно, вам нужно сменить компьютер на более качественный.
|
||||
game.crash.reason.resolution_too_high=Невозможно запустить игру из-за того, что вы используете набор текстур, разрешение которых слишком большое.\nВам следует использовать набор текстур с меньшим разрешением или приобрести видеокарту с большим объемом памяти.
|
||||
game.crash.reason.stacktrace=Неизвестно. Вы можете просмотреть подробности, кликнув на кнопку «Журналы».\nЕсть несколько ключевых слов, которые могут представлять Mod ID. Вы можете поискать их в интернете, чтобы выяснить причину.\n%s
|
||||
game.crash.reason.too_old_java=Игра не может запуститься, потому что версия виртуальной машины Java слишком низкая.\nYou need to switch to a newer version (%1$s) of Java virtual machine in the game settings and restart the game. If not, you can download it online.
|
||||
game.crash.reason.too_old_java=Невозможно запустить игру из-за того, что версия виртуальной машины Java слишком низкая.\nYou need to switch to a newer version (%1$s) of Java virtual machine in the game settings and restart the game. If not, you can download it online.
|
||||
game.crash.reason.unknown=Неизвестно. Вы можете просмотреть подробности, кликнув на кнопку «Журналы».
|
||||
game.crash.reason.unsatisfied_link_error=Игра не может запуститься, потому что отсутствуют необходимые встроенные библиотеки.\nОтсутствующая нативная библиотека: %1$s.\nЕсли в настройках игры вы изменили параметр пути к нативной библиотеке, лучше переключиться обратно в стандартный режим.\nЕсли это уже стандартный режим, проверьте, принадлежит ли отсутствующая нативная библиотека моду или игре. Если вы уверены, что это произошло по вине HMCL, вы можете написать нам через обратную связь.\nЕсли вам действительно нужно настроить путь к нативным библиотекам, вы должны поместить в каталог все нативные библиотеки, которые требуются игре.
|
||||
game.crash.title=Игра рухнула
|
||||
game.crash.reason.unsatisfied_link_error=Невозможно запустить игру из-за того, что отсутствуют необходимые встроенные библиотеки.\nОтсутствующая нативная библиотека: %1$s.\nЕсли в настройках игры вы изменили параметр пути к нативной библиотеке, лучше переключиться обратно в стандартный режим.\nЕсли это уже стандартный режим, проверьте, принадлежит ли отсутствующая нативная библиотека моду или игре. Если вы уверены, что это произошло по вине HMCL, вы можете написать нам через обратную связь.\nЕсли вам действительно нужно настроить путь к нативным библиотекам, вы должны поместить в каталог все нативные библиотеки, которые требуются игре.
|
||||
game.crash.title=Игра вылетела
|
||||
game.directory=Каталог игры
|
||||
game.version=Версия игры
|
||||
|
||||
@@ -397,7 +411,7 @@ help=Помощь
|
||||
help.doc=Документация
|
||||
help.detail=Для создателей датапаков, модпаков и т.д.
|
||||
|
||||
input.email=Имя пользователя должно быть адресом электронной почты.
|
||||
input.email=Имя пользователя должно быть электронной почтой.
|
||||
input.number=Должно быть число.
|
||||
input.not_empty=Обязательное поле
|
||||
input.url=Должна быть действительная ссылка.
|
||||
@@ -419,7 +433,7 @@ install.installer.choose=Выбрать версию %s
|
||||
install.installer.depend=Зависит от %s
|
||||
install.installer.fabric=Fabric
|
||||
install.installer.fabric-api=Fabric API
|
||||
install.installer.fabric-api.warning=Предупреждение: Fabric API - это мод, который будет установлен в каталог модов новой игры. Не следует менять каталог запуска новой игры, иначе установка Fabric API будет потеряна. Если вы хотите изменить эти настройки, вам следует переустановить Fabric API.
|
||||
install.installer.fabric-api.warning=Fabric API - это мод, который будет установлен в каталог модов новой игры. Не следует менять каталог запуска новой игры, иначе установка Fabric API будет потеряна. Если вы хотите изменить эти настройки, вам следует переустановить Fabric API.
|
||||
install.installer.forge=Forge
|
||||
install.installer.game=Minecraft
|
||||
install.installer.incompatible=Несовместимо с %s
|
||||
@@ -432,6 +446,8 @@ install.installer.install_online.tooltip=Поддержка установки F
|
||||
install.installer.liteloader=LiteLoader
|
||||
install.installer.not_installed=Не установлен
|
||||
install.installer.optifine=OptiFine
|
||||
install.installer.quilt=Quilt
|
||||
install.installer.quilt-api=Quilt API
|
||||
install.installer.version=%s
|
||||
install.modpack=Установить модпак
|
||||
install.new_game=Установить новую игру
|
||||
@@ -451,6 +467,7 @@ launch.advice.uncorrected=Если вы уверены, что игра може
|
||||
launch.advice.different_platform=Ваша ОС - 64-разрядная, а Java - 32-разрядная. Рекомендуется использовать 64-разрядную версию Java.
|
||||
launch.advice.forge2760_liteloader=Forge 2760 и выше не совместим с LiteLoader. Обновите Forge до 2773 или более новой версии. Продолжить запуск?
|
||||
launch.advice.forge28_2_2_optifine=Forge 28.2.2 и более поздние версии не совместимы с OptiFine. Смените Forge на версию 28.2.1 или более старую версию. Продолжить запуск?
|
||||
launch.advice.forge37_0_60=Версии Forge ранее 37.0.60 не совместимы с Java 17. Обновите Forge или запустите игру с Java 16. Продолжить запуск?
|
||||
launch.advice.java8_1_13=Minecraft 1.13 и выше могут работать только на Java 8 или более новых версиях.
|
||||
launch.advice.java8_51_1_13=Minecraft 1.13 может аварийно завершить работу на более старой версии, чем Java 8 1.8.0_51. Установите последнюю версию Java 8.
|
||||
launch.advice.java9=Вы не сможете запустить Minecraft 1.12 и ниже с Java 9 и более новыми версиями Java.
|
||||
@@ -459,7 +476,7 @@ launch.advice.not_enough_space=Вы выделили слишком много
|
||||
launch.advice.require_newer_java_version=Minecraft %1$s требует Java %2$s или более новую версию, скачать её сейчас?
|
||||
launch.advice.too_large_memory_for_32bit=Вы выделили слишком много памяти, из-за 32-разрядной Java Runtime Environment ваша игра может рухнуть. Максимальный объем памяти для 32-разрядных систем составляет 1024 МБ. Продолжить запуск?
|
||||
launch.advice.vanilla_linux_java_8=На Linux x86-64, Minecraft 1.12.2 и ниже может работать только на Java 8.\nВерсии Java 9 и выше не могут загружать 32-битные нативные библиотеки, такие как liblwjgl.so.
|
||||
launch.advice.vanilla_x86=В настоящее время Minecraft не обеспечивает официальную поддержку архитектур, отличных от x86 и x86-64.\nВы можете попробовать скачать соответствующую нативную библиотеку платформы и указать её путь размещения.
|
||||
launch.advice.modlauncher8=Используемая вами версия Forge несовместима с текущей версией Java. Попробуйте обновить Forge или начните с Java 8u312/11.0.13/17.0.1 или более ранней версии. Продолжить запуск?
|
||||
launch.advice.vanilla_x86.translation=В настоящее время Minecraft не обеспечивает официальную поддержку архитектур, отличных от x86 и x86-64.\nПожалуйста, используйте Java для x86-64 для запуска minecraft через переводчик, или загрузите соответствующую нативную библиотеку платформы и укажите её путь размещения.
|
||||
launch.failed=Невозможно запустить
|
||||
launch.failed.cannot_create_jvm=Не удалось создать виртуальную машину Java. Аргументы Java могут вызвать проблемы. Перезапустите без аргументов JVM.
|
||||
@@ -471,26 +488,26 @@ launch.failed.executable_permission=Невозможно добавить раз
|
||||
launch.failed.execution_policy=Установите политику выполнения
|
||||
launch.failed.execution_policy.failed_to_set=Не удалось установить политику выполнения
|
||||
launch.failed.execution_policy.hint=Текущая политика выполнения предотвращает запуск скриптов PowerShell.\nНажмите «ОК», чтобы разрешить текущему пользователю выполнять скрипт PowerShell, или нажмите «Отмена», чтобы сохранить статус кво.
|
||||
launch.failed.exited_abnormally=Игра завершилась с ошибкой, проверьте журнал или попросите кого-нибудь о помощи.
|
||||
launch.failed.exited_abnormally=Произошёл сбой игры, проверьте логи или попросите кого-нибудь о помощи.
|
||||
launch.failed.no_accepted_java=Не удаётся найти установку Java, подходящую для текущей игры. Если вы считаете, что у вас установлена подходящая Java VM, вы можете вручную выбрать её в настройках игры.
|
||||
launch.state.dependencies=Зависимости
|
||||
launch.state.dependencies=Проверка зависимостей
|
||||
launch.state.done=Готово
|
||||
launch.state.java=Определить версию Java
|
||||
launch.state.java=Определение версии Java
|
||||
launch.state.logging_in=Вход в систему
|
||||
launch.state.modpack=Загрузка модпака
|
||||
launch.state.waiting_launching=Запуск игры
|
||||
launch.wrong_javadir=Неверный каталог Java, будет применен путь Java по умолчанию.
|
||||
launch.wrong_javadir=Неверный каталог Java, будет применён путь Java по умолчанию.
|
||||
|
||||
launcher=Лаунчер
|
||||
launcher.agreement=EULA
|
||||
launcher.agreement.accept=Принять
|
||||
launcher.agreement.decline=Отклонить
|
||||
launcher.agreement.hint=Вы должны согласиться с EULA, чтобы использовать это программное обеспечение.
|
||||
launcher.agreement.hint=Нужно согласиться с EULA, чтобы использовать это программное обеспечение.
|
||||
launcher.background=Фоновое изображение
|
||||
launcher.background.classic=Классическое
|
||||
launcher.background.choose=Выберите файл фонового изображения
|
||||
launcher.background.default=Стандартное
|
||||
launcher.background.network=Сеть
|
||||
launcher.background.default=По умолчанию
|
||||
launcher.background.network=Ссылка на изображение
|
||||
launcher.background.translucent=Полупрозрачное
|
||||
launcher.cache_directory=Каталог для кэширования
|
||||
launcher.cache_directory.clean=Очистить
|
||||
@@ -550,7 +567,7 @@ modpack.files.mods=Моды
|
||||
modpack.files.mods.voxelmods=Параметры VoxelMods (включая VoxelMap)
|
||||
modpack.files.options_txt=Параметры игры
|
||||
modpack.files.optionsshaders_txt=Параметры шейдеров
|
||||
modpack.files.resourcepacks=Наборы текстур/ресурсов
|
||||
modpack.files.resourcepacks=Наборы текстур
|
||||
modpack.files.saves=Сохранения игры
|
||||
modpack.files.scripts=Конфигурация MineTweaker
|
||||
modpack.files.servers_dat=Список серверов
|
||||
@@ -571,8 +588,8 @@ modpack.task.install.error=Этот файл модпака не может бы
|
||||
modpack.task.install.will=Установить модпак:
|
||||
modpack.type.curse=Curse
|
||||
modpack.type.curse.completion=Установите файлы, связанные с модпаком Curse
|
||||
modpack.type.curse.tolerable_error=Мы не можем завершить скачивание всех файлов этого модпака Curse. Вы можете повторить скачивание при запуске соответствующей версии игры. Вы можете повторить попытку несколько раз из-за проблем с сетью.
|
||||
modpack.type.curse.error=Невозможно установить этот модпак Curse. Повторите ещё раз.
|
||||
modpack.type.curse.tolerable_error=Мы не можем завершить скачивание всех файлов этого модпака. Вы можете повторить скачивание при запуске соответствующей версии игры. Вы можете повторить попытку несколько раз из-за проблем с сетью.
|
||||
modpack.type.curse.error=Невозможно установить этот модпак. Повторите ещё раз.
|
||||
modpack.type.curse.not_found=Некоторые из необходимых ресурсов отсутствуют и поэтому не могут быть загружены. Скачайте последнюю версию или другие модпаки.
|
||||
modpack.type.manual.warning=Возможно, вам нужно напрямую распаковать файл этого модпака, а не импортировать его. И запустите игру с помощью прилагаемого лаунчера. Этот модпак создаётся вручную путем сжатия директории .minecraft, а не экспортируется лаунчером. HMCL может попытаться импортировать этот модпак, продолжать?
|
||||
modpack.type.mcbbs=Стандарт MCBBS
|
||||
@@ -660,67 +677,67 @@ multiplayer.nat.not_yet_tested=Ещё не протестировано
|
||||
multiplayer.nat.packet_loss_ratio=Коэффициент потери пакетов
|
||||
multiplayer.nat.testing=Тестирование
|
||||
multiplayer.nat.type=Тип NAT
|
||||
multiplayer.nat.type.blocked_udp=Очень плохо (заблокированный UDP)
|
||||
multiplayer.nat.type.full_cone=Хорошо (полный конус)
|
||||
multiplayer.nat.type.open_access=Хорошо (открытый доступ)
|
||||
multiplayer.nat.type.port_restricted_cone=Средне (конус с ограничением порта)
|
||||
multiplayer.nat.type.restricted_cone=Средне (ограниченный конус)
|
||||
multiplayer.nat.type.symmetric=Плохо (симметричный)
|
||||
multiplayer.nat.type.blocked_udp=Очень плохой (заблокированный UDP)
|
||||
multiplayer.nat.type.full_cone=Хороший (полный конус)
|
||||
multiplayer.nat.type.open_access=Хороший (открытый доступ)
|
||||
multiplayer.nat.type.port_restricted_cone=Средний (конус с ограничением порта)
|
||||
multiplayer.nat.type.restricted_cone=Средний (ограниченный конус)
|
||||
multiplayer.nat.type.symmetric=Плохой (симметричный)
|
||||
multiplayer.nat.type.symmetric_udp_firewall=Плохо (симметричный с брандмауэром UDP)
|
||||
multiplayer.nat.type.unknown=Неизвестно
|
||||
multiplayer.powered_by=Сетевая игра предоставлена <a href="https://noin.cn">noin.cn</a>. <a href="https://noin.cn/agreement">EULA</a>
|
||||
multiplayer.powered_by=Сетевая игра предоставлена <a href="https://mcer.cn">mcer.cn</a>. <a href="https://hmcl.huangyuhui.net/api/redirect/multiplayer-agreement">EULA</a>
|
||||
multiplayer.report=Отчёт
|
||||
multiplayer.relay=Режим реле
|
||||
multiplayer.relay.hint=Для пользователей, у которых есть статический токен, и чей проверенный тип NAT - плохо-семитричный
|
||||
multiplayer.session=Сессия
|
||||
multiplayer.session=Комната
|
||||
multiplayer.session.name.format=Комната %1$s
|
||||
multiplayer.session.name.motd=Сетевая сессия HMCL - %s
|
||||
multiplayer.session.name.motd=Сетевая комната - %s
|
||||
multiplayer.session.close=Закрыть комнату
|
||||
multiplayer.session.close.warning=После закрытия комнаты все игроки, присоединившиеся к комнате, потеряют связь. Продолжить?
|
||||
multiplayer.session.close.warning=После закрытия комнаты, все игроки присоединившиеся к комнате потеряют связь. Продолжить?
|
||||
multiplayer.session.copy_room_code=Скопировать код приглашения
|
||||
multiplayer.session.create=Создать сессию
|
||||
multiplayer.session.create.error.dynamic_token=Не удалось создать сетевую сессию. Повторите попытку позже.
|
||||
multiplayer.session.create.error.static_token=Не удалось создать сетевую сессию. Вы можете повторить попытку с динамическим токеном.
|
||||
multiplayer.session.create.hint=Перед созданием сетевой сессии необходимо нажать кнопку «Открыть локальный сервер». в запущенной игре, и ввести порт, отображаемый в игре, в пустом поле ниже.
|
||||
multiplayer.session.create=Создать комнату
|
||||
multiplayer.session.create.error.dynamic_token=Невозможно создать сетевую комнату. Повторите попытку позже.
|
||||
multiplayer.session.create.error.static_token=Невозможно создать сетевую комнату. Вы можете повторить попытку с динамическим токеном.
|
||||
multiplayer.session.create.hint=Перед созданием сетевой комнаты необходимо нажать кнопку «Открыть локальный сервер». в запущенной игре, и ввести порт отображаемый в игре, в пустом поле ниже.
|
||||
multiplayer.session.create.join=Запрос на подключение
|
||||
multiplayer.session.create.join.allow=Принимать все запросы на присоединение (Если этот параметр отключен, вам необходимо принимать запросы на присоединение вручную, чтобы предотвратить атаки.)
|
||||
multiplayer.session.create.join.prompt=Игрок %s хочет присоединиться к сетевой сессии. Принять?
|
||||
multiplayer.session.create.join.prompt=Игрок %s хочет присоединиться к сетевой комнате. Принять?
|
||||
multiplayer.session.create.members=Участники
|
||||
multiplayer.session.create.members.kick=Выгнать
|
||||
multiplayer.session.create.members.kick.prompt=Выгнав этого игрока, установленная связь с этим игроком будет разорвана.
|
||||
multiplayer.session.create.name=Название сессии
|
||||
multiplayer.session.create.name=Название
|
||||
multiplayer.session.create.port=Порт
|
||||
multiplayer.session.create.port.error=Не удается обнаружить игровой порт, необходимо нажать кнопку «Открыть локальный сервер» в игре, чтобы включить сетевую игру.
|
||||
multiplayer.session.create.token=Токен
|
||||
multiplayer.session.create.token.apply=Подать заявку на статический токен
|
||||
multiplayer.session.create.token.prompt=По умолчанию рандомизирован. Вы можете подать заявку на получение собственного токена на сайте noin.cn (китайский сайт).
|
||||
multiplayer.session.create.token.prompt=Случайный по умолчанию. Вы можете подать заявку на получение собственного токена на сайте mcer.cn.
|
||||
multiplayer.session.error.already_started=Сервис Cato уже запущен локально. Проверьте, обслуживаются ли другие сетевые сервисы или нет. Или вы можете завершить процесс cato в диспетчере задач.
|
||||
multiplayer.session.error.file_not_found=Не удается найти исполняемый файл cato. Этот файл должен быть скачан при входе на эту страницу. Проверьте, не изолировал ли ваш антивирус файл. Если это так, откатитесь.
|
||||
multiplayer.session.expired=Срок действия сетевой сессии истёк. Вы должны пересоздать или повторно присоединиться к комнате, чтобы продолжить.
|
||||
multiplayer.session.error.file_not_found=Не удается найти исполняемый файл cato. Этот файл должен быть скачан при входе на эту страницу. Проверьте, не изолировал ли ваш антивирус файл. Если это так, восстановите его.
|
||||
multiplayer.session.expired=Срок действия сетевой комнаты истёк. Вы должны пересоздать или повторно присоединиться к комнате, чтобы продолжить.
|
||||
multiplayer.session.hint=Вы должны нажать «Открыть локальный сервер» в игре, чтобы включить сетевую игру.
|
||||
multiplayer.session.join=Присоединиться к сессии
|
||||
multiplayer.session.join.error=Не удалось присоединиться к сетевой сессии
|
||||
multiplayer.session.join.error.connection=Не удалось присоединиться к сетевой сессии. Не удаётся установить соединение.
|
||||
multiplayer.session.join.hint=Вы должны получить пригласительный код от игрока, который уже создал сетевую сессию.
|
||||
multiplayer.session.join=Подключиться
|
||||
multiplayer.session.join.error=Невозможно подключиться к сетевой комнате
|
||||
multiplayer.session.join.error.connection=Невозможно подключиться к сетевой комнате. Не удаётся установить соединение.
|
||||
multiplayer.session.join.hint=Вы должны получить код приглашения от игрока, который уже создал сетевую комнату.
|
||||
multiplayer.session.join.invitation_code=Код приглашения
|
||||
multiplayer.session.join.invitation_code.error=Неверный код приглашения. Получите пригласительный код от игрока, который создаёт сетевую сессию.
|
||||
multiplayer.session.join.kicked=Потеря связи: %s.
|
||||
multiplayer.session.join.kicked.join_acceptance_timeout=Владелец сессии не принял вовремя ваш запрос на присоединение.
|
||||
multiplayer.session.join.kicked.kicked=Владелец сессии вас выгнал.
|
||||
multiplayer.session.join.invitation_code.error=Неверный код приглашения. Получите код от игрока, который создаёт сетевую комнату.
|
||||
multiplayer.session.join.kicked=Связь потеряна: %s.
|
||||
multiplayer.session.join.kicked.join_acceptance_timeout=Владелец комнаты не успел принять ваш запрос на подключение.
|
||||
multiplayer.session.join.kicked.kicked=Владелец комнаты вас выгнал.
|
||||
multiplayer.session.join.kicked.version_not_matched=У вас разные версии сетевой игры
|
||||
multiplayer.session.join.lost_connection=Потеряно соединение с сетевой сессией. Возможно, сессия закрыта создателем, или вы не можете установить связь с этой сессией.
|
||||
multiplayer.session.join.port.error=Не удается найти доступный порт локальной сети для прослушивания. Убедитесь, что HMCL имеет разрешение на прослушивание порта.
|
||||
multiplayer.session.join.rejected=Ваше соединение отклонено держателем сессии.
|
||||
multiplayer.session.join.lost_connection=Потеряно соединение с сетевой комнате. Возможно, сессия закрыта создателем или вы не можете установить с ней связь.
|
||||
multiplayer.session.join.port.error=Не удается найти доступный порт локальной сети для прослушивания. Убедитесь, что лаунчер имеет разрешение на прослушивание порта.
|
||||
multiplayer.session.join.rejected=Ваше соединение отклонено владельцем комнаты.
|
||||
multiplayer.session.join.wait=Ожидание принятия.
|
||||
multiplayer.session.members=Участники комнаты
|
||||
multiplayer.session.quit=Выйти из комнаты
|
||||
multiplayer.session.quit.warning=После выхода из комнаты вы потеряете связь с сервером. Продолжить?
|
||||
multiplayer.session.username=Имя пользователя
|
||||
multiplayer.state.connecting=Подключение
|
||||
multiplayer.state.disconnected=Не создал/не вошёл в сетевую сессию
|
||||
multiplayer.state.disconnected.hint=Кто-то должен создать сетевую сессию, а другие присоединяются к ней, чтобы вместе играть в игру.
|
||||
multiplayer.state.disconnected=Нет комнат
|
||||
multiplayer.state.disconnected.hint=Кто-то должен создать сетевую комнату, а другие присоединяются к ней, чтобы вместе играть в игру.
|
||||
multiplayer.state.master=Создана комната: %1$s, порт: %2$d
|
||||
multiplayer.state.master.hint=После создания сетевой комнаты кликните «Скопировать код приглашения». и отправьте код приглашения игрокам, которых вы хотите пригласить. Эти игроки смогут вставить код приглашения при входе в комнату.
|
||||
multiplayer.state.master.hint=После создания сетевой комнаты кликните «Скопировать код приглашения» и отправьте код приглашения игрокам, которых вы хотите пригласить. Эти игроки смогут вставить код приглашения при входе в комнату.
|
||||
multiplayer.state.slave=Вошедших в комнату: %1$s, адрес: %2$s
|
||||
multiplayer.state.slave.copy=Скопировать адрес
|
||||
multiplayer.state.slave.hint=После присоединения к сетевой комнате вы должны попасть на страницу сетевой игры в Minecraft и подключиться к серверу "Сетевой сессии HMCL", или вручную добавьте сервер с адресом, показанным ниже.
|
||||
@@ -729,16 +746,16 @@ datapack=Датапаки
|
||||
datapack.add=Установить датапак
|
||||
datapack.choose_datapack=Выберите архив датапака для импорта
|
||||
datapack.extension=Датапак
|
||||
datapack.title=Мир %s - Датапаки
|
||||
datapack.title=Датапаки мира %s
|
||||
|
||||
world=Миры
|
||||
world.add=Добавить мир (.zip)
|
||||
world.add=Добавить мир
|
||||
world.datapack=Управление пакетами данных
|
||||
world.datapack.1_13=Только Minecraft 1.13 и выше поддерживают пакеты данных.
|
||||
world.description=%s. Время последней игры: %s. Версия игры: %s.
|
||||
world.download=Загрузки
|
||||
world.export=Экспортировать этот мир
|
||||
world.export.title=Выберите место для хранения вашего мира
|
||||
world.download=Скачать мир
|
||||
world.export=Экспортировать мир
|
||||
world.export.title=Выберите место для сохранения вашего мира
|
||||
world.export.location=Экспортировать в
|
||||
world.export.wizard=Экспортировать мир %s
|
||||
world.extension=Архив мира
|
||||
@@ -747,12 +764,12 @@ world.import.already_exists=Этот мир уже существует.
|
||||
world.import.choose=Выберите архив для импорта
|
||||
world.import.failed=Невозможно импортировать этот мир: %s
|
||||
world.import.invalid=Неверный архив мира
|
||||
world.manage=Миры / Датапаки
|
||||
world.manage=Миры и датапаки
|
||||
world.name=Название мира
|
||||
world.name.enter=Введите название мира
|
||||
world.reveal=Показать в проводнике
|
||||
world.show_all=Показать все
|
||||
world.time=EEE, d MMM, yyyy HH:mm:ss
|
||||
world.time=dd.MM.yyyy HH:mm:ss
|
||||
|
||||
profile=Каталоги игры
|
||||
profile.already_exists=Это название уже существует, используйте другое.
|
||||
@@ -773,11 +790,11 @@ repositories.aliyun_mirror=Материковый Китай (Репозитор
|
||||
repositories.chooser=JavaFX отсутствует, для работы HMCL требуется JavaFX.\nНажмите кнопку «ОК», чтобы скачать компоненты среды выполнения JavaFX из указанного источника и запустить HMCL, или нажмите кнопку «Отмена», чтобы выйти.\nВыберите источники скачивания:
|
||||
repositories.chooser.title=Выберите источник для скачивания JavaFX
|
||||
|
||||
resourcepack=Набор ресурсов
|
||||
resourcepack=Пакет текстур
|
||||
|
||||
search=Поиск
|
||||
search.hint.chinese=Поиск поддержки на китайском и английском языках
|
||||
search.hint.english=Поиск поддержки только на английском языке
|
||||
search.hint.chinese=Поиск на китайском и английском языках
|
||||
search.hint.english=Поиск только на английском языке
|
||||
search.sort=Сортировать
|
||||
|
||||
selector.choose=Выбрать
|
||||
@@ -801,16 +818,18 @@ settings.advanced.custom_commands.hint=Пользовательские кома
|
||||
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/<название версии>/, за исключением активов и библиотек)
|
||||
settings.advanced.game_dir.independent=Независимыое (.minecraft/versions/<версия>/)
|
||||
settings.advanced.java_permanent_generation_space=PermGen места/МБ
|
||||
settings.advanced.java_permanent_generation_space.prompt=Метапространство на Java 8 и выше, МБ
|
||||
settings.advanced.jvm=Параметры Java VM
|
||||
settings.advanced.jvm_args=Аргументы Java VM
|
||||
settings.advanced.jvm_args.prompt=Заполните здесь, чтобы переопределить настройки по умолчанию
|
||||
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=- Если входной параметр совпадает с параметром по умолчанию, он не будет добавлен\n\
|
||||
- Введите любые параметры GC, параметр G1 параметра по умолчанию будет отключен\n\
|
||||
- Нажмите «Отсутствие аргументов JVM по умолчанию» ниже, чтобы не добавлять аргументы по умолчанию при добавлении.
|
||||
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=Аргументы Minecraft
|
||||
settings.advanced.minecraft_arguments.prompt=По умолчанию
|
||||
@@ -839,21 +858,24 @@ settings.advanced.workaround.warning=Настройки обходного пу
|
||||
settings.advanced.wrapper_launcher=Команда оболочки
|
||||
settings.advanced.wrapper_launcher.prompt=Позволяет запускать с помощью дополнительной программы-оболочки, такой как 'optirun' в Linux.
|
||||
|
||||
settings.custom=Пользовательские
|
||||
settings.custom=Настроить
|
||||
|
||||
settings.game=Игры
|
||||
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.template=%s (%s)
|
||||
settings.game.management=Управлять
|
||||
settings.game.working_directory=Рабочий каталог
|
||||
settings.game.working_directory.choose=Выбрать рабочий каталог
|
||||
settings.game.working_directory=Расположение игры
|
||||
settings.game.working_directory.choose=Выбрать папку для игр
|
||||
settings.game.working_directory.hint=Включите параметр «Независимый» в «Расположение игры», чтобы текущая версия хранила настройки, архивы, моды и другие игровые данные независимо друг от друга. Рекомендуется включать эту опцию при использовании модов, чтобы избежать конфликтов между модами разных версий игры. После изменения этого параметра, переместите архив и другие файлы игровых данных самостоятельно.
|
||||
|
||||
settings.icon=Иконка игры
|
||||
|
||||
@@ -885,30 +907,30 @@ settings.launcher.proxy.none=Без прокси
|
||||
settings.launcher.proxy.password=Пароль
|
||||
settings.launcher.proxy.port=Порт
|
||||
settings.launcher.proxy.socks=Socks
|
||||
settings.launcher.proxy.username=Учётная запись
|
||||
settings.launcher.proxy.username=Имя пользователя
|
||||
settings.launcher.theme=Тема
|
||||
settings.launcher.title_transparent=Прозрачный заголовок
|
||||
settings.launcher.version_list_source=Источник списка версий
|
||||
|
||||
settings.memory=Память игры
|
||||
settings.memory.allocate.auto=Минимальный %1$.1f ГБ / Фактический %2$.1f ГБ
|
||||
settings.memory.allocate.auto.exceeded=Минимальный %1$.1f ГБ / Фактический %2$.1f ГБ (доступно %3$.1f ГБ)
|
||||
settings.memory=Оперативная память
|
||||
settings.memory.allocate.auto=Минимально %1$.1f ГБ / Максимально %2$.1f ГБ
|
||||
settings.memory.allocate.auto.exceeded=Минимально %1$.1f ГБ / Максимально %2$.1f ГБ (доступно %3$.1f ГБ)
|
||||
settings.memory.allocate.manual=Выделено %1$.1f ГБ
|
||||
settings.memory.allocate.manual.exceeded=Выделено %1$.1f ГБ (доступно %3$.1f ГБ)
|
||||
settings.memory.auto_allocate=Размер памяти определяется автоматически
|
||||
settings.memory.auto_allocate=Автоопределять размер памяти
|
||||
settings.memory.lower_bound=Минимальная память
|
||||
settings.memory.used_per_total=Использовано %1$.1f ГБ / Всего %2$.1f ГБ
|
||||
settings.memory.used_per_total=Используется %1$.1f ГБ / Всего %2$.1f ГБ
|
||||
settings.physical_memory=Размер физической памяти
|
||||
settings.show_log=Показать журналы
|
||||
settings.skin=Теперь поддерживается смена скина учётной записи без пароля! Перейдите на страницу аккаунта и смените скин прямо сейчас!
|
||||
settings.show_log=Показать логи
|
||||
settings.skin=Теперь поддерживается смена скина офлайн аккаунта! Перейдите на страницу аккаунта и смените скин прямо сейчас!
|
||||
settings.tabs.installers=Установщики
|
||||
settings.take_effect_after_restart=Вступает в силу после перезапуска
|
||||
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.global=Общие настройки
|
||||
settings.type.global.manage=Общие настройки
|
||||
settings.type.global.edit=Изменить общие настройки
|
||||
settings.type.special.enable=Индивидуальные настройки для этой игры
|
||||
settings.type.special.edit=Изменить текущие настройки игры
|
||||
settings.type.special.edit.hint=В текущей версии игры %s включены специализированные настройки, в результате чего параметры на текущей странице не применяются к этой версии игры. Нажмите на ссылку, чтобы настроить текущие настройки игры.
|
||||
|
||||
sponsor=Пожертвования
|
||||
@@ -918,19 +940,19 @@ sponsor.hmcl=Hello Minecraft! Launcher - это бесплатный лаунч
|
||||
system.architecture=Архитектура
|
||||
system.operating_system=ОС
|
||||
|
||||
update=Обновить
|
||||
update=Поиск обновлений
|
||||
update.accept=Обновить
|
||||
update.changelog=Изменения
|
||||
update.channel.dev=Бета
|
||||
update.channel.dev.hint=Вы используете бета-версию, которая может включать некоторые дополнительные функции по сравнению с релизной версией, используемой только для тестирования.\n\
|
||||
Бета-версия может быть нестабильной!\n\
|
||||
Если вы встретили какие-то проблемы, вы можете зайти на <a href="hmcl://settings/feedback">страницу обратной связи</a>, чтобы сообщить о них, или присоединиться к чату <a href="https://discord.gg/jVvC7HfM6U">Discord</a> или <a href="https://kaihei.co/Kx7n3t">KaiHeiLa</a>, чтобы сообщить о проблемах.\n\n\
|
||||
Если вы встретили какие-то проблемы, вы можете зайти на <a href="hmcl://settings/feedback">страницу обратной связи</a>, чтобы сообщить о них, или присоединиться к чату <a href="https://discord.gg/jVvC7HfM6U">Discord</a> или <a href="https://kook.top/Kx7n3t">KOOK</a>, чтобы сообщить о проблемах.\n\n\
|
||||
Чтобы скрыть эту подсказку, нужно <a href="https://hmcl.huangyuhui.net/api/redirect/sponsor">пожертвовать</a> или сделать внушительный вклад через обратную связь.
|
||||
update.channel.dev.title=Подсказки для бета-версии
|
||||
update.channel.nightly=Альфа
|
||||
update.channel.nightly.hint=Вы используете альфа-версию, которая может включать некоторые дополнительные функциональные возможности по сравнению с бета-версией и версией релиза, используемой только для тестирования.\n\
|
||||
Альфа-версия может быть нестабильной!\n\
|
||||
Если вы встретили какие-то проблемы, вы можете зайти на <a href="hmcl://settings/feedback">страницу обратной связи</a>, чтобы сообщить о них, или присоединиться к чату <a href="https://discord.gg/jVvC7HfM6U">Discord</a> или <a href="https://kaihei.co/Kx7n3t">KaiHeiLa</a>, чтобы сообщить о проблемах.\n\n\
|
||||
Если вы встретили какие-то проблемы, вы можете зайти на <a href="hmcl://settings/feedback">страницу обратной связи</a>, чтобы сообщить о них, или присоединиться к чату <a href="https://discord.gg/jVvC7HfM6U">Discord</a> или <a href="https://kook.top/Kx7n3t">KOOK</a>, чтобы сообщить о проблемах.\n\n\
|
||||
Чтобы скрыть эту подсказку, нужно <a href="https://hmcl.huangyuhui.net/api/redirect/sponsor">пожертвовать</a> или сделать внушительный вклад через обратную связь.
|
||||
update.channel.nightly.title=Подсказки для альфа-версии
|
||||
update.channel.stable=Релиз
|
||||
@@ -951,7 +973,7 @@ 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=Снапшот
|
||||
@@ -964,26 +986,26 @@ version.launch_script.save=Сохранить сценарий запуска
|
||||
version.launch_script.success=Создан сценарий %s.
|
||||
version.manage=Все версии
|
||||
version.manage.clean=Очистить каталог игры
|
||||
version.manage.clean.tooltip=Очистить журналы, отчеты об авариях
|
||||
version.manage.clean.tooltip=Очистить логи, отчёты о сбоях
|
||||
version.manage.duplicate=Скопировать экземпляр игры
|
||||
version.manage.duplicate.duplicate_save=Скопировать сохранения
|
||||
version.manage.duplicate.prompt=Введите название новой версии
|
||||
version.manage.duplicate.confirm=
|
||||
version.manage.duplicate.confirm=Новая версия-копия, будет заблокирована: принудительный независимый режим, независимые игровые настройки.
|
||||
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=Удалить
|
||||
version.manage.remove.confirm=Удалить %s? Вы не сможете восстановить эту версию снова!
|
||||
version.manage.remove.confirm.trash=Удалить %s? Вы можете восстановить эту версию с именем %s из корзины системы.
|
||||
version.manage.remove.confirm.independent=Поскольку эта версия находится в независимом режиме, удаление этой версии также приведёт к удалению всех сохранённых миров, принадлежащих этой версии. Удалить %s?
|
||||
version.manage.remove_libraries=Удалить файлы библиотек
|
||||
version.manage.rename=Переименовать эту версию
|
||||
version.manage.rename=Переименовать
|
||||
version.manage.rename.message=Введите новое название для этой версии
|
||||
version.manage.rename.fail=Не удалось переименовать версию.
|
||||
version.settings=Настройки
|
||||
version.update=Обновить модпак
|
||||
|
||||
wizard.prev=< Назад
|
||||
wizard.failed=Не удалось
|
||||
wizard.finish=Завершено
|
||||
wizard.failed=Невозможно
|
||||
wizard.finish=Завершить
|
||||
wizard.next=Далее >
|
||||
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2021 huangyuhui <[email protected]> and contributors
|
||||
# Copyright (C) 2022 huangyuhui <[email protected]> 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
|
||||
@@ -27,20 +27,21 @@ about.claim.statement=點擊連結以查看全文
|
||||
about.community=社區
|
||||
about.community.discord=Discord
|
||||
about.community.discord.statement=歡迎加入 Discord 討論區,加入後請遵守討論區規定
|
||||
about.community.kaiheila=開黑啦
|
||||
about.community.kaiheila.statement=歡迎加入開黑啦討論區,加入後請遵守討論區規定
|
||||
about.community.kookapp=KOOK
|
||||
about.community.kookapp.statement=歡迎加入 KOOK 討論區,加入後請遵守討論區規定
|
||||
about.dependency=相依元件
|
||||
about.legal=法律聲明
|
||||
about.thanks_to=鳴謝
|
||||
about.thanks_to.bangbang93.statement=提供 BMCLAPI 下載源,請贊助支持 BMCLAPI!
|
||||
about.thanks_to.bangbang93.statement=提供 BMCLAPI 下載地點,請贊助支持 BMCLAPI!
|
||||
about.thanks_to.contributors=所有通過 Issues、Pull Requests 等管道參與本項目的貢獻者
|
||||
about.thanks_to.contributors.statement=沒有開源社區的支持,Hello Minecraft! Launcher 無法走到今天
|
||||
about.thanks_to.gamerteam.statement=提供預設背景圖
|
||||
about.thanks_to.glavo.statement=為 HMCL 貢獻許多技術支持
|
||||
about.thanks_to.mcbbs=MCBBS 我的世界中文論壇
|
||||
about.thanks_to.mcbbs.statement=提供 MCBBS 下載源
|
||||
about.thanks_to.mcbbs.statement=提供 MCBBS 下載地點
|
||||
about.thanks_to.mcmod=MC 百科
|
||||
about.thanks_to.mcmod.statement=提供 Mod 中文名映射表與 Mod 百科
|
||||
about.thanks_to.noin=這裡 (noin.cn)
|
||||
about.thanks_to.noin=阿貓 (mcer.cn)
|
||||
about.thanks_to.noin.statement=提供多人聯機服務 (cato - ioi 系列作品)
|
||||
about.thanks_to.red_lnn.statement=提供預設背景圖
|
||||
about.thanks_to.users=HMCL 用戶群成員
|
||||
@@ -61,8 +62,8 @@ account.create.authlibInjector=添加外置登入帳戶 (authlib-injector)
|
||||
account.email=電子信箱
|
||||
account.failed=錯誤碼: %d
|
||||
account.failed.character_deleted=已刪除此角色
|
||||
account.failed.connect_authentication_server=無法連線至認證伺服器,可能是網路問題
|
||||
account.failed.connect_injector_server=無法連線至認證伺服器,可能是網路故障或網址輸入錯誤
|
||||
account.failed.connect_authentication_server=無法連線至認證伺服器,可能是網路問題,請檢查設備能否正常上網或使用代理服務
|
||||
account.failed.connect_injector_server=無法連線至認證伺服器,可能是網路故障,請檢查設備能否正常上網、檢查網址是否輸入錯誤或使用代理服務
|
||||
account.failed.injector_download_failure=無法下載 authlib-injector,請檢查網路或嘗試切換下載來源
|
||||
account.failed.invalid_credentials=您的使用者名稱或密碼錯誤,或者登入次數過多被暫時禁止登入,請稍後再試
|
||||
account.failed.invalid_password=密碼無效
|
||||
@@ -71,9 +72,11 @@ account.failed.migration=你的帳號需要被遷移至微軟帳號。如果你
|
||||
account.failed.no_character=該帳戶沒有角色
|
||||
account.failed.server_response_malformed=無法解析認證伺服器回應,可能是伺服器故障
|
||||
account.failed.wrong_account=登錄了錯誤的帳號
|
||||
account.hmcl.hint=你需要點擊“登入”按鈕,並在打開的網頁中完成登入
|
||||
account.injector.add=新增認證伺服器
|
||||
account.injector.empty=無 (按一下右側 + 加入)
|
||||
account.injector.http=警告: 此伺服器使用不安全的 HTTP 協定,您的密碼在登入時會被明文傳輸。
|
||||
account.injector.link.homepage=首頁
|
||||
account.injector.link.register=註冊
|
||||
account.injector.server=認證伺服器
|
||||
account.injector.server_url=伺服器位址
|
||||
@@ -86,28 +89,31 @@ account.register=註冊
|
||||
account.manage=帳戶列表
|
||||
account.methods=登入方式
|
||||
account.methods.authlib_injector=authlib-injector 登入
|
||||
account.methods.little_skin=Little Skin
|
||||
account.methods.microsoft=微軟帳戶
|
||||
account.methods.microsoft.birth=如何修改帳戶出生日期
|
||||
account.methods.microsoft.deauthorize=解除帳戶授權
|
||||
account.methods.microsoft.close_page=已完成微軟帳號授權,接下來啟動器還需要完成剩餘登入步驟。你已經可以關閉本頁面了。
|
||||
account.methods.microsoft.error.add_family=由於你未滿 18 歲,你的帳戶必須被加入到家庭中才能登錄遊戲。你也可以點擊上方連結更改你的帳戶的出生日期,使年齡滿 18 歲以上以繼續登錄。
|
||||
account.methods.microsoft.error.add_family_probably=請檢查你的帳戶設置,如果你未滿 18 歲,你的帳戶必須被加入到家庭中才能登錄遊戲。你也可以點擊上方連結更改你的帳戶的出生日期,使年齡滿 18 歲以上以繼續登錄。
|
||||
account.methods.microsoft.error.country_unavailable=你所在的國家或地區不受 XBox Live 的支援。
|
||||
account.methods.microsoft.error.missing_xbox_account=你的微軟帳號尚未關聯 XBox 帳號,你必須先創建 XBox 帳號,才能登入遊戲。
|
||||
account.methods.microsoft.error.no_character=該帳號沒有包含 Minecraft Java 版購買記錄
|
||||
account.methods.microsoft.error.no_character=該帳號沒有包含 Minecraft Java 版購買記錄,
|
||||
account.methods.microsoft.error.unknown=登入失敗,錯誤碼:%d
|
||||
account.methods.microsoft.logging_in=登入中...
|
||||
account.methods.microsoft.hint=您需要點擊登錄按鈕,並在新打開的瀏覽器窗口中完成登錄,並輸入待會在此處顯示的設備代碼。
|
||||
account.methods.microsoft.manual=設備代碼為:<b>%1$s</b>,已經幫你拷貝到剪切板中,如果未能黏貼,你可以點擊此處再複製一次。你需要在新打開的瀏覽器窗口中完成登錄,若登錄頁面未能打開,您手動在瀏覽器中打開網頁:%2$s。
|
||||
account.methods.microsoft.hint=你需要按照以下步驟添加:\n1.點擊“登入”按鈕\n2.在網頁瀏覽器顯示的網站中輸入 HMCL 顯示的代碼(自動拷貝,直接粘貼即可),並點擊“下一步”\n3.按照網站的提示登入\n4.當網站顯示“ 是否允許此應用訪問你的信息?”的標識時,請點擊“是”\n5.當網站提示“大功告成”的標示時,及表示登入成功!然後等待賬戶完成添加即可!\n-若網站提示“出现错误”的標識,請嘗試重新按照以上步驟登入\n-若賬戶添加失敗,請嘗試重新按照以上步驟登入\n<b>-若設備網絡環境不佳,可能登陸網站加載很慢甚至無法加載,此時請使用 VPN 或代理伺服器並重試</b>\n-若登入微軟賬號的 Token 洩露,可點擊下方“解除帳戶授權”解除登入授權,然後等待 Token 過期
|
||||
account.methods.microsoft.manual=你需要按照以下步驟添加:\n1.點擊“登入”按鈕\n2.在網頁瀏覽器顯示的網站中輸入該代碼:<b>%1$s</b>(已自動拷貝,請點此再次拷貝),並點擊“下一步”\n3.按照網站的提示登入\n4.當網站顯示“ 是否允許此應用訪問你的信息?”的標識時,請點擊“是”\n5.當網站提示“大功告成”的標示時,及表示登入成功!然後等待賬戶完成添加即可\n-若網站提示“出现错误”的標識,請嘗試重新按照以上步驟登入\n-若賬戶添加失敗,請嘗試重新按照以上步驟登入\n-若網站未能打開,請手動在網頁瀏覽器中打開:%2$s\n<b>-若設備網絡環境不佳,可能登陸網站加載很慢甚至無法加載,此時請使用 VPN 或代理伺服器並重試</b>\n-若登入微軟賬號的 Token 洩露,可點擊下方“解除帳戶授權”解除登入授權,然後等待 Token 過期
|
||||
account.methods.microsoft.profile=帳戶設置頁
|
||||
account.methods.microsoft.snapshot=HMCL 快照版不支持微软登录
|
||||
account.methods.microsoft.snapshot=HMCL 快照版暫不提供微軟登入支持,請使用穩定版或測試版進行登入。
|
||||
account.methods.microsoft.waiting_browser=等待在新打開的瀏覽器窗口中完成登入...
|
||||
account.methods.offline=離線模式
|
||||
account.methods.offline.uuid=UUID
|
||||
account.methods.offline.uuid.hint=UUID 是 Minecraft 對玩家角色的唯一標識符,每個啟動器生成 UUID 的方式可能不同。通過修改 UUID 選項至原啟動器所生成的 UUID,你可以保證在切換啟動器後,遊戲還能將你的遊戲角色識別為給定 UUID 所對應的角色,從而保留原來角色的背包物品。UUID 選項為高級選項,除非你知道你在做什麼,否則你不需要調整該選項。
|
||||
account.methods.offline.uuid.malformed=格式錯誤
|
||||
account.methods.yggdrasil=正版登入
|
||||
account.methods.yggdrasil=Mojang 登入
|
||||
account.methods.yggdrasil.migration=遷移至微軟帳戶
|
||||
account.methods.yggdrasil.migration.how=如何遷移至微軟帳戶?
|
||||
account.methods.yggdrasil.migration.hint=自 2022 年 3 月 10 日起,Mojang 帳戶不再受支持,您必須遷移至微軟帳戶才能登入 Minecraft。\n下方連結將指引您將 Mojang 帳戶遷移至微軟帳戶。
|
||||
account.methods.yggdrasil.purchase=購買 Minecraft
|
||||
account.missing=沒有遊戲帳戶
|
||||
account.missing.add=按一下此處加入帳戶
|
||||
@@ -166,7 +172,7 @@ curse.category.0=全部
|
||||
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/4471
|
||||
curse.category.4474=科幻
|
||||
curse.category.4481=輕量整合包
|
||||
curse.category.4481=輕量模組包
|
||||
curse.category.4483=戰鬥 PVP
|
||||
curse.category.4477=小遊戲
|
||||
curse.category.4478=任務
|
||||
@@ -174,14 +180,22 @@ curse.category.4484=多人
|
||||
curse.category.4476=探索
|
||||
curse.category.4736=空島
|
||||
curse.category.4475=冒險 RPG
|
||||
curse.category.4487=FTB 整合包
|
||||
curse.category.4487=FTB 模組包
|
||||
curse.category.4480=有特定地圖
|
||||
curse.category.4479=高難度
|
||||
curse.category.4482=大型整合包
|
||||
curse.category.4482=大型模組包
|
||||
curse.category.4472=科技
|
||||
curse.category.4473=魔法
|
||||
curse.category.5128=原版增強
|
||||
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/6
|
||||
curse.category.5299=教育
|
||||
curse.category.5232=額外行星
|
||||
curse.category.5129=原版增強
|
||||
curse.category.5189=實用與QOL
|
||||
curse.category.5190=QoL
|
||||
curse.category.5191=實用與QoL
|
||||
curse.category.5192=夢幻菜單
|
||||
curse.category.423=訊息展示
|
||||
curse.category.426=模組擴展
|
||||
curse.category.434=裝備武器
|
||||
@@ -224,6 +238,8 @@ curse.category.4843=自動化
|
||||
curse.category.4906=MCreator
|
||||
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/6
|
||||
curse.category.5244=字體包
|
||||
curse.category.5193=封包
|
||||
curse.category.399=蒸汽朋克
|
||||
curse.category.396=128x
|
||||
curse.category.398=512x 及更高
|
||||
@@ -284,13 +300,14 @@ download.provider.mojang=官方伺服器 (OptiFine 自動安裝的下載來源
|
||||
download.provider.official=儘量使用官方源(最新,但可能加載慢)
|
||||
download.provider.balanced=選擇加載速度快的下載源(平衡,但可能不是最新)
|
||||
download.provider.mirror=儘量使用鏡像源(加載快,但可能不是最新)
|
||||
download.java=下載 Java
|
||||
download.javafx=正在下載必要的運行時組件
|
||||
download.javafx.notes=正在通過網絡下載 HMCL 必要的運行時組件。\n點擊“切換下載源”按鈕查看詳情以及選擇下載源,點擊“取消”按鈕停止並退出。\n注意:如果下載速度過慢,請嘗試切换下載源。
|
||||
download.javafx.component=正在下載模塊 %s
|
||||
download.javafx.prepare=準備開始下載
|
||||
|
||||
exception.access_denied=因為無法訪問文件 %s,HMCL 沒有對該文件的訪問權限,或者該文件被其他程序打開。\n\
|
||||
請你檢查當前操作系統帳戶是否能訪問該文件,比如非管理員用戶可能不能訪問其他帳戶的個人文件夾內的文件。\n\
|
||||
exception.access_denied=因為無法存取檔案 %s,HMCL 沒有對該文件的訪問權限,或者該文件被其他程序打開。\n\
|
||||
請你檢查當前操作系統帳戶是否能訪存取檔案,比如非管理員用戶可能不能訪問其他帳戶的個人資料夾內的文件。\n\
|
||||
對於 Windows 用戶,你還可以嘗試通過資源監視器查看是否有程序占用了該文件,如果是,你可以關閉占用此文件相關程序,或者重啟電腦再試。
|
||||
exception.artifact_malformed=下載的文件正確,無法通過校驗。
|
||||
exception.ssl_handshake=無法建立 SSL 連接,因為當前 Java 虛擬機缺少相關的 SSL 證書。你可以嘗試使用其他的 Java 虛擬機啟動 HMCL 再試。
|
||||
@@ -302,12 +319,15 @@ extension.ps1=PowerShell 指令碼
|
||||
extension.sh=Bash 指令碼
|
||||
|
||||
fatal.javafx.incompatible=缺少 JavaFX 運行環境。\nHMCL 無法在低於 Java 11 且缺少 JavaFX 的 Java 環境上自行補全 JavaFX 運行環境。\n請更換如 Oracle Java 8 等包含 JavaFX 的 Java 運行環境,或者更新到 Java 11 或更高版本。
|
||||
fatal.javafx.incompatible.loongson=缺少 JavaFX 運行環境。\n請使龍芯 JDK 8 (http://www.loongnix.cn/zh/api/java/downloads-jdk8/index.html) 啟動 HMCL。
|
||||
fatal.javafx.missing=找不到 JavaFX。\n如果您使用的是 Java 11 或更高版本,請降級到 Oracle JRE 8,或者安裝 BellSoft Liberica Full JRE。\n如果您使用的是其他 OpenJDK 构建,請確保其包含 OpenJFX。
|
||||
fatal.missing_dst_root_ca_x3=目前的 Java 平台缺少 DST Root CA X3 憑證。\n您依然可以使用 Hello Minecraft! Launcher,但會無法連線到部分網站 (如使用 Lets Encrypt 憑證的站台),這可能會使 Hello Minecraft! Launcher 無法正常運作。\n請將您的 Java 升級到 8u101 以上來解決此問題。
|
||||
fatal.config_loading_failure=Hello Minecraft! Launcher 無法載入設定檔案。\n請確保 Hello Minecraft! Launcher 對 "%s" 目錄及該目錄下的檔案擁有讀寫權限。
|
||||
fatal.migration_requires_manual_reboot=Hello Minecraft! Launcher 即將升級完成,請重新開啟 Hello Minecraft! Launcher。
|
||||
fatal.apply_update_failure=我們很抱歉 Hello Minecraft! Launcher 無法自動完成升級程式,因為出現了一些問題。\n但你依然可以從 %s 處手動下載 Hello Minecraft! Launcher 來完成升級。\n請考慮向我們回報該問題。
|
||||
fatal.samba=如果您正在通過 Samba 共亯的資料夾中運行 HMCL,HMCL 可能無法正常工作,請嘗試更新您的 Java 或在本地資料夾內運行 HMCL。
|
||||
fatal.illegal_char=您的用戶資料夾路徑中存在非法字元‘=’。HMCL 能够運行,但一些功能無法正常使用。 \n您將無法使用外置登入帳戶以及離線登入更換皮膚功能,並且在進行多人遊戲時可能存在更大的安全隱患。
|
||||
fatal.unsupported_platform=Minecraft 尚未你您的平臺提供完善支持,所以可能影響遊戲體驗或無法啟動遊戲。\n若無法啟動 Minecraft 1.17 及以上版本,可以嘗試在版本設定中打開“使用 OpenGL 軟渲染器”選項,\n使用 CPU 渲染以獲得更好的相容性。
|
||||
|
||||
feedback=回饋
|
||||
feedback.add=新增回饋
|
||||
@@ -361,10 +381,10 @@ game.crash.reason.fabric_warnings=Fabric 提供了一些警告訊息:\n%1$s
|
||||
game.crash.reason.file_already_exists=當前遊戲因為文件 %1$s 已經存在,無法繼續運行。\n如果你認為這個文件可以刪除,你可以在備份這個文件後嘗試刪除它,並重新啟動遊戲。
|
||||
game.crash.reason.file_changed=當前遊戲因為檔案校驗失敗,無法繼續運行。\n如果你手動修改了 Minecraft.jar 檔案,你需要回退修改,或者重新下載遊戲。
|
||||
game.crash.reason.gl_operation_failure=當前遊戲因為你使用的某些 Mod、光影包、材質包,無法繼續運行。\n請先嘗試禁用你所使用的Mod/光影包/材質包再試。
|
||||
game.crash.reason.graphics_driver=當前遊戲因為你的顯示卡驅動存在問題崩潰了。\n\
|
||||
請嘗試升級你的顯示卡驅動到最新版本後再嘗試啟動遊戲。\n\
|
||||
game.crash.reason.graphics_driver=當前遊戲因為你的顯示卡驅動存在問題崩潰了,請嘗試升級你的顯示卡驅動到最新版本後再嘗試啟動遊戲。\n\
|
||||
如果你的電腦存在獨立顯示卡,你需要檢查遊戲是否使用集成/核芯顯示卡啟動。如果是,請嘗試使用獨立顯示卡啟動 HMCL 與遊戲。如果仍有問題,你可能需要考慮換一個新顯示卡或新電腦。\n\
|
||||
如果你確實需要使用核芯顯示卡,請檢查你的電腦的 CPU 是否是 Intel(R) Core(TM) 3000 系列或更舊的處理器,如果是,對於 Minecraft 1.16.5 及更舊版本,請你將遊戲所使用的 Java 版本降級至 1.8.0_51 及以下版本,否則你必須更換獨立顯示卡或新電腦。\n\
|
||||
如果你確實需要使用核芯顯示卡,請檢查你的電腦的 CPU 是否是 Intel(R) Core(TM) 3000 系列或更舊的處理器,如果是,對於 Minecraft 1.16.5 及更舊版本,請你將遊戲所使用的 Java 版本降級至 1.8.0_51 及以下版本,否則你需要更換獨立顯示卡或新電腦。\n\
|
||||
在版本設定中打開“使用 OpenGL 軟渲染器”選項也可以解决此問題,但打開此選項後在 CPU 效能不足的情况下幀數會顯著降低,僅推薦以調試為目的或應急時開啟。\n\
|
||||
詳情請點擊 <a href="https://hmcl.huangyuhui.net/help/game/graphics-card">此處</a> 查看
|
||||
game.crash.reason.illegal_access_error=當前遊戲因為某些 Mod 的問題,無法繼續運行。\n如果你認識:%1$s,你可以更新或刪除對應 Mod 再試。
|
||||
game.crash.reason.jdk_9=當前遊戲因為 Java 版本過高,無法繼續運行。\n你需要下載安裝 Java 8,並在遊戲設置中將 Java 設置為 1.8 的版本。
|
||||
@@ -380,6 +400,7 @@ game.crash.reason.mod_resolution_missing=當前遊戲因為缺少 Mod 前置,
|
||||
game.crash.reason.mod_resolution_missing_minecraft=當前遊戲因為 Mod 和 Minecraft 遊戲版本不匹配,無法繼續運行。\n%1$s 需要 Minecraft %2$s 才能運行。\n如果你要繼續使用你已經安裝的 Mod,你可以選擇安裝對應的 Minecraft 版本;如果你要繼續使用當前 Minecraft 版本,你需要安裝對應版本的 Mod。
|
||||
game.crash.reason.mod_resolution_mod_version=%1$s (版本號 %2$s)
|
||||
game.crash.reason.mod_resolution_mod_version.any=%1$s (任意版本)
|
||||
game.crash.reason.modlauncher_8=當前遊戲因為您所使用的 Forge 版本與當前使用的 Java 衝突崩潰。請嘗試更新 Forge,或使用 Java 8u312/11.0.13/17.0.1 及更早版本啟動。
|
||||
game.crash.reason.no_class_def_found_error=當前遊戲因為代碼不完整,無法繼續運行。\n你的遊戲可能缺失了某個 Mod,或者某些 Mod 檔案不完整,或者 Mod 與遊戲的版本不匹配。\n你可能需要重新安裝遊戲和 Mod,或請求他人幫助。\n缺失:%1$s
|
||||
game.crash.reason.no_such_method_error=當前遊戲因為代碼不完整,無法繼續運行。\n你的遊戲可能缺失了某個 Mod,或者某些 Mod 檔案不完整,或者 Mod 與遊戲的版本不匹配。\n你可能需要重新安裝遊戲和 Mod,或請求他人幫助。
|
||||
game.crash.reason.opengl_not_supported=當前遊戲因為你的顯示卡驅動存在問題,無法繼續運行。\n原因是 OpenGL 不受支援,你現在是否在遠程桌面或者串流模式下?如果是,請直接使用原電腦啟動遊戲。\n或者嘗試升級你的顯示卡驅動到最新版本後再嘗試啟動遊戲。如果你的電腦存在獨立顯示卡,你需要檢查遊戲是否使用集成/核心顯示卡啟動,如果是,請嘗試使用獨立顯示卡啟動 HMCL 與遊戲。如果仍有問題,你可能需要考慮換一個新顯示卡或新電腦。
|
||||
@@ -388,7 +409,7 @@ game.crash.reason.out_of_memory=當前遊戲因為記憶體不足,無法繼續
|
||||
game.crash.reason.resolution_too_high=當前遊戲因為材質包解析度過高,無法繼續運行\n你可以更換一個解析度更低的材質,或者更換一個視訊記憶體更大的顯示卡。
|
||||
game.crash.reason.stacktrace=原因未知,請點擊日誌按鈕查看詳細訊息。\n下面是一些關鍵字,其中可能包含 Mod 名稱,你可以透過搜索的方式查找有關訊息。\n%s
|
||||
game.crash.reason.too_old_java=當前遊戲因為 Java 虛擬機版本過低,無法繼續運行。\n你需要在遊戲設置中更換 %1$s 或更新版本的 Java 虛擬機,並重新啟動遊戲。如果沒有下載安裝,你可以點擊 <a href="https://docs.microsoft.com/zh-TW/java/openjdk/download">此處</a> 下載微軟 JDK。
|
||||
game.crash.reason.unknown=原因未知,請點擊日誌按鈕查看詳細訊息。\n不要將本界面截圖給他人!如果你要求助他人,請你點擊左下角導出遊戲崩潰訊息後將導出的文件發送給他人以供分析!\n你可以向我們提供遊戲崩潰日誌以提升 HMCL 的日誌分析能力。
|
||||
game.crash.reason.unknown=原因未知,請點擊日誌按鈕查看詳細訊息。\n<b>不要將本界面截圖給他人!</b>如果你要求助他人,請你點擊左下角導出遊戲崩潰訊息後將導出的文件發送給他人以供分析!\n你可以向我們提供遊戲崩潰日誌以提升 HMCL 的日誌分析能力。
|
||||
game.crash.reason.unsatisfied_link_error=當前遊戲因為缺少本地庫,無法繼續運行。\n這些本地庫缺失:%1$s。\n如果你在遊戲設置中修改了本地庫路徑選項,請你修改回預設模式。\n如果已經在預設模式下,請檢查本地庫缺失是否是 Mod 引起的,或由 HMCL 引起的。如果你確定是 HMCL 引起的,建議你向我們回饋。\n如果你確實需要自訂本地庫路徑,你需要保證其中包含缺失的本地庫。
|
||||
game.crash.title=遊戲意外退出
|
||||
game.directory=遊戲路徑
|
||||
@@ -396,7 +417,7 @@ game.version=遊戲版本
|
||||
|
||||
help=說明
|
||||
help.doc=Hello Minecraft! Launcher 說明檔案
|
||||
help.detail=可查閱資料包、整合包製作指南等內容。
|
||||
help.detail=可查閱資料包、模組包製作指南等內容。
|
||||
|
||||
input.email=[使用者名稱] 必須是電子信箱格式
|
||||
input.number=必須是數字
|
||||
@@ -433,8 +454,10 @@ install.installer.install_online.tooltip=支援安裝 Fabric、Forge、OptiFine
|
||||
install.installer.liteloader=LiteLoader
|
||||
install.installer.not_installed=不安裝
|
||||
install.installer.optifine=OptiFine
|
||||
install.installer.quilt=Quilt
|
||||
install.installer.quilt-api=Quilt API
|
||||
install.installer.version=%s
|
||||
install.modpack=安裝整合包
|
||||
install.modpack=安裝模組包
|
||||
install.new_game=安裝新遊戲版本
|
||||
install.new_game.already_exists=此版本已經存在,請重新命名
|
||||
install.new_game.current_game_version=目前遊戲版本
|
||||
@@ -452,16 +475,17 @@ launch.advice.uncorrected=如果您確實希望使用您自訂的 Java 虛擬機
|
||||
launch.advice.different_platform=您的系統為 64 位元,但是 Java 是 32 位元,建議您安裝 64 位 Java。
|
||||
launch.advice.forge2760_liteloader=Forge 2760 與 LiteLoader 不相容。請刪除 LiteLoader 或者變更 Forge 至 2773 或更新的版本。是否繼續啟動?
|
||||
launch.advice.forge28_2_2_optifine=Forge 28.2.2 或更高版本與 OptiFine 不相容。請變更 Forge 至 28.2.1 或更低版本。是否繼續啟動?
|
||||
launch.advice.forge37_0_60=Forge 低於 37.0.60 的版本不相容 Java 17。請更新 Forge 到 37.0.60 或更高版本,或者使用 Java 16 啟動遊戲。是否繼續啟動?
|
||||
launch.advice.java8_1_13=Minecraft 1.13 只支援 Java 8 或更高版本,請使用 Java 8 或最新版本。
|
||||
launch.advice.java8_51_1_13=低於 1.8.0_51 的 Java 版本可能會導致 Minecraft 1.13 崩潰。建議您到 https://java.com 安裝最新版的 Java 8。
|
||||
launch.advice.java9=低於 (包含) 1.13 的有安裝 Mod 的 Minecraft 版本不支援 Java 9 或更高版本,請使用 Java 8。
|
||||
launch.advice.modlauncher8=您所使用的 Forge 版本與當前使用的 Java 不相容。請嘗試更新 Forge,或使用 Java 8u312/11.0.13/17.0.1 及更早版本啟動。是否繼續啟動?
|
||||
launch.advice.newer_java=偵測到您未使用 Java 8 及更新版本,Java 8 能使遊戲更順暢而且 Minecraft 1.12 及更新版本和很多 Mod 強制需要 Java 8 版本。
|
||||
launch.advice.not_enough_space=您設定的記憶體大小過大,由於超過了系統記憶體大小 %dMB,所以可能影響遊戲體驗或無法啟動遊戲。是否繼續啟動?
|
||||
launch.advice.require_newer_java_version=Minecraft %1$s 僅能運行在 Java %2$s 或更高版本上,是否下載?
|
||||
launch.advice.require_newer_java_version=Minecraft %1$s 僅能運行在 Java %2$s 或更高版本上,但 HMCL 未能找到該 Java 版本,你可以點擊“是” ,HMCL 會自動下載他,是否下載?
|
||||
launch.advice.too_large_memory_for_32bit=您設定的記憶體大小過大,由於可能超過了 32 位元 Java 的記憶體分配限制,所以可能無法啟動遊戲,請將記憶體調至低於 1024MB 的值。是否繼續啟動?
|
||||
launch.advice.vanilla_linux_java_8=對於 Linux x86-64 平台,Minecraft 1.12.2 及以下版本僅支持 Java 8。\nJava 9+ 版本會無法載入 32 位的 liblwjgl.so。
|
||||
launch.advice.vanilla_x86=Minecraft 官方尚未提供對非 x86-64 平臺的支持。\n您可以嘗試下載您所用平臺對應的本機庫,在遊戲設定的調試選項中指定其路徑後再嘗試運行。
|
||||
launch.advice.vanilla_x86.translation=Minecraft 官方尚未提供對非 x86-64 平臺的支持。\n請使用 x86-64 平臺的 Java 通過轉譯執行,或者嘗試下載您所用平臺對應的本機庫,在遊戲設定的調試選項中指定其路徑後再嘗試運行。
|
||||
launch.advice.vanilla_linux_java_8=對於 Linux x86-64 平台,Minecraft 1.12.2 及以下版本僅支援 Java 8。\nJava 9+ 版本會無法載入 32 位的 liblwjgl.so。
|
||||
launch.advice.vanilla_x86.translation=Minecraft 尚未為您的平臺提供完善支持,所以可能影響遊戲體驗或無法啟動遊戲。\n你可以在 <a href="https://docs.microsoft.com/java/openjdk/download"> 這裡 </a>下載<b> X86-64 </b> 架構的 Java 以獲得更完整的體驗。\n是否繼續啟動?
|
||||
launch.failed=啟動失敗
|
||||
launch.failed.cannot_create_jvm=偵測到無法建立 Java 虛擬機,可能是 Java 參數有問題。可以在設定中開啟無參數模式啟動。
|
||||
launch.failed.creating_process=啟動失敗,在建立新處理程式時發生錯誤。可能是 Java 路徑錯誤。
|
||||
@@ -473,7 +497,7 @@ launch.failed.execution_policy=設定執行策略
|
||||
launch.failed.execution_policy.failed_to_set=設定執行策略失敗
|
||||
launch.failed.execution_policy.hint=當前執行策略封锁您執行 PowerShell 腳本。\n點擊“確定”允許當前用戶執行本地 PowerShell 腳本,或點擊“取消”保持現狀。
|
||||
launch.failed.exited_abnormally=遊戲非正常退出,請查看記錄檔案,或聯絡他人尋求幫助。
|
||||
launch.failed.no_accepted_java=找不到適合當前遊戲使用的 Java。如果您認為實際存在合適的 Java,您可以在遊戲設置中手動設置 Java。
|
||||
launch.failed.no_accepted_java=找不到適合當前遊戲使用的 Java,是否使用默認 Java 啟動遊戲?點擊“是”使用默認 Java 繼續啟動遊戲,\n或者請到遊戲設定中選擇一個合適的Java虛擬機器版本。
|
||||
launch.state.dependencies=處理遊戲相依元件
|
||||
launch.state.done=啟動完成
|
||||
launch.state.java=檢測 Java 版本
|
||||
@@ -528,20 +552,21 @@ message.success=完成
|
||||
message.unknown=未知
|
||||
message.warning=警告
|
||||
|
||||
modpack=整合包
|
||||
modpack.choose=選擇要安裝的遊戲整合包檔案
|
||||
modpack.choose.local=匯入本機整合包檔案
|
||||
modpack.choose.local.detail=你可以直接將整合包檔案拖入本頁面以安裝
|
||||
modpack.choose.remote=從網路下載整合包
|
||||
modpack.choose.remote.detail=需要提供整合包的下載連結
|
||||
modpack.choose.remote.tooltip=要下載的整合包的連結
|
||||
modpack.desc=描述你要製作的整合包,比如整合包注意事項和更新記錄,支援 Markdown(圖片請上傳至網路)。
|
||||
modpack.description=整合包描述
|
||||
modpack.download=下載整合包
|
||||
modpack=模組包
|
||||
modpack.choose=選擇要安裝的遊戲模組包檔案
|
||||
modpack.choose.local=匯入本機模組包檔案
|
||||
modpack.choose.local.detail=你可以直接將模組包檔案拖入本頁面以安裝
|
||||
modpack.choose.remote=從網路下載模組包
|
||||
modpack.choose.remote.detail=需要提供模組包的下載連結
|
||||
modpack.choose.remote.tooltip=要下載的模組包的連結
|
||||
modpack.completion=下載模組包相關檔案
|
||||
modpack.desc=描述你要製作的模組包,比如模組包注意事項和更新記錄,支援 Markdown(圖片請上傳至網路)。
|
||||
modpack.description=模組包描述
|
||||
modpack.download=下載模組包
|
||||
modpack.enter_name=給遊戲取個你喜歡的名稱
|
||||
modpack.export=匯出整合包
|
||||
modpack.export.as=請選擇整合包類型。若你無法決定,請選擇 HMCL 類型。
|
||||
modpack.file_api=整合包下載連結前綴
|
||||
modpack.export=匯出模組包
|
||||
modpack.export.as=請選擇模組包類型。若你無法決定,請選擇 HMCL 類型。
|
||||
modpack.file_api=模組包下載連結前綴
|
||||
modpack.files.blueprints=BuildCraft 藍圖
|
||||
modpack.files.config=Mod 模組設定檔案
|
||||
modpack.files.dumps=NEI 調校輸出
|
||||
@@ -555,59 +580,70 @@ modpack.files.resourcepacks=資源包 (材質包)
|
||||
modpack.files.saves=遊戲存檔
|
||||
modpack.files.scripts=MineTweaker 設定
|
||||
modpack.files.servers_dat=多人遊戲伺服器列表
|
||||
modpack.install=安裝 %s 整合包
|
||||
modpack.installing=正在安裝整合包
|
||||
modpack.introduction=支援 Curse、MultiMC、HMCL 整合包。
|
||||
modpack.invalid=無效的整合包升級檔案,可能是下載時出現問題。
|
||||
modpack.mismatched_type=整合包類型不符,目前遊戲是 %s 整合包,但是提供的整合包更新檔案是 %s 整合包。
|
||||
modpack.name=整合包名稱
|
||||
modpack.not_a_valid_name=整合包名稱無效
|
||||
modpack.install=安裝 %s 模組包
|
||||
modpack.installing=正在安裝模組包
|
||||
modpack.introduction=支援 Curse、MultiMC、HMCL 模組包。
|
||||
modpack.invalid=無效的模組包升級檔案,可能是下載時出現問題。
|
||||
modpack.mismatched_type=模組包類型不符,目前遊戲是 %s 模組包,但是提供的模組包更新檔案是 %s 模組包。
|
||||
modpack.name=模組包名稱
|
||||
modpack.not_a_valid_name=模組包名稱無效
|
||||
modpack.origin=來源
|
||||
modpack.origin.url=官方網站
|
||||
modpack.origin.mcbbs=MCBBS
|
||||
modpack.origin.mcbbs.prompt=貼子 id
|
||||
modpack.scan=解析整合包
|
||||
modpack.task.install=匯入整合包
|
||||
modpack.task.install.error=無法識別該整合包,目前僅支援匯入 Curse、MultiMC、HMCL 整合包。
|
||||
modpack.task.install.will=將會安裝整合包:
|
||||
modpack.scan=解析模組包
|
||||
modpack.task.install=匯入模組包
|
||||
modpack.task.install.error=無法識別該模組包,目前僅支援匯入 Curse、Modrinth、MultiMC、HMCL 模組包。
|
||||
modpack.task.install.will=將會安裝模組包:
|
||||
modpack.type.curse=Curse
|
||||
modpack.type.curse.completion=下載 Curse 整合包相關檔案
|
||||
modpack.type.curse.tolerable_error=但未能完成 Curse 整合包檔案的下載,您可以在啟動該遊戲版本時繼續 Curse 整合包檔案的下載。由於網路問題,您可能需要重試多次。
|
||||
modpack.type.curse.error=無法完成 Curse 整合包的下載,請多次重試或設定代理
|
||||
modpack.type.curse.not_found=部分必需檔案已經從網路中被刪除並且再也無法下載,請嘗試該整合包的最新版本或者安裝其他整合包。
|
||||
modpack.type.manual.warning=您大概需要直接解压该整合包,并使用其自带的启动器即可开始游戏,而不需要导入整合包。该整合包不是由启动器导出的整合包,而是人工打包 .minecraft 资料夹而来的,这类整合包通常附带游戏启动器。HMCL 可以尝试导入该整合包,但不保证可用性,是否继续?
|
||||
modpack.type.mcbbs=我的世界中文論壇整合包標準
|
||||
modpack.type.curse.tolerable_error=但未能完成該模組包檔案的下載,您可以在啟動該遊戲版本時繼續該模組包檔案的下載。由於網路問題,您可能需要重試多次……
|
||||
modpack.type.curse.error=無法完成該模組包所需的依賴下載,請多次重試或設定代理……
|
||||
modpack.type.curse.not_found=部分必需檔案已經從網路中被刪除並且再也無法下載,請嘗試該模組包的最新版本或者安裝其他模組包。
|
||||
modpack.type.manual.warning=您大概需要直接解压该模組包,并使用其自带的启动器即可开始游戏,而不需要导入模組包。该模組包不是由启动器导出的模組包,而是人工打包 .minecraft 资料夹而来的,这类模組包通常附带游戏启动器。HMCL 可以尝试导入该模組包,但不保证可用性,是否继续?
|
||||
modpack.type.mcbbs=我的世界中文論壇模組包標準
|
||||
modpack.type.mcbbs.export=可以被 Hello Minecraft! Launcher (HMCL) 和 MultiMC 匯入
|
||||
modpack.type.modrinth=Modrinth
|
||||
modpack.type.multimc=MultiMC
|
||||
modpack.type.multimc.export=可以被 Hello Minecraft! Launcher (HMCL) 和 MultiMC 匯入
|
||||
modpack.type.server=伺服器自動更新整合包
|
||||
modpack.type.server=伺服器自動更新模組包
|
||||
modpack.type.server.export=允許伺服器管理員遠端更新遊戲用戶端
|
||||
modpack.type.server.malformed=伺服器整合包配置格式錯誤,請聯絡伺服器管理員解決此問題
|
||||
modpack.unsupported=不支援該整合包。僅 HMCL、MultiMC、Curse 整合包受支援。
|
||||
modpack.update=正在升級整合包
|
||||
modpack.wizard=匯出整合包引導
|
||||
modpack.type.server.malformed=伺服器模組包配置格式錯誤,請聯絡伺服器管理員解決此問題
|
||||
modpack.unsupported=不支援該模組包。僅 HMCL、MultiMC、Curse 模組包受支援。
|
||||
modpack.update=正在升級模組包
|
||||
modpack.wizard=匯出模組包引導
|
||||
modpack.wizard.step.1=基本設定
|
||||
modpack.wizard.step.1.title=設定整合包的主要訊息
|
||||
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.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.force_update=強制升級模組包至最新版本(需要自建伺服器)
|
||||
modpack.wizard.step.initialization.include_launcher=包含啟動器
|
||||
modpack.wizard.step.initialization.save=選擇要匯出到的遊戲整合包位置
|
||||
modpack.wizard.step.initialization.warning=在製作整合包前,請您確認您選擇的版本可以正常啟動,\n並保證您的 Minecraft 是正式版而非快照版,\n而且不應將不允許非官方途徑傳播的 Mod 模組、材質包等納入整合包。\n整合包會儲存您目前的下載來源設定
|
||||
modpack.wizard.step.initialization.server=點選此處查看有關伺服器自動更新整合包的製作教學
|
||||
modpack.wizard.step.initialization.save=選擇要匯出到的遊戲模組包位置
|
||||
modpack.wizard.step.initialization.warning=在製作模組包前,請您確認您選擇的版本可以正常啟動,\n並保證您的 Minecraft 是正式版而非快照版,\n而且不應將不允許非官方途徑傳播的 Mod 模組、材質包等納入模組包。\n模組包會儲存您目前的下載來源設定
|
||||
modpack.wizard.step.initialization.server=點選此處查看有關伺服器自動更新模組包的製作教學
|
||||
|
||||
modrinth.category.adventure=冒險
|
||||
modrinth.category.challenging=高難度
|
||||
modrinth.category.combat=戰鬥
|
||||
modrinth.category.cursed=Cursed
|
||||
modrinth.category.decoration=裝飾
|
||||
modrinth.category.equipment=裝備
|
||||
modrinth.category.fabric=Fabric
|
||||
modrinth.category.food=食物
|
||||
modrinth.category.forge=Forge
|
||||
modrinth.category.kitchen-sink=大雜燴
|
||||
modrinth.category.library=支持庫
|
||||
modrinth.category.lightweight=輕量
|
||||
modrinth.category.magic=魔法
|
||||
modrinth.category.misc=其他
|
||||
modrinth.category.modloader=Modloader
|
||||
modrinth.category.multiplayer=多人
|
||||
modrinth.category.optimization=最佳化
|
||||
modrinth.category.quests=任務
|
||||
modrinth.category.quilt=Quilt
|
||||
modrinth.category.rift=Rift
|
||||
modrinth.category.storage=儲存
|
||||
modrinth.category.technology=科技
|
||||
modrinth.category.utility=實用
|
||||
@@ -620,8 +656,8 @@ mods.add.success=成功新增模組 %s。
|
||||
mods.category=類別
|
||||
mods.check_updates=檢查模組更新
|
||||
mods.check_updates.current_version=當前版本
|
||||
mods.check_updates.failed=部分文件下載失敗
|
||||
mods.check_updates.file=文件
|
||||
mods.check_updates.failed=部分檔案下載失敗
|
||||
mods.check_updates.file=檔案
|
||||
mods.check_updates.source=來源
|
||||
mods.check_updates.target_version=目標版本
|
||||
mods.check_updates.update=更新
|
||||
@@ -646,12 +682,12 @@ mods.url=官方頁面
|
||||
multiplayer=多人聯機
|
||||
multiplayer.agreement.prompt=使用多人聯機功能前,你需要先同意多人聯機服務提供方 ioi 系列作品的用戶協議與免責聲明。\n用戶使用聯機服務的行為,將被視為無條件同意該用戶協議與免責聲明。
|
||||
multiplayer.download=正在下載相依元件
|
||||
multiplayer.download.failed=初始化失敗,部分文件未能完成下載
|
||||
multiplayer.download.failed=初始化失敗,部分檔案未能完成下載
|
||||
multiplayer.download.success=多人聯機初始化完成
|
||||
multiplayer.download.unsupported=多人聯機依賴不支持當前系統或平台
|
||||
multiplayer.exit.after_ready=多人聯機會話意外退出,退出碼 %d
|
||||
multiplayer.exit.before_ready=多人聯機房間創建失敗,cato 退出碼 %d
|
||||
multiplayer.exit.timeout.static_token=無法連接多人聯機服務,你的憑證可能無法正常工作,你可以使用空憑證再試。
|
||||
multiplayer.exit.timeout.static_token=無法連接多人聯機服務,你的憑證可能無法正常工作,你可以清空憑證再試。\n你可以聯系在線管理員請問鑑權伺服器是否正常工作,或者耐心等待十分鍾。
|
||||
multiplayer.exit.timeout.dynamic_token=無法連接多人聯機服務,你可以在多人聯機頁面的回饋中回饋問題。
|
||||
multiplayer.hint=多人聯機功能處於實驗階段,如果有問題請回饋。
|
||||
multiplayer.nat=網路檢測
|
||||
@@ -670,10 +706,10 @@ multiplayer.nat.type.restricted_cone=中(受限圓錐型)
|
||||
multiplayer.nat.type.symmetric=差(對稱型)
|
||||
multiplayer.nat.type.symmetric_udp_firewall=差(對稱型+防火牆)
|
||||
multiplayer.nat.type.unknown=未知
|
||||
multiplayer.powered_by=多人聯機服務由 這裡 (<a href="https://noin.cn">noin.cn</a>) 提供。<a href="https://noin.cn/agreement">用戶協議與免責聲明</a>
|
||||
multiplayer.powered_by=多人聯機服務由(<a href="https://mcer.cn">mcer.cn</a>) 提供。<a href="https://hmcl.huangyuhui.net/api/redirect/multiplayer-agreement">用戶協議與免責聲明</a>
|
||||
multiplayer.report=違法違規檢舉
|
||||
multiplayer.relay=橋接服務
|
||||
multiplayer.relay.hint=Cato 面向較差網路情況提供的網路質量增強最佳化解決方案。若無法聯機請到 這裡 的個人中心兌換憑證後啟用。
|
||||
multiplayer.relay.hint=Cato 面向較差網路提供的網路質量增強最佳化解決方案|若無法聯機請到 mcer.cn 的個人中心兌換憑證後啟用
|
||||
multiplayer.session=房間
|
||||
multiplayer.session.name.format=%1$s 的房間
|
||||
multiplayer.session.name.motd=HMCL 多人聯機房間 - %s
|
||||
@@ -682,7 +718,7 @@ multiplayer.session.close.warning=關閉房間後,已經加入聯機房間的
|
||||
multiplayer.session.copy_room_code=複製邀請碼
|
||||
multiplayer.session.create=創建房間
|
||||
multiplayer.session.create.error.dynamic_token=創建聯機房間失敗,請稍後再試。
|
||||
multiplayer.session.create.error.static_token=創建聯機房間失敗,你的憑證可能無法正常工作,你可以使用空憑證再試。
|
||||
multiplayer.session.create.error.static_token=創建聯機房間失敗,你的憑證可能無法正常工作,你可以清空憑證再試。\n你可以聯系在線管理員請問鑑權伺服器是否正常工作,或者耐心等待十分鍾。
|
||||
multiplayer.session.create.hint=創建聯機房間前,你需要先在正在運行的遊戲內的遊戲菜單中選擇 對區域網路開放 選項,然後在下方的輸入框中輸入遊戲內提示的埠號(通常是 5 位的數字)
|
||||
multiplayer.session.create.join=連接申請
|
||||
multiplayer.session.create.join.allow=自動接受所有連接申請(不啟用此選項時,你需要手動同意申請,以避免不相關人士誤連你的伺服器)
|
||||
@@ -695,7 +731,7 @@ multiplayer.session.create.port=埠號
|
||||
multiplayer.session.create.port.error=無法檢測遊戲埠號,你必須先啟動遊戲並在遊戲內打開對區域網路開放選項後才能啟動聯機。
|
||||
multiplayer.session.create.token=憑證
|
||||
multiplayer.session.create.token.apply=申請憑證
|
||||
multiplayer.session.create.token.prompt=預設為無憑證。你可以在 這裡 的個人中心兌換憑證
|
||||
multiplayer.session.create.token.prompt=預設為無憑證。你可以點擊旁邊的“申請憑證”獲得詳情
|
||||
multiplayer.session.error.already_started=本地已經開啟 cato 服務,請檢查是否有其他 HMCL 正在運行聯機服務。或者你可以在任務管理器裡殺掉 cato 進程以繼續。
|
||||
multiplayer.session.error.file_not_found=找不到 cato 程序。該程序應該在進入多人聯機頁面時完成下載。請檢查你電腦的防毒軟體是否將 cato 標記為病毒,如果是,請恢復 cato。
|
||||
multiplayer.session.expired=聯機會話連續使用時間超過了 3 小時,你需要重新創建/加入房間以繼續聯機。
|
||||
@@ -747,6 +783,34 @@ world.import.already_exists=此世界已經存在
|
||||
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.game_version=遊戲版本
|
||||
world.info.last_played=上一次遊戲時間
|
||||
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.survival=生存
|
||||
world.info.player.health=生命值
|
||||
world.info.player.last_death_location=上次死亡位置
|
||||
world.info.player.location=位置
|
||||
world.info.player.spawn=床/重生錨位置
|
||||
world.info.player.xp_level=經驗等級
|
||||
world.info.random_seed=種子
|
||||
world.info.time=遊戲內時間
|
||||
world.info.time.format=%s 天
|
||||
world.game_version=遊戲版本
|
||||
world.manage=世界/資料包
|
||||
world.name=世界名稱
|
||||
@@ -792,7 +856,7 @@ settings.advanced.custom_commands=自訂命令
|
||||
settings.advanced.custom_commands.hint=自訂命令被調用時將包含如下的環境變數:\n\
|
||||
\ - $INST_NAME: 版本名稱\n\
|
||||
\ - $INST_ID: 版本名稱\n\
|
||||
\ - $INST_DIR: 版本文件夾\n\
|
||||
\ - $INST_DIR: 版本資料夾\n\
|
||||
\ - $INST_MC_DIR: 遊戲運行路徑\n\
|
||||
\ - $INST_JAVA: 遊戲運行使用的 Java 路徑\n\
|
||||
\ - $INST_FORGE: 若安裝了 Forge,將會存在本環境變數\n\
|
||||
@@ -801,13 +865,16 @@ settings.advanced.custom_commands.hint=自訂命令被調用時將包含如下
|
||||
\ - $INST_FABRIC: 若安裝了 Fabric,將會存在本環境變數
|
||||
settings.advanced.dont_check_game_completeness=不檢查遊戲完整性
|
||||
settings.advanced.dont_check_jvm_validity=不檢查 JVM 與遊戲的相容性
|
||||
settings.advanced.dont_patch_natives=不嘗試自動替換本機庫
|
||||
settings.advanced.game_dir.default=預設(.minecraft/)
|
||||
settings.advanced.game_dir.independent=各版本獨立(.minecraft/versions/<版本名>/,除 assets、libraries)
|
||||
settings.advanced.java_permanent_generation_space=記憶體永久儲存區域
|
||||
settings.advanced.java_permanent_generation_space.prompt=格式: MB
|
||||
settings.advanced.jvm=Java 虛擬機設定
|
||||
settings.advanced.jvm_args=Java 虛擬機參數
|
||||
settings.advanced.jvm_args.prompt=填寫此處可以覆蓋預設設定
|
||||
settings.advanced.jvm_args.prompt=- 若在“Java 虛擬機參數”中輸入的參數與默認參數相同,則不會添加\n\
|
||||
- 在“Java 虛擬機參數”輸入任何 GC 參數,默認參數的 G1 參數會禁用\n\
|
||||
- 點擊下方“不添加默認的 JVM 參數”可在啟動遊戲時不添加默認參數
|
||||
settings.advanced.launcher_visibility.close=遊戲啟動後結束啟動器
|
||||
settings.advanced.launcher_visibility.hide=遊戲啟動後隱藏啟動器
|
||||
settings.advanced.launcher_visibility.hide_and_reopen=隱藏啟動器並在遊戲結束後重新開啟
|
||||
@@ -835,6 +902,7 @@ settings.advanced.server_ip=伺服器位址
|
||||
settings.advanced.server_ip.prompt=預設,啟動遊戲後直接進入對應伺服器
|
||||
settings.advanced.use_native_glfw=[Linux] 使用系統 GLFW
|
||||
settings.advanced.use_native_openal=[Linux] 使用系統 OpenAL
|
||||
settings.advanced.use_software_renderer=使用 OpenGL 軟渲染器(相容性更好,但效能較差)
|
||||
settings.advanced.workaround=除錯選項
|
||||
settings.advanced.workaround.warning=除錯選項僅提供給專業玩家使用。修改除錯選項可能會導致遊戲無法啟動。除非你知道你在做什麼,否則請你不要修改這些選項。
|
||||
settings.advanced.wrapper_launcher=前置指令
|
||||
@@ -857,6 +925,7 @@ settings.game.java_directory.template=%s(%s)
|
||||
settings.game.management=管理
|
||||
settings.game.working_directory=執行路徑(版本隔離,修改後請自行移動相關遊戲檔案,如存檔模組設定等)
|
||||
settings.game.working_directory.choose=選擇執行路徑
|
||||
settings.game.working_directory.hint=在“運行路徑(版本隔離)”選項中開啟“各版本獨立”使當前版本獨立存放設定、存檔、模組等數據,使用模組時建議開啟此選項以避免不同版本模組衝突。修改此選項後請自行移動存檔等檔案。
|
||||
|
||||
settings.icon=遊戲圖示
|
||||
|
||||
@@ -927,13 +996,13 @@ update.changelog=更新日誌
|
||||
update.channel.dev=測試版
|
||||
update.channel.dev.hint=你正在使用測試版。測試版包含一些未在正式版中包含的測試性功能,僅用於體驗新功能。\n\
|
||||
測試版功能未受充分驗證,使用起來可能不穩定!\n\
|
||||
如果你遇到了使用問題,可以在設置的 <a href="hmcl://settings/feedback">回饋頁面</a> 中進行回饋,或加入回饋頁面中提供的 <a href="https://discord.gg/jVvC7HfM6U">Discord</a> 或 <a href="https://kaihei.co/Kx7n3t">開黑啦</a>群以回饋問題。\n\n\
|
||||
如果你遇到了使用問題,可以在設置的 <a href="hmcl://settings/feedback">回饋頁面</a> 中進行回饋,或加入回饋頁面中提供的 <a href="https://discord.gg/jVvC7HfM6U">Discord</a> 或 <a href="https://kook.top/Kx7n3t">KOOK</a>群以回饋問題。\n\n\
|
||||
你需要 <a href="https://hmcl.huangyuhui.net/api/redirect/sponsor">贊助</a> 或在回饋過程中作出貢獻以隱藏本提示。
|
||||
update.channel.dev.title=測試版提示
|
||||
update.channel.nightly=預覽版
|
||||
update.channel.nightly.hint=你正在使用預覽版。預覽版可能會每天更新一次,包含一些未在正式版和測試版中包含的測試性功能,僅用於體驗新功能\n\
|
||||
測試版功能未受充分驗證,使用起來可能不穩定!\n\
|
||||
如果你遇到了使用問題,可以在設置的 <a href="hmcl://settings/feedback">回饋頁面</a> 中進行回饋,或加入回饋頁面中提供的 <a href="https://discord.gg/jVvC7HfM6U">Discord</a> 或 <a href="https://kaihei.co/Kx7n3t">開黑啦</a>群以回饋問題。\n\n\
|
||||
如果你遇到了使用問題,可以在設置的 <a href="hmcl://settings/feedback">回饋頁面</a> 中進行回饋,或加入回饋頁面中提供的 <a href="https://discord.gg/jVvC7HfM6U">Discord</a> 或 <a href="https://kook.top/Kx7n3t">KOOK</a>群以回饋問題。\n\n\
|
||||
你需要 <a href="https://hmcl.huangyuhui.net/api/redirect/sponsor">贊助</a> 或在回饋過程中作出貢獻以隱藏本提示。
|
||||
update.channel.nightly.title=預覽版提示
|
||||
update.channel.stable=建議版本
|
||||
@@ -943,7 +1012,7 @@ update.found=發現到更新
|
||||
update.newest_version=最新版本為: %s
|
||||
update.bubble.title=發現更新: %s
|
||||
update.bubble.subtitle=點選此處進行升級
|
||||
update.note=開發版包含更多的功能以及錯誤修復,但也可能會包含其他的問題。
|
||||
update.note=測試版與開發版包含更多的功能以及錯誤修復,但也可能會包含其他的問題。
|
||||
update.latest=目前版本為最新版本
|
||||
update.no_browser=無法開啟瀏覽器,網址已經複製到剪貼簿了,您可以手動複製網址開啟頁面
|
||||
update.tooltip=更新
|
||||
@@ -984,7 +1053,7 @@ version.manage.rename=重新命名該版本
|
||||
version.manage.rename.message=請輸入新名稱
|
||||
version.manage.rename.fail=重新命名版本失敗,可能檔案被佔用或者名稱有特殊字元
|
||||
version.settings=遊戲設定
|
||||
version.update=更新整合包
|
||||
version.update=更新模組包
|
||||
|
||||
wizard.prev=< 上一步
|
||||
wizard.failed=失敗
|
||||
|
||||
Loaded 100 of 250 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user