Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba91c60e95 | ||
|
|
389b93178e | ||
|
|
37aaf153f8 | ||
|
|
2249f4ee0e | ||
|
|
eadfff349a | ||
|
|
cb4cd696a0 | ||
|
|
c85c074eee | ||
|
|
b26c478d4a | ||
|
|
2d83de7c31 | ||
|
|
20d93dd085 | ||
|
|
24fc436870 | ||
|
|
3a6c23aa9d | ||
|
|
0ee8b2dd82 | ||
|
|
7cce90bed8 | ||
|
|
c806a5d412 | ||
|
|
14e8b9212f | ||
|
|
67e96fb84f | ||
|
|
c39c19f215 | ||
|
|
88d7493870 | ||
|
|
404bc88ec8 | ||
|
|
e2355ca2ff | ||
|
|
aab271ac75 | ||
|
|
0da0a1f386 | ||
|
|
59316a866d | ||
|
|
d3087bd763 | ||
|
|
df7b63c491 | ||
|
|
dff893ba4b | ||
|
|
c539702a1b | ||
|
|
6fad149410 | ||
|
|
a072fb7bea | ||
|
|
08a3850dcf | ||
|
|
030fba4630 | ||
|
|
559aefd6b6 | ||
|
|
2ee5d9ceed | ||
|
|
20ed42ab8a | ||
|
|
815b90845a | ||
|
|
e9846ea644 | ||
|
|
02065094ca | ||
|
|
9f6124d617 | ||
|
|
76229a894c | ||
|
|
03de1598c8 | ||
|
|
3a13ff0e10 | ||
|
|
63345f52ce | ||
|
|
0bff282d3d | ||
|
|
a81462425d | ||
|
|
e9ae43b1f1 | ||
|
|
dc31f0d5c9 | ||
|
|
76f11b6a6f | ||
|
|
d6ea2ef1f9 | ||
|
|
14db21211f | ||
|
|
f354f65adb | ||
|
|
83e8ea3735 | ||
|
|
0e9e9811eb | ||
|
|
420d77de9f | ||
|
|
9361719a67 | ||
|
|
b09a5868d4 | ||
|
|
56c48e5595 | ||
|
|
4d6cb547ea | ||
|
|
95ecd29018 | ||
|
|
5d26a106e9 | ||
|
|
95707560ef | ||
|
|
c54cb88db8 | ||
|
|
95afaa5112 | ||
|
|
fe608e32f1 | ||
|
|
82fc1c067d | ||
|
|
94890e2924 | ||
|
|
9a76b1ff27 | ||
|
|
8db8693132 | ||
|
|
59cc5b9114 | ||
|
|
c1acd0b0b5 | ||
|
|
fb6538f042 | ||
|
|
20fe5430dd | ||
|
|
7de07319d9 | ||
|
|
8e3c19651e | ||
|
|
2740bdeb77 | ||
|
|
59ab49e0c5 | ||
|
|
bc4aa43834 | ||
|
|
c7f3f2a229 |
No files matched your search
@@ -8,18 +8,16 @@ body:
|
||||
attributes:
|
||||
value: |
|
||||
提交前请确认:
|
||||
* 该问题确实是 **HMCL 的错误**,而**不是 Minecraft 非正常退出**,如果你的 Minecraft 非正常退出,请前往 [KOOK 频道](https://kook.top/Kx7n3t) 或 [Discord 频道](https://discord.gg/jVvC7HfM6U) 中获取帮助。
|
||||
* 该问题确实是 **HMCL 的错误**,而**不是 Minecraft 非正常退出**,如果你的 Minecraft 非正常退出,请前往 [QQ 群](https://docs.hmcl.net/groups.html)/[KOOK 频道](https://kook.top/Kx7n3t)/[Discord 频道](https://discord.gg/jVvC7HfM6U) 中获取帮助。
|
||||
* 你的启动器版本是**最新的快照版本**,可以点击 [此处](https://github.com/burningtnt/HMCL-Snapshot-Update/raw/master/datas/HMCL-dev.jar) 下载最新快照版本。
|
||||
|
||||
如果你的问题并不属于上述两类,你可以选取另一种 Issue 类型,或者直接前往 [KOOK 频道](https://kook.top/Kx7n3t) 或 [Discord 频道](https://discord.gg/jVvC7HfM6U) 中获取帮助。
|
||||
如果你希望在 QQ 群中反馈问题,请 [赞助 HMCL](https://hmcl.huangyuhui.net/api/redirect/sponsor) 后申请加入群聊,也请将下面的信息表填好直接发在群中,加快我们的沟通速度。
|
||||
如果你的问题并不属于上述两类,你可以选取另一种 Issue 类型,或者直接前往 [QQ 群](https://docs.hmcl.net/groups.html)/[KOOK 频道](https://kook.top/Kx7n3t)/[Discord 频道](https://discord.gg/jVvC7HfM6U) 中获取帮助。
|
||||
|
||||
Before submitting, please confirm:
|
||||
* The issue is indeed **an error of HMCL**, not **Minecraft abnormal exit**. If your Minecraft exits abnormally, please go to the [KOOK channel](https://kook.top/Kx7n3t) or [Discord channel](https://discord.gg/jVvC7HfM6U) for help.
|
||||
* The issue is indeed **an error of HMCL**, not **Minecraft abnormal exit**. If your Minecraft exits abnormally, please go to the [QQ group](https://docs.hmcl.net/groups.html) or [KOOK channel](https://kook.top/Kx7n3t) or [Discord channel](https://discord.gg/jVvC7HfM6U) for help.
|
||||
* Your launcher version is **the latest snapshot version**. You can click [here](https://github.com/burningtnt/HMCL-Snapshot-Update/raw/master/datas/HMCL-dev.jar) to download the latest snapshot version.
|
||||
|
||||
If your issue does not fall into the above two categories, you can choose another type of issue or directly go to the [KOOK channel](https://kook.top/Kx7n3t) or [Discord channel](https://discord.gg/jVvC7HfM6U) for help.
|
||||
If you want to report a problem in the QQ group, please [sponsor HMCL](https://hmcl.huangyuhui.net/api/redirect/sponsor) and apply to join the group chat. Please also fill in the information form below and send it directly in the group to speed up our communication.
|
||||
If your issue does not fall into the above two categories, you can choose another type of issue or directly go to the [QQ group](https://docs.hmcl.net/groups.html) or [KOOK channel](https://kook.top/Kx7n3t) or [Discord channel](https://discord.gg/jVvC7HfM6U) for help.
|
||||
- type: input
|
||||
id: platform
|
||||
attributes:
|
||||
|
||||
@@ -6,9 +6,9 @@ contact_links:
|
||||
- name: Discord 频道 | Discord Channel
|
||||
url: https://discord.gg/jVvC7HfM6U
|
||||
about: Hello Minecraft! Launcher 的官方 Discord 频道。| The official Discord channel of Hello Minecraft! Launcher.
|
||||
- name: 赞助通道 | Sponsorship channel
|
||||
url: https://afdian.net/@huanghongxun
|
||||
about: 前往爱发电赞助 HMCL,赞助后可以申请加入官方 QQ 群反馈问题。| Sponsor Hello Minecraft! Launcher on afdian.net and you can apply to join the official QQ group.
|
||||
- name: QQ 群 | QQ Groups
|
||||
url: https://docs.hmcl.net/groups.html
|
||||
about: Hello Minecraft! Launcher 的官方 QQ 交流群。| The official QQ groups of Hello Minecraft! Laucher.
|
||||
- name: 其他反馈 | Others
|
||||
url: https://github.com/HMCL-dev/HMCL/discussions/new/choose
|
||||
about: 通过 Discussions 反馈其他问题。| Report other problems in Discussions.
|
||||
@@ -0,0 +1,46 @@
|
||||
name: Build HMCLauncher
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'HMCLauncher/**'
|
||||
- '.github/workflows/build-launcher.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'HMCLauncher/**'
|
||||
- '.github/workflows/build-launcher.yml'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Add msbuild to PATH
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
with:
|
||||
msbuild-architecture: x86
|
||||
- name: Build HMCLauncher
|
||||
run: msbuild /p:Configuration=Release /t:Rebuild /verbosity:detailed .\HMCLauncher\
|
||||
- name: Copy HMCLauncher to assets
|
||||
run: Copy-Item .\HMCLauncher\Release\HMCLauncher.exe -Destination .\HMCL\src\main\resources\assets\HMCLauncher.exe
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: '11'
|
||||
java-package: 'jdk+fx'
|
||||
- name: Build with Gradle
|
||||
run: .\gradlew makeExecutables --no-daemon
|
||||
env:
|
||||
MICROSOFT_AUTH_ID: ${{ secrets.MICROSOFT_AUTH_ID }}
|
||||
MICROSOFT_AUTH_SECRET: ${{ secrets.MICROSOFT_AUTH_SECRET }}
|
||||
CURSEFORGE_API_KEY: ${{ secrets.CURSEFORGE_API_KEY }}
|
||||
- name: Get short SHA
|
||||
run: echo "SHORT_SHA=$("${{ github.sha }}".SubString(0, 7))" >> $env:GITHUB_ENV
|
||||
- name: Copy HMCLauncher to libs
|
||||
run: Copy-Item .\HMCLauncher\Release\HMCLauncher.exe -Destination .\HMCL\build\libs\HMCLauncher.exe
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: HMCLauncher-${{ env.SHORT_SHA }}
|
||||
path: HMCL/build/libs/*.exe
|
||||
@@ -1,21 +1,25 @@
|
||||
name: Check Style
|
||||
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '**.java'
|
||||
pull_request:
|
||||
paths:
|
||||
- '**.java'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: '11'
|
||||
java-package: 'jdk+fx'
|
||||
- name: Check style main
|
||||
run: ./gradlew checkstyleMain
|
||||
run: ./gradlew checkstyleMain --no-daemon
|
||||
- name: Check style test
|
||||
run: ./gradlew checkstyleTest
|
||||
run: ./gradlew checkstyleTest --no-daemon
|
||||
@@ -1,19 +1,23 @@
|
||||
name: Check Translations
|
||||
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '**.properties'
|
||||
pull_request:
|
||||
paths:
|
||||
- '**.properties'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: '11'
|
||||
java-package: 'jdk+fx'
|
||||
- name: Check style test
|
||||
run: ./gradlew checkTranslations
|
||||
run: ./gradlew checkTranslations --no-daemon
|
||||
@@ -5,12 +5,15 @@ on:
|
||||
schedule:
|
||||
- cron: '30 * * * *'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
dev-check-update:
|
||||
if: ${{ github.repository_owner == 'HMCL-dev' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Fetch tags
|
||||
run: git fetch --all --tags
|
||||
- name: Install tools
|
||||
@@ -36,20 +39,24 @@ jobs:
|
||||
if: ${{ env.continue == 'true' }}
|
||||
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.exe.sha256"
|
||||
wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.jar"
|
||||
wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.jar.sha1"
|
||||
wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.jar.sha256"
|
||||
wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.sh"
|
||||
wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.sh.sha1"
|
||||
wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.sh.sha256"
|
||||
env:
|
||||
DOWNLOAD_BASE_URL: https://ci.huangyuhui.net/job/HMCL/lastSuccessfulBuild/artifact/HMCL/build/libs
|
||||
- name: Generate release note
|
||||
if: ${{ env.continue == 'true' }}
|
||||
run: |
|
||||
echo "HMCL v$HMCL_VERSION" >> RELEASE_NOTE
|
||||
echo "" >> RELEASE_NOTE
|
||||
echo "The full changelogs can be found on the website: https://docs.hmcl.net/changelog/dev.html" >> RELEASE_NOTE
|
||||
echo "Notice: changelogs are written in Chinese." >> RELEASE_NOTE
|
||||
echo "" >> RELEASE_NOTE
|
||||
echo "| File Name | SHA-256 Checksum |" >> RELEASE_NOTE
|
||||
echo "| --- | --- |" >> RELEASE_NOTE
|
||||
echo "| HMCL-$HMCL_VERSION.exe | $(cat HMCL-$HMCL_VERSION.exe.sha256) |" >> RELEASE_NOTE
|
||||
echo "| HMCL-$HMCL_VERSION.jar | $(cat HMCL-$HMCL_VERSION.jar.sha256) |" >> RELEASE_NOTE
|
||||
echo "| HMCL-$HMCL_VERSION.sh | $(cat HMCL-$HMCL_VERSION.sh.sha256) |" >> RELEASE_NOTE
|
||||
- name: Create release
|
||||
if: ${{ env.continue == 'true' }}
|
||||
uses: softprops/action-gh-release@v1
|
||||
@@ -57,16 +64,13 @@ jobs:
|
||||
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
|
||||
HMCL-${{ env.HMCL_VERSION }}.sh
|
||||
HMCL-${{ env.HMCL_VERSION }}.sh.sha1
|
||||
target_commitish: ${{ env.HMCL_COMMIT_SHA }}
|
||||
name: ${{ env.HMCL_TAG_NAME }}
|
||||
tag_name: ${{ env.HMCL_TAG_NAME }}
|
||||
stable-check-update:
|
||||
if: ${{ github.repository_owner == 'huanghongxun' }}
|
||||
if: ${{ github.repository_owner == 'HMCL-dev' }}
|
||||
needs: dev-check-update
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -96,21 +100,27 @@ jobs:
|
||||
if: ${{ env.continue == 'true' }}
|
||||
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.exe.sha256"
|
||||
wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.jar"
|
||||
wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.jar.sha1"
|
||||
wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.jar.sha256"
|
||||
wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.sh"
|
||||
wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.sh.sha1"
|
||||
wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.sh.sha256"
|
||||
env:
|
||||
DOWNLOAD_BASE_URL: https://ci.huangyuhui.net/job/HMCL-stable/lastSuccessfulBuild/artifact/HMCL/build/libs
|
||||
- name: Generate release note
|
||||
if: ${{ env.continue == 'true' }}
|
||||
run: |
|
||||
echo " === **Stable Version** === " >> RELEASE_NOTE
|
||||
echo "HMCL v$HMCL_VERSION" >> RELEASE_NOTE
|
||||
echo "**This version is a stable version.**" >> RELEASE_NOTE
|
||||
echo "" >> RELEASE_NOTE
|
||||
echo "The full changelogs can be found on the website: https://docs.hmcl.net/changelog/stable.html" >> RELEASE_NOTE
|
||||
echo "" >> RELEASE_NOTE
|
||||
echo "Notice: changelogs are written in Chinese." >> RELEASE_NOTE
|
||||
echo "" >> RELEASE_NOTE
|
||||
echo "| File Name | SHA-256 Checksum |" >> RELEASE_NOTE
|
||||
echo "| --- | --- |" >> RELEASE_NOTE
|
||||
echo "| HMCL-$HMCL_VERSION.exe | $(cat HMCL-$HMCL_VERSION.exe.sha256) |" >> RELEASE_NOTE
|
||||
echo "| HMCL-$HMCL_VERSION.jar | $(cat HMCL-$HMCL_VERSION.jar.sha256) |" >> RELEASE_NOTE
|
||||
echo "| HMCL-$HMCL_VERSION.sh | $(cat HMCL-$HMCL_VERSION.sh.sha256) |" >> RELEASE_NOTE
|
||||
- name: Create release
|
||||
if: ${{ env.continue == 'true' }}
|
||||
uses: softprops/action-gh-release@v1
|
||||
@@ -118,11 +128,8 @@ jobs:
|
||||
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
|
||||
HMCL-${{ env.HMCL_VERSION }}.sh
|
||||
HMCL-${{ env.HMCL_VERSION }}.sh.sha1
|
||||
target_commitish: ${{ env.HMCL_COMMIT_SHA }}
|
||||
name: ${{ env.HMCL_TAG_NAME }}
|
||||
tag_name: ${{ env.HMCL_TAG_NAME }}
|
||||
@@ -1,6 +1,11 @@
|
||||
name: Java CI
|
||||
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'HMCLauncher/**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -8,15 +13,15 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: '11'
|
||||
java-package: 'jdk+fx'
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew build
|
||||
run: ./gradlew build --no-daemon
|
||||
env:
|
||||
MICROSOFT_AUTH_ID: ${{ secrets.MICROSOFT_AUTH_ID }}
|
||||
MICROSOFT_AUTH_SECRET: ${{ secrets.MICROSOFT_AUTH_SECRET }}
|
||||
@@ -24,7 +29,7 @@ jobs:
|
||||
- name: Get short SHA
|
||||
run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: HMCL-${{ env.SHORT_SHA }}
|
||||
path: HMCL/build/libs
|
||||
@@ -1,6 +1,3 @@
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonObject
|
||||
import java.net.URI
|
||||
import java.nio.file.FileSystems
|
||||
import java.nio.file.Files
|
||||
@@ -10,16 +7,6 @@ import java.security.Signature
|
||||
import java.security.spec.PKCS8EncodedKeySpec
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath("com.google.code.gson:gson:2.10.1")
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id("com.github.johnrengelman.shadow") version "7.1.2"
|
||||
}
|
||||
@@ -94,59 +81,6 @@ fun attachSignature(jar: File) {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.getByName<JavaCompile>("compileJava") {
|
||||
dependsOn(tasks.create("computeDynamicResources") {
|
||||
this@create.inputs.file(rootProject.rootDir.toPath().resolve("data-json/dynamic-remote-resources-raw.json"))
|
||||
this@create.outputs.file(rootProject.rootDir.toPath().resolve("data-json/dynamic-remote-resources.json"))
|
||||
|
||||
doLast {
|
||||
Gson().also { gsonInstance ->
|
||||
rootProject.rootDir.resolve("data-json/dynamic-remote-resources-raw.json").bufferedReader().use { br ->
|
||||
(gsonInstance.fromJson(br, JsonElement::class.java) as JsonObject)
|
||||
}.also { data ->
|
||||
data.asMap().forEach { (namespace, namespaceData) ->
|
||||
(namespaceData as JsonObject).asMap().forEach { (name, nameData) ->
|
||||
(nameData as JsonObject).asMap().forEach { (version, versionData) ->
|
||||
require(versionData is JsonObject)
|
||||
val localPath =
|
||||
(versionData.get("local_path") as com.google.gson.JsonPrimitive).asString
|
||||
val sha1 = (versionData.get("sha1") as com.google.gson.JsonPrimitive).asString
|
||||
|
||||
val currentSha1 = digest(
|
||||
"SHA-1",
|
||||
rootProject.rootDir.resolve(localPath).readBytes()
|
||||
).joinToString(separator = "") { "%02x".format(it) }
|
||||
|
||||
if (!sha1.equals(currentSha1, ignoreCase = true)) {
|
||||
throw IllegalStateException("Mismatched SHA-1 in $.${namespace}.${name}.${version} of dynamic remote resources detected. Require ${currentSha1}, but found $sha1")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.rootDir.resolve("data-json/dynamic-remote-resources.json").also { zippedPath ->
|
||||
gsonInstance.toJson(data).also { expectedData ->
|
||||
if (zippedPath.exists()) {
|
||||
zippedPath.readText().also { rawData ->
|
||||
if (rawData != expectedData) {
|
||||
if (System.getenv("GITHUB_SHA") == null) {
|
||||
zippedPath.writeText(expectedData)
|
||||
} else {
|
||||
throw IllegalStateException("Mismatched zipped dynamic-remote-resources json file!")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
zippedPath.writeText(expectedData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
val java11 = sourceSets.create("java11") {
|
||||
java {
|
||||
srcDir("src/main/java11")
|
||||
|
||||
|
After Width: | Height: | Size: 31 KiB |
@@ -33,12 +33,10 @@ import org.jackhuang.hmcl.setting.SambaException;
|
||||
import org.jackhuang.hmcl.task.AsyncTaskExecutor;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.upgrade.hmcl.UpdateChecker;
|
||||
import org.jackhuang.hmcl.upgrade.hmcl.UpdateHandler;
|
||||
import org.jackhuang.hmcl.upgrade.resource.RemoteResourceManager;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateHandler;
|
||||
import org.jackhuang.hmcl.util.CrashReporter;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.JarUtils;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
@@ -123,10 +121,6 @@ public final class Launcher extends Application {
|
||||
|
||||
UpdateChecker.init();
|
||||
|
||||
RemoteResourceManager.init();
|
||||
|
||||
RemoteResourceManager.register();
|
||||
|
||||
primaryStage.show();
|
||||
});
|
||||
} catch (Throwable e) {
|
||||
@@ -290,11 +284,9 @@ public final class Launcher extends Application {
|
||||
.findAny()
|
||||
.map(bean -> bean.getUsage().getUsed() / 1024 / 1024 + "MB")
|
||||
.orElse("Unknown"));
|
||||
if (OperatingSystem.CURRENT_OS.isLinuxOrBSD())
|
||||
if (OperatingSystem.CURRENT_OS.isLinuxOrBSD()) {
|
||||
LOG.info("XDG Session Type: " + System.getenv("XDG_SESSION_TYPE"));
|
||||
|
||||
if (System.getProperty("hmcl.update_source.override") != null) {
|
||||
Logging.LOG.log(Level.WARNING, "'hmcl.update_source.override' is deprecated! Please use 'hmcl.hmcl_update_source.override' instead");
|
||||
LOG.info("XDG Current Desktop: " + System.getenv("XDG_CURRENT_DESKTOP"));
|
||||
}
|
||||
|
||||
launch(Launcher.class, args);
|
||||
|
||||
@@ -37,13 +37,11 @@ public final class Metadata {
|
||||
public static final String TITLE = NAME + " " + VERSION;
|
||||
public static final String FULL_TITLE = FULL_NAME + " v" + VERSION;
|
||||
|
||||
// hmcl.update_source.override is deprecated. If it is used, a warning message will be printed in org.jackhuang.hmcl.Launcher.main .
|
||||
public static final String HMCL_UPDATE_URL = System.getProperty("hmcl.hmcl_update_source.override", System.getProperty("hmcl.update_source.override", "https://hmcl.huangyuhui.net/api/update_link"));
|
||||
public static final String RESOURCE_UPDATE_URL = System.getProperty("hmcl.resource_update_source.override", "https://hmcl.huangyuhui.net/api/dynamic_remote_resource/update_link");
|
||||
public static final String HMCL_UPDATE_URL = System.getProperty("hmcl.update_source.override", "https://hmcl.huangyuhui.net/api/update_link");
|
||||
public static final String CONTACT_URL = "https://docs.hmcl.net/help.html";
|
||||
public static final String HELP_URL = "https://docs.hmcl.net";
|
||||
public static final String CHANGELOG_URL = "https://docs.hmcl.net/changelog/";
|
||||
public static final String PUBLISH_URL = "https://www.mcbbs.net/thread-142335-1-1.html";
|
||||
public static final String PUBLISH_URL = "https://hmcl.huangyuhui.net";
|
||||
public static final String EULA_URL = "https://docs.hmcl.net/eula/hmcl.html";
|
||||
|
||||
public static final String BUILD_CHANNEL = JarUtils.getManifestAttribute("Build-Channel", "nightly");
|
||||
|
||||
@@ -20,10 +20,9 @@ package org.jackhuang.hmcl.countly;
|
||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
@@ -40,7 +39,7 @@ public class Countly {
|
||||
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("tz", Integer.toString(getTimezoneOffset())),
|
||||
pair("hour", Integer.toString(currentHour())),
|
||||
pair("dow", Integer.toString(currentDayOfWeek())),
|
||||
pair("app_key", APP_KEY),
|
||||
@@ -50,7 +49,7 @@ public class Countly {
|
||||
}
|
||||
|
||||
private static int getTimezoneOffset() {
|
||||
return TimeZone.getDefault().getOffset(new Date().getTime()) / 60000;
|
||||
return ZonedDateTime.now().getOffset().getTotalSeconds() / 60;
|
||||
}
|
||||
|
||||
private static String getLocale() {
|
||||
|
||||
@@ -7,8 +7,8 @@ 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.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
@@ -68,7 +68,7 @@ public class CrashReport {
|
||||
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" +
|
||||
" Time: " + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()) + "\n" +
|
||||
" Thread: " + thread + "\n" +
|
||||
"\n Content: \n " +
|
||||
stackTrace + "\n\n" +
|
||||
|
||||
@@ -25,7 +25,7 @@ import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.platform.ManagedProcess;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -84,7 +84,7 @@ public final class HMCLGameLauncher extends DefaultLauncher {
|
||||
1.11 ~ 12:zh_cn 时正常,zh_CN 时虽然显示了中文但语言设置会错误地显示选择英文
|
||||
1.13+ :zh_cn 时正常,zh_CN 时自动切换为英文
|
||||
*/
|
||||
VersionNumber gameVersion = VersionNumber.asVersion(repository.getGameVersion(version).orElse("0.0"));
|
||||
GameVersionNumber gameVersion = GameVersionNumber.asGameVersion(repository.getGameVersion(version));
|
||||
if (gameVersion.compareTo("1.1") < 0) {
|
||||
lang = null;
|
||||
} else if (gameVersion.compareTo("1.11") < 0) {
|
||||
|
||||
@@ -48,6 +48,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -103,7 +104,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
public Stream<Version> getDisplayVersions() {
|
||||
return getVersions().stream()
|
||||
.filter(v -> !v.isHidden())
|
||||
.sorted(Comparator.comparing((Version v) -> v.getReleaseTime() == null ? new Date(0L) : v.getReleaseTime())
|
||||
.sorted(Comparator.comparing((Version v) -> Lang.requireNonNullElse(v.getReleaseTime(), Instant.EPOCH))
|
||||
.thenComparing(v -> VersionNumber.asVersion(v.getId())));
|
||||
}
|
||||
|
||||
@@ -281,7 +282,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
}
|
||||
|
||||
if (LibraryAnalyzer.isModded(this, version)) {
|
||||
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version);
|
||||
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version, null);
|
||||
if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.FABRIC))
|
||||
return VersionIconType.FABRIC.getIcon();
|
||||
else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.FORGE))
|
||||
|
||||
@@ -93,7 +93,7 @@ public final class HMCLModpackInstallTask extends Task<Void> {
|
||||
public void execute() throws Exception {
|
||||
String json = CompressingUtils.readTextZipEntry(zipFile, "minecraft/pack.json");
|
||||
Version originalVersion = JsonUtils.GSON.fromJson(json, Version.class).setId(name).setJar(null);
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(originalVersion);
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(originalVersion, null);
|
||||
Task<Version> libraryTask = Task.supplyAsync(() -> originalVersion);
|
||||
// reinstall libraries
|
||||
// libraries of Forge and OptiFine should be obtained by installation.
|
||||
|
||||
@@ -45,6 +45,7 @@ import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.io.ResponseCodeException;
|
||||
import org.jackhuang.hmcl.util.platform.*;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.io.File;
|
||||
@@ -332,7 +333,7 @@ public final class LauncherHelper {
|
||||
}
|
||||
|
||||
private static Task<JavaVersion> checkGameState(Profile profile, VersionSetting setting, Version version) {
|
||||
VersionNumber gameVersion = VersionNumber.asVersion(profile.getRepository().getGameVersion(version).orElse("Unknown"));
|
||||
GameVersionNumber gameVersion = GameVersionNumber.asGameVersion(profile.getRepository().getGameVersion(version));
|
||||
|
||||
if (setting.isNotCheckJVM()) {
|
||||
return Task.composeAsync(() -> setting.getJavaVersion(gameVersion, version))
|
||||
@@ -416,7 +417,7 @@ public final class LauncherHelper {
|
||||
JavaVersionConstraint violatedMandatoryConstraint = null;
|
||||
List<JavaVersionConstraint> violatedSuggestedConstraints = null;
|
||||
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version);
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version, gameVersion.toString());
|
||||
for (JavaVersionConstraint constraint : JavaVersionConstraint.ALL) {
|
||||
if (constraint.appliesToVersion(gameVersion, version, javaVersion, analyzer)) {
|
||||
if (!constraint.checkJava(gameVersion, version, javaVersion)) {
|
||||
@@ -549,14 +550,14 @@ public final class LauncherHelper {
|
||||
// Forge 2760~2773 will crash game with LiteLoader.
|
||||
boolean hasForge2760 = forgeVersion != null && (forgeVersion.compareTo("1.12.2-14.23.5.2760") >= 0) && (forgeVersion.compareTo("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) {
|
||||
if (hasForge2760 && hasLiteLoader && gameVersion.compareTo("1.12.2") == 0) {
|
||||
suggestions.add(i18n("launch.advice.forge2760_liteloader"));
|
||||
}
|
||||
|
||||
// OptiFine 1.14.4 is not compatible with Forge 28.2.2 and later versions.
|
||||
boolean hasForge28_2_2 = forgeVersion != null && (forgeVersion.compareTo("1.14.4-28.2.2") >= 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) {
|
||||
if (hasForge28_2_2 && hasOptiFine && gameVersion.compareTo("1.14.4") == 0) {
|
||||
suggestions.add(i18n("launch.advice.forge28_2_2_optifine"));
|
||||
}
|
||||
|
||||
@@ -834,7 +835,7 @@ public final class LauncherHelper {
|
||||
@Override
|
||||
public void onExit(int exitCode, ExitType exitType) {
|
||||
if (showLogs) {
|
||||
Platform.runLater(() -> logWindow.logLine(String.format("[HMCL ProcessListener] Minecraft exit with code %d(0x%x).", exitCode, exitCode), Log4jLevel.INFO));
|
||||
Platform.runLater(() -> logWindow.logLine(String.format("[HMCL ProcessListener] Minecraft exit with code %d(0x%x), type is %s.", exitCode, exitCode, exitType), Log4jLevel.INFO));
|
||||
}
|
||||
|
||||
launchingLatch.countDown();
|
||||
|
||||
@@ -94,8 +94,12 @@ public final class LogExporter {
|
||||
if (Files.isRegularFile(file)) {
|
||||
FileTime time = Files.readAttributes(file, BasicFileAttributes.class).lastModifiedTime();
|
||||
if (time.toMillis() >= processStartTime) {
|
||||
String crashLog = Logging.filterForbiddenToken(FileUtils.readText(file, OperatingSystem.NATIVE_CHARSET));
|
||||
zipper.putTextFile(crashLog, file.getFileName().toString());
|
||||
try {
|
||||
String crashLog = Logging.filterForbiddenToken(FileUtils.readText(file, OperatingSystem.NATIVE_CHARSET));
|
||||
zipper.putTextFile(crashLog, file.getFileName().toString());
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to read log file: " + file, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,10 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.WeakInvalidationListener;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.scene.canvas.Canvas;
|
||||
@@ -29,9 +32,13 @@ import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.ServerResponseMalformedException;
|
||||
import org.jackhuang.hmcl.auth.microsoft.MicrosoftAccount;
|
||||
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
|
||||
import org.jackhuang.hmcl.auth.offline.Skin;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.*;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.util.ResourceNotFoundError;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.util.Holder;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
|
||||
@@ -141,16 +148,7 @@ public final class TexturesLoader {
|
||||
}
|
||||
|
||||
private static void loadDefaultSkin(String path, TextureModel model) {
|
||||
Image skin;
|
||||
try {
|
||||
skin = new Image(path);
|
||||
if (skin.isError())
|
||||
throw skin.getException();
|
||||
} catch (Throwable e) {
|
||||
throw new ResourceNotFoundError("Cannot load default skin from " + path, e);
|
||||
}
|
||||
|
||||
DEFAULT_SKINS.put(model, new LoadedTexture(skin, singletonMap("model", model.modelName)));
|
||||
DEFAULT_SKINS.put(model, new LoadedTexture(FXUtils.newBuiltinImage(path), singletonMap("model", model.modelName)));
|
||||
}
|
||||
|
||||
public static LoadedTexture getDefaultSkin(TextureModel model) {
|
||||
@@ -188,27 +186,61 @@ public final class TexturesLoader {
|
||||
}, uuidFallback);
|
||||
}
|
||||
|
||||
public static ObjectBinding<LoadedTexture> skinBinding(Account account) {
|
||||
public static ObservableValue<LoadedTexture> skinBinding(Account account) {
|
||||
LoadedTexture uuidFallback = getDefaultSkin(TextureModel.detectUUID(account.getUUID()));
|
||||
return BindingMapping.of(account.getTextures())
|
||||
.map(textures -> textures
|
||||
.flatMap(it -> Optional.ofNullable(it.get(TextureType.SKIN)))
|
||||
.filter(it -> StringUtils.isNotBlank(it.getUrl())))
|
||||
.asyncMap(it -> {
|
||||
if (it.isPresent()) {
|
||||
Texture texture = it.get();
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
return loadTexture(texture);
|
||||
} catch (Throwable e) {
|
||||
LOG.log(Level.WARNING, "Failed to load texture " + texture.getUrl() + ", using fallback texture", e);
|
||||
return uuidFallback;
|
||||
if (account instanceof OfflineAccount) {
|
||||
OfflineAccount offlineAccount = (OfflineAccount) account;
|
||||
|
||||
SimpleObjectProperty<LoadedTexture> binding = new SimpleObjectProperty<>();
|
||||
InvalidationListener listener = o -> {
|
||||
Skin skin = offlineAccount.getSkin();
|
||||
String username = offlineAccount.getUsername();
|
||||
|
||||
binding.set(uuidFallback);
|
||||
if (skin != null) {
|
||||
skin.load(username).setExecutor(POOL).whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
if (exception != null) {
|
||||
LOG.log(Level.WARNING, "Failed to load texture", exception);
|
||||
} else if (result != null && result.getSkin() != null && result.getSkin().getImage() != null) {
|
||||
Map<String, String> metadata;
|
||||
if (result.getModel() != null) {
|
||||
metadata = singletonMap("model", result.getModel().modelName);
|
||||
} else {
|
||||
metadata = emptyMap();
|
||||
}
|
||||
}, POOL);
|
||||
} else {
|
||||
|
||||
binding.set(new LoadedTexture(result.getSkin().getImage(), metadata));
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
};
|
||||
|
||||
listener.invalidated(offlineAccount);
|
||||
|
||||
binding.addListener(new Holder<>(listener));
|
||||
offlineAccount.addListener(new WeakInvalidationListener(listener));
|
||||
|
||||
return binding;
|
||||
} else {
|
||||
return BindingMapping.of(account.getTextures())
|
||||
.asyncMap(textures -> {
|
||||
if (textures.isPresent()) {
|
||||
Texture texture = textures.get().get(TextureType.SKIN);
|
||||
if (texture != null && StringUtils.isNotBlank(texture.getUrl())) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
return loadTexture(texture);
|
||||
} catch (Throwable e) {
|
||||
LOG.log(Level.WARNING, "Failed to load texture " + texture.getUrl() + ", using fallback texture", e);
|
||||
return uuidFallback;
|
||||
}
|
||||
}, POOL);
|
||||
}
|
||||
}
|
||||
|
||||
return CompletableFuture.completedFuture(uuidFallback);
|
||||
}
|
||||
}, uuidFallback);
|
||||
}, uuidFallback);
|
||||
}
|
||||
}
|
||||
|
||||
// ====
|
||||
@@ -277,9 +309,9 @@ public final class TexturesLoader {
|
||||
static final WeakHashMap<Canvas, SkinBindingChangeListener> hole = new WeakHashMap<>();
|
||||
|
||||
final WeakReference<Canvas> canvasRef;
|
||||
final ObjectBinding<LoadedTexture> binding;
|
||||
final ObservableValue<LoadedTexture> binding;
|
||||
|
||||
SkinBindingChangeListener(Canvas canvas, ObjectBinding<LoadedTexture> binding) {
|
||||
SkinBindingChangeListener(Canvas canvas, ObservableValue<LoadedTexture> binding) {
|
||||
this.canvasRef = new WeakReference<>(canvas);
|
||||
this.binding = binding;
|
||||
}
|
||||
@@ -293,14 +325,14 @@ public final class TexturesLoader {
|
||||
}
|
||||
}
|
||||
|
||||
public static void fxAvatarBinding(Canvas canvas, ObjectBinding<LoadedTexture> skinBinding) {
|
||||
public static void fxAvatarBinding(Canvas canvas, ObservableValue<LoadedTexture> skinBinding) {
|
||||
synchronized (SkinBindingChangeListener.hole) {
|
||||
SkinBindingChangeListener oldListener = SkinBindingChangeListener.hole.remove(canvas);
|
||||
if (oldListener != null)
|
||||
oldListener.binding.removeListener(oldListener);
|
||||
|
||||
SkinBindingChangeListener listener = new SkinBindingChangeListener(canvas, skinBinding);
|
||||
listener.changed(skinBinding, null, skinBinding.get());
|
||||
listener.changed(skinBinding, null, skinBinding.getValue());
|
||||
skinBinding.addListener(listener);
|
||||
|
||||
SkinBindingChangeListener.hole.put(canvas, listener);
|
||||
@@ -312,7 +344,7 @@ public final class TexturesLoader {
|
||||
}
|
||||
|
||||
public static void bindAvatar(Canvas canvas, Account account) {
|
||||
if (account instanceof YggdrasilAccount || account instanceof MicrosoftAccount)
|
||||
if (account instanceof YggdrasilAccount || account instanceof MicrosoftAccount || account instanceof OfflineAccount)
|
||||
fxAvatarBinding(canvas, skinBinding(account));
|
||||
else {
|
||||
unbindAvatar(canvas);
|
||||
|
||||
@@ -33,8 +33,6 @@ import org.jackhuang.hmcl.auth.microsoft.MicrosoftService;
|
||||
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
|
||||
import org.jackhuang.hmcl.auth.offline.OfflineAccountFactory;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.RemoteAuthenticationException;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory;
|
||||
import org.jackhuang.hmcl.game.OAuthServer;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.util.InvocationDispatcher;
|
||||
@@ -83,17 +81,15 @@ public final class Accounts {
|
||||
public static final OAuthServer.Factory OAUTH_CALLBACK = new OAuthServer.Factory();
|
||||
|
||||
public static final OfflineAccountFactory FACTORY_OFFLINE = new OfflineAccountFactory(AUTHLIB_INJECTOR_DOWNLOADER);
|
||||
public static final YggdrasilAccountFactory FACTORY_MOJANG = YggdrasilAccountFactory.MOJANG;
|
||||
public static final AuthlibInjectorAccountFactory FACTORY_AUTHLIB_INJECTOR = new AuthlibInjectorAccountFactory(AUTHLIB_INJECTOR_DOWNLOADER, Accounts::getOrCreateAuthlibInjectorServer);
|
||||
public static final MicrosoftAccountFactory FACTORY_MICROSOFT = new MicrosoftAccountFactory(new MicrosoftService(OAUTH_CALLBACK));
|
||||
public static final List<AccountFactory<?>> FACTORIES = immutableListOf(FACTORY_OFFLINE, FACTORY_MOJANG, FACTORY_MICROSOFT, FACTORY_AUTHLIB_INJECTOR);
|
||||
public static final List<AccountFactory<?>> FACTORIES = immutableListOf(FACTORY_OFFLINE, FACTORY_MICROSOFT, FACTORY_AUTHLIB_INJECTOR);
|
||||
|
||||
// ==== login type / account factory mapping ====
|
||||
private static final Map<String, AccountFactory<?>> type2factory = new HashMap<>();
|
||||
private static final Map<AccountFactory<?>, String> factory2type = new HashMap<>();
|
||||
static {
|
||||
type2factory.put("offline", FACTORY_OFFLINE);
|
||||
type2factory.put("yggdrasil", FACTORY_MOJANG);
|
||||
type2factory.put("authlibInjector", FACTORY_AUTHLIB_INJECTOR);
|
||||
type2factory.put("microsoft", FACTORY_MICROSOFT);
|
||||
|
||||
@@ -126,8 +122,6 @@ public final class Accounts {
|
||||
return FACTORY_OFFLINE;
|
||||
else if (account instanceof AuthlibInjectorAccount)
|
||||
return FACTORY_AUTHLIB_INJECTOR;
|
||||
else if (account instanceof YggdrasilAccount)
|
||||
return FACTORY_MOJANG;
|
||||
else if (account instanceof MicrosoftAccount)
|
||||
return FACTORY_MICROSOFT;
|
||||
else
|
||||
@@ -414,7 +408,6 @@ public final class Accounts {
|
||||
// ==== Login type name i18n ===
|
||||
private static final Map<AccountFactory<?>, String> unlocalizedLoginTypeNames = mapOf(
|
||||
pair(Accounts.FACTORY_OFFLINE, "account.methods.offline"),
|
||||
pair(Accounts.FACTORY_MOJANG, "account.methods.yggdrasil"),
|
||||
pair(Accounts.FACTORY_AUTHLIB_INJECTOR, "account.methods.authlib_injector"),
|
||||
pair(Accounts.FACTORY_MICROSOFT, "account.methods.microsoft"));
|
||||
|
||||
|
||||
@@ -131,13 +131,13 @@ public final class Config implements Cloneable, Observable {
|
||||
private ObjectProperty<SupportedLocale> localization = new SimpleObjectProperty<>(Locales.DEFAULT);
|
||||
|
||||
@SerializedName("autoDownloadThreads")
|
||||
private BooleanProperty autoDownloadThreads = new SimpleBooleanProperty(false);
|
||||
private BooleanProperty autoDownloadThreads = new SimpleBooleanProperty(true);
|
||||
|
||||
@SerializedName("downloadThreads")
|
||||
private IntegerProperty downloadThreads = new SimpleIntegerProperty(64);
|
||||
|
||||
@SerializedName("downloadType")
|
||||
private StringProperty downloadType = new SimpleStringProperty("mcbbs");
|
||||
private StringProperty downloadType = new SimpleStringProperty(DownloadProviders.DEFAULT_RAW_PROVIDER_ID);
|
||||
|
||||
@SerializedName("autoChooseDownloadType")
|
||||
private BooleanProperty autoChooseDownloadType = new SimpleBooleanProperty(true);
|
||||
@@ -393,8 +393,8 @@ public final class Config implements Cloneable, Observable {
|
||||
return x;
|
||||
}
|
||||
|
||||
public void setX(double height) {
|
||||
this.x.set(height);
|
||||
public void setX(double x) {
|
||||
this.x.set(x);
|
||||
}
|
||||
|
||||
public double getY() {
|
||||
@@ -405,8 +405,8 @@ public final class Config implements Cloneable, Observable {
|
||||
return y;
|
||||
}
|
||||
|
||||
public void setY(double height) {
|
||||
this.y.set(height);
|
||||
public void setY(double y) {
|
||||
this.y.set(y);
|
||||
}
|
||||
|
||||
public double getWidth() {
|
||||
|
||||
@@ -21,12 +21,14 @@ import com.google.gson.JsonParseException;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.util.InvocationDispatcher;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.JarUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.*;
|
||||
import java.util.Locale;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
@@ -87,6 +89,8 @@ public final class ConfigHolder {
|
||||
globalConfigInstance = loadGlobalConfig();
|
||||
globalConfigInstance.addListener(source -> markGlobalConfigDirty());
|
||||
|
||||
Locale.setDefault(config().getLocalization().getLocale());
|
||||
I18n.setLocale(configInstance.getLocalization());
|
||||
Settings.init();
|
||||
|
||||
if (newlyCreated) {
|
||||
|
||||
@@ -18,16 +18,11 @@
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
@@ -46,94 +41,66 @@ final class ConfigUpgrader {
|
||||
* @param rawContent raw json content of the config settings without modification
|
||||
*/
|
||||
static void upgradeConfig(Config deserialized, String rawContent) {
|
||||
if (deserialized.getConfigVersion() == CURRENT_VERSION) {
|
||||
int configVersion = deserialized.getConfigVersion();
|
||||
|
||||
if (configVersion == CURRENT_VERSION) {
|
||||
return;
|
||||
}
|
||||
|
||||
int configVersion = deserialized.getConfigVersion();
|
||||
if (configVersion > CURRENT_VERSION) {
|
||||
LOG.log(Level.WARNING, String.format("Current HMCL only support the configuration version up to %d. However, the version now is %d.", CURRENT_VERSION, configVersion));
|
||||
deserialized.setConfigVersion(CURRENT_VERSION);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.log(Level.INFO, String.format("Updating configuration from %d to %d.", configVersion, CURRENT_VERSION));
|
||||
Map<?, ?> unmodifiableRawJson = Collections.unmodifiableMap(new Gson().<Map<?, ?>>fromJson(rawContent, Map.class));
|
||||
for (Map.Entry<Integer, BiConsumer<Config, Map<?, ?>>> dfu : collectDFU()) {
|
||||
if (configVersion < dfu.getKey()) {
|
||||
dfu.getValue().accept(deserialized, unmodifiableRawJson);
|
||||
configVersion = dfu.getKey();
|
||||
Map<?, ?> rawJson = Collections.unmodifiableMap(new Gson().<Map<?, ?>>fromJson(rawContent, Map.class));
|
||||
|
||||
if (configVersion < 1) {
|
||||
// Upgrade configuration of HMCL 2.x: Convert OfflineAccounts whose stored uuid is important.
|
||||
tryCast(rawJson.get("auth"), Map.class).ifPresent(auth -> {
|
||||
tryCast(auth.get("offline"), Map.class).ifPresent(offline -> {
|
||||
String selected = rawJson.containsKey("selectedAccount") ? null
|
||||
: tryCast(offline.get("IAuthenticator_UserName"), String.class).orElse(null);
|
||||
|
||||
tryCast(offline.get("uuidMap"), Map.class).ifPresent(uuidMap -> {
|
||||
((Map<?, ?>) uuidMap).forEach((key, value) -> {
|
||||
Map<Object, Object> storage = new HashMap<>();
|
||||
storage.put("type", "offline");
|
||||
storage.put("username", key);
|
||||
storage.put("uuid", value);
|
||||
if (key.equals(selected)) {
|
||||
storage.put("selected", true);
|
||||
}
|
||||
deserialized.getAccountStorages().add(storage);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Upgrade configuration of HMCL earlier than 3.1.70
|
||||
if (!rawJson.containsKey("commonDirType"))
|
||||
deserialized.setCommonDirType(deserialized.getCommonDirectory().equals(Settings.getDefaultCommonDirectory()) ? EnumCommonDirectory.DEFAULT : EnumCommonDirectory.CUSTOM);
|
||||
if (!rawJson.containsKey("backgroundType"))
|
||||
deserialized.setBackgroundImageType(StringUtils.isNotBlank(deserialized.getBackgroundImage()) ? EnumBackgroundImage.CUSTOM : EnumBackgroundImage.DEFAULT);
|
||||
if (!rawJson.containsKey("hasProxy"))
|
||||
deserialized.setHasProxy(StringUtils.isNotBlank(deserialized.getProxyHost()));
|
||||
if (!rawJson.containsKey("hasProxyAuth"))
|
||||
deserialized.setHasProxyAuth(StringUtils.isNotBlank(deserialized.getProxyUser()));
|
||||
|
||||
if (!rawJson.containsKey("downloadType")) {
|
||||
tryCast(rawJson.get("downloadtype"), Number.class)
|
||||
.map(Number::intValue)
|
||||
.ifPresent(id -> {
|
||||
if (id == 0) {
|
||||
deserialized.setDownloadType("mojang");
|
||||
} else if (id == 1) {
|
||||
deserialized.setDownloadType("bmclapi");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
deserialized.setConfigVersion(CURRENT_VERSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Initialize the dfu of HMCL. Feel free to use lambda as all the lambda here would not be initialized unless HMCL needs to update the configuration.
|
||||
* For each item in this list, it should be a Map.Entry.</p>
|
||||
*
|
||||
* <p>The key should be a version number. All the configuration with a version number which is less than the specific one will be applied to this upgrader.</p>
|
||||
* <p>The value should be the upgrader. The configuration which is waited to being processed, and the raw unmodifiable value of the json from the configuration file.</p>
|
||||
* <p>The return value should a target version number of this item.</p>
|
||||
*
|
||||
* <p>The last item must return CURRENT_VERSION, as the config file should always being updated to the latest version.</p>
|
||||
*/
|
||||
private static List<Map.Entry<Integer, BiConsumer<Config, Map<?, ?>>>> collectDFU() {
|
||||
List<Map.Entry<Integer, BiConsumer<Config, Map<?, ?>>>> dfu = Lang.immutableListOf(
|
||||
Pair.pair(1, (deserialized, rawJson) -> {
|
||||
// Upgrade configuration of HMCL 2.x: Convert OfflineAccounts whose stored uuid is important.
|
||||
tryCast(rawJson.get("auth"), Map.class).ifPresent(auth -> {
|
||||
tryCast(auth.get("offline"), Map.class).ifPresent(offline -> {
|
||||
String selected = rawJson.containsKey("selectedAccount") ? null
|
||||
: tryCast(offline.get("IAuthenticator_UserName"), String.class).orElse(null);
|
||||
|
||||
tryCast(offline.get("uuidMap"), Map.class).ifPresent(uuidMap -> {
|
||||
((Map<?, ?>) uuidMap).forEach((key, value) -> {
|
||||
Map<Object, Object> storage = new HashMap<>();
|
||||
storage.put("type", "offline");
|
||||
storage.put("username", key);
|
||||
storage.put("uuid", value);
|
||||
if (key.equals(selected)) {
|
||||
storage.put("selected", true);
|
||||
}
|
||||
deserialized.getAccountStorages().add(storage);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Upgrade configuration of HMCL earlier than 3.1.70
|
||||
if (!rawJson.containsKey("commonDirType"))
|
||||
deserialized.setCommonDirType(deserialized.getCommonDirectory().equals(Settings.getDefaultCommonDirectory()) ? EnumCommonDirectory.DEFAULT : EnumCommonDirectory.CUSTOM);
|
||||
if (!rawJson.containsKey("backgroundType"))
|
||||
deserialized.setBackgroundImageType(StringUtils.isNotBlank(deserialized.getBackgroundImage()) ? EnumBackgroundImage.CUSTOM : EnumBackgroundImage.DEFAULT);
|
||||
if (!rawJson.containsKey("hasProxy"))
|
||||
deserialized.setHasProxy(StringUtils.isNotBlank(deserialized.getProxyHost()));
|
||||
if (!rawJson.containsKey("hasProxyAuth"))
|
||||
deserialized.setHasProxyAuth(StringUtils.isNotBlank(deserialized.getProxyUser()));
|
||||
|
||||
if (!rawJson.containsKey("downloadType")) {
|
||||
tryCast(rawJson.get("downloadtype"), Number.class)
|
||||
.map(Number::intValue)
|
||||
.ifPresent(id -> {
|
||||
if (id == 0) {
|
||||
deserialized.setDownloadType("mojang");
|
||||
} else if (id == 1) {
|
||||
deserialized.setDownloadType("bmclapi");
|
||||
}
|
||||
});
|
||||
}
|
||||
}),
|
||||
Pair.pair(2, (deserialized, rawJson) -> {
|
||||
deserialized.setX(0.5D - deserialized.getWidth() / Controllers.SCREEN.getBounds().getWidth() / 2);
|
||||
deserialized.setY(0.5D - deserialized.getHeight() / Controllers.SCREEN.getBounds().getHeight() / 2);
|
||||
})
|
||||
);
|
||||
|
||||
if (dfu.get(dfu.size() - 1).getKey() != CURRENT_VERSION) {
|
||||
throw new IllegalStateException("The last dfu must adapt all the config version below CURRENT_VERSION");
|
||||
}
|
||||
|
||||
return dfu;
|
||||
}
|
||||
}
|
||||
@@ -55,10 +55,9 @@ public final class DownloadProviders {
|
||||
|
||||
private static final MojangDownloadProvider MOJANG;
|
||||
private static final BMCLAPIDownloadProvider BMCLAPI;
|
||||
private static final BMCLAPIDownloadProvider MCBBS;
|
||||
|
||||
public static final String DEFAULT_PROVIDER_ID = "balanced";
|
||||
public static final String DEFAULT_RAW_PROVIDER_ID = "mcbbs";
|
||||
public static final String DEFAULT_RAW_PROVIDER_ID = "bmclapi";
|
||||
|
||||
private static final InvalidationListener observer;
|
||||
|
||||
@@ -69,21 +68,19 @@ public final class DownloadProviders {
|
||||
|
||||
MOJANG = new MojangDownloadProvider();
|
||||
BMCLAPI = new BMCLAPIDownloadProvider(bmclapiRoot);
|
||||
MCBBS = new BMCLAPIDownloadProvider("https://download.mcbbs.net");
|
||||
rawProviders = mapOf(
|
||||
pair("mojang", MOJANG),
|
||||
pair("bmclapi", BMCLAPI),
|
||||
pair("mcbbs", MCBBS)
|
||||
pair("bmclapi", BMCLAPI)
|
||||
);
|
||||
|
||||
AdaptedDownloadProvider fileProvider = new AdaptedDownloadProvider();
|
||||
fileProvider.setDownloadProviderCandidates(Arrays.asList(MCBBS, BMCLAPI, MOJANG));
|
||||
BalancedDownloadProvider balanced = new BalancedDownloadProvider(MOJANG, MCBBS, BMCLAPI);
|
||||
fileProvider.setDownloadProviderCandidates(Arrays.asList(BMCLAPI, MOJANG));
|
||||
BalancedDownloadProvider balanced = new BalancedDownloadProvider(MOJANG, BMCLAPI);
|
||||
|
||||
providersById = mapOf(
|
||||
pair("official", new AutoDownloadProvider(MOJANG, fileProvider)),
|
||||
pair("balanced", new AutoDownloadProvider(balanced, fileProvider)),
|
||||
pair("mirror", new AutoDownloadProvider(MCBBS, fileProvider)));
|
||||
pair("mirror", new AutoDownloadProvider(BMCLAPI, fileProvider)));
|
||||
|
||||
observer = FXUtils.observeWeak(() -> {
|
||||
FetchTask.setDownloadExecutorConcurrency(
|
||||
@@ -102,12 +99,11 @@ public final class DownloadProviders {
|
||||
.orElse(providersById.get(DEFAULT_PROVIDER_ID));
|
||||
});
|
||||
|
||||
FXUtils.onChangeAndOperate(config().downloadTypeProperty(), downloadType -> {
|
||||
if (!rawProviders.containsKey(downloadType)) {
|
||||
config().setDownloadType(DEFAULT_RAW_PROVIDER_ID);
|
||||
return;
|
||||
}
|
||||
if (!rawProviders.containsKey(config().getDownloadType())) {
|
||||
config().setDownloadType(DEFAULT_RAW_PROVIDER_ID);
|
||||
}
|
||||
|
||||
FXUtils.onChangeAndOperate(config().downloadTypeProperty(), downloadType -> {
|
||||
DownloadProvider primary = Optional.ofNullable(rawProviders.get(downloadType))
|
||||
.orElse(rawProviders.get(DEFAULT_RAW_PROVIDER_ID));
|
||||
fileDownloadProvider.setDownloadProviderCandidates(
|
||||
|
||||
@@ -21,9 +21,7 @@ import com.google.gson.*;
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import org.jackhuang.hmcl.util.javafx.ObservableHelper;
|
||||
import org.jackhuang.hmcl.util.javafx.PropertyUtils;
|
||||
@@ -33,7 +31,7 @@ import java.lang.reflect.Type;
|
||||
import java.util.*;
|
||||
|
||||
@JsonAdapter(GlobalConfig.Serializer.class)
|
||||
public class GlobalConfig implements Cloneable, Observable {
|
||||
public final class GlobalConfig implements Cloneable, Observable {
|
||||
|
||||
@Nullable
|
||||
public static GlobalConfig fromJson(String json) throws JsonParseException {
|
||||
@@ -47,17 +45,13 @@ public class GlobalConfig implements Cloneable, Observable {
|
||||
return instance;
|
||||
}
|
||||
|
||||
private IntegerProperty agreementVersion = new SimpleIntegerProperty();
|
||||
private final IntegerProperty agreementVersion = new SimpleIntegerProperty();
|
||||
|
||||
private IntegerProperty platformPromptVersion = new SimpleIntegerProperty();
|
||||
|
||||
private BooleanProperty multiplayerRelay = new SimpleBooleanProperty();
|
||||
|
||||
private IntegerProperty multiplayerAgreementVersion = new SimpleIntegerProperty(0);
|
||||
private final IntegerProperty platformPromptVersion = new SimpleIntegerProperty();
|
||||
|
||||
private final Map<String, Object> unknownFields = new HashMap<>();
|
||||
|
||||
private transient ObservableHelper helper = new ObservableHelper(this);
|
||||
private final transient ObservableHelper helper = new ObservableHelper(this);
|
||||
|
||||
public GlobalConfig() {
|
||||
PropertyUtils.attachListener(this, helper);
|
||||
@@ -106,37 +100,10 @@ public class GlobalConfig implements Cloneable, Observable {
|
||||
this.platformPromptVersion.set(platformPromptVersion);
|
||||
}
|
||||
|
||||
public boolean isMultiplayerRelay() {
|
||||
return multiplayerRelay.get();
|
||||
}
|
||||
|
||||
public BooleanProperty multiplayerRelayProperty() {
|
||||
return multiplayerRelay;
|
||||
}
|
||||
|
||||
public void setMultiplayerRelay(boolean multiplayerRelay) {
|
||||
this.multiplayerRelay.set(multiplayerRelay);
|
||||
}
|
||||
|
||||
public int getMultiplayerAgreementVersion() {
|
||||
return multiplayerAgreementVersion.get();
|
||||
}
|
||||
|
||||
public IntegerProperty multiplayerAgreementVersionProperty() {
|
||||
return multiplayerAgreementVersion;
|
||||
}
|
||||
|
||||
public void setMultiplayerAgreementVersion(int multiplayerAgreementVersion) {
|
||||
this.multiplayerAgreementVersion.set(multiplayerAgreementVersion);
|
||||
}
|
||||
|
||||
public static class Serializer implements JsonSerializer<GlobalConfig>, JsonDeserializer<GlobalConfig> {
|
||||
public static final class Serializer implements JsonSerializer<GlobalConfig>, JsonDeserializer<GlobalConfig> {
|
||||
private static final Set<String> knownFields = new HashSet<>(Arrays.asList(
|
||||
"agreementVersion",
|
||||
"platformPromptVersion",
|
||||
"multiplayerToken",
|
||||
"multiplayerRelay",
|
||||
"multiplayerAgreementVersion"
|
||||
"platformPromptVersion"
|
||||
));
|
||||
|
||||
@Override
|
||||
@@ -148,8 +115,6 @@ public class GlobalConfig implements Cloneable, Observable {
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
jsonObject.add("agreementVersion", context.serialize(src.getAgreementVersion()));
|
||||
jsonObject.add("platformPromptVersion", context.serialize(src.getPlatformPromptVersion()));
|
||||
jsonObject.add("multiplayerRelay", context.serialize(src.isMultiplayerRelay()));
|
||||
jsonObject.add("multiplayerAgreementVersion", context.serialize(src.getMultiplayerAgreementVersion()));
|
||||
for (Map.Entry<String, Object> entry : src.unknownFields.entrySet()) {
|
||||
jsonObject.add(entry.getKey(), context.serialize(entry.getValue()));
|
||||
}
|
||||
@@ -166,8 +131,6 @@ public class GlobalConfig implements Cloneable, Observable {
|
||||
GlobalConfig config = new GlobalConfig();
|
||||
config.setAgreementVersion(Optional.ofNullable(obj.get("agreementVersion")).map(JsonElement::getAsInt).orElse(0));
|
||||
config.setPlatformPromptVersion(Optional.ofNullable(obj.get("platformPromptVersion")).map(JsonElement::getAsInt).orElse(0));
|
||||
config.setMultiplayerRelay(Optional.ofNullable(obj.get("multiplayerRelay")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
config.setMultiplayerAgreementVersion(Optional.ofNullable(obj.get("multiplayerAgreementVersion")).map(JsonElement::getAsInt).orElse(0));
|
||||
|
||||
for (Map.Entry<String, JsonElement> entry : obj.entrySet()) {
|
||||
if (!knownFields.contains(entry.getKey())) {
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import org.jackhuang.hmcl.auth.OAuth;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
|
||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
public final class HMCLAccounts {
|
||||
|
||||
private static final ObjectProperty<HMCLAccount> account = new SimpleObjectProperty<>();
|
||||
|
||||
private HMCLAccounts() {
|
||||
}
|
||||
|
||||
public static HMCLAccount getAccount() {
|
||||
return account.get();
|
||||
}
|
||||
|
||||
public static ObjectProperty<HMCLAccount> accountProperty() {
|
||||
return account;
|
||||
}
|
||||
|
||||
public static void setAccount(HMCLAccount account) {
|
||||
HMCLAccounts.account.set(account);
|
||||
}
|
||||
|
||||
public static Task<Void> login() {
|
||||
String nonce = UUIDTypeAdapter.fromUUID(UUID.randomUUID());
|
||||
String scope = "openid offline_access";
|
||||
|
||||
return Task.supplyAsync(() -> {
|
||||
OAuth.Session session = Accounts.OAUTH_CALLBACK.startServer();
|
||||
Accounts.OAUTH_CALLBACK.openBrowser(NetworkUtils.withQuery(
|
||||
"https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
|
||||
mapOf(
|
||||
pair("client_id", Accounts.OAUTH_CALLBACK.getClientId()),
|
||||
pair("response_type", "id_token code"),
|
||||
pair("response_mode", "form_post"),
|
||||
pair("scope", scope),
|
||||
pair("redirect_uri", session.getRedirectURI()),
|
||||
pair("nonce", nonce)
|
||||
)));
|
||||
String code = session.waitFor();
|
||||
|
||||
// Authorization Code -> Token
|
||||
String responseText = HttpRequest.POST("https://login.microsoftonline.com/common/oauth2/v2.0/token")
|
||||
.form(mapOf(pair("client_id", Accounts.OAUTH_CALLBACK.getClientId()), pair("code", code),
|
||||
pair("grant_type", "authorization_code"), pair("client_secret", Accounts.OAUTH_CALLBACK.getClientSecret()),
|
||||
pair("redirect_uri", session.getRedirectURI()), pair("scope", scope)))
|
||||
.getString();
|
||||
OAuth.AuthorizationResponse response = JsonUtils.fromNonNullJson(responseText,
|
||||
OAuth.AuthorizationResponse.class);
|
||||
|
||||
HMCLAccountProfile profile = HttpRequest.GET("https://hmcl.huangyuhui.net/api/user")
|
||||
.header("Token-Type", response.tokenType)
|
||||
.header("Access-Token", response.accessToken)
|
||||
.header("Authorization-Provider", "microsoft")
|
||||
.authorization("Bearer", session.getIdToken())
|
||||
.getJson(HMCLAccountProfile.class);
|
||||
|
||||
return new HMCLAccount("microsoft", profile.nickname, profile.email, profile.role, session.getIdToken(), response.tokenType, response.accessToken, response.refreshToken);
|
||||
}).thenAcceptAsync(Schedulers.javafx(), account -> {
|
||||
setAccount(account);
|
||||
});
|
||||
}
|
||||
|
||||
public static class HMCLAccount implements HttpRequest.Authorization {
|
||||
private final String provider;
|
||||
private final String nickname;
|
||||
private final String email;
|
||||
private final String role;
|
||||
private final String idToken;
|
||||
private final String tokenType;
|
||||
private final String accessToken;
|
||||
private final String refreshToken;
|
||||
|
||||
public HMCLAccount(String provider, String nickname, String email, String role, String idToken, String tokenType, String accessToken, String refreshToken) {
|
||||
this.provider = provider;
|
||||
this.nickname = nickname;
|
||||
this.email = email;
|
||||
this.role = role;
|
||||
this.idToken = idToken;
|
||||
this.tokenType = tokenType;
|
||||
this.accessToken = accessToken;
|
||||
this.refreshToken = refreshToken;
|
||||
}
|
||||
|
||||
public String getProvider() {
|
||||
return provider;
|
||||
}
|
||||
|
||||
public String getNickname() {
|
||||
return nickname;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public String getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public String getIdToken() {
|
||||
return idToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTokenType() {
|
||||
return tokenType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public String getRefreshToken() {
|
||||
return refreshToken;
|
||||
}
|
||||
}
|
||||
|
||||
private static class HMCLAccountProfile {
|
||||
@SerializedName("ID")
|
||||
String id;
|
||||
@SerializedName("Provider")
|
||||
String provider;
|
||||
@SerializedName("Email")
|
||||
String email;
|
||||
@SerializedName("NickName")
|
||||
String nickname;
|
||||
@SerializedName("Role")
|
||||
String role;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,8 +24,6 @@ import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
import org.jackhuang.hmcl.util.CacheRepository;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
public final class Settings {
|
||||
@@ -47,9 +45,6 @@ public final class Settings {
|
||||
}
|
||||
|
||||
private Settings() {
|
||||
config().localizationProperty().addListener(unused -> updateSystemLocale());
|
||||
updateSystemLocale();
|
||||
|
||||
DownloadProviders.init();
|
||||
ProxyManager.init();
|
||||
Accounts.init();
|
||||
@@ -81,8 +76,4 @@ public final class Settings {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateSystemLocale() {
|
||||
Locale.setDefault(config().getLocalization().getLocale());
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.Platform;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
@@ -644,11 +644,11 @@ public final class VersionSetting implements Cloneable {
|
||||
launcherVisibilityProperty.set(launcherVisibility);
|
||||
}
|
||||
|
||||
public Task<JavaVersion> getJavaVersion(VersionNumber gameVersion, Version version) {
|
||||
public Task<JavaVersion> getJavaVersion(GameVersionNumber gameVersion, Version version) {
|
||||
return getJavaVersion(gameVersion, version, true);
|
||||
}
|
||||
|
||||
public Task<JavaVersion> getJavaVersion(VersionNumber gameVersion, Version version, boolean checkJava) {
|
||||
public Task<JavaVersion> getJavaVersion(GameVersionNumber gameVersion, Version version, boolean checkJava) {
|
||||
return Task.runAsync(Schedulers.javafx(), () -> {
|
||||
if (StringUtils.isBlank(getJava())) {
|
||||
setJava(StringUtils.isBlank(getJavaDir()) ? "Auto" : "Custom");
|
||||
|
||||
@@ -37,10 +37,7 @@ import javafx.stage.StageStyle;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.game.ModpackHelper;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.EnumCommonDirectory;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.setting.*;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.account.AccountListPage;
|
||||
@@ -64,10 +61,11 @@ import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.*;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.newBuiltinImage;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class Controllers {
|
||||
public static final int MIN_WIDTH = 800 + 2 + 16; // bg width + border width*2 + shadow width*2
|
||||
public static final int MIN_HEIGHT = 450 + 2 + 40 + 16; // bg height + border width*2 + toolbar height + shadow width*2
|
||||
public static final Screen SCREEN = Screen.getPrimary();
|
||||
private static InvalidationListener stageSizeChangeListener;
|
||||
private static DoubleProperty stageX = new SimpleDoubleProperty();
|
||||
@@ -205,35 +203,37 @@ public final class Controllers {
|
||||
|
||||
WeakInvalidationListener weakListener = new WeakInvalidationListener(stageSizeChangeListener);
|
||||
|
||||
double initHeight = config().getHeight();
|
||||
double initWidth = config().getWidth();
|
||||
double initX = config().getX() * SCREEN.getBounds().getWidth();
|
||||
double initY = config().getY() * SCREEN.getBounds().getHeight();
|
||||
double initWidth = Math.max(MIN_WIDTH, config().getWidth());
|
||||
double initHeight = Math.max(MIN_HEIGHT, config().getHeight());
|
||||
|
||||
{
|
||||
double initX = config().getX() * SCREEN.getBounds().getWidth();
|
||||
double initY = config().getY() * SCREEN.getBounds().getHeight();
|
||||
|
||||
boolean invalid = true;
|
||||
double border = 20D;
|
||||
for (Screen screen : Screen.getScreens()) {
|
||||
Rectangle2D bound = screen.getBounds();
|
||||
|
||||
if (bound.getMinX() + border <= initX + initWidth && initX <= bound.getMaxX() - border && bound.getMinY() + border <= initY + initHeight && initY <= bound.getMaxY() - border) {
|
||||
if (bound.getMinX() + border <= initX + initWidth && initX <= bound.getMaxX() - border && bound.getMinY() + border <= initY && initY <= bound.getMaxY() - border) {
|
||||
invalid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (invalid) {
|
||||
initX = (0.5D - initWidth / Controllers.SCREEN.getBounds().getWidth() / 2) * SCREEN.getBounds().getWidth();
|
||||
initY = (0.5D - initHeight / Controllers.SCREEN.getBounds().getHeight() / 2) * SCREEN.getBounds().getHeight();
|
||||
initX = (0.5D - initWidth / SCREEN.getBounds().getWidth() / 2) * SCREEN.getBounds().getWidth();
|
||||
initY = (0.5D - initHeight / SCREEN.getBounds().getHeight() / 2) * SCREEN.getBounds().getHeight();
|
||||
}
|
||||
|
||||
stage.setX(initX);
|
||||
stage.setY(initY);
|
||||
stageX.set(initX);
|
||||
stageY.set(initY);
|
||||
}
|
||||
|
||||
stage.setX(initX);
|
||||
stage.setY(initY);
|
||||
stage.setHeight(initHeight);
|
||||
stage.setWidth(initWidth);
|
||||
stageX.set(initX);
|
||||
stageY.set(initY);
|
||||
stageHeight.set(initHeight);
|
||||
stageWidth.set(initWidth);
|
||||
stage.xProperty().addListener(weakListener);
|
||||
@@ -255,13 +255,13 @@ public final class Controllers {
|
||||
|
||||
scene = new Scene(decorator.getDecorator());
|
||||
scene.setFill(Color.TRANSPARENT);
|
||||
stage.setMinHeight(450 + 2 + 40 + 16); // bg height + border width*2 + toolbar height + shadow width*2
|
||||
stage.setMinWidth(800 + 2 + 16); // bg width + border width*2 + shadow width*2
|
||||
stage.setMinWidth(MIN_WIDTH);
|
||||
stage.setMinHeight(MIN_HEIGHT);
|
||||
decorator.getDecorator().prefWidthProperty().bind(scene.widthProperty());
|
||||
decorator.getDecorator().prefHeightProperty().bind(scene.heightProperty());
|
||||
scene.getStylesheets().setAll(Theme.getTheme().getStylesheets(config().getLauncherFontFamily()));
|
||||
|
||||
stage.getIcons().add(newBuiltinImage("/assets/img/icon.png"));
|
||||
FXUtils.setIcon(stage);
|
||||
stage.setTitle(Metadata.FULL_TITLE);
|
||||
stage.initStyle(StageStyle.TRANSPARENT);
|
||||
stage.setScene(scene);
|
||||
|
||||
@@ -28,9 +28,8 @@ import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.Stage;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.countly.CrashReport;
|
||||
import org.jackhuang.hmcl.upgrade.hmcl.UpdateChecker;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.newBuiltinImage;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
/**
|
||||
@@ -70,7 +69,7 @@ public class CrashWindow extends Stage {
|
||||
|
||||
Scene scene = new Scene(pane, 800, 480);
|
||||
setScene(scene);
|
||||
getIcons().add(newBuiltinImage("/assets/img/icon.png"));
|
||||
FXUtils.setIcon(this);
|
||||
setTitle(i18n("message.error"));
|
||||
|
||||
setOnCloseRequest(e -> System.exit(1));
|
||||
|
||||
@@ -43,6 +43,7 @@ import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Callback;
|
||||
import javafx.util.Duration;
|
||||
import javafx.util.StringConverter;
|
||||
@@ -78,6 +79,7 @@ import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
@@ -676,6 +678,16 @@ public final class FXUtils {
|
||||
comboBox.getSelectionModel().selectedIndexProperty().removeListener(listener);
|
||||
}
|
||||
|
||||
public static void setIcon(Stage stage) {
|
||||
String icon;
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
icon = "/assets/img/icon.png";
|
||||
} else {
|
||||
icon = "/assets/img/icon@4x.png";
|
||||
}
|
||||
stage.getIcons().add(newBuiltinImage(icon));
|
||||
}
|
||||
|
||||
/**
|
||||
* Suppress IllegalArgumentException since the url is supposed to be correct definitely.
|
||||
*
|
||||
@@ -769,8 +781,8 @@ public final class FXUtils {
|
||||
Task.runAsync(() -> {
|
||||
Path newPath = Files.createTempFile("hmcl-net-resource-cache-", ".cache");
|
||||
try ( // Make sure the file is released from JVM before we put the path into remoteImageCache.
|
||||
OutputStream outputStream = Files.newOutputStream(newPath);
|
||||
PNGWriter writer = new PNGWriter(outputStream, PNGType.RGBA, PNGWriter.DEFAULT_COMPRESS_LEVEL)
|
||||
OutputStream outputStream = Files.newOutputStream(newPath);
|
||||
PNGWriter writer = new PNGWriter(outputStream, PNGType.RGBA, PNGWriter.DEFAULT_COMPRESS_LEVEL)
|
||||
) {
|
||||
writer.write(PNGJavaFXUtils.asArgbImage(image));
|
||||
} catch (IOException e) {
|
||||
@@ -907,7 +919,9 @@ public final class FXUtils {
|
||||
content.putString(text);
|
||||
Clipboard.getSystemClipboard().setContent(content);
|
||||
|
||||
Controllers.showToast(i18n("message.copied"));
|
||||
if (!Controllers.isStopped()) {
|
||||
Controllers.showToast(i18n("message.copied"));
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Node> parseSegment(String segment, Consumer<String> hyperlinkAction) {
|
||||
@@ -930,7 +944,14 @@ public final class FXUtils {
|
||||
if ("a".equals(element.getTagName())) {
|
||||
String href = element.getAttribute("href");
|
||||
JFXHyperlink hyperlink = new JFXHyperlink(element.getTextContent());
|
||||
hyperlink.setOnAction(e -> hyperlinkAction.accept(href));
|
||||
hyperlink.setOnAction(e -> {
|
||||
String link = href;
|
||||
try {
|
||||
link = new URI(href).toASCIIString();
|
||||
} catch (URISyntaxException ignored) {
|
||||
}
|
||||
hyperlinkAction.accept(link);
|
||||
});
|
||||
texts.add(hyperlink);
|
||||
} else if ("b".equals(element.getTagName())) {
|
||||
Text text = new Text(element.getTextContent());
|
||||
|
||||
@@ -46,6 +46,7 @@ import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.util.Log4jLevel;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
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.CommandBuilder;
|
||||
@@ -66,7 +67,6 @@ import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.newBuiltinImage;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
@@ -99,11 +99,11 @@ public class GameCrashWindow extends Stage {
|
||||
this.version = version;
|
||||
this.launchOptions = launchOptions;
|
||||
this.logs = logs;
|
||||
this.analyzer = LibraryAnalyzer.analyze(version);
|
||||
this.analyzer = LibraryAnalyzer.analyze(version, repository.getGameVersion(version).orElse(null));
|
||||
|
||||
memory = Optional.ofNullable(launchOptions.getMaxMemory()).map(i -> i + " MB").orElse("-");
|
||||
|
||||
total_memory = Optional.ofNullable(OperatingSystem.TOTAL_MEMORY).map(i -> i + " MB").orElse("-");
|
||||
total_memory = OperatingSystem.TOTAL_MEMORY + " MB";
|
||||
|
||||
this.java = launchOptions.getJava().getArchitecture() == Architecture.SYSTEM_ARCH
|
||||
? launchOptions.getJava().getVersion()
|
||||
@@ -116,7 +116,7 @@ public class GameCrashWindow extends Stage {
|
||||
setScene(new Scene(view, 800, 480));
|
||||
getScene().getStylesheets().addAll(Theme.getTheme().getStylesheets(config().getLauncherFontFamily()));
|
||||
setTitle(i18n("game.crash.title"));
|
||||
getIcons().add(newBuiltinImage("/assets/img/icon.png"));
|
||||
FXUtils.setIcon(this);
|
||||
|
||||
analyzeCrashReport();
|
||||
}
|
||||
@@ -170,56 +170,51 @@ public class GameCrashWindow extends Stage {
|
||||
keywords.addAll(pair.getValue());
|
||||
}
|
||||
|
||||
List<Node> segments = new ArrayList<>();
|
||||
List<Node> segments = new ArrayList<>(FXUtils.parseSegment(i18n("game.crash.feedback"), Controllers::onHyperlinkAction));
|
||||
|
||||
boolean hasMultipleRules = results.keySet().stream().distinct().count() > 1;
|
||||
if (hasMultipleRules) {
|
||||
segments.addAll(FXUtils.parseSegment(i18n("game.crash.feedback"), Controllers::onHyperlinkAction));
|
||||
LOG.log(Level.INFO, "Number of reasons: " + results.size());
|
||||
if (results.size() > 1) {
|
||||
segments.add(new Text("\n"));
|
||||
segments.addAll(FXUtils.parseSegment(i18n("game.crash.reason.multiple"), Controllers::onHyperlinkAction));
|
||||
LOG.log(Level.INFO, "Multiple reasons detected");
|
||||
} else {
|
||||
segments.add(new Text("\n\n"));
|
||||
}
|
||||
|
||||
for (CrashReportAnalyzer.Result result : results.values()) {
|
||||
String message;
|
||||
switch (result.getRule()) {
|
||||
case TOO_OLD_JAVA:
|
||||
segments.addAll(FXUtils.parseSegment(i18n("game.crash.reason.too_old_java",
|
||||
CrashReportAnalyzer.getJavaVersionFromMajorVersion(Integer.parseInt(result.getMatcher().group("expected")))), Controllers::onHyperlinkAction));
|
||||
message = i18n("game.crash.reason.too_old_java", CrashReportAnalyzer.getJavaVersionFromMajorVersion(Integer.parseInt(result.getMatcher().group("expected"))));
|
||||
break;
|
||||
case MOD_RESOLUTION_CONFLICT:
|
||||
case MOD_RESOLUTION_MISSING:
|
||||
case MOD_RESOLUTION_COLLECTION:
|
||||
segments.addAll(FXUtils.parseSegment(i18n("game.crash.reason." + result.getRule().name().toLowerCase(Locale.ROOT),
|
||||
message = 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"))), Controllers::onHyperlinkAction));
|
||||
parseFabricModId(result.getMatcher().group("destmod")));
|
||||
break;
|
||||
case MOD_RESOLUTION_MISSING_MINECRAFT:
|
||||
segments.addAll(FXUtils.parseSegment(i18n("game.crash.reason." + result.getRule().name().toLowerCase(Locale.ROOT),
|
||||
message = i18n("game.crash.reason." + result.getRule().name().toLowerCase(Locale.ROOT),
|
||||
translateFabricModId(result.getMatcher().group("mod")),
|
||||
result.getMatcher().group("version")), Controllers::onHyperlinkAction));
|
||||
result.getMatcher().group("version"));
|
||||
break;
|
||||
case MOD_FOREST_OPTIFINE:
|
||||
case TWILIGHT_FOREST_OPTIFINE:
|
||||
case PERFORMANT_FOREST_OPTIFINE:
|
||||
case JADE_FOREST_OPTIFINE:
|
||||
segments.addAll(FXUtils.parseSegment(i18n("game.crash.reason.mod", "OptiFine"), Controllers::onHyperlinkAction));
|
||||
message = i18n("game.crash.reason.mod", "OptiFine");
|
||||
LOG.log(Level.INFO, "Crash cause: " + result.getRule() + ": " + i18n("game.crash.reason.mod", "OptiFine"));
|
||||
break;
|
||||
default:
|
||||
segments.addAll(FXUtils.parseSegment(i18n("game.crash.reason." + result.getRule().name().toLowerCase(Locale.ROOT),
|
||||
message = i18n("game.crash.reason." + result.getRule().name().toLowerCase(Locale.ROOT),
|
||||
Arrays.stream(result.getRule().getGroupNames()).map(groupName -> result.getMatcher().group(groupName))
|
||||
.toArray()), Controllers::onHyperlinkAction));
|
||||
.toArray());
|
||||
break;
|
||||
}
|
||||
segments.add(new Text("\n"));
|
||||
if (hasMultipleRules) {
|
||||
segments.add(new Text("\n"));
|
||||
} else {
|
||||
segments.add(new Text("\n"));
|
||||
segments.addAll(FXUtils.parseSegment(i18n("game.crash.feedback"), Controllers::onHyperlinkAction));
|
||||
segments.add(new Text("\n"));
|
||||
}
|
||||
LOG.log(Level.INFO, "Crash cause: " + result.getRule());
|
||||
LOG.log(Level.INFO, "Crash cause: " + result.getRule() + ": " + message);
|
||||
segments.addAll(FXUtils.parseSegment(message, Controllers::onHyperlinkAction));
|
||||
segments.add(new Text("\n\n"));
|
||||
}
|
||||
if (results.isEmpty()) {
|
||||
if (!keywords.isEmpty()) {
|
||||
@@ -285,15 +280,20 @@ public class GameCrashWindow extends Stage {
|
||||
logs.stream().map(Pair::getKey).collect(Collectors.joining(OperatingSystem.LINE_SEPARATOR)))
|
||||
.thenComposeAsync(logs ->
|
||||
LogExporter.exportLogs(logFile, repository, launchOptions.getVersionName(), logs, new CommandBuilder().addAll(managedProcess.getCommands()).toString()))
|
||||
.thenRunAsync(() -> {
|
||||
FXUtils.showFileInExplorer(logFile);
|
||||
.handleAsync((result, exception) -> {
|
||||
Alert alert;
|
||||
|
||||
if (exception == null) {
|
||||
FXUtils.showFileInExplorer(logFile);
|
||||
alert = new Alert(Alert.AlertType.INFORMATION, i18n("settings.launcher.launcher_log.export.success", logFile));
|
||||
} else {
|
||||
LOG.log(Level.WARNING, "Failed to export game crash info", exception);
|
||||
alert = new Alert(Alert.AlertType.WARNING, i18n("settings.launcher.launcher_log.export.failed") + "\n" + StringUtils.getStackTrace(exception));
|
||||
}
|
||||
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION, i18n("settings.launcher.launcher_log.export.success", logFile));
|
||||
alert.setTitle(i18n("settings.launcher.launcher_log.export"));
|
||||
alert.showAndWait();
|
||||
}, Schedulers.javafx())
|
||||
.exceptionally(e -> {
|
||||
LOG.log(Level.WARNING, "Failed to export game crash info", e);
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.setting.VersionIconType;
|
||||
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@@ -170,7 +170,7 @@ public class InstallerItem extends Control {
|
||||
public InstallerItemGroup(String gameVersion) {
|
||||
mutualIncompatible(forge, fabric, quilt, neoForge, liteLoader);
|
||||
addIncompatibles(optiFine, fabric, quilt, neoForge);
|
||||
addIncompatibles(fabricApi, forge, quilt, quiltApi, neoForge, liteLoader, optiFine);
|
||||
addIncompatibles(fabricApi, forge, quiltApi, neoForge, liteLoader, optiFine);
|
||||
addIncompatibles(quiltApi, forge, fabric, fabricApi, neoForge, liteLoader, optiFine);
|
||||
|
||||
InvalidationListener listener = o -> {
|
||||
@@ -204,7 +204,7 @@ public class InstallerItem extends Control {
|
||||
|
||||
if (gameVersion == null) {
|
||||
this.libraries = new InstallerItem[]{game, forge, neoForge, liteLoader, optiFine, fabric, fabricApi, quilt, quiltApi};
|
||||
} else if (VersionNumber.compare(gameVersion, "1.13") < 0) {
|
||||
} else if (GameVersionNumber.compare(gameVersion, "1.13") < 0) {
|
||||
this.libraries = new InstallerItem[]{game, forge, liteLoader, optiFine};
|
||||
} else {
|
||||
this.libraries = new InstallerItem[]{game, forge, neoForge, optiFine, fabric, fabricApi, quilt, quiltApi};
|
||||
|
||||
@@ -60,7 +60,6 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.newBuiltinImage;
|
||||
import static org.jackhuang.hmcl.util.Lang.thread;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.StringUtils.parseEscapeSequence;
|
||||
@@ -98,7 +97,7 @@ public final class LogWindow extends Stage {
|
||||
setScene(new Scene(impl, 800, 480));
|
||||
getScene().getStylesheets().addAll(Theme.getTheme().getStylesheets(config().getLauncherFontFamily()));
|
||||
setTitle(i18n("logwindow.title"));
|
||||
getIcons().add(newBuiltinImage("/assets/img/icon.png"));
|
||||
FXUtils.setIcon(this);
|
||||
|
||||
levelShownMap.values().forEach(property -> property.addListener((a, b, newValue) -> shakeLogs()));
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ public enum SVG {
|
||||
GEAR_OUTLINE("M12,8A4,4 0 0,1 16,12A4,4 0 0,1 12,16A4,4 0 0,1 8,12A4,4 0 0,1 12,8M12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12A2,2 0 0,0 12,10M10,22C9.75,22 9.54,21.82 9.5,21.58L9.13,18.93C8.5,18.68 7.96,18.34 7.44,17.94L4.95,18.95C4.73,19.03 4.46,18.95 4.34,18.73L2.34,15.27C2.21,15.05 2.27,14.78 2.46,14.63L4.57,12.97L4.5,12L4.57,11L2.46,9.37C2.27,9.22 2.21,8.95 2.34,8.73L4.34,5.27C4.46,5.05 4.73,4.96 4.95,5.05L7.44,6.05C7.96,5.66 8.5,5.32 9.13,5.07L9.5,2.42C9.54,2.18 9.75,2 10,2H14C14.25,2 14.46,2.18 14.5,2.42L14.87,5.07C15.5,5.32 16.04,5.66 16.56,6.05L19.05,5.05C19.27,4.96 19.54,5.05 19.66,5.27L21.66,8.73C21.79,8.95 21.73,9.22 21.54,9.37L19.43,11L19.5,12L19.43,13L21.54,14.63C21.73,14.78 21.79,15.05 21.66,15.27L19.66,18.73C19.54,18.95 19.27,19.04 19.05,18.95L16.56,17.95C16.04,18.34 15.5,18.68 14.87,18.93L14.5,21.58C14.46,21.82 14.25,22 14,22H10M11.25,4L10.88,6.61C9.68,6.86 8.62,7.5 7.85,8.39L5.44,7.35L4.69,8.65L6.8,10.2C6.4,11.37 6.4,12.64 6.8,13.8L4.68,15.36L5.43,16.66L7.86,15.62C8.63,16.5 9.68,17.14 10.87,17.38L11.24,20H12.76L13.13,17.39C14.32,17.14 15.37,16.5 16.14,15.62L18.57,16.66L19.32,15.36L17.2,13.81C17.6,12.64 17.6,11.37 17.2,10.2L19.31,8.65L18.56,7.35L16.15,8.39C15.38,7.5 14.32,6.86 13.12,6.62L12.75,4H11.25Z"),
|
||||
BACK("M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z"),
|
||||
CANCEL("M12 2C17.5 2 22 6.5 22 12S17.5 22 12 22 2 17.5 2 12 6.5 2 12 2M12 4C10.1 4 8.4 4.6 7.1 5.7L18.3 16.9C19.3 15.5 20 13.8 20 12C20 7.6 16.4 4 12 4M16.9 18.3L5.7 7.1C4.6 8.4 4 10.1 4 12C4 16.4 7.6 20 12 20C13.9 20 15.6 19.4 16.9 18.3Z"),
|
||||
CHAT("M20,2A2,2 0 0,1 22,4V16A2,2 0 0,1 20,18H6L2,22V4C2,2.89 2.9,2 4,2H20M4,4V17.17L5.17,16H20V4H4M6,7H18V9H6V7M6,11H15V13H6V11Z"),
|
||||
CLOSE("M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z"),
|
||||
COPY("M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"),
|
||||
DOTS_VERTICAL("M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z"),
|
||||
|
||||
@@ -25,7 +25,7 @@ import javafx.scene.web.WebEngine;
|
||||
import javafx.scene.web.WebView;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.upgrade.hmcl.RemoteVersion;
|
||||
import org.jackhuang.hmcl.upgrade.RemoteVersion;
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.newBuiltinImage;
|
||||
|
||||
public class WebStage extends Stage {
|
||||
protected final StackPane pane = new StackPane();
|
||||
@@ -44,7 +43,7 @@ public class WebStage extends Stage {
|
||||
public WebStage(int width, int height) {
|
||||
setScene(new Scene(pane, width, height));
|
||||
getScene().getStylesheets().addAll(Theme.getTheme().getStylesheets(config().getLauncherFontFamily()));
|
||||
getIcons().add(newBuiltinImage("/assets/img/icon.png"));
|
||||
FXUtils.setIcon(this);
|
||||
webView.getEngine().setUserDataDirectory(Metadata.HMCL_DIRECTORY.toFile());
|
||||
webView.setContextMenuEnabled(false);
|
||||
progressBar.progressProperty().bind(webView.getEngine().getLoadWorker().progressProperty());
|
||||
|
||||
@@ -120,19 +120,15 @@ public class AccountListItem extends RadioButton {
|
||||
}
|
||||
|
||||
public ObservableBooleanValue canUploadSkin() {
|
||||
if (account instanceof YggdrasilAccount) {
|
||||
if (account instanceof AuthlibInjectorAccount) {
|
||||
AuthlibInjectorAccount aiAccount = (AuthlibInjectorAccount) account;
|
||||
ObjectBinding<Optional<CompleteGameProfile>> profile = aiAccount.getYggdrasilService().getProfileRepository().binding(aiAccount.getUUID());
|
||||
return createBooleanBinding(() -> {
|
||||
Set<TextureType> uploadableTextures = profile.get()
|
||||
.map(AuthlibInjectorAccount::getUploadableTextures)
|
||||
.orElse(emptySet());
|
||||
return uploadableTextures.contains(TextureType.SKIN);
|
||||
}, profile);
|
||||
} else {
|
||||
return createBooleanBinding(() -> true);
|
||||
}
|
||||
if (account instanceof AuthlibInjectorAccount) {
|
||||
AuthlibInjectorAccount aiAccount = (AuthlibInjectorAccount) account;
|
||||
ObjectBinding<Optional<CompleteGameProfile>> profile = aiAccount.getYggdrasilService().getProfileRepository().binding(aiAccount.getUUID());
|
||||
return createBooleanBinding(() -> {
|
||||
Set<TextureType> uploadableTextures = profile.get()
|
||||
.map(AuthlibInjectorAccount::getUploadableTextures)
|
||||
.orElse(emptySet());
|
||||
return uploadableTextures.contains(TextureType.SKIN);
|
||||
}, profile);
|
||||
} else if (account instanceof OfflineAccount || account instanceof MicrosoftAccount) {
|
||||
return createBooleanBinding(() -> true);
|
||||
} else {
|
||||
|
||||
@@ -107,14 +107,6 @@ public class AccountListPage extends DecoratorAnimatedPage implements DecoratorP
|
||||
offlineItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_OFFLINE)));
|
||||
boxMethods.getChildren().add(offlineItem);
|
||||
|
||||
AdvancedListItem mojangItem = new AdvancedListItem();
|
||||
mojangItem.getStyleClass().add("navigation-drawer-item");
|
||||
mojangItem.setActionButtonVisible(false);
|
||||
mojangItem.setTitle(i18n("account.methods.yggdrasil"));
|
||||
mojangItem.setLeftGraphic(wrap(SVG.MOJANG));
|
||||
mojangItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_MOJANG)));
|
||||
boxMethods.getChildren().add(mojangItem);
|
||||
|
||||
AdvancedListItem microsoftItem = new AdvancedListItem();
|
||||
microsoftItem.getStyleClass().add("navigation-drawer-item");
|
||||
microsoftItem.setActionButtonVisible(false);
|
||||
|
||||
@@ -24,20 +24,15 @@ import com.jfoenix.controls.JFXProgressBar;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
import org.jackhuang.hmcl.auth.ClassicAccount;
|
||||
import org.jackhuang.hmcl.auth.NoSelectedCharacterException;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.JFXHyperlink;
|
||||
import org.jackhuang.hmcl.ui.construct.RequiredValidator;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
@@ -84,20 +79,6 @@ public class ClassicAccountLoginDialog extends StackPane {
|
||||
txtPassword.setPromptText(i18n("account.password"));
|
||||
|
||||
body.getChildren().setAll(usernameLabel, txtPassword);
|
||||
|
||||
if (oldAccount instanceof YggdrasilAccount && !(oldAccount instanceof AuthlibInjectorAccount)) {
|
||||
HBox linkPane = new HBox(8);
|
||||
body.getChildren().add(linkPane);
|
||||
|
||||
JFXHyperlink migrationLink = new JFXHyperlink(i18n("account.methods.yggdrasil.migration"));
|
||||
migrationLink.setExternalLink(YggdrasilService.PROFILE_URL);
|
||||
|
||||
JFXHyperlink migrationHowLink = new JFXHyperlink(i18n("account.methods.yggdrasil.migration.how"));
|
||||
migrationHowLink.setExternalLink(YggdrasilService.MIGRATION_FAQ_URL);
|
||||
|
||||
linkPane.getChildren().setAll(migrationLink, migrationHowLink);
|
||||
}
|
||||
|
||||
dialogLayout.setBody(body);
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,6 @@ import org.jackhuang.hmcl.auth.authlibinjector.BoundAuthlibInjectorAccountFactor
|
||||
import org.jackhuang.hmcl.auth.microsoft.MicrosoftAccountFactory;
|
||||
import org.jackhuang.hmcl.auth.offline.OfflineAccountFactory;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.GameProfile;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService;
|
||||
import org.jackhuang.hmcl.game.OAuthServer;
|
||||
import org.jackhuang.hmcl.game.TexturesLoader;
|
||||
@@ -58,7 +57,7 @@ import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.upgrade.hmcl.IntegrityChecker;
|
||||
import org.jackhuang.hmcl.upgrade.IntegrityChecker;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
@@ -312,7 +311,7 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
birthLink.setExternalLink("https://support.microsoft.com/account-billing/837badbc-999e-54d2-2617-d19206b9540a");
|
||||
JFXHyperlink profileLink = new JFXHyperlink(i18n("account.methods.microsoft.profile"));
|
||||
profileLink.setExternalLink("https://account.live.com/editprof.aspx");
|
||||
JFXHyperlink purchaseLink = new JFXHyperlink(i18n("account.methods.yggdrasil.purchase"));
|
||||
JFXHyperlink purchaseLink = new JFXHyperlink(i18n("account.methods.microsoft.purchase"));
|
||||
purchaseLink.setExternalLink(YggdrasilService.PURCHASE_URL);
|
||||
JFXHyperlink deauthorizeLink = new JFXHyperlink(i18n("account.methods.microsoft.deauthorize"));
|
||||
deauthorizeLink.setExternalLink("https://account.live.com/consent/Edit?client_id=000000004C794E0A");
|
||||
@@ -344,24 +343,6 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
}
|
||||
|
||||
detailsPane = vbox;
|
||||
} 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());
|
||||
@@ -526,30 +507,11 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
rowIndex++;
|
||||
}
|
||||
|
||||
if (factory instanceof YggdrasilAccountFactory) {
|
||||
HBox box = new HBox();
|
||||
GridPane.setColumnSpan(box, 2);
|
||||
|
||||
JFXHyperlink migrationLink = new JFXHyperlink(i18n("account.methods.yggdrasil.migration"));
|
||||
migrationLink.setExternalLink("https://aka.ms/MinecraftMigration");
|
||||
|
||||
JFXHyperlink migrationHowLink = new JFXHyperlink(i18n("account.methods.yggdrasil.migration.how"));
|
||||
migrationHowLink.setExternalLink("https://help.minecraft.net/hc/articles/4411173197709");
|
||||
|
||||
JFXHyperlink purchaseLink = new JFXHyperlink(i18n("account.methods.yggdrasil.purchase"));
|
||||
purchaseLink.setExternalLink(YggdrasilService.PURCHASE_URL);
|
||||
|
||||
box.getChildren().setAll(migrationLink, migrationHowLink, purchaseLink);
|
||||
add(box, 0, rowIndex);
|
||||
|
||||
rowIndex++;
|
||||
}
|
||||
|
||||
if (factory instanceof OfflineAccountFactory) {
|
||||
txtUsername.setPromptText(i18n("account.methods.offline.name.special_characters"));
|
||||
runInFX(() -> FXUtils.installFastTooltip(txtUsername, i18n("account.methods.offline.name.special_characters")));
|
||||
|
||||
JFXHyperlink purchaseLink = new JFXHyperlink(i18n("account.methods.yggdrasil.purchase"));
|
||||
JFXHyperlink purchaseLink = new JFXHyperlink(i18n("account.methods.microsoft.purchase"));
|
||||
purchaseLink.setExternalLink(YggdrasilService.PURCHASE_URL);
|
||||
HBox linkPane = new HBox(purchaseLink);
|
||||
GridPane.setColumnSpan(linkPane, 2);
|
||||
@@ -620,9 +582,7 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
}
|
||||
|
||||
private boolean requiresEmailAsUsername() {
|
||||
if (factory instanceof YggdrasilAccountFactory) {
|
||||
return true;
|
||||
} else if ((factory instanceof AuthlibInjectorAccountFactory) && this.server != null) {
|
||||
if ((factory instanceof AuthlibInjectorAccountFactory) && this.server != null) {
|
||||
return !server.isNonEmailLogin();
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -70,7 +70,7 @@ public class OAuthAccountLoginDialog extends DialogPane {
|
||||
birthLink.setOnAction(e -> FXUtils.openLink("https://support.microsoft.com/account-billing/how-to-change-a-birth-date-on-a-microsoft-account-837badbc-999e-54d2-2617-d19206b9540a"));
|
||||
JFXHyperlink profileLink = new JFXHyperlink(i18n("account.methods.microsoft.profile"));
|
||||
profileLink.setOnAction(e -> FXUtils.openLink("https://account.live.com/editprof.aspx"));
|
||||
JFXHyperlink purchaseLink = new JFXHyperlink(i18n("account.methods.yggdrasil.purchase"));
|
||||
JFXHyperlink purchaseLink = new JFXHyperlink(i18n("account.methods.microsoft.purchase"));
|
||||
purchaseLink.setOnAction(e -> FXUtils.openLink(YggdrasilService.PURCHASE_URL));
|
||||
box.getChildren().setAll(profileLink, birthLink, purchaseLink);
|
||||
GridPane.setColumnSpan(box, 2);
|
||||
|
||||
@@ -40,7 +40,7 @@ import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class MultiFileItem<T> extends VBox {
|
||||
public final class MultiFileItem<T> extends VBox {
|
||||
private final ObjectProperty<T> selectedData = new SimpleObjectProperty<>(this, "selectedData");
|
||||
private final ObjectProperty<T> fallbackData = new SimpleObjectProperty<>(this, "fallbackData");
|
||||
|
||||
@@ -176,8 +176,8 @@ public class MultiFileItem<T> extends VBox {
|
||||
}
|
||||
}
|
||||
|
||||
public static class StringOption<T> extends Option<T> {
|
||||
private JFXTextField customField = new JFXTextField();
|
||||
public static final class StringOption<T> extends Option<T> {
|
||||
private final JFXTextField customField = new JFXTextField();
|
||||
|
||||
public StringOption(String title, T data) {
|
||||
super(title, data);
|
||||
@@ -196,7 +196,7 @@ public class MultiFileItem<T> extends VBox {
|
||||
}
|
||||
|
||||
public StringOption<T> bindBidirectional(Property<String> property) {
|
||||
customField.textProperty().bindBidirectional(property);
|
||||
FXUtils.bindString(customField, property);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -230,8 +230,8 @@ public class MultiFileItem<T> extends VBox {
|
||||
}
|
||||
}
|
||||
|
||||
public static class FileOption<T> extends Option<T> {
|
||||
private FileSelector selector = new FileSelector();
|
||||
public static final class FileOption<T> extends Option<T> {
|
||||
private final FileSelector selector = new FileSelector();
|
||||
|
||||
public FileOption(String title, T data) {
|
||||
super(title, data);
|
||||
|
||||
@@ -84,7 +84,8 @@ class AdditionalInstallersPage extends InstallersPage {
|
||||
|
||||
@Override
|
||||
protected void reload() {
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version.resolvePreservingPatches(repository));
|
||||
Version resolved = version.resolvePreservingPatches(repository);
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(resolved, repository.getGameVersion(resolved).orElse(null));
|
||||
String game = analyzer.getVersion(MINECRAFT).orElse(null);
|
||||
String currentGameVersion = Lang.nonNull(getVersion("game"), game);
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ import org.jackhuang.hmcl.download.quilt.QuiltAPIRemoteVersion;
|
||||
import org.jackhuang.hmcl.download.quilt.QuiltRemoteVersion;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.setting.VersionIconType;
|
||||
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;
|
||||
@@ -51,7 +52,6 @@ import org.jackhuang.hmcl.ui.construct.RipplerContainer;
|
||||
import org.jackhuang.hmcl.ui.wizard.Navigation;
|
||||
import org.jackhuang.hmcl.ui.wizard.Refreshable;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import org.jackhuang.hmcl.util.HMCLService;
|
||||
import org.jackhuang.hmcl.util.Holder;
|
||||
|
||||
import java.util.List;
|
||||
@@ -263,7 +263,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
private void onBack() { navigation.onPrev(true); }
|
||||
|
||||
private void onSponsor() {
|
||||
HMCLService.openRedirectLink("bmclapi_sponsor");
|
||||
FXUtils.openLink("https://bmclapidoc.bangbang93.com");
|
||||
}
|
||||
|
||||
private static class RemoteVersionListCell extends ListCell<RemoteVersion> {
|
||||
@@ -298,7 +298,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
|
||||
content.setTitle(remoteVersion.getSelfVersion());
|
||||
if (remoteVersion.getReleaseDate() != null) {
|
||||
content.setSubtitle(formatDateTime(remoteVersion.getReleaseDate().toInstant()));
|
||||
content.setSubtitle(formatDateTime(remoteVersion.getReleaseDate()));
|
||||
} else {
|
||||
content.setSubtitle(null);
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ public final class ModpackInfoPage extends Control implements WizardPage {
|
||||
if (skinnable.controller.getSettings().get(MODPACK_TYPE) == MODPACK_TYPE_SERVER) {
|
||||
Hyperlink hyperlink = new Hyperlink(i18n("modpack.wizard.step.initialization.server"));
|
||||
hyperlink.setOnMouseClicked(e -> {
|
||||
FXUtils.openLink("https://hmcl.huangyuhui.net/api/redirect/server-modpack");
|
||||
FXUtils.openLink("https://docs.hmcl.net/modpack/serverpack.htm");
|
||||
});
|
||||
borderPane.setTop(hyperlink);
|
||||
} else {
|
||||
|
||||
@@ -34,7 +34,7 @@ public class AboutPage extends StackPane {
|
||||
ComponentList about = new ComponentList();
|
||||
{
|
||||
IconedTwoLineListItem launcher = new IconedTwoLineListItem();
|
||||
launcher.setImage(FXUtils.newBuiltinImage("/assets/img/craft_table.png"));
|
||||
launcher.setImage(FXUtils.newBuiltinImage("/assets/img/icon.png"));
|
||||
launcher.setTitle("Hello Minecraft! Launcher");
|
||||
launcher.setSubtitle(Metadata.VERSION);
|
||||
launcher.setExternalLink("https://hmcl.huangyuhui.net");
|
||||
@@ -61,19 +61,37 @@ 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(FXUtils.newBuiltinImage("/assets/img/glavo.png"));
|
||||
glavo.setTitle("Glavo");
|
||||
glavo.setSubtitle(i18n("about.thanks_to.glavo.statement"));
|
||||
glavo.setExternalLink("https://github.com/Glavo");
|
||||
|
||||
|
||||
IconedTwoLineListItem zekerzhayard = new IconedTwoLineListItem();
|
||||
zekerzhayard.setImage(FXUtils.newBuiltinImage("/assets/img/zekerzhayard.png"));
|
||||
zekerzhayard.setTitle("ZekerZhayard");
|
||||
zekerzhayard.setSubtitle(i18n("about.thanks_to.zekerzhayard.statement"));
|
||||
zekerzhayard.setExternalLink("https://github.com/ZekerZhayard");
|
||||
|
||||
IconedTwoLineListItem zkitefly = new IconedTwoLineListItem();
|
||||
zkitefly.setImage(FXUtils.newBuiltinImage("/assets/img/zkitefly.png"));
|
||||
zkitefly.setTitle("Zkitefly");
|
||||
zkitefly.setSubtitle(i18n("about.thanks_to.zkitefly.statement"));
|
||||
zkitefly.setExternalLink("https://github.com/zkitefly");
|
||||
|
||||
IconedTwoLineListItem burningtnt = new IconedTwoLineListItem();
|
||||
burningtnt.setImage(FXUtils.newBuiltinImage("/assets/img/burningtnt.png"));
|
||||
burningtnt.setTitle("Burning_TNT");
|
||||
burningtnt.setSubtitle(i18n("about.thanks_to.burningtnt.statement"));
|
||||
burningtnt.setExternalLink("https://github.com/burningtnt");
|
||||
|
||||
IconedTwoLineListItem shulkerSakura = new IconedTwoLineListItem();
|
||||
shulkerSakura.setTitle("ShulkerSakura");
|
||||
shulkerSakura.setImage(FXUtils.newBuiltinImage("/assets/img/ShulkerSakura.png"));
|
||||
shulkerSakura.setSubtitle(i18n("about.thanks_to.shulkersakura.statement"));
|
||||
shulkerSakura.setExternalLink("https://github.com/ShulkerSakura");
|
||||
|
||||
IconedTwoLineListItem gamerteam = new IconedTwoLineListItem();
|
||||
gamerteam.setTitle("gamerteam");
|
||||
gamerteam.setImage(FXUtils.newBuiltinImage("/assets/img/gamerteam.png"));
|
||||
@@ -85,12 +103,6 @@ public class AboutPage extends StackPane {
|
||||
redLnn.setImage(FXUtils.newBuiltinImage("/assets/img/red_lnn.png"));
|
||||
redLnn.setSubtitle(i18n("about.thanks_to.red_lnn.statement"));
|
||||
|
||||
IconedTwoLineListItem mcbbs = new IconedTwoLineListItem();
|
||||
mcbbs.setImage(FXUtils.newBuiltinImage("/assets/img/chest.png"));
|
||||
mcbbs.setTitle(i18n("about.thanks_to.mcbbs"));
|
||||
mcbbs.setSubtitle(i18n("about.thanks_to.mcbbs.statement"));
|
||||
mcbbs.setExternalLink("https://www.mcbbs.net/");
|
||||
|
||||
IconedTwoLineListItem mcmod = new IconedTwoLineListItem();
|
||||
mcmod.setImage(FXUtils.newBuiltinImage("/assets/img/mcmod.png"));
|
||||
mcmod.setTitle(i18n("about.thanks_to.mcmod"));
|
||||
@@ -104,12 +116,12 @@ public class AboutPage extends StackPane {
|
||||
contributors.setExternalLink("https://github.com/HMCL-dev/HMCL/graphs/contributors");
|
||||
|
||||
IconedTwoLineListItem users = new IconedTwoLineListItem();
|
||||
users.setImage(FXUtils.newBuiltinImage("/assets/img/craft_table.png"));
|
||||
users.setImage(FXUtils.newBuiltinImage("/assets/img/icon.png"));
|
||||
users.setTitle(i18n("about.thanks_to.users"));
|
||||
users.setSubtitle(i18n("about.thanks_to.users.statement"));
|
||||
users.setExternalLink("https://hmcl.huangyuhui.net/api/redirect/sponsor");
|
||||
users.setExternalLink("https://docs.hmcl.net/groups.html");
|
||||
|
||||
thanks.getContent().setAll(yushijinhun, bangbang93, glavo, zkitefly ,mcbbs, mcmod, gamerteam, redLnn, contributors, users);
|
||||
thanks.getContent().setAll(yushijinhun, bangbang93, glavo, zekerzhayard, zkitefly, burningtnt, mcmod, shulkerSakura, gamerteam, redLnn, contributors, users);
|
||||
}
|
||||
|
||||
ComponentList dep = new ComponentList();
|
||||
|
||||
@@ -42,10 +42,10 @@ public class FeedbackPage extends SpinnerPane {
|
||||
ComponentList community = new ComponentList();
|
||||
{
|
||||
IconedTwoLineListItem users = new IconedTwoLineListItem();
|
||||
users.setImage(FXUtils.newBuiltinImage("/assets/img/craft_table.png"));
|
||||
users.setImage(FXUtils.newBuiltinImage("/assets/img/icon.png"));
|
||||
users.setTitle(i18n("feedback.qq_group"));
|
||||
users.setSubtitle(i18n("feedback.qq_group.statement"));
|
||||
users.setExternalLink("https://hmcl.huangyuhui.net/api/redirect/sponsor");
|
||||
users.setExternalLink("https://docs.hmcl.net/groups.html");
|
||||
|
||||
IconedTwoLineListItem github = new IconedTwoLineListItem();
|
||||
github.setImage(FXUtils.newBuiltinImage("/assets/img/github.png"));
|
||||
|
||||
@@ -46,7 +46,6 @@ public class LauncherSettingsPage extends DecoratorAnimatedPage implements Decor
|
||||
private final TabHeader.Tab<HelpPage> helpTab = new TabHeader.Tab<>("helpPage");
|
||||
private final TabHeader.Tab<AboutPage> aboutTab = new TabHeader.Tab<>("aboutPage");
|
||||
private final TabHeader.Tab<FeedbackPage> feedbackTab = new TabHeader.Tab<>("feedbackPage");
|
||||
private final TabHeader.Tab<SponsorPage> sponsorTab = new TabHeader.Tab<>("sponsorPage");
|
||||
private final TransitionPane transitionPane = new TransitionPane();
|
||||
|
||||
public LauncherSettingsPage() {
|
||||
@@ -56,9 +55,8 @@ public class LauncherSettingsPage extends DecoratorAnimatedPage implements Decor
|
||||
downloadTab.setNodeSupplier(DownloadSettingsPage::new);
|
||||
helpTab.setNodeSupplier(HelpPage::new);
|
||||
feedbackTab.setNodeSupplier(FeedbackPage::new);
|
||||
sponsorTab.setNodeSupplier(SponsorPage::new);
|
||||
aboutTab.setNodeSupplier(AboutPage::new);
|
||||
tab = new TabHeader(gameTab, settingsTab, personalizationTab, downloadTab, helpTab, feedbackTab, sponsorTab, aboutTab);
|
||||
tab = new TabHeader(gameTab, settingsTab, personalizationTab, downloadTab, helpTab, feedbackTab, aboutTab);
|
||||
|
||||
tab.select(gameTab);
|
||||
gameTab.initializeIfNeeded();
|
||||
@@ -108,12 +106,6 @@ public class LauncherSettingsPage extends DecoratorAnimatedPage implements Decor
|
||||
feedbackItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(feedbackTab));
|
||||
feedbackItem.setOnAction(e -> tab.select(feedbackTab));
|
||||
})
|
||||
.addNavigationDrawerItem(sponsorItem -> {
|
||||
sponsorItem.setTitle(i18n("sponsor"));
|
||||
sponsorItem.setLeftGraphic(wrap(SVG.HAND_HEAR_OUTLINE));
|
||||
sponsorItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(sponsorTab));
|
||||
sponsorItem.setOnAction(e -> tab.select(sponsorTab));
|
||||
})
|
||||
.addNavigationDrawerItem(aboutItem -> {
|
||||
aboutItem.setTitle(i18n("about"));
|
||||
aboutItem.setLeftGraphic(wrap(SVG.INFORMATION_OUTLINE));
|
||||
|
||||
@@ -53,9 +53,9 @@ import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.ui.versions.GameItem;
|
||||
import org.jackhuang.hmcl.ui.versions.Versions;
|
||||
import org.jackhuang.hmcl.upgrade.hmcl.RemoteVersion;
|
||||
import org.jackhuang.hmcl.upgrade.hmcl.UpdateChecker;
|
||||
import org.jackhuang.hmcl.upgrade.hmcl.UpdateHandler;
|
||||
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;
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@ import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.account.AccountAdvancedListItem;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
|
||||
import org.jackhuang.hmcl.ui.construct.JFXHyperlink;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
@@ -43,15 +42,16 @@ import org.jackhuang.hmcl.ui.nbt.NBTEditorPage;
|
||||
import org.jackhuang.hmcl.ui.nbt.NBTHelper;
|
||||
import org.jackhuang.hmcl.ui.versions.GameAdvancedListItem;
|
||||
import org.jackhuang.hmcl.ui.versions.Versions;
|
||||
import org.jackhuang.hmcl.upgrade.hmcl.UpdateChecker;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.Instant;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.logging.Level;
|
||||
@@ -118,9 +118,8 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
List<Version> children = repository.getVersions().parallelStream()
|
||||
.filter(version -> !version.isHidden())
|
||||
.sorted(Comparator
|
||||
.comparing((Version version) -> version.getReleaseTime() == null ? new Date(0L)
|
||||
: version.getReleaseTime())
|
||||
.thenComparing(a -> VersionNumber.asVersion(a.getId())))
|
||||
.comparing((Version version) -> Lang.requireNonNullElse(version.getReleaseTime(), Instant.EPOCH))
|
||||
.thenComparing(version -> VersionNumber.asVersion(repository.getGameVersion(version).orElse(version.getId()))))
|
||||
.collect(Collectors.toList());
|
||||
runInFX(() -> {
|
||||
if (profile == Profiles.getSelectedProfile())
|
||||
@@ -170,25 +169,19 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
runInFX(() -> FXUtils.installFastTooltip(downloadItem, i18n("download.hint")));
|
||||
|
||||
// fifth item in left sidebar
|
||||
AdvancedListItem multiplayerItem = new AdvancedListItem();
|
||||
multiplayerItem.setLeftGraphic(wrap(SVG.LAN));
|
||||
multiplayerItem.setActionButtonVisible(false);
|
||||
multiplayerItem.setTitle(i18n("multiplayer"));
|
||||
JFXHyperlink link = new JFXHyperlink(i18n("multiplayer.hint.details"));
|
||||
link.setExternalLink("https://hmcl.huangyuhui.net/api/redirect/multiplayer-migrate");
|
||||
multiplayerItem.setOnAction(e -> Controllers.dialog(
|
||||
new MessageDialogPane.Builder(i18n("multiplayer.hint"), null, MessageDialogPane.MessageType.INFO)
|
||||
.addAction(link)
|
||||
.ok(null)
|
||||
.build()));
|
||||
|
||||
// sixth item in left sidebar
|
||||
AdvancedListItem launcherSettingsItem = new AdvancedListItem();
|
||||
launcherSettingsItem.setLeftGraphic(wrap(SVG.GEAR_OUTLINE));
|
||||
launcherSettingsItem.setActionButtonVisible(false);
|
||||
launcherSettingsItem.setTitle(i18n("settings"));
|
||||
launcherSettingsItem.setOnAction(e -> Controllers.navigate(Controllers.getSettingsPage()));
|
||||
|
||||
// sixth item in left sidebar
|
||||
AdvancedListItem chatItem = new AdvancedListItem();
|
||||
chatItem.setLeftGraphic(wrap(SVG.CHAT));
|
||||
chatItem.setActionButtonVisible(false);
|
||||
chatItem.setTitle(i18n("chat"));
|
||||
chatItem.setOnAction(e -> FXUtils.openLink("https://docs.hmcl.net/groups.html"));
|
||||
|
||||
// the left sidebar
|
||||
AdvancedListBox sideBar = new AdvancedListBox()
|
||||
.startCategory(i18n("account").toUpperCase(Locale.ROOT))
|
||||
@@ -198,8 +191,9 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
.add(gameItem)
|
||||
.add(downloadItem)
|
||||
.startCategory(i18n("settings.launcher.general").toUpperCase(Locale.ROOT))
|
||||
.add(multiplayerItem)
|
||||
.add(launcherSettingsItem);
|
||||
.add(launcherSettingsItem)
|
||||
.add(chatItem)
|
||||
;
|
||||
|
||||
// the root page, with the sidebar in left, navigator in center.
|
||||
setLeft(sideBar);
|
||||
|
||||
@@ -27,11 +27,12 @@ import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.upgrade.hmcl.RemoteVersion;
|
||||
import org.jackhuang.hmcl.upgrade.hmcl.UpdateChannel;
|
||||
import org.jackhuang.hmcl.upgrade.hmcl.UpdateChecker;
|
||||
import org.jackhuang.hmcl.upgrade.hmcl.UpdateHandler;
|
||||
import org.jackhuang.hmcl.upgrade.RemoteVersion;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChannel;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateHandler;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.i18n.Locales;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
@@ -132,7 +133,7 @@ public final class SettingsPage extends SettingsView {
|
||||
try {
|
||||
Files.write(logFile, Logging.getRawLogs());
|
||||
} catch (IOException e) {
|
||||
Platform.runLater(() -> Controllers.dialog(i18n("settings.launcher.launcher_log.export.failed") + "\n" + e, null, MessageType.ERROR));
|
||||
Platform.runLater(() -> Controllers.dialog(i18n("settings.launcher.launcher_log.export.failed") + "\n" + StringUtils.getStackTrace(e), null, MessageType.ERROR));
|
||||
LOG.log(Level.WARNING, "Failed to export logs", e);
|
||||
return;
|
||||
}
|
||||
@@ -144,7 +145,7 @@ public final class SettingsPage extends SettingsView {
|
||||
|
||||
@Override
|
||||
protected void onSponsor() {
|
||||
FXUtils.openLink("https://hmcl.huangyuhui.net/api/redirect/sponsor");
|
||||
FXUtils.openLink("https://github.com/HMCL-dev/HMCL");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -37,6 +37,7 @@ import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.ComponentList;
|
||||
import org.jackhuang.hmcl.ui.construct.ComponentSublist;
|
||||
import org.jackhuang.hmcl.ui.construct.MultiFileItem;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.i18n.Locales.SupportedLocale;
|
||||
|
||||
import java.util.Arrays;
|
||||
@@ -176,8 +177,8 @@ public abstract class SettingsView extends StackPane {
|
||||
languagePane.setLeft(left);
|
||||
|
||||
cboLanguage = new JFXComboBox<>();
|
||||
cboLanguage.setConverter(stringConverter(locale -> locale.getName(config().getLocalization().getResourceBundle())));
|
||||
FXUtils.setLimitWidth(cboLanguage, 400);
|
||||
cboLanguage.setConverter(stringConverter(locale -> locale.getName(I18n.getCurrentLocale().getResourceBundle())));
|
||||
FXUtils.setLimitWidth(cboLanguage, 300);
|
||||
languagePane.setRight(cboLanguage);
|
||||
|
||||
settingsPane.getContent().add(languagePane);
|
||||
|
||||
@@ -1,174 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.main;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.jfoenix.controls.JFXListCell;
|
||||
import com.jfoenix.controls.JFXListView;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.VPos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.text.TextAlignment;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.util.Holder;
|
||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class SponsorPage extends StackPane {
|
||||
private final JFXListView<Sponsor> listView;
|
||||
|
||||
public SponsorPage() {
|
||||
VBox content = new VBox();
|
||||
content.setPadding(new Insets(10));
|
||||
content.setSpacing(10);
|
||||
content.setFillWidth(true);
|
||||
|
||||
{
|
||||
StackPane sponsorPane = new StackPane();
|
||||
sponsorPane.getStyleClass().add("card");
|
||||
sponsorPane.setCursor(Cursor.HAND);
|
||||
sponsorPane.setOnMouseClicked(e -> onSponsor());
|
||||
|
||||
GridPane gridPane = new GridPane();
|
||||
|
||||
ColumnConstraints col = new ColumnConstraints();
|
||||
col.setHgrow(Priority.SOMETIMES);
|
||||
col.setMaxWidth(Double.POSITIVE_INFINITY);
|
||||
|
||||
gridPane.getColumnConstraints().setAll(col);
|
||||
|
||||
RowConstraints row = new RowConstraints();
|
||||
row.setMinHeight(Double.NEGATIVE_INFINITY);
|
||||
row.setValignment(VPos.TOP);
|
||||
row.setVgrow(Priority.SOMETIMES);
|
||||
gridPane.getRowConstraints().setAll(row);
|
||||
|
||||
{
|
||||
Label label = new Label(i18n("sponsor.hmcl"));
|
||||
label.setWrapText(true);
|
||||
label.setTextAlignment(TextAlignment.JUSTIFY);
|
||||
GridPane.setRowIndex(label, 0);
|
||||
GridPane.setColumnIndex(label, 0);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
|
||||
sponsorPane.getChildren().setAll(gridPane);
|
||||
content.getChildren().add(sponsorPane);
|
||||
}
|
||||
|
||||
{
|
||||
StackPane pane = new StackPane();
|
||||
pane.getStyleClass().add("card");
|
||||
listView = new JFXListView<>();
|
||||
Holder<Object> lastCell = new Holder<>();
|
||||
listView.setCellFactory((listView) -> new JFXListCell<Sponsor>() {
|
||||
@Override
|
||||
public void updateItem(Sponsor item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
// https://mail.openjdk.org/pipermail/openjfx-dev/2022-July/034764.html
|
||||
if (this == lastCell.value && !isVisible())
|
||||
return;
|
||||
lastCell.value = this;
|
||||
|
||||
if (!empty) {
|
||||
setText(item.getName());
|
||||
setGraphic(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
VBox.setVgrow(pane, Priority.ALWAYS);
|
||||
pane.getChildren().setAll(listView);
|
||||
content.getChildren().add(pane);
|
||||
}
|
||||
|
||||
loadSponsorList();
|
||||
|
||||
getChildren().setAll(content);
|
||||
}
|
||||
|
||||
private void onSponsor() {
|
||||
FXUtils.openLink("https://hmcl.huangyuhui.net/api/redirect/sponsor");
|
||||
}
|
||||
|
||||
private void loadSponsorList() {
|
||||
Task.<List<Sponsor>>supplyAsync(() -> HttpRequest.GET("https://hmcl.huangyuhui.net/api/sponsor").getJson(new TypeToken<List<Sponsor>>() {
|
||||
}.getType())).thenAcceptAsync(Schedulers.javafx(), sponsors -> {
|
||||
listView.getItems().setAll(sponsors);
|
||||
}).start();
|
||||
}
|
||||
|
||||
private static class Sponsor {
|
||||
@SerializedName("name")
|
||||
private final String name;
|
||||
|
||||
@SerializedName("create_time")
|
||||
private final Date createTime;
|
||||
|
||||
@SerializedName("money")
|
||||
private final BigDecimal money;
|
||||
|
||||
@SerializedName("contact")
|
||||
private final String contact;
|
||||
|
||||
@SerializedName("afdian_id")
|
||||
private final String afdianId;
|
||||
|
||||
public Sponsor() {
|
||||
this("", new Date(), BigDecimal.ZERO, "", "");
|
||||
}
|
||||
|
||||
public Sponsor(String name, Date createTime, BigDecimal money, String contact, String afdianId) {
|
||||
this.name = name;
|
||||
this.createTime = createTime;
|
||||
this.money = money;
|
||||
this.contact = contact;
|
||||
this.afdianId = afdianId;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Date getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public BigDecimal getMoney() {
|
||||
return money;
|
||||
}
|
||||
|
||||
public String getContact() {
|
||||
return contact;
|
||||
}
|
||||
|
||||
public String getAfdianId() {
|
||||
return afdianId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,6 @@ import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.*;
|
||||
import org.jackhuang.hmcl.game.GameVersion;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
import org.jackhuang.hmcl.mod.RemoteModRepository;
|
||||
@@ -57,8 +56,8 @@ import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -163,24 +162,23 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
retrySearch = null;
|
||||
setLoading(true);
|
||||
setFailed(false);
|
||||
File versionJar = StringUtils.isNotBlank(version.get().getVersion())
|
||||
? version.get().getProfile().getRepository().getVersionJar(version.get().getVersion())
|
||||
: null;
|
||||
|
||||
if (executor != null && !executor.isCancelled()) {
|
||||
executor.cancel();
|
||||
}
|
||||
|
||||
executor = Task.supplyAsync(() -> {
|
||||
String gameVersion;
|
||||
if (StringUtils.isBlank(version.get().getVersion())) {
|
||||
gameVersion = userGameVersion;
|
||||
Profile.ProfileVersion version = this.version.get();
|
||||
if (StringUtils.isBlank(version.getVersion())) {
|
||||
return userGameVersion;
|
||||
} else {
|
||||
gameVersion = GameVersion.minecraftVersion(versionJar).orElse("");
|
||||
return StringUtils.isNotBlank(version.getVersion())
|
||||
? version.getProfile().getRepository().getGameVersion(version.getVersion()).orElse("")
|
||||
: "";
|
||||
}
|
||||
return gameVersion;
|
||||
}).thenApplyAsync(gameVersion -> {
|
||||
return repository.search(gameVersion, category, pageOffset, 50, searchFilter, sort, RemoteModRepository.SortOrder.DESC);
|
||||
}).whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
}).thenApplyAsync(gameVersion ->
|
||||
repository.search(gameVersion, category, pageOffset, 50, searchFilter, sort, RemoteModRepository.SortOrder.DESC)
|
||||
).whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
setLoading(false);
|
||||
if (exception == null) {
|
||||
items.setAll(result.getResults().collect(Collectors.toList()));
|
||||
@@ -295,7 +293,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
JFXComboBox<String> gameVersionField = new JFXComboBox<>();
|
||||
gameVersionField.setMaxWidth(Double.MAX_VALUE);
|
||||
gameVersionField.setEditable(true);
|
||||
gameVersionField.getItems().setAll(RemoteModRepository.DEFAULT_GAME_VERSIONS);
|
||||
gameVersionField.getItems().setAll(GameVersionNumber.getDefaultGameVersions());
|
||||
Label lblGameVersion = new Label(i18n("world.game_version"));
|
||||
searchPane.addRow(rowIndex++, new Label(i18n("mods.name")), nameField, lblGameVersion, gameVersionField);
|
||||
|
||||
@@ -415,7 +413,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
searchAction.handle(event);
|
||||
});
|
||||
lastPageButton.setDisable(true);
|
||||
control.pageCount.addListener((observable, oldValue, newValue) -> lastPageButton.setDisable(control.pageCount.get() == -1));
|
||||
control.pageCount.addListener((observable, oldValue, newValue) -> lastPageButton.setDisable(control.pageCount.get() == -1 || control.pageOffset.get() >= control.pageCount.get() - 1));
|
||||
|
||||
Pane placeholder = new Pane();
|
||||
HBox.setHgrow(placeholder, Priority.SOMETIMES);
|
||||
|
||||
@@ -32,9 +32,9 @@ import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.mod.ModLoaderType;
|
||||
import org.jackhuang.hmcl.mod.ModManager;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
import org.jackhuang.hmcl.mod.RemoteModRepository;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
@@ -51,7 +51,7 @@ import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
@@ -254,13 +254,13 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
openMcmodButton.setMinWidth(Region.USE_PREF_SIZE);
|
||||
runInFX(() -> FXUtils.installFastTooltip(openMcmodButton, i18n("mods.mcmod")));
|
||||
|
||||
if (StringUtils.isNotBlank(getSkinnable().mod.getMcbbs())) {
|
||||
JFXHyperlink openMcbbsButton = new JFXHyperlink(i18n("mods.mcbbs"));
|
||||
openMcbbsButton.setExternalLink(ModManager.getMcbbsUrl(getSkinnable().mod.getMcbbs()));
|
||||
descriptionPane.getChildren().add(openMcbbsButton);
|
||||
openMcbbsButton.setMinWidth(Region.USE_PREF_SIZE);
|
||||
runInFX(() -> FXUtils.installFastTooltip(openMcbbsButton, i18n("mods.mcbbs")));
|
||||
}
|
||||
// if (StringUtils.isNotBlank(getSkinnable().mod.getMcbbs())) {
|
||||
// JFXHyperlink openMcbbsButton = new JFXHyperlink(i18n("mods.mcbbs"));
|
||||
// openMcbbsButton.setExternalLink(ModManager.getMcbbsUrl(getSkinnable().mod.getMcbbs()));
|
||||
// descriptionPane.getChildren().add(openMcbbsButton);
|
||||
// openMcbbsButton.setMinWidth(Region.USE_PREF_SIZE);
|
||||
// runInFX(() -> FXUtils.installFastTooltip(openMcbbsButton, i18n("mods.mcbbs")));
|
||||
// }
|
||||
}
|
||||
|
||||
JFXHyperlink openUrlButton = new JFXHyperlink(control.page.getLocalizedOfficialPage());
|
||||
@@ -292,8 +292,9 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
if (control.versions == null) return;
|
||||
|
||||
if (control.version.getProfile() != null && control.version.getVersion() != null) {
|
||||
Version game = control.version.getProfile().getRepository().getResolvedPreservingPatchesVersion(control.version.getVersion());
|
||||
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(game);
|
||||
HMCLGameRepository repository = control.version.getProfile().getRepository();
|
||||
Version game = repository.getResolvedPreservingPatchesVersion(control.version.getVersion());
|
||||
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(game, repository.getGameVersion(game).orElse(null));
|
||||
libraryAnalyzer.getVersion(LibraryAnalyzer.LibraryType.MINECRAFT).ifPresent(currentGameVersion -> {
|
||||
Set<ModLoaderType> currentGameModLoaders = libraryAnalyzer.getModLoaders();
|
||||
if (control.versions.containsKey(currentGameVersion)) {
|
||||
@@ -309,7 +310,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
}
|
||||
|
||||
for (String gameVersion : control.versions.keys().stream()
|
||||
.sorted(Collections.reverseOrder(VersionNumber::compare))
|
||||
.sorted(Collections.reverseOrder(GameVersionNumber::compare))
|
||||
.collect(Collectors.toList())) {
|
||||
ComponentList sublist = new ComponentList(() ->
|
||||
control.versions.get(gameVersion).stream()
|
||||
@@ -382,7 +383,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
TwoLineListItem content = new TwoLineListItem();
|
||||
HBox.setHgrow(content, Priority.ALWAYS);
|
||||
content.setTitle(dataItem.getName());
|
||||
content.setSubtitle(FORMATTER.format(dataItem.getDatePublished().toInstant()));
|
||||
content.setSubtitle(FORMATTER.format(dataItem.getDatePublished()));
|
||||
|
||||
switch (dataItem.getVersionType()) {
|
||||
case Alpha:
|
||||
|
||||
@@ -59,10 +59,10 @@ public class GameItem extends Control {
|
||||
this.version = id;
|
||||
|
||||
// GameVersion.minecraftVersion() is a time-costing job (up to ~200 ms)
|
||||
CompletableFuture.supplyAsync(() -> profile.getRepository().getGameVersion(id).orElse(i18n("message.unknown")), POOL_VERSION_RESOLVE)
|
||||
CompletableFuture.supplyAsync(() -> profile.getRepository().getGameVersion(id), POOL_VERSION_RESOLVE)
|
||||
.thenAcceptAsync(game -> {
|
||||
StringBuilder libraries = new StringBuilder(game);
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(profile.getRepository().getResolvedPreservingPatchesVersion(id));
|
||||
StringBuilder libraries = new StringBuilder(game.orElse(i18n("message.unknown")));
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(profile.getRepository().getResolvedPreservingPatchesVersion(id), game.orElse(null));
|
||||
for (LibraryAnalyzer.LibraryMark mark : analyzer) {
|
||||
String libraryId = mark.getLibraryId();
|
||||
String libraryVersion = mark.getLibraryVersion();
|
||||
|
||||
@@ -72,7 +72,7 @@ public class InstallerListPage extends ListPageBase<InstallerItem> implements Ve
|
||||
CompletableFuture.supplyAsync(() -> {
|
||||
gameVersion = profile.getRepository().getGameVersion(version).orElse(null);
|
||||
|
||||
return LibraryAnalyzer.analyze(profile.getRepository().getResolvedPreservingPatchesVersion(versionId));
|
||||
return LibraryAnalyzer.analyze(profile.getRepository().getResolvedPreservingPatchesVersion(versionId), gameVersion);
|
||||
}).thenAcceptAsync(analyzer -> {
|
||||
Function<String, Runnable> removeAction = libraryId -> () -> {
|
||||
profile.getDependency().removeLibraryAsync(version, libraryId)
|
||||
|
||||
@@ -24,6 +24,8 @@ import javafx.collections.ObservableList;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.mod.LocalModFile;
|
||||
import org.jackhuang.hmcl.mod.ModManager;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
@@ -84,7 +86,9 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
this.profile = profile;
|
||||
this.versionId = id;
|
||||
|
||||
libraryAnalyzer = LibraryAnalyzer.analyze(profile.getRepository().getResolvedPreservingPatchesVersion(id));
|
||||
HMCLGameRepository repository = profile.getRepository();
|
||||
Version resolved = repository.getResolvedPreservingPatchesVersion(id);
|
||||
libraryAnalyzer = LibraryAnalyzer.analyze(resolved, repository.getGameVersion(resolved).orElse(null));
|
||||
modded.set(libraryAnalyzer.hasModLoader());
|
||||
loadMods(profile.getRepository().getModManager(id));
|
||||
}
|
||||
|
||||
@@ -223,7 +223,7 @@ class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
}
|
||||
} else {
|
||||
String lowerQueryString = queryString.toLowerCase(Locale.ROOT);
|
||||
predicate = s -> s.contains(lowerQueryString);
|
||||
predicate = s -> s.toLowerCase(Locale.ROOT).contains(lowerQueryString);
|
||||
}
|
||||
|
||||
// Do we need to search in the background thread?
|
||||
@@ -391,14 +391,14 @@ class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
getActions().add(officialPageButton);
|
||||
}
|
||||
|
||||
if (modInfo.getMod() != null && StringUtils.isNotBlank(modInfo.getMod().getMcbbs())) {
|
||||
JFXHyperlink mcbbsButton = new JFXHyperlink(i18n("mods.mcbbs"));
|
||||
mcbbsButton.setOnAction(e -> {
|
||||
fireEvent(new DialogCloseEvent());
|
||||
FXUtils.openLink(ModManager.getMcbbsUrl(modInfo.getMod().getMcbbs()));
|
||||
});
|
||||
getActions().add(mcbbsButton);
|
||||
}
|
||||
// if (modInfo.getMod() != null && StringUtils.isNotBlank(modInfo.getMod().getMcbbs())) {
|
||||
// JFXHyperlink mcbbsButton = new JFXHyperlink(i18n("mods.mcbbs"));
|
||||
// mcbbsButton.setOnAction(e -> {
|
||||
// fireEvent(new DialogCloseEvent());
|
||||
// FXUtils.openLink(ModManager.getMcbbsUrl(modInfo.getMod().getMcbbs()));
|
||||
// });
|
||||
// getActions().add(mcbbsButton);
|
||||
// }
|
||||
|
||||
if (modInfo.getMod() == null || StringUtils.isBlank(modInfo.getMod().getMcmod())) {
|
||||
JFXHyperlink searchButton = new JFXHyperlink(i18n("mods.mcmod.search"));
|
||||
|
||||
@@ -18,13 +18,11 @@
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import org.jackhuang.hmcl.mod.RemoteModRepository;
|
||||
import org.jackhuang.hmcl.upgrade.resource.RemoteResourceManager;
|
||||
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.io.InputStream;
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -38,19 +36,19 @@ import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
* @see <a href="https://www.mcmod.cn">mcmod.cn</a>
|
||||
*/
|
||||
public enum ModTranslations {
|
||||
MOD("/assets/mod_data.txt", "translation", "mod_data", "1") {
|
||||
MOD("/assets/mod_data.txt") {
|
||||
@Override
|
||||
public String getMcmodUrl(Mod mod) {
|
||||
return String.format("https://www.mcmod.cn/class/%s.html", mod.getMcmod());
|
||||
}
|
||||
},
|
||||
MODPACK("/assets/modpack_data.txt", "translation", "modpack_data", "1") {
|
||||
MODPACK("/assets/modpack_data.txt") {
|
||||
@Override
|
||||
public String getMcmodUrl(Mod mod) {
|
||||
return String.format("https://www.mcmod.cn/modpack/%s.html", mod.getMcmod());
|
||||
}
|
||||
},
|
||||
EMPTY("", "", "", "") {
|
||||
EMPTY("") {
|
||||
@Override
|
||||
public String getMcmodUrl(Mod mod) {
|
||||
return "";
|
||||
@@ -68,18 +66,15 @@ public enum ModTranslations {
|
||||
}
|
||||
}
|
||||
|
||||
private final String defaultResourceName;
|
||||
private final RemoteResourceManager.RemoteResourceKey remoteResourceKey;
|
||||
private final String resourceName;
|
||||
private List<Mod> mods;
|
||||
private Map<String, Mod> modIdMap; // mod id -> mod
|
||||
private Map<String, Mod> curseForgeMap; // curseforge id -> mod
|
||||
private List<Pair<String, Mod>> keywords;
|
||||
private int maxKeywordLength = -1;
|
||||
|
||||
ModTranslations(String defaultResourceName, String namespace, String name, String version) {
|
||||
this.defaultResourceName = defaultResourceName;
|
||||
|
||||
remoteResourceKey = RemoteResourceManager.get(namespace, name, version, () -> ModTranslations.class.getResourceAsStream(defaultResourceName));
|
||||
ModTranslations(String resourceName) {
|
||||
this.resourceName = resourceName;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -120,24 +115,19 @@ public enum ModTranslations {
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private boolean loaded() {
|
||||
private boolean loadFromResource() {
|
||||
if (mods != null) return true;
|
||||
if (StringUtils.isBlank(defaultResourceName)) {
|
||||
if (StringUtils.isBlank(resourceName)) {
|
||||
mods = Collections.emptyList();
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
InputStream inputStream = remoteResourceKey.getResource();
|
||||
if (inputStream == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String modData = IOUtils.readFullyAsString(inputStream);
|
||||
String modData = IOUtils.readFullyAsString(ModTranslations.class.getResourceAsStream(resourceName));
|
||||
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 " + defaultResourceName, e);
|
||||
LOG.log(Level.WARNING, "Failed to load " + resourceName, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -148,7 +138,7 @@ public enum ModTranslations {
|
||||
}
|
||||
|
||||
if (mods == null) {
|
||||
if (!loaded()) return false;
|
||||
if (!loadFromResource()) return false;
|
||||
}
|
||||
|
||||
curseForgeMap = new HashMap<>();
|
||||
@@ -166,7 +156,7 @@ public enum ModTranslations {
|
||||
}
|
||||
|
||||
if (mods == null) {
|
||||
if (!loaded()) return false;
|
||||
if (!loadFromResource()) return false;
|
||||
}
|
||||
|
||||
modIdMap = new HashMap<>();
|
||||
@@ -186,7 +176,7 @@ public enum ModTranslations {
|
||||
}
|
||||
|
||||
if (mods == null) {
|
||||
if (!loaded()) return false;
|
||||
if (!loadFromResource()) return false;
|
||||
}
|
||||
|
||||
keywords = new ArrayList<>();
|
||||
|
||||
@@ -46,7 +46,7 @@ import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
@@ -611,10 +611,10 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
|
||||
Task.composeAsync(Schedulers.javafx(), () -> {
|
||||
if (versionId == null) {
|
||||
return versionSetting.getJavaVersion(VersionNumber.asVersion("Unknown"), null);
|
||||
return versionSetting.getJavaVersion(GameVersionNumber.unknown(), null);
|
||||
} else {
|
||||
return versionSetting.getJavaVersion(
|
||||
VersionNumber.asVersion(GameVersion.minecraftVersion(profile.getRepository().getVersionJar(versionId)).orElse("Unknown")),
|
||||
GameVersionNumber.asGameVersion(profile.getRepository().getGameVersion(versionId)),
|
||||
profile.getRepository().getVersion(versionId));
|
||||
}
|
||||
}).thenAcceptAsync(Schedulers.javafx(), javaVersion -> javaSublist.setSubtitle(Optional.ofNullable(javaVersion)
|
||||
|
||||
@@ -29,7 +29,7 @@ import org.jackhuang.hmcl.game.World;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.wizard.SinglePageWizardProvider;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.Instant;
|
||||
@@ -94,8 +94,7 @@ public class WorldListItem extends Control {
|
||||
|
||||
public void manageDatapacks() {
|
||||
if (world.getGameVersion() == null || // old game will not write game version to level.dat
|
||||
(VersionNumber.isIntVersionNumber(world.getGameVersion()) // we don't parse snapshot version
|
||||
&& VersionNumber.asVersion(world.getGameVersion()).compareTo(VersionNumber.asVersion("1.13")) < 0)) {
|
||||
GameVersionNumber.compare(world.getGameVersion(), "1.13") < 0) {
|
||||
Controllers.dialog(i18n("world.datapack.1_13"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ public class WorldListPage extends ListPageBase<WorldListItem> implements Versio
|
||||
return Arrays.asList(chkShowAll,
|
||||
createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, skinnable::refresh),
|
||||
createToolbarButton2(i18n("world.add"), SVG.PLUS, skinnable::add),
|
||||
createToolbarButton2(i18n("world.download"), SVG.PLUS, skinnable::download));
|
||||
createToolbarButton2(i18n("world.download"), SVG.DOWNLOAD_OUTLINE, skinnable::download));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
* 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.upgrade.hmcl;
|
||||
package org.jackhuang.hmcl.upgrade;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -15,7 +15,7 @@
|
||||
* 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.upgrade.hmcl;
|
||||
package org.jackhuang.hmcl.upgrade;
|
||||
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.util.Pack200Utils;
|
||||
@@ -15,7 +15,7 @@
|
||||
* 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.upgrade.hmcl;
|
||||
package org.jackhuang.hmcl.upgrade;
|
||||
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.util.DigestUtils;
|
||||
@@ -15,7 +15,7 @@
|
||||
* 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.upgrade.hmcl;
|
||||
package org.jackhuang.hmcl.upgrade;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
@@ -38,10 +38,11 @@ public class RemoteVersion {
|
||||
String jarHash = Optional.ofNullable(response.get("jarsha1")).map(JsonElement::getAsString).orElse(null);
|
||||
String packXZUrl = Optional.ofNullable(response.get("packxz")).map(JsonElement::getAsString).orElse(null);
|
||||
String packXZHash = Optional.ofNullable(response.get("packxzsha1")).map(JsonElement::getAsString).orElse(null);
|
||||
boolean force = Optional.ofNullable(response.get("force")).map(JsonElement::getAsBoolean).orElse(false);
|
||||
if (Pack200Utils.isSupported() && packXZUrl != null && packXZHash != null) {
|
||||
return new RemoteVersion(channel, version, packXZUrl, Type.PACK_XZ, new IntegrityCheck("SHA-1", packXZHash));
|
||||
return new RemoteVersion(channel, version, packXZUrl, Type.PACK_XZ, new IntegrityCheck("SHA-1", packXZHash), force);
|
||||
} else if (jarUrl != null && jarHash != null) {
|
||||
return new RemoteVersion(channel, version, jarUrl, Type.JAR, new IntegrityCheck("SHA-1", jarHash));
|
||||
return new RemoteVersion(channel, version, jarUrl, Type.JAR, new IntegrityCheck("SHA-1", jarHash), force);
|
||||
} else {
|
||||
throw new IOException("No download url is available");
|
||||
}
|
||||
@@ -55,13 +56,15 @@ public class RemoteVersion {
|
||||
private final String url;
|
||||
private final Type type;
|
||||
private final IntegrityCheck integrityCheck;
|
||||
private final boolean force;
|
||||
|
||||
public RemoteVersion(UpdateChannel channel, String version, String url, Type type, IntegrityCheck integrityCheck) {
|
||||
public RemoteVersion(UpdateChannel channel, String version, String url, Type type, IntegrityCheck integrityCheck, boolean force) {
|
||||
this.channel = channel;
|
||||
this.version = version;
|
||||
this.url = url;
|
||||
this.type = type;
|
||||
this.integrityCheck = integrityCheck;
|
||||
this.force = force;
|
||||
}
|
||||
|
||||
public UpdateChannel getChannel() {
|
||||
@@ -84,6 +87,10 @@ public class RemoteVersion {
|
||||
return integrityCheck;
|
||||
}
|
||||
|
||||
public boolean isForce() {
|
||||
return force;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" + version + " from " + url + "]";
|
||||
@@ -15,7 +15,7 @@
|
||||
* 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.upgrade.hmcl;
|
||||
package org.jackhuang.hmcl.upgrade;
|
||||
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
* 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.upgrade.hmcl;
|
||||
package org.jackhuang.hmcl.upgrade;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
@@ -24,6 +24,7 @@ import javafx.beans.property.*;
|
||||
import javafx.beans.value.ObservableBooleanValue;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
@@ -32,25 +33,27 @@ import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Lang.thread;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.versioning.VersionNumber.asVersion;
|
||||
|
||||
public final class UpdateChecker {
|
||||
private UpdateChecker() {}
|
||||
|
||||
private static ObjectProperty<RemoteVersion> latestVersion = new SimpleObjectProperty<>();
|
||||
private static BooleanBinding outdated = Bindings.createBooleanBinding(
|
||||
private static final ObjectProperty<RemoteVersion> latestVersion = new SimpleObjectProperty<>();
|
||||
private static final BooleanBinding outdated = Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
RemoteVersion latest = latestVersion.get();
|
||||
if (latest == null || isDevelopmentVersion(Metadata.VERSION)) {
|
||||
return false;
|
||||
} else if (latest.isForce()
|
||||
|| Metadata.isNightly()
|
||||
|| latest.getChannel() == UpdateChannel.NIGHTLY
|
||||
|| latest.getChannel() != UpdateChannel.getChannel()) {
|
||||
return !latest.getVersion().equals(Metadata.VERSION);
|
||||
} else {
|
||||
// We can update from development version to stable version,
|
||||
// which can be downgrading.
|
||||
return asVersion(latest.getVersion()).compareTo(asVersion(Metadata.VERSION)) != 0;
|
||||
return VersionNumber.compare(Metadata.VERSION, latest.getVersion()) < 0;
|
||||
}
|
||||
},
|
||||
latestVersion);
|
||||
private static ReadOnlyBooleanWrapper checkingUpdate = new ReadOnlyBooleanWrapper(false);
|
||||
private static final ReadOnlyBooleanWrapper checkingUpdate = new ReadOnlyBooleanWrapper(false);
|
||||
|
||||
public static void init() {
|
||||
requestCheckUpdate(UpdateChannel.getChannel());
|
||||
@@ -15,7 +15,7 @@
|
||||
* 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.upgrade.hmcl;
|
||||
package org.jackhuang.hmcl.upgrade;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonParseException;
|
||||
@@ -1,204 +0,0 @@
|
||||
/*
|
||||
* 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.upgrade.resource;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.versions.ModTranslations;
|
||||
import org.jackhuang.hmcl.upgrade.hmcl.IntegrityChecker;
|
||||
import org.jackhuang.hmcl.util.DigestUtils;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalSupplier;
|
||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class RemoteResourceManager {
|
||||
private RemoteResourceManager() {
|
||||
}
|
||||
|
||||
private static final class RemoteResource {
|
||||
@SerializedName("sha1")
|
||||
private final String sha1;
|
||||
|
||||
@SerializedName("urls")
|
||||
private final String[] urls;
|
||||
|
||||
private transient byte[] data = null;
|
||||
|
||||
private RemoteResource(String sha1, String[] urls) {
|
||||
this.sha1 = sha1;
|
||||
this.urls = urls;
|
||||
}
|
||||
|
||||
public void download(Path path, Runnable callback) {
|
||||
if (data != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
new FileDownloadTask(Arrays.stream(urls).map(NetworkUtils::toURL).collect(Collectors.toList()), path.toFile(), new FileDownloadTask.IntegrityCheck("SHA-1", sha1))
|
||||
.whenComplete(Schedulers.defaultScheduler(), (result, exception) -> {
|
||||
if (exception != null) {
|
||||
data = Files.readAllBytes(path);
|
||||
callback.run();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
public static final class RemoteResourceKey {
|
||||
private final String namespace;
|
||||
private final String name;
|
||||
private final String version;
|
||||
private final Path cachePath;
|
||||
private final ExceptionalSupplier<InputStream, IOException> localResourceSupplier;
|
||||
private String localResourceSha1 = null;
|
||||
|
||||
public RemoteResourceKey(String namespace, String name, String version, ExceptionalSupplier<InputStream, IOException> localResourceSupplier) {
|
||||
this.namespace = namespace;
|
||||
this.name = name;
|
||||
this.version = version;
|
||||
this.localResourceSupplier = localResourceSupplier;
|
||||
|
||||
this.cachePath = Metadata.HMCL_DIRECTORY.resolve("remoteResources").resolve(namespace).resolve(name).resolve(version).resolve(String.format("%s-%s-%s.resource", namespace, name, version));
|
||||
}
|
||||
|
||||
private InputStream getLocalResource() throws IOException {
|
||||
if (Files.isReadable(cachePath)) {
|
||||
return Files.newInputStream(cachePath);
|
||||
}
|
||||
return localResourceSupplier.get();
|
||||
}
|
||||
|
||||
private String getLocalResourceSha1() throws IOException {
|
||||
if (localResourceSha1 == null) {
|
||||
localResourceSha1 = DigestUtils.digestToString("SHA-1", IOUtils.readFullyAsByteArray(getLocalResource()));
|
||||
}
|
||||
|
||||
return localResourceSha1;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private RemoteResource getRemoteResource() {
|
||||
return Optional.ofNullable(remoteResources.get(namespace)).map(map -> map.get(name)).map(map -> map.get(version)).orElse(null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public InputStream getResource() throws IOException {
|
||||
RemoteResource remoteResource = getRemoteResource();
|
||||
|
||||
if (remoteResource == null) {
|
||||
return getLocalResource();
|
||||
}
|
||||
|
||||
if (remoteResource.sha1.equals(getLocalResourceSha1())) {
|
||||
return getLocalResource();
|
||||
}
|
||||
|
||||
if (remoteResource.data == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ByteArrayInputStream(remoteResource.data);
|
||||
}
|
||||
|
||||
public void downloadRemoteResourceIfNecessary() throws IOException {
|
||||
RemoteResource remoteResource = getRemoteResource();
|
||||
|
||||
if (remoteResource == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (remoteResource.sha1.equals(getLocalResourceSha1())) {
|
||||
return;
|
||||
}
|
||||
|
||||
remoteResource.download(cachePath, () -> localResourceSha1 = null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
RemoteResourceKey that = (RemoteResourceKey) o;
|
||||
|
||||
if (!namespace.equals(that.namespace)) return false;
|
||||
if (!name.equals(that.name)) return false;
|
||||
return version.equals(that.version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = namespace.hashCode();
|
||||
result = 31 * result + name.hashCode();
|
||||
result = 31 * result + version.hashCode();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private static final Map<String, Map<String, Map<String, RemoteResource>>> remoteResources = new ConcurrentHashMap<>();
|
||||
|
||||
private static final Map<String, RemoteResourceKey> keys = new ConcurrentHashMap<>();
|
||||
|
||||
public static void init() {
|
||||
Task.<Map<String, Map<String, Map<String, RemoteResource>>>>supplyAsync(() ->
|
||||
IntegrityChecker.isSelfVerified() ? HttpRequest.GET(Metadata.RESOURCE_UPDATE_URL).getJson(
|
||||
new TypeToken<Map<String, Map<String, Map<String, RemoteResource>>>>() {
|
||||
}.getType()
|
||||
) : null
|
||||
).whenComplete(Schedulers.defaultScheduler(), (result, exception) -> {
|
||||
if (exception == null && result != null) {
|
||||
remoteResources.clear();
|
||||
remoteResources.putAll(result);
|
||||
|
||||
for (RemoteResourceKey key : keys.values()) {
|
||||
key.downloadRemoteResourceIfNecessary();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
public static void register() {
|
||||
ModTranslations.values();
|
||||
}
|
||||
|
||||
public static RemoteResourceKey get(@NotNull String namespace, @NotNull String name, @NotNull String version, ExceptionalSupplier<InputStream, IOException> defaultSupplier) {
|
||||
String stringKey = String.format("%s:%s:%s", namespace, name, version);
|
||||
RemoteResourceKey key = keys.containsKey(stringKey) ? keys.get(stringKey) : new RemoteResourceKey(namespace, name, version, defaultSupplier);
|
||||
Task.runAsync(key::downloadRemoteResourceIfNecessary).start();
|
||||
keys.put(stringKey, key);
|
||||
return key;
|
||||
}
|
||||
}
|
||||
@@ -23,8 +23,8 @@ 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.hmcl.IntegrityChecker;
|
||||
import org.jackhuang.hmcl.upgrade.hmcl.UpdateChecker;
|
||||
import org.jackhuang.hmcl.upgrade.IntegrityChecker;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
|
||||
public final class HMCLService {
|
||||
private HMCLService() {
|
||||
}
|
||||
|
||||
public static void openRedirectLink(String id) {
|
||||
FXUtils.openLink("https://hmcl.huangyuhui.net/api/redirect/" + id);
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.Platform;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
@@ -45,7 +45,7 @@ public final class NativePatcher {
|
||||
|
||||
public static Version patchNative(Version version, String gameVersion, JavaVersion javaVersion, VersionSetting settings) {
|
||||
if (settings.getNativesDirType() == NativesDirectoryType.CUSTOM) {
|
||||
if (gameVersion != null && VersionNumber.compare(gameVersion, "1.19") < 0)
|
||||
if (gameVersion != null && GameVersionNumber.compare(gameVersion, "1.19") < 0)
|
||||
return version;
|
||||
|
||||
ArrayList<Library> newLibraries = new ArrayList<>();
|
||||
@@ -66,7 +66,7 @@ public final class NativePatcher {
|
||||
final boolean useNativeOpenAL = settings.isUseNativeOpenAL();
|
||||
|
||||
if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() && (useNativeGLFW || useNativeOpenAL)
|
||||
&& VersionNumber.compare(gameVersion, "1.19") >= 0) {
|
||||
&& GameVersionNumber.compare(gameVersion, "1.19") >= 0) {
|
||||
|
||||
version = version.setLibraries(version.getLibraries().stream()
|
||||
.filter(library -> {
|
||||
@@ -88,7 +88,7 @@ public final class NativePatcher {
|
||||
|
||||
OperatingSystem os = javaVersion.getPlatform().getOperatingSystem();
|
||||
Architecture arch = javaVersion.getArchitecture();
|
||||
VersionNumber gameVersionNumber = gameVersion != null ? VersionNumber.asVersion(gameVersion) : null;
|
||||
GameVersionNumber gameVersionNumber = gameVersion != null ? GameVersionNumber.asGameVersion(gameVersion) : null;
|
||||
|
||||
if (settings.isNotPatchNatives())
|
||||
return version;
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.util.i18n;
|
||||
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.util.i18n.Locales.SupportedLocale;
|
||||
|
||||
import java.time.Instant;
|
||||
@@ -34,14 +33,14 @@ public final class I18n {
|
||||
private I18n() {
|
||||
}
|
||||
|
||||
private static volatile SupportedLocale locale = Locales.DEFAULT;
|
||||
|
||||
public static void setLocale(SupportedLocale locale) {
|
||||
I18n.locale = locale;
|
||||
}
|
||||
|
||||
public static SupportedLocale getCurrentLocale() {
|
||||
try {
|
||||
return ConfigHolder.config().getLocalization();
|
||||
} catch (IllegalStateException e) {
|
||||
// e is thrown by ConfigHolder.config(), indicating the config hasn't been loaded
|
||||
// fallback to use default locale
|
||||
return Locales.DEFAULT;
|
||||
}
|
||||
return locale;
|
||||
}
|
||||
|
||||
public static ResourceBundle getResourceBundle() {
|
||||
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 16 KiB |
@@ -30,16 +30,19 @@ about.dependency=Third-party Libraries
|
||||
about.legal=Legal Acknowledgement
|
||||
about.thanks_to=Thanks to
|
||||
about.thanks_to.bangbang93.statement=For providing BMCLAPI download API, please consider donating.
|
||||
about.thanks_to.burningtnt.statement=Contribute a lot of technical support to HMCL.
|
||||
about.thanks_to.contributors=All contributors on GitHub
|
||||
about.thanks_to.contributors.statement=Without the awesome open-source community, Hello Minecraft! Launcher would not make it so far.
|
||||
about.thanks_to.gamerteam.statement=For providing the default background image.
|
||||
about.thanks_to.glavo.statement=Contribute a lot of technical support to HMCL.
|
||||
about.thanks_to.zkitefly.statement=Contribute a lot of technical support to HMCL.
|
||||
about.thanks_to.glavo.statement=Responsible for maintaining HMCL.
|
||||
about.thanks_to.zekerzhayard.statement=Contribute a lot of technical support to HMCL.
|
||||
about.thanks_to.zkitefly.statement=Responsible for maintaining documentation of HMCL.
|
||||
about.thanks_to.mcbbs=MCBBS (Minecraft Chinese Forum)
|
||||
about.thanks_to.mcbbs.statement=For providing mcbbs.net download mirror for Mainland China users.
|
||||
about.thanks_to.mcmod=mcmod.cn
|
||||
about.thanks_to.mcmod.statement=For providing Chinese translations and wiki for various mods.
|
||||
about.thanks_to.red_lnn.statement=For providing the default background image.
|
||||
about.thanks_to.shulkersakura.statement=For providing the icon for HMCL
|
||||
about.thanks_to.users=HMCL User Group Members
|
||||
about.thanks_to.users.statement=Thanks for donations, bug reports, and so on.
|
||||
about.thanks_to.yushijinhun.statement=For providing authlib-injector related support.
|
||||
@@ -52,7 +55,6 @@ account.character=character
|
||||
account.choose=Choose a Character
|
||||
account.create=Add an account
|
||||
account.create.microsoft=Add a Microsoft account
|
||||
account.create.yggdrasil=Add a Mojang account
|
||||
account.create.offline=Add an offline account
|
||||
account.create.authlibInjector=Create an authlib-injector account
|
||||
account.email=Email
|
||||
@@ -116,6 +118,7 @@ If the token used to log in to the Microsoft account is leaked, you can click on
|
||||
If you encounter any problems, you can click the help button in the upper right corner for help.
|
||||
account.methods.microsoft.makegameidsettings=Create Profile / Edit Profile Name
|
||||
account.methods.microsoft.profile=Account Profile
|
||||
account.methods.microsoft.purchase=Buy Minecraft
|
||||
account.methods.microsoft.snapshot=You are using an unofficial build of HMCL. Please download the official build for login.
|
||||
account.methods.microsoft.snapshot.website=Official Website
|
||||
account.methods.offline=Offline
|
||||
@@ -129,14 +132,7 @@ account.methods.offline.uuid.hint=UUID is the unique identifier for the game cha
|
||||
\n\
|
||||
This option is for advanced users only. We do not recommend modifying this option unless you know what you are doing.
|
||||
account.methods.offline.uuid.malformed=Invalid Format
|
||||
account.methods.yggdrasil=Mojang
|
||||
account.methods.forgot_password=Forgot Password
|
||||
account.methods.yggdrasil.migration=Migrate to Microsoft Account
|
||||
account.methods.yggdrasil.migration.how=How to migrate to Microsoft Account?
|
||||
account.methods.yggdrasil.migration.hint=Since March 10, 2022, Mojang accounts are no longer supported, and you are required to migrate to a Microsoft account in order to play Minecraft.\n\
|
||||
\n\
|
||||
The link down below will guide you to migrate your Mojang account to a Microsoft account.
|
||||
account.methods.yggdrasil.purchase=Buy Minecraft
|
||||
account.missing=No Accounts
|
||||
account.missing.add=Click here to add one.
|
||||
account.move_to_global=Convert to global account
|
||||
@@ -189,6 +185,8 @@ button.save_as=Save As
|
||||
button.select_all=Select All
|
||||
button.yes=Yes
|
||||
|
||||
chat=Join Group Chat
|
||||
|
||||
color.recent=Recommended
|
||||
color.custom=Custom Color
|
||||
crash.NoClassDefFound=Please verify the integrity of this software, or try updating your Java.
|
||||
@@ -204,7 +202,7 @@ curse.category.4477=Mini Game
|
||||
curse.category.4478=Quests
|
||||
curse.category.4484=Multiplayer
|
||||
curse.category.4476=Exploration
|
||||
curse.category.6145=Skyblock
|
||||
curse.category.4736=Skyblock
|
||||
curse.category.4475=Adventure and RPG
|
||||
curse.category.4487=FTB
|
||||
curse.category.4480=Map Based
|
||||
@@ -219,6 +217,11 @@ curse.category.5299=Education
|
||||
curse.category.5232=Galacticraft
|
||||
curse.category.5129=Vanilla+
|
||||
curse.category.5189=Utility & QOL
|
||||
curse.category.6814=Performance
|
||||
curse.category.6954=Integrated Dynamics
|
||||
curse.category.6484=Create
|
||||
curse.category.6821=Bug Fixes
|
||||
curse.category.6145=Skyblock
|
||||
curse.category.5190=QoL
|
||||
curse.category.5191=Utility & QoL
|
||||
curse.category.5192=FancyMenu
|
||||
@@ -334,7 +337,7 @@ download.java=Downloading Java
|
||||
download.javafx=Downloading dependencies for the launcher...
|
||||
download.javafx.notes=We are currently downloading dependencies for HMCL from the Internet.\n\
|
||||
\n\
|
||||
You can click on 'Change Download Mirror' to select the download mirror or click on "Cancel" to stop and exit.\n\
|
||||
You can click on 'Change Download Source' to select the download source or\nclick on 'Cancel' to stop and exit.\n\
|
||||
Note: If your download speed is too slow, you can try switching to another mirror.
|
||||
download.javafx.component=Downloading module %s
|
||||
download.javafx.prepare=Preparing to download
|
||||
@@ -356,9 +359,7 @@ HMCL cannot automatically install JavaFX under Java versions below 11.\n\
|
||||
Please update your Java to version 11 or higher.
|
||||
fatal.javafx.incomplete=The JavaFX environment is incomplete.\n\
|
||||
Please try replacing your Java or reinstalling OpenJFX.
|
||||
fatal.javafx.missing=Missing JavaFX environment.\n\
|
||||
If you are using Java 11 or higher, please downgrade it to Oracle JRE 8 (java.com), or install BellSoft Liberica Full JRE (bell-sw.com/pages/downloads/?package\=jre-full).\n\
|
||||
Or, if you are using OpenJDK distributions, please make sure it has OpenJFX included.
|
||||
fatal.javafx.missing=Missing JavaFX environment. Please launch Hello Minecraft! Launcher with a Java which includes OpenJFX.
|
||||
fatal.config_change_owner_root=You are using the root account to start Hello Minecraft! Launcher, this may cause you to fail to start Hello Minecraft! Launcher with other account in the future.\n\
|
||||
Do you still want to continue?
|
||||
fatal.config_in_temp_dir=You are start Hello Minecraft! Launcher in a temporary directory, your settings and game data may be lost.\n\
|
||||
@@ -400,7 +401,7 @@ feedback.github.statement=Creating an issue on GitHub.
|
||||
feedback.kookapp=KOOK
|
||||
feedback.kookapp.statement=Join our KOOK community.
|
||||
feedback.qq_group=HMCL User Group
|
||||
feedback.qq_group.statement=Join HMCL user QQ group through donation.
|
||||
feedback.qq_group.statement=Join HMCL user QQ group.
|
||||
|
||||
file=File
|
||||
|
||||
@@ -522,6 +523,7 @@ game.crash.reason.forgemod_resolution=The game crashed due to mod resolution fai
|
||||
\n\
|
||||
Forge provided the following details:\n\
|
||||
%1$s
|
||||
game.crash.reason.forge_found_duplicate_mods=The game cannot continue due to a duplicate mods issue. Forge provides the following information: \n%1$s
|
||||
game.crash.reason.mod_resolution_collection=The game crashed because the mod version is not compatible.\n\
|
||||
\n\
|
||||
%1$s requires %2$s.\n\
|
||||
@@ -570,7 +572,7 @@ If you still encounter these problems, you may need a better computer.
|
||||
game.crash.reason.resolution_too_high=The game crashed because you are using a resource pack whose texture resolution was too high.\n\
|
||||
\n\
|
||||
You should switch to a resource pack with lower resolution, or consider buying a better graphics card with more VRAM.
|
||||
game.crash.reason.processing_of_javaagent_failed=The current game crashed because processing of -javaagent failed.\nIf you add relevant parameters to the Java virtual machine parameters, please check whether they are legal and correct.\nIf you do not add relevant parameters or confirm that they are legal and correct, Please try:\nOpen the control panel -- Clock and region classification (this option is only available if the option is category display, and it will be skipped if not) -- Region -- the upper management tab -- the lower change system regional setting button -- turn off the "Use Unicode UTF-8 to provide global language support" option in the pop-up window, restart the device, and then try to start the game.\nYou can be accessed in <a href="https://discord.gg/jVvC7HfM6U">Discard</a> or <a href="https://kook.top/Kx7n3t">KOOK</a> ask for help.
|
||||
game.crash.reason.processing_of_javaagent_failed=The current game crashed because processing of -javaagent failed.\nIf you add relevant parameters to the Java virtual machine parameters, please check whether they are legal and correct.\nIf you do not add relevant parameters or confirm that they are legal and correct, Please try:\nOpen the control panel -- Clock and region classification (this option is only available if the option is category display, and it will be skipped if not) -- Region -- the upper management tab -- the lower change system regional setting button -- turn off the "Use Unicode UTF-8 to provide global language support" option in the pop-up window, restart the device, and then try to start the game.\nYou can be accessed in <a href="https://discord.gg/jVvC7HfM6U">Discard</a> or <a href="https://docs.hmcl.net/groups.html">QQ</a> ask for help.
|
||||
game.crash.reason.stacktrace=The crash reason is unknown. You can view its details by clicking the "Logs" button.\n\
|
||||
\n\
|
||||
There are some keywords that might contain some Mod Names. You can search them online to figure out the issue yourself.\n\
|
||||
@@ -730,7 +732,7 @@ logwindow.export_game_crash_logs=Export Crash Logs
|
||||
logwindow.export_dump.dependency_ok.button=Export Game Stack Dump
|
||||
logwindow.export_dump.dependency_ok.doing_button=Exporting Game Stack Dump (May take up to 15 seconds)
|
||||
logwindow.export_dump.no_dependency.button=Export Game Stack Dump (Not compatible)
|
||||
logwindow.export_dump.no_dependency.tooltip=Your Java does not contain the dependencies to create the stack dump. Please turn to HMCL KOOK or HMCL Discord for help.
|
||||
logwindow.export_dump.no_dependency.tooltip=Your Java does not contain the dependencies to create the stack dump. Please turn to HMCL QQ group or HMCL Discord for help.
|
||||
|
||||
main_page=Home
|
||||
|
||||
@@ -908,10 +910,6 @@ mods.restore=Rollback
|
||||
mods.url=Official Page
|
||||
mods.update_modpack_mod.warning=Updating mods in a modpack can lead to irreparable results, possibly corrupting the modpack so that it cannot start. Are you sure you want to update?
|
||||
|
||||
multiplayer=Multiplayer online
|
||||
multiplayer.hint=Multiplayer online function is under maintenance.
|
||||
multiplayer.hint.details=View details
|
||||
|
||||
nbt.entries=%s entries
|
||||
nbt.open.failed=Fail to open file
|
||||
nbt.save.failed=Fail to save file
|
||||
@@ -1122,7 +1120,7 @@ settings.launcher.download_source.auto=Auto Choose Download Mirror
|
||||
settings.launcher.enable_game_list=Show version list in home page
|
||||
settings.launcher.font=Font
|
||||
settings.launcher.general=General
|
||||
settings.launcher.language=Language
|
||||
settings.launcher.language=Language (applies after restart)
|
||||
settings.launcher.launcher_log.export=Export launcher logs
|
||||
settings.launcher.launcher_log.export.failed=Unable to export logs
|
||||
settings.launcher.launcher_log.export.success=Logs have been exported to %s
|
||||
@@ -1165,8 +1163,8 @@ settings.type.special.edit=Edit Current Instance Settings
|
||||
settings.type.special.edit.hint=Current instance [%s] has enabled per-instance settings, all options on this page will NOT affect that instance. Click here to modify its own options.
|
||||
|
||||
sponsor=Donators
|
||||
sponsor.bmclapi=Downloads are provided by BMCLAPI and MCBBS. Click here for more information.
|
||||
sponsor.hmcl=Hello Minecraft! Launcher is a FOSS Minecraft launcher which allows users to manage multiple Minecraft instances easily. You can donate to us on Afdian to support our file hosting and project development. Click here for more information.
|
||||
sponsor.bmclapi=Downloads are provided by BMCLAPI. Click here for more information.
|
||||
sponsor.hmcl=Hello Minecraft! Launcher is a FOSS Minecraft launcher which allows users to manage multiple Minecraft instances easily. Click here for more information.
|
||||
|
||||
system.architecture=Architecture
|
||||
system.operating_system=Operating System
|
||||
@@ -1179,14 +1177,14 @@ update.changelog=Changelog
|
||||
update.channel.dev=Beta
|
||||
update.channel.dev.hint=You are currently using a beta build of the launcher, which may include some extra features, but is also sometimes more unstable than the release versions.\n\
|
||||
\n\
|
||||
If you encounter any bug or issue, you can go to <a href="hmcl://settings/feedback">the feedback page</a> to report it, or tell us in our <a href="https://discord.gg/jVvC7HfM6U">Discord</a> or <a href="https://kook.top/Kx7n3t">KOOK</a> community.\n\
|
||||
If you encounter any bug or issue, you can go to <a href="hmcl://settings/feedback">the feedback page</a> to report it, or tell us in our <a href="https://discord.gg/jVvC7HfM6U">Discord</a> or <a href="https://docs.hmcl.net/groups.html">QQ</a> community.\n\
|
||||
\n\
|
||||
<a href="hmcl://hide-announcement">Click here</a> to hide this tooltip for the current version.
|
||||
update.channel.dev.title=Beta Version Notice
|
||||
update.channel.nightly=Nightly
|
||||
update.channel.nightly.hint=You are currently using a nightly build of the launcher, which may include some extra features, but is also always more unstable than the other versions.\n\
|
||||
\n\
|
||||
If you encounter any bug or issue, you can go to <a href="hmcl://settings/feedback">the feedback page</a> to report it, or tell us in our <a href="https://discord.gg/jVvC7HfM6U">Discord</a> or <a href="https://kook.top/Kx7n3t">KOOK</a> community.
|
||||
If you encounter any bug or issue, you can go to <a href="hmcl://settings/feedback">the feedback page</a> to report it, or tell us in our <a href="https://discord.gg/jVvC7HfM6U">Discord</a> or <a href="https://docs.hmcl.net/groups.html">QQ</a> community.
|
||||
update.channel.nightly.title=Nightly Version Notice
|
||||
update.channel.stable=Release
|
||||
update.checking=Checking for Updates
|
||||
|
||||
@@ -34,6 +34,7 @@ about.thanks_to.contributors=Todos los colaboradores en GitHub
|
||||
about.thanks_to.contributors.statement=Sin la impresionante comunidad de código abierto, Hello Minecraft! Launcher no habría llegado tan lejos.
|
||||
about.thanks_to.gamerteam.statement=Por proporcionar la imagen de fondo por defecto.
|
||||
about.thanks_to.glavo.statement=Aportar mucho apoyo técnico a HMCL.
|
||||
about.thanks_to.zekerzhayard.statement=Aportar mucho apoyo técnico a HMCL.
|
||||
about.thanks_to.zkitefly.statement=Aportar mucho apoyo técnico a HMCL.
|
||||
about.thanks_to.mcbbs=MCBBS (Foro chino de Minecraft)
|
||||
about.thanks_to.mcbbs.statement=Por proporcionar un espejo de descarga de mcbbs.net para los usuarios de China continental.
|
||||
@@ -52,7 +53,6 @@ account.character=personaje
|
||||
account.choose=Elige un personaje
|
||||
account.create=Añadir una cuenta
|
||||
account.create.microsoft=Añadir una cuenta Microsoft
|
||||
account.create.yggdrasil=Añadir una cuenta de Mojang
|
||||
account.create.offline=Añadir una cuenta offline
|
||||
account.create.authlibInjector=Crear una cuenta authlib-injector
|
||||
account.email=Correo electrónico
|
||||
@@ -108,6 +108,7 @@ account.methods.microsoft.manual=El código de su dispositivo es <b>%1$s</b>, po
|
||||
Si el token utilizado para acceder a la cuenta de Microsoft se ha filtrado, puedes hacer clic en "Desautorizar la cuenta" para desautorizarla.\n\
|
||||
Si encuentra algún problema, puede hacer clic en el botón de ayuda en la esquina superior derecha para obtener ayuda.
|
||||
account.methods.microsoft.profile=Perfil de la cuenta
|
||||
account.methods.microsoft.purchase=Comprar Minecraft
|
||||
account.methods.microsoft.snapshot=Estás usando una construcción no oficial de hmcls, por favor descargue la construcción oficial para iniciar sesión en microsoft.
|
||||
account.methods.offline=Sin conexión
|
||||
account.methods.offline.uuid=UUID
|
||||
@@ -116,14 +117,7 @@ account.methods.offline.uuid.hint=UUID es el identificador único del personaje
|
||||
Esta opción es sólo para usuarios avanzados. No recomendamos modificar esta opción a menos que sepas lo que estás haciendo.\n\
|
||||
Esta opción no es necesaria para unirse a servidores.
|
||||
account.methods.offline.uuid.malformed=Formato no válido
|
||||
account.methods.yggdrasil=Mojang
|
||||
account.methods.forgot_password=OLVIDÉ MI CONTRASEÑA
|
||||
account.methods.yggdrasil.migration=Migrar a cuenta Microsoft
|
||||
account.methods.yggdrasil.migration.how=¿Cómo migrar a la Cuenta Microsoft?
|
||||
account.methods.yggdrasil.migration.hint=Desde el 10 de marzo de 2022, las cuentas de Mojang ya no son compatibles, y es necesario migrar a una cuenta de Microsoft para poder jugar a Minecraft.\n
|
||||
\n\
|
||||
El enlace de abajo te guiará para migrar tu cuenta de Mojang a una cuenta de Microsoft.
|
||||
account.methods.yggdrasil.purchase=Comprar Minecraft
|
||||
account.missing=No hay cuentas
|
||||
account.missing.add=Haz clic aquí para añadir una.
|
||||
account.not_logged_in=No ha iniciado sesión
|
||||
@@ -187,7 +181,7 @@ curse.category.4477=Mini Juego
|
||||
curse.category.4478=Quests
|
||||
curse.category.4484=Multijugador
|
||||
curse.category.4476=Exploración
|
||||
curse.category.6145=Skyblock
|
||||
curse.category.4736=Skyblock
|
||||
curse.category.4475=Aventura y RPG
|
||||
curse.category.4487=FTB
|
||||
curse.category.4480=Basado en mapa
|
||||
@@ -203,6 +197,11 @@ curse.category.5232=Galacticraft
|
||||
curse.category.5129=Vanilla+
|
||||
curse.category.5189=Utilidad y calidad de vida
|
||||
curse.category.5190=Calidad de vida
|
||||
curse.category.6145=Skyblock
|
||||
curse.category.6814=Performance
|
||||
curse.category.6954=Integrated Dynamics
|
||||
curse.category.6484=Create
|
||||
curse.category.6821=Bug Fixes
|
||||
curse.category.5191=Utilidad y calidad de vida
|
||||
curse.category.5192=Menú elegante
|
||||
curse.category.423=Mapa e información
|
||||
@@ -314,7 +313,7 @@ download.provider.mirror=Desde espejo
|
||||
download.javafx=Descargar las dependencias para el launcher...
|
||||
download.javafx.notes=Estamos descargando dependencias para HMCL desde Internet.\n\
|
||||
\n\
|
||||
Puede hacer clic en "Cambiar el espejo de descarga" para seleccionar el espejo de descarga o hacer clic en "Cancelar" para detener y salir.\n\
|
||||
Puede hacer clic en "Cambiar fuente de descarga" para seleccionar el espejo de descarga o hacer clic en "Cancelar" para detener y salir.\n\
|
||||
Nota: Si su velocidad de descarga es demasiado lenta, puede intentar cambiar a otro espejo.
|
||||
download.javafx.component=Descargando módulo %s
|
||||
download.javafx.prepare=Preparando la descarga
|
||||
@@ -451,6 +450,7 @@ game.crash.reason.forgemod_resolution=El juego se ha bloqueado debido a un fallo
|
||||
\n\
|
||||
Forge proporcionó los siguientes detalles\:\n\
|
||||
%1$s
|
||||
game.crash.reason.forge_found_duplicate_mods=El juego no puede continuar debido a un problema de duplicación de mods. Forge proporciona la siguiente información: \n%1$s
|
||||
game.crash.reason.night_config_fixes=El juego actual no puede seguir ejecutándose debido a algunos problemas con Night Config. \nPuedes intentar instalar el mod <a href="https://www.curseforge.com/minecraft/mc-mods/night-config-fixes">Night Config Fixes</a>, que puede ayudarte con este problema. \nPara obtener más información, visite el <a href="https://www.github.com/Fuzss/nightconfigfixes">repositorio de GitHub</a> del mod.
|
||||
game.crash.reason.mod_resolution_collection=El juego se ha bloqueado porque la versión del mod no es compatible.\n\
|
||||
\n\
|
||||
@@ -484,7 +484,7 @@ game.crash.reason.mod_profile_causes_game_crash=El juego actual no puede seguir
|
||||
game.crash.reason.fabric_reports_an_error_and_gives_a_solution=Forge puede haber dado un mensaje de error.\nPuede ver el registro y realizar las acciones correspondientes de acuerdo con la información de registro en el informe de errores.\nSi no ve un mensaje de error, puede consultar el informe de errores para saber cómo ocurrió el error.
|
||||
game.crash.reason.java_version_is_too_high=El juego actual se bloqueó porque la versión de Java es demasiado alta y no puede seguir ejecutándose.\nCambie a una versión anterior de Java en la pestaña Java Path de Configuración global del juego o Configuración específica del juego antes de iniciar el juego.\nSi no es así, puede descargarlo desde <a href="https://www.java.com/download/">java.com (Java8)</a> o <a href="https://bell-sw.com/pages/downloads/#downloads">BellSoft Liberica Full JRE (Java17)</a> y otras plataformas para descargar e instalar una (reinicie el iniciador después de la instalación).
|
||||
game.crash.reason.mod_name=El juego actual no puede continuar ejecutándose debido a un problema con el nombre del archivo mod. El nombre del archivo de\nMod solo debe usar mayúsculas y minúsculas (Aa ~ Zz), números (0 ~ 9), líneas horizontales (-), subrayado (_) y puntos (.) en toda la mitad del inglés.\n Por favor, vaya a la carpeta mod para agregar un carácter de Cumplimiento anterior a todos los nombres de archivo mod que no cumplan.
|
||||
game.crash.reason.incomplete_forge_installation=O jogo atual não pode continuar devido a uma instalação incompleta do Forge / NeoForge.\nDesinstale e reinstale o Forge em Configurações de versão - Instalação automática.
|
||||
game.crash.reason.incomplete_forge_installation=O jogo atual não pode continuar devido a uma instalação incompleta do Forge / NeoForge.\nDesinstale e reinstale o Forge / NeoForge em Configurações de versão - Instalação automática.
|
||||
game.crash.reason.forge_error=Forge puede haber proporcionado un mensaje de error. \nPuede ver el registro y realizar las acciones correspondientes de acuerdo con la información de registro en el informe de errores. \nSi no ve un mensaje de error, puede consultar el informe de errores para saber cómo ocurrió el error.\n%1$s
|
||||
game.crash.reason.mod_solution0=El juego actual no puede seguir ejecutándose debido a algunos problemas de modificación. \nPuede consultar el registro para ver si hay un mod incorrecto.
|
||||
game.crash.reason.mod_profile_causes_game_crash=El juego actual no puede seguir ejecutándose debido a un problema con el perfil de mod. \nPuede consultar el registro del mod defectuoso y su archivo de configuración.
|
||||
@@ -689,7 +689,7 @@ logwindow.export_game_crash_logs=Exportar registros de errores
|
||||
logwindow.export_dump.dependency_ok.button=Exportar volcado de pila de juegos
|
||||
logwindow.export_dump.dependency_ok.doing_button=Exportación de volcado de pila de juego (puede tardar hasta 15 segundos)
|
||||
logwindow.export_dump.no_dependency.button=Exportar volcado de pila de juegos (no compatible)
|
||||
logwindow.export_dump.no_dependency.tooltip=Su Java no contiene las dependencias para crear el volcado de pila. Dirígete a HMCL KOOK o HMCL Discord para obtener ayuda.
|
||||
logwindow.export_dump.no_dependency.tooltip=Su Java no contiene las dependencias para crear el volcado de pila. Dirígete a HMCL QQ o HMCL Discord para obtener ayuda.
|
||||
|
||||
|
||||
main_page=Inicio
|
||||
@@ -838,8 +838,6 @@ mods.restore=Restaurar
|
||||
mods.url=Página oficial
|
||||
mods.update_modpack_mod.warning=Actualizar mods en un modpack puede generar resultados irreparables, posiblemente corrompiendo el modpack para que no pueda iniciarse. ¿Seguro que quieres actualizar?
|
||||
|
||||
multiplayer=Multijugador
|
||||
|
||||
datapack=Datapacks
|
||||
datapack.add=Instalar datapacks
|
||||
datapack.choose_datapack=Seleccionar un paquet de datos para importar
|
||||
@@ -1042,7 +1040,7 @@ settings.type.special.edit=Editar ajustes de la instancia actual
|
||||
settings.type.special.edit.hint=La instancia actual [%s] tiene activada la configuración por instancia, todas las opciones de esta página NO afectarán a esa instancia. Haga clic aquí para modificar sus propias opciones.
|
||||
|
||||
sponsor=Donantes
|
||||
sponsor.bmclapi=Las descargas son proporcionadas por BMCLAPI y MCBBS. Haz clic aquí para obtener más información.
|
||||
sponsor.bmclapi=Las descargas son proporcionadas por BMCLAPI. Haz clic aquí para obtener más información.
|
||||
sponsor.hmcl=¡Hello Minecraft! Launcher es un launcher FOSS de Minecraft que permite a los usuarios gestionar múltiples instancias de Minecraft fácilmente. Usted puede donar a nosotros en Afdian para apoyar nuestro alojamiento de archivos y el desarrollo del proyecto. Haga clic aquí para obtener más información.
|
||||
|
||||
system.architecture=Arquitectura
|
||||
@@ -1054,14 +1052,14 @@ update.changelog=Registro de cambios
|
||||
update.channel.dev=Beta
|
||||
update.channel.dev.hint=Actualmente está utilizando una versión beta del launcher, que puede incluir algunas características adicionales, pero también es más inestable que las versiones de lanzamiento.\n\
|
||||
\n\
|
||||
Si encuentra algún error o problema, puede ir a la <a href="hmcl://settings/feedback">página de comentarios</a> para informar de ello, o bien para decírnoslo en nuestro <a href="https://discord.gg/jVvC7HfM6U">Discord</a> o en la comunidad <a href="https://kookapp.co/Kx7n3t">KOOK</a>.\n\
|
||||
Si encuentra algún error o problema, puede ir a la <a href="hmcl://settings/feedback">página de comentarios</a> para informar de ello, o bien para decírnoslo en nuestro <a href="https://discord.gg/jVvC7HfM6U">Discord</a> o en la comunidad <a href="https://docs.hmcl.net/groups.html">QQ</a>.\n\
|
||||
\n\
|
||||
<a href="hmcl://hide-announcement">Haga clic aquí</a> para ocultar el recordatorio para la versión de prueba actual.
|
||||
update.channel.dev.title=Aviso de versión beta
|
||||
update.channel.nightly=Nocturna
|
||||
update.channel.nightly.hint=Estás utilizando una versión nocturna del launcher, que puede incluir algunas características adicionales, pero también es siempre más inestable que las otras versiones.\n\
|
||||
\n\
|
||||
Si encuentra algún error o problema, puede ir a la <a href="hmcl://settings/feedback">página de comentarios</a> para informar de ello, o bien para decírnoslo en nuestro <a href="https://discord.gg/jVvC7HfM6U">Discord</a> o en la comunidad <a href="https://kookapp.co/Kx7n3t">KOOK</a> community.
|
||||
Si encuentra algún error o problema, puede ir a la <a href="hmcl://settings/feedback">página de comentarios</a> para informar de ello, o bien para decírnoslo en nuestro <a href="https://discord.gg/jVvC7HfM6U">Discord</a> o en la comunidad <a href="https://docs.hmcl.net/groups.html">QQ</a> community.
|
||||
update.channel.nightly.title=Aviso de versión nocturna
|
||||
update.channel.stable=Estable
|
||||
update.checking=Buscando actualizaciones
|
||||
|
||||
@@ -32,6 +32,7 @@ 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.zekerzhayard.statement=HMCLに多くのテクニカルサポートを提供
|
||||
about.thanks_to.zkitefly.statement=HMCLに多くのテクニカルサポートを提供。
|
||||
about.thanks_to.mcbbs=MCBBS
|
||||
about.thanks_to.mcbbs.statement=MCBBSダウンロードソースプロバイダー。
|
||||
@@ -50,7 +51,6 @@ account.character=キャラクター
|
||||
account.choose=キャラクターを選択する
|
||||
account.create=アカウントを追加
|
||||
account.create.microsoft=Microsoft Login
|
||||
account.create.yggdrasil=Mojang Login
|
||||
account.create.offline=Offline
|
||||
account.create.authlibInjector=サードパーティーアカウントを追加
|
||||
account.email=メールアドレス
|
||||
@@ -99,15 +99,12 @@ account.methods.microsoft.makegameidsettings=プロファイルを作成/プロ
|
||||
account.methods.microsoft.hint=「ログイン」ボタンをクリックして、新しく開いたブラウザウィンドウでログインプロセスを続行する必要があります。\nMicrosoftアカウントへのログインに使用されたトークンが誤って漏洩した場合は、下の[アカウントのバインドを解除]をクリックして、ログイン認証をキャンセルできます。\n問題が発生した場合は、右上隅にあるヘルプ ボタンをクリックするとヘルプが表示されます。
|
||||
account.methods.microsoft.manual=「ログイン」ボタンをクリックした後、新しく開いたブラウザウィンドウで認証を完了する必要があります。ブラウザウィンドウが表示されない場合は、ここをクリックしてURLをコピーし、ブラウザで手動で開くことができます。\nMicrosoftアカウントへのログインに使用されたトークンが誤って漏洩した場合は、下の[アカウントのバインドを解除]をクリックして、ログイン認証をキャンセルできます。\n問題が発生した場合は、右上隅にあるヘルプ ボタンをクリックするとヘルプが表示されます。
|
||||
account.methods.microsoft.profile=アカウントプロファイル..
|
||||
account.methods.microsoft.purchase=Minecraftを購入する
|
||||
account.methods.microsoft.snapshot=非公式構築 HMCL を使用しているので、公式構築をダウンロードしてマイクロソフトにログインしてください。
|
||||
account.methods.offline=オフライン
|
||||
account.methods.offline.uuid=UUID
|
||||
account.methods.offline.uuid.hint=UUIDは、Minecraftのゲームキャラクターの一意の識別子です。UUIDの生成方法は、ゲームランチャーによって異なります。UUIDを他のランチャーによって生成されたものに変更すると、オフラインアカウントのバックパック内のゲームブロック/アイテムが残ることが約束されます。このオプションは専門家向けです。何をしているのかわからない限り、このオプションを変更することはお勧めしません。\nこのオプションはサーバーに参加する場合には必要ありません。
|
||||
account.methods.offline.uuid.malformed=Malformed
|
||||
account.methods.yggdrasil=Mojang
|
||||
account.methods.yggdrasil.migration=Microsoftアカウントに移行する
|
||||
account.methods.yggdrasil.migration.how=Microsoftアカウントに移行する方法は?
|
||||
account.methods.yggdrasil.purchase=Minecraftを購入する
|
||||
account.missing=アカウントなし
|
||||
account.missing.add=追加するにはここをクリック
|
||||
account.not_logged_in=ログインしていません
|
||||
@@ -171,7 +168,7 @@ curse.category.4477=ミニゲーム
|
||||
curse.category.4478=クエスト
|
||||
curse.category.4484=マルチプレイヤー
|
||||
curse.category.4476=探索
|
||||
curse.category.6145=Skyblock
|
||||
curse.category.4736=Skyblock
|
||||
curse.category.4475=アドベンチャーとRPG
|
||||
curse.category.4487=FTB
|
||||
curse.category.4480=マップベース
|
||||
@@ -187,6 +184,11 @@ curse.category.5190=QoL
|
||||
curse.category.5191=Utility & QoL
|
||||
curse.category.5192=FancyMenu
|
||||
curse.category.423=地図と情報
|
||||
curse.category.6814=パフォーマンス
|
||||
curse.category.6484=Create
|
||||
curse.category.6954=Integrated Dynamics
|
||||
curse.category.6145=Skyblock
|
||||
curse.category.6821=バグ修正
|
||||
curse.category.426=Addons
|
||||
curse.category.434=武器、道具、武器
|
||||
curse.category.409=構造
|
||||
@@ -359,7 +361,7 @@ game.crash.reason.mod_profile_causes_game_crash=mod プロファイルに問題
|
||||
game.crash.reason.fabric_reports_an_error_and_gives_a_solution=Forge がエラー メッセージを表示した可能性があります。 \nログを表示し、エラー レポートのログ情報に従って対応するアクションを実行できます。 \nエラー メッセージが表示されない場合は、エラー レポートをチェックして、エラーがどのように発生したかを知ることができます。
|
||||
game.crash.reason.java_version_is_too_high=Java のバージョンが高すぎて実行を継続できないため、現在のゲームがクラッシュしました。 \nゲームを起動する前に、グローバル ゲーム設定またはゲーム固有の設定の Java パス タブで Java の以前のバージョンに切り替えてください。 \nそうでない場合は、<a href="https://www.java.com/download/">java.com (Java8)</a> または <a href="https://bell-sw.com/pages/downloads/#downloads">BellSoft Liberica Full JRE (Java17)</a> およびその他のプラットフォームをダウンロードしてインストールします (インストール後にランチャーを再起動します)。
|
||||
game.crash.reason.mod_name=現在のゲームはModファイル名の問題で続行できません。\nModファイル名は、英文の全半角の大文字と小文字(Aa ~ Zz)、数字(0 ~ 9)、横線(-)、アンダースコア(_)、点(.)のみを使用してください。\n上記のコンプライアンス文字をModフォルダに追加してください。
|
||||
game.crash.reason.incomplete_forge_installation=Forge / NeoForge のインストールが不完全なため、現在のゲームを続行できません。\nバージョン設定 - 自動インストールで Forge をアンインストールしてから再インストールしてください。
|
||||
game.crash.reason.incomplete_forge_installation=Forge / NeoForge のインストールが不完全なため、現在のゲームを続行できません。\nバージョン設定 - 自動インストールで Forge / NeoForge をアンインストールしてから再インストールしてください。
|
||||
game.crash.reason.file_already_exists=ファイル %1$s が既に存在するので、現在のゲームは続行できません。\このファイルを削除してもよいと思われる場合は、このファイルをバックアップして、ゲームを再起動してから削除してみてください。
|
||||
game.crash.reason.need_jdk11=Java 仮想マシンのバージョンが不適切なため、現在のゲームは実行を続行できません。\nJava 11 をダウンロードしてインストールし、グローバル (特定) ゲーム設定で Java を 11 で始まるバージョンに設定する必要があります。
|
||||
game.crash.reason.file_changed=ファイルのチェックサムに失敗したため、現在のゲームを続行できません。\nMinecraft.jar ファイルを手動で変更した場合、変更をロールバックするか、ゲームを再度ダウンロードする必要があります。
|
||||
@@ -378,6 +380,7 @@ game.crash.reason.memory_exceeded=JVMが割り当てるのに十分なメモリ
|
||||
game.crash.reason.mod=modが原因でゲームがクラッシュしました:%1$s。\nmodを更新または削除して、再試行できます。
|
||||
game.crash.reason.mod_resolution=modの解決に失敗したため、ゲームがクラッシュしました。\nFabricは次の情報を提供します:\n%s
|
||||
game.crash.reason.forgemod_resolution=modの解決に失敗したため、ゲームがクラッシュしました。\nForgeは次の情報を提供します:\n%s
|
||||
game.crash.reason.forge_found_duplicate_mods=現在のゲームは、モッズの重複の問題により、続行できません。Forge が次の情報を提供しました:\n%1$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 をダウンロードすることができます。
|
||||
@@ -657,8 +660,6 @@ mods.restore=Restore
|
||||
mods.url=公式ページ
|
||||
mods.update_modpack_mod.warning=modpack 内の mod を更新すると、修復不可能な結果につながる可能性があり、modpack が破損して起動できなくなる可能性があります。更新してもよろしいですか?
|
||||
|
||||
multiplayer=マルチプレイ
|
||||
|
||||
datapack=Datapacks
|
||||
datapack.add=データパックをインストールします
|
||||
datapack.choose_datapack=インポートするデータパックのzipを選択してください
|
||||
@@ -854,7 +855,7 @@ settings.type.special.edit=現在のゲーム設定を構成します
|
||||
settings.type.special.edit.hint=現在のゲームバージョン %s で特殊な設定が有効になっているため、現在のページのオプションはそのゲームバージョンに適用されません。リンクをクリックして、現在のゲーム設定を構成します。
|
||||
|
||||
sponsor=寄付
|
||||
sponsor.bmclapi=ダウンロードサービスはBMCLAPIとMCBBSによって提供されます。詳細については、ここをクリックしてください。
|
||||
sponsor.bmclapi=ダウンロードサービスはBMCLAPIによって提供されます。詳細については、ここをクリックしてください。
|
||||
sponsor.hmcl=こんにちはマインクラフト! Launcherは、無料のオープンソースのMinecraftランチャーであり、ユーザーは複数の個別のMinecraftインストールを簡単に管理できます。私たちはAfdianを使用して、ホスティングとプロジェクト開発の費用を引き続き支払います。詳細については、ここをクリックしてください。
|
||||
|
||||
system.architecture=Arch
|
||||
@@ -866,7 +867,7 @@ update.changelog=変更
|
||||
update.channel.dev=ベータ
|
||||
update.channel.dev.hint=ベータ版を使用しています。ベータ版には、リリースバージョンと比較していくつかの追加機能が含まれている可能性があり、テストにのみ使用されます。\n\
|
||||
ベータ版は不安定なはずです!\n\
|
||||
問題が発生した場合は、<a href="hmcl://settings/feedback">フィードバックページ</a>にアクセスして報告するか、チャット <a href="https://discord.gg/jVvC7HfM6U">Discord</a>または<a href="https://kook.top/Kx7n3t">KOOK</a>で問題を報告します。\n\n\
|
||||
問題が発生した場合は、<a href="hmcl://settings/feedback">フィードバックページ</a>にアクセスして報告するか、チャット <a href="https://discord.gg/jVvC7HfM6U">Discord</a>または<a href="https://docs.hmcl.net/groups.html">QQ</a>で問題を報告します。\n\n\
|
||||
現在のテスト版のヒントを非表示にするには、<a href="hmcl://hide-announcement">ここをクリックしてください</a>。
|
||||
update.channel.dev.title=ベータ版のヒント
|
||||
update.channel.nightly=アルファ
|
||||
|
||||
@@ -34,6 +34,7 @@ about.thanks_to.contributors=Всем участникам на GitHub
|
||||
about.thanks_to.contributors.statement=Без потрясающего сообщества с открытым исходным кодом, Hello Minecraft! Launcher не смог бы добраться так далеко.
|
||||
about.thanks_to.gamerteam.statement=За предоставление фонового изображения.
|
||||
about.thanks_to.glavo.statement=Оказал большую техническую поддержку HMCL.
|
||||
about.thanks_to.zekerzhayard.statement=Оказал большую техническую поддержку HMCL.
|
||||
about.thanks_to.zkitefly.statement=Оказал большую техническую поддержку HMCL.
|
||||
about.thanks_to.mcbbs=MCBBS (Minecraft Chinese Forum)
|
||||
about.thanks_to.mcbbs.statement=За предоставление зеркала скачивания MCBBS.NET для пользователей Китая.
|
||||
@@ -52,7 +53,6 @@ account.character=Персонаж
|
||||
account.choose=Выберите персонажа
|
||||
account.create=Добавить аккаунт
|
||||
account.create.microsoft=Добавить аккаунт Microsoft
|
||||
account.create.yggdrasil=Добавить аккаунт Mojang
|
||||
account.create.offline=Добавить автономный аккаунт
|
||||
account.create.authlibInjector=Создайте аккаунт authlib-injector
|
||||
account.email=Почта
|
||||
@@ -105,21 +105,14 @@ account.methods.microsoft.logging_in=Авторизация...
|
||||
account.methods.microsoft.makegameidsettings=Создать архив / изменение имени профиля
|
||||
account.methods.microsoft.hint=Вам необходимо нажать кнопку «Войти» и продолжить процесс авторизации во вновь открывшемся окне браузера.\nЕсли токен, используемый для входа в аккаунт Microsoft случайно утёк, вы можете нажать «Отменить авторизацию аккаунта» ниже, чтобы отменить авторизацию.\nЕсли у вас возникнут какие-либо проблемы, вы можете нажать кнопку справки в правом верхнем углу для получения помощи.
|
||||
account.methods.microsoft.manual=После нажатия кнопки «Войти», вы должны завершить авторизацию во вновь открывшемся окне браузера. Если окно браузера не открылось, вы можете щёлкнуть здесь, чтобы скопировать ссылку и вручную открыть её в браузере.\nЕсли токен, используемый для входа в аккаунт Microsoft случайно утёк, вы можете нажать «Отменить авторизацию аккаунта» ниже, чтобы отменить авторизацию.\n問題が発生した場合は、右上隅にあるヘルプ ボタンをクリックするとヘルプが表示されます。
|
||||
account.methods.microsoft.profile=Профиль аккаунта...
|
||||
account.methods.microsoft.profile=Профиль аккаунта...
|
||||
account.methods.microsoft.purchase=Купить Minecraft
|
||||
account.methods.forgot_password=ЗАБЫЛ ПАРОЛЬ
|
||||
account.methods.microsoft.snapshot=Вы используете неофициальное построение HMCL, загрузите официальное построение для входа в Microsoft.
|
||||
account.methods.offline=Офлайн
|
||||
account.methods.offline.uuid=UUID
|
||||
account.methods.offline.uuid.hint=UUID - это уникальный идентификатор игрового персонажа в Minecraft. Способ генерации UUID различается в разных лаунчерах игр. Изменение UUID на тот, который генерируется другим лаунчером гарантирует, что игровые блоки/предметы в рюкзаке вашего офлайн аккаунта останутся. Этот параметр предназначен для экспертов. Если вы не знаете что делаете, мы не советуем вам изменять этот параметр.\nЭта опция не требуется для присоединения к серверам.
|
||||
account.methods.offline.uuid.malformed=Malformed
|
||||
account.methods.yggdrasil=Mojang
|
||||
account.methods.forgot_password=Забыли пароль
|
||||
account.methods.yggdrasil.migration=Перейти на аккаунт Microsoft
|
||||
account.methods.yggdrasil.migration.how=Как перейти на аккаунт Microsoft?
|
||||
account.methods.yggdrasil.migration.hint=С 10 марта 2022 года аккаунты Mojang больше не поддерживаются, и для игры в Minecraft необходимо перейти на аккаунт Microsoft.\n\
|
||||
\n\
|
||||
Ссылка ниже поможет перенести ваш аккаунт Mojang на Microsoft.
|
||||
account.methods.yggdrasil.purchase=Купить Minecraft
|
||||
account.missing=Нет аккаунтов
|
||||
account.missing.add=Нажмите здесь, чтобы добавить.
|
||||
account.not_logged_in=Вход не произведён
|
||||
@@ -185,7 +178,7 @@ curse.category.4477=Mini Game
|
||||
curse.category.4478=Quests
|
||||
curse.category.4484=Multiplayer
|
||||
curse.category.4476=Exploration
|
||||
curse.category.6145=Skyblock
|
||||
curse.category.4736=Skyblock
|
||||
curse.category.4475=Adventure and RPG
|
||||
curse.category.4487=FTB
|
||||
curse.category.4480=Map Based
|
||||
@@ -203,6 +196,11 @@ curse.category.5189=Utility & QOL
|
||||
curse.category.5190=QoL
|
||||
curse.category.5191=Utility & QoL
|
||||
curse.category.5192=FancyMenu
|
||||
curse.category.6814=Performance
|
||||
curse.category.6954=Integrated Dynamics
|
||||
curse.category.6484=Create
|
||||
curse.category.6821=Bug Fixes
|
||||
curse.category.6145=Skyblock
|
||||
curse.category.423=Map and Information
|
||||
curse.category.426=Addons
|
||||
curse.category.434=Armor, Tools, and Weapons
|
||||
@@ -315,7 +313,7 @@ download.java=Скачивание Java
|
||||
download.javafx=Скачивание зависимостей лаунчера...
|
||||
download.javafx.notes=Скачиваем зависимостей лаунчера из интернета.\n\
|
||||
\n\
|
||||
Можно нажать «Изменить зеркало скачивания», чтобы выбрать зеркало загрузки, или нажать «Отмена», чтобы остановить и выйти.\n\
|
||||
Можно нажать «Изменить источник скачивания», чтобы выбрать зеркало загрузки, или нажать «Отмена», чтобы остановить и выйти.\n\
|
||||
Примечание\: Если ваша скорость скачивания слишком низкая, вы можете попробовать переключиться на другое зеркало.
|
||||
download.javafx.component=Скачивание %s
|
||||
download.javafx.prepare=Подготовка к скачиванию
|
||||
@@ -401,6 +399,7 @@ game.crash.reason.memory_exceeded=Игра вылетела, потому что
|
||||
game.crash.reason.mod=Игра вылетела из-за мода: %1$s.\nВы можете обновить или удалить мод и повторить попытку.
|
||||
game.crash.reason.mod_resolution=Игра вылетела из-за сбоя разрешения мода.\nFabric даёт некоторую информацию:\n%s
|
||||
game.crash.reason.forgemod_resolution=Игра вылетела из-за сбоя разрешения мода.\nForge даёт некоторую информацию:\n%s
|
||||
game.crash.reason.forge_found_duplicate_mods=В настоящее время игра не может продолжаться из-за проблемы с повторяющимися модами. Forge предоставляет следующую информацию: \n%1$s
|
||||
game.crash.reason.mod_resolution_collection=Игра вылетела из-за несовместимости требуемой версии мода.\n%1$s требует %2$s.\nЭто означает, что вам необходимо обновить или понизить версию %2$s. Вам необходимо скачать %3$s.
|
||||
game.crash.reason.mod_resolution_conflict=Игра вылетела из-за конфликтующих модов.\n%1$s конфликтуют с %2$s.
|
||||
game.crash.reason.mod_resolution_missing=Игра вылетела, потому что мод, от которого зависит другой мод, не установлен.\n%1$s требует установки мода: %2$s.\nЭто означает, что вам необходимо скачать %2$s со страницы скачивания или через сайт.
|
||||
@@ -694,8 +693,6 @@ mods.restore=Откат
|
||||
mods.url=Официальная страница
|
||||
mods.update_modpack_mod.warning=Обновление модов в пакете модов может привести к непоправимым результатам, возможно, повредить пакет модов так, что он не сможет запуститься. Вы уверены, что хотите обновить?
|
||||
|
||||
multiplayer=Сетевая игра
|
||||
|
||||
datapack=Датапаки
|
||||
datapack.add=Установить
|
||||
datapack.choose_datapack=Выберите датапак для импорта
|
||||
@@ -929,7 +926,7 @@ settings.type.special.edit=Изменить настройки текущего
|
||||
settings.type.special.edit.hint=В текущем экземпляре [%s] включены раздельные настройки для экземпляров, параметры на этой странице НЕ повлияют на этот экземпляр. Нажмите здесь, чтобы изменить раздельные параметры.
|
||||
|
||||
sponsor=Спонсоры
|
||||
sponsor.bmclapi=Скачивание предоставляется BMCLAPI и MCBBS. Нажмите здесь для получения подробностей.
|
||||
sponsor.bmclapi=Скачивание предоставляется BMCLAPI. Нажмите здесь для получения подробностей.
|
||||
sponsor.hmcl=Hello Minecraft! Launcher — лаунчер Minecraft с открытым исходным кодом, который позволяет пользователям легко управлять несколькими экземплярами Minecraft. Пожертвуйте нам на Afdian для поддержки нашего файлового хостинга и развития проекта. Для получения подробностей нажмите здесь.
|
||||
|
||||
system.architecture=Архитектура
|
||||
@@ -941,13 +938,13 @@ update.changelog=Изменения
|
||||
update.channel.dev=Бета
|
||||
update.channel.dev.hint=Вы используете бета-версию, которая может включать некоторые дополнительные функции по сравнению с релизной версией, используемой только для тестирования.\n\
|
||||
Бета-версия может быть нестабильной!\n\
|
||||
Если вы встретили какие-то проблемы, вы можете зайти на <a href="hmcl://settings/feedback">страницу обратной связи</a>, чтобы сообщить о них, или присоединиться к чату <a href="https://discord.gg/jVvC7HfM6U">Discord</a> или <a href="https://kook.top/Kx7n3t">KOOK</a>, чтобы сообщить о проблемах.\n\n\
|
||||
Если вы встретили какие-то проблемы, вы можете зайти на <a href="hmcl://settings/feedback">страницу обратной связи</a>, чтобы сообщить о них, или присоединиться к чату <a href="https://discord.gg/jVvC7HfM6U">Discord</a> или <a href="https://docs.hmcl.net/groups.html">QQ</a>, чтобы сообщить о проблемах.\n\n\
|
||||
<a href="hmcl://hide-announcement">Нажмите здесь</a>, чтобы скрыть подсказку для текущей тестовой версии.
|
||||
update.channel.dev.title=Подсказки для бета-версии
|
||||
update.channel.nightly=Альфа
|
||||
update.channel.nightly.hint=Вы используете альфа-версию, которая может включать некоторые дополнительные функциональные возможности по сравнению с бета-версией и версией релиза, используемой только для тестирования.\n\
|
||||
Альфа-версия может быть нестабильной!\n\
|
||||
Если вы встретили какие-то проблемы, вы можете зайти на <a href="hmcl://settings/feedback">страницу обратной связи</a>, чтобы сообщить о них, или присоединиться к чату <a href="https://discord.gg/jVvC7HfM6U">Discord</a> или <a href="https://kook.top/Kx7n3t">KOOK</a>, чтобы сообщить о проблемах.
|
||||
Если вы встретили какие-то проблемы, вы можете зайти на <a href="hmcl://settings/feedback">страницу обратной связи</a>, чтобы сообщить о них, или присоединиться к чату <a href="https://discord.gg/jVvC7HfM6U">Discord</a> или <a href="https://docs.hmcl.net/groups.html">QQ</a>, чтобы сообщить о проблемах.
|
||||
update.channel.nightly.title=Подсказки для альфа-версии
|
||||
update.channel.stable=Релиз
|
||||
update.checking=Проверка наличия обновлений
|
||||
|
||||
@@ -28,16 +28,19 @@ about.dependency=相依元件
|
||||
about.legal=法律聲明
|
||||
about.thanks_to=鳴謝
|
||||
about.thanks_to.bangbang93.statement=提供 BMCLAPI 下載地點。請贊助支持 BMCLAPI!
|
||||
about.thanks_to.burningtnt.statement=為 HMCL 貢獻許多技術支持
|
||||
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.zkitefly.statement=為 HMCL 貢獻許多技術支持
|
||||
about.thanks_to.glavo.statement=負責 HMCL 的日常維護
|
||||
about.thanks_to.zekerzhayard.statement=為 HMCL 貢獻許多技術支持
|
||||
about.thanks_to.zkitefly.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.red_lnn.statement=提供預設背景圖
|
||||
about.thanks_to.shulkersakura.statement=提供 HMCL 的圖標
|
||||
about.thanks_to.users=HMCL 用戶群成員
|
||||
about.thanks_to.users.statement=感謝用戶群成員贊助充電、積極催更、迴響問題、出謀劃策
|
||||
about.thanks_to.yushijinhun.statement=authlib-injector 相关支援
|
||||
@@ -50,7 +53,6 @@ account.character=角色
|
||||
account.choose=請選擇角色
|
||||
account.create=建立帳戶
|
||||
account.create.microsoft=添加微軟帳戶
|
||||
account.create.yggdrasil=添加 Mojang 帳戶
|
||||
account.create.offline=添加離線模式帳戶
|
||||
account.create.authlibInjector=添加外置登入帳戶 (authlib-injector)
|
||||
account.email=電子信箱
|
||||
@@ -114,7 +116,7 @@ account.methods.microsoft.hint=你需要按照以下步驟添加賬戶:\n\
|
||||
5.在網站提示“大功告成”後,只需等待帳戶完成添加即可\n\
|
||||
若網站提示“出现错误”的標識,請嘗試重新按照以上步驟登入\n\
|
||||
-若賬戶添加失敗,請嘗試重新按照以上步驟登入\n\
|
||||
-若登入微軟賬號的 Token 洩露,可點擊下方“解除帳戶授權”解除登入授權,然後等待 Token 過期
|
||||
-若登入微軟賬號的 Token 洩露,可點擊下方“解除帳戶授權”解除登入授權,然後等待 Token 過期\n\
|
||||
如遇到問題,你可以點擊右上角幫助按鈕進行求助。
|
||||
account.methods.microsoft.manual=你需要按照以下步驟添加账户:\n\
|
||||
1.點擊“登入”按鈕\n\
|
||||
@@ -124,9 +126,10 @@ account.methods.microsoft.manual=你需要按照以下步驟添加账户:\n\
|
||||
5.在網站提示“大功告成”後,只需等待帳戶完成添加即可\n\
|
||||
-若網站提示“出现错误”的標識或賬戶添加失敗時,請嘗試重新按照以上步驟登入\n\
|
||||
-若網站未能打開,請手動在網頁瀏覽器中打開:%2$s\n\
|
||||
-若登入微軟賬號的 Token 洩露,可點擊下方“解除帳戶授權”解除登入授權,然後等待 Token 過期
|
||||
-若登入微軟賬號的 Token 洩露,可點擊下方“解除帳戶授權”解除登入授權,然後等待 Token 過期\n\
|
||||
如遇到問題,你可以點擊右上角幫助按鈕進行求助。
|
||||
account.methods.microsoft.profile=帳戶設置頁
|
||||
account.methods.microsoft.purchase=購買 Minecraft
|
||||
account.methods.forgot_password=忘記密碼
|
||||
account.methods.microsoft.snapshot=你正在使用非官方構建的 HMCL,請下載官方構建進行微軟登入。
|
||||
account.methods.microsoft.snapshot.website=官方網站
|
||||
@@ -139,11 +142,6 @@ account.methods.offline.name.invalid=正常情況下,遊戲用戶名只能包
|
||||
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=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=按一下此處加入帳戶
|
||||
account.move_to_global=轉換為全域帳戶
|
||||
@@ -196,6 +194,8 @@ button.save_as=另存為
|
||||
button.select_all=全選
|
||||
button.yes=是
|
||||
|
||||
chat=官方群組
|
||||
|
||||
color.recent=建議
|
||||
color.custom=自訂顏色
|
||||
|
||||
@@ -212,7 +212,7 @@ curse.category.4477=小遊戲
|
||||
curse.category.4478=任務
|
||||
curse.category.4484=多人
|
||||
curse.category.4476=探索
|
||||
curse.category.6145=空島
|
||||
curse.category.4736=空島
|
||||
curse.category.4475=冒險 RPG
|
||||
curse.category.4487=FTB 模組包
|
||||
curse.category.4480=有特定地圖
|
||||
@@ -227,6 +227,11 @@ curse.category.5299=教育
|
||||
curse.category.5232=額外行星
|
||||
curse.category.5129=原版增強
|
||||
curse.category.5189=實用與QOL
|
||||
curse.category.6814=性能
|
||||
curse.category.6954=動態聯合/集成動力 (Integrated Dynamics)
|
||||
curse.category.6484=機械動力 (Create)
|
||||
curse.category.6821=錯誤修復
|
||||
curse.category.6145=空島
|
||||
curse.category.5190=QoL
|
||||
curse.category.5191=實用與QoL
|
||||
curse.category.5192=夢幻菜單
|
||||
@@ -383,7 +388,7 @@ feedback.github.statement=打開一個 GitHub Issue。
|
||||
feedback.kookapp=KOOK
|
||||
feedback.kookapp.statement=歡迎加入 KOOK 討論區,加入後請遵守討論區規定。
|
||||
feedback.qq_group=HMCL 用戶群
|
||||
feedback.qq_group.statement=通過捐贈支持加入 HMCL 用戶 QQ 群。
|
||||
feedback.qq_group.statement=歡迎加入 HMCL 用戶群,加入後請遵守群規。
|
||||
|
||||
file=檔案
|
||||
|
||||
@@ -441,6 +446,7 @@ game.crash.reason.memory_exceeded=當前遊戲因為分配的記憶體過大,
|
||||
game.crash.reason.mod=當前遊戲因為 %1$s 的問題,無法繼續運行。\n你可以更新或刪除已經安裝的 %1$s 再試。
|
||||
game.crash.reason.mod_resolution=當前遊戲因為 Mod 依賴問題,無法繼續運行。Fabric 提供了如下訊息:\n%1$s
|
||||
game.crash.reason.forgemod_resolution=當前遊戲因為 Mod 依賴問題,無法繼續運行。Forge 提供了如下訊息:\n%1$s
|
||||
game.crash.reason.forge_found_duplicate_mods=遊戲崩潰原因模組重複的問題,無法繼續運行。Forge 提供了以下信息:\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 不能相容。
|
||||
game.crash.reason.mod_resolution_missing=當前遊戲因為缺少 Mod 前置,無法繼續運行。\n%1$s 需要前置 Mod:%2$s 才能繼續運行。\n這表示你少安裝了 Mod,或該 Mod 版本不夠。你可以到下載頁的模組下載,或到網路上下載 %2$s。
|
||||
@@ -463,14 +469,13 @@ game.crash.reason.no_such_method_error=當前遊戲因為代碼不完整,無
|
||||
game.crash.reason.opengl_not_supported=當前遊戲因為你的顯示卡驅動存在問題,無法繼續運行。\n原因是 OpenGL 不受支援,你現在是否在遠程桌面或者串流模式下?如果是,請直接使用原電腦啟動遊戲。\n或者嘗試升級你的顯示卡驅動到最新版本後再嘗試啟動遊戲。如果你的電腦存在獨立顯示卡,你需要檢查遊戲是否使用集成/核心顯示卡啟動,如果是,請嘗試使用獨立顯示卡啟動 HMCL 與遊戲。如果仍有問題,你可能需要考慮換一個新顯示卡或新電腦。
|
||||
game.crash.reason.openj9=當前遊戲無法運行在 OpenJ9 虛擬機上,請你在遊戲設置中更換 Hotspot Java 虛擬機,並重新啟動遊戲。如果沒有下載安裝,你可以在網路上自行下載。
|
||||
game.crash.reason.out_of_memory=當前遊戲因為記憶體不足,無法繼續運行。\n這可能是記憶體分配太小,或者 Mod 數量過多導致的。\n你可以在遊戲設置中調大遊戲記憶體分配值以允許遊戲在更大的記憶體下運行。\n如果仍然出現該錯誤,你可能需要換一台更好的電腦。
|
||||
game.crash.reason.processing_of_javaagent_failed=當前遊戲因為加載 -javaagent 參數失敗,無法繼續運行。\n如果你在 Java 虛擬機參數 中添加了相關參數,請檢查是否正確。\n如果你沒有添加相關參數或參數確認正確, 請嘗試:\n打開 控制面板 -- 時鐘和區域 分類(選項為類別顯示才有此選項,沒有就跳過)-- 區域 -- 上方的 管理 選項卡 -- 下方的 更改系統區域設置 按鈕 -- 在彈出的視窗中將 “使用Unicode UTF-8提供全球語言支持” 選項關閉,重啓設備後再嘗試啟動遊戲。\n可在<a href="https://discord.gg/jVvC7HfM6U">Discord</a>或<a href="https://kook.top/Kx7n3t">KOOK群</a>尋求幫助
|
||||
game.crash.reason.processing_of_javaagent_failed=當前遊戲因為加載 -javaagent 參數失敗,無法繼續運行。\n如果你在 Java 虛擬機參數 中添加了相關參數,請檢查是否正確。\n如果你沒有添加相關參數或參數確認正確, 請嘗試:\n打開 控制面板 -- 時鐘和區域 分類(選項為類別顯示才有此選項,沒有就跳過)-- 區域 -- 上方的 管理 選項卡 -- 下方的 更改系統區域設置 按鈕 -- 在彈出的視窗中將 “使用Unicode UTF-8提供全球語言支持” 選項關閉,重啓設備後再嘗試啟動遊戲。\n可在 <a href="https://discord.gg/jVvC7HfM6U">Discord</a> 或 <a href="https://docs.hmcl.net/groups.html">QQ 群</a>尋求幫助
|
||||
game.crash.reason.resolution_too_high=當前遊戲因為材質包解析度過高,無法繼續運行\n你可以更換一個解析度更低的材質,或者更換一個視訊記憶體更大的顯示卡。
|
||||
game.crash.reason.stacktrace=原因未知,請點擊日誌按鈕查看詳細訊息。\n下面是一些關鍵字,其中可能包含 Mod 名稱,你可以透過搜索的方式查找有關訊息。\n%s
|
||||
game.crash.reason.too_old_java=當前遊戲因為 Java 虛擬機版本過低,無法繼續運行。\n你需要在遊戲設置中更換 %1$s 或更新版本的 Java 虛擬機,並重新啟動遊戲。如果沒有下載安裝,你可以點擊 <a href="https://docs.microsoft.com/java/openjdk/download">此處</a> 下載微軟 JDK。
|
||||
game.crash.reason.unknown=原因未知,請點擊日誌按鈕查看詳細訊息。
|
||||
game.crash.reason.unsatisfied_link_error=當前遊戲因為缺少本地庫,無法繼續運行。\n這些本地庫缺失:%1$s。\n如果你在全局(特定)遊戲設置中修改了本地庫路徑選項,請你修改回預設模式。\n如果已經在預設模式下,請檢查本地庫缺失是否是 Mod 引起的,或由 HMCL 引起的。如果你確定是 HMCL 引起的,建議你向我們反饋。\n<b>或將游戲路徑中的所有非英文字符的名稱修改為英文字符(例如中文,空格等)</b>\n如果你確實需要自定義本地庫路徑,你需要保證其中包含缺失的本地庫!
|
||||
game.crash.reason.failed_to_load_a_library=當前遊戲因為加載本地庫失敗,無法繼續運行。\n如果你在全局(特定)遊戲設置中修改了本地庫路徑選項,請你修改回預設模式。\n如果已經在預設模式下,請檢查本地庫缺失是否是 Mod 引起的,或由 HMCL 引起的。如果你確定是 HMCL 引起的,建議你向我們反饋。\n<b>或將游戲路徑中的所有非英文字符的名稱修改為英文字符(例如中文,空格等)</b>\n如果你確實需要自定義本地庫路徑,你需要保證其中包含缺失的本地庫!
|
||||
game.crash.reason.unsatisfied_link_error=當前遊戲因為缺少本地庫,無法繼續運行。\n這些本地庫缺失:%1$s。\n如果你在遊戲設置中修改了本地庫路徑選項,請你修改回預設模式。\n如果您正在使用預設模式,請檢查遊戲路徑是否僅包含英文字母(大寫和小寫)、數字和底線。\n如果是,則請調查是由於 Mod 或者 HMCL 導致了本地庫缺失的問題。如果你確定是 HMCL 引起的,建議你向我們回饋。\n如果你確實需要自訂本地庫路徑,你需要保證其中包含缺失的本地庫。
|
||||
game.crash.title=遊戲意外退出
|
||||
game.directory=遊戲路徑
|
||||
game.version=遊戲版本
|
||||
@@ -606,7 +611,7 @@ logwindow.export_game_crash_logs=導出遊戲崩潰訊息
|
||||
logwindow.export_dump.dependency_ok.button=導出遊戲運行棧
|
||||
logwindow.export_dump.dependency_ok.doing_button=正在導出遊戲運行棧(可能需要 15 秒)
|
||||
logwindow.export_dump.no_dependency.button=導出遊戲運行棧(不兼容)
|
||||
logwindow.export_dump.no_dependency.tooltip=你的 Java 不包含用於創建遊戲運行棧的依賴。請前往 HMCL KOOK 或 HMCL Discord 尋求幫助。
|
||||
logwindow.export_dump.no_dependency.tooltip=你的 Java 不包含用於創建遊戲運行棧的依賴。請前往 HMCL QQ 群或 Discord 频道尋求幫助。
|
||||
|
||||
main_page=首頁
|
||||
|
||||
@@ -781,10 +786,6 @@ mods.restore=回退
|
||||
mods.url=官方頁面
|
||||
mods.update_modpack_mod.warning=更新模組包中的 Mod 可能導致綜合包損壞,使模組包無法正常啟動。該操作不可逆,確定要更新嗎?
|
||||
|
||||
multiplayer=多人聯機
|
||||
multiplayer.hint=多人聯機服務正在維護中。
|
||||
multiplayer.hint.details=查看詳情
|
||||
|
||||
nbt.entries=%s 個條目
|
||||
nbt.open.failed=打開檔案失敗
|
||||
nbt.save.failed=保存檔案失敗
|
||||
@@ -985,7 +986,7 @@ settings.launcher.download_source.auto=自動選擇下載來源
|
||||
settings.launcher.enable_game_list=在首頁內顯示遊戲列表
|
||||
settings.launcher.font=字體
|
||||
settings.launcher.general=通用
|
||||
settings.launcher.language=語言
|
||||
settings.launcher.language=語言 (重啟後生效)
|
||||
settings.launcher.launcher_log.export=匯出啟動器日誌
|
||||
settings.launcher.launcher_log.export.failed=無法匯出日誌
|
||||
settings.launcher.launcher_log.export.success=日誌已儲存到 %s
|
||||
@@ -1028,8 +1029,8 @@ settings.type.special.edit=編輯遊戲特定設置
|
||||
settings.type.special.edit.hint=當前遊戲版本 %s 啟動了遊戲特定設置,本頁面選項不對當前遊戲生效。點擊連結以修改當前遊戲設置。
|
||||
|
||||
sponsor=贊助
|
||||
sponsor.bmclapi=大中華區下載源由 BMCLAPI 和我的世界中文論壇 (MCBBS) 提供高速下載服務
|
||||
sponsor.hmcl=Hello Minecraft! Launcher 是一個免費、開源的 Minecraft 啟動器,允許玩家方便快捷地安裝、管理、執行遊戲。您的贊助將幫助 Hello Minecraft! Launcher 獲得更好的發展、支援穩定高速的遊戲安裝與檔案下載服務。點選此處查閱更多詳細訊息。
|
||||
sponsor.bmclapi=大中華區下載源由 BMCLAPI 提供高速下載服務
|
||||
sponsor.hmcl=Hello Minecraft! Launcher 是一個免費、開源的 Minecraft 啟動器,允許玩家方便快捷地安裝、管理、執行遊戲。點選此處查閱更多詳細訊息。
|
||||
|
||||
system.architecture=架構
|
||||
system.operating_system=操作系統
|
||||
@@ -1042,13 +1043,13 @@ update.changelog=更新日誌
|
||||
update.channel.dev=測試版
|
||||
update.channel.dev.hint=你正在使用測試版。測試版包含一些未在正式版中包含的測試性功能,僅用於體驗新功能。\n\
|
||||
測試版功能未受充分驗證,使用起來可能不穩定!\n\
|
||||
如果你遇到了使用問題,可以在設置的 <a href="hmcl://settings/feedback">回饋頁面</a> 中進行回饋,或加入回饋頁面中提供的 <a href="https://discord.gg/jVvC7HfM6U">Discord</a> 或 <a href="https://kook.top/Kx7n3t">KOOK 群</a>以回饋問題。\n\n\
|
||||
如果你遇到了使用問題,可以在設置的 <a href="hmcl://settings/feedback">回饋頁面</a> 中進行回饋,或加入回饋頁面中提供的 <a href="https://discord.gg/jVvC7HfM6U">Discord</a> 或 <a href="https://docs.hmcl.net/groups.html">QQ 群</a>以回饋問題。\n\n\
|
||||
<a href="hmcl://hide-announcement">點擊此處</a>為當前版本隱藏該提示。
|
||||
update.channel.dev.title=測試版提示
|
||||
update.channel.nightly=預覽版
|
||||
update.channel.nightly.hint=你正在使用預覽版。預覽版可能會每天更新一次,包含一些未在正式版和測試版中包含的測試性功能,僅用於體驗新功能\n\
|
||||
測試版功能未受充分驗證,使用起來可能不穩定!\n\
|
||||
如果你遇到了使用問題,可以在設置的 <a href="hmcl://settings/feedback">回饋頁面</a> 中進行回饋,或加入回饋頁面中提供的 <a href="https://discord.gg/jVvC7HfM6U">Discord</a> 或 <a href="https://kook.top/Kx7n3t">KOOK 群</a>以回饋問題。
|
||||
如果你遇到了使用問題,可以在設置的 <a href="hmcl://settings/feedback">回饋頁面</a> 中進行回饋,或加入 <a href="https://discord.gg/jVvC7HfM6U">Discord</a> 或 <a href="https://docs.hmcl.net/groups.html">QQ 群</a>以回饋問題。
|
||||
update.channel.nightly.title=預覽版提示
|
||||
update.channel.stable=建議版本
|
||||
update.checking=正在檢查更新
|
||||
|
||||
@@ -28,16 +28,19 @@ about.dependency=依赖
|
||||
about.legal=法律声明
|
||||
about.thanks_to=鸣谢
|
||||
about.thanks_to.bangbang93.statement=提供 BMCLAPI 下载源。请赞助支持 BMCLAPI!
|
||||
about.thanks_to.burningtnt.statement=为 HMCL 贡献许多技术支持
|
||||
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.zkitefly.statement=为 HMCL 贡献许多技术支持
|
||||
about.thanks_to.glavo.statement=负责 HMCL 的日常维护
|
||||
about.thanks_to.zekerzhayard.statement=为 HMCL 贡献许多技术支持
|
||||
about.thanks_to.zkitefly.statement=负责维护 HMCL 的文档
|
||||
about.thanks_to.mcbbs=MCBBS 我的世界中文论坛
|
||||
about.thanks_to.mcbbs.statement=提供 MCBBS 下载源
|
||||
about.thanks_to.mcmod=MC 百科
|
||||
about.thanks_to.mcmod.statement=提供模组中文名映射表与 Mod 百科
|
||||
about.thanks_to.red_lnn.statement=提供默认背景图
|
||||
about.thanks_to.shulkersakura.statement=提供 HMCL 的图标
|
||||
about.thanks_to.users=HMCL 用户群成员
|
||||
about.thanks_to.users.statement=感谢用户群成员赞助充电、积极催更、反馈问题、出谋划策
|
||||
about.thanks_to.yushijinhun.statement=authlib-injector 相关支持
|
||||
@@ -50,7 +53,6 @@ account.character=角色
|
||||
account.choose=选择一个角色
|
||||
account.create=添加账户
|
||||
account.create.microsoft=添加微软账户
|
||||
account.create.yggdrasil=添加 Mojang 账户
|
||||
account.create.offline=添加离线模式账户
|
||||
account.create.authlibInjector=添加外置登录账户 (authlib-injector)
|
||||
account.email=邮箱
|
||||
@@ -127,6 +129,7 @@ account.methods.microsoft.manual=你需要按照以下步骤添加:\n\
|
||||
<b>-若设备网络环境不佳,可能登录网站加载很慢甚至无法加载,此时请使用网络代理并重试</b>\n\
|
||||
如遇到问题,你可以点击右上角帮助按钮进行求助。
|
||||
account.methods.microsoft.profile=账户设置页
|
||||
account.methods.microsoft.purchase=购买 Minecraft
|
||||
account.methods.forgot_password=忘记密码
|
||||
account.methods.microsoft.snapshot=你正在使用非官方构建的 HMCL,请下载官方构建进行微软登录。
|
||||
account.methods.microsoft.snapshot.website=官方网站
|
||||
@@ -140,11 +143,6 @@ account.methods.offline.name.invalid=正常情况下,游戏用户名只能包
|
||||
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=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=点击此处添加账户
|
||||
account.move_to_global=转换为全局账户\n该账户的信息会保存至用户目录的配置文件中
|
||||
@@ -197,6 +195,8 @@ button.save_as=另存为
|
||||
button.select_all=全选
|
||||
button.yes=是
|
||||
|
||||
chat=官方群组
|
||||
|
||||
color.recent=推荐
|
||||
color.custom=自定义颜色
|
||||
|
||||
@@ -213,7 +213,7 @@ curse.category.4477=小游戏
|
||||
curse.category.4478=任务
|
||||
curse.category.4484=多人
|
||||
curse.category.4476=探索
|
||||
curse.category.6145=空岛
|
||||
curse.category.4736=空岛
|
||||
curse.category.4475=冒险 RPG
|
||||
curse.category.4487=FTB 整合包
|
||||
curse.category.4480=有特定地图
|
||||
@@ -231,6 +231,11 @@ curse.category.5189=实用与QOL
|
||||
curse.category.5190=QoL
|
||||
curse.category.5191=实用与QoL
|
||||
curse.category.5192=梦幻菜单
|
||||
curse.category.6145=空岛
|
||||
curse.category.6814=性能
|
||||
curse.category.6954=动态联合/集成动力 (Integrated Dynamics)
|
||||
curse.category.6484=机械动力 (Create)
|
||||
curse.category.6821=错误修复
|
||||
curse.category.423=信息展示
|
||||
curse.category.426=模组扩展
|
||||
curse.category.434=装备武器
|
||||
@@ -385,7 +390,7 @@ feedback.github.statement=打开一个 GitHub Issue。
|
||||
feedback.kookapp=KOOK
|
||||
feedback.kookapp.statement=欢迎加入 KOOK 讨论区,加入后请遵守讨论区规定。
|
||||
feedback.qq_group=HMCL 用户群
|
||||
feedback.qq_group.statement=通过捐赠支持加入 HMCL 用户 QQ 群。
|
||||
feedback.qq_group.statement=欢迎加入 HMCL 用户群,加入后请遵守群规。
|
||||
|
||||
file=文件
|
||||
|
||||
@@ -412,13 +417,13 @@ game.crash.reason.multiple=检测到多个原因:\n\n
|
||||
game.crash.reason.debug_crash=当前游戏因为手动触发崩溃,无法继续运行。\n事实上游戏并没有问题,问题都是你造成的!
|
||||
game.crash.reason.duplicated_mod=当前游戏因为模组 %1$s 重复安装,无法继续运行。\n%2$s\n每种模组只能安装一个,请你删除多余的模组再试。
|
||||
game.crash.reason.entity=当前游戏因为某个实体不能正常工作,无法继续运行。\n你可以尝试通过 <a href="https://www.mcedit.net">MCEdit</a> 工具编辑存档删除该实体,或者直接删除相应的模组。\n实体类型:%1$s\n实体坐标:%2$s
|
||||
game.crash.reason.fabric_version_0_12=Fabric 0.12 及以上版本与当前已经安装的模组可能不兼容,你需要将 Fabric 降级至 0.11.7。
|
||||
game.crash.reason.fabric_warnings=Fabric 提供了一些警告信息:\n%1$s
|
||||
game.crash.reason.file_already_exists=当前游戏因为文件 %1$s 已经存在,无法继续运行。\n如果你认为这个文件可以删除,你可以在备份这个文件后尝试删除它,并重新启动游戏。
|
||||
game.crash.reason.file_changed=当前游戏因为文件校验失败,无法继续运行。\n如果你手动修改了 Minecraft.jar 文件,你需要回退修改,或者重新下载游戏。
|
||||
game.crash.reason.gl_operation_failure=当前游戏因为你使用的某些模组、光影包、材质包,无法继续运行。\n请先尝试禁用你所使用的模组/光影包/材质包再试。
|
||||
game.crash.reason.fabric_version_0_12=Fabric 0.12 及以上版本与当前已经安装的模组可能不兼容,你需要将 Fabric 降级至 0.11.7。
|
||||
game.crash.reason.fabric_warnings=Fabric 提供了一些警告信息:\n%1$s
|
||||
game.crash.reason.file_already_exists=当前游戏因为文件 %1$s 已经存在,无法继续运行。\n如果你认为这个文件可以删除,你可以在备份这个文件后尝试删除它,并重新启动游戏。
|
||||
game.crash.reason.file_changed=当前游戏因为文件校验失败,无法继续运行。\n如果你手动修改了 Minecraft.jar 文件,你需要回退修改,或者重新下载游戏。
|
||||
game.crash.reason.gl_operation_failure=当前游戏因为你使用的某些模组、光影包、材质包,无法继续运行。\n请先尝试禁用你所使用的模组/光影包/材质包再试。
|
||||
game.crash.reason.graphics_driver=当前游戏因为显卡驱动问题而崩溃,请尝试以下操作:\n\
|
||||
- 如果你的电脑存在独立显卡,请尝试使用 独立显卡 而非 Intel 核显启动 HMCL 与游戏 <a href="https://www.bing.com/search?q=%E4%BD%BF%E7%94%A8%E7%8B%AC%E7%AB%8B%E6%98%BE%E5%8D%A1%E8%BF%90%E8%A1%8CMinecraft">详情</a>;\n\
|
||||
- 如果你的电脑存在独立显卡,请尝试使用 独立显卡 而非 Intel 核显启动 HMCL 与游戏 <a href="https://www.bing.com/search?q=使用独立显卡运行Minecraft">详情</a> ;\n\
|
||||
- <a href="https://minecrafthopper.net/help/pixel-format-not-accelerated/">尝试升级你的 显卡驱动 到最新版本</a>,或回退到出厂版本;\n\
|
||||
- 如果你确实需要使用核芯显卡,请检查你的电脑的 CPU 是否是 Intel(R) Core(TM) 3000 系列或更旧的处理器,如果是,对于 Minecraft 1.16.5 及更旧版本,请你将游戏所使用的 Java 版本降级至 1.8.0_51 及以下版本 <a href="https://github.com/frekele/oracle-java/releases/8u51-b16">Java 1.8.0 历史版本</a> ,否则请跳过;\n\
|
||||
- 在全局(特定)游戏设置,高级设置中打开“使用 OpenGL 软渲染器”(开启此选项后帧数会显著降低,仅推荐在以调试为目的或应急时开启)。\n\
|
||||
@@ -430,11 +435,10 @@ game.crash.reason.jdk_9=当前游戏因为 Java 版本过高,无法继续运
|
||||
game.crash.reason.jvm_32bit=当前游戏因为内存分配过大,超过了 32 位 Java 内存限制,无法继续运行。\n如果你的电脑是 64 位系统,请下载安装并更换 64 位 Java。<a href="https://bell-sw.com/pages/downloads/#downloads">下载 Java</a>\n如果你的电脑是 32 位系统,你或许可以重新安装 64 位系统,或换一台新电脑。\n或者,你可以关闭游戏内存的自动分配,并且把内存限制调节为 1024 MB 或以下。
|
||||
game.crash.reason.loading_crashed_forge=当前游戏因为模组 %1$s (%2$s) 错误,无法继续运行。\n你可以尝试删除或更新该模组以解决问题。
|
||||
game.crash.reason.loading_crashed_fabric=当前游戏因为模组 %1$s 错误,无法继续运行。\n你可以尝试删除或更新该模组以解决问题。
|
||||
game.crash.reason.memory_exceeded=当前游戏因为分配的内存过大,无法继续运行。\n该问题是由于系统页面文件太小导致的。\n你需要在全局(特定)游戏设置中关闭游戏内存的自动分配,并将游戏内存调低至游戏能正常启动为止。\n你还可以尝试将 虚拟内存 设置调整至「自动管理所有驱动器分页文件大小」,<a href="https://docs.hmcl.net/assets/img/hmcl/%E8%87%AA%E5%8A%A8%E7%AE%A1%E7%90%86%E6%89%80%E6%9C%89%E9%A9%B1%E5%8A%A8%E5%99%A8%E5%88%86%E9%A1%B5%E6%96%87%E4%BB%B6%E5%A4%A7%E5%B0%8F.webp">详情</a>。
|
||||
game.crash.reason.memory_exceeded=当前游戏因为分配的内存过大,无法继续运行。\n该问题是由于系统页面文件太小导致的。\n你需要在全局(特定)游戏设置中关闭游戏内存的自动分配,并将游戏内存调低至游戏能正常启动为止。\n你还可以尝试将 虚拟内存 设置调整至「自动管理所有驱动器分页文件大小」,<a href="https://docs.hmcl.net/assets/img/hmcl/自动管理所有驱动器分页文件大小.webp">详情</a>。
|
||||
game.crash.reason.mac_jdk_8u261=当前游戏因为你所使用的 Forge 或 OptiFine 与 Java 冲突崩溃。\n请尝试更新 Forge 和 OptiFine,或使用 Java 8u251 及更早版本启动。
|
||||
game.crash.reason.mod=当前游戏因为 %1$s 的问题,无法继续运行。\n你可以更新或删除已经安装的 %1$s 再试。
|
||||
game.crash.reason.mod_resolution=当前游戏因为 Mod 依赖问题,无法继续运行。Fabric 提供了如下信息:\n%1$s
|
||||
game.crash.reason.forgemod_resolution=当前游戏因为 Mod 依赖问题,无法继续运行。Forge 提供了如下信息:\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 不能兼容。
|
||||
game.crash.reason.mod_resolution_missing=当前游戏因为缺少 Mod 前置,无法继续运行。\n%1$s 需要前置 Mod:%2$s 才能继续运行。\n这表示你少安装了 Mod,或该 Mod 版本不够。你可以到下载页的模组下载,或到网上下载 %3$s。
|
||||
@@ -443,16 +447,8 @@ game.crash.reason.mod_resolution_mod_version=%1$s (版本号 %2$s)
|
||||
game.crash.reason.mod_resolution_mod_version.any=%1$s (任意版本)
|
||||
game.crash.reason.forge_repeat_installation=当前游戏因为 Forge 重复安装,无法继续运行。<a href="https://github.com/HMCL-dev/HMCL/issues/1880">此为已知问题</a>\n建议将日志上传反馈至 GitHub ,以便我们找到更多线索并修复此问题。\n目前你可以到 自动安装 里头卸载 Forge 并重新安装。
|
||||
game.crash.reason.optifine_repeat_installation=当前游戏因为 Optifine 重复安装,无法继续运行。\n请删除 Mod 文件夹下的 Optifine 或前往 游戏管理-自动安装 卸载自动安装的 Optifine。
|
||||
game.crash.reason.mod_resolution=当前游戏因为模组依赖问题,无法继续运行。Fabric 提供了如下信息:\n%1$s
|
||||
game.crash.reason.forgemod_resolution=当前游戏因为模组依赖问题,无法继续运行。Forge 提供了如下信息:\n%1$s
|
||||
game.crash.reason.mod_resolution_collection=当前游戏因为前置模组版本不匹配,无法继续运行。\n%1$s 需要前置模组:%2$s 才能继续运行。\n这表示你需要更新或降级前置。你可以到下载页的模组下载,或到网上下载 %3$s。
|
||||
game.crash.reason.mod_resolution_conflict=当前游戏因为模组冲突,无法继续运行。\n%1$s 与 %2$s 不能兼容。
|
||||
game.crash.reason.mod_resolution_missing=当前游戏因为缺少模组前置,无法继续运行。\n%1$s 需要前置模组:%2$s 才能继续运行。\n这表示你少安装了模组,或该模组版本不够。你可以到下载页的模组下载,或到网上下载 %3$s。
|
||||
game.crash.reason.mod_resolution_missing_minecraft=当前游戏因为模组和 Minecraft 游戏版本不匹配,无法继续运行。\n%1$s 需要 Minecraft %2$s 才能运行。\n如果你要继续使用你已经安装的模组,你可以选择安装对应的 Minecraft 版本;如果你要继续使用当前 Minecraft 版本,你需要安装对应版本的模组。
|
||||
game.crash.reason.mod_resolution_mod_version=%1$s (版本号 %2$s)
|
||||
game.crash.reason.mod_resolution_mod_version.any=%1$s (任意版本)
|
||||
game.crash.reason.forge_repeat_installation=当前游戏因为 Forge 重复安装,无法继续运行。<a href="https://github.com/HMCL-dev/HMCL/issues/1880">此为已知问题</a>\n建议将日志上传反馈至 GitHub ,以便我们找到更多线索并修复此问题。\n目前你可以到 自动安装 里头卸载 Forge 并重新安装。
|
||||
game.crash.reason.optifine_repeat_installation=当前游戏因为 OptiFine 重复安装,无法继续运行。\n请删除 Mod 文件夹下的 OptiFine 或前往 游戏管理-自动安装 卸载自动安装的 OptiFine。
|
||||
game.crash.reason.forge_found_duplicate_mods=当前游戏因为模组重复问题,无法继续运行。Forge 提供了如下信息:\n%1$s
|
||||
game.crash.reason.modmixin_failure=当前游戏因为某些 Mod 注入失败,无法继续运行。\n这一般代表着该 Mod 存在 Bug,或与当前环境不兼容。\n你可以查看日志寻找出错模组。
|
||||
game.crash.reason.night_config_fixes=当前游戏因为 Night Config 库的一些问题,无法继续运行。\n你可以尝试安装 <a href="https://www.curseforge.com/minecraft/mc-mods/night-config-fixes">Night Config Fixes</a> 模组,这或许能帮助你解决这个问题。\n了解更多,可访问该模组的 <a href="https://www.github.com/Fuzss/nightconfigfixes">GitHub 仓库</a>。
|
||||
game.crash.reason.forge_error=Forge 可能已经提供了错误信息。\n你可以查看日志,并根据错误报告中的日志信息进行对应处。\n如果没有看到报错信息,可以查看错误报告了解错误具体是如何发生的。\n%1$s
|
||||
@@ -460,14 +456,14 @@ game.crash.reason.mod_resolution0=当前游戏因为一些模组出现问题,
|
||||
game.crash.reason.fabric_reports_an_error_and_gives_a_solution=Fabric 可能已经提供了错误信息。\n你可以查看日志,并根据错误报告中的日志信息进行对应处。\n如果没有看到报错信息,可以查看错误报告了解错误具体是如何发生的。
|
||||
game.crash.reason.java_version_is_too_high=当前游戏因为 Java 虚拟机版本过高,无法继续运行。\n请在全局(特定)游戏设置的 Java 路径选项卡中改用较低版本的 Java,然后再启动游戏。\n如果没有,可以从 <a href="https://www.java.com/download/">java.com(Java8)</a> 或 <a href="https://bell-sw.com/pages/downloads/#downloads">BellSoft Liberica Full JRE(Java17)</a> 等平台下载、安装一个(安装完后需重启启动器)。
|
||||
game.crash.reason.mod_name=当前游戏因为模组文件名称问题,无法继续运行。\n模组文件名称应只使用英文全半角的大小写字母(Aa~Zz)、数字(0~9)、横线(-)、下划线(_)和点(.)。\n请到模组文件夹中将所有不合规的模组文件名称添加一个上述的合规的字符。
|
||||
game.crash.reason.incomplete_forge_installation=当前游戏因为 Forge 安装不完整,无法继续运行。\n请在 版本设置 - 自动安装 中卸载 Forge 并重新安装。
|
||||
game.crash.reason.incomplete_forge_installation=当前游戏因为 Forge / NeoForge 安装不完整,无法继续运行。\n请在 版本设置 - 自动安装 中卸载 Forge 并重新安装。
|
||||
game.crash.reason.optifine_is_not_compatible_with_forge=当前游戏因为 OptiFine 与当前版本的 Forge 不兼容,导致了游戏崩溃。\n点击 <a href="https://zkitefly.github.io/optifine-forge-support-list">此处</a> 查看 OptiFine 所兼容的 Forge 版本,并严格按照对应版本重新安装游戏或在 版本设置 - 自动安装 中更换版本。\n经测试,Forge 版本过高或过低都可能导致崩溃。
|
||||
game.crash.reason.mod_files_are_decompressed=当前游戏因为模组文件被解压了,无法继续运行。\n请直接把整个模组文件放进模组文件夹中即可。\n解压模组会导致游戏出错,请删除模组文件夹中已被解压的模组,然后再启动游戏。
|
||||
game.crash.reason.shaders_mod=当前游戏因为同时安装了 OptiFine 和 Shaders 模组,无法继续运行。\n因为 OptiFine 已集成 Shaders 模组的功能,只需删除 Shaders 模组即可。
|
||||
game.crash.reason.rtss_forest_sodium=当前游戏因为 RivaTuner Statistics Server (RTSS) 与 Sodium 不兼容,导致了游戏崩溃。\n点击 <a href="https://github.com/CaffeineMC/sodium-fabric/wiki/Known-Issues#rtss-incompatible">此处</a> 查看详情。
|
||||
game.crash.reason.too_many_mods_lead_to_exceeding_the_id_limit=当前游戏因为您所安装的模组过多,超出了游戏的 ID 限制,无法继续运行。\n请尝试安装 <a href="https://www.curseforge.com/minecraft/mc-mods/jeid">JEID</a> 等修复模组,或删除部分大型模组。
|
||||
game.crash.reason.too_many_mods_lead_to_exceeding_the_id_limit=当前游戏因为您所安装的模组过多,超出了游戏的 ID 限制,无法继续运行。\n请尝试安装 <a href="https://www.curseforge.com/minecraft/mc-mods/jeid">JEID</a> 等修复模组,或删除部分大型模组。
|
||||
game.crash.reason.optifine_causes_the_world_to_fail_to_load=当前游戏可能因为 OptiFine ,无法继续运行。\n该问题只在特定 OptiFine 版本中出现,你可以尝试在 版本设置 - 自动安装 中更换 OptiFine 的版本。
|
||||
game.crash.reason.modlauncher_8=当前游戏因为您所使用的 Forge 版本与当前使用的 Java 冲突崩溃,请尝试更新 Forge 到 36.2.26 或更高版本或换用版本低于 1.8.0.320 的 Java,<a href="https://github.com/bell-sw/Liberica/releases/8u312%2B7">Liberica JDK 8u312+7</a>。
|
||||
game.crash.reason.modlauncher_8=当前游戏因为您所使用的 Forge 版本与当前使用的 Java 冲突崩溃,请尝试更新 Forge 到 36.2.26 或更高版本或换用版本低于 1.8.0.320 的 Java,<a href="https://bell-sw.com/pages/downloads/?version=java-8&package=jre-full">Liberica JDK 8u312+7</a>。
|
||||
game.crash.reason.no_class_def_found_error=当前游戏因为代码不完整,无法继续运行。\n你的游戏可能缺失了某个模组,或者某些模组文件不完整,或者模组与游戏的版本不匹配。\n你可能需要重新安装游戏和模组,或请求他人帮助。\n缺失:\n%1$s
|
||||
game.crash.reason.no_such_method_error=当前游戏因为代码不完整,无法继续运行。\n你的游戏可能缺失了某个模组,或者某些模组文件不完整,或者模组与游戏的版本不匹配。\n你可能需要重新安装游戏和模组,或请求他人帮助。
|
||||
game.crash.reason.opengl_not_supported=当前游戏因为你的显卡驱动存在问题,无法继续运行。\n原因是 OpenGL 不受支持,你现在是否在远程桌面或者串流模式下?如果是,请直接使用原电脑启动游戏。\n或者尝试升级你的显卡驱动到最新版本后再尝试启动游戏。如果你的电脑存在独立显卡,你需要检查游戏是否使用集成/核心显卡启动,如果是,请尝试使用独立显卡启动 HMCL 与游戏。如果仍有问题,你可能需要考虑换一个新显卡或新电脑。
|
||||
@@ -559,7 +555,7 @@ launch.advice.too_large_memory_for_32bit=您设置的内存大小过大,由于
|
||||
launch.advice.vanilla_linux_java_8=对于 Linux x86-64 平台,Minecraft 1.12.2 及以下版本与 Java 9+ 不兼容,请使用 Java 8 启动游戏。\n如遇到问题,你可以点击右上角帮助按钮进行求助。
|
||||
launch.advice.vanilla_x86.translation=Minecraft 尚未为您的平台提供完善支持,所以可能影响游戏体验或无法启动游戏。\n你可以在 <a href="https://docs.microsoft.com/java/openjdk/download"> 这里 </a> 下载 <b> X86-64 </b> 架构的 Java 以获得更完整的体验。
|
||||
launch.failed=启动失败
|
||||
launch.failed.cannot_create_jvm=截获到无法创建 Java 虚拟机,可能是 Java 参数有问题,可以在设置中开启无参数模式启动。\n你可以点击右上角帮助按钮进行求助。
|
||||
launch.failed.cannot_create_jvm=截获到无法创建 Java 虚拟机,可能是 Java 参数有问题,可以在设置中开启无参数模式启动。
|
||||
launch.failed.creating_process=启动失败,在创建新进程时发生错误,可能是 Java 路径错误。
|
||||
launch.failed.command_too_long=命令长度超过限制,无法创建 bat 脚本,请导出为 PowerShell 脚本。
|
||||
launch.failed.decompressing_natives=未能解压游戏本地库。
|
||||
@@ -614,7 +610,7 @@ logwindow.export_game_crash_logs=导出游戏崩溃信息
|
||||
logwindow.export_dump.dependency_ok.button=导出游戏运行栈
|
||||
logwindow.export_dump.dependency_ok.doing_button=正在导出游戏运行栈(可能需要 15 秒)
|
||||
logwindow.export_dump.no_dependency.button=导出游戏运行栈(不兼容)
|
||||
logwindow.export_dump.no_dependency.tooltip=你的 Java 不包含用于创建游戏运行栈的依赖。请前往 HMCL KOOK 或 HMCL Discord 寻求帮助。
|
||||
logwindow.export_dump.no_dependency.tooltip=你的 Java 不包含用于创建游戏运行栈的依赖。请前往 HMCL QQ 群或 Discord 频道寻求帮助。
|
||||
|
||||
main_page=主页
|
||||
|
||||
@@ -789,10 +785,6 @@ mods.restore=回退
|
||||
mods.url=官方页面
|
||||
mods.update_modpack_mod.warning=更新整合包中的模组可能导致整合包损坏,使整合包无法正常启动。该操作不可逆,确定要更新吗?
|
||||
|
||||
multiplayer=多人联机
|
||||
multiplayer.hint=多人联机服务正在维护中。
|
||||
multiplayer.hint.details=查看详情
|
||||
|
||||
nbt.entries=%s 个条目
|
||||
nbt.open.failed=打开文件失败
|
||||
nbt.save.failed=保存文件失败
|
||||
@@ -993,7 +985,7 @@ settings.launcher.download_source.auto=自动选择下载源
|
||||
settings.launcher.enable_game_list=在主页内显示版本列表
|
||||
settings.launcher.font=字体
|
||||
settings.launcher.general=通用
|
||||
settings.launcher.language=语言
|
||||
settings.launcher.language=语言 (重启后生效)
|
||||
settings.launcher.launcher_log.export=导出启动器日志
|
||||
settings.launcher.launcher_log.export.failed=无法导出日志
|
||||
settings.launcher.launcher_log.export.success=日志已保存到 %s
|
||||
@@ -1036,8 +1028,8 @@ settings.type.special.edit=编辑游戏特定设置
|
||||
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 提供高速下载服务。BMCLAPI 为公益服务,赞助 BMCLAPI 可以帮助作者更好的提供稳定高速的下载服务,[点击此处查阅详细信息]
|
||||
sponsor.hmcl=Hello Minecraft! Launcher 是一个免费、自由、开放源代码的 Minecraft 启动器。[点击此处查阅更多详细信息]
|
||||
|
||||
system.architecture=架构
|
||||
system.operating_system=操作系统
|
||||
@@ -1050,13 +1042,13 @@ update.changelog=更新日志
|
||||
update.channel.dev=测试版
|
||||
update.channel.dev.hint=你正在使用测试版。测试版包含一些未在正式版中包含的测试性功能,仅用于体验新功能。\n\
|
||||
测试版功能未受充分验证,使用起来可能不稳定!<a href="https://hmcl.huangyuhui.net/download">下载稳定版</a>\n\
|
||||
如果你遇到了使用问题,可以在设置的 <a href="hmcl://settings/feedback">反馈页面</a> 中进行反馈,或加入反馈页面中提供的 <a href="https://discord.gg/jVvC7HfM6U">Discord</a> 或 <a href="https://kook.top/Kx7n3t">KOOK</a>群以反馈问题。欢迎关注 B 站账号 <a href="https://space.bilibili.com/1445341">huanghongxun</a> 以关注 HMCL 的开发进展。\n\n\
|
||||
如果你遇到了使用问题,可以在设置的 <a href="hmcl://settings/feedback">反馈页面</a> 中进行反馈,或加入反馈页面中提供的 <a href="https://discord.gg/jVvC7HfM6U">Discord</a> 或 <a href="https://docs.hmcl.net/groups.html">QQ 群</a>以反馈问题。欢迎关注 B 站账号 <a href="https://space.bilibili.com/1445341">huanghongxun</a> 以关注 HMCL 的开发进展。\n\n\
|
||||
<a href="hmcl://hide-announcement">点击此处</a>为当前版本隐藏该提示。
|
||||
update.channel.dev.title=测试版提示
|
||||
update.channel.nightly=预览版
|
||||
update.channel.nightly.hint=你正在使用预览版。预览版可能会每天更新一次,包含一些未在正式版和测试版中包含的测试性功能,仅用于体验新功能。\n\
|
||||
测试版功能未受充分验证,使用起来可能不稳定!<a href="https://hmcl.huangyuhui.net/download">下载稳定版</a>\n\
|
||||
如果你遇到了使用问题,可以在设置的 <a href="hmcl://settings/feedback">反馈页面</a> 中进行反馈,或加入反馈页面中提供的 <a href="https://discord.gg/jVvC7HfM6U">Discord</a> 或 <a href="https://kook.top/Kx7n3t">KOOK</a>群以反馈问题。欢迎关注 B 站账号 <a href="https://space.bilibili.com/1445341">huanghongxun</a> 以关注 HMCL 的开发进展。
|
||||
如果你遇到了使用问题,可以在设置的 <a href="hmcl://settings/feedback">反馈页面</a> 中进行反馈,或加入反馈页面中提供的 <a href="https://discord.gg/jVvC7HfM6U">Discord</a> 或 <a href="https://docs.hmcl.net/groups.html">QQ 群</a>以反馈问题。欢迎关注 B 站账号 <a href="https://space.bilibili.com/1445341">huanghongxun</a> 以关注 HMCL 的开发进展。
|
||||
update.channel.nightly.title=预览版提示
|
||||
update.channel.stable=推荐版本
|
||||
update.checking=正在检查更新
|
||||
|
||||
@@ -2346,6 +2346,160 @@
|
||||
"org.lwjgl:lwjgl-glfw:3.3.1:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-stb:3.3.1:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-tinyfd:3.3.1:natives-linux": null,
|
||||
"org.lwjgl:lwjgl:3.3.2": {
|
||||
"name": "org.lwjgl:lwjgl:3.3.4-SNAPSHOT",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl/3.3.4-SNAPSHOT/lwjgl-3.3.4-20231218.151521-4.jar",
|
||||
"url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl/3.3.4-SNAPSHOT/lwjgl-3.3.4-20231218.151521-4.jar",
|
||||
"sha1": "7202012cf0cadb9ffad4874494920fd8bbd93413",
|
||||
"size": 792204
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl:3.3.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl:3.3.4-SNAPSHOT:natives-linux-riscv64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl/3.3.4-SNAPSHOT/lwjgl-3.3.4-20231218.151521-4-natives-linux-riscv64.jar",
|
||||
"url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl/3.3.4-SNAPSHOT/lwjgl-3.3.4-20231218.151521-4-natives-linux-riscv64.jar",
|
||||
"sha1": "642848dc964fc5332cda40225771a5143740b49c",
|
||||
"size": 79691
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-jemalloc:3.3.2": {
|
||||
"name": "org.lwjgl:lwjgl-jemalloc:3.3.4-SNAPSHOT",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-jemalloc/3.3.4-SNAPSHOT/lwjgl-jemalloc-3.3.4-20231218.151521-4.jar",
|
||||
"url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-jemalloc/3.3.4-SNAPSHOT/lwjgl-jemalloc-3.3.4-20231218.151521-4.jar",
|
||||
"sha1": "41256f2c098806304fd224613d3d01b02725470e",
|
||||
"size": 46421
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-jemalloc:3.3.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-jemalloc:3.3.4-SNAPSHOT:natives-linux-riscv64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-jemalloc/3.3.4-SNAPSHOT/lwjgl-jemalloc-3.3.4-20231218.151521-4-natives-linux-riscv64.jar",
|
||||
"url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-jemalloc/3.3.4-SNAPSHOT/lwjgl-jemalloc-3.3.4-20231218.151521-4-natives-linux-riscv64.jar",
|
||||
"sha1": "01129d98674994eb8204f62c26b5717d4d989536",
|
||||
"size": 200633
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-openal:3.3.2": {
|
||||
"name": "org.lwjgl:lwjgl-openal:3.3.4-SNAPSHOT",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-openal/3.3.4-SNAPSHOT/lwjgl-openal-3.3.4-20231218.151521-4.jar",
|
||||
"url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-openal/3.3.4-SNAPSHOT/lwjgl-openal-3.3.4-20231218.151521-4.jar",
|
||||
"sha1": "89d8868c2d688b55e3e923345e4a146c6d034229",
|
||||
"size": 113094
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-openal:3.3.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-openal:3.3.4-SNAPSHOT:natives-linux-riscv64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-openal/3.3.4-SNAPSHOT/lwjgl-openal-3.3.4-20231218.151521-4-natives-linux-riscv64.jar",
|
||||
"url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-openal/3.3.4-SNAPSHOT/lwjgl-openal-3.3.4-20231218.151521-4-natives-linux-riscv64.jar",
|
||||
"sha1": "bce3174f137d0ffcbbe88ddae305de11c5131137",
|
||||
"size": 520217
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-opengl:3.3.2": {
|
||||
"name": "org.lwjgl:lwjgl-opengl:3.3.4-SNAPSHOT",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-opengl/3.3.4-SNAPSHOT/lwjgl-opengl-3.3.4-20231218.151521-4.jar",
|
||||
"url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-opengl/3.3.4-SNAPSHOT/lwjgl-opengl-3.3.4-20231218.151521-4.jar",
|
||||
"sha1": "81d0a7fd96bf5eb6257fddf6b77e338d8918bf32",
|
||||
"size": 931744
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-opengl:3.3.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-opengl:3.3.4-SNAPSHOT:natives-linux-riscv64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-opengl/3.3.4-SNAPSHOT/lwjgl-opengl-3.3.4-20231218.151521-4-natives-linux-riscv64.jar",
|
||||
"url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-opengl/3.3.4-SNAPSHOT/lwjgl-opengl-3.3.4-20231218.151521-4-natives-linux-riscv64.jar",
|
||||
"sha1": "13dccf954b781883b0661bd55dfc423f8eece864",
|
||||
"size": 57244
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-glfw:3.3.2": {
|
||||
"name": "org.lwjgl:lwjgl-glfw:3.3.4-SNAPSHOT",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-glfw/3.3.4-SNAPSHOT/lwjgl-glfw-3.3.4-20231218.151521-4.jar",
|
||||
"url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-glfw/3.3.4-SNAPSHOT/lwjgl-glfw-3.3.4-20231218.151521-4.jar",
|
||||
"sha1": "e6dba9ab8532cb6aac273adf26496ce689999943",
|
||||
"size": 146829
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-glfw:3.3.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-glfw:3.3.4-SNAPSHOT:natives-linux-riscv64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-glfw/3.3.4-SNAPSHOT/lwjgl-glfw-3.3.4-20231218.151521-4-natives-linux-riscv64.jar",
|
||||
"url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-glfw/3.3.4-SNAPSHOT/lwjgl-glfw-3.3.4-20231218.151521-4-natives-linux-riscv64.jar",
|
||||
"sha1": "865417f4aa171aa8e7a448d88877a82844896c92",
|
||||
"size": 113875
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-stb:3.3.2": {
|
||||
"name": "org.lwjgl:lwjgl-stb:3.3.4-SNAPSHOT",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-stb/3.3.4-SNAPSHOT/lwjgl-stb-3.3.4-20231218.151521-3.jar",
|
||||
"url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-stb/3.3.4-SNAPSHOT/lwjgl-stb-3.3.4-20231218.151521-3.jar",
|
||||
"sha1": "033fe42d1b37e35afd8b6e2653abc77deadb0730",
|
||||
"size": 143099
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-stb:3.3.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-stb:3.3.4-SNAPSHOT:natives-linux-riscv64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-stb/3.3.4-SNAPSHOT/lwjgl-stb-3.3.4-20231218.151521-3-natives-linux-riscv64.jar",
|
||||
"url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-stb/3.3.4-SNAPSHOT/lwjgl-stb-3.3.4-20231218.151521-3-natives-linux-riscv64.jar",
|
||||
"sha1": "f5b96c0fd649cb1e18816f63bed49e0dfe1d1f1a",
|
||||
"size": 217005
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-tinyfd:3.3.2": {
|
||||
"name": "org.lwjgl:lwjgl-tinyfd:3.3.4-SNAPSHOT",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-tinyfd/3.3.4-SNAPSHOT/lwjgl-tinyfd-3.3.4-20231218.151521-3.jar",
|
||||
"url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-tinyfd/3.3.4-SNAPSHOT/lwjgl-tinyfd-3.3.4-20231218.151521-3.jar",
|
||||
"sha1": "8b7c94a57f56a5b38b23c02c1cada77dccba9930",
|
||||
"size": 15917
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-tinyfd:3.3.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-tinyfd:3.3.4-SNAPSHOT:natives-linux-riscv64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-tinyfd/3.3.4-SNAPSHOT/lwjgl-tinyfd-3.3.4-20231218.151521-3-natives-linux-riscv64.jar",
|
||||
"url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-tinyfd/3.3.4-SNAPSHOT/lwjgl-tinyfd-3.3.4-20231218.151521-3-natives-linux-riscv64.jar",
|
||||
"sha1": "f042c17188c5db910b195d298c284bbfbe550c53",
|
||||
"size": 46885
|
||||
}
|
||||
}
|
||||
},
|
||||
"net.java.jinput:jinput-platform:2.0.5:natives": null,
|
||||
"com.mojang:text2speech:1.10.3:natives": null,
|
||||
"com.mojang:text2speech:1.11.3:natives": null,
|
||||
|
||||
@@ -97,7 +97,7 @@ public class AuthlibInjectorDownloader implements AuthlibInjectorArtifactProvide
|
||||
}
|
||||
|
||||
try {
|
||||
new FileDownloadTask(new URL(downloadProvider.get().injectURL(latest.downloadUrl)), artifactLocation.toFile(),
|
||||
new FileDownloadTask(downloadProvider.get().injectURLWithCandidates(latest.downloadUrl), artifactLocation.toFile(),
|
||||
Optional.ofNullable(latest.checksums.get("sha256"))
|
||||
.map(checksum -> new IntegrityCheck("SHA-256", checksum))
|
||||
.orElse(null))
|
||||
|
||||
@@ -84,16 +84,20 @@ public class MicrosoftAccount extends OAuthAccount {
|
||||
|
||||
@Override
|
||||
public AuthInfo logIn() throws AuthenticationException {
|
||||
if (!authenticated || !service.validate(session.getNotAfter(), session.getTokenType(), session.getAccessToken())) {
|
||||
MicrosoftSession acquiredSession = service.refresh(session);
|
||||
if (!Objects.equals(acquiredSession.getProfile().getId(), session.getProfile().getId())) {
|
||||
throw new ServerResponseMalformedException("Selected profile changed");
|
||||
if (!authenticated) {
|
||||
if (service.validate(session.getNotAfter(), session.getTokenType(), session.getAccessToken())) {
|
||||
authenticated = true;
|
||||
} else {
|
||||
MicrosoftSession acquiredSession = service.refresh(session);
|
||||
if (!Objects.equals(acquiredSession.getProfile().getId(), session.getProfile().getId())) {
|
||||
throw new ServerResponseMalformedException("Selected profile changed");
|
||||
}
|
||||
|
||||
session = acquiredSession;
|
||||
|
||||
authenticated = true;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
session = acquiredSession;
|
||||
|
||||
authenticated = true;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
return session.toAuthInfo();
|
||||
|
||||
@@ -155,7 +155,8 @@ public class Skin {
|
||||
if (defaultSkinLoader == null) {
|
||||
return Task.supplyAsync(() -> null);
|
||||
}
|
||||
return Task.supplyAsync(() -> new LoadedSkin(TextureModel.STEVE, Texture.loadTexture(defaultSkinLoader.apply(type)), null));
|
||||
TextureModel model = type == Type.ALEX ? TextureModel.ALEX : TextureModel.STEVE;
|
||||
return Task.supplyAsync(() -> new LoadedSkin(model, Texture.loadTexture(defaultSkinLoader.apply(type)), null));
|
||||
case LOCAL_FILE:
|
||||
return Task.supplyAsync(() -> {
|
||||
Texture skin = null, cape = null;
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* 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.auth.yggdrasil;
|
||||
|
||||
import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
|
||||
public class MojangYggdrasilProvider implements YggdrasilProvider {
|
||||
|
||||
@Override
|
||||
public URL getAuthenticationURL() {
|
||||
return NetworkUtils.toURL("https://authserver.mojang.com/authenticate");
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getRefreshmentURL() {
|
||||
return NetworkUtils.toURL("https://authserver.mojang.com/refresh");
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getValidationURL() {
|
||||
return NetworkUtils.toURL("https://authserver.mojang.com/validate");
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getInvalidationURL() {
|
||||
return NetworkUtils.toURL("https://authserver.mojang.com/invalidate");
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getSkinUploadURL(UUID uuid) throws UnsupportedOperationException {
|
||||
return NetworkUtils.toURL("https://api.mojang.com/user/profile/" + UUIDTypeAdapter.fromUUID(uuid) + "/skin");
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getProfilePropertiesURL(UUID uuid) {
|
||||
return NetworkUtils.toURL("https://sessionserver.mojang.com/session/minecraft/profile/" + UUIDTypeAdapter.fromUUID(uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "mojang";
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ import java.util.logging.Level;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
public class YggdrasilAccount extends ClassicAccount {
|
||||
public abstract class YggdrasilAccount extends ClassicAccount {
|
||||
|
||||
protected final YggdrasilService service;
|
||||
protected final UUID characterUUID;
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* 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.auth.yggdrasil;
|
||||
|
||||
import org.jackhuang.hmcl.auth.AccountFactory;
|
||||
import org.jackhuang.hmcl.auth.AuthenticationException;
|
||||
import org.jackhuang.hmcl.auth.CharacterSelector;
|
||||
import org.jackhuang.hmcl.util.javafx.ObservableOptionalCache;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class YggdrasilAccountFactory extends AccountFactory<YggdrasilAccount> {
|
||||
|
||||
public static final YggdrasilAccountFactory MOJANG = new YggdrasilAccountFactory(YggdrasilService.MOJANG);
|
||||
|
||||
private final YggdrasilService service;
|
||||
|
||||
public YggdrasilAccountFactory(YggdrasilService service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountLoginType getLoginType() {
|
||||
return AccountLoginType.USERNAME_PASSWORD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public YggdrasilAccount create(CharacterSelector selector, String username, String password, ProgressCallback progressCallback, Object additionalData) throws AuthenticationException {
|
||||
Objects.requireNonNull(selector);
|
||||
Objects.requireNonNull(username);
|
||||
Objects.requireNonNull(password);
|
||||
|
||||
return new YggdrasilAccount(service, username, password, selector);
|
||||
}
|
||||
|
||||
@Override
|
||||
public YggdrasilAccount fromStorage(Map<Object, Object> storage) {
|
||||
Objects.requireNonNull(storage);
|
||||
|
||||
YggdrasilSession session = YggdrasilSession.fromStorage(storage);
|
||||
|
||||
String username = tryCast(storage.get("username"), String.class)
|
||||
.orElseThrow(() -> new IllegalArgumentException("storage does not have username"));
|
||||
|
||||
tryCast(storage.get("profileProperties"), Map.class).ifPresent(
|
||||
it -> {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> properties = it;
|
||||
GameProfile selected = session.getSelectedProfile();
|
||||
ObservableOptionalCache<UUID, CompleteGameProfile, AuthenticationException> profileRepository = service.getProfileRepository();
|
||||
profileRepository.put(selected.getId(), new CompleteGameProfile(selected, properties));
|
||||
profileRepository.invalidate(selected.getId());
|
||||
});
|
||||
|
||||
return new YggdrasilAccount(service, username, session);
|
||||
}
|
||||
}
|
||||
@@ -53,8 +53,6 @@ public class YggdrasilService {
|
||||
|
||||
private static final ThreadPoolExecutor POOL = threadPool("YggdrasilProfileProperties", true, 2, 10, TimeUnit.SECONDS);
|
||||
|
||||
public static final YggdrasilService MOJANG = new YggdrasilService(new MojangYggdrasilProvider());
|
||||
|
||||
private final YggdrasilProvider provider;
|
||||
private final ObservableOptionalCache<UUID, CompleteGameProfile, AuthenticationException> profileRepository;
|
||||
|
||||
@@ -272,7 +270,5 @@ public class YggdrasilService {
|
||||
.registerTypeAdapterFactory(ValidationTypeAdapterFactory.INSTANCE)
|
||||
.create();
|
||||
|
||||
public static final String PROFILE_URL = "https://aka.ms/MinecraftMigration";
|
||||
public static final String MIGRATION_FAQ_URL = "https://help.minecraft.net/articles/360050865492";
|
||||
public static final String PURCHASE_URL = "https://www.microsoft.com/store/productId/9NXP44L49SHJ";
|
||||
}
|
||||
@@ -106,7 +106,7 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
|
||||
Version original = repository.getVersion(version.getId());
|
||||
Version resolved = original.resolvePreservingPatches(repository);
|
||||
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(resolved);
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(resolved, gameVersion);
|
||||
for (LibraryAnalyzer.LibraryType type : LibraryAnalyzer.LibraryType.values()) {
|
||||
if (!analyzer.has(type))
|
||||
continue;
|
||||
@@ -218,8 +218,9 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
|
||||
if (version.isResolved())
|
||||
throw new IllegalArgumentException("removeLibraryWithoutSavingAsync requires non-resolved version");
|
||||
Version independentVersion = version.resolvePreservingPatches(repository);
|
||||
String gameVersion = repository.getGameVersion(independentVersion).orElse(null);
|
||||
|
||||
return Task.supplyAsync(() -> LibraryAnalyzer.analyze(independentVersion).removeLibrary(libraryId).build());
|
||||
return Task.supplyAsync(() -> LibraryAnalyzer.analyze(independentVersion, gameVersion).removeLibrary(libraryId).build());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -130,12 +130,16 @@ public final class LibraryAnalyzer implements Iterable<LibraryAnalyzer.LibraryMa
|
||||
return version;
|
||||
}
|
||||
|
||||
public static LibraryAnalyzer analyze(Version version) {
|
||||
public static LibraryAnalyzer analyze(Version version, String gameVersion) {
|
||||
if (version.getInheritsFrom() != null)
|
||||
throw new IllegalArgumentException("LibraryAnalyzer can only analyze independent game version");
|
||||
|
||||
Map<String, Pair<Library, String>> libraries = new HashMap<>();
|
||||
|
||||
if (gameVersion != null) {
|
||||
libraries.put(LibraryType.MINECRAFT.getPatchId(), pair(null, gameVersion));
|
||||
}
|
||||
|
||||
List<Library> rawLibraries = version.resolve(null).getLibraries();
|
||||
for (Library library : rawLibraries) {
|
||||
for (LibraryType type : LibraryType.values()) {
|
||||
|
||||
@@ -63,7 +63,7 @@ public class MaintainTask extends Task<Version> {
|
||||
String mainClass = version.resolve(null).getMainClass();
|
||||
|
||||
if (mainClass != null && mainClass.equals(LibraryAnalyzer.LAUNCH_WRAPPER_MAIN)) {
|
||||
version = maintainOptiFineLibrary(repository, maintainGameWithLaunchWrapper(unique(version), true), false);
|
||||
version = maintainOptiFineLibrary(repository, maintainGameWithLaunchWrapper(repository, unique(version), true), false);
|
||||
} else if (mainClass != null && mainClass.equals(LibraryAnalyzer.MOD_LAUNCHER_MAIN)) {
|
||||
// Forge 1.13 and OptiFine
|
||||
version = maintainOptiFineLibrary(repository, maintainGameWithCpwModLauncher(repository, unique(version)), true);
|
||||
@@ -97,8 +97,8 @@ public class MaintainTask extends Task<Version> {
|
||||
return newVersion.setPatches(version.getPatches()).markAsUnresolved();
|
||||
}
|
||||
|
||||
private static Version maintainGameWithLaunchWrapper(Version version, boolean reorderTweakClass) {
|
||||
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version);
|
||||
private static Version maintainGameWithLaunchWrapper(GameRepository repository, Version version, boolean reorderTweakClass) {
|
||||
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version, null);
|
||||
VersionLibraryBuilder builder = new VersionLibraryBuilder(version);
|
||||
String mainClass = null;
|
||||
|
||||
@@ -147,7 +147,7 @@ public class MaintainTask extends Task<Version> {
|
||||
}
|
||||
|
||||
private static Version maintainGameWithCpwModLauncher(GameRepository repository, Version version) {
|
||||
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version);
|
||||
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version, null);
|
||||
VersionLibraryBuilder builder = new VersionLibraryBuilder(version);
|
||||
|
||||
if (!libraryAnalyzer.has(FORGE)) return version;
|
||||
@@ -205,7 +205,7 @@ public class MaintainTask extends Task<Version> {
|
||||
|
||||
// Fix wrong configurations when launching 1.17+ with Forge.
|
||||
private static Version maintainGameWithCpwBoostrapLauncher(GameRepository repository, Version version) {
|
||||
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version);
|
||||
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version, null);
|
||||
VersionLibraryBuilder builder = new VersionLibraryBuilder(version);
|
||||
|
||||
if (!libraryAnalyzer.has(FORGE) && !libraryAnalyzer.has(NEO_FORGE)) return version;
|
||||
@@ -247,7 +247,7 @@ public class MaintainTask extends Task<Version> {
|
||||
}
|
||||
|
||||
private static Version maintainOptiFineLibrary(GameRepository repository, Version version, boolean remove) {
|
||||
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version);
|
||||
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version, null);
|
||||
List<Library> libraries = new ArrayList<>(version.getLibraries());
|
||||
|
||||
if (libraryAnalyzer.has(OPTIFINE)) {
|
||||
|
||||