Compare commits

...

10 Commits

Author SHA1 Message Date
lan
20c8f914d9 Update: 提交当前代码修改
Some checks failed
Check Codes / build (push) Failing after 5m37s
Java CI / build (push) Failing after 4s
2026-02-24 01:29:29 +08:00
辞庐
586dee4f1b 清理 CrashReporter (#5599) 2026-02-21 23:13:57 +08:00
辞庐
917d11009f 迁移微软授权代码流 client_secret 到 PKCE (#5575)
resolves #5566

Co-authored-by: Glavo <zjx001202@gmail.com>
2026-02-21 22:25:00 +08:00
Glavo
4fa07661bb 优化 MultipleSourceVersionList.refreshAsync 任务名称 (#5602) 2026-02-21 22:19:23 +08:00
辞庐
b1b90e0855 fix: 禁止取消未生效 (#5600) 2026-02-21 22:16:14 +08:00
Glavo
76370099e3 优化 GameVersionNumber.asGameVersion (#5587) 2026-02-21 09:09:27 +08:00
Glavo
2e5e21affa 修复 MultipleSourceVersionList 不会使用备用下载源的问题 (#5585) 2026-02-20 22:19:47 +08:00
Glavo
cceef4f8f8 优化无法补全 JavaFX 时的报错信息 (#5586) 2026-02-20 22:16:53 +08:00
mineDiamond
68c291e23a feat: 添加新版本世界指定资源包位置支持 (#5552) 2026-02-20 21:34:10 +08:00
辞庐
1e42034a0b fix: 光影下载被错误地显示为“支持中英文搜索” (#5558)
Co-authored-by: Glavo <zjx001202@gmail.com>
2026-02-20 21:23:52 +08:00
34 changed files with 197 additions and 399 deletions

View File

@@ -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

View File

@@ -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.

View File

@@ -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)

View File

@@ -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/*"

View File

@@ -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 "&nbsp;**[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

View File

@@ -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!!)
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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()));

View File

@@ -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);
}
});

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -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();
}
}

View File

@@ -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;

View File

@@ -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.

View File

@@ -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.

View File

@@ -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\

View File

@@ -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を再開してくださいランチャー。

View File

@@ -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猶啟諸

View File

@@ -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\

View File

@@ -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.

View File

@@ -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是否繼續開啟

View File

@@ -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是否继续启动

View File

@@ -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 {

View File

@@ -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 -> {

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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]