Compare commits

...
31 Commits
Author SHA1 Message Date
Glavo 0d9e2d92e0 [release/3.9] 修复 CurseForge 源无法搜索到内容的问题 (#5232)
https://github.com/HMCL-dev/HMCL/pull/5220
2026-01-14 20:49:09 +08:00
Glavoand辞庐 9f754e78e8 [release/3.9] Fix #5121 : NeoForge alpha 版本未正确归类的问题 (#5231)
https://github.com/HMCL-dev/HMCL/pull/5125

Co-authored-by: 辞庐 <[email protected]>
2026-01-14 20:48:26 +08:00
GlavoandWulian233 cecd28791b [release/3.9] Fix #5126: BMCLAPI 选择 Liteloader 不支持的版本时报错 (#5230)
https://github.com/HMCL-dev/HMCL/pull/5128

Co-authored-by: Wulian233 <[email protected]>
2026-01-14 20:47:09 +08:00
Glavoand辞庐 5e42652e75 [release/3.9] 更新 NeoForge 版本号规则 (#5229)
https://github.com/HMCL-dev/HMCL/pull/5110

Co-authored-by: 辞庐 <[email protected]>
2026-01-14 20:46:32 +08:00
Glavo 9f682e0c39 [release/3.9] 优化 JFXListView 的平滑滚动效果 (#5228)
https://github.com/HMCL-dev/HMCL/pull/5052
2026-01-14 20:36:57 +08:00
Glavo eac12efafb [release/3.9] 禁用 LogWindow 的平滑滚动效果 (#5227)
https://github.com/HMCL-dev/HMCL/pull/5046
2026-01-14 20:36:11 +08:00
Glavo d3d46f6a29 Fix #5039: 修复陶瓦联机页面部分文本在深色模式下颜色异常的问题 (#5040) 2025-12-21 15:13:58 +08:00
Glavo 6f1db67e9a 优化反馈页面 (#5008)
1. 将主页左下角“官方群组”按钮调整为跳转到“反馈”页面;
2. 将反馈页面分为“官方群组”和“提交反馈”两部分
2025-12-20 20:35:41 +08:00
mineDiamond 0d7ece819d feat: 添加快速进入世界功能 (#4872)
close #2563
2025-12-20 20:33:52 +08:00
Damon Lu 7f852080cd 在 macOS 上添加启动脚本的后缀 .command (#4927) 2025-12-20 20:32:34 +08:00
neveler e0360c2b38 使用 subtitle 显示预览更新提示 (#5019) 2025-12-20 16:04:20 +08:00
Glavo 3bf0eb2a16 Fix #5014: 修复 GameCrashWindow 中 feedbackTextFlow 样式错误的问题 (#5020) 2025-12-20 16:00:42 +08:00
Glavo 84b428b57c 在 JFXCustomColorPickerDialog 中使用 HEX 作为默认 Tab (#5011) 2025-12-20 15:33:03 +08:00
neveler 41895e97e1 修复 subtitle 颜色丢失的问题 (#5018)
fix #5017
2025-12-20 15:32:34 +08:00
zimzaza4 f839dc192c 修改AccountAdvancedListItem 控件, 在 RootPage 中添加快速选择账户 (#4932) 2025-12-19 22:18:10 +08:00
Glavo 24250a6239 更新 CNB Issue 模板 (#5010) 2025-12-19 22:03:43 +08:00
Glavo a3ab2b52b6 添加 CNB Issue 模板 (#5009) 2025-12-19 21:39:00 +08:00
Glavo c4d4c6fcf3 优化镜像仓库同步功能 (#5006) 2025-12-19 16:47:12 +08:00
Glavo f77f005f88 mirror.yml 中直接传递用户名 (#5005) 2025-12-19 16:10:02 +08:00
Glavo 9ec494664c 简化镜像仓库同步 workflow (#5004) 2025-12-19 16:02:02 +08:00
Glavo dc7c883962 在低于 JavaFX 23 的情况下隐藏主题模式跟随系统选项 (#4985) 2025-12-18 21:25:07 +08:00
Glavo 567bd15997 在陶瓦联机界面启动游戏时禁用离线皮肤功能 (#4916) 2025-12-18 21:18:03 +08:00
Glavo 25c9ef0f27 修复无法解析类似 "26.1 Snapshot 1" 的版本号的问题 (#5002) 2025-12-18 21:09:07 +08:00
Glavo d45d994307 将 Release 同步至 CNB (#5000) 2025-12-18 21:08:30 +08:00
Glavo 01b7a6b95a Revert "修复 TransitionPane 动画被打断时节点状态异常的问题" (#4986)
Reverts HMCL-dev/HMCL#4957
2025-12-18 20:47:48 +08:00
神奇的赵二 (OrzMiku) a0593c03c6 支持下载 Java25 (#4990) 2025-12-18 20:46:52 +08:00
Glavo 91f05315aa Add CNB badge (#4999) 2025-12-18 20:11:06 +08:00
Burning_TNT 84b8fff37b Bump authlib-injector to 1.2.7: fix compatibility with 26.1-snapshot-1 (#4992)
Fix #4989
2025-12-17 17:05:07 -05:00
Glavo 3a9cc2b9bb 修复 CNB 同步功能 (#4995) 2025-12-18 05:57:14 +08:00
Glavo 056f30f6f5 将代码同步至 CNB (#4994) 2025-12-18 05:47:29 +08:00
辞庐 2d6e01fcf9 添加输出游戏调试日志选项 (#4144) 2025-12-14 20:49:23 +08:00
65 changed files with 748 additions and 357 deletions

No files matched your search

+8
View File
@@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: QQ 群
url: https://docs.hmcl.net/groups.html
about: Hello Minecraft! Launcher 的官方 QQ 交流群。
- name: Discord 服务器
url: https://discord.gg/jVvC7HfM6U
about: Hello Minecraft! Launcher 的官方 Discord 服务器。
-79
View File
@@ -1,79 +0,0 @@
name: Check Update
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: Generate release note
if: ${{ env.continue == 'true' }}
run: |
if [ "${{ matrix.channel }}" = "stable" ]; then
echo "**This version is a stable version.**" >> RELEASE_NOTE
echo "" >> RELEASE_NOTE
fi
echo "The full changelogs can be found on the website: https://docs.hmcl.net/changelog/${{ matrix.channel }}.html" >> RELEASE_NOTE
echo "" >> RELEASE_NOTE
echo "*Notice: changelogs are written in Chinese.*" >> RELEASE_NOTE
echo "" >> RELEASE_NOTE
echo "| File Name | SHA-256 Checksum |" >> RELEASE_NOTE
echo "| --- | --- |" >> RELEASE_NOTE
echo "| [HMCL-$HMCL_VERSION.exe]($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
env:
GH_DOWNLOAD_BASE_URL: https://github.com/${{ github.repository }}/releases/download
- name: Create 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 }}
-21
View File
@@ -1,21 +0,0 @@
name: Sync to Gitee
on:
push
jobs:
run:
if: ${{ github.repository_owner == 'HMCL-dev' }}
runs-on: ubuntu-latest
steps:
- name: Mirror GitHub to Gitee
uses: Yikun/[email protected]
with:
src: github/HMCL-dev
dst: gitee/huanghongxun
static_list: 'HMCL'
force_update: true
debug: true
dst_key: ${{ secrets.GITEE_SYNC_BOT_PRIVATE_KEY }}
dst_token: ${{ secrets.GITEE_SYNC_BOT_TOKEN }}
cache_path: /github/workspace/hub-mirror-cache
+32
View File
@@ -0,0 +1,32 @@
name: Mirror Repository
on:
workflow_dispatch:
push:
concurrency:
group: mirror-repository
cancel-in-progress: true
jobs:
mirror:
strategy:
matrix:
target:
- 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.target.name }}
if: ${{ github.repository == 'HMCL-dev/HMCL' }}
runs-on: ubuntu-latest
steps:
- name: Mirror GitHub to ${{ matrix.target.name }}
run: |
git clone --mirror "https://github.com/$GITHUB_REPOSITORY.git" -- repo
cd repo
git push -f --prune "https://${{ matrix.target.user }}:${{ secrets[matrix.target.token] }}@${{ matrix.target.repo }}.git" "refs/heads/*:refs/heads/*" "refs/tags/*:refs/tags/*"
+125
View File
@@ -0,0 +1,125 @@
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
@@ -116,9 +116,9 @@ public class JFXCustomColorPickerDialog extends StackPane {
Tab hexTab = new Tab("HEX");
hexTab.setContent(hexField);
tabs.getTabs().add(hexTab);
tabs.getTabs().add(rgbTab);
tabs.getTabs().add(hsbTab);
tabs.getTabs().add(hexTab);
curvedColorPicker.selectedPath.addListener((o, oldVal, newVal) -> {
if (paraTransition != null) {
@@ -39,7 +39,10 @@ public class JFXListViewSkin<T> extends ListViewSkin<T> {
flow = (VirtualFlow<ListCell<T>>) getChildren().get(0);
JFXDepthManager.setDepth(flow, listView.depthProperty().get());
listView.depthProperty().addListener((o, oldVal, newVal) -> JFXDepthManager.setDepth(flow, newVal));
FXUtils.smoothScrolling(flow);
if (!Boolean.TRUE.equals(listView.getProperties().get("no-smooth-scrolling"))) {
FXUtils.smoothScrolling(flow);
}
}
@Override
@@ -25,20 +25,20 @@ import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.download.LibraryAnalyzer;
import org.jackhuang.hmcl.event.Event;
import org.jackhuang.hmcl.event.EventManager;
import org.jackhuang.hmcl.java.JavaRuntime;
import org.jackhuang.hmcl.mod.ModAdviser;
import org.jackhuang.hmcl.mod.Modpack;
import org.jackhuang.hmcl.mod.ModpackConfiguration;
import org.jackhuang.hmcl.mod.ModpackProvider;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.util.FileSaver;
import org.jackhuang.hmcl.setting.VersionIconType;
import org.jackhuang.hmcl.setting.VersionSetting;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.util.FileSaver;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.java.JavaRuntime;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import org.jackhuang.hmcl.util.platform.SystemInfo;
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
@@ -55,8 +55,8 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.Pair.pair;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
public final class HMCLGameRepository extends DefaultGameRepository {
private final Profile profile;
@@ -387,7 +387,7 @@ public final class HMCLGameRepository extends DefaultGameRepository {
vs.setUsesGlobal(true);
}
public LaunchOptions getLaunchOptions(String version, JavaRuntime javaVersion, Path gameDir, List<String> javaAgents, List<String> javaArguments, boolean makeLaunchScript) {
public LaunchOptions.Builder getLaunchOptions(String version, JavaRuntime javaVersion, Path gameDir, List<String> javaAgents, List<String> javaArguments, boolean makeLaunchScript) {
VersionSetting vs = getVersionSetting(version);
LaunchOptions.Builder builder = new LaunchOptions.Builder()
@@ -418,7 +418,6 @@ public final class HMCLGameRepository extends DefaultGameRepository {
.setWidth(vs.getWidth())
.setHeight(vs.getHeight())
.setFullscreen(vs.isFullscreen())
.setServerIp(vs.getServerIp())
.setWrapper(vs.getWrapper())
.setPreLaunchCommand(vs.getPreLaunchCommand())
.setPostExitCommand(vs.getPostExitCommand())
@@ -428,12 +427,17 @@ public final class HMCLGameRepository extends DefaultGameRepository {
.setNativesDir(vs.getNativesDir())
.setProcessPriority(vs.getProcessPriority())
.setRenderer(vs.getRenderer())
.setEnableDebugLogOutput(vs.isEnableDebugLogOutput())
.setUseNativeGLFW(vs.isUseNativeGLFW())
.setUseNativeOpenAL(vs.isUseNativeOpenAL())
.setDaemon(!makeLaunchScript && vs.getLauncherVisibility().isDaemon())
.setJavaAgents(javaAgents)
.setJavaArguments(javaArguments);
if (StringUtils.isNotBlank(vs.getServerIp())) {
builder.setQuickPlayOption(new QuickPlayOption.MultiPlayer(vs.getServerIp()));
}
if (config().hasProxy()) {
builder.setProxyType(config().getProxyType());
builder.setProxyHost(config().getProxyHost());
@@ -460,7 +464,7 @@ public final class HMCLGameRepository extends DefaultGameRepository {
if (vs.isAutoMemory() && builder.getJavaArguments().stream().anyMatch(it -> it.startsWith("-Xmx")))
builder.setMaxMemory(null);
return builder.create();
return builder;
}
@Override
@@ -22,6 +22,7 @@ import javafx.stage.Stage;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.auth.*;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDownloadException;
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.LibraryAnalyzer;
@@ -51,6 +52,7 @@ import org.jackhuang.hmcl.util.versioning.VersionNumber;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.nio.file.AccessDeniedException;
@@ -60,7 +62,6 @@ import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
import java.lang.ref.WeakReference;
import static javafx.application.Platform.runLater;
import static javafx.application.Platform.setImplicitExit;
@@ -81,6 +82,8 @@ public final class LauncherHelper {
private final VersionSetting setting;
private LauncherVisibility launcherVisibility;
private boolean showLogs;
private QuickPlayOption quickPlayOption;
private boolean disableOfflineSkin = false;
public LauncherHelper(Profile profile, Account account, String selectedVersion) {
this.profile = Objects.requireNonNull(profile);
@@ -111,6 +114,14 @@ public final class LauncherHelper {
launcherVisibility = LauncherVisibility.KEEP;
}
public void setQuickPlayOption(QuickPlayOption quickPlayOption) {
this.quickPlayOption = quickPlayOption;
}
public void setDisableOfflineSkin() {
disableOfflineSkin = true;
}
public void launch() {
FXUtils.checkFxUserThread();
@@ -188,8 +199,15 @@ public final class LauncherHelper {
.thenComposeAsync(() -> gameVersion.map(s -> new GameVerificationFixTask(dependencyManager, s, version.get())).orElse(null))
.thenComposeAsync(() -> logIn(account).withStage("launch.state.logging_in"))
.thenComposeAsync(authInfo -> Task.supplyAsync(() -> {
LaunchOptions launchOptions = repository.getLaunchOptions(
LaunchOptions.Builder launchOptionsBuilder = repository.getLaunchOptions(
selectedVersion, javaVersionRef.get(), profile.getGameDir(), javaAgents, javaArguments, scriptFile != null);
if (disableOfflineSkin) {
launchOptionsBuilder.setDaemon(false);
}
if (quickPlayOption != null) {
launchOptionsBuilder.setQuickPlayOption(quickPlayOption);
}
LaunchOptions launchOptions = launchOptionsBuilder.create();
LOG.info("Here's the structure of game mod directory:\n" + FileUtils.printFileStructure(repository.getModsDirectory(selectedVersion), 10));
@@ -639,10 +657,13 @@ public final class LauncherHelper {
return future;
}
private static Task<AuthInfo> logIn(Account account) {
private Task<AuthInfo> logIn(Account account) {
return Task.composeAsync(() -> {
try {
return Task.completed(account.logIn());
if (disableOfflineSkin && account instanceof OfflineAccount offlineAccount)
return Task.completed(offlineAccount.logInWithoutSkin());
else
return Task.completed(account.logIn());
} catch (CredentialExpiredException e) {
LOG.info("Credential has expired", e);
@@ -459,6 +459,20 @@ public final class VersionSetting implements Cloneable, Observable {
showLogsProperty.set(showLogs);
}
private final BooleanProperty enableDebugLogOutputProperty = new SimpleBooleanProperty(this, "enableDebugLogOutput", false);
public BooleanProperty enableDebugLogOutputProperty() {
return enableDebugLogOutputProperty;
}
public boolean isEnableDebugLogOutput() {
return enableDebugLogOutputProperty.get();
}
public void setEnableDebugLogOutput(boolean u) {
this.enableDebugLogOutputProperty.set(u);
}
// Minecraft settings.
private final StringProperty serverIpProperty = new SimpleStringProperty(this, "serverIp", "");
@@ -776,6 +790,7 @@ public final class VersionSetting implements Cloneable, Observable {
obj.addProperty("notCheckJVM", src.isNotCheckJVM());
obj.addProperty("notPatchNatives", src.isNotPatchNatives());
obj.addProperty("showLogs", src.isShowLogs());
obj.addProperty("enableDebugLogOutput", src.isEnableDebugLogOutput());
obj.addProperty("gameDir", src.getGameDir());
obj.addProperty("launcherVisibility", src.getLauncherVisibility().ordinal());
obj.addProperty("processPriority", src.getProcessPriority().ordinal());
@@ -847,6 +862,7 @@ public final class VersionSetting implements Cloneable, Observable {
vs.setNotCheckJVM(Optional.ofNullable(obj.get("notCheckJVM")).map(JsonElement::getAsBoolean).orElse(false));
vs.setNotPatchNatives(Optional.ofNullable(obj.get("notPatchNatives")).map(JsonElement::getAsBoolean).orElse(false));
vs.setShowLogs(Optional.ofNullable(obj.get("showLogs")).map(JsonElement::getAsBoolean).orElse(false));
vs.setEnableDebugLogOutput(Optional.ofNullable(obj.get("enableDebugLogOutput")).map(JsonElement::getAsBoolean).orElse(false));
vs.setLauncherVisibility(parseJsonPrimitive(obj.getAsJsonPrimitive("launcherVisibility"), LauncherVisibility.class, LauncherVisibility.HIDE));
vs.setProcessPriority(parseJsonPrimitive(obj.getAsJsonPrimitive("processPriority"), ProcessPriority.class, ProcessPriority.NORMAL));
vs.setUseNativeGLFW(Optional.ofNullable(obj.get("useNativeGLFW")).map(JsonElement::getAsBoolean).orElse(false));
@@ -400,6 +400,8 @@ public class GameCrashWindow extends Stage {
reasonPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
reasonPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);
feedbackTextFlow.getStyleClass().add("crash-reason-text-flow");
gameDirPane.setPadding(new Insets(8));
VBox.setVgrow(gameDirPane, Priority.ALWAYS);
FXUtils.onChangeAndOperate(feedbackTextFlow.visibleProperty(), visible -> {
@@ -204,6 +204,7 @@ public final class LogWindow extends Stage {
LogWindowImpl() {
getStyleClass().add("log-window");
listView.getProperties().put("no-smooth-scrolling", true);
listView.setItems(FXCollections.observableList(new CircularArrayList<>(logs.size())));
for (int i = 0; i < LEVELS.length; i++) {
@@ -25,7 +25,6 @@ import javafx.animation.Animation.Status;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.event.EventHandler;
import javafx.scene.control.IndexedCell;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.skin.VirtualFlow;
import javafx.scene.input.MouseEvent;
@@ -226,7 +225,6 @@ final class ScrollUtils {
final double[] derivatives = new double[FRICTIONS.length];
Timeline timeline = new Timeline();
Holder<ScrollDirection> scrollDirectionHolder = new Holder<>();
final EventHandler<MouseEvent> mouseHandler = event -> timeline.stop();
final EventHandler<ScrollEvent> scrollHandler = event -> {
if (event.getEventType() == ScrollEvent.SCROLL) {
@@ -234,7 +232,6 @@ final class ScrollUtils {
if (scrollDirection == ScrollDirection.LEFT || scrollDirection == ScrollDirection.RIGHT) {
return;
}
scrollDirectionHolder.value = scrollDirection;
double currentSpeed = isTrackPad(event, scrollDirection) ? speed / trackPadAdjustment : speed;
derivatives[0] += scrollDirection.intDirection * currentSpeed;
@@ -256,15 +253,7 @@ final class ScrollUtils {
}
double dy = derivatives[derivatives.length - 1];
int cellCount = virtualFlow.getCellCount();
IndexedCell<?> firstVisibleCell = virtualFlow.getFirstVisibleCell();
double height = firstVisibleCell != null ? firstVisibleCell.getHeight() * cellCount : 0.0;
double delta = height > 0.0
? dy / height
: (scrollDirectionHolder.value == ScrollDirection.DOWN ? 0.001 : -0.001);
virtualFlow.setPosition(Math.min(Math.max(virtualFlow.getPosition() + delta, 0), 1));
virtualFlow.scrollPixels(dy);
if (Math.abs(dy) < 0.001) {
timeline.stop();
@@ -68,6 +68,10 @@ public class AccountAdvancedListItem extends AdvancedListItem {
};
public AccountAdvancedListItem() {
this(null);
}
public AccountAdvancedListItem(Account account) {
tooltip = new Tooltip();
FXUtils.installFastTooltip(this, tooltip);
@@ -76,9 +80,13 @@ public class AccountAdvancedListItem extends AdvancedListItem {
setActionButtonVisible(false);
FXUtils.onScroll(this, Accounts.getAccounts(),
accounts -> accounts.indexOf(account.get()),
Accounts::setSelectedAccount);
if (account != null) {
this.accountProperty().set(account);
} else {
FXUtils.onScroll(this, Accounts.getAccounts(),
accounts -> accounts.indexOf(accountProperty().get()),
Accounts::setSelectedAccount);
}
}
public ObjectProperty<Account> accountProperty() {
@@ -31,7 +31,6 @@ import org.jetbrains.annotations.Nullable;
public class TransitionPane extends StackPane {
private Node currentNode;
private Animation oldAnimation;
public TransitionPane() {
FXUtils.setOverflowHidden(this);
@@ -51,11 +50,6 @@ public class TransitionPane extends StackPane {
public void setContent(Node newView, AnimationProducer transition,
Duration duration, Interpolator interpolator) {
if (oldAnimation != null) {
oldAnimation.stop();
oldAnimation = null;
}
Node previousNode = currentNode != newView && getWidth() > 0 && getHeight() > 0 ? currentNode : null;
currentNode = newView;
@@ -86,19 +80,15 @@ public class TransitionPane extends StackPane {
previousNode,
newView,
duration, interpolator);
newAnimation.statusProperty().addListener((observable, oldValue, newValue) -> {
if (oldValue == Animation.Status.RUNNING && newValue != Animation.Status.RUNNING) {
setMouseTransparent(false);
getChildren().remove(previousNode);
newAnimation.setOnFinished(e -> {
setMouseTransparent(false);
getChildren().remove(previousNode);
if (cacheHint != null) {
newView.setCache(false);
}
if (cacheHint != null) {
newView.setCache(false);
}
});
oldAnimation = newAnimation;
newAnimation.play();
FXUtils.playAnimation(this, "transition_pane", newAnimation);
});
}
@@ -51,8 +51,9 @@ public class OptionToggleButton extends StackPane {
Label titleLabel = new Label();
titleLabel.textProperty().bind(title);
Label subtitleLabel = new Label();
subtitleLabel.setMouseTransparent(true);
subtitleLabel.setWrapText(true);
subtitleLabel.setMouseTransparent(true);
subtitleLabel.getStyleClass().add("subtitle");
subtitleLabel.textProperty().bind(subtitle);
pane.setCenter(left);
left.setAlignment(Pos.CENTER_LEFT);
@@ -373,9 +373,8 @@ public class DecoratorController {
if (navigator.getCurrentPage() instanceof DecoratorPage) {
DecoratorPage page = (DecoratorPage) navigator.getCurrentPage();
// FIXME: Get WorldPage working first, and revisit this later
page.closePage();
if (page.isPageCloseable()) {
page.closePage();
return;
}
}
@@ -43,34 +43,41 @@ public class FeedbackPage extends SpinnerPane {
FXUtils.smoothScrolling(scrollPane);
setContent(scrollPane);
ComponentList community = new ComponentList();
ComponentList groups = new ComponentList();
{
IconedTwoLineListItem users = new IconedTwoLineListItem();
users.setImage(FXUtils.newBuiltinImage("/assets/img/icon.png"));
users.setTitle(i18n("feedback.qq_group"));
users.setSubtitle(i18n("feedback.qq_group.statement"));
users.setTitle(i18n("contact.chat.qq_group"));
users.setSubtitle(i18n("contact.chat.qq_group.statement"));
users.setExternalLink(Metadata.GROUPS_URL);
IconedTwoLineListItem discord = new IconedTwoLineListItem();
discord.setImage(FXUtils.newBuiltinImage("/assets/img/discord.png"));
discord.setTitle(i18n("contact.chat.discord"));
discord.setSubtitle(i18n("contact.chat.discord.statement"));
discord.setExternalLink("https://discord.gg/jVvC7HfM6U");
groups.getContent().setAll(users, discord);
}
ComponentList feedback = new ComponentList();
{
IconedTwoLineListItem github = new IconedTwoLineListItem();
github.imageProperty().bind(Bindings.when(Themes.darkModeProperty())
.then(FXUtils.newBuiltinImage("/assets/img/github-white.png"))
.otherwise(FXUtils.newBuiltinImage("/assets/img/github.png")));
github.setTitle(i18n("feedback.github"));
github.setSubtitle(i18n("feedback.github.statement"));
github.setTitle(i18n("contact.feedback.github"));
github.setSubtitle(i18n("contact.feedback.github.statement"));
github.setExternalLink("https://github.com/HMCL-dev/HMCL/issues/new/choose");
IconedTwoLineListItem discord = new IconedTwoLineListItem();
discord.setImage(FXUtils.newBuiltinImage("/assets/img/discord.png"));
discord.setTitle(i18n("feedback.discord"));
discord.setSubtitle(i18n("feedback.discord.statement"));
discord.setExternalLink("https://discord.gg/jVvC7HfM6U");
community.getContent().setAll(users, github, discord);
feedback.getContent().setAll(github);
}
content.getChildren().addAll(
ComponentList.createComponentListTitle(i18n("feedback.channel")),
community
ComponentList.createComponentListTitle(i18n("contact.chat")),
groups,
ComponentList.createComponentListTitle(i18n("contact.feedback")),
feedback
);
this.setContent(content);
@@ -69,7 +69,7 @@ public class LauncherSettingsPage extends DecoratorAnimatedPage implements Decor
.addNavigationDrawerTab(tab, downloadTab, i18n("download"), SVG.DOWNLOAD)
.startCategory(i18n("help").toUpperCase(Locale.ROOT))
.addNavigationDrawerTab(tab, helpTab, i18n("help"), SVG.HELP, SVG.HELP_FILL)
.addNavigationDrawerTab(tab, feedbackTab, i18n("feedback"), SVG.FEEDBACK, SVG.FEEDBACK_FILL)
.addNavigationDrawerTab(tab, feedbackTab, i18n("contact"), SVG.FEEDBACK, SVG.FEEDBACK_FILL)
.addNavigationDrawerTab(tab, aboutTab, i18n("about"), SVG.INFO, SVG.INFO_FILL);
FXUtils.setLimitWidth(sideBar, 200);
setLeft(sideBar);
@@ -346,7 +346,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
private void launch() {
Profile profile = Profiles.getSelectedProfile();
Versions.launch(profile, profile.getSelectedVersion(), null);
Versions.launch(profile, profile.getSelectedVersion());
}
private void launchNoGame() {
@@ -44,6 +44,7 @@ import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
@@ -86,8 +87,11 @@ public class PersonalizationPage extends StackPane {
brightnessPane.setLeft(left);
JFXComboBox<String> cboBrightness = new JFXComboBox<>(
FXCollections.observableArrayList("auto", "light", "dark"));
JFXComboBox<String> cboBrightness = new JFXComboBox<>(FXCollections.observableArrayList(
FXUtils.DARK_MODE != null
? List.of("auto", "light", "dark")
: List.of("light", "dark")
));
cboBrightness.setConverter(FXUtils.stringConverter(name -> i18n("settings.launcher.brightness." + name)));
cboBrightness.valueProperty().bindBidirectional(config().themeBrightnessProperty());
brightnessPane.setRight(cboBrightness);
@@ -17,9 +17,11 @@
*/
package org.jackhuang.hmcl.ui.main;
import com.jfoenix.controls.JFXPopup;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.scene.input.MouseButton;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.event.EventBus;
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
import org.jackhuang.hmcl.game.HMCLGameRepository;
@@ -39,6 +41,7 @@ import org.jackhuang.hmcl.ui.animation.AnimationUtils;
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
import org.jackhuang.hmcl.ui.construct.PopupMenu;
import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
@@ -64,8 +67,8 @@ import java.util.stream.Collectors;
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
private MainPage mainPage = null;
@@ -144,6 +147,13 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
// first item in left sidebar
AccountAdvancedListItem accountListItem = new AccountAdvancedListItem();
accountListItem.setOnAction(e -> Controllers.navigate(Controllers.getAccountListPage()));
accountListItem.setOnMouseClicked(e -> {
if (e.getButton() == MouseButton.SECONDARY) {
if (!Accounts.getAccounts().isEmpty())
showAccountListPopupMenu(accountListItem);
e.consume();
}
});
accountListItem.accountProperty().bind(Accounts.selectedAccountProperty());
// second item in left sidebar
@@ -234,13 +244,39 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
.startCategory(i18n("settings.launcher.general").toUpperCase(Locale.ROOT))
.add(launcherSettingsItem)
.add(terracottaItem)
.addNavigationDrawerItem(i18n("chat"), SVG.CHAT, () -> FXUtils.openLink(Metadata.GROUPS_URL));
.addNavigationDrawerItem(i18n("contact.chat"), SVG.CHAT, () -> {
Controllers.getSettingsPage().showFeedback();
Controllers.navigate(Controllers.getSettingsPage());
});
// the root page, with the sidebar in left, navigator in center.
setLeft(sideBar);
setCenter(getSkinnable().getMainPage());
}
public void showAccountListPopupMenu(
AccountAdvancedListItem accountListItem
) {
PopupMenu popupMenu = new PopupMenu();
JFXPopup popup = new JFXPopup(popupMenu);
AdvancedListBox scrollPane = new AdvancedListBox();
scrollPane.getStyleClass().add("no-padding");
scrollPane.setPrefWidth(220);
scrollPane.setPrefHeight(-1);
scrollPane.setMaxHeight(260);
for (Account account : Accounts.getAccounts()) {
AccountAdvancedListItem item = new AccountAdvancedListItem(account);
item.setOnAction(e -> {
Accounts.setSelectedAccount(account);
popup.hide();
});
scrollPane.add(item);
}
popupMenu.getContent().add(scrollPane);
popup.show(accountListItem, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, accountListItem.getWidth(), 0);
}
}
private boolean checkedModpack = false;
@@ -210,8 +210,8 @@ public final class SettingsPage extends ScrollPane {
{
OptionToggleButton previewPane = new OptionToggleButton();
previewPane.setTitle(i18n("update.preview"));
previewPane.setSubtitle(i18n("update.preview.subtitle"));
previewPane.selectedProperty().bindBidirectional(config().acceptPreviewUpdateProperty());
FXUtils.installFastTooltip(previewPane, i18n("update.preview.tooltip"));
ObjectProperty<UpdateChannel> updateChannel = selectedItemPropertyFor(updateChannelGroup, UpdateChannel.class);
updateChannel.set(UpdateChannel.getChannel());
@@ -167,7 +167,7 @@ public class TerracottaControllerPage extends StackPane {
progressProperty.set(0);
TextFlow body = FXUtils.segmentToTextFlow(i18n("terracotta.confirm.desc"), Controllers::onHyperlinkAction);
body.getStyleClass().add("terracotta-license");
body.getStyleClass().add("terracotta-hint");
body.setLineSpacing(4);
LineButton download = LineButton.of();
@@ -211,7 +211,7 @@ public class TerracottaControllerPage extends StackPane {
progressProperty.set(1);
TextFlow flow = FXUtils.segmentToTextFlow(i18n("terracotta.confirm.desc"), Controllers::onHyperlinkAction);
flow.getStyleClass().add("terracotta-license");
flow.getStyleClass().add("terracotta-hint");
flow.setLineSpacing(4);
LineButton host = LineButton.of();
@@ -232,7 +232,10 @@ public class TerracottaControllerPage extends StackPane {
MessageDialogPane.MessageType.QUESTION
).addAction(i18n("version.launch"), () -> {
Profile profile = Profiles.getSelectedProfile();
Versions.launch(profile, profile.getSelectedVersion(), LauncherHelper::setKeep);
Versions.launch(profile, profile.getSelectedVersion(), launcherHelper -> {
launcherHelper.setKeep();
launcherHelper.setDisableOfflineSkin();
});
}).addCancel(i18n("terracotta.status.waiting.host.launch.skip"), () -> {
TerracottaState.HostScanning s1 = TerracottaManager.setScanning();
if (s1 != null) {
@@ -283,6 +286,7 @@ public class TerracottaControllerPage extends StackPane {
progressProperty.set(-1);
TextFlow body = FXUtils.segmentToTextFlow(i18n("terracotta.status.scanning.desc"), Controllers::onHyperlinkAction);
body.getStyleClass().add("terracotta-hint");
body.setLineSpacing(4);
LineButton room = LineButton.of();
@@ -24,7 +24,6 @@ import javafx.geometry.Insets;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.game.LauncherHelper;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.Profiles;
import org.jackhuang.hmcl.terracotta.TerracottaMetadata;
@@ -71,7 +70,10 @@ public class TerracottaPage extends DecoratorAnimatedPage implements DecoratorPa
AdvancedListBox toolbar = new AdvancedListBox()
.addNavigationDrawerItem(i18n("version.launch"), SVG.ROCKET_LAUNCH, () -> {
Profile profile = Profiles.getSelectedProfile();
Versions.launch(profile, profile.getSelectedVersion(), LauncherHelper::setKeep);
Versions.launch(profile, profile.getSelectedVersion(), launcherHelper -> {
launcherHelper.setKeep();
launcherHelper.setDisableOfflineSkin();
});
}, item -> {
instanceChangeListenerHolder = FXUtils.onWeakChangeAndOperate(Profiles.selectedVersionProperty(),
instanceName -> item.setSubtitle(StringUtils.isNotBlank(instanceName) ? instanceName : i18n("version.empty"))
@@ -85,10 +85,14 @@ public class GameListItem extends Control {
Versions.openFolder(profile, version);
}
public void launch() {
public void testGame() {
Versions.testGame(profile, version);
}
public void launch() {
Versions.launch(profile, version);
}
public void modifyGameSettings() {
Versions.modifyGameSettings(profile, version);
}
@@ -44,7 +44,7 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
JFXPopup popup = new JFXPopup(menu);
menu.getContent().setAll(
new IconedMenuItem(SVG.ROCKET_LAUNCH, i18n("version.launch.test"), () -> currentSkinnable.launch(), popup),
new IconedMenuItem(SVG.ROCKET_LAUNCH, i18n("version.launch.test"), () -> currentSkinnable.testGame(), popup),
new IconedMenuItem(SVG.SCRIPT, i18n("version.launch_script"), () -> currentSkinnable.generateLaunchScript(), popup),
new MenuSeparator(),
new IconedMenuItem(SVG.SETTINGS, i18n("version.manage.manage"), () -> currentSkinnable.modifyGameSettings(), popup),
@@ -87,7 +87,7 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
{
JFXButton btnLaunch = new JFXButton();
btnLaunch.setOnAction(e -> skinnable.launch());
btnLaunch.setOnAction(e -> skinnable.testGame());
btnLaunch.getStyleClass().add("toggle-icon4");
BorderPane.setAlignment(btnLaunch, Pos.CENTER);
btnLaunch.setGraphic(FXUtils.limitingSize(SVG.ROCKET_LAUNCH.createIcon(24), 24, 24));
@@ -111,6 +111,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
private final MultiFileItem.FileOption<GameDirectoryType> gameDirCustomOption;
private final JFXComboBox<ProcessPriority> cboProcessPriority;
private final OptionToggleButton showLogsPane;
private final OptionToggleButton enableDebugLogOutputPane;
private final ImagePickerItem iconPickerItem;
private final ChangeListener<Collection<JavaRuntime>> javaListChangeListener;
@@ -410,6 +411,9 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
showLogsPane = new OptionToggleButton();
showLogsPane.setTitle(i18n("settings.show_log"));
enableDebugLogOutputPane = new OptionToggleButton();
enableDebugLogOutputPane.setTitle(i18n("settings.enable_debug_log_output"));
BorderPane processPriorityPane = new BorderPane();
{
Label label = new Label(i18n("settings.advanced.process_priority"));
@@ -474,6 +478,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
launcherVisibilityPane,
dimensionPane,
showLogsPane,
enableDebugLogOutputPane,
processPriorityPane,
serverPane,
showAdvancedSettingPane
@@ -551,6 +556,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
chkAutoAllocate.selectedProperty().unbindBidirectional(lastVersionSetting.autoMemoryProperty());
chkFullscreen.selectedProperty().unbindBidirectional(lastVersionSetting.fullscreenProperty());
showLogsPane.selectedProperty().unbindBidirectional(lastVersionSetting.showLogsProperty());
enableDebugLogOutputPane.selectedProperty().unbindBidirectional(lastVersionSetting.enableDebugLogOutputProperty());
FXUtils.unbindEnum(cboLauncherVisibility, lastVersionSetting.launcherVisibilityProperty());
FXUtils.unbindEnum(cboProcessPriority, lastVersionSetting.processPriorityProperty());
@@ -585,6 +591,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
chkAutoAllocate.selectedProperty().bindBidirectional(versionSetting.autoMemoryProperty());
chkFullscreen.selectedProperty().bindBidirectional(versionSetting.fullscreenProperty());
showLogsPane.selectedProperty().bindBidirectional(versionSetting.showLogsProperty());
enableDebugLogOutputPane.selectedProperty().bindBidirectional(versionSetting.enableDebugLogOutputProperty());
FXUtils.bindEnum(cboLauncherVisibility, versionSetting.launcherVisibilityProperty());
FXUtils.bindEnum(cboProcessPriority, versionSetting.processPriorityProperty());
@@ -23,10 +23,7 @@ import javafx.stage.FileChooser;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
import org.jackhuang.hmcl.download.game.GameAssetDownloadTask;
import org.jackhuang.hmcl.game.GameDirectoryType;
import org.jackhuang.hmcl.game.GameRepository;
import org.jackhuang.hmcl.game.HMCLGameRepository;
import org.jackhuang.hmcl.game.LauncherHelper;
import org.jackhuang.hmcl.game.*;
import org.jackhuang.hmcl.mod.RemoteMod;
import org.jackhuang.hmcl.setting.*;
import org.jackhuang.hmcl.task.*;
@@ -200,7 +197,8 @@ public final class Versions {
}
}
public static void generateLaunchScript(Profile profile, String id) {
@SafeVarargs
public static void generateLaunchScript(Profile profile, String id, Consumer<LauncherHelper>... injecters) {
if (!checkVersionForLaunching(profile, id))
return;
ensureSelectedAccount(account -> {
@@ -209,23 +207,35 @@ public final class Versions {
if (Files.isDirectory(repository.getRunDirectory(id)))
chooser.setInitialDirectory(repository.getRunDirectory(id).toFile());
chooser.setTitle(i18n("version.launch_script.save"));
if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {
chooser.getExtensionFilters().add(
new FileChooser.ExtensionFilter(i18n("extension.command"), "*.command")
);
}
chooser.getExtensionFilters().add(OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS
? new FileChooser.ExtensionFilter(i18n("extension.bat"), "*.bat")
: new FileChooser.ExtensionFilter(i18n("extension.sh"), "*.sh"));
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("extension.ps1"), "*.ps1"));
Path file = FileUtils.toPath(chooser.showSaveDialog(Controllers.getStage()));
if (file != null)
new LauncherHelper(profile, account, id).makeLaunchScript(file);
if (file != null) {
LauncherHelper launcherHelper = new LauncherHelper(profile, account, id);
for (Consumer<LauncherHelper> injecter : injecters) {
injecter.accept(launcherHelper);
}
launcherHelper.makeLaunchScript(file);
}
});
}
public static void launch(Profile profile, String id, Consumer<LauncherHelper> injecter) {
@SafeVarargs
public static void launch(Profile profile, String id, Consumer<LauncherHelper>... injecters) {
if (!checkVersionForLaunching(profile, id))
return;
ensureSelectedAccount(account -> {
LauncherHelper launcherHelper = new LauncherHelper(profile, account, id);
if (injecter != null)
for (Consumer<LauncherHelper> injecter : injecters) {
injecter.accept(launcherHelper);
}
launcherHelper.launch();
});
}
@@ -234,6 +244,16 @@ public final class Versions {
launch(profile, id, LauncherHelper::setTestMode);
}
public static void launchAndEnterWorld(Profile profile, String id, String worldFolderName) {
launch(profile, id, launcherHelper ->
launcherHelper.setQuickPlayOption(new QuickPlayOption.SinglePlayer(worldFolderName)));
}
public static void generateLaunchScriptForQuickEnterWorld(Profile profile, String id, String worldFolderName) {
generateLaunchScript(profile, id, launcherHelper ->
launcherHelper.setQuickPlayOption(new QuickPlayOption.SinglePlayer(worldFolderName)));
}
private static boolean checkVersionForLaunching(Profile profile, String id) {
if (id == null || !profile.getRepository().isLoaded() || !profile.getRepository().hasVersion(id)) {
JFXButton gotoDownload = new JFXButton(i18n("version.empty.launch.goto_download"));
@@ -22,6 +22,7 @@ import javafx.scene.control.Skin;
import javafx.stage.FileChooser;
import org.jackhuang.hmcl.game.World;
import org.jackhuang.hmcl.game.WorldLockedException;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.Controllers;
@@ -39,11 +40,15 @@ public final class WorldListItem extends Control {
private final World world;
private final Path backupsDir;
private final WorldListPage parent;
private final Profile profile;
private final String id;
public WorldListItem(WorldListPage parent, World world, Path backupsDir) {
public WorldListItem(WorldListPage parent, World world, Path backupsDir, Profile profile, String id) {
this.world = world;
this.backupsDir = backupsDir;
this.parent = parent;
this.profile = profile;
this.id = id;
}
@Override
@@ -91,6 +96,14 @@ public final class WorldListItem extends Control {
}
public void showManagePage() {
Controllers.navigate(new WorldManagePage(world, backupsDir));
Controllers.navigate(new WorldManagePage(world, backupsDir, profile, id));
}
public void launch() {
Versions.launchAndEnterWorld(profile, id, world.getFileName());
}
public void generateLaunchScript() {
Versions.generateLaunchScriptForQuickEnterWorld(profile, id, world.getFileName());
}
}
@@ -115,8 +115,19 @@ public final class WorldListItemSkin extends SkinBase<WorldListItem> {
WorldListItem item = getSkinnable();
World world = item.getWorld();
popupMenu.getContent().addAll(
new IconedMenuItem(SVG.SETTINGS, i18n("world.manage.button"), item::showManagePage, popup));
if (world.getGameVersion() != null && world.getGameVersion().isAtLeast("1.20", "23w14a")) {
IconedMenuItem launchItem = new IconedMenuItem(SVG.ROCKET_LAUNCH, i18n("version.launch_and_enter_world"), item::launch, popup);
launchItem.setDisable(world.isLocked());
popupMenu.getContent().add(launchItem);
popupMenu.getContent().addAll(
new IconedMenuItem(SVG.SCRIPT, i18n("version.launch_script"), item::generateLaunchScript, popup),
new MenuSeparator()
);
}
popupMenu.getContent().add(new IconedMenuItem(SVG.SETTINGS, i18n("world.manage.button"), item::showManagePage, popup));
if (ChunkBaseApp.isSupported(world)) {
popupMenu.getContent().addAll(
@@ -61,7 +61,7 @@ public final class WorldListPage extends ListPageBase<WorldListItem> implements
if (worlds != null)
itemsProperty().setAll(worlds.stream()
.filter(world -> isShowAll() || world.getGameVersion() == null || world.getGameVersion().equals(gameVersion))
.map(world -> new WorldListItem(this, world, backupsDir)).toList());
.map(world -> new WorldListItem(this, world, backupsDir, profile, id)).toList());
});
}
@@ -100,7 +100,7 @@ public final class WorldListPage extends ListPageBase<WorldListItem> implements
if (exception == null) {
itemsProperty().setAll(result.stream()
.filter(world -> isShowAll() || world.getGameVersion() == null || world.getGameVersion().equals(gameVersion))
.map(world -> new WorldListItem(this, world, backupsDir))
.map(world -> new WorldListItem(this, world, backupsDir, profile, id))
.collect(Collectors.toList()));
} else {
LOG.warning("Failed to load world list page", exception);
@@ -131,7 +131,7 @@ public final class WorldListPage extends ListPageBase<WorldListItem> implements
Controllers.prompt(i18n("world.name.enter"), (name, resolve, reject) -> {
Task.runAsync(() -> world.install(savesDir, name))
.whenComplete(Schedulers.javafx(), () -> {
itemsProperty().add(new WorldListItem(this, new World(savesDir.resolve(name)), backupsDir));
itemsProperty().add(new WorldListItem(this, new World(savesDir.resolve(name)), backupsDir, profile, id));
resolve.run();
}, e -> {
if (e instanceof FileAlreadyExistsException)
@@ -26,6 +26,7 @@ import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.game.World;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.animation.TransitionPane;
@@ -49,6 +50,8 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco
private final ObjectProperty<State> state;
private final World world;
private final Path backupsDir;
private final Profile profile;
private final String id;
private final TabHeader header;
private final TabHeader.Tab<WorldInfoPage> worldInfoTab = new TabHeader.Tab<>("worldInfoPage");
@@ -59,10 +62,13 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco
private FileChannel sessionLockChannel;
public WorldManagePage(World world, Path backupsDir) {
public WorldManagePage(World world, Path backupsDir, Profile profile, String id) {
this.world = world;
this.backupsDir = backupsDir;
this.profile = profile;
this.id = id;
this.worldInfoTab.setNodeSupplier(() -> new WorldInfoPage(this));
this.worldBackupsTab.setNodeSupplier(() -> new WorldBackupsPage(this));
this.datapackTab.setNodeSupplier(() -> new DatapackListPage(this));
@@ -71,6 +77,13 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco
this.header = new TabHeader(transitionPane, worldInfoTab, worldBackupsTab);
header.select(worldInfoTab);
// Does it need to be done in the background?
try {
sessionLockChannel = world.lock();
LOG.info("Acquired lock on world " + world.getFileName());
} catch (IOException ignored) {
}
setCenter(transitionPane);
BorderPane left = new BorderPane();
@@ -91,6 +104,12 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco
left.setTop(sideBar);
AdvancedListBox toolbar = new AdvancedListBox();
if (world.getGameVersion() != null && world.getGameVersion().isAtLeast("1.20", "23w14a")) {
toolbar.addNavigationDrawerItem(i18n("version.launch"), SVG.ROCKET_LAUNCH, this::launch, null);
toolbar.addNavigationDrawerItem(i18n("version.launch_script"), SVG.SCRIPT, this::generateLaunchScript, null);
}
if (ChunkBaseApp.isSupported(world)) {
PopupMenu popupMenu = new PopupMenu();
JFXPopup popup = new JFXPopup(popupMenu);
@@ -117,12 +136,7 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco
BorderPane.setMargin(toolbar, new Insets(0, 0, 12, 0));
left.setBottom(toolbar);
// Does it need to be done in the background?
try {
sessionLockChannel = world.lock();
LOG.info("Acquired lock on world " + world.getFileName());
} catch (IOException ignored) {
}
this.addEventHandler(Navigator.NavigationEvent.EXITED, this::onExited);
}
@Override
@@ -142,14 +156,7 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco
return sessionLockChannel == null;
}
@Override
public boolean back() {
closePage();
return true;
}
@Override
public void closePage() {
public void onExited(Navigator.NavigationEvent event) {
if (sessionLockChannel != null) {
try {
sessionLockChannel.close();
@@ -161,4 +168,13 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco
sessionLockChannel = null;
}
}
public void launch() {
fireEvent(new PageCloseEvent());
Versions.launchAndEnterWorld(profile, id, world.getFileName());
}
public void generateLaunchScript() {
Versions.generateLaunchScriptForQuickEnterWorld(profile, id, world.getFileName());
}
}
+9 -1
View File
@@ -265,6 +265,10 @@
-fx-spacing: 0;
}
.no-padding .advanced-list-box-content {
-fx-padding: 0 0 0 0;
}
.iconed-item {
-jfx-rippler-fill: -monet-secondary-container;
}
@@ -346,7 +350,7 @@
-fx-font-size: 15px;
}
.terracotta-license Text {
.terracotta-hint Text {
-fx-fill: -monet-on-surface;
}
@@ -1005,6 +1009,10 @@
-fx-text-fill: -monet-on-surface;
}
.options-list-item .label.subtitle {
-fx-text-fill: -monet-on-surface-variant;
}
.options-list-item.no-padding {
-fx-padding: 0;
}
@@ -197,7 +197,15 @@ button.select_all=Select All
button.view=View
button.yes=Yes
chat=Join Group Chat
contact=Feedback
contact.chat=Join Group Chat
contact.chat.discord=Discord
contact.chat.discord.statement=Welcome to join our Discord server.
contact.chat.qq_group=HMCL User QQ Group
contact.chat.qq_group.statement=Welcome to join our user QQ group.
contact.feedback=Feedback Channel
contact.feedback.github=GitHub Issues
contact.feedback.github.statement=Submit an issue on GitHub.
color.recent=Recommended
color.custom=Custom Color
@@ -376,6 +384,7 @@ extension.mod=Mod File
extension.png=Image File
extension.ps1=Windows PowerShell Script
extension.sh=Shell Script
extension.command=macOS Shell Script
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\
@@ -432,15 +441,6 @@ fatal.unsupported_platform.windows_arm64=Hello Minecraft! Launcher has provided
If you are using the <b>Qualcomm</b> platform, you may need to install the <a href="ms-windows-store://pdp/?productid=9NQPSL29BFFF">OpenGL Compatibility Pack</a> before playing games.\n\
Click the link to navigate to the Microsoft Store and install the compatibility pack.
feedback=Feedback
feedback.channel=Feedback Channel
feedback.discord=Discord
feedback.discord.statement=Welcome to join our Discord server.
feedback.github=GitHub Issues
feedback.github.statement=Submit an issue on GitHub.
feedback.qq_group=HMCL User QQ Group
feedback.qq_group.statement=Welcome to join our user QQ group.
file=File
folder.config=Configs
@@ -1412,6 +1412,7 @@ settings.memory.unit.mib=MiB
settings.memory.used_per_total=%1$.1f GiB Used / %2$.1f GiB Total
settings.physical_memory=Physical Memory Size
settings.show_log=Show Logs
settings.enable_debug_log_output=Output debug log
settings.tabs.installers=Loaders
settings.take_effect_after_restart=Applies After Restart
settings.type=Settings Type of Instance
@@ -1537,7 +1538,7 @@ update.latest=This is the latest version
update.no_browser=Cannot open in system browser. But we copied the link to your clipboard, and you can open it manually.
update.tooltip=Update
update.preview=Preview HMCL releases early
update.preview.tooltip=Enable this option to receive new versions of HMCL early for testing before their official release.
update.preview.subtitle=Enable this option to receive new versions of HMCL early for testing before their official release.
version=Games
version.name=Instance Name
@@ -1558,6 +1559,7 @@ version.game.support_status.unsupported=Unsupported
version.game.support_status.untested=Untested
version.game.type=Type
version.launch=Launch Game
version.launch_and_enter_world=Play World
version.launch.empty=Start Game
version.launch.empty.installing=Installing Game
version.launch.empty.tooltip=Install and launch the latest official release
@@ -197,7 +197,15 @@ button.select_all=تحديد الكل
button.view=عرض
button.yes=نعم
chat=الانضمام إلى دردشة جماعية
contact=الملاحظات
contact.chat=الانضمام إلى دردشة جماعية
contact.chat.discord=Discord
contact.chat.discord.statement=مرحباً بك للانضمام إلى خادم Discord الخاص بنا.
contact.chat.qq_group=مجموعة QQ لمستخدمي HMCL
contact.chat.qq_group.statement=مرحباً بك للانضمام إلى مجموعة QQ الخاصة بمستخدمينا.
contact.feedback=قناة الملاحظات
contact.feedback.github=GitHub Issues
contact.feedback.github.statement=إرسال مشكلة على GitHub.
color.recent=موصى به
color.custom=لون مخصص
@@ -432,15 +440,6 @@ fatal.unsupported_platform.windows_arm64=Hello Minecraft! Launcher وفر دعم
إذا كنت تستخدم منصة <b>Qualcomm</b>، فقد تحتاج إلى تثبيت <a href="ms-windows-store://pdp/?productid=9NQPSL29BFFF">حزمة توافق OpenGL</a> قبل لعب الألعاب.\n\
انقر على الرابط للانتقال إلى متجر Microsoft وتثبيت حزمة التوافق.
feedback=الملاحظات
feedback.channel=قناة الملاحظات
feedback.discord=Discord
feedback.discord.statement=مرحباً بك للانضمام إلى خادم Discord الخاص بنا.
feedback.github=GitHub Issues
feedback.github.statement=إرسال مشكلة على GitHub.
feedback.qq_group=مجموعة QQ لمستخدمي HMCL
feedback.qq_group.statement=مرحباً بك للانضمام إلى مجموعة QQ الخاصة بمستخدمينا.
file=ملف
folder.config=التكوينات
@@ -1526,7 +1525,7 @@ update.latest=هذا هو أحدث إصدار
update.no_browser=لا يمكن الفتح في متصفح النظام. ولكننا نسخنا الرابط إلى الحافظة الخاصة بك، ويمكنك فتحه يدويًا.
update.tooltip=تحديث
update.preview=معاينة إصدارات HMCL مبكرًا
update.preview.tooltip=قم بتفعيل هذا الخيار لاستلام إصدارات جديدة من HMCL مبكرًا للاختبار قبل إصدارها الرسمي.
update.preview.subtitle=قم بتفعيل هذا الخيار لاستلام إصدارات جديدة من HMCL مبكرًا للاختبار قبل إصدارها الرسمي.
version=الألعاب
version.name=اسم النسخة
@@ -198,7 +198,15 @@ button.select_all=Seleccionar todo
button.view=Vista
button.yes=Sí
chat=Chat de grupo
contact=Comentarios
contact.chat=Chat de grupo
contact.chat.discord=Discord
contact.chat.discord.statement=¡Únase a nuestro servidor Discord!
contact.chat.qq_group=Grupo QQ de usuarios de HMCL
contact.chat.qq_group.statement=¡Únase a nuestro grupo QQ de usuarios!
contact.feedback=Canal de comentarios
contact.feedback.github=GitHub Issues
contact.feedback.github.statement=Envíe una propuesta en GitHub.
color.recent=Recomendado
color.custom=Color personalizado
@@ -433,15 +441,6 @@ fatal.unsupported_platform.windows_arm64=Hello Minecraft! Launcher ha proporcion
Si utilizas la plataforma <b>Qualcomm</b>, es posible que tengas que instalar el <a href="ms-windows-store://pdp/?productid=9NQPSL29BFFF">paquete de compatibilidad OpenGL</a> antes de jugar.\n\
Haz clic en el enlace para ir a Microsoft Store e instalar el paquete de compatibilidad.
feedback=Comentarios
feedback.channel=Canal de comentarios
feedback.discord=Discord
feedback.discord.statement=¡Únase a nuestro servidor Discord!
feedback.github=GitHub Issues
feedback.github.statement=Envíe una propuesta en GitHub.
feedback.qq_group=Grupo QQ de usuarios de HMCL
feedback.qq_group.statement=¡Únase a nuestro grupo QQ de usuarios!
file=Archivo
folder.config=Configuraciones de mod
@@ -1447,7 +1446,7 @@ update.latest=Esta es la última versión.
update.no_browser=No se puede abrir en el navegador del sistema. Pero, hemos copiado el enlace a su portapapeles y puede abrirlo manualmente.
update.tooltip=Actualización
update.preview=Vista previa de actualizaciones anticipadas
update.preview.tooltip=Activa esta opción para recibir nuevas versiones del lanzador antes de su lanzamiento oficial para probarlas
update.preview.subtitle=Activa esta opción para recibir nuevas versiones del lanzador antes de su lanzamiento oficial para probarlas
version=Juegos
version.name=Nombre de instancia
@@ -157,7 +157,15 @@ button.select_all=すべて選択
button.view=読む
button.yes=はい
chat=グループチャット
contact=フィードバック
contact.chat=グループチャット
contact.chat.discord=Discord
contact.chat.discord.statement=Discordサーバーに参加してください!
contact.chat.qq_group=HMCLユーザーQQグループ
contact.chat.qq_group.statement=ユーザーQQグループに参加してください!
contact.feedback=フィードバックチャンネル
contact.feedback.github=GitHub Issues
contact.feedback.github.statement=GitHubで問題を送信します。
color.recent=推奨
color.custom=カスタムカラー
@@ -326,16 +334,6 @@ fatal.apply_update_failure=ごめんなさい、Hello Minecraft! Launcher 何か
fatal.samba=If you are trying to run HMCL in a shared folder by Samba, HMCL may not working, please try updating your Java or running HMCL in a local folder.
fatal.illegal_char=ユーザーフォルダーのパスに不正な文字'='が含まれています, ログインアカウントやオフラインログインではスキンの変更ができなくなり。
feedback=フィードバック
feedback.channel=フィードバックチャンネル
feedback.discord=Discord
feedback.discord.statement=Discordサーバーに参加してください!
feedback.github=GitHub Issues
feedback.github.statement=GitHubで問題を送信します。
feedback.qq_group=HMCLユーザーQQグループ
feedback.qq_group.statement=ユーザーQQグループに参加してください!
file=ファイル
folder.config=Config
@@ -206,11 +206,19 @@ button.select_all=悉擇之
button.view=覽
button.yes=然
chat=會集
color.recent=薦
color.custom=自定色
contact=建言
contact.chat=會集
contact.chat.discord=齟齬
contact.chat.discord.statement=恭迎至齟齬伺服器,且循論議之規
contact.chat.qq_group=HMCL 群組
contact.chat.qq_group.statement=恭迎至 HMCL 群組,且循論議之規
contact.feedback=建言之徑
contact.feedback.github=Github 議題
contact.feedback.github.statement=舉一 Github 議題
crash.NoClassDefFound=宜驗 HMCL 之案全否,抑更迭爪哇。\n君可求助於 https://docs.hmcl.net/help.html。
crash.user_fault=君之械網與爪哇或有謬,是以崩。宜驗爪哇與算機。\n君可求助於 https://docs.hmcl.net/help.html。
@@ -401,15 +409,6 @@ fatal.unsupported_platform.loongarch=HMCL 既適龍芯。\n凡有謬,遽求助
fatal.unsupported_platform.macos_arm64=HMCL 既適蘋矽。宜啟以 ARM 之爪哇,以益君之戲事。\n誠有謬,宜啟以 x86-64 之爪哇,以益其兼。\n凡有謬,遽求助於右上之鈕。
fatal.unsupported_platform.windows_arm64=HMCL 既適 ARM 之視窗。誠有謬,宜啟以 x86 之爪哇。\n誠用<b>栝柑</b>,或須置<a href="ms-windows-store://pdp/?productid=9NQPSL29BFFF">開圖庫兼囊</a>,而後可戯。擊鏈以置之於微軟貨舍。\n君可求助於右上之鈕。
feedback=建言
feedback.channel=建言之徑
feedback.discord=齟齬
feedback.discord.statement=恭迎至齟齬伺服器,且循論議之規
feedback.github=Github 議題
feedback.github.statement=舉一 Github 議題
feedback.qq_group=HMCL 群組
feedback.qq_group.statement=恭迎至 HMCL 群組,且循論議之規
file=案
folder.config=置設案夾
@@ -197,7 +197,15 @@ button.select_all=Выбрать все
button.view=Просмотреть
button.yes=Да
chat=Групповой чат
contact=Обратная связь
contact.chat=Групповой чат
contact.chat.discord=Discord
contact.chat.discord.statement=Добро пожаловать в наш Discord.
contact.chat.qq_group=Группа QQ пользователя HMCL
contact.chat.qq_group.statement=Добро пожаловать в нашу группу QQ.
contact.feedback=Канал обратной связи
contact.feedback.github=Проблемы GitHub
contact.feedback.github.statement=Отправить проблему на GitHub.
color.recent=Рекомендуемые
color.custom=Пользовательский цвет
@@ -429,15 +437,6 @@ fatal.unsupported_platform.windows_arm64=Лаунчер обеспечил на
Если вы используете платформу <b>Qualcomm</b>, вам может потребоваться установить <a href="ms-windows-store://pdp/?productid=9NQPSL29BFFF">пакет совместимости OpenGL</a> перед началом игры.\n\
Щелкните ссылку, чтобы перейти в Microsoft Store и установить пакет совместимости.
feedback=Обратная связь
feedback.channel=Канал обратной связи
feedback.discord=Discord
feedback.discord.statement=Добро пожаловать в наш Discord.
feedback.github=Проблемы GitHub
feedback.github.statement=Отправить проблему на GitHub.
feedback.qq_group=Группа QQ пользователя HMCL
feedback.qq_group.statement=Добро пожаловать в нашу группу QQ.
file=Файл
folder.config=Конфигурация мод
@@ -194,7 +194,15 @@ button.select_all=Вибрати все
button.view=Переглянути
button.yes=Так
chat=Приєднатися до групового чату
contact=Зворотний зв'язок
contact.chat=Приєднатися до групового чату
contact.chat.discord=Discord
contact.chat.discord.statement=Ласкаво просимо приєднатися до нашого сервера Discord.
contact.chat.qq_group=Група користувачів QQ HMCL
contact.chat.qq_group.statement=Ласкаво просимо приєднатися до нашої групи користувачів QQ.
contact.feedback=Канал зворотного зв'язку
contact.feedback.github=GitHub Issues
contact.feedback.github.statement=Надіслати проблему на GitHub.
color.recent=Рекомендовані
color.custom=Власний колір
@@ -411,15 +419,6 @@ fatal.unsupported_platform.windows_arm64=Hello Minecraft! Лаунчер над
Якщо ви використовуєте платформу <b>Qualcomm</b>, вам може знадобитися встановити <a href="ms-windows-store://pdp/?productid=9NQPSL29BFFF">Пакет сумісності OpenGL</a> перед грою.\n\
Натисніть посилання, щоб перейти до Microsoft Store та встановити пакет сумісності.
feedback=Зворотний зв'язок
feedback.channel=Канал зворотного зв'язку
feedback.discord=Discord
feedback.discord.statement=Ласкаво просимо приєднатися до нашого сервера Discord.
feedback.github=GitHub Issues
feedback.github.statement=Надіслати проблему на GitHub.
feedback.qq_group=Група користувачів QQ HMCL
feedback.qq_group.statement=Ласкаво просимо приєднатися до нашої групи користувачів QQ.
file=Файл
folder.config=Конфігурації
@@ -200,7 +200,15 @@ button.select_all=全選
button.view=查看
button.yes=是
chat=官方群組
contact=回報
contact.chat=官方群組
contact.chat.discord=Discord 伺服器
contact.chat.discord.statement=歡迎加入 Discord 伺服器,加入後請遵守討論區規則
contact.chat.qq_group=使用者 QQ 群組
contact.chat.qq_group.statement=歡迎加入 HMCL 使用者 QQ 群組,加入後請遵守群組規則
contact.feedback=回報管道
contact.feedback.github=GitHub Issues
contact.feedback.github.statement=提交一個 GitHub Issue
color.recent=建議
color.custom=自訂顏色
@@ -374,6 +382,7 @@ extension.mod=模組檔案
extension.png=圖片檔案
extension.ps1=PowerShell 指令碼
extension.sh=Bash 指令碼
extension.command=macOS Shell 指令碼
fatal.create_hmcl_current_directory_failure=Hello Minecraft! Launcher 無法建立 HMCL 資料夾 (%s),請將 HMCL 移動至其他位置再開啟。
fatal.javafx.incompatible=缺少 JavaFX 執行環境。\nHMCL 無法在低於 Java 11 的 Java 環境上自行補全 JavaFX 執行環境。請更新到 Java 11 或更高版本。
@@ -398,15 +407,6 @@ fatal.unsupported_platform.loongarch=Hello Minecraft! Launcher 已為龍芯提
fatal.unsupported_platform.macos_arm64=Hello Minecraft! Launcher 已為 Apple Silicon 平臺提供支援。使用 ARM 原生 Java 啟動遊戲以獲得更流暢的遊戲體驗。\n如果你在遊戲中遭遇問題,使用 x86-64 架構的 Java 啟動遊戲可能有更好的相容性。
fatal.unsupported_platform.windows_arm64=Hello Minecraft! Launcher 已為 Windows on Arm 平臺提供原生支援。如果你在遊戲中遭遇問題,請嘗試使用 x86 架構的 Java 啟動遊戲。\n\n如果你正在使用<b>高通</b>平臺,你可能需要安裝 <a href="ms-windows-store://pdp/?productid=9NQPSL29BFFF">OpenGL 相容包</a>後才能進行遊戲。點擊連結前往 Microsoft Store 安裝相容包。
feedback=回報
feedback.channel=回報管道
feedback.discord=Discord 伺服器
feedback.discord.statement=歡迎加入 Discord 伺服器,加入後請遵守討論區規則
feedback.github=GitHub Issues
feedback.github.statement=提交一個 GitHub Issue
feedback.qq_group=HMCL 使用者 QQ 群組
feedback.qq_group.statement=歡迎加入 HMCL 使用者 QQ 群組,加入後請遵守群組規則
file=檔案
folder.config=模組設定目錄
@@ -1200,6 +1200,7 @@ settings.memory.unit.mib=MiB
settings.memory.used_per_total=已使用 %1$.1f GiB / 總記憶體 %2$.1f GiB
settings.physical_memory=實體記憶體大小
settings.show_log=查看日誌
settings.enable_debug_log_output=輸出除錯日誌
settings.tabs.installers=自動安裝
settings.take_effect_after_restart=重啟後生效
settings.type=實例遊戲設定類型
@@ -1321,7 +1322,7 @@ update.latest=目前版本為最新版本
update.no_browser=無法開啟瀏覽器。網址已經複製到剪貼簿了,你可以手動複製網址開啟頁面。
update.tooltip=更新
update.preview=提前測試 HMCL 預覽版本
update.preview.tooltip=啟用此選項,你將可以提前取得 HMCL 的新版本,以便在正式發布前進行測試。
update.preview.subtitle=啟用此選項,你將可以提前取得 HMCL 的新版本,以便在正式發布前進行測試。
version=遊戲
version.name=遊戲實例名稱
@@ -1342,6 +1343,7 @@ version.game.support_status.unsupported=不支援
version.game.support_status.untested=未經測試
version.game.type=版本類型
version.launch=啟動遊戲
version.launch_and_enter_world=進入世界
version.launch.empty=開始遊戲
version.launch.empty.installing=安裝遊戲
version.launch.empty.tooltip=安裝並啟動最新正式版遊戲
@@ -208,11 +208,19 @@ button.select_all=全选
button.view=查看
button.yes=是
chat=官方群组
color.recent=推荐
color.custom=自定义颜色
contact=反馈
contact.feedback=提交反馈
contact.feedback.github=GitHub Issue
contact.feedback.github.statement=提交一个 GitHub Issue
contact.chat=官方群组
contact.chat.qq_group=用户 QQ 群
contact.chat.qq_group.statement=欢迎加入 HMCL 官方 QQ 群,加入后请遵守群规
contact.chat.discord=Discord
contact.chat.discord.statement=欢迎加入 Discord 服务器,加入后请遵守讨论区规定
crash.NoClassDefFound=请确认 Hello Minecraft! Launcher 本体是否完整,或更新你的 Java。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。
crash.user_fault=你的系统或 Java 环境可能安装不当导致本软件崩溃,请检查你的 Java 环境或你的电脑。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。
@@ -383,6 +391,7 @@ extension.mod=模组文件
extension.png=图片文件
extension.ps1=PowerShell 脚本
extension.sh=Bash 脚本
extension.command=macOS Shell 脚本
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 页面寻求帮助。
@@ -407,15 +416,6 @@ fatal.unsupported_platform.loongarch=Hello Minecraft! Launcher 已为龙芯提
fatal.unsupported_platform.macos_arm64=Hello Minecraft! Launcher 已为 Apple Silicon 平台提供支持。使用 ARM 原生 Java 启动游戏以获得更流畅的游戏体验。\n如果你在游戏中遇到问题,使用 x86-64 架构的 Java 启动游戏可能有更好的兼容性。\n如遇到问题,你可以点击右上角帮助按钮进行求助。
fatal.unsupported_platform.windows_arm64=Hello Minecraft! Launcher 已为 Windows on Arm 平台提供原生支持。如果你在游戏中遇到问题,请尝试使用 x86 架构的 Java 启动游戏。\n如果你正在使用 <b>高通</b> 平台,你可能需要安装 <a href="ms-windows-store://pdp/?productid=9NQPSL29BFFF">OpenGL 兼容包</a> 后才能进行游戏。点击链接前往 Microsoft Store 安装兼容包。\n如遇到问题,你可以点击右上角帮助按钮进行求助。
feedback=反馈
feedback.channel=反馈渠道
feedback.discord=Discord
feedback.discord.statement=欢迎加入 Discord 服务器,加入后请遵守讨论区规定
feedback.github=GitHub Issue
feedback.github.statement=提交一个 GitHub Issue
feedback.qq_group=HMCL 用户群
feedback.qq_group.statement=欢迎加入 HMCL 用户群,加入后请遵守群规
file=文件
folder.config=配置文件夹
@@ -1210,6 +1210,7 @@ settings.memory.unit.mib=MiB
settings.memory.used_per_total=设备中已使用 %1$.1f GiB / 设备总内存 %2$.1f GiB
settings.physical_memory=物理内存大小
settings.show_log=查看日志
settings.enable_debug_log_output=输出调试日志
settings.tabs.installers=自动安装
settings.take_effect_after_restart=重启后生效
settings.type=实例游戏设置类型
@@ -1331,7 +1332,7 @@ update.latest=当前版本为最新版本
update.no_browser=无法打开浏览器。网址已经复制到剪贴板,你可以手动粘贴网址打开页面。
update.tooltip=更新
update.preview=提前预览 HMCL 版本
update.preview.tooltip=启用此选项,你将可以提前获取 HMCL 的新版本,以便在正式发布前进行测试。
update.preview.subtitle=启用此选项,你将可以提前获取 HMCL 的新版本,以便在正式发布前进行测试。
version=游戏
version.name=游戏实例名称
@@ -1352,6 +1353,7 @@ version.game.support_status.unsupported=不支持
version.game.support_status.untested=未经测试
version.game.type=版本类型
version.launch=启动游戏
version.launch_and_enter_world=进入世界
version.launch.empty=开始游戏
version.launch.empty.installing=安装游戏
version.launch.empty.tooltip=安装并启动最新正式版游戏
@@ -103,10 +103,14 @@ public class OfflineAccount extends Account {
return skin != null && skin.getType() != Skin.Type.DEFAULT;
}
public AuthInfo logInWithoutSkin() throws AuthenticationException {
// Using "legacy" user type here because "mojang" user type may cause "invalid session token" or "disconnected" when connecting to a game server.
return new AuthInfo(username, uuid, UUIDTypeAdapter.fromUUID(UUID.randomUUID()), AuthInfo.USER_TYPE_MSA, "{}");
}
@Override
public AuthInfo logIn() throws AuthenticationException {
// Using "legacy" user type here because "mojang" user type may cause "invalid session token" or "disconnected" when connecting to a game server.
AuthInfo authInfo = new AuthInfo(username, uuid, UUIDTypeAdapter.fromUUID(UUID.randomUUID()), AuthInfo.USER_TYPE_MSA, "{}");
AuthInfo authInfo = logInWithoutSkin();
if (loadAuthlibInjector(skin)) {
CompletableFuture<AuthlibInjectorArtifactInfo> artifactTask = CompletableFuture.supplyAsync(() -> {
@@ -22,6 +22,7 @@ import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.download.VersionList;
import org.jackhuang.hmcl.task.GetTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import java.util.Collections;
@@ -64,11 +65,13 @@ public final class LiteLoaderBMCLVersionList extends VersionList<LiteLoaderRemot
NetworkUtils.withQuery(downloadProvider.getApiRoot() + "/liteloader/list", Map.of(
"mcversion", gameVersion
)))
.thenGetJsonAsync(LiteLoaderBMCLVersion.class)
.thenApplyAsync(json -> JsonUtils.fromMaybeMalformedJson(json, LiteLoaderBMCLVersion.class))
.thenAcceptAsync(v -> {
lock.writeLock().lock();
try {
versions.clear();
if (v == null)
return;
versions.put(gameVersion, new LiteLoaderRemoteVersion(
gameVersion, v.version, RemoteVersion.Type.UNCATEGORIZED,
Collections.singletonList(NetworkUtils.withQuery(
@@ -63,7 +63,13 @@ public final class NeoForgeOfficialVersionList extends VersionList<NeoForgeRemot
if (majorVersion == 0) { // Snapshot version.
mcVersion = version.substring(si1 + 1, si2);
} else {
mcVersion = "1." + version.substring(0, Integer.parseInt(version.substring(si1 + 1, si2)) == 0 ? si1 : si2);
String ver = version.substring(0, Integer.parseInt(version.substring(si1 + 1, si2)) == 0 ? si1 : si2);
if (majorVersion >= 26) {
int separator = version.indexOf('+');
mcVersion = separator < 0 ? ver : ver + "-" + version.substring(separator + 1);
} else {
mcVersion = "1." + ver;
}
}
} catch (RuntimeException e) {
LOG.warning(String.format("Cannot parse NeoForge version %s for cracking its mc version.", version), e);
@@ -19,7 +19,7 @@ public class NeoForgeRemoteVersion extends RemoteVersion {
}
private static Type getType(String version) {
return version.contains("beta") ? Type.SNAPSHOT : Type.RELEASE;
return version.contains("beta") || version.contains("alpha") ? Type.SNAPSHOT : Type.RELEASE;
}
public static String normalize(String version) {
@@ -25,6 +25,7 @@ import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import java.util.*;
public final class GameJavaVersion {
public static final GameJavaVersion JAVA_25 = new GameJavaVersion("java-runtime-epsilon", 25);
public static final GameJavaVersion JAVA_21 = new GameJavaVersion("java-runtime-delta", 21);
public static final GameJavaVersion JAVA_17 = new GameJavaVersion("java-runtime-beta", 17);
public static final GameJavaVersion JAVA_16 = new GameJavaVersion("java-runtime-alpha", 16);
@@ -33,6 +34,8 @@ public final class GameJavaVersion {
public static final GameJavaVersion LATEST = JAVA_21;
public static GameJavaVersion getMinimumJavaVersion(GameVersionNumber gameVersion) {
if (gameVersion.compareTo("26.1") >= 0)
return JAVA_25;
if (gameVersion.compareTo("1.20.5") >= 0)
return JAVA_21;
if (gameVersion.compareTo("1.18") >= 0)
@@ -45,18 +48,14 @@ public final class GameJavaVersion {
}
public static GameJavaVersion get(int major) {
switch (major) {
case 8:
return JAVA_8;
case 16:
return JAVA_16;
case 17:
return JAVA_17;
case 21:
return JAVA_21;
default:
return null;
}
return switch (major) {
case 8 -> JAVA_8;
case 16 -> JAVA_16;
case 17 -> JAVA_17;
case 21 -> JAVA_21;
case 25 -> JAVA_25;
default -> null;
};
}
public static List<GameJavaVersion> getSupportedVersions(Platform platform) {
@@ -74,13 +73,13 @@ public final class GameJavaVersion {
case WINDOWS:
case LINUX:
case MACOS:
return Arrays.asList(JAVA_8, JAVA_16, JAVA_17, JAVA_21);
return Arrays.asList(JAVA_8, JAVA_16, JAVA_17, JAVA_21, JAVA_25);
}
} else if (architecture == Architecture.ARM64) {
switch (operatingSystem) {
case WINDOWS:
case MACOS:
return Arrays.asList(JAVA_17, JAVA_21);
return Arrays.asList(JAVA_17, JAVA_21, JAVA_25);
}
}
@@ -47,7 +47,7 @@ public class LaunchOptions implements Serializable {
private Integer width;
private Integer height;
private boolean fullscreen;
private String serverIp;
private QuickPlayOption quickPlayOption;
private String wrapper;
private Proxy.Type proxyType;
private String proxyHost;
@@ -64,6 +64,7 @@ public class LaunchOptions implements Serializable {
private Renderer renderer = Renderer.DEFAULT;
private boolean useNativeGLFW;
private boolean useNativeOpenAL;
private boolean enableDebugLogOutput;
private boolean daemon;
/**
@@ -180,11 +181,11 @@ public class LaunchOptions implements Serializable {
return fullscreen;
}
/**
* The server ip that will connect to when enter game main menu.
*/
public String getServerIp() {
return serverIp;
/// The quick play option.
///
/// @see <a href="https://minecraft.wiki/w/Quick_Play">Quick Play - Minecraft Wiki</a>
public QuickPlayOption getQuickPlayOption() {
return quickPlayOption;
}
/**
@@ -282,6 +283,10 @@ public class LaunchOptions implements Serializable {
return useNativeOpenAL;
}
public boolean isEnableDebugLogOutput() {
return enableDebugLogOutput;
}
/**
* Will launcher keeps alive after game launched or not.
*/
@@ -407,8 +412,8 @@ public class LaunchOptions implements Serializable {
return this;
}
public Builder setServerIp(String serverIp) {
options.serverIp = serverIp;
public Builder setQuickPlayOption(QuickPlayOption quickPlayOption) {
options.quickPlayOption = quickPlayOption;
return this;
}
@@ -497,5 +502,9 @@ public class LaunchOptions implements Serializable {
return this;
}
public Builder setEnableDebugLogOutput(boolean u) {
options.enableDebugLogOutput = u;
return this;
}
}
}
@@ -0,0 +1,32 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.game;
/// The quick play option.
///
/// @see <a href="https://minecraft.wiki/w/Quick_Play">Quick Play - Minecraft Wiki</a>
public sealed interface QuickPlayOption {
record SinglePlayer(String worldFolderName) implements QuickPlayOption {
}
record MultiPlayer(String serverIP) implements QuickPlayOption {
}
record Realm(String realmID) implements QuickPlayOption {
}
}
@@ -149,7 +149,7 @@ public class DefaultLauncher extends Launcher {
res.addDefault("-Dcom.sun.jndi.cosnaming.object.trustURLCodebase=", "false");
String formatMsgNoLookups = res.addDefault("-Dlog4j2.formatMsgNoLookups=", "true");
if (!"-Dlog4j2.formatMsgNoLookups=false".equals(formatMsgNoLookups) && isUsingLog4j()) {
if (isUsingLog4j() && (options.isEnableDebugLogOutput() || !"-Dlog4j2.formatMsgNoLookups=false".equals(formatMsgNoLookups))) {
res.addDefault("-Dlog4j.configurationFile=", FileUtils.getAbsolutePath(getLog4jConfigurationFile()));
}
@@ -304,23 +304,31 @@ public class DefaultLauncher extends Launcher {
if (argumentsFromAuthInfo != null && argumentsFromAuthInfo.getGame() != null && !argumentsFromAuthInfo.getGame().isEmpty())
res.addAll(Arguments.parseArguments(argumentsFromAuthInfo.getGame(), configuration, features));
if (StringUtils.isNotBlank(options.getServerIp())) {
String address = options.getServerIp();
if (options.getQuickPlayOption() instanceof QuickPlayOption.MultiPlayer multiPlayer) {
String address = multiPlayer.serverIP();
try {
ServerAddress parsed = ServerAddress.parse(address);
if (GameVersionNumber.asGameVersion(gameVersion).compareTo("1.20") < 0) {
if (GameVersionNumber.asGameVersion(gameVersion).isAtLeast("1.20", "23w14a")) {
res.add("--quickPlayMultiplayer");
res.add(parsed.getPort() >= 0 ? address : parsed.getHost() + ":25565");
} else {
res.add("--server");
res.add(parsed.getHost());
res.add("--port");
res.add(parsed.getPort() >= 0 ? String.valueOf(parsed.getPort()) : "25565");
} else {
res.add("--quickPlayMultiplayer");
res.add(parsed.getPort() < 0 ? address + ":25565" : address);
}
} catch (IllegalArgumentException e) {
LOG.warning("Invalid server address: " + address, e);
}
} else if (options.getQuickPlayOption() instanceof QuickPlayOption.SinglePlayer singlePlayer
&& GameVersionNumber.asGameVersion(gameVersion).isAtLeast("1.20", "23w14a")) {
res.add("--quickPlaySingleplayer");
res.add(singlePlayer.worldFolderName());
} else if (options.getQuickPlayOption() instanceof QuickPlayOption.Realm realm
&& GameVersionNumber.asGameVersion(gameVersion).isAtLeast("1.20", "23w14a")) {
res.add("--quickPlayRealms");
res.add(realm.realmID());
}
if (options.isFullscreen())
@@ -421,14 +429,24 @@ public class DefaultLauncher extends Launcher {
public void extractLog4jConfigurationFile() throws IOException {
Path targetFile = getLog4jConfigurationFile();
InputStream source;
String sourcePath;
if (GameVersionNumber.asGameVersion(repository.getGameVersion(version)).compareTo("1.12") < 0) {
source = DefaultLauncher.class.getResourceAsStream("/assets/game/log4j2-1.7.xml");
if (options.isEnableDebugLogOutput()) {
sourcePath = "/assets/game/log4j2-1.7-debug.xml";
} else {
sourcePath = "/assets/game/log4j2-1.7.xml";
}
} else {
source = DefaultLauncher.class.getResourceAsStream("/assets/game/log4j2-1.12.xml");
if (options.isEnableDebugLogOutput()) {
sourcePath = "/assets/game/log4j2-1.12-debug.xml";
} else {
sourcePath = "/assets/game/log4j2-1.12.xml";
}
}
try (InputStream input = source) {
try (InputStream input = DefaultLauncher.class.getResourceAsStream(sourcePath)) {
Files.copy(input, targetFile, StandardCopyOption.REPLACE_EXISTING);
}
}
@@ -609,8 +627,8 @@ public class DefaultLauncher extends Launcher {
if (!usePowerShell) {
if (isWindows && !scriptExtension.equals("bat"))
throw new IllegalArgumentException("The extension of " + scriptFile + " is not 'bat' or 'ps1' in Windows");
else if (!isWindows && !scriptExtension.equals("sh"))
throw new IllegalArgumentException("The extension of " + scriptFile + " is not 'sh' or 'ps1' in macOS/Linux");
else if (!isWindows && !(scriptExtension.equals("sh") || scriptExtension.equals("command")))
throw new IllegalArgumentException("The extension of " + scriptFile + " is not 'sh', 'ps1' or 'command' in macOS/Linux");
}
final Command commandLine = generateCommandLine(nativeFolder);
@@ -114,16 +114,20 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository
if (category != null && category.getSelf() instanceof CurseAddon.Category) {
categoryId = ((CurseAddon.Category) category.getSelf()).getId();
}
Response<List<CurseAddon>> response = withApiKey(HttpRequest.GET(downloadProvider.injectURL(NetworkUtils.withQuery(PREFIX + "/v1/mods/search", mapOf(
pair("gameId", "432"),
pair("classId", Integer.toString(section)),
pair("categoryId", Integer.toString(categoryId)),
pair("gameVersion", gameVersion),
pair("searchFilter", searchFilter),
pair("sortField", Integer.toString(toModsSearchSortField(sortType))),
pair("sortOrder", toSortOrder(sortOrder)),
pair("index", Integer.toString(pageOffset * pageSize)),
pair("pageSize", Integer.toString(pageSize)))))))
var query = new LinkedHashMap<String, String>();
query.put("gameId", "432");
query.put("classId", Integer.toString(section));
if (categoryId != 0)
query.put("categoryId", Integer.toString(categoryId));
query.put("gameVersion", gameVersion);
query.put("searchFilter", searchFilter);
query.put("sortField", Integer.toString(toModsSearchSortField(sortType)));
query.put("sortOrder", toSortOrder(sortOrder));
query.put("index", Integer.toString(pageOffset * pageSize));
query.put("pageSize", Integer.toString(pageSize));
Response<List<CurseAddon>> response = withApiKey(HttpRequest.GET(downloadProvider.injectURL(NetworkUtils.withQuery(PREFIX + "/v1/mods/search", query))))
.getJson(Response.typeOf(listTypeOf(CurseAddon.class)));
if (searchFilter.isEmpty()) {
return new SearchResult(response.getData().stream().map(CurseAddon::toMod), calculateTotalPages(response, pageSize));
@@ -337,6 +337,10 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
} else if (suffix.startsWith("-snapshot-")) {
releaseType = ReleaseType.SNAPSHOT;
eaVersion = VersionNumber.asVersion(suffix.substring("-snapshot-".length()));
} else if (suffix.startsWith(" Snapshot ")) {
needNormalize = true;
releaseType = ReleaseType.SNAPSHOT;
eaVersion = VersionNumber.asVersion(suffix.substring(" Snapshot ".length()));
} else if (suffix.startsWith("-pre")) {
releaseType = ReleaseType.PRE_RELEASE;
eaVersion = VersionNumber.asVersion(suffix.substring("-pre".length()));
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" packages="">
<Appenders>
<Console name="SysOut" target="SYSTEM_OUT">
<PatternLayout pattern="[%d{HH:mm:ss}] [%t/%level]: %msg{nolookups}%n" />
</Console>
<Queue name="ServerGuiConsole">
<PatternLayout pattern="[%d{HH:mm:ss} %level]: %msg{nolookups}%n" />
</Queue>
<RollingRandomAccessFile name="File" fileName="logs/latest.log" filePattern="logs/%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="[%d{HH:mm:ss}] [%t/%level] [%logger]: %msg{nolookups}%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<OnStartupTriggeringPolicy/>
</Policies>
</RollingRandomAccessFile>
<RollingRandomAccessFile name="DebugFile" fileName="logs/debug.log" filePattern="logs/debug-%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss}] [%t/%level] [%logger]: %msg{nolookups}%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<OnStartupTriggeringPolicy/>
</Policies>
<DefaultRolloverStrategy max="999" fileIndex="min"/>
</RollingRandomAccessFile>
</Appenders>
<Loggers>
<Root level="all">
<filters>
<MarkerFilter marker="NETWORK_PACKETS" onMatch="DENY" onMismatch="NEUTRAL"/>
</filters>
<AppenderRef ref="SysOut" level="info"/>
<AppenderRef ref="ServerGuiConsole" level="info"/>
<AppenderRef ref="File" level="info"/>
<AppenderRef ref="DebugFile" level="all"/>
</Root>
</Loggers>
</Configuration>
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="SysOut" target="SYSTEM_OUT">
<PatternLayout pattern="[%d{HH:mm:ss}] [%t/%level]: %msg%n" />
</Console>
<Queue name="ServerGuiConsole">
<PatternLayout pattern="[%d{HH:mm:ss} %level]: %msg%n" />
</Queue>
<RollingRandomAccessFile name="File" fileName="logs/latest.log" filePattern="logs/%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="[%d{HH:mm:ss}] [%t/%level] [%logger]: %msg%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<OnStartupTriggeringPolicy/>
</Policies>
</RollingRandomAccessFile>
<RollingRandomAccessFile name="DebugFile" fileName="logs/debug.log" filePattern="logs/debug-%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss}] [%t/%level] [%logger]: %msg%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<OnStartupTriggeringPolicy/>
</Policies>
</RollingRandomAccessFile>
</Appenders>
<Loggers>
<Root level="all">
<filters>
<MarkerFilter marker="NETWORK_PACKETS" onMatch="DENY" onMismatch="NEUTRAL"/>
</filters>
<AppenderRef ref="SysOut" level="info"/>
<AppenderRef ref="ServerGuiConsole" level="info"/>
<AppenderRef ref="File" level="info"/>
<AppenderRef ref="DebugFile" level="all"/>
</Root>
</Loggers>
</Configuration>
@@ -432,6 +432,7 @@ public final class GameVersionNumberTest {
assertNormalized(version, version);
}
assertNormalized("26.1-snapshot-1", "26.1 Snapshot 1");
assertNormalized("1.21.11-pre3", "1.21.11 Pre-Release 3");
assertNormalized("1.21.11-pre3_unobfuscated", "1.21.11 Pre-Release 3 Unobfuscated");
assertNormalized("1.21.11-pre3_unobfuscated", "1.21.11-pre3 Unobfuscated");
+1
View File
@@ -2,6 +2,7 @@
<!-- #BEGIN BLOCK -->
<!-- #PROPERTY NAME=BADGES -->
[![CNB](https://img.shields.io/badge/cnb-mirror-ff6200?logo=cloudnativebuild)](https://cnb.cool/HMCL-dev/HMCL)
[![Downloads](https://img.shields.io/github/downloads/HMCL-dev/HMCL/total?label=Downloads&style=flat)](https://github.com/HMCL-dev/HMCL/releases)
![Stars](https://img.shields.io/github/stars/HMCL-dev/HMCL?style=flat)
[![Discord](https://img.shields.io/discord/995291757799538688.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/jVvC7HfM6U)
+1
View File
@@ -2,6 +2,7 @@
<!-- #BEGIN COPY -->
<!-- #PROPERTY NAME=BADGES -->
[![CNB](https://img.shields.io/badge/cnb-mirror-ff6200?logo=cloudnativebuild)](https://cnb.cool/HMCL-dev/HMCL)
[![Downloads](https://img.shields.io/github/downloads/HMCL-dev/HMCL/total?label=Downloads&style=flat)](https://github.com/HMCL-dev/HMCL/releases)
![Stars](https://img.shields.io/github/stars/HMCL-dev/HMCL?style=flat)
[![Discord](https://img.shields.io/discord/995291757799538688.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/jVvC7HfM6U)
+1
View File
@@ -2,6 +2,7 @@
<!-- #BEGIN COPY -->
<!-- #PROPERTY NAME=BADGES -->
[![CNB](https://img.shields.io/badge/cnb-mirror-ff6200?logo=cloudnativebuild)](https://cnb.cool/HMCL-dev/HMCL)
[![Downloads](https://img.shields.io/github/downloads/HMCL-dev/HMCL/total?label=Downloads&style=flat)](https://github.com/HMCL-dev/HMCL/releases)
![Stars](https://img.shields.io/github/stars/HMCL-dev/HMCL?style=flat)
[![Discord](https://img.shields.io/discord/995291757799538688.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/jVvC7HfM6U)
+1
View File
@@ -2,6 +2,7 @@
<!-- #BEGIN COPY -->
<!-- #PROPERTY NAME=BADGES -->
[![CNB](https://img.shields.io/badge/cnb-mirror-ff6200?logo=cloudnativebuild)](https://cnb.cool/HMCL-dev/HMCL)
[![Downloads](https://img.shields.io/github/downloads/HMCL-dev/HMCL/total?label=Downloads&style=flat)](https://github.com/HMCL-dev/HMCL/releases)
![Stars](https://img.shields.io/github/stars/HMCL-dev/HMCL?style=flat)
[![Discord](https://img.shields.io/discord/995291757799538688.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/jVvC7HfM6U)
+1
View File
@@ -2,6 +2,7 @@
<!-- #BEGIN COPY -->
<!-- #PROPERTY NAME=BADGES -->
[![CNB](https://img.shields.io/badge/cnb-mirror-ff6200?logo=cloudnativebuild)](https://cnb.cool/HMCL-dev/HMCL)
[![Downloads](https://img.shields.io/github/downloads/HMCL-dev/HMCL/total?label=Downloads&style=flat)](https://github.com/HMCL-dev/HMCL/releases)
![Stars](https://img.shields.io/github/stars/HMCL-dev/HMCL?style=flat)
[![Discord](https://img.shields.io/discord/995291757799538688.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/jVvC7HfM6U)
+1
View File
@@ -2,6 +2,7 @@
<!-- #BEGIN COPY -->
<!-- #PROPERTY NAME=BADGES -->
[![CNB](https://img.shields.io/badge/cnb-mirror-ff6200?logo=cloudnativebuild)](https://cnb.cool/HMCL-dev/HMCL)
[![Downloads](https://img.shields.io/github/downloads/HMCL-dev/HMCL/total?label=Downloads&style=flat)](https://github.com/HMCL-dev/HMCL/releases)
![Stars](https://img.shields.io/github/stars/HMCL-dev/HMCL?style=flat)
[![Discord](https://img.shields.io/discord/995291757799538688.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/jVvC7HfM6U)
+1
View File
@@ -2,6 +2,7 @@
<!-- #BEGIN COPY -->
<!-- #PROPERTY NAME=BADGES -->
[![CNB](https://img.shields.io/badge/cnb-mirror-ff6200?logo=cloudnativebuild)](https://cnb.cool/HMCL-dev/HMCL)
[![Downloads](https://img.shields.io/github/downloads/HMCL-dev/HMCL/total?label=Downloads&style=flat)](https://github.com/HMCL-dev/HMCL/releases)
![Stars](https://img.shields.io/github/stars/HMCL-dev/HMCL?style=flat)
[![Discord](https://img.shields.io/discord/995291757799538688.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/jVvC7HfM6U)
+1
View File
@@ -2,6 +2,7 @@
<!-- #BEGIN COPY -->
<!-- #PROPERTY NAME=BADGES -->
[![CNB](https://img.shields.io/badge/cnb-mirror-ff6200?logo=cloudnativebuild)](https://cnb.cool/HMCL-dev/HMCL)
[![Downloads](https://img.shields.io/github/downloads/HMCL-dev/HMCL/total?label=Downloads&style=flat)](https://github.com/HMCL-dev/HMCL/releases)
![Stars](https://img.shields.io/github/stars/HMCL-dev/HMCL?style=flat)
[![Discord](https://img.shields.io/discord/995291757799538688.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/jVvC7HfM6U)
+1 -1
View File
@@ -16,7 +16,7 @@ twelvemonkeys = "3.12.0"
jna = "5.18.1"
pci-ids = "0.4.0"
java-info = "1.0"
authlib-injector = "1.2.6"
authlib-injector = "1.2.7"
monet-fx = "0.4.0"
# testing