Compare commits
261
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
83b4b58cb2 | ||
|
|
11f53c0e03 | ||
|
|
2fa8aee055 | ||
|
|
d2848e12f3 | ||
|
|
36ba840fe8 | ||
|
|
819d41a75c | ||
|
|
3a6c3dc51d | ||
|
|
db721107ae | ||
|
|
5b9be471e3 | ||
|
|
e8f8617412 | ||
|
|
eb472037db | ||
|
|
81b7afd08d | ||
|
|
7a71ecf177 | ||
|
|
a12e485a27 | ||
|
|
af521b2893 | ||
|
|
aab6f68492 | ||
|
|
f67c3d5284 | ||
|
|
46385a8445 | ||
|
|
9f617c33f5 | ||
|
|
6aef034880 | ||
|
|
c4941a01d6 | ||
|
|
7380423eee | ||
|
|
f35073ffb5 | ||
|
|
f89ea56d86 | ||
|
|
a240b8ca9f | ||
|
|
230ec2bc8e | ||
|
|
a3c2642b93 | ||
|
|
de755d5495 | ||
|
|
08054f3026 | ||
|
|
026239903d | ||
|
|
a320fe4cc4 | ||
|
|
b122d4c5e4 |
No files matched your search
+1117
File diff suppressed because it is too large.
Load diff
@@ -1,29 +0,0 @@
|
||||
# 公告
|
||||
|
||||
提交前请确保你的启动器版本是**最新的开发版**,可以在启动器设置中更换更新通道至开发版获取更新。
|
||||
|
||||
**如果你在阅读完本公告后仍然希望在此处发布 issue,请将公告内容删去,并按照下面的模板填入相关信息。**
|
||||
|
||||
|
||||
|
||||
我们发现,对于不经常检查邮箱的人来说,GitHub Issues 的反馈效率太低,时隔几个小时甚至一天的情况很多。
|
||||
为了改善反馈效率,并将积极为 HMCL 提供问题反馈和建议的人聚集起来,我们希望在这里反馈的人可以在 [Discord频道](https://discord.gg/jVvC7HfM6U) [开黑啦频道](https://kaihei.co/Kx7n3t) 中反馈信息,**而不在 GitHub Issues 中反馈问题**。
|
||||
|
||||
如果你希望在 QQ 群中反馈问题,请[赞助 HMCL](https://afdian.net/@huanghongxun) 后申请加入群聊,也请将下面的信息表填好直接发在群中,加快我们的沟通速度。
|
||||
|
||||
|
||||
|
||||
# 问题提交
|
||||
|
||||
*完整地填下面的问题提交表对我们很重要,这可以加快我们分析问题原因的速度。*
|
||||
|
||||
* 启动器版本:
|
||||
* 操作系统:
|
||||
* Java 版本:
|
||||
* 错误截图(最好请将整个电脑屏幕的截图发上来):
|
||||
* 游戏版本(如果是启动通过启动器自带的自动安装功能安装的游戏):
|
||||
* 对游戏做的修改(是否自行通过安装器安装 Rift 等 API,以及 mod):
|
||||
* 游戏崩溃报告(如果有):
|
||||
* 启动器崩溃报告(如果有):
|
||||
* 启动器日志文件(出现问题后点击启动器设置中的【导出启动器日志】按钮):
|
||||
* 问题描述(如何触发问题):
|
||||
@@ -0,0 +1,69 @@
|
||||
name: Bug Feedback
|
||||
description:
|
||||
反馈一个 Bug。为了提高反馈效率,请优先在开黑啦或者或 Discord 频道中反馈。若确认是 HMCL 问题,请发布 issue。
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
提交前请确保你的启动器版本是**最新的开发版**,可以在启动器设置中更换更新通道至开发版获取更新。
|
||||
|
||||
我们发现,对于不经常检查邮箱的人来说,GitHub Issues 的反馈效率太低,时隔几个小时甚至一天的情况很多。
|
||||
为了改善反馈效率,并将积极为 HMCL 提供问题反馈和建议的人聚集起来,我们希望在这里反馈的人可以在 [开黑啦频道](https://kaihei.co/Kx7n3t)
|
||||
或 [Discord 频道](https://discord.gg/jVvC7HfM6U)中反馈信息,**而不在 GitHub Issues 中反馈问题**。
|
||||
|
||||
如果你希望在 QQ 群中反馈问题,请[赞助 HMCL](https://afdian.net/@huanghongxun) 后申请加入群聊,也请将下面的信息表填好直接发在群中,加快我们的沟通速度。
|
||||
- type: input
|
||||
id: hmcl-version
|
||||
attributes:
|
||||
label: 启动器版本 Launcher Version
|
||||
description: 请输入您所使用的的 HMCL 启动器版本。 Please enter the HMCL launcher version you are using.
|
||||
placeholder: e.g. 3.5.2.218
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: platform
|
||||
attributes:
|
||||
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 的名称与版本。 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: 游戏版本 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.17.1
|
||||
- type: input
|
||||
id: game-modify
|
||||
attributes:
|
||||
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: 游戏崩溃报告 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: 启动器崩溃报告 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: 启动器日志文件 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: 问题描述 Problem Description
|
||||
description: 请尽可能地详细描述问题,特别是您是怎么遇到这个 BUG 的。 Please describe the problem in as much detail as possible, especially how you encountered this bug.
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: 开黑啦频道
|
||||
url: https://kaihei.co/Kx7n3t
|
||||
about: Hello Minecraft! Launcher 的官方开黑啦频道。
|
||||
- name: Discord 频道
|
||||
url: https://discord.gg/jVvC7HfM6U
|
||||
about: Hello Minecraft! Launcher 的官方 Discord 频道。
|
||||
- name: 赞助通道
|
||||
url: https://afdian.net/@huanghongxun
|
||||
about: 前往爱发电赞助 HMCL。赞助后可以申请加入官方 QQ 群反馈问题。
|
||||
- name: 其他反馈
|
||||
url: https://github.com/huanghongxun/HMCL/issues/new
|
||||
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/hub-mirror-action@v1.1
|
||||
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,313 +0,0 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
maven { url 'https://jitpack.io' }
|
||||
}
|
||||
dependencies {
|
||||
classpath 'org.tukaani:xz:1.8'
|
||||
classpath 'org.glavo:pack200:0.3.0'
|
||||
|
||||
classpath 'com.guardsquare:proguard-gradle:7.1.0' // The ProGuard Gradle plugin.
|
||||
}
|
||||
}
|
||||
|
||||
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 rootProject.files("lib/JFoenix.jar")
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
classifier = "shadow"
|
||||
|
||||
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(" ")
|
||||
}
|
||||
}
|
||||
|
||||
task proguard(type: proguard.gradle.ProGuardTask) {
|
||||
dependsOn shadowJar
|
||||
injars shadowJar
|
||||
|
||||
outjars "${buildDir}/libs/${project.name}-${project.version}-proguard.jar"
|
||||
|
||||
dontobfuscate
|
||||
dontoptimize
|
||||
|
||||
keep 'class org.jackhuang.** { *; }'
|
||||
keep 'class com.jfoenix.** { *; }'
|
||||
keep 'class com.google.gson.** { *; }'
|
||||
keep 'class com.github.steveice10.opennbt.** { *; }' // TagRegistry use reflection to create object instance
|
||||
|
||||
keep 'class * extends com.google.gson.TypeAdapter'
|
||||
keep 'class * implements com.google.gson.TypeAdapterFactory'
|
||||
keep 'class * implements com.google.gson.JsonSerializer'
|
||||
keep 'class * implements com.google.gson.JsonDeserializer'
|
||||
|
||||
printusage "${buildDir}/libs/proguard-shrinking-details.txt"
|
||||
|
||||
System.out.println(new File(".").absolutePath)
|
||||
|
||||
dontwarn 'com.nqzero.**'
|
||||
dontwarn 'org.slf4j.**'
|
||||
dontwarn 'org.jackhuang.hmcl.util.Pack200Utils'
|
||||
dontwarn 'com.sun.javafx.**'
|
||||
dontwarn 'com.jfoenix.**'
|
||||
dontwarn 'org.apache.**'
|
||||
|
||||
adaptclassstrings
|
||||
|
||||
// next block taken verbatim from Proguard's documentation examples:
|
||||
|
||||
libraryjars files(configurations.compileClasspath.collect())
|
||||
|
||||
keepattributes 'SourceFile,LineNumberTable'
|
||||
keepattributes '*Annotation*'
|
||||
keepattributes 'Signature'
|
||||
|
||||
var javaHome = System.getProperty('java.home')
|
||||
|
||||
// Automatically handle the Java version of this build.
|
||||
if (System.getProperty('java.version').startsWith('1.')) {
|
||||
// Before Java 9, the runtime classes were packaged in a single jar file.
|
||||
libraryjars "${javaHome}/lib/rt.jar"
|
||||
libraryjars "${javaHome}/lib/ext/jfxrt.jar"
|
||||
} else {
|
||||
// As of Java 9, the runtime classes are packaged in modular jmod files.
|
||||
libraryjars "${javaHome}/jmods/java.base.jmod", jarfilter: '!**.jar', filter: '!module-info.class'
|
||||
libraryjars "${javaHome}/jmods/java.desktop.jmod", jarfilter: '!**.jar', filter: '!module-info.class'
|
||||
libraryjars "${javaHome}/jmods/java.logging.jmod", jarfilter: '!**.jar', filter: '!module-info.class'
|
||||
libraryjars "${javaHome}/jmods/java.management.jmod", jarfilter: '!**.jar', filter: '!module-info.class'
|
||||
libraryjars "${javaHome}/jmods/java.sql.jmod", jarfilter: '!**.jar', filter: '!module-info.class'
|
||||
libraryjars "${javaHome}/jmods/java.xml.jmod", jarfilter: '!**.jar', filter: '!module-info.class'
|
||||
|
||||
libraryjars "${javaHome}/jmods/jdk.management.jmod", jarfilter: '!**.jar', filter: '!module-info.class'
|
||||
libraryjars "${javaHome}/jmods/jdk.unsupported.jmod", jarfilter: '!**.jar', filter: '!module-info.class'
|
||||
|
||||
if (new File("${javaHome}/jmods/javafx.base.jmod").exists()) {
|
||||
libraryjars "${javaHome}/jmods/javafx.base.jmod", jarfilter: '!**.jar', filter: '!module-info.class'
|
||||
libraryjars "${javaHome}/jmods/javafx.controls.jmod", jarfilter: '!**.jar', filter: '!module-info.class'
|
||||
libraryjars "${javaHome}/jmods/javafx.graphics.jmod", jarfilter: '!**.jar', filter: '!module-info.class'
|
||||
libraryjars "${javaHome}/jmods/javafx.media.jmod", jarfilter: '!**.jar', filter: '!module-info.class'
|
||||
libraryjars "${javaHome}/jmods/javafx.fxml.jmod", jarfilter: '!**.jar', filter: '!module-info.class'
|
||||
libraryjars "${javaHome}/jmods/javafx.web.jmod", jarfilter: '!**.jar', filter: '!module-info.class'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task finalJar(type: Jar) {
|
||||
dependsOn proguard
|
||||
|
||||
manifest = shadowJar.manifest
|
||||
|
||||
from { proguard.outJarFiles.collect { zipTree(it)} }
|
||||
|
||||
into('/') {
|
||||
from { shadowJar.outputs.files.collect { zipTree(it) } }
|
||||
include("META-INF/versions/**")
|
||||
}
|
||||
|
||||
doLast {
|
||||
repack(jar.archivePath) // see repack()
|
||||
attachSignature(jar.archivePath)
|
||||
createChecksum(jar.archivePath)
|
||||
}
|
||||
}
|
||||
|
||||
jar {
|
||||
enabled = false
|
||||
dependsOn finalJar
|
||||
}
|
||||
|
||||
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,283 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
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,10 @@ 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"));
|
||||
}
|
||||
|
||||
// runLater to ensure ConfigHolder.init() finished initialization
|
||||
Platform.runLater(() -> {
|
||||
// When launcher visibility is set to "hide and reopen" without Platform.implicitExit = false,
|
||||
@@ -154,34 +153,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);
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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.countly;
|
||||
|
||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
public class Countly {
|
||||
|
||||
private String deviceId;
|
||||
private String endpoint;
|
||||
private String serverURL;
|
||||
|
||||
public void sendMetric(String metrics) throws IOException {
|
||||
HttpRequest.GET(serverURL + endpoint,
|
||||
pair("begin_session", "1"),
|
||||
pair("session_id", "1"),
|
||||
pair("metrics", metrics),
|
||||
pair("device_id", deviceId),
|
||||
pair("timestamp", Long.toString(System.currentTimeMillis())),
|
||||
pair("tz", Integer.toString(TimeZone.getDefault().getOffset(new Date().getTime()) / 60000)),
|
||||
pair("hour", Integer.toString(currentHour())),
|
||||
pair("dow", Integer.toString(currentDayOfWeek())),
|
||||
pair("app_key", APP_KEY),
|
||||
pair("sdk_name", "java-native"),
|
||||
pair("sdk_version", "20.11.1"))
|
||||
.getString();
|
||||
}
|
||||
|
||||
private static int getTimezoneOffset() {
|
||||
return TimeZone.getDefault().getOffset(new Date().getTime()) / 60000;
|
||||
}
|
||||
|
||||
private static String getLocale() {
|
||||
final Locale locale = Locale.getDefault();
|
||||
return locale.getLanguage() + "_" + locale.getCountry();
|
||||
}
|
||||
|
||||
private static int currentHour() {
|
||||
return Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
|
||||
}
|
||||
|
||||
private int currentDayOfWeek() {
|
||||
int day = Calendar.getInstance().get(Calendar.DAY_OF_WEEK);
|
||||
switch (day) {
|
||||
case Calendar.SUNDAY:
|
||||
return 0;
|
||||
case Calendar.MONDAY:
|
||||
return 1;
|
||||
case Calendar.TUESDAY:
|
||||
return 2;
|
||||
case Calendar.WEDNESDAY:
|
||||
return 3;
|
||||
case Calendar.THURSDAY:
|
||||
return 4;
|
||||
case Calendar.FRIDAY:
|
||||
return 5;
|
||||
case Calendar.SATURDAY:
|
||||
return 6;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static final String APP_KEY = "";
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package org.jackhuang.hmcl.countly;
|
||||
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
public class CrashReport {
|
||||
|
||||
private final Thread thread;
|
||||
private final Throwable throwable;
|
||||
private final String stackTrace;
|
||||
|
||||
private boolean nonFatal;
|
||||
|
||||
public CrashReport(Thread thread, Throwable throwable) {
|
||||
this.thread = thread;
|
||||
this.throwable = throwable;
|
||||
stackTrace = StringUtils.getStackTrace(throwable);
|
||||
nonFatal = false;
|
||||
}
|
||||
|
||||
public CrashReport setNonFatal() {
|
||||
nonFatal = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean shouldBeReport() {
|
||||
if (!stackTrace.contains("org.jackhuang"))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public Map<String, Object> getMetrics(long runningTime) {
|
||||
return mapOf(
|
||||
pair("_run", runningTime),
|
||||
pair("_app_version", Metadata.VERSION),
|
||||
pair("_os", OperatingSystem.SYSTEM_NAME),
|
||||
pair("_os_version", OperatingSystem.SYSTEM_VERSION),
|
||||
pair("_disk_current", getDiskAvailable()),
|
||||
pair("_disk_total", getDiskTotal()),
|
||||
pair("_ram_current", getMemoryAvailable()),
|
||||
pair("_ram_total", Runtime.getRuntime().maxMemory() / BYTES_IN_MB),
|
||||
pair("_error", stackTrace),
|
||||
pair("_logs", Logging.getLogs()),
|
||||
pair("_name", throwable.getLocalizedMessage()),
|
||||
pair("_nonfatal", nonFatal)
|
||||
);
|
||||
}
|
||||
|
||||
public String getDisplayText() {
|
||||
return "---- Hello Minecraft! Crash Report ----\n" +
|
||||
" Version: " + Metadata.VERSION + "\n" +
|
||||
" Time: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "\n" +
|
||||
" Thread: " + thread + "\n" +
|
||||
"\n Content: \n " +
|
||||
stackTrace + "\n\n" +
|
||||
"-- System Details --\n" +
|
||||
" Operating System: " + OperatingSystem.SYSTEM_NAME + ' ' + OperatingSystem.SYSTEM_VERSION + "\n" +
|
||||
" System Architecture: " + Architecture.SYSTEM_ARCH_NAME + "\n" +
|
||||
" Java Architecture: " + Architecture.CURRENT_ARCH_NAME + "\n" +
|
||||
" Java Version: " + System.getProperty("java.version") + ", " + System.getProperty("java.vendor") + "\n" +
|
||||
" Java VM Version: " + System.getProperty("java.vm.name") + " (" + System.getProperty("java.vm.info") + "), " + System.getProperty("java.vm.vendor") + "\n" +
|
||||
" JVM Max Memory: " + Runtime.getRuntime().maxMemory() + "\n" +
|
||||
" JVM Total Memory: " + Runtime.getRuntime().totalMemory() + "\n" +
|
||||
" JVM Free Memory: " + Runtime.getRuntime().freeMemory() + "\n";
|
||||
}
|
||||
|
||||
private static final Long BYTES_IN_MB = 1024L * 1024;
|
||||
|
||||
private static long getMemoryAvailable() {
|
||||
Long total = Runtime.getRuntime().totalMemory();
|
||||
Long availMem = Runtime.getRuntime().freeMemory();
|
||||
return (total - availMem) / BYTES_IN_MB;
|
||||
}
|
||||
|
||||
private static long getDiskAvailable() {
|
||||
long total = 0, free = 0;
|
||||
for (File f : File.listRoots()) {
|
||||
total += f.getTotalSpace();
|
||||
free += f.getUsableSpace();
|
||||
}
|
||||
return (total - free) / BYTES_IN_MB;
|
||||
}
|
||||
|
||||
private static long getDiskTotal() {
|
||||
long total = 0;
|
||||
for (File f : File.listRoots()) {
|
||||
total += f.getTotalSpace();
|
||||
}
|
||||
return total / BYTES_IN_MB;
|
||||
}
|
||||
}
|
||||
@@ -20,16 +20,17 @@ 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;
|
||||
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;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
@@ -38,6 +39,7 @@ import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -58,6 +60,8 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
private final Map<String, VersionSetting> localVersionSettings = new HashMap<>();
|
||||
private final Set<String> beingModpackVersions = new HashSet<>();
|
||||
|
||||
public final EventManager<Event> onVersionIconChanged = new EventManager<>();
|
||||
|
||||
public HMCLGameRepository(Profile profile, File baseDirectory) {
|
||||
super(baseDirectory);
|
||||
this.profile = profile;
|
||||
@@ -231,6 +235,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
*
|
||||
* @return corresponding version setting, null if the version has no its own version setting.
|
||||
*/
|
||||
@Nullable
|
||||
public VersionSetting getLocalVersionSetting(String id) {
|
||||
if (!localVersionSettings.containsKey(id))
|
||||
loadLocalVersionSetting(id);
|
||||
@@ -240,6 +245,15 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
return setting;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public VersionSetting getLocalVersionSettingOrCreate(String id) {
|
||||
VersionSetting vs = getLocalVersionSetting(id);
|
||||
if (vs == null) {
|
||||
vs = createLocalVersionSetting(id);
|
||||
}
|
||||
return vs;
|
||||
}
|
||||
|
||||
public VersionSetting getVersionSetting(String id) {
|
||||
VersionSetting vs = getLocalVersionSetting(id);
|
||||
if (vs == null || vs.isUsesGlobal()) {
|
||||
@@ -258,14 +272,21 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
if (id == null || !isLoaded())
|
||||
return newImage("/assets/img/grass.png");
|
||||
|
||||
Version version = getVersion(id).resolve(this);
|
||||
File iconFile = getVersionIconFile(id);
|
||||
if (iconFile.exists())
|
||||
return new Image("file:" + iconFile.getAbsolutePath());
|
||||
else if (LibraryAnalyzer.isModded(this, version))
|
||||
return newImage("/assets/img/furnace.png");
|
||||
else
|
||||
return newImage("/assets/img/grass.png");
|
||||
VersionSetting vs = getLocalVersionSettingOrCreate(id);
|
||||
VersionIconType iconType = Optional.ofNullable(vs).map(VersionSetting::getVersionIcon).orElse(VersionIconType.DEFAULT);
|
||||
|
||||
if (iconType == VersionIconType.DEFAULT) {
|
||||
Version version = getVersion(id).resolve(this);
|
||||
File iconFile = getVersionIconFile(id);
|
||||
if (iconFile.exists())
|
||||
return new Image("file:" + iconFile.getAbsolutePath());
|
||||
else if (LibraryAnalyzer.isModded(this, version))
|
||||
return newImage("/assets/img/furnace.png");
|
||||
else
|
||||
return newImage("/assets/img/grass.png");
|
||||
} else {
|
||||
return newImage(iconType.getResourceUrl());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean saveVersionSetting(String id) {
|
||||
@@ -305,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()
|
||||
@@ -335,7 +356,9 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
.setNativesDir(vs.getNativesDir())
|
||||
.setProcessPriority(vs.getProcessPriority())
|
||||
.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()) {
|
||||
@@ -349,11 +372,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();
|
||||
}
|
||||
@@ -429,6 +449,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 <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
|
||||
@@ -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,6 +20,7 @@ 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;
|
||||
@@ -30,14 +31,9 @@ import org.jackhuang.hmcl.download.game.GameVerificationFixTask;
|
||||
import org.jackhuang.hmcl.download.game.LibraryDownloadException;
|
||||
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.mod.ModpackProvider;
|
||||
import org.jackhuang.hmcl.setting.DownloadProviders;
|
||||
import org.jackhuang.hmcl.setting.LauncherVisibility;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
@@ -55,16 +51,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 +92,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;
|
||||
@@ -124,9 +125,12 @@ public final class LauncherHelper {
|
||||
Optional<String> gameVersion = repository.getGameVersion(version);
|
||||
boolean integrityCheck = repository.unmarkVersionLaunchedAbnormally(selectedVersion);
|
||||
CountDownLatch launchingLatch = new CountDownLatch(1);
|
||||
List<String> javaAgents = new ArrayList<>(0);
|
||||
|
||||
AtomicReference<JavaVersion> javaVersionRef = new AtomicReference<>();
|
||||
|
||||
getLog4jPatch(version).ifPresent(javaAgents::add);
|
||||
|
||||
TaskExecutor executor = checkGameState(profile, setting, version)
|
||||
.thenComposeAsync(javaVersion -> {
|
||||
javaVersionRef.set(Objects.requireNonNull(javaVersion));
|
||||
@@ -144,14 +148,9 @@ public final class LauncherHelper {
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
@@ -172,7 +171,7 @@ 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,
|
||||
@@ -197,10 +196,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 +229,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
|
||||
@@ -339,12 +338,27 @@ public final class LauncherHelper {
|
||||
JavaVersionConstraint.VersionRanges range = JavaVersionConstraint.findSuitableJavaVersionRange(gameVersion, version);
|
||||
GameJavaVersion targetJavaVersion;
|
||||
|
||||
if (range.getMandatory().contains(VersionNumber.asVersion("16"))) {
|
||||
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 if (range.getMandatory().contains(VersionNumber.asVersion("1.8.0_51"))) {
|
||||
targetJavaVersion = GameJavaVersion.JAVA_8;
|
||||
} else {
|
||||
targetJavaVersion = null;
|
||||
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) {
|
||||
@@ -413,16 +427,20 @@ 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;
|
||||
case VANILLA_JAVA_17:
|
||||
Controllers.confirm(i18n("launch.advice.require_newer_java_version", gameVersion.toString(), 17), i18n("message.warning"), () -> {
|
||||
FXUtils.openLink(OPENJDK_DOWNLOAD_LINK);
|
||||
}, breakAction);
|
||||
return null;
|
||||
case VANILLA_JAVA_8:
|
||||
@@ -481,15 +499,21 @@ public final class LauncherHelper {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -501,7 +525,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;
|
||||
}
|
||||
}
|
||||
@@ -513,12 +537,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);
|
||||
}
|
||||
|
||||
@@ -531,7 +555,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());
|
||||
});
|
||||
|
||||
@@ -573,8 +597,7 @@ public final class LauncherHelper {
|
||||
private static CompletableFuture<JavaVersion> downloadJavaImpl(GameJavaVersion javaVersion, DownloadProvider downloadProvider) {
|
||||
CompletableFuture<JavaVersion> future = new CompletableFuture<>();
|
||||
|
||||
TaskExecutorDialogPane javaDownloadingPane = new TaskExecutorDialogPane(it -> {
|
||||
});
|
||||
TaskExecutorDialogPane javaDownloadingPane = new TaskExecutorDialogPane(TaskCancellationAction.NORMAL);
|
||||
|
||||
TaskExecutor executor = JavaRepository.downloadJava(javaVersion, downloadProvider)
|
||||
.whenComplete(Schedulers.javafx(), (downloadedJava, exception) -> {
|
||||
@@ -593,6 +616,47 @@ public final class LauncherHelper {
|
||||
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:
|
||||
@@ -626,7 +690,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;
|
||||
@@ -643,13 +706,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<>();
|
||||
}
|
||||
|
||||
@@ -658,8 +714,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);
|
||||
|
||||
@@ -711,10 +765,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);
|
||||
@@ -762,6 +813,8 @@ public final class LauncherHelper {
|
||||
|
||||
}
|
||||
|
||||
private static final String OPENJDK_DOWNLOAD_LINK = "https://docs.microsoft.com/zh-cn/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 <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.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) {
|
||||
|
||||
@@ -169,6 +169,10 @@ public final class OAuthServer extends NanoHTTPD implements OAuth.Session {
|
||||
JarUtils.thisJar().flatMap(JarUtils::getManifest).map(manifest -> manifest.getMainAttributes().getValue("Microsoft-Auth-Secret")).orElse(""));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPublicClient() {
|
||||
return true; // We have turned on the device auth flow.
|
||||
}
|
||||
}
|
||||
|
||||
public static class GrantDeviceCodeEvent extends Event {
|
||||
|
||||
@@ -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);
|
||||
@@ -163,6 +164,9 @@ public final class Config implements Cloneable, Observable {
|
||||
@SerializedName("authlibInjectorServers")
|
||||
private ObservableList<AuthlibInjectorServer> authlibInjectorServers = FXCollections.observableArrayList(server -> new Observable[] { server });
|
||||
|
||||
@SerializedName("promptedVersion")
|
||||
private StringProperty promptedVersion = new SimpleStringProperty();
|
||||
|
||||
@SerializedName("_version")
|
||||
private IntegerProperty configVersion = new SimpleIntegerProperty(0);
|
||||
|
||||
@@ -579,4 +583,16 @@ public final class Config implements Cloneable, Observable {
|
||||
public void setTitleTransparent(boolean titleTransparent) {
|
||||
this.titleTransparent.set(titleTransparent);
|
||||
}
|
||||
|
||||
public String getPromptedVersion() {
|
||||
return promptedVersion.get();
|
||||
}
|
||||
|
||||
public StringProperty promptedVersionProperty() {
|
||||
return promptedVersion;
|
||||
}
|
||||
|
||||
public void setPromptedVersion(String promptedVersion) {
|
||||
this.promptedVersion.set(promptedVersion);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.io.ResponseCodeException;
|
||||
|
||||
import javax.net.ssl.SSLHandshakeException;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URL;
|
||||
@@ -156,6 +157,8 @@ public final class DownloadProviders {
|
||||
return i18n("install.failed.downloading.detail", url) + "\n" + i18n("exception.access_denied", ((AccessDeniedException) exception.getCause()).getFile());
|
||||
} else if (exception.getCause() instanceof ArtifactMalformedException) {
|
||||
return i18n("install.failed.downloading.detail", url) + "\n" + i18n("exception.artifact_malformed");
|
||||
} else if (exception.getCause() instanceof SSLHandshakeException) {
|
||||
return i18n("install.failed.downloading.detail", url) + "\n" + i18n("exception.ssl_handshake");
|
||||
} else {
|
||||
return i18n("install.failed.downloading.detail", url) + "\n" + StringUtils.getStackTrace(exception.getCause());
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
public enum VersionIconType {
|
||||
DEFAULT("/assets/img/grass.png"),
|
||||
|
||||
GRASS("/assets/img/grass.png"),
|
||||
CHEST("/assets/img/chest.png"),
|
||||
CHICKEN("/assets/img/chicken.png"),
|
||||
COMMAND("/assets/img/command.png"),
|
||||
CRAFT_TABLE("/assets/img/craft_table.png"),
|
||||
FABRIC("/assets/img/fabric.png"),
|
||||
FORGE("/assets/img/forge.png"),
|
||||
FURNACE("/assets/img/furnace.png");
|
||||
|
||||
// Please append new items at last
|
||||
|
||||
private final String resourceUrl;
|
||||
|
||||
VersionIconType(String resourceUrl) {
|
||||
this.resourceUrl = resourceUrl;
|
||||
}
|
||||
|
||||
public String getResourceUrl() {
|
||||
return resourceUrl;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -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;
|
||||
@@ -568,6 +568,20 @@ public final class VersionSetting implements Cloneable {
|
||||
this.useNativeOpenAL.set(useNativeOpenAL);
|
||||
}
|
||||
|
||||
private final ObjectProperty<VersionIconType> versionIcon = new SimpleObjectProperty<>(this, "versionIcon", VersionIconType.DEFAULT);
|
||||
|
||||
public VersionIconType getVersionIcon() {
|
||||
return versionIcon.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<VersionIconType> versionIconProperty() {
|
||||
return versionIcon;
|
||||
}
|
||||
|
||||
public void setVersionIcon(VersionIconType versionIcon) {
|
||||
this.versionIcon.set(versionIcon);
|
||||
}
|
||||
|
||||
// launcher settings
|
||||
|
||||
/**
|
||||
@@ -670,6 +684,7 @@ public final class VersionSetting implements Cloneable {
|
||||
defaultJavaPathProperty.addListener(listener);
|
||||
nativesDirProperty.addListener(listener);
|
||||
nativesDirTypeProperty.addListener(listener);
|
||||
versionIcon.addListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -703,6 +718,7 @@ public final class VersionSetting implements Cloneable {
|
||||
versionSetting.setUseNativeOpenAL(isUseNativeOpenAL());
|
||||
versionSetting.setLauncherVisibility(getLauncherVisibility());
|
||||
versionSetting.setNativesDir(getNativesDir());
|
||||
versionSetting.setVersionIcon(getVersionIcon());
|
||||
return versionSetting;
|
||||
}
|
||||
|
||||
@@ -741,6 +757,7 @@ public final class VersionSetting implements Cloneable {
|
||||
obj.addProperty("defaultJavaPath", src.getDefaultJavaPath());
|
||||
obj.addProperty("nativesDir", src.getNativesDir());
|
||||
obj.addProperty("nativesDirType", src.getNativesDirType().ordinal());
|
||||
obj.addProperty("versionIcon", src.getVersionIcon().ordinal());
|
||||
|
||||
return obj;
|
||||
}
|
||||
@@ -784,6 +801,7 @@ public final class VersionSetting implements Cloneable {
|
||||
vs.setGameDirType(GameDirectoryType.values()[Optional.ofNullable(obj.get("gameDirType")).map(JsonElement::getAsInt).orElse(GameDirectoryType.ROOT_FOLDER.ordinal())]);
|
||||
vs.setDefaultJavaPath(Optional.ofNullable(obj.get("defaultJavaPath")).map(JsonElement::getAsString).orElse(null));
|
||||
vs.setNativesDirType(NativesDirectoryType.values()[Optional.ofNullable(obj.get("nativesDirType")).map(JsonElement::getAsInt).orElse(0)]);
|
||||
vs.setVersionIcon(VersionIconType.values()[Optional.ofNullable(obj.get("versionIcon")).map(JsonElement::getAsInt).orElse(0)]);
|
||||
|
||||
return vs;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ 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 +52,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 +76,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"));
|
||||
});
|
||||
@@ -262,11 +263,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 +271,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,13 +24,17 @@ import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
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;
|
||||
@@ -71,7 +75,7 @@ public class GameCrashWindow extends Stage {
|
||||
private final LibraryAnalyzer analyzer;
|
||||
private final StringProperty os = new SimpleStringProperty(OperatingSystem.SYSTEM_NAME);
|
||||
private final StringProperty arch = new SimpleStringProperty(Architecture.SYSTEM_ARCH.getDisplayName());
|
||||
private final StringProperty reason = new SimpleStringProperty(i18n("game.crash.reason.unknown"));
|
||||
private final TextFlow reasonTextFlow = new TextFlow(new Text(i18n("game.crash.reason.unknown")));
|
||||
private final BooleanProperty loading = new SimpleBooleanProperty();
|
||||
private final Label feedbackLabel = new Label(i18n("game.crash.feedback"));
|
||||
|
||||
@@ -133,52 +137,53 @@ public class GameCrashWindow extends Stage {
|
||||
|
||||
if (exception != null) {
|
||||
LOG.log(Level.WARNING, "Failed to analyze crash report", exception);
|
||||
reason.set(i18n("game.crash.reason.unknown"));
|
||||
reasonTextFlow.getChildren().setAll(new Text(i18n("game.crash.reason.unknown")));
|
||||
} else {
|
||||
List<CrashReportAnalyzer.Result> results = pair.getKey();
|
||||
Set<String> keywords = pair.getValue();
|
||||
StringBuilder reasonText = new StringBuilder();
|
||||
|
||||
List<Node> segments = new ArrayList<>();
|
||||
for (CrashReportAnalyzer.Result result : results) {
|
||||
switch (result.getRule()) {
|
||||
case TOO_OLD_JAVA:
|
||||
reasonText.append(i18n("game.crash.reason.too_old_java",
|
||||
CrashReportAnalyzer.getJavaVersionFromMajorVersion(Integer.parseInt(result.getMatcher().group("expected")))))
|
||||
.append("\n");
|
||||
segments.addAll(FXUtils.parseSegment(i18n("game.crash.reason.too_old_java",
|
||||
CrashReportAnalyzer.getJavaVersionFromMajorVersion(Integer.parseInt(result.getMatcher().group("expected")))), Controllers::onHyperlinkAction));
|
||||
break;
|
||||
case MOD_RESOLUTION_CONFLICT:
|
||||
case MOD_RESOLUTION_MISSING:
|
||||
case MOD_RESOLUTION_COLLECTION:
|
||||
reasonText.append(i18n("game.crash.reason." + result.getRule().name().toLowerCase(Locale.ROOT),
|
||||
segments.addAll(FXUtils.parseSegment(i18n("game.crash.reason." + result.getRule().name().toLowerCase(Locale.ROOT),
|
||||
translateFabricModId(result.getMatcher().group("sourcemod")),
|
||||
parseFabricModId(result.getMatcher().group("destmod")),
|
||||
parseFabricModId(result.getMatcher().group("destmod"))));
|
||||
parseFabricModId(result.getMatcher().group("destmod"))), Controllers::onHyperlinkAction));
|
||||
break;
|
||||
case MOD_RESOLUTION_MISSING_MINECRAFT:
|
||||
reasonText.append(i18n("game.crash.reason." + result.getRule().name().toLowerCase(Locale.ROOT),
|
||||
segments.addAll(FXUtils.parseSegment(i18n("game.crash.reason." + result.getRule().name().toLowerCase(Locale.ROOT),
|
||||
translateFabricModId(result.getMatcher().group("mod")),
|
||||
result.getMatcher().group("version")));
|
||||
result.getMatcher().group("version")), Controllers::onHyperlinkAction));
|
||||
break;
|
||||
case TWILIGHT_FOREST_OPTIFINE:
|
||||
reasonText.append(i18n("game.crash.reason.mod", "OptiFine"));
|
||||
segments.addAll(FXUtils.parseSegment(i18n("game.crash.reason.mod", "OptiFine"), Controllers::onHyperlinkAction));
|
||||
break;
|
||||
default:
|
||||
reasonText.append(i18n("game.crash.reason." + result.getRule().name().toLowerCase(Locale.ROOT),
|
||||
segments.addAll(FXUtils.parseSegment(i18n("game.crash.reason." + result.getRule().name().toLowerCase(Locale.ROOT),
|
||||
Arrays.stream(result.getRule().getGroupNames()).map(groupName -> result.getMatcher().group(groupName))
|
||||
.toArray()))
|
||||
.append("\n");
|
||||
.toArray()), Controllers::onHyperlinkAction));
|
||||
break;
|
||||
}
|
||||
segments.add(new Text("\n"));
|
||||
}
|
||||
if (results.isEmpty()) {
|
||||
if (!keywords.isEmpty()) {
|
||||
reason.set(i18n("game.crash.reason.stacktrace", String.join(", ", keywords)));
|
||||
reasonTextFlow.getChildren().setAll(new Text(i18n("game.crash.reason.stacktrace", String.join(", ", keywords))));
|
||||
} else {
|
||||
reason.set(i18n("game.crash.reason.unknown"));
|
||||
reasonTextFlow.getChildren().setAll(new Text(i18n("game.crash.reason.unknown")));
|
||||
}
|
||||
|
||||
feedbackLabel.setVisible(true);
|
||||
} else {
|
||||
reason.set(reasonText.toString());
|
||||
feedbackLabel.setVisible(false);
|
||||
reasonTextFlow.getChildren().setAll(segments);
|
||||
}
|
||||
}
|
||||
}, Schedulers.javafx()).exceptionally(Lang::handleUncaughtException);
|
||||
@@ -217,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());
|
||||
|
||||
@@ -277,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"));
|
||||
@@ -302,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);
|
||||
@@ -311,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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,14 +345,12 @@ public class GameCrashWindow extends Stage {
|
||||
javaDir.setTitle(i18n("settings.game.java_directory"));
|
||||
javaDir.setSubtitle(launchOptions.getJava().getBinary().toAbsolutePath().toString());
|
||||
|
||||
TwoLineListItem reason = new TwoLineListItem();
|
||||
reason.getStyleClass().setAll("two-line-item-second-large", "wrap-text");
|
||||
reason.setTitle(i18n("game.crash.reason"));
|
||||
reason.subtitleProperty().bind(GameCrashWindow.this.reason);
|
||||
Label reasonTitle = new Label(i18n("game.crash.reason"));
|
||||
reasonTitle.getStyleClass().add("two-line-item-second-large-title");
|
||||
|
||||
gameDirPane.setPadding(new Insets(8));
|
||||
VBox.setVgrow(gameDirPane, Priority.ALWAYS);
|
||||
gameDirPane.getChildren().setAll(gameDir, javaDir, reason);
|
||||
gameDirPane.getChildren().setAll(gameDir, javaDir, new VBox(reasonTitle, reasonTextFlow));
|
||||
}
|
||||
|
||||
HBox toolBar = new HBox();
|
||||
|
||||
@@ -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/profile/skin");
|
||||
return null;
|
||||
}
|
||||
if (!(account instanceof YggdrasilAccount)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -285,21 +285,40 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
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/zh-cn/account-billing/如何更改-microsoft-帐户上的出生日期-837badbc-999e-54d2-2617-d19206b9540a"));
|
||||
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);
|
||||
box.getChildren().setAll(profileLink, birthLink, purchaseLink, deauthorizeLink);
|
||||
GridPane.setColumnSpan(box, 2);
|
||||
|
||||
vbox.getChildren().setAll(hintPane, box);
|
||||
|
||||
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());
|
||||
@@ -435,10 +454,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));
|
||||
@@ -671,5 +690,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";
|
||||
}
|
||||
@@ -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()));
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -42,10 +42,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 +128,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,6 +138,10 @@ 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) {
|
||||
@@ -186,7 +195,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);
|
||||
});
|
||||
|
||||
@@ -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,17 +40,15 @@ 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;
|
||||
|
||||
import java.nio.file.Path;
|
||||
@@ -82,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);
|
||||
@@ -166,30 +163,33 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
if (version == null) version = profile.getSelectedVersion();
|
||||
|
||||
Path runDirectory = profile.getRepository().hasVersion(version) ? profile.getRepository().getRunDirectory(version).toPath() : profile.getRepository().getBaseDirectory().toPath();
|
||||
Path dest = runDirectory.resolve(subdirectoryName).resolve(file.getFile().getFilename());
|
||||
|
||||
TaskExecutorDialogPane downloadingPane = new TaskExecutorDialogPane(it -> {
|
||||
});
|
||||
|
||||
TaskExecutor executor = Task.composeAsync(() -> {
|
||||
FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(file.getFile().getUrl()), dest.toFile());
|
||||
task.setName(file.getName());
|
||||
return task;
|
||||
}).whenComplete(Schedulers.javafx(), exception -> {
|
||||
if (exception != null) {
|
||||
if (exception instanceof CancellationException) {
|
||||
Controllers.showToast(i18n("message.cancelled"));
|
||||
} else {
|
||||
Controllers.dialog(DownloadProviders.localizeErrorMessage(exception), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR);
|
||||
}
|
||||
} else {
|
||||
Controllers.showToast(i18n("install.success"));
|
||||
Controllers.prompt(i18n("archive.name"), (result, resolve, reject) -> {
|
||||
if (!OperatingSystem.isNameValid(result)) {
|
||||
reject.accept(i18n("install.new_game.malformed"));
|
||||
return;
|
||||
}
|
||||
}).executor(false);
|
||||
Path dest = runDirectory.resolve(subdirectoryName).resolve(result);
|
||||
|
||||
Controllers.taskDialog(Task.composeAsync(() -> {
|
||||
FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(file.getFile().getUrl()), dest.toFile());
|
||||
task.setName(file.getName());
|
||||
return task;
|
||||
}).whenComplete(Schedulers.javafx(), exception -> {
|
||||
if (exception != null) {
|
||||
if (exception instanceof CancellationException) {
|
||||
Controllers.showToast(i18n("message.cancelled"));
|
||||
} else {
|
||||
Controllers.dialog(DownloadProviders.localizeErrorMessage(exception), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR);
|
||||
}
|
||||
} else {
|
||||
Controllers.showToast(i18n("install.success"));
|
||||
}
|
||||
}), i18n("message.downloading"), TaskCancellationAction.NORMAL);
|
||||
|
||||
resolve.run();
|
||||
}, file.getFile().getFilename());
|
||||
|
||||
downloadingPane.setExecutor(executor, true);
|
||||
Controllers.dialog(downloadingPane);
|
||||
executor.start();
|
||||
}
|
||||
|
||||
private void loadVersions(Profile profile) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -31,6 +31,7 @@ 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;
|
||||
|
||||
@@ -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/");
|
||||
|
||||
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();
|
||||
@@ -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);
|
||||
|
||||
@@ -42,9 +42,11 @@ import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
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.ui.construct.AnnouncementCard;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.PopupMenu;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
@@ -53,11 +55,14 @@ import org.jackhuang.hmcl.ui.versions.Versions;
|
||||
import org.jackhuang.hmcl.upgrade.RemoteVersion;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateHandler;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.SINE;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
@@ -69,7 +74,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
|
||||
private final StringProperty currentGame = new SimpleStringProperty(this, "currentGame");
|
||||
private final BooleanProperty showUpdate = new SimpleBooleanProperty(this, "showUpdate");
|
||||
private final StringProperty latestVersion = new SimpleStringProperty(this, "latestVersion");
|
||||
private final ObjectProperty<RemoteVersion> latestVersion = new SimpleObjectProperty<>(this, "latestVersion");
|
||||
private final ObservableList<Version> versions = FXCollections.observableArrayList();
|
||||
private final ObservableList<Node> versionNodes;
|
||||
private Profile profile;
|
||||
@@ -103,7 +108,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
FXUtils.setLimitHeight(updatePane, 55);
|
||||
StackPane.setAlignment(updatePane, Pos.TOP_RIGHT);
|
||||
updatePane.setOnMouseClicked(e -> onUpgrade());
|
||||
FXUtils.onChange(showUpdateProperty(), this::doAnimation);
|
||||
FXUtils.onChange(showUpdateProperty(), this::showUpdate);
|
||||
|
||||
{
|
||||
HBox hBox = new HBox();
|
||||
@@ -118,7 +123,8 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
TwoLineListItem prompt = new TwoLineListItem();
|
||||
prompt.setSubtitle(i18n("update.bubble.subtitle"));
|
||||
prompt.setPickOnBounds(false);
|
||||
prompt.titleProperty().bind(latestVersionProperty());
|
||||
prompt.titleProperty().bind(BindingMapping.of(latestVersionProperty()).map(latestVersion ->
|
||||
latestVersion == null ? "" : i18n("update.bubble.title", latestVersion.getVersion())));
|
||||
|
||||
hBox.getChildren().setAll(lblIcon, prompt);
|
||||
}
|
||||
@@ -224,6 +230,17 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
}
|
||||
}
|
||||
|
||||
private void showUpdate(boolean show) {
|
||||
doAnimation(show);
|
||||
|
||||
if (show && getLatestVersion() != null && !Objects.equals(config().getPromptedVersion(), getLatestVersion().getVersion())) {
|
||||
Controllers.dialog("", i18n("update.bubble.title", getLatestVersion().getVersion()), MessageDialogPane.MessageType.INFO, () -> {
|
||||
config().setPromptedVersion(getLatestVersion().getVersion());
|
||||
onUpgrade();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void doAnimation(boolean show) {
|
||||
Duration duration = Duration.millis(320);
|
||||
Timeline nowAnimation = new Timeline();
|
||||
@@ -289,15 +306,15 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
this.showUpdate.set(showUpdate);
|
||||
}
|
||||
|
||||
public String getLatestVersion() {
|
||||
public RemoteVersion getLatestVersion() {
|
||||
return latestVersion.get();
|
||||
}
|
||||
|
||||
public StringProperty latestVersionProperty() {
|
||||
public ObjectProperty<RemoteVersion> latestVersionProperty() {
|
||||
return latestVersion;
|
||||
}
|
||||
|
||||
public void setLatestVersion(String latestVersion) {
|
||||
public void setLatestVersion(RemoteVersion latestVersion) {
|
||||
this.latestVersion.set(latestVersion);
|
||||
}
|
||||
|
||||
|
||||
@@ -40,9 +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.javafx.BindingMapping;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.io.File;
|
||||
@@ -83,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),
|
||||
@@ -92,8 +91,7 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
|
||||
FXUtils.onChangeAndOperate(Profiles.selectedVersionProperty(), mainPage::setCurrentGame);
|
||||
mainPage.showUpdateProperty().bind(UpdateChecker.outdatedProperty());
|
||||
mainPage.latestVersionProperty().bind(BindingMapping.of(UpdateChecker.latestVersionProperty())
|
||||
.map(version -> version == null ? "" : i18n("update.bubble.title", version.getVersion())));
|
||||
mainPage.latestVersionProperty().bind(UpdateChecker.latestVersionProperty());
|
||||
|
||||
Profiles.registerVersionsListener(profile -> {
|
||||
HMCLGameRepository repository = profile.getRepository();
|
||||
@@ -201,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;
|
||||
}
|
||||
|
||||
@@ -87,6 +87,7 @@ public class MultiplayerClient extends Thread {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
|
||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8))) {
|
||||
MultiplayerServer.Endpoint endpoint = new MultiplayerServer.Endpoint(socket, writer);
|
||||
socket.setSoTimeout(30000);
|
||||
LOG.info("Connected to 127.0.0.1:" + port);
|
||||
|
||||
endpoint.write(new HandshakeRequest());
|
||||
@@ -107,7 +108,7 @@ public class MultiplayerClient extends Thread {
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to close socket", e);
|
||||
}
|
||||
}, 15 * 1000);
|
||||
}, 25 * 1000);
|
||||
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
|
||||
@@ -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;
|
||||
@@ -44,73 +46,65 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.PosixFilePermission;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
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-202110231414";
|
||||
// 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/to/ioi_bin/-/raw/acb0524bcad82a31fa5a09bf4c79248ebd674de1/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", "6b7630f843d4b8f8e11feff4c1cb3dc1916d1fdb"),
|
||||
pair("cato-client-darwin-arm64", "5f00153f0117eebb4209a4484152ec4077d34b34"),
|
||||
pair("cato-client-freebsd-amd64", "ac401f51e69058696e342ddaa62968d21c8252e8"),
|
||||
pair("cato-client-freebsd-arm7", "168dfdabc773ce87f0f1d901bee76f7ea3beacb3"),
|
||||
pair("cato-client-freebsd-arm64", "08daef0f3acc279411ff39e8a22510a9876d06cb"),
|
||||
pair("cato-client-freebsd-i386", "febdf99be30c671708dd80f8d3b48335a0d5920a"),
|
||||
pair("cato-client-js.wasm", "0439d2a1cd8ee854ef6057a8ae6898db47aba2bd"),
|
||||
pair("cato-client-linux-amd64", "816aecb116e2bc0727de9362e893bd9cefdf0485"),
|
||||
pair("cato-client-linux-arm7", "710a047fdf528917d8a45ed0161ca155e2e05ff1"),
|
||||
pair("cato-client-linux-arm64", "7a9a22d39f0ba200e1e4b2f6fca8cbe218a3eedf"),
|
||||
pair("cato-client-linux-i386", "3eb0b37bdff8b9c8dc1e425b91ddb4d0a72a0da0"),
|
||||
pair("cato-client-linux-mips", "8d2b383fcd4edb7903a14a93947ed5cb54838e60"),
|
||||
pair("cato-client-linux-mips64", "248f1a3fe69ec97f6c095b63fa597ee099b4cb8c"),
|
||||
pair("cato-client-linux-mips64le", "8d2f8d93ca582ab5f43dc4570a0d5b18dbe06df3"),
|
||||
pair("cato-client-linux-mipsle", "f38bd14e6cdd6bfcd045ccd5a0a183bc8083c028"),
|
||||
pair("cato-client-linux-ppc64", "db420847ed8e60a58a69b2e1cb55cf21b7b56e4b"),
|
||||
pair("cato-client-linux-ppc64le", "717149f52a0808ee09cd5a7e89f8b9a6ed604cb0"),
|
||||
pair("cato-client-openbsd-amd64", "248aaf3ca3bcaa18d4d1391325d171ef17e65244"),
|
||||
pair("cato-client-openbsd-arm7", "1ab97264dba5d2b61388ee68e49f890c29c4e09f"),
|
||||
pair("cato-client-openbsd-arm64", "5f28541ace9d298b816eb43963be8e4cd87bd0fd"),
|
||||
pair("cato-client-openbsd-i386", "613b5c3fc0382815843a6e9b2a5281b05967bc1c"),
|
||||
pair("cato-client-windows-amd64.exe", "64f19648d281882eefc5afaa3af347907af5cb23"),
|
||||
pair("cato-client-windows-arm64.exe", "48a4bc5e18c35c93c9a3ecc500420d3abb5ae6f0"),
|
||||
pair("cato-client-windows-i386.exe", "fcb4f245da2e293badb73fac04cf4e83fbd79b18")
|
||||
|
||||
);
|
||||
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() {
|
||||
@@ -118,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);
|
||||
@@ -129,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();
|
||||
@@ -178,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);
|
||||
|
||||
@@ -189,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 CancellationException());
|
||||
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) {
|
||||
@@ -229,6 +234,7 @@ public final class MultiplayerManager {
|
||||
MultiplayerServer server = new MultiplayerServer(sessionName, gamePort, allowAllJoinRequests);
|
||||
server.startServer();
|
||||
|
||||
session.setName(sessionName);
|
||||
session.allowForwardingAddress(REMOTE_ADDRESS, server.getPort());
|
||||
session.allowForwardingAddress(REMOTE_ADDRESS, gamePort);
|
||||
session.showAllowedAddress();
|
||||
@@ -255,16 +261,18 @@ 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;
|
||||
}));
|
||||
}
|
||||
|
||||
public static final Pattern INVITATION_CODE_PATTERN = Pattern.compile("^(?<id>(idx|mix)(.*?))#(?<port>\\d{2,5})$");
|
||||
public static final Pattern INVITATION_CODE_PATTERN = Pattern.compile("^(?<id>.*?)#(?<port>\\d{2,5})$");
|
||||
|
||||
public static Invitation parseInvitationCode(String invitationCode) throws JsonParseException {
|
||||
Matcher matcher = INVITATION_CODE_PATTERN.matcher(invitationCode);
|
||||
@@ -349,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;
|
||||
}
|
||||
@@ -401,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);
|
||||
}
|
||||
@@ -413,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;
|
||||
}
|
||||
|
||||
@@ -595,6 +604,18 @@ public final class MultiplayerManager {
|
||||
public static class ConnectionErrorException extends RuntimeException {
|
||||
}
|
||||
|
||||
public static class KickedException extends RuntimeException {
|
||||
private final String reason;
|
||||
|
||||
public KickedException(String reason) {
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
public String getReason() {
|
||||
return reason;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CatoNotExistsException extends RuntimeException {
|
||||
private final Path file;
|
||||
|
||||
|
||||
@@ -38,7 +38,11 @@ import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
|
||||
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;
|
||||
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
@@ -58,7 +62,7 @@ public class MultiplayerPage extends DecoratorAnimatedPage implements DecoratorP
|
||||
|
||||
private final ObjectProperty<MultiplayerManager.State> multiplayerState = new SimpleObjectProperty<>(MultiplayerManager.State.DISCONNECTED);
|
||||
private final ReadOnlyStringWrapper token = new ReadOnlyStringWrapper();
|
||||
private final ReadOnlyObjectWrapper<DiscoveryInfo> natState = new ReadOnlyObjectWrapper<>();
|
||||
private final ReadOnlyObjectWrapper<@Nullable Result<DiscoveryInfo>> natState = new ReadOnlyObjectWrapper<>();
|
||||
private final ReadOnlyIntegerWrapper gamePort = new ReadOnlyIntegerWrapper(-1);
|
||||
private final ReadOnlyObjectWrapper<MultiplayerManager.CatoSession> session = new ReadOnlyObjectWrapper<>();
|
||||
private final ObservableList<MultiplayerChannel.CatoClient> clients = FXCollections.observableArrayList();
|
||||
@@ -97,11 +101,11 @@ public class MultiplayerPage extends DecoratorAnimatedPage implements DecoratorP
|
||||
this.multiplayerState.set(multiplayerState);
|
||||
}
|
||||
|
||||
public DiscoveryInfo getNatState() {
|
||||
public Result<DiscoveryInfo> getNatState() {
|
||||
return natState.get();
|
||||
}
|
||||
|
||||
public ReadOnlyObjectProperty<DiscoveryInfo> natStateProperty() {
|
||||
public ReadOnlyObjectProperty<Result<DiscoveryInfo>> natStateProperty() {
|
||||
return natState.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
@@ -131,15 +135,15 @@ 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) -> {
|
||||
LOG.log(Level.INFO, "Nat test result " + MultiplayerPageSkin.getNATType(info), exception);
|
||||
if (exception == null) {
|
||||
natState.set(info);
|
||||
natState.set(Result.ok(info));
|
||||
} else {
|
||||
natState.set(null);
|
||||
natState.set(Result.error());
|
||||
}
|
||||
LOG.log(Level.INFO, "Nat test result " + MultiplayerPageSkin.getNATType(natState.get()), exception);
|
||||
}).start();
|
||||
}
|
||||
|
||||
@@ -193,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);
|
||||
@@ -345,7 +349,10 @@ public class MultiplayerPage extends DecoratorAnimatedPage implements DecoratorP
|
||||
Throwable e = resolveException(t);
|
||||
if (e instanceof CancellationException) {
|
||||
LOG.info("Connection rejected by the server");
|
||||
return i18n("multiplayer.session.join.rejected");
|
||||
return i18n("message.cancelled");
|
||||
} else if (e instanceof MultiplayerManager.KickedException) {
|
||||
LOG.info("Kicked by server");
|
||||
return i18n("multiplayer.session.join.kicked", localizeKickMessage(((MultiplayerManager.KickedException) e).getReason()));
|
||||
} else if (e instanceof MultiplayerManager.CatoAlreadyStartedException) {
|
||||
LOG.info("Cato already started");
|
||||
return i18n("multiplayer.session.error.already_started");
|
||||
@@ -367,11 +374,14 @@ 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");
|
||||
} else {
|
||||
return fallback.apply(e);
|
||||
}
|
||||
@@ -503,4 +513,5 @@ public class MultiplayerPage extends DecoratorAnimatedPage implements DecoratorP
|
||||
public ReadOnlyObjectProperty<State> stateProperty() {
|
||||
return state;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -41,8 +41,10 @@ import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
|
||||
import org.jackhuang.hmcl.ui.versions.Versions;
|
||||
import org.jackhuang.hmcl.util.HMCLService;
|
||||
import org.jackhuang.hmcl.util.Result;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.globalConfig;
|
||||
import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
|
||||
@@ -126,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"));
|
||||
@@ -276,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));
|
||||
@@ -295,22 +297,24 @@ public class MultiplayerPageSkin extends DecoratorAnimatedPage.DecoratorAnimated
|
||||
}
|
||||
}
|
||||
|
||||
public static String getNATType(DiscoveryInfo info) {
|
||||
public static String getNATType(@Nullable Result<DiscoveryInfo> info) {
|
||||
if (info == null) {
|
||||
return i18n("multiplayer.nat.testing");
|
||||
} else if (info.isBlockedUDP()) {
|
||||
} else if (info.isError()) {
|
||||
return i18n("multiplayer.nat.failed");
|
||||
} else if (info.get().isBlockedUDP()) {
|
||||
return i18n("multiplayer.nat.type.blocked_udp");
|
||||
} else if (info.isFullCone()) {
|
||||
} else if (info.get().isFullCone()) {
|
||||
return i18n("multiplayer.nat.type.full_cone");
|
||||
} else if (info.isOpenAccess()) {
|
||||
} else if (info.get().isOpenAccess()) {
|
||||
return i18n("multiplayer.nat.type.open_access");
|
||||
} else if (info.isPortRestrictedCone()) {
|
||||
} else if (info.get().isPortRestrictedCone()) {
|
||||
return i18n("multiplayer.nat.type.port_restricted_cone");
|
||||
} else if (info.isRestrictedCone()) {
|
||||
} else if (info.get().isRestrictedCone()) {
|
||||
return i18n("multiplayer.nat.type.restricted_cone");
|
||||
} else if (info.isSymmetric()) {
|
||||
} else if (info.get().isSymmetric()) {
|
||||
return i18n("multiplayer.nat.type.symmetric");
|
||||
} else if (info.isSymmetricUDPFirewall()) {
|
||||
} else if (info.get().isSymmetricUDPFirewall()) {
|
||||
return i18n("multiplayer.nat.type.symmetric_udp_firewall");
|
||||
} else {
|
||||
return i18n("multiplayer.nat.type.unknown");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -238,13 +243,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 +264,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++;
|
||||
@@ -307,17 +320,12 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
}).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 +349,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 +368,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 +398,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))
|
||||
@@ -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()) {
|
||||
|
||||
@@ -20,17 +20,25 @@ package org.jackhuang.hmcl.ui.versions;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.image.ImageView;
|
||||
import org.jackhuang.hmcl.event.Event;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.newImage;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class GameAdvancedListItem extends AdvancedListItem {
|
||||
private final Tooltip tooltip;
|
||||
private final ImageView imageView;
|
||||
private final WeakListenerHolder holder = new WeakListenerHolder();
|
||||
private Profile profile;
|
||||
private Consumer<Event> onVersionIconChangedListener;
|
||||
|
||||
public GameAdvancedListItem() {
|
||||
tooltip = new Tooltip();
|
||||
@@ -39,23 +47,33 @@ public class GameAdvancedListItem extends AdvancedListItem {
|
||||
setLeftGraphic(view.getKey());
|
||||
imageView = view.getValue();
|
||||
|
||||
FXUtils.onChangeAndOperate(Profiles.selectedVersionProperty(), version -> {
|
||||
if (version != null && Profiles.getSelectedProfile() != null &&
|
||||
Profiles.getSelectedProfile().getRepository().hasVersion(version)) {
|
||||
FXUtils.installFastTooltip(this, tooltip);
|
||||
setTitle(version);
|
||||
setSubtitle(null);
|
||||
imageView.setImage(Profiles.getSelectedProfile().getRepository().getVersionIconImage(version));
|
||||
tooltip.setText(version);
|
||||
} else {
|
||||
Tooltip.uninstall(this,tooltip);
|
||||
setTitle(i18n("version.empty"));
|
||||
setSubtitle(i18n("version.empty.add"));
|
||||
imageView.setImage(newImage("/assets/img/grass.png"));
|
||||
tooltip.setText("");
|
||||
}
|
||||
});
|
||||
holder.add(FXUtils.onWeakChangeAndOperate(Profiles.selectedVersionProperty(), this::loadVersion));
|
||||
|
||||
setActionButtonVisible(false);
|
||||
}
|
||||
|
||||
private void loadVersion(String version) {
|
||||
if (Profiles.getSelectedProfile() != profile) {
|
||||
profile = Profiles.getSelectedProfile();
|
||||
if (profile != null) {
|
||||
onVersionIconChangedListener = profile.getRepository().onVersionIconChanged.registerWeak(event -> {
|
||||
this.loadVersion(Profiles.getSelectedVersion());
|
||||
});
|
||||
}
|
||||
}
|
||||
if (version != null && Profiles.getSelectedProfile() != null &&
|
||||
Profiles.getSelectedProfile().getRepository().hasVersion(version)) {
|
||||
FXUtils.installFastTooltip(this, tooltip);
|
||||
setTitle(version);
|
||||
setSubtitle(null);
|
||||
imageView.setImage(Profiles.getSelectedProfile().getRepository().getVersionIconImage(version));
|
||||
tooltip.setText(version);
|
||||
} else {
|
||||
Tooltip.uninstall(this,tooltip);
|
||||
setTitle(i18n("version.empty"));
|
||||
setSubtitle(i18n("version.empty.add"));
|
||||
imageView.setImage(newImage("/assets/img/grass.png"));
|
||||
tooltip.setText("");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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.*;
|
||||
@@ -35,27 +37,47 @@ 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 static ModTranslations MOD = new ModTranslations("/assets/mod_data.txt");
|
||||
public static ModTranslations MODPACK = new ModTranslations("/assets/modpack_data.txt");
|
||||
public static ModTranslations EMPTY = new ModTranslations("");
|
||||
|
||||
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;
|
||||
|
||||
private 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 List<Mod> searchMod(String query) {
|
||||
if (!loadKeywords()) return Collections.emptyList();
|
||||
|
||||
StringBuilder newQuery = query.chars()
|
||||
@@ -77,19 +99,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 +133,7 @@ public final class ModTranslations {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean loadModIdMap() {
|
||||
private boolean loadModIdMap() {
|
||||
if (modIdMap != null) {
|
||||
return true;
|
||||
}
|
||||
@@ -127,7 +153,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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.FlowPane;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.event.Event;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.setting.VersionIconType;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class VersionIconDialog extends DialogPane {
|
||||
private final Profile profile;
|
||||
private final String versionId;
|
||||
private final Runnable onFinish;
|
||||
private final VersionSetting vs;
|
||||
|
||||
public VersionIconDialog(Profile profile, String versionId, Runnable onFinish) {
|
||||
this.profile = profile;
|
||||
this.versionId = versionId;
|
||||
this.onFinish = onFinish;
|
||||
this.vs = profile.getRepository().getLocalVersionSettingOrCreate(versionId);
|
||||
|
||||
setTitle(i18n("settings.icon"));
|
||||
FlowPane pane = new FlowPane();
|
||||
setBody(pane);
|
||||
|
||||
pane.getChildren().setAll(
|
||||
createCustomIcon(),
|
||||
createIcon(VersionIconType.GRASS),
|
||||
createIcon(VersionIconType.CHEST),
|
||||
createIcon(VersionIconType.CHICKEN),
|
||||
createIcon(VersionIconType.COMMAND),
|
||||
createIcon(VersionIconType.CRAFT_TABLE),
|
||||
createIcon(VersionIconType.FABRIC),
|
||||
createIcon(VersionIconType.FORGE),
|
||||
createIcon(VersionIconType.FURNACE)
|
||||
);
|
||||
}
|
||||
|
||||
private void exploreIcon() {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("extension.png"), "*.png"));
|
||||
File selectedFile = chooser.showOpenDialog(Controllers.getStage());
|
||||
if (selectedFile != null) {
|
||||
File iconFile = profile.getRepository().getVersionIconFile(versionId);
|
||||
try {
|
||||
FileUtils.copyFile(selectedFile, iconFile);
|
||||
|
||||
if (vs != null) {
|
||||
vs.setVersionIcon(VersionIconType.DEFAULT);
|
||||
}
|
||||
|
||||
onAccept();
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.log(Level.SEVERE, "Failed to copy icon file from " + selectedFile + " to " + iconFile, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Node createCustomIcon() {
|
||||
Node shape = SVG.plusCircleOutline(Theme.blackFillBinding(), 32, 32);
|
||||
shape.setMouseTransparent(true);
|
||||
RipplerContainer container = new RipplerContainer(shape);
|
||||
FXUtils.setLimitWidth(container, 36);
|
||||
FXUtils.setLimitHeight(container, 36);
|
||||
container.setOnMouseClicked(e -> {
|
||||
exploreIcon();
|
||||
});
|
||||
return container;
|
||||
}
|
||||
|
||||
private Node createIcon(VersionIconType type) {
|
||||
ImageView imageView = new ImageView(new Image(type.getResourceUrl(), 32, 32, true, true));
|
||||
imageView.setMouseTransparent(true);
|
||||
RipplerContainer container = new RipplerContainer(imageView);
|
||||
FXUtils.setLimitWidth(container, 36);
|
||||
FXUtils.setLimitHeight(container, 36);
|
||||
container.setOnMouseClicked(e -> {
|
||||
if (vs != null) {
|
||||
vs.setVersionIcon(type);
|
||||
onAccept();
|
||||
}
|
||||
});
|
||||
return container;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAccept() {
|
||||
profile.getRepository().onVersionIconChanged.fireEvent(new Event(this));
|
||||
onFinish.run();
|
||||
super.onAccept();
|
||||
}
|
||||
}
|
||||
@@ -28,10 +28,15 @@ import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.EventPriority;
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||
import org.jackhuang.hmcl.game.GameRepository;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
@@ -43,6 +48,7 @@ import java.io.File;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
@@ -55,6 +61,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
private final TransitionPane transitionPane = new TransitionPane();
|
||||
private final BooleanProperty currentVersionUpgradable = new SimpleBooleanProperty();
|
||||
private final ObjectProperty<Profile.ProfileVersion> version = new SimpleObjectProperty<>();
|
||||
private final WeakListenerHolder listenerHolder = new WeakListenerHolder();
|
||||
|
||||
private String preferredVersionName = null;
|
||||
|
||||
@@ -72,6 +79,22 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
FXUtils.onChangeAndOperate(tab.getSelectionModel().selectedItemProperty(), newValue -> {
|
||||
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE.getAnimationProducer());
|
||||
});
|
||||
|
||||
listenerHolder.add(EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).registerWeak(event -> checkSelectedVersion(), EventPriority.HIGHEST));
|
||||
}
|
||||
|
||||
private void checkSelectedVersion() {
|
||||
runInFX(() -> {
|
||||
if (this.version.get() == null) return;
|
||||
GameRepository repository = this.version.get().getProfile().getRepository();
|
||||
if (!repository.hasVersion(this.version.get().getVersion())) {
|
||||
if (preferredVersionName != null) {
|
||||
loadVersion(preferredVersionName, this.version.get().getProfile());
|
||||
} else {
|
||||
fireEvent(new PageCloseEvent());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private <T extends Node> Supplier<T> loadVersionFor(Supplier<T> nodeSupplier) {
|
||||
|
||||
@@ -32,10 +32,7 @@ import javafx.scene.layout.*;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.setting.LauncherVisibility;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
import org.jackhuang.hmcl.setting.*;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
@@ -44,9 +41,7 @@ import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
@@ -55,17 +50,14 @@ import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.newImage;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
@@ -120,7 +112,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();
|
||||
@@ -162,6 +154,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);
|
||||
|
||||
@@ -775,18 +771,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
if (versionId == null)
|
||||
return;
|
||||
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("extension.png"), "*.png"));
|
||||
File selectedFile = chooser.showOpenDialog(Controllers.getStage());
|
||||
if (selectedFile != null) {
|
||||
File iconFile = profile.getRepository().getVersionIconFile(versionId);
|
||||
try {
|
||||
FileUtils.copyFile(selectedFile, iconFile);
|
||||
loadIcon();
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.log(Level.SEVERE, "Failed to copy icon file from " + selectedFile + " to " + iconFile, e);
|
||||
}
|
||||
}
|
||||
Controllers.dialog(new VersionIconDialog(profile, versionId, this::loadIcon));
|
||||
}
|
||||
|
||||
private void onDeleteIcon() {
|
||||
@@ -796,6 +781,10 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
File iconFile = profile.getRepository().getVersionIconFile(versionId);
|
||||
if (iconFile.exists())
|
||||
iconFile.delete();
|
||||
VersionSetting localVersionSetting = profile.getRepository().getLocalVersionSettingOrCreate(versionId);
|
||||
if (localVersionSetting != null) {
|
||||
localVersionSetting.setVersionIcon(VersionIconType.DEFAULT);
|
||||
}
|
||||
loadIcon();
|
||||
}
|
||||
|
||||
@@ -804,11 +793,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
return;
|
||||
}
|
||||
|
||||
File iconFile = profile.getRepository().getVersionIconFile(versionId);
|
||||
if (iconFile.exists())
|
||||
iconPickerItem.setImage(new Image("file:" + iconFile.getAbsolutePath()));
|
||||
else
|
||||
iconPickerItem.setImage(newImage("/assets/img/grass.png"));
|
||||
iconPickerItem.setImage(profile.getRepository().getVersionIconImage(versionId));
|
||||
FXUtils.limitSize(iconPickerItem.getImageView(), 32, 32);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
@@ -122,8 +127,13 @@ public final class Versions {
|
||||
return;
|
||||
}
|
||||
if (profile.getRepository().renameVersion(version, newName)) {
|
||||
profile.getRepository().refreshVersionsAsync().start();
|
||||
resolve.run();
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
+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")) {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
@@ -21,22 +21,16 @@ import javafx.application.Platform;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Alert.AlertType;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.countly.CrashReport;
|
||||
import org.jackhuang.hmcl.ui.CrashWindow;
|
||||
import org.jackhuang.hmcl.upgrade.IntegrityChecker;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static java.util.Collections.newSetFromMap;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
@@ -65,8 +59,6 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
pair("com.sun.javafx.css.StyleManager.findMatchingStyles", i18n("launcher.update_java")),
|
||||
pair("NoSuchAlgorithmException", "Has your operating system been installed completely or is a ghost system?")
|
||||
};
|
||||
|
||||
static final Set<String> CAUGHT_EXCEPTIONS = newSetFromMap(new ConcurrentHashMap<>());
|
||||
}
|
||||
|
||||
private boolean checkThrowable(Throwable e) {
|
||||
@@ -101,29 +93,11 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
LOG.log(Level.SEVERE, "Uncaught exception in thread " + t.getName(), e);
|
||||
|
||||
try {
|
||||
String stackTrace = StringUtils.getStackTrace(e);
|
||||
if (!stackTrace.contains("org.jackhuang"))
|
||||
CrashReport report = new CrashReport(t, e);
|
||||
if (!report.shouldBeReport())
|
||||
return;
|
||||
|
||||
if (Hole.CAUGHT_EXCEPTIONS.contains(stackTrace))
|
||||
return;
|
||||
Hole.CAUGHT_EXCEPTIONS.add(stackTrace);
|
||||
|
||||
String text = "---- Hello Minecraft! Crash Report ----\n" +
|
||||
" Version: " + Metadata.VERSION + "\n" +
|
||||
" Time: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "\n" +
|
||||
" Thread: " + t + "\n" +
|
||||
"\n Content: \n " +
|
||||
stackTrace + "\n\n" +
|
||||
"-- System Details --\n" +
|
||||
" Operating System: " + OperatingSystem.SYSTEM_NAME + ' ' + OperatingSystem.SYSTEM_VERSION + "\n" +
|
||||
" System Architecture: " + Architecture.SYSTEM_ARCH_NAME + "\n" +
|
||||
" Java Architecture: " + Architecture.CURRENT_ARCH_NAME + "\n" +
|
||||
" Java Version: " + System.getProperty("java.version") + ", " + System.getProperty("java.vendor") + "\n" +
|
||||
" Java VM Version: " + System.getProperty("java.vm.name") + " (" + System.getProperty("java.vm.info") + "), " + System.getProperty("java.vm.vendor") + "\n" +
|
||||
" JVM Max Memory: " + Runtime.getRuntime().maxMemory() + "\n" +
|
||||
" JVM Total Memory: " + Runtime.getRuntime().totalMemory() + "\n" +
|
||||
" JVM Free Memory: " + Runtime.getRuntime().freeMemory() + "\n";
|
||||
String text = report.getDisplayText();
|
||||
|
||||
LOG.log(Level.SEVERE, text);
|
||||
Platform.runLater(() -> {
|
||||
@@ -132,7 +106,7 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
new CrashWindow(text).show();
|
||||
}
|
||||
if (!UpdateChecker.isOutdated() && IntegrityChecker.isSelfVerified()) {
|
||||
reportToServer(text);
|
||||
reportToServer(report);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -141,10 +115,10 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private void reportToServer(final String text) {
|
||||
private void reportToServer(CrashReport crashReport) {
|
||||
Thread t = new Thread(() -> {
|
||||
HashMap<String, String> map = new HashMap<>();
|
||||
map.put("crash_report", text);
|
||||
map.put("crash_report", crashReport.getDisplayText());
|
||||
map.put("version", Metadata.VERSION);
|
||||
map.put("log", Logging.getLogs());
|
||||
try {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -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);
|
||||
}
|
||||
@@ -285,7 +285,7 @@
|
||||
|
||||
}
|
||||
|
||||
.two-line-item-second-large > .first-line > .title {
|
||||
.two-line-item-second-large > .first-line > .title, .two-line-item-second-large-title {
|
||||
-fx-text-fill: rgba(0, 0, 0, 0.5);
|
||||
-fx-font-weight: normal;
|
||||
-fx-font-size: 12px;
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<VBox fx:id="borderPane" alignment="CENTER" FXUtils.limitWidth="500">
|
||||
<ComponentList>
|
||||
<BorderPane><left><Label text="%modpack.task.install.will" /></left><right><Label fx:id="lblModpackLocation" /></right></BorderPane>
|
||||
<BorderPane><left><Label text="%modpack.enter_name" /></left><right><JFXTextField fx:id="txtModpackName" StackPane.margin="$insets" /></right></BorderPane>
|
||||
<BorderPane><left><Label BorderPane.alignment="CENTER_LEFT" text="%archive.name" /></left><right><JFXTextField fx:id="txtModpackName" StackPane.margin="$insets" /></right></BorderPane>
|
||||
|
||||
<BorderPane><left><Label text="%modpack.name"/></left><right><Label fx:id="lblName" /></right></BorderPane>
|
||||
<BorderPane><left><Label text="%archive.version"/></left><right><Label fx:id="lblVersion" /></right></BorderPane>
|
||||
|
||||
@@ -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 |
@@ -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
|
||||
@@ -36,11 +36,12 @@ about.thanks_to.bangbang93.statement=BMCLAPI download source provider.
|
||||
about.thanks_to.contributors=All contributors who participated in this project via issues, pull requests, etc.
|
||||
about.thanks_to.contributors.statement=Thanks to the open source community for supporting the Hello Minecraft! Launcher
|
||||
about.thanks_to.gamerteam.statement=Default background image provider.
|
||||
about.thanks_to.glavo.statement=Contributed a lot of technical support to HMCL
|
||||
about.thanks_to.mcbbs=MCBBS
|
||||
about.thanks_to.mcbbs.statement=MCBBS download source provider.
|
||||
about.thanks_to.mcmod=mcmod.cn
|
||||
about.thanks_to.mcmod.statement=Mod Chinese name translations provider.
|
||||
about.thanks_to.noin=noin.cn
|
||||
about.thanks_to.noin=mcer.cn
|
||||
about.thanks_to.noin.statement=Multiplayer service provider.
|
||||
about.thanks_to.red_lnn.statement=Default background image provider.
|
||||
about.thanks_to.users=Members of HMCL User Group
|
||||
@@ -71,6 +72,7 @@ account.failed.migration=Your account needs to be migrated to a Microsoft accoun
|
||||
account.failed.no_character=No character in this account.
|
||||
account.failed.server_response_malformed=Invalid server response. The authentication server may have an error.
|
||||
account.failed.wrong_account=Logged in with mismatched account.
|
||||
account.hmcl.hint= You need to click "Login" and complete the login in the open web page
|
||||
account.injector.add=Add an authentication server
|
||||
account.injector.empty=Empty (Click the plus button on the right to add)
|
||||
account.injector.http=Warning: This server uses HTTP so your password will be transmitted in clear text.
|
||||
@@ -88,6 +90,7 @@ account.methods=Login Type
|
||||
account.methods.authlib_injector=authlib-injector
|
||||
account.methods.microsoft=Microsoft Account
|
||||
account.methods.microsoft.birth=How to edit birthday setting...
|
||||
account.methods.microsoft.deauthorize=Unauthorize the account
|
||||
account.methods.microsoft.close_page=Microsoft account authorization has been finished. There are some remaining logging-in steps to be finished later. You can close this page right now.
|
||||
account.methods.microsoft.error.add_family=Since you are not yet 18 years old, an adult must add you to a family in order for you to play Minecraft.
|
||||
account.methods.microsoft.error.add_family_probably=Please check your account settings whether you are already 18 years old or not. If not, adding your account to a family is necessary.
|
||||
@@ -96,8 +99,8 @@ account.methods.microsoft.error.missing_xbox_account=Your Microsoft account is n
|
||||
account.methods.microsoft.error.no_character=Account is missing a Minecraft Java profile. While the Microsoft account is valid, it does not own the game.
|
||||
account.methods.microsoft.error.unknown=Failed to log in. Microsoft respond with error code %d.
|
||||
account.methods.microsoft.logging_in=Logging in...
|
||||
account.methods.microsoft.hint=You should click "login" button, paste the device code shown here later and continue login process in newly opened browser window.
|
||||
account.methods.microsoft.manual=Your device code is <b>%1$s</b>, click here to copy. After clicking "login" button, you should finish authorization in the newly opened browser window. If the browser window failed to show, you can open %2$s manually.
|
||||
account.methods.microsoft.hint=You should click "login" button, paste the device code shown here later and continue login process in newly opened browser window.\nIf the Token used to log in to the Microsoft account is accidentally leaked, you can click "Unauthorize the account" below to release the login authorization
|
||||
account.methods.microsoft.manual=Your device code is <b>%1$s</b>, click here to copy. After clicking "login" button, you should finish authorization in the newly opened browser window. If the browser window failed to show, you can open %2$s manually.\nIf the Token used to log in to the Microsoft account is accidentally leaked, you can click "Unauthorize the account" below to release the login authorization
|
||||
account.methods.microsoft.profile=Account Profile...
|
||||
account.methods.microsoft.snapshot=HMCL Snapshot version does not support Microsoft login.
|
||||
account.methods.microsoft.waiting_browser=Waiting for authorization in opened browser window...
|
||||
@@ -108,6 +111,7 @@ account.methods.offline.uuid.malformed=Malformed
|
||||
account.methods.yggdrasil=Mojang
|
||||
account.methods.yggdrasil.migration=Migrate to Microsoft Account
|
||||
account.methods.yggdrasil.migration.how=How to migrate to Microsoft Account?
|
||||
account.methods.yggdrasil.migration.hint=As of March 10, 2022, Mojang accounts are no longer supported, and you must migrate to a Microsoft account to log into Minecraft.\nThe link below will guide you to migrate your Mojang account to a Microsoft account.
|
||||
account.methods.yggdrasil.purchase=Purchase Minecraft
|
||||
account.missing=No Account
|
||||
account.missing.add=Click here to add
|
||||
@@ -132,7 +136,7 @@ account.username=Name
|
||||
archive.author=Authors
|
||||
archive.date=Publish Date
|
||||
archive.game_version=Game
|
||||
archive.name=Nickname
|
||||
archive.name=Filename
|
||||
archive.version=Version
|
||||
|
||||
assets.download=Downloading assets
|
||||
@@ -180,8 +184,16 @@ curse.category.4479=Hardcore
|
||||
curse.category.4482=Extra Large
|
||||
curse.category.4472=Tech
|
||||
curse.category.4473=Magic
|
||||
curse.category.5128=Vanilla+
|
||||
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/6
|
||||
curse.category.5299=Education
|
||||
curse.category.5232=Galacticraft
|
||||
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
|
||||
@@ -223,7 +235,9 @@ curse.category.4558=Redstone
|
||||
curse.category.4843=Automation
|
||||
curse.category.4906=MCreator
|
||||
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/6
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/12
|
||||
curse.category.5244=Font Packs
|
||||
curse.category.5193=Data Packs
|
||||
curse.category.399=Steampunk
|
||||
curse.category.396=128x
|
||||
curse.category.398=512x and Higher
|
||||
@@ -293,6 +307,7 @@ exception.access_denied=It's denied by operating system to access file %s. Maybe
|
||||
Please check if current operating system user has permission to access that file.\n\
|
||||
For Windows users, you can also try the Resource Monitor, find if some programs is holding the file, try to close related program, or restart your computer.
|
||||
exception.artifact_malformed=The file cannot pass verification.
|
||||
exception.ssl_handshake=Cannot establish SSL connection because of missing SSL certifcate in current Java installation. Try to start HMCL on another Java VM.
|
||||
|
||||
extension.bat=Windows Bat file
|
||||
extension.mod=Mod file
|
||||
@@ -307,6 +322,7 @@ fatal.config_loading_failure=The configuration is not accessible.\nPlease ensure
|
||||
fatal.migration_requires_manual_reboot=The update is complete. Please reopen Hello Minecraft! Launcher.
|
||||
fatal.apply_update_failure=We're sorry, Hello Minecraft! Launcher couldn't finish the update because something went wrong.\nBut you can still manually finish the update by downloading Hello Minecraft! Launcher from %s.\nPlease consider reporting this issue to us.
|
||||
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=Illegal character '=' in your user folder path. HMCL can work, but some features don't work.\nYou will not be able to use the authlib-injector account or changing skin of offline account, and there may be greater security risks in multiplayer games.
|
||||
|
||||
feedback=Feedback
|
||||
feedback.add=Add Feedback
|
||||
@@ -379,6 +395,7 @@ game.crash.reason.mod_resolution_missing=Game crashed because the mod that some
|
||||
game.crash.reason.mod_resolution_missing_minecraft=Game crashed because a mod is incompatible with current Minecraft version.\n%1$s requires Minecraft version %2$s。\nIf you want to keep the installed version of the mod, you should change the game version.\nIf you want to continue using current game version, you should reinstall a suitable mod.
|
||||
game.crash.reason.mod_resolution_mod_version=%1$s (Version: %2$s)
|
||||
game.crash.reason.mod_resolution_mod_version.any=%1$s (Any version)
|
||||
game.crash.reason.modlauncher_8=Game crashed because the Forge version you are using is not compatible with the current Java version. Please try updating Forge, or start with Java 8u312/11.0.13/17.0.1 or earlier.
|
||||
game.crash.reason.no_class_def_found_error=Game cannot run because of incomplete code.\nYour game may be missing a mod, or some mod files may be incomplete, or some mods are incompatible with current game.\nYou may need to reinstall the game and mods, or ask for help.\nMissing %1$s
|
||||
game.crash.reason.no_such_method_error=Game cannot run because of incomplete code.\nYour game may be missing a mod, or some mod files may be incomplete, or some mods are incompatible with current game.\nYou may need to reinstall the game and mods, or ask for help.
|
||||
game.crash.reason.opengl_not_supported=Game cannot run because of a problem with your graphics driver.\nBecause OpenGL is not supported, are you in remote desktop or streaming mode? If yes, please use the original computer to start game.\nOr try to update your graphics driver to the latest version and restart game. If your computer has a discrete graphics, you need to check whether the game uses integrated/core graphics. If so, try to run HMCL and game using discrete graphics card. If you still have these problems, you may consider getting a new graphics card or a new computer.
|
||||
@@ -386,7 +403,7 @@ game.crash.reason.openj9=Game cannot run on OpenJ9 virtual machine, please switc
|
||||
game.crash.reason.out_of_memory=Game cannot run because out of memory.\nThis may be due to too little memory allocation, or too many mods.\nYou can increase the game memory allocation in the game settings to allow the game running on larger memory.\nIf you still have these problems, you may need to change a better computer.
|
||||
game.crash.reason.resolution_too_high=Game cannot run because you are using a resource pack whose resolution is too high\nYou should use a resource pack with lower resolution, or buy a graphics card with larger memory.
|
||||
game.crash.reason.stacktrace=Unknown. You can look into details by clicking "Logs" button.\nThere are some keywords that may represent Mod ID. You can search them online to figure out the reason.\n%s
|
||||
game.crash.reason.too_old_java=Game cannot run because the Java virtual machine version is too low.\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=Game cannot run because the Java virtual machine version is too low.\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 from <a href="https://docs.microsoft.com/en-US/java/openjdk/download">here</a>.
|
||||
game.crash.reason.unknown=Unknown. You can look into details by clicking "Logs" button.
|
||||
game.crash.reason.unsatisfied_link_error=Game cannot run because the required native libraries are missing.\nThe missing native library: %1$s。\nIf you have modified the native library path option in game settings, you'd better switch back to standard mode.\nIf it's already standard mode, please check if the missing native library belongs to a mod or the game. If you are sure that it's caused by HMCL, you can give feedback to us.\nIf you do really need to customize native library path, you should place all the native libraries the the game requires in the directory.
|
||||
game.crash.title=Game crashed
|
||||
@@ -451,6 +468,7 @@ launch.advice.uncorrected=If you are sure that the game can be started normally,
|
||||
launch.advice.different_platform=Your OS is 64-Bit but your Java is 32-Bit. The 64-Bit Java is recommended.
|
||||
launch.advice.forge2760_liteloader=Forge 2760 and higher are not compatible with LiteLoader, please consider upgrading Forge to 2773 or later. Shall we continue launching?
|
||||
launch.advice.forge28_2_2_optifine=Forge 28.2.2 and later versions are not compatible with OptiFine. Please consider downgrading Forge to 28.2.1 or earlier versions. Shall we continue launching?
|
||||
launch.advice.forge37_0_60=Forge versions earlier than 37.0.60 are not compatible with Java 17. Please update forge or start the game with Java 16. Shall we continue launching?
|
||||
launch.advice.java8_1_13=Minecraft 1.13 and later can only run on Java 8 or later.
|
||||
launch.advice.java8_51_1_13=Minecraft 1.13 may crash on Java 8 earlier than 1.8.0_51. Please install the latest version of Java 8.
|
||||
launch.advice.java9=You cannot launch Minecraft 1.12 or earlier with Java 9 or later versions of Java.
|
||||
@@ -459,6 +477,7 @@ launch.advice.not_enough_space=You have allocated too much memory, because the p
|
||||
launch.advice.require_newer_java_version=Minecraft %1$s requires Java %2$s or later, are you willing to download one now?
|
||||
launch.advice.too_large_memory_for_32bit=You have allocated too much memory, because of your 32-Bit Java Runtime Environment, your game may crash. The maximum memory capacity for 32 bit systems is is 1024MB. Shall we continue launching?
|
||||
launch.advice.vanilla_linux_java_8=For Linux x86-64, Minecraft 1.12.2 and below can only run on Java 8.\nJava 9 and above versions cannot load 32-bit native libraries like liblwjgl.so.
|
||||
launch.advice.modlauncher8=The Forge version you are using is not compatible with the current Java version. Please try updating Forge, or start with Java 8u312/11.0.13/17.0.1 or earlier. Shall we continue launching?
|
||||
launch.advice.vanilla_x86=Minecraft currently does not provide official support for architectures other than x86 and x86-64.\nYou can try to download the corresponding native library of the platform and specify its placement path.
|
||||
launch.advice.vanilla_x86.translation=Minecraft currently does not provide official support for architectures other than x86 and x86-64.\nPlease use Java for x86-64 to run minecraft through translator, or download the corresponding native library of the platform and specify its placement path.
|
||||
launch.failed=Unable to launch
|
||||
@@ -534,6 +553,7 @@ modpack.choose.local.detail=You can drag the modpack file into this page to inst
|
||||
modpack.choose.remote=Download modpack from Internet
|
||||
modpack.choose.remote.detail=Requires a direct download link to the remote modpack file
|
||||
modpack.choose.remote.tooltip=A direct download link to the remote modpack file
|
||||
modpack.completion=Install files related to modpack
|
||||
modpack.desc=Describe your modpack, including precautions and changelog. Markdown and online pictures are supported.
|
||||
modpack.description=Description
|
||||
modpack.download=Modpack Downloads
|
||||
@@ -567,16 +587,16 @@ modpack.origin.mcbbs=MCBBS
|
||||
modpack.origin.mcbbs.prompt=Thread id
|
||||
modpack.scan=Scanning this modpack
|
||||
modpack.task.install=Import Modpack
|
||||
modpack.task.install.error=This modpack file cannot be recognized. Only Curse and MultiMC modpacks are supported.
|
||||
modpack.task.install.error=This modpack file cannot be recognized. Only Curse, Modrinth, MCBBS and MultiMC modpacks are supported.
|
||||
modpack.task.install.will=Install the modpack:
|
||||
modpack.type.curse=Curse
|
||||
modpack.type.curse.completion=Install files related to Curse modpack
|
||||
modpack.type.curse.tolerable_error=We cannot complete the download of all files of this Curse modpack. You can retry the download when starting corresponding game version. You may retry for a couple of times due to network problems.
|
||||
modpack.type.curse.error=Unable to install this Curse modpack. Please retry.
|
||||
modpack.type.curse.not_found=Some of required resources are missing and thus could not be downloaded. Please consider the latest version or other modpacks.
|
||||
modpack.type.manual.warning=You probably need to directly decompress this modpack file, instead of importing this modpack. And launch the game using bundled launcher. This modpack is manually created by compressing .minecraft directory, not exported by launcher. HMCL can try to import this modpack, continue?
|
||||
modpack.type.mcbbs=MCBBS Standard
|
||||
modpack.type.mcbbs.export=Can be imported by Hello Minecraft! Launcher and MultiMC
|
||||
modpack.type.modrinth=Modrinth
|
||||
modpack.type.multimc=MultiMC
|
||||
modpack.type.multimc.export=Can be imported by Hello Minecraft! Launcher and MultiMC
|
||||
modpack.type.server=Server Auto-Update Modpack
|
||||
@@ -604,9 +624,14 @@ modrinth.category.decoration=Decoration
|
||||
modrinth.category.equipment=Equipment
|
||||
modrinth.category.fabric=Fabric
|
||||
modrinth.category.food=Food
|
||||
modrinth.category.forge=Forge
|
||||
modrinth.category.library=Library
|
||||
modrinth.category.magic=Magic
|
||||
modrinth.category.misc=Misc
|
||||
modrinth.category.modloader=Modloader
|
||||
modrinth.category.optimization=Optimization
|
||||
modrinth.category.quilt=Quilt
|
||||
modrinth.category.rift=Rift
|
||||
modrinth.category.storage=Storage
|
||||
modrinth.category.technology=Technology
|
||||
modrinth.category.utility=Utility
|
||||
@@ -654,6 +679,7 @@ multiplayer.exit.timeout.static_token=Failed to connect to multiplayer server. P
|
||||
multiplayer.exit.timeout.dynamic_token=Failed to connect to multiplayer server.
|
||||
multiplayer.hint=Multiplayer functionality is experimental. Please give feedback.
|
||||
multiplayer.nat=Network Type Detection
|
||||
multiplayer.nat.failed=Failed to detect network type.
|
||||
multiplayer.nat.hint=Network type detection will make it clear whether your network fulfills our requirement for multiplayer mode.
|
||||
multiplayer.nat.latency=Latency
|
||||
multiplayer.nat.not_yet_tested=Not yet testsed
|
||||
@@ -668,7 +694,7 @@ multiplayer.nat.type.restricted_cone=Medium (Restricted Cone)
|
||||
multiplayer.nat.type.symmetric=Bad (Symmetric)
|
||||
multiplayer.nat.type.symmetric_udp_firewall=Bad (Symmetric with UDP Firewall)
|
||||
multiplayer.nat.type.unknown=Unknown
|
||||
multiplayer.powered_by=Multiplayer service is provided by <a href="https://noin.cn">noin.cn</a>. <a href="https://noin.cn/agreement">EULA</a>
|
||||
multiplayer.powered_by=Multiplayer service is provided by <a href="https://mcer.cn">mcer.cn</a>. <a href="https://hmcl.huangyuhui.net/api/redirect/multiplayer-agreement">EULA</a>
|
||||
multiplayer.report=Report
|
||||
multiplayer.relay=Relay Mode
|
||||
multiplayer.relay.hint=For users who has a static token, and whose tested NAT type is bad-symmetric
|
||||
@@ -693,11 +719,10 @@ multiplayer.session.create.port=Port
|
||||
multiplayer.session.create.port.error=Cannot detect game port, you must click "Open LAN Server" in game to enable multiplayer functionality.
|
||||
multiplayer.session.create.token=Token
|
||||
multiplayer.session.create.token.apply=Apply for static token
|
||||
multiplayer.session.create.token.prompt=Default randomized. You can apply for your own token at noin.cn (Chinese website).
|
||||
multiplayer.session.create.token.prompt=Default randomized. You can apply for your own token at mcer.cn.
|
||||
multiplayer.session.error.already_started=Cato service has already started locally. Please check whether there are other multiplayer services served or not. Or you can kill cato process in task manager.
|
||||
multiplayer.session.error.file_not_found=Cannot find cato executable. This file should be downloaded when entering this page. Please check if your anti-virus software has isolated the file. If true, please rollback.
|
||||
multiplayer.session.expired=Multiplayer session has expired. You should re-create or re-join a room to continue.
|
||||
multiplayer.session.hint=You must click "Open LAN Server" in game in order to enable multiplayer functionality.
|
||||
multiplayer.session.join=Join Session
|
||||
multiplayer.session.join.error=Failed to join multiplayer session
|
||||
multiplayer.session.join.error.connection=Failed to join multiplayer session. Cannot establish connection.
|
||||
@@ -856,6 +881,7 @@ settings.game.java_directory.template=%s (%s)
|
||||
settings.game.management=Manage
|
||||
settings.game.working_directory=Working Directory
|
||||
settings.game.working_directory.choose=Choose Working Directory
|
||||
settings.game.working_directory.hint=Turn on the "Independent" in the "Working Directory" option to make the current version store settings, archives, modules and other game data independently. It is recommended to turn on this option when using mods to avoid conflicts between modules of different game versions. After modifying this option, please move the game archive and other game data files by yourself.
|
||||
|
||||
settings.icon=Game Icon
|
||||
|
||||
@@ -942,7 +968,7 @@ update.found=Update Available!
|
||||
update.newest_version=Latest version: %s
|
||||
update.bubble.title=Update Available: %s
|
||||
update.bubble.subtitle=Click here to update
|
||||
update.note=Warning: Beta versions may have more functionality and bug fixes but also more possible errors.
|
||||
update.note=Warning: Beta versions and nightly versions may have more functionality and bug fixes but also more possible errors.
|
||||
update.latest=This is the latest version.
|
||||
update.no_browser=Cannot open a browser. The link has been copied to the clipboard. Paste it into the address bar of browser to update.
|
||||
update.tooltip=Update
|
||||
@@ -970,7 +996,7 @@ version.manage.clean.tooltip=Clear logs, crash-reports
|
||||
version.manage.duplicate=Copy game instance
|
||||
version.manage.duplicate.duplicate_save=Copy saves
|
||||
version.manage.duplicate.prompt=Type new version name
|
||||
version.manage.duplicate.confirm=
|
||||
version.manage.duplicate.confirm=New version generated by replication will be locked: forced independent mode, game setting independence.
|
||||
version.manage.manage=Manage Version
|
||||
version.manage.manage.title=Manage Version - %1s
|
||||
version.manage.redownload_assets_index=Update Game Asset Files
|
||||
|
||||
@@ -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
|
||||
@@ -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を使用するため、パスワードはクリアテキストで送信されます。
|
||||
@@ -88,16 +90,17 @@ account.methods=ログインタイプ
|
||||
account.methods.authlib_injector=authlib-インジェクター
|
||||
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 +151,7 @@ button.install=インストール
|
||||
button.export=エクスポート
|
||||
button.no=いいえ
|
||||
button.ok=OK
|
||||
button.refresh=リフレッシュ
|
||||
button.refresh=更新
|
||||
button.remove=削除
|
||||
button.remove.confirm=削除してもよろしいですか?この操作をロールバックすることはできません!
|
||||
button.save=保存
|
||||
@@ -182,6 +185,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 +232,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 +283,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 +295,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 +315,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 +360,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 +413,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インストールファイルのインポートをサポートします
|
||||
@@ -425,7 +445,7 @@ install.installer.not_installed=インストールされていません
|
||||
install.installer.optifine=OptiFine
|
||||
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,7 +467,7 @@ 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プラットフォームの対応するネイティブライブラリをダウンロードして、その配置パスを指定してみてください。
|
||||
@@ -457,7 +477,7 @@ launch.failed.cannot_create_jvm=Java仮想マシンを作成できませんで
|
||||
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 +500,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 +565,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
|
||||
@@ -604,8 +625,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 +640,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 +659,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 +680,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 +696,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 +705,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 +740,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 +773,7 @@ profile.instance_directory=ゲームディレクトリ
|
||||
profile.instance_directory.choose=ゲームディレクトリを選択してください
|
||||
profile.manage=ゲームディレクトリリスト
|
||||
profile.name=名前
|
||||
profile.new=新しい構成
|
||||
profile.new=新規作成
|
||||
profile.title=ゲームディレクトリ
|
||||
profile.selected=選択済み
|
||||
profile.use_relative_path=可能であれば、ゲームディレクトリの相対パスを使用します
|
||||
@@ -799,7 +822,7 @@ 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 +845,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 +859,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 +886,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 +904,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 +922,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によって提供されます。詳細については、ここをクリックしてください。
|
||||
@@ -958,20 +983,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=次へ >
|
||||
File diff suppressed because it is too large.
Load diff
@@ -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
|
||||
@@ -36,11 +36,12 @@ about.thanks_to.bangbang93.statement=提供 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.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,6 +72,7 @@ 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 協定,您的密碼在登入時會被明文傳輸。
|
||||
@@ -88,16 +90,17 @@ account.methods=登入方式
|
||||
account.methods.authlib_injector=authlib-injector 登入
|
||||
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.waiting_browser=等待在新打開的瀏覽器窗口中完成登入...
|
||||
@@ -105,9 +108,10 @@ 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 +170,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 +178,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 +236,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 及更高
|
||||
@@ -293,6 +307,7 @@ exception.access_denied=因為無法訪問文件 %s,HMCL 沒有對該文件的
|
||||
請你檢查當前操作系統帳戶是否能訪問該文件,比如非管理員用戶可能不能訪問其他帳戶的個人文件夾內的文件。\n\
|
||||
對於 Windows 用戶,你還可以嘗試通過資源監視器查看是否有程序占用了該文件,如果是,你可以關閉占用此文件相關程序,或者重啟電腦再試。
|
||||
exception.artifact_malformed=下載的文件正確,無法通過校驗。
|
||||
exception.ssl_handshake=無法建立 SSL 連接,因為當前 Java 虛擬機缺少相關的 SSL 證書。你可以嘗試使用其他的 Java 虛擬機啟動 HMCL 再試。
|
||||
|
||||
extension.bat=Windows 指令碼
|
||||
extension.mod=模組檔案
|
||||
@@ -307,6 +322,7 @@ fatal.config_loading_failure=Hello Minecraft! Launcher 無法載入設定檔案
|
||||
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您將無法使用外置登入帳戶以及離線登入更換皮膚功能,並且在進行多人遊戲時可能存在更大的安全隱患。
|
||||
|
||||
feedback=回饋
|
||||
feedback.add=新增回饋
|
||||
@@ -379,6 +395,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 與遊戲。如果仍有問題,你可能需要考慮換一個新顯示卡或新電腦。
|
||||
@@ -386,8 +403,8 @@ game.crash.reason.openj9=當前遊戲無法運行在 OpenJ9 虛擬機上,請
|
||||
game.crash.reason.out_of_memory=當前遊戲因為記憶體不足,無法繼續運行。\n這可能是記憶體分配太小,或者 Mod 數量過多導致的。\n你可以在遊戲設置中調大遊戲記憶體分配值以允許遊戲在更大的記憶體下運行。\n如果仍然出現該錯誤,你可能需要換一台更好的電腦。
|
||||
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 虛擬機,並重新啟動遊戲。如果沒有下載安裝,你可以在網路上自行下載。
|
||||
game.crash.reason.unknown=原因未知,請點擊日誌按鈕查看詳細訊息。\n不要將本界面截圖給他人!如果你要求助他人,請你點擊左下角導出遊戲崩潰訊息後將導出的文件發送給他人以供分析!\n你可以向我們提供遊戲崩潰日誌以提升 HMCL 的日誌分析能力。
|
||||
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<b>不要將本界面截圖給他人!</b>如果你要求助他人,請你點擊左下角導出遊戲崩潰訊息後將導出的文件發送給他人以供分析!\n你可以向我們提供遊戲崩潰日誌以提升 HMCL 的日誌分析能力。
|
||||
game.crash.reason.unsatisfied_link_error=當前遊戲因為缺少本地庫,無法繼續運行。\n這些本地庫缺失:%1$s。\n如果你在遊戲設置中修改了本地庫路徑選項,請你修改回預設模式。\n如果已經在預設模式下,請檢查本地庫缺失是否是 Mod 引起的,或由 HMCL 引起的。如果你確定是 HMCL 引起的,建議你向我們回饋。\n如果你確實需要自訂本地庫路徑,你需要保證其中包含缺失的本地庫。
|
||||
game.crash.title=遊戲意外退出
|
||||
game.directory=遊戲路徑
|
||||
@@ -395,7 +412,7 @@ game.version=遊戲版本
|
||||
|
||||
help=說明
|
||||
help.doc=Hello Minecraft! Launcher 說明檔案
|
||||
help.detail=可查閱資料包、整合包製作指南等內容。
|
||||
help.detail=可查閱資料包、模組包製作指南等內容。
|
||||
|
||||
input.email=[使用者名稱] 必須是電子信箱格式
|
||||
input.number=必須是數字
|
||||
@@ -433,7 +450,7 @@ install.installer.liteloader=LiteLoader
|
||||
install.installer.not_installed=不安裝
|
||||
install.installer.optifine=OptiFine
|
||||
install.installer.version=%s
|
||||
install.modpack=安裝整合包
|
||||
install.modpack=安裝模組包
|
||||
install.new_game=安裝新遊戲版本
|
||||
install.new_game.already_exists=此版本已經存在,請重新命名
|
||||
install.new_game.current_game_version=目前遊戲版本
|
||||
@@ -451,16 +468,18 @@ 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_x86.translation=你的電腦中沒有安裝 X64 架構的 Java 虛擬機(JDK),你可以在 <a href="https://docs.microsoft.com/zh-tw/java/openjdk/download"> 這裡 </a> 下載 <b> X64 </b> 架構的 Java 虛擬機。\nMinecraft 尚未提供對 ARM 平台的支持,不能在您目前使用的 ARM64 架構 JDK 上運行。因此 HMCL 會借助 Rosetta 2 轉譯執行 X64 架構的 JDK 來啟動 Minecraft。
|
||||
launch.failed=啟動失敗
|
||||
launch.failed.cannot_create_jvm=偵測到無法建立 Java 虛擬機,可能是 Java 參數有問題。可以在設定中開啟無參數模式啟動。
|
||||
launch.failed.creating_process=啟動失敗,在建立新處理程式時發生錯誤。可能是 Java 路徑錯誤。
|
||||
@@ -527,20 +546,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 調校輸出
|
||||
@@ -554,49 +574,49 @@ 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=但未能完成 Curse 模組包檔案的下載,您可以在啟動該遊戲版本時繼續 Curse 模組包檔案的下載。由於網路問題,您可能需要重試多次。
|
||||
modpack.type.curse.error=無法完成 Curse 模組包的下載,請多次重試或設定代理
|
||||
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.cursed=Cursed
|
||||
@@ -604,9 +624,14 @@ modrinth.category.decoration=裝飾
|
||||
modrinth.category.equipment=裝備
|
||||
modrinth.category.fabric=Fabric
|
||||
modrinth.category.food=食物
|
||||
modrinth.category.forge=Forge
|
||||
modrinth.category.library=支持庫
|
||||
modrinth.category.magic=魔法
|
||||
modrinth.category.misc=其他
|
||||
modrinth.category.modloader=Modloader
|
||||
modrinth.category.optimization=最佳化
|
||||
modrinth.category.quilt=Quilt
|
||||
modrinth.category.rift=Rift
|
||||
modrinth.category.storage=儲存
|
||||
modrinth.category.technology=科技
|
||||
modrinth.category.utility=實用
|
||||
@@ -650,10 +675,11 @@ multiplayer.download.success=多人聯機初始化完成
|
||||
multiplayer.download.unsupported=多人聯機依賴不支持當前系統或平台
|
||||
multiplayer.exit.after_ready=多人聯機會話意外退出,退出碼 %d
|
||||
multiplayer.exit.before_ready=多人聯機房間創建失敗,cato 退出碼 %d
|
||||
multiplayer.exit.timeout.static_token=無法連接多人聯機服務,請你切換動態 Token 重試。
|
||||
multiplayer.exit.timeout.static_token=無法連接多人聯機服務,你的憑證可能無法正常工作,你可以清空憑證再試。\n你可以聯系在線管理員請問鑑權伺服器是否正常工作,或者耐心等待十分鍾。
|
||||
multiplayer.exit.timeout.dynamic_token=無法連接多人聯機服務,你可以在多人聯機頁面的回饋中回饋問題。
|
||||
multiplayer.hint=多人聯機功能處於實驗階段,如果有問題請回饋。
|
||||
multiplayer.nat=網路檢測
|
||||
multiplayer.nat.failed=檢測失敗,但你仍然可以繼續使用聯機功能。
|
||||
multiplayer.nat.hint=執行網路檢測可以讓你更清楚你的網路狀況是否符合聯機功能的需求。不符合聯機功能運行條件的網路狀況將可能導致聯機失敗。
|
||||
multiplayer.nat.latency=延遲
|
||||
multiplayer.nat.not_yet_tested=尚未檢測
|
||||
@@ -668,10 +694,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=NAT 網路類型為差-對稱型的用戶,若無法聯機,申請靜態 Token 後可以啟用本選項
|
||||
multiplayer.relay=橋接服務
|
||||
multiplayer.relay.hint=Cato 面向較差網路提供的網路質量增強最佳化解決方案|若無法聯機請到 mcer.cn 的個人中心兌換憑證後啟用
|
||||
multiplayer.session=房間
|
||||
multiplayer.session.name.format=%1$s 的房間
|
||||
multiplayer.session.name.motd=HMCL 多人聯機房間 - %s
|
||||
@@ -680,7 +706,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=創建聯機房間失敗,你可以使用動態 Token 再試。
|
||||
multiplayer.session.create.error.static_token=創建聯機房間失敗,你的憑證可能無法正常工作,你可以清空憑證再試。\n你可以聯系在線管理員請問鑑權伺服器是否正常工作,或者耐心等待十分鍾。
|
||||
multiplayer.session.create.hint=創建聯機房間前,你需要先在正在運行的遊戲內的遊戲菜單中選擇 對區域網路開放 選項,然後在下方的輸入框中輸入遊戲內提示的埠號(通常是 5 位的數字)
|
||||
multiplayer.session.create.join=連接申請
|
||||
multiplayer.session.create.join.allow=自動接受所有連接申請(不啟用此選項時,你需要手動同意申請,以避免不相關人士誤連你的伺服器)
|
||||
@@ -691,9 +717,9 @@ multiplayer.session.create.members.kick.prompt=是否踢出該玩家?踢出後
|
||||
multiplayer.session.create.name=房間名稱
|
||||
multiplayer.session.create.port=埠號
|
||||
multiplayer.session.create.port.error=無法檢測遊戲埠號,你必須先啟動遊戲並在遊戲內打開對區域網路開放選項後才能啟動聯機。
|
||||
multiplayer.session.create.token=Token
|
||||
multiplayer.session.create.token.apply=申請靜態 Token
|
||||
multiplayer.session.create.token.prompt=預設為臨時 Token。你可以在 noin.cn 上申請靜態 Token 並填寫至此處
|
||||
multiplayer.session.create.token=憑證
|
||||
multiplayer.session.create.token.apply=申請憑證
|
||||
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 小時,你需要重新創建/加入房間以繼續聯機。
|
||||
@@ -855,6 +881,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=遊戲圖示
|
||||
|
||||
@@ -941,7 +968,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=更新
|
||||
@@ -982,7 +1009,7 @@ version.manage.rename=重新命名該版本
|
||||
version.manage.rename.message=請輸入新名稱
|
||||
version.manage.rename.fail=重新命名版本失敗,可能檔案被佔用或者名稱有特殊字元
|
||||
version.settings=遊戲設定
|
||||
version.update=更新整合包
|
||||
version.update=更新模組包
|
||||
|
||||
wizard.prev=< 上一步
|
||||
wizard.failed=失敗
|
||||
|
||||
@@ -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
|
||||
@@ -28,26 +28,27 @@ about.community=社区
|
||||
about.community.discord=Discord
|
||||
about.community.discord.statement=欢迎加入 Discord 讨论区,加入后请遵守讨论区规定
|
||||
about.community.kaiheila=开黑啦
|
||||
about.community.kaiheila.statement=欢迎加入开黑啦讨论区,加入后请遵守讨论区规定
|
||||
about.community.kaiheila.statement=欢迎加入开黑啦讨论区,加入后请遵守讨论区规定!
|
||||
about.dependency=依赖
|
||||
about.legal=法律声明
|
||||
about.thanks_to=鸣谢
|
||||
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.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.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 用户群成员
|
||||
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/)
|
||||
about.open_source.statement=GPL v3 (https://github.com/huanghongxun/HMCL)
|
||||
|
||||
account=帐户
|
||||
account.cape=披风
|
||||
@@ -61,19 +62,20 @@ 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.invalid_credentials=您的用户名或密码错误,或者登录次数过多被暂时禁止登录,请稍后再试
|
||||
account.failed.connect_authentication_server=无法连接认证服务器,可能是网络问题,请检查设备能否正常上网或使用代理服务
|
||||
account.failed.connect_injector_server=无法连接认证服务器,可能是网络问题,请检查设备是否能正常上网,检查 URL 是否输入错误,或使用代理服务
|
||||
account.failed.injector_download_failure=无法下载 authlib-injector,可能是网络问题,请检查设备是否能正常上网、尝试切换下载源或使用代理服务
|
||||
account.failed.invalid_credentials=您的用户名或密码错误,或登录次数过多被暂时禁止登录,请稍后再试……
|
||||
account.failed.invalid_password=无效的密码
|
||||
account.failed.invalid_token=请尝试登出并重新输入密码登录
|
||||
account.failed.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.http=警告:此服务器使用不安全的 HTTP 协议,您的密码在登录时会被明文传输!
|
||||
account.injector.link.register=注册
|
||||
account.injector.server=认证服务器
|
||||
account.injector.server_url=服务器地址
|
||||
@@ -89,25 +91,27 @@ account.methods.authlib_injector=外置登录 (authlib-injector)
|
||||
account.methods.microsoft=微软帐户
|
||||
account.methods.microsoft.birth=如何修改帐户出生日期
|
||||
account.methods.microsoft.close_page=已完成微软帐户授权,接下来启动器还需要完成剩余登录步骤。你已经可以关闭本页面了。
|
||||
account.methods.microsoft.error.add_family=由于你未满 18 岁,你的帐户必须被加入到家庭中才能登录游戏。你也可以点击上方链接更改你的帐户的出生日期,使年龄满 18 岁以上以继续登录。
|
||||
account.methods.microsoft.deauthorize=解除账户授权
|
||||
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.unknown=登录失败,错误码:%d
|
||||
account.methods.microsoft.logging_in=登录中...
|
||||
account.methods.microsoft.hint=您需要点击登录按钮,并在新打开的浏览器窗口中完成登录,并输入待会在此处显示的设备代码。
|
||||
account.methods.microsoft.manual=设备代码为:<b>%1$s</b>,已经帮你拷贝到剪切板中,如果未能粘贴,你可以点击此处再复制一次。你需要在新打开的浏览器窗口中完成登录,若登录页面未能打开,您手动在浏览器中打开网页:%2$s。
|
||||
account.methods.microsoft.logging_in=登录中……
|
||||
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.waiting_browser=等待在新打开的浏览器窗口中完成登录...
|
||||
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.hint=UUID 是 Minecraft 对玩家角色的唯一标识符,每个启动器生成 UUID 的方式可能不同。通过修改 UUID 选项至原启动器所生成的 UUID,你可以保证在切换启动器后,游戏还能将你的游戏角色识别为给定 UUID 所对应的角色,从而保留原来角色的背包物品。UUID 选项为高级选项,除非你知道你在做什么,否则你不需要调整该选项!
|
||||
account.methods.offline.uuid.malformed=格式错误
|
||||
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=点击此处添加帐户
|
||||
@@ -137,7 +141,7 @@ archive.version=版本
|
||||
|
||||
assets.download=下载资源
|
||||
assets.download_all=检查资源文件完整性
|
||||
assets.index.malformed=资源文件的索引文件损坏,您可以在游戏设置页面右上角的设置按钮中选择更新游戏资源文件以修复该问题
|
||||
assets.index.malformed=资源文件的索引文件损坏,您可以在相应的游戏管理,点击左下角的管理按钮选择【更新游戏资源文件】以修复该问题
|
||||
|
||||
button.cancel=取消
|
||||
button.change_source=切换下载源
|
||||
@@ -180,8 +184,16 @@ curse.category.4479=高难度
|
||||
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=装备武器
|
||||
@@ -223,7 +235,9 @@ curse.category.4558=红石
|
||||
curse.category.4843=自动化
|
||||
curse.category.4906=MCreator
|
||||
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/6
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/12
|
||||
curse.category.5244=字体包
|
||||
curse.category.5193=数据包
|
||||
curse.category.399=蒸汽朋克
|
||||
curse.category.396=128x
|
||||
curse.category.398=512x 及更高
|
||||
@@ -272,27 +286,28 @@ download=下载
|
||||
download.code.404=远程服务器不包含需要下载的文件: %s
|
||||
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.empty=[没有可供安装的版本,点击此处返回]
|
||||
download.failed.refresh=[加载版本列表失败,点击此处重试]
|
||||
download.game=新游戏
|
||||
download.provider.mcbbs=我的世界中文论坛 (MCBBS, https://www.mcbbs.net/)
|
||||
download.provider.bmclapi=BMCLAPI(bangbang93,https://bmclapi2.bangbang93.com/)
|
||||
download.provider.mcbbs=我的世界中文论坛 (MCBBS, https://www.mcbbs.net)
|
||||
download.provider.bmclapi=BMCLAPI(bangbang93,https://bmclapi2.bangbang93.com)
|
||||
download.provider.mojang=官方(OptiFine 自动安装使用 BMCLAPI 下载源)
|
||||
download.provider.official=尽量使用官方源(最新,但可能加载慢)
|
||||
download.provider.balanced=选择加载速度快的下载源(平衡,但可能不是最新)
|
||||
download.provider.mirror=尽量使用镜像源(加载快,但可能不是最新)
|
||||
download.javafx=正在下载必要的运行时组件
|
||||
download.javafx.notes=正在通过网络下载 HMCL 必要的运行时组件。\n点击“切换下载源”按钮查看详情以及选择下载源,点击“取消”按钮停止并退出。\n注意:如果下载速度过慢,请尝试切换下载源。
|
||||
download.javafx=正在下载必要的运行时组件……
|
||||
download.javafx.notes=正在通过网络下载 HMCL 必要的运行时组件。\n点击“切换下载源”按钮查看详情以及选择下载源,点击“取消”按钮停止并退出。\n注意:若下载速度过慢,请尝试切换下载源
|
||||
download.javafx.component=正在下载模块 %s
|
||||
download.javafx.prepare=准备开始下载
|
||||
|
||||
exception.access_denied=因为无法访问文件 %s,HMCL 没有对该文件的访问权限,或者该文件被其他程序打开。\n\
|
||||
请你检查当前操作系统帐户是否能访问该文件,比如非管理员用户可能不能访问其他帐户的个人文件夹内的文件。\n\
|
||||
对于 Windows 用户,你还可以尝试通过资源监视器查看是否有程序占用了该文件,如果是,你可以关闭占用此文件相关程序,或者重启电脑再试。
|
||||
exception.artifact_malformed=下载的文件正确,无法通过校验。
|
||||
对于 Windows 用户,你还可以尝试通过资源监视器查看是否有程序占用了该文件,如果是,请关闭占用此文件相关程序,或者重启电脑再试……
|
||||
exception.artifact_malformed=下载的文件无法通过校验。
|
||||
exception.ssl_handshake=无法建立 SSL 连接,因为当前 Java 虚拟机缺少相关的 SSL 证书。你可以尝试使用其他的 Java 虚拟机启动 HMCL 再试……
|
||||
|
||||
extension.bat=Windows 脚本
|
||||
extension.mod=模组文件
|
||||
@@ -301,26 +316,27 @@ 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.missing=缺少 JavaFX 运行环境。\n如果您使用的是 Java 11 或更高版本,请降级到 Oracle JRE 8,或者安装 BellSoft Liberica Full JRE。\n如果您使用的是其他 OpenJDK 构建,请确保其包含 OpenJFX。
|
||||
fatal.javafx.missing=缺少 JavaFX 运行环境。\n如果您使用的是 Java 11 或更高版本,请降级到 Oracle JRE 8(java.com),或者安装 BellSoft Liberica Full JRE(bell-sw.com/pages/downloads/?package=jre-full)。\n如果您使用的是其他 OpenJDK 构建,请确保其包含 OpenJFX!
|
||||
fatal.missing_dst_root_ca_x3=当前 Java 平台缺少 DST Root CA X3 证书。\n您依然可以使用 Hello Minecraft! Launcher,但将无法连接到部分站点(如使用 Lets Encrypt 证书的站点),这可能会使 HMCL 无法正常工作。\n请将您的 Java 升级到 8u101 以上以解决此问题。
|
||||
fatal.config_loading_failure=Hello Minecraft! Launcher 无法加载配置文件。\n请确保 Hello Minecraft! Launcher 对 "%s" 目录及该目录下的文件拥有读写权限。
|
||||
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.apply_update_failure=我们很抱歉 Hello Minecraft! Launcher 无法自动完成升级,因为出现了一些问题……\n但你依可以从 %s 处手动下载 Hello Minecraft! Launcher 来完成升级\n请考虑向我们反馈该问题!
|
||||
fatal.samba=如果您正在通过 Samba 共享的文件夹中运行 HMCL,HMCL 可能无法正常工作,请尝试更新您的 Java 或在本地文件夹内运行 HMCL。
|
||||
fatal.illegal_char=您的用户文件夹路径中存在非法字符‘=’。HMCL 能够运行,但可能某些功能无法正常使用!\n你将无法使用外置登录账户以及离线登录更换皮肤功能,并且在进行多人游戏时可能存在更大的安全隐患!
|
||||
|
||||
feedback=反馈
|
||||
feedback.add=新增反馈
|
||||
feedback.add.hint.search_before_add=添加反馈前,请先搜索已有反馈中是否已经有人提出过相关内容,如果有,你可以通过给对应反馈点赞来提升对应反馈的优先级。\n请你发布有意义的反馈。如果您发布了违反国家法律法规、灌水等不良信息,或求助游戏内容玩法等问题,你的账号将被封禁。
|
||||
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.too_frequently=搜索次数太频繁啦
|
||||
feedback.failed.permission=登录账户并取得反馈权限后才能根据关键字搜索反馈!
|
||||
feedback.failed.too_frequently=搜索次数太频繁啦!
|
||||
feedback.like=赞成
|
||||
feedback.login=登录 HMCL 帐户
|
||||
feedback.response=回复
|
||||
@@ -345,15 +361,15 @@ folder.saves=存档文件夹
|
||||
folder.screenshots=截图文件夹
|
||||
|
||||
game=游戏
|
||||
game.crash.feedback=查看了日志发现问题很常见?在反馈页面提交反馈丰富崩溃分析系统!
|
||||
game.crash.feedback=查看了日志发现该问题很常见?在反馈页面或 Github Issues 提交反馈,可丰富崩溃分析系统!
|
||||
game.crash.info=游戏信息
|
||||
game.crash.reason=崩溃原因
|
||||
game.crash.reason.analyzing=分析中...
|
||||
game.crash.reason.block=当前游戏因为某个方块不能正常工作,无法继续运行。\n你可以尝试通过 MCEdit 工具编辑存档删除该方块,或者直接删除相应的 Mod。\n方块类型:%1$s\n方块坐标:%2$s
|
||||
game.crash.reason.analyzing=分析中……
|
||||
game.crash.reason.block=当前游戏因为某个方块不能正常工作,无法继续运行。\n你可以尝试通过 MCEdit(podshot.github.io/MCEdit-Unified)工具编辑存档删除该方块,或者直接删除相应的 Mod。\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=当前游戏因为模组 %1$s 重复安装,无法继续运行。\n%2$s\n每种 Mod 只能安装一个,请你删除多余的 Mod 再试。
|
||||
game.crash.reason.debug_crash=当前游戏因为手动触发崩溃,无法继续运行。\n事实上游戏并没有问题,问题都是你造成的!
|
||||
game.crash.reason.duplicated_mod=当前游戏因为模组 %1$s 重复安装,无法继续运行。\n%2$s\n每种 Mod 只能安装一个,请你删除多余的 Mod 再试……
|
||||
game.crash.reason.entity=当前游戏因为某个实体不能正常工作,无法继续运行。\n你可以尝试通过 MCEdit 工具编辑存档删除该实体,或者直接删除相应的 Mod。\n实体类型:%1$s\n实体坐标:%2$s
|
||||
game.crash.reason.fabric_version_0_12=Fabric 0.12 及以上版本与当前已经安装的 Mod 可能不兼容,你需要将 Fabric 降级至 0.11.7。
|
||||
game.crash.reason.fabric_warnings=Fabric 提供了一些警告信息:\n%1$s
|
||||
@@ -362,16 +378,16 @@ game.crash.reason.file_changed=当前游戏因为文件校验失败,无法继
|
||||
game.crash.reason.gl_operation_failure=当前游戏因为你使用的某些 Mod、光影包、材质包,无法继续运行。\n请先尝试禁用你所使用的Mod/光影包/材质包再试。
|
||||
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\
|
||||
如果你的电脑存在独立显卡,你需要检查游戏是否使用集成/核芯显卡启动。如果是,请尝试使用独立显卡启动 HMCL 与游戏。如果仍有问题,你可能需要考虑换一个新显卡或新电脑……\n\
|
||||
如果你确实需要使用核芯显卡,请检查你的电脑的 CPU 是否是 Intel(R) Core(TM) 3000 系列或更旧的处理器,如果是,对于 Minecraft 1.16.5 及更旧版本,请你将游戏所使用的 Java 版本降级至 1.8.0_51 及以下版本 <a href="https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html">Java 1.8.0 历史版本</a> ,否则你必须更换独立显卡或新电脑。\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.illegal_access_error=当前游戏因为某些 Mod 的问题,无法继续运行。\n如果你认识:%1$s,你可以更新或删除对应 Mod 再试……
|
||||
game.crash.reason.jdk_9=当前游戏因为 Java 版本过高,无法继续运行。\n你需要下载安装 Java 8,并在游戏设置中将 Java 设置为 1.8 的版本。
|
||||
game.crash.reason.jvm_32bit=当前游戏因为内存分配过大,超过了 32 位 Java 内存限制,无法继续运行。\n如果你的电脑是 64 位系统,请下载安装并更换 64 位 Java。如果你的电脑室 32 位系统,你或许可以重新安装 64 位系统,或换一台新电脑。\n或者,你可以关闭游戏内存的自动分配,并且把内存限制调节为 1024 MB 或以下。
|
||||
game.crash.reason.jvm_32bit=当前游戏因为内存分配过大,超过了 32 位 Java 内存限制,无法继续运行。\n如果你的电脑是 64 位系统,请下载安装并更换 64 位 Java。如果你的电脑是 32 位系统,你或许可以重新安装 64 位系统,或换一台新电脑。\n或者,你可以关闭游戏内存的自动分配,并且把内存限制调节为 1024 MB 或以下。
|
||||
game.crash.reason.loading_crashed_forge=当前游戏因为模组 %1$s (%2$s) 错误,无法继续运行。\n你可以尝试删除或更新该模组以解决问题。
|
||||
game.crash.reason.loading_crashed_fabric=当前游戏因为模组 %1$s 错误,无法继续运行。\n你可以尝试删除或更新该模组以解决问题。
|
||||
game.crash.reason.memory_exceeded=当前游戏因为分配的内存过大,无法继续运行。\n该问题是由于系统页面文件太小导致的。\n你需要在游戏设置中关闭游戏内存的自动分配,并将游戏内存调低至游戏能正常启动为止。\n你还可以尝试调大系统的页面大小。
|
||||
game.crash.reason.mod=当前游戏因为 %1$s 的问题,无法继续运行。\n你可以更新或删除已经安装的 %1$s 再试。
|
||||
game.crash.reason.mod=当前游戏因为 %1$s 的问题,无法继续运行。\n你可以更新或删除已经安装的 %1$s 再试……
|
||||
game.crash.reason.mod_resolution=当前游戏因为 Mod 依赖问题,无法继续运行。Fabric 提供了如下信息:\n%1$s
|
||||
game.crash.reason.mod_resolution_collection=当前游戏因为前置 Mod 版本不匹配,无法继续运行。\n%1$s 需要前置 Mod:%2$s 才能继续运行。\n这表示你需要更新或降级前置。你可以到下载页的模组下载,或到网上下载 %3$s。
|
||||
game.crash.reason.mod_resolution_conflict=当前游戏因为 Mod 冲突,无法继续运行。\n%1$s 与 %2$s 不能兼容。
|
||||
@@ -379,6 +395,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 及更早版本启动\n8u312 及更早版本下载:\noracle.com/java/technologies/javase/javase8u211-later-archive-downloads.html\n11.0.13 及更早版本下载:\noracle.com/java/technologies/javase/jdk11-archive-downloads.html\n17.0.1 及更早版本下载:\noracle.com/java/technologies/javase/jdk17-archive-downloads.html
|
||||
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 与游戏。如果仍有问题,你可能需要考虑换一个新显卡或新电脑。
|
||||
@@ -386,9 +403,9 @@ game.crash.reason.openj9=当前游戏无法运行在 OpenJ9 虚拟机上,请
|
||||
game.crash.reason.out_of_memory=当前游戏因为内存不足,无法继续运行。\n这可能是内存分配太小,或者 Mod 数量过多导致的。\n你可以在游戏设置中调大游戏内存分配值以允许游戏在更大的内存下运行。\n如果仍然出现该错误,你可能需要换一台更好的电脑。
|
||||
game.crash.reason.resolution_too_high=当前游戏因为材质包分辨率过高,无法继续运行\n你可以更换一个分辨率更低的材质,或者更换一个显存更大的显卡。
|
||||
game.crash.reason.stacktrace=原因未知,请点击日志按钮查看详细信息。\n下面是一些关键词,其中可能包含 Mod 名称,你可以通过搜索的方式查找有关信息。\n%s
|
||||
game.crash.reason.too_old_java=当前游戏因为 Java 虚拟机版本过低,无法继续运行。\n你需要在游戏设置中更换 Java %1$s 或更新版本的 Java 虚拟机,并重新启动游戏。如果没有下载安装,你可以在 https://adoptopenjdk.net 上自行下载。
|
||||
game.crash.reason.unknown=原因未知,请点击日志按钮查看详细信息。\n不要将本界面截图给他人!如果你要求助他人,请你点击左下角导出游戏崩溃信息后将导出的文件发送给他人以供分析!\n你可以向我们提供游戏崩溃日志以提升 HMCL 的日志分析能力。
|
||||
game.crash.reason.unsatisfied_link_error=当前游戏因为缺少本地库,无法继续运行。\n这些本地库缺失:%1$s。\n如果你在游戏设置中修改了本地库路径选项,请你修改回预设模式。\n如果已经在预设模式下,请检查本地库缺失是否是 Mod 引起的,或由 HMCL 引起的。如果你确定是 HMCL 引起的,建议你向我们反馈。\n如果你确实需要自定义本地库路径,你需要保证其中包含缺失的本地库。
|
||||
game.crash.reason.too_old_java=当前游戏因为 Java 虚拟机版本过低,无法继续运行。\n你需要在游戏设置中更换 Java %1$s 或更新版本的 Java 虚拟机,并重新启动游戏。如果没有下载安装,你可以点击 <a href="https://docs.microsoft.com/java/openjdk/download">此处</a> 下载微软 JDK。
|
||||
game.crash.reason.unknown=原因未知,请点击日志按钮查看详细信息!\n<b>!!!请不要将本界面[截图]给他人!!!</b>如果你要求助他人,请你点击左下角导出游戏崩溃信息后将导出的文件发送给他人以供分析!!!\n查看了日志发现该问题很常见?你可以向我们提供游戏崩溃日志在反馈页面或 Github Issues 提交反馈以提升 HMCL 的日志分析能力
|
||||
game.crash.reason.unsatisfied_link_error=当前游戏因为缺少本地库,无法继续运行。\n这些本地库缺失:%1$s。\n如果你在游戏设置中修改了本地库路径选项,请你修改回预设模式。\n如果已经在预设模式下,请检查本地库缺失是否是 Mod 引起的,或由 HMCL 引起的。如果你确定是 HMCL 引起的,建议你向我们反馈。\n如果你确实需要自定义本地库路径,你需要保证其中包含缺失的本地库!
|
||||
game.crash.title=游戏意外退出
|
||||
game.directory=游戏路径
|
||||
game.version=游戏版本
|
||||
@@ -404,22 +421,22 @@ input.url=必须是合法的链接
|
||||
|
||||
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.detail=未能下载文件:%s
|
||||
install.failed.downloading.timeout=下载超时:%s
|
||||
install.failed.install_online=无法识别要安装的软件。如果你要安装 Mod,你需要在模组管理页面安装模组。
|
||||
install.failed.malformed=下载的文件格式损坏。您可以切换到其他下载源来解决此问题。
|
||||
install.failed.optifine_conflict=暂不支持 OptiFine, Fabric 与 Forge 同时安装在 Minecraft 1.13 及以上版本
|
||||
install.failed.install_online=无法识别要安装的组件。如果你要安装 Mod,你需要在模组管理页面安装模组。
|
||||
install.failed.malformed=下载的文件格式损坏。您可以在设置-下载中切换到其他下载源来尝试解决此问题。
|
||||
install.failed.optifine_conflict=暂不支持 OptiFine, Fabric 或 OptiFine , Forge 同时安装在 Minecraft 1.13 及以上版本
|
||||
install.failed.optifine_forge_1.17=Minecraft 1.17.1 下,仅 OptiFine H1 Pre2 及以上版本能兼容 Forge。你可以从 OptiFine 测试版中选择最新版本。
|
||||
install.failed.version_mismatch=该软件需要的游戏版本为 %s,但实际的游戏版本为 %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 文件夹,请你在安装游戏后不要修改当前游戏的版本隔离/游戏运行路径设置,如果你在之后修改了相关设置,Fabric API 需要被重新安装。
|
||||
install.installer.fabric-api.warning=警告:Fabric API 是一个 Mod,将会被安装到新游戏的 Mod 文件夹,请你在安装游戏后不要修改当前游戏的版本隔离/游戏运行路径设置,如果你在之后修改了相关设置,Fabric API 需要被重新安装!
|
||||
install.installer.forge=Forge
|
||||
install.installer.game=Minecraft
|
||||
install.installer.incompatible=与 %s 不兼容
|
||||
@@ -444,29 +461,31 @@ install.success=安装成功
|
||||
lang=简体中文
|
||||
lang.default=跟随系统语言
|
||||
|
||||
launch.advice.java.auto=当前选择的 Java 虚拟机版本不满足游戏要求。是否自动选择合适的 Java 虚拟机版本?或者你可以到游戏设置中选择一个合适的 Java 虚拟机版本。
|
||||
launch.advice.java.auto=当前选择的 Java 虚拟机版本不满足游戏要求\n点击“是”即可由 HMCL 来自动选取合适的 Java 虚拟机版本\n或者你可以到游戏设置中选择一个合适的 Java 虚拟机版本。
|
||||
launch.advice.java.modded_java_7=Minecraft 1.7.2 及以下版本需要 Java 7 及以下版本。
|
||||
launch.advice.corrected=我们已经修复了 Java 虚拟机版本问题。如果您确实希望使用您自定义的 Java 虚拟机,您可以在游戏设置中关闭 Java 虚拟机兼容性检查。
|
||||
launch.advice.uncorrected=如果您确实希望使用您自定义的 Java 虚拟机,您可以在游戏设置中关闭 Java 虚拟机兼容性检查。
|
||||
launch.advice.different_platform=您的系统是 64 位,但是 Java 是 32 位的,建议您安装 64 位 Java。
|
||||
launch.advice.corrected=我们已经修复了 Java 虚拟机版本问题。如果您确实希望使用您自定义的 Java 虚拟机,您可以在游戏设置往下滑,关闭 Java 虚拟机兼容性检查\。
|
||||
launch.advice.uncorrected=如果您确实希望使用您自定义的 Java 虚拟机,您可以在游戏设置往下滑,关闭 Java 虚拟机兼容性检查
|
||||
launch.advice.different_platform=您的系统是 64 位,但是 Java 是 32 位的,建议您安装 64 位 Java (java.com)
|
||||
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_linux_java_8=对于 Linux x86-64 平台,Minecraft 1.12.2 及以下版本仅支持 Java 8。\n请到 java.com 或其他途径下载 Java8!\n(可将 Java 路径选择至“自动选择合适的 Java”,启动时会自动下载该 Java)\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_x86.translation=你的电脑中没有安装 X64 架构的 Java 虚拟机(JDK),你可以在 <a href="https://docs.microsoft.com/java/openjdk/download"> 这里 </a> 下载 <b> X64 </b> 架构的 Java 虚拟机。\nMinecraft 尚未提供对 ARM 平台的支持,不能在您目前使用的 ARM64 架构 JDK 上运行。因此 HMCL 会借助 Rosetta 2 转译执行 X64 架构的 JDK 来启动 Minecraft。
|
||||
launch.failed=启动失败
|
||||
launch.failed.cannot_create_jvm=截获到无法创建 Java 虚拟机,可能是 Java 参数有问题,可以在设置中开启无参数模式启动。
|
||||
launch.failed.creating_process=启动失败,在创建新进程时发生错误,可能是 Java 路径错误。
|
||||
launch.failed.command_too_long=命令长度超过限制,无法创建 bat 脚本,请导出为 PowerShell 脚本。
|
||||
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=设置执行策略失败
|
||||
@@ -485,7 +504,7 @@ launcher=启动器
|
||||
launcher.agreement=用户协议与免责声明
|
||||
launcher.agreement.accept=同意
|
||||
launcher.agreement.decline=拒绝
|
||||
launcher.agreement.hint=同意本软件的用户协议与免责声明以使用本软件。
|
||||
launcher.agreement.hint=同意本软件的用户协议与免责声明以使用本软件!
|
||||
launcher.background=背景地址
|
||||
launcher.background.choose=选择背景路径
|
||||
launcher.background.classic=经典
|
||||
@@ -493,14 +512,14 @@ launcher.background.default=默认(自动检索启动器同目录下的 backgr
|
||||
launcher.background.network=网络
|
||||
launcher.background.translucent=半透明
|
||||
launcher.cache_directory=文件下载缓存目录
|
||||
launcher.cache_directory.clean=清理
|
||||
launcher.cache_directory.clean=清理缓存
|
||||
launcher.cache_directory.choose=选择文件下载缓存目录
|
||||
launcher.cache_directory.default=默认(%AppData%/.minecraft 或者 ~/.minecraft)
|
||||
launcher.cache_directory.disabled=禁用(总是使用游戏路径)
|
||||
launcher.cache_directory.invalid=无法创建自定义的缓存目录,恢复默认设置
|
||||
launcher.contact=联系我们
|
||||
launcher.crash=Hello Minecraft! Launcher 遇到了无法处理的错误,请复制下列内容并通过 MCBBS、贴吧、GitHub 或 Minecraft Forum 反馈 bug。
|
||||
launcher.crash_out_dated=Hello Minecraft! Launcher 遇到了无法处理的错误,已检测到您的启动器不是最新版本,请更新后再试!
|
||||
launcher.crash=Hello Minecraft! Launcher 遇到了无法处理的错误,请复制下列内容并通过 MCBBS、贴吧、GitHub Issues 或 Minecraft Forum 反馈 bug
|
||||
launcher.crash_out_dated=Hello Minecraft! Launcher 遇到了无法处理的错误,已检测到您的启动器不是最新版本,请更新后再试……
|
||||
launcher.update_java=请更新您的 Java
|
||||
|
||||
login.empty_username=你还未设置用户名!
|
||||
@@ -516,7 +535,7 @@ main_page=主页
|
||||
|
||||
message.cancelled=操作被取消
|
||||
message.confirm=提示
|
||||
message.copied=已复制到剪贴板
|
||||
message.copied=已拷贝至剪贴板
|
||||
message.default=默认
|
||||
message.doing=请耐心等待
|
||||
message.downloading=正在下载
|
||||
@@ -534,7 +553,8 @@ modpack.choose.local.detail=你可以直接将整合包文件拖入本页面以
|
||||
modpack.choose.remote=从互联网下载整合包
|
||||
modpack.choose.remote.detail=需要提供整合包的下载链接
|
||||
modpack.choose.remote.tooltip=要下载的整合包的链接
|
||||
modpack.desc=描述你要制作的整合包,比如整合包注意事项和更新记录,支持 HTML(图片请用网络图)。
|
||||
modpack.completion=下载整合包相关文件
|
||||
modpack.desc=描述你要制作的整合包,比如整合包注意事项和更新记录,支持 HTML(图片请用网络图)
|
||||
modpack.description=整合包描述
|
||||
modpack.download=下载整合包
|
||||
modpack.enter_name=给游戏起个你喜欢的名字
|
||||
@@ -557,7 +577,7 @@ modpack.files.servers_dat=多人游戏服务器列表
|
||||
modpack.install=安装 %s 整合包
|
||||
modpack.installing=正在安装整合包
|
||||
modpack.introduction=支持 Curse、MultiMC、HMCL 整合包。
|
||||
modpack.invalid=无效的整合包升级文件,可能是下载时出现问题。
|
||||
modpack.invalid=无效的整合包升级文件,可能是下载时出现问题……
|
||||
modpack.mismatched_type=整合包类型不匹配,当前游戏是 %s 整合包,但是提供的整合包更新文件是 %s 整合包。
|
||||
modpack.name=整合包名称
|
||||
modpack.not_a_valid_name=不是一个有效的整合包名称
|
||||
@@ -567,22 +587,22 @@ modpack.origin.mcbbs=MCBBS
|
||||
modpack.origin.mcbbs.prompt=贴子 id
|
||||
modpack.scan=解析整合包
|
||||
modpack.task.install=导入整合包
|
||||
modpack.task.install.error=无法识别该整合包,目前仅支持导入 Curse、MultiMC、HMCL 整合包。
|
||||
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.tolerable_error=但未能完成 Curse 整合包所需的依赖下载,您可以在启动该游戏版本时继续 Curse 整合包文件的下载。由于网络问题,您可能需要重试多次……
|
||||
modpack.type.curse.error=未能完成 Curse 整合包所需的依赖下载,请多次重试或设置代理
|
||||
modpack.type.curse.not_found=部分必需文件已经在网络中被删除并且再也无法下载,请尝试该整合包的最新版本或者安装其他整合包。
|
||||
modpack.type.manual.warning=您大概需要直接解压该整合包,并使用其自带的启动器即可开始游戏,而不需要导入整合包。该整合包不是由启动器导出的整合包,而是人工打包 .minecraft 文件夹而来的,这类整合包通常附带游戏启动器。HMCL 可以尝试导入该整合包,但不保证可用性,是否继续?
|
||||
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.export=允许服务器管理员远程更新游戏客户端
|
||||
modpack.type.server.malformed=服务器整合包配置格式错误,请联系服务器管理员解决此问题
|
||||
modpack.unsupported=该整合包不被支持。仅 HMCL、MultiMC、Curse 整合包受支持。
|
||||
modpack.unsupported=该整合包不被支持。仅 HMCL、MultiMC、Curse 整合包受支持!
|
||||
modpack.update=正在升级整合包
|
||||
modpack.wizard=导出整合包向导
|
||||
modpack.wizard.step.1=基本设置
|
||||
@@ -604,9 +624,14 @@ modrinth.category.decoration=装饰
|
||||
modrinth.category.equipment=装备
|
||||
modrinth.category.fabric=Fabric
|
||||
modrinth.category.food=食物
|
||||
modrinth.category.forge=Forge
|
||||
modrinth.category.library=支持库
|
||||
modrinth.category.magic=魔法
|
||||
modrinth.category.misc=其他
|
||||
modrinth.category.modloader=Modloader
|
||||
modrinth.category.optimization=优化
|
||||
modrinth.category.quilt=Quilt
|
||||
modrinth.category.rift=Rift
|
||||
modrinth.category.storage=存储
|
||||
modrinth.category.technology=科技
|
||||
modrinth.category.utility=实用
|
||||
@@ -638,7 +663,7 @@ mods.mcmod.page=MC 百科页面
|
||||
mods.mcmod.search=MC 百科搜索
|
||||
mods.modrinth=Modrinth
|
||||
mods.name=名称
|
||||
mods.not_modded=你需要先在自动安装页面安装 Fabric、Forge 或 LiteLoader 才能进行模组管理。
|
||||
mods.not_modded=你需要先在自动安装页面安装 Fabric、Forge 或 LiteLoader 才能进行模组管理!
|
||||
mods.restore=回退
|
||||
mods.url=官方页面
|
||||
|
||||
@@ -650,11 +675,12 @@ multiplayer.download.success=多人联机初始化完成
|
||||
multiplayer.download.unsupported=多人联机依赖不支持当前系统或平台
|
||||
multiplayer.exit.after_ready=多人联机会话意外退出,退出码 %d
|
||||
multiplayer.exit.before_ready=多人联机房间创建失败,cato 退出码 %d
|
||||
multiplayer.exit.timeout.static_token=无法连接多人联机服务,请你切换动态 Token 重试。
|
||||
multiplayer.exit.timeout.dynamic_token=无法连接多人联机服务,你可以在多人联机页面的反馈中反馈问题。
|
||||
multiplayer.hint=多人联机功能处于实验阶段,如果有问题请反馈。
|
||||
multiplayer.exit.timeout.static_token=无法连接多人联机服务,你的凭证可能无法正常工作,你可以清空凭证再试。\n你可以联系在线管理员询问鉴权服务器是否正常工作,或者耐心等待十分钟。
|
||||
multiplayer.exit.timeout.dynamic_token=无法连接多人联机服务,可能是鉴权服务器被人占满,请等待每个半点!鉴权服务器会自动刷新一次\n没有耐心且有闲钱的可以考虑购买联机凭证,不受鉴权服务器影响
|
||||
multiplayer.hint=多人联机功能处于实验阶段,如果有问题请前往 mcer.cn 反馈
|
||||
multiplayer.nat=网络检测
|
||||
multiplayer.nat.hint=执行网络检测可以让你更清楚你的网络状况是否符合联机功能的需求。检测结果为差的网络可能导致联机失败。
|
||||
multiplayer.nat.failed=检测失败
|
||||
multiplayer.nat.hint=执行网络检测可以让你更清楚你的网络状况是否符合联机功能的需求\n若检测结果为 差 或 检测失败 的网络可能导致联机失败!
|
||||
multiplayer.nat.latency=延迟
|
||||
multiplayer.nat.not_yet_tested=尚未检测
|
||||
multiplayer.nat.packet_loss_ratio=丢包率
|
||||
@@ -668,49 +694,49 @@ 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.relay=中继模式
|
||||
multiplayer.relay.hint=NAT 网络类型为差-对称型的用户,若无法联机,申请静态 Token 后可以启用本选项
|
||||
multiplayer.powered_by=多人联机服务由 (<a href="https://mcer.cn">mcer.cn</a>) 提供 <a href="https://hmcl.huangyuhui.net/api/redirect/multiplayer-agreement">用户协议与免责声明</a>
|
||||
multiplayer.relay=桥接服务
|
||||
multiplayer.relay.hint=Cato 面向较差网络提供的联机质量优化解决方案|无法联机时可在 mcer.cn 兑换凭证后启用
|
||||
multiplayer.report=违法违规举报
|
||||
multiplayer.session=房间
|
||||
multiplayer.session.name.format=%1$s 的房间
|
||||
multiplayer.session.name.motd=HMCL 多人联机房间 - %s
|
||||
multiplayer.session.close=关闭房间
|
||||
multiplayer.session.close.warning=关闭房间后,已经加入联机房间的玩家将会断开连接,是否继续?
|
||||
multiplayer.session.copy_room_code=复制邀请码
|
||||
multiplayer.session.copy_room_code=拷贝邀请码
|
||||
multiplayer.session.create=创建房间
|
||||
multiplayer.session.create.error.dynamic_token=创建联机房间失败,请稍后再试。
|
||||
multiplayer.session.create.error.static_token=创建联机房间失败,你可以使用动态 Token 再试。
|
||||
multiplayer.session.create.error.dynamic_token=创建联机房间失败,可能是服务器出现问题,请稍后再试……
|
||||
multiplayer.session.create.error.static_token=创建联机房间失败,你的凭证可能无法正常工作,你可以清空凭证再试……\n你可以联系在线管理员询问鉴权服务器是否正常工作,或者耐心等待十分钟。
|
||||
multiplayer.session.create.hint=创建联机房间前,你需要先在正在运行的游戏内的游戏菜单中选择 对局域网开放 选项,然后在下方的输入框中确认游戏内提示的端口号(通常是 5 位的数字)
|
||||
multiplayer.session.create.join=连接申请
|
||||
multiplayer.session.create.join.allow=自动接受所有连接申请(不启用此选项时,你需要手动同意申请,以避免不相关人士误连你的服务器)
|
||||
multiplayer.session.create.join.allow=自动接受所有连接申请(不启用时,需要手动同意申请,以免无关人士误连服务器)
|
||||
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.port=端口号
|
||||
multiplayer.session.create.port.error=无法检测游戏端口号,你必须先启动游戏并在游戏内打开对局域网开放选项后才能启动联机。
|
||||
multiplayer.session.create.token=Token
|
||||
multiplayer.session.create.token.apply=申请静态 Token
|
||||
multiplayer.session.create.token.prompt=默认为临时 Token。你可以在 noin.cn 上申请静态 Token 并填写至此处
|
||||
multiplayer.session.create.port.error=无法检测游戏端口号,你须启动游戏并在游戏内 打开对局域网 开放选项方能启动联机!
|
||||
multiplayer.session.create.token=凭证
|
||||
multiplayer.session.create.token.apply=申请凭证
|
||||
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 小时,你需要重新创建/加入房间以继续联机。
|
||||
multiplayer.session.error.file_not_found=找不到 cato 程序。该程序应该在进入多人联机页面时完成下载。请重启HMCL再试。\n请检查你电脑的杀毒软件是否将 cato 标记为病毒,如果是,请恢复 cato。
|
||||
multiplayer.session.expired=联机会话连续使用时间超过了 3 小时,你需要重新创建/加入房间以继续联机!
|
||||
multiplayer.session.join=加入房间
|
||||
multiplayer.session.join.error=加入房间失败。如果你或对方的网络类型是差(对称型),可能无法使用联机功能。
|
||||
multiplayer.session.join.error.connection=加入房间失败。无法与对方建立连接。如果你或对方的网络类型是差(对称型),可能无法使用联机功能。
|
||||
multiplayer.session.join.error=加入房间失败。若你或对方网络类型是 差(对称型)或 检测失败,可能无法使用联机功能,你可以使用凭证开启桥接模式后再试……
|
||||
multiplayer.session.join.error.connection=加入房间失败。无法与对方建立连接。请检查你和对方的HMCL版本是否一致。\n如果你或对方的网络类型是差(对称型),可能无法使用联机功能。
|
||||
multiplayer.session.join.hint=你需要向已经创建好房间的玩家索要邀请码以便加入多人联机房间
|
||||
multiplayer.session.join.invitation_code=邀请码
|
||||
multiplayer.session.join.invitation_code.error=邀请码不正确,请向开服玩家获取邀请码
|
||||
multiplayer.session.join.invitation_code.error=邀请码格式不合法!请向房主获取邀请码,且检查你和对方使用的 HMCL 版本是否一致!
|
||||
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.wait=等待对方同意加入申请。
|
||||
multiplayer.session.join.lost_connection=你已与房间失去连接。这可能意味着房主已经解散房间(非桥接服务的房间每个半点服务器自动刷新),或者你无法连接至房间……
|
||||
multiplayer.session.join.port.error=无法找到可用的本地网络端口,请确保 HMCL 拥有绑定本地端口的权限!
|
||||
multiplayer.session.join.rejected=你被房主拒绝连接,或房主忘记同意……
|
||||
multiplayer.session.join.wait=等待对方同意加入申请……
|
||||
multiplayer.session.members=房间成员
|
||||
multiplayer.session.quit=退出房间
|
||||
multiplayer.session.quit.warning=退出房间后,你将会与服务器断开连接,是否继续?
|
||||
@@ -719,7 +745,7 @@ multiplayer.state.connecting=连接中
|
||||
multiplayer.state.disconnected=未创建/加入房间
|
||||
multiplayer.state.disconnected.hint=多人联机功能需要先有一位玩家创建房间后,其他玩家加入房间后继续游戏。
|
||||
multiplayer.state.master=你已创建房间:%1$s,端口号 %2$d
|
||||
multiplayer.state.master.hint=创建房间后,你可以点击复制邀请码,并发送给你希望加入联机的玩家。这些玩家在进入 HMCL 多人联机页面后,点击加入房间并粘贴邀请码即可加入联机房间。
|
||||
multiplayer.state.master.hint=创建房间后,你可以点击拷贝邀请码,并发送给你希望加入联机的玩家。这些玩家在进入 HMCL 多人联机页面后,点击加入房间并粘贴邀请码即可加入联机房间。
|
||||
multiplayer.state.slave=你已加入房间: %1$s,地址为 %2$s
|
||||
multiplayer.state.slave.copy=拷贝地址
|
||||
multiplayer.state.slave.hint=加入房间后,你需要在 Minecraft 的多人游戏页面选择 HMCL 多人联机房间服务器,或者手动添加下方的地址的服务器。
|
||||
@@ -733,8 +759,8 @@ datapack.title=世界 %s - 数据包
|
||||
world=世界
|
||||
world.add=添加世界
|
||||
world.datapack=管理数据包
|
||||
world.datapack.1_13=仅 Minecraft 1.13 及之后的版本支持数据包
|
||||
world.description=%s. 上一次游戏时间: %s. 游戏版本: %s
|
||||
world.datapack.1_13=仅 Minecraft 1.13 及之后的版本支持数据包!
|
||||
world.description=%s。 上一次游戏时间: %s。 游戏版本: %s。
|
||||
world.download=存档下载
|
||||
world.export=导出此世界
|
||||
world.export.title=选择该世界的存储位置
|
||||
@@ -745,7 +771,7 @@ world.game_version=游戏版本
|
||||
world.import.already_exists=此世界已经存在
|
||||
world.import.choose=选择要导入的存档压缩包
|
||||
world.import.failed=无法导入此世界:%s
|
||||
world.import.invalid=无法识别的存档压缩包
|
||||
world.import.invalid=无法识别该存档压缩包
|
||||
world.manage=世界/数据包
|
||||
world.name=世界名称
|
||||
world.name.enter=输入世界名称
|
||||
@@ -800,7 +826,7 @@ 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/<版本名>/,除 assets、libraries)
|
||||
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 虚拟机设置
|
||||
@@ -817,7 +843,7 @@ settings.advanced.natives_directory=本地库路径(LWJGL)
|
||||
settings.advanced.natives_directory.choose=选择本地库路径
|
||||
settings.advanced.natives_directory.custom=自定义(由你提供游戏需要的本地库)
|
||||
settings.advanced.natives_directory.default=预设(由启动器提供游戏本地库)
|
||||
settings.advanced.natives_directory.hint=本选项提供给 Apple M1 等未受游戏官方支持的平台来自定义游戏本地库,如果你不知道本选项的含义,请你不要修改本选项,否则会导致游戏无法启动。\n如果你要修改本选项,你需要保证自定义目录下有游戏所需的本地库文件,如 lwjgl.dll(liblwjgl.so), openal.dll(libopenal.so) 等文件。启动器不会帮你补全缺少的本地库文件。
|
||||
settings.advanced.natives_directory.hint=本选项提供给 Apple M1 等未受游戏官方支持的平台来自定义游戏本地库,如果你不知道本选项的含义,请你不要修改本选项,否则会导致游戏无法启动!\n如果你要修改本选项,你需要保证自定义目录下有游戏所需的本地库文件,如 lwjgl.dll(liblwjgl.so), openal.dll(libopenal.so) 等文件。启动器不会帮你补全缺少的本地库文件!
|
||||
settings.advanced.no_jvm_args=不添加默认的 JVM 参数
|
||||
settings.advanced.precall_command=游戏启动前执行命令
|
||||
settings.advanced.precall_command.prompt=将在游戏启动前调用
|
||||
@@ -834,7 +860,7 @@ settings.advanced.server_ip.prompt=默认,启动游戏后可以直接进入对
|
||||
settings.advanced.use_native_glfw=[Linux] 使用系统 GLFW
|
||||
settings.advanced.use_native_openal=[Linux] 使用系统 OpenAL
|
||||
settings.advanced.workaround=调试选项
|
||||
settings.advanced.workaround.warning=调试选项仅提供给专业玩家使用。调试选项可能会导致游戏无法启动。除非你知道你在做什么,否则请你不要修改这些选项。
|
||||
settings.advanced.workaround.warning=调试选项仅提供给专业玩家使用。调试选项可能会导致游戏无法启动。除非你知道你在做什么,否则请你不要修改这些选项!
|
||||
settings.advanced.wrapper_launcher=包装命令
|
||||
settings.advanced.wrapper_launcher.prompt=如填写 optirun 后,启动命令将从 "java ..." 变为 "optirun java ..."
|
||||
|
||||
@@ -853,8 +879,9 @@ 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=版本隔离(建议使用模组时开启“各版本隔离”,改后需移动存档模组等相关游戏文件)
|
||||
settings.game.working_directory.choose=选择运行路径
|
||||
settings.game.working_directory.hint=在“运行路径(版本隔离)”选项中开启“各版本独立”使当前版本独立存放设置、存档、模组等数据,使用模组时建议开启此选项以避免不同版本模组冲突。修改此选项后请自行移动存档等文件!
|
||||
|
||||
settings.icon=游戏图标
|
||||
|
||||
@@ -895,26 +922,26 @@ 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.lower_bound=最低分配
|
||||
settings.memory.used_per_total=已使用 %1$.1f GB / 总内存 %2$.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$.1f GB
|
||||
settings.physical_memory=物理内存大小
|
||||
settings.show_log=查看日志
|
||||
settings.skin=现已支持离线账户更换皮肤,你可以到账户页面更改离线账户的皮肤和披风(多人游戏下其他玩家无法看到你的皮肤)
|
||||
settings.tabs.installers=自动安装
|
||||
settings.take_effect_after_restart=重启后生效
|
||||
settings.type=版本设置类型
|
||||
settings.type.global=全局版本设置(使用该设置的版本共用一套设定)
|
||||
settings.type.global=全局版本设置(未启用游戏特定设置的共用此设定)
|
||||
settings.type.global.manage=全局游戏设置
|
||||
settings.type.global.edit=编辑全局版本设置
|
||||
settings.type.special.enable=启用游戏特定设置(不影响其他游戏版本)
|
||||
settings.type.special.edit=编辑游戏特定设置
|
||||
settings.type.special.edit.hint=当前游戏版本 %s 启动了游戏特定设置,本页面选项不对当前游戏生效。点击链接以修改当前游戏设置。
|
||||
settings.type.special.edit.hint=当前游戏版本 [%s] 启用了游戏特定设置,因此本页面选项不对该游戏生效!点击链接前往该游戏版本的游戏特定设置页
|
||||
|
||||
sponsor=赞助
|
||||
sponsor.bmclapi=国内下载源由 BMCLAPI 和我的世界中文论坛 (MCBBS) 提供高速下载服务。BMCLAPI 是公益服务,请赞助 BMCLAPI 以获得稳定高速的下载服务,点击此处查阅详细信息。
|
||||
sponsor.hmcl=Hello Minecraft! Launcher 是一个免费、自由、开放源代码的 Minecraft 启动器。您的赞助将帮助 Hello Minecraft! Launcher 获得更好的发展,还可以获得 HMCL 的开发进展。点击此处查阅更多详细信息。
|
||||
sponsor.bmclapi=国内下载源由 BMCLAPI 和我的世界中文论坛 (MCBBS) 提供高速下载服务。BMCLAPI 为公益服务,赞助 BMCLAPI 可以帮助作者更好的提供稳定高速的下载服务,[点击此处查阅详细信息]
|
||||
sponsor.hmcl=Hello Minecraft! Launcher 是一个免费、自由、开放源代码的 Minecraft 启动器。您的赞助将帮助 Hello Minecraft! Launcher 获得更好的发展,还可以获得 HMCL 的开发进展。[点击此处查阅更多详细信息]
|
||||
|
||||
system.architecture=架构
|
||||
system.operating_system=操作系统
|
||||
@@ -941,7 +968,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=更新
|
||||
@@ -969,18 +996,18 @@ version.manage.clean.tooltip=清理 logs, crash-reports
|
||||
version.manage.duplicate=复制游戏实例
|
||||
version.manage.duplicate.duplicate_save=复制存档
|
||||
version.manage.duplicate.prompt=请输入新游戏实例名称
|
||||
version.manage.duplicate.confirm=将锁定复制产生的新游戏实例:强制版本隔离、游戏设置独立。
|
||||
version.manage.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=真的要删除版本 %s 吗?你将无法找回被删除的文件!!!
|
||||
version.manage.remove.confirm.trash=真的要删除版本 %s 吗?你可以在系统的回收站中恢复文件夹 %s 来找回该版本。
|
||||
version.manage.remove.confirm.independent=由于该游戏使用了版本隔离,所以删除该版本将导致该游戏的存档等数据一同被删除,真的要删除版本 %s 吗?
|
||||
version.manage.remove.confirm.independent=由于该游戏使用了版本隔离,所以删除该版本将导致该游戏的存档等数据一同被删除!真的要删除版本 %s 吗?
|
||||
version.manage.remove_libraries=删除所有库文件
|
||||
version.manage.rename=重命名该版本
|
||||
version.manage.rename.message=请输入要改成的名字
|
||||
version.manage.rename.fail=重命名版本失败,可能文件被占用或者名字有特殊字符
|
||||
version.manage.rename.message=请输入要修改的名称
|
||||
version.manage.rename.fail=重命名版本失败,可能文件被占用或者名字有特殊字符……
|
||||
version.settings=游戏设置
|
||||
version.update=更新整合包
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,437 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2022 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
#
|
||||
# mcmod.cn
|
||||
# Copyright (C) 2022. All Rights Reserved.
|
||||
#
|
||||
gt-new-horizons;1;818239;;格雷科技:新视野;GT:New Horizons;GTNH
|
||||
enigtech-2;2;920201;;玄理2;EnigTech 2;ET2
|
||||
;3;808284;;飞翔之路3;Flying Road 3;
|
||||
dungeons-dragons-and-space-shuttles;4;;;龙与地下城和太空飞船;Dungeons, Dragons and Space Shuttles;DDSS
|
||||
skyfactory-4;5;1173025;;天空工厂4;SkyFactory 4;SF4
|
||||
life-in-the-village;6;;;Life in the village;;LitV
|
||||
expedition-in-the-sky;7;;;Expedition In The Sky/Journey Across The Void;;JATV
|
||||
levitated;8;1101021;;失重;Levitated;LTT
|
||||
all-the-mods-3;9;;;All The Mods 3;;ATM3
|
||||
create101;10;1085589;;Create 101;;C101
|
||||
ftb-interactions;11;;;FTB Interactions;;
|
||||
material-energy-5-entity;12;;;Material Energy^5: Entity;;ME5
|
||||
minecraft-eternal;13;;;永恒的MC;MC Eternal;MCE
|
||||
mc-eternal-lite;14;;;永恒的MC精简版;MC Eternal Lite;
|
||||
mc-eternal-hardcore;15;;;永恒的MC 硬核版;MC Eternal Hardcore;
|
||||
rlcraft;16;927630;;超现实 x 虚拟生存;RLCraft;RLC
|
||||
the-trial-of-god;17;858670;;神之试炼;The Trial of God;TIMW4
|
||||
project-ozone-3-a-new-way-forward;18;;;臭氧计划3;Project Ozone 3 A New Way Forward;PO3
|
||||
equivalent-skies;19;;;等价空岛;Equivalent Skies;
|
||||
ftb-continuum;20;;;FTB Continuum;;
|
||||
enigmatica;21;;;Enigmatica;;E
|
||||
enigmatica2;22;;;Enigmatica 2;;E2
|
||||
enigmatica2expert;23;;;Enigmatica 2: Expert;;E2E
|
||||
enigmatica2light;24;;;Enigmatica 2: Light;;E2L
|
||||
enigmatica4;25;;;Enigmatica 4;;E4
|
||||
enigmatica5;26;;;Enigmatica 5;;E5
|
||||
enigmatica6;27;;;Enigmatica 6;;E6
|
||||
roguelike-adventures-and-dungeons;28;816228;;Roguelike冒险与地牢;Roguelike Adventures and Dungeons;R.A.D
|
||||
farming-valley;29;;;农场物语;Farming Valley;
|
||||
compact-claustrophobia;30;1062339;;幽恐噩梦;Compact Claustrophobia;
|
||||
modern-skyblock-3-departed;31;;;现代空岛3;Modern Skyblock 3: Departed;
|
||||
gregblock;32;;;格雷空岛;Gregblock;
|
||||
sevtech-ages;33;;;赛文科技;SevTech: Ages;STA
|
||||
;34;870716;;伊卡洛斯号迫降;Icarus Landing;IL
|
||||
greedycraft;35;1089190;;贪婪整合包;GreedyCraft;
|
||||
stoneblock;36;;;StoneBlock;;
|
||||
ftb-presents-skyfactory-3;37;;;天空工厂3;FTB Presents SkyFactory 3;SF3
|
||||
space-astronomy;38;;;Space Astronomy;;
|
||||
world-of-dragons;39;;;World of Dragons;;
|
||||
modern-skyblock-2;40;;;现代空岛2;Modern Skyblock 2;
|
||||
all-the-mods-2;41;;;All the Mods 2;;ATM2
|
||||
automaton;42;;;Automaton;;
|
||||
botania-skyblock-the-modpack-the-mod-the-modpack;43;;;Botania Skyblock;;
|
||||
farming-valley-lite;44;;;Farming Valley - Lite;;
|
||||
atm-all-the-magic;45;;;All the Mods: All the Magic;;
|
||||
hr-new-beginnings;46;;;HR: New Beginnings;;
|
||||
valhelsia-2;47;;;Valhelsia 2;;
|
||||
ftb-presents-stoneblock-2;48;;;FTB Presents Stoneblock 2;;
|
||||
ftb-sky-odyssey;49;;;天空奥德赛;FTB Sky Odyssey;
|
||||
all-of-fabric-3;50;;;All of Fabric 3;;AOF3
|
||||
terrafirma-rescue;51;977365;;群峦:救援;Terrafirma:Rescue;TFR
|
||||
antimatter-chemistry;52;;;Antimatter Chemistry;;
|
||||
re-build;53;;;RE-BUILD;;
|
||||
integration-by-parts;54;;;Integration By Parts;;IBP
|
||||
crafttoexiledissonance;55;;;放逐之路;Craft to Exile [Dissonance];CTE
|
||||
crafttoexileharmony;56;;;放逐之路;Craft to Exile [Harmony];CTE
|
||||
exoria;57;;;艾柯索瑞亚;Exoria;
|
||||
age-of-engineering;58;;;时代工业;Age of Engineering;AOE
|
||||
amnesia;60;;;Amnesia;;
|
||||
the-vanilla-experience;61;;;The Vanilla Experience;;
|
||||
gregicality-skyblock-edition;62;;;Gregicality Skyblock Edition;;
|
||||
TownCraft;63;;;TownCraft;;
|
||||
ftb-revelation;64;;;FTB Revelation;;
|
||||
ftb-infinity-evolved;65;;;FTB Infinity Evolved 1.7;;
|
||||
ftb-beyond;66;;;FTB Beyond;;
|
||||
ftb-infinity-evolved-skyblock;67;;;FTB Infinity Evolved Skyblock;;
|
||||
ftb-ultimate-reloaded;68;;;FTB Ultimate Reloaded;;
|
||||
ftb-ultimate;69;;;FTB Ultimate;;
|
||||
ftb-academy;70;;;FTB Academy 1.12;;
|
||||
;71;;;FTB University 1.12;;
|
||||
ftb-presents-direwolf20;72;;;FTB Presents Direwolf20;;
|
||||
ftb-presents-direwolf20-1-10;73;;;FTB Presents Direwolf20 1.10;;
|
||||
ftb-presents-direwolf20-1-12;74;;;FTB Presents Direwolf20 1.12;;
|
||||
direwolf20-1-4-7;75;;;Direwolf20 1.4.7;;
|
||||
direwolf20-1-5-2;76;;;Direwolf20 - 1.5.2;;
|
||||
direwolf20-1-6-4;77;;;Direwolf20 - 1.6.4;;
|
||||
ftb-unstable-1-12;78;;;FTB Unstable 1.12;;
|
||||
ftb-unstable-1-14;79;;;FTB Unstable 1.14;;
|
||||
ftb-unstable-1-10;80;;;FTB Unstable 1.10;;
|
||||
;81;;;FTB Unstable 1.16;;
|
||||
ftb-presents-skyfactory-2-5;82;;;天空工厂2.5;FTB Presents SkyFactory 2.5;SF2.5
|
||||
ftb-skyfactory-challenges;83;;;FTB Skyfactory Challenges;;
|
||||
ftb-sky-adventures;84;;;FTB Sky Adventures;;
|
||||
volcano-block;85;;;Volcano Block;;VB
|
||||
chemical-exchange;86;;;Chemical Exchange;;
|
||||
ftb-builders-paradise;87;;;FTB Builders Paradise;;
|
||||
ftb-pyramid-reborn;88;;;FTB Pyramid Reborn 3.0;;
|
||||
feed-the-beast-egg-hunt;89;;;FTB Egg Hunt;;
|
||||
ftb-presents-cloud-9;90;;;FTB Presents Cloud 9;;
|
||||
ftb-retro-smp;91;;;FTB Retro SMP;;
|
||||
ftb-horizons-iii;92;;;FTB Horizons III;;
|
||||
ftb-infinity-lite-1-10;93;;;FTB Infinity Lite 1.10;;
|
||||
ftb-presents-hermitpack;94;;;FTB Presents HermitPack;;
|
||||
yogcraft-modpack;95;;;YogCraft Modpack;;
|
||||
tech-world;96;;;Tech World;;
|
||||
mindcrack;97;;;MindCrack Pack;;
|
||||
magic-world-1-4-7;98;;;Magic World - 1.4.7;;
|
||||
ftb-lite;99;;;FTB Lite;;
|
||||
ftb-inventions;100;;;FTB Inventions;;
|
||||
ftb-presents-crackpack;101;;;FTB Presents Crackpack;;
|
||||
ftb-vanilla;102;;;FTB Vanilla +;;
|
||||
ftb-lite-3;103;;;FTB Lite 3;;
|
||||
ftb-departed;104;;;FTB Departed;;
|
||||
ftb-trident;105;;;FTB Trident;;
|
||||
ftb-mage-quest;106;;;FTB Mage Quest;;
|
||||
ftb-retro-ssp;107;;;FTB Retro SSP;;
|
||||
ftb-resurrection;108;;;FTB Resurrection;;
|
||||
ftb-monster;109;;;FTB Monster;;
|
||||
ftb-horizons;110;;;FTB Horizons;;
|
||||
tech-world-2;111;;;FTB Tech World 2;;
|
||||
magic-world-2;112;;;FTB Magic World 2;;
|
||||
ftb-unleashed;113;;;FTB Unleashed;;
|
||||
ftb-lite-2;114;;;FTB Lite 2;;
|
||||
unstable-1-7-x-public-beta-test-pack;115;;;Unstable 1.7.x;;
|
||||
pax-prime-2014-challenge;116;;;PAX Prime 2014 Challenge;;
|
||||
ftb-unhinged;117;;;FTB Unhinged;;
|
||||
slows-stream-pack;118;;;Slow's Stream Pack;;
|
||||
feed-the-beast-beta-pack;119;;;Feed The Beast Beta Pack;;
|
||||
pax-prime-2015-challenge;120;;;PAX Prime 2015 Challenge;;
|
||||
pax-south-2015-challenge;121;;;PAX South 2015 Challenge;;
|
||||
pax-east-2014-challenge;122;;;PAX East 2014 Challenge;;
|
||||
pax-east-2013-challenge;123;;;FTB Pax Challenge Pack 2013;;
|
||||
ftb-terra;124;;;FTB Terra;;
|
||||
ftb-horizons-daybreaker;125;;;FTB Horizons: Daybreaker;;
|
||||
omnifactory;126;924850;;全能工厂;Omnifactory;
|
||||
;127;;;IMPACT;;
|
||||
kinda-crazy-craft;128;;;Kinda Crazy Craft;;KCC
|
||||
kinda-crazy-craft-2-0;129;;;Kinda Crazy Craft 2.0;;KCC2
|
||||
sevtech-ages-of-the-sky;130;;;SevTech: Ages of the Sky;;
|
||||
one-hundred-years-after-the-apocalypse;131;;;One Hundred Years After the Apocalypse;;
|
||||
staged-learning;132;;;Staged Learning;;
|
||||
technocratica;133;;;Technocratica;;
|
||||
journey-beyond-the-abyss;134;;;超越深渊之旅;Journey Beyond the Abyss;JBtA
|
||||
awakening-sky-of-diamonds;135;;;Awakening - Sky of Diamonds;;
|
||||
ragnamod-v;136;;;Ragnamod V;;
|
||||
all-the-mods-3-expert;137;;;All the Mods 3 专家版;All the Mods 3 Expert;ATM3E
|
||||
seablock-rustic-waters;138;994352;;乡村水域;Seablock: Rustic Waters;
|
||||
engineers-life;139;;;Engineer's Life;;
|
||||
star-factory;140;;;Star Factory;;
|
||||
lunar-laboratory;141;;;Lunar Laboratory;;
|
||||
glacial-awakening;142;;;Glacial Awakening;;
|
||||
project-equivalence;143;;;Project Equivalence;;
|
||||
breakout;144;;;Break Out;;
|
||||
scientists-dream;145;;;Scientific Dreams;;
|
||||
abandend;146;;;AbandEnd;;
|
||||
tech-expansion;147;;;Tech Expansion;;
|
||||
minefanstasyworld;148;967574;;我的幻想世界;MineFantasyWorld;
|
||||
slightly-vanilla-flavoured;149;;;Slightly Vanilla Flavoured;;
|
||||
all-the-mods;150;;;All the Mods;;ATM1
|
||||
crackpack-3;151;;;Crackpack 3;;
|
||||
valhelsia-3;152;;;Valhelsia 3;;
|
||||
cortex-3;153;;;Cortex 3;;
|
||||
techblock-caveblock;154;;;TechBlock: CaveBlock;;
|
||||
;155;;;星空;Galaxy Space Integration;GSI
|
||||
valhelsia;156;;;Valhelsia 1;;
|
||||
valhelsia-origins;157;;;Valhelsia: Origins;;
|
||||
galactic-science;159;;;Galactic Science;;
|
||||
space-astronomy-2;160;;;Space Astronomy 2;;
|
||||
;161;959784;;匠魂的生存;Tinker's Survival;
|
||||
life-in-the-village-2;162;;;Life in the village 2;;LitV2
|
||||
;163;;;魔法与工业;Magic And Creat;MAC
|
||||
all-the-mods-6;164;;;All the Mods 6;;ATM6
|
||||
garden-of-glass-questbook-edition;165;;;Garden of Glass (Questbook Edition);;
|
||||
;166;;;圣剑传奇;Sword Legendary;
|
||||
;167;;;FTB Presents Direwolf20 1.16;;
|
||||
team-rustic;168;;;Team Rustic;;
|
||||
enigmatica2expertskyblock;169;;;Enigmatica 2: Expert Skyblock;;E2ES
|
||||
;170;;;休闲-生存-2021版;Leisure Survival-2021;ls21
|
||||
hexxit-updated;171;;;Hexxit Updated;;
|
||||
create-101-season-2;172;1172401;;Create 101 第二季;Create 101 Season 2;C101 S2
|
||||
forever-stranded;173;;;Forever Stranded;;
|
||||
minecraft-ultimato;174;;;MC Ultimato;;
|
||||
lapitos-galacticraft;175;;;Lapito's Galacticraft;;
|
||||
material-energy-4;176;;;Material Energy^4;;
|
||||
material-energy-classic;177;;;Material Energy^3;;
|
||||
slashaoa3;178;;;SlashAoA3;;
|
||||
popularmmos-epic-proportions-season-10;179;;;PopularMMOS Epic Proportions Season 10;;
|
||||
mcmod-testpack;180;;;MC百科-测试包;MCMOD-TestPack;
|
||||
;181;;;FTB Endeavour;;
|
||||
;182;1173278;;铅笔空岛;How are you block;HB
|
||||
;183;;;花鸟旅途;Hananichi On The Road;HOTR
|
||||
sky-bees;184;;;Sky Bees;;
|
||||
rebirth-of-the-night;185;;;Rebirth of the Night;;RotN
|
||||
zombie-apocalypse-slow-zombies-by-forge-labs;186;;;僵尸启示录;Zombie Apocalypse / Mustard Virus;
|
||||
star-wars-conquest;187;;;Star Wars Conquest;;
|
||||
tekkit-classic-reloaded;188;;;Tekkit Classic Reloaded;;TCR
|
||||
mineshafts-monsters;189;1193321;;矿井与怪物;Mineshafts & Monsters;
|
||||
crazy-craft-remastered;190;;;Crazy Craft Remastered;;
|
||||
engineers-life-2;191;;;Engineer's Life 2;;EL2
|
||||
hr-new-beginnings-2;192;;;HR: New Beginnings 2;;
|
||||
minecolonies-testpack;193;;;模拟殖民地;MineColonies Official;
|
||||
tnp-limitless;194;;;TNP Limitless;;
|
||||
tnp-limitless-2;195;;;TNP Limitless 2;;
|
||||
tnp-limitless-3;196;;;TNP Limitless 3;;
|
||||
all-in-one-modded-one-block;197;;;All in One;;
|
||||
all-the-mods-6-to-the-sky-atm6s;198;;;All the Mods 6 - To the Sky;;ATM6S
|
||||
all-the-mods-4;199;;;All the Mods 4;;ATM4
|
||||
all-the-mods-5;200;;;All the Mods 5;;ATM5
|
||||
atm-3-lite;201;;;All the Mods 3: Lite;;ATM3L
|
||||
;202;914868;;伪装者;The Disguiser;
|
||||
createch-1165;203;;;科瑞亚科技;CreaTech;CT
|
||||
reboot;204;;;重启;Reboot;
|
||||
better-minecraft-fabric;205;;;更好的我的世界;Better Minecraft;
|
||||
crucial-2;206;;;Crucial 2;;
|
||||
better-minecraft-plus;207;;;Better Minecraft [PLUS];;
|
||||
parasites-by-forge-labs;208;;;Parasites by Forge Labs;;
|
||||
create-live;209;;;Create Live;;
|
||||
;210;1198408;;穿越航班;;CYH
|
||||
airaka-tech;211;;;艾瑞卡科技;Airaka Tech;
|
||||
;212;;;TeaCon;;
|
||||
originssmp;213;;;起源SMP;OriginsSMP;
|
||||
create-live-2;214;;;Create Live 2;;
|
||||
origin-smp-copy;215;;;Origin - SMP;;
|
||||
;216;1133826;;逆迫降;INUERSE FORCED LANDING;
|
||||
avatar-the-four-elements;217;;;Avatar: The Four Elements;;
|
||||
seaopolis;218;;;海之都;Seaopolis;
|
||||
zombie-apocalypse-remastered;219;;;僵尸启示录:重制;Zombie Apocalypse Remastered;ZAR
|
||||
stablejava;220;;;StableJava;;SJ
|
||||
technodefirmacraft;221;;;TechnodeFirmaCraft;;TNFC
|
||||
agrarian-skies;222;;;Agrarian Skies;;
|
||||
agrarian-skies-2;223;;;Agrarian Skies 2;;
|
||||
;224;1172950;;交汇;interactions;IA
|
||||
skyexchange;225;;;Skyexchange;;
|
||||
project-ozone;226;;;臭氧计划;Project Ozone;PO
|
||||
project-ozone-2-reloaded;227;;;臭氧计划2;Project Ozone 2: Reloaded;PO2
|
||||
ocean-outlast;228;;;Ocean Outlast;;
|
||||
fabric-adventure-pack;229;;;Adventure Pack;;AP
|
||||
crash-landing;230;;;Crash Landing;;
|
||||
invasion;231;;;Invasion;;
|
||||
;232;;;FTB Cotton;;
|
||||
chroma-sky;233;;;色度空岛;Chroma Sky;
|
||||
astroblock;234;;;AstroBlock;;
|
||||
;235;1188038;;群峦:核工程师;;TFAE
|
||||
;236;;;北极星;Borealis;
|
||||
multiblock-madness;237;;;Multiblock Madness;;
|
||||
happy-bat;238;;;Happy Bat;;HB
|
||||
;239;1212452;;氮元素;;N
|
||||
fictioncraft-atomic-space;240;1149410;;合虚为实:原子空间;FictionCraft: Atomic Space;
|
||||
stacia-expert;241;;;Stacia Expert;;
|
||||
mind;242;1212987;;思维;Mind;
|
||||
all-the-tinkers-construct;243;1196264;;最全的匠魂;All The Tinker's Construct;ATTC
|
||||
;244;;;秋水;;
|
||||
stay-true;246;;;Stay True;;
|
||||
all-of-fabric-4;247;;;All of Fabric 4;;AOF4
|
||||
fabulously-optimized;248;;;难以置信的优化;Fabulously Optimized;FO
|
||||
better-minecraft-otv-edition;249;;;Better Minecraft (OfflineTV Edition);;
|
||||
evolution-reset;250;1029654;;进化:重启;Evolution: Reset;EVOR
|
||||
the-nuclear-wasteland;251;;;核荒原;The Nuclear Wasteland;
|
||||
blessed-or-cursed-expedition-to-infinite-force;252;1225741;;祝福或诅咒:无限力量之征;Blessed or Cursed: Expedition to Infinite Force;BOC
|
||||
;253;1051281;;边界生存;Border Survival;BSV
|
||||
;254;970470;;冒险者的世界;Adventurer's world;
|
||||
medieval-minecraft-fabric;255;;;Medieval Minecraft;;
|
||||
heavens-of-sorcery;256;;;巫术天堂;Heavens of Sorcery;
|
||||
;257;1299458;;希罗多德;Herodotus;HDS
|
||||
;258;1235662;;重生世界;Rebirth world;RW
|
||||
permafrost-eots;259;;;永久冻土:风暴之眼;Permafrost - Eyes of the Storm;
|
||||
ultimate-alchemy;260;;;终极炼金;Ultimate Alchemy;
|
||||
virtual-survival-and-climate-control;261;;;虚拟生存·文明时代;virtual survival and climate control Remastered;VSACCR
|
||||
;262;;;超级英雄无限;Superheroes Unlimited;SU
|
||||
;263;1332500;;植物大战僵尸:沉浸动力;Create in PVZ;CIP
|
||||
sky-high;264;;;Sky High;;
|
||||
createtogether;265;;;CreateTogether;;
|
||||
;266;;;FTB Academy 1.16;;
|
||||
the-kingdom-of-daldar-forge-labs;267;;;The Kingdom of Daldar - Forge Labs;;
|
||||
demon-slayers-unleashed;268;;;Demon Slayers Unleashed;;
|
||||
valhelsia-fabric;269;;;Valhelsia: Enhanced Vanilla;;
|
||||
;270;;;FTB University 1.16;;
|
||||
;271;1067214;;神秘复兴;Thaumic Renaissance;
|
||||
the-winter-rescue;273;1227167;;冬季救援;The Winter Rescue;TWR
|
||||
;274;620632;;飞翔之路;;
|
||||
forever-stranded-lost-souls;275;;;Forever Stranded Lost Souls;;
|
||||
regrowth-an-hqm-pack;276;;;Regrowth;;
|
||||
roguelike-adventures-and-dungeons-tic-edition;277;;;RAD匠魂版;Roguelike Adventures and Dungeons TiC Edition;
|
||||
vanilla-forge-essentials;279;;;Vanilla Forge Essentials;;VFE
|
||||
rad-pack-lite;280;;;RAD pack(LITE);;
|
||||
manufactio;281;;;Manufactio;;
|
||||
world-of-dragons-ii;282;;;World of Dragons II;;
|
||||
tensura-unleashed;283;;;Tensura Unleashed;;
|
||||
skyopolis-4;284;;;Skyopolis 4;;
|
||||
;286;;;FTB OceanBlock;;
|
||||
tolkiencraft-iii-return-to-middle-earth;287;;;TolkienCraft III - Return to Middle-earth;;
|
||||
delivery-inc;288;;;Delivery Inc.;;
|
||||
poetica;289;;;Poetica;;
|
||||
divine-journey-2;290;;;Divine Journey 2;;DJ2
|
||||
all-the-mods-7;291;;;All the Mods 7;;ATM7
|
||||
skyfactory-one;292;;;SkyFactory One;;
|
||||
feed-the-singularity;293;;;喂养奇点生存;Feed the Singularity;
|
||||
crucial;294;;;Crucial;;C
|
||||
;295;1245996;;废土文明;Wasteland civilization;
|
||||
;296;;;SkyFactory 2;;
|
||||
a-bit-of-everything-official;297;;;A Bit Of Everything;;ABOE
|
||||
resonantrise-5;298;;;Resonant Rise 5;;
|
||||
fabric-exploration;299;;;Fabric Exploration;;
|
||||
into-the-betweenlands;300;;;Into The Betweenlands;;ITB
|
||||
annals-mechanizac;301;998400;;尘烬:消失世界;Annals:Mechanizac;ANS
|
||||
techopolis;302;;;科技之都;Techopolis;
|
||||
another-quality-modpack-2;303;;;Another Quality Modpack 2;;AQM2
|
||||
go-create;304;;;Go Create;;
|
||||
rlcraft-remastered;305;;;RLCraft Recrafted;;RLCR
|
||||
cave-factory;306;;;洞穴工厂;Cave Factory;
|
||||
naruto-modpack-reforged;307;;;Naruto Modpack Reforged;;
|
||||
euphoriccuriosity;308;;;Euphoric Curiosity;;
|
||||
beyond-the-void-2;309;;;Beyond The Void 2;;
|
||||
journey-in-the-sky;310;;;Journey In The Sky;;
|
||||
winter-regrowth;311;;;Winter Regrowth;;
|
||||
create-above-and-beyond;312;;;机械动力:永无止境;Create: Above and Beyond;CAB
|
||||
monumental-experience;313;;;Monumental Experience;;
|
||||
chroma-technology-2;314;;;Chroma Technology 2;;
|
||||
huntingmonstersmodpack;315;;;Hunting Monsters The Witcher Modpack;;
|
||||
cuboid-outpost;316;;;Cuboid Outpost;;
|
||||
ragnamod-vi;317;;;Ragnamod VI;;
|
||||
crundee-craft;318;;;Crundee Craft;;
|
||||
;319;;;Yogscast Complete Pack;;
|
||||
insanecraft-modpack;320;;;InsaneCraft Modpack;;
|
||||
all-the-magic-spellbound;321;;;All the Magic Spellbound;;ATMS
|
||||
odyssey-space-a-new-beginning;322;;;Odyssey: Space - A New Beginning;;
|
||||
nuts-and-bolts;323;;;Nuts and Bolts;;
|
||||
vault-hunters-official-modpack;324;;;Vault Hunters;;
|
||||
;325;;;FTB Ultimate: Anniversary Edition;;
|
||||
enigmatica8;326;;;Enigmatica 8;;E8
|
||||
all-of-fabric-5;327;;;All of Fabric 5;;AOF5
|
||||
last-days-of-humanity;328;;;人类末日;Last Days of Humanity;LDH
|
||||
underdog;329;;;弱者;Underdog;
|
||||
mc-dripcraft;330;;;DripCraft;;
|
||||
atr1;331;;;All the RPMTW 1;;ATR1
|
||||
techno-ages;332;;;技术时代;Techno Ages;
|
||||
expert-creation;333;;;Expert Creation;;
|
||||
spookyjam-2021-forge-edition;334;;;SpookyJam 2021 - Forge Edition;;
|
||||
digsite;335;;;DigSite;;
|
||||
dark-souls-simulator;336;;;Dark Souls Simulator;;
|
||||
advent-of-ascension-plus;337;;;Advent of Ascension Plus;;
|
||||
bounds;338;;;Bounds;;
|
||||
reimagined-rlcraft;339;;;RLCraft - Reimagined (not official);;
|
||||
;340;;;大轩整合包;Biggest Xuan Pack;BxP
|
||||
plunger;341;;;Plunger;;
|
||||
foolcraft-3;342;;;FoolCraft 3;;
|
||||
;343;1301641;;命定之旅;Deep To Destiny;DTD
|
||||
one-thousand-years-after-the-apocalypse;344;;;One Thousand Years After the Apocalypse;;
|
||||
all-the-mods-3-remix;345;;;All the Mods 3 - Remix;;ATM3R
|
||||
ultimate-progression;346;;;Ultimate Progression;;
|
||||
benchmark-ii;347;;;Benchmark II;;
|
||||
ragnamod-ii-reborn;348;;;Ragnamod II Reborn;;
|
||||
ragnamod-iii;349;;;Ragnamod III;;
|
||||
ragnamod-iv;350;;;Ragnamod IV;;
|
||||
pokehaan-craft;351;;;Pokehaan Craft;;
|
||||
all-the-sky;352;;;All The Sky;;
|
||||
all-the-mods-expert;353;;;All the Mods Expert;;ATM1E
|
||||
all-the-mods-lite;354;;;All the Mods Lite;;ATM1L
|
||||
all-the-mods-expert-remastered;355;;;All The Mods Expert: Remastered;;
|
||||
all-the-mods-0;356;;;All the Mods 0;;ATM0
|
||||
all-the-mods-fabric;357;;;All the Mods Fabric;;ATMF
|
||||
the-pixelmon-modpack;358;;;The Pixelmon Modpack;;
|
||||
better-minecraft-skyblock;359;;;更好的我的世界:空岛;Better Minecraft Skyblock;
|
||||
age-of-fate;360;;;Age of Fate;;
|
||||
deathdusk;361;;;Deathdusk;;
|
||||
;362;;;幻想旅程;Imaginary Journey;
|
||||
moonlitmyths;363;;;月耀神话;Moonlit Myths;
|
||||
;364;1283063;;晴天整合;Sunny Modpack;
|
||||
valhelsia-4;365;;;Valhelsia 4;;
|
||||
another-quality-modpack-3;366;;;Another Quality Modpack 3;;
|
||||
nomifactory;367;;;Nomifactory;;
|
||||
allusiveness;368;;;Allusiveness;;
|
||||
;370;1246147;;转生之路;The way of reincarnation;
|
||||
sky-friends;371;;;Sky Friends;;
|
||||
;372;;;魔法包裹;Magic Pouch;
|
||||
magica-infinitum-iii;373;;;Magica Infinitum III;;
|
||||
ardentcraft-the-beginning;374;;;ArdentCraft: The Beginning;;
|
||||
;375;;;神秘启示录2:梦魇般的回忆;Nightmare Memories Pack;
|
||||
jurassic-world-reborn;376;;;Jurassic World Reborn;;
|
||||
;377;;;FTB Omnia;;
|
||||
fantazy-tech-expert-mode;378;;;幻想科技;Fantazy Tech;
|
||||
crainer-craft-2;379;;;Crainer Craft 2;;
|
||||
;380;;;All The Forge 8;;
|
||||
colony-space-modpack;381;;;KN: Colony New Worlds;;
|
||||
skyopolis-3;382;;;Skyopolis 3;;
|
||||
;383;;;A Bit of Everything 3;;
|
||||
sky-alchemist;384;;;天空炼金术士;Sky Alchemist;
|
||||
;385;1186932;;奥德赛;OdysseyCraft;OC
|
||||
;386;;;字字落实;;
|
||||
nuclear-earth-reirradiated;387;;;核地球:再辐照;Nuclear Earth: Reirradiated;
|
||||
god-block;388;;;God Block;;
|
||||
faruk-v;389;;;Faruk V;;
|
||||
kreezcraft-presents-cobbleblock;390;;;Cobbleblock;;
|
||||
;391;1304828;;寄生虫:血染黎明;Parasites: Blood Dawn;PBD
|
||||
minecraft-trillionaire;392;;;亿万富翁;Minecraft Trillionaire;
|
||||
gregtech-community-pack;393;;;GregTech Community Pack;;GCP
|
||||
;394;1206714;;诸神匠心;Tinkers Heartwork and Deities;THD
|
||||
create-yourself;395;;;Create Yourself;;
|
||||
starrynight-pray-stp;397;1323610;;星夜祈临;StarryNight Pray;StP
|
||||
rustic-waters-ii;398;;;乡村水域2;Rustic Waters II;
|
||||
enigmatica6expert;399;;;Enigmatica 6 Expert;;E6E
|
||||
celestial-journey;400;;;Celestial Journey;;
|
||||
;401;;;天空舞台;Skyblock Create Show;
|
||||
project-architect;402;;;Project Architect;;
|
||||
greed-the-resource-3-factory;403;;;Greed the Resource 3: Factory;;
|
||||
f-o-g-of-magic;404;;;F.O.G. of Magic;;
|
||||
;405;;;僵尸生存;Mike's Zombie Survival;MZS
|
||||
darkrpg;406;;;DarkRPG;;
|
||||
;407;;;征途;Conquest journey;CJ
|
||||
;408;;;创生银莲;Create Anemone;
|
||||
kimetsu-no-yaiba-demon-slayer-mod-pack;409;;;鬼灭之刃;Kimetsu no Yaiba;
|
||||
;410;991420;;七曜之际;FOR A WEEK;
|
||||
not-too-complicated;411;;;Not Too Complicated;;NTC
|
||||
fiefdom;412;;;列土封疆;Fiefdom;
|
||||
reinforce-reality;413;1325523;;死亡工艺&重生;Deathcraft & Rebirth;
|
||||
afterlife;414;;;The Afterlife;;
|
||||
create-skyblock;415;;;CREATE Skyblock;;
|
||||
lightcraftproject;416;;;Light Craft;;
|
||||
epoch-runner-formerly-nuclear-beyond-modpack;417;;;时代领跑人;Epoch Runner;
|
||||
;418;;;无尽星河;Endless Galaxy;EG
|
||||
roguelike-adventures-and-dungeons-2;419;;;冒险与地牢2;Roguelike Adventures and Dungeons 2;RAD2
|
||||
multiskyblock;420;;;MultiSkyBlock;;
|
||||
;421;;;人性化;Humanized;H
|
||||
create-live-3;422;;;Create Live 3;;
|
||||
magnificia;423;;;Magnificia;;
|
||||
;424;1328128;;魔法金属-新手向导;ManaMetalMod - WithNewbHelper;
|
||||
;425;;;奇美拉;Chimera;CMA
|
||||
chroma-sky-2;426;;;色度空岛2;Chroma Sky 2;
|
||||
infinity-fabric;427;1298525;;无限;Infinity;IF
|
||||
enigmatica-2-expert-extended;428;;;Enigmatica 2: Expert - Extended;;E2E-E
|
||||
;429;1097781;;水星迫降重制版;Mercury landing;
|
||||
magic-sky;430;;;Magic Sky;;
|
||||
;431;;;科技飞升;Rise of Tech;RoT
|
||||
not-too-complicated-2;432;;;Not Too Complicated 2;;
|
||||
low-res-trailer-recreation;433;;;Low Res Trailer Recreation;;
|
||||
shattered-ring;434;;;Shattered Ring;;
|
||||
;435;;;FTB Chocolate;;
|
||||
;436;;;Intergalactical Modpack;;
|
||||
dude-wheres-my-blocks;437;;;谁动了我的合成表;Dude where's my blocks;
|
||||
;438;;;我的原神:自然;mineshin impact:nature;MSIPN
|
||||
@@ -1,90 +0,0 @@
|
||||
[
|
||||
{
|
||||
"module": "javafx.base",
|
||||
"groupId": "org.openjfx",
|
||||
"artifactId": "javafx-base",
|
||||
"version": "17",
|
||||
"sha1": {
|
||||
"linux": "476c7640c2320244d3156edbbc19a7eda275c1ed",
|
||||
"linux-arm32-monocle": "1dad2b0479066fc65ce83ae276632b0be4a648f7",
|
||||
"linux-aarch64": "b62b6cae95847fda1dce4a8a29edcf8a31b85333",
|
||||
"mac": "61d52392b063be78f7cc1b0fbfc04dda7a835a15",
|
||||
"mac-aarch64": "d77b645fa3e1a6270783178541944e4b4a457cd2",
|
||||
"win": "b63b9696f73009ca5002f49ec4db73576e2de168",
|
||||
"win-x86": "d35c89f5cb5f9dd8c54c69b4a153a94377e60931"
|
||||
}
|
||||
},
|
||||
{
|
||||
"module": "javafx.graphics",
|
||||
"groupId": "org.openjfx",
|
||||
"artifactId": "javafx-graphics",
|
||||
"version": "17",
|
||||
"sha1": {
|
||||
"linux": "88dc59568c14ec37ea1c42b3ba472d78b4b1030a",
|
||||
"linux-arm32-monocle": "44284bba939b1249fcc3f5502a70bd48e88e1fa9",
|
||||
"linux-aarch64": "40bd7094b8f43d4637a8a274b4b199835d6cfd5c",
|
||||
"mac": "8b1d1124ce83e874db390016913fba7708f3592d",
|
||||
"mac-aarch64": "b6326f61f7e0effbf733d62e83b8317eb8e768b4",
|
||||
"win": "2c56d252b97eba8b42f049c6ce145172d744ce08",
|
||||
"win-x86": "c4d74a11a7ffb182fb43803756e678c87876a0a4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"module": "javafx.controls",
|
||||
"groupId": "org.openjfx",
|
||||
"artifactId": "javafx-controls",
|
||||
"version": "17",
|
||||
"sha1": {
|
||||
"linux": "851a1896fbd8548fb3671aa689b305b2cfad956a",
|
||||
"linux-arm32-monocle": "9add14fda362a1a2d1cd3a84de60d67da2d7a5ae",
|
||||
"linux-aarch64": "eb4116ebad81183980dde74b072a15b0f6c795ef",
|
||||
"mac": "e73acf695ffccb4f904ff7b76de8999eefb547c2",
|
||||
"mac-aarch64": "2254b351c1e6ce2ac9adcd3480fed883cebbb08b",
|
||||
"win": "efc0f4edde079c22987064922530c67fa1350a9b",
|
||||
"win-x86": "c9997e885f2dd71d8a4a7b89bc510923d2d412d6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"module": "javafx.fxml",
|
||||
"groupId": "org.openjfx",
|
||||
"artifactId": "javafx-fxml",
|
||||
"version": "17",
|
||||
"sha1": {
|
||||
"linux": "0a6f029f4fab0f01f00a838de1e65e20c6a92f02",
|
||||
"linux-arm32-monocle": "922b73ca1813dfa87e66613d2904286bdd06e7f1",
|
||||
"linux-aarch64": "7cd2f92f399f48acedc85636c9f07f6634d29669",
|
||||
"mac": "54026a476bb95426e2e159bcf25bbf0832d2b7bf",
|
||||
"mac-aarch64": "09ba33b0d9c19e9dcfd76fe7d95748a933e09b3b",
|
||||
"win": "08d79ed85fa9dde644d4b37d93a02a2ef2818769",
|
||||
"win-x86": "3bde4e98cee0a464f327b9e983aded3017fddbc0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"module": "javafx.media",
|
||||
"groupId": "org.openjfx",
|
||||
"artifactId": "javafx-media",
|
||||
"version": "17",
|
||||
"sha1": {
|
||||
"linux": "e3bb829ce1447a37a8b0b669a446e0414e54e5c7",
|
||||
"linux-aarch64": "7e7a50e66e44b902c624680601d5336d7fd16614",
|
||||
"mac": "ef463caf627c4c97b8e5c6a5ae16050c56aed966",
|
||||
"mac-aarch64": "970178b32e17650591698bae9f23bf5ce1295bb6",
|
||||
"win": "6012cc30ced1539f167b64959cb9d6a4283f7aa7",
|
||||
"win-x86": "8568e1edcfc803a75349124b21a27c0d609b3870"
|
||||
}
|
||||
},
|
||||
{
|
||||
"module": "javafx.web",
|
||||
"groupId": "org.openjfx",
|
||||
"artifactId": "javafx-web",
|
||||
"version": "17",
|
||||
"sha1": {
|
||||
"linux": "bbd71beaf561b72f0d3eb6268d3114573b7799c3",
|
||||
"linux-aarch64": "3cd86d9593e63ce7cbd5f9b5a09f8dcc0bbd7fdb",
|
||||
"mac": "bf188c003e54ffb4cff6a9be7ce437fa269964fe",
|
||||
"mac-aarch64": "a9408d443fa95cd3e2b1f170a3abeaace33d3af5",
|
||||
"win": "be4a308dbcc947af2a9d9c42a72fd7f6d0ba77b9",
|
||||
"win-x86": "3d93e1801c720747d4551de26ef5869ff9eacabc"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -101,7 +101,7 @@ public abstract class SkinCanvasSupport implements Consumer<SkinCanvas> {
|
||||
Stage stage = new Stage();
|
||||
stage.setTitle(title);
|
||||
SkinCanvas canvas = Test.createSkinCanvas();
|
||||
canvas.updateSkin(new Image(input), skin.getName().contains("[alex]"));
|
||||
canvas.updateSkin(new Image(input), skin.getName().contains("[alex]"), null);
|
||||
Scene scene = new Scene(canvas);
|
||||
stage.setScene(scene);
|
||||
stage.show();
|
||||
|
||||
Loaded 100 of 227 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user