Compare commits
10 Commits
53e90ea0f4
...
20c8f914d9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20c8f914d9 | ||
|
|
586dee4f1b | ||
|
|
917d11009f | ||
|
|
4fa07661bb | ||
|
|
b1b90e0855 | ||
|
|
76370099e3 | ||
|
|
2e5e21affa | ||
|
|
cceef4f8f8 | ||
|
|
68c291e23a | ||
|
|
1e42034a0b |
49
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
49
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@@ -1,49 +0,0 @@
|
||||
name: Bug 反馈 | Bug Report
|
||||
description:
|
||||
反馈一个 HMCL 错误。| File a bug report for HMCL.
|
||||
title: "[Bug] "
|
||||
labels: bug
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
提交前请确认:
|
||||
|
||||
* 该问题确实是 **HMCL 的错误**,而**不是 Minecraft 非正常退出**。如果你的 Minecraft 非正常退出,请前往 [QQ 群](https://docs.hmcl.net/groups.html)/[Discord 服务器](https://discord.gg/jVvC7HfM6U) 获取帮助。
|
||||
* 你的启动器版本是**最新的预览版本**。你可以从 [GitHub Actions](https://github.com/HMCL-dev/HMCL/actions/workflows/gradle.yml?query=branch%3Amain+event%3Apush) 或 [nightly.link](https://nightly.link/HMCL-dev/HMCL/workflows/gradle/main) 下载最新预览版本。
|
||||
|
||||
如果你的问题并不属于上述两类,你可以选取另一种 Issue 类型,或者直接前往 [QQ 群](https://docs.hmcl.net/groups.html)/[Discord 服务器](https://discord.gg/jVvC7HfM6U) 获取帮助。
|
||||
|
||||
Before submitting, please confirm:
|
||||
|
||||
* The issue is indeed **a bug of HMCL**, not **Minecraft abnormal exit**. If your Minecraft exits abnormally, please go to the [QQ group](https://docs.hmcl.net/groups.html) or [Discord server](https://discord.gg/jVvC7HfM6U) for help.
|
||||
* Your launcher is the **latest nightly build**. You can download the latest nightly build from [GitHub Actions](https://github.com/HMCL-dev/HMCL/actions/workflows/gradle.yml?query=branch%3Amain+event%3Apush) or [nightly.link](https://nightly.link/HMCL-dev/HMCL/workflows/gradle/main).
|
||||
|
||||
If your issue does not fall into the above two categories, you can choose another type of issue or directly go to the [QQ group](https://docs.hmcl.net/groups.html) or [Discord server](https://discord.gg/jVvC7HfM6U) for help.
|
||||
- type: textarea
|
||||
id: bug-report
|
||||
attributes:
|
||||
label: 问题描述 | Bug Description
|
||||
description: |
|
||||
请尽可能地详细描述你所遇到的问题,并描述如何重新触发这个问题。
|
||||
Please describe the bug you met in as much detail as possible. Additionally, describe the steps to reproduce this bug.
|
||||
placeholder: |
|
||||
1. 点击 HMCL 上的某个按钮 | Click a button named ...
|
||||
2. 向下翻页 | Scroll down
|
||||
3. ...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: hmcl-crash-report-or-logs
|
||||
attributes:
|
||||
label: 启动器崩溃报告 / 启动器日志文件 | Launcher Crash Report / Launcher Log File
|
||||
description: |
|
||||
如果你的启动器崩溃了,请将崩溃报告填入 (或将文件拖入) 下方。
|
||||
如果你的启动器没有崩溃,请在遇到问题后**不要退出启动器**,在启动器的 “设置 → 通用 → 调试” 一栏中点击 “导出启动器日志”,并将导出的日志拖到下方的输入栏中。
|
||||
**请注意:启动器崩溃报告或日志文件是诊断问题的重要依据,请务必上传!**
|
||||
|
||||
If your launcher crashes, please fill in (or drag the file in) the following input field with the crash report.
|
||||
If your launcher does not crash, please DO NOT EXIT your launcher, click "Export Launcher Logs" in the "Settings → General → Debug" of the launcher, and drag the exported log to the following input field.
|
||||
**ATTENTION: The crash report or log file is the key to resolving the bug. Please upload them!**
|
||||
validations:
|
||||
required: true
|
||||
11
.github/ISSUE_TEMPLATE/config.yml
vendored
11
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,11 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: QQ 群 | QQ Group
|
||||
url: https://docs.hmcl.net/groups.html
|
||||
about: Hello Minecraft! Launcher 的官方 QQ 交流群。| The official QQ group of Hello Minecraft! Launcher.
|
||||
- name: Discord 服务器 | Discord Server
|
||||
url: https://discord.gg/jVvC7HfM6U
|
||||
about: Hello Minecraft! Launcher 的官方 Discord 服务器。| The official Discord server of Hello Minecraft! Launcher.
|
||||
- name: 其他反馈 | Others
|
||||
url: https://github.com/HMCL-dev/HMCL/discussions/new/choose
|
||||
about: 通过 Discussions 反馈其他问题。| Report other problems in Discussions.
|
||||
35
.github/ISSUE_TEMPLATE/feature.yml
vendored
35
.github/ISSUE_TEMPLATE/feature.yml
vendored
@@ -1,35 +0,0 @@
|
||||
name: 新功能 | Feature Request
|
||||
description: 为 HMCL 提出新功能。| Suggest a new feature or enhancement for HMCL.
|
||||
title: "[Feature] "
|
||||
labels: enhancement
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
请确认 Issues 列表无重复的项目。
|
||||
Please make sure that no duplicate issues have already been submitted.
|
||||
- type: textarea
|
||||
id: summary
|
||||
attributes:
|
||||
label: 概述 | Summary
|
||||
description: |
|
||||
请介绍你想加入的新功能。
|
||||
Please describe the new feature.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: reason
|
||||
attributes:
|
||||
label: 原因 | Reason
|
||||
description: |
|
||||
请描述该功能带来的好处及原因。
|
||||
Please describe why you want to add the feature or enhancement to HMCL.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: 详情 | Description
|
||||
description: |
|
||||
在这里可以补充描述该功能的具体实现方式或建议。(可选)
|
||||
Describe implementation details or suggestions here. (Optional)
|
||||
33
.github/workflows/mirror.yml
vendored
33
.github/workflows/mirror.yml
vendored
@@ -1,33 +0,0 @@
|
||||
name: Mirror Repository
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
|
||||
concurrency:
|
||||
group: mirror-repository
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
mirror:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- name: Gitee
|
||||
repo: gitee.com/huanghongxun/HMCL
|
||||
user: 'hmcl-sync'
|
||||
token: 'GITEE_SYNC_TOKEN'
|
||||
- name: CNB
|
||||
repo: cnb.cool/HMCL-dev/HMCL
|
||||
user: 'cnb'
|
||||
token: 'CNB_SYNC_TOKEN'
|
||||
name: Mirror to ${{ matrix.name }}
|
||||
if: ${{ github.repository == 'HMCL-dev/HMCL' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Mirror GitHub to ${{ matrix.name }}
|
||||
run: |
|
||||
git clone --mirror "https://github.com/${{ github.repository }}.git" -- repo
|
||||
cd repo
|
||||
git push -f --prune "https://${{ matrix.user }}:${{ secrets[matrix.token] }}@${{ matrix.repo }}.git" "refs/heads/*:refs/heads/*" "refs/tags/*:refs/tags/*"
|
||||
125
.github/workflows/release.yml
vendored
125
.github/workflows/release.yml
vendored
@@ -1,125 +0,0 @@
|
||||
name: Create Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# schedule:
|
||||
# - cron: '30 * * * *'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
check-update:
|
||||
if: ${{ github.repository_owner == 'HMCL-dev' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 1
|
||||
matrix:
|
||||
include:
|
||||
- channel: dev
|
||||
task: checkUpdateDev
|
||||
- channel: stable
|
||||
task: checkUpdateStable
|
||||
runs-on: ubuntu-latest
|
||||
name: check-update-${{ matrix.channel }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '25'
|
||||
- name: Fetch last version
|
||||
run: ./gradlew ${{ matrix.task }} --no-daemon --info --stacktrace
|
||||
- name: Check for existing tags
|
||||
run: if [ -z "$(git tag -l "$HMCL_TAG_NAME")" ]; then echo "continue=true" >> $GITHUB_ENV; fi
|
||||
- name: Download artifacts
|
||||
if: ${{ env.continue == 'true' }}
|
||||
run: |
|
||||
wget "$HMCL_CI_DOWNLOAD_BASE_URI/HMCL-$HMCL_VERSION.exe"
|
||||
wget "$HMCL_CI_DOWNLOAD_BASE_URI/HMCL-$HMCL_VERSION.exe.sha256"
|
||||
wget "$HMCL_CI_DOWNLOAD_BASE_URI/HMCL-$HMCL_VERSION.jar"
|
||||
wget "$HMCL_CI_DOWNLOAD_BASE_URI/HMCL-$HMCL_VERSION.jar.sha256"
|
||||
wget "$HMCL_CI_DOWNLOAD_BASE_URI/HMCL-$HMCL_VERSION.sh"
|
||||
wget "$HMCL_CI_DOWNLOAD_BASE_URI/HMCL-$HMCL_VERSION.sh.sha256"
|
||||
- name: Verify artifacts
|
||||
if: ${{ env.continue == 'true' }}
|
||||
run: |
|
||||
export JAR_SHA256=$(cat HMCL-$HMCL_VERSION.jar.sha256 | tr -d '\n')
|
||||
export EXE_SHA256=$(cat HMCL-$HMCL_VERSION.exe.sha256 | tr -d '\n')
|
||||
export SH_SHA256=$(cat HMCL-$HMCL_VERSION.sh.sha256 | tr -d '\n')
|
||||
|
||||
echo "$JAR_SHA256 HMCL-$HMCL_VERSION.jar" | sha256sum -c
|
||||
echo "$EXE_SHA256 HMCL-$HMCL_VERSION.exe" | sha256sum -c
|
||||
echo "$SH_SHA256 HMCL-$HMCL_VERSION.sh" | sha256sum -c
|
||||
- name: Generate release note
|
||||
if: ${{ env.continue == 'true' }}
|
||||
run: |
|
||||
# GitHub Release Note
|
||||
echo " **[Changelog](https://docs.hmcl.net/changelog/${{ matrix.channel }}.html#HMCL-$HMCL_VERSION)** (Chinese)" >> RELEASE_NOTE
|
||||
echo "" >> RELEASE_NOTE
|
||||
echo "| File | SHA-256 Checksum |" >> RELEASE_NOTE
|
||||
echo "| --- | --- |" >> RELEASE_NOTE
|
||||
echo "| [HMCL-$HMCL_VERSION.exe]($GH_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.exe) | \`$(cat HMCL-$HMCL_VERSION.exe.sha256)\` |" >> RELEASE_NOTE
|
||||
echo "| [HMCL-$HMCL_VERSION.jar]($GH_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.jar) | \`$(cat HMCL-$HMCL_VERSION.jar.sha256)\` |" >> RELEASE_NOTE
|
||||
echo "| [HMCL-$HMCL_VERSION.sh]($GH_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.sh) | \`$(cat HMCL-$HMCL_VERSION.sh.sha256)\` |" >> RELEASE_NOTE
|
||||
|
||||
# CNB Release Note
|
||||
echo "[更新日志](https://docs.hmcl.net/changelog/${{ matrix.channel }}.html#HMCL-$HMCL_VERSION)" >> CNB_RELEASE_NOTE
|
||||
echo "" >> CNB_RELEASE_NOTE
|
||||
echo "| 文件 | SHA-256 校验码 |" >> CNB_RELEASE_NOTE
|
||||
echo "| :--- | --- |" >> CNB_RELEASE_NOTE
|
||||
echo "| [HMCL-$HMCL_VERSION.exe]($CNB_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.exe) | \`$(cat HMCL-$HMCL_VERSION.exe.sha256)\` |" >> CNB_RELEASE_NOTE
|
||||
echo "| [HMCL-$HMCL_VERSION.jar]($CNB_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.jar) | \`$(cat HMCL-$HMCL_VERSION.jar.sha256)\` |" >> CNB_RELEASE_NOTE
|
||||
echo "| [HMCL-$HMCL_VERSION.sh]($CNB_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.sh) | \`$(cat HMCL-$HMCL_VERSION.sh.sha256)\` |" >> CNB_RELEASE_NOTE
|
||||
env:
|
||||
GH_DOWNLOAD_BASE_URL: https://github.com/${{ github.repository }}/releases/download
|
||||
CNB_DOWNLOAD_BASE_URL: https://cnb.cool/HMCL-dev/HMCL/-/releases/download
|
||||
- name: Create GitHub release
|
||||
if: ${{ env.continue == 'true' }}
|
||||
run: |
|
||||
gh release create "${{ env.HMCL_TAG_NAME }}" \
|
||||
"HMCL-${{ env.HMCL_VERSION }}.exe" \
|
||||
"HMCL-${{ env.HMCL_VERSION }}.jar" \
|
||||
"HMCL-${{ env.HMCL_VERSION }}.sh" \
|
||||
--target "${{ env.HMCL_COMMIT_SHA }}" \
|
||||
--title "${{ env.HMCL_TAG_NAME }}" \
|
||||
--notes-file RELEASE_NOTE \
|
||||
--prerelease
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Install git-cnb
|
||||
if: ${{ env.continue == 'true' }}
|
||||
run: go install "cnb.cool/looc/git-cnb@$GIT_CNB_VERSION"
|
||||
env:
|
||||
GIT_CNB_VERSION: '1.1.2'
|
||||
- name: Create CNB release
|
||||
if: ${{ env.continue == 'true' }}
|
||||
run: |
|
||||
echo "Uploading tags to CNB"
|
||||
git fetch --tags
|
||||
git push "https://cnb:${{ secrets.CNB_SYNC_TOKEN }}@cnb.cool/$CNB_REPO.git" "$HMCL_TAG_NAME"
|
||||
|
||||
echo "Creating CNB release"
|
||||
~/go/bin/git-cnb release create \
|
||||
--repo "$CNB_REPO" \
|
||||
--tag "$HMCL_TAG_NAME" \
|
||||
--name "HMCL $HMCL_VERSION" \
|
||||
--body "$(cat CNB_RELEASE_NOTE)" \
|
||||
--prerelease true
|
||||
|
||||
echo "Uploading HMCL-$HMCL_VERSION.jar"
|
||||
~/go/bin/git-cnb release asset-upload --repo="$CNB_REPO" --tag-name "$HMCL_TAG_NAME" --file-name "HMCL-$HMCL_VERSION.jar"
|
||||
|
||||
echo "Uploading HMCL-$HMCL_VERSION.exe"
|
||||
~/go/bin/git-cnb release asset-upload --repo="$CNB_REPO" --tag-name "$HMCL_TAG_NAME" --file-name "HMCL-$HMCL_VERSION.exe"
|
||||
|
||||
echo "Uploading HMCL-$HMCL_VERSION.sh"
|
||||
~/go/bin/git-cnb release asset-upload --repo="$CNB_REPO" --tag-name "$HMCL_TAG_NAME" --file-name "HMCL-$HMCL_VERSION.sh"
|
||||
env:
|
||||
CNB_TOKEN: ${{ secrets.CNB_SYNC_TOKEN }}
|
||||
CNB_REPO: HMCL-dev/HMCL
|
||||
@@ -28,7 +28,6 @@ val versionType = System.getenv("VERSION_TYPE") ?: if (isOfficial) "nightly" els
|
||||
val versionRoot = System.getenv("VERSION_ROOT") ?: projectConfig.getProperty("versionRoot") ?: "3"
|
||||
|
||||
val microsoftAuthId = System.getenv("MICROSOFT_AUTH_ID") ?: ""
|
||||
val microsoftAuthSecret = System.getenv("MICROSOFT_AUTH_SECRET") ?: ""
|
||||
val curseForgeApiKey = System.getenv("CURSEFORGE_API_KEY") ?: ""
|
||||
|
||||
val launcherExe = System.getenv("HMCL_LAUNCHER_EXE") ?: ""
|
||||
@@ -154,7 +153,6 @@ val hmclProperties = buildList {
|
||||
}
|
||||
add("hmcl.version.type" to versionType)
|
||||
add("hmcl.microsoft.auth.id" to microsoftAuthId)
|
||||
add("hmcl.microsoft.auth.secret" to microsoftAuthSecret)
|
||||
add("hmcl.curseforge.apikey" to curseForgeApiKey)
|
||||
add("hmcl.authlib-injector.version" to libs.authlib.injector.get().version!!)
|
||||
}
|
||||
|
||||
@@ -200,9 +200,6 @@ public final class EntryPoint {
|
||||
} catch (SelfDependencyPatcher.PatchException e) {
|
||||
LOG.error("Unable to patch JVM", e);
|
||||
showErrorAndExit(i18n("fatal.javafx.missing"));
|
||||
} catch (SelfDependencyPatcher.IncompatibleVersionException e) {
|
||||
LOG.error("Unable to patch JVM", e);
|
||||
showErrorAndExit(i18n("fatal.javafx.incompatible"));
|
||||
} catch (CancellationException e) {
|
||||
LOG.error("User cancels downloading JavaFX", e);
|
||||
exit(0);
|
||||
|
||||
@@ -34,8 +34,8 @@ public final class Metadata {
|
||||
private Metadata() {
|
||||
}
|
||||
|
||||
public static final String NAME = "HMCL";
|
||||
public static final String FULL_NAME = "Hello Minecraft! Launcher";
|
||||
public static final String NAME = "HMCL-HITWH";
|
||||
public static final String FULL_NAME = "Hitwh Games Launcher";
|
||||
public static final String VERSION = System.getProperty("hmcl.version.override", JarUtils.getAttribute("hmcl.version", "@develop@"));
|
||||
|
||||
public static final String TITLE = NAME + " " + VERSION;
|
||||
|
||||
@@ -29,9 +29,8 @@ import org.jackhuang.hmcl.util.io.JarUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
@@ -43,6 +42,8 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
public final class OAuthServer extends NanoHTTPD implements OAuth.Session {
|
||||
private final int port;
|
||||
private final CompletableFuture<String> future = new CompletableFuture<>();
|
||||
private final String codeVerifier;
|
||||
private final String state;
|
||||
|
||||
public static String lastlyOpenedURL;
|
||||
|
||||
@@ -52,6 +53,34 @@ public final class OAuthServer extends NanoHTTPD implements OAuth.Session {
|
||||
super(port);
|
||||
|
||||
this.port = port;
|
||||
|
||||
var encoder = Base64.getUrlEncoder().withoutPadding();
|
||||
var random = new SecureRandom();
|
||||
|
||||
{
|
||||
// https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1
|
||||
// https://datatracker.ietf.org/doc/html/rfc6749#section-10.12
|
||||
byte[] bytes = new byte[32];
|
||||
random.nextBytes(bytes);
|
||||
this.state = encoder.encodeToString(bytes);
|
||||
}
|
||||
|
||||
{
|
||||
// https://datatracker.ietf.org/doc/html/rfc7636#section-4.1
|
||||
byte[] bytes = new byte[64];
|
||||
random.nextBytes(bytes);
|
||||
this.codeVerifier = encoder.encodeToString(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCodeVerifier() {
|
||||
return codeVerifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -93,12 +122,22 @@ public final class OAuthServer extends NanoHTTPD implements OAuth.Session {
|
||||
String parameters = session.getQueryParameterString();
|
||||
|
||||
Map<String, String> query = mapOf(NetworkUtils.parseQuery(parameters));
|
||||
if (query.containsKey("code")) {
|
||||
idToken = query.get("id_token");
|
||||
future.complete(query.get("code"));
|
||||
|
||||
String code = query.get("code");
|
||||
if (code != null) {
|
||||
if (this.state.equals(query.get("state"))) {
|
||||
idToken = query.get("id_token");
|
||||
future.complete(code);
|
||||
} else if (query.containsKey("state")) {
|
||||
LOG.warning("Failed to authenticate: invalid state in parameters");
|
||||
future.completeExceptionally(new AuthenticationException("Failed to authenticate: invalid state"));
|
||||
} else {
|
||||
LOG.warning("Failed to authenticate: missing state in parameters");
|
||||
future.completeExceptionally(new AuthenticationException("Failed to authenticate: missing state"));
|
||||
}
|
||||
} else {
|
||||
LOG.warning("Error: " + parameters);
|
||||
future.completeExceptionally(new AuthenticationException("failed to authenticate"));
|
||||
LOG.warning("Failed to authenticate: missing authorization code in parameters");
|
||||
future.completeExceptionally(new AuthenticationException("Failed to authenticate: missing authorization code"));
|
||||
}
|
||||
|
||||
String html;
|
||||
@@ -168,17 +207,6 @@ public final class OAuthServer extends NanoHTTPD implements OAuth.Session {
|
||||
return System.getProperty("hmcl.microsoft.auth.id",
|
||||
JarUtils.getAttribute("hmcl.microsoft.auth.id", ""));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientSecret() {
|
||||
return System.getProperty("hmcl.microsoft.auth.secret",
|
||||
JarUtils.getAttribute("hmcl.microsoft.auth.secret", ""));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPublicClient() {
|
||||
return true; // We have turned on the device auth flow.
|
||||
}
|
||||
}
|
||||
|
||||
public static class GrantDeviceCodeEvent extends Event {
|
||||
|
||||
@@ -198,7 +198,7 @@ public final class Accounts {
|
||||
throw new IllegalStateException("Already initialized");
|
||||
|
||||
if (!config().isAddedLittleSkin()) {
|
||||
AuthlibInjectorServer littleSkin = new AuthlibInjectorServer("https://littleskin.cn/api/yggdrasil/");
|
||||
AuthlibInjectorServer littleSkin = new AuthlibInjectorServer("https://skin.littlelan.cn/api/yggdrasil/");
|
||||
|
||||
if (config().getAuthlibInjectorServers().stream().noneMatch(it -> littleSkin.getUrl().equals(it.getUrl()))) {
|
||||
config().getAuthlibInjectorServers().add(0, littleSkin);
|
||||
|
||||
@@ -213,7 +213,7 @@ public class OfflineAccountSkinPane extends StackPane {
|
||||
});
|
||||
|
||||
JFXHyperlink littleSkinLink = new JFXHyperlink(i18n("account.skin.type.little_skin"));
|
||||
littleSkinLink.setOnAction(e -> FXUtils.openLink("https://littleskin.cn/"));
|
||||
littleSkinLink.setOnAction(e -> FXUtils.openLink("https://skin.littlelan.cn/"));
|
||||
JFXButton cancelButton = new JFXButton(i18n("button.cancel"));
|
||||
cancelButton.getStyleClass().add("dialog-cancel");
|
||||
cancelButton.setOnAction(e -> fireEvent(new DialogCloseEvent()));
|
||||
|
||||
@@ -31,7 +31,6 @@ import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
|
||||
@@ -83,8 +82,9 @@ public class TaskExecutorDialogPane extends BorderPane {
|
||||
setCancel(cancel);
|
||||
|
||||
btnCancel.setOnAction(e -> {
|
||||
Optional.ofNullable(executor).ifPresent(TaskExecutor::cancel);
|
||||
if (onCancel.getCancellationAction() != null) {
|
||||
if (executor != null)
|
||||
executor.cancel();
|
||||
onCancel.getCancellationAction().accept(this);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -21,6 +21,7 @@ import javafx.beans.property.BooleanProperty;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.game.World;
|
||||
import org.jackhuang.hmcl.mod.Datapack;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
@@ -44,13 +45,13 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public final class DatapackListPage extends ListPageBase<DatapackListPageSkin.DatapackInfoObject> implements WorldManagePage.WorldRefreshable {
|
||||
private final Path worldDir;
|
||||
private final World world;
|
||||
private final Datapack datapack;
|
||||
final BooleanProperty readOnly;
|
||||
|
||||
public DatapackListPage(WorldManagePage worldManagePage) {
|
||||
this.worldDir = worldManagePage.getWorld().getFile();
|
||||
datapack = new Datapack(worldDir.resolve("datapacks"));
|
||||
world = worldManagePage.getWorld();
|
||||
datapack = new Datapack(world.getFile().resolve("datapacks"));
|
||||
setItems(MappedObservableList.create(datapack.getPacks(), DatapackListPageSkin.DatapackInfoObject::new));
|
||||
readOnly = worldManagePage.readOnlyProperty();
|
||||
FXUtils.applyDragListener(this, it -> Objects.equals("zip", FileUtils.getExtension(it)),
|
||||
@@ -68,7 +69,7 @@ public final class DatapackListPage extends ListPageBase<DatapackListPageSkin.Da
|
||||
|
||||
private void installSingleDatapack(Path datapack) {
|
||||
try {
|
||||
this.datapack.installPack(datapack);
|
||||
this.datapack.installPack(datapack, world.getGameVersion());
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
LOG.warning("Unable to parse datapack file " + datapack, e);
|
||||
}
|
||||
|
||||
@@ -50,7 +50,9 @@ public final class HMCLLocalizedDownloadListPage extends DownloadListPage {
|
||||
}
|
||||
|
||||
public static DownloadListPage ofShaderPack(DownloadPage.DownloadCallback callback, boolean versionSelection) {
|
||||
return new HMCLLocalizedDownloadListPage(callback, versionSelection, RemoteModRepository.Type.SHADER_PACK, null, ModrinthRemoteModRepository.SHADER_PACKS);
|
||||
var page = new HMCLLocalizedDownloadListPage(callback, versionSelection, RemoteModRepository.Type.SHADER_PACK, null, ModrinthRemoteModRepository.SHADER_PACKS);
|
||||
page.supportChinese.set(false);
|
||||
return page;
|
||||
}
|
||||
|
||||
private HMCLLocalizedDownloadListPage(DownloadPage.DownloadCallback callback, boolean versionSelection, RemoteModRepository.Type type, CurseForgeRemoteModRepository curseForge, ModrinthRemoteModRepository modrinth) {
|
||||
|
||||
@@ -20,20 +20,12 @@ package org.jackhuang.hmcl.util;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Alert.AlertType;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.countly.CrashReport;
|
||||
import org.jackhuang.hmcl.ui.CrashWindow;
|
||||
import org.jackhuang.hmcl.upgrade.IntegrityChecker;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
@@ -103,9 +95,6 @@ public final class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
if (showCrashWindow) {
|
||||
new CrashWindow(report).show();
|
||||
}
|
||||
if (!UpdateChecker.isOutdated() && IntegrityChecker.isSelfVerified()) {
|
||||
reportToServer(report);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Throwable handlingException) {
|
||||
@@ -115,22 +104,4 @@ public final class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
FileSaver.shutdown();
|
||||
LOG.shutdown();
|
||||
}
|
||||
|
||||
private void reportToServer(CrashReport crashReport) {
|
||||
Thread t = new Thread(() -> {
|
||||
HashMap<String, String> map = new HashMap<>();
|
||||
map.put("crash_report", crashReport.getDisplayText());
|
||||
map.put("version", Metadata.VERSION);
|
||||
map.put("log", LOG.getLogs());
|
||||
try {
|
||||
String response = NetworkUtils.doPost(URI.create(Metadata.PUBLISH_URL + "/hmcl/crash.php"), map);
|
||||
if (StringUtils.isNotBlank(response))
|
||||
LOG.error("Crash server response: " + response);
|
||||
} catch (IOException ex) {
|
||||
LOG.error("Unable to post HMCL server.", ex);
|
||||
}
|
||||
});
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,10 +79,11 @@ public final class SelfDependencyPatcher {
|
||||
private final byte[] buffer = new byte[IOUtils.DEFAULT_BUFFER_SIZE];
|
||||
private final MessageDigest digest = DigestUtils.getDigest("SHA-1");
|
||||
|
||||
private SelfDependencyPatcher() throws IncompatibleVersionException {
|
||||
private SelfDependencyPatcher() throws PatchException {
|
||||
// We can only self-patch JavaFX on specific platform.
|
||||
if (dependencies == null) {
|
||||
throw new IncompatibleVersionException();
|
||||
throw new PatchException("Unsupported platform: operating system %s, architecture %s".formatted(
|
||||
System.getProperty("os.name"), System.getProperty("os.arch")));
|
||||
}
|
||||
|
||||
final String customUrl = System.getProperty("hmcl.openjfx.repo");
|
||||
@@ -159,18 +160,12 @@ public final class SelfDependencyPatcher {
|
||||
/**
|
||||
* Patch in any missing dependencies, if any.
|
||||
*/
|
||||
public static void patch() throws PatchException, IncompatibleVersionException, CancellationException {
|
||||
public static void patch() throws PatchException, CancellationException {
|
||||
// Do nothing if JavaFX is detected
|
||||
try {
|
||||
try {
|
||||
Class.forName("javafx.application.Application");
|
||||
return;
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
} catch (UnsupportedClassVersionError error) {
|
||||
// Loading the JavaFX class was unsupported.
|
||||
// We are probably on 8 and its on 11
|
||||
throw new IncompatibleVersionException();
|
||||
Class.forName("javafx.application.Application");
|
||||
return;
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
SelfDependencyPatcher patcher = new SelfDependencyPatcher();
|
||||
@@ -378,14 +373,15 @@ public final class SelfDependencyPatcher {
|
||||
}
|
||||
|
||||
public static class PatchException extends Exception {
|
||||
PatchException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
PatchException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
||||
public static class IncompatibleVersionException extends Exception {
|
||||
}
|
||||
|
||||
public static class ProgressFrame extends JDialog {
|
||||
|
||||
private final JProgressBar progressBar;
|
||||
|
||||
@@ -158,7 +158,7 @@ account.skin.type.alex=Alex
|
||||
account.skin.type.csl_api=Blessing Skin
|
||||
account.skin.type.csl_api.location=Address
|
||||
account.skin.type.csl_api.location.hint=CustomSkinAPI URL
|
||||
account.skin.type.little_skin=LittleSkin
|
||||
account.skin.type.little_skin=Hitwh Games
|
||||
account.skin.type.little_skin.hint=You need to create a player with the same player name as your offline account on your skin provider site. Your skin will now be set to the skin assigned to your player on the skin provider site.
|
||||
account.skin.type.local_file=Local Skin File
|
||||
account.skin.type.steve=Steve
|
||||
@@ -401,9 +401,6 @@ extension.schematic=Schematic File
|
||||
extension.world=World Archive
|
||||
|
||||
fatal.create_hmcl_current_directory_failure=Hello Minecraft! Launcher cannot create the HMCL directory (%s). Please move HMCL to another location and reopen it.
|
||||
fatal.javafx.incompatible=Missing JavaFX environment.\n\
|
||||
Hello Minecraft! Launcher cannot automatically install JavaFX on Java <11.\n\
|
||||
Please update your Java to version 11 or later.
|
||||
fatal.javafx.incomplete=The JavaFX environment is incomplete.\n\
|
||||
Please try replacing your Java or reinstalling OpenJFX.
|
||||
fatal.javafx.missing=Missing JavaFX environment. Please open Hello Minecraft! Launcher with Java, which includes OpenJFX.
|
||||
|
||||
@@ -137,7 +137,7 @@ account.skin.type.alex=Alex
|
||||
account.skin.type.csl_api=Blessing Skin
|
||||
account.skin.type.csl_api.location=العنوان
|
||||
account.skin.type.csl_api.location.hint=عنوان URL لـ CustomSkinAPI
|
||||
account.skin.type.little_skin=LittleSkin
|
||||
account.skin.type.little_skin=Hitwh Games
|
||||
account.skin.type.little_skin.hint=يجب عليك إنشاء لاعب بنفس اسم اللاعب لحسابك غير المتصل على موقع مزود المظهر الخاص بك. سيتم الآن تعيين مظهرك على المظهر المخصص للاعبك على موقع مزود المظهر.
|
||||
account.skin.type.local_file=ملف مظهر محلي
|
||||
account.skin.type.steve=Steve
|
||||
@@ -370,9 +370,6 @@ extension.mod=ملف التعديل
|
||||
extension.world=أرشيف العالم
|
||||
|
||||
fatal.create_hmcl_current_directory_failure=Hello Minecraft! Launcher لا يمكنه إنشاء دليل HMCL (%s). يرجى نقل HMCL إلى موقع آخر وإعادة فتحه.
|
||||
fatal.javafx.incompatible=بيئة JavaFX مفقودة.\n\
|
||||
Hello Minecraft! Launcher لا يمكنه تثبيت JavaFX تلقائياً على Java <11.\n\
|
||||
يرجى تحديث Java الخاص بك إلى الإصدار 11 أو أحدث.
|
||||
fatal.javafx.incomplete=بيئة JavaFX غير مكتملة.\n\
|
||||
يرجى محاولة استبدال Java الخاص بك أو إعادة تثبيت OpenJFX.
|
||||
fatal.javafx.missing=بيئة JavaFX مفقودة. يرجى فتح Hello Minecraft! Launcher باستخدام Java، والذي يتضمن OpenJFX.
|
||||
|
||||
@@ -140,7 +140,7 @@ account.skin.type.alex=Alex
|
||||
account.skin.type.csl_api=Blessing skin
|
||||
account.skin.type.csl_api.location=URL
|
||||
account.skin.type.csl_api.location.hint=CustomSkinAPI URL
|
||||
account.skin.type.little_skin=LittleSkin
|
||||
account.skin.type.little_skin=Hitwh Games
|
||||
account.skin.type.little_skin.hint=Tienes que crear un jugador con el mismo nombre de jugador que tu cuenta sin conexión en el sitio web del proveedor de aspectos. Tu aspecto se ajustará ahora al aspecto asignado a tu jugador en el sitio web del proveedor de aspectos.
|
||||
account.skin.type.local_file=Archivo local de aspecto
|
||||
account.skin.type.steve=Steve
|
||||
@@ -372,9 +372,6 @@ extension.mod=Archivo mod
|
||||
extension.world=Archivo del mundo
|
||||
|
||||
fatal.create_hmcl_current_directory_failure=El lanzador no puede crear el directorio HMCL (%s). Por favor, mueva el lanzador a otra ubicación y vuelva a abrirlo.
|
||||
fatal.javafx.incompatible=No se encontró un entorno JavaFX.\n\
|
||||
Hello Minecraft! Launcher no puede instalar automáticamente JavaFX con versiones de Java inferiores a la 11.\n\
|
||||
Por favor, actualice su Java a la versión 11 o posterior.
|
||||
fatal.javafx.incomplete=El entorno JavaFX está incompleto.\n\
|
||||
Por favor, intente reemplazar su Java o reinstalar OpenJFX.
|
||||
fatal.javafx.missing=No se encontró un entorno JavaFX.\n\
|
||||
|
||||
@@ -110,7 +110,7 @@ account.skin.model.slim=Slim
|
||||
account.skin.type.csl_api=Blessing Skin
|
||||
account.skin.type.csl_api.location=アドレス
|
||||
account.skin.type.csl_api.location.hint=CustomSkinAPI
|
||||
account.skin.type.little_skin=LittleSkin
|
||||
account.skin.type.little_skin=Hitwh Games
|
||||
account.skin.type.little_skin.hint=スキンのWebサイトでキャラクターを作成する必要があります。そして、このオフラインアカウントのスキンは、同じ名前のスキンWebサイトのキャラクターのスキンにバインドされます。
|
||||
account.skin.type.local_file=ローカルスキン画像ファイル
|
||||
account.skin.upload=スキンをアップロードする
|
||||
@@ -322,7 +322,6 @@ extension.datapack=Datapack
|
||||
extension.mod=Modファイル
|
||||
extension.world=World zip
|
||||
|
||||
fatal.javafx.incompatible=アプリケーションは11未満の現在のJava環境でJavaFXにパッチを適用できません。\nこのアプリは、JDK11以降またはJavaFXがバンドルされたJDKを使用して実行してください。
|
||||
fatal.javafx.missing=JavaFXがありません。\nJava11以降を使用している場合は、Oracle JRE 8にダウングレードするか、BellSoft Liberica FullJREをインストールしてください。\n他のOpenJDKビルドを使用している場合は、OpenJFXが含まれていることを確認してください。
|
||||
fatal.config_loading_failure=構成にアクセスできません。\nHelloMinecraftを確認してください。Launcherには、「%s」とその中のファイルへの読み取りおよび書き込みアクセス権があります。
|
||||
fatal.migration_requires_manual_reboot=更新が完了しました。Hello Minecraftを再開してください!ランチャー。
|
||||
|
||||
@@ -139,7 +139,7 @@ account.skin.type.alex=艾麗思
|
||||
account.skin.type.csl_api=Blessing Skin 伺服器
|
||||
account.skin.type.csl_api.location=伺服器之址
|
||||
account.skin.type.csl_api.location.hint=CustomSkinAPI 之址
|
||||
account.skin.type.little_skin=LittleSkin 外觀站
|
||||
account.skin.type.little_skin=Hitwh Games 外觀站
|
||||
account.skin.type.little_skin.hint=君須立角之同名者於外觀站,爰傳外觀。離綫戶簿之外觀,其用外觀站之角也。\n君可求助於右上之鈕。
|
||||
account.skin.type.local_file=案頭之外觀圖案
|
||||
account.skin.type.steve=史迪武
|
||||
@@ -368,7 +368,6 @@ extension.mod=改囊案
|
||||
extension.world=生界緊囊
|
||||
|
||||
fatal.create_hmcl_current_directory_failure=HMCL 不能立其案夾 (%s),宜稍遷而再啟之。\n君可求助於 https://docs.hmcl.net/help.html。
|
||||
fatal.javafx.incompatible=JavaFX 闕。\n既行於爪哇之次乎爪哇十一者,HMCL 不能自補之也。宜晉爪哇於十一抑後之者。\n君可求助於 https://docs.hmcl.net/help.html。
|
||||
fatal.javafx.incomplete=JavaFX 損。宜更爪哇,抑復置 OpenJFX。
|
||||
fatal.javafx.missing=JavaFX 闕。君須行 HMCL 於爪哇之俻 OpenJFX 者。\n君可求助於 https://docs.hmcl.net/help.html。
|
||||
fatal.config_change_owner_root=君方啟 HMCL 以根戶簿,是則他戶簿或不能啟之。\n君可求助於 https://docs.hmcl.net/help.html。\n猶啟諸?
|
||||
|
||||
@@ -139,7 +139,7 @@ account.skin.type.alex=Алекс
|
||||
account.skin.type.csl_api=Blessing Skin
|
||||
account.skin.type.csl_api.location=Адрес
|
||||
account.skin.type.csl_api.location.hint=URL-адрес CustomSkinAPI
|
||||
account.skin.type.little_skin=LittleSkin
|
||||
account.skin.type.little_skin=Hitwh Games
|
||||
account.skin.type.little_skin.hint=Вам нужно создать игрока с тем же именем, что и у вашего офлайн-аукнта на сайте поставщика скинов. Теперь ваш скин будет установлен на скин, назначенный вашему игроку на сайте поставщика скинов.
|
||||
account.skin.type.local_file=Локальный файл скина
|
||||
account.skin.type.steve=Стив
|
||||
@@ -372,9 +372,6 @@ extension.mod=Файл мода
|
||||
extension.world=Архив мира
|
||||
|
||||
fatal.create_hmcl_current_directory_failure=Лаунчер не может создать папку HMCL (%s). Пожалуйста, переместите лаунчер в другое место и откройте его снова.
|
||||
fatal.javafx.incompatible=Отсутствует среда JavaFX.\n\
|
||||
Лаунчер не может автоматически установить JavaFX на Java <11.\n\
|
||||
Обновите Java до версии 11 или более поздней.
|
||||
fatal.javafx.incomplete=Среда JavaFX является неполной.\n\
|
||||
Попробуйте заменить Java или переустановить OpenJFX.
|
||||
fatal.javafx.missing=Отсутствует среда JavaFX.\n\
|
||||
|
||||
@@ -139,7 +139,7 @@ account.skin.type.alex=Алекс
|
||||
account.skin.type.csl_api=Blessing Skin
|
||||
account.skin.type.csl_api.location=Адреса
|
||||
account.skin.type.csl_api.location.hint=URL CustomSkinAPI
|
||||
account.skin.type.little_skin=LittleSkin
|
||||
account.skin.type.little_skin=Hitwh Games
|
||||
account.skin.type.little_skin.hint=Вам потрібно створити гравця з тим же ім'ям гравця, що й у вашого автономного облікового запису, на сайті постачальника скінів. Ваш скін тепер буде встановлено на скін, призначений вашому гравцю на сайті постачальника скінів.
|
||||
account.skin.type.local_file=Локальний файл скіна
|
||||
account.skin.type.steve=Стів
|
||||
@@ -370,9 +370,6 @@ extension.mod=Файл мода
|
||||
extension.world=Архів світу
|
||||
|
||||
fatal.create_hmcl_current_directory_failure=Hello Minecraft! Лаунчер не може створити каталог HMCL (%s). Перемістіть HMCL в інше місце та відкрийте його знову.
|
||||
fatal.javafx.incompatible=Відсутнє середовище JavaFX. \n\
|
||||
Hello Minecraft! Лаунчер не може автоматично встановити JavaFX на Java <11. \n\
|
||||
Оновіть вашу Java до версії 11 або новішої.
|
||||
fatal.javafx.incomplete=Середовище JavaFX неповне. \n\
|
||||
Спробуйте замінити вашу Java або перевстановити OpenJFX.
|
||||
fatal.javafx.missing=Відсутнє середовище JavaFX. Відкрийте Hello Minecraft! Лаунчер за допомогою Java, яка включає OpenJFX.
|
||||
|
||||
@@ -155,7 +155,7 @@ account.skin.type.alex=Alex
|
||||
account.skin.type.csl_api=Blessing Skin 伺服器
|
||||
account.skin.type.csl_api.location=伺服器位址
|
||||
account.skin.type.csl_api.location.hint=CustomSkinAPI 位址
|
||||
account.skin.type.little_skin=LittleSkin 皮膚站
|
||||
account.skin.type.little_skin=Hitwh Games 皮膚站
|
||||
account.skin.type.little_skin.hint=你需要在皮膚站中新增並使用和該離線帳戶同名角色。此時離線帳戶外觀將為皮膚站上對應角色所設定的外觀。
|
||||
account.skin.type.local_file=本機外觀圖片檔案
|
||||
account.skin.type.steve=Steve
|
||||
@@ -393,7 +393,6 @@ extension.schematic=原理圖檔案
|
||||
extension.world=世界壓縮檔
|
||||
|
||||
fatal.create_hmcl_current_directory_failure=Hello Minecraft! Launcher 無法建立 HMCL 資料夾 (%s),請將 HMCL 移動至其他位置再開啟。
|
||||
fatal.javafx.incompatible=缺少 JavaFX 執行環境。\nHMCL 無法在低於 Java 11 的 Java 環境上自行補全 JavaFX 執行環境。請更新到 Java 11 或更高版本。
|
||||
fatal.javafx.incomplete=JavaFX 執行環境不完整。請嘗試更換你的 Java 或者重新安裝 OpenJFX。
|
||||
fatal.javafx.missing=缺少 JavaFX 執行環境。請使用包含 OpenJFX 的 Java 執行環境開啟 Hello Minecraft! Launcher。
|
||||
fatal.config_change_owner_root=你正在使用 root 帳戶開啟 Hello Minecraft! Launcher,這可能導致你未來無法使用其他帳戶正常開啟 HMCL。\n是否繼續開啟?
|
||||
|
||||
@@ -157,7 +157,7 @@ account.skin.type.alex=Alex
|
||||
account.skin.type.csl_api=Blessing Skin 服务器
|
||||
account.skin.type.csl_api.location=服务器地址
|
||||
account.skin.type.csl_api.location.hint=CustomSkinAPI 地址
|
||||
account.skin.type.little_skin=LittleSkin 皮肤站
|
||||
account.skin.type.little_skin=Hitwh Games 皮肤站
|
||||
account.skin.type.little_skin.hint=你需要在皮肤站中创建并使用和该离线账户同名的角色。此时离线账户皮肤将显示为皮肤站上对应角色所设置的皮肤。\n你可以点击右上角帮助按钮进行求助。
|
||||
account.skin.type.local_file=本地皮肤图片文件
|
||||
account.skin.type.steve=Steve
|
||||
@@ -396,7 +396,6 @@ extension.schematic=原理图文件
|
||||
extension.world=世界压缩包
|
||||
|
||||
fatal.create_hmcl_current_directory_failure=Hello Minecraft! Launcher 无法创建 HMCL 文件夹 (%s),请将 HMCL 移动至其他位置再启动。\n如遇到问题,你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。
|
||||
fatal.javafx.incompatible=缺少 JavaFX 运行环境。\nHello Minecraft! Launcher 无法在低于 Java 11 的 Java 环境上自行补全 JavaFX 运行环境。请更新到 Java 11 或更高版本。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。
|
||||
fatal.javafx.incomplete=JavaFX 运行环境不完整。请尝试更换你的 Java 或者重新安装 OpenJFX。
|
||||
fatal.javafx.missing=缺少 JavaFX 运行环境。请使用包含 OpenJFX 的 Java 运行环境启动 Hello Minecraft! Launcher。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。
|
||||
fatal.config_change_owner_root=你正在使用 root 账户启动 Hello Minecraft! Launcher,这可能导致你未来无法正常使用其他账户正常启动 HMCL。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。\n是否继续启动?
|
||||
|
||||
@@ -24,12 +24,16 @@ import org.jackhuang.hmcl.util.io.HttpRequest;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Base64;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public class OAuth {
|
||||
public static final OAuth MICROSOFT = new OAuth(
|
||||
@@ -77,17 +81,31 @@ public class OAuth {
|
||||
|
||||
private Result authenticateAuthorizationCode(Options options) throws IOException, InterruptedException, JsonParseException, ExecutionException, AuthenticationException {
|
||||
Session session = options.callback.startServer();
|
||||
|
||||
String codeVerifier = session.getCodeVerifier();
|
||||
String state = session.getState();
|
||||
String codeChallenge = generateCodeChallenge(codeVerifier);
|
||||
|
||||
options.callback.openBrowser(GrantFlow.AUTHORIZATION_CODE, NetworkUtils.withQuery(authorizationURL,
|
||||
mapOf(pair("client_id", options.callback.getClientId()), pair("response_type", "code"),
|
||||
pair("redirect_uri", session.getRedirectURI()), pair("scope", options.scope),
|
||||
pair("prompt", "select_account"))));
|
||||
mapOf(pair("client_id", options.callback.getClientId()),
|
||||
pair("response_type", "code"),
|
||||
pair("redirect_uri", session.getRedirectURI()),
|
||||
pair("scope", options.scope),
|
||||
pair("prompt", "select_account"),
|
||||
pair("code_challenge", codeChallenge),
|
||||
pair("state", state),
|
||||
pair("code_challenge_method", "S256")
|
||||
)));
|
||||
String code = session.waitFor();
|
||||
|
||||
// Authorization Code -> Token
|
||||
AuthorizationResponse response = HttpRequest.POST(accessTokenURL)
|
||||
.form(pair("client_id", options.callback.getClientId()), pair("code", code),
|
||||
pair("grant_type", "authorization_code"), pair("client_secret", options.callback.getClientSecret()),
|
||||
pair("redirect_uri", session.getRedirectURI()), pair("scope", options.scope))
|
||||
.form(pair("client_id", options.callback.getClientId()),
|
||||
pair("code", code),
|
||||
pair("grant_type", "authorization_code"),
|
||||
pair("code_verifier", codeVerifier),
|
||||
pair("redirect_uri", session.getRedirectURI()),
|
||||
pair("scope", options.scope))
|
||||
.ignoreHttpCode()
|
||||
.retry(5)
|
||||
.getJson(AuthorizationResponse.class);
|
||||
@@ -153,10 +171,6 @@ public class OAuth {
|
||||
pair("grant_type", "refresh_token")
|
||||
);
|
||||
|
||||
if (!options.callback.isPublicClient()) {
|
||||
query.put("client_secret", options.callback.getClientSecret());
|
||||
}
|
||||
|
||||
RefreshResponse response = HttpRequest.POST(tokenURL)
|
||||
.form(query)
|
||||
.accept("application/json")
|
||||
@@ -174,6 +188,20 @@ public class OAuth {
|
||||
}
|
||||
}
|
||||
|
||||
private static String generateCodeChallenge(String codeVerifier) {
|
||||
// https://datatracker.ietf.org/doc/html/rfc7636#section-4.2
|
||||
try {
|
||||
byte[] bytes = codeVerifier.getBytes(StandardCharsets.US_ASCII);
|
||||
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
|
||||
messageDigest.update(bytes, 0, bytes.length);
|
||||
byte[] digest = messageDigest.digest();
|
||||
return Base64.getUrlEncoder().withoutPadding().encodeToString(digest);
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to generate code challenge", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void handleErrorResponse(ErrorResponse response) throws AuthenticationException {
|
||||
if (response.error == null || response.errorDescription == null) {
|
||||
return;
|
||||
@@ -207,6 +235,9 @@ public class OAuth {
|
||||
}
|
||||
|
||||
public interface Session {
|
||||
String getState();
|
||||
|
||||
String getCodeVerifier();
|
||||
|
||||
String getRedirectURI();
|
||||
|
||||
@@ -243,10 +274,6 @@ public class OAuth {
|
||||
void openBrowser(GrantFlow grantFlow, String url) throws IOException;
|
||||
|
||||
String getClientId();
|
||||
|
||||
String getClientSecret();
|
||||
|
||||
boolean isPublicClient();
|
||||
}
|
||||
|
||||
public enum GrantFlow {
|
||||
|
||||
@@ -164,7 +164,7 @@ public class Skin {
|
||||
case LITTLE_SKIN:
|
||||
case CUSTOM_SKIN_LOADER_API:
|
||||
String realCslApi = type == Type.LITTLE_SKIN
|
||||
? "https://littleskin.cn/csl"
|
||||
? "https://skin.littlelan.cn/csl"
|
||||
: NetworkUtils.addHttpsIfMissing(StringUtils.removeSuffix(Lang.requireNonNullElse(cslApi, ""), "/"));
|
||||
return Task.composeAsync(() -> new GetTask(String.format("%s/%s.json", realCslApi, username)))
|
||||
.thenComposeAsync(json -> {
|
||||
|
||||
@@ -20,6 +20,8 @@ package org.jackhuang.hmcl.download;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
@@ -47,25 +49,60 @@ public class MultipleSourceVersionList extends VersionList<RemoteVersion> {
|
||||
|
||||
private Task<?> refreshAsync(String gameVersion, int sourceIndex) {
|
||||
VersionList<?> versionList = backends[sourceIndex];
|
||||
return versionList.refreshAsync(gameVersion)
|
||||
.thenComposeAsync(() -> {
|
||||
Task<?> refreshTask = versionList.refreshAsync(gameVersion);
|
||||
|
||||
return new Task<>() {
|
||||
private Task<?> nextTask = null;
|
||||
|
||||
{
|
||||
setSignificance(TaskSignificance.MODERATE);
|
||||
setName("MultipleSourceVersionList.refreshAsync(task=%s, index=%d, all=%d)".formatted(
|
||||
refreshTask.getName(), sourceIndex, backends.length)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Task<?>> getDependents() {
|
||||
return List.of(refreshTask);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends Task<?>> getDependencies() {
|
||||
return nextTask != null ? List.of(nextTask) : List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRelyingOnDependents() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
if (isDependentsSucceeded()) {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
versions.putAll(gameVersion, versionList.getVersions(gameVersion));
|
||||
} catch (Exception e) {
|
||||
if (sourceIndex == backends.length - 1) {
|
||||
LOG.warning("Failed to fetch versions list from all sources", e);
|
||||
throw e;
|
||||
} else {
|
||||
LOG.warning("Failed to fetch versions list and try to fetch from other source", e);
|
||||
return refreshAsync(gameVersion, sourceIndex + 1);
|
||||
}
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
setResult(refreshTask.getResult());
|
||||
} else {
|
||||
Exception exception = refreshTask.getException();
|
||||
assert exception != null;
|
||||
|
||||
if (sourceIndex == backends.length - 1) {
|
||||
LOG.warning("Failed to fetch versions list from all sources", exception);
|
||||
setSignificance(TaskSignificance.MINOR);
|
||||
throw exception;
|
||||
} else {
|
||||
LOG.warning("Failed to fetch versions list and try to fetch from other source", exception);
|
||||
nextTask = refreshAsync(gameVersion, sourceIndex + 1);
|
||||
nextTask.storeTo(this::setResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -29,6 +29,7 @@ import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.Unzipper;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.*;
|
||||
@@ -57,7 +58,7 @@ public class Datapack {
|
||||
return packs;
|
||||
}
|
||||
|
||||
public static void installPack(Path sourceDatapackPath, Path targetDatapackDirectory) throws IOException {
|
||||
public static void installPack(Path sourceDatapackPath, Path targetDatapackDirectory, GameVersionNumber gameVersionNumber) throws IOException {
|
||||
boolean containsMultiplePacks;
|
||||
Set<String> packs = new HashSet<>();
|
||||
try (FileSystem fs = CompressingUtils.readonly(sourceDatapackPath).setAutoDetectEncoding(true).build()) {
|
||||
@@ -106,7 +107,19 @@ public class Datapack {
|
||||
.setSubDirectory("/datapacks/")
|
||||
.unzip();
|
||||
|
||||
try (FileSystem outputResourcesZipFS = CompressingUtils.createWritableZipFileSystem(targetDatapackDirectory.getParent().resolve("resources.zip"));
|
||||
Path targetResourceZipPath;
|
||||
// When the version cannot be obtained, the old version logic is used by default.
|
||||
boolean useNewResourcePath = gameVersionNumber != null
|
||||
&& gameVersionNumber.compareTo("26.1-snapshot-6") >= 0;
|
||||
|
||||
if (useNewResourcePath) {
|
||||
Files.createDirectories(targetDatapackDirectory.getParent().resolve("resourcepacks"));
|
||||
targetResourceZipPath = targetDatapackDirectory.getParent().resolve("resourcepacks/resources.zip");
|
||||
} else {
|
||||
targetResourceZipPath = targetDatapackDirectory.getParent().resolve("resources.zip");
|
||||
}
|
||||
|
||||
try (FileSystem outputResourcesZipFS = CompressingUtils.createWritableZipFileSystem(targetResourceZipPath);
|
||||
FileSystem inputPackZipFS = CompressingUtils.createReadOnlyZipFileSystem(sourceDatapackPath)) {
|
||||
Path resourcesZip = inputPackZipFS.getPath("resources.zip");
|
||||
if (Files.isRegularFile(resourcesZip)) {
|
||||
@@ -138,8 +151,8 @@ public class Datapack {
|
||||
}
|
||||
}
|
||||
|
||||
public void installPack(Path sourcePackPath) throws IOException {
|
||||
installPack(sourcePackPath, path);
|
||||
public void installPack(Path sourcePackPath, GameVersionNumber gameVersionNumber) throws IOException {
|
||||
installPack(sourcePackPath, path, gameVersionNumber);
|
||||
loadFromDir();
|
||||
}
|
||||
|
||||
|
||||
@@ -390,7 +390,7 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
|
||||
if (majorLength == 0 || value.length() < majorLength + 2 || value.charAt(majorLength) != '.')
|
||||
throw new IllegalArgumentException(value);
|
||||
|
||||
int major = Integer.parseInt(value.substring(0, majorLength));
|
||||
int major = Integer.parseInt(value, 0, majorLength, 10);
|
||||
if (major != 1 && major < MINIMUM_YEAR_MAJOR_VERSION)
|
||||
throw new IllegalArgumentException(value);
|
||||
|
||||
@@ -401,7 +401,7 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
|
||||
throw new IllegalArgumentException(value);
|
||||
|
||||
try {
|
||||
int minor = Integer.parseInt(value.substring(minorOffset, minorOffset + minorLength));
|
||||
int minor = Integer.parseInt(value, minorOffset, minorOffset + minorLength, 10);
|
||||
int patch = 0;
|
||||
|
||||
if (minorOffset + minorLength < value.length()) {
|
||||
@@ -410,7 +410,7 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
|
||||
if (patchOffset >= value.length() || value.charAt(patchOffset - 1) != '.')
|
||||
throw new IllegalArgumentException(value);
|
||||
|
||||
patch = Integer.parseInt(value.substring(patchOffset));
|
||||
patch = Integer.parseInt(value, patchOffset, value.length(), 10);
|
||||
}
|
||||
|
||||
return new Release(value, value, major, minor, patch, ReleaseType.UNKNOWN, VersionNumber.ZERO, Additional.NONE);
|
||||
@@ -593,8 +593,8 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
|
||||
int year;
|
||||
int week;
|
||||
try {
|
||||
year = Integer.parseInt(value.substring(0, 2));
|
||||
week = Integer.parseInt(value.substring(3, 5));
|
||||
year = Integer.parseInt(value, 0, 2, 10);
|
||||
week = Integer.parseInt(value, 3, 5, 10);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException(value);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[authlib-injector] [INFO] Logging file: F:\.minecraft\authlib-injector.log
|
||||
[authlib-injector] [INFO] Version: 1.2.1
|
||||
[authlib-injector] [INFO] Authentication server: https://littleskin.cn/api/yggdrasil/
|
||||
[authlib-injector] [INFO] Authentication server: https://skin.littlelan.cn/api/yggdrasil/
|
||||
2022-12-08 13:15:41,589 main ERROR Error processing element Queue ([Appenders: null]): CLASS_NOT_FOUND
|
||||
[13:15:43] [main/INFO]: ModLauncher running: args [--username, 哎呀呀呀, --version, 1.16.2, --gameDir, F:\\.minecraft, --assetsDir, F:\.minecraft\assets, --assetIndex, 1.16, --uuid, e3c2fb57f8764ecfa1564c1cc92143f2, --accessToken, ❄❄❄❄❄❄❄❄, --userType, mojang, --versionType, HMCL 3.5.3.228, --width, 854, --height, 480, --launchTarget, fmlclient, --fml.forgeVersion, 33.0.61, --fml.mcVersion, 1.16.2, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20200812.004259]
|
||||
[13:15:43] [main/INFO]: ModLauncher 7.0.1+78+master.e9771d8 starting: java version 17.0.4.1 by Oracle Corporation
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
WARNING: Unknown module: cpw.mods.bootstraplauncher specified to --add-exports
|
||||
[authlib-injector] [INFO] Logging file: C:\Users\********\AppData\Roaming\.minecraft\versions\鎮犵劧浜虹敓1.0\authlib-injector.log
|
||||
[authlib-injector] [INFO] Version: 1.2.3
|
||||
[authlib-injector] [INFO] Authentication server: https://littleskin.cn/api/yggdrasil
|
||||
[authlib-injector] [INFO] Authentication server: https://skin.littlelan.cn/api/yggdrasil
|
||||
[authlib-injector] [INFO] Httpd is running on port 50187
|
||||
[authlib-injector] [INFO] Transformed [com.mojang.patchy.BlockedServers] with [Constant URL Transformer]
|
||||
[authlib-injector] [INFO] Transformed [com.mojang.authlib.HttpAuthenticationService] with [ConcatenateURL Workaround]
|
||||
|
||||
Reference in New Issue
Block a user