diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 331b5e472b..cfe697b62a 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -1,6 +1,6 @@ -name: Bug 反馈 | Bug Feedback +name: Bug 反馈 | Bug Report description: - 反馈一个 HMCL 错误。| Bug Feedback. + 反馈一个 HMCL 错误。| File a bug report for HMCL. title: "[Bug] " labels: bug body: @@ -8,29 +8,35 @@ body: attributes: value: | 提交前请确认: - * 该问题确实是 **HMCL 的错误**,而**不是 Minecraft 非正常退出**,如果你的 Minecraft 非正常退出,请前往 [QQ 群](https://docs.hmcl.net/groups.html)/[Discord 频道](https://discord.gg/jVvC7HfM6U) 中获取帮助。 - * 你的启动器版本是**最新的快照版本**,可以点击 [此处](https://zkitefly.github.io/HMCL-Snapshot-Update/) 下载最新快照版本。 - - 如果你的问题并不属于上述两类,你可以选取另一种 Issue 类型,或者直接前往 [QQ 群](https://docs.hmcl.net/groups.html)/[Discord 频道](https://discord.gg/jVvC7HfM6U) 中获取帮助。 - + + * 该问题确实是 **HMCL 的错误**,而**不是 Minecraft 非正常退出**,如果你的 Minecraft 非正常退出,请前往 [QQ 群](https://docs.hmcl.net/groups.html)/[Discord 服务器](https://discord.gg/jVvC7HfM6U) 获取帮助。 + * 你的启动器版本是**最新的预览版本**,可以点击 [此处](https://zkitefly.github.io/HMCL-Snapshot-Update/) 下载最新预览版本。 + + 如果你的问题并不属于上述两类,你可以选取另一种 Issue 类型,或者直接前往 [QQ 群](https://docs.hmcl.net/groups.html)/[Discord 服务器](https://discord.gg/jVvC7HfM6U) 获取帮助。 + Before submitting, please confirm: - * The issue is indeed **an error of HMCL**, not **Minecraft abnormal exit**. If your Minecraft exits abnormally, please go to the [QQ group](https://docs.hmcl.net/groups.html) or [Discord channel](https://discord.gg/jVvC7HfM6U) for help. - * Your launcher version is **the latest snapshot version**. You can click [here](https://github.com/burningtnt/HMCL-Snapshot-Update/raw/master/datas/HMCL-dev.jar) to download the latest snapshot version. - - If your issue does not fall into the above two categories, you can choose another type of issue or directly go to the [QQ group](https://docs.hmcl.net/groups.html) or [Discord channel](https://discord.gg/jVvC7HfM6U) for help. + + * 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 click [here](https://zkitefly.github.io/HMCL-Snapshot-Update/en) to download the latest nightly build. + + 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: input id: platform attributes: label: 平台 | Platform - description: 请输入您遇到 BUG 的平台。 Please enter the platform on which you encountered the bug. + description: | + 请输入你遇到问题的平台。 + Please enter the platform on which you encountered the bug. placeholder: e.g. Windows 11 validations: required: true - type: textarea id: bug-report attributes: - label: 问题描述 | Problem Description - description: 请尽可能地详细描述你所遇到的问题,并描述如何重新触发这个问题。 Please describe the problem you met in as much detail as possible. Besides, please describe how to trigger this problem again. + 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 @@ -42,12 +48,12 @@ body: attributes: label: 启动器崩溃报告 / 启动器日志文件 | Launcher Crash Report / Launcher Log File description: | - 如果您的启动器崩溃了,请将崩溃报告填入(或将文件拖入)下方。 - 如果您的启动器没有崩溃,请在遇到问题后不要退出启动器,在启动器的【设置】>【通用】>【调试】一栏中点击“导出启动器日志”,并将导出的日志拖到下方的输入栏中。 + 如果你的启动器崩溃了,请将崩溃报告填入(或将文件拖入)下方。 + 如果你的启动器没有崩溃,请在遇到问题后**不要退出启动器**,在启动器的 “设置 → 通用 → 调试” 一栏中点击 “导出启动器日志”,并将导出的日志拖到下方的输入栏中。 **请注意:启动器崩溃报告或日志文件是诊断问题的重要依据,请务必上传!** - If your launcher crashes, please fill it in (or drag the file in) below. - If your launcher doesn't crash, please DO NOT EXIT your launcher, click "Export Launcher Log" in the [Settings] > [General] > [Debug] column of the launcher, and drag the exported log to the input field below. - **Attention: The crash report or the log file is the key to solve the problem. Please update it!** + 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 \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 9136328d37..421efa07ab 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,11 +1,11 @@ blank_issues_enabled: false contact_links: - - name: QQ 群 | QQ Groups + - name: QQ 群 | QQ Group url: https://docs.hmcl.net/groups.html - about: Hello Minecraft! Launcher 的官方 QQ 交流群。| The official QQ groups of Hello Minecraft! Laucher. - - name: Discord 频道 | Discord Channel + 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 channel of Hello Minecraft! Launcher. + 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. diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml index 5bab343941..13808fc650 100644 --- a/.github/ISSUE_TEMPLATE/feature.yml +++ b/.github/ISSUE_TEMPLATE/feature.yml @@ -1,22 +1,28 @@ name: 新功能 | Feature Request -description: 为 HMCL 提出新功能 | A new feature for HMCL +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 duplicated issues has already been delivered. + value: | + 请确认 Issues 列表无重复的项目。 + Please make sure that no duplicate issues have already been submitted. - type: textarea id: description attributes: label: 描述 | Description - description: 请详细描述你想加入的新功能。| Please describe the new feature detaily. + description: | + 请详细描述你想加入的新功能。 + Please describe the new feature in detail. validations: required: true - type: textarea id: reason attributes: label: 原因 | Reason - description: 请描述该功能带来的好处及原因。| Please describe why you want to add the feature into HMCL. + description: | + 请描述该功能带来的好处及原因。 + Please describe why you want to add the feature or enhancement to HMCL. validations: required: true diff --git a/.github/workflows/check-update.yml b/.github/workflows/check-update.yml index 65cab990ed..9bf77cf74f 100644 --- a/.github/workflows/check-update.yml +++ b/.github/workflows/check-update.yml @@ -50,16 +50,19 @@ jobs: if: ${{ env.continue == 'true' }} run: | echo "The full changelogs can be found on the website: https://docs.hmcl.net/changelog/dev.html" >> RELEASE_NOTE - echo "Notice: changelogs are written in Chinese." >> RELEASE_NOTE + echo "" >> RELEASE_NOTE + echo "*Notice: changelogs are written in Chinese.*" >> RELEASE_NOTE echo "" >> RELEASE_NOTE echo "| File Name | SHA-256 Checksum |" >> RELEASE_NOTE echo "| --- | --- |" >> RELEASE_NOTE - echo "| HMCL-$HMCL_VERSION.exe | $(cat HMCL-$HMCL_VERSION.exe.sha256) |" >> RELEASE_NOTE - echo "| HMCL-$HMCL_VERSION.jar | $(cat HMCL-$HMCL_VERSION.jar.sha256) |" >> RELEASE_NOTE - echo "| HMCL-$HMCL_VERSION.sh | $(cat HMCL-$HMCL_VERSION.sh.sha256) |" >> RELEASE_NOTE + 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/HMCL-dev/HMCL/releases/download - name: Create release if: ${{ env.continue == 'true' }} - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: body_path: RELEASE_NOTE files: | @@ -69,6 +72,7 @@ jobs: target_commitish: ${{ env.HMCL_COMMIT_SHA }} name: ${{ env.HMCL_TAG_NAME }} tag_name: ${{ env.HMCL_TAG_NAME }} + prerelease: true stable-check-update: if: ${{ github.repository_owner == 'HMCL-dev' }} needs: dev-check-update @@ -114,16 +118,18 @@ jobs: echo "" >> RELEASE_NOTE echo "The full changelogs can be found on the website: https://docs.hmcl.net/changelog/stable.html" >> RELEASE_NOTE echo "" >> RELEASE_NOTE - echo "Notice: changelogs are written in Chinese." >> RELEASE_NOTE + echo "*Notice: changelogs are written in Chinese.*" >> RELEASE_NOTE echo "" >> RELEASE_NOTE echo "| File Name | SHA-256 Checksum |" >> RELEASE_NOTE echo "| --- | --- |" >> RELEASE_NOTE - echo "| HMCL-$HMCL_VERSION.exe | $(cat HMCL-$HMCL_VERSION.exe.sha256) |" >> RELEASE_NOTE - echo "| HMCL-$HMCL_VERSION.jar | $(cat HMCL-$HMCL_VERSION.jar.sha256) |" >> RELEASE_NOTE - echo "| HMCL-$HMCL_VERSION.sh | $(cat HMCL-$HMCL_VERSION.sh.sha256) |" >> RELEASE_NOTE + echo "| [HMCL-$HMCL_VERSION.exe]($GH_DOWNLOAD_BASE_URL/release-$HMCL_VERSION/HMCL-$HMCL_VERSION.exe) | \`$(cat HMCL-$HMCL_VERSION.exe.sha256)\` |" >> RELEASE_NOTE + echo "| [HMCL-$HMCL_VERSION.jar]($GH_DOWNLOAD_BASE_URL/release-$HMCL_VERSION/HMCL-$HMCL_VERSION.jar) | \`$(cat HMCL-$HMCL_VERSION.jar.sha256)\` |" >> RELEASE_NOTE + echo "| [HMCL-$HMCL_VERSION.sh]($GH_DOWNLOAD_BASE_URL/release-$HMCL_VERSION/HMCL-$HMCL_VERSION.sh) | \`$(cat HMCL-$HMCL_VERSION.sh.sha256)\` |" >> RELEASE_NOTE + env: + GH_DOWNLOAD_BASE_URL: https://github.com/HMCL-dev/HMCL/releases/download - name: Create release if: ${{ env.continue == 'true' }} - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: body_path: RELEASE_NOTE files: | diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index fe45a94b22..acc107f824 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -14,11 +14,11 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up JDK 11 + - name: Set up JDK 8 uses: actions/setup-java@v4 with: distribution: 'zulu' - java-version: '11' + java-version: 8 java-package: 'jdk+fx' - name: Build with Gradle run: ./gradlew build --no-daemon diff --git a/HMCL/build.gradle.kts b/HMCL/build.gradle.kts index 25b2687df0..aca92b7af3 100644 --- a/HMCL/build.gradle.kts +++ b/HMCL/build.gradle.kts @@ -25,7 +25,7 @@ val buildNumber = System.getenv("BUILD_NUMBER")?.toInt().let { number -> if (!shortCommit.isNullOrEmpty()) "$prefix-$shortCommit" else "SNAPSHOT" } } -val versionRoot = System.getenv("VERSION_ROOT") ?: "3.5" +val versionRoot = System.getenv("VERSION_ROOT") ?: "3.6" val versionType = System.getenv("VERSION_TYPE") ?: if (isOfficial) "nightly" else "unofficial" val microsoftAuthId = System.getenv("MICROSOFT_AUTH_ID") ?: "" @@ -37,13 +37,13 @@ version = "$versionRoot.$buildNumber" dependencies { implementation(project(":HMCLCore")) implementation("libs:JFoenix") + implementation("com.twelvemonkeys.imageio:imageio-webp:3.12.0") } fun digest(algorithm: String, bytes: ByteArray): ByteArray = MessageDigest.getInstance(algorithm).digest(bytes) fun createChecksum(file: File) { val algorithms = linkedMapOf( - "MD5" to "md5", "SHA-1" to "sha1", "SHA-256" to "sha256", "SHA-512" to "sha512" @@ -111,6 +111,9 @@ tasks.getByName("sha exclude("**/package-info.class") exclude("META-INF/maven/**") + exclude("META-INF/services/javax.imageio.spi.ImageReaderSpi") + exclude("META-INF/services/javax.imageio.spi.ImageInputStreamSpi") + minimize { exclude(dependency("com.google.code.gson:.*:.*")) exclude(dependency("libs:JFoenix:.*")) @@ -118,7 +121,7 @@ tasks.getByName("sha manifest { attributes( - "Created-By" to "Copyright(c) 2013-2024 huangyuhui.", + "Created-By" to "Copyright(c) 2013-2025 huangyuhui.", "Main-Class" to "org.jackhuang.hmcl.Main", "Multi-Release" to "true", "Implementation-Version" to project.version, @@ -167,33 +170,10 @@ fun createExecutable(suffix: String, header: String) { } tasks.processResources { - fun convertToBSS(resource: String) { - doFirst { - val cssFile = File(projectDir, "src/main/resources/$resource") - val bssFile = File(projectDir, "build/compiled-resources/${resource.substring(0, resource.length - 4)}.bss") - bssFile.parentFile.mkdirs() - javaexec { - classpath = sourceSets["main"].compileClasspath - mainClass.set("com.sun.javafx.css.parser.Css2Bin") - args(cssFile, bssFile) - } - } - } - - from("build/compiled-resources") - - convertToBSS("assets/css/root.css") - convertToBSS("assets/css/blue.css") - into("META-INF/versions/11") { from(sourceSets["java11"].output) } dependsOn(tasks["java11Classes"]) - - into("assets") { - from(project.layout.buildDirectory.file("openjfx-dependencies.json")) - } - dependsOn(rootProject.tasks["generateOpenJFXDependencies"]) } val makeExecutables = tasks.create("makeExecutables") { @@ -208,6 +188,56 @@ tasks.build { dependsOn(makeExecutables) } +fun parseToolOptions(options: String?): List { + if (options == null) + return listOf() + + val builder = StringBuilder() + val result = mutableListOf() + + var offset = 0 + + loop@ while (offset < options.length) { + val ch = options[offset] + if (Character.isWhitespace(ch)) { + if (builder.isNotEmpty()) { + result += builder.toString() + builder.clear() + } + + while (offset < options.length && Character.isWhitespace(options[offset])) { + offset++ + } + + continue@loop + } + + if (ch == '\'' || ch == '"') { + offset++ + + while (offset < options.length) { + val ch2 = options[offset++] + if (ch2 != ch) { + builder.append(ch2) + } else { + continue@loop + } + } + + throw GradleException("Unmatched quote in $options") + } + + builder.append(ch) + offset++ + } + + if (builder.isNotEmpty()) { + result += builder.toString() + } + + return result +} + tasks.create("run") { dependsOn(tasks.jar) @@ -215,4 +245,11 @@ tasks.create("run") { classpath = files(jarPath) workingDir = rootProject.rootDir + + val vmOptions = parseToolOptions(System.getenv("HMCL_JAVA_OPTS")) + jvmArgs(vmOptions) + + doFirst { + logger.quiet("HMCL_JAVA_OPTS: $vmOptions") + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java index db71cb536d..189313ca9e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java @@ -33,19 +33,19 @@ import org.jackhuang.hmcl.setting.ProxyManager; import org.jackhuang.hmcl.setting.VersionIconType; import org.jackhuang.hmcl.setting.VersionSetting; +import org.jackhuang.hmcl.ui.FXUtils; 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.util.javafx.PropertyUtils; import org.jackhuang.hmcl.java.JavaRuntime; import org.jackhuang.hmcl.util.platform.OperatingSystem; import org.jackhuang.hmcl.util.versioning.VersionNumber; import org.jetbrains.annotations.Nullable; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.time.Instant; @@ -218,7 +218,7 @@ public VersionSetting createLocalVersionSetting(String id) { private VersionSetting initLocalVersionSetting(String id, VersionSetting vs) { localVersionSettings.put(id, vs); - vs.addPropertyChangedListener(a -> saveVersionSetting(id)); + vs.addListener(a -> saveVersionSetting(id)); return vs; } @@ -251,7 +251,6 @@ public VersionSetting getLocalVersionSettingOrCreate(String id) { public VersionSetting getVersionSetting(String id) { VersionSetting vs = getLocalVersionSetting(id); if (vs == null || vs.isUsesGlobal()) { - profile.getGlobal().setGlobal(true); // always keep global.isGlobal = true profile.getGlobal().setUsesGlobal(true); return profile.getGlobal(); } else @@ -261,24 +260,11 @@ public VersionSetting getVersionSetting(String id) { public Optional getVersionIconFile(String id) { File root = getVersionRoot(id); - File iconFile = new File(root, "icon.png"); - if (iconFile.exists()) { - return Optional.of(iconFile); - } - - iconFile = new File(root, "icon.jpg"); - if (iconFile.exists()) { - return Optional.of(iconFile); - } - - iconFile = new File(root, "icon.bmp"); - if (iconFile.exists()) { - return Optional.of(iconFile); - } - - iconFile = new File(root, "icon.gif"); - if (iconFile.exists()) { - return Optional.of(iconFile); + for (String extension : FXUtils.IMAGE_EXTENSIONS) { + File file = new File(root, "icon." + extension); + if (file.exists()) { + return Optional.of(file); + } } return Optional.empty(); @@ -286,7 +272,7 @@ public Optional getVersionIconFile(String id) { public void setVersionIconFile(String id, File iconFile) throws IOException { String ext = FileUtils.getExtension(iconFile).toLowerCase(Locale.ROOT); - if (!ext.equals("png") && !ext.equals("jpg") && !ext.equals("bmp") && !ext.equals("gif")) { + if (!FXUtils.IMAGE_EXTENSIONS.contains(ext)) { throw new IllegalArgumentException("Unsupported icon file: " + ext); } @@ -297,11 +283,9 @@ public void setVersionIconFile(String id, File iconFile) throws IOException { public void deleteIconFile(String id) { File root = getVersionRoot(id); - - new File(root, "icon.png").delete(); - new File(root, "icon.jpg").delete(); - new File(root, "icon.bmp").delete(); - new File(root, "icon.gif").delete(); + for (String extension : FXUtils.IMAGE_EXTENSIONS) { + new File(root, "icon." + extension).delete(); + } } public Image getVersionIconImage(String id) { @@ -315,9 +299,9 @@ public Image getVersionIconImage(String id) { Version version = getVersion(id).resolve(this); Optional iconFile = getVersionIconFile(id); if (iconFile.isPresent()) { - try (InputStream inputStream = new FileInputStream(iconFile.get())) { - return new Image(inputStream); - } catch (IOException e) { + try { + return FXUtils.loadImage(iconFile.get().toPath()); + } catch (Exception e) { LOG.warning("Failed to load version icon of " + id, e); } } @@ -353,6 +337,8 @@ public boolean saveVersionSetting(String id) { if (!FileUtils.makeDirectory(file.getAbsoluteFile().getParentFile())) return false; + LOG.info("Saving version setting: " + id); + try { FileUtils.writeText(file, GSON.toJson(localVersionSettings.get(id))); return true; @@ -373,7 +359,12 @@ public VersionSetting specializeVersionSetting(String id) { vs = createLocalVersionSetting(id); if (vs == null) return null; - vs.setUsesGlobal(false); + VersionIconType versionIcon = vs.getVersionIcon(); + if (vs.isUsesGlobal()) { + PropertyUtils.copyProperties(profile.getGlobal(), vs); + vs.setUsesGlobal(false); + } + vs.setVersionIcon(versionIcon); // versionIcon is preserved return vs; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackInstallTask.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackInstallTask.java index 19c8147ad1..494e9b6490 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackInstallTask.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackInstallTask.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.game; import com.google.gson.JsonParseException; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.LibraryAnalyzer; import org.jackhuang.hmcl.mod.MinecraftInstanceTask; @@ -67,8 +66,7 @@ public HMCLModpackInstallTask(Profile profile, File zipFile, Modpack modpack, St ModpackConfiguration config = null; try { if (json.exists()) { - config = JsonUtils.GSON.fromJson(FileUtils.readText(json), new TypeToken>() { - }.getType()); + config = JsonUtils.GSON.fromJson(FileUtils.readText(json), ModpackConfiguration.typeOf(Modpack.class)); if (!HMCLModpackProvider.INSTANCE.getName().equals(config.getType())) throw new IllegalArgumentException("Version " + name + " is not a HMCL modpack. Cannot update this version."); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java index c11bb0aa55..e837fa1e25 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java @@ -36,10 +36,14 @@ import org.jackhuang.hmcl.setting.*; import org.jackhuang.hmcl.task.*; import org.jackhuang.hmcl.ui.*; -import org.jackhuang.hmcl.ui.construct.*; +import org.jackhuang.hmcl.ui.construct.DialogCloseEvent; +import org.jackhuang.hmcl.ui.construct.MessageDialogPane; import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType; +import org.jackhuang.hmcl.ui.construct.PromptDialogPane; +import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane; import org.jackhuang.hmcl.util.*; import org.jackhuang.hmcl.util.i18n.I18n; +import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.io.ResponseCodeException; import org.jackhuang.hmcl.util.platform.*; import org.jackhuang.hmcl.util.versioning.GameVersionNumber; @@ -60,9 +64,10 @@ import static javafx.application.Platform.setImplicitExit; import static org.jackhuang.hmcl.ui.FXUtils.runInFX; import static org.jackhuang.hmcl.util.Lang.resolveException; -import static org.jackhuang.hmcl.util.logging.Logger.LOG; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; -import static org.jackhuang.hmcl.util.platform.Platform.*; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; +import static org.jackhuang.hmcl.util.platform.Platform.SYSTEM_PLATFORM; +import static org.jackhuang.hmcl.util.platform.Platform.isCompatibleWithX86Java; public final class LauncherHelper { @@ -178,6 +183,9 @@ private void launch0() { .thenComposeAsync(() -> logIn(account).withStage("launch.state.logging_in")) .thenComposeAsync(authInfo -> Task.supplyAsync(() -> { LaunchOptions launchOptions = repository.getLaunchOptions(selectedVersion, javaVersionRef.get(), profile.getGameDir(), javaAgents, scriptFile != null); + + LOG.info("Here's the structure of game mod directory:\n" + FileUtils.printFileStructure(repository.getModManager(selectedVersion).getModsDirectory(), 10)); + return new HMCLGameLauncher( repository, version.get(), @@ -244,9 +252,9 @@ public void onStop(boolean success, TaskExecutor executor) { } else if (ex instanceof PermissionException) { message = i18n("launch.failed.executable_permission"); } else if (ex instanceof ProcessCreationException) { - message = i18n("launch.failed.creating_process") + ex.getLocalizedMessage(); + message = i18n("launch.failed.creating_process") + "\n" + ex.getLocalizedMessage(); } else if (ex instanceof NotDecompressingNativesException) { - message = i18n("launch.failed.decompressing_natives") + ex.getLocalizedMessage(); + message = i18n("launch.failed.decompressing_natives") + "\n" + ex.getLocalizedMessage(); } else if (ex instanceof LibraryDownloadException) { message = i18n("launch.failed.download_library", ((LibraryDownloadException) ex).getLibrary().getName()) + "\n"; if (ex.getCause() instanceof ResponseCodeException) { @@ -522,6 +530,9 @@ else if (violatedMandatoryConstraints.contains(JavaVersionConstraint.VANILLA)) case MODDED_JAVA_17: suggestions.add(i18n("launch.advice.modded_java", 17, gameVersion)); break; + case MODDED_JAVA_21: + suggestions.add(i18n("launch.advice.modded_java", 21, gameVersion)); + break; case VANILLA_JAVA_8_51: suggestions.add(i18n("launch.advice.java8_51_1_13")); break; @@ -881,8 +892,8 @@ public void onExit(int exitCode, ExitType exitType) { } - private static final String ORACLEJDK_DOWNLOAD_LINK = "https://www.java.com/download"; - private static final String OPENJDK_DOWNLOAD_LINK = "https://docs.microsoft.com/java/openjdk/download"; + private static final String ORACLEJDK_DOWNLOAD_LINK = "https://www.java.com/download/"; + private static final String OPENJDK_DOWNLOAD_LINK = "https://learn.microsoft.com/java/openjdk/download"; public static final Queue PROCESSES = new ConcurrentLinkedQueue<>(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/ModpackHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/ModpackHelper.java index c076765976..78b96db501 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/ModpackHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/ModpackHelper.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.game; import com.google.gson.JsonParseException; -import com.google.gson.reflect.TypeToken; import org.apache.commons.compress.archivers.zip.ZipFile; import org.jackhuang.hmcl.mod.*; import org.jackhuang.hmcl.mod.curse.CurseModpackProvider; @@ -141,8 +140,7 @@ public static ModpackConfiguration readModpackConfiguration(File file) throws throw new FileNotFoundException(file.getPath()); else try { - return JsonUtils.GSON.fromJson(FileUtils.readText(file), new TypeToken>() { - }.getType()); + return JsonUtils.GSON.fromJson(FileUtils.readText(file), ModpackConfiguration.class); } catch (JsonParseException e) { throw new IOException("Malformed modpack configuration"); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/TexturesLoader.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/TexturesLoader.java index d314869eef..0321615a3c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/TexturesLoader.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/TexturesLoader.java @@ -87,7 +87,7 @@ public Map getMetadata() { } private static final ThreadPoolExecutor POOL = threadPool("TexturesDownload", true, 2, 10, TimeUnit.SECONDS); - private static final Path TEXTURES_DIR = Metadata.MINECRAFT_DIRECTORY.resolve("assets").resolve("skins"); + private static final Path TEXTURES_DIR = Metadata.HMCL_DIRECTORY.resolve("skins"); private static Path getTexturePath(Texture texture) { String url = texture.getUrl(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/java/JavaManager.java b/HMCL/src/main/java/org/jackhuang/hmcl/java/JavaManager.java index 3b9e86e815..a5592e6a9b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/java/JavaManager.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/java/JavaManager.java @@ -404,9 +404,9 @@ private static Map searchPotentialJavaExecutables() { FileUtils.tryGetPath(Lang.requireNonNullElse(System.getenv("ProgramFiles(x86)"), "C:\\Program Files (x86)"), "Minecraft Launcher\\runtime") .ifPresent(it -> searchAllOfficialJava(javaRuntimes, it, false)); } else if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX && Architecture.SYSTEM_ARCH == Architecture.X86_64) { - searchAllOfficialJava(javaRuntimes, Paths.get(System.getProperty("user.home")).resolve(".minecraft/runtime"), false); + searchAllOfficialJava(javaRuntimes, Paths.get(System.getProperty("user.home"), ".minecraft/runtime"), false); } else if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX) { - searchAllOfficialJava(javaRuntimes, Paths.get(System.getProperty("user.home")).resolve("Library/Application Support/minecraft/runtime"), false); + searchAllOfficialJava(javaRuntimes, Paths.get(System.getProperty("user.home"), "Library/Application Support/minecraft/runtime"), false); } searchAllOfficialJava(javaRuntimes, CacheRepository.getInstance().getCacheDirectory().resolve("java"), true); @@ -430,6 +430,7 @@ private static Map searchPotentialJavaExecutables() { } } } + searchAllJavaInDirectory(javaRuntimes, Paths.get(System.getProperty("user.home"), ".jdks")); for (String javaPath : ConfigHolder.globalConfig().getUserJava()) { try { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/java/JavaManifest.java b/HMCL/src/main/java/org/jackhuang/hmcl/java/JavaManifest.java index 974ed92cd1..856b2a7195 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/java/JavaManifest.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/java/JavaManifest.java @@ -19,7 +19,6 @@ import com.google.gson.*; import com.google.gson.annotations.JsonAdapter; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.util.platform.Architecture; import org.jackhuang.hmcl.util.platform.OperatingSystem; import org.jackhuang.hmcl.util.platform.Platform; @@ -29,6 +28,8 @@ import java.util.Map; import java.util.Optional; +import static org.jackhuang.hmcl.util.gson.JsonUtils.mapTypeOf; + /** * @author Glavo */ @@ -63,8 +64,7 @@ public Map getFiles() { public static final class Serializer implements JsonSerializer, JsonDeserializer { - private static final Type LOCAL_FILES_TYPE = new TypeToken>() { - }.getType(); + private static final Type LOCAL_FILES_TYPE = mapTypeOf(String.class, JavaLocalFiles.Local.class).getType(); @Override public JsonElement serialize(JavaManifest src, Type typeOfSrc, JsonSerializationContext context) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java index 6223c6da8f..009cf6b40e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java @@ -17,7 +17,6 @@ */ package org.jackhuang.hmcl.setting; -import com.google.gson.reflect.TypeToken; import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.property.ObjectProperty; @@ -55,6 +54,8 @@ import static org.jackhuang.hmcl.util.Lang.immutableListOf; import static org.jackhuang.hmcl.util.Lang.mapOf; import static org.jackhuang.hmcl.util.Pair.pair; +import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf; +import static org.jackhuang.hmcl.util.gson.JsonUtils.mapTypeOf; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.logging.Logger.LOG; @@ -171,14 +172,11 @@ private static void updateAccountStorages() { config().getAccountStorages().setAll(portable); } - @SuppressWarnings("unchecked") private static void loadGlobalAccountStorages() { Path globalAccountsFile = Metadata.HMCL_DIRECTORY.resolve("accounts.json"); if (Files.exists(globalAccountsFile)) { try (Reader reader = Files.newBufferedReader(globalAccountsFile)) { - globalAccountStorages.setAll((List>) - Config.CONFIG_GSON.fromJson(reader, new TypeToken>>() { - }.getType())); + globalAccountStorages.setAll(Config.CONFIG_GSON.fromJson(reader, listTypeOf(mapTypeOf(Object.class, Object.class)))); } catch (Throwable e) { LOG.warning("Failed to load global accounts", e); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java index b124c509a5..44e7d3a6be 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java @@ -47,7 +47,7 @@ import java.util.Map; import java.util.TreeMap; -public final class Config implements Cloneable, Observable { +public final class Config implements Observable { public static final int CURRENT_UI_VERSION = 0; @@ -223,11 +223,6 @@ public String toJson() { return CONFIG_GSON.toJson(this); } - @Override - public Config clone() { - return fromJson(this.toJson()); - } - // Getters & Setters & Properties public String getSelectedProfile() { return selectedProfile.get(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/ConfigHolder.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/ConfigHolder.java index cbd034e958..8d5e4809b5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/ConfigHolder.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/ConfigHolder.java @@ -197,7 +197,7 @@ private static void writeToConfig(String content) throws IOException { } } - static void markConfigDirty() { + private static void markConfigDirty() { configWriter.accept(configInstance.toJson()); } @@ -241,11 +241,7 @@ private static void writeToGlobalConfig(String content) throws IOException { } } - static void markGlobalConfigDirty() { + private static void markGlobalConfigDirty() { globalConfigWriter.accept(globalConfigInstance.toJson()); } - - private static void saveGlobalConfigSync() throws IOException { - writeToConfig(globalConfigInstance.toJson()); - } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/GlobalConfig.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/GlobalConfig.java index 7979f17fde..b78acc0cc2 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/GlobalConfig.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/GlobalConfig.java @@ -33,7 +33,7 @@ import java.util.*; @JsonAdapter(GlobalConfig.Serializer.class) -public final class GlobalConfig implements Cloneable, Observable { +public final class GlobalConfig implements Observable { @Nullable public static GlobalConfig fromJson(String json) throws JsonParseException { @@ -79,11 +79,6 @@ public String toJson() { return Config.CONFIG_GSON.toJson(this); } - @Override - public GlobalConfig clone() { - return fromJson(this.toJson()); - } - public int getAgreementVersion() { return agreementVersion.get(); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Profile.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Profile.java index 5b8da0995b..239eef2eca 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Profile.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Profile.java @@ -188,7 +188,7 @@ private void addPropertyChangedListener(InvalidationListener listener) { global.addListener(listener); gameDir.addListener(listener); useRelativePath.addListener(listener); - global.get().addPropertyChangedListener(listener); + global.get().addListener(listener); selectedVersion.addListener(listener); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Theme.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Theme.java index 076c284611..a120b6640b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Theme.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Theme.java @@ -24,9 +24,8 @@ import javafx.beans.binding.ObjectBinding; import javafx.scene.paint.Color; import javafx.scene.text.Font; -import org.jackhuang.hmcl.util.Lang; +import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.util.io.FileUtils; -import org.jackhuang.hmcl.util.io.IOUtils; import java.io.File; import java.io.IOException; @@ -36,11 +35,10 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Base64; import java.util.Locale; import java.util.Objects; import java.util.Optional; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import static org.jackhuang.hmcl.setting.ConfigHolder.config; import static org.jackhuang.hmcl.util.logging.Logger.LOG; @@ -58,34 +56,6 @@ public class Theme { Color.web("#B71C1C") // red }; - private static Charset cssCharset; - - private static Charset getCssCharset() { - if (cssCharset != null) - return cssCharset; - - Charset defaultCharset = Charset.defaultCharset(); - if (defaultCharset != StandardCharsets.UTF_8) { - // https://bugs.openjdk.org/browse/JDK-8279328 - // For JavaFX 17 or earlier, native encoding should be used - String jfxVersion = System.getProperty("javafx.version"); - if (jfxVersion != null) { - Matcher matcher = Pattern.compile("^(?[0-9]+)").matcher(jfxVersion); - if (matcher.find()) { - int v = Lang.parseInt(matcher.group(), -1); - if (v >= 18) { - cssCharset = StandardCharsets.UTF_8; - } - } - } - } - - if (cssCharset == null) - cssCharset = defaultCharset; - - return cssCharset; - } - @SuppressWarnings("OptionalUsedAsFieldOrParameterType") private static Optional font; @@ -145,6 +115,14 @@ public Color getForegroundColor() { return isLight() ? Color.BLACK : Color.WHITE; } + private static String rgba(Color color, double opacity) { + return String.format("rgba(%d, %d, %d, %.1f)", + (int) Math.ceil(color.getRed() * 256), + (int) Math.ceil(color.getGreen() * 256), + (int) Math.ceil(color.getBlue() * 256), + opacity); + } + public String[] getStylesheets(String overrideFontFamily) { String css = "/assets/css/blue.css"; @@ -163,30 +141,44 @@ public String[] getStylesheets(String overrideFontFamily) { if (fontFamily != null || !this.color.equalsIgnoreCase(BLUE.color)) { Color textFill = getForegroundColor(); - String fontCss = ""; - if (fontFamily != null) { - fontCss = "-fx-font-family: \"" + fontFamily + "\";"; - if (fontStyle != null && !fontStyle.isEmpty()) - fontCss += " -fx-font-style: \"" + fontStyle + "\";"; - } - try { - File temp = File.createTempFile("hmcl", ".css"); - String themeText = IOUtils.readFullyAsString(Theme.class.getResourceAsStream("/assets/css/custom.css")) - .replace("%base-color%", color) - .replace("%base-red%", Integer.toString((int) Math.ceil(paint.getRed() * 256))) - .replace("%base-green%", Integer.toString((int) Math.ceil(paint.getGreen() * 256))) - .replace("%base-blue%", Integer.toString((int) Math.ceil(paint.getBlue() * 256))) - .replace("%base-rippler-color%", String.format("rgba(%d, %d, %d, 0.3)", (int) Math.ceil(paint.getRed() * 256), (int) Math.ceil(paint.getGreen() * 256), (int) Math.ceil(paint.getBlue() * 256))) - .replace("%disabled-font-color%", String.format("rgba(%d, %d, %d, 0.7)", (int) Math.ceil(textFill.getRed() * 256), (int) Math.ceil(textFill.getGreen() * 256), (int) Math.ceil(textFill.getBlue() * 256))) - .replace("%font-color%", getColorDisplayName(getForegroundColor())) - .replace("%font%", fontCss); - FileUtils.writeText(temp, themeText, getCssCharset()); - temp.deleteOnExit(); - css = temp.toURI().toString(); - } catch (IOException | NullPointerException e) { - LOG.error("Unable to create theme stylesheet. Fallback to blue theme.", e); - } + StringBuilder themeBuilder = new StringBuilder(512); + themeBuilder.append(".root {") + .append("-fx-base-color:").append(color).append(';') + .append("-fx-base-darker-color: derive(-fx-base-color, -10%);") + .append("-fx-base-check-color: derive(-fx-base-color, 30%);") + .append("-fx-rippler-color:").append(rgba(paint, 0.3)).append(';') + .append("-fx-base-rippler-color: derive(").append(rgba(paint, 0.3)).append(", 100%);") + .append("-fx-base-disabled-text-fill:").append(rgba(textFill, 0.7)).append(";") + .append("-fx-base-text-fill:").append(getColorDisplayName(getForegroundColor())).append(";") + .append("-theme-thumb:").append(rgba(paint, 0.7)).append(";"); + + if (fontFamily == null) + // https://github.com/HMCL-dev/HMCL/pull/3423 + themeBuilder.append("-fx-font-family: -fx-base-font-family;"); + else + themeBuilder.append("-fx-font-family:\"").append(fontFamily).append("\";"); + + if (fontStyle != null && !fontStyle.isEmpty()) + themeBuilder.append("-fx-font-style:\"").append(fontStyle).append("\";"); + + themeBuilder.append('}'); + + if (FXUtils.JAVAFX_MAJOR_VERSION >= 17) + // JavaFX 17+ support loading stylesheets from data URIs + // https://bugs.openjdk.org/browse/JDK-8267554 + css = "data:text/css;charset=UTF-8;base64," + Base64.getEncoder().encodeToString(themeBuilder.toString().getBytes(StandardCharsets.UTF_8)); + else + try { + File temp = File.createTempFile("hmcl", ".css"); + // For JavaFX 17 or earlier, CssParser uses the default charset + // https://bugs.openjdk.org/browse/JDK-8279328 + FileUtils.writeText(temp, themeBuilder.toString(), Charset.defaultCharset()); + temp.deleteOnExit(); + css = temp.toURI().toString(); + } catch (IOException | NullPointerException e) { + LOG.error("Unable to create theme stylesheet. Fallback to blue theme.", e); + } } return new String[]{css, "/assets/css/root.css"}; @@ -235,7 +227,7 @@ else if (name.startsWith("#")) } public static String getColorDisplayName(Color c) { - return c != null ? String.format("#%02x%02x%02x", Math.round(c.getRed() * 255.0D), Math.round(c.getGreen() * 255.0D), Math.round(c.getBlue() * 255.0D)).toUpperCase(Locale.ROOT) : null; + return c != null ? String.format("#%02X%02X%02X", Math.round(c.getRed() * 255.0D), Math.round(c.getGreen() * 255.0D), Math.round(c.getBlue() * 255.0D)) : null; } private static ObjectBinding FOREGROUND_FILL; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.java index aa084d119b..39fb1afd48 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.java @@ -20,11 +20,14 @@ import com.google.gson.*; import com.google.gson.annotations.JsonAdapter; import javafx.beans.InvalidationListener; +import javafx.beans.Observable; import javafx.beans.property.*; import org.jackhuang.hmcl.game.*; import org.jackhuang.hmcl.java.JavaManager; import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.javafx.ObservableHelper; +import org.jackhuang.hmcl.util.javafx.PropertyUtils; import org.jackhuang.hmcl.java.JavaRuntime; import org.jackhuang.hmcl.util.platform.OperatingSystem; import org.jackhuang.hmcl.util.versioning.GameVersionNumber; @@ -42,16 +45,12 @@ * @author huangyuhui */ @JsonAdapter(VersionSetting.Serializer.class) -public final class VersionSetting implements Cloneable { +public final class VersionSetting implements Cloneable, Observable { - private boolean global = false; + private transient ObservableHelper helper = new ObservableHelper(this); - public boolean isGlobal() { - return global; - } - - public void setGlobal(boolean global) { - this.global = global; + public VersionSetting() { + PropertyUtils.attachListener(this, helper); } private final BooleanProperty usesGlobalProperty = new SimpleBooleanProperty(this, "usesGlobal", true); @@ -708,79 +707,21 @@ public JavaRuntime getJava(GameVersionNumber gameVersion, Version version) throw } } - public void addPropertyChangedListener(InvalidationListener listener) { - usesGlobalProperty.addListener(listener); - javaVersionProperty.addListener(listener); - javaDirProperty.addListener(listener); - wrapperProperty.addListener(listener); - permSizeProperty.addListener(listener); - maxMemoryProperty.addListener(listener); - minMemoryProperty.addListener(listener); - autoMemory.addListener(listener); - preLaunchCommandProperty.addListener(listener); - postExitCommand.addListener(listener); - javaArgsProperty.addListener(listener); - minecraftArgsProperty.addListener(listener); - environmentVariablesProperty.addListener(listener); - noJVMArgsProperty.addListener(listener); - notCheckGameProperty.addListener(listener); - notCheckJVMProperty.addListener(listener); - notPatchNativesProperty.addListener(listener); - showLogsProperty.addListener(listener); - serverIpProperty.addListener(listener); - fullscreenProperty.addListener(listener); - widthProperty.addListener(listener); - heightProperty.addListener(listener); - gameDirTypeProperty.addListener(listener); - gameDirProperty.addListener(listener); - processPriorityProperty.addListener(listener); - rendererProperty.addListener(listener); - useNativeGLFW.addListener(listener); - useNativeOpenAL.addListener(listener); - launcherVisibilityProperty.addListener(listener); - defaultJavaPathProperty.addListener(listener); - nativesDirProperty.addListener(listener); - nativesDirTypeProperty.addListener(listener); - versionIcon.addListener(listener); + @Override + public void addListener(InvalidationListener listener) { + helper.addListener(listener); + } + + @Override + public void removeListener(InvalidationListener listener) { + helper.removeListener(listener); } @Override public VersionSetting clone() { - VersionSetting versionSetting = new VersionSetting(); - versionSetting.setUsesGlobal(isUsesGlobal()); - versionSetting.setJavaVersionType(getJavaVersionType()); - versionSetting.setJavaVersion(getJavaVersion()); - versionSetting.setDefaultJavaPath(getDefaultJavaPath()); - versionSetting.setJavaDir(getJavaDir()); - versionSetting.setWrapper(getWrapper()); - versionSetting.setPermSize(getPermSize()); - versionSetting.setMaxMemory(getMaxMemory()); - versionSetting.setMinMemory(getMinMemory()); - versionSetting.setAutoMemory(isAutoMemory()); - versionSetting.setPreLaunchCommand(getPreLaunchCommand()); - versionSetting.setPostExitCommand(getPostExitCommand()); - versionSetting.setJavaArgs(getJavaArgs()); - versionSetting.setMinecraftArgs(getMinecraftArgs()); - versionSetting.setEnvironmentVariables(getEnvironmentVariables()); - versionSetting.setNoJVMArgs(isNoJVMArgs()); - versionSetting.setNotCheckGame(isNotCheckGame()); - versionSetting.setNotCheckJVM(isNotCheckJVM()); - versionSetting.setNotPatchNatives(isNotPatchNatives()); - versionSetting.setShowLogs(isShowLogs()); - versionSetting.setServerIp(getServerIp()); - versionSetting.setFullscreen(isFullscreen()); - versionSetting.setWidth(getWidth()); - versionSetting.setHeight(getHeight()); - versionSetting.setGameDirType(getGameDirType()); - versionSetting.setGameDir(getGameDir()); - versionSetting.setProcessPriority(getProcessPriority()); - versionSetting.setRenderer(getRenderer()); - versionSetting.setUseNativeGLFW(isUseNativeGLFW()); - versionSetting.setUseNativeOpenAL(isUseNativeOpenAL()); - versionSetting.setLauncherVisibility(getLauncherVisibility()); - versionSetting.setNativesDir(getNativesDir()); - versionSetting.setVersionIcon(getVersionIcon()); - return versionSetting; + VersionSetting cloned = new VersionSetting(); + PropertyUtils.copyProperties(this, cloned); + return cloned; } public static class Serializer implements JsonSerializer, JsonDeserializer { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java index 724eb7deff..44e208ac58 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java @@ -391,9 +391,6 @@ public static void onHyperlinkAction(String href) { Controllers.getSettingsPage().showFeedback(); Controllers.navigate(Controllers.getSettingsPage()); break; - case "hmcl://hide-announcement": - Controllers.getRootPage().getMainPage().hideAnnouncementPane(); - break; } } else { FXUtils.openLink(href); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/CrashWindow.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/CrashWindow.java index c1f7158d44..5af27b6a60 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/CrashWindow.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/CrashWindow.java @@ -53,7 +53,7 @@ else if (UpdateChecker.isOutdated()) Button btnContact = new Button(); btnContact.setText(i18n("launcher.contact")); - btnContact.setOnMouseClicked(event -> FXUtils.openLink(Metadata.CONTACT_URL)); + btnContact.setOnAction(event -> FXUtils.openLink(Metadata.CONTACT_URL)); HBox box = new HBox(); box.setStyle("-fx-padding: 8px;"); box.getChildren().add(btnContact); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java index b7394c40e7..20efb21eb1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -18,19 +18,23 @@ package org.jackhuang.hmcl.ui; import com.jfoenix.controls.*; +import com.twelvemonkeys.imageio.plugins.webp.WebPImageReaderSpi; import javafx.animation.*; import javafx.application.Platform; import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.WeakInvalidationListener; +import javafx.beans.property.BooleanProperty; import javafx.beans.property.Property; -import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; -import javafx.beans.value.WeakChangeListener; -import javafx.beans.value.WritableValue; +import javafx.beans.value.*; +import javafx.event.Event; +import javafx.event.EventDispatcher; +import javafx.event.EventType; import javafx.geometry.Insets; import javafx.geometry.Pos; +import javafx.scene.Cursor; import javafx.scene.Node; +import javafx.scene.Scene; import javafx.scene.control.Label; import javafx.scene.control.ScrollPane; import javafx.scene.control.*; @@ -45,6 +49,7 @@ import javafx.scene.shape.Rectangle; import javafx.scene.text.Text; import javafx.scene.text.TextFlow; +import javafx.stage.FileChooser; import javafx.stage.Stage; import javafx.util.Callback; import javafx.util.Duration; @@ -52,14 +57,11 @@ import org.glavo.png.PNGType; import org.glavo.png.PNGWriter; import org.glavo.png.javafx.PNGJavaFXUtils; -import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.animation.AnimationUtils; -import org.jackhuang.hmcl.ui.construct.JFXHyperlink; -import org.jackhuang.hmcl.util.Holder; -import org.jackhuang.hmcl.util.ResourceNotFoundError; -import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.*; import org.jackhuang.hmcl.util.io.FileUtils; +import org.jackhuang.hmcl.util.io.NetworkUtils; import org.jackhuang.hmcl.util.javafx.ExtendedProperties; import org.jackhuang.hmcl.util.javafx.SafeStringConverter; import org.jackhuang.hmcl.util.platform.OperatingSystem; @@ -70,26 +72,26 @@ import org.xml.sax.InputSource; import org.xml.sax.SAXException; -import javax.swing.*; -import javax.swing.event.HyperlinkEvent; +import javax.imageio.ImageIO; +import javax.imageio.ImageReader; +import javax.imageio.stream.ImageInputStream; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; -import java.awt.*; import java.io.*; -import java.lang.ref.WeakReference; -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; -import java.net.URI; -import java.net.URISyntaxException; +import java.net.*; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.List; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BooleanSupplier; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; import static org.jackhuang.hmcl.util.Lang.thread; @@ -101,8 +103,26 @@ public final class FXUtils { private FXUtils() { } + public static final int JAVAFX_MAJOR_VERSION; + + static { + String jfxVersion = System.getProperty("javafx.version"); + int majorVersion = -1; + if (jfxVersion != null) { + Matcher matcher = Pattern.compile("^(?[0-9]+)").matcher(jfxVersion); + if (matcher.find()) { + majorVersion = Lang.parseInt(matcher.group(), -1); + } + } + JAVAFX_MAJOR_VERSION = majorVersion; + } + public static final String DEFAULT_MONOSPACE_FONT = OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS ? "Consolas" : "Monospace"; + public static final List IMAGE_EXTENSIONS = Lang.immutableListOf( + "png", "jpg", "jpeg", "bmp", "gif", "webp" + ); + private static final Map builtinImageCache = new ConcurrentHashMap<>(); private static final Map remoteImageCache = new ConcurrentHashMap<>(); @@ -218,6 +238,21 @@ public static void removeListener(Node node, String key) { }); } + @SuppressWarnings("unchecked") + public static void ignoreEvent(Node node, EventType type, Predicate filter) { + EventDispatcher oldDispatcher = node.getEventDispatcher(); + node.setEventDispatcher((event, tail) -> { + EventType t = event.getEventType(); + while (t != null && t != type) + t = t.getSuperType(); + if (t == type && filter.test((T) event)) { + return tail.dispatchEvent(event); + } else { + return oldDispatcher.dispatchEvent(event, tail); + } + }); + } + public static void setupCellValueFactory(JFXTreeTableColumn column, Function> mapper) { column.setCellValueFactory(param -> { if (column.validateValue(param)) @@ -303,8 +338,12 @@ public static void smoothScrolling(ScrollPane scrollPane) { ScrollUtils.addSmoothScrolling(scrollPane); } + private static final Duration TOOLTIP_FAST_SHOW_DELAY = Duration.millis(50); + private static final Duration TOOLTIP_SLOW_SHOW_DELAY = Duration.millis(500); + private static final Duration TOOLTIP_SHOW_DURATION = Duration.millis(5000); + public static void installFastTooltip(Node node, Tooltip tooltip) { - installTooltip(node, 50, 5000, 0, tooltip); + runInFX(() -> TooltipInstaller.INSTALLER.installTooltip(node, TOOLTIP_FAST_SHOW_DELAY, TOOLTIP_SHOW_DURATION, Duration.ZERO, tooltip)); } public static void installFastTooltip(Node node, String tooltip) { @@ -312,39 +351,13 @@ public static void installFastTooltip(Node node, String tooltip) { } public static void installSlowTooltip(Node node, Tooltip tooltip) { - installTooltip(node, 500, 5000, 0, tooltip); + runInFX(() -> TooltipInstaller.INSTALLER.installTooltip(node, TOOLTIP_SLOW_SHOW_DELAY, TOOLTIP_SHOW_DURATION, Duration.ZERO, tooltip)); } public static void installSlowTooltip(Node node, String tooltip) { installSlowTooltip(node, new Tooltip(tooltip)); } - public static void installTooltip(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip) { - runInFX(() -> { - try { - // Java 8 - Class behaviorClass = Class.forName("javafx.scene.control.Tooltip$TooltipBehavior"); - Constructor behaviorConstructor = behaviorClass.getDeclaredConstructor(Duration.class, Duration.class, Duration.class, boolean.class); - behaviorConstructor.setAccessible(true); - Object behavior = behaviorConstructor.newInstance(new Duration(openDelay), new Duration(visibleDelay), new Duration(closeDelay), false); - Method installMethod = behaviorClass.getDeclaredMethod("install", Node.class, Tooltip.class); - installMethod.setAccessible(true); - installMethod.invoke(behavior, node, tooltip); - } catch (ReflectiveOperationException e) { - try { - // Java 9 - Tooltip.class.getMethod("setShowDelay", Duration.class).invoke(tooltip, new Duration(openDelay)); - Tooltip.class.getMethod("setShowDuration", Duration.class).invoke(tooltip, new Duration(visibleDelay)); - Tooltip.class.getMethod("setHideDelay", Duration.class).invoke(tooltip, new Duration(closeDelay)); - } catch (ReflectiveOperationException e2) { - e.addSuppressed(e2); - LOG.error("Cannot install tooltip", e); - } - Tooltip.install(node, tooltip); - } - }); - } - public static void playAnimation(Node node, String animationKey, Timeline timeline) { animationKey = "FXUTILS.ANIMATION." + animationKey; Object oldTimeline = node.getProperties().get(animationKey); @@ -411,15 +424,20 @@ else if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() && new File("/usr/bin/xdg-ope }); } - private static boolean testLinuxCommand(String command) { - try (final InputStream is = Runtime.getRuntime().exec(new String[]{"which", command}).getInputStream()) { - if (is.read() != -1) { - return true; + private static String which(String command) { + String path = System.getenv("PATH"); + if (path == null) + return null; + + for (String item : path.split(OperatingSystem.PATH_SEPARATOR)) { + try { + Path program = Paths.get(item, command); + if (Files.isExecutable(program)) + return program.toRealPath().toString(); + } catch (Throwable ignored) { } - } catch (Throwable ignored) { } - - return false; + return null; } public static void showFileInExplorer(Path file) { @@ -430,7 +448,7 @@ public static void showFileInExplorer(Path file) { openCommands = new String[]{"explorer.exe", "/select,", path}; else if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX) openCommands = new String[]{"/usr/bin/open", "-R", path}; - else if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() && testLinuxCommand("dbus-send")) + else if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() && which("dbus-send") != null) openCommands = new String[]{ "dbus-send", "--print-reply", @@ -496,12 +514,13 @@ public static void openLink(String link) { } if (OperatingSystem.CURRENT_OS.isLinuxOrBSD()) { for (String browser : linuxBrowsers) { - try (final InputStream is = Runtime.getRuntime().exec(new String[]{"which", browser}).getInputStream()) { - if (is.read() != -1) { + String path = which(browser); + if (path != null) { + try { Runtime.getRuntime().exec(new String[]{browser, link}); return; + } catch (Throwable ignored) { } - } catch (Throwable ignored) { } LOG.warning("No known browser found"); } @@ -520,57 +539,13 @@ public static void openLink(String link) { }); } - public static void showWebDialog(String title, String content) { - showWebDialog(title, content, 800, 480); - } - - public static void showWebDialog(String title, String content, int width, int height) { - try { - WebStage stage = new WebStage(width, height); - stage.getWebView().getEngine().loadContent(content); - stage.setTitle(title); - stage.showAndWait(); - } catch (NoClassDefFoundError | UnsatisfiedLinkError e) { - LOG.warning("WebView is missing or initialization failed, use JEditorPane replaced", e); - - SwingUtils.initLookAndFeel(); - SwingUtilities.invokeLater(() -> { - final JFrame frame = new JFrame(title); - frame.setSize(width, height); - frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); - frame.setLocationByPlatform(true); - frame.setIconImage(new ImageIcon(FXUtils.class.getResource("/assets/img/icon.png")).getImage()); - frame.setLayout(new BorderLayout()); - - final JProgressBar progressBar = new JProgressBar(); - progressBar.setIndeterminate(true); - frame.add(progressBar, BorderLayout.PAGE_START); - - Schedulers.defaultScheduler().execute(() -> { - final JEditorPane pane = new JEditorPane("text/html", content); - pane.setEditable(false); - pane.addHyperlinkListener(event -> { - if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { - openLink(event.getURL().toExternalForm()); - } - }); - SwingUtilities.invokeLater(() -> { - progressBar.setVisible(false); - frame.add(new JScrollPane(pane), BorderLayout.CENTER); - }); - }); - - frame.setVisible(true); - frame.toFront(); - }); - } - } - public static void bind(JFXTextField textField, Property property, StringConverter converter) { - textField.setText(converter == null ? (String) property.getValue() : converter.toString(property.getValue())); - TextFieldBindingListener listener = new TextFieldBindingListener<>(textField, property, converter); - textField.focusedProperty().addListener((ChangeListener) listener); - property.addListener(listener); + TextFieldBinding binding = new TextFieldBinding<>(textField, property, converter); + binding.updateTextField(); + textField.getProperties().put("FXUtils.bind.binding", binding); + textField.focusedProperty().addListener(binding.focusedListener); + textField.sceneProperty().addListener(binding.sceneListener); + property.addListener(binding.propertyListener); } public static void bindInt(JFXTextField textField, Property property) { @@ -582,68 +557,66 @@ public static void bindString(JFXTextField textField, Property property) } public static void unbind(JFXTextField textField, Property property) { - TextFieldBindingListener listener = new TextFieldBindingListener<>(textField, property, null); - textField.focusedProperty().removeListener((ChangeListener) listener); - property.removeListener(listener); + TextFieldBinding binding = (TextFieldBinding) textField.getProperties().remove("FXUtils.bind.binding"); + if (binding != null) { + textField.focusedProperty().removeListener(binding.focusedListener); + textField.sceneProperty().removeListener(binding.sceneListener); + property.removeListener(binding.propertyListener); + } } - private static final class TextFieldBindingListener implements ChangeListener, InvalidationListener { - private final int hashCode; - private final WeakReference textFieldRef; - private final WeakReference> propertyRef; + private static final class TextFieldBinding { + private final JFXTextField textField; + private final Property property; private final StringConverter converter; - TextFieldBindingListener(JFXTextField textField, Property property, StringConverter converter) { - this.textFieldRef = new WeakReference<>(textField); - this.propertyRef = new WeakReference<>(property); + public final ChangeListener focusedListener; + public final ChangeListener sceneListener; + public final InvalidationListener propertyListener; + + public TextFieldBinding(JFXTextField textField, Property property, StringConverter converter) { + this.textField = textField; + this.property = property; this.converter = converter; - this.hashCode = System.identityHashCode(textField) ^ System.identityHashCode(property); - } - @Override - public void changed(ObservableValue observable, Boolean oldValue, Boolean focused) { // On TextField changed - JFXTextField textField = textFieldRef.get(); - Property property = this.propertyRef.get(); - - if (textField != null && property != null && oldValue == Boolean.TRUE && focused == Boolean.FALSE) { - if (textField.validate()) { - String newText = textField.getText(); - @SuppressWarnings("unchecked") - T newValue = converter == null ? (T) newText : converter.fromString(newText); - - if (!Objects.equals(newValue, property.getValue())) - property.setValue(newValue); - } else { - // Rollback to old value - invalidated(null); + focusedListener = (observable, oldFocused, newFocused) -> { + if (oldFocused && !newFocused) { + if (textField.validate()) { + uppdateProperty(); + } else { + // Rollback to old value + updateTextField(); + } } - } - } + }; - @Override - public void invalidated(Observable observable) { // On property change - JFXTextField textField = textFieldRef.get(); - Property property = this.propertyRef.get(); + sceneListener = (observable, oldScene, newScene) -> { + if (oldScene != null && newScene == null) { + // Component is being removed from scene + if (textField.validate()) { + uppdateProperty(); + } + } + }; - if (textField != null && property != null) { - T value = property.getValue(); - textField.setText(converter == null ? (String) value : converter.toString(value)); - } + propertyListener = observable -> { + updateTextField(); + }; } - @Override - public int hashCode() { - return hashCode; + public void uppdateProperty() { + String newText = textField.getText(); + @SuppressWarnings("unchecked") + T newValue = converter == null ? (T) newText : converter.fromString(newText); + + if (!Objects.equals(newValue, property.getValue())) { + property.setValue(newValue); + } } - @Override - public boolean equals(Object obj) { - if (!(obj instanceof TextFieldBindingListener)) - return false; - TextFieldBindingListener other = (TextFieldBindingListener) obj; - return this.hashCode == other.hashCode - && this.textFieldRef.get() == other.textFieldRef.get() - && this.propertyRef.get() == other.propertyRef.get(); + public void updateTextField() { + T value = property.getValue(); + textField.setText(converter == null ? (String) value : converter.toString(value)); } } @@ -700,6 +673,61 @@ public static void unbindEnum(JFXComboBox> comboBox) { comboBox.getSelectionModel().selectedIndexProperty().removeListener(listener); } + public static void bindAllEnabled(BooleanProperty allEnabled, BooleanProperty... children) { + int itemCount = children.length; + int childSelectedCount = 0; + for (BooleanProperty child : children) { + if (child.get()) + childSelectedCount++; + } + + allEnabled.set(childSelectedCount == itemCount); + + class Listener implements InvalidationListener { + private int childSelectedCount; + private boolean updating = false; + + public Listener(int childSelectedCount) { + this.childSelectedCount = childSelectedCount; + } + + @Override + public void invalidated(Observable observable) { + if (updating) + return; + + updating = true; + try { + boolean value = ((BooleanProperty) observable).get(); + + if (observable == allEnabled) { + for (BooleanProperty child : children) { + child.setValue(value); + } + childSelectedCount = value ? itemCount : 0; + } else { + if (value) + childSelectedCount++; + else + childSelectedCount--; + + allEnabled.set(childSelectedCount == itemCount); + } + } finally { + updating = false; + } + } + } + + InvalidationListener listener = new Listener(childSelectedCount); + + WeakInvalidationListener weakListener = new WeakInvalidationListener(listener); + allEnabled.addListener(listener); + for (BooleanProperty child : children) { + child.addListener(weakListener); + } + } + public static void setIcon(Stage stage) { String icon; if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) { @@ -710,6 +738,50 @@ public static void setIcon(Stage stage) { stage.getIcons().add(newBuiltinImage(icon)); } + private static Image loadWebPImage(InputStream input) throws IOException { + WebPImageReaderSpi spi = new WebPImageReaderSpi(); + ImageReader reader = spi.createReaderInstance(null); + + try (ImageInputStream imageInput = ImageIO.createImageInputStream(input)) { + reader.setInput(imageInput, true, true); + return SwingFXUtils.toFXImage(reader.read(0, reader.getDefaultReadParam()), null); + } finally { + reader.dispose(); + } + } + + public static Image loadImage(Path path) throws Exception { + try (InputStream input = Files.newInputStream(path)) { + if ("webp".equalsIgnoreCase(FileUtils.getExtension(path))) + return loadWebPImage(input); + else { + Image image = new Image(input); + if (image.isError()) + throw image.getException(); + return image; + } + } + } + + public static Image loadImage(URL url) throws Exception { + URLConnection connection = NetworkUtils.createConnection(url); + if (connection instanceof HttpURLConnection) { + connection = NetworkUtils.resolveConnection((HttpURLConnection) connection); + } + + try (InputStream input = connection.getInputStream()) { + String path = url.getPath(); + if (path != null && "webp".equalsIgnoreCase(StringUtils.substringAfterLast(path, '.'))) + return loadWebPImage(input); + else { + Image image = new Image(input); + if (image.isError()) + throw image.getException(); + return image; + } + } + } + /** * Suppress IllegalArgumentException since the url is supposed to be correct definitely. * @@ -947,6 +1019,15 @@ public static void onEscPressed(Node node, Runnable action) { }); } + public static void onClicked(Node node, Runnable action) { + node.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> { + if (e.getButton() == MouseButton.PRIMARY && e.getClickCount() == 1) { + action.run(); + e.consume(); + } + }); + } + public static void copyText(String text) { ClipboardContent content = new ClipboardContent(); content.putString(text); @@ -968,7 +1049,7 @@ public static List parseSegment(String segment, Consumer hyperlink Element r = doc.getDocumentElement(); NodeList children = r.getChildNodes(); - List texts = new ArrayList<>(); + List texts = new ArrayList<>(); for (int i = 0; i < children.getLength(); i++) { org.w3c.dom.Node node = children.item(i); @@ -976,8 +1057,8 @@ public static List parseSegment(String segment, Consumer hyperlink Element element = (Element) node; if ("a".equals(element.getTagName())) { String href = element.getAttribute("href"); - JFXHyperlink hyperlink = new JFXHyperlink(element.getTextContent()); - hyperlink.setOnAction(e -> { + Text text = new Text(element.getTextContent()); + onClicked(text, () -> { String link = href; try { link = new URI(href).toASCIIString(); @@ -985,7 +1066,10 @@ public static List parseSegment(String segment, Consumer hyperlink } hyperlinkAction.accept(link); }); - texts.add(hyperlink); + text.setCursor(Cursor.HAND); + text.setFill(Color.web("#0070E0")); + text.setUnderline(true); + texts.add(text); } else if ("b".equals(element.getTagName())) { Text text = new Text(element.getTextContent()); text.getStyleClass().add("bold"); @@ -1019,4 +1103,9 @@ public static String toWeb(Color color) { return String.format("#%02x%02x%02x", r, g, b); } + + public static FileChooser.ExtensionFilter getImageExtensionFilter() { + return new FileChooser.ExtensionFilter(i18n("extension.png"), + IMAGE_EXTENSIONS.stream().map(ext -> "*." + ext).toArray(String[]::new)); + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/GameCrashWindow.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/GameCrashWindow.java index a93e073e9a..d3d74d24b0 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/GameCrashWindow.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/GameCrashWindow.java @@ -65,7 +65,6 @@ import java.util.stream.Collectors; import static org.jackhuang.hmcl.setting.ConfigHolder.config; -import static org.jackhuang.hmcl.ui.FXUtils.runInFX; 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; @@ -387,13 +386,13 @@ private final class View extends VBox { gameDir.getStyleClass().setAll("two-line-item-second-large"); gameDir.setTitle(i18n("game.directory")); gameDir.setSubtitle(launchOptions.getGameDir().getAbsolutePath()); - runInFX(() -> FXUtils.installFastTooltip(gameDir, i18n("game.directory"))); + FXUtils.installFastTooltip(gameDir, i18n("game.directory")); TwoLineListItem javaDir = new TwoLineListItem(); javaDir.getStyleClass().setAll("two-line-item-second-large"); javaDir.setTitle(i18n("settings.game.java_directory")); javaDir.setSubtitle(launchOptions.getJava().getBinary().toAbsolutePath().toString()); - runInFX(() -> FXUtils.installFastTooltip(javaDir, i18n("settings.game.java_directory"))); + FXUtils.installFastTooltip(javaDir, i18n("settings.game.java_directory")); Label reasonTitle = new Label(i18n("game.crash.reason")); reasonTitle.getStyleClass().add("two-line-item-second-large-title"); @@ -417,14 +416,14 @@ private final class View extends VBox { HBox toolBar = new HBox(); { JFXButton exportGameCrashInfoButton = FXUtils.newRaisedButton(i18n("logwindow.export_game_crash_logs")); - exportGameCrashInfoButton.setOnMouseClicked(e -> exportGameCrashInfo()); + exportGameCrashInfoButton.setOnAction(e -> exportGameCrashInfo()); JFXButton logButton = FXUtils.newRaisedButton(i18n("logwindow.title")); - logButton.setOnMouseClicked(e -> showLogWindow()); + logButton.setOnAction(e -> showLogWindow()); JFXButton helpButton = FXUtils.newRaisedButton(i18n("help")); helpButton.setOnAction(e -> FXUtils.openLink("https://docs.hmcl.net/help.html")); - runInFX(() -> FXUtils.installFastTooltip(helpButton, i18n("logwindow.help"))); + FXUtils.installFastTooltip(helpButton, i18n("logwindow.help")); toolBar.setPadding(new Insets(8)); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/HTMLRenderer.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/HTMLRenderer.java new file mode 100644 index 0000000000..dccedf77f2 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/HTMLRenderer.java @@ -0,0 +1,266 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui 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 . + */ +package org.jackhuang.hmcl.ui; + +import javafx.scene.Cursor; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.text.Text; +import javafx.scene.text.TextFlow; +import org.jsoup.nodes.Node; +import org.jsoup.nodes.TextNode; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import static org.jackhuang.hmcl.util.logging.Logger.LOG; + +/** + * @author Glavo + */ +public final class HTMLRenderer { + private static URI resolveLink(Node linkNode) { + String href = linkNode.absUrl("href"); + if (href.isEmpty()) + return null; + + try { + return new URI(href); + } catch (Throwable e) { + return null; + } + } + + private final List children = new ArrayList<>(); + private final List stack = new ArrayList<>(); + + private boolean bold; + private boolean italic; + private boolean underline; + private boolean strike; + private boolean highlight; + private String headerLevel; + private Node hyperlink; + + private final Consumer onClickHyperlink; + + public HTMLRenderer(Consumer onClickHyperlink) { + this.onClickHyperlink = onClickHyperlink; + } + + private void updateStyle() { + bold = false; + italic = false; + underline = false; + strike = false; + highlight = false; + headerLevel = null; + hyperlink = null; + + for (Node node : stack) { + String nodeName = node.nodeName(); + switch (nodeName) { + case "b": + case "strong": + bold = true; + break; + case "i": + case "em": + italic = true; + break; + case "ins": + underline = true; + break; + case "del": + strike = true; + break; + case "mark": + highlight = true; + break; + case "a": + hyperlink = node; + break; + case "h1": + case "h2": + case "h3": + case "h4": + case "h5": + case "h6": + headerLevel = nodeName; + break; + } + } + } + + private void pushNode(Node node) { + stack.add(node); + updateStyle(); + } + + private void popNode() { + stack.remove(stack.size() - 1); + updateStyle(); + } + + private void applyStyle(Text text) { + if (hyperlink != null) { + URI target = resolveLink(hyperlink); + if (target != null) { + FXUtils.onClicked(text, () -> onClickHyperlink.accept(target)); + text.setCursor(Cursor.HAND); + } + text.getStyleClass().add("html-hyperlink"); + } + + if (hyperlink != null || underline) + text.setUnderline(true); + + if (strike) + text.setStrikethrough(true); + + if (bold || highlight) + text.getStyleClass().add("html-bold"); + + if (italic) + text.getStyleClass().add("html-italic"); + + if (headerLevel != null) + text.getStyleClass().add("html-" + headerLevel); + } + + private void appendText(String text) { + Text textNode = new Text(text); + applyStyle(textNode); + children.add(textNode); + } + + private void appendImage(Node node) { + String src = node.absUrl("src"); + URI imageUri = null; + try { + if (!src.isEmpty()) + imageUri = URI.create(src); + } catch (Exception ignored) { + } + + String alt = node.attr("alt"); + + if (imageUri != null) { + URI uri = URI.create(src); + + String widthAttr = node.attr("width"); + String heightAttr = node.attr("height"); + + double width = 0; + double height = 0; + + if (!widthAttr.isEmpty() && !heightAttr.isEmpty()) { + try { + width = Double.parseDouble(widthAttr); + height = Double.parseDouble(heightAttr); + } catch (NumberFormatException ignored) { + } + + if (width <= 0 || height <= 0) { + width = 0; + height = 0; + } + } + + Image image = FXUtils.newRemoteImage(uri.toString(), width, height, true, true, false); + if (image.isError()) { + LOG.warning("Failed to load image: " + uri, image.getException()); + } else { + ImageView imageView = new ImageView(image); + if (hyperlink != null) { + URI target = resolveLink(hyperlink); + if (target != null) { + FXUtils.onClicked(imageView, () -> onClickHyperlink.accept(target)); + imageView.setCursor(Cursor.HAND); + } + } + children.add(imageView); + return; + } + } + + if (!alt.isEmpty()) + appendText(alt); + } + + public void appendNode(Node node) { + if (node instanceof TextNode) { + appendText(((TextNode) node).text()); + } + + String name = node.nodeName(); + switch (name) { + case "img": + appendImage(node); + break; + case "li": + appendText("\n \u2022 "); + break; + case "dt": + appendText(" "); + break; + case "p": + case "h1": + case "h2": + case "h3": + case "h4": + case "h5": + case "h6": + case "tr": + if (!children.isEmpty()) + appendText("\n\n"); + break; + } + + if (node.childNodeSize() > 0) { + pushNode(node); + for (Node childNode : node.childNodes()) { + appendNode(childNode); + } + popNode(); + } + + switch (name) { + case "br": + case "dd": + case "p": + case "h1": + case "h2": + case "h3": + case "h4": + case "h5": + case "h6": + appendText("\n"); + break; + } + } + + public TextFlow render() { + TextFlow textFlow = new TextFlow(); + textFlow.getStyleClass().add("html"); + textFlow.getChildren().setAll(children); + return textFlow; + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java index d622fe9c0b..17224a8823 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java @@ -23,7 +23,6 @@ import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.css.PseudoClass; -import javafx.event.EventHandler; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Cursor; @@ -33,7 +32,7 @@ import javafx.scene.control.Skin; import javafx.scene.control.SkinBase; import javafx.scene.image.ImageView; -import javafx.scene.input.MouseEvent; +import javafx.scene.input.MouseButton; import javafx.scene.layout.*; import org.jackhuang.hmcl.download.LibraryAnalyzer; import org.jackhuang.hmcl.setting.Theme; @@ -60,8 +59,8 @@ public class InstallerItem extends Control { private final ObjectProperty versionProperty = new SimpleObjectProperty<>(this, "version", null); private final ObjectProperty resolvedStateProperty = new SimpleObjectProperty<>(this, "resolvedState", InstallableState.INSTANCE); - private final ObjectProperty> installActionProperty = new SimpleObjectProperty<>(this, "installAction"); - private final ObjectProperty> removeActionProperty = new SimpleObjectProperty<>(this, "removeAction"); + private final ObjectProperty onInstall = new SimpleObjectProperty<>(this, "onInstall"); + private final ObjectProperty onRemove = new SimpleObjectProperty<>(this, "onRemove"); public interface State { } @@ -170,12 +169,28 @@ public ObjectProperty resolvedStateProperty() { return resolvedStateProperty; } - public ObjectProperty> installActionProperty() { - return installActionProperty; + public ObjectProperty onInstallProperty() { + return onInstall; } - public ObjectProperty> removeActionProperty() { - return removeActionProperty; + public Runnable getOnInstall() { + return onInstall.get(); + } + + public void setOnInstall(Runnable onInstall) { + this.onInstall.set(onInstall); + } + + public ObjectProperty onRemoveProperty() { + return onRemove; + } + + public Runnable getOnRemove() { + return onRemove.get(); + } + + public void setOnRemove(Runnable onRemove) { + this.onRemove.set(onRemove); } @Override @@ -371,7 +386,11 @@ private static final class InstallerItemSkin extends SkinBase { removeButton.visibleProperty().bind(Bindings.createBooleanBinding(() -> control.resolvedStateProperty.get() instanceof InstalledState, control.resolvedStateProperty)); } removeButton.managedProperty().bind(removeButton.visibleProperty()); - removeButton.onMouseClickedProperty().bind(control.removeActionProperty); + removeButton.setOnAction(e -> { + Runnable onRemove = control.getOnRemove(); + if (onRemove != null) + onRemove.run(); + }); buttonsContainer.getChildren().add(removeButton); JFXButton installButton = new JFXButton(); @@ -383,7 +402,7 @@ private static final class InstallerItemSkin extends SkinBase { )); installButton.getStyleClass().add("toggle-icon4"); installButton.visibleProperty().bind(Bindings.createBooleanBinding(() -> { - if (control.installActionProperty.get() == null) { + if (control.getOnInstall() == null) { return false; } @@ -396,18 +415,27 @@ private static final class InstallerItemSkin extends SkinBase { } return false; - }, control.resolvedStateProperty, control.installActionProperty)); + }, control.resolvedStateProperty, control.onInstall)); installButton.managedProperty().bind(installButton.visibleProperty()); - installButton.onMouseClickedProperty().bind(control.installActionProperty); + installButton.setOnAction(e -> { + Runnable onInstall = control.getOnInstall(); + if (onInstall != null) + onInstall.run(); + }); buttonsContainer.getChildren().add(installButton); FXUtils.onChangeAndOperate(installButton.visibleProperty(), clickable -> { if (clickable) { - container.onMouseClickedProperty().bind(control.installActionProperty); + container.setOnMouseClicked(event -> { + Runnable onInstall = control.getOnInstall(); + if (onInstall != null && event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 1) { + onInstall.run(); + event.consume(); + } + }); pane.setCursor(Cursor.HAND); } else { - container.onMouseClickedProperty().unbind(); - container.onMouseClickedProperty().set(null); + container.setOnMouseClicked(null); pane.setCursor(Cursor.DEFAULT); } }); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPageSkin.java index ffbe90d466..89fe706518 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPageSkin.java @@ -70,7 +70,7 @@ public ListPageSkin(ListPage skinnable) { btnAdd.getStyleClass().add("jfx-button-raised-round"); btnAdd.setButtonType(JFXButton.ButtonType.RAISED); btnAdd.setGraphic(SVG.PLUS.createIcon(Theme.whiteFill(), -1, -1)); - btnAdd.setOnMouseClicked(e -> skinnable.add()); + btnAdd.setOnAction(e -> skinnable.add()); JFXButton btnRefresh = new JFXButton(); FXUtils.setLimitWidth(btnRefresh, 40); @@ -78,7 +78,7 @@ public ListPageSkin(ListPage skinnable) { btnRefresh.getStyleClass().add("jfx-button-raised-round"); btnRefresh.setButtonType(JFXButton.ButtonType.RAISED); btnRefresh.setGraphic(SVG.REFRESH.createIcon(Theme.whiteFill(), -1, -1)); - btnRefresh.setOnMouseClicked(e -> skinnable.refresh()); + btnRefresh.setOnAction(e -> skinnable.refresh()); vBox.getChildren().setAll(btnAdd); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/LogWindow.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/LogWindow.java index 7e28b6d7d9..bcefd16e04 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/LogWindow.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/LogWindow.java @@ -34,6 +34,7 @@ import javafx.scene.control.Label; import javafx.scene.control.*; import javafx.scene.input.KeyCode; +import javafx.scene.input.MouseButton; import javafx.scene.layout.*; import javafx.stage.Stage; import org.jackhuang.hmcl.game.GameDumpGenerator; @@ -322,6 +323,9 @@ private static final class LogWindowSkin extends SkinBase { setGraphic(null); setOnMouseClicked(event -> { + if (event.getButton() != MouseButton.PRIMARY) + return; + if (!event.isControlDown()) { for (ListCell logListCell : selected) { if (logListCell != this) { @@ -340,6 +344,8 @@ private static final class LogWindowSkin extends SkinBase { if (getItem() != null) { getItem().setSelected(true); } + + event.consume(); }); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java index 9231c4b37f..f0efac676a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java @@ -32,7 +32,7 @@ public enum SVG { CANCEL("M12 2C17.5 2 22 6.5 22 12S17.5 22 12 22 2 17.5 2 12 6.5 2 12 2M12 4C10.1 4 8.4 4.6 7.1 5.7L18.3 16.9C19.3 15.5 20 13.8 20 12C20 7.6 16.4 4 12 4M16.9 18.3L5.7 7.1C4.6 8.4 4 10.1 4 12C4 16.4 7.6 20 12 20C13.9 20 15.6 19.4 16.9 18.3Z"), CHAT("M20,2A2,2 0 0,1 22,4V16A2,2 0 0,1 20,18H6L2,22V4C2,2.89 2.9,2 4,2H20M4,4V17.17L5.17,16H20V4H4M6,7H18V9H6V7M6,11H15V13H6V11Z"), CLOSE("M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z"), - COPY("M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"), + COPY("M15 16H7V6h8m.4-2H6.6A1.6 1.6 90 005 5.6V16.4a1.6 1.6 90 001.6 1.6h8.8A1.6 1.6 90 0017 16.4V5.6A1.6 1.6 90 0015.4 4M13 1H3.6A1.6 1.6 90 002 2.6V13h2V3H13V1Z"), DOTS_VERTICAL("M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z"), DOTS_HORIZONTAL("M16,12A2,2 0 0,1 18,10A2,2 0 0,1 20,12A2,2 0 0,1 18,14A2,2 0 0,1 16,12M10,12A2,2 0 0,1 12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12M4,12A2,2 0 0,1 6,10A2,2 0 0,1 8,12A2,2 0 0,1 6,14A2,2 0 0,1 4,12Z"), DELETE("M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z"), @@ -59,6 +59,7 @@ public enum SVG { ALERT_OUTLINE("M12,2L1,21H23M12,6L19.53,19H4.47M11,10V14H13V10M11,16V18H13V16"), PLUS("M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z"), PLUS_CIRCLE_OUTLINE("M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M13,7H11V11H7V13H11V17H13V13H17V11H13V7Z"), + MINUS("M19 13H5V11H19V13Z"), IMPORT_ICON("M14,12L10,8V11H2V13H10V16M20,18V6C20,4.89 19.1,4 18,4H6A2,2 0 0,0 4,6V9H6V6H18V18H6V15H4V18A2,2 0 0,0 6,20H18A2,2 0 0,0 20,18Z"), EXPORT("M23,12L19,8V11H10V13H19V16M1,18V6C1,4.89 1.9,4 3,4H15A2,2 0 0,1 17,6V9H15V6H3V18H15V15H17V18A2,2 0 0,1 15,20H3A2,2 0 0,1 1,18Z"), OPEN_IN_NEW("M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z"), diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ToolbarListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ToolbarListPageSkin.java index 518ceb2f0e..ba4d42c814 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ToolbarListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ToolbarListPageSkin.java @@ -91,7 +91,7 @@ public static JFXButton createToolbarButton(String text, SVG svg, Runnable onCli ret.textFillProperty().bind(Theme.foregroundFillBinding()); ret.setGraphic(wrap(svg.createIcon(Theme.foregroundFillBinding(), -1, -1))); ret.setText(text); - ret.setOnMouseClicked(e -> onClick.run()); + ret.setOnAction(e -> onClick.run()); return ret; } @@ -100,7 +100,7 @@ public static JFXButton createToolbarButton2(String text, SVG svg, Runnable onCl ret.getStyleClass().add("jfx-tool-bar-button"); ret.setGraphic(wrap(svg.createIcon(Theme.blackFill(), -1, -1))); ret.setText(text); - ret.setOnMouseClicked(e -> onClick.run()); + ret.setOnAction(e -> onClick.run()); return ret; } @@ -110,7 +110,7 @@ public static JFXButton createDecoratorButton(String tooltip, SVG svg, Runnable ret.textFillProperty().bind(Theme.foregroundFillBinding()); ret.setGraphic(wrap(svg.createIcon(Theme.foregroundFillBinding(), -1, -1))); FXUtils.installFastTooltip(ret, tooltip); - ret.setOnMouseClicked(e -> onClick.run()); + ret.setOnAction(e -> onClick.run()); return ret; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/TooltipInstaller.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/TooltipInstaller.java new file mode 100644 index 0000000000..acc1530d12 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/TooltipInstaller.java @@ -0,0 +1,123 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui 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 . + */ +package org.jackhuang.hmcl.ui; + +import javafx.scene.Node; +import javafx.scene.control.Tooltip; +import javafx.util.Duration; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +import static org.jackhuang.hmcl.util.logging.Logger.LOG; + +/** + * @author Glavo + */ +public class TooltipInstaller { + public static final TooltipInstaller INSTALLER; + + static { + TooltipInstaller installer = null; + + try { + installer = new NewInstaller(); + } catch (Throwable e) { + try { + installer = new OldInstaller(); + } catch (Throwable e2) { + e2.addSuppressed(e); + LOG.warning("Failed to initialize TooltipInstaller", e2); + } + } + + INSTALLER = installer != null ? installer : new TooltipInstaller(); + } + + TooltipInstaller() { + } + + public void installTooltip(Node node, Duration showDelay, Duration showDuration, Duration hideDelay, Tooltip tooltip) { + Tooltip.install(node, tooltip); + } + + // For Java 8 + private static final class OldInstaller extends TooltipInstaller { + private static final Constructor createTooltipBehavior; + private static final Method installTooltipBehavior; + + static { + try { + Class behaviorClass = Class.forName("javafx.scene.control.Tooltip$TooltipBehavior"); + createTooltipBehavior = behaviorClass.getDeclaredConstructor(Duration.class, Duration.class, Duration.class, boolean.class); + createTooltipBehavior.setAccessible(true); + installTooltipBehavior = behaviorClass.getDeclaredMethod("install", Node.class, Tooltip.class); + installTooltipBehavior.setAccessible(true); + } catch (Exception e) { + throw new ExceptionInInitializerError(e); + } + } + + @Override + public void installTooltip(Node node, Duration showDelay, Duration showDuration, Duration hideDelay, Tooltip tooltip) { + try { + Object behavior = createTooltipBehavior.newInstance(showDelay, showDuration, hideDelay, false); + installTooltipBehavior.invoke(behavior, node, tooltip); + } catch (ReflectiveOperationException e) { + LOG.warning("Failed to set tooltip show delay", e); + Tooltip.install(node, tooltip); + } + } + } + + // For Java 9+ + private static final class NewInstaller extends TooltipInstaller { + private static final MethodHandle setTooltipShowDelay; + private static final MethodHandle setTooltipShowDuration; + private static final MethodHandle setTooltipHideDelay; + + static { + try { + MethodHandles.Lookup lookup = MethodHandles.publicLookup(); + MethodType methodType = MethodType.methodType(void.class, Duration.class); + + setTooltipShowDelay = lookup.findVirtual(Tooltip.class, "setShowDelay", methodType); + setTooltipShowDuration = lookup.findVirtual(Tooltip.class, "setShowDuration", methodType); + setTooltipHideDelay = lookup.findVirtual(Tooltip.class, "setHideDelay", methodType); + } catch (Exception e) { + throw new ExceptionInInitializerError(e); + } + } + + @Override + public void installTooltip(Node node, Duration showDelay, Duration showDuration, Duration hideDelay, Tooltip tooltip) { + try { + setTooltipShowDelay.invokeExact(tooltip, showDelay); + setTooltipShowDuration.invokeExact(tooltip, showDuration); + setTooltipHideDelay.invokeExact(tooltip, hideDelay); + } catch (Throwable e) { + LOG.warning("Failed to set tooltip show delay", e); + } + + Tooltip.install(node, tooltip); + } + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/UpgradeDialog.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/UpgradeDialog.java index e8602e394d..abc62af4c2 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/UpgradeDialog.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/UpgradeDialog.java @@ -19,58 +19,76 @@ import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXDialogLayout; -import javafx.concurrent.Worker; import javafx.scene.control.Label; -import javafx.scene.web.WebEngine; -import javafx.scene.web.WebView; -import org.jackhuang.hmcl.Metadata; +import javafx.scene.control.ProgressIndicator; +import javafx.scene.control.ScrollPane; +import org.jackhuang.hmcl.task.Schedulers; +import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.construct.DialogCloseEvent; +import org.jackhuang.hmcl.ui.construct.JFXHyperlink; import org.jackhuang.hmcl.upgrade.RemoteVersion; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Node; +import java.io.IOException; +import java.net.URL; import static org.jackhuang.hmcl.Metadata.CHANGELOG_URL; import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed; -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 UpgradeDialog extends JFXDialogLayout { +public final class UpgradeDialog extends JFXDialogLayout { public UpgradeDialog(RemoteVersion remoteVersion, Runnable updateRunnable) { - { - setHeading(new Label(i18n("update.changelog"))); - } + maxWidthProperty().bind(Controllers.getScene().widthProperty().multiply(0.7)); + maxHeightProperty().bind(Controllers.getScene().heightProperty().multiply(0.7)); + + setHeading(new Label(i18n("update.changelog"))); + setBody(new ProgressIndicator()); + + String url = CHANGELOG_URL + remoteVersion.getChannel().channelName + ".html"; + Task.supplyAsync(Schedulers.io(), () -> { + Document document = Jsoup.parse(new URL(url), 30 * 1000); + Node node = document.selectFirst("#nowchange"); + if (node == null) + throw new IOException("Cannot find #nowchange in document"); - { - String url = CHANGELOG_URL + remoteVersion.getChannel().channelName + ".html#nowchange"; - try { - WebView webView = new WebView(); - webView.getEngine().setUserDataDirectory(Metadata.HMCL_DIRECTORY.toFile()); - WebEngine engine = webView.getEngine(); - engine.load(url); - engine.getLoadWorker().stateProperty().addListener((observable, oldValue, newValue) -> { - if (newValue == Worker.State.FAILED) { - LOG.warning("Failed to load update log, trying to open it in browser"); - FXUtils.openLink(url); - setBody(); - } - }); - setBody(webView); - } catch (NoClassDefFoundError | UnsatisfiedLinkError e) { - LOG.warning("WebView is missing or initialization failed", e); + HTMLRenderer renderer = new HTMLRenderer(uri -> { + LOG.info("Open link: " + uri); + FXUtils.openLink(uri.toString()); + }); + + do { + renderer.appendNode(node); + node = node.nextSibling(); + } while (node != null); + + return renderer.render(); + }).whenComplete(Schedulers.javafx(), (result, exception) -> { + if (exception == null) { + ScrollPane scrollPane = new ScrollPane(result); + scrollPane.setFitToWidth(true); + setBody(scrollPane); + } else { + LOG.warning("Failed to load update log, trying to open it in browser"); FXUtils.openLink(url); + setBody(); } - } + }).start(); + + JFXHyperlink openInBrowser = new JFXHyperlink(i18n("web.view_in_browser")); + openInBrowser.setExternalLink(url); - { - JFXButton updateButton = new JFXButton(i18n("update.accept")); - updateButton.getStyleClass().add("dialog-accept"); - updateButton.setOnMouseClicked(e -> updateRunnable.run()); + JFXButton updateButton = new JFXButton(i18n("update.accept")); + updateButton.getStyleClass().add("dialog-accept"); + updateButton.setOnAction(e -> updateRunnable.run()); - JFXButton cancelButton = new JFXButton(i18n("button.cancel")); - cancelButton.getStyleClass().add("dialog-cancel"); - cancelButton.setOnMouseClicked(e -> fireEvent(new DialogCloseEvent())); + JFXButton cancelButton = new JFXButton(i18n("button.cancel")); + cancelButton.getStyleClass().add("dialog-cancel"); + cancelButton.setOnAction(e -> fireEvent(new DialogCloseEvent())); - setActions(updateButton, cancelButton); - onEscPressed(this, cancelButton::fire); - } + setActions(openInBrowser, updateButton, cancelButton); + onEscPressed(this, cancelButton::fire); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/WebPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/WebPage.java new file mode 100644 index 0000000000..96c23f51af --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/WebPage.java @@ -0,0 +1,75 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui 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 . + */ +package org.jackhuang.hmcl.ui; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.Background; +import javafx.scene.layout.BackgroundFill; +import javafx.scene.paint.Color; +import org.jackhuang.hmcl.task.Schedulers; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.ui.construct.SpinnerPane; +import org.jackhuang.hmcl.ui.decorator.DecoratorPage; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; + +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; + +/** + * @author Glavo + */ +public final class WebPage extends SpinnerPane implements DecoratorPage { + + private final ObjectProperty stateProperty; + + public WebPage(String title, String content) { + this.stateProperty = new SimpleObjectProperty<>(DecoratorPage.State.fromTitle(title)); + this.setBackground(new Background(new BackgroundFill(Color.WHITE, null, null))); + + Task.supplyAsync(() -> { + Document document = Jsoup.parseBodyFragment(content); + HTMLRenderer renderer = new HTMLRenderer(uri -> { + Controllers.confirm(i18n("web.open_in_browser", uri), i18n("message.confirm"), () -> { + FXUtils.openLink(uri.toString()); + }, null); + }); + renderer.appendNode(document); + return renderer.render(); + }).whenComplete(Schedulers.javafx(), ((result, exception) -> { + if (exception == null) { + ScrollPane scrollPane = new ScrollPane(); + scrollPane.setFitToWidth(true); + scrollPane.setContent(result); + setContent(scrollPane); + setFailedReason(null); + } else { + LOG.warning("Failed to load content", exception); + setFailedReason(i18n("web.failed")); + } + })).start(); + } + + @Override + public ReadOnlyObjectProperty stateProperty() { + return stateProperty; + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/WebStage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/WebStage.java deleted file mode 100644 index 1b57ca0aa3..0000000000 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/WebStage.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Hello Minecraft! Launcher - * Copyright (C) 2020 huangyuhui 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 . - */ -package org.jackhuang.hmcl.ui; - -import com.jfoenix.controls.JFXProgressBar; -import javafx.beans.binding.Bindings; -import javafx.scene.Scene; -import javafx.scene.layout.BorderPane; -import javafx.scene.layout.StackPane; -import javafx.scene.web.WebEngine; -import javafx.scene.web.WebView; -import javafx.stage.Stage; -import org.jackhuang.hmcl.Metadata; -import org.jackhuang.hmcl.setting.Theme; - -import static org.jackhuang.hmcl.setting.ConfigHolder.config; - -public class WebStage extends Stage { - protected final StackPane pane = new StackPane(); - protected final JFXProgressBar progressBar = new JFXProgressBar(); - protected final WebView webView = new WebView(); - protected final WebEngine webEngine = webView.getEngine(); - - public WebStage() { - this(800, 480); - } - - public WebStage(int width, int height) { - setScene(new Scene(pane, width, height)); - getScene().getStylesheets().addAll(Theme.getTheme().getStylesheets(config().getLauncherFontFamily())); - FXUtils.setIcon(this); - webView.getEngine().setUserDataDirectory(Metadata.HMCL_DIRECTORY.toFile()); - webView.setContextMenuEnabled(false); - progressBar.progressProperty().bind(webView.getEngine().getLoadWorker().progressProperty()); - - progressBar.visibleProperty().bind(Bindings.createBooleanBinding(() -> { - switch (webView.getEngine().getLoadWorker().getState()) { - case SUCCEEDED: - case FAILED: - case CANCELLED: - return false; - default: - return true; - } - }, webEngine.getLoadWorker().stateProperty())); - - BorderPane borderPane = new BorderPane(); - borderPane.setPickOnBounds(false); - borderPane.setTop(progressBar); - progressBar.prefWidthProperty().bind(borderPane.widthProperty()); - pane.getChildren().setAll(webView, borderPane); - } - - public WebView getWebView() { - return webView; - } -} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItem.java index 22a0b39f9e..6fc255dd9b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItem.java @@ -32,17 +32,14 @@ import org.jackhuang.hmcl.auth.CredentialExpiredException; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer; -import org.jackhuang.hmcl.auth.microsoft.MicrosoftAccount; import org.jackhuang.hmcl.auth.offline.OfflineAccount; import org.jackhuang.hmcl.auth.yggdrasil.CompleteGameProfile; import org.jackhuang.hmcl.auth.yggdrasil.TextureType; -import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount; import org.jackhuang.hmcl.setting.Accounts; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.DialogController; -import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType; import org.jackhuang.hmcl.util.skin.InvalidSkinException; import org.jackhuang.hmcl.util.skin.NormalizedSkin; @@ -128,7 +125,7 @@ public ObservableBooleanValue canUploadSkin() { .orElse(emptySet()); return uploadableTextures.contains(TextureType.SKIN); }, profile); - } else if (account instanceof OfflineAccount || account instanceof MicrosoftAccount) { + } else if (account instanceof OfflineAccount || account.canUploadSkin()) { return createBooleanBinding(() -> true); } else { return createBooleanBinding(() -> false); @@ -144,11 +141,7 @@ public Task uploadSkin() { Controllers.dialog(new OfflineAccountSkinPane((OfflineAccount) account)); return null; } - if (account instanceof MicrosoftAccount) { - FXUtils.openLink("https://www.minecraft.net/msaprofile/mygames/editskin"); - return null; - } - if (!(account instanceof YggdrasilAccount)) { + if (!account.canUploadSkin()) { return null; } @@ -174,7 +167,7 @@ public Task uploadSkin() { NormalizedSkin skin = new NormalizedSkin(skinImg); String model = skin.isSlim() ? "slim" : ""; LOG.info("Uploading skin [" + selectedFile + "], model [" + model + "]"); - ((YggdrasilAccount) account).uploadSkin(model, selectedFile.toPath()); + account.uploadSkin(skin.isSlim(), selectedFile.toPath()); }) .thenComposeAsync(refreshAsync()) .whenComplete(Schedulers.javafx(), e -> { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java index bb99d8af4d..ea6b28dbfa 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java @@ -42,10 +42,9 @@ import org.jackhuang.hmcl.ui.construct.SpinnerPane; import org.jackhuang.hmcl.util.javafx.BindingMapping; -import static org.jackhuang.hmcl.ui.FXUtils.runInFX; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; -public class AccountListItemSkin extends SkinBase { +public final class AccountListItemSkin extends SkinBase { public AccountListItemSkin(AccountListItem skinnable) { super(skinnable); @@ -94,7 +93,7 @@ public void fire() { JFXButton btnMove = new JFXButton(); SpinnerPane spinnerMove = new SpinnerPane(); spinnerMove.getStyleClass().add("small-spinner-pane"); - btnMove.setOnMouseClicked(e -> { + btnMove.setOnAction(e -> { Account account = skinnable.getAccount(); Accounts.getAccounts().remove(account); if (account.isPortable()) { @@ -118,10 +117,10 @@ public void fire() { btnMove.getStyleClass().add("toggle-icon4"); if (skinnable.getAccount().isPortable()) { btnMove.setGraphic(SVG.EARTH.createIcon(Theme.blackFill(), -1, -1)); - runInFX(() -> FXUtils.installFastTooltip(btnMove, i18n("account.move_to_global"))); + FXUtils.installFastTooltip(btnMove, i18n("account.move_to_global")); } else { btnMove.setGraphic(SVG.EXPORT.createIcon(Theme.blackFill(), -1, -1)); - runInFX(() -> FXUtils.installFastTooltip(btnMove, i18n("account.move_to_portable"))); + FXUtils.installFastTooltip(btnMove, i18n("account.move_to_portable")); } spinnerMove.setContent(btnMove); right.getChildren().add(spinnerMove); @@ -129,7 +128,7 @@ public void fire() { JFXButton btnRefresh = new JFXButton(); SpinnerPane spinnerRefresh = new SpinnerPane(); spinnerRefresh.getStyleClass().setAll("small-spinner-pane"); - btnRefresh.setOnMouseClicked(e -> { + btnRefresh.setOnAction(e -> { spinnerRefresh.showSpinner(); skinnable.refreshAsync() .whenComplete(Schedulers.javafx(), ex -> { @@ -143,13 +142,13 @@ public void fire() { }); btnRefresh.getStyleClass().add("toggle-icon4"); btnRefresh.setGraphic(SVG.REFRESH.createIcon(Theme.blackFill(), -1, -1)); - runInFX(() -> FXUtils.installFastTooltip(btnRefresh, i18n("button.refresh"))); + FXUtils.installFastTooltip(btnRefresh, i18n("button.refresh")); spinnerRefresh.setContent(btnRefresh); right.getChildren().add(spinnerRefresh); JFXButton btnUpload = new JFXButton(); SpinnerPane spinnerUpload = new SpinnerPane(); - btnUpload.setOnMouseClicked(e -> { + btnUpload.setOnAction(e -> { Task uploadTask = skinnable.uploadSkin(); if (uploadTask != null) { spinnerUpload.showSpinner(); @@ -160,7 +159,7 @@ public void fire() { }); btnUpload.getStyleClass().add("toggle-icon4"); btnUpload.setGraphic(SVG.HANGER.createIcon(Theme.blackFill(), -1, -1)); - runInFX(() -> FXUtils.installFastTooltip(btnUpload, i18n("account.skin.upload"))); + FXUtils.installFastTooltip(btnUpload, i18n("account.skin.upload")); spinnerUpload.managedProperty().bind(spinnerUpload.visibleProperty()); spinnerUpload.visibleProperty().bind(skinnable.canUploadSkin()); spinnerUpload.setContent(btnUpload); @@ -170,18 +169,19 @@ public void fire() { JFXButton btnCopyUUID = new JFXButton(); SpinnerPane spinnerCopyUUID = new SpinnerPane(); spinnerCopyUUID.getStyleClass().add("small-spinner-pane"); - btnCopyUUID.setOnMouseClicked(e -> FXUtils.copyText(skinnable.getAccount().getUUID().toString())); + btnUpload.getStyleClass().add("toggle-icon4"); + btnCopyUUID.setOnAction(e -> FXUtils.copyText(skinnable.getAccount().getUUID().toString())); btnCopyUUID.setGraphic(SVG.COPY.createIcon(Theme.blackFill(), -1, -1)); - runInFX(() -> FXUtils.installFastTooltip(btnCopyUUID, i18n("account.copy_uuid"))); + FXUtils.installFastTooltip(btnCopyUUID, i18n("account.copy_uuid")); spinnerCopyUUID.setContent(btnCopyUUID); right.getChildren().add(spinnerCopyUUID); JFXButton btnRemove = new JFXButton(); - btnRemove.setOnMouseClicked(e -> Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), skinnable::remove, null)); + btnRemove.setOnAction(e -> Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), skinnable::remove, null)); btnRemove.getStyleClass().add("toggle-icon4"); BorderPane.setAlignment(btnRemove, Pos.CENTER); - btnRemove.setGraphic(SVG.DELETE.createIcon(Theme.blackFill(), -1, -1)); - runInFX(() -> FXUtils.installFastTooltip(btnRemove, i18n("button.delete"))); + btnRemove.setGraphic(SVG.DELETE_OUTLINE.createIcon(Theme.blackFill(), -1, -1)); + FXUtils.installFastTooltip(btnRemove, i18n("button.delete")); right.getChildren().add(btnRemove); root.setRight(right); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java index 253ca699b3..ae970c8643 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java @@ -44,6 +44,7 @@ import org.jackhuang.hmcl.util.javafx.MappedObservableList; import java.net.URI; +import java.util.Locale; import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap; import static org.jackhuang.hmcl.util.logging.Logger.LOG; @@ -95,7 +96,7 @@ public AccountListPageSkin(AccountListPage skinnable) { VBox boxMethods = new VBox(); { boxMethods.getStyleClass().add("advanced-list-box-content"); - boxMethods.getChildren().add(new ClassTitle(i18n("account.create"))); + boxMethods.getChildren().add(new ClassTitle(i18n("account.create").toUpperCase(Locale.ROOT))); FXUtils.setLimitWidth(boxMethods, 200); AdvancedListItem offlineItem = new AdvancedListItem(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AddAuthlibInjectorServerPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AddAuthlibInjectorServerPane.java index 914046649d..c19c80131c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AddAuthlibInjectorServerPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AddAuthlibInjectorServerPane.java @@ -147,7 +147,7 @@ public AddAuthlibInjectorServerPane() { confirmServerPane.setActions(prevButton, cancelButton, finishButton); } - this.setContent(addServerPane, ContainerAnimations.NONE.getAnimationProducer()); + this.setContent(addServerPane, ContainerAnimations.NONE); lblCreationWarning.maxWidthProperty().bind(((FlowPane) lblCreationWarning.getParent()).widthProperty()); btnAddNext.disableProperty().bind(txtServerUrl.textProperty().isEmpty()); @@ -198,7 +198,7 @@ private void onAddNext() { lblServerWarning.setVisible("http".equals(NetworkUtils.toURL(serverBeingAdded.getUrl()).getProtocol())); - this.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT.getAnimationProducer()); + this.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT); } else { LOG.warning("Failed to resolve auth server: " + url, exception); lblCreationWarning.setText(resolveFetchExceptionMessage(exception)); @@ -208,7 +208,7 @@ private void onAddNext() { } private void onAddPrev() { - this.setContent(addServerPane, ContainerAnimations.SWIPE_RIGHT.getAnimationProducer()); + this.setContent(addServerPane, ContainerAnimations.SWIPE_RIGHT); } private void onAddFinish() { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java index 9a17ba55cc..a5d8bee8b9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java @@ -65,6 +65,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.UUID; import java.util.concurrent.CountDownLatch; @@ -296,7 +297,7 @@ private void initDetailsPane() { hintPane.setSegment(i18n("account.methods.microsoft.hint")); } }); - hintPane.setOnMouseClicked(e -> { + FXUtils.onClicked(hintPane, () -> { if (deviceCode.get() != null) { FXUtils.copyText(deviceCode.get().getUserCode()); } @@ -316,7 +317,7 @@ private void initDetailsPane() { JFXHyperlink deauthorizeLink = new JFXHyperlink(i18n("account.methods.microsoft.deauthorize")); deauthorizeLink.setExternalLink("https://account.live.com/consent/Edit?client_id=000000004C794E0A"); JFXHyperlink forgotpasswordLink = new JFXHyperlink(i18n("account.methods.forgot_password")); - forgotpasswordLink.setExternalLink("https://www.minecraft.net/password/forgot"); + forgotpasswordLink.setExternalLink("https://account.live.com/ResetPassword.aspx"); JFXHyperlink createProfileLink = new JFXHyperlink(i18n("account.methods.microsoft.makegameidsettings")); createProfileLink.setExternalLink("https://www.minecraft.net/msaprofile/mygames/editprofile"); box.getChildren().setAll(profileLink, birthLink, purchaseLink, deauthorizeLink, forgotpasswordLink, createProfileLink); @@ -509,7 +510,7 @@ public AccountDetailsInputPane(AccountFactory factory, Runnable onAction) { if (factory instanceof OfflineAccountFactory) { txtUsername.setPromptText(i18n("account.methods.offline.name.special_characters")); - runInFX(() -> FXUtils.installFastTooltip(txtUsername, i18n("account.methods.offline.name.special_characters"))); + FXUtils.installFastTooltip(txtUsername, i18n("account.methods.offline.name.special_characters")); JFXHyperlink purchaseLink = new JFXHyperlink(i18n("account.methods.microsoft.purchase")); purchaseLink.setExternalLink(YggdrasilService.PURCHASE_URL); @@ -637,7 +638,7 @@ public DialogCharacterSelector() { StackPane.setAlignment(cancel, Pos.BOTTOM_RIGHT); cancel.setOnAction(e -> latch.countDown()); - listBox.startCategory(i18n("account.choose")); + listBox.startCategory(i18n("account.choose").toUpperCase(Locale.ROOT)); setCenter(listBox); @@ -657,7 +658,7 @@ public GameProfile select(YggdrasilService service, List profiles) TexturesLoader.bindAvatar(portraitCanvas, service, profile.getId()); IconedItem accountItem = new IconedItem(portraitCanvas, profile.getName()); - accountItem.setOnMouseClicked(e -> { + FXUtils.onClicked(accountItem, () -> { selectedProfile = profile; latch.countDown(); }); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/OAuthAccountLoginDialog.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/OAuthAccountLoginDialog.java index bf9f98bce4..36780c3d47 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/OAuthAccountLoginDialog.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/OAuthAccountLoginDialog.java @@ -58,7 +58,7 @@ public OAuthAccountLoginDialog(OAuthAccount account, Consumer success, ); } }); - hintPane.setOnMouseClicked(e -> { + FXUtils.onClicked(hintPane, () -> { if (deviceCode.get() != null) { FXUtils.copyText(deviceCode.get().getUserCode()); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/OfflineAccountSkinPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/OfflineAccountSkinPane.java index c143c60b99..73e940d137 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/OfflineAccountSkinPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/OfflineAccountSkinPane.java @@ -28,9 +28,9 @@ import javafx.scene.input.DragEvent; import javafx.scene.input.TransferMode; import javafx.scene.layout.*; -import moe.mickey.minecraft.skin.fx.SkinCanvas; -import moe.mickey.minecraft.skin.fx.animation.SkinAniRunning; -import moe.mickey.minecraft.skin.fx.animation.SkinAniWavingArms; +import org.jackhuang.hmcl.ui.skin.SkinCanvas; +import org.jackhuang.hmcl.ui.skin.animation.SkinAniRunning; +import org.jackhuang.hmcl.ui.skin.animation.SkinAniWavingArms; import org.jackhuang.hmcl.auth.offline.OfflineAccount; import org.jackhuang.hmcl.auth.offline.Skin; import org.jackhuang.hmcl.auth.yggdrasil.TextureModel; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/ContainerAnimations.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/ContainerAnimations.java index 20bca631dd..281ac618db 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/ContainerAnimations.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/ContainerAnimations.java @@ -1,6 +1,6 @@ /* * Hello Minecraft! Launcher - * Copyright (C) 2020 huangyuhui and contributors + * Copyright (C) 2020 huangyuhui 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 @@ -27,165 +27,225 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.function.Consumer; -import java.util.function.Function; -public enum ContainerAnimations { - NONE(c -> { - c.getPreviousNode().setTranslateX(0); - c.getPreviousNode().setTranslateY(0); - c.getPreviousNode().setScaleX(1); - c.getPreviousNode().setScaleY(1); - c.getPreviousNode().setOpacity(1); - c.getCurrentNode().setTranslateX(0); - c.getCurrentNode().setTranslateY(0); - c.getCurrentNode().setScaleX(1); - c.getCurrentNode().setScaleY(1); - c.getCurrentNode().setOpacity(1); - }, c -> Collections.emptyList()), +public enum ContainerAnimations implements AnimationProducer { + NONE { + @Override + public void init(AnimationHandler c) { + c.getPreviousNode().setTranslateX(0); + c.getPreviousNode().setTranslateY(0); + c.getPreviousNode().setScaleX(1); + c.getPreviousNode().setScaleY(1); + c.getPreviousNode().setOpacity(1); + c.getCurrentNode().setTranslateX(0); + c.getCurrentNode().setTranslateY(0); + c.getCurrentNode().setScaleX(1); + c.getCurrentNode().setScaleY(1); + c.getCurrentNode().setOpacity(1); + } + + @Override + public List animate(AnimationHandler c) { + return Collections.emptyList(); + } + }, /** * A fade between the old and new view */ - FADE(c -> { - c.getPreviousNode().setTranslateX(0); - c.getPreviousNode().setTranslateY(0); - c.getPreviousNode().setScaleX(1); - c.getPreviousNode().setScaleY(1); - c.getPreviousNode().setOpacity(1); - c.getCurrentNode().setTranslateX(0); - c.getCurrentNode().setTranslateY(0); - c.getCurrentNode().setScaleX(1); - c.getCurrentNode().setScaleY(1); - c.getCurrentNode().setOpacity(0); - }, c -> - Arrays.asList(new KeyFrame(Duration.ZERO, + FADE { + @Override + public void init(AnimationHandler c) { + c.getPreviousNode().setTranslateX(0); + c.getPreviousNode().setTranslateY(0); + c.getPreviousNode().setScaleX(1); + c.getPreviousNode().setScaleY(1); + c.getPreviousNode().setOpacity(1); + c.getCurrentNode().setTranslateX(0); + c.getCurrentNode().setTranslateY(0); + c.getCurrentNode().setScaleX(1); + c.getCurrentNode().setScaleY(1); + c.getCurrentNode().setOpacity(0); + } + + @Override + public List animate(AnimationHandler c) { + return Arrays.asList(new KeyFrame(Duration.ZERO, new KeyValue(c.getPreviousNode().opacityProperty(), 1, Interpolator.EASE_BOTH), new KeyValue(c.getCurrentNode().opacityProperty(), 0, Interpolator.EASE_BOTH)), new KeyFrame(c.getDuration(), new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH), - new KeyValue(c.getCurrentNode().opacityProperty(), 1, Interpolator.EASE_BOTH)))), + new KeyValue(c.getCurrentNode().opacityProperty(), 1, Interpolator.EASE_BOTH))); + } + }, /** * A fade between the old and new view */ - FADE_IN(c -> { - c.getCurrentNode().setTranslateX(0); - c.getCurrentNode().setTranslateY(0); - c.getCurrentNode().setScaleX(1); - c.getCurrentNode().setScaleY(1); - c.getCurrentNode().setOpacity(0); - }, c -> - Arrays.asList(new KeyFrame(Duration.ZERO, + FADE_IN { + @Override + public void init(AnimationHandler c) { + c.getCurrentNode().setTranslateX(0); + c.getCurrentNode().setTranslateY(0); + c.getCurrentNode().setScaleX(1); + c.getCurrentNode().setScaleY(1); + c.getCurrentNode().setOpacity(0); + } + + @Override + public List animate(AnimationHandler c) { + return Arrays.asList(new KeyFrame(Duration.ZERO, new KeyValue(c.getCurrentNode().opacityProperty(), 0, FXUtils.SINE)), new KeyFrame(c.getDuration(), - new KeyValue(c.getCurrentNode().opacityProperty(), 1, FXUtils.SINE)))), + new KeyValue(c.getCurrentNode().opacityProperty(), 1, FXUtils.SINE))); + } + }, /** * A fade between the old and new view */ - FADE_OUT(c -> { - c.getCurrentNode().setTranslateX(0); - c.getCurrentNode().setTranslateY(0); - c.getCurrentNode().setScaleX(1); - c.getCurrentNode().setScaleY(1); - c.getCurrentNode().setOpacity(1); - }, c -> - Arrays.asList(new KeyFrame(Duration.ZERO, + FADE_OUT { + @Override + public void init(AnimationHandler c) { + c.getCurrentNode().setTranslateX(0); + c.getCurrentNode().setTranslateY(0); + c.getCurrentNode().setScaleX(1); + c.getCurrentNode().setScaleY(1); + c.getCurrentNode().setOpacity(1); + } + + @Override + public List animate(AnimationHandler c) { + return Arrays.asList(new KeyFrame(Duration.ZERO, new KeyValue(c.getCurrentNode().opacityProperty(), 1, FXUtils.SINE)), new KeyFrame(c.getDuration(), - new KeyValue(c.getCurrentNode().opacityProperty(), 0, FXUtils.SINE)))), + new KeyValue(c.getCurrentNode().opacityProperty(), 0, FXUtils.SINE))); + } + }, /** * A zoom effect */ - ZOOM_IN(c -> { - c.getPreviousNode().setTranslateX(0); - c.getPreviousNode().setTranslateY(0); - c.getPreviousNode().setScaleX(1); - c.getPreviousNode().setScaleY(1); - c.getPreviousNode().setOpacity(1); - c.getCurrentNode().setTranslateX(0); - c.getCurrentNode().setTranslateY(0); - }, c -> - Arrays.asList(new KeyFrame(Duration.ZERO, + ZOOM_IN { + @Override + public void init(AnimationHandler c) { + c.getPreviousNode().setTranslateX(0); + c.getPreviousNode().setTranslateY(0); + c.getPreviousNode().setScaleX(1); + c.getPreviousNode().setScaleY(1); + c.getPreviousNode().setOpacity(1); + c.getCurrentNode().setTranslateX(0); + c.getCurrentNode().setTranslateY(0); + } + + @Override + public List animate(AnimationHandler c) { + return Arrays.asList(new KeyFrame(Duration.ZERO, new KeyValue(c.getPreviousNode().scaleXProperty(), 1, Interpolator.EASE_BOTH), new KeyValue(c.getPreviousNode().scaleYProperty(), 1, Interpolator.EASE_BOTH), new KeyValue(c.getPreviousNode().opacityProperty(), 1, Interpolator.EASE_BOTH)), new KeyFrame(c.getDuration(), new KeyValue(c.getPreviousNode().scaleXProperty(), 4, Interpolator.EASE_BOTH), new KeyValue(c.getPreviousNode().scaleYProperty(), 4, Interpolator.EASE_BOTH), - new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH)))), + new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH))); + } + }, /** * A zoom effect */ - ZOOM_OUT(c -> { - c.getPreviousNode().setTranslateX(0); - c.getPreviousNode().setTranslateY(0); - c.getPreviousNode().setScaleX(1); - c.getPreviousNode().setScaleY(1); - c.getPreviousNode().setOpacity(1); - c.getCurrentNode().setTranslateX(0); - c.getCurrentNode().setTranslateY(0); - }, c -> - (Arrays.asList(new KeyFrame(Duration.ZERO, + ZOOM_OUT { + @Override + public void init(AnimationHandler c) { + c.getPreviousNode().setTranslateX(0); + c.getPreviousNode().setTranslateY(0); + c.getPreviousNode().setScaleX(1); + c.getPreviousNode().setScaleY(1); + c.getPreviousNode().setOpacity(1); + c.getCurrentNode().setTranslateX(0); + c.getCurrentNode().setTranslateY(0); + } + + @Override + public List animate(AnimationHandler c) { + return Arrays.asList(new KeyFrame(Duration.ZERO, new KeyValue(c.getPreviousNode().scaleXProperty(), 1, Interpolator.EASE_BOTH), new KeyValue(c.getPreviousNode().scaleYProperty(), 1, Interpolator.EASE_BOTH), new KeyValue(c.getPreviousNode().opacityProperty(), 1, Interpolator.EASE_BOTH)), new KeyFrame(c.getDuration(), new KeyValue(c.getPreviousNode().scaleXProperty(), 0, Interpolator.EASE_BOTH), new KeyValue(c.getPreviousNode().scaleYProperty(), 0, Interpolator.EASE_BOTH), - new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH))))), + new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH))); + } + }, /** * A swipe effect */ - SWIPE_LEFT(c -> { - c.getPreviousNode().setScaleX(1); - c.getPreviousNode().setScaleY(1); - c.getPreviousNode().setOpacity(0); - c.getPreviousNode().setTranslateX(0); - c.getCurrentNode().setScaleX(1); - c.getCurrentNode().setScaleY(1); - c.getCurrentNode().setOpacity(1); - c.getCurrentNode().setTranslateX(c.getCurrentRoot().getWidth()); - }, c -> - Arrays.asList(new KeyFrame(Duration.ZERO, + SWIPE_LEFT { + @Override + public void init(AnimationHandler c) { + c.getPreviousNode().setScaleX(1); + c.getPreviousNode().setScaleY(1); + c.getPreviousNode().setOpacity(0); + c.getPreviousNode().setTranslateX(0); + c.getCurrentNode().setScaleX(1); + c.getCurrentNode().setScaleY(1); + c.getCurrentNode().setOpacity(1); + c.getCurrentNode().setTranslateX(c.getCurrentRoot().getWidth()); + } + + @Override + public List animate(AnimationHandler c) { + return Arrays.asList(new KeyFrame(Duration.ZERO, new KeyValue(c.getCurrentNode().translateXProperty(), c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH), new KeyValue(c.getPreviousNode().translateXProperty(), 0, Interpolator.EASE_BOTH)), new KeyFrame(c.getDuration(), new KeyValue(c.getCurrentNode().translateXProperty(), 0, Interpolator.EASE_BOTH), - new KeyValue(c.getPreviousNode().translateXProperty(), -c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH)))), + new KeyValue(c.getPreviousNode().translateXProperty(), -c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH))); + } + }, /** * A swipe effect */ - SWIPE_RIGHT(c -> { - c.getPreviousNode().setScaleX(1); - c.getPreviousNode().setScaleY(1); - c.getPreviousNode().setOpacity(0); - c.getPreviousNode().setTranslateX(0); - c.getCurrentNode().setScaleX(1); - c.getCurrentNode().setScaleY(1); - c.getCurrentNode().setOpacity(1); - c.getCurrentNode().setTranslateX(-c.getCurrentRoot().getWidth()); - }, c -> - Arrays.asList(new KeyFrame(Duration.ZERO, + SWIPE_RIGHT { + @Override + public void init(AnimationHandler c) { + c.getPreviousNode().setScaleX(1); + c.getPreviousNode().setScaleY(1); + c.getPreviousNode().setOpacity(0); + c.getPreviousNode().setTranslateX(0); + c.getCurrentNode().setScaleX(1); + c.getCurrentNode().setScaleY(1); + c.getCurrentNode().setOpacity(1); + c.getCurrentNode().setTranslateX(-c.getCurrentRoot().getWidth()); + } + + @Override + public List animate(AnimationHandler c) { + return Arrays.asList(new KeyFrame(Duration.ZERO, new KeyValue(c.getCurrentNode().translateXProperty(), -c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH), new KeyValue(c.getPreviousNode().translateXProperty(), 0, Interpolator.EASE_BOTH)), new KeyFrame(c.getDuration(), new KeyValue(c.getCurrentNode().translateXProperty(), 0, Interpolator.EASE_BOTH), - new KeyValue(c.getPreviousNode().translateXProperty(), c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH)))), + new KeyValue(c.getPreviousNode().translateXProperty(), c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH))); + } + }, + + SWIPE_LEFT_FADE_SHORT { + @Override + public void init(AnimationHandler c) { + c.getPreviousNode().setScaleX(1); + c.getPreviousNode().setScaleY(1); + c.getPreviousNode().setOpacity(0); + c.getPreviousNode().setTranslateX(0); + c.getCurrentNode().setScaleX(1); + c.getCurrentNode().setScaleY(1); + c.getCurrentNode().setOpacity(1); + c.getCurrentNode().setTranslateX(c.getCurrentRoot().getWidth()); + } - SWIPE_LEFT_FADE_SHORT(c -> { - c.getPreviousNode().setScaleX(1); - c.getPreviousNode().setScaleY(1); - c.getPreviousNode().setOpacity(0); - c.getPreviousNode().setTranslateX(0); - c.getCurrentNode().setScaleX(1); - c.getCurrentNode().setScaleY(1); - c.getCurrentNode().setOpacity(1); - c.getCurrentNode().setTranslateX(c.getCurrentRoot().getWidth()); - }, c -> - Arrays.asList(new KeyFrame(Duration.ZERO, + @Override + public List animate(AnimationHandler c) { + return Arrays.asList(new KeyFrame(Duration.ZERO, new KeyValue(c.getCurrentNode().translateXProperty(), 50, Interpolator.EASE_BOTH), new KeyValue(c.getPreviousNode().translateXProperty(), 0, Interpolator.EASE_BOTH), new KeyValue(c.getCurrentNode().opacityProperty(), 0, Interpolator.EASE_BOTH), @@ -194,19 +254,26 @@ public enum ContainerAnimations { new KeyValue(c.getCurrentNode().translateXProperty(), 0, Interpolator.EASE_BOTH), new KeyValue(c.getPreviousNode().translateXProperty(), -50, Interpolator.EASE_BOTH), new KeyValue(c.getCurrentNode().opacityProperty(), 1, Interpolator.EASE_BOTH), - new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH)))), + new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH))); + } + }, - SWIPE_RIGHT_FADE_SHORT(c -> { - c.getPreviousNode().setScaleX(1); - c.getPreviousNode().setScaleY(1); - c.getPreviousNode().setOpacity(0); - c.getPreviousNode().setTranslateX(0); - c.getCurrentNode().setScaleX(1); - c.getCurrentNode().setScaleY(1); - c.getCurrentNode().setOpacity(1); - c.getCurrentNode().setTranslateX(c.getCurrentRoot().getWidth()); - }, c -> - Arrays.asList(new KeyFrame(Duration.ZERO, + SWIPE_RIGHT_FADE_SHORT { + @Override + public void init(AnimationHandler c) { + c.getPreviousNode().setScaleX(1); + c.getPreviousNode().setScaleY(1); + c.getPreviousNode().setOpacity(0); + c.getPreviousNode().setTranslateX(0); + c.getCurrentNode().setScaleX(1); + c.getCurrentNode().setScaleY(1); + c.getCurrentNode().setOpacity(1); + c.getCurrentNode().setTranslateX(c.getCurrentRoot().getWidth()); + } + + @Override + public List animate(AnimationHandler c) { + return Arrays.asList(new KeyFrame(Duration.ZERO, new KeyValue(c.getCurrentNode().translateXProperty(), -50, Interpolator.EASE_BOTH), new KeyValue(c.getPreviousNode().translateXProperty(), 0, Interpolator.EASE_BOTH), new KeyValue(c.getCurrentNode().opacityProperty(), 0, Interpolator.EASE_BOTH), @@ -215,38 +282,12 @@ public enum ContainerAnimations { new KeyValue(c.getCurrentNode().translateXProperty(), 0, Interpolator.EASE_BOTH), new KeyValue(c.getPreviousNode().translateXProperty(), 50, Interpolator.EASE_BOTH), new KeyValue(c.getCurrentNode().opacityProperty(), 1, Interpolator.EASE_BOTH), - new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH)))); + new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH))); + } + }; - private final AnimationProducer animationProducer; private ContainerAnimations opposite; - ContainerAnimations(Consumer init, Function> animationProducer) { - this.animationProducer = new AnimationProducer() { - @Override - public void init(AnimationHandler handler) { - init.accept(handler); - } - - @Override - public List animate(AnimationHandler handler) { - return animationProducer.apply(handler); - } - - @Override - public @Nullable AnimationProducer opposite() { - return opposite != null ? opposite.getAnimationProducer() : null; - } - }; - } - - public AnimationProducer getAnimationProducer() { - return animationProducer; - } - - public ContainerAnimations getOpposite() { - return opposite; - } - static { NONE.opposite = NONE; FADE.opposite = FADE; @@ -257,4 +298,15 @@ public ContainerAnimations getOpposite() { ZOOM_IN.opposite = ZOOM_OUT; ZOOM_OUT.opposite = ZOOM_IN; } + + @Override + public abstract void init(AnimationHandler handler); + + @Override + public abstract List animate(AnimationHandler handler); + + @Override + public @Nullable ContainerAnimations opposite() { + return opposite; + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AdvancedListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AdvancedListItem.java index 8464699081..cdbedba2f3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AdvancedListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AdvancedListItem.java @@ -25,7 +25,6 @@ import javafx.scene.control.Skin; import javafx.scene.image.Image; import javafx.scene.image.ImageView; -import javafx.scene.input.MouseEvent; import javafx.scene.layout.StackPane; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.util.Pair; @@ -42,7 +41,7 @@ public class AdvancedListItem extends Control { public AdvancedListItem() { getStyleClass().add("advanced-list-item"); - addEventHandler(MouseEvent.MOUSE_CLICKED, e -> fireEvent(new ActionEvent())); + FXUtils.onClicked(this, () -> fireEvent(new ActionEvent())); } public Node getLeftGraphic() { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AnnouncementCard.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AnnouncementCard.java index 7262bcadd3..5e80222c4d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AnnouncementCard.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AnnouncementCard.java @@ -17,21 +17,36 @@ */ package org.jackhuang.hmcl.ui.construct; +import javafx.scene.Cursor; +import javafx.scene.Node; import javafx.scene.control.Label; +import javafx.scene.layout.BorderPane; import javafx.scene.layout.VBox; import javafx.scene.text.TextFlow; +import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.SVG; -public class AnnouncementCard extends VBox { +public final class AnnouncementCard extends VBox { - public AnnouncementCard(String title, String content) { - TextFlow tf = FXUtils.segmentToTextFlow(content, Controllers::onHyperlinkAction); + public AnnouncementCard(String title, String content, Runnable onClose) { + TextFlow body = FXUtils.segmentToTextFlow(content, Controllers::onHyperlinkAction); + body.setLineSpacing(4); - Label label = new Label(title); - label.getStyleClass().add("title"); - getChildren().setAll(label, tf); - setSpacing(14); + BorderPane titleBar = new BorderPane(); + titleBar.getStyleClass().add("title"); + titleBar.setLeft(new Label(title)); + + if (onClose != null) { + Node hideNode = SVG.CLOSE.createIcon(Theme.blackFill(), 20, 20); + hideNode.setOnMouseClicked(e -> onClose.run()); + hideNode.setCursor(Cursor.HAND); + titleBar.setRight(hideNode); + } + + getChildren().setAll(titleBar, body); + setSpacing(16); getStyleClass().addAll("card", "announcement"); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java index d6f2a5f5c0..bd97fba095 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java @@ -25,9 +25,6 @@ import javafx.application.Platform; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; -import javafx.event.ActionEvent; -import javafx.event.Event; -import javafx.event.EventHandler; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Node; @@ -139,7 +136,7 @@ private void updateLayout() { container.getChildren().setAll(content); groupNode.getChildren().add(container); - EventHandler onExpand = e -> { + Runnable onExpand = () -> { if (expandAnimation != null && expandAnimation.getStatus() == Animation.Status.RUNNING) { expandAnimation.stop(); } @@ -182,8 +179,8 @@ private void updateLayout() { }); }; - headerRippler.setOnMouseClicked(onExpand); - expandButton.setOnAction((EventHandler) (Object) onExpand); + FXUtils.onClicked(headerRippler, onExpand); + expandButton.setOnAction(e -> onExpand.run()); expandedProperty().addListener((a, b, newValue) -> expandIcon.setRotate(newValue ? 180 : 0)); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FileItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FileItem.java index 7a636c58ad..1df0657605 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FileItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FileItem.java @@ -60,7 +60,7 @@ public FileItem() { JFXButton right = new JFXButton(); right.setGraphic(SVG.PENCIL.createIcon(Theme.blackFill(), 15, 15)); right.getStyleClass().add("toggle-icon4"); - right.setOnMouseClicked(e -> onExplore()); + right.setOnAction(e -> onExplore()); FXUtils.installFastTooltip(right, i18n("button.edit")); setRight(right); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FontComboBox.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FontComboBox.java index 58b0923beb..7438a318fd 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FontComboBox.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FontComboBox.java @@ -21,6 +21,7 @@ import static javafx.collections.FXCollections.observableList; import static javafx.collections.FXCollections.singletonObservableList; +import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.util.javafx.BindingMapping; import com.jfoenix.controls.JFXComboBox; @@ -34,6 +35,8 @@ public class FontComboBox extends JFXComboBox { private boolean loaded = false; public FontComboBox() { + setMinWidth(260); + styleProperty().bind(Bindings.concat("-fx-font-family: \"", valueProperty(), "\"")); setCellFactory(listView -> new JFXListCell() { @@ -51,7 +54,7 @@ public void updateItem(String item, boolean empty) { itemsProperty().bind(BindingMapping.of(valueProperty()) .map(value -> value == null ? emptyObservableList() : singletonObservableList(value))); - setOnMouseClicked(e -> { + FXUtils.onClicked(this, () -> { if (loaded) return; itemsProperty().unbind(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedMenuItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedMenuItem.java index b54c6f70b3..dcc1d86aa8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedMenuItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedMenuItem.java @@ -30,9 +30,9 @@ public IconedMenuItem(SVG icon, String text, Runnable action, JFXPopup popup) { getStyleClass().setAll("iconed-menu-item"); if (popup == null) { - setOnMouseClicked(e -> action.run()); + FXUtils.onClicked(this, action); } else { - setOnMouseClicked(e -> { + FXUtils.onClicked(this, () -> { action.run(); popup.hide(); }); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedTwoLineListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedTwoLineListItem.java index f3f9f2d60e..8de2e245c7 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedTwoLineListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedTwoLineListItem.java @@ -17,7 +17,6 @@ import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; -import org.jackhuang.hmcl.util.Lazy; import org.jackhuang.hmcl.util.StringUtils; public class IconedTwoLineListItem extends HBox { @@ -29,13 +28,8 @@ public class IconedTwoLineListItem extends HBox { private final ImageView imageView = new ImageView(); private final TwoLineListItem twoLineListItem = new TwoLineListItem(); - private final Lazy externalLinkButton = new Lazy<>(() -> { - JFXButton button = new JFXButton(); - button.getStyleClass().add("toggle-icon4"); - button.setGraphic(SVG.OPEN_IN_NEW.createIcon(Theme.blackFill(), -1, -1)); - button.setOnAction(e -> FXUtils.openLink(externalLink.get())); - return button; - }); + private JFXButton externalLinkButton; + @SuppressWarnings("FieldCanBeLocal") private final InvalidationListener observer; @@ -52,7 +46,7 @@ public IconedTwoLineListItem() { getChildren().clear(); if (image.get() != null) getChildren().add(imageView); getChildren().add(twoLineListItem); - if (StringUtils.isNotBlank(externalLink.get())) getChildren().add(externalLinkButton.get()); + if (StringUtils.isNotBlank(externalLink.get())) getChildren().add(getExternalLinkButton()); }, image, externalLink); } @@ -111,4 +105,14 @@ public void setImage(Image image) { public ImageView getImageView() { return imageView; } + + public JFXButton getExternalLinkButton() { + if (externalLinkButton == null) { + externalLinkButton = new JFXButton(); + externalLinkButton.getStyleClass().add("toggle-icon4"); + externalLinkButton.setGraphic(SVG.OPEN_IN_NEW.createIcon(Theme.blackFill(), -1, -1)); + externalLinkButton.setOnAction(e -> FXUtils.openLink(externalLink.get())); + } + return externalLinkButton; + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ImagePickerItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ImagePickerItem.java index e7a0a6d00d..810256c7c5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ImagePickerItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ImagePickerItem.java @@ -23,12 +23,12 @@ import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; +import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Pos; import javafx.scene.control.Label; import javafx.scene.image.Image; import javafx.scene.image.ImageView; -import javafx.scene.input.MouseEvent; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; @@ -44,8 +44,8 @@ public final class ImagePickerItem extends BorderPane { private final ImageView imageView; private final StringProperty title = new SimpleStringProperty(this, "title"); - private final ObjectProperty> onSelectButtonClicked = new SimpleObjectProperty<>(this, "onSelectButtonClicked"); - private final ObjectProperty> onDeleteButtonClicked = new SimpleObjectProperty<>(this, "onDeleteButtonClicked"); + private final ObjectProperty> onSelectButtonClicked = new SimpleObjectProperty<>(this, "onSelectButtonClicked"); + private final ObjectProperty> onDeleteButtonClicked = new SimpleObjectProperty<>(this, "onDeleteButtonClicked"); private final ObjectProperty image = new SimpleObjectProperty<>(this, "image"); public ImagePickerItem() { @@ -55,12 +55,12 @@ public ImagePickerItem() { JFXButton selectButton = new JFXButton(); selectButton.setGraphic(SVG.PENCIL.createIcon(Theme.blackFill(), 20, 20)); - selectButton.onMouseClickedProperty().bind(onSelectButtonClicked); + selectButton.onActionProperty().bind(onSelectButtonClicked); selectButton.getStyleClass().add("toggle-icon4"); JFXButton deleteButton = new JFXButton(); deleteButton.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), 20, 20)); - deleteButton.onMouseClickedProperty().bind(onDeleteButtonClicked); + deleteButton.onActionProperty().bind(onDeleteButtonClicked); deleteButton.getStyleClass().add("toggle-icon4"); FXUtils.installFastTooltip(selectButton, i18n("button.edit")); @@ -93,27 +93,27 @@ public void setTitle(String title) { this.title.set(title); } - public EventHandler getOnSelectButtonClicked() { + public EventHandler getOnSelectButtonClicked() { return onSelectButtonClicked.get(); } - public ObjectProperty> onSelectButtonClickedProperty() { + public ObjectProperty> onSelectButtonClickedProperty() { return onSelectButtonClicked; } - public void setOnSelectButtonClicked(EventHandler onSelectButtonClicked) { + public void setOnSelectButtonClicked(EventHandler onSelectButtonClicked) { this.onSelectButtonClicked.set(onSelectButtonClicked); } - public EventHandler getOnDeleteButtonClicked() { + public EventHandler getOnDeleteButtonClicked() { return onDeleteButtonClicked.get(); } - public ObjectProperty> onDeleteButtonClickedProperty() { + public ObjectProperty> onDeleteButtonClickedProperty() { return onDeleteButtonClicked; } - public void setOnDeleteButtonClicked(EventHandler onDeleteButtonClicked) { + public void setOnDeleteButtonClicked(EventHandler onDeleteButtonClicked) { this.onDeleteButtonClicked.set(onDeleteButtonClicked); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/InputDialogPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/InputDialogPane.java index 770c4af9ca..d2c06b5d90 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/InputDialogPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/InputDialogPane.java @@ -27,6 +27,7 @@ import java.util.concurrent.CompletableFuture; +import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public class InputDialogPane extends JFXDialogLayout { @@ -68,6 +69,8 @@ public InputDialogPane(String text, String initialValue, FutureCallback lblCreationWarning.setText(msg); }); }); + + onEscPressed(this, cancelButton::fire); } public CompletableFuture getCompletableFuture() { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MDListCell.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MDListCell.java index fe2f3263f4..156e62d12b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MDListCell.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MDListCell.java @@ -18,6 +18,7 @@ package org.jackhuang.hmcl.ui.construct; import com.jfoenix.controls.JFXListView; +import javafx.beans.binding.DoubleBinding; import javafx.css.PseudoClass; import javafx.scene.control.ListCell; import javafx.scene.layout.Region; @@ -45,9 +46,11 @@ public MDListCell(JFXListView listView, Holder lastCell) { Region clippedContainer = (Region) listView.lookup(".clipped-container"); setPrefWidth(0); if (clippedContainer != null) { - maxWidthProperty().bind(clippedContainer.widthProperty()); - prefWidthProperty().bind(clippedContainer.widthProperty()); - minWidthProperty().bind(clippedContainer.widthProperty()); + DoubleBinding converted = clippedContainer.widthProperty().subtract(1); + + maxWidthProperty().bind(converted); + prefWidthProperty().bind(converted); + minWidthProperty().bind(converted); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java index dd70b735b6..a709305f1a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java @@ -166,6 +166,16 @@ public Builder addAction(Node actionNode) { return this; } + public Builder addAction(String text, @Nullable Runnable action) { + JFXButton btnAction = new JFXButton(text); + btnAction.getStyleClass().add("dialog-accept"); + if (action != null) { + btnAction.setOnAction(e -> action.run()); + } + dialog.addButton(btnAction); + return this; + } + public Builder ok(@Nullable Runnable ok) { JFXButton btnOk = new JFXButton(i18n("button.ok")); btnOk.getStyleClass().add("dialog-accept"); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MultiFileItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MultiFileItem.java index 4f09e376c9..30379ecc34 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MultiFileItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MultiFileItem.java @@ -21,7 +21,6 @@ import com.jfoenix.controls.JFXTextField; import com.jfoenix.validation.base.ValidatorBase; import javafx.beans.property.*; -import javafx.collections.ObservableList; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Node; @@ -114,6 +113,7 @@ public void setFallbackData(T fallbackData) { public static class Option { protected final String title; protected String subtitle; + protected String tooltip; protected final T data; protected final BooleanProperty selected = new SimpleBooleanProperty(); protected final JFXRadioButton left = new JFXRadioButton(); @@ -140,6 +140,11 @@ public Option setSubtitle(String subtitle) { return this; } + public Option setTooltip(String tooltip) { + this.tooltip = tooltip; + return this; + } + public boolean isSelected() { return left.isSelected(); } @@ -161,6 +166,8 @@ protected Node createItem(ToggleGroup group) { BorderPane.setAlignment(left, Pos.CENTER_LEFT); left.setToggleGroup(group); left.setUserData(data); + if (StringUtils.isNotBlank(tooltip)) + FXUtils.installFastTooltip(left, tooltip); pane.setLeft(left); if (StringUtils.isNotBlank(subtitle)) { @@ -268,8 +275,9 @@ public FileOption setChooserTitle(String chooserTitle) { return this; } - public ObservableList getExtensionFilters() { - return selector.getExtensionFilters(); + public FileOption addExtensionFilter(FileChooser.ExtensionFilter filter) { + selector.getExtensionFilters().add(filter); + return this; } @Override diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/Navigator.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/Navigator.java index 209c893f7e..4ec4989929 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/Navigator.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/Navigator.java @@ -126,7 +126,7 @@ public void close(Node from) { if (obj instanceof AnimationProducer) { setContent(node, (AnimationProducer) obj); } else { - setContent(node, ContainerAnimations.NONE.getAnimationProducer()); + setContent(node, ContainerAnimations.NONE); } NavigationEvent navigated = new NavigationEvent(this, node, Navigation.NavigationDirection.PREVIOUS, NavigationEvent.NAVIGATED); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/OptionToggleButton.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/OptionToggleButton.java index fd9a6359dc..7883bb37c2 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/OptionToggleButton.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/OptionToggleButton.java @@ -61,9 +61,7 @@ public OptionToggleButton() { toggleButton.setSize(8); FXUtils.setLimitHeight(toggleButton, 30); - container.setOnMouseClicked(e -> { - toggleButton.setSelected(!toggleButton.isSelected()); - }); + FXUtils.onClicked(container, () -> toggleButton.setSelected(!toggleButton.isSelected())); FXUtils.onChangeAndOperate(subtitleProperty(), subtitle -> { if (StringUtils.isNotBlank(subtitle)) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/SpinnerPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/SpinnerPane.java index 8035df0894..2d6ee81f89 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/SpinnerPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/SpinnerPane.java @@ -32,7 +32,6 @@ import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.animation.ContainerAnimations; import org.jackhuang.hmcl.ui.animation.TransitionPane; -import org.jackhuang.hmcl.util.javafx.BindingMapping; @DefaultProperty("content") public class SpinnerPane extends Control { @@ -101,18 +100,19 @@ public final EventHandler getOnFailedAction() { return onFailedActionProperty().get(); } - private ObjectProperty> onFailedAction = new SimpleObjectProperty>(this, "onFailedAction") { + private final ObjectProperty> onFailedAction = new SimpleObjectProperty>(this, "onFailedAction") { @Override protected void invalidated() { setEventHandler(FAILED_ACTION, get()); } }; + @Override - protected Skin createDefaultSkin() { + protected SkinBase createDefaultSkin() { return new Skin(this); } - private static class Skin extends SkinBase { + private static final class Skin extends SkinBase { private final JFXSpinner spinner = new JFXSpinner(); private final StackPane contentPane = new StackPane(); private final StackPane topPane = new StackPane(); @@ -122,20 +122,18 @@ private static class Skin extends SkinBase { @SuppressWarnings("FieldCanBeLocal") // prevent from gc. private final InvalidationListener observer; - protected Skin(SpinnerPane control) { + Skin(SpinnerPane control) { super(control); topPane.getChildren().setAll(spinner); topPane.getStyleClass().add("notice-pane"); failedPane.getStyleClass().add("notice-pane"); failedPane.getChildren().setAll(failedReasonLabel); - failedPane.onMouseClickedProperty().bind( - BindingMapping.of(control.onFailedAction) - .map(actionHandler -> (e -> { - if (actionHandler != null) { - actionHandler.handle(new Event(FAILED_ACTION)); - } - }))); + FXUtils.onClicked(failedPane, () -> { + EventHandler action = control.getOnFailedAction(); + if (action != null) + action.handle(new Event(FAILED_ACTION)); + }); FXUtils.onChangeAndOperate(getSkinnable().content, newValue -> { if (newValue == null) { @@ -148,12 +146,12 @@ protected Skin(SpinnerPane control) { observer = FXUtils.observeWeak(() -> { if (getSkinnable().getFailedReason() != null) { - root.setContent(failedPane, ContainerAnimations.FADE.getAnimationProducer()); + root.setContent(failedPane, ContainerAnimations.FADE); failedReasonLabel.setText(getSkinnable().getFailedReason()); } else if (getSkinnable().isLoading()) { - root.setContent(topPane, ContainerAnimations.FADE.getAnimationProducer()); + root.setContent(topPane, ContainerAnimations.FADE); } else { - root.setContent(contentPane, ContainerAnimations.FADE.getAnimationProducer()); + root.setContent(contentPane, ContainerAnimations.FADE); } }, getSkinnable().loadingProperty(), getSkinnable().failedReasonProperty()); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TabHeader.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TabHeader.java index 8f11574995..c91ac1c82f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TabHeader.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TabHeader.java @@ -30,7 +30,6 @@ import javafx.geometry.Side; import javafx.scene.Node; import javafx.scene.control.*; -import javafx.scene.input.MouseButton; import javafx.scene.layout.*; import javafx.scene.paint.Color; import javafx.scene.transform.Rotate; @@ -594,11 +593,9 @@ public TabHeaderContainer(Tab tab) { FXUtils.onChangeAndOperate(tab.selectedProperty(), selected -> inner.pseudoClassStateChanged(SELECTED_PSEUDOCLASS_STATE, selected)); - this.setOnMouseClicked(event -> { - if (event.getButton() == MouseButton.PRIMARY) { - this.setOpacity(1); - getSkinnable().getSelectionModel().select(tab); - } + FXUtils.onClicked(this, () -> { + this.setOpacity(1); + getSkinnable().getSelectionModel().select(tab); }); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java index 9b07239994..2d0d555a0d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java @@ -30,6 +30,8 @@ import javafx.scene.image.Image; import javafx.scene.input.DragEvent; import javafx.scene.input.KeyEvent; +import javafx.scene.input.MouseButton; +import javafx.scene.input.MouseEvent; import javafx.scene.layout.*; import javafx.scene.paint.Color; import javafx.stage.Stage; @@ -47,13 +49,10 @@ import org.jackhuang.hmcl.ui.construct.StackContainerPane; import org.jackhuang.hmcl.ui.wizard.Refreshable; import org.jackhuang.hmcl.ui.wizard.WizardProvider; -import org.jackhuang.hmcl.util.io.NetworkUtils; +import org.jetbrains.annotations.Nullable; import java.io.IOException; -import java.io.InputStream; -import java.net.HttpURLConnection; import java.net.URL; -import java.net.URLConnection; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -144,6 +143,18 @@ public DecoratorController(Stage stage, Node mainPage) { // press ESC to go back onEscPressed(navigator, this::back); + + try { + // For JavaFX 12+ + MouseButton button = MouseButton.valueOf("BACK"); + navigator.addEventFilter(MouseEvent.MOUSE_CLICKED, e -> { + if (e.getButton() == button) { + back(); + e.consume(); + } + }); + } catch (IllegalArgumentException ignored) { + } } public Decorator getDecorator() { @@ -166,23 +177,13 @@ private Background getBackground() { case CUSTOM: String backgroundImage = config().getBackgroundImage(); if (backgroundImage != null) - image = tryLoadImage(Paths.get(backgroundImage)).orElse(null); + image = tryLoadImage(Paths.get(backgroundImage)); break; case NETWORK: String backgroundImageUrl = config().getBackgroundImageUrl(); if (backgroundImageUrl != null) { try { - URLConnection connection = NetworkUtils.createConnection(new URL(backgroundImageUrl)); - if (connection instanceof HttpURLConnection) { - connection = NetworkUtils.resolveConnection((HttpURLConnection) connection); - } - - try (InputStream input = connection.getInputStream()) { - image = new Image(input); - if (image.isError()) { - throw image.getException(); - } - } + image = FXUtils.loadImage(new URL(backgroundImageUrl)); } catch (Exception e) { LOG.warning("Couldn't load background image", e); } @@ -204,73 +205,57 @@ private Background getBackground() { * Load background image from bg/, background.png, background.jpg, background.gif */ private Image loadDefaultBackgroundImage() { - Optional image = randomImageIn(Paths.get("bg")); - if (!image.isPresent()) { - image = tryLoadImage(Paths.get("background.png")); - } - if (!image.isPresent()) { - image = tryLoadImage(Paths.get("background.jpg")); - } - if (!image.isPresent()) { - image = tryLoadImage(Paths.get("background.gif")); + Image image = randomImageIn(Paths.get("bg")); + if (image != null) + return image; + + for (String extension : FXUtils.IMAGE_EXTENSIONS) { + image = tryLoadImage(Paths.get("background." + extension)); + if (image != null) + return image; } - return image.orElseGet(() -> newBuiltinImage("/assets/img/background.jpg")); + + return newBuiltinImage("/assets/img/background.jpg"); } - private Optional randomImageIn(Path imageDir) { + private @Nullable Image randomImageIn(Path imageDir) { if (!Files.isDirectory(imageDir)) { - return Optional.empty(); + return null; } List candidates; try (Stream stream = Files.list(imageDir)) { candidates = stream + .filter(it -> FXUtils.IMAGE_EXTENSIONS.contains(getExtension(it).toLowerCase(Locale.ROOT))) .filter(Files::isReadable) - .filter(it -> { - String ext = getExtension(it).toLowerCase(Locale.ROOT); - return ext.equals("png") || ext.equals("jpg") || ext.equals("gif"); - }) .collect(toList()); } catch (IOException e) { LOG.warning("Failed to list files in ./bg", e); - return Optional.empty(); + return null; } Random rnd = new Random(); - while (candidates.size() > 0) { + while (!candidates.isEmpty()) { int selected = rnd.nextInt(candidates.size()); - Optional loaded = tryLoadImage(candidates.get(selected)); - if (loaded.isPresent()) { + Image loaded = tryLoadImage(candidates.get(selected)); + if (loaded != null) return loaded; - } else { + else candidates.remove(selected); - } } - return Optional.empty(); + return null; } - private Optional tryLoadImage(Path path) { + private @Nullable Image tryLoadImage(Path path) { if (!Files.isReadable(path)) - return Optional.empty(); - - return tryLoadImage(path.toAbsolutePath().toUri().toString()); - } + return null; - private Optional tryLoadImage(String url) { - Image img; try { - img = new Image(url); - } catch (IllegalArgumentException e) { + return FXUtils.loadImage(path); + } catch (Exception e) { LOG.warning("Couldn't load background image", e); - return Optional.empty(); - } - - if (img.getException() != null) { - LOG.warning("Couldn't load background image", img.getException()); - return Optional.empty(); + return null; } - - return Optional.of(img); } // ==== Navigation ==== @@ -443,7 +428,7 @@ public void startWizard(WizardProvider wizardProvider) { public void startWizard(WizardProvider wizardProvider, String category) { FXUtils.checkFxUserThread(); - navigator.navigate(new DecoratorWizardDisplayer(wizardProvider, category), ContainerAnimations.FADE.getAnimationProducer()); + navigator.navigate(new DecoratorWizardDisplayer(wizardProvider, category), ContainerAnimations.FADE); } // ==== Authlib Injector DnD ==== diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorSkin.java index 21b33277a8..d179cedb45 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorSkin.java @@ -18,10 +18,8 @@ package org.jackhuang.hmcl.ui.decorator; import com.jfoenix.controls.JFXButton; -import com.jfoenix.svg.SVGGlyph; import javafx.beans.binding.Bindings; import javafx.collections.ListChangeListener; -import javafx.css.PseudoClass; import javafx.geometry.Bounds; import javafx.geometry.Insets; import javafx.geometry.Pos; @@ -29,11 +27,10 @@ import javafx.scene.Node; import javafx.scene.control.Label; import javafx.scene.control.SkinBase; +import javafx.scene.effect.BlurType; +import javafx.scene.effect.DropShadow; import javafx.scene.input.MouseEvent; -import javafx.scene.layout.AnchorPane; -import javafx.scene.layout.BorderPane; -import javafx.scene.layout.HBox; -import javafx.scene.layout.StackPane; +import javafx.scene.layout.*; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import javafx.stage.Stage; @@ -44,20 +41,14 @@ import org.jackhuang.hmcl.ui.animation.ContainerAnimations; import org.jackhuang.hmcl.ui.animation.TransitionPane; import org.jackhuang.hmcl.ui.wizard.Navigation; -import org.jackhuang.hmcl.util.Lang; public class DecoratorSkin extends SkinBase { - private static final PseudoClass TRANSPARENT = PseudoClass.getPseudoClass("transparent"); - private static final SVGGlyph minus = Lang.apply(new SVGGlyph(0, "MINUS", "M804.571 420.571v109.714q0 22.857-16 38.857t-38.857 16h-694.857q-22.857 0-38.857-16t-16-38.857v-109.714q0-22.857 16-38.857t38.857-16h694.857q22.857 0 38.857 16t16 38.857z", Color.WHITE), - glyph -> { glyph.setSize(12, 2); glyph.setTranslateY(4); }); - private final StackPane root, parent; private final StackPane titleContainer; private final Stage primaryStage; private final TransitionPane navBarPane; - private double xOffset, yOffset, newX, newY, initX, initY; - private boolean titleBarTransparent = true; + private double mouseInitX, mouseInitY, stageInitX, stageInitY, stageInitWidth, stageInitHeight; /** * Constructor for all SkinBase instances. @@ -69,14 +60,13 @@ public DecoratorSkin(Decorator control) { primaryStage = control.getPrimaryStage(); - minus.fillProperty().bind(Theme.foregroundFillBinding()); - Decorator skinnable = getSkinnable(); root = new StackPane(); root.getStyleClass().add("window"); StackPane shadowContainer = new StackPane(); shadowContainer.getStyleClass().add("body"); + shadowContainer.setEffect(new DropShadow(BlurType.ONE_PASS_BOX, Color.rgb(0, 0, 0, 0.4), 10, 0.3, 0.0, 0.0)); parent = new StackPane(); Rectangle clip = new Rectangle(); @@ -174,11 +164,11 @@ public DecoratorSkin(Decorator control) { if (s.isAnimate()) { AnimationProducer animation; if (skinnable.getNavigationDirection() == Navigation.NavigationDirection.NEXT) { - animation = ContainerAnimations.SWIPE_LEFT_FADE_SHORT.getAnimationProducer(); + animation = ContainerAnimations.SWIPE_LEFT_FADE_SHORT; } else if (skinnable.getNavigationDirection() == Navigation.NavigationDirection.PREVIOUS) { - animation = ContainerAnimations.SWIPE_RIGHT_FADE_SHORT.getAnimationProducer(); + animation = ContainerAnimations.SWIPE_RIGHT_FADE_SHORT; } else { - animation = ContainerAnimations.FADE.getAnimationProducer(); + animation = ContainerAnimations.FADE; } skinnable.setNavigationDirection(Navigation.NavigationDirection.START); navBarPane.setContent(node, animation); @@ -202,9 +192,7 @@ public DecoratorSkin(Decorator control) { btnHelp.setOnAction(e -> FXUtils.openLink("https://docs.hmcl.net/help.html")); JFXButton btnMin = new JFXButton(); - StackPane pane = new StackPane(minus); - pane.setAlignment(Pos.CENTER); - btnMin.setGraphic(pane); + btnMin.setGraphic(SVG.MINUS.createIcon(Theme.foregroundFillBinding(), -1, -1)); btnMin.getStyleClass().add("jfx-decorator-button"); btnMin.setOnAction(e -> skinnable.minimize()); @@ -310,13 +298,6 @@ private Node createNavBar(Decorator skinnable, double leftPaneWidth, boolean can return navBar; } - private void updateInitMouseValues(MouseEvent mouseEvent) { - initX = mouseEvent.getScreenX(); - initY = mouseEvent.getScreenY(); - xOffset = mouseEvent.getSceneX(); - yOffset = mouseEvent.getSceneY(); - } - private boolean isRightEdge(double x, double y, Bounds boundsInParent) { return x < root.getWidth() && x >= root.getWidth() - root.snappedLeftInset(); } @@ -333,146 +314,139 @@ private boolean isLeftEdge(double x, double y, Bounds boundsInParent) { return x >= 0 && x <= root.snappedLeftInset(); } - private boolean setStageWidth(double width) { - if (width >= primaryStage.getMinWidth() && width >= titleContainer.getMinWidth()) { - primaryStage.setWidth(width); - initX = newX; - return true; - } else { - if (width >= primaryStage.getMinWidth() && width <= titleContainer.getMinWidth()) - primaryStage.setWidth(titleContainer.getMinWidth()); - - return false; - } - } - - private boolean setStageHeight(double height) { - if (height >= primaryStage.getMinHeight() && height >= titleContainer.getHeight()) { - primaryStage.setHeight(height); - initY = newY; - return true; - } else { - if (height >= primaryStage.getMinHeight() && height <= titleContainer.getHeight()) - primaryStage.setHeight(titleContainer.getHeight()); - - return false; - } + private void resizeStage(double newWidth, double newHeight) { + if (newWidth < 0) + newWidth = primaryStage.getWidth(); + if (newWidth < primaryStage.getMinWidth()) + newWidth = primaryStage.getMinWidth(); + if (newWidth < titleContainer.getMinWidth()) + newWidth = titleContainer.getMinWidth(); + + if (newHeight < 0) + newHeight = primaryStage.getHeight(); + if (newHeight < primaryStage.getMinHeight()) + newHeight = primaryStage.getMinHeight(); + if (newHeight < titleContainer.getMinHeight()) + newHeight = titleContainer.getMinHeight(); + + // Width and height must be set simultaneously to avoid JDK-8344372 (https://github.com/openjdk/jfx/pull/1654) + primaryStage.setWidth(newWidth); + primaryStage.setHeight(newHeight); } - // ==== - - protected void onMouseMoved(MouseEvent mouseEvent) { - if (!primaryStage.isFullScreen()) { - updateInitMouseValues(mouseEvent); - if (primaryStage.isResizable()) { - double x = mouseEvent.getX(), y = mouseEvent.getY(); - Bounds boundsInParent = root.getBoundsInParent(); - double diagonalSize = root.snappedLeftInset() + 10; - if (this.isRightEdge(x, y, boundsInParent)) { - if (y < diagonalSize) { - root.setCursor(Cursor.NE_RESIZE); - } else if (y > root.getHeight() - diagonalSize) { - root.setCursor(Cursor.SE_RESIZE); - } else { - root.setCursor(Cursor.E_RESIZE); - } - } else if (this.isLeftEdge(x, y, boundsInParent)) { - if (y < diagonalSize) { - root.setCursor(Cursor.NW_RESIZE); - } else if (y > root.getHeight() - diagonalSize) { - root.setCursor(Cursor.SW_RESIZE); - } else { - root.setCursor(Cursor.W_RESIZE); - } - } else if (this.isTopEdge(x, y, boundsInParent)) { - if (x < diagonalSize) { - root.setCursor(Cursor.NW_RESIZE); - } else if (x > root.getWidth() - diagonalSize) { - root.setCursor(Cursor.NE_RESIZE); - } else { - root.setCursor(Cursor.N_RESIZE); - } - } else if (this.isBottomEdge(x, y, boundsInParent)) { - if (x < diagonalSize) { - root.setCursor(Cursor.SW_RESIZE); - } else if (x > root.getWidth() - diagonalSize) { - root.setCursor(Cursor.SE_RESIZE); - } else { - root.setCursor(Cursor.S_RESIZE); - } + private void onMouseMoved(MouseEvent mouseEvent) { + if (!primaryStage.isFullScreen() && primaryStage.isResizable()) { + double x = mouseEvent.getX(), y = mouseEvent.getY(); + Bounds boundsInParent = root.getBoundsInParent(); + double diagonalSize = root.snappedLeftInset() + 10; + if (this.isRightEdge(x, y, boundsInParent)) { + if (y < diagonalSize) { + root.setCursor(Cursor.NE_RESIZE); + } else if (y > root.getHeight() - diagonalSize) { + root.setCursor(Cursor.SE_RESIZE); } else { - root.setCursor(Cursor.DEFAULT); + root.setCursor(Cursor.E_RESIZE); } + } else if (this.isLeftEdge(x, y, boundsInParent)) { + if (y < diagonalSize) { + root.setCursor(Cursor.NW_RESIZE); + } else if (y > root.getHeight() - diagonalSize) { + root.setCursor(Cursor.SW_RESIZE); + } else { + root.setCursor(Cursor.W_RESIZE); + } + } else if (this.isTopEdge(x, y, boundsInParent)) { + if (x < diagonalSize) { + root.setCursor(Cursor.NW_RESIZE); + } else if (x > root.getWidth() - diagonalSize) { + root.setCursor(Cursor.NE_RESIZE); + } else { + root.setCursor(Cursor.N_RESIZE); + } + } else if (this.isBottomEdge(x, y, boundsInParent)) { + if (x < diagonalSize) { + root.setCursor(Cursor.SW_RESIZE); + } else if (x > root.getWidth() - diagonalSize) { + root.setCursor(Cursor.SE_RESIZE); + } else { + root.setCursor(Cursor.S_RESIZE); + } + } else { + root.setCursor(Cursor.DEFAULT); } } else { root.setCursor(Cursor.DEFAULT); } } - protected void onMouseReleased(MouseEvent mouseEvent) { + private void onMouseReleased(MouseEvent mouseEvent) { getSkinnable().setDragging(false); } - protected void onMouseDragged(MouseEvent mouseEvent) { - getSkinnable().setDragging(true); - if (mouseEvent.isPrimaryButtonDown() && (this.xOffset != -1.0 || this.yOffset != -1.0)) { - if (!this.primaryStage.isFullScreen() && !mouseEvent.isStillSincePress()) { - this.newX = mouseEvent.getScreenX(); - this.newY = mouseEvent.getScreenY(); - double deltaX = this.newX - this.initX; - double deltaY = this.newY - this.initY; - Cursor cursor = root.getCursor(); - if (Cursor.E_RESIZE == cursor) { - this.setStageWidth(this.primaryStage.getWidth() + deltaX); - mouseEvent.consume(); - } else if (Cursor.NE_RESIZE == cursor) { - if (this.setStageHeight(this.primaryStage.getHeight() - deltaY)) { - this.primaryStage.setY(this.primaryStage.getY() + deltaY); - } - - this.setStageWidth(this.primaryStage.getWidth() + deltaX); - mouseEvent.consume(); - } else if (Cursor.SE_RESIZE == cursor) { - this.setStageWidth(this.primaryStage.getWidth() + deltaX); - this.setStageHeight(this.primaryStage.getHeight() + deltaY); - mouseEvent.consume(); - } else if (Cursor.S_RESIZE == cursor) { - this.setStageHeight(this.primaryStage.getHeight() + deltaY); - mouseEvent.consume(); - } else if (Cursor.W_RESIZE == cursor) { - if (this.setStageWidth(this.primaryStage.getWidth() - deltaX)) { - this.primaryStage.setX(this.primaryStage.getX() + deltaX); - } + private void onMouseDragged(MouseEvent mouseEvent) { + if (!getSkinnable().isDragging()) { + getSkinnable().setDragging(true); + mouseInitX = mouseEvent.getScreenX(); + mouseInitY = mouseEvent.getScreenY(); + stageInitX = primaryStage.getX(); + stageInitY = primaryStage.getY(); + stageInitWidth = primaryStage.getWidth(); + stageInitHeight = primaryStage.getHeight(); + } - mouseEvent.consume(); - } else if (Cursor.SW_RESIZE == cursor) { - if (this.setStageWidth(this.primaryStage.getWidth() - deltaX)) { - this.primaryStage.setX(this.primaryStage.getX() + deltaX); - } + if (primaryStage.isFullScreen() || !mouseEvent.isPrimaryButtonDown() || mouseEvent.isStillSincePress()) + return; - this.setStageHeight(this.primaryStage.getHeight() + deltaY); - mouseEvent.consume(); - } else if (Cursor.NW_RESIZE == cursor) { - if (this.setStageWidth(this.primaryStage.getWidth() - deltaX)) { - this.primaryStage.setX(this.primaryStage.getX() + deltaX); - } - - if (this.setStageHeight(this.primaryStage.getHeight() - deltaY)) { - this.primaryStage.setY(this.primaryStage.getY() + deltaY); - } + double dx = mouseEvent.getScreenX() - mouseInitX; + double dy = mouseEvent.getScreenY() - mouseInitY; - mouseEvent.consume(); - } else if (Cursor.N_RESIZE == cursor) { - if (this.setStageHeight(this.primaryStage.getHeight() - deltaY)) { - this.primaryStage.setY(this.primaryStage.getY() + deltaY); - } + Cursor cursor = root.getCursor(); + if (getSkinnable().isAllowMove()) { + if (cursor == Cursor.DEFAULT) { + primaryStage.setX(stageInitX + dx); + primaryStage.setY(stageInitY + dy); + mouseEvent.consume(); + } + } - mouseEvent.consume(); - } else if (getSkinnable().isAllowMove()) { - this.primaryStage.setX(mouseEvent.getScreenX() - this.xOffset); - this.primaryStage.setY(mouseEvent.getScreenY() - this.yOffset); - mouseEvent.consume(); - } + if (getSkinnable().isResizable()) { + if (cursor == Cursor.E_RESIZE) { + resizeStage(stageInitWidth + dx, -1); + mouseEvent.consume(); + + } else if (cursor == Cursor.S_RESIZE) { + resizeStage(-1, stageInitHeight + dy); + mouseEvent.consume(); + + } else if (cursor == Cursor.W_RESIZE) { + resizeStage(stageInitWidth - dx, -1); + primaryStage.setX(stageInitX + stageInitWidth - primaryStage.getWidth()); + mouseEvent.consume(); + + } else if (cursor == Cursor.N_RESIZE) { + resizeStage(-1, stageInitHeight - dy); + primaryStage.setY(stageInitY + stageInitHeight - primaryStage.getHeight()); + mouseEvent.consume(); + + } else if (cursor == Cursor.SE_RESIZE) { + resizeStage(stageInitWidth + dx, stageInitHeight + dy); + mouseEvent.consume(); + + } else if (cursor == Cursor.SW_RESIZE) { + resizeStage(stageInitWidth - dx, stageInitHeight + dy); + primaryStage.setX(stageInitX + stageInitWidth - primaryStage.getWidth()); + mouseEvent.consume(); + + } else if (cursor == Cursor.NW_RESIZE) { + resizeStage(stageInitWidth - dx, stageInitHeight - dy); + primaryStage.setX(stageInitX + stageInitWidth - primaryStage.getWidth()); + primaryStage.setY(stageInitY + stageInitHeight - primaryStage.getHeight()); + mouseEvent.consume(); + + } else if (cursor == Cursor.NE_RESIZE) { + resizeStage(stageInitWidth + dx, stageInitHeight - dy); + primaryStage.setY(stageInitY + stageInitHeight - primaryStage.getHeight()); + mouseEvent.consume(); } } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorTabPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorTabPage.java index 325edd2691..e7cf6b0339 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorTabPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorTabPage.java @@ -36,7 +36,7 @@ public DecoratorTabPage() { if (newValue.getNode() != null) { onNavigating(getCurrentPage()); if (getCurrentPage() != null) getCurrentPage().fireEvent(new Navigator.NavigationEvent(null, getCurrentPage(), Navigation.NavigationDirection.NEXT, Navigator.NavigationEvent.NAVIGATING)); - navigate(newValue.getNode(), ContainerAnimations.FADE.getAnimationProducer()); + navigate(newValue.getNode(), ContainerAnimations.FADE); onNavigated(getCurrentPage()); if (getCurrentPage() != null) getCurrentPage().fireEvent(new Navigator.NavigationEvent(null, getCurrentPage(), Navigation.NavigationDirection.NEXT, Navigator.NavigationEvent.NAVIGATED)); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorWizardDisplayer.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorWizardDisplayer.java index ed6164e7fa..e6a6b5b997 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorWizardDisplayer.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorWizardDisplayer.java @@ -74,7 +74,7 @@ public void onEnd() { @Override public void navigateTo(Node page, Navigation.NavigationDirection nav) { displayer.navigateTo(page, nav); - navigate(page, nav.getAnimation().getAnimationProducer()); + navigate(page, nav.getAnimation()); String prefix = category == null ? "" : category + " - "; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/AdditionalInstallersPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/AdditionalInstallersPage.java index e2b14a5986..0e7d1ce649 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/AdditionalInstallersPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/AdditionalInstallersPage.java @@ -59,7 +59,7 @@ public AdditionalInstallersPage(String gameVersion, Version version, WizardContr for (InstallerItem library : group.getLibraries()) { String libraryId = library.getLibraryId(); if (libraryId.equals("game")) continue; - library.removeActionProperty().set(e -> { + library.setOnRemove(() -> { controller.getSettings().put(libraryId, new UpdateInstallerWizardProvider.RemoveVersionAction(libraryId)); reload(); }); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java index 20a0692bd1..74e447abe8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java @@ -55,6 +55,7 @@ import java.nio.file.Path; import java.util.HashMap; import java.util.Map; +import java.util.Locale; import java.util.concurrent.CancellationException; import java.util.function.Supplier; @@ -96,12 +97,12 @@ public DownloadPage() { tab.select(newGameTab); FXUtils.onChangeAndOperate(tab.getSelectionModel().selectedItemProperty(), newValue -> { - transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE.getAnimationProducer()); + transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE); }); { AdvancedListBox sideBar = new AdvancedListBox() - .startCategory(i18n("download.game")) + .startCategory(i18n("download.game").toUpperCase(Locale.ROOT)) .addNavigationDrawerItem(item -> { item.setTitle(i18n("game")); item.setLeftGraphic(wrap(SVG.GAMEPAD)); @@ -114,7 +115,7 @@ public DownloadPage() { settingsItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(modpackTab)); settingsItem.setOnAction(e -> tab.select(modpackTab)); }) - .startCategory(i18n("download.content")) + .startCategory(i18n("download.content").toUpperCase(Locale.ROOT)) .addNavigationDrawerItem(item -> { item.setTitle(i18n("mods")); item.setLeftGraphic(wrap(SVG.PUZZLE)); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java index b9c8e0c338..8a80a0ba1b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java @@ -40,6 +40,7 @@ import org.jackhuang.hmcl.ui.wizard.WizardController; import org.jackhuang.hmcl.ui.wizard.WizardPage; +import java.nio.charset.StandardCharsets; import java.util.Map; import static javafx.beans.binding.Bindings.createBooleanBinding; @@ -52,6 +53,8 @@ public class InstallersPage extends Control implements WizardPage { protected JFXTextField txtName = new JFXTextField(); protected BooleanProperty installable = new SimpleBooleanProperty(); + private boolean isNameModifiedByUser = false; + public InstallersPage(WizardController controller, HMCLGameRepository repository, String gameVersion, DownloadProvider downloadProvider) { this.controller = controller; this.group = new InstallerItem.InstallerItemGroup(gameVersion, getInstallerItemStyle()); @@ -61,12 +64,13 @@ public InstallersPage(WizardController controller, HMCLGameRepository repository new Validator(i18n("install.new_game.already_exists"), str -> !repository.versionIdConflicts(str)), new Validator(i18n("install.new_game.malformed"), HMCLGameRepository::isValidVersionId)); installable.bind(createBooleanBinding(txtName::validate, txtName.textProperty())); - txtName.setText(gameVersion); + + txtName.textProperty().addListener((obs, oldText, newText) -> isNameModifiedByUser = true); for (InstallerItem library : group.getLibraries()) { String libraryId = library.getLibraryId(); if (libraryId.equals(LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId())) continue; - library.installActionProperty().set(e -> { + library.setOnInstall(() -> { if (LibraryAnalyzer.LibraryType.FABRIC_API.getPatchId().equals(libraryId)) { Controllers.dialog(i18n("install.installer.fabric-api.warning"), i18n("message.warning"), MessageDialogPane.MessageType.WARNING); } @@ -74,7 +78,7 @@ public InstallersPage(WizardController controller, HMCLGameRepository repository if (!(library.resolvedStateProperty().get() instanceof InstallerItem.IncompatibleState)) controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer." + libraryId)), gameVersion, downloadProvider, libraryId, () -> controller.onPrev(false))); }); - library.removeActionProperty().set(e -> { + library.setOnRemove(() -> { controller.getSettings().remove(libraryId); reload(); }); @@ -103,6 +107,9 @@ protected void reload() { library.versionProperty().set(null); } } + if (!isNameModifiedByUser) { + setTxtNameWithLoaders(); + } } @Override @@ -115,8 +122,25 @@ public void cleanup(Map settings) { } protected void onInstall() { - controller.getSettings().put("name", txtName.getText()); - controller.onFinish(); + String name = txtName.getText(); + + // Check for non-ASCII characters. + if (!StandardCharsets.US_ASCII.newEncoder().canEncode(name)) { + Controllers.dialog(new MessageDialogPane.Builder( + i18n("install.name.invalid"), + i18n("message.warning"), + MessageDialogPane.MessageType.QUESTION) + .yesOrNo(() -> { + controller.getSettings().put("name", name); + controller.onFinish(); + }, () -> { + // The user selects Cancel and does nothing. + }) + .build()); + } else { + controller.getSettings().put("name", name); + controller.onFinish(); + } } @Override @@ -124,6 +148,49 @@ protected Skin createDefaultSkin() { return new InstallersPageSkin(this); } + private void setTxtNameWithLoaders() { + StringBuilder nameBuilder = new StringBuilder(group.getGame().versionProperty().get().getVersion()); + + for (InstallerItem library : group.getLibraries()) { + String libraryId = library.getLibraryId().replace(LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId(), ""); + if (!controller.getSettings().containsKey(libraryId)) { + continue; + } + + LibraryAnalyzer.LibraryType libraryType = LibraryAnalyzer.LibraryType.fromPatchId(libraryId); + if (libraryType != null) { + String loaderName; + switch (libraryType) { + case FORGE: + loaderName = i18n("install.installer.forge"); + break; + case NEO_FORGE: + loaderName = i18n("install.installer.neoforge"); + break; + case FABRIC: + loaderName = i18n("install.installer.fabric"); + break; + case LITELOADER: + loaderName = i18n("install.installer.liteloader"); + break; + case QUILT: + loaderName = i18n("install.installer.quilt"); + break; + case OPTIFINE: + loaderName = i18n("install.installer.optifine"); + break; + default: + continue; + } + + nameBuilder.append("-").append(loaderName); + } + } + + txtName.setText(nameBuilder.toString()); + isNameModifiedByUser = false; + } + protected static class InstallersPageSkin extends SkinBase { /** @@ -173,7 +240,7 @@ protected InstallersPageSkin(InstallersPage control) { installButton.disableProperty().bind(control.installable.not()); installButton.setPrefWidth(100); installButton.setPrefHeight(40); - installButton.setOnMouseClicked(e -> control.onInstall()); + installButton.setOnAction(e -> control.onInstall()); BorderPane.setAlignment(installButton, Pos.CENTER_RIGHT); root.setBottom(installButton); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/LocalModpackPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/LocalModpackPage.java index 54480e5524..9c3b4fab2e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/LocalModpackPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/LocalModpackPage.java @@ -31,15 +31,18 @@ import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.WebPage; import org.jackhuang.hmcl.ui.construct.MessageDialogPane; import org.jackhuang.hmcl.ui.construct.RequiredValidator; import org.jackhuang.hmcl.ui.construct.Validator; import org.jackhuang.hmcl.ui.wizard.WizardController; +import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.io.FileUtils; import java.io.File; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.Optional; @@ -78,6 +81,8 @@ public LocalModpackPage(WizardController controller) { }); } + btnDescription.setVisible(false); + File selectedFile; Optional filePath = tryCast(controller.getSettings().get(MODPACK_FILE), File.class); if (filePath.isPresent()) { @@ -107,7 +112,6 @@ public LocalModpackPage(WizardController controller) { hideSpinner(); lblName.setText(selectedFile.getName()); installAsVersion.set(false); - lblModpackLocation.setText(selectedFile.getAbsolutePath()); if (!name.isPresent()) { // trim: https://github.com/HMCL-dev/HMCL/issues/962 @@ -130,12 +134,12 @@ public LocalModpackPage(WizardController controller) { lblVersion.setText(manifest.getVersion()); lblAuthor.setText(manifest.getAuthor()); - lblModpackLocation.setText(selectedFile.getAbsolutePath()); - if (!name.isPresent()) { // trim: https://github.com/HMCL-dev/HMCL/issues/962 txtModpackName.setText(manifest.getName().trim()); } + + btnDescription.setVisible(StringUtils.isNotBlank(manifest.getDescription())); } }).start(); } @@ -146,16 +150,32 @@ public void cleanup(Map settings) { } protected void onInstall() { - if (!txtModpackName.validate()) return; - controller.getSettings().put(MODPACK_NAME, txtModpackName.getText()); - controller.getSettings().put(MODPACK_CHARSET, charset); - controller.onFinish(); + String name = txtModpackName.getText(); + + // Check for non-ASCII characters. + if (!StandardCharsets.US_ASCII.newEncoder().canEncode(name)) { + Controllers.dialog(new MessageDialogPane.Builder( + i18n("install.name.invalid"), + i18n("message.warning"), + MessageDialogPane.MessageType.QUESTION) + .yesOrNo(() -> { + controller.getSettings().put(MODPACK_NAME, name); + controller.getSettings().put(MODPACK_CHARSET, charset); + controller.onFinish(); + }, () -> { + // The user selects Cancel and does nothing. + }) + .build()); + } else { + controller.getSettings().put(MODPACK_NAME, name); + controller.getSettings().put(MODPACK_CHARSET, charset); + controller.onFinish(); + } } protected void onDescribe() { - if (manifest != null) { - FXUtils.showWebDialog(i18n("modpack.description"), manifest.getDescription()); - } + if (manifest != null) + Controllers.navigate(new WebPage(i18n("modpack.description"), manifest.getDescription())); } public static final String MODPACK_FILE = "MODPACK_FILE"; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackPage.java index 2240b5d18f..107429813b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackPage.java @@ -22,9 +22,9 @@ public abstract class ModpackPage extends SpinnerPane implements WizardPage { protected final Label lblName; protected final Label lblVersion; protected final Label lblAuthor; - protected final Label lblModpackLocation; protected final JFXTextField txtModpackName; protected final JFXButton btnInstall; + protected final JFXButton btnDescription; protected ModpackPage(WizardController controller) { this.controller = controller; @@ -37,15 +37,6 @@ protected ModpackPage(WizardController controller) { ComponentList componentList = new ComponentList(); { - BorderPane locationPane = new BorderPane(); - { - locationPane.setLeft(new Label(i18n("modpack.task.install.will"))); - - lblModpackLocation = new Label(); - BorderPane.setAlignment(lblModpackLocation, Pos.CENTER_RIGHT); - locationPane.setCenter(lblModpackLocation); - } - BorderPane archiveNamePane = new BorderPane(); { Label label = new Label(i18n("archive.file.name")); @@ -87,7 +78,7 @@ protected ModpackPage(WizardController controller) { BorderPane descriptionPane = new BorderPane(); { - JFXButton btnDescription = new JFXButton(i18n("modpack.description")); + btnDescription = new JFXButton(i18n("modpack.description")); btnDescription.getStyleClass().add("jfx-button-border"); btnDescription.setOnAction(e -> onDescribe()); descriptionPane.setLeft(btnDescription); @@ -99,7 +90,7 @@ protected ModpackPage(WizardController controller) { } componentList.getContent().setAll( - locationPane, archiveNamePane, modpackNamePane, versionPane, authorPane, descriptionPane); + archiveNamePane, modpackNamePane, versionPane, authorPane, descriptionPane); } borderPane.getChildren().setAll(componentList); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/RemoteModpackPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/RemoteModpackPage.java index b38c663462..1123f3fd8a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/RemoteModpackPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/RemoteModpackPage.java @@ -22,11 +22,12 @@ import org.jackhuang.hmcl.mod.server.ServerModpackManifest; import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.ui.Controllers; -import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.WebPage; import org.jackhuang.hmcl.ui.construct.MessageDialogPane; import org.jackhuang.hmcl.ui.construct.RequiredValidator; import org.jackhuang.hmcl.ui.construct.Validator; import org.jackhuang.hmcl.ui.wizard.WizardController; +import org.jackhuang.hmcl.util.StringUtils; import java.io.IOException; import java.util.Map; @@ -43,7 +44,6 @@ public RemoteModpackPage(WizardController controller) { manifest = tryCast(controller.getSettings().get(MODPACK_SERVER_MANIFEST), ServerModpackManifest.class) .orElseThrow(() -> new IllegalStateException("MODPACK_SERVER_MANIFEST should exist")); - lblModpackLocation.setText(manifest.getFileApi()); try { controller.getSettings().put(MODPACK_MANIFEST, manifest.toModpack(null)); @@ -70,6 +70,8 @@ public RemoteModpackPage(WizardController controller) { new Validator(i18n("install.new_game.already_exists"), str -> !profile.getRepository().versionIdConflicts(str)), new Validator(i18n("install.new_game.malformed"), HMCLGameRepository::isValidVersionId)); } + + btnDescription.setVisible(StringUtils.isNotBlank(manifest.getDescription())); } @Override @@ -84,7 +86,7 @@ protected void onInstall() { } protected void onDescribe() { - FXUtils.showWebDialog(i18n("modpack.description"), manifest.getDescription()); + Controllers.navigate(new WebPage(i18n("modpack.description"), manifest.getDescription())); } public static final String MODPACK_SERVER_MANIFEST = "MODPACK_SERVER_MANIFEST"; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java index e919424ed3..754ad121c1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java @@ -21,12 +21,18 @@ import com.jfoenix.controls.JFXCheckBox; import com.jfoenix.controls.JFXListView; import com.jfoenix.controls.JFXSpinner; +import com.jfoenix.controls.JFXTextField; +import javafx.animation.PauseTransition; import javafx.application.Platform; import javafx.beans.InvalidationListener; import javafx.geometry.Insets; +import javafx.geometry.Pos; import javafx.scene.control.Label; import javafx.scene.control.ListCell; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; import javafx.scene.layout.*; +import javafx.util.Duration; import org.jackhuang.hmcl.download.DownloadProvider; import org.jackhuang.hmcl.download.RemoteVersion; import org.jackhuang.hmcl.download.VersionList; @@ -55,14 +61,20 @@ import org.jackhuang.hmcl.util.Holder; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.function.Predicate; +import java.util.regex.Pattern; import java.util.stream.Collectors; +import static org.jackhuang.hmcl.ui.FXUtils.ignoreEvent; +import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed; import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.wrap; import static org.jackhuang.hmcl.util.logging.Logger.LOG; import static org.jackhuang.hmcl.util.i18n.I18n.formatDateTime; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; +import static org.jackhuang.hmcl.util.StringUtils.isBlank; public final class VersionsPage extends BorderPane implements WizardPage, Refreshable { private final String gameVersion; @@ -86,6 +98,11 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres private final VersionList versionList; private CompletableFuture executor; + private final TransitionPane toolbarPane; + private final HBox searchBar; + private final JFXTextField searchField; + private boolean isSearching = false; + public VersionsPage(Navigation navigation, String title, String gameVersion, DownloadProvider downloadProvider, String libraryId, Runnable callback) { this.title = title; this.gameVersion = gameVersion; @@ -95,7 +112,7 @@ public VersionsPage(Navigation navigation, String title, String gameVersion, Dow HintPane hintPane = new HintPane(); hintPane.setText(i18n("sponsor.bmclapi")); hintPane.getStyleClass().add("sponsor-pane"); - hintPane.setOnMouseClicked(e -> onSponsor()); + FXUtils.onClicked(hintPane, this::onSponsor); BorderPane.setMargin(hintPane, new Insets(10, 10, 0, 10)); this.setTop(hintPane); @@ -136,6 +153,9 @@ public VersionsPage(Navigation navigation, String title, String gameVersion, Dow list.getStyleClass().add("jfx-list-view-float"); VBox.setVgrow(list, Priority.ALWAYS); + // ListViewBehavior would consume ESC pressed event, preventing us from handling it, so we ignore it here + ignoreEvent(list, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE); + centrePane.getContent().setAll(checkPane, list); } @@ -146,7 +166,7 @@ public VersionsPage(Navigation navigation, String title, String gameVersion, Dow failedPane.getStyleClass().add("notice-pane"); { Label label = new Label(i18n("download.failed.refresh")); - label.setOnMouseClicked(e -> onRefresh()); + FXUtils.onClicked(label, this::onRefresh); failedPane.getChildren().setAll(label); } @@ -155,7 +175,7 @@ public VersionsPage(Navigation navigation, String title, String gameVersion, Dow emptyPane.getStyleClass().add("notice-pane"); { Label label = new Label(i18n("download.failed.empty")); - label.setOnMouseClicked(e -> onBack()); + FXUtils.onClicked(label, this::onBack); emptyPane.getChildren().setAll(label); } @@ -163,7 +183,15 @@ public VersionsPage(Navigation navigation, String title, String gameVersion, Dow this.setCenter(root); versionList = downloadProvider.getVersionListById(libraryId); - if (versionList.hasType()) { + boolean hasType = versionList.hasType(); + chkRelease.setManaged(hasType); + chkRelease.setVisible(hasType); + chkSnapshot.setManaged(hasType); + chkSnapshot.setVisible(hasType); + chkOld.setManaged(hasType); + chkOld.setVisible(hasType); + + if (hasType) { centrePane.getContent().setAll(checkPane, list); } else { centrePane.getContent().setAll(list); @@ -178,15 +206,59 @@ public VersionsPage(Navigation navigation, String title, String gameVersion, Dow btnRefresh.setGraphic(wrap(SVG.REFRESH.createIcon(Theme.blackFill(), -1, -1))); Holder lastCell = new Holder<>(); - list.setCellFactory(listView -> new RemoteVersionListCell(lastCell)); + list.setCellFactory(listView -> new RemoteVersionListCell(lastCell, libraryId)); - list.setOnMouseClicked(e -> { + FXUtils.onClicked(list, () -> { if (list.getSelectionModel().getSelectedIndex() < 0) return; navigation.getSettings().put(libraryId, list.getSelectionModel().getSelectedItem()); callback.run(); }); + { + toolbarPane = new TransitionPane(); + searchBar = new HBox(); + searchBar.setAlignment(Pos.CENTER); + searchBar.setPadding(new Insets(0, 5, 0, 5)); + searchField = new JFXTextField(); + searchField.setPromptText(i18n("search")); + HBox.setHgrow(searchField, Priority.ALWAYS); + + JFXButton closeSearchBar = new JFXButton(); + closeSearchBar.getStyleClass().add("jfx-tool-bar-button"); + closeSearchBar.setGraphic(wrap(SVG.CLOSE.createIcon(Theme.blackFill(), -1, -1))); + closeSearchBar.setOnAction(e -> { + toolbarPane.setContent(checkPane, ContainerAnimations.FADE); + isSearching = false; + searchField.clear(); + list.getItems().setAll(loadVersions()); + }); + onEscPressed(searchField, closeSearchBar::fire); + + searchBar.getChildren().setAll(searchField, closeSearchBar); + + JFXButton searchButton = new JFXButton(i18n("search")); + searchButton.getStyleClass().add("jfx-tool-bar-button"); + searchButton.setGraphic(wrap(SVG.MAGNIFY.createIcon(Theme.blackFill(), -1, -1))); + searchButton.setOnAction(e -> { + toolbarPane.setContent(searchBar, ContainerAnimations.FADE); + searchField.requestFocus(); + }); + + checkPane.getChildren().add(checkPane.getChildren().size() - 1, searchButton); + + centrePane.getContent().remove(checkPane); + toolbarPane.setContent(checkPane, ContainerAnimations.FADE); + centrePane.getContent().add(0, toolbarPane); + + PauseTransition pause = new PauseTransition(Duration.millis(100)); + pause.setOnFinished(e -> search()); + searchField.textProperty().addListener((observable, oldValue, newValue) -> { + pause.setRate(1); + pause.playFromStart(); + }); + } + refresh(); } @@ -210,7 +282,7 @@ private List loadVersions() { @Override public void refresh() { VersionList currentVersionList = versionList; - root.setContent(spinner, ContainerAnimations.FADE.getAnimationProducer()); + root.setContent(spinner, ContainerAnimations.FADE); executor = currentVersionList.refreshAsync(gameVersion).whenComplete((result, exception) -> { if (exception == null) { List items = loadVersions(); @@ -218,7 +290,7 @@ public void refresh() { Platform.runLater(() -> { if (versionList != currentVersionList) return; if (currentVersionList.getVersions(gameVersion).isEmpty()) { - root.setContent(emptyPane, ContainerAnimations.FADE.getAnimationProducer()); + root.setContent(emptyPane, ContainerAnimations.FADE); } else { if (items.isEmpty()) { chkRelease.setSelected(true); @@ -227,14 +299,14 @@ public void refresh() { } else { list.getItems().setAll(items); } - root.setContent(center, ContainerAnimations.FADE.getAnimationProducer()); + root.setContent(center, ContainerAnimations.FADE); } }); } else { LOG.warning("Failed to fetch versions list", exception); Platform.runLater(() -> { if (versionList != currentVersionList) return; - root.setContent(failedPane, ContainerAnimations.FADE.getAnimationProducer()); + root.setContent(failedPane, ContainerAnimations.FADE); }); } @@ -265,6 +337,37 @@ private void onSponsor() { FXUtils.openLink("https://bmclapidoc.bangbang93.com"); } + private void search() { + isSearching = true; + String queryString = searchField.getText(); + + if (isBlank(queryString)) { + list.getItems().setAll(loadVersions()); + } else { + list.getItems().clear(); + + Predicate predicate; + if (queryString.startsWith("regex:")) { + try { + Pattern pattern = Pattern.compile(queryString.substring("regex:".length())); + predicate = s -> pattern.matcher(s).find(); + } catch (Throwable e) { + LOG.warning("Illegal regular expression", e); + return; + } + } else { + String lowerQueryString = queryString.toLowerCase(Locale.ROOT); + predicate = s -> s.toLowerCase(Locale.ROOT).contains(lowerQueryString); + } + + for (RemoteVersion version : loadVersions()) { + if (predicate.test(version.getSelfVersion())) { + list.getItems().add(version); + } + } + } + } + private static class RemoteVersionListCell extends ListCell { final IconedTwoLineListItem content = new IconedTwoLineListItem(); final RipplerContainer ripplerContainer = new RipplerContainer(content); @@ -272,8 +375,12 @@ private static class RemoteVersionListCell extends ListCell { private final Holder lastCell; - RemoteVersionListCell(Holder lastCell) { + RemoteVersionListCell(Holder lastCell, String libraryId) { this.lastCell = lastCell; + if ("game".equals(libraryId)) { + content.getExternalLinkButton().setGraphic(SVG.EARTH.createIcon(Theme.blackFill(), -1, -1)); + FXUtils.installFastTooltip(content.getExternalLinkButton(), i18n("wiki.tooltip")); + } pane.getStyleClass().add("md-list-cell"); StackPane.setMargin(content, new Insets(10, 16, 10, 16)); @@ -307,14 +414,19 @@ public void updateItem(RemoteVersion remoteVersion, boolean empty) { case RELEASE: content.getTags().setAll(i18n("version.game.release")); content.setImage(VersionIconType.GRASS.getIcon()); + content.setExternalLink(i18n("wiki.version.game.release", remoteVersion.getGameVersion())); break; case SNAPSHOT: content.getTags().setAll(i18n("version.game.snapshot")); content.setImage(VersionIconType.COMMAND.getIcon()); + + + content.setExternalLink(i18n("wiki.version.game.snapshot", remoteVersion.getGameVersion())); break; default: content.getTags().setAll(i18n("version.game.old")); content.setImage(VersionIconType.CRAFT_TABLE.getIcon()); + content.setExternalLink(null); break; } } else { @@ -339,6 +451,7 @@ else if (remoteVersion instanceof QuiltRemoteVersion || remoteVersion instanceof content.setSubtitle(remoteVersion.getGameVersion()); else content.getTags().setAll(remoteVersion.getGameVersion()); + content.setExternalLink(null); } } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ExportWizardProvider.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ExportWizardProvider.java index c4087f0387..aa6c04bd69 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ExportWizardProvider.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ExportWizardProvider.java @@ -29,6 +29,7 @@ import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.setting.VersionSetting; import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.wizard.WizardController; import org.jackhuang.hmcl.ui.wizard.WizardProvider; import org.jackhuang.hmcl.util.Lang; @@ -129,17 +130,13 @@ public void execute() throws Exception { if (bg.isDirectory()) zip.putDirectory(bg.toPath(), "bg"); - File background_png = new File("background.png").getAbsoluteFile(); - if (background_png.isFile()) - zip.putFile(background_png, "background.png"); + for (String extension : FXUtils.IMAGE_EXTENSIONS) { + String fileName = "background." + extension; - File background_jpg = new File("background.jpg").getAbsoluteFile(); - if (background_jpg.isFile()) - zip.putFile(background_jpg, "background.jpg"); - - File background_gif = new File("background.gif").getAbsoluteFile(); - if (background_gif.isFile()) - zip.putFile(background_gif, "background.gif"); + File background = new File(fileName).getAbsoluteFile(); + if (background.isFile()) + zip.putFile(background, "background.png"); + } zip.putFile(launcherJar, launcherJar.getFileName().toString()); } @@ -195,7 +192,8 @@ public void execute() { /* overrideJavaArgs */ true, /* overrideConsole */ true, /* overrideCommands */ true, - /* overrideWindow */ true + /* overrideWindow */ true, + /* iconKey */ null // TODO ), modpackFile); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackInfoPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackInfoPage.java index e3908c5b91..024c0f40b9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackInfoPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackInfoPage.java @@ -21,12 +21,10 @@ import javafx.beans.binding.Bindings; import javafx.beans.property.*; import javafx.collections.ObservableList; -import javafx.event.EventHandler; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.*; -import javafx.scene.input.MouseEvent; import javafx.scene.layout.*; import javafx.stage.FileChooser; import org.jackhuang.hmcl.auth.Account; @@ -78,7 +76,6 @@ public final class ModpackInfoPage extends Control implements WizardPage { private final SimpleStringProperty authlibInjectorServer = new SimpleStringProperty(); private final SimpleStringProperty launchArguments = new SimpleStringProperty(""); private final SimpleStringProperty javaArguments = new SimpleStringProperty(""); - private final ObjectProperty> next = new SimpleObjectProperty<>(); private final SimpleStringProperty mcbbsThreadId = new SimpleStringProperty(""); public ModpackInfoPage(WizardController controller, HMCLGameRepository gameRepository, String version) { @@ -97,8 +94,6 @@ public ModpackInfoPage(WizardController controller, HMCLGameRepository gameRepos javaArguments.set(versionSetting.getJavaArgs()); canIncludeLauncher = JarUtils.thisJarPath() != null; - - next.set(e -> onNext()); } private void onNext() { @@ -178,9 +173,7 @@ public ModpackInfoPageSkin(ModpackInfoPage skinnable) { if (skinnable.controller.getSettings().get(MODPACK_TYPE) == MODPACK_TYPE_SERVER) { Hyperlink hyperlink = new Hyperlink(i18n("modpack.wizard.step.initialization.server")); - hyperlink.setOnMouseClicked(e -> { - FXUtils.openLink("https://docs.hmcl.net/modpack/serverpack.html"); - }); + hyperlink.setOnAction(e -> FXUtils.openLink("https://docs.hmcl.net/modpack/serverpack.html")); borderPane.setTop(hyperlink); } else { HintPane pane = new HintPane(MessageDialogPane.MessageType.INFO); @@ -378,7 +371,7 @@ public ModpackInfoPageSkin(ModpackInfoPage skinnable) { borderPane.setBottom(hbox); JFXButton nextButton = FXUtils.newRaisedButton(i18n("wizard.next")); - nextButton.onMouseClickedProperty().bind(skinnable.next); + nextButton.setOnAction(e -> skinnable.onNext()); nextButton.setPrefWidth(100); nextButton.setPrefHeight(40); nextButton.disableProperty().bind( diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java index 0c3e8c78ff..f250384566 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java @@ -17,18 +17,31 @@ */ package org.jackhuang.hmcl.ui.main; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; import javafx.geometry.Insets; import javafx.scene.control.ScrollPane; +import javafx.scene.image.Image; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.construct.ComponentList; import org.jackhuang.hmcl.ui.construct.IconedTwoLineListItem; +import org.jackhuang.hmcl.util.gson.JsonUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.StandardCharsets; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; -public class AboutPage extends StackPane { +public final class AboutPage extends StackPane { public AboutPage() { ComponentList about = new ComponentList(); @@ -48,131 +61,9 @@ public AboutPage() { about.getContent().setAll(launcher, author); } - ComponentList thanks = new ComponentList(); - { - IconedTwoLineListItem yushijinhun = new IconedTwoLineListItem(); - yushijinhun.setImage(FXUtils.newBuiltinImage("/assets/img/yushijinhun.png")); - yushijinhun.setTitle("yushijinhun"); - yushijinhun.setSubtitle(i18n("about.thanks_to.yushijinhun.statement")); - yushijinhun.setExternalLink("https://yushi.moe/"); - - IconedTwoLineListItem bangbang93 = new IconedTwoLineListItem(); - bangbang93.setImage(FXUtils.newBuiltinImage("/assets/img/bangbang93.png")); - bangbang93.setTitle("bangbang93"); - bangbang93.setSubtitle(i18n("about.thanks_to.bangbang93.statement")); - bangbang93.setExternalLink("https://bmclapi2.bangbang93.com/"); - - IconedTwoLineListItem glavo = new IconedTwoLineListItem(); - glavo.setImage(FXUtils.newBuiltinImage("/assets/img/glavo.png")); - glavo.setTitle("Glavo"); - glavo.setSubtitle(i18n("about.thanks_to.glavo.statement")); - glavo.setExternalLink("https://github.com/Glavo"); - - IconedTwoLineListItem zekerzhayard = new IconedTwoLineListItem(); - zekerzhayard.setImage(FXUtils.newBuiltinImage("/assets/img/zekerzhayard.png")); - zekerzhayard.setTitle("ZekerZhayard"); - zekerzhayard.setSubtitle(i18n("about.thanks_to.zekerzhayard.statement")); - zekerzhayard.setExternalLink("https://github.com/ZekerZhayard"); - - IconedTwoLineListItem zkitefly = new IconedTwoLineListItem(); - zkitefly.setImage(FXUtils.newBuiltinImage("/assets/img/zkitefly.png")); - zkitefly.setTitle("Zkitefly"); - zkitefly.setSubtitle(i18n("about.thanks_to.zkitefly.statement")); - zkitefly.setExternalLink("https://github.com/zkitefly"); - - IconedTwoLineListItem burningtnt = new IconedTwoLineListItem(); - burningtnt.setImage(FXUtils.newBuiltinImage("/assets/img/burningtnt.png")); - burningtnt.setTitle("Burning_TNT"); - burningtnt.setSubtitle(i18n("about.thanks_to.burningtnt.statement")); - burningtnt.setExternalLink("https://github.com/burningtnt"); - - IconedTwoLineListItem shulkerSakura = new IconedTwoLineListItem(); - shulkerSakura.setTitle("ShulkerSakura"); - shulkerSakura.setImage(FXUtils.newBuiltinImage("/assets/img/ShulkerSakura.png")); - shulkerSakura.setSubtitle(i18n("about.thanks_to.shulkersakura.statement")); - shulkerSakura.setExternalLink("https://github.com/ShulkerSakura"); - - IconedTwoLineListItem gamerteam = new IconedTwoLineListItem(); - gamerteam.setTitle("gamerteam"); - gamerteam.setImage(FXUtils.newBuiltinImage("/assets/img/gamerteam.png")); - gamerteam.setSubtitle(i18n("about.thanks_to.gamerteam.statement")); - gamerteam.setExternalLink("http://www.zhaisoul.com/"); - - IconedTwoLineListItem redLnn = new IconedTwoLineListItem(); - redLnn.setTitle("Red_lnn"); - redLnn.setImage(FXUtils.newBuiltinImage("/assets/img/red_lnn.png")); - redLnn.setSubtitle(i18n("about.thanks_to.red_lnn.statement")); - - IconedTwoLineListItem mcmod = new IconedTwoLineListItem(); - mcmod.setImage(FXUtils.newBuiltinImage("/assets/img/mcmod.png")); - mcmod.setTitle(i18n("about.thanks_to.mcmod")); - mcmod.setSubtitle(i18n("about.thanks_to.mcmod.statement")); - mcmod.setExternalLink("https://www.mcmod.cn/"); - - IconedTwoLineListItem mcbbs = new IconedTwoLineListItem(); - mcbbs.setImage(FXUtils.newBuiltinImage("/assets/img/chest.png")); - mcbbs.setTitle(i18n("about.thanks_to.mcbbs")); - mcbbs.setSubtitle(i18n("about.thanks_to.mcbbs.statement")); - - IconedTwoLineListItem contributors = new IconedTwoLineListItem(); - contributors.setImage(FXUtils.newBuiltinImage("/assets/img/github.png")); - contributors.setTitle(i18n("about.thanks_to.contributors")); - contributors.setSubtitle(i18n("about.thanks_to.contributors.statement")); - contributors.setExternalLink("https://github.com/HMCL-dev/HMCL/graphs/contributors"); - - IconedTwoLineListItem users = new IconedTwoLineListItem(); - users.setImage(FXUtils.newBuiltinImage("/assets/img/icon.png")); - users.setTitle(i18n("about.thanks_to.users")); - users.setSubtitle(i18n("about.thanks_to.users.statement")); - users.setExternalLink("https://docs.hmcl.net/groups.html"); - - thanks.getContent().setAll(yushijinhun, bangbang93, glavo, zekerzhayard, zkitefly, burningtnt, mcmod, mcbbs, shulkerSakura, gamerteam, redLnn, contributors, users); - } + ComponentList thanks = loadIconedTwoLineList("/assets/about/thanks.json"); - ComponentList dep = new ComponentList(); - { - IconedTwoLineListItem javafx = new IconedTwoLineListItem(); - javafx.setTitle("JavaFX"); - javafx.setSubtitle("Copyright © 2013, 2024, Oracle and/or its affiliates.\nLicensed under the GPL 2 with Classpath Exception."); - javafx.setExternalLink("https://openjfx.io/"); - - IconedTwoLineListItem jfoenix = new IconedTwoLineListItem(); - jfoenix.setTitle("JFoenix"); - jfoenix.setSubtitle("Copyright © 2016 JFoenix.\nLicensed under the MIT License."); - jfoenix.setExternalLink("https://github.com/sshahine/JFoenix"); - - IconedTwoLineListItem gson = new IconedTwoLineListItem(); - gson.setTitle("Gson"); - gson.setSubtitle("Copyright © 2008 Google Inc.\nLicensed under the Apache 2.0 License."); - gson.setExternalLink("https://github.com/google/gson"); - - IconedTwoLineListItem xz = new IconedTwoLineListItem(); - xz.setTitle("XZ for Java"); - xz.setSubtitle("Lasse Collin, Igor Pavlov, and/or Brett Okken.\nPublic Domain."); - xz.setExternalLink("https://tukaani.org/xz/java.html"); - - IconedTwoLineListItem fxgson = new IconedTwoLineListItem(); - fxgson.setTitle("fx-gson"); - fxgson.setSubtitle("Copyright © 2016 Joffrey Bion.\nLicensed under the MIT License."); - fxgson.setExternalLink("https://github.com/joffrey-bion/fx-gson"); - - IconedTwoLineListItem constantPoolScanner = new IconedTwoLineListItem(); - constantPoolScanner.setTitle("Constant Pool Scanner"); - constantPoolScanner.setSubtitle("Copyright © 1997-2010 Oracle and/or its affiliates.\nLicensed under the GPL 2 or the CDDL."); - constantPoolScanner.setExternalLink("https://github.com/jenkinsci/constant-pool-scanner"); - - IconedTwoLineListItem openNBT = new IconedTwoLineListItem(); - openNBT.setTitle("OpenNBT"); - openNBT.setSubtitle("Copyright © 2013-2021 Steveice10.\nLicensed under the MIT License."); - openNBT.setExternalLink("https://github.com/GeyserMC/OpenNBT"); - - IconedTwoLineListItem minecraftJFXSkin = new IconedTwoLineListItem(); - minecraftJFXSkin.setTitle("minecraft-jfx-skin"); - minecraftJFXSkin.setSubtitle("Copyright © 2016 InfinityStudio.\nLicensed under the GPL 3."); - minecraftJFXSkin.setExternalLink("https://github.com/InfinityStudio/minecraft-jfx-skin"); - - dep.getContent().setAll(javafx, jfoenix, gson, xz, fxgson, constantPoolScanner, openNBT, minecraftJFXSkin); - } + ComponentList deps = loadIconedTwoLineList("/assets/about/deps.json"); ComponentList legal = new ComponentList(); { @@ -204,7 +95,7 @@ public AboutPage() { thanks, ComponentList.createComponentListTitle(i18n("about.dependency")), - dep, + deps, ComponentList.createComponentListTitle(i18n("about.legal")), legal @@ -216,4 +107,49 @@ public AboutPage() { FXUtils.smoothScrolling(scrollPane); getChildren().setAll(scrollPane); } + + private static ComponentList loadIconedTwoLineList(String path) { + ComponentList componentList = new ComponentList(); + + InputStream input = FXUtils.class.getResourceAsStream(path); + if (input == null) { + LOG.warning("Resources not found: " + path); + return componentList; + } + + try (Reader reader = new InputStreamReader(input, StandardCharsets.UTF_8)) { + JsonArray array = JsonUtils.GSON.fromJson(reader, JsonArray.class); + + for (JsonElement element : array) { + JsonObject obj = element.getAsJsonObject(); + IconedTwoLineListItem item = new IconedTwoLineListItem(); + + if (obj.has("image")) { + String image = obj.get("image").getAsString(); + item.setImage(image.startsWith("/") + ? FXUtils.newBuiltinImage(image) + : new Image(image)); + } + + if (obj.has("title")) + item.setTitle(obj.get("title").getAsString()); + else if (obj.has("titleLocalized")) + item.setTitle(i18n(obj.get("titleLocalized").getAsString())); + + if (obj.has("subtitle")) + item.setSubtitle(obj.get("subtitle").getAsString()); + else if (obj.has("subtitleLocalized")) + item.setSubtitle(i18n(obj.get("subtitleLocalized").getAsString())); + + if (obj.has("externalLink")) + item.setExternalLink(obj.get("externalLink").getAsString()); + + componentList.getContent().add(item); + } + } catch (IOException | JsonParseException e) { + LOG.warning("Failed to load list: " + path, e); + } + + return componentList; + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/DownloadSettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/DownloadSettingsPage.java index 23abc00563..695c89ddfa 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/DownloadSettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/DownloadSettingsPage.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.ui.main; import com.jfoenix.controls.*; -import javafx.geometry.HPos; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.Label; @@ -100,7 +99,7 @@ public DownloadSettingsPage() { downloadSource.getChildren().setAll(chooseWrapper, versionListSourcePane, downloadSourcePane); } - content.getChildren().addAll(ComponentList.createComponentListTitle(i18n("settings.launcher.version_list_source")), downloadSource); + content.getChildren().addAll(ComponentList.createComponentListTitle(i18n("settings.launcher.download_source")), downloadSource); } { @@ -204,7 +203,6 @@ public DownloadSettingsPage() { Label host = new Label(i18n("settings.launcher.proxy.host")); GridPane.setRowIndex(host, 1); GridPane.setColumnIndex(host, 0); - GridPane.setHalignment(host, HPos.RIGHT); gridPane.getChildren().add(host); } @@ -220,7 +218,6 @@ public DownloadSettingsPage() { Label port = new Label(i18n("settings.launcher.proxy.port")); GridPane.setRowIndex(port, 2); GridPane.setColumnIndex(port, 0); - GridPane.setHalignment(port, HPos.RIGHT); gridPane.getChildren().add(port); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/HelpPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/HelpPage.java index 33f7935e30..75223967c3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/HelpPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/HelpPage.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.ui.main; import com.google.gson.annotations.SerializedName; -import com.google.gson.reflect.TypeToken; import javafx.geometry.Insets; import javafx.scene.control.ScrollPane; import javafx.scene.layout.VBox; @@ -34,6 +33,7 @@ import java.util.Collections; import java.util.List; +import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public class HelpPage extends SpinnerPane { @@ -63,8 +63,7 @@ public HelpPage() { private void loadHelp() { showSpinner(); - Task.>supplyAsync(() -> HttpRequest.GET("https://docs.hmcl.net/index.json").getJson(new TypeToken>() { - }.getType())) + Task.supplyAsync(() -> HttpRequest.GET("https://docs.hmcl.net/index.json").getJson(listTypeOf(HelpCategory.class))) .thenAcceptAsync(Schedulers.javafx(), helpCategories -> { for (HelpCategory category : helpCategories) { ComponentList categoryPane = new ComponentList(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaDownloadDialog.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaDownloadDialog.java index 7e4e80f66d..f5d27ea2e5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaDownloadDialog.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaDownloadDialog.java @@ -21,7 +21,6 @@ import javafx.beans.binding.Bindings; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; -import javafx.beans.value.ChangeListener; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.scene.control.Label; @@ -46,16 +45,18 @@ import org.jackhuang.hmcl.ui.construct.DialogPane; import org.jackhuang.hmcl.ui.construct.JFXHyperlink; import org.jackhuang.hmcl.ui.wizard.SinglePageWizardProvider; -import org.jackhuang.hmcl.util.Pair; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.TaskCancellationAction; import org.jackhuang.hmcl.util.gson.JsonUtils; +import org.jackhuang.hmcl.util.platform.Architecture; +import org.jackhuang.hmcl.util.platform.OperatingSystem; import org.jackhuang.hmcl.util.platform.Platform; import java.io.File; import java.io.IOException; import java.util.*; import java.util.concurrent.CancellationException; +import java.util.function.Consumer; import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed; import static org.jackhuang.hmcl.util.Lang.resolveException; @@ -118,6 +119,8 @@ private final class DownloadMojangJava extends DialogPane { button.setUserData(version); vbox.getChildren().add(button); toggleGroup.getToggles().add(button); + if (JavaManager.REPOSITORY.isInstalled(platform, version)) + button.setDisable(true); } setBody(vbox); @@ -166,19 +169,6 @@ protected void onAccept() { } private final class DownloadDiscoJava extends JFXDialogLayout { - - private boolean isLTS(int major) { - if (major <= 8) { - return true; - } - - if (major < 21) { - return major == 11 || major == 17; - } - - return major % 4 == 1; - } - private final JFXComboBox distributionBox; private final JFXComboBox remoteVersionBox; private final JFXComboBox packageTypeBox; @@ -189,11 +179,8 @@ private boolean isLTS(int major) { private final DownloadProvider downloadProvider = DownloadProviders.getDownloadProvider(); - private final ObjectProperty currentDiscoJavaVersionList = new SimpleObjectProperty<>(); - - private final Map, DiscoJavaVersionList> javaVersionLists = new HashMap<>(); - - private boolean changingDistribution = false; + private final Map javaVersionLists = new HashMap<>(); + private final ObjectProperty currentJavaVersionList = new SimpleObjectProperty<>(); DownloadDiscoJava() { assert !distributions.isEmpty(); @@ -204,7 +191,7 @@ private boolean isLTS(int major) { this.remoteVersionBox = new JFXComboBox<>(); this.remoteVersionBox.setConverter(FXUtils.stringConverter(JavaRemoteVersion::getDistributionVersion)); - this.packageTypeBox = new JFXComboBox<>(); + this.packageTypeBox = new JFXComboBox<>(FXCollections.observableArrayList()); this.downloadButton = new JFXButton(i18n("download")); downloadButton.setOnAction(e -> onDownload()); @@ -227,58 +214,87 @@ private boolean isLTS(int major) { body.addRow(2, new Label(i18n("java.download.packageType")), packageTypeBox); distributionBox.setItems(FXCollections.observableList(new ArrayList<>(distributions))); - ChangeListener updateStatusListener = (observable, oldValue, newValue) -> updateStatus(newValue); - this.currentDiscoJavaVersionList.addListener((observable, oldValue, newValue) -> { - if (oldValue != null) { - oldValue.status.removeListener(updateStatusListener); + + FXUtils.onChange(packageTypeBox.getSelectionModel().selectedItemProperty(), packageType -> { + ObservableList versions; + if (packageType == null + || currentJavaVersionList.get() == null + || (versions = currentJavaVersionList.get().versions.get(packageType)) == null) { + remoteVersionBox.setItems(null); + return; } - if (newValue != null) { - newValue.status.addListener(updateStatusListener); - updateStatus(newValue.status.get()); - } else { - updateStatus(null); + remoteVersionBox.setItems(versions); + + for (int i = 0; i < versions.size(); i++) { + DiscoJavaRemoteVersion version = versions.get(i); + if (version.getJdkVersion() == GameJavaVersion.LATEST.getMajorVersion()) { + remoteVersionBox.getSelectionModel().select(i); + return; + } } + + for (int i = 0; i < versions.size(); i++) { + DiscoJavaRemoteVersion version = versions.get(i); + if (version.isLTS()) { + remoteVersionBox.getSelectionModel().select(i); + return; + } + } + + remoteVersionBox.getSelectionModel().selectFirst(); }); - packageTypeBox.getSelectionModel().selectedItemProperty().addListener(ignored -> updateVersions()); - FXUtils.onChangeAndOperate(distributionBox.getSelectionModel().selectedItemProperty(), distribution -> { - if (distribution != null) { - changingDistribution = true; - packageTypeBox.setItems(FXCollections.observableList(new ArrayList<>(distribution.getSupportedPackageTypes()))); - packageTypeBox.getSelectionModel().select(0); - changingDistribution = false; - updateVersions(); - packageTypeBox.setDisable(false); - remoteVersionBox.setDisable(false); + Consumer updateListStatus = list -> { + remoteVersionBox.setItems(null); + packageTypeBox.getItems().clear(); + remoteVersionBox.setDisable(true); + packageTypeBox.setDisable(true); + warningLabel.setText(null); + + if (list == null || (list.versions != null && list.versions.isEmpty())) + downloadButtonPane.getChildren().setAll(downloadButton); + else if (list.status == DiscoJavaVersionList.Status.LOADING) + downloadButtonPane.getChildren().setAll(new JFXSpinner()); + else { + downloadButtonPane.getChildren().setAll(downloadButton); + + if (list.status == DiscoJavaVersionList.Status.SUCCESS) { + packageTypeBox.getItems().setAll(list.versions.keySet()); + packageTypeBox.getSelectionModel().selectFirst(); + + remoteVersionBox.setDisable(false); + packageTypeBox.setDisable(false); + } else + warningLabel.setText(i18n("java.download.load_list.failed")); + } + }; + + currentJavaVersionList.addListener((observable, oldValue, newValue) -> { + if (oldValue != null) + oldValue.listener = null; + + if (newValue != null) { + updateListStatus.accept(newValue); + + if (newValue.status == DiscoJavaVersionList.Status.LOADING) + newValue.listener = updateListStatus; } else { - packageTypeBox.setItems(null); - updateVersions(); - remoteVersionBox.setItems(null); - packageTypeBox.setDisable(true); - remoteVersionBox.setDisable(true); + currentJavaVersionList.set(null); + updateListStatus.accept(null); } }); + FXUtils.onChange(distributionBox.getSelectionModel().selectedItemProperty(), + it -> currentJavaVersionList.set(getJavaVersionList(it))); + setHeading(new Label(i18n("java.download"))); setBody(body); setActions(warningLabel, downloadButtonPane, cancelButton); - } - - private void updateStatus(DiscoJavaVersionList.Status status) { - if (status == DiscoJavaVersionList.Status.LOADING) { - downloadButtonPane.getChildren().setAll(new JFXSpinner()); - remoteVersionBox.setDisable(true); - warningLabel.setText(null); - } else { - downloadButtonPane.getChildren().setAll(downloadButton); - if (status == DiscoJavaVersionList.Status.SUCCESS || status == null) { - remoteVersionBox.setDisable(false); - warningLabel.setText(null); - } else if (status == DiscoJavaVersionList.Status.FAILED) { - remoteVersionBox.setDisable(true); - warningLabel.setText(i18n("java.download.load_list.failed")); - } + if (platform.getOperatingSystem() == OperatingSystem.LINUX && platform.getArchitecture() == Architecture.RISCV64) { + JFXHyperlink hyperlink = new JFXHyperlink(i18n("java.download.banshanjdk-8")); + hyperlink.setExternalLink("https://www.zthread.cn/#product"); + getActions().add(0, hyperlink); } } @@ -287,6 +303,7 @@ private void onDownload() { DiscoJavaDistribution distribution = distributionBox.getSelectionModel().getSelectedItem(); DiscoJavaRemoteVersion version = remoteVersionBox.getSelectionModel().getSelectedItem(); + JavaPackageType packageType = packageTypeBox.getSelectionModel().getSelectedItem(); if (version == null) return; @@ -312,8 +329,15 @@ private void onDownload() { getIntegrityCheck = Task.completed(new FileDownloadTask.IntegrityCheck(fileInfo.getChecksumType(), fileInfo.getChecksum())); else if (StringUtils.isNotBlank(fileInfo.getChecksumUri())) getIntegrityCheck = new GetTask(downloadProvider.injectURLWithCandidates(fileInfo.getChecksumUri())) - .thenApplyAsync(checksum -> - new FileDownloadTask.IntegrityCheck(fileInfo.getChecksumType(), checksum.trim())); + .thenApplyAsync(checksum -> { + checksum = checksum.trim(); + + int idx = checksum.indexOf(' '); + if (idx > 0) + checksum = checksum.substring(0, idx); + + return new FileDownloadTask.IntegrityCheck(fileInfo.getChecksumType(), checksum); + }); else throw new IOException("Unable to get checksum for file"); @@ -336,7 +360,7 @@ else if (StringUtils.isNotBlank(fileInfo.getChecksumUri())) if (idx > 0) { javaVersion = javaVersion.substring(0, idx); } - String defaultName = distribution.getApiParameter() + "-" + javaVersion; + String defaultName = distribution.getApiParameter() + "-" + javaVersion + "-" + packageType.name().toLowerCase(Locale.ROOT); Controllers.getDecorator().startWizard(new SinglePageWizardProvider(controller -> new JavaInstallPage(controller::onFinish, info, version, updateInfo, defaultName, result))); } else { @@ -350,65 +374,39 @@ else if (StringUtils.isNotBlank(fileInfo.getChecksumUri())) } - private void updateVersions() { - if (changingDistribution) return; - - DiscoJavaDistribution distribution = distributionBox.getSelectionModel().getSelectedItem(); - if (distribution == null) { - this.currentDiscoJavaVersionList.set(null); - return; - } - - JavaPackageType packageType = packageTypeBox.getSelectionModel().getSelectedItem(); - - DiscoJavaVersionList list = javaVersionLists.computeIfAbsent(Pair.pair(distribution, packageType), pair -> { - DiscoJavaVersionList res = new DiscoJavaVersionList(); - new DiscoFetchJavaListTask(downloadProvider, distribution, platform, packageType).setExecutor(Schedulers.io()).thenApplyAsync(versions -> { - if (versions.isEmpty()) return Collections.emptyList(); - - int lastLTS = -1; - for (int v : versions.keySet()) { - if (isLTS(v)) { - lastLTS = v; - } - } - - ArrayList remoteVersions = new ArrayList<>(); - for (Map.Entry entry : versions.entrySet()) { - int v = entry.getKey(); - if (v >= lastLTS || isLTS(v) || v == 16) { - remoteVersions.add(entry.getValue()); - } - } - Collections.reverse(remoteVersions); - return remoteVersions; + private DiscoJavaVersionList getJavaVersionList(DiscoJavaDistribution distribution) { + if (distribution == null) + return null; + return javaVersionLists.computeIfAbsent(distribution, it -> { + DiscoJavaVersionList versionList = new DiscoJavaVersionList(it); + new DiscoFetchJavaListTask(downloadProvider, it, platform).setExecutor(Schedulers.io()).thenApplyAsync(versions -> { + EnumMap> result = new EnumMap<>(JavaPackageType.class); + if (versions.isEmpty()) + return result; + + for (Map.Entry> entry : versions.entrySet()) + for (DiscoJavaRemoteVersion version : entry.getValue().values()) + if (version.isLTS() + || version.getJdkVersion() == entry.getValue().lastKey() // latest version + || version.getJdkVersion() == 16) + result.computeIfAbsent(entry.getKey(), ignored -> FXCollections.observableArrayList()) + .add(version); + + for (List l : result.values()) + Collections.reverse(l); + return result; }).whenComplete(Schedulers.javafx(), ((result, exception) -> { if (exception == null) { - res.status.set(DiscoJavaVersionList.Status.SUCCESS); - res.versions.setAll(result); - selectLTS(res); + versionList.status = DiscoJavaVersionList.Status.SUCCESS; + versionList.versions = result; } else { LOG.warning("Failed to load java list", exception); - res.status.set(DiscoJavaVersionList.Status.FAILED); + versionList.status = DiscoJavaVersionList.Status.FAILED; } + versionList.invalidate(); })).start(); - return res; + return versionList; }); - this.currentDiscoJavaVersionList.set(list); - this.remoteVersionBox.setItems(list.versions); - selectLTS(list); - } - - private void selectLTS(DiscoJavaVersionList list) { - if (remoteVersionBox.getItems() == list.versions) { - for (int i = 0; i < list.versions.size(); i++) { - JavaRemoteVersion item = list.versions.get(i); - if (item.getJdkVersion() == GameJavaVersion.LATEST.getMajorVersion()) { - remoteVersionBox.getSelectionModel().select(i); - break; - } - } - } } } @@ -417,7 +415,19 @@ enum Status { LOADING, SUCCESS, FAILED } - final ObservableList versions = FXCollections.observableArrayList(); - final ObjectProperty status = new SimpleObjectProperty<>(Status.LOADING); + final DiscoJavaDistribution distribution; + + Status status = Status.LOADING; + EnumMap> versions; + Consumer listener; + + DiscoJavaVersionList(DiscoJavaDistribution distribution) { + this.distribution = distribution; + } + + void invalidate() { + if (listener != null) + listener.accept(this); + } } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaManagementPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaManagementPage.java index 86d3a0c2ea..6eb9c2d450 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaManagementPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaManagementPage.java @@ -50,7 +50,6 @@ import org.jackhuang.hmcl.util.platform.Architecture; import org.jackhuang.hmcl.util.platform.OperatingSystem; import org.jackhuang.hmcl.util.platform.Platform; -import org.jackhuang.hmcl.util.tree.TarFileTree; import java.io.*; import java.nio.file.Files; @@ -151,7 +150,7 @@ private void onAddJavaHome(Path file) { private void onInstallArchive(Path file) { Task.supplyAsync(() -> { - try (ArchiveFileTree tree = TarFileTree.open(file)) { + try (ArchiveFileTree tree = ArchiveFileTree.open(file)) { JavaInfo info = JavaInfo.fromArchive(tree); if (!JavaManager.isCompatible(info.getPlatform())) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java index 155459d376..8e59718feb 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java @@ -33,8 +33,9 @@ import org.jackhuang.hmcl.ui.decorator.DecoratorPage; import org.jackhuang.hmcl.ui.versions.VersionSettingsPage; +import java.util.Locale; + import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap; -import static org.jackhuang.hmcl.ui.FXUtils.runInFX; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public class LauncherSettingsPage extends DecoratorAnimatedPage implements DecoratorPage, PageAware { @@ -65,7 +66,7 @@ public LauncherSettingsPage() { gameTab.initializeIfNeeded(); gameTab.getNode().loadVersion(Profiles.getSelectedProfile(), null); FXUtils.onChangeAndOperate(tab.getSelectionModel().selectedItemProperty(), newValue -> { - transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE.getAnimationProducer()); + transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE); }); { @@ -74,7 +75,6 @@ public LauncherSettingsPage() { settingsItem.setTitle(i18n("settings.type.global.manage")); settingsItem.setLeftGraphic(wrap(SVG.GAMEPAD)); settingsItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(gameTab)); - runInFX(() -> FXUtils.installFastTooltip(settingsItem, i18n("settings.type.global.manage"))); settingsItem.setOnAction(e -> tab.select(gameTab)); }) .addNavigationDrawerItem(javaItem -> { @@ -83,7 +83,7 @@ public LauncherSettingsPage() { javaItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(javaManagementTab)); javaItem.setOnAction(e -> tab.select(javaManagementTab)); }) - .startCategory(i18n("launcher")) + .startCategory(i18n("launcher").toUpperCase(Locale.ROOT)) .addNavigationDrawerItem(settingsItem -> { settingsItem.setTitle(i18n("settings.launcher.general")); settingsItem.setLeftGraphic(wrap(SVG.APPLICATION_OUTLINE)); @@ -102,7 +102,7 @@ public LauncherSettingsPage() { downloadItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(downloadTab)); downloadItem.setOnAction(e -> tab.select(downloadTab)); }) - .startCategory(i18n("help")) + .startCategory(i18n("help").toUpperCase(Locale.ROOT)) .addNavigationDrawerItem(helpItem -> { helpItem.setTitle(i18n("help")); helpItem.setLeftGraphic(wrap(SVG.HELP_CIRCLE_OUTLINE)); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java index 6f1cf7f176..7cded1d271 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java @@ -32,7 +32,6 @@ import javafx.scene.control.Label; import javafx.scene.image.ImageView; import javafx.scene.layout.HBox; -import javafx.scene.layout.Pane; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import javafx.scene.shape.Rectangle; @@ -46,6 +45,8 @@ import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.animation.AnimationUtils; +import org.jackhuang.hmcl.ui.animation.ContainerAnimations; +import org.jackhuang.hmcl.ui.animation.TransitionPane; import org.jackhuang.hmcl.ui.construct.AnnouncementCard; import org.jackhuang.hmcl.ui.construct.MessageDialogPane; import org.jackhuang.hmcl.ui.construct.PopupMenu; @@ -65,7 +66,6 @@ import static org.jackhuang.hmcl.setting.ConfigHolder.config; import static org.jackhuang.hmcl.ui.FXUtils.SINE; -import static org.jackhuang.hmcl.ui.FXUtils.runInFX; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public final class MainPage extends StackPane implements DecoratorPage { @@ -83,7 +83,7 @@ public final class MainPage extends StackPane implements DecoratorPage { private final ObservableList versionNodes; private Profile profile; - private VBox announcementPane; + private TransitionPane announcementPane; private final StackPane updatePane; private final JFXButton menuButton; @@ -102,13 +102,23 @@ public final class MainPage extends StackPane implements DecoratorPage { setPadding(new Insets(20)); if (Metadata.isNightly() || (Metadata.isDev() && !Objects.equals(Metadata.VERSION, config().getShownTips().get(ANNOUNCEMENT)))) { - announcementPane = new VBox(16); + AnnouncementCard announcementCard = null; + if (Metadata.isNightly()) { - announcementPane.getChildren().add(new AnnouncementCard(i18n("update.channel.nightly.title"), i18n("update.channel.nightly.hint"))); + announcementCard = new AnnouncementCard(i18n("update.channel.nightly.title"), i18n("update.channel.nightly.hint"), null); } else if (Metadata.isDev()) { - announcementPane.getChildren().add(new AnnouncementCard(i18n("update.channel.dev.title"), i18n("update.channel.dev.hint"))); + announcementCard = new AnnouncementCard(i18n("update.channel.dev.title"), i18n("update.channel.dev.hint"), this::hideAnnouncementPane); + } + + if (announcementCard != null) { + VBox announcementBox = new VBox(16); + announcementBox.getChildren().add(announcementCard); + + announcementPane = new TransitionPane(); + announcementPane.setContent(announcementBox, ContainerAnimations.NONE); + + getChildren().add(announcementPane); } - getChildren().add(announcementPane); } updatePane = new StackPane(); @@ -117,7 +127,7 @@ public final class MainPage extends StackPane implements DecoratorPage { FXUtils.setLimitWidth(updatePane, 230); FXUtils.setLimitHeight(updatePane, 55); StackPane.setAlignment(updatePane, Pos.TOP_RIGHT); - updatePane.setOnMouseClicked(e -> onUpgrade()); + FXUtils.onClicked(updatePane, this::onUpgrade); FXUtils.onChange(showUpdateProperty(), this::showUpdate); { @@ -144,7 +154,7 @@ public final class MainPage extends StackPane implements DecoratorPage { StackPane.setAlignment(closeUpdateButton, Pos.TOP_RIGHT); closeUpdateButton.getStyleClass().add("toggle-icon-tiny"); StackPane.setMargin(closeUpdateButton, new Insets(5)); - closeUpdateButton.setOnMouseClicked(e -> closeUpdateBubble()); + closeUpdateButton.setOnAction(e -> closeUpdateBubble()); updatePane.getChildren().setAll(hBox, closeUpdateButton); } @@ -206,14 +216,14 @@ public final class MainPage extends StackPane implements DecoratorPage { menuButton.setPrefWidth(230); //menuButton.setButtonType(JFXButton.ButtonType.RAISED); menuButton.setStyle("-fx-font-size: 15px;"); - menuButton.setOnMouseClicked(e -> onMenu()); + menuButton.setOnAction(e -> onMenu()); menuButton.setClip(new Rectangle(211, -100, 100, 200)); StackPane graphic = new StackPane(); Node svg = SVG.TRIANGLE.createIcon(Theme.foregroundFillBinding(), 10, 10); StackPane.setAlignment(svg, Pos.CENTER_RIGHT); graphic.getChildren().setAll(svg); graphic.setTranslateX(12); - runInFX(() -> FXUtils.installFastTooltip(menuButton, i18n("version.switch"))); + FXUtils.installFastTooltip(menuButton, i18n("version.switch")); menuButton.setGraphic(graphic); launchPane.getChildren().setAll(launchButton, separator, menuButton); @@ -224,10 +234,10 @@ public final class MainPage extends StackPane implements DecoratorPage { menu.setMaxHeight(365); menu.setMaxWidth(545); menu.setAlwaysShowingVBar(true); - menu.setOnMouseClicked(e -> popup.hide()); + FXUtils.onClicked(menu, popup::hide); versionNodes = MappedObservableList.create(versions, version -> { Node node = PopupMenu.wrapPopupMenuItem(new GameItem(profile, version.getId())); - node.setOnMouseClicked(e -> profile.setSelectedVersion(version.getId())); + FXUtils.onClicked(node, () -> profile.setSelectedVersion(version.getId())); return node; }); Bindings.bindContent(menu.getContent(), versionNodes); @@ -237,10 +247,13 @@ private void showUpdate(boolean show) { doAnimation(show); if (show && getLatestVersion() != null && !Objects.equals(config().getPromptedVersion(), getLatestVersion().getVersion())) { - Controllers.dialog("", i18n("update.bubble.title", getLatestVersion().getVersion()), MessageDialogPane.MessageType.INFO, () -> { - config().setPromptedVersion(getLatestVersion().getVersion()); - onUpgrade(); - }); + Controllers.dialog(new MessageDialogPane.Builder("", i18n("update.bubble.title", getLatestVersion().getVersion()), MessageDialogPane.MessageType.INFO) + .addAction(i18n("button.view"), () -> { + config().setPromptedVersion(getLatestVersion().getVersion()); + onUpgrade(); + }) + .addCancel(null) + .build()); } } @@ -287,10 +300,7 @@ private void closeUpdateBubble() { public void hideAnnouncementPane() { if (announcementPane != null) { config().getShownTips().put(ANNOUNCEMENT, Metadata.VERSION); - Pane parent = (Pane) announcementPane.getParent(); - if (parent != null) - parent.getChildren().remove(announcementPane); - announcementPane = null; + announcementPane.setContent(new StackPane(), ContainerAnimations.FADE); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/PersonalizationPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/PersonalizationPage.java index f1938c8efc..0f3aac4a44 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/PersonalizationPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/PersonalizationPage.java @@ -106,11 +106,13 @@ public PersonalizationPage() { backgroundSublist.setHasSubtitle(true); backgroundItem.loadChildren(Arrays.asList( - new MultiFileItem.Option<>(i18n("launcher.background.default"), EnumBackgroundImage.DEFAULT), + new MultiFileItem.Option<>(i18n("launcher.background.default"), EnumBackgroundImage.DEFAULT) + .setTooltip(i18n("launcher.background.default.tooltip")), new MultiFileItem.Option<>(i18n("launcher.background.classic"), EnumBackgroundImage.CLASSIC), new MultiFileItem.Option<>(i18n("launcher.background.translucent"), EnumBackgroundImage.TRANSLUCENT), new MultiFileItem.FileOption<>(i18n("settings.custom"), EnumBackgroundImage.CUSTOM) .setChooserTitle(i18n("launcher.background.choose")) + .addExtensionFilter(FXUtils.getImageExtensionFilter()) .bindBidirectional(config().backgroundImageProperty()), new MultiFileItem.StringOption<>(i18n("launcher.background.network"), EnumBackgroundImage.NETWORK) .setValidators(new URLValidator(true)) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java index abf24d6ce0..21301dee70 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java @@ -165,7 +165,7 @@ protected Skin(RootPage control) { downloadItem.setActionButtonVisible(false); downloadItem.setTitle(i18n("download")); downloadItem.setOnAction(e -> Controllers.navigate(Controllers.getDownloadPage())); - runInFX(() -> FXUtils.installFastTooltip(downloadItem, i18n("download.hint"))); + FXUtils.installFastTooltip(downloadItem, i18n("download.hint")); // fifth item in left sidebar AdvancedListItem launcherSettingsItem = new AdvancedListItem(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsView.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsView.java index 39bd0ae17f..0bfc1ba6a6 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsView.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsView.java @@ -30,6 +30,7 @@ import javafx.scene.text.Text; import javafx.scene.text.TextAlignment; import javafx.scene.text.TextFlow; +import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.setting.EnumCommonDirectory; import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.ui.FXUtils; @@ -72,7 +73,7 @@ public SettingsView() { { StackPane sponsorPane = new StackPane(); sponsorPane.setCursor(Cursor.HAND); - sponsorPane.setOnMouseClicked(e -> onSponsor()); + FXUtils.onClicked(sponsorPane, this::onSponsor); sponsorPane.setPadding(new Insets(8, 0, 8, 0)); GridPane gridPane = new GridPane(); @@ -120,7 +121,7 @@ public SettingsView() { { btnUpdate = new JFXButton(); - btnUpdate.setOnMouseClicked(e -> onUpdate()); + btnUpdate.setOnAction(e -> onUpdate()); btnUpdate.getStyleClass().add("toggle-icon4"); btnUpdate.setGraphic(SVG.UPDATE.createIcon(Theme.blackFill(), 20, 20)); @@ -160,7 +161,7 @@ public SettingsView() { { JFXButton cleanButton = new JFXButton(i18n("launcher.cache_directory.clean")); - cleanButton.setOnMouseClicked(e -> clearCacheDirectory()); + cleanButton.setOnAction(e -> clearCacheDirectory()); cleanButton.getStyleClass().add("jfx-button-border"); fileCommonLocationSublist.setHeaderRight(cleanButton); @@ -191,10 +192,19 @@ public SettingsView() { BorderPane.setAlignment(left, Pos.CENTER_LEFT); debugPane.setLeft(left); + JFXButton openLogFolderButton = new JFXButton(i18n("settings.launcher.launcher_log.reveal")); + openLogFolderButton.setOnAction(e -> openLogFolder()); + openLogFolderButton.getStyleClass().add("jfx-button-border"); + JFXButton logButton = new JFXButton(i18n("settings.launcher.launcher_log.export")); - logButton.setOnMouseClicked(e -> onExportLogs()); + logButton.setOnAction(e -> onExportLogs()); logButton.getStyleClass().add("jfx-button-border"); - debugPane.setRight(logButton); + + HBox buttonBox = new HBox(); + buttonBox.setSpacing(10); + buttonBox.getChildren().addAll(openLogFolderButton, logButton); + BorderPane.setAlignment(buttonBox, Pos.CENTER_RIGHT); + debugPane.setRight(buttonBox); settingsPane.getContent().add(debugPane); } @@ -205,6 +215,10 @@ public SettingsView() { } } + public void openLogFolder() { + FXUtils.openFolder(Metadata.HMCL_DIRECTORY.resolve("logs").toFile()); + } + protected abstract void onUpdate(); protected abstract void onExportLogs(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/profile/ProfileListItemSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/profile/ProfileListItemSkin.java index 83b5ed3367..e17047341c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/profile/ProfileListItemSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/profile/ProfileListItemSkin.java @@ -22,7 +22,6 @@ import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.SkinBase; -import javafx.scene.input.MouseEvent; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import org.jackhuang.hmcl.setting.Theme; @@ -47,9 +46,7 @@ public ProfileListItemSkin(ProfileListItem skinnable) { skinnable.pseudoClassStateChanged(SELECTED, active); }); - getSkinnable().addEventHandler(MouseEvent.MOUSE_CLICKED, e -> { - getSkinnable().setSelected(true); - }); + FXUtils.onClicked(getSkinnable(), () -> getSkinnable().setSelected(true)); Node left = VersionPage.wrap(SVG.FOLDER_OUTLINE); root.setLeft(left); @@ -64,7 +61,7 @@ public ProfileListItemSkin(ProfileListItem skinnable) { right.setAlignment(Pos.CENTER_RIGHT); JFXButton btnRemove = new JFXButton(); - btnRemove.setOnMouseClicked(e -> skinnable.remove()); + btnRemove.setOnAction(e -> skinnable.remove()); btnRemove.getStyleClass().add("toggle-icon4"); BorderPane.setAlignment(btnRemove, Pos.CENTER); btnRemove.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), 14, 14)); diff --git a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/FunctionHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/FunctionHelper.java similarity index 98% rename from HMCL/src/main/java/moe/mickey/minecraft/skin/fx/FunctionHelper.java rename to HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/FunctionHelper.java index 43e88bde57..7c8871f485 100644 --- a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/FunctionHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/FunctionHelper.java @@ -1,4 +1,4 @@ -package moe.mickey.minecraft.skin.fx; +package org.jackhuang.hmcl.ui.skin; import javafx.event.Event; import javafx.event.EventHandler; diff --git a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinAnimation.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/SkinAnimation.java similarity index 97% rename from HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinAnimation.java rename to HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/SkinAnimation.java index f7f49b2340..2c8c9c6050 100644 --- a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinAnimation.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/SkinAnimation.java @@ -1,4 +1,4 @@ -package moe.mickey.minecraft.skin.fx; +package org.jackhuang.hmcl.ui.skin; import javafx.event.ActionEvent; import javafx.event.EventHandler; diff --git a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinAnimationPlayer.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/SkinAnimationPlayer.java similarity index 98% rename from HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinAnimationPlayer.java rename to HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/SkinAnimationPlayer.java index 683b478fac..8132566441 100644 --- a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinAnimationPlayer.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/SkinAnimationPlayer.java @@ -1,4 +1,4 @@ -package moe.mickey.minecraft.skin.fx; +package org.jackhuang.hmcl.ui.skin; import javafx.animation.AnimationTimer; diff --git a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinCanvas.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/SkinCanvas.java similarity index 99% rename from HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinCanvas.java rename to HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/SkinCanvas.java index 8faf860b23..5bf3cc4c1c 100644 --- a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinCanvas.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/SkinCanvas.java @@ -1,4 +1,4 @@ -package moe.mickey.minecraft.skin.fx; +package org.jackhuang.hmcl.ui.skin; import javafx.scene.*; import javafx.scene.image.Image; diff --git a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinCube.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/SkinCube.java similarity index 99% rename from HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinCube.java rename to HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/SkinCube.java index 0929156c6e..d94bbd24d8 100644 --- a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinCube.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/SkinCube.java @@ -1,4 +1,4 @@ -package moe.mickey.minecraft.skin.fx; +package org.jackhuang.hmcl.ui.skin; import javafx.scene.shape.Mesh; import javafx.scene.shape.MeshView; diff --git a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinGroup.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/SkinGroup.java similarity index 96% rename from HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinGroup.java rename to HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/SkinGroup.java index 70134e85af..5fcf2476fd 100644 --- a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinGroup.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/SkinGroup.java @@ -1,4 +1,4 @@ -package moe.mickey.minecraft.skin.fx; +package org.jackhuang.hmcl.ui.skin; import javafx.scene.Group; import javafx.scene.Node; diff --git a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/SkinHelper.java similarity index 99% rename from HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinHelper.java rename to HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/SkinHelper.java index 5125f3e0a7..e31ee03ea5 100644 --- a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/SkinHelper.java @@ -1,4 +1,4 @@ -package moe.mickey.minecraft.skin.fx; +package org.jackhuang.hmcl.ui.skin; import javafx.scene.image.Image; import javafx.scene.image.PixelReader; diff --git a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinMultipleCubes.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/SkinMultipleCubes.java similarity index 99% rename from HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinMultipleCubes.java rename to HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/SkinMultipleCubes.java index c8f3f128e6..4e9e1b4b19 100644 --- a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinMultipleCubes.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/SkinMultipleCubes.java @@ -1,4 +1,4 @@ -package moe.mickey.minecraft.skin.fx; +package org.jackhuang.hmcl.ui.skin; import javafx.geometry.Point2D; import javafx.scene.Group; diff --git a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinTransition.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/SkinTransition.java similarity index 96% rename from HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinTransition.java rename to HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/SkinTransition.java index ab6e9514d5..f1bbfd70ba 100644 --- a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/SkinTransition.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/SkinTransition.java @@ -1,4 +1,4 @@ -package moe.mickey.minecraft.skin.fx; +package org.jackhuang.hmcl.ui.skin; import javafx.animation.Transition; import javafx.beans.value.WritableValue; diff --git a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/animation/SkinAniRunning.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/animation/SkinAniRunning.java similarity index 80% rename from HMCL/src/main/java/moe/mickey/minecraft/skin/fx/animation/SkinAniRunning.java rename to HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/animation/SkinAniRunning.java index 5a2b836937..c6fa30d516 100644 --- a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/animation/SkinAniRunning.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/animation/SkinAniRunning.java @@ -1,10 +1,10 @@ -package moe.mickey.minecraft.skin.fx.animation; +package org.jackhuang.hmcl.ui.skin.animation; import javafx.util.Duration; -import moe.mickey.minecraft.skin.fx.FunctionHelper; -import moe.mickey.minecraft.skin.fx.SkinAnimation; -import moe.mickey.minecraft.skin.fx.SkinCanvas; -import moe.mickey.minecraft.skin.fx.SkinTransition; +import org.jackhuang.hmcl.ui.skin.FunctionHelper; +import org.jackhuang.hmcl.ui.skin.SkinAnimation; +import org.jackhuang.hmcl.ui.skin.SkinCanvas; +import org.jackhuang.hmcl.ui.skin.SkinTransition; public final class SkinAniRunning extends SkinAnimation { diff --git a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/animation/SkinAniWavingArms.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/animation/SkinAniWavingArms.java similarity index 76% rename from HMCL/src/main/java/moe/mickey/minecraft/skin/fx/animation/SkinAniWavingArms.java rename to HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/animation/SkinAniWavingArms.java index 46060216dd..0eabc7bb4e 100644 --- a/HMCL/src/main/java/moe/mickey/minecraft/skin/fx/animation/SkinAniWavingArms.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/skin/animation/SkinAniWavingArms.java @@ -1,10 +1,10 @@ -package moe.mickey.minecraft.skin.fx.animation; +package org.jackhuang.hmcl.ui.skin.animation; import javafx.util.Duration; -import moe.mickey.minecraft.skin.fx.FunctionHelper; -import moe.mickey.minecraft.skin.fx.SkinAnimation; -import moe.mickey.minecraft.skin.fx.SkinCanvas; -import moe.mickey.minecraft.skin.fx.SkinTransition; +import org.jackhuang.hmcl.ui.skin.FunctionHelper; +import org.jackhuang.hmcl.ui.skin.SkinAnimation; +import org.jackhuang.hmcl.ui.skin.SkinCanvas; +import org.jackhuang.hmcl.ui.skin.SkinTransition; public final class SkinAniWavingArms extends SkinAnimation { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DatapackListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DatapackListPageSkin.java index 4baa9f3164..dc6241f230 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DatapackListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DatapackListPageSkin.java @@ -27,6 +27,8 @@ import javafx.geometry.Pos; import javafx.scene.control.SelectionMode; import javafx.scene.control.SkinBase; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Region; @@ -38,6 +40,7 @@ import org.jackhuang.hmcl.ui.construct.TwoLineListItem; import org.jackhuang.hmcl.util.StringUtils; +import static org.jackhuang.hmcl.ui.FXUtils.ignoreEvent; import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; @@ -110,6 +113,9 @@ protected void updateControl(DatapackInfoObject dataItem, boolean empty) { listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); Bindings.bindContent(listView.getItems(), skinnable.getItems()); + // ListViewBehavior would consume ESC pressed event, preventing us from handling it, so we ignore it here + ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE); + center.setContent(listView); root.setCenter(center); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java index ceb0c9ed3c..e1fe3ae8fe 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java @@ -36,6 +36,8 @@ import javafx.scene.control.Skin; import javafx.scene.control.SkinBase; import javafx.scene.image.ImageView; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; import javafx.scene.layout.*; import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.mod.RemoteMod; @@ -64,7 +66,7 @@ import java.util.Optional; import java.util.stream.Collectors; -import static org.jackhuang.hmcl.ui.FXUtils.runInFX; +import static org.jackhuang.hmcl.ui.FXUtils.ignoreEvent; import static org.jackhuang.hmcl.ui.FXUtils.stringConverter; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.selectedItemPropertyFor; @@ -302,7 +304,7 @@ protected ModDownloadListPageSkin(DownloadListPage control) { lblGameVersion.visibleProperty().bind(hasVersion); gameVersionField.managedProperty().bind(hasVersion); gameVersionField.visibleProperty().bind(hasVersion); - runInFX(() -> FXUtils.installFastTooltip(gameVersionField, i18n("search.enter"))); + FXUtils.installFastTooltip(gameVersionField, i18n("search.enter")); FXUtils.onChangeAndOperate(getSkinnable().version, version -> { if (StringUtils.isNotBlank(version.getVersion())) { @@ -488,12 +490,15 @@ protected ModDownloadListPageSkin(DownloadListPage control) { JFXListView listView = new JFXListView<>(); spinnerPane.setContent(listView); Bindings.bindContent(listView.getItems(), getSkinnable().items); - listView.setOnMouseClicked(e -> { + FXUtils.onClicked(listView, () -> { if (listView.getSelectionModel().getSelectedIndex() < 0) return; RemoteMod selectedItem = listView.getSelectionModel().getSelectedItem(); Controllers.navigate(new DownloadPage(getSkinnable(), selectedItem, getSkinnable().getProfileVersion(), getSkinnable().callback)); }); + + // ListViewBehavior would consume ESC pressed event, preventing us from handling it, so we ignore it here + ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE); listView.setCellFactory(x -> new FloatListCell(listView) { TwoLineListItem content = new TwoLineListItem(); ImageView imageView = new ImageView(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java index d16124206f..b00adb7754 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java @@ -62,7 +62,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static org.jackhuang.hmcl.ui.FXUtils.runInFX; +import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public class DownloadPage extends Control implements DecoratorPage { @@ -241,14 +241,14 @@ protected ModDownloadPageSkin(DownloadPage control) { openMcmodButton.setExternalLink(getSkinnable().translations.getMcmodUrl(getSkinnable().mod)); descriptionPane.getChildren().add(openMcmodButton); openMcmodButton.setMinWidth(Region.USE_PREF_SIZE); - runInFX(() -> FXUtils.installFastTooltip(openMcmodButton, i18n("mods.mcmod"))); + FXUtils.installFastTooltip(openMcmodButton, i18n("mods.mcmod")); } JFXHyperlink openUrlButton = new JFXHyperlink(control.page.getLocalizedOfficialPage()); openUrlButton.setExternalLink(getSkinnable().addon.getPageUrl()); descriptionPane.getChildren().add(openUrlButton); openUrlButton.setMinWidth(Region.USE_PREF_SIZE); - runInFX(() -> FXUtils.installFastTooltip(openUrlButton, control.page.getLocalizedOfficialPage())); + FXUtils.installFastTooltip(openUrlButton, control.page.getLocalizedOfficialPage()); } SpinnerPane spinnerPane = new SpinnerPane(); @@ -346,7 +346,10 @@ private static final class DependencyModItem extends StackPane { pane.getChildren().setAll(FXUtils.limitingSize(imageView, 40, 40), content); RipplerContainer container = new RipplerContainer(pane); - container.setOnMouseClicked(e -> Controllers.navigate(new DownloadPage(page, addon, version, callback))); + FXUtils.onClicked(container, () -> { + fireEvent(new DialogCloseEvent()); + Controllers.navigate(new DownloadPage(page, addon, version, callback)); + }); getChildren().setAll(container); if (addon != RemoteMod.BROKEN) { @@ -378,6 +381,7 @@ private static final class ModItem extends StackPane { HBox descPane = new HBox(8); descPane.setPadding(new Insets(0, 8, 0, 8)); descPane.setAlignment(Pos.CENTER_LEFT); + descPane.setMouseTransparent(true); { StackPane graphicPane = new StackPane(); @@ -388,15 +392,15 @@ private static final class ModItem extends StackPane { switch (dataItem.getVersionType()) { case Alpha: - content.getTags().add(i18n("version.game.snapshot")); + content.getTags().add(i18n("mods.channel.alpha")); graphicPane.getChildren().setAll(SVG.ALPHA_CIRCLE_OUTLINE.createIcon(Theme.blackFill(), 24, 24)); break; case Beta: - content.getTags().add(i18n("version.game.snapshot")); + content.getTags().add(i18n("mods.channel.beta")); graphicPane.getChildren().setAll(SVG.BETA_CIRCLE_OUTLINE.createIcon(Theme.blackFill(), 24, 24)); break; case Release: - content.getTags().add(i18n("version.game.release")); + content.getTags().add(i18n("mods.channel.release")); graphicPane.getChildren().setAll(SVG.RELEASE_CIRCLE_OUTLINE.createIcon(Theme.blackFill(), 24, 24)); break; } @@ -428,7 +432,7 @@ private static final class ModItem extends StackPane { } RipplerContainer container = new RipplerContainer(pane); - container.setOnMouseClicked(e -> Controllers.dialog(new ModVersion(dataItem, selfPage))); + FXUtils.onClicked(container, () -> Controllers.dialog(new ModVersion(dataItem, selfPage))); getChildren().setAll(container); // Workaround for https://github.com/HMCL-dev/HMCL/issues/2129 @@ -443,7 +447,7 @@ public ModVersion(RemoteMod.Version version, DownloadPage selfPage) { VBox box = new VBox(8); box.setPadding(new Insets(8)); ModItem modItem = new ModItem(version, selfPage); - modItem.setOnMouseClicked(e -> fireEvent(new DialogCloseEvent())); + modItem.setMouseTransparent(true); // Item is displayed for info, clicking shouldn't open the dialog again box.getChildren().setAll(modItem); SpinnerPane spinnerPane = new SpinnerPane(); ScrollPane scrollPane = new ScrollPane(); @@ -486,6 +490,8 @@ public ModVersion(RemoteMod.Version version, DownloadPage selfPage) { this.prefWidthProperty().bind(BindingMapping.of(Controllers.getStage().widthProperty()).map(w -> w.doubleValue() * 0.7)); this.prefHeightProperty().bind(BindingMapping.of(Controllers.getStage().heightProperty()).map(w -> w.doubleValue() * 0.7)); + + onEscPressed(this, cancelButton::fire); } private void loadDependencies(RemoteMod.Version version, DownloadPage selfPage, SpinnerPane spinnerPane, ComponentList dependenciesList) { @@ -505,7 +511,6 @@ private void loadDependencies(RemoteMod.Version version, DownloadPage selfPage, dependencies.put(dependency.getType(), list); } DependencyModItem dependencyModItem = new DependencyModItem(selfPage.page, dependency.load(), selfPage.version, selfPage.callback); - dependencyModItem.setOnMouseClicked(e -> fireEvent(new DialogCloseEvent())); dependencies.get(dependency.getType()).add(dependencyModItem); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameAdvancedListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameAdvancedListItem.java index da7306d5ac..29743853bf 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameAdvancedListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameAdvancedListItem.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.ui.versions; import javafx.scene.Node; -import javafx.scene.control.Tooltip; import javafx.scene.image.ImageView; import org.jackhuang.hmcl.event.Event; import org.jackhuang.hmcl.setting.Profile; @@ -34,15 +33,13 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public class GameAdvancedListItem extends AdvancedListItem { - private final Tooltip tooltip; private final ImageView imageView; private final WeakListenerHolder holder = new WeakListenerHolder(); private Profile profile; + @SuppressWarnings("unused") private Consumer onVersionIconChangedListener; public GameAdvancedListItem() { - tooltip = new Tooltip(); - Pair view = createImageView(null); setLeftGraphic(view.getKey()); imageView = view.getValue(); @@ -63,17 +60,13 @@ private void loadVersion(String version) { } if (version != null && Profiles.getSelectedProfile() != null && Profiles.getSelectedProfile().getRepository().hasVersion(version)) { - FXUtils.installFastTooltip(this, tooltip); - setTitle(version); - setSubtitle(null); + setTitle(i18n("version.manage.manage")); + setSubtitle(version); imageView.setImage(Profiles.getSelectedProfile().getRepository().getVersionIconImage(version)); - tooltip.setText(version); } else { - Tooltip.uninstall(this,tooltip); setTitle(i18n("version.empty")); setSubtitle(i18n("version.empty.add")); imageView.setImage(VersionIconType.DEFAULT.getIcon()); - tooltip.setText(""); } } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListItemSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListItemSkin.java index e4e767214f..15b8264722 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListItemSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListItemSkin.java @@ -35,7 +35,6 @@ import org.jackhuang.hmcl.ui.construct.RipplerContainer; import org.jackhuang.hmcl.util.Lazy; -import static org.jackhuang.hmcl.ui.FXUtils.runInFX; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public class GameListItemSkin extends SkinBase { @@ -79,33 +78,33 @@ public GameListItemSkin(GameListItem skinnable) { right.setAlignment(Pos.CENTER_RIGHT); if (skinnable.canUpdate()) { JFXButton btnUpgrade = new JFXButton(); - btnUpgrade.setOnMouseClicked(e -> skinnable.update()); + btnUpgrade.setOnAction(e -> skinnable.update()); btnUpgrade.getStyleClass().add("toggle-icon4"); btnUpgrade.setGraphic(FXUtils.limitingSize(SVG.UPDATE.createIcon(Theme.blackFill(), 24, 24), 24, 24)); - runInFX(() -> FXUtils.installFastTooltip(btnUpgrade, i18n("version.update"))); + FXUtils.installFastTooltip(btnUpgrade, i18n("version.update")); right.getChildren().add(btnUpgrade); } { JFXButton btnLaunch = new JFXButton(); - btnLaunch.setOnMouseClicked(e -> skinnable.launch()); + btnLaunch.setOnAction(e -> skinnable.launch()); btnLaunch.getStyleClass().add("toggle-icon4"); BorderPane.setAlignment(btnLaunch, Pos.CENTER); btnLaunch.setGraphic(FXUtils.limitingSize(SVG.ROCKET_LAUNCH_OUTLINE.createIcon(Theme.blackFill(), 24, 24), 24, 24)); - runInFX(() -> FXUtils.installFastTooltip(btnLaunch, i18n("version.launch.test"))); + FXUtils.installFastTooltip(btnLaunch, i18n("version.launch.test")); right.getChildren().add(btnLaunch); } { JFXButton btnManage = new JFXButton(); - btnManage.setOnMouseClicked(e -> { + btnManage.setOnAction(e -> { currentSkinnable = skinnable; popup.get().show(root, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, root.getHeight()); }); btnManage.getStyleClass().add("toggle-icon4"); BorderPane.setAlignment(btnManage, Pos.CENTER); btnManage.setGraphic(FXUtils.limitingSize(SVG.DOTS_VERTICAL.createIcon(Theme.blackFill(), 24, 24), 24, 24)); - runInFX(() -> FXUtils.installFastTooltip(btnManage, i18n("settings.game.management"))); + FXUtils.installFastTooltip(btnManage, i18n("settings.game.management")); right.getChildren().add(btnManage); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListPage.java index 1482b510a5..26216e5463 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListPage.java @@ -91,13 +91,11 @@ public GameListPage() { installNewGameItem.setTitle(i18n("install.new_game")); installNewGameItem.setLeftGraphic(VersionPage.wrap(SVG.PLUS_CIRCLE_OUTLINE)); installNewGameItem.setOnAction(e -> Versions.addNewGame()); - runInFX(() -> FXUtils.installFastTooltip(installNewGameItem, i18n("install.new_game"))); }) .addNavigationDrawerItem(installModpackItem -> { installModpackItem.setTitle(i18n("install.modpack")); installModpackItem.setLeftGraphic(VersionPage.wrap(SVG.PACK)); installModpackItem.setOnAction(e -> Versions.importModpack()); - runInFX(() -> FXUtils.installFastTooltip(installModpackItem, i18n("install.modpack"))); }) .addNavigationDrawerItem(refreshItem -> { refreshItem.setTitle(i18n("button.refresh")); @@ -108,7 +106,6 @@ public GameListPage() { globalManageItem.setTitle(i18n("settings.type.global.manage")); globalManageItem.setLeftGraphic(VersionPage.wrap(SVG.GEAR_OUTLINE)); globalManageItem.setOnAction(e -> modifyGlobalGameSettings()); - runInFX(() -> FXUtils.installFastTooltip(globalManageItem, i18n("settings.type.global.manage"))); }); FXUtils.setLimitHeight(bottomLeftCornerList, 40 * 4 + 12 * 2); setLeft(pane, bottomLeftCornerList); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java index cca80f4af6..02a6d121f0 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java @@ -97,11 +97,11 @@ public void loadVersion(Profile profile, String versionId) { item.versionProperty().set(null); } - item.installActionProperty().set(e -> { + item.setOnInstall(() -> { Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, libraryId, libraryVersion)); }); - item.removeActionProperty().set(e -> profile.getDependency().removeLibraryAsync(version, libraryId) + item.setOnRemove(() -> profile.getDependency().removeLibraryAsync(version, libraryId) .thenComposeAsync(profile.getRepository()::saveAsync) .withComposeAsync(profile.getRepository().refreshVersionsAsync()) .withRunAsync(Schedulers.javafx(), () -> loadVersion(this.profile, this.versionId)) @@ -121,7 +121,7 @@ public void loadVersion(Profile profile, String versionId) { InstallerItem installerItem = new InstallerItem(libraryId, InstallerItem.Style.LIST_ITEM); installerItem.versionProperty().set(new InstallerItem.InstalledState(libraryVersion, false, false)); - installerItem.removeActionProperty().set(e -> profile.getDependency().removeLibraryAsync(version, libraryId) + installerItem.setOnRemove(() -> profile.getDependency().removeLibraryAsync(version, libraryId) .thenComposeAsync(profile.getRepository()::saveAsync) .withComposeAsync(profile.getRepository().refreshVersionsAsync()) .withRunAsync(Schedulers.javafx(), () -> loadVersion(this.profile, this.versionId)) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java index cdbb333503..5ce5eddbd1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java @@ -192,7 +192,7 @@ public void checkUpdates() { }) .whenComplete(Schedulers.javafx(), (result, exception) -> { if (exception != null || result == null) { - Controllers.dialog("Failed to check updates", "failed", MessageDialogPane.MessageType.ERROR); + Controllers.dialog(i18n("mods.check_updates.failed_check"), i18n("message.failed"), MessageDialogPane.MessageType.ERROR); } else if (result.isEmpty()) { Controllers.dialog(i18n("mods.check_updates.empty")); } else { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java index 4c35fe6308..66d3d6e1da 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java @@ -19,6 +19,7 @@ import com.jfoenix.controls.*; import com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject; +import javafx.animation.PauseTransition; import javafx.beans.binding.Bindings; import javafx.beans.property.BooleanProperty; import javafx.collections.ListChangeListener; @@ -30,9 +31,12 @@ import javafx.scene.control.SkinBase; import javafx.scene.image.Image; import javafx.scene.image.ImageView; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.StackPane; +import javafx.util.Duration; import org.jackhuang.hmcl.mod.LocalModFile; import org.jackhuang.hmcl.mod.ModLoaderType; import org.jackhuang.hmcl.mod.RemoteMod; @@ -68,6 +72,7 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; +import static org.jackhuang.hmcl.ui.FXUtils.ignoreEvent; import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed; import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2; import static org.jackhuang.hmcl.util.Lang.mapOf; @@ -113,7 +118,12 @@ class ModListPageSkin extends SkinBase { searchField = new JFXTextField(); searchField.setPromptText(i18n("search")); HBox.setHgrow(searchField, Priority.ALWAYS); - searchField.setOnAction(e -> search()); + PauseTransition pause = new PauseTransition(Duration.millis(100)); + pause.setOnFinished(e -> search()); + searchField.textProperty().addListener((observable, oldValue, newValue) -> { + pause.setRate(1); + pause.playFromStart(); + }); JFXButton closeSearchBar = createToolbarButton2(null, SVG.CLOSE, () -> { @@ -124,6 +134,8 @@ class ModListPageSkin extends SkinBase { Bindings.bindContent(listView.getItems(), getSkinnable().getItems()); }); + onEscPressed(searchField, closeSearchBar::fire); + searchBar.getChildren().setAll(searchField, closeSearchBar); // Toolbar Normal @@ -161,6 +173,16 @@ class ModListPageSkin extends SkinBase { changeToolbar(toolbarSelecting); }); root.getContent().add(toolbarPane); + + // Clear selection when pressing ESC + root.addEventHandler(KeyEvent.KEY_PRESSED, e -> { + if (e.getCode() == KeyCode.ESCAPE) { + if (listView.getSelectionModel().getSelectedItem() != null) { + listView.getSelectionModel().clearSelection(); + e.consume(); + } + } + }); } { @@ -179,6 +201,18 @@ class ModListPageSkin extends SkinBase { } }); + listView.setOnContextMenuRequested(event -> { + ModInfoObject selectedItem = listView.getSelectionModel().getSelectedItem(); + if (selectedItem != null && listView.getSelectionModel().getSelectedItems().size() == 1) { + listView.getSelectionModel().clearSelection(); + Controllers.dialog(new ModInfoDialog(selectedItem)); + } + }); + + // ListViewBehavior would consume ESC pressed event, preventing us from handling it + // So we ignore it here + ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE); + center.setContent(listView); root.getContent().add(center); } @@ -197,7 +231,7 @@ class ModListPageSkin extends SkinBase { private void changeToolbar(HBox newToolbar) { Node oldToolbar = toolbarPane.getCurrentNode(); if (newToolbar != oldToolbar) { - toolbarPane.setContent(newToolbar, ContainerAnimations.FADE.getAnimationProducer()); + toolbarPane.setContent(newToolbar, ContainerAnimations.FADE); if (newToolbar == searchBar) { searchField.requestFocus(); } @@ -532,7 +566,7 @@ protected void updateControl(ModInfoObject dataItem, boolean empty) { } checkBox.selectedProperty().bindBidirectional(booleanProperty = dataItem.active); restoreButton.setVisible(!dataItem.getModInfo().getMod().getOldFiles().isEmpty()); - restoreButton.setOnMouseClicked(e -> { + restoreButton.setOnAction(e -> { menu.get().getContent().setAll(dataItem.getModInfo().getMod().getOldFiles().stream() .map(localModFile -> new IconedMenuItem(null, localModFile.getVersion(), () -> getSkinnable().rollback(dataItem.getModInfo(), localModFile), @@ -542,12 +576,8 @@ protected void updateControl(ModInfoObject dataItem, boolean empty) { popup.get().show(restoreButton, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, restoreButton.getHeight()); }); - revealButton.setOnMouseClicked(e -> { - FXUtils.showFileInExplorer(dataItem.getModInfo().getFile()); - }); - infoButton.setOnMouseClicked(e -> { - Controllers.dialog(new ModInfoDialog(dataItem)); - }); + revealButton.setOnAction(e -> FXUtils.showFileInExplorer(dataItem.getModInfo().getFile())); + infoButton.setOnAction(e -> Controllers.dialog(new ModInfoDialog(dataItem))); } } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModUpdatesPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModUpdatesPage.java index 0d080f731f..fcd2841046 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModUpdatesPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModUpdatesPage.java @@ -24,6 +24,7 @@ import javafx.collections.ObservableList; import javafx.geometry.Insets; import javafx.geometry.Pos; +import javafx.scene.control.CheckBox; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.cell.CheckBoxTableCell; @@ -73,6 +74,8 @@ public ModUpdatesPage(ModManager modManager, List update getStyleClass().add("gray-background"); TableColumn enabledColumn = new TableColumn<>(); + CheckBox allEnabledBox = new CheckBox(); + enabledColumn.setGraphic(allEnabledBox); enabledColumn.setCellFactory(CheckBoxTableCell.forTableColumn(enabledColumn)); setupCellValueFactory(enabledColumn, ModUpdateObject::enabledProperty); enabledColumn.setEditable(true); @@ -95,6 +98,7 @@ public ModUpdatesPage(ModManager modManager, List update setupCellValueFactory(sourceColumn, ModUpdateObject::sourceProperty); objects = FXCollections.observableList(updates.stream().map(ModUpdateObject::new).collect(Collectors.toList())); + FXUtils.bindAllEnabled(allEnabledBox.selectedProperty(), objects.stream().map(o -> o.enabled).toArray(BooleanProperty[]::new)); TableView table = new TableView<>(objects); table.setEditable(true); @@ -135,7 +139,7 @@ private void updateMods() { task.whenComplete(Schedulers.javafx(), exception -> { fireEvent(new PageCloseEvent()); if (!task.getFailedMods().isEmpty()) { - Controllers.dialog(i18n("mods.check_updates.failed") + "\n" + + Controllers.dialog(i18n("mods.check_updates.failed_download") + "\n" + task.getFailedMods().stream().map(LocalModFile::getFileName).collect(Collectors.joining("\n")), i18n("install.failed"), MessageDialogPane.MessageType.ERROR); @@ -170,7 +174,7 @@ private void exportList() { csvTable.write(Files.newOutputStream(path)); FXUtils.showFileInExplorer(path); - }).whenComplete(Schedulers.javafx() ,exception -> { + }).whenComplete(Schedulers.javafx(), exception -> { if (exception == null) { Controllers.dialog(path.toString(), i18n("message.success")); } else { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java index 106dfcc114..44b2ada093 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java @@ -72,7 +72,7 @@ public VersionIconDialog(Profile profile, String versionId, Runnable onFinish) { private void exploreIcon() { FileChooser chooser = new FileChooser(); - chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("extension.png"), "*.png", "*.jpg", "*.bmp", "*.gif")); + chooser.getExtensionFilters().add(FXUtils.getImageExtensionFilter()); File selectedFile = chooser.showOpenDialog(Controllers.getStage()); if (selectedFile != null) { try { @@ -95,9 +95,7 @@ private Node createCustomIcon() { RipplerContainer container = new RipplerContainer(shape); FXUtils.setLimitWidth(container, 36); FXUtils.setLimitHeight(container, 36); - container.setOnMouseClicked(e -> { - exploreIcon(); - }); + FXUtils.onClicked(container, this::exploreIcon); return container; } @@ -107,7 +105,7 @@ private Node createIcon(VersionIconType type) { RipplerContainer container = new RipplerContainer(imageView); FXUtils.setLimitWidth(container, 36); FXUtils.setLimitHeight(container, 36); - container.setOnMouseClicked(e -> { + FXUtils.onClicked(container, () -> { if (vs != null) { vs.setVersionIcon(type); onAccept(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java index bb328b7eaf..6d36a9051e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java @@ -56,8 +56,8 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage private final ReadOnlyObjectWrapper state = new ReadOnlyObjectWrapper<>(); private final TabHeader tab; private final TabHeader.Tab versionSettingsTab = new TabHeader.Tab<>("versionSettingsTab"); - private final TabHeader.Tab modListTab = new TabHeader.Tab<>("modListTab"); private final TabHeader.Tab installerListTab = new TabHeader.Tab<>("installerListTab"); + private final TabHeader.Tab modListTab = new TabHeader.Tab<>("modListTab"); private final TabHeader.Tab worldListTab = new TabHeader.Tab<>("worldList"); private final TransitionPane transitionPane = new TransitionPane(); private final BooleanProperty currentVersionUpgradable = new SimpleBooleanProperty(); @@ -68,17 +68,17 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage { versionSettingsTab.setNodeSupplier(loadVersionFor(() -> new VersionSettingsPage(false))); - modListTab.setNodeSupplier(loadVersionFor(ModListPage::new)); installerListTab.setNodeSupplier(loadVersionFor(InstallerListPage::new)); + modListTab.setNodeSupplier(loadVersionFor(ModListPage::new)); worldListTab.setNodeSupplier(loadVersionFor(WorldListPage::new)); - tab = new TabHeader(versionSettingsTab, modListTab, installerListTab, worldListTab); + tab = new TabHeader(versionSettingsTab, installerListTab, modListTab, worldListTab); addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onNavigated); tab.select(versionSettingsTab); FXUtils.onChangeAndOperate(tab.getSelectionModel().selectedItemProperty(), newValue -> { - transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE.getAnimationProducer()); + transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE); }); listenerHolder.add(EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).registerWeak(event -> checkSelectedVersion(), EventPriority.HIGHEST)); @@ -128,10 +128,10 @@ public void loadVersion(String version, Profile profile) { if (versionSettingsTab.isInitialized()) versionSettingsTab.getNode().loadVersion(profile, version); - if (modListTab.isInitialized()) - modListTab.getNode().loadVersion(profile, version); if (installerListTab.isInitialized()) installerListTab.getNode().loadVersion(profile, version); + if (modListTab.isInitialized()) + modListTab.getNode().loadVersion(profile, version); if (worldListTab.isInitialized()) worldListTab.getNode().loadVersion(profile, version); currentVersionUpgradable.set(profile.getRepository().isModpack(version)); @@ -244,40 +244,36 @@ protected Skin(VersionPage control) { versionSettingsItem.setLeftGraphic(wrap(SVG.GEAR_OUTLINE)); versionSettingsItem.setActionButtonVisible(false); versionSettingsItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.versionSettingsTab)); - runInFX(() -> FXUtils.installFastTooltip(versionSettingsItem, i18n("settings.game"))); versionSettingsItem.setOnAction(e -> control.tab.select(control.versionSettingsTab)); - AdvancedListItem modListItem = new AdvancedListItem(); - modListItem.getStyleClass().add("navigation-drawer-item"); - modListItem.setTitle(i18n("mods.manage")); - modListItem.setLeftGraphic(wrap(SVG.PUZZLE)); - modListItem.setActionButtonVisible(false); - modListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.modListTab)); - runInFX(() -> FXUtils.installFastTooltip(modListItem, i18n("mods.manage"))); - modListItem.setOnAction(e -> control.tab.select(control.modListTab)); - AdvancedListItem installerListItem = new AdvancedListItem(); installerListItem.getStyleClass().add("navigation-drawer-item"); installerListItem.setTitle(i18n("settings.tabs.installers")); installerListItem.setLeftGraphic(wrap(SVG.CUBE)); installerListItem.setActionButtonVisible(false); installerListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.installerListTab)); - runInFX(() -> FXUtils.installFastTooltip(installerListItem, i18n("settings.tabs.installers"))); installerListItem.setOnAction(e -> control.tab.select(control.installerListTab)); + AdvancedListItem modListItem = new AdvancedListItem(); + modListItem.getStyleClass().add("navigation-drawer-item"); + modListItem.setTitle(i18n("mods.manage")); + modListItem.setLeftGraphic(wrap(SVG.PUZZLE)); + modListItem.setActionButtonVisible(false); + modListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.modListTab)); + modListItem.setOnAction(e -> control.tab.select(control.modListTab)); + AdvancedListItem worldListItem = new AdvancedListItem(); worldListItem.getStyleClass().add("navigation-drawer-item"); worldListItem.setTitle(i18n("world.manage")); worldListItem.setLeftGraphic(wrap(SVG.EARTH)); worldListItem.setActionButtonVisible(false); worldListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.worldListTab)); - runInFX(() -> FXUtils.installFastTooltip(worldListItem, i18n("world.manage"))); worldListItem.setOnAction(e -> control.tab.select(control.worldListTab)); AdvancedListBox sideBar = new AdvancedListBox() .add(versionSettingsItem) - .add(modListItem) .add(installerListItem) + .add(modListItem) .add(worldListItem); VBox.setVgrow(sideBar, Priority.ALWAYS); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java index dbdd0813d7..20dd1c4d8b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java @@ -60,10 +60,27 @@ import java.util.concurrent.atomic.AtomicBoolean; import static org.jackhuang.hmcl.ui.FXUtils.stringConverter; +import static org.jackhuang.hmcl.util.Lang.getTimer; import static org.jackhuang.hmcl.util.Pair.pair; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public final class VersionSettingsPage extends StackPane implements DecoratorPage, VersionPage.VersionLoadable, PageAware { + + private static final ObjectProperty memoryStatus = new SimpleObjectProperty<>(OperatingSystem.PhysicalMemoryStatus.INVALID); + private static TimerTask memoryStatusUpdateTask; + private static void initMemoryStatusUpdateTask() { + FXUtils.checkFxUserThread(); + if (memoryStatusUpdateTask != null) + return; + memoryStatusUpdateTask = new TimerTask() { + @Override + public void run() { + Platform.runLater(() -> memoryStatus.set(OperatingSystem.getPhysicalMemoryStatus())); + } + }; + getTimer().scheduleAtFixedRate(memoryStatusUpdateTask, 0, 1000); + } + private final ReadOnlyObjectWrapper state = new ReadOnlyObjectWrapper<>(new State("", null, false, false, false)); private AdvancedVersionSettingPage advancedVersionSettingPage; @@ -95,7 +112,8 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag private final ImagePickerItem iconPickerItem; private final ChangeListener> javaListChangeListener; - private final InvalidationListener specificSettingsListener; + private final InvalidationListener usesGlobalListener; + private final ChangeListener specificSettingsListener; private final InvalidationListener javaListener = any -> initJavaSubtitle(); private boolean updatingJavaSetting = false; private boolean updatingSelectedJava = false; @@ -104,7 +122,6 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag private final BooleanProperty navigateToSpecificSettings = new SimpleBooleanProperty(false); private final BooleanProperty enableSpecificSettings = new SimpleBooleanProperty(false); private final IntegerProperty maxMemory = new SimpleIntegerProperty(); - private final ObjectProperty memoryStatus = new SimpleObjectProperty<>(OperatingSystem.PhysicalMemoryStatus.INVALID); private final BooleanProperty modpack = new SimpleBooleanProperty(); public VersionSettingsPage(boolean globalSetting) { @@ -188,7 +205,7 @@ public VersionSettingsPage(boolean globalSetting) { javaCustomOption = new MultiFileItem.FileOption>(i18n("settings.custom"), pair(JavaVersionType.CUSTOM, null)) .setChooserTitle(i18n("settings.game.java_directory.choose")); if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) - javaCustomOption.getExtensionFilters().add(new FileChooser.ExtensionFilter("Java", "java.exe")); + javaCustomOption.addExtensionFilter(new FileChooser.ExtensionFilter("Java", "java.exe")); javaListChangeListener = FXUtils.onWeakChangeAndOperate(JavaManager.getAllJavaProperty(), allJava -> { List>> options = new ArrayList<>(); @@ -263,6 +280,7 @@ protected Node createItem(ToggleGroup group) { VBox.setMargin(chkAutoAllocate, new Insets(0, 0, 8, 5)); HBox lowerBoundPane = new HBox(8); + lowerBoundPane.setStyle("-fx-view-order: -1;"); // prevent the indicator from being covered by the progress bar lowerBoundPane.setAlignment(Pos.CENTER); VBox.setMargin(lowerBoundPane, new Insets(0, 0, 0, 16)); { @@ -471,22 +489,8 @@ protected Node createItem(ToggleGroup group) { rootPane.getChildren().add(componentList); - initialize(); - - specificSettingsListener = any -> enableSpecificSettings.set(!lastVersionSetting.isUsesGlobal()); - - addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onDecoratorPageNavigating); - - cboLauncherVisibility.getItems().setAll(LauncherVisibility.values()); - cboLauncherVisibility.setConverter(stringConverter(e -> i18n("settings.advanced.launcher_visibility." + e.name().toLowerCase(Locale.ROOT)))); - - cboProcessPriority.getItems().setAll(ProcessPriority.values()); - cboProcessPriority.setConverter(stringConverter(e -> i18n("settings.advanced.process_priority." + e.name().toLowerCase(Locale.ROOT)))); - } - - private void initialize() { - memoryStatus.set(OperatingSystem.getPhysicalMemoryStatus()); - enableSpecificSettings.addListener((a, b, newValue) -> { + usesGlobalListener = any -> enableSpecificSettings.set(!lastVersionSetting.isUsesGlobal()); + specificSettingsListener = (a, b, newValue) -> { if (versionId == null) return; // do not call versionSettings.setUsesGlobal(true/false) @@ -498,9 +502,20 @@ private void initialize() { profile.getRepository().globalizeVersionSetting(versionId); Platform.runLater(() -> loadVersion(profile, versionId)); - }); + }; + addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onDecoratorPageNavigating); + + cboLauncherVisibility.getItems().setAll(LauncherVisibility.values()); + cboLauncherVisibility.setConverter(stringConverter(e -> i18n("settings.advanced.launcher_visibility." + e.name().toLowerCase(Locale.ROOT)))); + + cboProcessPriority.getItems().setAll(ProcessPriority.values()); + cboProcessPriority.setConverter(stringConverter(e -> i18n("settings.advanced.process_priority." + e.name().toLowerCase(Locale.ROOT)))); + + memoryStatus.set(OperatingSystem.getPhysicalMemoryStatus()); componentList.disableProperty().bind(enableSpecificSettings.not()); + + initMemoryStatusUpdateTask(); } @Override @@ -549,7 +564,7 @@ public void loadVersion(Profile profile, String versionId) { FXUtils.unbindEnum(cboLauncherVisibility); FXUtils.unbindEnum(cboProcessPriority); - lastVersionSetting.usesGlobalProperty().removeListener(specificSettingsListener); + lastVersionSetting.usesGlobalProperty().removeListener(usesGlobalListener); lastVersionSetting.javaVersionTypeProperty().removeListener(javaListener); lastVersionSetting.javaDirProperty().removeListener(javaListener); lastVersionSetting.defaultJavaPathPropertyProperty().removeListener(javaListener); @@ -558,6 +573,8 @@ public void loadVersion(Profile profile, String versionId) { gameDirItem.selectedDataProperty().unbindBidirectional(lastVersionSetting.gameDirTypeProperty()); gameDirSublist.subtitleProperty().unbind(); + enableSpecificSettings.removeListener(specificSettingsListener); + if (advancedVersionSettingPage != null) { advancedVersionSettingPage.unbindProperties(); advancedVersionSettingPage = null; @@ -582,9 +599,10 @@ public void loadVersion(Profile profile, String versionId) { FXUtils.bindEnum(cboLauncherVisibility, versionSetting.launcherVisibilityProperty()); FXUtils.bindEnum(cboProcessPriority, versionSetting.processPriorityProperty()); - versionSetting.usesGlobalProperty().addListener(specificSettingsListener); if (versionId != null) enableSpecificSettings.set(!versionSetting.isUsesGlobal()); + versionSetting.usesGlobalProperty().addListener(usesGlobalListener); + enableSpecificSettings.addListener(specificSettingsListener); javaItem.setToggleSelectedListener(newValue -> { if (javaItem.getSelectedData() == null || updatingSelectedJava) @@ -715,7 +733,7 @@ private void initJavaSubtitle() { version = null; } else { gameVersionNumber = GameVersionNumber.asGameVersion(repository.getGameVersion(versionId)); - version = repository.getVersion(versionId); + version = repository.getResolvedVersion(versionId); } try { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java index 2b6277d427..94bdc1cd18 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java @@ -83,7 +83,7 @@ public static void downloadModpackImpl(Profile profile, String version, RemoteMo } catch (IOException e) { Controllers.dialog( i18n("install.failed.downloading.detail", file.getFile().getUrl()) + "\n" + StringUtils.getStackTrace(e), - i18n("download.failed.no_code", file.getFile().getUrl()), MessageDialogPane.MessageType.ERROR); + i18n("download.failed.no_code"), MessageDialogPane.MessageType.ERROR); return; } Controllers.taskDialog( @@ -96,7 +96,7 @@ public static void downloadModpackImpl(Profile profile, String version, RemoteMo } else { Controllers.dialog( i18n("install.failed.downloading.detail", file.getFile().getUrl()) + "\n" + StringUtils.getStackTrace(e), - i18n("download.failed.no_code", file.getFile().getUrl()), MessageDialogPane.MessageType.ERROR); + i18n("download.failed.no_code"), MessageDialogPane.MessageType.ERROR); } }).executor(true), i18n("message.downloading"), diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldExportPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldExportPageSkin.java index 38af59549b..98226cbb28 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldExportPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldExportPageSkin.java @@ -83,7 +83,7 @@ public WorldExportPageSkin(WorldExportPage skinnable) { JFXButton btnExport = FXUtils.newRaisedButton(i18n("button.export")); btnExport.disableProperty().bind(Bindings.createBooleanBinding(() -> txtWorldName.getText().isEmpty() || Files.exists(Paths.get(fileItem.getPath())), txtWorldName.textProperty().isEmpty(), fileItem.pathProperty())); - btnExport.setOnMouseClicked(e -> skinnable.export()); + btnExport.setOnAction(e -> skinnable.export()); HBox bottom = new HBox(); bottom.setAlignment(Pos.CENTER_RIGHT); bottom.getChildren().setAll(btnExport); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java index d7ab4e1863..a8d7e2c5bd 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java @@ -1,3 +1,20 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui 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 . + */ package org.jackhuang.hmcl.ui.versions; import com.github.steveice10.opennbt.tag.builtin.*; @@ -10,11 +27,14 @@ import javafx.collections.ObservableList; import javafx.geometry.Pos; import javafx.scene.control.Label; +import javafx.scene.control.ProgressIndicator; import javafx.scene.control.ScrollPane; import javafx.scene.layout.BorderPane; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import org.jackhuang.hmcl.game.World; +import org.jackhuang.hmcl.task.Schedulers; +import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.construct.ComponentList; import org.jackhuang.hmcl.ui.construct.DoubleValidator; @@ -32,22 +52,36 @@ import static org.jackhuang.hmcl.util.i18n.I18n.formatDateTime; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; +/** + * @author Glavo + */ public final class WorldInfoPage extends StackPane implements DecoratorPage { private final World world; - private final CompoundTag levelDat; - private final CompoundTag dataTag; + private CompoundTag levelDat; - private final ObjectProperty stateProperty = new SimpleObjectProperty<>(); + private final ObjectProperty stateProperty; - public WorldInfoPage(World world) throws IOException { + public WorldInfoPage(World world) { this.world = world; - this.levelDat = world.readLevelDat(); - this.dataTag = levelDat.get("Data"); + this.stateProperty = new SimpleObjectProperty<>(State.fromTitle(i18n("world.info.title", world.getWorldName()))); + + this.getChildren().add(new ProgressIndicator()); + Task.supplyAsync(world::readLevelDat) + .whenComplete(Schedulers.javafx(), ((result, exception) -> { + if (exception == null) { + this.levelDat = result; + loadWorldInfo(); + } else { + LOG.warning("Failed to load level.dat", exception); + this.getChildren().setAll(new Label(i18n("world.info.failed"))); + } + })).start(); + } + private void loadWorldInfo() { + CompoundTag dataTag = levelDat.get("Data"); CompoundTag worldGenSettings = dataTag.get("WorldGenSettings"); - stateProperty.set(State.fromTitle(i18n("world.info.title", world.getWorldName()))); - ScrollPane scrollPane = new ScrollPane(); scrollPane.setFitToHeight(true); scrollPane.setFitToWidth(true); @@ -483,7 +517,6 @@ String formatPosition(Tag tag) { Tag z = listTag.get(2); if (x instanceof DoubleTag && y instanceof DoubleTag && z instanceof DoubleTag) { - //noinspection MalformedFormatString return this == OVERWORLD ? String.format("(%.2f, %.2f, %.2f)", x.getValue(), y.getValue(), z.getValue()) : String.format("%s (%.2f, %.2f, %.2f)", name, x.getValue(), y.getValue(), z.getValue()); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListItem.java index 5f98c22554..4a45486ced 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListItem.java @@ -51,7 +51,7 @@ public WorldListItem(World world) { subtitle.set(i18n("world.description", world.getFileName(), formatDateTime(Instant.ofEpochMilli(world.getLastPlayed())), world.getGameVersion() == null ? i18n("message.unknown") : world.getGameVersion())); - setOnMouseClicked(event -> showInfo()); + FXUtils.onClicked(this, this::showInfo); } @Override @@ -102,10 +102,6 @@ public void manageDatapacks() { } public void showInfo() { - try { - Controllers.navigate(new WorldInfoPage(world)); - } catch (Exception e) { - // TODO - } + Controllers.navigate(new WorldInfoPage(world)); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListItemSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListItemSkin.java index e83c7f962f..27a8f3dbd1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListItemSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListItemSkin.java @@ -76,9 +76,7 @@ public WorldListItemSkin(WorldListItem skinnable) { right.setAlignment(Pos.CENTER_RIGHT); JFXButton btnManage = new JFXButton(); - btnManage.setOnMouseClicked(e -> { - popup.show(root, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, root.getHeight()); - }); + btnManage.setOnAction(e -> popup.show(root, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, root.getHeight())); btnManage.getStyleClass().add("toggle-icon4"); BorderPane.setAlignment(btnManage, Pos.CENTER); btnManage.setGraphic(SVG.DOTS_VERTICAL.createIcon(Theme.blackFill(), -1, -1)); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardPage.java index cf5c2438b7..b9d04ba341 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardPage.java @@ -23,7 +23,8 @@ public interface WizardPage { default void onNavigate(Map settings) { } - void cleanup(Map settings); + default void cleanup(Map settings) { + } String getTitle(); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/util/NativePatcher.java b/HMCL/src/main/java/org/jackhuang/hmcl/util/NativePatcher.java index 79d21fbb4a..8052493ee9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/util/NativePatcher.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/util/NativePatcher.java @@ -17,7 +17,6 @@ */ package org.jackhuang.hmcl.util; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.game.*; import org.jackhuang.hmcl.setting.VersionSetting; import org.jackhuang.hmcl.util.gson.JsonUtils; @@ -34,6 +33,7 @@ import java.util.*; import java.util.stream.Collectors; +import static org.jackhuang.hmcl.util.gson.JsonUtils.mapTypeOf; import static org.jackhuang.hmcl.util.logging.Logger.LOG; /** @@ -51,9 +51,7 @@ private static Map getNatives(Platform platform) { return natives.computeIfAbsent(platform, p -> { //noinspection ConstantConditions try (Reader reader = new InputStreamReader(NativePatcher.class.getResourceAsStream("/assets/natives.json"), StandardCharsets.UTF_8)) { - Map> natives = JsonUtils.GSON.fromJson(reader, new TypeToken>>() { - }.getType()); - + Map> natives = JsonUtils.GSON.fromJson(reader, mapTypeOf(String.class, mapTypeOf(String.class, Library.class))); return natives.getOrDefault(p.toString(), Collections.emptyMap()); } catch (IOException e) { LOG.warning("Failed to load native library list", e); @@ -155,6 +153,6 @@ public static Version patchNative(Version version, String gameVersion, JavaRunti } public static Library getMesaLoader(JavaRuntime javaVersion, Renderer renderer) { - return getNatives(javaVersion.getPlatform()).get(renderer == Renderer.LLVMPIPE ? "software-renderer-loader" : "mesa-loader"); + return getNatives(javaVersion.getPlatform()).get("mesa-loader"); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/util/SelfDependencyPatcher.java b/HMCL/src/main/java/org/jackhuang/hmcl/util/SelfDependencyPatcher.java index cde4fcda0d..8d88115fde 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/util/SelfDependencyPatcher.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/util/SelfDependencyPatcher.java @@ -41,10 +41,9 @@ */ package org.jackhuang.hmcl.util; -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.Main; import org.jackhuang.hmcl.ui.SwingUtils; +import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.io.ChecksumMismatchException; import org.jackhuang.hmcl.util.io.IOUtils; import org.jackhuang.hmcl.util.io.JarUtils; @@ -68,6 +67,8 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.stream.Collectors.toSet; import static org.jackhuang.hmcl.Metadata.HMCL_DIRECTORY; +import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf; +import static org.jackhuang.hmcl.util.gson.JsonUtils.mapTypeOf; import static org.jackhuang.hmcl.util.logging.Logger.LOG; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; @@ -104,28 +105,14 @@ private static final class DependencyDescriptor { private static final Path DEPENDENCIES_DIR_PATH = HMCL_DIRECTORY.resolve("dependencies").resolve(Platform.getPlatform().toString()).resolve("openjfx"); static List readDependencies() { - ArrayList dependencies; //noinspection ConstantConditions try (Reader reader = new InputStreamReader(SelfDependencyPatcher.class.getResourceAsStream(DEPENDENCIES_LIST_FILE), UTF_8)) { - Map> allDependencies = - new Gson().fromJson(reader, new TypeToken>>(){}.getType()); - dependencies = allDependencies.get(Platform.getPlatform().toString()); + Map> allDependencies = + JsonUtils.GSON.fromJson(reader, mapTypeOf(String.class, listTypeOf(DependencyDescriptor.class))); + return allDependencies.get(Platform.getPlatform().toString()); } catch (IOException e) { throw new UncheckedIOException(e); } - - if (dependencies == null) return null; - - try { - ClassLoader classLoader = SelfDependencyPatcher.class.getClassLoader(); - Class.forName("netscape.javascript.JSObject", false, classLoader); - Class.forName("org.w3c.dom.html.HTMLDocument", false, classLoader); - } catch (Throwable e) { - LOG.warning("Disable javafx.web because JRE is incomplete", e); - dependencies.removeIf(it -> "javafx.web".equals(it.module) || "javafx.media".equals(it.module)); - } - - return dependencies; } public String module; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/util/SwingFXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/util/SwingFXUtils.java new file mode 100644 index 0000000000..1163c0e8ce --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/util/SwingFXUtils.java @@ -0,0 +1,124 @@ +// Copy from javafx.swing +/* + * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jackhuang.hmcl.util; + +import javafx.scene.image.*; +import javafx.scene.image.Image; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; +import java.awt.image.SampleModel; +import java.awt.image.SinglePixelPackedSampleModel; +import java.nio.IntBuffer; + +/** + * This class provides utility methods for converting data types between + * Swing/AWT and JavaFX formats. + * + * @since JavaFX 2.2 + */ +public final class SwingFXUtils { + private SwingFXUtils() { + } + + /** + * Snapshots the specified {@link BufferedImage} and stores a copy of + * its pixels into a JavaFX {@link Image} object, creating a new + * object if needed. + * The returned {@code Image} will be a static snapshot of the state + * of the pixels in the {@code BufferedImage} at the time the method + * completes. Further changes to the {@code BufferedImage} will not + * be reflected in the {@code Image}. + *

+ * The optional JavaFX {@link WritableImage} parameter may be reused + * to store the copy of the pixels. + * A new {@code Image} will be created if the supplied object is null, + * is too small or of a type which the image pixels cannot be easily + * converted into. + * + * @param bimg the {@code BufferedImage} object to be converted + * @param wimg an optional {@code WritableImage} object that can be + * used to store the returned pixel data + * @return an {@code Image} object representing a snapshot of the + * current pixels in the {@code BufferedImage}. + * @since JavaFX 2.2 + */ + public static WritableImage toFXImage(BufferedImage bimg, WritableImage wimg) { + int bw = bimg.getWidth(); + int bh = bimg.getHeight(); + switch (bimg.getType()) { + case BufferedImage.TYPE_INT_ARGB: + case BufferedImage.TYPE_INT_ARGB_PRE: + break; + default: + BufferedImage converted = + new BufferedImage(bw, bh, BufferedImage.TYPE_INT_ARGB_PRE); + Graphics2D g2d = converted.createGraphics(); + g2d.drawImage(bimg, 0, 0, null); + g2d.dispose(); + bimg = converted; + break; + } + // assert(bimg.getType == TYPE_INT_ARGB[_PRE]); + if (wimg != null) { + int iw = (int) wimg.getWidth(); + int ih = (int) wimg.getHeight(); + if (iw < bw || ih < bh) { + wimg = null; + } else if (bw < iw || bh < ih) { + int[] empty = new int[iw]; + PixelWriter pw = wimg.getPixelWriter(); + PixelFormat pf = PixelFormat.getIntArgbPreInstance(); + if (bw < iw) { + pw.setPixels(bw, 0, iw - bw, bh, pf, empty, 0, 0); + } + if (bh < ih) { + pw.setPixels(0, bh, iw, ih - bh, pf, empty, 0, 0); + } + } + } + if (wimg == null) { + wimg = new WritableImage(bw, bh); + } + PixelWriter pw = wimg.getPixelWriter(); + DataBufferInt db = (DataBufferInt) bimg.getRaster().getDataBuffer(); + int[] data = db.getData(); + int offset = bimg.getRaster().getDataBuffer().getOffset(); + int scan = 0; + SampleModel sm = bimg.getRaster().getSampleModel(); + if (sm instanceof SinglePixelPackedSampleModel) { + scan = ((SinglePixelPackedSampleModel) sm).getScanlineStride(); + } + + PixelFormat pf = (bimg.isAlphaPremultiplied() ? + PixelFormat.getIntArgbPreInstance() : + PixelFormat.getIntArgbInstance()); + pw.setPixels(0, 0, bw, bh, pf, data, offset, scan); + return wimg; + } +} + diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/Locales.java b/HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/Locales.java index 6f5309d3e1..868371dce8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/Locales.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/Locales.java @@ -39,16 +39,6 @@ private Locales() { */ public static final SupportedLocale EN = new SupportedLocale(Locale.ROOT); - /** - * Traditional Chinese - */ - public static final SupportedLocale ZH = new SupportedLocale(Locale.TRADITIONAL_CHINESE); - - /** - * Simplified Chinese - */ - public static final SupportedLocale ZH_CN = new SupportedLocale(Locale.SIMPLIFIED_CHINESE); - /** * Spanish */ @@ -64,23 +54,33 @@ private Locales() { */ public static final SupportedLocale JA = new SupportedLocale(Locale.JAPANESE); - public static final List LOCALES = Lang.immutableListOf(DEFAULT, EN, ZH_CN, ZH, ES, RU, JA); + /** + * Traditional Chinese + */ + public static final SupportedLocale ZH = new SupportedLocale(Locale.TRADITIONAL_CHINESE); + + /** + * Simplified Chinese + */ + public static final SupportedLocale ZH_CN = new SupportedLocale(Locale.SIMPLIFIED_CHINESE); + + public static final List LOCALES = Lang.immutableListOf(DEFAULT, EN, ES, JA, RU, ZH_CN, ZH); public static SupportedLocale getLocaleByName(String name) { if (name == null) return DEFAULT; switch (name.toLowerCase(Locale.ROOT)) { case "en": return EN; - case "zh": - return ZH; - case "zh_cn": - return ZH_CN; case "es": return ES; - case "ru": - return RU; case "ja": return JA; + case "ru": + return RU; + case "zh": + return ZH; + case "zh_cn": + return ZH_CN; default: return DEFAULT; } @@ -88,11 +88,11 @@ public static SupportedLocale getLocaleByName(String name) { public static String getNameByLocale(SupportedLocale locale) { if (locale == EN) return "en"; - else if (locale == ZH) return "zh"; - else if (locale == ZH_CN) return "zh_CN"; else if (locale == ES) return "es"; else if (locale == RU) return "ru"; else if (locale == JA) return "ja"; + else if (locale == ZH) return "zh"; + else if (locale == ZH_CN) return "zh_CN"; else if (locale == DEFAULT) return "def"; else throw new IllegalArgumentException("Unknown locale: " + locale); } diff --git a/HMCL/src/main/java11/org/jackhuang/hmcl/util/logging/CallerFinder.java b/HMCL/src/main/java11/org/jackhuang/hmcl/util/logging/CallerFinder.java index 89547137f8..8c934cdaab 100644 --- a/HMCL/src/main/java11/org/jackhuang/hmcl/util/logging/CallerFinder.java +++ b/HMCL/src/main/java11/org/jackhuang/hmcl/util/logging/CallerFinder.java @@ -9,13 +9,13 @@ * @author Glavo */ final class CallerFinder { - private static final StackWalker WALKER = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); private static final String PACKAGE_PREFIX = CallerFinder.class.getPackageName() + "."; private static final Predicate PREDICATE = stackFrame -> !stackFrame.getClassName().startsWith(PACKAGE_PREFIX); private static final Function, Optional> FUNCTION = stream -> stream.filter(PREDICATE).findFirst(); + private static final Function FRAME_MAPPING = frame -> frame.getClassName() + "." + frame.getMethodName(); static String getCaller() { - return WALKER.walk(FUNCTION).map(it -> it.getClassName() + "." + it.getMethodName()).orElse(null); + return StackWalker.getInstance().walk(FUNCTION).map(FRAME_MAPPING).orElse(null); } private CallerFinder() { diff --git a/HMCL/src/main/resources/assets/HMCLauncher.exe b/HMCL/src/main/resources/assets/HMCLauncher.exe index 1278064551..12ccc52167 100644 Binary files a/HMCL/src/main/resources/assets/HMCLauncher.exe and b/HMCL/src/main/resources/assets/HMCLauncher.exe differ diff --git a/HMCL/src/main/resources/assets/HMCLauncher.sh b/HMCL/src/main/resources/assets/HMCLauncher.sh index 824c0aa552..4b61be0031 100644 --- a/HMCL/src/main/resources/assets/HMCLauncher.sh +++ b/HMCL/src/main/resources/assets/HMCLauncher.sh @@ -33,6 +33,8 @@ case "$(uname -m)" in _HMCL_ARCH="arm64";; arm|arm32|aarch32|armv7*) _HMCL_ARCH="arm32";; + riscv64) + _HMCL_ARCH="riscv64";; loongarch64) _HMCL_ARCH="loongarch64";; *) @@ -56,17 +58,42 @@ else _HMCL_VM_OPTIONS="-XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=15" fi +function show_warning_console() { + echo -e "\033[1;31m$1\033[0m" >&2 +} + +function show_warning_dialog() { + if [ -n "$3" ]; then + if [ -n "$(command -v xdg-open)" ]; then + if [ -n "$(command -v zenity)" ]; then + zenity --question --title="$1" --text="$2" && xdg-open "$3" + elif [ -n "$(command -v kdialog)" ]; then + kdialog --title "$1" --warningyesno "$2" && xdg-open "$3" + fi + fi + else + if [ -n "$(command -v zenity)" ]; then + zenity --info --title="$1" --text="$2" + elif [ -n "$(command -v kdialog)" ]; then + kdialog --title "$1" --msgbox "$2" + fi + fi +} + +function show_warning() { + show_warning_console "$1: $2" + show_warning_dialog "$1" "$2" +} + # First, find Java in HMCL_JAVA_HOME if [ -n "${HMCL_JAVA_HOME+x}" ]; then if [ -x "$HMCL_JAVA_HOME/bin/$_HMCL_JAVA_EXE_NAME" ]; then exec "$HMCL_JAVA_HOME/bin/$_HMCL_JAVA_EXE_NAME" $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH" else if [ "$_HMCL_USE_CHINESE" == true ]; then - echo "环境变量 HMCL_JAVA_HOME 的值无效,请设置为合法的 Java 路径。" 1>&2 - echo "你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。" 1>&2 + show_warning "错误" "环境变量 HMCL_JAVA_HOME 的值无效,请设置为合法的 Java 路径。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。" else - echo "The value of the environment variable HMCL_JAVA_HOME is invalid, please set it to a valid Java path." 1>&2 - echo "You can visit the https://docs.hmcl.net/help.html page for help." 1>&2 + show_warning "Error" "The value of the environment variable HMCL_JAVA_HOME is invalid, please set it to a valid Java path.\nYou can visit the https://docs.hmcl.net/help.html page for help." fi exit 1 fi @@ -97,6 +124,11 @@ case "$_HMCL_ARCH" in exec "$_HMCL_DIR/jre-arm32/bin/$_HMCL_JAVA_EXE_NAME" $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH" fi ;; + riscv64) + if [ -x "$_HMCL_DIR/jre-riscv64/bin/$_HMCL_JAVA_EXE_NAME" ]; then + exec "$_HMCL_DIR/jre-riscv64/bin/$_HMCL_JAVA_EXE_NAME" $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH" + fi + ;; loongarch64) if [ -x "$_HMCL_DIR/jre-loongarch64/bin/$_HMCL_JAVA_EXE_NAME" ]; then exec "$_HMCL_DIR/jre-loongarch64/bin/$_HMCL_JAVA_EXE_NAME" $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH" @@ -114,6 +146,10 @@ if [ -x "$(command -v $_HMCL_JAVA_EXE_NAME)" ]; then exec $_HMCL_JAVA_EXE_NAME $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH" fi +if [ "$_HMCL_OS" == "freebsd" ] && [ -x "$(command -v javavm)" ]; then + exec javavm $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH" +fi + # Java not found if [ "$_HMCL_OS" == "osx" ]; then @@ -123,28 +159,28 @@ else fi case "$_HMCL_OS-$_HMCL_ARCH" in - windows-x86|windows-x86_64|windows-arm64|linux-x86|linux-x86_64|linux-arm32|linux-arm64|linux-loongarch64|macos-x86_64|macos-arm64) - if [ "$_HMCL_USE_CHINESE" == true ]; then - echo "运行 HMCL 需要 Java 运行时环境,请安装 Java 并设置环境变量后重试。" 1>&2 - echo "https://docs.hmcl.net/downloads/$_HMCL_DOWNLOAD_PAGE_OS/$_HMCL_HMCL_ARCH.html" 1>&2 - echo "你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。" 1>&2 - else - echo "The Java runtime environment is required to run HMCL. " 1>&2 - echo "Please install Java and set the environment variables and try again." 1>&2 - echo "https://docs.hmcl.net/downloads/$_HMCL_DOWNLOAD_PAGE_OS/$_HMCL_HMCL_ARCH.html" 1>&2 - echo "You can visit the https://docs.hmcl.net/help.html page for help." 1>&2 - fi - ;; - *) - if [ "$_HMCL_USE_CHINESE" == true ]; then - echo "运行 HMCL 需要 Java 运行时环境,请安装 Java 并设置环境变量后重试。" 1>&2 - echo "你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。" 1>&2 - else - echo "The Java runtime environment is required to run HMCL. " 1>&2 - echo "Please install Java and set the environment variables and try again." 1>&2 - echo "You can visit the https://docs.hmcl.net/help.html page for help." 1>&2 - fi + windows-x86|windows-x86_64|windows-arm64|linux-x86|linux-x86_64|linux-arm32|linux-arm64|linux-riscv64|linux-loongarch64|macos-x86_64|macos-arm64) + _HMCL_JAVA_DOWNLOAD_PAGE="https://docs.hmcl.net/downloads/$_HMCL_DOWNLOAD_PAGE_OS/$_HMCL_ARCH.html" ;; esac +if [ "$_HMCL_USE_CHINESE" == true ]; then + _HMCL_WARNING_MESSAGE="运行 HMCL 需要 Java 运行时环境,请安装 Java 并设置环境变量后重试。" + + if [ -n "$_HMCL_JAVA_DOWNLOAD_PAGE" ]; then + show_warning_console "错误: $_HMCL_WARNING_MESSAGE\n你可以前往此处下载:\n$_HMCL_JAVA_DOWNLOAD_PAGE" + show_warning_dialog "错误" "$_HMCL_WARNING_MESSAGE\n\n是否要前往 Java 下载页面?" "$_HMCL_JAVA_DOWNLOAD_PAGE" + else + show_warning "错误" "$_HMCL_WARNING_MESSAGE\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。" + fi +else + _HMCL_WARNING_MESSAGE="The Java runtime environment is required to run HMCL.\nPlease install Java and set the environment variables and try again." + if [ -n "$_HMCL_JAVA_DOWNLOAD_PAGE" ]; then + show_warning_console "Error: $_HMCL_WARNING_MESSAGE\nYou can download it from here:\n$_HMCL_JAVA_DOWNLOAD_PAGE" + show_warning_dialog "Error" "$_HMCL_WARNING_MESSAGE\n\nDo you want to go to the Java download page?" "$_HMCL_JAVA_DOWNLOAD_PAGE" + else + show_warning "Error" "$_HMCL_WARNING_MESSAGE\nYou can visit the https://docs.hmcl.net/help.html page for help." + fi +fi + exit 1 diff --git a/HMCL/src/main/resources/assets/about/deps.json b/HMCL/src/main/resources/assets/about/deps.json new file mode 100644 index 0000000000..5b0cf68475 --- /dev/null +++ b/HMCL/src/main/resources/assets/about/deps.json @@ -0,0 +1,62 @@ +[ + { + "title" : "OpenJFX", + "subtitle" : "Copyright © 2013, 2024, Oracle and/or its affiliates.\nLicensed under the GPL 2 with Classpath Exception.", + "externalLink" : "https://openjfx.io" + }, + { + "title" : "JFoenix", + "subtitle" : "Copyright © 2016 JFoenix.\nLicensed under the MIT License.", + "externalLink" : "https://github.com/sshahine/JFoenix" + }, + { + "title" : "Gson", + "subtitle" : "Copyright © 2008 Google Inc.\nLicensed under the Apache 2.0 License.", + "externalLink" : "https://github.com/google/gson" + }, + { + "title" : "Apache Commons Compress", + "subtitle" : "Licensed under the Apache 2.0 License.", + "externalLink" : "https://commons.apache.org/proper/commons-compress/" + }, + { + "title" : "XZ for Java", + "subtitle" : "Lasse Collin, Igor Pavlov, and/or Brett Okken.\nPublic Domain.", + "externalLink" : "https://tukaani.org/xz/java.html" + }, + { + "title" : "FX Gson", + "subtitle" : "Copyright © 2016 Joffrey Bion.\nLicensed under the MIT License.", + "externalLink" : "https://github.com/joffrey-bion/fx-gson" + }, + { + "title" : "jsoup", + "subtitle" : "Copyright © 2009 - 2024 Jonathan Hedley (https://jsoup.org/)\nLicensed under the MIT License.", + "externalLink" : "https://jsoup.org/" + }, + { + "title" : "NanoHTTPD", + "subtitle" : "Copyright © 2012 - 2015 nanohttpd.\nLicensed under the BSD 3-clause License.", + "externalLink" : "https://github.com/NanoHttpd/nanohttpd" + }, + { + "title" : "Constant Pool Scanner", + "subtitle" : "Copyright © 1997-2010 Oracle and/or its affiliates.\nLicensed under the GPL 2 or the CDDL.", + "externalLink" : "https://github.com/jenkinsci/constant-pool-scanner" + }, + { + "title" : "OpenNBT", + "subtitle" : "Copyright © 2013-2021 Steveice10.\nLicensed under the MIT License.", + "externalLink" : "https://github.com/GeyserMC/OpenNBT" + }, + { + "title" : "minecraft-jfx-skin", + "subtitle" : "Copyright © 2016 InfinityStudio.\nLicensed under the GPL 3.", + "externalLink" : "https://github.com/InfinityStudio/minecraft-jfx-skin" + }, + { + "title" : "SimplePNG", + "subtitle" : "Copyright © 2023 Glavo.\nLicensed under the Apache 2.0 License.", + "externalLink" : "https://github.com/Glavo/SimplePNG" + } +] \ No newline at end of file diff --git a/HMCL/src/main/resources/assets/about/thanks.json b/HMCL/src/main/resources/assets/about/thanks.json new file mode 100644 index 0000000000..189ff30129 --- /dev/null +++ b/HMCL/src/main/resources/assets/about/thanks.json @@ -0,0 +1,78 @@ +[ + { + "image" : "/assets/img/yushijinhun.png", + "title" : "yushijinhun", + "subtitleLocalized" : "about.thanks_to.yushijinhun.statement", + "externalLink" : "https://github.com/yushijinhun" + }, + { + "image" : "/assets/img/bangbang93.png", + "title" : "bangbang93", + "subtitleLocalized" : "about.thanks_to.bangbang93.statement", + "externalLink" : "https://www.bangbang93.com" + }, + { + "image" : "/assets/img/glavo.png", + "title" : "Glavo", + "subtitleLocalized" : "about.thanks_to.glavo.statement", + "externalLink" : "https://github.com/Glavo" + }, + { + "image" : "/assets/img/zekerzhayard.png", + "title" : "ZekerZhayard", + "subtitleLocalized" : "about.thanks_to.zekerzhayard.statement", + "externalLink" : "https://github.com/ZekerZhayard" + }, + { + "image" : "/assets/img/zkitefly.png", + "title" : "Zkitefly", + "subtitleLocalized" : "about.thanks_to.zkitefly.statement", + "externalLink" : "https://github.com/zkitefly" + }, + { + "image" : "/assets/img/burningtnt.png", + "title" : "Burning_TNT", + "subtitleLocalized" : "about.thanks_to.burningtnt.statement", + "externalLink" : "https://github.com/burningtnt" + }, + { + "image" : "/assets/img/ShulkerSakura.png", + "title" : "ShulkerSakura", + "subtitleLocalized" : "about.thanks_to.shulkersakura.statement", + "externalLink" : "https://github.com/ShulkerSakura" + }, + { + "image" : "/assets/img/gamerteam.png", + "title" : "gamerteam", + "subtitleLocalized" : "about.thanks_to.gamerteam.statement", + "externalLink" : "https://github.com/ZhaiSoul" + }, + { + "image" : "/assets/img/red_lnn.png", + "title" : "Red_lnn", + "subtitleLocalized" : "about.thanks_to.red_lnn.statement" + }, + { + "image" : "/assets/img/mcmod.png", + "titleLocalized" : "about.thanks_to.mcmod", + "subtitleLocalized" : "about.thanks_to.mcmod.statement", + "externalLink" : "https://www.mcmod.cn" + }, + { + "image" : "/assets/img/chest.png", + "titleLocalized" : "about.thanks_to.mcbbs", + "subtitleLocalized" : "about.thanks_to.mcbbs.statement" + }, + { + "image" : "/assets/img/github.png", + "titleLocalized" : "about.thanks_to.contributors", + "subtitleLocalized" : "about.thanks_to.contributors.statement", + "externalLink" : "https://github.com/HMCL-dev/HMCL/graphs/contributors" + }, + { + "image" : "/assets/img/icon.png", + "titleLocalized" : "about.thanks_to.users", + "subtitleLocalized" : "about.thanks_to.users.statement", + "externalLink" : "https://docs.hmcl.net/groups.html" + } +] \ No newline at end of file diff --git a/HMCL/src/main/resources/assets/css/blue.css b/HMCL/src/main/resources/assets/css/blue.css index c02cbb1b89..4e427b9c47 100644 --- a/HMCL/src/main/resources/assets/css/blue.css +++ b/HMCL/src/main/resources/assets/css/blue.css @@ -24,5 +24,8 @@ -fx-base-disabled-text-fill: rgba(256, 256, 256, 0.7); -fx-base-text-fill: white; + /* https://github.com/HMCL-dev/HMCL/pull/3423 */ + -fx-font-family: -fx-base-font-family; + -theme-thumb: rgba(92, 107, 192, 0.7); } \ No newline at end of file diff --git a/HMCL/src/main/resources/assets/css/root.css b/HMCL/src/main/resources/assets/css/root.css index 36c589bbe3..e836437e54 100644 --- a/HMCL/src/main/resources/assets/css/root.css +++ b/HMCL/src/main/resources/assets/css/root.css @@ -431,7 +431,6 @@ .jfx-layout-heading { -fx-font-size: 20.0px; - -fx-font-family: -fx-base-font-family; -fx-alignment: center-left; -fx-padding: 5.0 0.0 5.0 0.0; } @@ -449,18 +448,15 @@ .dialog-error { -fx-text-fill: #d32f2f; - -fx-font-family: -fx-base-font-family; -fx-padding: 0.7em 0.8em; } .dialog-accept { -fx-text-fill: #03A9F4; - -fx-font-family: -fx-base-font-family; -fx-padding: 0.7em 0.8em; } .dialog-cancel { - -fx-font-family: -fx-base-font-family; -fx-padding: 0.7em 0.8em; } @@ -1311,11 +1307,6 @@ -fx-padding: 8; } -.body { - -fx-border-radius: 5; - -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.4), 10, 0.3, 0.0, 0.0); -} - .debug-border { -fx-border-color: red; -fx-border-width: 1; @@ -1438,3 +1429,37 @@ .fit-width { -fx-pref-width: 100%; } + +/******************************************************************************* +* * +* HTML Renderer * +* * +*******************************************************************************/ + +.html { + -fx-font-size: 16; +} + +.html-hyperlink { + -fx-fill: blue; +} + +.html-h1 { + -fx-font-size: 22; +} + +.html-h2 { + -fx-font-size: 20; +} + +.html-h3 { + -fx-font-size: 18; +} + +.html-bold { + -fx-font-weight: bold; +} + +.html-italic { + -fx-font-style: italic; +} diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index e274433490..8a2d6fa944 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -1,6 +1,6 @@ # # Hello Minecraft! Launcher -# Copyright (C) 2023 huangyuhui and contributors +# Copyright (C) 2025 huangyuhui 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 @@ -21,123 +21,124 @@ about=About about.copyright=Copyright -about.copyright.statement=Copyright © 2024 huangyuhui. +about.copyright.statement=Copyright © 2025 huangyuhui about.author=Author -about.author.statement=@huanghongxun on bilibili +about.author.statement=bilibili @huanghongxun about.claim=EULA -about.claim.statement=Click on this link for full text. +about.claim.statement=Click this link for full text. about.dependency=Third-party Libraries about.legal=Legal Acknowledgement about.thanks_to=Thanks to -about.thanks_to.bangbang93.statement=For providing BMCLAPI download API, please consider donating. +about.thanks_to.bangbang93.statement=For providing the BMCLAPI download mirror. Please consider donating! about.thanks_to.burningtnt.statement=Contribute a lot of technical support to HMCL. about.thanks_to.contributors=All contributors on GitHub -about.thanks_to.contributors.statement=Without the awesome open-source community, Hello Minecraft! Launcher would not make it so far. +about.thanks_to.contributors.statement=Without the awesome open-source community, HMCL would not have made it so far. about.thanks_to.gamerteam.statement=For providing the default background image. about.thanks_to.glavo.statement=Responsible for maintaining HMCL. about.thanks_to.zekerzhayard.statement=Contribute a lot of technical support to HMCL. -about.thanks_to.zkitefly.statement=Responsible for maintaining documentation of HMCL. +about.thanks_to.zkitefly.statement=Responsible for maintaining the documentation of HMCL. about.thanks_to.mcbbs=MCBBS (Minecraft Chinese Forum) -about.thanks_to.mcbbs.statement=or providing mcbbs.net download mirror for Mainland China users. -about.thanks_to.mcmod=mcmod.cn -about.thanks_to.mcmod.statement=For providing Chinese translations and wiki for various mods. +about.thanks_to.mcbbs.statement=For providing the mcbbs.net download mirror for Chinese Mainland users. (No longer available) +about.thanks_to.mcmod=MCMod (mcmod.cn) +about.thanks_to.mcmod.statement=For providing the Simplified Chinese translations and wiki for various mods. about.thanks_to.red_lnn.statement=For providing the default background image. -about.thanks_to.shulkersakura.statement=For providing the icon for HMCL +about.thanks_to.shulkersakura.statement=For providing the logo for HMCL. about.thanks_to.users=HMCL User Group Members about.thanks_to.users.statement=Thanks for donations, bug reports, and so on. -about.thanks_to.yushijinhun.statement=For providing authlib-injector related support. +about.thanks_to.yushijinhun.statement=For providing the authlib-injector related support. about.open_source=Open Source about.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL) account=Accounts account.cape=Cape -account.character=character -account.choose=Choose a Character -account.create=Add an account -account.create.microsoft=Add a Microsoft account -account.create.offline=Add an offline account -account.create.authlibInjector=Create an authlib-injector account +account.character=Player +account.choose=Choose a Player +account.create=Add Account +account.create.microsoft=Add a Microsoft Account +account.create.offline=Add an Offline Account +account.create.authlibInjector=Add an authlib-injector Account account.email=Email -account.failed=Account refresh failed -account.failed.character_deleted=The character has already been deleted. -account.failed.connect_authentication_server=Unable to contact authentication servers, your Internet connection may be down. -account.failed.connect_injector_server=Unable to connect to the authentication server. Please check your network and make sure you entered the correct URL. -account.failed.injector_download_failure=Unable to download authlib-injector. Please check your network, or try switching to a different download mirror. -account.failed.invalid_credentials=Incorrect password or rate limited, please try again later. -account.failed.invalid_password=Invalid password +account.failed=Failed to refresh account. +account.failed.character_deleted=The player has already been deleted. +account.failed.connect_authentication_server=Failed to connect to the authentication server, your network connection may be down. +account.failed.connect_injector_server=Failed to connect to the authentication server. Please check your network and make sure you entered the correct URL. +account.failed.injector_download_failure=Failed to download the authlib-injector. Please check your network, or try switching to a different download source. +account.failed.invalid_credentials=Incorrect password or rate limited. Please try again later. +account.failed.invalid_password=Invalid password. account.failed.invalid_token=Please try logging in again. -account.failed.migration=Your account needs to be migrated to a Microsoft account. If you already did, you should re-login to your migrated Microsoft account instead. +account.failed.migration=Your account needs to be migrated to a Microsoft account. If you already did, you should log in using your migrated Microsoft account again. account.failed.no_character=There are no characters linked to this account. -account.failed.server_disconnected=Cannot access authentication server. You can log in offline or try logging in again.\n\ - If you try multiple times and still fail, please try adding the account again. -account.failed.server_response_malformed=Invalid server response, the authentication server may not be working. +account.failed.server_disconnected=Failed to connect to the authentication server. You can log in using offline mode or try logging in again.\n\ + If the issue persists after multiple attempts, please try logging in to the account again. +account.failed.server_response_malformed=Invalid server response. The authentication server may be down. account.failed.ssl=An SSL error occurred while connecting to the server. Please try updating your Java. account.failed.wrong_account=You have logged in to the wrong account. -account.hmcl.hint=You need to click on "Login" and complete the process in the opened tab in your browser. -# avoid too long sequence. +account.hmcl.hint=You need to click "Log in" and complete the process in the opened browser window. account.injector.add=New Auth Server -account.injector.empty=None (You can click on the plus button on the right to add one) -account.injector.http=Warning: This server uses the unsafe HTTP protocol, anyone between your connection will be able to see your credentials in cleartext. +account.injector.empty=None (You can click "+" to add one) +account.injector.http=Warning: This server uses the unsafe HTTP protocol. Anyone intercepting your connection would be able to see your credentials in plaintext. account.injector.link.homepage=Homepage account.injector.link.register=Register account.injector.server=Authentication Server account.injector.server_url=Server URL account.injector.server_name=Server Name -account.login=Login -account.login.hint=We will not store your password. -account.login.skip=Login offline +account.login=Log in +account.login.hint=We never store your password. +account.login.skip=Log in offline account.login.retry=Retry -account.login.refresh=Re-login -account.login.refresh.microsoft.hint=Because the account authorization is invalid, you need to re-add your Microsoft account +account.login.refresh=Log in again +account.login.refresh.microsoft.hint=You need to log in to your Microsoft account again because the account authorization is invalid. account.logout=Logout account.register=Register account.manage=Account List -account.copy_uuid=Copy the UUID of the account. +account.copy_uuid=Copy UUID of the Account account.methods=Login Type account.methods.authlib_injector=authlib-injector -account.methods.microsoft=Microsoft Account -account.methods.microsoft.birth=How to update your account birthday +account.methods.microsoft=Microsoft +account.methods.microsoft.birth=How to Change Your Account Birth Date account.methods.microsoft.close_page=Microsoft account authorization is now completed.\n\ -\n\ -There are some extra works for us, but you can safely close this tab for now. + \n\ + There are some extra works for us, but you can safely close this tab for now. account.methods.microsoft.deauthorize=Deauthorize -account.methods.microsoft.error.add_family=Since you are not yet 18 years old, an adult must add you to a family in order for you to play Minecraft. -account.methods.microsoft.error.add_family_probably=Please check if the age indicated in your account settings is at least 18 years old. If not and you believe this is an error, you can click on the above link to change it. +account.methods.microsoft.error.add_family=An adult must add you to a family in order for you to play Minecraft because you are not yet 18 years old. +account.methods.microsoft.error.add_family_probably=Please check if the age indicated in your account settings is at least 18 years old. If not and you believe this is an error, you can click "How to Change Your Account Birth Date" to learn how to change it. account.methods.microsoft.error.country_unavailable=Xbox Live is not available in your current country/region. -account.methods.microsoft.error.missing_xbox_account=Your Microsoft account does not have a linked Xbox account yet. Please create one before continuing. -account.methods.microsoft.error.no_character=Your account does not own the Minecraft Java Edition.\nThe game profile may not have been created,\nplease click the link above to create it. -account.methods.microsoft.error.unknown=Failed to log in, error: %d. -account.methods.microsoft.error.wrong_verify_method=Please log in using your account & password on the Microsoft account login page. Please do not use a verification code to log in. +account.methods.microsoft.error.missing_xbox_account=Your Microsoft account does not have a linked Xbox account yet. Please click "Create Profile / Edit Profile Name" to create one before continuing. +account.methods.microsoft.error.no_character=Please ensure you have purchased Minecraft: Java Edition.\nIf you have already purchased the game, a profile may not have been created yet.\nClick "Create Profile / Edit Profile Name" to create it. +account.methods.microsoft.error.unknown=Failed to log in, error code: %d. +account.methods.microsoft.error.wrong_verify_method=Please log in using your password on the Microsoft account login page, and do not use a verification code to log in. account.methods.microsoft.logging_in=Logging in... -account.methods.microsoft.hint=Please click on the "login" button, and copy the code shown here later to finish the login process in the opened browser window.\n\ -\n\ -If the token used to log in to the Microsoft account is leaked, you can click on "Deauthorize" to deauthorize it.\n\ -If you encounter any problems, you can click the help button in the upper right corner for help. -account.methods.microsoft.manual=Your device code is %1$s, please click here to copy. After clicking on the "Login" button, you should finish the login process in the opened browser window. If it did not show, you can go to %2$s manually.\n\ -\n\ -If the token used to log in to the Microsoft account is leaked, you can click on "Deauthorize" to deauthorize it.\n\ -If you encounter any problems, you can click the help button in the upper right corner for help. +account.methods.microsoft.hint=Please click "Log in" and copy the code displayed here to complete the login process in the browser window that opens.\n\ + \n\ + If the token used to log in to the Microsoft account is leaked, you can click "Deauthorize" to deauthorize it. +account.methods.microsoft.manual=Your device code is %1$s. Please click here to copy.\n\ + \n\ + After clicking "Log in", you should complete the login process in the opened browser window. If that does not show up, you can navigate to %2$s manually.\n\ + \n\ + If the token used to log in to the Microsoft account is leaked, you can click "Deauthorize" to deauthorize it. account.methods.microsoft.makegameidsettings=Create Profile / Edit Profile Name account.methods.microsoft.profile=Account Profile account.methods.microsoft.purchase=Buy Minecraft -account.methods.microsoft.snapshot=You are using an unofficial build of HMCL. Please download the official build for login. +account.methods.microsoft.snapshot=You are using an unofficial build of HMCL. Please download the official build to log in. account.methods.microsoft.snapshot.website=Official Website account.methods.offline=Offline -account.methods.offline.name.special_characters=Recommended to use letters, numbers and underscores for naming -account.methods.offline.name.invalid=Under normal circumstances, the game username can only contain English characters, numbers and underscores, and the length cannot exceed 16 characters.\n\ - Some legitimate usernames: HuangYu, huang_Yu, Huang_Yu_123;\n\ - Some illegal usernames: Huang Yu, Huang-Yu_%%%, Huang_Yu_hello_world_hello_world.\n\ - If you believe that there is a corresponding mod or plugin on the server side to remove this restriction, you can ignore this warning. +account.methods.offline.name.special_characters=Using only English letters, numbers, and underscores is recommended +account.methods.offline.name.invalid=Generally, game usernames are limited to English letters, numbers, and underscores and cannot exceed 16 characters in length.\n\ + \n\ + \ · Valid usernames: HuangYu, huang_Yu, Huang_Yu_123;\n\ + \ · Invalid usernames: Huang Yu, Huang-Yu_%%%, Huang_Yu_hello_world_hello_world.\n\ + \n\ + If you believe there is a corresponding mod or plugin on the server side to remove this restriction, you can ignore this warning. account.methods.offline.uuid=UUID -account.methods.offline.uuid.hint=UUID is the unique identifier for the game character in Minecraft. The way that it is generated might vary between different game launchers. Changing it to the one generated by other launchers allows you to keep your items in your offline account inventory.\n\ -\n\ -This option is for advanced users only. We do not recommend modifying this option unless you know what you are doing. -account.methods.offline.uuid.malformed=Invalid Format +account.methods.offline.uuid.hint=UUID is a unique identifier for Minecraft players, and each launcher may generate UUIDs differently. Changing it to the one generated by other launchers allows you to keep your items in your offline account inventory.\n\ + \n\ + This option is for advanced users only. We do not recommend changing this option unless you know what you are doing. +account.methods.offline.uuid.malformed=Invalid format. account.methods.forgot_password=Forgot Password account.missing=No Accounts account.missing.add=Click here to add one. -account.move_to_global=Convert to global account -account.move_to_portable=Convert to portable account +account.move_to_global=Convert to Global Account\nThe account information will be saved in a config file of the current user directory of the system. +account.move_to_portable=Convert to Portable Account\nThe account information will be saved in a config file in the same directory as HMCL. account.not_logged_in=Not Logged in account.password=Password account.portable=Portable Account @@ -150,11 +151,11 @@ 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.hint=You need to create a player with the same name as your offline account on your skin provider website. Your skin will then be your uploaded skin to that site. +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.upload=Upload Skin -account.skin.upload.failed=Unable to upload skin -account.skin.invalid_skin=Invalid skin file +account.skin.upload=Upload/Edit Skin +account.skin.upload.failed=Failed to upload skin. +account.skin.invalid_skin=Invalid skin file. account.username=Username archive.author=Author(s) @@ -163,8 +164,8 @@ archive.file.name=File Name archive.version=Version assets.download=Downloading Assets -assets.download_all=Validating assets integrity -assets.index.malformed=Index files of downloaded assets were corrupted. You can try using 'Update Game Assets' in its game instance settings to fix this issue. +assets.download_all=Validating Assets Integrity +assets.index.malformed=Index files of downloaded assets are corrupted. You can resolve this problem by clicking "Manage → Update Game Assets" on the "Edit Instance" page. button.cancel=Cancel button.change_source=Change Download Source @@ -183,14 +184,16 @@ button.retry=Retry button.save=Save button.save_as=Save As button.select_all=Select All +button.view=View button.yes=Yes chat=Join Group Chat color.recent=Recommended color.custom=Custom Color + crash.NoClassDefFound=Please verify the integrity of this software, or try updating your Java. -crash.user_fault=The launcher crashed due to a corrupted Java or system environment. Please make sure your Java or operating system is installed properly. +crash.user_fault=The launcher crashed due to corrupted Java or system environment. Please make sure your Java or OS is installed properly. curse.category.0=All @@ -223,8 +226,8 @@ curse.category.6954=Integrated Dynamics curse.category.6484=Create curse.category.6821=Bug Fixes curse.category.6145=Skyblock -curse.category.5190=QoL -curse.category.5191=Utility & QoL +curse.category.5190=QOL +curse.category.5191=Utility & QOL curse.category.5192=FancyMenu curse.category.423=Map and Information curse.category.426=Addons @@ -250,7 +253,7 @@ curse.category.430=Thaumcraft curse.category.422=Adventure and RPG curse.category.413=Processing curse.category.417=Energy -curse.category.415=Energy, Fluid and Item Transport +curse.category.415=Energy, Fluid, and Item Transport curse.category.433=Forestry curse.category.425=Miscellaneous curse.category.4545=Applied Energistics 2 @@ -317,280 +320,333 @@ curse.sort.popularity=Popularity curse.sort.total_downloads=Total Downloads download=Download -download.hint=Install games and modpacks or download mods, resource packs and worlds -download.code.404=File not found on the remote server: %s +download.hint=Install games and modpacks or download mods, resource packs, and worlds. +download.code.404=File "%s" not found on the remote server. download.content=Addons -download.curseforge.unavailable=HMCL nightly build does not support access to CurseForge, please use release version or beta version to download. -download.existing=The file cannot be saved because it already exists. You can use 'Save As' to save the file elsewhere. -download.external_link=Open Download Website -download.failed=Failed to download %1$s, response code: %2$d -download.failed.empty=No versions are available, please click here to go back. -download.failed.no_code=Failed to download %s -download.failed.refresh=Unable to fetch version list. Please click here to retry. -download.game=Game +download.curseforge.unavailable=HMCL Nightly channel build does not support access to CurseForge. Please use Release or Beta channel builds to download. +download.existing=The file cannot be saved because it already exists. You can click "Save As" to save the file elsewhere. +download.external_link=Visit Download Website +download.failed=Failed to download "%1$s", response code: %2$d. +download.failed.empty=No versions are available. Please click here to go back. +download.failed.no_code=Failed to download +download.failed.refresh=Failed to fetch version list. Please click here to retry. +download.game=New Game download.provider.bmclapi=BMCLAPI (bangbang93, https://bmclapi2.bangbang93.com/) -download.provider.mojang=Mojang (OptiFine is provided by BMCLAPI) +download.provider.mojang=Official (OptiFine is provided by BMCLAPI) download.provider.official=From Official Sources download.provider.balanced=From Fastest Available download.provider.mirror=From Mirror download.java=Downloading Java -download.java.override=This Java version already exists, do you want to uninstall and reinstall it? +download.java.override=This Java version already exists. Do you want to uninstall and reinstall it? download.javafx=Downloading dependencies for the launcher... download.javafx.notes=We are currently downloading dependencies for HMCL from the Internet.\n\ -\n\ -You can click on 'Change Download Source' to select the download source or\nclick on 'Cancel' to stop and exit.\n\ -Note: If your download speed is too slow, you can try switching to another mirror. -download.javafx.component=Downloading module %s + \n\ + You can click "Change Download Source" to choose the download source, or\nclick "Cancel" to stop and exit.\n\ + Note: If your download speed is too slow, you can try switching to another mirror. +download.javafx.component=Downloading module "%s" download.javafx.prepare=Preparing to download -exception.access_denied=HMCL is unable to access the file %s, it may be locked by another process.\n\ -\n\ -For Windows users, you can open up 'Resource Monitor' to check if another process is currently using it. If so, you can try again after closing that process.\n\ -If not, please check if your account has enough permissions to access it. -exception.artifact_malformed=Cannot verify the integrity of the downloaded files . -exception.ssl_handshake=Unable to establish SSL connection due to missing SSL certificates in current Java installation. You can try starting HMCL in another Java, then try again. -extension.bat=Windows Batch File(.bat) + +exception.access_denied=HMCL is unable to access the file "%s". It may be locked by another process.\n\ + \n\ + For Windows users, you can open "Resource Monitor" to check if another process is currently using it. If so, you can try again after terminating that process.\n\ + If not, please check if your user account has adequate permissions to access it. +exception.artifact_malformed=Cannot verify the integrity of the downloaded files. +exception.ssl_handshake=Failed to establish SSL connection because the SSL certificate is missing from the current Java installation. You can try opening HMCL with another Java installation and try again. + +extension.bat=Windows Batch File (.bat) extension.mod=Mod File extension.png=Image File -extension.ps1=Windows PowerShell Script(.ps1) -extension.sh=Shell Script(.sh) -fatal.fractureiser=Hello Minecraft! Launcher has detected that your computer has been infected with the Fraureiser virus, which poses a serious security issue.\n\ -Please use antivirus software to perform a full scan immediately, and then change the passwords of all accounts you have logged in on this computer. +extension.ps1=Windows PowerShell Script (.ps1) +extension.sh=Shell Script (.sh) + fatal.javafx.incompatible=Missing JavaFX environment.\n\ -HMCL cannot automatically install JavaFX under Java versions below 11.\n\ -Please update your Java to version 11 or higher. + 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 launch Hello Minecraft! Launcher with a Java which includes OpenJFX. -fatal.config_change_owner_root=You are using the root account to start Hello Minecraft! Launcher, this may cause you to fail to start Hello Minecraft! Launcher with other account in the future.\n\ -Do you still want to continue? -fatal.config_in_temp_dir=You are start Hello Minecraft! Launcher in a temporary directory, your settings and game data may be lost.\n\ -It is recommended to move HMCL to another location and restart it.\n\ -Do you still want to continue? + Please try replacing your Java or reinstalling OpenJFX. +fatal.javafx.missing=Missing JavaFX environment. Please open Hello Minecraft! Launcher with Java, which includes OpenJFX. +fatal.config_change_owner_root=You are using the root account to open Hello Minecraft! Launcher. This may prevent you from opening HMCL with another account in the future.\n\ + Do you still want to continue? +fatal.config_in_temp_dir=You are opening Hello Minecraft! Launcher in a temporary directory. Your settings and game data may be lost.\n\ + It is recommended to move HMCL to another location and reopen it.\n\ + Do you still want to continue? fatal.config_loading_failure=Cannot load configuration files.\n\ -Please make sure that "Hello Minecraft! Launcher" has read and write access to "%s" and the files in it.\n\ -For macOS, try putting HMCL somewhere with permissions other than "Desktop", "Downloads" and "Documents" and try again. -fatal.config_loading_failure.unix=Hello Minecraft! Launcher could not load the profile because the profile was created by user %1$s.\n\ -Please start HMCL with root user (not recommended), or execute the following command in the terminal to change the ownership of the configuration file to the current user:\n%2$s -fatal.mac_app_translocation=Due to the security mechanism of macOS, Hello Minecraft! Launcher is quarantined by the system to the temporary folder.\n\ -Please move Hello Minecraft! Launcher to a different folder before attempting to start, otherwise your settings and game data may be lost after restarting.\n\ -Do you still want to continue? -fatal.migration_requires_manual_reboot=Hello Minecraft! Launcher has been upgraded. Please reopen the launcher. -fatal.apply_update_failure=We are sorry, but Hello Minecraft! Launcher is unable to update autoamtically.\n\ -\n\ -You can update manually by downloading a newer version of the launcher from %s.\n\ -If the problem persists, please consider reporting this to us. + Please make sure that Hello Minecraft! Launcher has read and write access to "%s" and the files in it.\n\ + For macOS, try putting HMCL somewhere with permissions other than "Desktop", "Downloads", and "Documents" and try again. +fatal.config_loading_failure.unix=Hello Minecraft! Launcher could not load the configuration file because it was created by user "%1$s".\n\ + Please open HMCL as root user (not recommended), or execute the following command in the terminal to change the ownership of the configuration file to the current user:\n%2$s +fatal.mac_app_translocation=Hello Minecraft! Launcher is isolated to a temporary directory by the OS due to macOS security mechanisms.\n\ + Please move HMCL to a different directory before attempting to open. Otherwise, your settings and game data may be lost after restarting.\n\ + Do you still want to continue? +fatal.migration_requires_manual_reboot=Hello Minecraft! Launcher has been upgraded. Please restart the launcher. +fatal.apply_update_failure=We are sorry, but Hello Minecraft! Launcher is unable to update automatically.\n\ + \n\ + You can update manually by downloading a newer launcher version from %s.\n\ + If the problem persists, please consider reporting this to us. fatal.apply_update_need_win7=Hello Minecraft! Launcher cannot automatically update on Windows XP/Vista.\n\ -\n\ -You can update manually by downloading a newer version of the launcher from %s. -fatal.samba=If you launched HMCL from a Samba network drive, some features might not be working. Please try updating your Java or copy and run the launcher in a local folder. -fatal.illegal_char=Your user path contains an illegal character '=', you will not be able to use authlib-injector or change the skin of your offline account. -fatal.unsupported_platform=Minecraft is not yet fully supported for your platform, so you may experience missing functionality,\nor even be unable to launch the game.\n\ - \n\ - If you can't start Minecraft 1.17 and above, you can try switchiong the Renderer to Software in instance settings to use CPU rendering for better compatibility. -# ' -fatal.unsupported_platform.loongarch=Hello Minecraft! Launcher has provided support for Loongson Platform.\n\ -If you encounter problems when playing game, you can click the help button in the upper right corner for help. -fatal.unsupported_platform.osx_arm64=Hello Minecraft! Launcher has provided support for Apple Silicon platform, using native ARM java to launch games to get a smoother game experience.\nIf you encounter problems in the game, starting the game with Java based on x86-64 architecture may have better compatibility. -fatal.unsupported_platform.windows_arm64=Hello Minecraft! Launcher has provided native support for the Windows on ARM platform. If you encounter problems when playing game, please try starting the game with Java based on x86 architecture.\n\nIf you are using the Qualcomm platform, you may need to install the OpenGL Compatibility Pack before playing games.\nClick the link to go to the Microsoft Store and install the compatibility pack. + \n\ + You can update manually by downloading a newer launcher version from %s. +fatal.samba=If you opened Hello Minecraft! Launcher from a Samba network drive, some features might not be working. Please try updating your Java or moving the launcher to another directory. +fatal.illegal_char=Your user path contains an illegal character "=". You will not be able to use authlib-injector or change the skin of your offline account. +fatal.unsupported_platform=Minecraft is not fully supported on your platform yet, so you may experience missing features or even be unable to launch the game.\n\ + \n\ + If you cannot launch Minecraft 1.17 and later, you can try switching the "Renderer" to "Software" in "Global/Instance-specific Settings → Advanced Settings" to use CPU rendering for better compatibility. +fatal.unsupported_platform.loongarch=Hello Minecraft! Launcher has provided support for the Loongson platform.\n\ + If you encounter problems when playing a game, you can visit https://docs.hmcl.net/groups.html for help. +fatal.unsupported_platform.osx_arm64=Hello Minecraft! Launcher has provided support for the Apple silicon platform, using native ARM Java to launch games to get a smoother gaming experience.\n\ + If you encounter problems when playing a game, launching the game with Java based on x86-64 architecture may offer better compatibility. +fatal.unsupported_platform.windows_arm64=Hello Minecraft! Launcher has provided native support for the Windows on Arm platform. If you encounter problems when playing a game, please try launching the game with Java based on x86 architecture.\n\ + \n\ + If you are using the Qualcomm platform, you may need to install the OpenGL Compatibility Pack 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.channel=Feedback Channel feedback.discord=Discord -feedback.discord.statement=Join our Discord community. -feedback.github=GitHub Issue -feedback.github.statement=Creating an issue on GitHub. -feedback.qq_group=HMCL User Group -feedback.qq_group.statement=Join HMCL user QQ group. +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 -folder.game=Game Directory +folder.game=Working Directory folder.logs=Logs folder.mod=Mods folder.resourcepacks=Resource Packs -folder.shaderpacks=Shader packs +folder.shaderpacks=Shader Packs folder.saves=Saves folder.screenshots=Screenshots -game=Game -game.crash.feedback=Please do not share screenshots of this interface with others! If you ask for help from others, please click to export the game crash information in the lower left corner and send the exported file to others for analysis. +game=Games +game.crash.feedback=Please do not share screenshots of this window with others! If you ask for help from others, please click "Export Crash Logs" and send the exported file to others for analysis. game.crash.info=Crash Info game.crash.reason=Crash Cause game.crash.reason.analyzing=Analyzing... -game.crash.reason.multiple=Multiple reasons detected:\n\n -game.crash.reason.block=The game crashed due to a block.\n\ -\n\ -You can try removing this block using MCEdit or delete that mod that added it.\n\ -\n\ -Block Type: %1$s\n\ -Location: %2$s -game.crash.reason.bootstrap_failed=The game crashed due to mod %1$s.\n\ -\n\ -You can try deleting or updating it. -game.crash.reason.config=The game crashed because a mod %1$s cannot parse its config file %2$s. -game.crash.reason.debug_crash=The game crashed because you manually triggered it.\n\ -\n\ -So, you probably know why. -game.crash.reason.duplicated_mod=The game cannot launch due to duplicate mods: %1$s.\n\ -\n\ -%2$s\n\ -\n\ -Each mod can only be installed once, please delete the duplicate mod and try again. -game.crash.reason.entity=The game crashed due to an entity.\n\ -\n\ -You can try removing this entity using MCEdit or delete that mod that added it.\n\ -\n\ -Block Type: %1$s\n\ -Location: %2$s -game.crash.reason.modmixin_failure=The current game cannot continue to run because some Mod injection failed.\nThis generally means that the Mod has a bug or is incompatible with the current environment.\nYou can check the log to find the error mod. -game.crash.reason.file_or_content_verification_failed=The current game has a problem because some files or content verification failed.\nPlease try deleting the instance (including Mod) and download it again, or try using a proxy when downloading again. -game.crash.reason.mod_repeat_installation=Because the current game has installed duplicate Mods, each Mod can only appear once. Please delete the duplicate Mods and then restart the game. -game.crash.reason.forge_error=Forge may have provided error information.\nYou can view the log and make corresponding processing according to the log information in the error report.\nIf you do not see the error message, you can view the error report to understand how the error occurred.\n%1$s -game.crash.reason.mod_resolution0=The current game cannot continue to run because of some Mod problems.\nYou can check the log to find the error mod(s). -game.crash.reason.mixin_apply_mod_failed=The current game cannot continue to run because Mixin failed to apply the %1$s mod.\nYou can try deleting or updating the mod to resolve the issue. -game.crash.reason.mod_profile_causes_game_crash=The current game cannot continue to run because of a problem with the Mod configuration file.\nYou can check the log to find the error mod(s) and its configuration file. -game.crash.reason.fabric_reports_an_error_and_gives_a_solution=Forge may have provided error information.\nYou can view the log and make corresponding processing according to the log information in the error report.\nIf you do not see the error message, you can view the error report to understand how the error occurred. -game.crash.reason.java_version_is_too_high=The current game crashed because the Java version is too high to continue running.\nPlease use a lower version of Java in the Java Path tab of global game settings or per-instance game settings, and then start the game.\nIf not, you can download it from java.com (Java8) or BellSoft Liberica Full JRE (Java17) and other distributions to download and install one (restart the launcher after installation). -game.crash.reason.need_jdk11=The current game cannot continue to run due to an inappropriate version of the Java virtual machine. \nYou need to download and install Java 11, and set Java to a version starting with 11 in the global (per-instance) game settings. -game.crash.reason.mod_name=The current game cannot continue to run because of Mod file name problems. \nMod file names should use only English letters (Aa~Zz), numbers (0~9), hyphens (-), underscores (_), and dots (.) in half width. \nPlease go to the mods folder and add all non-compliant Mod file names with one of the above compliant characters. -game.crash.reason.incomplete_forge_installation=The current game cannot continue due to an incomplete installation of Forge / NeoForge. \nPlease reinstall Forge / NeoForge in Instance Settings - Modloaders / OptiFine. -game.crash.reason.fabric_version_0_12=Fabric 0.12 or above are incompatible with currently installed mods. You need to downgrade it to 0.11.7. -game.crash.reason.fabric_warnings=The Fabric modloader warned:\n\ -%1$s -game.crash.reason.file_already_exists=The game crashed because file %1$s already exists.\n\ -\n\ -You can try backing up and delete that file, then relaunch the game. -game.crash.reason.file_changed=The game crashed because it did not pass the integrity checks.\n\ -\n\ -If you modified the Minecraft jar, you will need to rollback the changes, or redownload the game. -game.crash.reason.gl_operation_failure=The game crashed due to some mods, shaders, and resource packs.\n\ -\n\ -Please disable the mods/shaders/resource packs you are using and then try again. -game.crash.reason.graphics_driver=The game crashed due to an issue with your graphics driver.\n\ -\n\ -Please try again after updating your graphics driver to the latest version.\n\ -\n\ -If your computer has a discrete graphics card, you need to check whether the game uses integrated/core graphics. If so, please start the launcher using your discrete graphics card. If the problem persists, you probably should consider getting a new graphics card or a new computer.\n\ -\n\ -If you are using your integrated graphics card, please notice that Minecraft 1.16.5 or older requires Java 1.8.0_51 or older for Intel(R) Core(TM) 3000 processor series or earlier.\n\ -\n\ -Turning on the "Use OpenGL software renderer" option in the instance settings can also solve this problem, but when this option is turned on, the frame rate will be significantly reduced in the case of insufficient CPU performance. So it is only recommended to turn it on for debugging purposes or in case of emergency. -game.crash.reason.macos_failed_to_find_service_port_for_display=The current game cannot continue due to a failure to initialize the OpenGL window on the Apple silicon platform.\nFor this issue, HMCL does not have direct solutions at the moment. Please try opening any browser and going fullscreen, then return to HMCL, launch the game, and quickly return to the browser page before the game window pops up, wait for the game window to appear, and then switch back to the game window. +game.crash.reason.multiple=Several reasons detected:\n\n +game.crash.reason.block=The game crashed because of a block in the world.\n\ + \n\ + You can try removing this block using MCEdit or deleting the mod that added it.\n\ + \n\ + Block Type: %1$s\n\ + Location: %2$s +game.crash.reason.bootstrap_failed=The game crashed because of the "%1$s" mod.\n\ + \n\ + You can try deleting or updating it. +game.crash.reason.config=The game crashed because the mod "%1$s" could not parse its config file "%2$s". +game.crash.reason.debug_crash=The game crashed because you triggered it manually. So you probably know why :) +game.crash.reason.duplicated_mod=The game cannot continue to run because of duplicate mods "%1$s".\n\ + \n\ + %2$s\n\ + \n\ + Each mod can only be installed once. Please delete the duplicate mod and try again. +game.crash.reason.entity=The game crashed because of an entity in the world.\n\ + \n\ + You can try removing the entity using MCEdit or deleting the mod that added it.\n\ + \n\ + Entity Type: %1$s\n\ + Location: %2$s +game.crash.reason.modmixin_failure=The game crashed because some mods failed to inject.\n\ + \n\ + This generally means that the mod has a bug or is incompatible with the current environment.\n\ + \n\ + You can check the log to find the error mod. +game.crash.reason.mod_repeat_installation=The game crashed because of duplicate mods.\n\ + \n\ + Each mod can only be installed once. Please delete the duplicate mod and then relaunch the game. +game.crash.reason.forge_error=Forge/NeoForge may have provided error information.\n\ + \n\ + You can view the log and make corresponding processing according to the log information in the error report.\n\ + \n\ + If you do not see the error message, you can view the error report to understand how the error occurred.\n\ + %1$s +game.crash.reason.mod_resolution0=The game crashed because of some mod problems. You can check the logs to find the faulty mod(s). +game.crash.reason.mixin_apply_mod_failed=The game crashed because the mixin could not be applied to the mod "%1$s".\n\ + \n\ + You can try deleting or updating the mod to resolve the problem. +game.crash.reason.java_version_is_too_high=The game crashed because the Java version is too new to continue running.\n\ + \n\ + Please use the previous major Java version in "Global/Instance-specific Settings → Java" and then launch the game.\n\ + \n\ + If not, you can download it from java.com (Java 8) or BellSoft Liberica Full JRE (Java 17) and other distributions to download and install one (restart the launcher after installation). +game.crash.reason.need_jdk11=The game crashed because of an inappropriate Java VM version.\n\ + \n\ + You need to download and install Java 11, and set it in "Global/Instance-specific Settings → Java". +game.crash.reason.mod_name=The game crashed because of mod filename problems.\n\ + \n\ + Mod file names should use only English letters (A~Z, a~z), numbers (0~9), hyphens (-), underscores (_), and dots (.) in half-width.\n\ + \n\ + Please navigate to the mod directory and change all non-compliant mod file names using the compliant characters above. +game.crash.reason.incomplete_forge_installation=The game cannot continue to run because of an incomplete Forge/NeoForge installation.\n\ + \n\ + Please reinstall Forge/NeoForge in "Edit Instance → Loaders". +game.crash.reason.fabric_version_0_12=Fabric Loader 0.12 or later is incompatible with currently installed mods. You need to downgrade it to 0.11.7. +game.crash.reason.fabric_warnings=The Fabric Loader warned:\n\ + \n\ + %1$s +game.crash.reason.file_already_exists=The game crashed because the file "%1$s" already exists.\n\ + \n\ + You can try backing up and deleting that file, then relaunching the game. +game.crash.reason.file_changed=The game crashed because the file "%1$s" already exists.\n\ + \n\ + You can try backing up and deleting that file, then relaunching the game. +game.crash.reason.gl_operation_failure=The game crashed because of some mods, shaders, or resource packs.\n\ + \n\ + Please disable the mods, shaders, or resource packs you are using and then try again. +game.crash.reason.graphics_driver=The game crashed because of a problem with your graphics driver.\n\ + \n\ + Please try again after updating your graphics driver to the latest version.\n\ + \n\ + If your computer has a dedicated graphics card, you need to check whether the game uses integrated/core graphics. If so, please open the launcher using your dedicated graphics card. If the problem persists, you probably should consider using a new graphics card or a new computer.\n\ + \n\ + If you are using your integrated graphics card, please notice that Minecraft 1.16.5 or earlier requires Java 1.8.0_51 or earlier for Intel(R) Core(TM) 3000 processor series or earlier. +game.crash.reason.macos_failed_to_find_service_port_for_display=The game cannot continue because the OpenGL window on the Apple silicon platform failed to initialize.\n\ + \n\ + For this problem, HMCL does not have direct solutions at the moment. Please try opening any browser and going fullscreen, then return to HMCL, launch the game, and quickly return to the browser page before the game window pops up, wait for the game window to appear, and then switch back to the game window. game.crash.reason.illegal_access_error=The game crashed because of some mod(s).\n\ -\n\ -If you know: %1$s, you can update or delete the mod(s) and then try again. -game.crash.reason.install_mixinbootstrap=The current game cannot continue to run due to missing MixinBootstrap.\nYou can try installing MixinBootstrap to solve the problem. If it crashes after installation, try adding an English "!" in front of the module's filename. in front of the file name of the module to try to solve the problem. -game.crash.reason.optifine_is_not_compatible_with_forge=The current game crashes because OptiFine is incompatible with the current version of Forge.\nPlease go toOn the official website of OptiFine, check whether the Forge version is compatible with OptiFine, and reinstall the game in strict accordance with the corresponding version or change the version in the instance settings - Modloaders / OptiFine.\nAfter testing, too high or too low a Forge version may cause a crash. -game.crash.reason.mod_files_are_decompressed=The current game cannot continue to run because the Mod file has been decompressed.\nPlease put the entire Mod file directly into the Mod folder!\nIf unzipping will cause errors in the game, please delete the unzipped Mod in the Mod folder, and then start the game. -game.crash.reason.shaders_mod=The current game cannot continue to run because both OptiFine and Shaders Mod are installed. \nBecause OptiFine has built-in support for shaders, just remove Shaders Mod. -game.crash.reason.rtss_forest_sodium=The current game crashed because RivaTuner Statistics Server (RTSS) is incompatible with Sodium.\nClick here for more details. -game.crash.reason.too_many_mods_lead_to_exceeding_the_id_limit=The current game cannot continue to run because you have installed too many Mods, which exceeds the ID limit of the game.\nPlease try installingJEID, or delete some large Mods. -game.crash.reason.night_config_fixes=The current game cannot continue to run due to some problems with Night Config. \nYou can try to install the Night Config Fixes mod, which may help you this problem. \nFor more information, visit the mod's GitHub repository. -game.crash.reason.optifine_causes_the_world_to_fail_to_load=The current game may not continue to run because of OptiFine.\nThis problem only occurs in a specific version of OptiFine. You can try changing the version of OptiFine in instance settings - Modloader / OptiFine. -game.crash.reason.jdk_9=The game cannot run because the Java version is too new for this instance.\n\ -\n\ -You need to download and install Java 8 and select it in the instance settings. -game.crash.reason.jvm_32bit=The game crashed because the current memory allocation exceeds the limit of the 32-bit Java VM.\n\ -\n\ -If your OS is 64-bit, please install and use a 64-bit version of Java. Otherwise, you may need to reinstall a 64-bit OS or get a moderner computer.\n\ -\n\ -Or, you can disable the "Automatically allocate" option and set the maximum memory allocation size to 1024MB or below. -game.crash.reason.loading_crashed_forge=The game crashed due to mod %1$s (%2$s).\n\ -\n\ -You can try deleting or updating it. -game.crash.reason.loading_crashed_fabric=The game crashed due to mod %1$s.\n\ -\n\ -You can try deleting or updating it. -game.crash.reason.mac_jdk_8u261=The game crashed because your current Forge or OptiFine version is not compatible with your Java installation.\n\ -\n\ -Please try updating Forge and OptiFine, or try using Java 8u251 or earlier versions. -game.crash.reason.forge_repeat_installation=The current game cannot continue to run due to a duplicate installation of Forge. This is a known issue\nIt is recommended to upload the log feedback to GitHub so that we can find more clues and fix this question. \nCurrently you can go to the automatic installation to uninstall Forge and reinstall it. -game.crash.reason.optifine_repeat_installation=The current game cannot continue to run due to repeated installation of Optifine. \nPlease delete Optifine under the Mod folder or go to Game Management-Automatic Installation to uninstall Optifine that is automatically installed. -game.crash.reason.memory_exceeded=The game crashed due to too much memory allocated for a small page file.\n\ -\n\ -You can try turning off the automatically allocate memory option in settings, and adjust the value till the game launches.\n\ -You can also try increasing the page file size in system settings. -game.crash.reason.mod=The game crashed due to the mod %1$s.\n\ -\n\ -You may update or delete the mod and then try again. -game.crash.reason.mod_resolution=The game crashed due to mod resolution failure.\n\ -\n\ -Fabric provided the following details:\n\ -%1$s -game.crash.reason.forgemod_resolution=The game crashed due to mod resolution failure.\n\ -\n\ -Forge provided the following details:\n\ -%1$s -game.crash.reason.forge_found_duplicate_mods=The game cannot continue due to a duplicate mods issue. Forge provides the following information: \n%1$s + \n\ + If you know this: "%1$s", you can update or delete the mod(s) and then try again. +game.crash.reason.install_mixinbootstrap=The game crashed because of the missing MixinBootstrap.\n\ + \n\ + You can try installing MixinBootstrap to resolve the problem. If it crashes after installation, try adding an exclamation mark (!) in front of the file name of this mod to try to resolve the problem. +game.crash.reason.optifine_is_not_compatible_with_forge=The game crashed because OptiFine is not compatible with the current Forge installation.\n\ + \n\ + Please navigate tothe official website of OptiFine, check whether the Forge version is compatible with OptiFine, and reinstall the instance in strict accordance with the corresponding version, or change the OptiFine version in "Edit Instance → Loaders".\n\ + \n\ + After testing, we believe that too high or too low OptiFine versions may cause crashes. +game.crash.reason.mod_files_are_decompressed=The game crashed because the mod file was extracted.\n\ + \n\ + Please put the entire mod file directly into the mod directory!\n\ + \n\ + If extraction causes errors in the game, please delete the extracted mod in the mod directory and then launch the game. +game.crash.reason.shaders_mod=The game crashed because of both OptiFine and Shaders mod are installed at the same time.\n\ + \n\ + Just remove the Shader mod because OptiFine has built-in support for shaders. +game.crash.reason.rtss_forest_sodium=The game crashed because the RivaTuner Statistical Server (RTSS) is not compatible with Sodium.\n\ + \n\ + Click here for more details. +game.crash.reason.too_many_mods_lead_to_exceeding_the_id_limit=The game crashed because you installed too many mods and exceeded the game ID limit.\n\ + \n\ + Please try installingJEID or deleting some large mods. +game.crash.reason.night_config_fixes=The game crashed because of some problems with Night Config.\n\ + \n\ + You can try installing the Night Config Fixes mod, which may help you with this problem.\n\ + \n\ + For more information, visit the GitHub repository of this mod. +game.crash.reason.optifine_causes_the_world_to_fail_to_load=The game may not continue to run because of the OptiFine.\n\ + \n\ + This problem only occurs in a specific OptiFine version. You can try changing the OptiFine version in "Edit Instance → Loaders". +game.crash.reason.jdk_9=The game crashed because the Java version is too new for this instance.\n\ + \n\ + You need to download and install Java 8 and choose it in "Global/Instance-specific Settings → Java". +game.crash.reason.jvm_32bit=The game crashed because the current memory allocation exceeded the 32-bit Java VM limit.\n\ + \n\ + If your OS is 64-bit, please install and use a 64-bit Java version. Otherwise, you may need to reinstall a 64-bit OS or get a moderner computer.\n\ + \n\ + Or, you can disable the "Automatically Allocate" option in "Global/Instance-specific Settings → Memory" and set the maximum memory allocation size to 1024 MB or below. +game.crash.reason.loading_crashed_forge=The game crashed because of the mod "%1$s" (%2$s).\n\ + \n\ + You can try deleting or updating it. +game.crash.reason.loading_crashed_fabric=The game crashed because of the mod "%1$s".\n\ + \n\ + You can try deleting or updating it. +game.crash.reason.mac_jdk_8u261=The game crashed because your current Forge or OptiFine version is incompatible with your Java installation.\n\ + \n\ + Please try updating Forge and OptiFine, or try using Java 8u251 or previous versions. +game.crash.reason.forge_repeat_installation=The game crashed because of a duplicate Forge installation. This is a known problem\n\ + \n\ + It is recommended to upload the log feedback to GitHub so that we can find more clues and resolve this problem.\n\ + \n\ + Currently you can uninstall Forge and reinstall it in "Edit Instance → Loaders". +game.crash.reason.optifine_repeat_installation=The game crashed because of a duplicate OptiFine installation.\n\ + \n\ + Please delete OptiFine in the mod directory or uninstall it in "Edit Instance → Loaders". +game.crash.reason.memory_exceeded=The game crashed because too much memory was allocated for a small page file.\n\ + \n\ + You can try disabling the "Automatically Allocate" option in "Global/Instance-specific Settings → Memory" and adjust the value till the game launches.\n\ + \n\ + You can also try increasing the page file size in system settings. +game.crash.reason.mod=The game crashed because of the mod "%1$s".\n\ + \n\ + You may update or delete the mod and then try again. +game.crash.reason.mod_resolution=The game cannot continue to run because of mod dependency problems.\n\ + \n\ + Fabric provided the following details:\n\ + \n\ + %1$s +game.crash.reason.forgemod_resolution=The game cannot continue to run because of mod dependency problems.\n\ + \n\ + Forge/NeoForge provided the following details:\n\ + %1$s +game.crash.reason.forge_found_duplicate_mods=The game crashed because of a duplicate mod problem. Forge/NeoForge provided the following information:\n\ + %1$s game.crash.reason.mod_resolution_collection=The game crashed because the mod version is not compatible.\n\ -\n\ -%1$s requires %2$s.\n\ -\n\ -You need to upgrade or downgrade %3$s before continuing. + \n\ + "%1$s" requires "%2$s".\n\ + \n\ + You need to upgrade or downgrade %3$s before continuing. game.crash.reason.mod_resolution_conflict=The game crashed because of conflicting mods.\n\ -\n\ -%1$s is incompatible with %2$s. -game.crash.reason.mod_resolution_missing=The game crashed because some dependency mods are not installed.\n\ -\n\ -%1$s requires mod: %2$s.\n\ -\n\ -This means that you have to download and install %2$s first to continue playing. + \n\ + "%1$s" is incompatible with "%2$s". +game.crash.reason.mod_resolution_missing=The game crashed because some dependent mods were not installed.\n\ + \n\ + "%1$s" requires mod "%2$s".\n\ + \n\ + This means that you have to download and install "%2$s" first to continue playing. game.crash.reason.mod_resolution_missing_minecraft=The game crashed because a mod is incompatible with the current Minecraft version.\n\ -\n\ -%1$s requires Minecraft version %2$s.\n\ -\n\ -If you want to play with this version of the mod installed, you should change the instance version.\n\ -Otherwise, you should install a version that is compatible with this Minecraft version. + \n\ + "%1$s" requires Minecraft version %2$s.\n\ + \n\ + If you want to play with this mod version installed, you should change the game version of your instance.\n\ + \n\ + Otherwise, you should install a version that is compatible with this Minecraft version. game.crash.reason.mod_resolution_mod_version=%1$s (Version: %2$s) game.crash.reason.mod_resolution_mod_version.any=%1$s (Any Version) -game.crash.reason.modlauncher_8=The game crashed because your current Forge version is not compatible with your Java installation, please try updating Forge. -game.crash.reason.no_class_def_found_error=The game cannot run because of incomplete code.\n\ -\n\ -Your game instance is missing %1$s, this might be due to a mod missing, an incompatible mod installed, or some files might be corrupted.\n\ -\n\ -You may need to reinstall the game and all mods or ask someone for help. -game.crash.reason.no_such_method_error=The game cannot run because of incomplete code.\n\ -\n\ -Your game instance might be missing a mod, installed an incompatible mod, or some files might be corrupted.\n\ -\n\ -You may need to reinstall the game and all mods or ask someone for help. -game.crash.reason.opengl_not_supported=The game crashed because OpenGL is not supported by your graphics driver.\n\ -\n\ -If you're streaming the game over the Internet or using a remote desktop environment, please play the game on your local one.\n\ -Or, you can try updating your driver to the latest version and then try again.\n\ -\n\ -If your computer has a discrete graphics card, please make sure the game is actually using it for rendering. If the problem persists, please consider getting a new graphics card or a new computer. -# ' -game.crash.reason.openj9=The game is unable to run on an OpenJ9 VM. Please switch to a Hotspot Java VM in the game settings and relaunch the game. If do not have one, you can download one online. -game.crash.reason.out_of_memory=The game crashed because it ran out of memory.\n\ -\n\ -Maybe because there is not enough memory available, or too many mods installed. You can try fixing it by increasing the allocated memory under the game settings.\n\ -\n\ -If you still encounter these problems, you may need a better computer. -game.crash.reason.resolution_too_high=The game crashed because you are using a resource pack whose texture resolution was too high.\n\ -\n\ -You should switch to a resource pack with lower resolution, or consider buying a better graphics card with more VRAM. -game.crash.reason.processing_of_javaagent_failed=The current game crashed because processing of -javaagent failed.\nIf you add relevant parameters to the Java virtual machine parameters, please check whether they are legal and correct.\nIf you do not add relevant parameters or confirm that they are legal and correct, Please try:\nOpen the control panel -- Clock and region classification (this option is only available if the option is category display, and it will be skipped if not) -- Region -- the upper management tab -- the lower change system regional setting button -- turn off the "Use Unicode UTF-8 to provide global language support" option in the pop-up window, restart the device, and then try to start the game.\nYou can be accessed in Discard or QQ ask for help. -game.crash.reason.stacktrace=The crash reason is unknown. You can view its details by clicking the "Logs" button.\n\ -\n\ -There are some keywords that might contain some Mod Names. You can search them online to figure out the issue yourself.\n\ -\n\ -%s -game.crash.reason.too_old_java=The game crashed because you are using a historical Java VM version.\n\ -\n\ -You need to switch to a newer version (%1$s) of Java in the game settings and then relaunch the game. You can download Java from here. -game.crash.reason.unknown=We are not able to figure out why the game crashed, please refer to the game logs. -game.crash.reason.unsatisfied_link_error=Unable to launch Minecraft due to missing libraries: %1$s.\n\ -\n\ -If you have modified native library settings, please make sure these libraries do exist. Or, please try launching again after reverting it back to default.\n\ -If you did not, please check if you have missing dependency mods.\n\ -Otherwise, if you believe this is caused by HMCL, please feedback to us. -game.crash.reason.failed_to_load_a_library=Failed to load a library.\n\ -\n\ -If you have modified native library settings, please make sure these libraries do exist. Or, please try launching again after reverting it back to default.\n\ -If you did not, please check if you have missing dependency mods.\n\ -Otherwise, if you believe this is caused by HMCL, please feed back to us. +game.crash.reason.modlauncher_8=The game crashed because your current Forge version is not compatible with your Java installation. Please try updating Forge. +game.crash.reason.no_class_def_found_error=The game cannot continue to run because of incomplete code.\n\ + \n\ + Your game instance is missing %1$s. This might be because a mod is missing, an incompatible mod is installed, or some files are corrupted.\n\ + \n\ + You may need to reinstall the game and all mods or ask someone for help. +game.crash.reason.no_such_method_error=The game cannot continue to run because of incomplete code.\n\ + \n\ + Your game instance might be missing a mod, installed an incompatible mod, or some files might be corrupted.\n\ + \n\ + You may need to reinstall the game and all mods or ask someone for help. +game.crash.reason.opengl_not_supported=The game crashed because your graphics driver does not support OpenGL.\n\ + \n\ + If you are streaming the game over the Internet or using a remote desktop environment, please play the game on your local machine.\n\ + \n\ + Or, you can update your graphic driver to the latest version and then try again.\n\ + \n\ + If your computer has a dedicated graphics card, please make sure the game is indeed using it for rendering. If the problem persists, please consider getting a new graphics card or a new computer. +game.crash.reason.openj9=The game is unable to run on an OpenJ9 VM. Please switch to a Java that uses the Hotspot VM in "Global/Instance-specific Settings → Java" and relaunch the game. If you do not have one, you can download one. +game.crash.reason.out_of_memory=The game crashed because the computer ran out of memory.\n\ + \n\ + Maybe there is not enough memory available or too many mods installed. You can try resolving it by increasing the allocated memory in "Global/Instance-specific Settings → Memory".\n\ + \n\ + If you still encounter these problems, you may need a better computer. +game.crash.reason.resolution_too_high=The game crashed because the resource pack resolution is too high.\n\ + \n\ + You should switch to a resource pack with lower resolution or consider buying a better graphics card with more VRAM. +game.crash.reason.stacktrace=The crash reason is unknown. You can view its details by clicking "Logs".\n\ + \n\ + There are some keywords that might contain some mod names. You can search them online to figure out the problem yourself.\n\ + \n\ + %s +game.crash.reason.too_old_java=The game crashed because you are using an outdated Java VM version.\n\ + \n\ + You need to switch to a newer Java version (%1$s) in "Global/Instance-specific Settings → Java" and then relaunch the game. You can download Java from here. +game.crash.reason.unknown=We are not able to figure out why the game crashed. Please refer to the game logs. +game.crash.reason.unsatisfied_link_error=Failed to launch Minecraft because of missing libraries: %1$s.\n\ + \n\ + If you have edited the native library path, please make sure these libraries do exist. Or, please try launching again after reverting it to default.\n\ + \n\ + If you have not, please check if you have missing dependency mods.\n\ + \n\ + Otherwise, if you believe this is caused by HMCL, please provide feedback to us. game.crash.title=Game Crashed game.directory=Game Path game.version=Game Version @@ -607,45 +663,46 @@ input.url=The input must be a valid URL. install=New Instance install.change_version=Change Version install.change_version.confirm=Are you sure you want to switch %s from version %s to %s? -install.failed=Installation Failed -install.failed.downloading=We are unable to download some required files. -install.failed.downloading.detail=Unable to download file: %s +install.failed=Failed to install +install.failed.downloading=Failed to download some required files. +install.failed.downloading.detail=Failed to download file: %s install.failed.downloading.timeout=Download timeout when fetching: %s -install.failed.install_online=Unable to identify the provided file. If you are installing a mod, go to the "Manage Mods" page. -install.failed.malformed=The downloaded files are corrupted. You can try fixing this issue by switching to another download source. -install.failed.optifine_conflict=Cannot install both Fabric, OptiFine, and Forge on Minecraft 1.13 or above. -install.failed.optifine_forge_1.17=For Minecraft version 1.17.1 or lower, Forge only supports OptiFine H1 Pre2 or newer. You can install them under the snapshot versions tab. -install.failed.version_mismatch=This library requires the game version %s, but the installed one is %s. +install.failed.install_online=Failed to identify the provided file. If you are installing a mod, navigate to the "Mods" page. +install.failed.malformed=The downloaded files are corrupted. You can try resolving this problem by switching to another download source in "Settings → Download → Download Source". +install.failed.optifine_conflict=Cannot install both OptiFine and Fabric on Minecraft 1.13 or later. +install.failed.optifine_forge_1.17=For Minecraft 1.17.1, Forge is only compatible with OptiFine H1 pre2 or later. You can install them by checking "Snapshots" when choosing an OptiFine version in HMCL. +install.failed.version_mismatch=This loader requires the game version %s, but the installed one is %s. install.installer.change_version=%s Incompatible install.installer.choose=Choose Your %s Version install.installer.depend=Requires %s install.installer.fabric=Fabric install.installer.fabric-api=Fabric API -install.installer.fabric-api.warning=Warning: Fabric API is a mod, and will be installed into the mods folder of the game instance. Please do not change the working directory of the game, or the Fabric API will not work. If you do want to change these settings, you should reinstall it. +install.installer.fabric-api.warning=Warning: Fabric API is a mod and will be installed into the mod directory of the game instance. Please do not change the working directory of the game, or the Fabric API won't function. If you do want to change the directory, you should reinstall it. install.installer.forge=Forge install.installer.neoforge=NeoForge install.installer.game=Minecraft install.installer.incompatible=Incompatible with %s install.installer.install=Install %s -install.installer.install_offline=Install/Update from the Local File -install.installer.install_offline.extension=Forge/OptiFine installer -install.installer.install_offline.tooltip=We support using the local Forge/OptiFine installer. +install.installer.install_offline=Install/Update from Local File +install.installer.install_offline.extension=(Neo)Forge/OptiFine installer +install.installer.install_offline.tooltip=We support using the local (Neo)Forge/OptiFine installer. install.installer.install_online=Online Install -install.installer.install_online.tooltip=We currently support Fabric, Forge, OptiFine, and LiteLoader. +install.installer.install_online.tooltip=We currently support Forge, NeoForge, OptiFine, Fabric, Quilt, and LiteLoader. install.installer.liteloader=LiteLoader -install.installer.not_installed=Not Selected +install.installer.not_installed=Do not install install.installer.optifine=OptiFine install.installer.quilt=Quilt install.installer.quilt-api=QSL/QFAPI install.installer.version=%s -install.installer.external_version=%s Installed by external process, which cannot be configured -install.modpack=Install a Modpack -install.new_game=Add a New Instance -install.new_game.already_exists=This instance already exists. Please use another name. +install.installer.external_version=%s (Installed by external process, which cannot be configured) +install.modpack=Install Modpack +install.name.invalid=The name contains non-ASCII characters (such as emoji or CJK characters).\nIt is recommended to change the name to include only English letters, numbers, and underscores to avoid potential issues when launching the game.\nDo you want to proceed with the installation? +install.new_game=Install Instance +install.new_game.already_exists=This instance name already exists. Please use another name. install.new_game.current_game_version=Current Instance Version -install.new_game.malformed=Invalid Name -install.select=Select an operation -install.success=Installed successfully. +install.new_game.malformed=Invalid name. +install.select=Choose operation +install.success=Successfully installed. java.add=Add Java java.add.failed=This Java is invalid or incompatible with the current platform. @@ -655,6 +712,7 @@ java.disabled.management=Disabled Java java.disabled.management.remove=Remove this Java from the list java.disabled.management.restore=Re-enable this Java java.download=Download Java +java.download.banshanjdk-8=Download Banshan JDK 8 java.download.load_list.failed=Failed to load version list java.download.more=More Java distributions java.download.prompt=Please choose the Java version you want to download: @@ -677,49 +735,44 @@ java.reveal=Reveal the Java directory java.uninstall=Uninstall Java java.uninstall.confirm=Are you sure you want to uninstall this Java? This action cannot be undone! -lang=English (US) +lang=English lang.default=Use System Locales + launch.advice=%s Do you still want to continue to launch? -launch.advice.multi=The following problems were detected:\n\n%s\n\nThese problems may cause unable to launch the game or affect the game experience.\nDo you still want to continue to launch? -launch.advice.java.auto=The current Java Virtual Machine version does not compatible with the instance.\n\ -\n\ -Click on 'Yes' to automatically choose the most compatible Java VM version. Or, you can go to the instance settings to select one yourself. -launch.advice.java.modded_java_7=Minecraft 1.7.2 and older versions require Java 7 or earlier. -launch.advice.corrected=We have fixed the Java VM issue. If you still want to use your choice of Java version, you can disable the compatibility checks under launcher game settings. -launch.advice.uncorrected=If you still want to use your choice of Java version, you can disable the compatibility checks under launcher game settings. -launch.advice.different_platform=The 64-bit version of Java is recommended for your device, but you have installed a 32-bit one. -launch.advice.forge2760_liteloader=Forge version 2760 is not compatible with LiteLoader, please consider upgrading Forge to version 2773 or later. +launch.advice.multi=The following problems were detected:\n\n%s\n\nThese problems may prevent you from launching the game or affect gaming experience.\nDo you still want to continue to launch? +launch.advice.java.auto=The current Java VM version is not compatible with the instance.\n\nClick "Yes" to automatically choose the most compatible Java VM version. Or, you can navigate to "Global/Instance-specific Settings → Java" to choose one yourself. +launch.advice.java.modded_java_7=Minecraft 1.7.2 and previous versions require Java 7 or earlier. +launch.advice.corrected=We have resolved the Java VM problem. If you still want to use your choice of Java version, you can disable "Do not check Java VM compatibility" in "Global/Instance-specific Settings → Advanced Settings". +launch.advice.uncorrected=If you still want to use your choice of Java version, you can disable "Do not check Java VM compatibility" in "Global/Instance-specific Settings → Advanced Settings". +launch.advice.different_platform=The 64-bit Java version is recommended for your device, but you have installed a 32-bit one. +launch.advice.forge2760_liteloader=Forge version 2760 is not compatible with LiteLoader. Please consider upgrading Forge to version 2773 or later. launch.advice.forge28_2_2_optifine=Forge version 28.2.2 or later is not compatible with OptiFine. Please consider downgrading Forge to version 28.2.1 or earlier. -launch.advice.forge37_0_60=Forge versions earlier than 37.0.60 are not compatible with Java 17. Please update Forge to 37.0.60 or higher, or launch the game with Java 16. -launch.advice.java8_1_13=Minecraft 1.13 and later can only be run on Java 8 or later. Please use Java 8 or newer versions. -launch.advice.java8_51_1_13=Minecraft 1.13 may crash on Java 8 versions earlier than 1.8.0_51. Please install the latest version of Java 8. -launch.advice.java9=You cannot launch Minecraft 1.12 or earlier with Java 9 or newer, please use Java 8 instead. -launch.advice.modded_java=Some Mods may not be compatible with higher versions of Java. It is recommended to use Java %s to start Minecraft %s. -launch.advice.modlauncher8=The Forge version you are using is not compatible with the current Java version, please try updating Forge. -launch.advice.newer_java=You are using the old Java to start the game. It is recommended to update to Java 8, otherwise some mods may cause the game to crash. -launch.advice.not_enough_space=You have allocated a memory size larger than the actual %d MB of memory installed on your computer. You may experience degraded performance, or even be unable to launch the game. -launch.advice.require_newer_java_version=Current game version requires Java %s, but we could not find one. Do you want to download one now? +launch.advice.forge37_0_60=Forge versions prior to 37.0.60 are not compatible with Java 17. Please update Forge to 37.0.60 or later, or launch the game with Java 16. +launch.advice.java8_1_13=Minecraft 1.13 and later can only be run on Java 8 or later. Please use Java 8 or later versions. +launch.advice.java8_51_1_13=Minecraft 1.13 may crash on Java 8 versions prior to 1.8.0_51. Please install the latest Java 8 version. +launch.advice.java9=You cannot launch Minecraft 1.12 or earlier with Java 9 or later. Please use Java 8 instead. +launch.advice.modded_java=Some mods may not be compatible with newer Java versions. It is recommended to use Java %s to launch Minecraft %s. +launch.advice.modlauncher8=The Forge version you are using is not compatible with the current Java version. Please try updating Forge. +launch.advice.newer_java=You are using an older Java version to launch the game. It is recommended to update to Java 8, otherwise some mods may cause the game to crash. +launch.advice.not_enough_space=You have allocated a memory size larger than the actual %d MB of memory installed on your computer. You may experience degraded performance or even be unable to launch the game. +launch.advice.require_newer_java_version=The current game version requires Java %s, but we could not find one. Do you want to download one now? launch.advice.too_large_memory_for_32bit=You have allocated a memory size larger than the memory limitation of the 32-bit Java installation. You may be unable to launch the game. -launch.advice.vanilla_linux_java_8=Minecraft 1.12.2 or below only supports Java 8 for the Linux x86-64 platform, because later versions cannot load 32-bit native libraries like liblwjgl.so\n\ -\n\ -Please download it from java.com, or install OpenJDK 8. -launch.advice.vanilla_x86.translation=Minecraft is not fully supported for your platform, so you may experience missing functionality, or even be unable to launch the game.\nYou can play through the Rosetta translation environment for a full gaming experience. +launch.advice.vanilla_linux_java_8=Minecraft 1.12.2 or earlier only supports Java 8 for the Linux x86-64 platform because the later versions cannot load 32-bit native libraries like liblwjgl.so\n\nPlease download it from java.com or install OpenJDK 8. +launch.advice.vanilla_x86.translation=Minecraft is not fully supported on your platform, so you may experience missing features or even be unable to launch the game.\nYou can download Java for the x86-64 architecture here for a full gaming experience. launch.advice.unknown=The game cannot be launched due to the following reasons: launch.failed=Failed to launch -launch.failed.cannot_create_jvm=We are unable to create a Java virtual machine. It may be caused by incorrect Java VM arguments. You can try fixing it by removing all arguments you added under instance settings. -launch.failed.creating_process=We are unable to create a new process, please check your Java path. -launch.failed.command_too_long=The command length exceeds the maximum length of a bat script. Please try exporting it as a PowerShell script. -launch.failed.decompressing_natives=Unable to unzip native libraries. -launch.failed.download_library=Unable to download libraries %s. -launch.failed.executable_permission=Unable to make the launch script executable. +launch.failed.cannot_create_jvm=We are unable to create a Java VM. It may be caused by incorrect Java VM arguments. You can try resolving it by removing all arguments you added in "Global/Instance-specific Settings → Advanced Settings → Java VM Options". +launch.failed.creating_process=We are unable to create a new process. Please check your Java path.\n +launch.failed.command_too_long=The command length exceeds the maximum length of a batch script. Please try exporting it as a PowerShell script. +launch.failed.decompressing_natives=Failed to extract native libraries.\n +launch.failed.download_library=Failed to download libraries "%s". +launch.failed.executable_permission=Failed to make the launch script executable. launch.failed.execution_policy=Set Execution Policy -launch.failed.execution_policy.failed_to_set=Unable to set execution policy -launch.failed.execution_policy.hint=The current execution policy prevents the execution of PowerShell scripts.\n\ -\n\ -Click on 'OK' to allow the current user to execute PowerShell scripts, or click on 'Cancel' to keep it as it is. +launch.failed.execution_policy.failed_to_set=Failed to set execution policy +launch.failed.execution_policy.hint=The current execution policy prevents the execution of PowerShell scripts.\n\nClick "OK" to allow the current user to execute PowerShell scripts, or click "Cancel" to keep it as it is. launch.failed.exited_abnormally=Game crashed. Please refer to the crash log for more details. -launch.failed.java_version_too_low=The Java version you specified is too low, please reset the Java version. -launch.failed.no_accepted_java=Unable to find a compatible Java version, do you want to start the game with the default Java?\nClick on 'Yes' to start the game with the default Java.\nOr, you can go to the instance settings to select one yourself. +launch.failed.java_version_too_low=The Java version you specified is too low. Please reset the Java version. +launch.failed.no_accepted_java=Failed to find a compatible Java version, do you want to launch the game with the default Java?\nClick "Yes" to launch the game with the default Java.\nOr, you can navigate to "Global/Instance-specific Settings → Java" to choose one yourself. launch.failed.sigkill=Game was forcibly terminated by the user or system. launch.state.dependencies=Resolving dependencies launch.state.done=Launched @@ -727,7 +780,7 @@ launch.state.java=Checking Java version launch.state.logging_in=Logging in launch.state.modpack=Downloading required files launch.state.waiting_launching=Waiting for the game to launch -launch.invalid_java=Invalid Java path, please reset the Java path. +launch.invalid_java=Invalid Java path. Please reset the Java path. launcher=Launcher launcher.agreement=ToS and EULA @@ -735,20 +788,21 @@ launcher.agreement.accept=Accept launcher.agreement.decline=Decline launcher.agreement.hint=You must agree to the EULA to use this software. launcher.background=Background Image -launcher.background.choose=Choose a Background Image +launcher.background.choose=Choose background image launcher.background.classic=Classic -launcher.background.default=Default (or background.png/jpg/gif, or images under bg folder) +launcher.background.default=Default +launcher.background.default.tooltip=Or "background.png/.jpg/.gif/.webp" and the images in the "bg" directory launcher.background.network=From URL launcher.background.translucent=Translucent launcher.cache_directory=Cache Directory launcher.cache_directory.clean=Clear Cache -launcher.cache_directory.choose=Choose the cache directory -launcher.cache_directory.default=Default (%AppData%/.minecraft or ~/.minecraft) +launcher.cache_directory.choose=Choose cache directory +launcher.cache_directory.default=Default ("%APPDATA%/.minecraft" or "~/.minecraft") launcher.cache_directory.disabled=Disabled -launcher.cache_directory.invalid=Unable to create cache directory, falling back to default. +launcher.cache_directory.invalid=Failed to create a cache directory, falling back to default. launcher.contact=Contact Us -launcher.crash=Hello Minecraft! Launcher has encountered a fatal error! Please copy the following log and ask for help on our Discord community, GitHub or Minecraft Forum. -launcher.crash.java_internal_error=Hello Minecraft! Launcher has encountered a fatal error because your Java is broken. Please uninstall your Java, and download a suitable Java here. +launcher.crash=Hello Minecraft! Launcher has encountered a fatal error! Please copy the following log and ask for help on our Discord, QQ group, GitHub, or other Minecraft forum. +launcher.crash.java_internal_error=Hello Minecraft! Launcher has encountered a fatal error because your Java is corrupted. Please uninstall your Java and download a suitable Java here. launcher.crash.hmcl_out_dated=Hello Minecraft! Launcher has encountered a fatal error! Your launcher is outdated. Please update your launcher! launcher.update_java=Please update your Java version. @@ -758,15 +812,15 @@ login.enter_password=Please enter your password. logwindow.show_lines=Show Row Number logwindow.terminate_game=Kill Game Process logwindow.title=Log -logwindow.help=You can go to the HMCL community and find others for help +logwindow.help=You can navigate to the HMCL community and find others for help. logwindow.autoscroll=Auto-scroll logwindow.export_game_crash_logs=Export Crash Logs logwindow.export_dump=Export Game Stack Dump -logwindow.export_dump.no_dependency=Your Java does not contain the dependencies to create the stack dump. Please turn to HMCL QQ group or HMCL Discord for help. +logwindow.export_dump.no_dependency=Your Java does not contain the dependencies to create the stack dump. Please join our Discord or QQ group for help. main_page=Home -message.cancelled=Operation was cancelled +message.cancelled=Operation Canceled message.confirm=Confirm message.copied=Copied to clipboard message.default=Default @@ -775,34 +829,34 @@ message.downloading=Downloading message.error=Error message.failed=Operation Failed message.info=Information -message.success=Operation completed successfully +message.success=Operation successfully completed message.unknown=Unknown message.warning=Warning -modpack=Modpack -modpack.choose=Select a modpack -modpack.choose.local=Import from local file -modpack.choose.local.detail=You can drag the modpack file here +modpack=Modpacks +modpack.choose=Choose Modpack +modpack.choose.local=Import from Local File +modpack.choose.local.detail=You can drag the modpack file here. modpack.choose.remote=Download from URL -modpack.choose.remote.detail=A direct download link to the remote modpack file is required -modpack.choose.repository=Download a modpack from Curseforge or Modrinth -modpack.choose.repository.detail=Remember to go back to this page and drop the modpack file here after the modpack is downloaded +modpack.choose.remote.detail=A direct download link to the remote modpack file is required. +modpack.choose.repository=Download Modpack from CurseForge or Modrinth +modpack.choose.repository.detail=Remember to go back to this page and drop the modpack file here after the modpack is downloaded. modpack.choose.remote.tooltip=Please enter your modpack URL modpack.completion=Downloading dependencies modpack.desc=Describe your modpack, including an introduction and probably some changelog. Markdown and images from URL are currently supported. modpack.description=Modpack Description modpack.download=Download Modpacks modpack.enter_name=Enter a name for this modpack. -modpack.export=Export the Modpack +modpack.export=Export as Modpack modpack.export.as=Export Modpack As... modpack.file_api=Modpack URL Prefix modpack.files.blueprints=BuildCraft Blueprints modpack.files.config=Mod Configs modpack.files.dumps=NEI Debug Output File modpack.files.hmclversion_cfg=Launcher Configuration File -modpack.files.liteconfig=Mod Configuration File +modpack.files.liteconfig=LiteLoader Related Files modpack.files.mods=Mods -modpack.files.mods.voxelmods=VoxelMods options +modpack.files.mods.voxelmods=VoxelMods Options modpack.files.options_txt=Minecraft Options File modpack.files.optionsshaders_txt=Shaders Settings File modpack.files.resourcepacks=Resource/Texture Packs @@ -811,50 +865,47 @@ modpack.files.scripts=MineTweaker Configuration File modpack.files.servers_dat=Server List File modpack.install=Install Modpack %s modpack.installing=Installing Modpack -modpack.introduction=CurseForge, Modrinth, MultiMC, and MCBBS modpacks are currently supported. +modpack.introduction=Curse, Modrinth, MultiMC, and MCBBS modpacks are currently supported. modpack.invalid=Invalid modpack, you can try downloading it again. -modpack.mismatched_type=Modpack type mismatched, the current instance is a(an) %s type, but the provided one is %s type. +modpack.mismatched_type=Modpack type mismatched, the current instance is a(n) %s type, but the provided one is %s type. modpack.name=Modpack Name -modpack.not_a_valid_name=Invalid Modpack Name +modpack.not_a_valid_name=Invalid modpack name. modpack.origin=Source modpack.origin.url=Official Website modpack.origin.mcbbs=MCBBS modpack.origin.mcbbs.prompt=Post ID modpack.scan=Parsing Modpack Index modpack.task.install=Import Modpack -modpack.task.install.error=Unable to identify this modpack. We currently only support Curse, Modrinth, MultiMC, and MCBBS modpacks. -modpack.task.install.will=The location of the modpack you are going to install: +modpack.task.install.error=Failed to identify this modpack. We currently only support Curse, Modrinth, MultiMC, and MCBBS modpacks. modpack.type.curse=Curse -modpack.type.curse.tolerable_error=Unable to download dependencies, you can try continuing to download by launching this game instance. -modpack.type.curse.error=Unable to download dependencies, please try again or use a proxy connection. -modpack.type.curse.not_found=Some dependencies are no longer available, please try installing a newer version of the modpack. -modpack.type.manual.warning=The modpack is manually packaged by the publisher, which may already contain a launcher. It is recommended to try unzipping the modpack and running the game with its own launcher. HMCL can still import it, with no guarantee of its usability, still continue? -modpack.type.mcbbs=MCBBS Type +modpack.type.curse.error=Failed to download dependencies. Please try again or use a proxy server. +modpack.type.curse.not_found=Some dependencies are no longer available. Please try installing a newer modpack version. +modpack.type.manual.warning=The modpack is manually packaged by the publisher, which may already contain a launcher. It is recommended to try extracting the modpack and running the game with its own launcher. HMCL can still import it, with no guarantee of its usability. Still continue? +modpack.type.mcbbs=MCBBS modpack.type.mcbbs.export=Can be imported by Hello Minecraft! Launcher modpack.type.modrinth=Modrinth modpack.type.multimc=MultiMC modpack.type.multimc.export=Can be imported by Hello Minecraft! Launcher and MultiMC modpack.type.server=Auto-Update Modpack from Server -modpack.type.server.export=Allows server owner to update the game instance remotely -modpack.type.server.malformed=Invalid modpack manifest, please refer to the modpack maker to fix this issue. +modpack.type.server.export=Allows server owner to remotely update the game instance +modpack.type.server.malformed=Invalid modpack manifest. Please contact the modpack maker to resolve this problem. modpack.unsupported=Unsupported modpack format modpack.update=Updating modpack modpack.wizard=Modpack Export Guide modpack.wizard.step.1=Basic Settings modpack.wizard.step.1.title=Some basic informations for the modpack. -modpack.wizard.step.2=Select Files -modpack.wizard.step.2.title=Select files you wanted to add to the modpack. +modpack.wizard.step.2=Choose Files +modpack.wizard.step.2.title=Choose files you wanted to add to the modpack. modpack.wizard.step.3=Modpack Type -modpack.wizard.step.3.title=Choose the modpack type you wanted to export as. +modpack.wizard.step.3.title=Choose modpack type you wanted to export as. modpack.wizard.step.initialization.exported_version=Game version to export -modpack.wizard.step.initialization.force_update=Force updating the modpack to the latest version (you'll need a file-hosting server) -# ' +modpack.wizard.step.initialization.force_update=Force updating the modpack to the latest version (you will need a file-hosting server) modpack.wizard.step.initialization.include_launcher=Include the launcher modpack.wizard.step.initialization.save=Export to... -modpack.wizard.step.initialization.warning=Before making a modpack, please make sure the game launches normally, and Minecraft is a release version instead of a snapshot version. The launcher will save your download settings.\n\ -\n\ -Keep in mind that you are not allowed to add mods and resource packs that are explicitly said not to be distributed or put in a modpack. -modpack.wizard.step.initialization.server=Click here for more tutorials for making a server modpack that can be automatically updated. +modpack.wizard.step.initialization.warning=Before making a modpack, please make sure the game can be launched normally and Minecraft is a release version instead of a snapshot. The launcher will save your download settings.\n\ + \n\ + Keep in mind that you are not allowed to add mods and resource packs that explicitly state they could not to be distributed or put in a modpack. +modpack.wizard.step.initialization.server=Click here for more information on how to make a server modpack that can be automatically updated. modrinth.category.adventure=Adventure modrinth.category.audio=Audio @@ -926,78 +977,86 @@ modrinth.category.256x=256x modrinth.category.512x+=512x+ mods=Mods -mods.add=Add Mods +mods.add=Add Mod mods.add.failed=Failed to add mod %s. -mods.add.success=%s was added successfully. +mods.add.success=%s was successfully added. mods.broken_dependency.title=Broken dependency -mods.broken_dependency.desc=This dependency existed before. However, it doesn't exist now. Try using another download source. +mods.broken_dependency.desc=This dependency existed before, but it does not exist anymore. Try using another download source. mods.category=Category +mods.channel.alpha=Alpha +mods.channel.beta=Beta +mods.channel.release=Release mods.check_updates=Check for Updates mods.check_updates.current_version=Current Version mods.check_updates.empty=All mods are up-to-date -mods.check_updates.failed=Failed to download some files. +mods.check_updates.failed_check=Failed to check for updates. +mods.check_updates.failed_download=Failed to download some files. mods.check_updates.file=File mods.check_updates.source=Source mods.check_updates.target_version=Target Version mods.check_updates.update=Update -mods.choose_mod=Choose a mod +mods.choose_mod=Choose mod mods.curseforge=CurseForge -mods.dependency.embedded=built-in pre-mod (already packaged in the mod file by the author, no need to download separately) -mods.dependency.optional=optional pre-mod (if the game is missing, but mod functionality may be missing) -mods.dependency.required=required pre-mod (must be downloaded separately, missing may cause the game to fail to launch) -mods.dependency.tool=precursor library (must be downloaded separately, missing may cause the game to fail to launch) -mods.dependency.include=built-in pre-mod (already packaged in the mod file by the author, no need to download separately) -mods.dependency.incompatible=incompatible mod (installing both the mod and the mod being downloaded will cause the game to fail to launch) -mods.dependency.broken=Broken pre-mod (This premod used to exist on the mod repository, but is now deleted.) Try a different download source. +mods.dependency.embedded=Built-in Dependencies (Already packaged in the mod file by the author. No need to download separately) +mods.dependency.optional=Optional Dependencies (If missing, the game will run normally, but the mod features may be missing) +mods.dependency.required=Required Dependencies (Must be downloaded separately. Missing may prevent the game from launching) +mods.dependency.tool=Required Dependencies (Must be downloaded separately. Missing may prevent the game from launching) +mods.dependency.include=Built-in Dependencies (Already packaged in the mod file by the author. No need to download separately) +mods.dependency.incompatible=Incompatible Mods (Installing these mods at the same time will prevent the game from launching) +mods.dependency.broken=Broken Dependencies (This mod existed before, but it does not exist anymore. Try using another download source.) mods.disable=Disable -mods.download=Mod Download -mods.download.title=Mod Download - %1s +mods.download=Download Mod +mods.download.title=Download Mod - %1s mods.download.recommend=Recommended Mod Version - Minecraft %1s mods.enable=Enable -mods.manage=Manage Mods +mods.manage=Mods mods.mcbbs=MCBBS -mods.mcmod=MCMOD -mods.mcmod.page=MCMOD Page -mods.mcmod.search=Search in MCMOD +mods.mcmod=MCMod +mods.mcmod.page=MCMod Page +mods.mcmod.search=Search in MCMod mods.modrinth=Modrinth mods.name=Name -mods.not_modded=You must install a mod loader (Fabric, Forge, Quilt or LiteLoader) first to manage your mods! -mods.restore=Rollback +mods.not_modded=You must install a modloader (Forge, NeoForge, Fabric, Quilt, or LiteLoader) first to manage your mods! +mods.restore=Restore mods.url=Official Page -mods.update_modpack_mod.warning=Updating mods in a modpack can lead to irreparable results, possibly corrupting the modpack so that it cannot start. Are you sure you want to update? +mods.update_modpack_mod.warning=Updating mods in a modpack can lead to irreparable results, possibly corrupting the modpack so that it cannot launch. Are you sure you want to update? mods.install=Install mods.save_as=Save As nbt.entries=%s entries -nbt.open.failed=Fail to open file -nbt.save.failed=Fail to save file +nbt.open.failed=Failed to open file +nbt.save.failed=Failed to save file nbt.title=View File - %s datapack=Datapacks -datapack.add=Install datapack -datapack.choose_datapack=Select a datapack to import +datapack.add=Install Datapack +datapack.choose_datapack=Choose datapack to import datapack.extension=Datapack -datapack.title=World %s - Datapacks +datapack.title=World [%s] - Datapacks + +web.failed=Failed to load page +web.open_in_browser=Do you want to open this address in a browser:\n%s +web.view_in_browser=View in browser world=Worlds -world.add=Add a World (.zip) +world.add=Add World world.datapack=Manage Datapacks world.datapack.1_13=Only Minecraft 1.13 or later supports datapacks. -world.description=%s. Last played on %s. Game Version: %s. -world.download=Download a World +world.description=%s | Last played on %s | Game Version: %s +world.download=Download World world.export=Export the World -world.export.title=Select a directory for this exported world +world.export.title=Choose the directory for this exported world world.export.location=Save As -world.export.wizard=Export World %s +world.export.wizard=Export World "%s" world.extension=World Archive world.game_version=Game Version world.import.already_exists=This world already exists. -world.import.choose=Select the save archive you want to import +world.import.choose=Choose world archive you want to import world.import.failed=Failed to import this world: %s -world.import.invalid=Unable to parse the save. -world.info.title=World %s - Information +world.import.invalid=Failed to parse the world. +world.info.title=World [%s] - Information world.info.basic=Basic Information -world.info.allow_cheats=Allow Cheats +world.info.allow_cheats=Allow Commands/Cheats world.info.dimension.the_nether=The Nether world.info.dimension.the_end=The End world.info.difficulty=Difficulty @@ -1005,6 +1064,7 @@ world.info.difficulty.peaceful=Peaceful world.info.difficulty.easy=Easy world.info.difficulty.normal=Normal world.info.difficulty.hard=Hard +world.info.failed=Failed to read the world info world.info.game_version=Game Version world.info.last_played=Last Played world.info.generate_features=Generate Structures @@ -1023,39 +1083,41 @@ world.info.player.xp_level=Experience Level world.info.random_seed=Seed world.info.time=Game Time world.info.time.format=%s days -world.manage=Worlds / Datapacks +world.manage=Worlds world.name=World Name world.name.enter=Enter the world name world.reveal=Reveal in Explorer world.show_all=Show All -world.time=EEE, MMM d, yyyy HH\:mm\:ss +world.time=h\:mm\:ss a, EEE, MMM d yyyy -profile=Game Repository -profile.already_exists=This name already exists, please use a different name. -profile.default=Current Directory -profile.home=Vanilla Launcher Repository -profile.instance_directory=Instance Directory -profile.instance_directory.choose=Select an instance Directory +profile=Game Directories +profile.already_exists=This name already exists. Please use a different name. +profile.default=Current +profile.home=Minecraft Launcher +profile.instance_directory=Game Directory +profile.instance_directory.choose=Choose game directory +profile.manage=Instance Directory List profile.name=Name -profile.new=New Repository -profile.title=Game Repositories -profile.use_relative_path=Use relative path for game repository path if possible +profile.new=New Directory +profile.title=Game Directories +profile.selected=Selected +profile.use_relative_path=Use a relative path for the game directory if possible repositories.custom=Custom Maven Repository (%s) repositories.maven_central=Universal (Maven Central) -repositories.tencentcloud_mirror=Mainland China Mirror (Tencent Cloud Maven Repository) +repositories.tencentcloud_mirror=Chinese Mainland Mirror (Tencent Cloud Maven Repository) repositories.chooser=HMCL requires JavaFX to work.\n\ -\n\ -Please click on 'OK' to download JavaFX from the specified repository, or click on 'Cancel' to exit.\n\ -\n\ -Repositories: -repositories.chooser.title=Select a download source to download JavaFX from + \n\ + Please click "OK" to download JavaFX from the specified repository, or click "Cancel" to exit.\n\ + \n\ + Repositories: +repositories.chooser.title=Choose download source for JavaFX resourcepack=Resource Packs search=Search -search.hint.chinese=Search queries support both Chinese and English -search.hint.english=Only English is supported +search.hint.chinese=Search in English and Chinese +search.hint.english=Search in English only search.enter=Enter text here search.sort=Sort By search.first_page=First @@ -1065,56 +1127,56 @@ search.last_page=Last search.page_n=%d / %s selector.choose=Choose -selector.choose_file=Select a file +selector.choose_file=Choose file selector.custom=Custom settings=Settings settings.advanced=Advanced Settings -settings.advanced.modify=Modify Advanced Settings +settings.advanced.modify=Edit Advanced Settings settings.advanced.title=Advanced Settings - %s settings.advanced.custom_commands=Custom Commands settings.advanced.custom_commands.hint=The following environment variables are provided:\n\ - - $INST_NAME: version name\n\ - - $INST_ID: version id\n\ - - $INST_DIR: absolute path of the version\n\ - - $INST_MC_DIR: absolute path of minecraft\n\ - - $INST_JAVA: java binary used for launch\n\ - - $INST_FORGE: set if Forge installed\n\ - - $INST_NEOFORGE: set if NeoForge installed\n\ - - $INST_LITELOADER: set if LiteLoader installed\n\ - - $INST_OPTIFINE: set if OptiFine installed\n\ - - $INST_FABRIC: set if Fabric installed\n\ - - $INST_QUILT: set if Quilt installed + \ · $INST_NAME: instance name.\n\ + \ · $INST_ID: instance name.\n\ + \ · $INST_DIR: absolute path of the instance working directory.\n\ + \ · $INST_MC_DIR: absolute path of the game directory.\n\ + \ · $INST_JAVA: java binary used for launch.\n\ + \ · $INST_FORGE: set if Forge is installed.\n\ + \ · $INST_NEOFORGE: set if NeoForge is installed.\n\ + \ · $INST_LITELOADER: set if LiteLoader is installed.\n\ + \ · $INST_OPTIFINE: set if OptiFine is installed.\n\ + \ · $INST_FABRIC: set if Fabric is installed.\n\ + \ · $INST_QUILT: set if Quilt is installed. settings.advanced.dont_check_game_completeness=Do not check game integrity -settings.advanced.dont_check_jvm_validity=Do not check JVM compatibility +settings.advanced.dont_check_jvm_validity=Do not check Java VM compatibility settings.advanced.dont_patch_natives=Do not attempt to automatically replace native libraries settings.advanced.environment_variables=Environment Variables -settings.advanced.game_dir.default=Default (.minecraft/) -settings.advanced.game_dir.independent=Isolated (.minecraft/versions//, except for assets and libraries) +settings.advanced.game_dir.default=Default (".minecraft/") +settings.advanced.game_dir.independent=Isolated (".minecraft/versions//", except for assets and libraries) settings.advanced.java_permanent_generation_space=PermGen Space settings.advanced.java_permanent_generation_space.prompt=in MB -settings.advanced.jvm=Java Virtual Machine options -settings.advanced.jvm_args=Java VM arguments -settings.advanced.jvm_args.prompt=- If the parameter entered in "Java Virtual Machine Parameters" is the same as the\ndefault parameter,it will not be added\n\ -- Enter any GC parameters in "Java Virtual Machine Parameters",\nthe G1 parameter of the\ndefault parameter will be disabled\n\ -- Click "Don't add default JVM parameters" below to start the game without adding\ndefault parameters -settings.advanced.launcher_visibility.close=Close the launcher after the game launches. -settings.advanced.launcher_visibility.hide=Hide the launcher after the game launches. -settings.advanced.launcher_visibility.hide_and_reopen=Hide the launcher and reopen it when the game closes. -settings.advanced.launcher_visibility.keep=Keep the launcher visible. +settings.advanced.jvm=Java VM Options +settings.advanced.jvm_args=Java VM Arguments +settings.advanced.jvm_args.prompt=\ · If the arguments entered in "Java VM arguments" are the same as the default arguments, it will not be added.\n\ + \ · Enter any GC arguments in "Java VM arguments", and the G1 argument of the default arguments will be disabled.\n\ + \ · Enable "Do not add default Java VM arguments" to launch the game without adding default arguments. +settings.advanced.launcher_visibility.close=Close the launcher after the game launches +settings.advanced.launcher_visibility.hide=Hide the launcher after the game launches +settings.advanced.launcher_visibility.hide_and_reopen=Hide the launcher and show it when the game closes +settings.advanced.launcher_visibility.keep=Keep the launcher visible settings.advanced.launcher_visible=Launcher Visibility settings.advanced.minecraft_arguments=Launch Arguments settings.advanced.minecraft_arguments.prompt=Default settings.advanced.natives_directory=Native Library Path -settings.advanced.natives_directory.choose=Select where your desired native library is located +settings.advanced.natives_directory.choose=Choose the location of the desired native library settings.advanced.natives_directory.custom=Custom settings.advanced.natives_directory.default=Default -settings.advanced.natives_directory.hint=This option is intended only for users of Apple M1 or other not officially supported platforms. Please do not modify this option unless you know what you are doing.\n\ -\n\ -Before proceeding, please make sure all libraries (e.g. lwjgl.dll, libopenal.so) are provided in your desired directory.\n\ -Note: It is recommended to use a fully English character path for the specified local library file, otherwise it may lead to game launch failure. -settings.advanced.no_jvm_args=Do not add default JVM arguments +settings.advanced.natives_directory.hint=This option is intended only for users of Apple silicon or other not officially supported platforms. Please do not edit this option unless you know what you are doing.\n\ + \n\ + Before proceeding, please make sure all libraries (e.g. lwjgl.dll, libopenal.so) are provided in your desired directory.\n\ + Note: It is recommended to use a fully English-letters path for the specified local library file. Otherwise it may lead to game launch failure. +settings.advanced.no_jvm_args=Do not add default Java VM arguments settings.advanced.precall_command=Pre-launch Command settings.advanced.precall_command.prompt=Commands to execute before the game launches settings.advanced.process_priority=Process Priority @@ -1128,74 +1190,75 @@ settings.advanced.post_exit_command.prompt=Commands to execute after the game ex settings.advanced.renderer=Renderer settings.advanced.renderer.default=OpenGL (Default) settings.advanced.renderer.d3d12=DirectX 12 (Poor performance and compatibility) -settings.advanced.renderer.llvmpipe=Software (Poor efficiency and best compatibility) -settings.advanced.renderer.zink=Vulkan (Best performance and poor compatibility) +settings.advanced.renderer.llvmpipe=Software (Poor performance, best compatibility) +settings.advanced.renderer.zink=Vulkan (Best performance, poor compatibility) settings.advanced.server_ip=Server Address -settings.advanced.server_ip.prompt=Join automatically after launching the game. -settings.advanced.use_native_glfw=[Linux Only] Use system GLFW -settings.advanced.use_native_openal=[Linux Only] Use system OpenAL -settings.advanced.workaround=Workarounds -settings.advanced.workaround.warning=Workaround options are intended only for expert users. Tweaking with these options may crash the game. Unless you know what you are doing, please do not modify these options. +settings.advanced.server_ip.prompt=Automatically join after launching the game +settings.advanced.use_native_glfw=[Linux/FreeBSD Only] Use System GLFW +settings.advanced.use_native_openal=[Linux/FreeBSD Only] Use System OpenAL +settings.advanced.workaround=Workaround +settings.advanced.workaround.warning=Workaround options are intended only for advanced users. Tweaking with these options may crash the game. Unless you know what you are doing, please do not edit these options. settings.advanced.wrapper_launcher=Wrapper Command -settings.advanced.wrapper_launcher.prompt=Allows launching using an extra wrapper program like 'optirun' on Linux. +settings.advanced.wrapper_launcher.prompt=Allows launching using an extra wrapper program like "optirun" on Linux settings.custom=Custom -settings.game=Game Settings +settings.game=Settings settings.game.current=Game settings.game.dimension=Resolution settings.game.exploration=Explore settings.game.fullscreen=Fullscreen -settings.game.java_directory=Java Path -settings.game.java_directory.auto=Automatically Select +settings.game.java_directory=Java +settings.game.java_directory.auto=Automatically Choose settings.game.java_directory.auto.not_found=No suitable Java version was installed. settings.game.java_directory.bit=%s bit -settings.game.java_directory.choose=Select Java path. -settings.game.java_directory.invalid=Incorrect Java path. +settings.game.java_directory.choose=Choose Java +settings.game.java_directory.invalid=Incorrect Java path settings.game.java_directory.version=Specify Java Version settings.game.java_directory.template=%s (%s) settings.game.management=Manage settings.game.working_directory=Working Directory -settings.game.working_directory.choose=Select working directory -settings.game.working_directory.hint=Turn on the 'Isolated' option under 'Working Directory' to allow the current instance to store its settings, saves, and mods in a separate directory.\n\ -\n\ -It is recommended to turn this option on to avoid mod conflicts, but you'll need to move your saves manually. +settings.game.working_directory.choose=Choose the working directory +settings.game.working_directory.hint=Enable the "Isolated" option in "Working Directory" to allow the current instance to store its settings, saves, and mods in a separate directory.\n\ + \n\ + It is recommended to enable this option to avoid mod conflicts, but you will need to move your saves manually. settings.game.working_directory.should_use_game_repo_feature=Willing to change the game storage path? Go to HMCL Homepage - Version List, and click [Add game repository]. settings.icon=Icon settings.launcher=Launcher Settings settings.launcher.appearance=Appearance -settings.launcher.common_path.tooltip=This app will put all game assets and dependencies here. If there are existing libraries under the game directory, the launcher will prefer to use them first. +settings.launcher.common_path.tooltip=HMCL will put all game assets and dependencies here. If there are existing libraries in the game directory, then HMCL will prefer to use them first. settings.launcher.debug=Debug settings.launcher.download=Download settings.launcher.download.threads=Threads settings.launcher.download.threads.auto=Automatically Determine settings.launcher.download.threads.hint=Too many threads may cause your system to freeze, and your download speed may be affected by your ISP and download servers. It is not always the case that more threads increase your download speed. settings.launcher.download_source=Download Source -settings.launcher.download_source.auto=Auto Choose Download Mirror -settings.launcher.enable_game_list=Show version list in home page +settings.launcher.download_source.auto=Automatically Choose Download Sources +settings.launcher.enable_game_list=Show instance list in homepage settings.launcher.font=Font settings.launcher.general=General -settings.launcher.language=Language (applies after restart) -settings.launcher.launcher_log.export=Export launcher logs -settings.launcher.launcher_log.export.failed=Unable to export logs -settings.launcher.launcher_log.export.success=Logs have been exported to %s +settings.launcher.language=Language (Applies After Restart) +settings.launcher.launcher_log.export=Export Launcher Logs +settings.launcher.launcher_log.reveal=Reveal Logs in Explorer +settings.launcher.launcher_log.export.failed=Failed to export logs. +settings.launcher.launcher_log.export.success=Logs have been exported to "%s". settings.launcher.log=Logging settings.launcher.log.font=Font settings.launcher.proxy=Proxy -settings.launcher.proxy.authentication=Requires authentication -settings.launcher.proxy.disable=Use system proxy +settings.launcher.proxy.authentication=Requires Authentication +settings.launcher.proxy.disable=Use System Proxy settings.launcher.proxy.host=Host settings.launcher.proxy.http=HTTP -settings.launcher.proxy.none=No proxy +settings.launcher.proxy.none=No Proxy settings.launcher.proxy.password=Password settings.launcher.proxy.port=Port -settings.launcher.proxy.socks=SOCKS +settings.launcher.proxy.socks=Socks settings.launcher.proxy.username=Username settings.launcher.theme=Theme -settings.launcher.title_transparent=Transparent titlebar -settings.launcher.turn_off_animations=Turn off animation (applies after restart) +settings.launcher.title_transparent=Transparent Titlebar +settings.launcher.turn_off_animations=Disable Animation (Applies After Restart) settings.launcher.version_list_source=Version List settings.memory=Memory @@ -1203,25 +1266,25 @@ settings.memory.allocate.auto=%1$.1f GB Minimum / %2$.1f GB Allocated settings.memory.allocate.auto.exceeded=%1$.1f GB Minimum / %2$.1f GB Allocated (%3$.1f GB Available) settings.memory.allocate.manual=%1$.1f GB Allocated settings.memory.allocate.manual.exceeded=%1$.1f GB Allocated (%3$.1f GB Available) -settings.memory.auto_allocate=Automatically allocate +settings.memory.auto_allocate=Automatically Allocate settings.memory.lower_bound=Minimum Memory settings.memory.used_per_total=%1$.1f GB Used / %2$.1f GB Total settings.physical_memory=Physical Memory Size settings.show_log=Show Logs -settings.skin=Skins for offline accounts are now supported. You can go to the account settings to change your skin and cape, but other players cannot see it in multiplayer. -settings.tabs.installers=Modloaders / OptiFine -settings.take_effect_after_restart=Applies after restart -settings.type=Instance Settings Type -settings.type.global=Global Instance Settings (shared among instances) -settings.type.global.manage=Global Game Settings -settings.type.global.edit=Edit Global Instance Settings -settings.type.special.enable=Enable per-instance settings +settings.skin=Skins for offline accounts are now supported. You can navigate to your account list to change your skin and cape, but other players cannot see it in multiplayer. +settings.tabs.installers=Loaders +settings.take_effect_after_restart=Applies After Restart +settings.type=Settings Type of Instance +settings.type.global=Global Settings (Shared Among Instances without the "Instance-specific Settings" enabled) +settings.type.global.manage=Global Settings +settings.type.global.edit=Edit Global Settings +settings.type.special.enable=Enable Instance-specific Settings settings.type.special.edit=Edit Current Instance Settings -settings.type.special.edit.hint=Current instance [%s] has enabled per-instance settings, all options on this page will NOT affect that instance. Click here to modify its own options. +settings.type.special.edit.hint=Current instance "%s" has enabled the "Instance-specific Settings". All options on this page will NOT affect that instance. Click here to edit its own settings. -sponsor=Donators -sponsor.bmclapi=Downloads are provided by BMCLAPI. Click here for more information. -sponsor.hmcl=Hello Minecraft! Launcher is a FOSS Minecraft launcher which allows users to manage multiple Minecraft instances easily. Click here for more information. +sponsor=Donors +sponsor.bmclapi=Downloads for the Chinese Mainland are provided by BMCLAPI. Click here for more information. +sponsor.hmcl=Hello Minecraft! Launcher is a FOSS Minecraft launcher that allows users to manage multiple Minecraft instances easily. Click here for more information. system.architecture=Architecture system.operating_system=Operating System @@ -1232,36 +1295,34 @@ update=Update update.accept=Update update.changelog=Changelog update.channel.dev=Beta -update.channel.dev.hint=You are currently using a beta build of the launcher, which may include some extra features, but is also sometimes more unstable than the release versions.\n\ -\n\ -If you encounter any bug or issue, you can go to the feedback page to report it, or tell us in our Discord or QQ community.\n\ -\n\ -Click here to hide this tooltip for the current version. -update.channel.dev.title=Beta Version Notice +update.channel.dev.hint=You are currently using a Beta channel build of the launcher. While it may include some extra features, it is also sometimes less stable than the Release channel builds.\n\ + \n\ + If you encounter any bugs or problems, please navigate to Settings → Feedback page to submit your feedback. +update.channel.dev.title=Beta Channel Notice update.channel.nightly=Nightly -update.channel.nightly.hint=You are currently using a nightly build of the launcher, which may include some extra features, but is also always more unstable than the other versions.\n\ -\n\ -If you encounter any bug or issue, you can go to the feedback page to report it, or tell us in our Discord or QQ community. -update.channel.nightly.title=Nightly Version Notice +update.channel.nightly.hint=You are currently using a Nightly channel build of the launcher. While it may include some extra features, it is also always less stable than the other channel builds.\n\ + \n\ + If you encounter any bugs or problems, please navigate to Settings → Feedback page to submit your feedback. +update.channel.nightly.title=Nightly Channel Notice update.channel.stable=Release update.checking=Checking for Updates -update.failed=Unable to update +update.failed=Failed to update update.found=Update Available! update.newest_version=Latest version: %s update.bubble.title=Update Available: %s update.bubble.subtitle=Click here to update -update.note=Warning: Beta versions and nightly versions may have more features or fixes, but they also come with more potential issues. -update.latest=This is the latest version. -update.no_browser=Cannot open in the system browser. But, we copied the link to your clipboard and you can open it manually. +update.note=Beta and Nightly channels may have more features or fixes, but they also come with more potential problems. +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 version=Games version.name=Instance Name -version.cannot_read=Unable to parse the game version, automatic installation cannot continue. +version.cannot_read=Failed to parse the game instance, installation cannot continue. version.empty=No Instances -version.empty.add=Add an Instance -version.empty.launch=No instances, you can install one on the 'Download' tab. -version.empty.hint=There are no Minecraft instances here. You can try switching to another game directory or click here to download one. +version.empty.add=Add new instance +version.empty.launch=No instances. You can install one in "Download → New Game". +version.empty.hint=There are no Minecraft instances here. You can try switching to another game directory or clicking here to download one. version.game.old=Historical version.game.release=Release version.game.releases=Releases @@ -1269,33 +1330,37 @@ version.game.snapshot=Snapshot version.game.snapshots=Snapshots version.launch=Launch Game version.launch.test=Test Launch -version.switch=Switch version +version.switch=Switch Instance version.launch_script=Export Launch Script -version.launch_script.failed=Unable to export launch script. +version.launch_script.failed=Failed to export launch script. version.launch_script.save=Export Launch Script version.launch_script.success=Exported launch script as %s. version.manage=All Instances version.manage.clean=Delete Log Files -version.manage.clean.tooltip=Remove logs and crash reports. +version.manage.clean.tooltip=Delete the files in "logs" and "crash-reports" directories. version.manage.duplicate=Duplicate Instance -version.manage.duplicate.duplicate_save=Duplicate Save -version.manage.duplicate.prompt=Enter instance name -version.manage.duplicate.confirm=The duplicated instance will have a copy of all the files of this instance, with an isolated game directory and settings. -version.manage.manage=Manage Instances -version.manage.manage.title=Manage Instance - %1s +version.manage.duplicate.duplicate_save=Duplicate Saves +version.manage.duplicate.prompt=Enter New Instance Name +version.manage.duplicate.confirm=The duplicated instance will have a copy of all files in the instance directory (".minecraft/versions/"), with an isolated working directory and settings. +version.manage.manage=Edit Instance +version.manage.manage.title=Edit Instance - %1s version.manage.redownload_assets_index=Update Game Assets version.manage.remove=Delete Instance -version.manage.remove.confirm=Are you sure you want to permanently remove the version %s? This action cannot be undone! -version.manage.remove.confirm.trash=Are you sure you want to remove the version %s? You can still find its files in your recycle bin by the name of %s. -version.manage.remove.confirm.independent=Since this instance is stored in an isolated directory, deleting it will also delete its saves and other data. Do you still want to delete instance %s? +version.manage.remove.confirm=Are you sure you want to permanently remove the instance "%s"? This operation cannot be undone!!! +version.manage.remove.confirm.trash=Are you sure you want to remove the instance "%s"? You can still find its files in your recycle bin by the name of "%s". +version.manage.remove.confirm.independent=Since this instance is stored in an isolated directory, deleting it will also delete its saves and other data. Do you still want to delete the instance "%s"? version.manage.remove_assets=Delete All Assets version.manage.remove_libraries=Delete All Libraries version.manage.rename=Rename Instance -version.manage.rename.message=Please enter the new name for this instance -version.manage.rename.fail=Unable to rename the instance, some files might be in use or the name contains an invalid character. +version.manage.rename.message=Enter New Instance Name +version.manage.rename.fail=Failed to rename the instance. Some files might be in use, or the name contains an invalid character. version.settings=Settings version.update=Update Modpack +wiki.tooltip=Minecraft Wiki Page +wiki.version.game.release=https://minecraft.wiki/w/Java_Edition_%s +wiki.version.game.snapshot=https://minecraft.wiki/w/Java_Edition_%s + wizard.prev=< Prev wizard.failed=Failed wizard.finish=Finish diff --git a/HMCL/src/main/resources/assets/lang/I18N_es.properties b/HMCL/src/main/resources/assets/lang/I18N_es.properties index e3c983843f..538ca7efdf 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_es.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_es.properties @@ -1,6 +1,6 @@ # # Hello Minecraft! Launcher -# Copyright (C) 2023 huangyuhui and contributors +# Copyright (C) 2025 huangyuhui 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 @@ -21,119 +21,141 @@ about=Acerca de about.copyright=Copyright -about.copyright.statement=Copyright © 2024 huangyuhui. +about.copyright.statement=Copyright © 2025 huangyuhui. about.author=Autor -about.author.statement=@huanghongxun en bilibili +about.author.statement=bilibili @huanghongxun about.claim=EULA -about.claim.statement=Haz clic en este enlace para ver el texto completo. +about.claim.statement=Haga clic en este enlace para ver el texto completo. about.dependency=Bibliotecas de terceros about.legal=Reconocimiento legal about.thanks_to=Gracias a -about.thanks_to.bangbang93.statement=Por proporcionar la API de descarga de BMCLAPI, por favor considere hacer una donación. +about.thanks_to.bangbang93.statement=Por proporcionar el espejo de descarga de BMCLAPI, ¡por favor, considere hacer una donación! +about.thanks_to.burningtnt.statement=Aportar mucho apoyo técnico a HMCL. about.thanks_to.contributors=Todos los colaboradores en GitHub -about.thanks_to.contributors.statement=Sin la impresionante comunidad de código abierto, Hello Minecraft! Launcher no habría llegado tan lejos. +about.thanks_to.contributors.statement=Sin la impresionante comunidad de código abierto, HMCL no habría llegado tan lejos. about.thanks_to.gamerteam.statement=Por proporcionar la imagen de fondo por defecto. -about.thanks_to.glavo.statement=Aportar mucho apoyo técnico a HMCL. +about.thanks_to.glavo.statement=Responsable del mantenimiento de HMCL. about.thanks_to.zekerzhayard.statement=Aportar mucho apoyo técnico a HMCL. -about.thanks_to.zkitefly.statement=Aportar mucho apoyo técnico a HMCL. -about.thanks_to.mcmod=mcmod.cn -about.thanks_to.mcmod.statement=Por facilitar las traducciones al chino y el wiki de varios mods. +about.thanks_to.zkitefly.statement=Responsable del mantenimiento de la documentación de HMCL. +about.thanks_to.mcbbs=MCBBS (Foro Chino Minecraft) +about.thanks_to.mcbbs.statement=Por proporcionar el espejo de descarga de mcbbs.net para los usuarios de China continental. (Ya no disponible) +about.thanks_to.mcmod=MCMod (mcmod.cn) +about.thanks_to.mcmod.statement=Por proporcionar las traducciones al chino simplificado y la wiki de varios mods. about.thanks_to.red_lnn.statement=Por proporcionar la imagen de fondo por defecto. +about.thanks_to.shulkersakura.statement=Por proporcionar el logotipo de HMCL. about.thanks_to.users=Miembros del grupo de usuarios de HMCL about.thanks_to.users.statement=Gracias por las donaciones, los informes de errores, etc. -about.thanks_to.yushijinhun.statement=Por proporcionar soporte relacionado con authlib-injector. +about.thanks_to.yushijinhun.statement=Por proporcionar el soporte relacionado con authlib-injector. about.open_source=Código abierto about.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL) account=Cuentas account.cape=Capa -account.character=personaje -account.choose=Elige un personaje -account.create=Añadir una cuenta +account.character=Jugador +account.choose=Elige un jugador +account.create=Añadir cuenta account.create.microsoft=Añadir una cuenta Microsoft -account.create.offline=Añadir una cuenta offline -account.create.authlibInjector=Crear una cuenta authlib-injector +account.create.offline=Añadir una cuenta sin conexión +account.create.authlibInjector=Añadir una cuenta authlib-injector account.email=Correo electrónico -account.failed=error de inicio de sesion -account.failed.character_deleted=El personaje ya ha sido eliminado. -account.failed.connect_authentication_server=No se ha podido conectar con los servidores de autenticación, es posible que la conexión a Internet esté caída. -account.failed.connect_injector_server=No se ha podido conectar con el servidor de autenticación. Por favor, compruebe su red y asegúrese de que ha introducido la URL correcta. +account.failed=Error al actualizar la cuenta. +account.failed.character_deleted=El jugador ya ha sido eliminado. +account.failed.connect_authentication_server=No se ha podido conectar con el servidor de autenticación. Es posible que su conexión de red no funcione. +account.failed.connect_injector_server=No se ha podido conectar con el servidor de autenticación. Por favor, comprueba su red y asegúrate de que has introducido la URL correcta. account.failed.injector_download_failure=No se ha podido descargar authlib-injector. Por favor, compruebe su red, o intente cambiar a un espejo de descarga diferente. -account.failed.invalid_credentials=Contraseña incorrecta o tasa limitada, por favor inténtelo más tarde. -account.failed.invalid_password=Contraseña no válida +account.failed.invalid_credentials=Contraseña incorrecta o tasa limitada, por favor, inténtelo más tarde. +account.failed.invalid_password=Contraseña no válida. account.failed.invalid_token=Por favor, intente iniciar sesión de nuevo. account.failed.migration=Su cuenta necesita ser migrada a una cuenta Microsoft. Si ya lo has hecho, debes volver a iniciar sesión con tu cuenta de Microsoft migrada. account.failed.no_character=No hay personajes vinculados a esta cuenta. +account.failed.server_disconnected=No se ha podido conectar con el servidor de autenticación. Puedes conectarte utilizando el modo sin conexión o intentar conectarte de nuevo.\n\ + Si lo intentas varias veces y sigue fallando, vuelve a intentar iniciar sesión en la cuenta. account.failed.server_response_malformed=Respuesta del servidor no válida, el servidor de autenticación puede no estar funcionando. +account.failed.ssl=Se ha producido un error SSL al conectar con el servidor. Por favor, intente actualizar su Java. account.failed.wrong_account=Ha iniciado sesión en la cuenta equivocada. -account.hmcl.hint=Debe hacer clic en "Acceder" y completar el proceso en la pestaña abierta en su navegador. -account.injector.add=Añadir un servidor de autenticación +account.hmcl.hint=Debe hacer clic en "Iniciar sesión" y completar el proceso en la ventana abierta del navegador. +account.injector.add=Nuevo servidor Auth account.injector.empty=Ninguno (Puedes hacer clic en el botón más de la derecha para añadir uno) -account.injector.http=Atención: Este servidor utiliza el protocolo HTTP inseguro, cualquiera entre su conexión podrá ver sus credenciales en texto claro. -account.injector.link.register=Registro +account.injector.http=Atención: Este servidor utiliza el protocolo HTTP inseguro. Cualquiera entre su conexión podrá ver sus credenciales en texto claro. +account.injector.link.homepage=Página de inicio +account.injector.link.register=Registrarse account.injector.server=Servidor de autenticación account.injector.server_url=URL del servidor account.injector.server_name=Nombre del servidor -account.login=Acceder +account.login=Iniciar sesión account.login.hint=No almacenaremos su contraseña. -account.login.refresh=Reiniciar sesión -account.login.refresh.microsoft.hint=Because the account authorization is invalid, you need to re-add your Microsoft account +account.login.skip=Iniciar sesión sin conexión +account.login.retry=Reintentar +account.login.refresh=Iniciar sesión de nuevo +account.login.refresh.microsoft.hint=Debe volver a iniciar sesión en su cuenta Microsoft porque la autorización de la cuenta no es válida. account.logout=Salir account.register=Registrarse account.manage=Lista de cuentas +account.copy_uuid=Copiar UUID de la cuenta account.methods=Tipo de inicio de sesión account.methods.authlib_injector=authlib-injector -account.methods.microsoft=Cuenta Microsoft -account.methods.microsoft.birth=Cómo actualizar la fecha de nacimiento de tu cuenta +account.methods.microsoft=Microsoft +account.methods.microsoft.birth=Cómo cambiar la fecha de nacimiento de su cuenta account.methods.microsoft.close_page=La autorización de la cuenta de Microsoft ha finalizado.\n\ -\n\ -Hay algunos trabajos extra para nosotros, pero puedes cerrar esta pestaña con seguridad por ahora. -account.methods.microsoft.makegameidsettings=Create Profile / Editar el nombre de perfil + \n\ + Hay algunos trabajos extra para nosotros, pero puedes cerrar esta pestaña con seguridad por ahora. +account.methods.microsoft.makegameidsettings=Crear perfil / Editar nombre del perfil account.methods.microsoft.deauthorize=Desautorizar -account.methods.microsoft.error.add_family=Como todavía no tienes 18 años, un adulto debe añadirte a una familia para que puedas jugar a Minecraft. -account.methods.microsoft.error.add_family_probably=Por favor, comprueba si la edad indicada en la configuración de tu cuenta es de al menos 18 años. Si no es así y crees que se trata de un error, puedes hacer clic en el enlace anterior para cambiarlo. +account.methods.microsoft.error.add_family=Un adulto debe añadirte a una familia para que puedas jugar a Minecraft porque aún no tienes 18 años. +account.methods.microsoft.error.add_family_probably=Por favor, compruebe si la edad indicada en la configuración de su cuenta es de al menos 18 años. Si no es así y cree que se trata de un error, puede hacer clic en "Cómo cambiar la fecha de nacimiento de su cuenta" para saber cómo cambiarla. account.methods.microsoft.error.country_unavailable=Xbox Live no está disponible en tu país/región actual. -account.methods.microsoft.error.missing_xbox_account=Tu cuenta Microsoft aún no tiene una cuenta Xbox vinculada. Crea una antes de continuar. -account.methods.microsoft.error.no_character=Tu cuenta no posee la edición Java de Minecraft.\nThe game profile may not have been created,\nplease click the link above to create it. +account.methods.microsoft.error.missing_xbox_account=Tu cuenta Microsoft aún no tiene una cuenta Xbox vinculada. Haga clic en "Crear perfil / Editar nombre de perfil" para crear una antes de continuar. +account.methods.microsoft.error.no_character=Por favor, asegúrese de que ha comprado Minecraft: Java Edition. \nSi ya lo has comprado, es posible que no hayas creado un perfil de juego.\nPor favor, haga clic en "Crear perfil / Editar nombre de perfil" para crearlo. account.methods.microsoft.error.unknown=No se ha podido iniciar sesión, error: %d. -account.methods.microsoft.logging_in=Inicio de sesión... -account.methods.microsoft.hint=Por favor, haz clic en el botón "Acceder", y copia el código que se muestra aquí para terminar el proceso de acceso en la ventana abierta del navegador.\n\ -\n\ -Si el token utilizado para acceder a la cuenta de Microsoft se ha filtrado, puedes hacer clic en "Desautorizar la cuenta" para desautorizarla.\n\ -Si encuentra algún problema, puede hacer clic en el botón de ayuda en la esquina superior derecha para obtener ayuda. -account.methods.microsoft.manual=El código de su dispositivo es %1$s, por favor, haga clic aquí para copiar. Después de hacer clic en el botón "Iniciar sesión", debería terminar el proceso de inicio de sesión en la ventana abierta del navegador. Si no aparece, puede ir a %2$s manualmente.n\ -\n\ -Si el token utilizado para acceder a la cuenta de Microsoft se ha filtrado, puedes hacer clic en "Desautorizar la cuenta" para desautorizarla.\n\ -Si encuentra algún problema, puede hacer clic en el botón de ayuda en la esquina superior derecha para obtener ayuda. +account.methods.microsoft.error.wrong_verify_method=Inicie sesión con su contraseña en la página de inicio de sesión de la cuenta Microsoft y no utilice un código de verificación para iniciar sesión. +account.methods.microsoft.logging_in=Iniciando sesión... +account.methods.microsoft.hint=Por favor, haga clic en "Iniciar sesión" y copie el código que aparece aquí para completar el proceso de inicio de sesión en la ventana del navegador que se abre.\n\ + \n\ + Si el token utilizado para iniciar sesión en la cuenta de Microsoft se ha filtrado, puedes hacer clic en "Desautorizar" para desautorizarlo. +account.methods.microsoft.manual=El código de su dispositivo es %1$s. Por favor, haga clic aquí para copiarlo.\n\ + \n\ + Después de hacer clic en "Iniciar sesión", debe completar el proceso de inicio de sesión en la ventana abierta del navegador. Si no se muestra, puede navegar a %2$s manualmente.\n\ + \n\ + Si el token utilizado para iniciar sesión en la cuenta de Microsoft se ha filtrado, puedes hacer clic en "Desautorizar" para desautorizarlo. account.methods.microsoft.profile=Perfil de la cuenta account.methods.microsoft.purchase=Comprar Minecraft -account.methods.microsoft.snapshot=Estás usando una construcción no oficial de hmcls, por favor descargue la construcción oficial para iniciar sesión en microsoft. +account.methods.microsoft.snapshot=Está utilizando una versión no oficial de HMCL. Por favor, descargue la versión oficial para iniciar sesión. +account.methods.microsoft.snapshot.website=Sitio web oficial account.methods.offline=Sin conexión +account.methods.offline.name.special_characters=Se recomienda utilizar letras en inglés, números y guiones bajos +account.methods.offline.name.invalid=Por lo general, los nombres de usuario de los juegos sólo admiten letras en inglés, números y guiones bajos, y no pueden superar los 16 caracteres de longitud.\n\ + \n\ + \ · Algunos nombres de usuario legítimos: HuangYu, huang_Yu, Huang_Yu_123;\n\ + \ · Algunos nombres de usuario ilegales: Huang Yu, Huang-Yu_%%%, Huang_Yu_hello_world_hello_world.\n\ + \n\ + Si crees que existe un mod o plugin correspondiente en el lado del servidor para eliminar esta restricción, puedes ignorar esta advertencia. account.methods.offline.uuid=UUID -account.methods.offline.uuid.hint=UUID es el identificador único del personaje del juego en Minecraft. La forma en que se genera puede variar entre diferentes launcheres. Cambiarlo por el generado por otros launchers te permite mantener tus objetos en el inventario de tu cuenta offline.\n\ -\n\ -Esta opción es sólo para usuarios avanzados. No recomendamos modificar esta opción a menos que sepas lo que estás haciendo.\n\ -Esta opción no es necesaria para unirse a servidores. -account.methods.offline.uuid.malformed=Formato no válido -account.methods.forgot_password=OLVIDÉ MI CONTRASEÑA +account.methods.offline.uuid.hint=UUID es un identificador único para los jugadores de Minecraft, y cada launcher puede generar UUID de manera diferente. Editarlo al generado por otros launchers te permite mantener tus objetos en el inventario de tu cuenta sin conexión.\n\ + \n\ + Esta opción es sólo para usuarios avanzados. No recomendamos editar esta opción a menos que sepas lo que estás haciendo. +account.methods.offline.uuid.malformed=Formato no válido. +account.methods.forgot_password=Olvidé mi contraseña account.missing=No hay cuentas -account.missing.add=Haz clic aquí para añadir una. +account.missing.add=Haga clic aquí para añadir una. +account.move_to_global=Convertir en cuenta global\nLa información de la cuenta se guardará en un archivo de configuración del directorio de usuario actual del sistema. +account.move_to_portable=Convertir en cuenta portátil\nLa información de la cuenta se guardará en un archivo de configuración en el mismo directorio que HMCL. account.not_logged_in=No ha iniciado sesión account.password=Contraseña -account.skin=Skin -account.skin.file=Archivo de skin +account.portable=Cuenta portátil +account.skin=Aspecto +account.skin.file=Archivo de aspecto account.skin.model=Modelo account.skin.model.default=Clásico -account.skin.model.slim=Delgado +account.skin.model.slim=Slim 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.hint=Tienes que crear un personaje con el mismo nombre que tu cuenta offline en la web de tu proveedor de skins. Tu skin será entonces subido a ese sitio. -account.skin.type.local_file=Archivo de skin local -account.skin.upload=Subir skin -account.skin.upload.failed=No se pudo subir la skin -account.skin.invalid_skin=Archivo de skin inválido +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.upload=Subir/editar aspecto +account.skin.upload.failed=No se pudo subir el aspecto. +account.skin.invalid_skin=Archivo de aspecto inválido. account.username=Nombre de usuario archive.author=Autor(es) @@ -148,25 +170,30 @@ assets.index.malformed=Los archivos de índice de los activos descargados estaba button.cancel=Cancelar button.change_source=Cambiar fuente de descarga button.clear=Limpiar +button.copy_and_exit=Copiar y salir button.delete=Borrar button.edit=Editar button.install=Instalar button.export=Exportar button.no=No -botón.ok=Aceptar +button.ok=Aceptar button.refresh=Refrescar button.remove=Eliminar -button.remove.confirm=¿Está seguro de querer eliminarlo definitivamente? Esta acción no se puede deshacer. +button.remove.confirm=¿Estás seguro de que deseas eliminarlo de forma permanente? ¡Esta acción no se puede deshacer! +button.retry=Reintentar button.save=Guardar button.save_as=Guardar como button.select_all=Seleccionar todo +button.view=Vista button.yes=Sí +chat=Chat de grupo + color.recent=Recomendado color.custom=Color personalizado -crash.NoClassDefFound=Por favor, verifique la integridad de este software, o intente actualizar su versión de Java. -crash.user_fault=El launcher se ha bloqueado debido a un entorno Java o de sistema corrupto. Por favor, asegúrese de que su Java o sistema operativo está instalado correctamente. +crash.NoClassDefFound=Por favor, verifique la integridad de este software, o intente actualizar su Java. +crash.user_fault=El launcher se ha bloqueado debido a que Java o el entorno del sistema están dañados. Por favor, asegúrese de que su Java o sistema operativo está instalado correctamente. curse.category.0=Todos @@ -219,6 +246,7 @@ curse.category.4486=Lucky Blocks curse.category.432=Buildcraft curse.category.418=Genética curse.category.4671=Integración Twitch +curse.category.5314=KubeJS curse.category.408=Recursos y minerales curse.category.4773=CraftTweaker curse.category.430=Thaumcraft @@ -258,7 +286,7 @@ curse.category.393=16x curse.category.403=Tradicional curse.category.394=32x curse.category.404=Animado -curse.category.4465=Soporte de Mod. +curse.category.4465=Soporte de Mod curse.category.402=Medieval curse.category.401=Moderno @@ -292,79 +320,115 @@ curse.sort.popularity=Popularidad curse.sort.total_downloads=Descargas totales download=Descargar -download.hint=Instalar juegos y modpacks o descargar mods, paquetes de recursos y mapas +download.hint=Instalar juegos y modpacks o descargar mods, paquetes de recursos y mundos. download.code.404=Archivo no encontrado en el servidor remoto: %s download.content=Complementos -download.existing=El archivo no se puede guardar porque ya existe. Puedes usar 'Guardar como' para guardar el archivo en otro lugar. +download.curseforge.unavailable=La versión Nightly de HMCL no admite el acceso a CurseForge. Utilice las versiones Release o Beta para descargarlas. +download.existing=El archivo no se puede guardar porque ya existe. Puedes hacer clic en "Guardar como" para guardar el archivo en otro lugar. download.external_link=Abrir sitio web -download.failed=Falló la descarga de %1$s, código de respuesta: %2$d +download.failed=Falló la descarga de %1$s, código de respuesta: %2$d. download.failed.empty=No hay versiones disponibles, por favor haga clic aquí para volver. +download.failed.no_code=No se ha podido descargar download.failed.refresh=No se ha podido obtener la lista de versiones. Por favor, haga clic aquí para volver a intentarlo. -download.game=Juego +download.game=Nuevo juego download.provider.bmclapi=BMCLAPI (bangbang93, https://bmclapi2.bangbang93.com/) -download.provider.mojang=Mojang (OptiFine es proporcionado por BMCLAPI) +download.provider.mojang=Oficial (OptiFine es proporcionado por BMCLAPI) download.provider.official=De fuentes oficiales download.provider.balanced=De la fuente más rápida disponible download.provider.mirror=Desde espejo -download.javafx=Descargar las dependencias para el launcher... +download.java=Descargando Java +download.java.override=Esta versión de Java ya existe. ¿Desea desinstalarla y volver a instalarla? +download.javafx=Descargando dependencias para el launcher... download.javafx.notes=Estamos descargando dependencias para HMCL desde Internet.\n\ -\n\ -Puede hacer clic en "Cambiar fuente de descarga" para seleccionar el espejo de descarga o hacer clic en "Cancelar" para detener y salir.\n\ -Nota: Si su velocidad de descarga es demasiado lenta, puede intentar cambiar a otro espejo. + \n\ + Puede hacer clic en "Cambiar fuente de descarga" para seleccionar el\nespejo de descarga o hacer clic en "Cancelar" para detener y salir.\n\ + Nota: Si su velocidad de descarga es demasiado lenta, puede intentar cambiar a otro espejo. download.javafx.component=Descargando módulo %s download.javafx.prepare=Preparando la descarga -exception.access_denied=HMCL no puede acceder al archivo %s, puede estar bloqueado por otro proceso.\n\ -\n\ -Para los usuarios de Windows, puede abrir el 'Monitor de Recursos' para comprobar si otro proceso lo está utilizando actualmente. Si es así, puede intentarlo de nuevo después de cerrar ese proceso.\ -Si no es así, comprueba si tu cuenta tiene suficientes privilegios para acceder a él. -exception.artifact_malformed=No se puede verificar la integridad de los archivos descargados . -exception.ssl_handshake=No se puede establecer una conexión SSL debido a que faltan certificados SSL en la instalación actual de Java. Puede intentar ejecutar HMCL en otra versión de Java y luego volver a intentarlo. +exception.access_denied=HMCL no puede acceder al archivo %s. Puede estar bloqueado por otro proceso.\n\ + \n\ + Para los usuarios de Windows, puede abrir el "Monitor de Recursos" para comprobar si otro proceso lo está utilizando actualmente. Si es así, puede intentarlo de nuevo después de cerrar ese proceso.\n\ + Si no es así, comprueba si tu cuenta tiene permisos suficientes para acceder a ella. +exception.artifact_malformed=No se puede verificar la integridad de los archivos descargados. +exception.ssl_handshake=No se pudo establecer una conexión SSL porque falta el certificado SSL en la instalación actual de Java. Puede intentar abrir HMCL con otro Java y volver a intentarlo. -extension.bat=Fichero por lotes de DOS +extension.bat=Archivo por lotes de Windows (.bat) extension.mod=Archivo mod. extension.png=Archivo de imagen -extension.ps1=Fichero de Cmdlet de Windows PowerShell -extension.sh=Script de Shell Bash +extension.ps1=Script de Windows PowerShell (.ps1) +extension.sh=Script de Shell Script (.sh) fatal.javafx.incompatible=No se encontró un entorno JavaFX.\n\ -HMCL no puede instalar automáticamente JavaFX con versiones de Java inferiores a la 11.\n\ -Por favor, intente cambiar a versiones de Java como Oracle Java 8 que tiene JavaFX incorporado, o actualice a la versión 11 o superior. + 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\ -Si está utilizando Java 11 o superior, por favor, descargue Oracle JRE 8 (java.com), o instale BellSoft Liberica Full JRE (bell-sw.com/pages/downloads).\n\ -O, si está utilizando distribuciones de OpenJDK, asegúrese de que tiene incluido OpenJFX. -fatal.config_loading_failure=No se pueden cargar los archivos de configuración.\nPara macOS, intente colocar HMCL en un lugar con permiso que no sea "Escritorio", "Descargas" y "Documentos" y vuelva a intentarlo. -¡Por favor, asegúrese de que "Hello Minecraft! Launcher" tiene acceso de lectura y escritura a "%s" y a los archivos que contiene. + Por favor, abra Hello Minecraft! Launcher con Java, que incluye OpenJFX. +fatal.config_change_owner_root=Estás utilizando la cuenta root para abrir Hello Minecraft! Launcher. Esto puede hacer que no pueda abrir HMCL con otra cuenta en el futuro.\n\ + ¿Todavía quieres continuar? +fatal.config_in_temp_dir=Estás abriendo Hello Minecraft! Launcher en un directorio temporal. Tus ajustes y datos de juego pueden perderse.\n\ + Se recomienda trasladar el HMCL a otro lugar y reabrirlo.\n\ + ¿Todavía quieres continuar? +fatal.config_loading_failure=No se pueden cargar los archivos de configuración.\n\ + Por favor, asegúrese de que Hello Minecraft! Launcher tiene acceso de lectura y escritura a "%s" y a los archivos que contiene.\n\ + Para macOS, intente colocar HMCL en un lugar con permiso que no sea "Escritorio", "Descargas" y "Documentos" y vuelva a intentarlo. +fatal.config_loading_failure.unix=No se pudo cargar el archivo de configuración porque fue creado por el usuario "%1$s".\n\ + Por favor, abra HMCL como usuario root (no recomendado), o ejecute el siguiente comando en el terminal para cambiar la propiedad del archivo de configuración al usuario actual:\n%2$s +fatal.mac_app_translocation=El sistema operativo aísla Hello Minecraft! Launcher en un directorio temporal debido a los mecanismos de seguridad de macOS.\n\ + Por favor, mueve HMCL a un directorio diferente antes de intentar abrirlo. De lo contrario, tus ajustes y datos de juego podrían perderse tras reiniciar.\n\ + ¿Todavía quieres continuar? fatal.migration_requires_manual_reboot=Hello Minecraft! Launcher ha sido actualizado. Por favor, reinicie el launcher. fatal.apply_update_failure=Lo sentimos, pero Hello Minecraft! Launcher no puede actualizarse.\n\ -\n\ -Usted puede actualizar manualmente mediante la descarga de una versión más reciente del launcher de %s.\n\ -Si el problema persiste, por favor considere reportarlo a nosotros. -fatal.samba=Si ha iniciado HMCL desde una unidad de red Samba, es posible que algunas funciones no funcionen. Por favor, intente actualizar su versión de Java o copie y ejecute el launcher en una carpeta local. -fatal.illegal_char=Su ruta de usuario contiene un carácter ilegal '\=', por lo que algunas características podrían no funcionar correctamente.\n\ -Por ejemplo, no podrá utilizar authlib-injector o cambiar el skin de su cuenta offline. + \n\ + Puedes actualizar manualmente descargando una versión más reciente del launcher desde %s.\n\ + Si el problema persiste, por favor considere reportarlo a nosotros. +fatal.apply_update_need_win7=Hello Minecraft! Launcher no puede actualizarse automáticamente en Windows XP/Vista.\n\ + \n\ + Puedes actualizar manualmente descargando una versión más reciente del launcher desde %s. +fatal.samba=Si ha abierto Hello Minecraft! Launcher desde una unidad de red Samba, es posible que algunas funciones no funcionen. Intenta actualizar tu Java o mover el launcher a otro directorio. +fatal.illegal_char=Su ruta de usuario contiene un carácter ilegal "=", por lo que algunas características podrían no funcionar correctamente.\n\ + Por ejemplo, no podrá utilizar authlib-injector o cambiar el skin de su cuenta offline. +fatal.unsupported_platform=Minecraft aún no es totalmente compatible con tu plataforma, por lo que es posible que te falten funciones o incluso que no puedas iniciar el juego.\n\ + \n\ + Si no puedes iniciar Minecraft 1.17 y versiones posteriores, puedes probar a cambiar el "Renderizador" a "Software" en "Config. Global/Específica de instancia → Configuración avanzada" para utilizar el renderizado de la CPU y mejorar la compatibilidad. +fatal.unsupported_platform.loongarch=Hello Minecraft! Launcher ha prestado apoyo a la plataforma Loongson.\n\ + Si tienes problemas al jugar, puedes visitar https://docs.hmcl.net/groups.html para obtener ayuda. +fatal.unsupported_platform.osx_arm64=Hello Minecraft! Launcher ha proporcionado soporte para la plataforma de chips de Apple, utilizando Java nativo de ARM para ejecutar juegos y conseguir una experiencia de juego más fluida.\n\ + Si tienes problemas al jugar a un juego, ejecutarlo con Java de arquitectura x86-64 puede tener mejor compatibilidad. +fatal.unsupported_platform.windows_arm64=Hello Minecraft! Launcher ha proporcionado soporte nativo para la plataforma Windows en Arm. Si tiene problemas al jugar a un juego, intente iniciarlo con Java de arquitectura x86.\n\ + \n\ + Si utilizas la plataforma Qualcomm, es posible que tengas que instalar el paquete de compatibilidad OpenGL 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=¡Únete a nuestra comunidad de 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 -folder.game=Directorio de juego -folder.mod=Abrir carpeta de mods -folder.resourcepacks=Resource packs -folder.shaderpacks=Shader packs -folder.saves=Guardados +folder.game=Directorio de trabajo +folder.logs=Logs +folder.mod=Mods +folder.resourcepacks=Paquetes de recursos +folder.shaderpacks=Paquetes de sombreados +folder.saves=Mundos folder.screenshots=Capturas de pantalla -game=Juego -game.crash.feedback=¡Por favor, no comparta capturas de pantalla de esta interfaz con otras personas! Si solicita ayuda a otros, haga clic para exportar la información del bloqueo del juego en la esquina inferior izquierda y envíe el archivo exportado a otros para que lo analicen. +game=Juegos +game.crash.feedback=¡Por favor, no comparta capturas de pantalla de esta interfaz con otras personas! Si pide ayuda a otras personas, por favor haga clic en "Exportar registros de errores" y envíe el archivo exportado a otras personas para su análisis. game.crash.info=Información sobre el fallo game.crash.reason=Causa del fallo game.crash.reason.analyzing=Analizando... -game.crash.reason.multiple=Se detectaron múltiples razones:\n\n -game.crash.reason.block=El juego se ha bloqueado debido a un error.\ +game.crash.reason.multiple=Se han detectado varias razones:\n\n +game.crash.reason.block=El juego se ha bloqueado debido a un error.\n\ \n\ Puede tratar de eliminar este bloque utilizando MCEdit o eliminar el mod que lo ha añadido.\n\ \n\ @@ -374,7 +438,7 @@ game.crash.reason.bootstrap_failed=El juego se ha bloqueado debido al mod %1$s.\ \n\ Puedes intentar borrarlo o actualizarlo. game.crash.reason.config=El juego se ha bloqueado porque un mod %1$s no puede analizar su archivo de configuración %2$s. -game.crash.reason.debug_crash=El juego se ha bloqueado porque lo has activado manualmente. +game.crash.reason.debug_crash=El juego se ha bloqueado porque lo has activado manualmente.\n\ \n\ Entonces, probablemente sabes por qué. game.crash.reason.mixin_apply_mod_failed=El juego se ha bloqueado porque Mixin no puede aplicar el mod %1$s, no se puede continuar.\n\ @@ -431,7 +495,7 @@ game.crash.reason.loading_crashed_fabric=El juego se ha bloqueado debido al mod Puedes intentar borrarlo o actualizarlo. game.crash.reason.memory_exceeded=El juego se ha bloqueado debido a que se ha asignado demasiada memoria para un pequeño archivo de paginación.\n\ \n\ -Puede intentar desactivar la opción de asignación automática de memoria en la configuración y ajustar el valor hasta que el juego se inicie. +Puede intentar desactivar la opción de asignación automática de memoria en la configuración y ajustar el valor hasta que el juego se inicie.\n\ También puede tratar de aumentar el tamaño del archivo de paginación en la configuración del sistema. game.crash.reason.mod=El juego se ha bloqueado debido al mod %1$s.\n\ \n\ @@ -470,22 +534,19 @@ game.crash.reason.mod_resolution_mod_version.any=%1$s (Cualquier versión) game.crash.reason.forge_repeat_installation=El juego actual no puede continuar ejecutándose debido a una instalación duplicada de Forge. Este es un problema conocido\nSe recomienda cargar los comentarios del registro en GitHub para que podamos encontrar más pistas y arreglar esta pregunta. \nActualmente puede ir a la instalación automática para desinstalar Forge y volver a instalarlo. game.crash.reason.optifine_repeat_installation=El juego actual no puede seguir ejecutándose debido a la instalación repetida de OptiFine. \nElimine OptiFine de la carpeta Mod o vaya a Gestión de juegos-Instalación automática para desinstalar OptiFine que se instala automáticamente. game.crash.reason.modmixin_failure=El juego actual no puede continuar ejecutándose debido a algunas fallas en la inyección de mods. \nEsto generalmente significa que el mod tiene un error o no es compatible con el entorno actual. \nPuede consultar el registro para ver si hay un mod incorrecto. -game.crash.reason.file_or_content_verification_failed=El juego actual tiene un problema porque fallaron algunos archivos o la verificación de contenido. \nIntente eliminar esta versión (incluidas las modificaciones) y vuelva a descargarla, o intente usar un proxy cuando vuelva a descargar, etc. game.crash.reason.mod_repeat_installation=El juego actual tiene múltiples Mods idénticos instalados repetidamente, y cada Mod solo puede aparecer una vez. Elimina los Mods repetidos y luego inicia el juego. game.crash.reason.forge_error=Forge puede haber proporcionado un mensaje de error.\nPuede ver el registro y realizar las acciones correspondientes de acuerdo con la información de registro en el informe de errores.\nSi no ve un mensaje de error, puede consultar el informe de errores para saber cómo ocurrió el error.\n%1$s -game.crash.reason.mod_solution0=El juego actual no puede seguir ejecutándose debido a algunos problemas de modificación.\nPuede consultar el registro para ver si hay un mod incorrecto. -game.crash.reason.mod_profile_causes_game_crash=El juego actual no puede seguir ejecutándose debido a un problema con el perfil de mod.\nPuede consultar el registro del mod defectuoso y su archivo de configuración. -game.crash.reason.fabric_reports_an_error_and_gives_a_solution=Forge puede haber dado un mensaje de error.\nPuede ver el registro y realizar las acciones correspondientes de acuerdo con la información de registro en el informe de errores.\nSi no ve un mensaje de error, puede consultar el informe de errores para saber cómo ocurrió el error. +game.crash.reason.mod_resolution0=El juego actual no puede seguir ejecutándose debido a algunos problemas de modificación.\nPuede consultar el registro para ver si hay un mod incorrecto. game.crash.reason.java_version_is_too_high=El juego actual se bloqueó porque la versión de Java es demasiado alta y no puede seguir ejecutándose.\nCambie a una versión anterior de Java en la pestaña Java Path de Configuración global del juego o Configuración específica del juego antes de iniciar el juego.\nSi no es así, puede descargarlo desde java.com (Java8) o BellSoft Liberica Full JRE (Java17) y otras plataformas para descargar e instalar una (reinicie el iniciador después de la instalación). game.crash.reason.mod_name=El juego actual no puede continuar ejecutándose debido a un problema con el nombre del archivo mod. El nombre del archivo de\nMod solo debe usar mayúsculas y minúsculas (Aa ~ Zz), números (0 ~ 9), líneas horizontales (-), subrayado (_) y puntos (.) en toda la mitad del inglés.\n Por favor, vaya a la carpeta mod para agregar un carácter de Cumplimiento anterior a todos los nombres de archivo mod que no cumplan. game.crash.reason.incomplete_forge_installation=O jogo atual não pode continuar devido a uma instalação incompleta do Forge / NeoForge.\nDesinstale e reinstale o Forge / NeoForge em Configurações de versão - Instalação automática. game.crash.reason.modlauncher_8=El juego se ha bloqueado porque tu versión actual de Forge no es compatible con tu instalación de Java. Por favor, intente actualizar Forge, o intente utilizar Java 8u312/11.0.13/17.0.1 o versiones anteriores.\n\ \n\ 8u312 y anteriores\:\n\ -https://oracle.com/java/technologies/javase/javase8u211-later-archive-downloads.html +https://oracle.com/java/technologies/javase/javase8u211-later-archive-downloads.html\n\ \n\ 11.0.13 y anteriores\:\n\ -https://oracle.com/java/technologies/javase/jdk11-archive-downloads.html +https://oracle.com/java/technologies/javase/jdk11-archive-downloads.html\n\ \n\ 17.0.1 y anteriores\:\n\ https://oracle.com/java/technologies/javase/jdk17-archive-downloads.html @@ -494,21 +555,21 @@ game.crash.reason.no_class_def_found_error=El juego no puede ejecutarse debido a A su instancia de juego le falta %1$s, esto puede deberse a que falta un mod, a que hay un mod incompatible instalado o a que algunos archivos pueden estar dañados.\n\ \n\ Puede que tengas que reinstalar el juego y todos los mods o pedir ayuda a alguien. -game.crash.reason.no_sethod_error=El juego no puede ejecutarse debido a un código incompleto.\n\ +game.crash.reason.no_such_method_error=El juego no puede ejecutarse debido a un código incompleto.\n\ \n\ Es posible que a su instancia de juego le falte un mod, que haya instalado un mod incompatible o que algunos archivos estén dañados.\n\ \n\ Puede que tengas que reinstalar el juego y todos los mods o pedir ayuda a alguien. game.crash.reason.opengl_not_supported=El juego se ha bloqueado porque OpenGL no es compatible con su controlador de gráficos.\n\ \n\ -Si está transmitiendo el juego a través de Internet o utilizando un entorno de escritorio remoto, por favor, intente jugar el juego en su entorno local. +Si está transmitiendo el juego a través de Internet o utilizando un entorno de escritorio remoto, por favor, intente jugar el juego en su entorno local.\n\ O bien, puede intentar actualizar su controlador a la última versión y volver a intentarlo.\n\ \n\ Si tu ordenador tiene una tarjeta gráfica discreta, por favor, asegúrate de que el juego la está utilizando para el renderizado. Si el problema persiste, considera la posibilidad de adquirir una nueva tarjeta gráfica o un nuevo ordenador. game.crash.reason.openj9=El juego no puede ejecutarse en una máquina virtual OpenJ9. Por favor, cambia a una VM Java Hotspot en la configuración del juego y vuelve a ejecutar el juego. Si no tienes una, puedes descargar una en línea. game.crash.reason.out_of_memory=El juego se ha bloqueado porque se ha quedado sin memoria.\n\ \n\ -Esto puede deberse a que no hay suficiente memoria disponible, o a que hay demasiados mods instalados. Puedes intentar solucionarlo aumentando la memoria asignada en la configuración del juego. +Esto puede deberse a que no hay suficiente memoria disponible, o a que hay demasiados mods instalados. Puedes intentar solucionarlo aumentando la memoria asignada en la configuración del juego.\n\ Si sigues encontrando estos problemas, es posible que necesites una mejor computadora. game.crash.reason.resolution_too_high=El juego se ha bloqueado porque estás utilizando un resource packs cuya resolución de textura es demasiado alta.\n\ \n\ @@ -517,12 +578,12 @@ game.crash.reason.shaders_mod=El juego actual no puede seguir ejecutándose porq game.crash.reason.rtss_forest_sodium=El juego actual se bloquea debido a una incompatibilidad entre RivaTuner Statistics Server (RTSS) y Sodium, lo que provoca el fallo del juego. Haz clic aquí para obtener más detalles. game.crash.reason.stacktrace=El motivo del fallo es desconocido. Puede ver los detalles haciendo clic en el botón "Registros".\n\ \n\ -Hay algunas palabras clave que pueden contener algunos Mod IDs. Puedes buscarlas en Internet para averiguar el problema por ti mismo..\n\ +Hay algunas palabras clave que pueden contener algunos Mod IDs. Puedes buscarlas en Internet para averiguar el problema por ti mismo.\n\ \n\ %s game.crash.reason.too_old_java=El juego se ha bloqueado porque estás utilizando una versión histórica de Java VM..\n\ \n\ -Tienes que cambiar a una versión más reciente (%1$s) de Java en la configuración del juego y luego volver a ejecutar el juego. Puedes descargar Java desde here. +Tienes que cambiar a una versión más reciente (%1$s) de Java en la configuración del juego y luego volver a ejecutar el juego. Puedes descargar Java desde here. game.crash.reason.unknown=No somos capaces de averiguar por qué el juego se estrelló, por favor refiérase a los registros del juego.\n\ \n\ Cuando pidas ayuda a otra persona, por favor, comparte con ella el registro completo del juego y el archivo de informe de fallos relacionado.\n\ @@ -533,112 +594,140 @@ game.crash.reason.unsatisfied_link_error=No se puede iniciar Minecraft porque fa Si ha modificado la configuración de la biblioteca nativa, por favor, asegúrese de que estas bibliotecas existen. O, por favor, intente iniciar de nuevo después de revertir a los valores predeterminados.\n\ Si no lo ha hecho, por favor, compruebe si le faltan mods de dependencia.\n\ De lo contrario, si usted cree que esto es causado por HMCL, por favor, comentarios a nosotros. -game.crash.reason.failed_to_load_a_library=Failed to load a library.\n\ -\n\ -Si ha modificado la configuración de la biblioteca nativa, por favor, asegúrese de que estas bibliotecas existen. O, por favor, intente iniciar de nuevo después de revertir a los valores predeterminados.\n\ -Si no lo ha hecho, por favor, compruebe si le faltan mods de dependencia.\n\ -De lo contrario, si usted cree que esto es causado por HMCL, por favor, comentarios a nosotros. game.crash.title=Juego interrumpido game.directory=Ruta del juego game.version=Versión del juego help=Ayuda -help.doc=Documentación de ¡Hello Minecraft! Launcher -help.detail=Para los creadores de datapacks y modpacks. +help.doc=Documentación de Hello Minecraft! Launcher +help.detail=Para los creadores de paquetes de datos y mods. input.email=El nombre de usuario debe ser una dirección de correo electrónico. input.number=La entrada debe ser un número. input.not_empty=Este es un campo obligatorio. input.url=La entrada debe ser una URL válida. -install=Instalar +install=Nueva instancia install.change_version=Cambiar versión install.change_version.confirm=¿Está seguro de querer cambiar %s de la versión %s a %s? install.failed=Fallo en la instalación -install.failed.downloading=No podemos descargar algunos de los archivos necesarios. +install.failed.downloading=No se han podido descargar algunos archivos necesarios. install.failed.downloading.detail=No se ha podido descargar el archivo: %s -install.failed.downloading.timeout=Tiempo de descarga agotado en la búsqueda: %s +install.failed.downloading.timeout=Tiempo de espera de la descarga: %s install.failed.install_online=No se ha podido identificar el archivo proporcionado. Si está instalando un mod, vaya a la página "Mods". -install.failed.malformed=Los archivos descargados están dañados. Puedes intentar solucionar este problema cambiando a otra espejo de descarga. -install.failed.optifine_conflict=No se puede instalar Fabric, OptiFine y Forge en Minecraft 1.13 o superior. -install.failed.optifine_forge_1.17=Para Minecraft versión 1.17.1 o inferior, Forge sólo soporta OptiFine H1 Pre2 o más reciente. Puedes instalarlos en la pestaña de versiones instantáneas. -install.failed.version_mismatch=Esta biblioteca requiere la versión del juego %s, pero la instalada es %s. -install.installer.change_version=La versión %s no es compatible con la versión actual del juego. Haga clic aquí para sustituirla por otra versión o eliminarla. +install.failed.malformed=Los archivos descargados están dañados. Puedes intentar resolver este problema cambiando a otra fuente de descarga en "Ajustes → Descarga → Fuente de descarga". +install.failed.optifine_conflict=No se puede instalar tanto OptiFine como Fabric en Minecraft 1.13 o posterior. +install.failed.optifine_forge_1.17=Para Minecraft 1.17.1, Forge sólo es compatible con OptiFine H1 pre2 o posterior. Puedes instalarlos marcando "Snapshots" al elegir una versión de OptiFine en HMCL. +install.failed.version_mismatch=Este cargador requiere la versión del juego %s, pero la instalada es %s. +install.installer.change_version=%s Incompatible install.installer.choose=Elija su versión %s install.installer.depend=Requiere %s install.installer.fabric=Fabric install.installer.fabric-api=Fabric API install.installer.fabric-api.warning=Atención: Fabric API es un mod, y se instalará en el directorio de mods de la instancia del juego. Por favor, no cambies el directorio de trabajo del juego, o el Fabric API no funcionará. Si quieres cambiar esta configuración, debes reinstalarlo. install.installer.forge=Forge +install.installer.neoforge=NeoForge install.installer.game=Minecraft install.installer.incompatible=Incompatible con %s install.installer.install=Instalar %s install.installer.install_offline=Instalar/actualizar desde archivo local -install.installer.install_offline.extension=Instalador de Forge/OptiFine -install.installer.install_offline.tooltip=Apoyamos el uso del instalador local de Forge/OptiFine. +install.installer.install_offline.extension=Instalador de (Neo)Forge/OptiFine +install.installer.install_offline.tooltip=Apoyamos el uso del instalador local de (Neo)Forge/OptiFine. install.installer.install_online=Instalación en línea -install.installer.install_online.tooltip=Actualmente soportamos Fabric, Forge, OptiFine y LiteLoader. +install.installer.install_online.tooltip=Actualmente soportamos Forge, NeoForge, OptiFine, Fabric, Quilt y LiteLoader. install.installer.liteloader=LiteLoader -install.installer.not_installed=No seleccionado +install.installer.not_installed=No instalar install.installer.optifine=OptiFine install.installer.quilt=Quilt install.installer.quilt-api=QSL/QFAPI install.installer.version=%s -install.modpack=Instalar un Modpack +install.installer.external_version=%s (Instalado por un proceso externo, que no se puede configurar) +install.modpack=Añadir modpack +install.name.invalid=El nombre contiene caracteres no ASCII (como emoji o caracteres CJK).\n\nSe recomienda cambiar el nombre para que sólo incluya letras, números y guiones bajos en inglés para evitar posibles problemas al iniciar el juego.\n\n¿Desea continuar con la instalación? install.new_game=Añadir instancia install.new_game.already_exists=Esta instancia ya existe. Por favor, utilice otro nombre. install.new_game.current_game_version=Versión actual de la instancia -install.new_game.malformed=Nombre no válido -install.select=Seleccione una operación +install.new_game.malformed=Nombre no válido. +install.select=Elegir una operación install.success=Instalado con éxito. +java.add=Añadir Java +java.add.failed=Este Java no es válido o es incompatible con la plataforma actual. +java.disable=Deshabilitar este Java +java.disable.confirm=¿Está seguro de que desea desactivar este Java? +java.disabled.management=Java desactivado +java.disabled.management.remove=Eliminar este Java de la lista +java.disabled.management.restore=Activar este Java +java.download=Descargar Java +java.download.banshanjdk-8=Descargar Banshan JDK 8 +java.download.load_list.failed=No se ha podido cargar la lista de versiones +java.download.more=Más distribuciones de Java +java.download.prompt=Elija la versión de Java que desea descargar: +java.download.distribution=Distribución +java.download.version=Versión +java.download.packageType=Tipo de paquete +java.management=Gestión Java +java.info.architecture=Arquitectura +java.info.vendor=Proveedor +java.info.version=Versión +java.info.disco.distribution=Distribución +java.install=Instalar Java +java.install.archive=Fuente Path +java.install.failed.exists=Este nombre ya tiene dueño +java.install.failed.invalid=Este archivo no es un paquete de instalación de Java válido, por lo que no se puede instalar. +java.install.failed.unsupported_platform=Este Java no es compatible con la plataforma actual, por lo que no puede instalarse. +java.install.name=Nombre +java.install.warning.invalid_character=Carácter ilegal en el nombre +java.reveal=Revelar el directorio de Java +java.uninstall=Desinstalar Java +java.uninstall.confirm=¿Está seguro de que desea desinstalar este Java? ¡Esta acción no se puede deshacer! + lang=Español lang.default=Usar idioma del sistema -launch.advice.java.auto=La versión actual de la máquina virtual de Java no cumple los requisitos del juego.\n\ -\n\ -Haga clic en "Sí" para elegir automáticamente la versión de Java VM más compatible. O bien, puede ir a la configuración de la instancia para seleccionar una usted mismo. +launch.advice=%s ¿Todavía quieres continuar? +launch.advice.multi=Se han detectado los siguientes problemas:\n\n%s\n\nEstos problemas pueden impedir que inicies el juego o afectar a la experiencia de juego.\n\n¿Todavía quieres continuar? +launch.advice.java.auto=La versión actual de la máquina virtual de Java no cumple los requisitos del juego.\n\nHaga clic en "Sí" para elegir automáticamente la versión de Java VM más compatible. O bien, puede navegar hasta "Config. Global/Específica de instancia → Java" para elegir uno usted mismo. launch.advice.java.modded_java_7=Minecraft 1.7.2 y versiones anteriores requieren Java 7 o anterior. -launch.advice.corrected=Hemos solucionado el problema de la máquina virtual de Java. Si todavía quieres usar la versión de Java que elijas, puedes desactivar las comprobaciones de compatibilidad en los ajustes de juego del launcher. -launch.advice.uncorrected=Si todavía quieres utilizar la versión de Java que elijas, puedes desactivar las comprobaciones de compatibilidad en los ajustes de juego del launcher. +launch.advice.corrected=Hemos solucionado el problema de la máquina virtual de Java. Si todavía quieres utilizar la versión de Java que elijas, puedes desactivar la opción "No comprobar la compatibilidad de la JVM" en "Config. Global/Específica de instancia → Configuración avanzada". +launch.advice.uncorrected=Si todavía quieres utilizar la versión de Java que elijas, puedes desactivar la opción "No comprobar la compatibilidad de la JVM" en "Config. Global/Específica de instancia → Configuración avanzada". launch.advice.different_platform=Se recomienda la versión de 64 bits de Java para tu dispositivo, pero has instalado una de 32 bits. -launch.advice.forge2760_liteloader=La versión 2760 o superior de Forge no es compatible con LiteLoader, por favor considera actualizar Forge a la versión 2773 o superior. -launch.advice.forge28_2_2_optifine=La versión 28.2.2 de Forge o posterior no es compatible con OptiFine. Considere la posibilidad de degradar la versión de Forge a la 28.2.1 o inferior. -launch.advice.forge37_0_60=Las versiones de Forge anteriores a la 37.0.60 no son compatibles con Java 17. Por favor, actualiza Forge a la versión 37.0.60 o superior, o inicia el juego con Java 16. -launch.advice.java8_1_13=Minecraft 1.13 y posteriores sólo pueden ejecutarse con Java 8 o posterior. Por favor, utiliza Java 8 o versiones más recientes. +launch.advice.forge2760_liteloader=La versión 2760 o posterior de Forge no es compatible con LiteLoader, por favor considere actualizar Forge a la versión 2773 o posterior. +launch.advice.forge28_2_2_optifine=La versión 28.2.2 o posterior de Forge no es compatible con OptiFine. Considere la posibilidad de actualizar Forge a la versión 28.2.1 o anterior. +launch.advice.forge37_0_60=Las versiones de Forge anteriores a la 37.0.60 no son compatibles con Java 17. Por favor, actualiza Forge a la versión 37.0.60 o posterior, o inicia el juego con Java 16. +launch.advice.java8_1_13=Minecraft 1.13 y posteriores sólo se pueden ejecutar en Java 8 o posterior. Por favor, utilice Java 8 o versiones posteriores. launch.advice.java8_51_1_13=Minecraft 1.13 puede fallar en versiones de Java 8 anteriores a la 1.8.0_51. Por favor, instala la última versión de Java 8. launch.advice.java9=No puedes ejecutar Minecraft 1.12 o anterior con Java 9 o más reciente, por favor, utiliza Java 8 en su lugar. -launch.advice.modlauncher8=La versión de Forge que estás utilizando no es compatible con la versión actual de Java. Por favor, intenta actualizar Forge, o ejecutar el juego con Java 8u312/11.0.13/17.0.1 o anterior. -launch.advice.newer_java=Se recomienda Java 8 para una experiencia de juego más fluida. Y para Minecraft 1.12 o superior, y la mayoría de los mods, es obligatorio. +launch.advice.modded_java=Algunos mods pueden no ser compatibles con las nuevas versiones de Java. Se recomienda utilizar Java %s para iniciar Minecraft %s. +launch.advice.modlauncher8=La versión de Forge que estás utilizando no es compatible con la versión actual de Java. Por favor, intenta actualizar Forge. +launch.advice.newer_java=Estás utilizando una versión antigua de Java para iniciar el juego. Se recomienda actualizar a Java 8, de lo contrario algunos mods pueden hacer que el juego se bloquee. launch.advice.not_enough_space=Has asignado un tamaño de memoria mayor que los %d MB reales de memoria instalados en tu máquina. Es posible que el rendimiento del juego se vea afectado, o incluso que no puedas iniciar el juego. +launch.advice.require_newer_java_version=La versión actual del juego requiere Java %s, pero no hemos podido encontrar uno. ¿Quieres descargar uno ahora? launch.advice.too_large_memory_for_32bit=Has asignado un tamaño de memoria mayor que la limitación de memoria de la instalación de Java de 32 bits. Es posible que no puedas iniciar el juego. -launch.advice.vanilla_linux_java_8=Minecraft 1.12.2 o inferior sólo admite Java 8 para la plataforma Linux x86-64, porque las versiones posteriores no pueden cargar las bibliotecas nativas de 32 bits como liblwjgl.so\n\ -\n\ -Por favor, descárguelo de java.com, o instale OpenJDK 8. -launch.advice.vanilla_x86=Minecraft actualmente no proporciona soporte oficial para arquitecturas distintas de x86 o x86-64.\n\ -Puedes intentar descargar la librería nativa de tu plataforma y especificar su ruta, luego vuelve a intentarlo. -launch.advice.vanilla_x86.translation=Minecraft no proporciona actualmente soporte oficial para arquitecturas distintas de x86 y x86-64.\n\ -Por favor, instale Java para x86-64 para jugar a Minecraft a través del entorno de traducción Rosetta, o descargue sus bibliotecas nativas correspondientes y especifique su ruta. +launch.advice.vanilla_linux_java_8=Minecraft 1.12.2 o inferior sólo admite Java 8 para la plataforma Linux x86-64, porque las versiones posteriores no pueden cargar las bibliotecas nativas de 32 bits como liblwjgl.so\n\nPor favor, descárguelo de java.com, o instale OpenJDK 8. +launch.advice.vanilla_x86.translation=Minecraft no proporciona actualmente soporte oficial para arquitecturas distintas de x86 y x86-64.\n\nPor favor, instale Java para x86-64 para jugar a Minecraft a través del entorno de traducción Rosetta, o descargue sus bibliotecas nativas correspondientes y especifique su ruta. +launch.advice.unknown=El juego no puede iniciarse por las siguientes razones: launch.failed=No se puede ejecutar launch.failed.cannot_create_jvm=No podemos crear una máquina virtual Java. Puede deberse a un problema con los argumentos de ejecución proporcionados, puede intentar solucionarlo eliminando todos los argumentos que haya añadido en la configuración de la instancia. -launch.failed.creating_process=No podemos crear un nuevo proceso, por favor compruebe la ruta de Java. +launch.failed.creating_process=No podemos crear un nuevo proceso, por favor compruebe la ruta de Java.\n launch.failed.command_too_long=La longitud del comando excede la longitud máxima de un script bat. Por favor, intente exportarlo como un script de PowerShell. -launch.failed.decompressing_natives=No se han podido descomprimir las bibliotecas nativas. +launch.failed.decompressing_natives=No se han podido descomprimir las bibliotecas nativas.\n launch.failed.download_library=No se pudo descargar la biblioteca %s. launch.failed.executable_permission=No se pudo hacer ejecutable el script de ejecución. launch.failed.execution_policy=Configuración de la política de ejecución launch.failed.execution_policy.failed_to_set=No se pudo establecer la política de ejecución -launch.failed.execution_policy.hint=La política de ejecución actual impide la ejecución de scripts de PowerShell.\n\ -\n\ -Haga clic en "Aceptar" para permitir que el usuario actual ejecute scripts de PowerShell, o haga clic en "Cancelar" para mantenerlo como está. +launch.failed.execution_policy.hint=La política de ejecución actual impide la ejecución de scripts de PowerShell.\n\nHaga clic en "Aceptar" para permitir que el usuario actual ejecute scripts de PowerShell, o haga clic en "Cancelar" para mantenerlo como está. launch.failed.exited_abnormally=El juego se ha bloqueado. Por favor, consulte el registro de errores para más detalles. +launch.failed.java_version_too_low=La versión de Java que ha especificado es demasiado baja. Por favor, reajuste la versión de Java. launch.failed.no_accepted_java=No se ha podido encontrar una versión de Java compatible. Si crees que has descargado una compatible, puedes establecerla manualmente en los ajustes. +launch.failed.sigkill=El juego fue terminado a la fuerza por el usuario o el sistema. launch.state.dependencies=Resolviendo dependencias launch.state.done=Completando el inicio launch.state.java=Comprobando la versión de Java launch.state.logging_in=Iniciando sesión launch.state.modpack=Descargando dependencias launch.state.waiting_launching=Esperando la ejecución del juego +launch.invalid_java=Ruta Java inválida. Por favor, restablezca la ruta de Java. launcher=Launcher launcher.agreement=Términos de servicio y EULA @@ -648,18 +737,20 @@ launcher.agreement.hint=Debe aceptar el EULA para utilizar este software. launcher.background=Imagen de fondo launcher.background.choose=Elige una imagen de fondo launcher.background.classic=Clásico -launcher.background.default=Por defecto (o background.png/jpg/gif, o imágenes en la carpeta bg) +launcher.background.default=Por defecto +launcher.background.default.tooltip=O "background.png/.jpg/.gif/.webp", o imágenes en la carpeta "bg" launcher.background.network=Desde la URL launcher.background.translucent=Translúcido launcher.cache_directory=Directorio de la caché launcher.cache_directory.clean=Borrar caché launcher.cache_directory.choose=Elegir el directorio de la caché -launcher.cache_directory.default=Por defecto (%AppData%/.minecraft o ~/.minecraft) +launcher.cache_directory.default=Por defecto ("%APPDATA%/.minecraft" o "~/.minecraft") launcher.cache_directory.disabled=Desactivado launcher.cache_directory.invalid=No se ha podido crear el directorio de la caché, volviendo a los valores por defecto. launcher.contact=Contacta con nosotros -launcher.crash=¡Hello Minecraft! Launcher ha encontrado un error fatal. Por favor, copie el siguiente registro y pida ayuda en nuestra comunidad en Discord, GitHub o Minecraft Forums. -launcher.crash.hmcl_out_dated=¡Hello Minecraft! Launcher ha encontrado un error fatal. Su launcher está desactualizado. Por favor, ¡actualícelo!. +launcher.crash=Hello Minecraft! Launcher ha encontrado un error fatal. Por favor, copie el siguiente registro y pida ayuda en nuestra comunidad en Discord, GitHub o Minecraft Forums. +launcher.crash.java_internal_error=Hello Minecraft! Launcher ha encontrado un error fatal porque su Java está dañado. Por favor, desinstala tu Java y descarga un Java adecuado aquí. +launcher.crash.hmcl_out_dated=Hello Minecraft! Launcher ha encontrado un error fatal. Su launcher está desactualizado. Por favor, ¡actualícelo! launcher.update_java=Por favor, actualice su versión de Java. login.empty_username=¡Todavía no has puesto tu nombre de usuario! @@ -672,14 +763,13 @@ logwindow.help=Puede ir a la comunidad HMCL y encontrar a otros para ayudar logwindow.autoscroll=Desplazamiento automático logwindow.export_game_crash_logs=Exportar registros de errores logwindow.export_dump=Exportar volcado de pila de juegos -logwindow.export_dump.no_dependency=Su Java no contiene las dependencias para crear el volcado de pila. Dirígete a HMCL QQ o HMCL Discord para obtener ayuda. - +logwindow.export_dump.no_dependency=Su Java no contiene las dependencias para crear el volcado de pila. Dirígete a nuestro Discord o grupo QQ para obtener ayuda. main_page=Inicio message.cancelled=La operación se ha cancelado message.confirm=Confirmar -message.copied=Copiado en el portapapeles +message.copied=Copiado al portapapeles message.default=Por defecto message.doing=Por favor, espere message.downloading=Descargando @@ -690,36 +780,38 @@ message.success=Operación completada con éxito message.unknown=Desconocido message.warning=Atención -modpack=Modpack -modpack.choose=Seleccionar un modpack +modpack=Modpacks +modpack.choose=Elija Modpack modpack.choose.local=Importar desde un archivo local modpack.choose.local.detail=Puede arrastrar y soltar el archivo modpack aquí modpack.choose.remote=Descargar desde Internet modpack.choose.remote.detail=Se requiere un enlace de descarga directa al archivo modpack remoto modpack.choose.remote.tooltip=Por favor, introduzca la URL de su modpack -modpack.completion=Descargar las dependencias +modpack.choose.repository=Descargar Modpack de CurseForge o Modrinth +modpack.choose.repository.detail=Recuerda volver a esta página y soltar aquí el archivo del modpack una vez descargado. +modpack.completion=Descargando dependencias modpack.desc=Describa su modpack, incluyendo una introducción y probablemente algún registro de cambios. Actualmente se admiten Markdown e imágenes desde URL. modpack.description=Descripción -modpack.download=Descargar el modpack +modpack.download=Descargar Modpacks modpack.enter_name=Introduzca un nombre para este modpack. modpack.export=Exportar modpack modpack.export.as=Exportar Modpack como... modpack.file_api=Prefijo de la URL del modpack modpack.files.blueprints=Plantillas de BuildCraft -modpack.files.config=Configuración de los Mods +modpack.files.config=Archivo de configuración del Mod modpack.files.dumps=Archivo de salida de depuración NEI modpack.files.hmclversion_cfg=Archivo de configuración del launcher -modpack.files.liteconfig=Archivo de configuración del Mod +modpack.files.liteconfig=Archivos relacionados con LiteLoader modpack.files.mods=Mods modpack.files.mods.voxelmods=Opciones de VoxelMods modpack.files.options_txt=Archivo de configuración de Minecraft modpack.files.optionsshaders_txt=Archivo de configuración de shaders -modpack.files.resourcepacks=Resource packs/texturas -modpack.files.saves=Juegos guardados +modpack.files.resourcepacks=Paquetes de recursos/texturas +modpack.files.saves=Mundos de juego modpack.files.scripts=Archivo de configuración de MineTweaker modpack.files.servers_dat=Archivo de lista de servidores modpack.install=Instalar Modpack %s -modpack.installing=Instalar Modpack +modpack.installing=Instalando Modpack modpack.introduction=Actualmente se soportan los modpacks CurseForge, Modrinth, MultiMC y MCBBS. modpack.invalid=Modpack inválido, puede intentar volver a descargarlo. modpack.mismatched_type=Tipo de modpack erróneo, la instancia actual es del tipo %s, pero la proporcionada es del tipo %s. @@ -731,119 +823,214 @@ modpack.origin.mcbbs=MCBBS modpack.origin.mcbbs.prompt=ID de publicación modpack.scan=Análisis del índice de modpacks modpack.task.install=Instalar Modpack -modpack.task.install.error=No se ha podido identificar este modpack. Actualmente sólo soportamos los modpacks Curse, Modrinth, MultiMC y MCBBS. -modpack.task.install.will=Vas a instalar el modpack: +modpack.task.install.error=No se ha podido identificar este modpack. Actualmente sólo soportamos los modpacks Curse, Modrinth, MultiMC y MCBBS. modpack.type.curse=Curse -modpack.type.curse.tolerable_error=No se han podido descargar las dependencias, puedes intentar continuar la descarga ejecutando esta instancia del juego. modpack.type.curse.error=No se han podido descargar las dependencias de CurseForge, inténtalo de nuevo o utiliza una conexión proxy. modpack.type.curse.not_found=Algunas dependencias ya no están disponibles, por favor intenta instalar una versión más reciente del modpack. modpack.type.manual.warning=Este archivo contiene una copia completa de una instancia de Minecraft. La mayoría de las veces, sólo tienes que descomprimirlo y ejecutar el juego usando su launcher incorporado. Pero, HMCL todavía puede importarlo, sin garantía de su utilidad, ¿aún continúa? modpack.type.mcbbs=Tipo MCBBS -modpack.type.mcbbs.export=Puede ser importado por ¡Hello Minecraft! Launcher +modpack.type.mcbbs.export=Puede ser importado por Hello Minecraft! Launcher modpack.type.modrinth=Modrinth modpack.type.multimc=MultiMC -¡modpack.type.multimc.export=Puede ser importado por Hello Minecraft! Launcher y MultiMC +modpack.type.multimc.export=Puede ser importado por Hello Minecraft! Launcher y MultiMC modpack.type.server=Actualización automática del modpack desde el servidor modpack.type.server.export=Permite al propietario del servidor actualizar la instancia del juego de forma remota -modpack.type.server.malformed=Manifiesto de modpack inválido, por favor, consulte al creador del modpack del servidor para solucionar este problema. +modpack.type.server.malformed=Manifiesto de modpack inválido. Por favor, ponte en contacto con el creador del modpack para resolver este problema. modpack.unsupported=Hello Minecraft! Launcher no admite el formato del paquete de integración -modpack.update=Actualización del modpack +modpack.update=Actualizando modpack modpack.wizard=Asistente de exportación de modpack modpack.wizard.step.1=Configuración básica modpack.wizard.step.1.title=Algunos ajustes básicos para el modpack. -modpack.wizard.step.2=Seleccionar archivos -modpack.wizard.step.2.title=Seleccione los archivos que desea añadir al modpack. +modpack.wizard.step.2=Elegir archivos +modpack.wizard.step.2.title=Elige los archivos que quieras añadir al modpack. modpack.wizard.step.3=Tipo de modpack modpack.wizard.step.3.title=Elija el tipo de modpack que desea exportar. modpack.wizard.step.initialization.exported_version=Versión del juego a exportar modpack.wizard.step.initialization.force_update=Forzar la actualización del modpack a la última versión (necesitarás un servicio de alojamiento de archivos) modpack.wizard.step.initialization.include_launcher=Incluir el launcher modpack.wizard.step.initialization.save=Exportar a... -modpack.wizard.step.initialization.warning=Antes de crear un modpack, por favor asegúrate de que el juego se ejecuta, y que es una versión de lanzamiento en lugar de una versión instantánea. El launcher guardará su configuración de descarga. -\n\ -Ten en cuenta que no se te permite añadir mods y resource packs que se digan explícitamente que no se pueden distribuir o poner en un modpack. +modpack.wizard.step.initialization.warning=Antes de hacer un modpack, por favor asegúrate de que el juego puede ser lanzado normalmente y Minecraft es una versión de lanzamiento en lugar de una snapshot. El launcher guardará tu configuración de descarga.\n\ + \n\ + Ten en cuenta que no se te permite añadir mods y paquetes de recursos que se digan explícitamente que no se pueden distribuir o poner en un modpack. modpack.wizard.step.initialization.server=Haga clic aquí para ver más tutoriales para hacer un modpack de servidor que se pueda actualizar automáticamente. modrinth.category.adventure=Aventura +modrinth.category.audio=Audio +modrinth.category.blocks=Bloqueos +modrinth.category.bukkit=Bukkit +modrinth.category.bungeecord=BungeeCord modrinth.category.challenging=Desafío +modrinth.category.core-shaders=Sombreadores de núcleo modrinth.category.combat=Combate modrinth.category.cursed=Maldición modrinth.category.decoration=Decoración +modrinth.category.economy=Economía +modrinth.category.entities=Entidades +modrinth.category.environment=Medio ambiente modrinth.category.equipment=Equipo modrinth.category.fabric=Fabric +modrinth.category.fonts=Fonts modrinth.category.food=Alimentos modrinth.category.forge=Forge -modrinth.category.kitchen-sink=Cocina +modrinth.category.game-mechanics=Mecánica de juego +modrinth.category.gui=GUI +modrinth.category.items=Objetos +modrinth.category.kitchen-sink=Fregadero de cocina modrinth.category.library=Biblioteca -modrinth.category.lightweight=Ligero +modrinth.category.lightweight=Peso ligero +modrinth.category.liteloader=LiteLoader +modrinth.category.locale=Locale modrinth.category.magic=Magia +modrinth.category.management=Gestión +modrinth.category.minecraft=Minecraft +modrinth.category.minigame=Minijuego modrinth.category.misc=Misceláneo -modrinth.category.modloader=Modloader +modrinth.category.mobs=Creaturas +modrinth.category.modded=Modificado +modrinth.category.models=Modelos +modrinth.category.modloader=Cargador de mods modrinth.category.multiplayer=Multijugador +modrinth.category.neoforge=NeoForge modrinth.category.optimization=Optimización -modrinth.category.quests=Quests +modrinth.category.paper=Paper +modrinth.category.purpur=Purpur +modrinth.category.quests=Misiones modrinth.category.quilt=Quilt +modrinth.category.realistic=Realista modrinth.category.rift=Rift +modrinth.category.simplistic=Simplista +modrinth.category.social=Social +modrinth.category.spigot=Spigot +modrinth.category.sponge=Sponge modrinth.category.storage=Almacenamiento modrinth.category.technology=Tecnología +modrinth.category.themed=Temática +modrinth.category.transportation=Transporte +modrinth.category.tweaks=Ajustes modrinth.category.utility=Utilidad +modrinth.category.vanilla-like=Similar a la vainilla +modrinth.category.velocity=Velocity +modrinth.category.waterfall=Waterfall modrinth.category.worldgen=Generación de mundos -modrinth.category.datapack=Datapack +modrinth.category.datapack=Paquete de datos modrinth.category.folia=Folia +modrinth.category.8x-=8x- +modrinth.category.16x=16x +modrinth.category.32x=32x +modrinth.category.48x=48x +modrinth.category.64x=64x +modrinth.category.128x=128x +modrinth.category.256x=256x +modrinth.category.512x+=512x+ mods=Mods mods.add=Añadir mods -mods.add.failed=Fallo en la instalación del mod %s. -mods.add.success=%s fue instalado con éxito. +mods.add.failed=No se ha podido añadir el mod %s. +mods.add.success=%s se ha añadido correctamente. +mods.broken_dependency.title=Dependencia rota +mods.broken_dependency.desc=Esta dependencia existía antes, pero ahora no existe. Intente utilizar otra fuente de descarga. mods.category=Categoría +mods.channel.alpha=Alpha +mods.channel.beta=Beta +mods.channel.release=Release mods.check_updates=Comprobar actualizaciones mods.check_updates.current_version=Versión actual -mods.check_updates.failed=No se han podido descargar algunos de los archivos. +mods.check_updates.empty=Todos los mods están actualizados +mods.check_updates.failed_check=No se ha podido comprobar si hay actualizaciones. +mods.check_updates.failed_download=No se han podido descargar algunos de los archivos. mods.check_updates.file=Archivo mods.check_updates.source=Fuente mods.check_updates.target_version=Versión de destino mods.check_updates.update=Actualización mods.choose_mod=Elige un mod mods.curseforge=CurseForge +mods.dependency.embedded=Dependencias incorporadas (Already packaged in the mod file by the author. No need to download separately) +mods.dependency.optional=Dependencias opcionales (If missing, the game will run normally, but the mod features may be missing) +mods.dependency.required=Dependencias necesarias (Must be downloaded separately. Missing may cause the game to fail to launch) +mods.dependency.tool=Dependencias necesarias (Must be downloaded separately. Missing may cause the game to fail to launch) +mods.dependency.include=Dependencias incorporadas (Already packaged in the mod file by the author, no need to download separately) +mods.dependency.incompatible=Mods incompatibles (Installing these mods at the same time will cause the game to fail to launch) +mods.dependency.broken=Dependencias rotas (This mod existed before, but it does not exist now. Try using another download source.) mods.disable=Desactivar -mods.download=Descarga de mods -mods.download.title=Descarga de mods - %1s -mods.enable=Habilitar -mods.manage=Administrar Mods +mods.download=Descargar mod +mods.download.title=Descargar mod - %1s +mods.download.recommend=Versión recomendada - Minecraft %1s +mods.enable=Activar +mods.manage=Mods mods.mcbbs=MCBBS -mods.mcmod=MCMOD -mods.mcmod.page=Página de MCMOD -mods.mcmod.search=Búsqueda en MCMOD +mods.mcmod=MCMod +mods.mcmod.page=Página de MCMod +mods.mcmod.search=Búsqueda en MCMod mods.modrinth=Modrinth mods.name=Nombre -mods.not_modded=¡Debes instalar primero un cargador de mods (Fabric, Forge o LiteLoader) para gestionar tus mods! +mods.not_modded=¡Debes instalar primero un cargador de mods (Forge, NeoForge, Fabric, Quilt o LiteLoader) para gestionar tus mods! mods.restore=Restaurar mods.url=Página oficial mods.update_modpack_mod.warning=Actualizar mods en un modpack puede generar resultados irreparables, posiblemente corrompiendo el modpack para que no pueda iniciarse. ¿Seguro que quieres actualizar? +mods.install=Instalar +mods.save_as=Guardar como + +nbt.entries=%s entradas +nbt.open.failed=No se ha podido abrir el archivo +nbt.save.failed=No se ha podido guardar el archivo +nbt.title=Ver archivo - %s -datapack=Datapacks -datapack.add=Instalar datapacks -datapack.choose_datapack=Seleccionar un paquet de datos para importar -datapack.extension=Datapacks -datapack.title=Mundos %s - Datapacks +datapack=Paquetes de datos +datapack.add=Instalar paquetes de datos +datapack.choose_datapack=Elija el paquete de datos que desea importar +datapack.extension=Paquete de datos +datapack.title=Mundo [%s] - Paquetes de datos + +web.failed=No se ha podido cargar la página +web.open_in_browser=Desea abrir esta dirección en un navegador:\n%s +web.view_in_browser=Ver en navegador world=Mundos world.add=Añadir un mundo (.zip) -world.datapack=Gestionar datapacks -world.datapack.1_13=Sólo Minecraft 1.13 o superior soporta datapacks. -world.description=%s. Jugado por última vez en %s. Versión del juego: %s. -world.download=Descargar un mundo -world.export=Exportar un mundo -world.export.title=Selecciona un directorio para este mundo exportado -world.export.location=Exportar a +world.datapack=Gestionar paquetes de datos +world.datapack.1_13=Sólo Minecraft 1.13 o posterior soporta paquetes de datos. +world.description=%s | Jugado por última vez en %s | Versión del juego: %s +world.download=Descargar Mundo +world.export=Exportar el mundo +world.export.title=Elija el directorio para este mundo exportado +world.export.location=Guardar como world.export.wizard=Exportar Mundo %s world.extension=Archivo del mundo world.game_version=Versión del juego world.import.already_exists=Este mundo ya existe. -world.import.choose=Selecciona el archivo de guardado que quieres importar +world.import.choose=Elija el archivo de mundo que desea importar world.import.failed=No se ha podido importar este mundo: %s -world.import.invalid=No se ha podido analizar el archivo guardado. -world.manage=Mundos / Datapacks +world.import.invalid=No se ha podido analizar el mundo. +world.info.title=Mundo [%s] - Información +world.info.basic=Información básica +world.info.allow_cheats=Permitir comandos/trucos +world.info.dimension.the_nether=El Nether +world.info.dimension.the_end=El End +world.info.difficulty=Dificultad +world.info.difficulty.peaceful=Pacífico +world.info.difficulty.easy=Fácil +world.info.difficulty.normal=Normal +world.info.difficulty.hard=Difícil +world.info.failed=No se ha podido leer la información del mundo +world.info.game_version=Versión del juego +world.info.last_played=Jugado por última vez +world.info.generate_features=Generar estructuras +world.info.player=Información del jugador +world.info.player.food_level=Nivel de hambre +world.info.player.game_type=Modo de juego +world.info.player.game_type.adventure=Aventura +world.info.player.game_type.creative=Creativo +world.info.player.game_type.spectator=Espectador +world.info.player.game_type.survival=Supervivencia +world.info.player.health=Salud +world.info.player.last_death_location=Lugar de la última muerte +world.info.player.location=Ubicación +world.info.player.spawn=Ubicación de desove +world.info.player.xp_level=Nivel de experiencia +world.info.random_seed=Semilla +world.info.time=Tiempo de juego +world.info.time.format=%s días +world.manage=Mundos world.name=Nombre del mundo world.name.enter=Introducir el nombre del mundo world.reveal=Abrir en el Explorador @@ -853,9 +1040,9 @@ world.time=EEE, MMM d, yyyy HH\:mm\:ss profile=Directorios del juego profile.already_exists=Este nombre ya existe, por favor, utilice un nombre diferente. profile.default=Directorio actual -profile.home=Por defecto -profile.instance_directory=Directorio de la instancia -profile.instance_directory.choose=Seleccionar directorio de instancia +profile.home=Minecraft Launcher +profile.instance_directory=Directorio ded juego +profile.instance_directory.choose=Elegir directorio de juegos profile.manage=Lista de directorios de instancia profile.name=Nombre profile.new=Nuevo directorio @@ -867,82 +1054,97 @@ repositories.custom=Repositorio Maven personalizado (%s) repositories.maven_central=Universal (Maven Central) repositories.tencentcloud_mirror=Espejo de China continental (Repositorio Maven Tencent Cloud) repositories.chooser=HMCL requiere JavaFX para funcionar.\n\ -\n\ -Por favor, haga clic en "Aceptar" para descargar JavaFX desde el repositorio especificado, o haga clic en "Cancelar" para salir.\n\ -\n\ -Repositorios\: -repositories.chooser.title=Seleccione un espejo del que descargar JavaFX + \n\ + Por favor, haga clic en "Aceptar" para descargar JavaFX desde el repositorio especificado, o haga clic en "Cancelar" para salir.\n\ + \n\ + Repositorios: +repositories.chooser.title=Elija la fuente de descarga de JavaFX -resourcepack=Resource packs +resourcepack=Paquetes de recursos search=Búsqueda -search.hint.chinese=Las consultas de búsqueda admiten chino e inglés -search.hint.english=Sólo se admite el inglés -search.enter=Вводите здесь +search.hint.chinese=Buscar en inglés y chino +search.hint.english=Buscar sólo en inglés +search.enter=Introduzca aquí el texto search.sort=Ordenar por +search.first_page=Primera +search.previous_page=Previo +search.next_page=Siguiente +search.last_page=Last +search.page_n=%d / %s selector.choose=Elegir -selector.choose_file=Seleccionar un archivo +selector.choose_file=Elegir archivo selector.custom=Personalizar settings=Configuración settings.advanced=Configuración avanzada +settings.advanced.modify=Editar configuración avanzada settings.advanced.title=Configuración avanzada - %s settings.advanced.custom_commands=Comandos personalizados -settings.advanced.custom_commands.hint=Se proporcionan las siguientes variables de entorno\:\n\ - - $INST_NAME: nombre de la versión\n\ - - $INST_ID: id de la versión\n\ - - $INST_DIR: ruta absoluta de la versión\n\ - - $INST_MC_DIR: ruta absoluta de minecraft\n\ - - $INST_JAVA: binario de java utilizado para la ejecución\n\ - - $INST_FORGE: se establece si Forge está instalado\n\ - - $INST_NEOFORGE: se establece si NeoForge está instalado\n\ - - $INST_LITELOADER: se establece si LiteLoader está instalado\n\ - - $INST_OPTIFINE: establecer si se ha instalado OptiFine\n\ - - $INST_FABRIC: establecer si está instalado Fabric\n\ - - $INST_QUILT: establecer si está instalado Quilt +settings.advanced.custom_commands.hint=Se proporcionan las siguientes variables de entorno:\n\ + \ · $INST_NAME: nombre de la instancia.\n\ + \ · $INST_ID: nombre de la instancia.\n\ + \ · $INST_DIR: ruta absoluta del directorio de trabajo de la instancia.\n\ + \ · $INST_MC_DIR: ruta absoluta del directorio del juego.\n\ + \ · $INST_JAVA: binario de java utilizado para la ejecución\n\ + \ · $INST_FORGE: disponible si Forge está instalado.\n\ + \ · $INST_NEOFORGE: disponible si NeoForge está instalado.\n\ + \ · $INST_LITELOADER: disponible si LiteLoader está instalado.\n\ + \ · $INST_OPTIFINE: disponible si OptiFine está instalado.\n\ + \ · $INST_FABRIC: disponible si Fabric está instalado.\n\ + \ · $INST_QUILT: disponible si Quilt está instalado. settings.advanced.dont_check_game_completeness=No chequear integridad del juego settings.advanced.dont_check_jvm_validity=No comprobar la compatibilidad de la JVM -settings.advanced.game_dir.default=Por defecto (.minecraft/) -settings.advanced.game_dir.independent=Aislado (.minecraft/versions//, excepto para los activos y las bibliotecas) +settings.advanced.dont_patch_natives=No intente sustituir automáticamente las bibliotecas nativas +settings.advanced.environment_variables=Variables de entorno +settings.advanced.game_dir.default=Por defecto (".minecraft/") +settings.advanced.game_dir.independent=Aislar (".minecraft/versions//", excepto para los activos y las bibliotecas) settings.advanced.java_permanent_generation_space=Espacio PermGen settings.advanced.java_permanent_generation_space.prompt=en MB settings.advanced.jvm=Opciones de la máquina virtual de Java settings.advanced.jvm_args=Argumentos de la máquina virtual de Java -settings.advanced.jvm_args.prompt=Introduzca aquí para anular las opciones predeterminadas +settings.advanced.jvm_args.prompt=\ · Si los argumentos introducidos en "Argumentos de la máquina virtual de Java" son los mismos que los argumentos por defecto, no se añadirán.\n\ + \ · Introduzca cualquier argumento GC en "Argumentos de la máquina virtual de Java", y se desactivará el argumento G1 de los argumentos por defecto.\n\ + \ · Activa "No añadir argumentos por defecto de JVM" para ejecutar el juego sin añadir argumentos por defecto. settings.advanced.launcher_visibility.close=Cerrar el launcher después de ejecutar el juego. settings.advanced.launcher_visibility.hide=Ocultar el launcher después de ejecutar el juego. settings.advanced.launcher_visibility.hide_and_reopen=Ocultar el launcher y volver a abrirlo cuando se cierra el juego. settings.advanced.launcher_visibility.keep=Mantener visible el launcher. settings.advanced.launcher_visible=Visibilidad del launcher -settings.advanced.minecraft_arguments=Argumentos del launcher +settings.advanced.minecraft_arguments=Argumentos de lanzamiento settings.advanced.minecraft_arguments.prompt=Por defecto settings.advanced.natives_directory=Ruta de la biblioteca nativa -settings.advanced.natives_directory.choose=Selecciona dónde se encuentra tu biblioteca nativa deseada +settings.advanced.natives_directory.choose=Elija la ubicación de la biblioteca nativa deseada settings.advanced.natives_directory.custom=Personalizado settings.advanced.natives_directory.default=Por defecto settings.advanced.natives_directory.hint=Esta opción está pensada sólo para usuarios de Apple M1 u otras plataformas no soportadas oficialmente. Por favor, no modifique esta opción a menos que sepa lo que está haciendo.\n\ -\n\ -Antes de proceder, por favor, asegúrese de que todas las bibliotecas (por ejemplo, lwjgl.dll, libopenal.so) se proporcionan en su directorio deseado. -settings.advanced.no_jvm_args=No añadir argumentos JVM por defecto -settings.advanced.precall_command=Comandos de preejecución -settings.advanced.precall_command.prompt=Comandos a ejecutar antes del inicio del juego + \n\ + Antes de proceder, por favor, asegúrese de que todas las bibliotecas (por ejemplo, lwjgl.dll, libopenal.so) se proporcionan en su directorio deseado. +settings.advanced.no_jvm_args=No añadir argumentos por defecto de JVM +settings.advanced.precall_command=Comando pre-lanzamiento: +settings.advanced.precall_command.prompt=El comando se ejecuta antes de que los juegos se lance settings.advanced.process_priority=Prioridad del proceso settings.advanced.process_priority.low=Bajo -settings.advanced.process_priority.below_normal=Bajo normal +settings.advanced.process_priority.below_normal=Por debajo de lo normal settings.advanced.process_priority.normal=Normal -settings.advanced.process_priority.above_normal=Superior a lo normal +settings.advanced.process_priority.above_normal=Por encima de lo normal settings.advanced.process_priority.high=Alta settings.advanced.post_exit_command=Comando post-salida -settings.advanced.post_exit_command.prompt=Comandos a ejecutar tras la salida del juego +settings.advanced.post_exit_command.prompt=El comando se ejecuta después de que el juego se detenga +settings.advanced.renderer=Renderizador +settings.advanced.renderer.default=OpenGL (Por defecto) +settings.advanced.renderer.d3d12=DirectX 12 (Rendimiento y compatibilidad deficientes) +settings.advanced.renderer.llvmpipe=Software (Bajo rendimiento, máxima compatibilidad) +settings.advanced.renderer.zink=Vulkan (Máximo rendimiento, baja compatibilidad) settings.advanced.server_ip=Dirección del servidor settings.advanced.server_ip.prompt=Entrar automáticamente después de ejecutar el juego -settings.advanced.use_native_glfw=[Sólo Linux] Utilizar GLFW nativo -settings.advanced.use_native_openal=[Sólo Linux] Utilizar OpenAL nativo +settings.advanced.use_native_glfw=[Sólo Linux/FreeBSD] Utilizar GLFW nativo +settings.advanced.use_native_openal=[Sólo Linux/FreeBSD] Utilizar OpenAL nativo settings.advanced.workaround=Métodos alternativos settings.advanced.workaround.warning=Éstas opciones están pensadas sólo para usuarios expertos. Jugar con estas opciones puede romper el juego. A menos que sepas lo que estás haciendo, por favor no modifiques estas opciones. -settings.advanced.wrapper_launcher=Wrapper Launcher +settings.advanced.wrapper_launcher=Comando Wrapper settings.advanced.wrapper_launcher.prompt=Permite ejecutar el juego usando un programa extra como 'optirun' en Linux. settings.custom=Personalizado @@ -952,19 +1154,20 @@ settings.game.current=Juego settings.game.dimension=Resolución settings.game.exploration=Explorar settings.game.fullscreen=Pantalla completa -settings.game.java_directory=Ruta de Java -settings.game.java_directory.auto=Seleccionar automáticamente +settings.game.java_directory=Java +settings.game.java_directory.auto=Elegir automáticamente settings.game.java_directory.auto.not_found=No se ha instalado ninguna versión de Java adecuada. settings.game.java_directory.bit=%s bit -settings.game.java_directory.choose=Seleccione la ruta de Java. +settings.game.java_directory.choose=Elija Java settings.game.java_directory.invalid=Ruta de Java incorrecta. settings.game.java_directory.template=%s (%s) +settings.game.java_directory.version=Especifique la versión de Java settings.game.management=Gestionar settings.game.working_directory=Directorio de trabajo -settings.game.working_directory.choose=Seleccionar directorio de trabajo -settings.game.working_directory.hint=Activar la opción 'Aislado' en 'Directorio de trabajo' para permitir que la instancia actual almacene sus configuraciones, guardados y mods en un directorio separado.\n\ -\n\ -Se recomienda activar esta opción para evitar conflictos con los mods, pero tendrás que mover tus guardados manualmente. +settings.game.working_directory.choose=Elija el directorio de trabajo +settings.game.working_directory.hint=Activar la opción "Aislar" en "Directorio de trabajo" para permitir que la instancia actual almacene sus configuraciones, mundos y mods en un directorio separado.\n\ + \n\ + Se recomienda activar esta opción para evitar conflictos con los mods, pero tendrás que mover tus guardados manualmente. settings.icon=Icono @@ -984,7 +1187,8 @@ settings.launcher.general=General settings.launcher.language=Idioma settings.launcher.launcher_log.export=Exportar registros del launcher settings.launcher.launcher_log.export.failed=No se han podido exportar los registros -settings.launcher.launcher_log.export.success=Los registros se han exportado a %s +settings.launcher.launcher_log.export.success=Los registros se han exportado a "%s" +settings.launcher.launcher_log.reveal=Revelar registros en el Explorador settings.launcher.log=Registro settings.launcher.log.font=Fuente settings.launcher.proxy=Proxy @@ -995,54 +1199,55 @@ settings.launcher.proxy.http=HTTP settings.launcher.proxy.none=Sin proxy settings.launcher.proxy.password=Contraseña settings.launcher.proxy.port=Puerto -settings.launcher.proxy.socks=SOCKS +settings.launcher.proxy.socks=Socks settings.launcher.proxy.username=Nombre de usuario settings.launcher.theme=Tema settings.launcher.title_transparent=Barra de título transparente +settings.launcher.turn_off_animations=Desactivar animación (Se aplica después de reiniciar) settings.launcher.version_list_source=Lista de versiones settings.memory=Memoria -settings.memory.allocate.auto=%1$.1f GB mínimo / %2$.1f GB asignado +settings.memory.allocate.auto=%1$.1f GB Mínimo / %2$.1f GB Asignado settings.memory.allocate.auto.exceeded=%1$.1f GB Mínimo / %2$.1f GB Asignado (%3$.1f GB Disponible) settings.memory.allocate.manual=%1$.1f GB Asignados settings.memory.allocate.manual.exceeded=%1$.1f GB Asignados (%3$.1f GB Disponibles) settings.memory.auto_allocate=Asignar automáticamente settings.memory.lower_bound=Memoria mínima -settings.memory.used_per_total=%1$.1f GB utilizados / %2$.1f GB totales +settings.memory.used_per_total=%1$.1f GB Utilizados / %2$.1f GB Totales settings.physical_memory=Tamaño de la memoria física settings.show_log=Mostrar registros settings.skin=Ahora se admiten los skins para las cuentas sin conexión. Puedes ir a la configuración de la cuenta para cambiar tu skin y tu capa, pero otros jugadores no podrán verlo en el multijugador. -settings.tabs.installers=Modloaders / OptiFine +settings.tabs.installers=Cargadores settings.take_effect_after_restart=Se aplica después de reiniciar settings.type=Tipo de configuración de la instancia settings.type.global=Config. Global (compartida) settings.type.global.manage=Config. Global -settings.type.global.edit=Editar ajustes globales +settings.type.global.edit=Editar Config. Global settings.type.special.enable=Habilitar ajustes personalizados para la instancia actual -settings.type.special.edit=Editar ajustes de la instancia actual +settings.type.special.edit=Editar configuración de la instancia actual settings.type.special.edit.hint=La instancia actual [%s] tiene activada la configuración por instancia, todas las opciones de esta página NO afectarán a esa instancia. Haga clic aquí para modificar sus propias opciones. sponsor=Donantes -sponsor.bmclapi=Las descargas son proporcionadas por BMCLAPI. Haz clic aquí para obtener más información. -sponsor.hmcl=¡Hello Minecraft! Launcher es un launcher FOSS de Minecraft que permite a los usuarios gestionar múltiples instancias de Minecraft fácilmente. Usted puede donar a nosotros en Afdian para apoyar nuestro alojamiento de archivos y el desarrollo del proyecto. Haga clic aquí para obtener más información. +sponsor.bmclapi=Las descargas de China continental son proporcionadas por BMCLAPI. Haga clic aquí para obtener más información. +sponsor.hmcl=Hello Minecraft! Launcher es un launcher FOSS de Minecraft que permite a los usuarios gestionar múltiples instancias de Minecraft fácilmente. Haga clic aquí para obtener más información. system.architecture=Arquitectura system.operating_system=Sistema operativo +unofficial.hint=Está utilizando una versión no oficial de HMCL. No podemos garantizar su seguridad. + update=Actualizar update.accept=Actualizar update.changelog=Registro de cambios update.channel.dev=Beta update.channel.dev.hint=Actualmente está utilizando una versión beta del launcher, que puede incluir algunas características adicionales, pero también es más inestable que las versiones de lanzamiento.\n\ -\n\ -Si encuentra algún error o problema, puede ir a la página de comentarios para informar de ello, o bien para decírnoslo en nuestro Discord o en la comunidad QQ.\n\ -\n\ -Haga clic aquí para ocultar el recordatorio para la versión de prueba actual. + \n\ + Si encuentra algún error o problema, puede ir a la página de comentarios para informar de ello. update.channel.dev.title=Aviso de versión beta update.channel.nightly=Nocturna update.channel.nightly.hint=Estás utilizando una versión nocturna del launcher, que puede incluir algunas características adicionales, pero también es siempre más inestable que las otras versiones.\n\ -\n\ -Si encuentra algún error o problema, puede ir a la página de comentarios para informar de ello, o bien para decírnoslo en nuestro Discord o en la comunidad QQ community. + \n\ + Si encuentra algún error o problema, puede ir a la página de comentarios para informar de ello. update.channel.nightly.title=Aviso de versión nocturna update.channel.stable=Estable update.checking=Buscando actualizaciones @@ -1057,45 +1262,50 @@ update.no_browser=No se puede abrir en el navegador del sistema. Pero, hemos cop update.tooltip=Actualización version=Juegos -version.cannot_read=No se ha podido analizar la versión del juego, la instalación automática no puede continuar. +version.name=Nombre de instancia +version.cannot_read=No se ha podido analizar la instancia del juego, la instalación no puede continuar. version.empty=No hay instancias version.empty.add=Añadir una instancia -version.empty.launch=No hay instancias, puedes instalar una en la pestaña 'Descargar'. +version.empty.launch=No hay instancias. Puedes instalar una en "Descargar → Nuevo juego". version.empty.hint=No hay instancias de Minecraft aquí. Puedes intentar cambiar a otro directorio del juego o hacer clic aquí para descargar una. version.game.old=Histórico -version.game.release=Estable +version.game.release=Lanzamiento version.game.releases=Lanzamientos version.game.snapshot=Snapshot version.game.snapshots=Snapshots version.launch=Iniciar Minecraft version.launch.test=Probar instalación -version.switch=Cambiar versión +version.switch=Cambiar Instancia version.launch_script=Exportar script de ejecución version.launch_script.failed=No se ha podido exportar el script de ejecución. version.launch_script.save=Exportar script de ejecución version.launch_script.success=Exportado el script de ejecución como %s. version.manage=Todas las instancias -version.manage.clean=Borrar archivos de registro -version.manage.clean.tooltip=Eliminar registros e informes de errores. +version.manage.clean=Eliminar archivos de registros +version.manage.clean.tooltip=Eliminar los archivos de los directorios "logs" y "crash-reports". version.manage.duplicate=Duplicar instancia -version.manage.duplicate.duplicate_save=Guardar duplicado -version.manage.duplicate.prompt=Introducir el nombre de la instancia +version.manage.duplicate.duplicate_save=Duplicar mundos +version.manage.duplicate.prompt=Introduzca el nombre de la nueva instancia version.manage.duplicate.confirm=La instancia duplicada tendrá una copia de todos los archivos de esta instancia, con un directorio de juego y una configuración aislados. -version.manage.manage=Gestionar instancias -version.manage.manage.title=Gestionar instancia - %1s +version.manage.manage=Editar instancia +version.manage.manage.title=Editar instancia - %1s version.manage.redownload_assets_index=Actualizar activos del juego -version.manage.remove=Borrar instancia -version.manage.remove.confirm=¿Está seguro de que quiere eliminar permanentemente la versión %s? ¡Esta acción no se puede deshacer! -version.manage.remove.confirm.trash=¿Estás seguro de que quieres eliminar la versión %s? Todavía puedes encontrar sus archivos en tu papelera de reciclaje con el nombre de %s. +version.manage.remove=Eliminar instancia +version.manage.remove.confirm=¿Está seguro de que quiere eliminar permanentemente la instancia "%s"? ¡Esta operación no se puede deshacer! +version.manage.remove.confirm.trash=¿Estás seguro de que quieres eliminar la instancia "%s"? Todavía puedes encontrar sus archivos en tu papelera de reciclaje con el nombre de «%s». version.manage.remove.confirm.independent=Dado que esta instancia está almacenada en un directorio aislado, al eliminarla también se eliminarán sus guardados y otros datos. ¿Aún quieres borrar la instancia %s? version.manage.remove_assets=Borrar todas las activos del juego version.manage.remove_libraries=Borrar todas las bibliotecas version.manage.rename=Renombrar instancia -version.manage.rename.message=Por favor, introduzca el nuevo nombre de esta instancia +version.manage.rename.message=Introduzca el nombre de la nueva instancia version.manage.rename.fail=No se ha podido renombrar la instancia, algunos archivos pueden estar en uso o el nombre contiene un carácter no válido. version.settings=Configuración version.update=Actualizar Modpack +wiki.tooltip=Página de Minecraft Wiki +wiki.version.game.release=https://es.minecraft.wiki/w/Java_Edition_%s +wiki.version.game.snapshot=https://es.minecraft.wiki/w/Java_Edition_%s + wizard.prev=< Previo wizard.failed=Falló wizard.finish=Finalizar diff --git a/HMCL/src/main/resources/assets/lang/I18N_ja.properties b/HMCL/src/main/resources/assets/lang/I18N_ja.properties index 5aef7a0195..c21d74ee51 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ja.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ja.properties @@ -1,6 +1,6 @@ # # Hello Minecraft! Launcher -# Copyright (C) 2023 huangyuhui and contributors +# Copyright (C) 2025 huangyuhui 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 @@ -17,31 +17,36 @@ # # Contributors: zhixuan2333 -about=About + +about=について about.copyright=著作権 -about.copyright.statement=Copyright © 2024 huangyuhui。 -about.author=Author -about.author.statement=bilibili : @huanghongxun +about.copyright.statement=Copyright © 2025 huangyuhui +about.author=著者 +about.author.statement=bilibili @huanghongxun about.claim=EULA -about.claim.statement=テキスト全体のリンクをクリックします。 -about.dependency=Dependencies +about.claim.statement=全文はこのリンクをクリック +about.dependency=依存関係 about.legal=法的承認 -about.thanks_to=Thanks to -about.thanks_to.bangbang93.statement=BMCLAPIダウンロードソースプロバイダー。 -about.thanks_to.contributors=問題、プルリクエストなどを介してこのプロジェクトに参加したすべての貢献者。 -about.thanks_to.contributors.statement=Hello Minecraftをサポートしてくれたオープンソースコミュニティに感謝します!ランチャー -about.thanks_to.gamerteam.statement=デフォルトの背景画像プロバイダー。 -about.thanks_to.glavo.statement=HMCLに多くのテクニカルサポートを提供 +about.thanks_to=感謝します… +about.thanks_to.bangbang93.statement=BMCLAPIダウンロードミラープロバイダー。寄付をご検討ください! +about.thanks_to.burningtnt.statement=HMCLに多くのテクニカルサポートを提供 +about.thanks_to.contributors=GitHubのすべての貢献者 +about.thanks_to.contributors.statement=HMCLをサポートしてくれたオープンソースコミュニティに感謝する +about.thanks_to.gamerteam.statement=デフォルトの背景画像プロバイダー +about.thanks_to.glavo.statement=HMCLのメンテナ about.thanks_to.zekerzhayard.statement=HMCLに多くのテクニカルサポートを提供 -about.thanks_to.zkitefly.statement=HMCLに多くのテクニカルサポートを提供。 -about.thanks_to.mcmod=mcmod.cn -about.thanks_to.mcmod.statement=Mod中国語の名前翻訳プロバイダー。 -about.thanks_to.red_lnn.statement=デフォルトの背景画像プロバイダー。 +about.thanks_to.zkitefly.statement=HMCLドキュメントメンテナ +about.thanks_to.mcbbs=MCBBS (Minecraft中国語フォーラム) +about.thanks_to.mcbbs.statement=中国本土ユーザー向けのmcbbs.netダウンロードミラープロバイダー(もう利用できません) +about.thanks_to.mcmod=MCMod (mcmod.cn) +about.thanks_to.mcmod.statement=様々なMODの簡体字中国語名前翻訳とウィキを提供する +about.thanks_to.red_lnn.statement=デフォルトの背景画像プロバイダー +about.thanks_to.shulkersakura.statement=HMCLロゴプロバイダー about.thanks_to.users=HMCLユーザーグループのメンバー -about.thanks_to.users.statement=寄付、バグレポートなどに感謝します。 -about.thanks_to.yushijinhun.statement=authlib-インジェクターのサポート +about.thanks_to.users.statement=寄付、バグレポートなどに感謝します +about.thanks_to.yushijinhun.statement=authlib-injector関連のサポートを提供する about.open_source=オープンソース -about.open_source.statement=GPL v3(https://github.com/HMCL-dev/HMCL/) +about.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL) account=アカウント account.cape=マント @@ -128,13 +133,14 @@ archive.date=公開日 archive.file.name=名前 archive.version=バージョン -Assets.download=アセットのダウンロード -Assets.download_all=アセットの整合性チェック -Assets.index.malformed=アセットインデックスの形式が正しくありません。バージョン設定の[ゲームアセットファイルの更新]で再試行できます。 +assets.download=アセットのダウンロード +assets.download_all=アセットの整合性チェック +assets.index.malformed=アセットインデックスの形式が正しくありません。バージョン設定の[ゲームアセットファイルの更新]で再試行できます。 button.cancel=キャンセル button.change_source=ダウンロードソースの変更 button.clear=クリア +button.copy_and_exit=コピーして終了 button.delete=削除 button.edit=編集 button.install=インストール @@ -143,19 +149,23 @@ button.no=いいえ button.ok=OK button.refresh=更新 button.remove=削除 -button.remove.confirm=削除してもよろしいですか?この操作をロールバックすることはできません! +button.remove.confirm=本当に完全に削除しますか?この操作は元に戻せません! +button.retry=リトライ button.save=保存 button.save_as=名前を付けて保存 button.select_all=すべて選択 +button.view=読む button.yes=はい +chat=グループチャット + color.recent=推奨 color.custom=カスタムカラー -crack.NoClassDefFound=「HelloMinecraft!Launcher」ソフトウェアが破損していないことを確認してください。 -crack.user_fault=OSまたはJava環境が正しくインストールされていない可能性があり、クラッシュする可能性があります。Javaランタイム環境またはコンピュータを確認してください。 +crash.NoClassDefFound=このソフトウェアの整合性を確認するか、Javaの更新をお試しください。 +crash.user_fault=JavaまたはOSの環境が壊れているため、ランチャーがクラッシュしました。JavaまたはOSが正しくインストールされているかご確認ください。 -curse.category.0=すべて +curse.category.0=全て # https://addons-ecs.forgesvc.net/api/v2/category/section/4471 curse.category.4474=Sci-Fi @@ -313,9 +323,15 @@ 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=チャットに参加しよう! +feedback.discord.statement=Discordサーバーに参加してください! +feedback.github=GitHub Issues +feedback.github.statement=GitHubで問題を送信します。 +feedback.qq_group=HMCLユーザーQQグループ +feedback.qq_group.statement=ユーザーQQグループに参加してください! file=ファイル @@ -343,12 +359,9 @@ game.crash.reason.entity=エンティティが原因でゲームを実行でき game.crash.reason.fabric_version_0_12=Fabric 0.12以降は、現在インストールされているmodと互換性がありません。ファブリックを0.11.7にダウングレードする必要があります。 game.crash.reason.fabric_warnings=Fabricはいくつかの警告を出します:\n%1$s game.crash.reason.modmixin_failure=mod インジェクションの失敗により、現在のゲームを続行できません。\nこれは通常、MOD にバグがあるか、現在の環境と互換性がないことを意味します。\n間違った mod のログを確認できます。 -game.crash.reason.file_or_content_verification_failed=一部のファイルまたはコンテンツの検証に失敗したため、現在のゲームに問題があります。\nこのバージョン (mod を含む) を削除して再ダウンロードするか、再ダウンロード時にプロキシを使用するなどしてください。 game.crash.reason.mod_repeat_installation=現在のゲームには複数の同一の Mod が繰り返しインストールされており、各 Mod は 1 回しか表示できません。ゲームを開始する前に繰り返しの Mod を削除してください。 game.crash.reason.forge_error=Forge がエラー メッセージを表示した可能性があります。 \nログを表示し、エラー レポートのログ情報に従って対応するアクションを実行できます。 \nエラー メッセージが表示されない場合は、エラー レポートをチェックして、エラーがどのように発生したかを知ることができます。\n%1$s game.crash.reason.mod_resolution0=mod の問題により、現在のゲームを続行できません。 \n間違った mod のログを確認できます。 -game.crash.reason.mod_profile_causes_game_crash=mod プロファイルに問題があるため、現在のゲームを続行できません。 \n障害のある mod とその構成ファイルのログを確認できます。 -game.crash.reason.fabric_reports_an_error_and_gives_a_solution=Forge がエラー メッセージを表示した可能性があります。 \nログを表示し、エラー レポートのログ情報に従って対応するアクションを実行できます。 \nエラー メッセージが表示されない場合は、エラー レポートをチェックして、エラーがどのように発生したかを知ることができます。 game.crash.reason.java_version_is_too_high=Java のバージョンが高すぎて実行を継続できないため、現在のゲームがクラッシュしました。 \nゲームを起動する前に、グローバル ゲーム設定またはゲーム固有の設定の Java パス タブで Java の以前のバージョンに切り替えてください。 \nそうでない場合は、java.com (Java8) または BellSoft Liberica Full JRE (Java17) およびその他のプラットフォームをダウンロードしてインストールします (インストール後にランチャーを再起動します)。 game.crash.reason.mod_name=現在のゲームはModファイル名の問題で続行できません。\nModファイル名は、英文の全半角の大文字と小文字(Aa ~ Zz)、数字(0 ~ 9)、横線(-)、アンダースコア(_)、点(.)のみを使用してください。\n上記のコンプライアンス文字をModフォルダに追加してください。 game.crash.reason.incomplete_forge_installation=Forge / NeoForge のインストールが不完全なため、現在のゲームを続行できません。\nバージョン設定 - 自動インストールで Forge / NeoForge をアンインストールしてから再インストールしてください。 @@ -393,7 +406,6 @@ game.crash.reason.stacktrace=不明。[ログ]ボタンをクリックすると game.crash.reason.too_old_java=Java仮想マシンのバージョンが低すぎるため、ゲームを実行できません。\nゲーム設定でJava仮想マシンの新しいバージョン(%1$s)に切り替えて、ゲームを再起動する必要があります。そうでない場合は、オンラインでダウンロードできます。 game.crash.reason.unknown=不明。「ログ」ボタンをクリックすると詳細を確認できます。 game.crash.reason.unsatisfied_link_error=必要なネイティブライブラリがないため、ゲームを実行できません。\n不足しているネイティブライブラリ:%1$s。\nゲーム設定でネイティブライブラリパスオプションを変更した場合は、元に戻すことをお勧めします。標準モードに切り替えます。\nすでに標準モードになっている場合は、不足しているネイティブライブラリがmodまたはゲームに属しているかどうかを確認してください。HMCLが原因であることが確実な場合は、フィードバックをお送りください。\nネイティブライブラリパスを本当にカスタマイズする必要がある場合は、ゲームに必要なすべてのネイティブライブラリをディレクトリに配置する必要があります。 -game.crash.reason.failed_to_load_a_library=ネイティブ ライブラリのロードに失敗したため、ゲームを実行できません。\nゲーム設定でネイティブライブラリパスオプションを変更した場合は、元に戻すことをお勧めします。標準モードに切り替えます。\nすでに標準モードになっている場合は、不足しているネイティブライブラリがmodまたはゲームに属しているかどうかを確認してください。HMCLが原因であることが確実な場合は、フィードバックをお送りください。\nネイティブライブラリパスを本当にカスタマイズする必要がある場合は、ゲームに必要なすべてのネイティブライブラリをディレクトリに配置する必要があります。 game.crash.title=ゲームがクラッシュしました game.directory=ゲームディレクトリ game.version=ゲームバージョン @@ -448,6 +460,36 @@ install.new_game.malformed=無効な名前 install.select=操作を選択します install.success=正常にインストールされました +java.add=Javaの追加 +java.add.failed=このJavaは無効であるか、現在のプラットフォームと互換性がない。 +java.disable=無効化 +java.disable.confirm=本当にこのJavaを無効にしますか? +java.disabled.management=無効なJava +java.disabled.management.remove=このJavaをリストから削除する +java.disabled.management.restore=有効化 +java.download=Javaをダウンロード +java.download.load_list.failed=バージョンリストの読み込みに失敗しました +java.download.more=その他のJavaディストリビューション +java.download.prompt=ダウンロードしたいJavaのバージョンを選択してください: +java.download.distribution=配布の種類 +java.download.version=バージョン +java.download.packageType=パッケージの種類 +java.management=Javaの管理 +java.info.architecture=アーキテクチャ +java.info.vendor=発行元 +java.info.version=バージョン +java.info.disco.distribution=配布の種類 +java.install=Javaのインストール +java.install.archive=場所 +java.install.failed.exists=この名前はすでに使用されている +java.install.failed.invalid=このアーカイブは有効なJavaインストール・パッケージではないため、インストールできません。 +java.install.failed.unsupported_platform=このJavaは現在のプラットフォームと互換性がないため、インストールできません。 +java.install.name=名前 +java.install.warning.invalid_character=名前に不正な文字が含まれています +java.reveal=Javaディレクトリを表示する +java.uninstall=このJavaをアンインストールする +java.uninstall.confirm=本当にこのJavaをアンインストールしますか?この操作は元に戻せません! + lang=日本語 lang.default=システム言語を使用する @@ -460,8 +502,8 @@ launch.advice.forge2760_liteloader=Forge 2760以降はLiteLoaderと互換性が launch.advice.forge28_2_2_optifine=Forge28.2.2以降のバージョンはOptiFineと互換性がありません。Forgeを28.2.1以前のバージョンにダウングレードすることを検討してください。 launch.advice.java8_1_13=Minecraft 1.13以降は、Java8以降でのみ実行できます。 launch.advice.java8_51_1_13=Minecraft 1.13は、1.8.0_51より前のJava8でクラッシュする可能性があります。最新バージョンのJava8をインストールしてください。 -launch.advice.java9=Java9以降のバージョンのJavaでMinecraft1.12以前を起動することはできません。 -ゲームを高速化するには、launch.advice.newer_java=Java8をお勧めします。多くのMinecraft1.12以降、およびほとんどのModには、Java8が必要です。 +launch.advice.java9=Java9以降のバージョンのJavaでMinecraft1.12以前を起動することはできません。ゲームを高速化するには、Java8をお勧めします。 +launch.advice.newer_java=多くのMinecraft1.12以降、およびほとんどのModには、Java8が必要です。 launch.advice.not_enough_space=割り当てたメモリが多すぎます。物理メモリサイズが%dMBであるため、ゲームがクラッシュする可能性があります。 launch.advice.too_large_memory_for_32bit=32ビットJavaランタイム環境が原因で、割り当てたメモリが多すぎるため、ゲームがクラッシュする可能性があります。32ビットシステムの最大メモリ容量は1024MBです。 launch.advice.vanilla_linux_java_8=Linux x86-64の場合、Minecraft1.12.2以下はJava8でのみ実行できます。\nJava9以降のバージョンでは、liblwjgl.soなどの32ビットネイティブライブラリをロードできません。 @@ -493,13 +535,14 @@ launcher.agreement.hint=このソフトウェアを使用するには、EULAに launcher.background=背景画像 launcher.background.classic=クラシック launcher.background.choose=背景画像ファイルを選択してください -launcher.background.default=標準(ランチャーと同じディレクトリにある background.png/jpg/gif と bg フォルダから自動的に画像を取得します。) +launcher.background.default=標準 +launcher.background.default.tooltip=ランチャーと同じディレクトリにある background.png/.jpg/.gif/.webp と bg フォルダから自動的に画像を取得します。 launcher.background.network=ネットワーク launcher.background.translucent=半透明 launcher.cache_directory=キャッシュ用のディレクトリ launcher.cache_directory.clean=クリアランス launcher.cache_directory.choose=キャッシュするディレクトリを選択します -launcher.cache_directory.default=標準(%AppData%/.minecraft または ~/.minecraft) +launcher.cache_directory.default=標準(%APPDATA%/.minecraft または ~/.minecraft) launcher.cache_directory.disabled=無効(常にゲームパスを使用する) launcher.cache_directory.invalid=無効なディレクトリ。デフォルト設定の復元。 launcher.contact=お問い合わせ @@ -573,10 +616,7 @@ modpack.origin.mcbbs.prompt=スレッドID modpack.scan=このmodpackをスキャンしています modpack.task.install=Modpackのインポート modpack.task.install.error=このmodpackファイルは認識できません。CurseおよびMultiMCmodpackのみがサポートされています。 -modpack.task.install.will=modpackをインストールします: modpack.type.curse=Curse -modpack.type.curse.completion=Cursemodpackに関連するファイルをインストールします -modpack.type.curse.tolerable_error=このmodpackのすべてのファイルのダウンロードを完了できません。対応するゲームバージョンを開始するときに、ダウンロードを再試行できます。ネットワークの問題により、数回再試行する場合があります。 modpack.type.curse.error=このmodpackをインストールできません。再試行してください。 modpack.type.curse.not_found=必要なリソースの一部が欠落しているため、ダウンロードできませんでした。最新バージョンまたは他のmodpackを検討してください。 modpack.type.manual.warning=このmodpackをインポートする代わりに、おそらくこのmodpackファイルを直接解凍する必要があります。そして、バンドルされたランチャーを使用してゲームを起動します。このmodpackは、ランチャーによってエクスポートされるのではなく、.minecraftディレクトリを圧縮することによって手動で作成されます。HMCLはこのmodpackのインポートを試みることができます、続行しますか? @@ -626,7 +666,8 @@ mods.add.success=mods %s が正常に追加されました。 mods.category=Category mods.check_updates=更新を確認 mods.check_updates.current_version=Current -mods.check_updates.failed=一部のファイルのダウンロードに失敗しました +mods.check_updates.failed_check=更新のチェックに失敗しました +mods.check_updates.failed_download=一部のファイルのダウンロードに失敗しました mods.check_updates.file=ファイル mods.check_updates.source=Source mods.check_updates.target_version=Target @@ -762,8 +803,8 @@ settings.advanced.post_exit_command=終了後のコマンド settings.advanced.post_exit_command.prompt=ゲーム終了後に実行されます settings.advanced.server_ip=サーバーアドレス settings.advanced.server_ip.prompt=ゲームの起動時にサーバーに参加する -settings.advanced.use_native_glfw=システムGLFWを使用する -settings.advanced.use_native_openal=システムOpenALを使用する +settings.advanced.use_native_glfw=[Linux/FreeBSDのみ]システムGLFWを使用する +settings.advanced.use_native_openal=[Linux/FreeBSDのみ]システムOpenALを使用する settings.advanced.workaround=デバッグ用オプション settings.advanced.workaround.warning=デバッグオプションはプロフェッショナルのみ使用可能です。デバッグオプションにより、ゲームが起動しない場合があります。これらのオプションは、ご自分が何をしているのかが分からない限り、変更しないでください。 settings.advanced.wrapper_launcher=パッキングオーダー @@ -844,8 +885,8 @@ settings.type.special.edit=現在のゲーム設定を構成します settings.type.special.edit.hint=現在のゲームバージョン %s で特殊な設定が有効になっているため、現在のページのオプションはそのゲームバージョンに適用されません。リンクをクリックして、現在のゲーム設定を構成します。 sponsor=寄付 -sponsor.bmclapi=ダウンロードサービスはBMCLAPIによって提供されます。詳細については、ここをクリックしてください。 -sponsor.hmcl=こんにちはマインクラフト! Launcherは、無料のオープンソースのMinecraftランチャーであり、ユーザーは複数の個別のMinecraftインストールを簡単に管理できます。私たちはAfdianを使用して、ホスティングとプロジェクト開発の費用を引き続き支払います。詳細については、ここをクリックしてください。 +sponsor.bmclapi=中国本土向けのダウンロードサービスはBMCLAPIによって提供されています。詳細については、ここをクリックしてください。 +sponsor.hmcl=Hello Minecraft! Launcherは、無料のオープンソースのMinecraftランチャーであり、ユーザーは複数の個別のMinecraftインストールを簡単に管理できます。詳細については、ここをクリックしてください。 system.architecture=Arch system.operating_system=OS @@ -854,15 +895,14 @@ update=更新 update.accept=更新 update.changelog=変更 update.channel.dev=ベータ -update.channel.dev.hint=ベータ版を使用しています。ベータ版には、リリースバージョンと比較していくつかの追加機能が含まれている可能性があり、テストにのみ使用されます。\n\ - ベータ版は不安定なはずです!\n\ - 問題が発生した場合は、フィードバックページにアクセスして報告するか、チャット DiscordまたはQQで問題を報告します。\n\n\ - 現在のテスト版のヒントを非表示にするには、ここをクリックしてください。 +update.channel.dev.hint=ベータ版を使用しています。ベータ版には、リリースバージョンと比較していくつかの追加機能が含まれている可能性があり、テストにのみ使用されます。ベータ版は不安定なはずです!\n\ + \n\ + 問題が発生した場合は、フィードバックページにアクセスして報告してください。 update.channel.dev.title=ベータ版のヒント update.channel.nightly=アルファ -update.channel.nightly.hint=アルファ版を使用しています。これには、ベータ版およびリリース版と比較していくつかの追加機能が含まれている可能性があり、テストにのみ使用されます。\n\ - アルファ版は不安定です!\n\ - 問題が発生した場合は、フィードバックページにアクセスして報告するか、チャット Discordで問題を報告します。 +update.channel.nightly.hint=アルファ版を使用しています。これには、ベータ版およびリリース版と比較していくつかの追加機能が含まれている可能性があり、テストにのみ使用されます。アルファ版は不安定です!\n\ + \n\ + 問題が発生した場合は、フィードバックページにアクセスして報告してください。 update.channel.nightly.title=アルファ版のヒント update.channel.stable=リリース update.checking=更新の確認 diff --git a/HMCL/src/main/resources/assets/lang/I18N_ru.properties b/HMCL/src/main/resources/assets/lang/I18N_ru.properties index 1454eeb20b..1937c901ac 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ru.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ru.properties @@ -1,44 +1,47 @@ # # Hello Minecraft! Launcher -# Авторские права (C) 2023 huangyuhui и участников +# Copyright (C) 2025 huangyuhui and contributors # -# Эта программа является бесплатным программным обеспечением: -# вы можете распространять и/или изменять её на условиях GNU -# General Public License, опубликованной Free Software Foundation, -# либо 3й версией лицензии, либо (по вашему выбору) любой версией выше. +# 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. # -# Эта программа распространяется в надежде, что она окажется полезной, -# но БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ; даже без подразумеваемых гарантий -# ТОВАРНОЙ ПРИГОДНОСТИ или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННЫХ ЦЕЛЕЙ. -# Подробности смотрите в Стандартной общественной лицензии GNU. +# 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. # -# Вместе с этой программой вы должны были получить копию GNU -# General Public License. Если нет, смотрите . +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . # -# Соавторы: vanja-san -# +# Contributors: vanja-san about=О лаунчере about.copyright=Авторские права -about.copyright.statement=Авторские права © 2024 huangyuhui. +about.copyright.statement=Авторские права © 2025 huangyuhui. about.author=Автор -about.author.statement=bilibili: @huanghongxun +about.author.statement=bilibili @huanghongxun about.claim=EULA about.claim.statement=Кликните на ссылку, чтобы увидеть весь текст. about.dependency=Зависимости about.legal=Юридическое подтверждение about.thanks_to=Отдельная благодарность -about.thanks_to.bangbang93.statement=За предоставление API скачивания BMCLAPI. Рассмотрите возможность пожертвования. +about.thanks_to.bangbang93.statement=За предоставление зеркала загрузки BMCLAPI, пожалуйста, подумайте о пожертвовании! +about.thanks_to.burningtnt.statement=Оказал большую техническую поддержку HMCL. about.thanks_to.contributors=Всем участникам на GitHub -about.thanks_to.contributors.statement=Без потрясающего сообщества с открытым исходным кодом, Hello Minecraft! Launcher не смог бы добраться так далеко. +about.thanks_to.contributors.statement=Без потрясающего сообщества с открытым исходным кодом, HMCL не смог бы добраться так далеко. about.thanks_to.gamerteam.statement=За предоставление фонового изображения. -about.thanks_to.glavo.statement=Оказал большую техническую поддержку HMCL. +about.thanks_to.glavo.statement=Отвечает за поддержание HMCL. about.thanks_to.zekerzhayard.statement=Оказал большую техническую поддержку HMCL. -about.thanks_to.zkitefly.statement=Оказал большую техническую поддержку HMCL. -about.thanks_to.mcmod=mcmod.cn -about.thanks_to.mcmod.statement=За предоставление перевода на китайский и вики для различных модов. +about.thanks_to.zkitefly.statement=Отвечает за поддержание документации HMCL. +about.thanks_to.mcbbs=MCBBS (Китайский форум Minecraft) +about.thanks_to.mcbbs.statement=За предоставление зеркала загрузки mcbbs.net для пользователей материкового Китая. (Больше недоступно) +about.thanks_to.mcmod=MCMod (mcmod.cn) +about.thanks_to.mcmod.statement=За предоставление упрощенного китайского перевода и вики для различных модов. about.thanks_to.red_lnn.statement=За предоставление фонового изображения. +about.thanks_to.shulkersakura.statement=За предоставление логотипа для HMCL. about.thanks_to.users=Членам группы пользователей HMCL about.thanks_to.users.statement=Спасибо за пожертвования, отчёты об ошибках и так далее. about.thanks_to.yushijinhun.statement=За предоставление поддержки, связанной с authlib-injector. @@ -157,8 +160,11 @@ button.retry=Повторить снова button.save=Сохранить button.save_as=Сохранить как button.select_all=Выбрать все +button.view=Просмотреть button.yes=Да +chat=Групповой чат + color.recent=Рекомендуемые color.custom=Пользовательский цвет @@ -336,8 +342,13 @@ fatal.samba=Если вы пытаетесь запустить лаунчер fatal.illegal_char=Недопустимый символ '=' в пути к папке пользователя. Лаунчер может работать, но некоторые функции не будут работать.\nВы не сможете использовать аккаунт authlib-injector или изменить офлайн скин. feedback=Обратная связь +feedback.channel=Канал обратной связи feedback.discord=Discord -feedback.discord.statement=Присоединиться! +feedback.discord.statement=Добро пожаловать присоединиться к нашему Discord. +feedback.github=Проблемы GitHub +feedback.github.statement=Отправьте проблему на GitHub. +feedback.qq_group=Группа QQ пользователя HMCL +feedback.qq_group.statement=Добро пожаловать присоединиться к нашей группе QQ! file=Файл @@ -365,13 +376,10 @@ game.crash.reason.fabric_version_0_12=Fabric 0.12 (и выше) несовмес game.crash.reason.mixin_apply_mod_failed=Текущая игра не может продолжать работать из-за невозможности применения мода %1$s с помощью Mixin.\nВы можете попробовать удалить или обновить этот мод, чтобы решить проблему. game.crash.reason.fabric_warnings=Предупреждения от Fabric:\n%1$s game.crash.reason.modmixin_failure=Текущая игра не может продолжать работать из-за некоторых сбоев внедрения модов.\nОбычно это означает, что мод содержит ошибку или несовместим с текущей средой.\nВы можете проверить журнал на наличие неправильного мода. -game.crash.reason.file_or_content_verification_failed=В текущей игре возникла проблема, поскольку не удалось проверить некоторые файлы или содержимое.\nПожалуйста, попробуйте удалить эту версию (включая моды) и загрузить заново, или попробуйте использовать прокси при повторной загрузке и т. д. game.crash.reason.mod_repeat_installation=В текущей игре неоднократно установлено несколько одинаковых модов, и каждый мод может появиться только один раз. Пожалуйста, удалите повторяющиеся моды перед запуском игры. game.crash.reason.need_jdk11=Текущая игра не может продолжать работать из-за неподходящей версии виртуальной машины Java.\nВам необходимо загрузить и установить Java 11, а также установить версию Java, начинающуюся с 11, в глобальных (конкретных) настройках игры. game.crash.reason.forge_error=Возможно, Forge предоставил сообщение об ошибке.\nВы можете просмотреть журнал и выполнить соответствующие действия в соответствии с информацией журнала в отчете об ошибках.\nЕсли вы не видите сообщения об ошибке, вы можете просмотреть отчет об ошибке, чтобы узнать, как она возникла.\n%1$s game.crash.reason.mod_resolution0=Текущая игра не может продолжать работать из-за некоторых проблем с модом.\nВы можете проверить журнал на наличие неправильного мода. -game.crash.reason.mod_profile_causes_game_crash=Текущая игра не может продолжать работать из-за проблемы с профилем мода.\nВы можете проверить журнал на наличие неисправного мода и его конфигурационного файла. -game.crash.reason.fabric_reports_an_error_and_gives_a_solution=Forge мог выдать сообщение об ошибке.\nВы можете просмотреть журнал и выполнить соответствующие действия в соответствии с информацией журнала в отчете об ошибках.\nЕсли вы не видите сообщения об ошибке, вы можете просмотреть отчет об ошибке, чтобы узнать, как она возникла. game.crash.reason.java_version_is_too_high=Текущая игра аварийно завершилась из-за того, что версия Java слишком высока и не может продолжать работать.\nПеред запуском игры переключитесь на более раннюю версию Java на вкладке "Путь к Java" в глобальных настройках игры или в настройках игры.\nЕсли нет, вы можете загрузить его с java.com (Java8) или BellSoft Liberica Full JRE (Java17) и другие платформы, чтобы загрузить и установить ее (перезапустите программу запуска после установки). game.crash.reason.mod_name=Текущая игра не может продолжать работать из - за проблем с именем файла Mod. Имя файла\nMod должно быть использовано только в английском языке в полном углу с большими буквами (Aa ~ Zz), цифрами (0 ~ 9), горизонтальными линиями (-), подчеркнутыми (_) и точками (.) .\nПожалуйста, добавьте символ соответствия в папку Mod для всех несовместимых имен файлов Mod. game.crash.reason.incomplete_forge_installation=Текущая игра не может быть продолжена из-за незавершенной установки Forge / NeoForge.\nУдалите и переустановите Forge / NeoForge в настройках версии - Автоматическая установка. @@ -416,7 +424,6 @@ game.crash.reason.stacktrace=Неизвестно. Вы можете просм game.crash.reason.too_old_java=Невозможно запустить игру из-за того, что версия виртуальной машины Java слишком низкая.\nYou need to switch to a newer version (%1$s) of Java virtual machine in the game settings and restart the game. If not, you can download it online. game.crash.reason.unknown=Неизвестно. Вы можете просмотреть подробности, кликнув на кнопку «Журналы». game.crash.reason.unsatisfied_link_error=Невозможно запустить игру из-за того, что отсутствуют необходимые встроенные библиотеки.\nОтсутствующая нативная библиотека: %1$s.\nЕсли в настройках игры вы изменили параметр пути к нативной библиотеке, лучше переключиться обратно в стандартный режим.\nЕсли это уже стандартный режим, проверьте, принадлежит ли отсутствующая нативная библиотека моду или игре. Если вы уверены, что это произошло по вине HMCL, вы можете написать нам через обратную связь.\nЕсли вам действительно нужно настроить путь к нативным библиотекам, вы должны поместить в каталог все нативные библиотеки, которые требуются игре. -game.crash.reason.failed_to_load_a_library=Не удалось запустить игру, так как не удалось загрузить встроенную библиотеку.\nЕсли в настройках игры вы изменили параметр пути к нативной библиотеке, лучше переключиться обратно в стандартный режим.\nЕсли это уже стандартный режим, проверьте, принадлежит ли отсутствующая нативная библиотека моду или игре. Если вы уверены, что это произошло по вине HMCL, вы можете написать нам через обратную связь.\nЕсли вам действительно нужно настроить путь к нативным библиотекам, вы должны поместить в каталог все нативные библиотеки, которые требуются игре. game.crash.title=Игра вылетела game.directory=Путь игры game.version=Версия игры @@ -471,6 +478,36 @@ install.new_game.malformed=Недопустимое имя install.select=Выберите операцию install.success=Успешно установлено. +java.add=Добавить Java +java.add.failed=Этот Java недопустим или несовместим с текущей платформой. +java.disable=Отключить Java +java.disable.confirm=Вы уверены, что хотите отключить эту Java? +java.disabled.management=Отключенная Java +java.disabled.management.remove=Удалить эту Java из списка +java.disabled.management.restore=Включить эту Java +java.download=Скачать Java +java.download.load_list.failed=Не удалось загрузить список версий +java.download.more=Больше дистрибутивов Java +java.download.prompt=Выберите версию Java, которую вы хотите загрузить: +java.download.distribution=Дистрибуция +java.download.version=Версия +java.download.packageType=Тип упаковки +java.management=Управление Java +java.info.architecture=Архитектура +java.info.vendor=Поставщик +java.info.version=Версия +java.info.disco.distribution=Дистрибуция +java.install=Установите Java +java.install.archive=Расположение +java.install.failed.exists=Это имя уже используется +java.install.failed.invalid=Этот архив не является допустимым установочным пакетом Java, поэтому его установка невозможна. +java.install.failed.unsupported_platform=Эта Java несовместима с текущей платформой, поэтому ее невозможно установить. +java.install.name=Название +java.install.warning.invalid_character=Неправильный символ в названии +java.reveal=Откройте каталог Java +java.uninstall=Удалить Java +java.uninstall.confirm=Вы уверены, что хотите удалить эту Java? Это действие нельзя отменить! + lang=Русский lang.default=Использовать язык системы @@ -520,13 +557,14 @@ launcher.agreement.hint=Нужно принять пользовательско launcher.background=Фоновое изображение launcher.background.choose=Выберите фоновое изображение launcher.background.classic=Классическое -launcher.background.default=По умолчанию (или background.png/jpg/gif, или папку с изображениями) +launcher.background.default=По умолчанию +launcher.background.default.tooltip=или background.png/.jpg/.gif/.webp, или папку с изображениями launcher.background.network=По ссылке launcher.background.translucent=Полупрозрачный launcher.cache_directory=Папка с кэшем launcher.cache_directory.clean=Очистить кэш launcher.cache_directory.choose=Выберите папку для кэша -launcher.cache_directory.default=По умолчанию (%AppData%/.minecraft или ~/.minecraft) +launcher.cache_directory.default=По умолчанию (%APPDATA%/.minecraft или ~/.minecraft) launcher.cache_directory.disabled=Отключено launcher.cache_directory.invalid=Не удаётся создать папку для кэша, возвращаем к значению по умолчанию. launcher.contact=Связаться с нами @@ -601,10 +639,7 @@ modpack.origin.mcbbs.prompt=ID записи modpack.scan=Разбор индекса модпака modpack.task.install=Установить модпак modpack.task.install.error=Не удаётся идентифицировать этот модпак. Поддерживаются только модпаки Curse, Modrinth, MultiMC и MCBBS. -modpack.task.install.will=Вы собираетесь установить модпак\: modpack.type.curse=Curse -modpack.type.curse.completion=Установите файлы, связанные с модпаком Curse -modpack.type.curse.tolerable_error=Мы не можем завершить скачивание всех файлов этого модпака. Вы можете повторить скачивание при запуске соответствующей версии игры. Вы можете повторить попытку несколько раз из-за проблем с сетью. modpack.type.curse.error=Невозможно установить этот модпак. Повторите ещё раз. modpack.type.curse.not_found=Некоторые из необходимых ресурсов отсутствуют и поэтому не могут быть загружены. Скачайте последнюю версию или другие модпаки. modpack.type.manual.warning=Возможно, вам нужно напрямую распаковать файл этого модпака, а не импортировать его. И запустите игру с помощью прилагаемого лаунчера. Этот модпак создаётся вручную путем сжатия директории .minecraft, а не экспортируется лаунчером. HMCL может попытаться импортировать этот модпак, продолжать? @@ -662,7 +697,8 @@ mods.add.success=%s был успешно установлен. mods.category=Категория mods.check_updates=Проверить обновления mods.check_updates.current_version=Текущая версия -mods.check_updates.failed=Не удаётся скачать некоторые файлы. +mods.check_updates.failed_check=Не удалось проверить обновления. +mods.check_updates.failed_download=Не удаётся скачать некоторые файлы. mods.check_updates.file=Файл mods.check_updates.source=Источник mods.check_updates.target_version=Целевая версия @@ -709,7 +745,7 @@ world.import.failed=Не удаётся импортировать этот ми world.import.invalid=Не удаётся разобрать сохранение. world.info.title=Сведения о мире %s world.info.basic=Основные сведения -world.info.allow_cheats=Разрешить читы +world.info.allow_cheats=Разрешить команды/читы world.info.dimension.the_nether=Нижний мир world.info.dimension.the_end=Край world.info.difficulty=Сложность @@ -832,8 +868,8 @@ settings.advanced.post_exit_command=Команда после выхода settings.advanced.post_exit_command.prompt=Команды, которые необходимо выполнить после выхода из игры settings.advanced.server_ip=Адрес сервера settings.advanced.server_ip.prompt=Присоединяться к серверу при запуске игры -settings.advanced.use_native_glfw=Использовать системный GLFW -settings.advanced.use_native_openal=Использовать системный OpenAL +settings.advanced.use_native_glfw=[Только для Linux/FreeBSD] Использовать системный GLFW +settings.advanced.use_native_openal=[Только для Linux/FreeBSD] Использовать системный OpenAL settings.advanced.workaround=Обходные пути settings.advanced.workaround.warning=Варианты обхода предназначены только для опытных пользователей. Изменение этих параметров может привести к вылету игры. Если не знаете, что делаете, то не изменяйте эти параметры. settings.advanced.wrapper_launcher=Команда-оболочка @@ -918,8 +954,8 @@ settings.type.special.edit=Изменить настройки текущего settings.type.special.edit.hint=В текущем экземпляре [%s] включены раздельные настройки для экземпляров, параметры на этой странице НЕ повлияют на этот экземпляр. Нажмите здесь, чтобы изменить раздельные параметры. sponsor=Спонсоры -sponsor.bmclapi=Скачивание предоставляется BMCLAPI. Нажмите здесь для получения подробностей. -sponsor.hmcl=Hello Minecraft! Launcher — лаунчер Minecraft с открытым исходным кодом, который позволяет пользователям легко управлять несколькими экземплярами Minecraft. Пожертвуйте нам на Afdian для поддержки нашего файлового хостинга и развития проекта. Для получения подробностей нажмите здесь. +sponsor.bmclapi=Загрузки для материкового Китая предоставляются BMCLAPI. Нажмите здесь для получения подробностей. +sponsor.hmcl=Hello Minecraft! Launcher — лаунчер Minecraft с открытым исходным кодом, который позволяет пользователям легко управлять несколькими экземплярами Minecraft. Для получения подробностей нажмите здесь. system.architecture=Архитектура system.operating_system=Операционная система @@ -928,15 +964,14 @@ update=Обновление update.accept=Обновить update.changelog=Изменения update.channel.dev=Бета -update.channel.dev.hint=Вы используете бета-версию, которая может включать некоторые дополнительные функции по сравнению с релизной версией, используемой только для тестирования.\n\ - Бета-версия может быть нестабильной!\n\ - Если вы встретили какие-то проблемы, вы можете зайти на страницу обратной связи, чтобы сообщить о них, или присоединиться к чату Discord или QQ, чтобы сообщить о проблемах.\n\n\ - Нажмите здесь, чтобы скрыть подсказку для текущей тестовой версии. +update.channel.dev.hint=Вы используете бета-версию, которая может включать некоторые дополнительные функции по сравнению с релизной версией, используемой только для тестирования. Бета-версия может быть нестабильной!\n\ + \n\ + Если вы встретили какие-то проблемы, вы можете зайти на страницу обратной связи. update.channel.dev.title=Подсказки для бета-версии update.channel.nightly=Альфа -update.channel.nightly.hint=Вы используете альфа-версию, которая может включать некоторые дополнительные функциональные возможности по сравнению с бета-версией и версией релиза, используемой только для тестирования.\n\ - Альфа-версия может быть нестабильной!\n\ - Если вы встретили какие-то проблемы, вы можете зайти на страницу обратной связи, чтобы сообщить о них, или присоединиться к чату Discord или QQ, чтобы сообщить о проблемах. +update.channel.nightly.hint=Вы используете альфа-версию, которая может включать некоторые дополнительные функциональные возможности по сравнению с бета-версией и версией релиза, используемой только для тестирования. Альфа-версия может быть нестабильной!\n\ + \n\ + Если вы встретили какие-то проблемы, вы можете зайти на страницу обратной связи. update.channel.nightly.title=Подсказки для альфа-версии update.channel.stable=Релиз update.checking=Проверка наличия обновлений diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 73e7819f9d..ecce105a8c 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -1,6 +1,6 @@ # # Hello Minecraft! Launcher -# Copyright (C) 2023 huangyuhui and contributors +# Copyright (C) 2025 huangyuhui 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 @@ -17,153 +17,146 @@ # # Contributors: pan93412 + about=關於 about.copyright=著作權 -about.copyright.statement=著作權所有 © 2024 huangyuhui。 +about.copyright.statement=著作權所有 © 2025 huangyuhui about.author=作者 -about.author.statement=bilibili: @huanghongxun -about.claim=用戶協議 +about.author.statement=bilibili @huanghongxun +about.claim=使用者協議 about.claim.statement=點擊連結以查看全文 about.dependency=相依元件 -about.legal=法律聲明 +about.legal=法律宣告 about.thanks_to=鳴謝 -about.thanks_to.bangbang93.statement=提供 BMCLAPI 下載地點。請贊助支持 BMCLAPI! -about.thanks_to.burningtnt.statement=為 HMCL 貢獻許多技術支持 -about.thanks_to.contributors=所有通過 Issues、Pull Requests 等管道參與本項目的貢獻者 -about.thanks_to.contributors.statement=沒有開源社區的支持,Hello Minecraft! Launcher 無法走到今天 +about.thanks_to.bangbang93.statement=提供 BMCLAPI 下載來源。請贊助支援 BMCLAPI! +about.thanks_to.burningtnt.statement=為 HMCL 貢獻許多技術支援 +about.thanks_to.contributors=所有透過 Issue、Pull Request 等管道參與本項目的貢獻者 +about.thanks_to.contributors.statement=沒有開源社群的支援,HMCL 無法走到今天 about.thanks_to.gamerteam.statement=提供預設背景圖 about.thanks_to.glavo.statement=負責 HMCL 的日常維護 -about.thanks_to.zekerzhayard.statement=為 HMCL 貢獻許多技術支持 -about.thanks_to.zkitefly.statement=負責維護 HMCL 的文檔 -about.thanks_to.mcbbs=MCBBS 我的世界中文論壇 +about.thanks_to.zekerzhayard.statement=為 HMCL 貢獻許多技術支援 +about.thanks_to.zkitefly.statement=負責維護 HMCL 的文件 +about.thanks_to.mcbbs=MCBBS (我的世界中文論壇) about.thanks_to.mcbbs.statement=提供 MCBBS 下載源 (現已停止服務) -about.thanks_to.mcmod=MC 百科 -about.thanks_to.mcmod.statement=提供 Mod 中文名映射表與 Mod 百科 +about.thanks_to.mcmod=MC 百科 (mcmod.cn) +about.thanks_to.mcmod.statement=提供模組簡體中文名映射表與模組百科 about.thanks_to.red_lnn.statement=提供預設背景圖 -about.thanks_to.shulkersakura.statement=提供 HMCL 的圖標 -about.thanks_to.users=HMCL 用戶群成員 -about.thanks_to.users.statement=感謝用戶群成員贊助充電、積極催更、迴響問題、出謀劃策 -about.thanks_to.yushijinhun.statement=authlib-injector 相关支援 +about.thanks_to.shulkersakura.statement=提供 HMCL 的標誌 +about.thanks_to.users=HMCL 使用者群組成員 +about.thanks_to.users.statement=感謝使用者群組成員贊助充電、積極催更、回報問題、出謀劃策 +about.thanks_to.yushijinhun.statement=提供 authlib-injector 相關支援 about.open_source=開放原始碼 about.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL/) account=帳戶 account.cape=披風 account.character=角色 -account.choose=請選擇角色 +account.choose=請選取角色 account.create=建立帳戶 -account.create.microsoft=添加微軟帳戶 -account.create.offline=添加離線模式帳戶 -account.create.authlibInjector=添加外置登入帳戶 (authlib-injector) +account.create.microsoft=加入 Microsoft 帳戶 +account.create.offline=加入離線模式帳戶 +account.create.authlibInjector=加入 authlib-injector 帳戶 account.email=電子信箱 -account.failed=帳戶刷新失敗 +account.failed=帳戶重新整理失敗 account.failed.character_deleted=已刪除此角色 -account.failed.connect_authentication_server=無法連線至認證伺服器,可能是網路問題,請檢查設備能否正常上網或使用代理服務 -account.failed.connect_injector_server=無法連線至認證伺服器,可能是網路故障,請檢查設備能否正常上網、檢查網址是否輸入錯誤或使用代理服務 -account.failed.injector_download_failure=無法下載 authlib-injector,請檢查網路或嘗試切換下載來源 -account.failed.invalid_credentials=您的使用者名稱或密碼錯誤,或者登入次數過多被暫時禁止登入,請稍後再試 +account.failed.connect_authentication_server=無法連線至認證伺服器,可能是網路問題,請檢查裝置能否正常上網或使用代理服務。 +account.failed.connect_injector_server=無法連線至認證伺服器,可能是網路故障,請檢查裝置能否正常上網、檢查網址是否輸入錯誤或使用代理服務。 +account.failed.injector_download_failure=無法下載 authlib-injector,請檢查網路或嘗試切換下載來源。 +account.failed.invalid_credentials=你的使用者名稱或密碼錯誤,或者登入次數過多被暫時禁止登入,請稍後再試。 account.failed.invalid_password=密碼無效 -account.failed.invalid_token=請嘗試登出並重新輸入密碼登入 -account.failed.migration=你的帳號需要被遷移至微軟帳號。如果你已經遷移,你需要使用微軟登錄方式登錄遷移後的微軟帳號 -account.failed.no_character=該帳戶沒有角色 -account.failed.server_disconnected=無法訪問登錄伺服器,賬戶信息刷新失敗。\n\ - 您可以選擇“再次刷新賬戶”重新嘗試。\n\ - 您也可以選擇“跳過賬戶刷新”繼續啟動遊戲,但可能會使賬戶信息不是最新的。\n\ - 若最近沒有刷新賬戶信息,則可能導致賬戶信息過期失效,\n\ - 若為 微軟賬戶 啟動遊戲,賬戶信息過期失效可能將無法進入需賬戶驗證的伺服器。\n\ - 若嘗試多次無法成功刷新,可嘗試重新添加該賬戶嘗試解決該問題。 -account.failed.server_response_malformed=無法解析認證伺服器回應,可能是伺服器故障 -account.failed.ssl=連接服務器時發生了 SSL 錯誤,可能網站證書已過期或你使用的 Java 版本過低,請嘗試更新 Java -account.failed.wrong_account=登錄了錯誤的帳號 -account.hmcl.hint=你需要點擊“登入”按鈕,並在打開的網頁中完成登入 +account.failed.invalid_token=請嘗試登出並重新輸入密碼登入。 +account.failed.migration=你的帳戶需要遷移至 Microsoft 帳戶。如果你已經遷移,你需要使用遷移後的 Microsoft 帳戶登入。 +account.failed.no_character=該帳戶沒有角色。 +account.failed.server_disconnected=無法訪問登入伺服器,帳戶資訊重新整理失敗。\n\ + 你可以選取「再次重新整理帳戶」重新嘗試。\n\ + 你也可以選取「跳過帳戶重新整理」繼續啟動遊戲,但可能會導致帳戶資訊未同步更新。\n\ + 若最近沒有重新整理帳戶資訊,則可能導致帳戶資訊過期失效。\n\ + 若使用 Microsoft/authlib-injector 帳戶啟動遊戲,帳戶資訊過期失效可能將無法進入需線上驗證的伺服器。\n\ + 若嘗試多次無法重新整理,可嘗試重新加入該帳戶,或許可以解決該問題。 +account.failed.server_response_malformed=無法解析認證伺服器回應,可能是伺服器故障。 +account.failed.ssl=連線伺服器時發生了 SSL 錯誤,可能網站證書已過期或你使用的 Java 版本過低,請嘗試更新 Java。 +account.failed.wrong_account=登入了錯誤的帳戶 +account.hmcl.hint=你需要點擊「登入」按鈕,並在打開的網頁中完成登入 account.injector.add=新增認證伺服器 account.injector.empty=無 (按一下右側 + 加入) -account.injector.http=警告: 此伺服器使用不安全的 HTTP 協定,您的密碼在登入時會被明文傳輸。 +account.injector.http=警告: 此伺服器使用不安全的 HTTP 協定,你的密碼在登入時會被明文傳輸。 account.injector.link.homepage=首頁 account.injector.link.register=註冊 account.injector.server=認證伺服器 account.injector.server_url=伺服器位址 account.injector.server_name=伺服器名稱 account.login=登入 -account.login.hint=我們不會保存你的密碼 -account.login.skip=跳過刷新帳戶 -account.login.retry=再次刷新帳戶 -account.login.refresh=重新登錄 -account.login.refresh.microsoft.hint=因為賬戶授權失效,你需要重新添加微軟賬戶 +account.login.hint=我們不會儲存你的密碼 +account.login.skip=跳過重新整理帳戶 +account.login.retry=再次重新整理帳戶 +account.login.refresh=重新登入 +account.login.refresh.microsoft.hint=由於帳戶授權失效,你需要重新加入 Microsoft 帳戶 account.logout=登出 account.register=註冊 -account.manage=帳戶列表 -account.copy_uuid=複製該賬戶的 UUID。 +account.manage=帳戶清單 +account.copy_uuid=複製該帳戶的 UUID account.methods=登入方式 account.methods.authlib_injector=authlib-injector 登入 -account.methods.microsoft=微軟帳戶 -account.methods.microsoft.birth=如何修改帳戶出生日期 -account.methods.microsoft.deauthorize=解除帳戶授權 -account.methods.microsoft.close_page=已完成微軟帳號授權,接下來啟動器還需要完成剩餘登入步驟。你已經可以關閉本頁面了。 -account.methods.microsoft.error.add_family=由於你未滿 18 歲,你的帳戶必須被加入到家庭中才能登錄遊戲。你也可以點擊上方連結更改你的帳戶的出生日期,使年齡滿 18 歲以上以繼續登錄。 -account.methods.microsoft.error.add_family_probably=請檢查你的帳戶設置,如果你未滿 18 歲,你的帳戶必須被加入到家庭中才能登錄遊戲。你也可以點擊上方連結更改你的帳戶的出生日期,使年齡滿 18 歲以上以繼續登錄。 -account.methods.microsoft.error.country_unavailable=你所在的國家或地區不受 XBox Live 的支援。 -account.methods.microsoft.error.missing_xbox_account=你的微軟帳號尚未關聯 XBox 帳號,你必須先創建 XBox 帳號,才能登入遊戲。 -account.methods.microsoft.error.no_character=該帳戶未包含 Minecraft Java 版購買記錄\n可能未創建遊戲檔案,請點擊上方鏈接創建 +account.methods.microsoft=Microsoft 帳戶 +account.methods.microsoft.birth=如何變更帳戶出生日期 +account.methods.microsoft.deauthorize=移除應用存取權 +account.methods.microsoft.close_page=已完成 Microsoft 帳戶授權,接下來啟動器還需要完成其餘登入步驟。你現在可以關閉本頁面了。 +account.methods.microsoft.error.add_family=由於你未滿 18 歲,你的帳戶必須被加入到家庭中才能登入遊戲。你也可以點擊上方「編輯帳戶配置檔」更改你的帳戶出生日期,使年齡滿 18 歲以上以繼續登入。 +account.methods.microsoft.error.add_family_probably=請檢查你的帳戶設定,如果你未滿 18 歲,你的帳戶必須被加入到家庭中才能登入遊戲。你也可以點擊上方「編輯帳戶配置檔」更改你的帳戶出生日期,使年齡滿 18 歲以上以繼續登入。 +account.methods.microsoft.error.country_unavailable=你所在的國家或地區不受 Xbox Live 的支援。 +account.methods.microsoft.error.missing_xbox_account=你的 Microsoft 帳戶尚未關聯 Xbox 帳戶,你必須先建立 Xbox 帳戶,才能登入遊戲。 +account.methods.microsoft.error.no_character=該帳戶未包含 Minecraft: Java 版購買記錄。\n若已購買,則可能未建立遊戲檔案,請點擊上方連結建立。 account.methods.microsoft.error.unknown=登入失敗,錯誤碼:%d -account.methods.microsoft.error.wrong_verify_method=請在 Microsoft 帳號登陸頁面使用帳號 + 密碼登入。請不要使用驗證碼登入。 -account.methods.microsoft.logging_in=登入中... -account.methods.microsoft.makegameidsettings=創建檔案/編輯檔案名稱 -account.methods.microsoft.hint=你需要按照以下步驟添加賬戶:\n\ - 1.點擊“登入”按鈕\n\ - 2.在網頁瀏覽器顯示的網站中輸入 HMCL 顯示的代碼(自動拷貝,直接粘貼即可),並點擊“下一步”\n\ - 3.按照網站的提示登入\n\ - 4.當網站顯示“ 是否允許此應用訪問你的信息?”的標識時,請點擊“是”\n\ - 5.在網站提示“大功告成”後,只需等待帳戶完成添加即可\n\ - 若網站提示“出现错误”的標識,請嘗試重新按照以上步驟登入\n\ - -若賬戶添加失敗,請嘗試重新按照以上步驟登入\n\ - -若登入微軟賬號的 Token 洩露,可點擊下方“解除帳戶授權”解除登入授權,然後等待 Token 過期\n\ +account.methods.microsoft.error.wrong_verify_method=請在 Microsoft 帳戶登入頁面使用密碼登入,不要使用驗證碼登入。 +account.methods.microsoft.logging_in=登入中…… +account.methods.microsoft.makegameidsettings=建立檔案 / 編輯檔案名稱 +account.methods.microsoft.hint=請點擊「登入」按鈕,稍後複製此處顯示的代碼,在打開的登入網頁中完成登入過程。\n\ + \n\ + 如果登入 Microsoft 帳戶的令牌洩露,可點擊下方「移除應用存取權」,然後等待令牌過期。\n\ + \n\ 如遇到問題,你可以點擊右上角幫助按鈕進行求助。 -account.methods.microsoft.manual=你需要按照以下步驟添加账户:\n\ - 1.點擊“登入”按鈕\n\ - 2.在網頁瀏覽器顯示的網站中輸入該代碼:%1$s(已自動拷貝,請點此處再次拷貝),並點擊“下一步”\n\ - 3.按照網站的提示登入\n\ - 4.當網站顯示“ 是否允許此應用訪問你的信息?”的標識時,請點擊“是”\n\ - 5.在網站提示“大功告成”後,只需等待帳戶完成添加即可\n\ - -若網站提示“出现错误”的標識或賬戶添加失敗時,請嘗試重新按照以上步驟登入\n\ - -若網站未能打開,請手動在網頁瀏覽器中打開:%2$s\n\ - -若登入微軟賬號的 Token 洩露,可點擊下方“解除帳戶授權”解除登入授權,然後等待 Token 過期\n\ +account.methods.microsoft.manual=你的代碼為 %1$s,請點擊此處複製。\n\ + \n\ + 點擊「登入」按鈕後,你應該在打開的登入網頁中完成登入過程。如果網頁沒有打開,你可以自行在瀏覽器中訪問 %2$s\n\ + \n\ + 如果登入 Microsoft 帳戶的令牌洩露,可點擊下方「移除應用存取權」,然後等待令牌過期。\n\ + \n\ 如遇到問題,你可以點擊右上角幫助按鈕進行求助。 -account.methods.microsoft.profile=帳戶設置頁 +account.methods.microsoft.profile=編輯帳戶配置檔 account.methods.microsoft.purchase=購買 Minecraft account.methods.forgot_password=忘記密碼 -account.methods.microsoft.snapshot=你正在使用非官方構建的 HMCL,請下載官方構建進行微軟登入。 +account.methods.microsoft.snapshot=你正在使用第三方提供的 HMCL,請下載官方版本進行登入。 account.methods.microsoft.snapshot.website=官方網站 account.methods.offline=離線模式 -account.methods.offline.name.special_characters=建議使用英文字符、數字以及下劃線命名 -account.methods.offline.name.invalid=正常情況下,遊戲用戶名只能包括英文字符、數字以及下劃線,且長度不能超過 16 個字符。\n\ - 一些合法的用戶名:HuangYu,huang_Yu,Huang_Yu_123;\n\ - 一些不合法的用戶名:黃魚,Huang Yu,Huang-Yu_%%%,Huang_Yu_hello_world_hello_world。\n\ - 如果你相信服務器端有相應的 Mod 或插件來解除此限制,你可以忽略本警告。 +account.methods.offline.name.special_characters=建議使用英文字母、數字以及底線命名 +account.methods.offline.name.invalid=遊戲使用者名稱通常僅允許使用英文字母、數字及底線,且長度不能超過 16 個字元。\n\ + \ · 一些有效的使用者名稱:HuangYu、huang_Yu、Huang_Yu_123;\n\ + \ · 一些無效的使用者名稱:黃魚,Huang Yu、Huang-Yu_%%%、Huang_Yu_hello_world_hello_world。\n\ + 如果你相信伺服器端有相應的模組或插件來解除此限制,你可以忽略本警告。 account.methods.offline.uuid=UUID -account.methods.offline.uuid.hint=UUID 是 Minecraft 對玩家角色的唯一標識符,每個啟動器生成 UUID 的方式可能不同。通過修改 UUID 選項至原啟動器所生成的 UUID,你可以保證在切換啟動器後,遊戲還能將你的遊戲角色識別為給定 UUID 所對應的角色,從而保留原來角色的背包物品。UUID 選項為高級選項,除非你知道你在做什麼,否則你不需要調整該選項。 +account.methods.offline.uuid.hint=UUID 是 Minecraft 玩家的唯一標識符,每個啟動器生成 UUID 的方式可能不同。\n透過修改 UUID 選項至原啟動器所生成的 UUID,你可以保證在切換啟動器後,遊戲還能將你的遊戲角色識別為給定 UUID 所對應的角色,從而保留原來角色的背包物品。\nUUID 選項為進階選項,除非你知道你在做什麼,否則你不需要調整該選項。 account.methods.offline.uuid.malformed=格式錯誤 account.missing=沒有遊戲帳戶 account.missing.add=按一下此處加入帳戶 -account.move_to_global=轉換為全域帳戶 -account.move_to_portable=轉換為便攜帳戶 +account.move_to_global=轉換為全域帳戶\n該帳戶的資訊會儲存至系統目前使用者目錄的配置檔案中 +account.move_to_portable=轉換為可攜式帳戶\n該帳戶的資訊會儲存至與 HMCL 同目錄的配置檔案中 account.not_logged_in=未登入 account.password=密碼 -account.portable=便攜帳戶 -account.skin=皮膚 -account.skin.file=皮膚圖片檔案 +account.portable=可攜式帳戶 +account.skin=外觀 +account.skin.file=外觀圖片檔案 account.skin.model=模型 -account.skin.model.default=經典 -account.skin.model.slim=苗條 +account.skin.model.default=寬型 +account.skin.model.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.hint=你需要在皮膚站中創建並使用和該離線帳戶角色同名角色,此時離線帳戶皮膚將為皮膚站上角色所設定的皮膚。 -account.skin.type.local_file=本地皮膚圖片檔案 -account.skin.upload=上傳皮膚 -account.skin.upload.failed=皮膚上傳失敗 -account.skin.invalid_skin=無法識別的皮膚檔案 +account.skin.type.little_skin.hint=你需要在皮膚站中加入並使用和該離線帳戶同名角色,此時離線帳戶外觀將為皮膚站上對應角色所設定的外觀。 +account.skin.type.local_file=本機外觀圖片檔案 +account.skin.upload=上傳/編輯外觀 +account.skin.upload.failed=外觀上傳失敗 +account.skin.invalid_skin=無法識別的外觀檔案 account.username=使用者名稱 archive.author=作者 @@ -173,7 +166,7 @@ archive.version=版本 assets.download=下載資源 assets.download_all=驗證資源檔案完整性 -assets.index.malformed=資源檔案的索引檔案損壞,您可以在遊戲 [設定] 頁面右上角的設定按鈕中選擇 [更新遊戲資源檔案],以修復該問題 +assets.index.malformed=資源檔案的索引檔案損壞,你可以在相應實例的「實例管理」頁面中,點擊頁面左下角的「設定 → 更新遊戲資源檔案」以修復該問題。 button.cancel=取消 button.change_source=切換下載源 @@ -187,11 +180,12 @@ button.no=否 button.ok=確定 button.refresh=重新整理 button.remove=刪除 -button.remove.confirm=您確認要刪除嗎?該操作無法撤銷! +button.remove.confirm=你確認要刪除嗎?該操作無法復原! button.retry=重試 button.save=儲存 -button.save_as=另存為 +button.save_as=另存新檔 button.select_all=全選 +button.view=查看 button.yes=是 chat=官方群組 @@ -199,15 +193,15 @@ chat=官方群組 color.recent=建議 color.custom=自訂顏色 -crash.NoClassDefFound=請確認 Hello Minecraft! Launcher 本體是否完整,或更新您的 Java。 -crash.user_fault=您的系統或 Java 環境可能安裝不當導致本軟體當機,請檢查您的 Java 環境或您的電腦! 可以嘗試重新安裝 Java。 +crash.NoClassDefFound=請確認 Hello Minecraft! Launcher 本體是否完整,或更新你的 Java。 +crash.user_fault=你的系統或 Java 環境可能安裝不當導致本軟體當機,請檢查你的 Java 環境或你的電腦!可以嘗試重新安裝 Java。 curse.category.0=全部 # https://addons-ecs.forgesvc.net/api/v2/category/section/4471 curse.category.4474=科幻 curse.category.4481=輕量模組包 -curse.category.4483=戰鬥 PVP +curse.category.4483=戰鬥 PvP curse.category.4477=小遊戲 curse.category.4478=任務 curse.category.4484=多人 @@ -227,15 +221,15 @@ curse.category.7418=恐怖 curse.category.5299=教育 curse.category.5232=額外行星 curse.category.5129=原版增強 -curse.category.5189=實用與QOL +curse.category.5189=實用與 QOL curse.category.6814=性能 -curse.category.6954=動態聯合/集成動力 (Integrated Dynamics) +curse.category.6954=動態聯合/整合動力 (Integrated Dynamics) curse.category.6484=機械動力 (Create) curse.category.6821=錯誤修復 curse.category.6145=空島 -curse.category.5190=QoL -curse.category.5191=實用與QoL -curse.category.5192=夢幻菜單 +curse.category.5190=QOL +curse.category.5191=實用與 QOL +curse.category.5192=夢幻選單 curse.category.423=訊息展示 curse.category.426=模組擴展 curse.category.434=裝備武器 @@ -264,7 +258,7 @@ curse.category.433=林業 (Forestry) curse.category.425=其他 curse.category.4545=應用能源 2 (Applied Energistics 2) curse.category.416=農業 -curse.category.421=支持庫 +curse.category.421=支援庫 curse.category.4780=Fabric curse.category.424=裝飾 curse.category.406=世界生成 @@ -292,7 +286,7 @@ curse.category.393=16x curse.category.403=傳統 curse.category.394=32x curse.category.404=動態效果 -curse.category.4465=模組支持 +curse.category.4465=模組支援 curse.category.402=中世紀風格 curse.category.401=現代風格 @@ -316,219 +310,212 @@ curse.category.4549=指引書 curse.category.4547=配置 curse.category.4550=任務 curse.category.4555=世界生成 -curse.category.4552=腳本 +curse.category.4552=指令碼 curse.sort.author=作者 -curse.sort.date_created=創建日期 +curse.sort.date_created=建立日期 curse.sort.last_updated=最近更新 curse.sort.name=名稱 curse.sort.popularity=熱度 curse.sort.total_downloads=下載量 download=下載 -download.hint=安裝遊戲和整合包或下載模組、資源包和地圖 -download.code.404=遠端伺服器沒有需要下載的檔案: %s +download.hint=安裝遊戲和模組包或下載模組、資源包和地圖 +download.code.404=遠端伺服器沒有需要下載的檔案:%s download.content=遊戲內容 -download.curseforge.unavailable=HMCL 預覽版暫不支持訪問 CurseForge,請使用穩定版或測試版進行下載。 -download.existing=檔案已存在,無法保存。你可以選擇另存為將檔案保存至其他地方。 +download.curseforge.unavailable=HMCL 預覽版暫不支援訪問 CurseForge,請使用穩定版或開發版進行下載。 +download.existing=檔案已存在,無法儲存。你可以將檔案儲存至其他地方。 download.external_link=打開下載網站 -download.failed=下載失敗: %1$s,錯誤碼:%2$d -download.failed.empty=沒有能安裝的版本,按一下此處返回。 -download.failed.no_code=下載失敗: %s -download.failed.refresh=載入版本列表失敗,按一下此處重試。 +download.failed=下載失敗:%1$s\n錯誤碼:%2$d +download.failed.empty=沒有可供安裝的版本,按一下此處返回。 +download.failed.no_code=下載失敗 +download.failed.refresh=載入版本清單失敗,按一下此處重試。 download.game=新遊戲 -download.provider.bmclapi=BMCLAPI (bangbang93,https://bmclapi2.bangbang93.com/) -download.provider.mojang=官方伺服器 (OptiFine 自動安裝的下載來源是 BMCLAPI) -download.provider.official=儘量使用官方源(最新,但可能加載慢) -download.provider.balanced=選擇加載速度快的下載源(平衡,但可能不是最新) -download.provider.mirror=儘量使用鏡像源(加載快,但可能不是最新) +download.provider.bmclapi=BMCLAPI (bangbang93, https://bmclapi2.bangbang93.com/) +download.provider.mojang=官方伺服器 (OptiFine 由 BMCLAPI 提供) +download.provider.official=盡量使用官方源 (最新,但可能載入慢) +download.provider.balanced=選取載入速度快的下載源 (平衡,但可能不是最新) +download.provider.mirror=盡量使用鏡像源 (載入快,但可能不是最新) download.java=下載 Java download.java.override=此 Java 版本已經存在,是否移除並重新安裝? -download.javafx=正在下載必要的運行時組件 -download.javafx.notes=正在通過網絡下載 HMCL 必要的運行時組件。\n點擊“切換下載源”按鈕查看詳情以及選擇下載源,點擊“取消”按鈕停止並退出。\n注意:如果下載速度過慢,請嘗試切换下載源。 -download.javafx.component=正在下載模塊 %s +download.javafx=正在下載必要的執行時元件 +download.javafx.notes=正在透過網路下載 HMCL 必要的執行時元件。\n點擊「切換下載源」按鈕查看詳情以及選取下載源,點擊「取消」按鈕停止並退出。\n注意:如果下載速度過慢,請嘗試切換下載源。 +download.javafx.component=正在下載元件「%s」 download.javafx.prepare=準備開始下載 -exception.access_denied=因為無法存取檔案 %s,HMCL 沒有對該文件的訪問權限,或者該文件被其他程序打開。\n\ - 請你檢查當前操作系統帳戶是否能訪存取檔案,比如非管理員用戶可能不能訪問其他帳戶的個人資料夾內的文件。\n\ - 對於 Windows 用戶,你還可以嘗試通過資源監視器查看是否有程序占用了該文件,如果是,你可以關閉占用此文件相關程序,或者重啟電腦再試。 -exception.artifact_malformed=下載的文件正確,無法通過校驗。 -exception.ssl_handshake=無法建立 SSL 連接,因為當前 Java 虛擬機缺少相關的 SSL 證書。你可以嘗試使用其他的 Java 虛擬機或關閉網絡代理啟動 HMCL 再試。 +exception.access_denied=無法存取檔案「%s」,因為 HMCL 沒有對該檔案的訪問權限,或者該檔案已被其他程式打開。\n\ + 請你檢查目前作業系統帳戶是否能訪存取檔案,比如非管理員使用者可能不能訪問其他帳戶的個人目錄內的檔案。\n\ + 對於 Windows 使用者,你還可以嘗試透過資源監視器查看是否有程式占用了該檔案,如果是,你可以關閉占用此檔案的程式,或者重啟電腦再試。 +exception.artifact_malformed=下載的檔案正確,但無法透過校驗。 +exception.ssl_handshake=無法建立 SSL 連線,因為目前 Java 虛擬機缺少相關的 SSL 證書。你可以嘗試使用其他的 Java 虛擬機或關閉網路代理開啟 HMCL 再試。 -extension.bat=Windows 指令碼 +extension.bat=Windows 批次檔 extension.mod=模組檔案 extension.png=圖片檔案 extension.ps1=PowerShell 指令碼 extension.sh=Bash 指令碼 -fatal.fractureiser=Hello Minecraft! Launcher 檢測到你的電腦被 Fractureiser 病毒感染,存在嚴重安全問題。\n請立即使用殺毒軟體進行全盤查殺,隨後修改你在此電腦上登入過的所有帳號的密碼。 -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。\n你可以訪問 https://docs.hmcl.net/help.html 頁面尋求幫助。 -fatal.config_change_owner_root=你正在使用 root 帳戶啟動 Hello Minecraft! Launcher,這可能導致你未來無法使用其他帳戶正常啟動 Hello Minecraft! Launcher。\n是否繼續啟動? -fatal.config_in_temp_dir=你正在臨時資料夾中啟動 Hello Minecraft! Launcher,你的設定和遊戲數據可能會遺失,建議將 HMCL 移動至其他位置再啟動。\n是否繼續啟動? -fatal.config_loading_failure=Hello Minecraft! Launcher 無法載入設定檔案。\n請確保 Hello Minecraft! Launcher 對 "%s" 目錄及該目錄下的檔案擁有讀寫權限。 -fatal.config_loading_failure.unix=Hello Minecraft! Launcher 無法載入設定檔案,因為設定檔案是由用戶 %1$s 創建的。\n請使用 root 帳戶啟動 HMCL (不推薦),或在終端中執行以下命令將設定檔案的所有權變更為當前用戶:\n%2$s -fatal.mac_app_translocation=由於 macOS 的安全機制,Hello Minecraft! Launcher 被系統隔離至臨時資料夾中。\n請將Hello Minecraft! Launcher 移動到其他資料夾後再嘗試啟動,否則你的設定和遊戲數據可能會在重啟後遺失。\n是否繼續啟動? -fatal.migration_requires_manual_reboot=Hello Minecraft! Launcher 即將升級完成,請重新開啟 Hello Minecraft! Launcher。 -fatal.apply_update_failure=我們很抱歉 Hello Minecraft! Launcher 無法自動完成升級程式,因為出現了一些問題。\n但你依然可以從 %s 處手動下載 Hello Minecraft! Launcher 來完成升級。\n你可以訪問 https://docs.hmcl.net/help.html 網頁進行迴響。 -fatal.apply_update_need_win7=Hello Minecraft! Launcher 無法在 Windows XP/Vista 上進行自動更新,請從 %s 處手動下載 Hello Minecraft! Launcher 來完成升級。 -fatal.samba=如果您正在通過 Samba 共亯的資料夾中運行 Hello Minecraft! Launcher,啟動器可能無法正常工作,請嘗試更新您的 Java 或在本地資料夾內運行 HMCL。 -fatal.illegal_char=由於您的用戶資料夾路徑中存在非法字元‘=’,您將無法使用外置登入帳戶以及離線登入更換皮膚功能。 -fatal.unsupported_platform=Minecraft 尚未你您的平臺提供完善支持,所以可能影響遊戲體驗或無法啟動遊戲。\n若無法啟動 Minecraft 1.17 及以上版本,可以嘗試在版本設定中打開“使用 OpenGL 軟渲染器”選項,使用 CPU 渲染以獲得更好的相容性。 +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是否繼續開啟? +fatal.config_in_temp_dir=你正在暫存目錄中開啟 Hello Minecraft! Launcher,你的設定和遊戲資料可能會遺失,建議將 HMCL 移動至其他位置再開啟。\n是否繼續開啟? +fatal.config_loading_failure=Hello Minecraft! Launcher 無法載入設定檔案。\n請確保 HMCL 對「%s」目錄及該目錄下的檔案擁有讀寫權限。 +fatal.config_loading_failure.unix=Hello Minecraft! Launcher 無法載入設定檔案,因為設定檔案是由使用者「%1$s」建立的。\n請使用 root 帳戶開啟 HMCL (不推薦),或在終端中執行以下指令將設定檔案的所有權變更為目前使用者:\n%2$s +fatal.mac_app_translocation=由於 macOS 的安全機制,Hello Minecraft! Launcher 被系統隔離至暫存目錄中。\n請將 HMCL 移動到其他目錄後再嘗試開啟,否則你的設定和遊戲資料可能會在重啟後遺失。\n是否繼續開啟? +fatal.migration_requires_manual_reboot=Hello Minecraft! Launcher 即將升級完成,請重新開啟 HMCL。 +fatal.apply_update_failure=我們很抱歉 Hello Minecraft! Launcher 無法自動完成升級程式,因為出現了一些問題。\n但你依然可以從 %s 處手動下載 HMCL 來完成升級。 +fatal.apply_update_need_win7=Hello Minecraft! Launcher 無法在 Windows XP/Vista 上進行自動更新,請從 %s 處手動下載 HMCL 來完成升級。 +fatal.samba=如果您正在透過 Samba 共亯的目錄中開啟 Hello Minecraft! Launcher,啟動器可能無法正常工作,請嘗試更新您的 Java 或在本機目錄內開啟 HMCL。 +fatal.illegal_char=由於您的使用者目錄路徑中存在無效字元『=』,您將無法使用外部登入帳戶以及離線登入更換外觀功能。 +fatal.unsupported_platform=Minecraft 尚未你您的平臺提供完善支援,所以可能影響遊戲體驗或無法啟動遊戲。\n若無法啟動 Minecraft 1.17 及更高版本,可以嘗試在「(全域/實例特定) 遊戲設定 → 進階設定 → 除錯選項」中將「繪製器」切換為「軟繪製器」,以獲得更好的相容性。 fatal.unsupported_platform.loongarch=Hello Minecraft! Launcher 已為龍芯提供支援。\n如果遇到問題,你可以點擊右上角幫助按鈕進行求助。 fatal.unsupported_platform.osx_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如果你正在使用高通平臺,你可能需要安裝 OpenGL 相容包後才能進行遊戲。點擊連結前往 Microsoft Store 安裝相容包。 +fatal.unsupported_platform.windows_arm64=Hello Minecraft! Launcher 已為 Windows on Arm 平臺提供原生支援。如果你在遊戲中遭遇問題,請嘗試使用 x86 架構的 Java 啟動遊戲。\n\n如果你正在使用高通平臺,你可能需要安裝 OpenGL 相容包後才能進行遊戲。點擊連結前往 Microsoft Store 安裝相容包。 -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 用戶群,加入後請遵守群規。 +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=設定資料夾 -folder.game=遊戲資料夾 -folder.logs=日誌文件夾 -folder.mod=MOD 模組資料夾 -folder.resourcepacks=資源包資料夾 -folder.shaderpacks=著色器包文件夾 -folder.saves=遊戲存檔資料夾 -folder.screenshots=截圖資料夾 +folder.config=模組設定目錄 +folder.game=實例執行目錄 +folder.logs=日誌目錄 +folder.mod=模組目錄 +folder.resourcepacks=資源包目錄 +folder.shaderpacks=著色器包目錄 +folder.saves=遊戲存檔目錄 +folder.screenshots=截圖目錄 game=遊戲 -game.crash.feedback=請不要將本界面截圖給他人! 如果你要求助他人,請你點擊左下角 導出遊戲崩潰信息 後將導出的文件發送給他人以供分析。\n你可以點擊下方的 幫助 前往社區尋求幫助。 +game.crash.feedback=請不要將本介面截圖給他人!如果你要求助他人,請你點擊左下角「匯出遊戲崩潰資訊」後將匯出的檔案發送給他人以供分析。\n你可以點擊下方的「幫助」前往社群尋求幫助。 game.crash.info=遊戲訊息 game.crash.reason=崩潰原因 -game.crash.reason.analyzing=分析中... +game.crash.reason.analyzing=分析中…… game.crash.reason.multiple=檢測到多個原因:\n\n -game.crash.reason.block=當前遊戲因為某個方塊不能正常工作,無法繼續運行。\n你可以嘗試通過 MCEdit 工具編輯存檔刪除該方塊,或者直接刪除相應的 Mod。\n方塊類型:%1$s\n方塊坐標:%2$s -game.crash.reason.bootstrap_failed=當前遊戲因為模組 %1$s 錯誤,無法繼續運行。\n你可以嘗試刪除或更新該模組以解決問題。 -game.crash.reason.mixin_apply_mod_failed=當前遊戲因為 Mixin 無法應用 %1$s 模組,無法繼續運行。\n你可以嘗試刪除或更新該 Mod 以解決問題。 -game.crash.reason.config=當前遊戲因為無法解析模組配置文件,無法繼續運行\n模組 %1$s 的配置文件 %2$s 無法被解析。 -game.crash.reason.debug_crash=當前遊戲因為手動觸發崩潰,無法繼續運行。\n事實上遊戲並沒有問題,問題都是你造成的。 -game.crash.reason.duplicated_mod=當前遊戲因為 Mod 重複安裝,無法繼續運行。\n%s\n每種 Mod 只能安裝一個,請你刪除多餘的 Mod 再試。 -game.crash.reason.entity=當前遊戲因為某個實體不能正常工作,無法繼續運行。\n你可以嘗試通過 MCEdit 工具編輯存檔刪除該實體,或者直接刪除相應的 Mod。\n實體類型:%1$s\n實體坐標:%2$s -game.crash.reason.fabric_version_0_12=Fabric 0.12 及以上版本與當前已經安裝的 Mod 可能不相容,你需要將 Fabric 降級至 0.11.7。 +game.crash.reason.block=目前遊戲由於某個方塊不能正常工作,無法繼續執行。\n你可以嘗試透過 MCEdit 工具編輯存檔刪除該方塊,或者直接刪除相應的模組。\n方塊類型:%1$s\n方塊坐標:%2$s +game.crash.reason.bootstrap_failed=目前遊戲由於模組「%1$s」出現問題,無法繼續執行。\n你可以嘗試刪除或更新該模組以解決問題。 +game.crash.reason.mixin_apply_mod_failed=目前遊戲由於 Mixin 無法應用於「%1$s」模組,無法繼續執行。\n你可以嘗試刪除或更新該模組以解決問題。 +game.crash.reason.config=目前遊戲由於無法解析模組配置檔案,無法繼續執行\n模組「%1$s」的配置檔案「%2$s」無法被解析。 +game.crash.reason.debug_crash=目前遊戲由於手動觸發崩潰,無法繼續執行。\n事實上遊戲並沒有問題,問題都是你造成的! +game.crash.reason.duplicated_mod=目前遊戲由於模組重複安裝,無法繼續執行。\n%s\n每種模組只能安裝一個,請你刪除多餘的模組再試。 +game.crash.reason.entity=目前遊戲由於某個實體不能正常工作,無法繼續執行。\n你可以嘗試透過 MCEdit 工具編輯存檔刪除該實體,或者直接刪除相應的模組。\n實體類型:%1$s\n實體坐標:%2$s +game.crash.reason.fabric_version_0_12=Fabric Loader 0.12 及更高版本與目前已經安裝的模組可能不相容,你需要將 Fabric Loader 降級至 0.11.7。 game.crash.reason.fabric_warnings=Fabric 提供了一些警告訊息:\n%1$s -game.crash.reason.modmixin_failure=當前遊戲因為某些 Mod 注入失敗,無法繼續運行。\n這一般代表著該 Mod 存在 Bug,或與當前環境不兼容。\n你可以查看日誌尋找出錯模組。 -game.crash.reason.file_or_content_verification_failed=當前遊戲因為部分文件或內容校驗失敗,導致遊戲出現了問題。\n請嘗試刪除該版本(包括 Mod)並重新下載,或嘗試在重新下載時使用代理等。 -game.crash.reason.mod_repeat_installation=當前遊戲因為重複安裝了多個相同的 Mod,每個 Mod 只能出現一次,請刪除重複的 Mod,然後再啟動遊戲。 -game.crash.reason.forge_error=Forge 可能已經提供了錯誤信息。\n你可以查看日誌,並根據錯誤報告中的日誌信息進行對應處。\n如果沒有看到報錯信息,可以查看錯誤報告了解錯誤具體是如何發生的。\n%1$s -game.crash.reason.mod_resolution0=當前遊戲因為一些 Mod 出現問題,無法繼續運行。\n你可以查看日誌尋找出錯模組。 -game.crash.reason.need_jdk11=當前遊戲因為 Java 虛擬機版本不合適,無法繼續運行。\n你需要下載安裝 Java 11,並在全局(特定)遊戲設置中將 Java 設置為 11 開頭的版本。 -game.crash.reason.mod_profile_causes_game_crash=當前遊戲因為 Mod 配置文件出現問題,無法繼續運行。\n你可以查看日誌尋找出錯模組及其配置文件。 -game.crash.reason.fabric_reports_an_error_and_gives_a_solution=Forge 可能已經提供了錯誤信息。\n你可以查看日誌,並根據錯誤報告中的日誌信息進行對應處。\n如果沒有看到報錯信息,可以查看錯誤報告了解錯誤具體是如何發生的。 -game.crash.reason.java_version_is_too_high=當前遊戲因為使用的 Java 版本過高而崩潰了,無法繼續運行。\n請在 全局遊戲設置 或 遊戲特定設置 的 Java 路徑選項卡中改用較低版本的 Java,然後再啟動遊戲。\n如果沒有,可以從 java.com(Java8)BellSoft Liberica Full JRE(Java17) 等平台下載、安裝一個(安裝完後需重啟啟動器)。 -game.crash.reason.mod_name=當前遊戲因為 Mod 檔案名稱問題,無法繼續運行。\nMod 檔案名稱應只使用英文全半型的大小寫字母(Aa~Zz)、數位(0~9)、橫線(-)、底線(_)和點(.)。\n請到Mod資料夾中將所有不合規的Mod檔案名稱添加一個上述的合規的字元。 -game.crash.reason.incomplete_forge_installation=當前遊戲因為 Forge / NeoForge 安裝不完整,無法繼續運行。\n請在 版本設置 - 自動安裝 中移除 Forge / NeoForge 並重新安裝。 -game.crash.reason.file_already_exists=當前遊戲因為文件 %1$s 已經存在,無法繼續運行。\n如果你認為這個文件可以刪除,你可以在備份這個文件後嘗試刪除它,並重新啟動遊戲。 -game.crash.reason.file_changed=當前遊戲因為檔案校驗失敗,無法繼續運行。\n如果你手動修改了 Minecraft.jar 檔案,你需要回退修改,或者重新下載遊戲。 -game.crash.reason.gl_operation_failure=當前遊戲因為你使用的某些 Mod、光影包、材質包,無法繼續運行。\n請先嘗試禁用你所使用的Mod/光影包/材質包再試。 -game.crash.reason.graphics_driver=當前遊戲因為你的顯示卡驅動存在問題崩潰了,請嘗試升級你的顯示卡驅動到最新版本後再嘗試啟動遊戲。\n\ - 如果你的電腦存在獨立顯示卡,你需要檢查遊戲是否使用集成/核芯顯示卡啟動。如果是,請嘗試使用獨立顯示卡啟動 HMCL 與遊戲。如果仍有問題,你可能需要考慮換一個新顯示卡或新電腦。\n\ - 如果你確實需要使用核芯顯示卡,請檢查你的電腦的 CPU 是否是 Intel(R) Core(TM) 3000 系列或更舊的處理器,如果是,對於 Minecraft 1.16.5 及更舊版本,請你將遊戲所使用的 Java 版本降級至 1.8.0_51 及以下版本,否則你需要更換獨立顯示卡或新電腦。\n\ - 在版本設定中打開“使用 OpenGL 軟渲染器”選項也可以解决此問題,但打開此選項後在 CPU 效能不足的情况下幀數會顯著降低,僅推薦以調試為目的或應急時開啟。 -game.crash.reason.macos_failed_to_find_service_port_for_display=當前遊戲因為 apple silicon 平台下初始化 opengl 窗口失敗,無法繼續運行。\n對於該問題,HMCL 暫無直接性的解決方案。請您嘗試任意打開一個瀏覽器並全屏,然後再回到 HMCL 啟動遊戲,在彈出遊戲窗口前迅速切回瀏覽器頁面,等待遊戲窗口出現後再切回遊戲窗口。 -game.crash.reason.illegal_access_error=當前遊戲因為某些 Mod 的問題,無法繼續運行。\n如果你認識:%1$s,你可以更新或刪除對應 Mod 再試。 -game.crash.reason.install_mixinbootstrap=當前遊戲因為缺失 MixinBootstrap,無法繼續運行。\n你可以嘗試安裝 MixinBootstrap 解決該問題。若安裝後崩潰,嘗試在該模組的文件名前加入英文“!”嘗試解決。 -game.crash.reason.jdk_9=當前遊戲因為 Java 版本過高,無法繼續運行。\n你需要下載安裝 Java 8,並在遊戲設置中將 Java 設置為 1.8 的版本。 -game.crash.reason.jvm_32bit=當前遊戲因為記憶體分配過大,超過了 32 位 Java 記憶體限制,無法繼續運行。\n如果你的電腦是 64 位系統,請下載安裝並更換 64 位 Java。如果你的電腦室 32 位系統,你或許可以重新安裝 64 位系統,或換一台新電腦。\n或者,你可以關閉遊戲記憶體的自動分配,並且把記憶體限制調節為 1024 MB 或以下。 -game.crash.reason.loading_crashed_forge=當前遊戲因為模組 %1$s (%2$s) 錯誤,無法繼續運行。\n你可以嘗試刪除或更新該模組以解決問題。 -game.crash.reason.loading_crashed_fabric=當前遊戲因為模組 %1$s 錯誤,無法繼續運行。\n你可以嘗試刪除或更新該模組以解決問題。 -game.crash.reason.mac_jdk_8u261=當前遊戲因為你所使用的 Forge 或 OptiFine 與 Java 衝突崩潰。\n請嘗試更新 Forge 和 OptiFine,或使用 Java 8u251 及更早版本啟動。 -game.crash.reason.memory_exceeded=當前遊戲因為分配的記憶體過大,無法繼續運行。\n該問題是由於系統頁面文件太小導致的。\n你需要在遊戲設置中關閉遊戲記憶體的自動分配,並將遊戲記憶體調低至遊戲能正常啟動為止。\n你還可以嘗試調大系統的頁面大小。 -game.crash.reason.mod=當前遊戲因為 %1$s 的問題,無法繼續運行。\n你可以更新或刪除已經安裝的 %1$s 再試。 -game.crash.reason.mod_resolution=當前遊戲因為 Mod 依賴問題,無法繼續運行。Fabric 提供了如下訊息:\n%1$s -game.crash.reason.forgemod_resolution=當前遊戲因為 Mod 依賴問題,無法繼續運行。Forge 提供了如下訊息:\n%1$s -game.crash.reason.forge_found_duplicate_mods=遊戲崩潰原因模組重複的問題,無法繼續運行。Forge 提供了以下信息:\n%1$s -game.crash.reason.mod_resolution_collection=當前遊戲因為前置 Mod 版本不匹配,無法繼續運行。\n%1$s 需要前置 Mod:%2$s 才能繼續運行。\n這表示你需要更新或降級前置。你可以到下載頁的模組下載,或到網路上下載 %3$s。 -game.crash.reason.mod_resolution_conflict=當前遊戲因為 Mod 衝突,無法繼續運行。\n%1$s 與 %2$s 不能相容。 -game.crash.reason.mod_resolution_missing=當前遊戲因為缺少 Mod 前置,無法繼續運行。\n%1$s 需要前置 Mod:%2$s 才能繼續運行。\n這表示你少安裝了 Mod,或該 Mod 版本不夠。你可以到下載頁的模組下載,或到網路上下載 %2$s。 -game.crash.reason.mod_resolution_missing_minecraft=當前遊戲因為 Mod 和 Minecraft 遊戲版本不匹配,無法繼續運行。\n%1$s 需要 Minecraft %2$s 才能運行。\n如果你要繼續使用你已經安裝的 Mod,你可以選擇安裝對應的 Minecraft 版本;如果你要繼續使用當前 Minecraft 版本,你需要安裝對應版本的 Mod。 +game.crash.reason.modmixin_failure=目前遊戲由於某些模組注入失敗,無法繼續執行。\n這一般代表著該模組存在問題,或與目前環境不相容。\n你可以查看日誌尋找出錯模組。 +game.crash.reason.mod_repeat_installation=目前遊戲由於重複安裝了多個相同的模組,無法繼續執行。\n每個模組只能出現一次,請刪除重複的模組,然後再啟動遊戲。 +game.crash.reason.forge_error=Forge/NeoForge 可能已經提供了錯誤資訊。\n你可以查看日誌,並根據錯誤報告中的日誌資訊進行對應處。\n如果沒有看到報錯資訊,可以查看錯誤報告了解錯誤具體是如何發生的。\n%1$s +game.crash.reason.mod_resolution0=目前遊戲由於一些模組出現問題,無法繼續執行。\n你可以查看日誌尋找出錯模組。 +game.crash.reason.need_jdk11=目前遊戲由於 Java 虛擬機版本不合適,無法繼續執行。\n你需要下載安裝 Java 11,並在「(全域/實例特定) 遊戲設定 → 遊戲 Java」中將 Java 設定為 11 開頭的版本。 +game.crash.reason.java_version_is_too_high=目前遊戲由於使用的 Java 版本過高而崩潰了,無法繼續執行。\n請在「(全域/實例特定) 遊戲設定 → 遊戲 Java」中改用較低版本的 Java,然後再啟動遊戲。\n如果沒有,可以從 java.com (Java 8)BellSoft Liberica Full JRE (Java 17) 等平台下載、安裝一個 (安裝完後需重啟啟動器)。 +game.crash.reason.mod_name=目前遊戲由於模組檔案名稱問題,無法繼續執行。\n模組檔案名稱應只使用半型的大小寫字母 (Aa~Zz)、數位 (0~9)、橫線 (-)、底線 (_)和點 (.)。\n請到模組目錄中將所有不合規的模組檔案名稱修正為上述合規字元。 +game.crash.reason.incomplete_forge_installation=目前遊戲由於 Forge 安裝不完整,無法繼續執行。\n請在「實例管理 - 自動安裝」中移除 Forge 並重新安裝。 +game.crash.reason.file_already_exists=目前遊戲由於檔案「%1$s」已經存在,無法繼續執行。\n如果你認為這個檔案可以刪除,你可以在備份這個檔案後嘗試刪除它,並重新啟動遊戲。 +game.crash.reason.file_changed=目前遊戲由於檔案校驗失敗,無法繼續執行。\n如果你手動修改了 Minecraft.jar 檔案,你需要回退修改,或者重新下載遊戲。 +game.crash.reason.gl_operation_failure=目前遊戲由於你使用的某些模組/光影包/資源包出現問題,無法繼續執行。\n請先嘗試禁用你所使用的模組/光影包/資源包再試。 +game.crash.reason.graphics_driver=目前遊戲由於你的顯示卡驅動存在問題崩潰了,請嘗試升級你的顯示卡驅動到最新版本後再嘗試啟動遊戲。\n\ + 如果你的電腦存在獨立顯示卡,你需要檢查遊戲是否使用整合/核芯顯示卡啟動。如果是,請嘗試使用獨立顯示卡開啟 HMCL 與遊戲。如果仍有問題,你可能需要考慮換一個新顯示卡或新電腦。\n\ + 如果你確實需要使用核芯顯示卡,請檢查你電腦的 CPU 是否為 Intel(R) Core(TM) 3000 系列或更舊的處理器,如果是,對於 Minecraft 1.16.5 及更低版本,請你將遊戲所使用的 Java 版本降級至 1.8.0_51 及更低版本,否則你需要更換獨立顯示卡或新電腦。 +game.crash.reason.macos_failed_to_find_service_port_for_display=目前遊戲由於 Apple Silicon 平台下初始化 OpenGL 視窗失敗,無法繼續執行。\n對於該問題,HMCL 暫無直接性的解決方案。請你嘗試任意打開一個瀏覽器並切換為全螢幕,然後再回到 HMCL 啟動遊戲,在彈出遊戲視窗前迅速切回瀏覽器頁面,等待遊戲視窗出現後再切回遊戲視窗。 +game.crash.reason.illegal_access_error=目前遊戲由於某些模組的問題,無法繼續執行。\n如果你認識「%1$s」,你可以更新或刪除對應模組再試。 +game.crash.reason.install_mixinbootstrap=目前遊戲由於缺失 MixinBootstrap,無法繼續執行。\n你可以嘗試安裝 MixinBootstrap 解決該問題。若安裝後崩潰,嘗試在該模組的檔案名前加入半形驚嘆號 (!) 嘗試解決。 +game.crash.reason.jdk_9=目前遊戲由於 Java 版本過高,無法繼續執行。\n你需要下載安裝 Java 8,並在「(全域/實例特定) 遊戲設定 → 遊戲 Java」中將 Java 設定為 1.8 的版本。 +game.crash.reason.jvm_32bit=目前遊戲由於記憶體分配過大,超過了 32 位 Java 記憶體限制,無法繼續執行。\n如果你的電腦是 64 位系統,請下載安裝並更換 64 位 Java。\n如果你的電腦是 32 位系統,你或許可以重新安裝 64 位系統,或換一台新電腦。\n或者,你可以關閉「(全域/實例特定) 遊戲設定 → 遊戲記憶體」中的「自動分配」,並且把記憶體限制調節為 1024 MB 或以下。 +game.crash.reason.loading_crashed_forge=目前遊戲由於模組「%1$s (%2$s)」錯誤,無法繼續執行。\n你可以嘗試刪除或更新該模組以解決問題。 +game.crash.reason.loading_crashed_fabric=目前遊戲由於模組「%1$s」錯誤,無法繼續執行。\n你可以嘗試刪除或更新該模組以解決問題。 +game.crash.reason.mac_jdk_8u261=目前遊戲由於你所使用的 Forge 或 OptiFine 與 Java 衝突而崩潰。\n請嘗試更新 Forge 和 OptiFine,或使用 Java 8u251 及更早版本啟動。 +game.crash.reason.memory_exceeded=目前遊戲由於分配的記憶體過大,無法繼續執行。\n該問題是由於系統頁面檔案太小導致的。\n你需要在「(全域/實例特定) 遊戲設定 → 遊戲記憶體」中關閉「自動分配」,並將遊戲記憶體調低至遊戲能正常啟動為止。\n你還可以嘗試調大系統的頁面大小。 +game.crash.reason.mod=目前遊戲由於 %1$s 的問題,無法繼續執行。\n你可以更新或刪除已經安裝的「%1$s」再試。 +game.crash.reason.mod_resolution=目前遊戲由於相依模組問題,無法繼續執行。Fabric 提供了如下訊息:\n%1$s +game.crash.reason.forgemod_resolution=目前遊戲由於模組相依元件問題,無法繼續執行。Forge/NeoForge 提供了以下資訊:\n%1$s +game.crash.reason.forge_found_duplicate_mods=目前遊戲由於模組重複的問題,無法繼續執行。Forge/NeoForge 提供了以下資訊:\n%1$s +game.crash.reason.mod_resolution_collection=目前遊戲由於相依模組版本不匹配,無法繼續執行。\n「%1$s」需要相依模組「%2$s」才能繼續執行。\n這表示你需要更新或降級相依模組。你可以到「下載 → 模組」頁面下載,或到網路上下載「%3$s」。 +game.crash.reason.mod_resolution_conflict=目前遊戲由於模組衝突,無法繼續執行。\n「%1$s」與「%2$s」不能相容。 +game.crash.reason.mod_resolution_missing=目前遊戲由於缺少相依模組,無法繼續執行。\n「%1$s」需要相依模組「%2$s」才能繼續執行。\n這表示你少安裝了模組,或該模組版本不夠。你可以到「下載 → 模組」頁面下載,或到網路上下載「%2$s」。 +game.crash.reason.mod_resolution_missing_minecraft=目前遊戲由於模組和 Minecraft 遊戲版本不匹配,無法繼續執行。\n「%1$s」需要 Minecraft %2$s 才能執行。\n如果你要繼續使用你已經安裝的模組,你可以選取安裝對應的 Minecraft 版本;如果你要繼續使用目前的 Minecraft 版本,你需要安裝對應版本的模組。 game.crash.reason.mod_resolution_mod_version=%1$s (版本號 %2$s) game.crash.reason.mod_resolution_mod_version.any=%1$s (任意版本) -game.crash.reason.forge_repeat_installation=當前遊戲因為 Forge 重複安裝,無法繼續運行。此為已知問題\n建議將日誌上傳反饋至 GitHub ,以便我們找到更多線索並修復此問題。\n目前你可以到 自動安裝 裡頭移除 Forge 並重新安裝。 -game.crash.reason.optifine_repeat_installation=當前遊戲因為重複安裝 OptiFine,無法繼續運行。 \n請刪除 Mod 文件夾下的 OptiFine 或前往 遊戲管理-自動安裝 移除自動安裝的 OptiFine。 -game.crash.reason.optifine_is_not_compatible_with_forge=當前遊戲因為OptiFine與當前版本的Forge不相容,導致了遊戲崩潰。\n請前往 OptiFine 官網查看 OptiFine 所相容的 Forge 版本,並嚴格按照對應版本重新安裝遊戲或在版本設定-自動安裝中更換版本。\n經測試,Forge版本過高或過低都可能導致崩潰。 -game.crash.reason.night_config_fixes=當前遊戲因為 Night Config 庫的一些問題,無法繼續運行。\n你可以嘗試安裝 Night Config Fixes 模組,這或許能幫助你解決這個問題。\n了解更多,可訪問該模組的 GitHub 倉庫。 -game.crash.reason.mod_files_are_decompressed=當前遊戲因為 Mod 檔案被解壓了,無法繼續運行。\n請直接把整個 Mod 檔案放進 Mod 資料夾中即可。\n若解壓就會導致遊戲出錯,請删除Mod資料夾中已被解壓的Mod,然後再啟動遊戲。 -game.crash.reason.too_many_mods_lead_to_exceeding_the_id_limit=當前遊戲因為您所安裝的 Mod 過多,超出了遊戲的ID限制,無法繼續運行。\n請嘗試安裝JEID等修復Mod,或删除部分大型Mod。 -game.crash.reason.optifine_causes_the_world_to_fail_to_load=當前遊戲因為 Mod 檔案被解壓了,無法繼續運行。\n請直接把整個Mod檔案放進Mod資料夾中即可!\n若解壓就會導致遊戲出錯,請删除Mod資料夾中已被解壓的Mod,然後再啟動遊戲。 -game.crash.reason.modlauncher_8=當前遊戲因為你所使用的 Forge 版本與當前使用的 Java 衝突崩潰,請嘗試更新 Forge。 -game.crash.reason.cannot_find_launch_target_fmlclient=當前遊戲因為 Forge 安裝不完整,無法繼續運行。 \n你可嘗試前往 遊戲管理 - 自動安裝 中選擇 Forge 並重新安裝。 -game.crash.reason.shaders_mod=當前遊戲因為同時安裝了 OptiFine 和 Shaders Mod,無法繼續運行。 \n因為 OptiFine 已集成 Shaders Mod 的功能,只需刪除 Shaders Mod 即可。 -game.crash.reason.rtss_forest_sodium=當前遊戲因為 RivaTuner Statistics Server (RTSS) 與 Sodium 不相容,導致遊戲崩潰。\n點擊 此處 查看詳情。 -game.crash.reason.no_class_def_found_error=當前遊戲因為代碼不完整,無法繼續運行。\n你的遊戲可能缺失了某個 Mod,或者某些 Mod 檔案不完整,或者 Mod 與遊戲的版本不匹配。\n你可能需要重新安裝遊戲和 Mod,或請求他人幫助。\n缺失:%1$s -game.crash.reason.no_such_method_error=當前遊戲因為代碼不完整,無法繼續運行。\n你的遊戲可能缺失了某個 Mod,或者某些 Mod 檔案不完整,或者 Mod 與遊戲的版本不匹配。\n你可能需要重新安裝遊戲和 Mod,或請求他人幫助。 -game.crash.reason.opengl_not_supported=當前遊戲因為你的顯示卡驅動存在問題,無法繼續運行。\n原因是 OpenGL 不受支援,你現在是否在遠程桌面或者串流模式下?如果是,請直接使用原電腦啟動遊戲。\n或者嘗試升級你的顯示卡驅動到最新版本後再嘗試啟動遊戲。如果你的電腦存在獨立顯示卡,你需要檢查遊戲是否使用集成/核心顯示卡啟動,如果是,請嘗試使用獨立顯示卡啟動 HMCL 與遊戲。如果仍有問題,你可能需要考慮換一個新顯示卡或新電腦。 -game.crash.reason.openj9=當前遊戲無法運行在 OpenJ9 虛擬機上,請你在遊戲設置中更換 Hotspot Java 虛擬機,並重新啟動遊戲。如果沒有下載安裝,你可以在網路上自行下載。 -game.crash.reason.out_of_memory=當前遊戲因為記憶體不足,無法繼續運行。\n這可能是記憶體分配太小,或者 Mod 數量過多導致的。\n你可以在遊戲設置中調大遊戲記憶體分配值以允許遊戲在更大的記憶體下運行。\n如果仍然出現該錯誤,你可能需要換一台更好的電腦。 -game.crash.reason.processing_of_javaagent_failed=當前遊戲因為加載 -javaagent 參數失敗,無法繼續運行。\n如果你在 Java 虛擬機參數 中添加了相關參數,請檢查是否正確。\n如果你沒有添加相關參數或參數確認正確, 請嘗試:\n打開 控制面板 -- 時鐘和區域 分類(選項為類別顯示才有此選項,沒有就跳過)-- 區域 -- 上方的 管理 選項卡 -- 下方的 更改系統區域設置 按鈕 -- 在彈出的視窗中將 “使用Unicode UTF-8提供全球語言支持” 選項關閉,重啓設備後再嘗試啟動遊戲。\n可在 DiscordQQ 群尋求幫助 -game.crash.reason.resolution_too_high=當前遊戲因為材質包解析度過高,無法繼續運行\n你可以更換一個解析度更低的材質,或者更換一個視訊記憶體更大的顯示卡。 -game.crash.reason.stacktrace=原因未知,請點擊日誌按鈕查看詳細訊息。\n下面是一些關鍵字,其中可能包含 Mod 名稱,你可以透過搜索的方式查找有關訊息。\n%s -game.crash.reason.too_old_java=當前遊戲因為 Java 虛擬機版本過低,無法繼續運行。\n你需要在遊戲設置中更換 %1$s 或更新版本的 Java 虛擬機,並重新啟動遊戲。如果沒有下載安裝,你可以點擊 此處 下載微軟 JDK。 +game.crash.reason.forge_repeat_installation=目前遊戲由於 Forge 重複安裝,無法繼續執行。此為已知問題\n建議將日誌上傳並回報至 GitHub,以便我們找到更多線索並修復此問題。\n目前你可以在「實例管理 → 自動安裝」中移除 Forge 並重新安裝。 +game.crash.reason.optifine_repeat_installation=目前遊戲由於重複安裝 OptiFine,無法繼續執行。\n請刪除模組目錄下的 OptiFine 或前往「實例管理 → 自動安裝」移除安裝的 OptiFine。 +game.crash.reason.optifine_is_not_compatible_with_forge=目前遊戲由於 OptiFine 與目前版本的 Forge 不相容,導致了遊戲崩潰。\n請前往 OptiFine 官網查看 OptiFine 所相容的 Forge 版本,並嚴格按照對應版本重新安裝遊戲,或在「實例管理 → 自動安裝」中更換版本。\n經測試,Forge 版本過高或過低都可能導致崩潰。 +game.crash.reason.night_config_fixes=目前遊戲由於 Night Config 庫的一些問題,無法繼續執行。\n你可以嘗試安裝 Night Config Fixes 模組,這或許能幫助你解決這個問題。\n了解更多,可訪問該模組的 GitHub 倉庫。 +game.crash.reason.mod_files_are_decompressed=目前遊戲由於模組檔案被解壓了,無法繼續執行。\n請直接把整個模組檔案放進模組目錄中即可。\n若解壓就會導致遊戲出錯,請刪除模組目錄中已被解壓的模組,然後再啟動遊戲。 +game.crash.reason.too_many_mods_lead_to_exceeding_the_id_limit=目前遊戲由於你所安裝的模組過多,超出了遊戲的 ID 限制,無法繼續執行。\n請嘗試安裝JEID等修正模組,或刪除部分大型模組。 +game.crash.reason.optifine_causes_the_world_to_fail_to_load=目前遊戲可能由於 OptiFine 而無法繼續執行。\n該問題只在特定 OptiFine 版本中出現,你可以嘗試在「實例管理 → 自動安裝」中更換 OptiFine 的版本。 +game.crash.reason.modlauncher_8=目前遊戲由於你所使用的 Forge 版本與目前使用的 Java 衝突崩潰,請嘗試更新 Forge。 +game.crash.reason.shaders_mod=目前遊戲由於同時安裝了 OptiFine 和 Shaders 模組,無法繼續執行。 \n由於 OptiFine 已整合 Shaders 模組的功能,只需刪除 Shaders 模組即可。 +game.crash.reason.rtss_forest_sodium=目前遊戲由於 RivaTuner Statistics Server (RTSS) 與 Sodium 不相容,導致遊戲崩潰。\n點擊 此處 查看詳情。 +game.crash.reason.no_class_def_found_error=目前遊戲由於程式碼不完整,無法繼續執行。\n你的遊戲可能缺失了某個模組,或者某些模組檔案不完整,或者模組與遊戲的版本不匹配。\n你可能需要重新安裝遊戲和模組,或請求他人幫助。\n缺失:%1$s +game.crash.reason.no_such_method_error=目前遊戲由於程式碼不完整,無法繼續執行。\n你的遊戲可能缺失了某個模組,或者某些模組檔案不完整,或者模組與遊戲的版本不匹配。\n你可能需要重新安裝遊戲和模組,或請求他人幫助。 +game.crash.reason.opengl_not_supported=目前遊戲由於你的顯示卡驅動存在問題,無法繼續執行。\n原因是 OpenGL 不受支援,你現在是否在遠端桌面或者串流模式下?如果是,請直接使用原電腦啟動遊戲。\n或者嘗試升級你的顯示卡驅動到最新版本後再嘗試啟動遊戲。如果你的電腦存在獨立顯示卡,你需要檢查遊戲是否使用整合/核心顯示卡啟動,如果是,請嘗試使用獨立顯示卡開啟 HMCL 與遊戲。如果仍有問題,你可能需要考慮換一個新顯示卡或新電腦。 +game.crash.reason.openj9=目前遊戲無法執行在 OpenJ9 虛擬機上,請你在「(全域/實例特定) 遊戲設定 → 遊戲 Java」中更換 Hotspot Java 虛擬機,並重新啟動遊戲。如果沒有下載安裝,你可以在網路上自行下載。 +game.crash.reason.out_of_memory=目前遊戲由於記憶體不足,無法繼續執行。\n這可能是記憶體分配太小,或者模組數量過多導致的。\n你可以在「(全域/實例特定) 遊戲設定 → 遊戲記憶體」中調大遊戲記憶體分配值以允許遊戲在更大的記憶體下執行。\n如果仍然出現該錯誤,你可能需要換一台更好的電腦。 +game.crash.reason.resolution_too_high=目前遊戲由於資源包解析度過高,無法繼續執行。\n你可以更換一個解析度更低的資源包,或者更換一個視訊記憶體更大的顯示卡。 +game.crash.reason.stacktrace=原因未知,請點擊日誌按鈕查看詳細訊息。\n下面是一些關鍵字,其中可能包含模組名稱,你可以透過搜尋的方式尋找有關訊息。\n%s +game.crash.reason.too_old_java=目前遊戲由於 Java 虛擬機版本過低,無法繼續執行。\n你需要在「(全域/實例特定) 遊戲設定 → 遊戲 Java」中更換 %1$s 或更高版本的 Java 虛擬機,並重新啟動遊戲。如果沒有下載安裝,你可以點擊 此處 下載 Microsoft JDK。 game.crash.reason.unknown=原因未知,請點擊日誌按鈕查看詳細訊息。 -game.crash.reason.unsatisfied_link_error=當前遊戲因為缺少本地庫,無法繼續運行。\n這些本地庫缺失:%1$s。\n如果你在全局(特定)遊戲設置中修改了本地庫路徑選項,請你修改回預設模式。\n如果已經在預設模式下,請檢查本地庫缺失是否是 Mod 引起的,或由 HMCL 引起的。如果你確定是 HMCL 引起的,建議你向我們反饋。\n或將游戲路徑中的所有非英文字符的名稱修改為英文字符(例如中文,空格等)\n如果你確實需要自定義本地庫路徑,你需要保證其中包含缺失的本地庫! -game.crash.reason.failed_to_load_a_library=當前遊戲因為加載本地庫失敗,無法繼續運行。\n如果你在全局(特定)遊戲設置中修改了本地庫路徑選項,請你修改回預設模式。\n如果已經在預設模式下,請檢查本地庫缺失是否是 Mod 引起的,或由 HMCL 引起的。如果你確定是 HMCL 引起的,建議你向我們反饋。\n或將游戲路徑中的所有非英文字符的名稱修改為英文字符(例如中文,空格等)\n如果你確實需要自定義本地庫路徑,你需要保證其中包含缺失的本地庫! +game.crash.reason.unsatisfied_link_error=目前遊戲由於缺少本機庫,無法繼續執行。\n這些本機庫缺失:%1$s。\n如果你在「(全域/實例特定) 遊戲設定 → 進階設定」中修改了本機庫路徑選項,請你修改回預設模式。\n如果你正在使用預設模式,請檢查遊戲目錄路徑是否只包含英文字母、數字和底線,\n如果是,那麼請檢查是否為模組或 HMCL 導致了本機庫缺失的問題。如果你確定是 HMCL 引起的,建議你向我們回報。\n對於 Windows 使用者,你還可以嘗試在「控制台 → 時鐘和區域 → 地區 → 系統管理 → 變更系統區域設定」中,關閉「Beta:使用 Unicode UTF-8 提供全球語言支援」選項;\n或將遊戲目錄路徑中的所有非英文字母的名稱 (例如中文、空格等) 修改為英文字母。\n如果你確實需要自訂本機庫路徑,你需要保證其中包含缺失的本機庫! game.crash.title=遊戲意外退出 -game.directory=遊戲路徑 +game.directory=遊戲目錄路徑 game.version=遊戲版本 help=說明 -help.doc=Hello Minecraft! Launcher 說明檔案 -help.detail=可查閱資料包、模組包製作指南等內容。 +help.doc=Hello Minecraft! Launcher 說明文件 +help.detail=可查閱資料包、模組包製作教學等內容 input.email=[使用者名稱] 必須是電子信箱格式 input.number=必須是數字 input.not_empty=必填 input.url=必須是有效連結 -install=新增遊戲 +install=新增實例 install.change_version=變更版本 -install.change_version.confirm=你確定要 %s 從 %s 更新到 %s 嗎? +install.change_version.confirm=你確定要將 %s 從 %s 更新到 %s 嗎? install.failed=安裝失敗 install.failed.downloading=安裝失敗,部分檔案未能完成下載 install.failed.downloading.detail=未能下載檔案: %s install.failed.downloading.timeout=下載逾時: %s -install.failed.install_online=無法識別要安裝的軟體。如果你要安裝 Mod,你需要在模組管理頁面安裝模組。 -install.failed.malformed=剛才下載的檔案格式損壞。您可以切換到其他下載來源以解決此問題。 -install.failed.optifine_conflict=暫不支援 OptiFine 與 Forge 同時安裝在 Minecraft 1.13 上 -install.failed.optifine_forge_1.17=Minecraft 1.17.1 下,僅 OptiFine H1 Pre2 及以上版本能相容 Forge。你可以從 OptiFine 測試版中選擇最新版本。 -install.failed.version_mismatch=該軟體需要的遊戲版本為 %s,但實際的遊戲版本為 %s。 -install.installer.change_version=%s 與當前遊戲不相容,請更換版本 -install.installer.choose=選擇 %s 版本 +install.failed.install_online=無法識別要安裝的載入器。如果你要安裝模組,你需要在模組管理頁面安裝模組。 +install.failed.malformed=剛才下載的檔案已損壞。你可以在「設定 → 下載 → 下載來源」中切換其他下載來源以解決此問題。 +install.failed.optifine_conflict=暫不支援 OptiFine 與 Fabric 同時安裝在 Minecraft 1.13 上。 +install.failed.optifine_forge_1.17=對於 Minecraft 1.17.1 版本,僅 OptiFine H1 pre2 及更高版本與 Forge 相容。你可以從 OptiFine 預覽版 (Preview versions) 中選取最新版本。 +install.failed.version_mismatch=該載入器需要的遊戲版本為 %s,但實際的遊戲版本為 %s。 +install.installer.change_version=%s 與目前遊戲不相容,請更換版本 +install.installer.choose=選取 %s 版本 install.installer.depend=需要先安裝 %s install.installer.fabric=Fabric install.installer.fabric-api=Fabric API -install.installer.fabric-api.warning=警告:Fabric API 是 Mod,將會被安裝到新遊戲的 Mod 資料夾,請你在安裝遊戲後不要修改當前遊戲的版本隔離/遊戲運行路徑設置,如果你在之後修改了相關設置,Fabric API 需要被重新安裝。 +install.installer.fabric-api.warning=警告:Fabric API 是模組,將會被安裝到新遊戲的模組目錄,請你在安裝遊戲後不要修改目前遊戲的「執行路徑」設定,如果你在之後修改了相關設定,則需要重新安裝 Fabric API。 install.installer.forge=Forge install.installer.neoforge=NeoForge install.installer.game=Minecraft install.installer.incompatible=與 %s 不相容 install.installer.install=安裝 %s install.installer.install_offline=從本機檔案安裝或升級 -install.installer.install_offline.extension=Forge/OptiFine 安裝器 -install.installer.install_offline.tooltip=支援匯入已經下載好的 Forge/OptiFine 安裝器 +install.installer.install_offline.extension=(Neo)Forge/OptiFine 安裝器 +install.installer.install_offline.tooltip=支援匯入已經下載好的 (Neo)Forge/OptiFine 安裝器 install.installer.install_online=線上安裝 -install.installer.install_online.tooltip=支援安裝 Fabric、Forge、OptiFine、LiteLoader +install.installer.install_online.tooltip=支援安裝 Forge、NeoForge、Fabric、Quilt、LiteLoader 和 OptiFine install.installer.liteloader=LiteLoader install.installer.not_installed=不安裝 install.installer.optifine=OptiFine install.installer.quilt=Quilt install.installer.quilt-api=QSL/QFAPI install.installer.version=%s -install.installer.external_version=%s 由外部安裝的版本,無法解除安裝或更換 +install.installer.external_version=%s [由外部安裝的版本,無法解除安裝或更換] install.modpack=安裝模組包 -install.new_game=安裝新遊戲版本 -install.new_game.already_exists=此版本已經存在,請重新命名 +install.name.invalid=名稱中包含非 ASCII 字元(如 Emoji 表情或中文字元)。\n建議修改名稱,名稱建議僅包含英文字母、數字和底線,以防啟動遊戲時出現問題。是否繼續安裝? +install.new_game=安裝新實例 +install.new_game.already_exists=此實例已經存在,請重新命名 install.new_game.current_game_version=目前遊戲版本 install.new_game.malformed=名稱無效 -install.select=請選擇安裝方式 +install.select=請選取安裝方式 install.success=安裝成功 java.add=添加 Java @@ -539,6 +526,7 @@ java.disabled.management=管理已禁用的 Java java.disabled.management.remove=從清單中移除此 Java java.disabled.management.restore=重新啟用此 Java java.download=下載 Java +java.download.banshanjdk-8=下載 Banshan JDK 8 java.download.load_list.failed=載入版本清單失敗 java.download.more=更多發行版 java.download.prompt=請選擇你要下載的 Java 版本: @@ -554,161 +542,160 @@ java.install=安裝 Java java.install.archive=源路徑 java.install.failed.exists=該名稱已被使用 java.install.failed.invalid=該檔案不是合法的 Java 安裝包,無法繼續安裝。 -java.install.failed.unsupported_platform=此 Java 與當前平臺不相容,無法安裝。 +java.install.failed.unsupported_platform=此 Java 與目前平臺不相容,無法安裝。 java.install.name=名稱 -java.install.warning.invalid_character=名稱中包含非法字元 +java.install.warning.invalid_character=名稱中包含無效字元 java.reveal=瀏覽 Java 目錄 java.uninstall=移除此 Java java.uninstall.confirm=你確定要移除此 Java 嗎?此操作無法復原! -lang=正體中文 +lang=繁體中文 lang.default=使用系統語言 -launch.advice=%s是否繼續啟動? +launch.advice=%s 是否繼續啟動? launch.advice.multi=檢測到以下問題:\n\n%s\n\n這些問題可能導致遊戲無法正常啟動或影響遊戲體驗,是否繼續啟動? -launch.advice.java.auto=當前選擇的 Java 虛擬機版本不滿足遊戲要求,是否自動選擇合適的 Java 虛擬機版本?或者你可以到遊戲設置中選擇一個合適的 Java 虛擬機版本。 -launch.advice.java.modded_java_7=Minecraft 1.7.2 及以下版本需要 Java 7 及以下版本。 -launch.advice.corrected=我們已經修正了問題。如果確實希望使用你自訂的 Java 虛擬機,你可以在遊戲設定中關閉 Java 虛擬機相容性檢查。 -launch.advice.uncorrected=如果你確實希望使用你自訂的 Java 虛擬機,你可以在遊戲設定中關閉 Java 虛擬機相容性檢查。 +launch.advice.java.auto=目前選取的 Java 虛擬機版本不滿足遊戲要求,是否自動選取合適的 Java 虛擬機版本?\n或者你可以到「(全域/實例特定) 遊戲設定 → 遊戲 Java」中選取一個合適的 Java 虛擬機版本。 +launch.advice.java.modded_java_7=Minecraft 1.7.2 及更低版本需要 Java 7 及更低版本。 +launch.advice.corrected=我們已經修正了問題。如果你確實希望使用你自訂的 Java 虛擬機,你可以在「(全域/實例特定) 遊戲設定 → 進階設定」中往下滑,開啟「不檢查 JVM 與遊戲的相容性」。 +launch.advice.uncorrected=如果你確實希望使用你自訂的 Java 虛擬機,你可以在「(全域/實例特定) 遊戲設定 → 進階設定」中往下滑,開啟「不檢查 JVM 與遊戲的相容性」。 launch.advice.different_platform=你正在使用 32 位元 Java 啟動遊戲,建議更換至 64 位元 Java。 -launch.advice.forge2760_liteloader=Forge 2760 與 LiteLoader 不相容,請更新 Forge 到 2773 或更新的版本。 -launch.advice.forge28_2_2_optifine=Forge 28.2.2 或更高版本與 OptiFine 不相容,請将 Forge 降級至 28.2.1 或更低版本。 -launch.advice.forge37_0_60=Forge 低於 37.0.60 的版本不相容 Java 17。請更新 Forge 到 37.0.60 或更高版本,或者使用 Java 16 啟動遊戲。 -launch.advice.java8_1_13=Minecraft 1.13 只支援 Java 8 或更高版本,請使用 Java 8 或最新版本。 +launch.advice.forge2760_liteloader=Forge 14.23.5.2760 與 LiteLoader 不相容,請更新 Forge 至 14.23.5.2773 或更高版本。 +launch.advice.forge28_2_2_optifine=Forge 28.2.2 及更高版本與 OptiFine 不相容,請降級 Forge 至 28.2.1 或更低版本。 +launch.advice.forge37_0_60=Forge 37.0.59 及更低版本與 Java 17 不相容。請更新 Forge 到 37.0.60 或更高版本,或者使用 Java 16 啟動遊戲。 +launch.advice.java8_1_13=Minecraft 1.13 及更高版本僅支援 Java 8 或更高版本,請使用 Java 8 或最新版本。 launch.advice.java8_51_1_13=低於 1.8.0_51 的 Java 版本可能會導致 Minecraft 1.13 崩潰。建議你到 https://java.com 安裝最新版的 Java 8。 -launch.advice.java9=低於 (包含) 1.13 的有安裝 Mod 的 Minecraft 版本不支援 Java 9 或更高版本,請使用 Java 8。 -launch.advice.modded_java=部分 Mod 可能與高版本 Java 不相容,建議使用 Java %s 啟動 Minecraft %s。 -launch.advice.modlauncher8=你所使用的 Forge 版本與當前使用的 Java 不相容,請更新 Forge。 -launch.advice.newer_java=偵測到你正在使用舊版本 Java 啟動遊戲,這可能導致部分 Mod 引發遊戲崩潰,建議更新至 Java 8 後再次啟動。 -launch.advice.not_enough_space=你設定的記憶體大小過大,由於超過了系統記憶體大小 %dMB,所以可能影響遊戲體驗或無法啟動遊戲。 -launch.advice.require_newer_java_version=當前遊戲版本需要 Java %s,但 HMCL 未能找到該 Java 版本,你可以點擊“是”,HMCL 會自動下載他,是否下載? -launch.advice.too_large_memory_for_32bit=你設定的記憶體大小過大,由於可能超過了 32 位元 Java 的記憶體分配限制,所以可能無法啟動遊戲,請將記憶體調至低於 1024MB 的值。 -launch.advice.vanilla_linux_java_8=對於 Linux x86-64 平台,Minecraft 1.12.2 及以下版本與 Java 9+ 不相容,請使用 Java 8 啟動遊戲。 -launch.advice.vanilla_x86.translation=Minecraft 尚未為你的平臺提供完善支持,所以可能影響遊戲體驗或無法啟動遊戲。\n你可以在 這裡 下載 X86-64 架構的 Java 以獲得更完整的體驗。\n是否繼續啟動? +launch.advice.java9=低於 (包含) 1.13 的有安裝模組的 Minecraft 版本不支援 Java 9 或更高版本,請使用 Java 8。 +launch.advice.modded_java=部分模組可能與高版本 Java 不相容,建議使用 Java %s 啟動 Minecraft %s。 +launch.advice.modlauncher8=你所使用的 Forge 版本與目前使用的 Java 不相容。請更新 Forge。 +launch.advice.newer_java=偵測到你正在使用舊版本 Java 啟動遊戲,這可能導致部分模組引發遊戲崩潰,建議更新至 Java 8 後再次啟動。 +launch.advice.not_enough_space=你設定的記憶體大小過大,由於超過了系統記憶體大小 %d MB,所以可能影響遊戲體驗或無法啟動遊戲。 +launch.advice.require_newer_java_version=目前遊戲版本需要 Java %s,但 HMCL 未能找到該 Java 版本,你可以點擊「是」,HMCL 會自動下載他,是否下載? +launch.advice.too_large_memory_for_32bit=你設定的記憶體大小過大,由於可能超過了 32 位元 Java 的記憶體分配限制,所以可能無法啟動遊戲,請將記憶體調至低於 1024 MB 的值。 +launch.advice.vanilla_linux_java_8=對於 Linux x86-64 平台,Minecraft 1.12.2 及更低版本與 Java 9+ 不相容,請使用 Java 8 啟動遊戲。 +launch.advice.vanilla_x86.translation=Minecraft 尚未為你的平臺提供完善支援,所以可能影響遊戲體驗或無法啟動遊戲。\n你可以在 這裡 下載 x86-64 架構的 Java 以獲得更完整的體驗。\n是否繼續啟動? launch.advice.unknown=由於以下原因,無法繼續啟動遊戲: launch.failed=啟動失敗 -launch.failed.cannot_create_jvm=偵測到無法建立 Java 虛擬機,可能是 Java 參數有問題。可以在設定中開啟無參數模式啟動。 +launch.failed.cannot_create_jvm=無法建立 Java 虛擬機,可能是 Java 參數有問題。可以在「(全域/實例特定) 遊戲設定 → 進階設定 → Java 虛擬機設定」中移除所有 Java 虛擬機參數後,嘗試再次啟動遊戲。 launch.failed.creating_process=啟動失敗,在建立新處理程式時發生錯誤。可能是 Java 路徑錯誤。 -launch.failed.command_too_long=命令長度超過限制,無法創建 bat 腳本,請匯出為 PowerShell 腳本。 -launch.failed.decompressing_natives=無法解壓縮遊戲資源庫。 -launch.failed.download_library=無法下載遊戲相依元件 %s。 +launch.failed.command_too_long=指令長度超過限制,無法建立批次檔指令碼,請匯出為 PowerShell 指令碼。 +launch.failed.decompressing_natives=無法解壓縮遊戲本機庫。 +launch.failed.download_library=無法下載遊戲相依元件「%s」。 launch.failed.executable_permission=無法為啟動檔案新增執行權限。 launch.failed.execution_policy=設定執行策略 launch.failed.execution_policy.failed_to_set=設定執行策略失敗 -launch.failed.execution_policy.hint=當前執行策略封锁你執行 PowerShell 腳本。\n點擊“確定”允許當前用戶執行本地 PowerShell 腳本,或點擊“取消”保持現狀。 -launch.failed.exited_abnormally=遊戲非正常退出,請查看記錄檔案,或聯絡他人尋求幫助。 +launch.failed.execution_policy.hint=目前執行策略封鎖你執行 PowerShell 指令碼。\n點擊「確定」允許目前使用者執行本機 PowerShell 指令碼,或點擊「取消」保持現狀。 +launch.failed.exited_abnormally=遊戲非正常退出,請查看日誌檔案,或聯絡他人尋求幫助。 launch.failed.java_version_too_low=你所指定的 Java 版本過低,請重新設定 Java 版本。 -launch.failed.no_accepted_java=找不到適合當前遊戲使用的 Java,是否使用默認 Java 啟動遊戲?點擊“是”使用默認 Java 繼續啟動遊戲,\n或者請到遊戲設定中選擇一個合適的Java虛擬機器版本。 -launch.failed.sigkill=遊戲被用戶或系統強制終止。 +launch.failed.no_accepted_java=找不到適合目前遊戲使用的 Java,是否使用預設 Java 啟動遊戲?點擊「是」使用預設 Java 繼續啟動遊戲,\n或者請到「(全域/實例特定) 遊戲設定 → 遊戲 Java」中選取一個合適的 Java 虛擬機版本。 +launch.failed.sigkill=遊戲被使用者或系統強制終止。 launch.state.dependencies=處理遊戲相依元件 launch.state.done=啟動完成 launch.state.java=檢測 Java 版本 launch.state.logging_in=登入 launch.state.modpack=下載必要檔案 launch.state.waiting_launching=等待遊戲啟動 -launch.invalid_java=當前設定的 Java 路徑無效,請重新設定 Java 路徑。 +launch.invalid_java=目前設定的 Java 路徑無效,請重新設定 Java 路徑。 launcher=啟動器 -launcher.agreement=用戶協議與免責聲明 +launcher.agreement=使用者協議與免責宣告 launcher.agreement.accept=同意 launcher.agreement.decline=拒絕 -launcher.agreement.hint=同意本軟體的用戶協議與免責聲明以使用本軟體。 -launcher.background=背景位址 -launcher.background.choose=選擇背景路徑 +launcher.agreement.hint=同意本軟體的使用者協議與免責宣告以使用本軟體。 +launcher.background=背景圖片 +launcher.background.choose=選取背景圖片 launcher.background.classic=經典 -launcher.background.default=預設(自動尋找啟動器同目錄下的 background.png/jpg/gif 及 bg 資料夾內的圖片) +launcher.background.default=預設 +launcher.background.default.tooltip=自動尋找啟動器同目錄下的「background.png/.jpg/.gif/.webp」及「bg」目錄內的圖片 launcher.background.network=網路 launcher.background.translucent=半透明 launcher.cache_directory=檔案下載快取目錄 launcher.cache_directory.clean=清理 -launcher.cache_directory.choose=選擇檔案下載快取目錄 -launcher.cache_directory.default=預設 +launcher.cache_directory.choose=選取檔案下載快取目錄 +launcher.cache_directory.default=預設 ("%APPDATA%/.minecraft" 或 "~/.minecraft") launcher.cache_directory.disabled=停用 launcher.cache_directory.invalid=無法建立自訂的快取目錄,還原至預設設定 launcher.contact=聯絡我們 -launcher.crash=Hello Minecraft! Launcher 遇到了無法處理的錯誤,請複製下列內容並透過 MCBBS、貼吧、GitHub 或 Minecraft Forum 回報 bug。 -launcher.crash.java_internal_error=HHello Minecraft! Launcher 由於當前 Java 損壞而無法繼續運行,請移除當前 Java,點擊 此處 安裝合適的 Java 版本。 -launcher.crash.hmcl_out_dated=Hello Minecraft! Launcher 遇到了無法處理的錯誤,已偵測到您的啟動器不是最新版本,請更新後重試! +launcher.crash=Hello Minecraft! Launcher 遇到了無法處理的錯誤,請複製下列內容並透過 GitHub、Discord 或 HMCL QQ 群回報問題。 +launcher.crash.java_internal_error=Hello Minecraft! Launcher 由於目前 Java 損壞而無法繼續執行,請移除目前 Java,點擊 此處 安裝合適的 Java 版本。 +launcher.crash.hmcl_out_dated=Hello Minecraft! Launcher 遇到了無法處理的錯誤,已偵測到你的啟動器不是最新版本,請更新後重試! launcher.update_java=請更新你的 Java -login.empty_username=你還未設定使用者名稱! -login.enter_password=請輸入您的密碼 +login.empty_username=你還未設定使用者名稱! +login.enter_password=請輸入你的密碼 logwindow.show_lines=顯示行數 logwindow.terminate_game=結束遊戲處理程式 -logwindow.title=記錄 -logwindow.help=你可以前往 HMCL 社區,尋找他人幫助 +logwindow.title=日誌 +logwindow.help=你可以前往 HMCL 社群,向他人尋求幫助 logwindow.autoscroll=自動滾動 -logwindow.export_game_crash_logs=導出遊戲崩潰訊息 -logwindow.export_dump=導出遊戲運行棧 -logwindow.export_dump.no_dependency=你的 Java 不包含用於創建遊戲運行棧的依賴。請前往 HMCL QQ 群或 Discord 频道尋求幫助。 +logwindow.export_game_crash_logs=匯出遊戲崩潰訊息 +logwindow.export_dump=匯出遊戲執行堆疊 +logwindow.export_dump.no_dependency=你的 Java 不包含用於建立遊戲執行堆疊的相依元件。請前往 Discord 或 HMCL QQ 群尋求幫助。 main_page=首頁 message.cancelled=操作被取消 message.confirm=提示 -message.copied=已複製到剪貼板 +message.copied=已複製到剪貼簿 message.default=預設 message.doing=請耐心等待 -message.downloading=正在下載… +message.downloading=正在下載…… message.error=錯誤 message.failed=操作失敗 -message.info=資訊 +message.info=提示 message.success=完成 message.unknown=未知 message.warning=警告 modpack=模組包 -modpack.choose=選擇要安裝的遊戲模組包檔案 +modpack.choose=選取要安裝的遊戲模組包檔案 modpack.choose.local=匯入本機模組包檔案 modpack.choose.local.detail=你可以直接將模組包檔案拖入本頁面以安裝 modpack.choose.remote=從網路下載模組包 modpack.choose.remote.detail=需要提供模組包的下載連結 -modpack.choose.repository= 從 Curseforge / Modrinth 下載整合包 -modpack.choose.repository.detail=下載后記得回到這個界面,把整合包拖進來哦 +modpack.choose.repository=從 CurseForge/Modrinth 下載模組包 +modpack.choose.repository.detail=下載後記得回到這個介面,把模組包拖進來哦 modpack.choose.remote.tooltip=要下載的模組包的連結 modpack.completion=下載模組包相關檔案 -modpack.desc=描述你要製作的模組包,比如模組包注意事項和更新記錄,支援 Markdown(圖片請上傳至網路)。 +modpack.desc=描述你要製作的模組包,比如模組包注意事項和更新紀錄,支援 Markdown (圖片請上傳至網路)。 modpack.description=模組包描述 modpack.download=下載模組包 modpack.enter_name=給遊戲取個你喜歡的名稱 modpack.export=匯出模組包 -modpack.export.as=請選擇模組包類型。若你無法決定,請選擇 HMCL 類型。 +modpack.export.as=請選取模組包類型。若你無法決定,請選取 MCBBS 類型。 modpack.file_api=模組包下載連結前綴 modpack.files.blueprints=BuildCraft 藍圖 -modpack.files.config=Mod 模組設定檔案 +modpack.files.config=模組設定檔案 modpack.files.dumps=NEI 調校輸出 modpack.files.hmclversion_cfg=啟動器設定檔案 -modpack.files.liteconfig=Mod 模組設定檔案 -modpack.files.mods=Mod 模組 +modpack.files.liteconfig=LiteLoader 相關檔案 +modpack.files.mods=模組 modpack.files.mods.voxelmods=VoxelMods 設定,如小地圖 modpack.files.options_txt=遊戲設定 modpack.files.optionsshaders_txt=光影設定 -modpack.files.resourcepacks=資源包 (材質包) +modpack.files.resourcepacks=資源包 (紋理包) modpack.files.saves=遊戲存檔 modpack.files.scripts=MineTweaker 設定 -modpack.files.servers_dat=多人遊戲伺服器列表 +modpack.files.servers_dat=多人遊戲伺服器清單 modpack.install=安裝 %s 模組包 modpack.installing=正在安裝模組包 modpack.introduction=支援 Curse、Modrinth、MultiMC、MCBBS 模組包。 modpack.invalid=無效的模組包升級檔案,可能是下載時出現問題。 -modpack.mismatched_type=模組包類型不符,目前遊戲是 %s 模組包,但是提供的模組包更新檔案是 %s 模組包。 +modpack.mismatched_type=模組包類型不符,目前遊戲是「%s」模組包,但是提供的模組包更新檔案是「%s」模組包。 modpack.name=模組包名稱 modpack.not_a_valid_name=模組包名稱無效 modpack.origin=來源 modpack.origin.url=官方網站 modpack.origin.mcbbs=MCBBS -modpack.origin.mcbbs.prompt=貼子 id +modpack.origin.mcbbs.prompt=帖子 ID modpack.scan=解析模組包 modpack.task.install=匯入模組包 -modpack.task.install.error=無法識別該模組包,目前僅支援匯入 Curse、Modrinth、MultiMC、MCBBS 模組包。 -modpack.task.install.will=將會安裝模組包: +modpack.task.install.error=無法識別該模組包,目前僅支援匯入 Curse、Modrinth、MultiMC 和 MCBBS 模組包。 modpack.type.curse=Curse -modpack.type.curse.tolerable_error=但未能完成該模組包檔案的下載,您可以在啟動該遊戲版本時繼續該模組包檔案的下載。由於網路問題,您可能需要重試多次…… -modpack.type.curse.error=無法完成該模組包所需的依賴下載,請多次重試或設定代理…… +modpack.type.curse.error=無法完成該模組包所需的相依元件下載,請多次重試或設定代理…… modpack.type.curse.not_found=部分必需檔案已經從網路中被刪除並且再也無法下載,請嘗試該模組包的最新版本或者安裝其他模組包。 -modpack.type.manual.warning=該模組包由發佈者手動打包,其中可能已經包含啟動器,建議嘗試解壓後使用其自帶的啟動器運行遊戲。\nHMCL 可以嘗試導入該模組包,但不保證可用性,是否繼續? -modpack.type.mcbbs=我的世界中文論壇模組包標準 +modpack.type.manual.warning=該模組包由發佈者手動打包,其中可能已經包含啟動器,建議嘗試解壓後使用其內建的啟動器執行遊戲。\nHMCL 可以嘗試匯入該模組包,但不保證可用性,是否繼續? +modpack.type.mcbbs=MCBBS modpack.type.mcbbs.export=可以被 Hello Minecraft! Launcher 匯入 modpack.type.modrinth=Modrinth modpack.type.multimc=MultiMC @@ -716,20 +703,20 @@ modpack.type.multimc.export=可以被 Hello Minecraft! Launcher 和 MultiMC 匯 modpack.type.server=伺服器自動更新模組包 modpack.type.server.export=允許伺服器管理員遠端更新遊戲用戶端 modpack.type.server.malformed=伺服器模組包配置格式錯誤,請聯絡伺服器管理員解決此問題 -modpack.unsupported=Hello Minecraft! Launcher 不支持該綜合包格式 +modpack.unsupported=Hello Minecraft! Launcher 不支援該模組包格式 modpack.update=正在升級模組包 modpack.wizard=匯出模組包引導 modpack.wizard.step.1=基本設定 modpack.wizard.step.1.title=設定模組包的主要訊息 -modpack.wizard.step.2=檔案選擇 -modpack.wizard.step.2.title=選中你想加到模組包中的檔案或資料夾 +modpack.wizard.step.2=選取檔案 +modpack.wizard.step.2.title=選中你想加到模組包中的檔案或目錄 modpack.wizard.step.3=模組包類型 -modpack.wizard.step.3.title=選擇模組包匯出類型 +modpack.wizard.step.3.title=選取模組包匯出類型 modpack.wizard.step.initialization.exported_version=要匯出的遊戲版本 -modpack.wizard.step.initialization.force_update=強制升級模組包至最新版本(需要自建伺服器) +modpack.wizard.step.initialization.force_update=強制升級模組包至最新版本 (需要自建伺服器) modpack.wizard.step.initialization.include_launcher=包含啟動器 -modpack.wizard.step.initialization.save=選擇要匯出到的遊戲模組包位置 -modpack.wizard.step.initialization.warning=在製作模組包前,請您確認您選擇的版本可以正常啟動,\n並保證您的 Minecraft 是正式版而非快照版,\n而且不應將不允許非官方途徑傳播的 Mod 模組、材質包等納入模組包。\n模組包會儲存您目前的下載來源設定 +modpack.wizard.step.initialization.save=選取要匯出到的遊戲模組包位置 +modpack.wizard.step.initialization.warning=在製作模組包前,請你確認你選取的實例可以正常啟動,\n並保證你的 Minecraft 是正式版而非快照,\n而且不應將不允許非官方途徑傳播的模組、資源 (紋理) 包等納入模組包。\n模組包會儲存你目前的下載來源設定。 modpack.wizard.step.initialization.server=點選此處查看有關伺服器自動更新模組包的製作教學 modrinth.category.adventure=冒險 @@ -738,7 +725,7 @@ modrinth.category.blocks=方塊 modrinth.category.bukkit=Bukkit modrinth.category.bungeecord=BungeeCord modrinth.category.challenging=高難度 -modrinth.category.core-shaders=覈心著色器 +modrinth.category.core-shaders=核心著色器 modrinth.category.combat=戰鬥 modrinth.category.cursed=Cursed modrinth.category.decoration=裝飾 @@ -754,7 +741,7 @@ modrinth.category.game-mechanics=遊戲機制 modrinth.category.gui=GUI modrinth.category.items=物品 modrinth.category.kitchen-sink=大雜燴 -modrinth.category.library=支持庫 +modrinth.category.library=支援庫 modrinth.category.lightweight=輕量 modrinth.category.liteloader=LiteLoader modrinth.category.locale=在地化 @@ -766,7 +753,7 @@ modrinth.category.misc=其他 modrinth.category.mobs=生物 modrinth.category.modded=Modded modrinth.category.models=模型 -modrinth.category.modloader=Modloader +modrinth.category.modloader=ModLoader modrinth.category.multiplayer=多人 modrinth.category.neoforge=NeoForge modrinth.category.optimization=最佳化 @@ -784,39 +771,43 @@ modrinth.category.storage=儲存 modrinth.category.technology=科技 modrinth.category.themed=主題 modrinth.category.transportation=運輸 -modrinth.category.tweaks=優化 +modrinth.category.tweaks=最佳化 modrinth.category.utility=實用 modrinth.category.vanilla-like=類原生 modrinth.category.velocity=Velocity modrinth.category.waterfall=Waterfall modrinth.category.worldgen=世界生成 -modrinth.category.datapack=數據包 +modrinth.category.datapack=資料包 modrinth.category.folia=Folia mods=模組 mods.add=新增模組 -mods.add.failed=新增模組 %s 失敗。 -mods.add.success=成功新增模組 %s。 -mods.broken_dependency.title=損壞的前置模組 -mods.broken_dependency.desc=該前置模組曾經在該模組倉庫上存在過,但現在被刪除了,換個下載源試試吧。 +mods.add.failed=新增模組「%s」失敗。 +mods.add.success=成功新增模組「%s」。 +mods.broken_dependency.title=損壞的相依模組 +mods.broken_dependency.desc=該相依模組曾經存在於模組倉庫中,但現在已被刪除,請嘗試其他下載源。 mods.category=類別 +mods.channel.alpha=Alpha +mods.channel.beta=Beta +mods.channel.release=Release mods.check_updates=檢查模組更新 -mods.check_updates.current_version=當前版本 +mods.check_updates.current_version=目前版本 mods.check_updates.empty=沒有需要更新的模組 -mods.check_updates.failed=部分檔案下載失敗 +mods.check_updates.failed_check=檢查更新失敗 +mods.check_updates.failed_download=部分檔案下載失敗 mods.check_updates.file=檔案 mods.check_updates.source=來源 mods.check_updates.target_version=目標版本 mods.check_updates.update=更新 -mods.choose_mod=選擇模組 +mods.choose_mod=選取模組 mods.curseforge=CurseForge -mods.dependency.embedded=內置前端模組(作者已經打包在模組檔中,無需額外下載) -mods.dependency.optional=可選的前模組(如果缺少遊戲,遊戲可以運行,但模組功能可能缺失) -mods.dependency.required=必需的預模式(必須單獨下載,缺少可能會導致遊戲無法啟動) -mods.dependency.tool=前端庫(必須單獨下載,缺少可能會導致遊戲無法啟動) -mods.dependency.include=內置前綴模組(作者已經打包在模組檔中,無需額外下載) -mods.dependency.incompatible=模組不相容(同時安裝模組和模組都下載會導致遊戲無法啟動) -mods.dependency.broken=損壞的前綴(這個前綴曾經存在於 mod 倉庫中,但現在已被刪除)。嘗試其他下載源。 +mods.dependency.embedded=內建相依模組 (作者已經打包在模組檔中,無需單獨下載) +mods.dependency.optional=可選相依模組 (如果不安裝,遊戲可以執行,但模組功能可能缺失) +mods.dependency.required=必需相依模組 (必須單獨下載,缺少可能會導致遊戲無法啟動) +mods.dependency.tool=相依庫 (必須單獨下載,缺少可能會導致遊戲無法啟動) +mods.dependency.include=內建相依模組 (作者已經打包在模組檔中,無需單獨下載) +mods.dependency.incompatible=不相容模組 (同時安裝此模組和正在下載的模組會導致遊戲無法啟動) +mods.dependency.broken=損壞的相依模組 (該模組曾經存在於模組倉庫中,但現在已被刪除,請嘗試其他下載源) mods.disable=停用 mods.download=模組下載 mods.download.title=模組下載 - %1s @@ -826,52 +817,57 @@ mods.manage=模組管理 mods.mcbbs=MCBBS mods.mcmod=MC 百科 mods.mcmod.page=MC 百科頁面 -mods.mcmod.search=MC 百科蒐索 +mods.mcmod.search=MC 百科檢索 mods.modrinth=Modrinth mods.name=名稱 -mods.not_modded=你需要先在自動安裝頁面安裝 Fabric、Forge、Quilt 或 LiteLoader 才能進行模組管理。 +mods.not_modded=你需要先在「自動安裝」頁面安裝 Forge、NeoForge、Fabric、Quilt 或 LiteLoader 才能管理模組。 mods.restore=回退 mods.url=官方頁面 -mods.update_modpack_mod.warning=更新模組包中的 Mod 可能導致綜合包損壞,使模組包無法正常啟動。該操作不可逆,確定要更新嗎? -mods.install=安裝到當前實例 -mods.save_as=下載到本地目錄 +mods.update_modpack_mod.warning=更新模組包中的模組可能導致模組包損壞,使模組包無法正常啟動。該操作不可逆,確定要更新嗎? +mods.install=安裝到目前實例 +mods.save_as=下載到本機目錄 nbt.entries=%s 個條目 nbt.open.failed=打開檔案失敗 -nbt.save.failed=保存檔案失敗 +nbt.save.failed=儲存檔案失敗 nbt.title=查看檔案 - %s datapack=資料包 datapack.add=加入資料包 -datapack.choose_datapack=選擇要匯入的資料包壓縮檔 +datapack.choose_datapack=選取要匯入的資料包壓縮檔 datapack.extension=資料包 -datapack.title=世界 %s - 資料包 +datapack.title=世界 [%s] - 資料包 + +web.failed=加載頁面失敗 +web.open_in_browser=是否要在瀏覽器中打開此連結:\n%s +web.view_in_browser=在瀏覽器中查看 world=世界 world.add=加入世界 world.datapack=管理資料包 world.datapack.1_13=僅 Minecraft 1.13 及之後的版本支援資料包 -world.description=%s. 上一次遊戲時間: %s. 遊戲版本: %s +world.description=%s | 上一次遊戲時間: %s | 遊戲版本: %s world.download=存檔下載 world.export=匯出此世界 -world.export.title=選擇該世界的儲存位置 +world.export.title=選取該世界的儲存位置 world.export.location=儲存到 -world.export.wizard=匯出世界 %s +world.export.wizard=匯出世界「%s」 world.extension=存檔壓縮檔 world.import.already_exists=此世界已經存在 -world.import.choose=選擇要匯入的存檔壓縮檔 +world.import.choose=選取要匯入的存檔壓縮檔 world.import.failed=無法匯入此世界: %s -world.import.invalid=無法識別的存檔壓縮包 -world.info.title=世界 %s - 世界資訊 +world.import.invalid=無法識別的存檔壓縮檔 +world.info.title=世界 [%s] - 世界資訊 world.info.basic=基本資訊 -world.info.allow_cheats=允許作弊 -world.info.dimension.the_nether=下界 +world.info.allow_cheats=允許指令(作弊) +world.info.dimension.the_nether=地獄 world.info.dimension.the_end=末地 -world.info.difficulty=難度 +world.info.difficulty=難易度 world.info.difficulty.peaceful=和平 world.info.difficulty.easy=簡單 world.info.difficulty.normal=普通 world.info.difficulty.hard=困難 +world.info.failed=讀取世界資訊失敗 world.info.game_version=遊戲版本 world.info.last_played=上一次遊戲時間 world.info.generate_features=生成建築 @@ -880,40 +876,42 @@ world.info.player.food_level=饑餓值 world.info.player.game_type=遊戲模式 world.info.player.game_type.adventure=冒險 world.info.player.game_type.creative=創造 -world.info.player.game_type.spectator=旁觀 +world.info.player.game_type.spectator=旁觀者 world.info.player.game_type.survival=生存 world.info.player.health=生命值 world.info.player.last_death_location=上次死亡位置 world.info.player.location=位置 world.info.player.spawn=床/重生錨位置 world.info.player.xp_level=經驗等級 -world.info.random_seed=種子 +world.info.random_seed=種子碼 world.info.time=遊戲內時間 world.info.time.format=%s 天 world.game_version=遊戲版本 world.manage=世界/資料包 world.name=世界名稱 world.name.enter=輸入世界名稱 -world.reveal=開啟資料夾 +world.reveal=開啟目錄 world.show_all=全部顯示 -world.time=yyyy年MM月dd日 HH:mm:ss +world.time=yyyy 年 M 月 d 日, HH:mm:ss profile=遊戲倉庫 profile.already_exists=該名稱已存在 -profile.default=目前目錄倉庫 -profile.home=官方啟動器倉庫 -profile.instance_directory=遊戲路徑 -profile.instance_directory.choose=選擇遊戲路徑 +profile.default=目前目錄 +profile.home=官方啟動器目錄 +profile.instance_directory=遊戲目錄路徑 +profile.instance_directory.choose=選取遊戲目錄路徑 +profile.manage=遊戲目錄清單 profile.name=名稱 -profile.new=建立設定 -profile.title=遊戲倉庫 -profile.use_relative_path=如可行,則在遊戲倉庫使用相對路徑 +profile.new=建立新目錄 +profile.title=遊戲目錄 +profile.selected=已選取 +profile.use_relative_path=如可行,則對於遊戲目錄使用相對路徑 -repositories.custom=自定義 Maven 倉庫(%s) -repositories.maven_central=全球(Maven Central) -repositories.tencentcloud_mirror=中國大陸(騰訊雲 Maven 倉庫) -repositories.chooser=缺少 JavaFX 運行環境,HMCL 需要 JavaFX 才能正常運行。\n點擊“確認”從指定下載源下載 JavaFX 運行時組件並啟動 HMCL,點擊“取消”退出程式。\n選擇下載源: -repositories.chooser.title=選擇下載源下載JavaFX +repositories.custom=自訂 Maven 倉庫 (%s) +repositories.maven_central=全球 (Maven Central) +repositories.tencentcloud_mirror=中國大陸 (騰訊雲 Maven 倉庫) +repositories.chooser=缺少 JavaFX 執行環境,HMCL 需要 JavaFX 才能正常執行。\n點擊「確認」從指定下載源下載 JavaFX 執行時元件並開啟 HMCL,點擊「取消」退出程式。\n選取下載源: +repositories.chooser.title=選取 JavaFX 下載源 resourcepack=資源包 @@ -928,41 +926,41 @@ search.next_page=下一頁 search.last_page=最後一頁 search.page_n=%d / %s -selector.choose=選擇 -selector.choose_file=選擇檔案 +selector.choose=選取 +selector.choose_file=選取檔案 selector.custom=自訂 -settings=遊戲設定 +settings=設定 settings.advanced=進階設定 -settings.advanced.modify=修改進階設定 +settings.advanced.modify=編輯進階設定 settings.advanced.title=進階設定 - %s -settings.advanced.custom_commands=自訂命令 -settings.advanced.custom_commands.hint=自訂命令被調用時將包含如下的環境變數:\n\ - \ - $INST_NAME: 版本名稱\n\ - \ - $INST_ID: 版本名稱\n\ - \ - $INST_DIR: 版本資料夾\n\ - \ - $INST_MC_DIR: 遊戲運行路徑\n\ - \ - $INST_JAVA: 遊戲運行使用的 Java 路徑\n\ - \ - $INST_FORGE: 若安裝了 Forge,將會存在本環境變數\n\ - \ - $INST_NEOFORGE: 若安裝了 NeoForge,將會存在本環境變數\n\ - \ - $INST_LITELOADER: 若安裝了 LiteLoader,將會存在本環境變數\n\ - \ - $INST_OPTIFINE: 若安裝了 OptiFine,將會存在本環境變數\n\ - \ - $INST_FABRIC: 若安裝了 Fabric,將會存在本環境變數\n\ - \ - $INST_QUILT: 若安裝了 Quilt,將會存在本環境變數 +settings.advanced.custom_commands=自訂指令 +settings.advanced.custom_commands.hint=自訂指令被呼叫時將包含如下的環境變數:\n\ + \ · $INST_NAME: 實例名稱;\n\ + \ · $INST_ID: 實例名稱;\n\ + \ · $INST_DIR: 目前實例執行路徑;\n\ + \ · $INST_MC_DIR: 目前遊戲目錄路徑;\n\ + \ · $INST_JAVA: 遊戲執行使用的 Java 路徑;\n\ + \ · $INST_FORGE: 若安裝了 Forge,將會存在本環境變數;\n\ + \ · $INST_NEOFORGE: 若安裝了 NeoForge,將會存在本環境變數;\n\ + \ · $INST_LITELOADER: 若安裝了 LiteLoader,將會存在本環境變數;\n\ + \ · $INST_OPTIFINE: 若安裝了 OptiFine,將會存在本環境變數;\n\ + \ · $INST_FABRIC: 若安裝了 Fabric,將會存在本環境變數;\n\ + \ · $INST_QUILT: 若安裝了 Quilt,將會存在本環境變數。 settings.advanced.dont_check_game_completeness=不檢查遊戲完整性 settings.advanced.dont_check_jvm_validity=不檢查 JVM 與遊戲的相容性 -settings.advanced.dont_patch_natives=不嘗試自動替換本機庫 +settings.advanced.dont_patch_natives=不嘗試自動取代本機庫 settings.advanced.environment_variables=環境變數 -settings.advanced.game_dir.default=預設(.minecraft/) -settings.advanced.game_dir.independent=各版本獨立(.minecraft/versions/<版本名>/,除 assets、libraries) +settings.advanced.game_dir.default=預設 (".minecraft/") +settings.advanced.game_dir.independent=各實例獨立 (".minecraft/versions/<實例名>/",除 assets、libraries 外) settings.advanced.java_permanent_generation_space=記憶體永久儲存區域 -settings.advanced.java_permanent_generation_space.prompt=格式: MB +settings.advanced.java_permanent_generation_space.prompt=單位 MB settings.advanced.jvm=Java 虛擬機設定 settings.advanced.jvm_args=Java 虛擬機參數 -settings.advanced.jvm_args.prompt=- 若在“Java 虛擬機參數”中輸入的參數與默認參數相同,則不會添加\n\ -- 在“Java 虛擬機參數”輸入任何 GC 參數,默認參數的 G1 參數會禁用\n\ -- 點擊下方“不添加默認的 JVM 參數”可在啟動遊戲時不添加默認參數 +settings.advanced.jvm_args.prompt=\ · 若在「Java 虛擬機參數」中輸入的參數與預設參數相同,則不會加入;\n\ + \ · 在「Java 虛擬機參數」輸入任何 GC 參數,預設參數的 G1 參數會禁用;\n\ + \ · 點擊下方「不加入預設的 JVM 參數」可在啟動遊戲時不加入預設參數。 settings.advanced.launcher_visibility.close=遊戲啟動後結束啟動器 settings.advanced.launcher_visibility.hide=遊戲啟動後隱藏啟動器 settings.advanced.launcher_visibility.hide_and_reopen=隱藏啟動器並在遊戲結束後重新開啟 @@ -970,35 +968,35 @@ settings.advanced.launcher_visibility.keep=不隱藏啟動器 settings.advanced.launcher_visible=啟動器可見性 settings.advanced.minecraft_arguments=Minecraft 額外參數 settings.advanced.minecraft_arguments.prompt=預設 -settings.advanced.natives_directory=本地庫路徑(LWJGL) -settings.advanced.natives_directory.choose=選擇本地庫路徑 -settings.advanced.natives_directory.custom=自訂(由你提供遊戲需要的本地庫) -settings.advanced.natives_directory.default=預設(由啟動器提供遊戲本地庫) -settings.advanced.natives_directory.hint=本選項提供給 Apple M1 等未受遊戲官方支持的平台來自訂遊戲本地庫,如果你不知道本選項的含義,請你不要修改本選項,否則會導致遊戲無法啟動。\n如果你要修改本選項,你需要保證自訂目錄下有遊戲所需的本地庫文件,如 lwjgl.dll(liblwjgl.so), openal.dll(libopenal.so) 等文件。啟動器不會幫你補全缺少的本地庫文件。\n注意:建議指定的本地庫文件路徑使用全英文字符,否則可能導致遊戲啟動失敗。 -settings.advanced.no_jvm_args=不使用預設的 JVM 參數 +settings.advanced.natives_directory=本機庫路徑 (LWJGL) +settings.advanced.natives_directory.choose=選取本機庫路徑 +settings.advanced.natives_directory.custom=自訂 (由你提供遊戲需要的本機庫) +settings.advanced.natives_directory.default=預設 (由啟動器提供遊戲本機庫) +settings.advanced.natives_directory.hint=本選項提供給 Apple M1 等未受遊戲官方支援的平台來自訂遊戲本機庫,如果你不知道本選項的含義,請你不要修改本選項,否則會導致遊戲無法啟動。\n如果你要修改本選項,你需要保證自訂目錄下有遊戲所需的本機庫檔案,如 lwjgl.dll (liblwjgl.so), openal.dll (libopenal.so) 等檔案。啟動器不會幫你補全缺少的本機庫檔案。\n注意:建議指定的本機庫檔案路徑使用全英文字元,否則可能導致遊戲啟動失敗。 +settings.advanced.no_jvm_args=不加入預設的 JVM 參數 settings.advanced.precall_command=遊戲啟動前執行指令 settings.advanced.precall_command.prompt=將在遊戲啟動前呼叫使用 -settings.advanced.process_priority=進程優先度 -settings.advanced.process_priority.low=低(節省遊戲占用資源,可能會造成遊戲卡頓) -settings.advanced.process_priority.below_normal=較低(節省遊戲占用資源,可能會造成遊戲卡頓) -settings.advanced.process_priority.normal=中(平衡) -settings.advanced.process_priority.above_normal=較高(優先保證遊戲運行,但可能會導致其他程式卡頓) -settings.advanced.process_priority.high=高(優先保證遊戲運行,但可能會導致其他程式卡頓) -settings.advanced.post_exit_command=遊戲結束後執行命令 +settings.advanced.process_priority=處理程序優先度 +settings.advanced.process_priority.low=低 (節省遊戲占用資源,可能會造成遊戲卡頓) +settings.advanced.process_priority.below_normal=較低 (節省遊戲占用資源,可能會造成遊戲卡頓) +settings.advanced.process_priority.normal=中 (平衡) +settings.advanced.process_priority.above_normal=較高 (優先保證遊戲執行,但可能會導致其他程式卡頓) +settings.advanced.process_priority.high=高 (優先保證遊戲執行,但可能會導致其他程式卡頓) +settings.advanced.post_exit_command=遊戲結束後執行指令 settings.advanced.post_exit_command.prompt=將在遊戲結束後呼叫使用 -settings.advanced.renderer=渲染器 -settings.advanced.renderer.default=OpenGL(默認) -settings.advanced.renderer.d3d12=DirectX 12(效能與相容性較差,用於調試) -settings.advanced.renderer.llvmpipe=軟渲染器(效能較差,相容性最好) -settings.advanced.renderer.zink=Vulkan(效能最好,相容性較差) +settings.advanced.renderer=繪製器 +settings.advanced.renderer.default=OpenGL (預設) +settings.advanced.renderer.d3d12=DirectX 12 (效能與相容性較差,用於除錯) +settings.advanced.renderer.llvmpipe=軟繪製器 (效能較差,相容性最好) +settings.advanced.renderer.zink=Vulkan (效能最好,相容性較差) settings.advanced.server_ip=伺服器位址 settings.advanced.server_ip.prompt=預設,啟動遊戲後直接進入對應伺服器 -settings.advanced.use_native_glfw=使用系統 GLFW -settings.advanced.use_native_openal=使用系統 OpenAL +settings.advanced.use_native_glfw=[僅限 Linux/FreeBSD] 使用系統 GLFW +settings.advanced.use_native_openal=[僅限 Linux/FreeBSD] 使用系統 OpenAL settings.advanced.workaround=除錯選項 settings.advanced.workaround.warning=除錯選項僅提供給專業玩家使用。修改除錯選項可能會導致遊戲無法啟動。除非你知道你在做什麼,否則請不要修改這些選項。 settings.advanced.wrapper_launcher=前置指令 -settings.advanced.wrapper_launcher.prompt=如填寫 optirun 後,啟動命令將從 "java ..." 變為 "optirun java ..." +settings.advanced.wrapper_launcher.prompt=如填寫「optirun」後,啟動指令將從「java ...」變為「optirun java ...」 settings.custom=自訂 @@ -1007,45 +1005,46 @@ settings.game.current=遊戲 settings.game.dimension=遊戲介面解析度大小 settings.game.exploration=瀏覽 settings.game.fullscreen=全螢幕 -settings.game.java_directory=Java 路徑 -settings.game.java_directory.auto=自動選擇合適的 Java +settings.game.java_directory=遊戲 Java +settings.game.java_directory.auto=自動選取合適的 Java settings.game.java_directory.auto.not_found=沒有合適的 Java settings.game.java_directory.bit=%s 位 -settings.game.java_directory.choose=選擇 Java 路徑 +settings.game.java_directory.choose=選取 Java settings.game.java_directory.invalid=Java 路徑不正確 settings.game.java_directory.version=指定 Java 版本 -settings.game.java_directory.template=%s(%s) +settings.game.java_directory.template=%s (%s) settings.game.management=管理 -settings.game.working_directory=執行路徑(版本隔離,修改後請自行移動相關遊戲檔案,如存檔模組設定等) -settings.game.working_directory.choose=選擇執行路徑 -settings.game.working_directory.hint=在“運行路徑(版本隔離)”選項中開啟“各版本獨立”使當前版本獨立存放設定、存檔、模組等數據,使用模組時建議開啟此選項以避免不同版本模組衝突。修改此選項後請自行移動存檔等檔案。 +settings.game.working_directory=執行路徑 (建議使用模組時選取「各實例獨立」,修改後請自行移動相關遊戲檔案,如存檔、模組設定等) +settings.game.working_directory.choose=選取執行目錄 +settings.game.working_directory.hint=在「執行路徑」選項中選取「各實例獨立」使目前實例獨立存放設定、存檔、模組等資料,使用模組時建議開啟此選項以避免不同版本模組衝突。修改此選項後需自行移動存檔等檔案。 settings.game.working_directory.should_use_game_repo_feature=想要更改遊戲的儲存路徑?進入 HMCL 首頁 - 版本列表,點選【添加游戏仓库】 settings.icon=遊戲圖示 settings.launcher=啟動器設定 settings.launcher.appearance=外觀 -settings.launcher.common_path.tooltip=啟動器將所有遊戲資源及相依元件庫檔案放於此集中管理,如果遊戲資料夾內有現成的將不會使用公共庫檔案 +settings.launcher.common_path.tooltip=啟動器將所有遊戲資源及相依元件庫檔案放於此集中管理,如果遊戲目錄內有現成的將不會使用公共庫檔案。 settings.launcher.debug=除錯 settings.launcher.download=下載 -settings.launcher.download.threads=並發數 -settings.launcher.download.threads.auto=自動選擇並發數 -settings.launcher.download.threads.hint=並發數過大可能導致系統卡頓。你的下載速度會受到寬頻運營商、伺服器等方面的影響,調大下載並發數不一定能大幅提升總下載速度。 +settings.launcher.download.threads=執行緒數 +settings.launcher.download.threads.auto=自動選取執行緒數 +settings.launcher.download.threads.hint=執行緒數過高可能導致系統卡頓。你的下載速度會受到網際網路運營商、下載來源伺服器等方面的影響,調高下載執行緒數不一定能大幅提升總下載速度。 settings.launcher.download_source=下載來源 -settings.launcher.download_source.auto=自動選擇下載來源 -settings.launcher.enable_game_list=在首頁內顯示遊戲列表 +settings.launcher.download_source.auto=自動選取下載來源 +settings.launcher.enable_game_list=在首頁內顯示遊戲清單 settings.launcher.font=字體 -settings.launcher.general=通用 +settings.launcher.general=一般 settings.launcher.language=語言 (重啟後生效) settings.launcher.launcher_log.export=匯出啟動器日誌 -settings.launcher.launcher_log.export.failed=無法匯出日誌 -settings.launcher.launcher_log.export.success=日誌已儲存到 %s -settings.launcher.log=記錄 -settings.launcher.log.font=記錄字體 +settings.launcher.launcher_log.reveal=打開日誌目錄 +settings.launcher.launcher_log.export.failed=無法匯出日誌。 +settings.launcher.launcher_log.export.success=日誌已儲存到「%s」。 +settings.launcher.log=日誌 +settings.launcher.log.font=日誌字體 settings.launcher.proxy=代理 settings.launcher.proxy.authentication=身份驗證 settings.launcher.proxy.disable=使用系統代理 -settings.launcher.proxy.host=主機 +settings.launcher.proxy.host=IP 位址 settings.launcher.proxy.http=HTTP settings.launcher.proxy.none=不使用代理 settings.launcher.proxy.password=密碼 @@ -1055,7 +1054,7 @@ settings.launcher.proxy.username=帳戶 settings.launcher.theme=主題 settings.launcher.title_transparent=標題欄透明 settings.launcher.turn_off_animations=關閉動畫 (重啟後生效) -settings.launcher.version_list_source=版本列表來源 +settings.launcher.version_list_source=版本清單來源 settings.memory=遊戲記憶體 settings.memory.allocate.auto=最低分配 %1$.1f GB / 實際分配 %2$.1f GB @@ -1066,94 +1065,97 @@ settings.memory.auto_allocate=自動分配 settings.memory.lower_bound=最低分配 settings.memory.used_per_total=已使用 %1$.1f GB / 總記憶體 %2$.1f GB settings.physical_memory=實體記憶體大小 -settings.show_log=查看記錄 -settings.skin=現已支持離線帳戶更換皮膚,你可以到帳戶頁面更改離線帳戶的皮膚和披風(多人遊戲下其他玩家無法看到你的皮膚) +settings.show_log=查看日誌 +settings.skin=現已支援離線帳戶更換外觀,你可以到帳戶頁面更改離線帳戶的外觀和披風 (多人遊戲下其他玩家無法看到你的外觀)。 settings.tabs.installers=自動安裝 settings.take_effect_after_restart=重啟後生效 -settings.type=版本設定類型 -settings.type.global=全域版本設定(使用該設定的版本共用一套設定) +settings.type=實例遊戲設定類型 +settings.type.global=全域遊戲設定 (未啟用「實例特定遊戲設定」的實例共用一套設定) settings.type.global.manage=全域遊戲設定 settings.type.global.edit=編輯全域遊戲設定 -settings.type.special.enable=啟用遊戲特別設定(不影響其他遊戲版本) -settings.type.special.edit=編輯遊戲特定設置 -settings.type.special.edit.hint=當前遊戲版本 %s 啟動了遊戲特定設置,本頁面選項不對當前遊戲生效。點擊連結以修改當前遊戲設置。 +settings.type.special.enable=啟用實例特定遊戲設定 (不影響其他實例) +settings.type.special.edit=編輯實例特定遊戲設定 +settings.type.special.edit.hint=目前實例「%s」啟用了「實例特定遊戲設定」,本頁面選項不對目前實例生效。點擊連結以修改目前實例設定。 sponsor=贊助 -sponsor.bmclapi=大中華區下載源由 BMCLAPI 提供高速下載服務 -sponsor.hmcl=Hello Minecraft! Launcher 是一個免費、開源的 Minecraft 啟動器,允許玩家方便快捷地安裝、管理、執行遊戲。點選此處查閱更多詳細訊息。 +sponsor.bmclapi=中國大陸下載源由 BMCLAPI 提供高速下載服務。點選此處查閱詳細訊息。 +sponsor.hmcl=Hello Minecraft! Launcher 是一個免費、自由、開源的 Minecraft 啟動器,允許玩家方便快捷地安裝、管理、執行遊戲。點選此處查閱詳細訊息。 system.architecture=架構 -system.operating_system=操作系統 +system.operating_system=作業系統 -unofficial.hint=你正在使用非官方構建的 HMCL,我們無法保證其安全性,請注意甄別。 +unofficial.hint=你正在使用第三方提供的 HMCL,我們無法保證其安全性,請注意甄別。 update=啟動器更新 update.accept=更新 update.changelog=更新日誌 -update.channel.dev=測試版 -update.channel.dev.hint=你正在使用測試版。測試版包含一些未在正式版中包含的測試性功能,僅用於體驗新功能。\n\ - 測試版功能未受充分驗證,使用起來可能不穩定!\n\ - 如果你遇到了使用問題,可以在設置的 回饋頁面 中進行回饋,或加入回饋頁面中提供的 DiscordQQ 群以回饋問題。\n\n\ - 點擊此處為當前版本隱藏該提示。 -update.channel.dev.title=測試版提示 +update.channel.dev=開發版 +update.channel.dev.hint=你正在使用 HMCL 開發版。開發版包含一些未在穩定版中包含的測試性功能,僅用於體驗新功能。開發版功能未受充分驗證,使用起來可能不穩定!\n\ + \n\ + 如果你遇到了使用問題,可以透過設定中 回報頁面 提供的管道進行回報。 +update.channel.dev.title=開發版提示 update.channel.nightly=預覽版 -update.channel.nightly.hint=你正在使用預覽版。預覽版可能會每天更新一次,包含一些未在正式版和測試版中包含的測試性功能,僅用於體驗新功能\n\ - 測試版功能未受充分驗證,使用起來可能不穩定!\n\ - 如果你遇到了使用問題,可以在設置的 回饋頁面 中進行回饋,或加入 DiscordQQ 群以回饋問題。 +update.channel.nightly.hint=你正在使用 HMCL 預覽版。預覽版更新較為頻繁,包含一些未在穩定版和開發版中包含的測試性功能,僅用於體驗新功能。預覽版功能未受充分驗證,使用起來可能不穩定!\n\ + \n\ + 如果你遇到了使用問題,可以透過設定中 回報頁面 提供的管道進行回報。 update.channel.nightly.title=預覽版提示 -update.channel.stable=建議版本 +update.channel.stable=穩定版 update.checking=正在檢查更新 update.failed=更新失敗 update.found=發現到更新 -update.newest_version=最新版本為: %s -update.bubble.title=發現更新: %s +update.newest_version=最新版本為:%s +update.bubble.title=發現更新:%s update.bubble.subtitle=點選此處進行升級 -update.note=測試版與開發版包含更多的功能以及錯誤修復,但也可能會包含其他的問題。 +update.note=開發版與預覽版包含更多的功能以及錯誤修復,但也可能會包含其他的問題。 update.latest=目前版本為最新版本 -update.no_browser=無法開啟瀏覽器,網址已經複製到剪貼簿了,您可以手動複製網址開啟頁面 +update.no_browser=無法開啟瀏覽器,網址已經複製到剪貼簿了,你可以手動複製網址開啟頁面。 update.tooltip=更新 version=遊戲 -version.name=遊戲版本名稱 +version.name=遊戲實例名稱 version.cannot_read=讀取遊戲版本失敗,無法進行自動安裝 version.empty=沒有遊戲版本 version.empty.add=進入下載頁安裝遊戲 version.empty.launch=沒有可啟動的遊戲,你可以點擊左側邊欄內的下載按鈕安裝遊戲 version.empty.hint=沒有已安裝的遊戲,你可以切換其他遊戲目錄,或者點擊此處進入遊戲下載頁面 -version.game.old=老舊版本 -version.game.release=穩定版本 -version.game.releases=穩定版本 -version.game.snapshot=測試版本 -version.game.snapshots=測試版本 +version.game.old=遠古版 +version.game.release=正式版 +version.game.releases=正式版 +version.game.snapshot=快照 +version.game.snapshots=快照 version.launch=啟動遊戲 version.launch.test=測試遊戲 -version.switch=切換版本 +version.switch=切換實例 version.launch_script=生成啟動指令碼 version.launch_script.failed=生成啟動指令碼失敗 version.launch_script.save=儲存啟動指令碼 version.launch_script.success=啟動指令碼已生成完畢: %s -version.manage=遊戲列表 +version.manage=實例清單 version.manage.clean=清理遊戲目錄 -version.manage.clean.tooltip=清理 logs, crash-reports +version.manage.clean.tooltip=清理「logs」與「crash-reports」目錄 version.manage.duplicate=複製遊戲實例 version.manage.duplicate.duplicate_save=複製存檔 version.manage.duplicate.prompt=請輸入新遊戲實例名稱 -version.manage.duplicate.confirm=將鎖定複製產生的新遊戲實例:強制版本隔離、遊戲設置獨立。 -version.manage.manage=遊戲管理 -version.manage.manage.title=遊戲管理 - %1s +version.manage.duplicate.confirm=新的遊戲將複製該實例目錄 (".minecraft/versions/<實例名>") 下的檔案,並帶有獨立的執行目錄和設定。 +version.manage.manage=實例管理 +version.manage.manage.title=實例管理 - %1s version.manage.redownload_assets_index=更新遊戲資源檔案 version.manage.remove=刪除該版本 -version.manage.remove.confirm=真的要刪除版本 %s 嗎? 你將無法找回被刪除的檔案! -version.manage.remove.confirm.trash=真的要刪除版本 %s 嗎? 你可以在系統的資源回收桶 (或垃圾桶) 中還原資料夾 %s 來找回該版本。 -version.manage.remove.confirm.independent=由於該遊戲使用了版本隔離,所以刪除該版本將導致該遊戲的存檔等資料一同被刪除,真的要刪除版本 %s 嗎? +version.manage.remove.confirm=真的要刪除實例「%s」嗎? 你將無法找回被刪除的檔案!!! +version.manage.remove.confirm.trash=真的要刪除實例「%s」嗎? 你可以在系統的資源回收筒 (或垃圾桶) 中還原目錄「%s」來找回該實例。 +version.manage.remove.confirm.independent=由於該實例啟用了「(全域/實例特定) 遊戲設定 → 執行路徑 → 各實例獨立」設定,所以刪除該實例將導致該遊戲的存檔等資料一同被刪除,真的要刪除實例「%s」嗎? version.manage.remove_assets=刪除所有遊戲資源檔案 -version.manage.remove_libraries=刪除所有函式庫檔案 +version.manage.remove_libraries=刪除所有支援庫檔案 version.manage.rename=重新命名該版本 version.manage.rename.message=請輸入新名稱 -version.manage.rename.fail=重新命名版本失敗,可能檔案被佔用或者名稱有特殊字元 +version.manage.rename.fail=重新命名版本失敗,可能檔案被佔用或者名稱有特殊字元。 version.settings=遊戲設定 version.update=更新模組包 +wiki.tooltip=Minecraft Wiki 頁面 +wiki.version.game.release=https://zh.minecraft.wiki/w/Java版%s?variant=zh-tw +wiki.version.game.snapshot=https://zh.minecraft.wiki/w/%s?variant=zh-tw + wizard.prev=< 上一步 wizard.failed=失敗 wizard.finish=完成 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 0768edc4d1..ab55c1edaf 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -1,6 +1,6 @@ # # Hello Minecraft! Launcher -# Copyright (C) 2023 huangyuhui and contributors +# Copyright (C) 2025 huangyuhui 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 @@ -17,11 +17,12 @@ # # Contributors: huangyuhui + about=关于 about.copyright=版权 -about.copyright.statement=版权所有 © 2024 huangyuhui. +about.copyright.statement=版权所有 © 2025 huangyuhui about.author=作者 -about.author.statement=bilibili ID: @huanghongxun +about.author.statement=bilibili @huanghongxun about.claim=用户协议 about.claim.statement=点击链接以查看全文 about.dependency=依赖 @@ -29,21 +30,21 @@ about.legal=法律声明 about.thanks_to=鸣谢 about.thanks_to.bangbang93.statement=提供 BMCLAPI 下载源。请赞助支持 BMCLAPI! about.thanks_to.burningtnt.statement=为 HMCL 贡献许多技术支持 -about.thanks_to.contributors=所有通过 Issues、Pull Requests 等方式参与本项目的贡献者 -about.thanks_to.contributors.statement=没有开源社区的支持,Hello Minecraft! Launcher 就无法走到今天 +about.thanks_to.contributors=所有通过 Issue、Pull Request 等方式参与本项目的贡献者 +about.thanks_to.contributors.statement=没有开源社区的支持,HMCL 就无法走到今天 about.thanks_to.gamerteam.statement=提供默认背景图 about.thanks_to.glavo.statement=负责 HMCL 的日常维护 about.thanks_to.zekerzhayard.statement=为 HMCL 贡献许多技术支持 about.thanks_to.zkitefly.statement=负责维护 HMCL 的文档 -about.thanks_to.mcbbs=MCBBS 我的世界中文论坛 +about.thanks_to.mcbbs=MCBBS (我的世界中文论坛) about.thanks_to.mcbbs.statement=提供 MCBBS 下载源 (现已停止服务) -about.thanks_to.mcmod=MC 百科 -about.thanks_to.mcmod.statement=提供模组中文名映射表与 Mod 百科 +about.thanks_to.mcmod=MC 百科 (mcmod.cn) +about.thanks_to.mcmod.statement=提供模组简体中文名映射表与模组百科 about.thanks_to.red_lnn.statement=提供默认背景图 -about.thanks_to.shulkersakura.statement=提供 HMCL 的图标 +about.thanks_to.shulkersakura.statement=提供 HMCL 的徽标 about.thanks_to.users=HMCL 用户群成员 about.thanks_to.users.statement=感谢用户群成员赞助充电、积极催更、反馈问题、出谋划策 -about.thanks_to.yushijinhun.statement=authlib-injector 相关支持 +about.thanks_to.yushijinhun.statement=提供 authlib-injector 相关支持 about.open_source=开源 about.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL) @@ -58,28 +59,28 @@ account.create.authlibInjector=添加外置登录账户 (authlib-injector) account.email=邮箱 account.failed=账户刷新失败 account.failed.character_deleted=此角色已被删除 -account.failed.connect_authentication_server=无法连接认证服务器,可能是网络问题,请检查设备能否正常上网或使用代理服务\n你可以点击右上角帮助按钮进行求助。 -account.failed.connect_injector_server=无法连接认证服务器,可能是网络问题,请检查设备是否能正常上网,检查 URL 是否输入错误,或使用代理服务\n你可以点击右上角帮助按钮进行求助。 -account.failed.injector_download_failure=无法下载 authlib-injector,可能是网络问题,请检查设备是否能正常上网、尝试切换下载源或使用代理服务\n你可以点击右上角帮助按钮进行求助。 -account.failed.invalid_credentials=你的用户名或密码错误,或登录次数过多被暂时禁止登录,请稍后再试 +account.failed.connect_authentication_server=无法连接认证服务器,可能是网络问题,请检查设备能否正常上网或使用代理服务。\n你可以点击右上角帮助按钮进行求助。 +account.failed.connect_injector_server=无法连接认证服务器,可能是网络问题,请检查设备是否能正常上网、URL 是否输入错误,或使用代理服务。\n你可以点击右上角帮助按钮进行求助。 +account.failed.injector_download_failure=无法下载 authlib-injector,可能是网络问题,请检查设备是否能正常上网、尝试切换下载源或使用代理服务。\n你可以点击右上角帮助按钮进行求助。 +account.failed.invalid_credentials=你的用户名或密码错误,或登录次数过多被暂时禁止登录,请稍后再试。 account.failed.invalid_password=无效的密码 account.failed.invalid_token=请尝试登出并重新输入密码登录 -account.failed.migration=你的账户需要被迁移至微软账户。如果你已经迁移,你需要使用微软登录方式登录迁移后的微软账户 +account.failed.migration=你的账户需要迁移至微软账户。如果你已经迁移,你需要使用迁移后的微软账户登录。 account.failed.no_character=该账户没有角色 account.failed.server_disconnected=无法访问登录服务器,账户信息刷新失败。\n\ 你可以选择“再次刷新账户”重新尝试。\n\ - 你也可以选择“跳过账户刷新”继续启动游戏,但可能会使账户信息不是最新的。\n\ - 若最近没有刷新账户信息,则可能导致账户信息过期失效,\n\ - 若为 微软账户 启动游戏,账户信息过期失效可能将无法进入需账户验证的服务器。\n\ - 若尝试多次无法成功刷新,可尝试重新添加该账户尝试解决该问题。\n\ + 你也可以选择“跳过账户刷新”继续启动游戏,但可能会导致账户信息未同步更新。\n\ + 若最近没有刷新账户信息,则可能导致账户信息过期失效。\n\ + 若使用微软账户或外置登录账户启动游戏,账户信息过期失效可能将无法进入需在线验证的服务器。\n\ + 若尝试多次无法成功刷新,可尝试重新添加该账户,或许会解决该问题。\n\ 你可以点击右上角帮助按钮进行求助。 -account.failed.server_response_malformed=无法解析认证服务器响应,可能是服务器故障 +account.failed.server_response_malformed=无法解析认证服务器响应,可能是服务器故障。 account.failed.ssl=连接服务器时发生了 SSL 错误,可能网站证书已过期或你使用的 Java 版本过低,请尝试更新 Java,或关闭网络代理后再试。\n你可以点击右上角帮助按钮进行求助。 account.failed.wrong_account=登录了错误的账户 -account.hmcl.hint=你需要点击“登录”按钮,并在打开的网页中完成登录 +account.hmcl.hint=你需要点击“登录”按钮,并在弹出的网页中完成登录。 account.injector.add=添加认证服务器 -account.injector.empty=无(点击右侧加号添加) -account.injector.http=警告:此服务器使用不安全的 HTTP 协议,您的密码在登录时会被明文传输。 +account.injector.empty=无 (点击右侧加号添加) +account.injector.http=警告:此服务器使用不安全的 HTTP 协议,你的密码在登录时会被明文传输。 account.injector.link.homepage=主页 account.injector.link.register=注册 account.injector.server=认证服务器 @@ -90,79 +91,79 @@ account.login.hint=我们不会保存你的密码 account.login.skip=跳过账户刷新 account.login.retry=再次刷新账户 account.login.refresh=重新登录 -account.login.refresh.microsoft.hint=因为账户授权失效,你需要重新添加微软账户 +account.login.refresh.microsoft.hint=由于账户授权失效,你需要重新添加微软账户。 account.logout=登出 account.register=注册 account.manage=账户列表 -account.copy_uuid=复制该账户的 UUID。 +account.copy_uuid=复制该账户的 UUID account.methods=登录方式 account.methods.authlib_injector=外置登录 (authlib-injector) account.methods.microsoft=微软账户 -account.methods.microsoft.birth=如何修改账户出生日期 -account.methods.microsoft.close_page=已完成微软账户授权,接下来启动器还需要完成剩余登录步骤。你已经可以关闭本页面了。 +account.methods.microsoft.birth=如何更改账户出生日期 +account.methods.microsoft.close_page=已完成微软账户授权,接下来启动器还需要完成其余登录步骤。你现在可以关闭本页面了。 account.methods.microsoft.deauthorize=解除账户授权 -account.methods.microsoft.error.add_family=请点击上方【账户设置页】更改你的账户的出生日期,使年龄满 18 岁以上。或将账户加入到家庭中。\n你可以点击右上角帮助按钮进行求助。 -account.methods.microsoft.error.add_family_probably=请点击上方【账户设置页】更改你的账户的出生日期,使年龄满 18 岁以上。或将账户加入到家庭中。\n你可以点击右上角帮助按钮进行求助。 -account.methods.microsoft.error.country_unavailable=你所在的国家或地区不受 XBox Live 的支持。 -account.methods.microsoft.error.missing_xbox_account=请点击上方【创建档案】关联 XBox 账户。\n你可以点击右上角帮助按钮进行求助。 -account.methods.microsoft.error.no_character=请确认你购买了 Minecraft: Java 版。若已购买,该账户未包含 Minecraft Java 版购买记录\n请点击【创建档案】创建游戏档案。\n你可以点击右上角帮助按钮进行求助。 +account.methods.microsoft.error.add_family=请点击上方“编辑账户个人信息”更改你的账户出生日期,使年龄满 18 岁以上,或将账户加入到家庭中。\n你可以点击右上角帮助按钮进行求助。 +account.methods.microsoft.error.add_family_probably=请点击上方“编辑账户个人信息”更改你的账户出生日期,使年龄满 18 岁以上,或将账户加入到家庭中。\n你可以点击右上角帮助按钮进行求助。 +account.methods.microsoft.error.country_unavailable=你所在的国家或地区不受 Xbox Live 的支持。 +account.methods.microsoft.error.missing_xbox_account=请点击上方“创建档案”关联 Xbox 账户。\n你可以点击右上角帮助按钮进行求助。 +account.methods.microsoft.error.no_character=请确认你已经购买了 Minecraft: Java 版。\n若已购买,则可能未创建游戏档案,请点击上方“创建档案”以创建游戏档案。\n你可以点击右上角帮助按钮进行求助。 account.methods.microsoft.error.unknown=未知问题。错误码:%d。\n你可以点击右上角帮助按钮进行求助。 -account.methods.microsoft.error.wrong_verify_method=请在 Microsoft 账户登陆页面使用账户 + 密码登录。请不要使用验证码登录。\n你可以点击右上角帮助按钮进行求助。 +account.methods.microsoft.error.wrong_verify_method=请在微软账户登录页面使用密码登录,不要使用验证码登录。\n你可以点击右上角帮助按钮进行求助。 account.methods.microsoft.logging_in=登录中…… -account.methods.microsoft.makegameidsettings=创建档案/编辑档案名称 +account.methods.microsoft.makegameidsettings=创建档案 / 编辑档案名称 account.methods.microsoft.hint=你需要按照以下步骤添加账户:\n\ - 1.点击“登录”按钮\n\ - 2.在网页浏览器显示的网站中输入 HMCL 显示的代码(自动拷贝,直接粘贴即可),并点击“下一步”\n\ - 3.按照网站的提示登录\n\ - 4.当网站提示“是否允许此应用访问你的信息?”的标识时,请点击“是”\n\ - 5.在网站提示“大功告成”后,只需等待账户完成添加即可\n\ - -若网站提示“出现错误”的标识或账户添加失败时,请重新按照以上步骤重新添加\n\ - -若设备网络环境不佳,可能登录网站加载很慢甚至无法加载,此时请使用网络代理并重试\n\ + \ 1. 点击“登录”按钮;\n\ + \ 2. 在弹出的网页中输入 HMCL 显示的代码,并点击“允许访问”;\n\ + \ 3. 按照网站的提示登录;\n\ + \ 4. 当网站提示“是否允许此应用访问你的信息?”时,请点击“接受”;\n\ + \ 5. 在网站提示“大功告成”后,等待账户完成添加即可。\n\ + 若网站提示“出现错误”或账户添加失败时,请按照以上步骤重新添加。\n\ + 若设备网络环境不佳,可能会导致网页加载缓慢甚至无法加载,请使用网络代理并重试。\n\ 如遇到问题,你可以点击右上角帮助按钮进行求助。 account.methods.microsoft.manual=你需要按照以下步骤添加:\n\ - 1.点击“登录”按钮\n\ - 2.在网页浏览器显示的网站中输入:%1$s(已自动拷贝,点此再次拷贝),并点击“下一步”\n\ - 3.按照网站的提示登录\n\ - 4.当网站提示“是否允许此应用访问你的信息?”的标识时,请点击“是”\n\ - 5.在网站提示“大功告成”后,只需等待账户完成添加即可\n\ - -若网站提示“出现错误”的标识或账户添加失败时,请重新按照以上步骤重新添加\n\ - -若网站未能显示,请手动在网页浏览器中打开:%2$s\n\ - -若设备网络环境不佳,可能登录网站加载很慢甚至无法加载,此时请使用网络代理并重试\n\ + \ 1. 点击“登录”按钮;\n\ + \ 2. 在弹出的网页中输入 %1$s (已自动复制),并点击“允许访问”;\n\ + \ 3. 按照网站的提示登录;\n\ + \ 4. 当网站提示“是否允许此应用访问你的信息?”时,请点击“接受”;\n\ + \ 5. 在网站提示“大功告成”后,等待账户完成添加即可。\n\ + 若网站未能显示,请手动在浏览器中打开:%2$s\n\ + 若网站提示“出现错误”或账户添加失败时,请按照以上步骤重新添加。\n\ + 若设备网络环境不佳,可能会导致网页加载缓慢甚至无法加载,请使用网络代理并重试。\n\ 如遇到问题,你可以点击右上角帮助按钮进行求助。 -account.methods.microsoft.profile=账户设置页 +account.methods.microsoft.profile=编辑账户个人信息 account.methods.microsoft.purchase=购买 Minecraft account.methods.forgot_password=忘记密码 -account.methods.microsoft.snapshot=你正在使用非官方构建的 HMCL,请下载官方构建进行微软登录。 +account.methods.microsoft.snapshot=你正在使用第三方提供的 HMCL,请下载官方版本来登录微软账户。 account.methods.microsoft.snapshot.website=官方网站 account.methods.offline=离线模式 account.methods.offline.name.special_characters=建议使用英文字符、数字以及下划线命名 -account.methods.offline.name.invalid=正常情况下,游戏用户名只能包括英文字符、数字以及下划线,且长度不能超过 16 个字符。\n\ - 一些合法的用户名:HuangYu,huang_Yu,Huang_Yu_123;\n\ - 一些不合法的用户名:黄鱼,Huang Yu,Huang-Yu_%%%,Huang_Yu_hello_world_hello_world。\n\ - 如果你相信服务器端有相应的模组或插件来解除此限制,你可以忽略本警告。\n\ +account.methods.offline.name.invalid=游戏用户名通常仅允许使用英文字母、数字及下划线,且长度不能超过 16 个字符。\n\ + \ · 一些合法用户名:HuangYu、huang_Yu、Huang_Yu_123;\n\ + \ · 一些非法用户名:黄鱼、Huang Yu、Huang-Yu_%%%、Huang_Yu_hello_world_hello_world。\n\ + 如果你相信服务端有相应模组或插件解除此限制,可以忽略本警告。\n\ 如遇到问题,你可以点击右上角帮助按钮进行求助。 account.methods.offline.uuid=UUID -account.methods.offline.uuid.hint=UUID 是 Minecraft 对玩家角色的唯一标识符,每个启动器生成 UUID 的方式可能不同。通过修改 UUID 选项至原启动器所生成的 UUID,你可以保证在切换启动器后,游戏还能将你的游戏角色识别为给定 UUID 所对应的角色,从而保留原来角色的背包物品。UUID 选项为高级选项,除非你知道你在做什么,否则你不需要调整该选项。 +account.methods.offline.uuid.hint=UUID 是 Minecraft 玩家的唯一标识符,每个启动器生成 UUID 的方式可能不同。通过将 UUID 修改为原启动器所生成的 UUID,你可以保证在切换启动器后,游戏还能将你的游戏角色识别为给定 UUID 所对应的角色,从而保留原角色的背包物品。UUID 选项为高级选项,除非你知道你在做什么,否则你不需要调整该选项。 account.methods.offline.uuid.malformed=格式错误 account.missing=没有游戏账户 account.missing.add=点击此处添加账户 -account.move_to_global=转换为全局账户\n该账户的信息会保存至用户目录的配置文件中 -account.move_to_portable=转换为便携账户\n该账户的信息会保存至 HMCL 同目录的配置文件中 +account.move_to_global=转换为全局账户\n该账户的信息会保存至系统当前用户文件夹的配置文件中 +account.move_to_portable=转换为便携账户\n该账户的信息会保存至与 HMCL 同文件夹的配置文件中 account.not_logged_in=未登录 account.password=密码 account.portable=便携账户 account.skin=皮肤 account.skin.file=皮肤图片文件 account.skin.model=模型 -account.skin.model.default=经典 -account.skin.model.slim=苗条 +account.skin.model.default=宽型 +account.skin.model.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.hint=你需要在皮肤站中创建并使用和该离线账户角色同名角色,此时离线账户皮肤将为皮肤站上角色所设定的皮肤。\n你可以点击右上角帮助按钮进行求助。 +account.skin.type.little_skin.hint=你需要在皮肤站中创建并使用和该离线账户同名的角色,此时离线账户皮肤将显示为皮肤站上对应角色所设置的皮肤。\n你可以点击右上角帮助按钮进行求助。 account.skin.type.local_file=本地皮肤图片文件 -account.skin.upload=上传皮肤 +account.skin.upload=上传/编辑皮肤 account.skin.upload.failed=皮肤上传失败 account.skin.invalid_skin=无法识别的皮肤文件 account.username=用户名 @@ -174,7 +175,7 @@ archive.version=版本 assets.download=下载资源 assets.download_all=检查资源文件完整性 -assets.index.malformed=资源文件的索引文件损坏,您可以在相应的游戏管理,点击左下角的管理按钮选择【更新游戏资源文件】以修复该问题\n你可以点击右上角帮助按钮进行求助。 +assets.index.malformed=资源文件的索引文件损坏,你可以在相应版本的“版本管理”页面中,点击左下角“管理 → 更新游戏资源文件”以修复该问题。\n你可以点击右上角帮助按钮进行求助。 button.cancel=取消 button.change_source=切换下载源 @@ -188,11 +189,12 @@ button.no=否 button.ok=确定 button.refresh=刷新 button.remove=删除 -button.remove.confirm=您确定要删除吗?此操作无法撤销! +button.remove.confirm=你确定要删除吗?此操作无法撤销! button.retry=重试 button.save=保存 button.save_as=另存为 button.select_all=全选 +button.view=查看 button.yes=是 chat=官方群组 @@ -200,15 +202,15 @@ chat=官方群组 color.recent=推荐 color.custom=自定义颜色 -crash.NoClassDefFound=请确认 Hello Minecraft! Launcher 本体是否完整,\n或更新您的 Java。\n你可以访问\n https://docs.hmcl.net/help.html\n页面寻求帮助。 -crash.user_fault=您的系统或 Java 环境可能安装不当导致本软件崩溃,\n请检查您的 Java 环境或您的电脑。\n你可以访问\n https://docs.hmcl.net/help.html\n页面寻求帮助。 +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 页面寻求帮助。 curse.category.0=全部 # https://addons-ecs.forgesvc.net/api/v2/category/section/4471 curse.category.4474=科幻 curse.category.4481=轻量整合包 -curse.category.4483=战斗 PVP +curse.category.4483=战斗 PvP curse.category.4477=小游戏 curse.category.4478=任务 curse.category.4484=多人 @@ -228,9 +230,9 @@ curse.category.7418=恐怖 curse.category.5299=教育 curse.category.5232=额外行星 curse.category.5129=原版增强 -curse.category.5189=实用与QOL -curse.category.5190=QoL -curse.category.5191=实用与QoL +curse.category.5189=实用与 QOL +curse.category.5190=QOL +curse.category.5191=实用与 QOL curse.category.5192=梦幻菜单 curse.category.6145=空岛 curse.category.6814=性能 @@ -328,34 +330,34 @@ curse.sort.total_downloads=下载量 download=下载 download.hint=安装游戏和整合包或下载模组、资源包和地图 -download.code.404=远程服务器不包含需要下载的文件: %s +download.code.404=远程服务器不包含需要下载的文件: %s\n你可以点击右上角帮助按钮进行求助。 download.content=游戏内容 -download.curseforge.unavailable=HMCL 预览版暂不支持访问 CurseForge,请使用稳定版或测试版进行下载。 -download.existing=文件已存在,无法保存。你可以在模组选择栏中的右侧按钮另存为将文件保存至其他地方。 +download.curseforge.unavailable=HMCL 预览版暂不支持访问 CurseForge,请使用稳定版或开发版进行下载。 +download.existing=文件已存在,无法保存。你可以将文件保存至其他地方。 download.external_link=打开下载网站 -download.failed=下载失败: %1$s,错误码:%2$d -download.failed.empty=[没有可供安装的版本,点击此处返回] -download.failed.no_code=下载失败: %s -download.failed.refresh=[加载版本列表失败,点击此处重试] +download.failed=下载失败: %1$s,\n错误码:%2$d\n你可以点击右上角帮助按钮进行求助。 +download.failed.empty=[没有可供安装的版本,点击此处返回]\n(你可以点击右上角帮助按钮进行求助) +download.failed.no_code=下载失败 +download.failed.refresh=[加载版本列表失败,点击此处重试]\n(你可以点击右上角帮助按钮进行求助) download.game=新游戏 -download.provider.bmclapi=BMCLAPI(bangbang93,https://bmclapi2.bangbang93.com) -download.provider.mojang=官方(OptiFine 自动安装使用 BMCLAPI 下载源) -download.provider.official=尽量使用官方源(最新,但可能加载慢) -download.provider.balanced=选择加载速度快的下载源(平衡,但可能不是最新) -download.provider.mirror=尽量使用镜像源(加载快,但可能不是最新) +download.provider.bmclapi=BMCLAPI (bangbang93, https://bmclapi2.bangbang93.com) +download.provider.mojang=官方 (OptiFine 自动安装使用 BMCLAPI 下载源) +download.provider.official=尽量使用官方源 (最新,但可能加载慢) +download.provider.balanced=选择加载速度快的下载源 (平衡,但可能不是最新) +download.provider.mirror=尽量使用镜像源 (加载快,但可能不是最新) download.java=下载 Java download.java.override=此 Java 版本已经存在,是否卸载并重新安装? download.javafx=正在下载必要的运行时组件…… -download.javafx.notes=正在通过网络下载 HMCL 必要的运行时组件。\n点击“切换下载源”按钮查看详情以及选择下载源,点击“取消”按钮停止并退出。\n注意:若下载速度过慢,请尝试切换下载源 -download.javafx.component=正在下载模块 %s +download.javafx.notes=正在通过网络下载 HMCL 必要的运行时组件。\n点击“切换下载源”按钮查看详情以及选择下载源,点击“取消”按钮停止并退出。\n注意:若下载速度过慢,请尝试切换下载源。 +download.javafx.component=正在下载模块“%s” download.javafx.prepare=准备开始下载 -exception.access_denied=因为无法访问文件 %s,HMCL 没有对该文件的访问权限,或者该文件被其他程序打开。\n\ - 请你检查当前操作系统账户是否能访问该文件,比如非管理员用户可能不能访问其他账户的个人文件夹内的文件。\n\ - 对于 Windows 用户,你还可以尝试通过资源监视器查看是否有程序占用了该文件,如果是,请关闭占用此文件相关程序,或者重启电脑再试。\n\ +exception.access_denied=无法访问文件“%s”,因为 HMCL 没有对该文件的访问权限,或者该文件已被其他程序打开。\n\ + 请你检查当前操作系统账户是否能访问该文件,比如非管理员用户可能无法访问其他账户的个人文件夹内的文件。\n\ + 对于 Windows 用户,你还可以尝试通过资源监视器查看是否有程序占用了该文件,如果是,请关闭占用该文件的程序,或者重启电脑再试。\n\ 如遇到问题,你可以点击右上角帮助按钮进行求助。 exception.artifact_malformed=下载的文件无法通过校验。\n你可以点击右上角帮助按钮进行求助。 -exception.ssl_handshake=无法建立 SSL 连接,因为当前 Java 虚拟机缺少相关的 SSL 证书。你可以尝试使用其他的 Java 虚拟机启动 HMCL 再试。\n你可以点击右上角帮助按钮进行求助。 +exception.ssl_handshake=无法建立 SSL 连接,因为当前 Java 虚拟机缺少相关的 SSL 证书。你可以尝试使用其他 Java 虚拟机启动 HMCL 再试。\n你可以点击右上角帮助按钮进行求助。 extension.bat=Windows 脚本 extension.mod=模组文件 @@ -363,33 +365,32 @@ extension.png=图片文件 extension.ps1=PowerShell 脚本 extension.sh=Bash 脚本 -fatal.fractureiser=Hello Minecraft! Launcher 检测到你的电脑被 Fractureiser 病毒感染,存在严重安全问题。\n请立即使用杀毒软件进行全盘查杀,随后修改你在此电脑上登陆过的所有账户的密码。\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, 这可能导致你未来无法正常使用其他账户正常启动 Hello Minecraft! Launcher。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。\n是否继续启动? +fatal.config_change_owner_root=你正在使用 root 账户启动 Hello Minecraft! Launcher, 这可能导致你未来无法正常使用其他账户正常启动 HMCL。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。\n是否继续启动? fatal.config_in_temp_dir=你正在临时文件夹中启动 Hello Minecraft! Launcher, 你的设置和游戏数据可能会丢失,建议将 HMCL 移动至其他位置再启动。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。\n是否继续启动? -fatal.config_loading_failure=Hello Minecraft! Launcher 无法加载配置文件\n请确保 Hello Minecraft! Launcher 对 "%s" 目录及该目录下的文件拥有读写权限。\n对于 macOS,尝试将 HMCL 放在除「桌面」「下载」「文稿」之外的有权限的地方再试。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。 -fatal.config_loading_failure.unix=Hello Minecraft! Launcher 无法加载配置文件,因为配置文件是由用户 %1$s 创建的。\n请使用 root 账户启动 HMCL (不推荐),或在终端中执行以下命令将配置文件的所有权变更为当前用户:\n%2$s\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。 -fatal.mac_app_translocation=由于 macOS 的安全机制,Hello Minecraft! Launcher 被系统隔离至临时文件夹中。\n请将 Hello Minecraft! Launcher 移动到其他文件夹后再尝试启动,否则你的设置和游戏数据可能会在重启后丢失。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。\n是否继续启动? -fatal.migration_requires_manual_reboot=Hello Minecraft! Launcher 即将完成升级,请重新打开 Hello Minecraft! Launcher。\n如遇到问题,你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。 -fatal.apply_update_failure=我们很抱歉 Hello Minecraft! Launcher 无法自动完成升级,因为出现了一些问题。\n但你依可以从 %s 处手动下载 Hello Minecraft! Launcher 来完成升级\n你可以访问 https://docs.hmcl.net/help.html 网页进行反馈。 -fatal.apply_update_need_win7=Hello Minecraft! Launcher 无法在 Windows XP/Vista 上进行自动更新,请从 %s 处手动下载 Hello Minecraft! Launcher 来完成升级。 -fatal.samba=如果你正在通过 Samba 共享的文件夹中运行 Hello Minecraft! Launcher,启动器可能无法正常工作。请尝试更新你的 Java 或在本地文件夹内运行 Hello Minecraft! Launcher。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。 -fatal.illegal_char=由于你的用户文件夹路径中存在非法字符‘=’,你将无法使用外置登录账户以及离线登录更换皮肤功能。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。 -fatal.unsupported_platform=Minecraft 尚未为您的平台提供完善支持,所以可能影响游戏体验或无法启动游戏。\n若无法启动 Minecraft 1.17 及以上版本,可以尝试在版本设置中打开“使用 OpenGL 软渲染器”选项,使用 CPU 渲染以获得更好的兼容性。\n如遇到问题,你可以点击右上角帮助按钮进行求助。 +fatal.config_loading_failure=Hello Minecraft! Launcher 无法加载配置文件。\n请确保 HMCL 对“%s”文件夹及该文件夹下的文件拥有读写权限。\n对于 macOS,尝试将 HMCL 放在除“桌面”“下载”“文稿”之外的有权限的地方再试。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。 +fatal.config_loading_failure.unix=Hello Minecraft! Launcher 无法加载配置文件,因为配置文件是由用户“%1$s”创建的。\n请使用 root 账户启动 HMCL (不推荐),或在终端中执行以下命令将配置文件的所有权变更为当前用户:\n%2$s\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。 +fatal.mac_app_translocation=由于 macOS 的安全机制,Hello Minecraft! Launcher 被系统隔离至临时文件夹中。\n请将 HMCL 移动到其他文件夹后再尝试启动,否则你的设置和游戏数据可能会在重启后丢失。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。\n是否继续启动? +fatal.migration_requires_manual_reboot=Hello Minecraft! Launcher 即将完成升级,请重新打开 HMCL。\n如遇到问题,你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。 +fatal.apply_update_failure=我们很抱歉 Hello Minecraft! Launcher 无法自动完成升级,因为出现了一些问题。\n但你依可以从 %s 手动下载 HMCL 来完成升级。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。 +fatal.apply_update_need_win7=Hello Minecraft! Launcher 无法在 Windows XP/Vista 上进行自动更新,请从 %s 手动下载 HMCL 来完成升级。 +fatal.samba=如果你正在通过 Samba 共享的文件夹中运行 Hello Minecraft! Launcher,启动器可能无法正常工作。请尝试更新你的 Java 或在本地文件夹内运行 HMCL。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。 +fatal.illegal_char=由于你的用户文件夹路径中存在非法字符“=”,你将无法使用外置登录账户以及离线登录更换皮肤功能。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。 +fatal.unsupported_platform=Minecraft 尚未对你的平台提供完善支持,所以可能影响游戏体验或无法启动游戏。\n若无法启动 Minecraft 1.17 及更高版本,可以尝试在“(全局/版本特定) 游戏设置 → 高级设置 → 调试选项”中将“渲染器”切换为“软渲染器”,以获得更好的兼容性。\n如遇到问题,你可以点击右上角帮助按钮进行求助。 fatal.unsupported_platform.loongarch=Hello Minecraft! Launcher 已为龙芯提供支持。\n如果遇到问题,你可以点击右上角帮助按钮进行求助。 -fatal.unsupported_platform.osx_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\n如果你正在使用高通平台,你可能需要安装 OpenGL 兼容包后才能进行游戏。点击链接前往 Microsoft Store 安装兼容包。\n如遇到问题,你可以点击右上角帮助按钮进行求助。 +fatal.unsupported_platform.osx_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如果你正在使用 高通 平台,你可能需要安装 OpenGL 兼容包 后才能进行游戏。点击链接前往 Microsoft Store 安装兼容包。\n如遇到问题,你可以点击右上角帮助按钮进行求助。 feedback=反馈 feedback.channel=反馈渠道 feedback.discord=Discord -feedback.discord.statement=欢迎加入 Discord 讨论区,加入后请遵守讨论区规定。 +feedback.discord.statement=欢迎加入 Discord 服务器,加入后请遵守讨论区规定 feedback.github=GitHub Issue -feedback.github.statement=打开一个 GitHub Issue。 +feedback.github.statement=提交一个 GitHub Issue feedback.qq_group=HMCL 用户群 -feedback.qq_group.statement=欢迎加入 HMCL 用户群,加入后请遵守群规。 +feedback.qq_group.statement=欢迎加入 HMCL 用户群,加入后请遵守群规 file=文件 @@ -403,84 +404,81 @@ folder.saves=存档文件夹 folder.screenshots=截图文件夹 game=游戏 -game.crash.feedback=请不要将本界面截图给他人!如果你要求助他人,请你点击左下角 导出游戏崩溃信息 后将导出的文件发送给他人以供分析。\n你可以点击下方的 帮助 前往交流群寻求帮助。 +game.crash.feedback=请不要将本界面截图给他人!如果你要向他人求助,请你点击左下角“导出游戏崩溃信息”后将导出的文件发送给他人以供分析。\n你可以点击下方的“帮助”前往交流群寻求帮助。 game.crash.info=游戏信息 game.crash.reason=崩溃原因 game.crash.reason.analyzing=分析中…… -game.crash.reason.block=当前游戏因为某个方块不能正常工作,无法继续运行。\n你可以尝试通过 MCEdit 工具编辑存档删除该方块,或者直接删除相应的模组。\n方块类型:%1$s\n方块坐标:%2$s -game.crash.reason.bootstrap_failed=当前游戏因为模组 %1$s 错误,无法继续运行。\n你可以尝试删除或更新该模组以解决问题。 -game.crash.reason.mixin_apply_mod_failed=当前游戏因为 Mixin 无法应用 %1$s 模组,无法继续运行。\n你可以尝试删除或更新该 Mod 以解决问题。 -game.crash.reason.config=当前游戏因为无法解析模组配置文件,无法继续运行\n模组 %1$s 的配置文件 %2$s 无法被解析。 +game.crash.reason.block=当前游戏由于某个方块不能正常工作,无法继续运行。\n你可以尝试通过 MCEdit 工具编辑存档删除该方块,或者直接删除相应的模组。\n方块类型:%1$s\n方块坐标:%2$s +game.crash.reason.bootstrap_failed=当前游戏由于模组“%1$s”出现问题,无法继续运行。\n你可以尝试删除或更新该模组以解决问题。 +game.crash.reason.mixin_apply_mod_failed=当前游戏由于 Mixin 无法应用于“%1$s”模组,无法继续运行。\n你可以尝试删除或更新该模组以解决问题。 +game.crash.reason.config=当前游戏由于无法解析模组配置文件,无法继续运行。\n无法解析模组“%1$s”的配置文件“%2$s”。 game.crash.reason.multiple=检测到多个原因:\n\n -game.crash.reason.debug_crash=当前游戏因为手动触发崩溃,无法继续运行。\n事实上游戏并没有问题,问题都是你造成的! -game.crash.reason.duplicated_mod=当前游戏因为模组 %1$s 重复安装,无法继续运行。\n%2$s\n每种模组只能安装一个,请你删除多余的模组再试。 -game.crash.reason.entity=当前游戏因为某个实体不能正常工作,无法继续运行。\n你可以尝试通过 MCEdit 工具编辑存档删除该实体,或者直接删除相应的模组。\n实体类型:%1$s\n实体坐标:%2$s -game.crash.reason.fabric_version_0_12=Fabric 0.12 及以上版本与当前已经安装的模组可能不兼容,你需要将 Fabric 降级至 0.11.7。 +game.crash.reason.debug_crash=当前游戏由于手动触发崩溃,无法继续运行。\n事实上游戏并没有问题,问题都是你造成的! +game.crash.reason.duplicated_mod=当前游戏由于模组“%1$s”重复安装,无法继续运行。\n%2$s\n每种模组只能安装一个,请你删除多余的模组再试。 +game.crash.reason.entity=当前游戏由于某个实体不能正常工作,无法继续运行。\n你可以尝试通过 MCEdit 工具编辑存档删除该实体,或者直接删除相应的模组。\n实体类型:%1$s\n实体坐标:%2$s +game.crash.reason.fabric_version_0_12=Fabric Loader 0.12 及更高版本与当前已经安装的模组可能不兼容,你需要将 Fabric Loader 降级至 0.11.7。 game.crash.reason.fabric_warnings=Fabric 提供了一些警告信息:\n%1$s -game.crash.reason.file_already_exists=当前游戏因为文件 %1$s 已经存在,无法继续运行。\n如果你认为这个文件可以删除,你可以在备份这个文件后尝试删除它,并重新启动游戏。 -game.crash.reason.file_changed=当前游戏因为文件校验失败,无法继续运行。\n如果你手动修改了 Minecraft.jar 文件,你需要回退修改,或者重新下载游戏。 -game.crash.reason.gl_operation_failure=当前游戏因为你使用的某些模组、光影包、材质包,无法继续运行。\n请先尝试禁用你所使用的模组/光影包/材质包再试。 -game.crash.reason.graphics_driver=当前游戏因为显卡驱动问题而崩溃,请尝试以下操作:\n\ - - 如果你的电脑存在独立显卡,请尝试使用 独立显卡 而非 Intel 核显启动 HMCL 与游戏 详情 ;\n\ - - 尝试升级你的 显卡驱动 到最新版本,或回退到出厂版本;\n\ - - 如果你确实需要使用核芯显卡,请检查你的电脑的 CPU 是否是 Intel(R) Core(TM) 3000 系列或更旧的处理器,如果是,对于 Minecraft 1.16.5 及更旧版本,请你将游戏所使用的 Java 版本降级至 1.8.0_51 及以下版本 Java 1.8.0 历史版本 ,否则请跳过;\n\ - - 在全局(特定)游戏设置,高级设置中打开“使用 OpenGL 软渲染器”(开启此选项后帧数会显著降低,仅推荐在以调试为目的或应急时开启)。\n\ +game.crash.reason.file_already_exists=当前游戏由于文件“%1$s”已经存在,无法继续运行。\n如果你认为这个文件可以删除,你可以在备份这个文件后尝试删除它,并重新启动游戏。 +game.crash.reason.file_changed=当前游戏由于文件校验失败,无法继续运行。\n如果你手动修改了 Minecraft.jar 文件,你需要回退修改,或者重新下载游戏。 +game.crash.reason.gl_operation_failure=当前游戏由于你使用的某些模组/光影包/资源包有问题,导致无法继续运行。\n请先尝试禁用你所使用的模组/光影包/资源包再试。 +game.crash.reason.graphics_driver=当前游戏由于显卡驱动问题而崩溃,请尝试以下操作:\n\ + \ · 如果你的电脑存在独立显卡,请尝试使用独立显卡而非 Intel 核显启动 HMCL 与游戏;详情\n\ + \ · 尝试升级你的显卡驱动到最新版本,或回退到出厂版本;\n\ + \ · 如果你确实需要使用核芯显卡,请检查你电脑的 CPU 是否为 Intel(R) Core(TM) 3000 系列或更旧的处理器。如果是,对于 Minecraft 1.16.5 及更旧版本,请你将游戏所使用的 Java 降级至 1.8.0_51 及更低版本,否则请跳过;Java 1.8.0 历史版本\n\ 如果仍有问题,你可能需要考虑换一张新显卡或一台新电脑。 -game.crash.reason.macos_failed_to_find_service_port_for_display=当前游戏因为 Apple silicon 平台下初始化 OpenGL 窗口失败,无法继续运行。\n对于该问题,HMCL 暂无直接性的解决方案。请您尝试任意打开一个浏览器并全屏,然后再回到 HMCL 启动游戏,在弹出游戏窗口前迅速切回浏览器页面,等待游戏窗口出现后再切回游戏窗口。 -game.crash.reason.illegal_access_error=当前游戏因为某些模组的问题,无法继续运行。\n如果你认识:%1$s,你可以更新或删除对应模组再试。 -game.crash.reason.install_mixinbootstrap=当前游戏因为缺失 MixinBootstrap,无法继续运行。\n你可以尝试安装 MixinBootstrap 解决该问题。若安装后崩溃,尝试在该模组的文件名前加入英文“!”尝试解决。 -game.crash.reason.need_jdk11=当前游戏因为 Java 虚拟机版本不合适,无法继续运行。\n你需要下载安装 Java 11,并在全局(特定)游戏设置中将 Java 设置为 11 开头的版本。 -game.crash.reason.jdk_9=当前游戏因为 Java 版本过高,无法继续运行。\n你需要下载安装 Java 8,并在全局(特定)游戏设置中将 Java 设置为 1.8 的版本。 -game.crash.reason.jvm_32bit=当前游戏因为内存分配过大,超过了 32 位 Java 内存限制,无法继续运行。\n如果你的电脑是 64 位系统,请下载安装并更换 64 位 Java。下载 Java\n如果你的电脑是 32 位系统,你或许可以重新安装 64 位系统,或换一台新电脑。\n或者,你可以关闭游戏内存的自动分配,并且把内存限制调节为 1024 MB 或以下。 -game.crash.reason.loading_crashed_forge=当前游戏因为模组 %1$s (%2$s) 错误,无法继续运行。\n你可以尝试删除或更新该模组以解决问题。 -game.crash.reason.loading_crashed_fabric=当前游戏因为模组 %1$s 错误,无法继续运行。\n你可以尝试删除或更新该模组以解决问题。 -game.crash.reason.memory_exceeded=当前游戏因为分配的内存过大,无法继续运行。\n该问题是由于系统页面文件太小导致的。\n你需要在全局(特定)游戏设置中关闭游戏内存的自动分配,并将游戏内存调低至游戏能正常启动为止。\n你还可以尝试将 虚拟内存 设置调整至「自动管理所有驱动器分页文件大小」,详情。 -game.crash.reason.mac_jdk_8u261=当前游戏因为你所使用的 Forge 或 OptiFine 与 Java 冲突崩溃。\n请尝试更新 Forge 和 OptiFine,或使用 Java 8u251 及更早版本启动。 -game.crash.reason.mod=当前游戏因为 %1$s 的问题,无法继续运行。\n你可以更新或删除已经安装的 %1$s 再试。 -game.crash.reason.mod_resolution=当前游戏因为 Mod 依赖问题,无法继续运行。Fabric 提供了如下信息:\n%1$s -game.crash.reason.mod_resolution_collection=当前游戏因为前置 Mod 版本不匹配,无法继续运行。\n%1$s 需要前置 Mod:%2$s 才能继续运行。\n这表示你需要更新或降级前置。你可以到下载页的模组下载,或到网上下载 %3$s。 -game.crash.reason.mod_resolution_conflict=当前游戏因为 Mod 冲突,无法继续运行。\n%1$s 与 %2$s 不能兼容。 -game.crash.reason.mod_resolution_missing=当前游戏因为缺少 Mod 前置,无法继续运行。\n%1$s 需要前置 Mod:%2$s 才能继续运行。\n这表示你少安装了 Mod,或该 Mod 版本不够。你可以到下载页的模组下载,或到网上下载 %3$s。 -game.crash.reason.mod_resolution_missing_minecraft=当前游戏因为 Mod 和 Minecraft 游戏版本不匹配,无法继续运行。\n%1$s 需要 Minecraft %2$s 才能运行。\n如果你要继续使用你已经安装的 Mod,你可以选择安装对应的 Minecraft 版本;如果你要继续使用当前 Minecraft 版本,你需要安装对应版本的 Mod。 +game.crash.reason.macos_failed_to_find_service_port_for_display=当前游戏由于 Apple Silicon 平台下初始化 OpenGL 窗口失败,无法继续运行。\n对于该问题,HMCL 暂无直接性的解决方案。请你尝试任意打开一个浏览器并全屏,然后再回到 HMCL 启动游戏,在弹出游戏窗口前迅速切回浏览器页面,等待游戏窗口出现后再切回游戏窗口。 +game.crash.reason.illegal_access_error=当前游戏由于某些模组的问题,无法继续运行。\n如果你认识“%1$s”,你可以更新或删除对应模组再试。 +game.crash.reason.install_mixinbootstrap=当前游戏由于缺失 MixinBootstrap,无法继续运行。\n你可以尝试安装 MixinBootstrap 解决该问题。若安装后崩溃,尝试在该模组的文件名前加入半角感叹号 (!) 尝试解决。 +game.crash.reason.need_jdk11=当前游戏由于 Java 虚拟机版本不合适,无法继续运行。\n你需要下载安装 Java 11,并在“(全局/版本特定) 游戏设置 → 游戏 Java”中将 Java 设置为 11 开头的版本。 +game.crash.reason.jdk_9=当前游戏由于 Java 版本过高,无法继续运行。\n你需要下载安装 Java 8,并在“(全局/版本特定) 游戏设置 → 游戏 Java”中将 Java 设置为 1.8 的版本。 +game.crash.reason.jvm_32bit=当前游戏由于内存分配过大,超过了 32 位 Java 内存限制,无法继续运行。\n如果你的电脑是 64 位系统,请下载安装并更换 64 位 Java。下载 Java\n如果你的电脑是 32 位系统,你或许可以重新安装 64 位系统,或换一台新电脑。\n或者,你可以在“(全局/版本特定) 游戏设置 → 游戏内存”中关闭“自动分配内存”,并且把内存限制调节为 1024 MB 或以下。 +game.crash.reason.loading_crashed_forge=当前游戏由于模组“%1$s (%2$s)”错误,无法继续运行。\n你可以尝试删除或更新该模组以解决问题。 +game.crash.reason.loading_crashed_fabric=当前游戏由于模组“%1$s”错误,无法继续运行。\n你可以尝试删除或更新该模组以解决问题。 +game.crash.reason.memory_exceeded=当前游戏由于分配的内存过大,无法继续运行。\n该问题是由于系统页面文件太小导致的。\n你需要在全局(特定)游戏设置中关闭游戏内存的自动分配,并将游戏内存调低至游戏能正常启动为止。\n你还可以尝试将虚拟内存设置调整为“自动管理所有驱动器分页文件大小”,详情。 +game.crash.reason.mac_jdk_8u261=当前游戏由于你所使用的 Forge/OptiFine 与 Java 冲突而崩溃。\n请尝试更新 Forge/OptiFine,或使用 Java 8u251 及更早版本启动。 +game.crash.reason.mod=当前游戏由于“%1$s”的问题,无法继续运行。\n你可以更新或删除已经安装的“%1$s”再试。 +game.crash.reason.mod_resolution=当前游戏由于模组依赖问题,无法继续运行。Fabric 提供了如下信息:\n%1$s +game.crash.reason.mod_resolution_collection=当前游戏由于前置模组版本不匹配,无法继续运行。\n“%1$s”需要前置模组“%2$s”才能继续运行。\n这表示你需要更新或降级前置。你可以前往“下载 → 模组”页面下载,或到网上下载“%3$s”。 +game.crash.reason.mod_resolution_conflict=当前游戏由于模组冲突,无法继续运行。\n“%1$s”与“%2$s”不兼容。 +game.crash.reason.mod_resolution_missing=当前游戏由于缺少前置模组,无法继续运行。\n“%1$s”需要前置模组“%2$s”才能继续运行。\n这表示你少安装了模组,或该模组版本不够。你可以前往“下载 → 模组”页面下载,或到网上下载“%3$s”。 +game.crash.reason.mod_resolution_missing_minecraft=当前游戏由于模组和 Minecraft 游戏版本不匹配,无法继续运行。\n“%1$s”需要 Minecraft %2$s 才能运行。\n如果你要继续使用你已经安装的模组,你可以选择安装对应的 Minecraft 版本;如果你要继续使用当前 Minecraft 版本,你需要安装对应版本的模组。 game.crash.reason.mod_resolution_mod_version=%1$s (版本号 %2$s) game.crash.reason.mod_resolution_mod_version.any=%1$s (任意版本) -game.crash.reason.forge_repeat_installation=当前游戏因为 Forge 重复安装,无法继续运行。此为已知问题\n建议将日志上传反馈至 GitHub ,以便我们找到更多线索并修复此问题。\n目前你可以到 自动安装 里头卸载 Forge 并重新安装。 -game.crash.reason.optifine_repeat_installation=当前游戏因为 Optifine 重复安装,无法继续运行。\n请删除 Mod 文件夹下的 Optifine 或前往 游戏管理-自动安装 卸载自动安装的 Optifine。 -game.crash.reason.forgemod_resolution=当前游戏因为模组依赖问题,无法继续运行。Forge 提供了如下信息:\n%1$s -game.crash.reason.forge_found_duplicate_mods=当前游戏因为模组重复问题,无法继续运行。Forge 提供了如下信息:\n%1$s -game.crash.reason.modmixin_failure=当前游戏因为某些 Mod 注入失败,无法继续运行。\n这一般代表着该 Mod 存在 Bug,或与当前环境不兼容。\n你可以查看日志寻找出错模组。 -game.crash.reason.night_config_fixes=当前游戏因为 Night Config 库的一些问题,无法继续运行。\n你可以尝试安装 Night Config Fixes 模组,这或许能帮助你解决这个问题。\n了解更多,可访问该模组的 GitHub 仓库。 -game.crash.reason.forge_error=Forge 可能已经提供了错误信息。\n你可以查看日志,并根据错误报告中的日志信息进行对应处。\n如果没有看到报错信息,可以查看错误报告了解错误具体是如何发生的。\n%1$s -game.crash.reason.mod_resolution0=当前游戏因为一些模组出现问题,无法继续运行。\n你可以查看日志寻找出错模组。 -game.crash.reason.fabric_reports_an_error_and_gives_a_solution=Fabric 可能已经提供了错误信息。\n你可以查看日志,并根据错误报告中的日志信息进行对应处。\n如果没有看到报错信息,可以查看错误报告了解错误具体是如何发生的。 -game.crash.reason.java_version_is_too_high=当前游戏因为 Java 虚拟机版本过高,无法继续运行。\n请在全局(特定)游戏设置的 Java 路径选项卡中改用较低版本的 Java,然后再启动游戏。\n如果没有,可以从 java.com(Java8)BellSoft Liberica Full JRE(Java17) 等平台下载、安装一个(安装完后需重启启动器)。 -game.crash.reason.mod_name=当前游戏因为模组文件名称问题,无法继续运行。\n模组文件名称应只使用英文全半角的大小写字母(Aa~Zz)、数字(0~9)、横线(-)、下划线(_)和点(.)。\n请到模组文件夹中将所有不合规的模组文件名称添加一个上述的合规的字符。 -game.crash.reason.incomplete_forge_installation=当前游戏因为 Forge / NeoForge 安装不完整,无法继续运行。\n请在 版本设置 - 自动安装 中卸载 Forge 并重新安装。 -game.crash.reason.optifine_is_not_compatible_with_forge=当前游戏因为 OptiFine 与当前版本的 Forge 不兼容,导致了游戏崩溃。\n点击 此处 查看 OptiFine 所兼容的 Forge 版本,并严格按照对应版本重新安装游戏或在 版本设置 - 自动安装 中更换版本。\n经测试,Forge 版本过高或过低都可能导致崩溃。 -game.crash.reason.mod_files_are_decompressed=当前游戏因为模组文件被解压了,无法继续运行。\n请直接把整个模组文件放进模组文件夹中即可。\n解压模组会导致游戏出错,请删除模组文件夹中已被解压的模组,然后再启动游戏。 -game.crash.reason.shaders_mod=当前游戏因为同时安装了 OptiFine 和 Shaders 模组,无法继续运行。\n因为 OptiFine 已集成 Shaders 模组的功能,只需删除 Shaders 模组即可。 -game.crash.reason.rtss_forest_sodium=当前游戏因为 RivaTuner Statistics Server (RTSS) 与 Sodium 不兼容,导致了游戏崩溃。\n点击 此处 查看详情。 -game.crash.reason.too_many_mods_lead_to_exceeding_the_id_limit=当前游戏因为您所安装的模组过多,超出了游戏的 ID 限制,无法继续运行。\n请尝试安装 JEID 等修复模组,或删除部分大型模组。 -game.crash.reason.optifine_causes_the_world_to_fail_to_load=当前游戏可能因为 OptiFine ,无法继续运行。\n该问题只在特定 OptiFine 版本中出现,你可以尝试在 版本设置 - 自动安装 中更换 OptiFine 的版本。 -game.crash.reason.modlauncher_8=当前游戏因为您所使用的 Forge 版本与当前使用的 Java 冲突崩溃,请尝试更新 Forge 到 36.2.26 或更高版本或换用版本低于 1.8.0.320 的 Java,Liberica JDK 8u312+7。 -game.crash.reason.no_class_def_found_error=当前游戏因为代码不完整,无法继续运行。\n你的游戏可能缺失了某个模组,或者某些模组文件不完整,或者模组与游戏的版本不匹配。\n你可能需要重新安装游戏和模组,或请求他人帮助。\n缺失:\n%1$s -game.crash.reason.no_such_method_error=当前游戏因为代码不完整,无法继续运行。\n你的游戏可能缺失了某个模组,或者某些模组文件不完整,或者模组与游戏的版本不匹配。\n你可能需要重新安装游戏和模组,或请求他人帮助。 -game.crash.reason.opengl_not_supported=当前游戏因为你的显卡驱动存在问题,无法继续运行。\n原因是 OpenGL 不受支持,你现在是否在远程桌面或者串流模式下?如果是,请直接使用原电脑启动游戏。\n或者尝试升级你的显卡驱动到最新版本后再尝试启动游戏。如果你的电脑存在独立显卡,你需要检查游戏是否使用集成/核心显卡启动,如果是,请尝试使用独立显卡启动 HMCL 与游戏。如果仍有问题,你可能需要考虑换一个新显卡或新电脑。 -game.crash.reason.openj9=当前游戏无法运行在 OpenJ9 虚拟机上,请你在全局(特定)游戏设置中更换 Hotspot Java 虚拟机,并重新启动游戏。如果没有下载安装,你可以在网上自行下载。 -game.crash.reason.out_of_memory=当前游戏因为内存不足,无法继续运行。\n这可能是内存分配太小,或者模组数量过多导致的。\n你可以在全局(特定)游戏设置中调大游戏内存分配值以允许游戏在更大的内存下运行。\n如果仍然出现该错误,你可能需要换一台更好的电脑。 -game.crash.reason.resolution_too_high=当前游戏因为材质包分辨率过高,无法继续运行\n你可以更换一个分辨率更低的材质,或者更换一个显存更大的显卡。 +game.crash.reason.forge_repeat_installation=当前游戏由于 Forge 重复安装,无法继续运行。此为已知问题\n建议将日志上传并反馈至 GitHub,以便我们找到更多线索并修复此问题。\n目前你可以在“版本管理 → 自动安装”中卸载 Forge 并重新安装。 +game.crash.reason.optifine_repeat_installation=当前游戏由于 OptiFine 重复安装,无法继续运行。\n请删除模组文件夹下的 OptiFine 或前往“版本管理 → 自动安装”卸载安装的 OptiFine。 +game.crash.reason.forgemod_resolution=当前游戏由于模组依赖问题,无法继续运行。Forge/NeoForge 提供了如下信息:\n%1$s +game.crash.reason.forge_found_duplicate_mods=当前游戏由于模组重复安装,无法继续运行。Forge/NeoForge 提供了如下信息:\n%1$s +game.crash.reason.modmixin_failure=当前游戏由于某些模组注入失败,无法继续运行。\n这一般代表着该模组存在问题,或与当前环境不兼容。\n你可以查看日志寻找出错模组。 +game.crash.reason.night_config_fixes=当前游戏由于 Night Config 库的一些问题,无法继续运行。\n你可以尝试安装 Night Config Fixes 模组,这或许能帮助你解决这个问题。\n更多详情,可访问该模组的 GitHub 仓库。 +game.crash.reason.forge_error=Forge/NeoForge 可能已经提供了错误信息。\n你可以查看日志,并根据错误报告中的日志信息进行对应处理。\n如果没有看到报错信息,可以查看错误报告了解错误具体是如何发生的。\n%1$s +game.crash.reason.mod_resolution0=当前游戏由于一些模组出现问题,无法继续运行。\n你可以查看日志寻找出错模组。 +game.crash.reason.java_version_is_too_high=当前游戏由于 Java 虚拟机版本过高,无法继续运行。\n请在“(全局/版本特定) 游戏设置 → 游戏 Java”中改用较低版本的 Java,然后再启动游戏。\n如果没有,可以从 java.com (Java 8)BellSoft Liberica Full JRE (Java 17) 等平台下载、安装一个 (安装完后需重启启动器)。 +game.crash.reason.mod_name=当前游戏由于模组文件名称问题,无法继续运行。\n模组文件名称应只使用半角的大小写字母 (Aa~Zz)、数字 (0~9)、横线 (-)、下划线 (_)和点 (.)。\n请到模组文件夹中将所有不合规的模组文件名修改为上述合规字符。 +game.crash.reason.incomplete_forge_installation=当前游戏由于 Forge/NeoForge 安装不完整,无法继续运行。\n请在“版本管理 → 自动安装”中卸载 Forge 并重新安装。 +game.crash.reason.optifine_is_not_compatible_with_forge=当前游戏由于 OptiFine 与当前版本的 Forge 不兼容,导致游戏崩溃。\n点击 此处 查看 OptiFine 所兼容的 Forge 版本,并严格按照对应版本重新安装游戏或在“版本管理 → 自动安装”中更换版本。\n经测试,Forge 版本过高或过低都可能导致崩溃。 +game.crash.reason.mod_files_are_decompressed=当前游戏由于模组文件被解压了,无法继续运行。\n请直接把整个模组文件放进模组文件夹中即可。\n解压模组会导致游戏出错,请删除模组文件夹中已被解压的模组,然后再启动游戏。 +game.crash.reason.shaders_mod=当前游戏由于同时安装了 OptiFine 和 Shaders 模组,无法继续运行。\n由于 OptiFine 已集成 Shaders 模组的功能,所以只需删除 Shaders 模组即可。 +game.crash.reason.rtss_forest_sodium=当前游戏由于 RivaTuner Statistics Server (RTSS) 与 Sodium 不兼容,导致游戏崩溃。\n点击 此处 查看详情。 +game.crash.reason.too_many_mods_lead_to_exceeding_the_id_limit=当前游戏由于你所安装的模组过多,超出了游戏的 ID 限制,无法继续运行。\n请尝试安装 JEID 等修复模组,或删除部分大型模组。 +game.crash.reason.optifine_causes_the_world_to_fail_to_load=当前游戏可能由于 OptiFine 而无法继续运行。\n该问题只在特定 OptiFine 版本中出现,你可以尝试在“版本管理 → 自动安装”中更换 OptiFine 的版本。 +game.crash.reason.modlauncher_8=当前游戏由于你所使用的 Forge 版本与当前使用的 Java 冲突而崩溃,请尝试更新 Forge 到 36.2.26 或更高版本或换用版本低于 1.8.0.320 的 Java。Liberica JDK 8u312+7 +game.crash.reason.no_class_def_found_error=当前游戏由于代码不完整,无法继续运行。\n你的游戏可能缺失了某个模组,或者某些模组文件不完整,或者模组与游戏的版本不匹配。\n你可能需要重新安装游戏和模组,或寻求他人帮助。\n缺失:\n%1$s +game.crash.reason.no_such_method_error=当前游戏由于代码不完整,无法继续运行。\n你的游戏可能缺失了某个模组,或者某些模组文件不完整,或者模组与游戏的版本不匹配。\n你可能需要重新安装游戏和模组,或寻求他人帮助。 +game.crash.reason.opengl_not_supported=当前游戏由于你的显卡驱动存在问题,无法继续运行。\n原因是 OpenGL 不受支持,你现在是否在远程桌面或者串流模式下?如果是,请直接使用原电脑启动游戏。\n或者尝试升级你的显卡驱动到最新版本后再尝试启动游戏。如果你的电脑存在独立显卡,你需要检查游戏是否使用集成/核心显卡启动,如果是,请尝试使用独立显卡启动 HMCL 与游戏。如果仍有问题,你可能需要考虑换一个新显卡或新电脑。 +game.crash.reason.openj9=当前游戏无法在 OpenJ9 虚拟机上运行,请你在“(全局/版本特定) 游戏设置 → 游戏 Java”中更换 Hotspot Java 虚拟机,并重新启动游戏。如果没有下载安装,你可以在网上自行下载。 +game.crash.reason.out_of_memory=当前游戏由于内存不足,无法继续运行。\n这可能是内存分配太小,或者模组数量过多导致的。\n你可以在全局(特定)游戏设置中调大游戏内存分配值以允许游戏在更大的内存下运行。\n如果仍然出现该错误,你可能需要换一台更好的电脑。 +game.crash.reason.resolution_too_high=当前游戏由于材质包分辨率过高,无法继续运行\n你可以更换一个分辨率更低的材质,或者更换一个显存更大的显卡。 game.crash.reason.stacktrace=原因未知,请点击日志按钮查看详细信息。\n下面是一些关键词,其中可能包含模组名称,你可以通过搜索的方式查找有关信息。\n%s -game.crash.reason.too_old_java=当前游戏因为 Java 虚拟机版本过低,无法继续运行。\n你需要在全局(特定)游戏设置中更换 Java %1$s 或更新版本的 Java 虚拟机,并重新启动游戏。如果没有下载安装,你可以点击 此处 下载 Liberica JDK。 +game.crash.reason.too_old_java=当前游戏由于 Java 虚拟机版本过低,无法继续运行。\n你需要在“(全局/版本特定) 游戏设置 → 游戏 Java”中更换 Java %1$s 或更新版本的 Java 虚拟机,并重新启动游戏。如果没有下载安装,你可以点击 此处 下载 Microsoft JDK。 game.crash.reason.unknown=原因未知,请点击日志按钮查看详细信息。 -game.crash.reason.unsatisfied_link_error=当前游戏因为缺少本地库,无法继续运行。\n这些本地库缺失:%1$s。\n如果你在全局(特定)游戏设置中修改了本地库路径选项,请你修改回预设模式。\n如果你正在使用预设模式,请检查游戏路径是否只包含英文大小写字母,数字,下划线,\n如果是,那么请检查是否是由于模组或者 HMCL 导致了本地库缺失的问题。如果你确定是 HMCL 引起的,建议你向我们反馈。\n你可以尝试在控制在 控制面板 时钟和区域 区域 管理 更改系统区域设置 中将 当前系统区域设置选项卡修改为:中文(简体,中国),并且把 使用Unicode UTF-8提供全球语言支持 关闭;\n或将游戏路径中的所有 非英文字符的名称 修改为 英文字符(例如中文,空格等)\n如果你确实需要自定义本地库路径,你需要保证其中包含缺失的本地库! -game.crash.reason.failed_to_load_a_library=当前游戏因为加载本地库失败,无法继续运行。\n如果你在全局(特定)游戏设置中修改了本地库路径选项,请你修改回预设模式。\n如果已经在预设模式下,请检查本地库缺失是否是 Mod 引起的,或由 HMCL 引起的。如果你确定是 HMCL 引起的,建议你向我们反馈。\n你可以尝试在控制在 控制面板 时钟和区域 区域 管理 更改系统区域设置 中将 当前系统区域设置选项卡修改为:中文(简体,中国),并且把 使用Unicode UTF-8提供全球语言支持 关闭;\n或将游戏路径中的所有 非英文字符的名称 修改为 英文字符(例如中文,空格等)\n如果你确实需要自定义本地库路径,你需要保证其中包含缺失的本地库! +game.crash.reason.unsatisfied_link_error=当前游戏由于缺少本地库,无法继续运行。\n这些本地库缺失:%1$s。\n如果你在“(全局/版本特定) 游戏设置 → 高级设置”中修改了本地库路径选项,请你修改回默认模式。\n如果你正在使用默认模式,请检查游戏文件夹路径是否只包含英文字母、数字和下划线,\n如果是,那么请检查是否为模组或 HMCL 导致了本地库缺失的问题。如果你确定是 HMCL 引起的,建议你向我们反馈。\n对于 Windows 用户,你还可以尝试在“控制面板 → 时钟和区域 → 区域 → 管理 → 更改系统区域设置”中将“当前系统区域设置”修改为“中文(简体,中国大陆)”,并关闭“Beta 版:使用 Unicode UTF-8 提供全球语言支持”选项;\n或将游戏文件夹路径中的所有非英文字符的名称 (例如中文、空格等) 修改为英文字符。\n如果你确实需要自定义本地库路径,你需要保证其中包含缺失的本地库! game.crash.title=游戏意外退出 -game.directory=游戏路径 +game.directory=游戏文件夹路径 game.version=游戏版本 help=帮助 help.doc=Hello Minecraft! Launcher 帮助文档 -help.detail=可查阅数据包、整合包制作指南等内容。 +help.detail=可查阅数据包、整合包制作指南等内容 input.email=用户名必须是邮箱 input.number=必须是数字 @@ -495,35 +493,36 @@ install.failed.downloading=安装失败,部分文件未能完成下载 install.failed.downloading.detail=未能下载文件:%s install.failed.downloading.timeout=下载超时:%s install.failed.install_online=无法识别要安装的组件。如果你要安装模组,你需要在模组管理页面安装模组。 -install.failed.malformed=下载的文件格式损坏。您可以在设置-下载中切换到其他下载源来尝试解决此问题。 -install.failed.optifine_conflict=暂不支持 OptiFine, Fabric 或 OptiFine , Forge 同时安装在 Minecraft 1.13 及以上版本 -install.failed.optifine_forge_1.17=Minecraft 1.17.1 下,仅 OptiFine H1 Pre2 及以上版本能兼容 Forge。你可以从 OptiFine 测试版中选择最新版本。 +install.failed.malformed=下载的文件已损坏。你可以在“设置 → 下载 → 下载源”中切换其他下载源来尝试解决此问题。 +install.failed.optifine_conflict=暂不支持在 Minecraft 1.13 及更高版本同时安装 OptiFine 与 Fabric。 +install.failed.optifine_forge_1.17=对于 Minecraft 1.17.1 版本,仅 OptiFine H1 pre2 及更高版本与 Forge 兼容。你可以在 OptiFine 预览版 (Preview versions) 中选择最新版本。 install.failed.version_mismatch=该组件需要的游戏版本为 %s,但实际的游戏版本为 %s。 install.installer.change_version=%s 与当前游戏不兼容,请更换版本 install.installer.choose=选择 %s 版本 install.installer.depend=需要先安装 %s install.installer.fabric=Fabric install.installer.fabric-api=Fabric API -install.installer.fabric-api.warning=警告:Fabric API 是一个模组,将会被安装到新游戏的模组文件夹,请你在安装游戏后不要修改当前游戏的版本隔离/游戏运行路径设置,如果你在之后修改了相关设置,Fabric API 需要被重新安装。 +install.installer.fabric-api.warning=警告:Fabric API 是一个模组,将会被安装到新游戏的模组文件夹,请你在安装游戏后不要修改当前游戏的“运行路径”设置,如果你在之后修改了相关设置,则需要重新安装 Fabric API。 install.installer.forge=Forge install.installer.neoforge=NeoForge install.installer.game=Minecraft install.installer.incompatible=与 %s 不兼容 install.installer.install=安装 %s install.installer.install_offline=从本地文件安装/升级 -install.installer.install_offline.extension=Forge/OptiFine 安装器 -install.installer.install_offline.tooltip=支持导入已经下载好的 Forge/OptiFine 安装器 +install.installer.install_offline.extension=(Neo)Forge/OptiFine 安装器 +install.installer.install_offline.tooltip=支持导入已经下载好的 (Neo)Forge/OptiFine 安装器 install.installer.install_online=在线安装 -install.installer.install_online.tooltip=支持安装 Fabric、Forge、OptiFine、LiteLoader +install.installer.install_online.tooltip=支持安装 Forge、NeoForge、Fabric、Quilt、LiteLoader 和 OptiFine install.installer.liteloader=LiteLoader install.installer.not_installed=不安装 install.installer.optifine=OptiFine install.installer.quilt=Quilt install.installer.quilt-api=QSL/QFAPI install.installer.version=%s -install.installer.external_version=%s 由外部安装的版本,无法卸载或更换 +install.installer.external_version=%s (由外部安装的版本,无法卸载或更换) install.modpack=安装整合包 -install.new_game=安装新游戏版本 +install.name.invalid=名称中包含非 ASCII 字符(如 Emoji 表情或中文字符)。\n建议修改名称,名称建议仅包含英文字母、数字和下划线,以防启动游戏时出现问题。是否继续安装? +install.new_game=安装新游戏 install.new_game.already_exists=此版本已经存在,请换一个名字 install.new_game.current_game_version=当前游戏版本 install.new_game.malformed=名字不合法 @@ -533,11 +532,12 @@ install.success=安装成功 java.add=添加 Java java.add.failed=Java 无效或与当前平台不兼容。 java.disable=禁用此 Java -java.disable.confirm=您确定要禁用此 Java 吗? +java.disable.confirm=你确定要禁用此 Java 吗? java.disabled.management=管理已禁用的 Java java.disabled.management.remove=从列表中移除此 Java java.disabled.management.restore=重新启用此 Java java.download=下载 Java +java.download.banshanjdk-8=下载 Banshan JDK 8 java.download.load_list.failed=加载版本列表失败 java.download.more=更多发行版 java.download.prompt=请选择你要下载的 Java 版本: @@ -556,48 +556,48 @@ java.install.failed.invalid=该压缩包不是合法的 Java 安装包,无法 java.install.failed.unsupported_platform=此 Java 与当前平台不兼容,无法安装。 java.install.name=名称 java.install.warning.invalid_character=名称中包含非法字符 -java.reveal=浏览 Java 目录 +java.reveal=浏览 Java 文件夹 java.uninstall=卸载此 Java -java.uninstall.confirm=您确定要卸载此 Java 吗?此操作无法撤销! +java.uninstall.confirm=你确定要卸载此 Java 吗?此操作无法撤销! lang=简体中文 lang.default=跟随系统语言 -launch.advice=%s是否继续启动? +launch.advice=%s 是否继续启动? launch.advice.multi=检测到以下问题:\n\n%s\n\n这些问题可能导致游戏无法正常启动或影响游戏体验,是否继续启动?\n你可以点击右上角帮助按钮进行求助。 -launch.advice.java.auto=当前选择的 Java 虚拟机版本不满足游戏要求\n点击“是”即可由 HMCL 来自动选取合适的 Java 虚拟机版本\n或者你可以到全局(特定)游戏设置中选择一个合适的 Java 虚拟机版本。 -launch.advice.java.modded_java_7=Minecraft 1.7.2 及以下版本需要 Java 7 及以下版本。 -launch.advice.corrected=我们已经修复了 Java 虚拟机版本问题。如果您确实希望使用您自定义的 Java 虚拟机,您可以在全局(特定)游戏设置往下滑,关闭 Java 虚拟机兼容性检查。 -launch.advice.uncorrected=如果您确实希望使用您自定义的 Java 虚拟机,您可以在全局(特定)游戏设置往下滑,关闭 Java 虚拟机兼容性检查 +launch.advice.java.auto=当前选择的 Java 虚拟机版本不满足游戏要求。\n点击“是”即可由 HMCL 来自动选取合适的 Java 虚拟机版本。\n或者你可以在“(全局/版本特定) 游戏设置 → 游戏 Java”中选择一个合适的 Java 虚拟机版本。 +launch.advice.java.modded_java_7=Minecraft 1.7.2 及更低版本需要 Java 7 及更低版本。 +launch.advice.corrected=我们已经修复了 Java 虚拟机版本问题。如果你确实希望使用你自定义的 Java 虚拟机,你可以在“(全局/版本特定) 游戏设置 → 高级设置”中往下滑,启用“不检查 JVM 与游戏的兼容性”。 +launch.advice.uncorrected=如果你确实希望使用你自定义的 Java 虚拟机,你可以在“(全局/版本特定) 游戏设置 → 高级设置”中往下滑,启用“不检查 JVM 与游戏的兼容性”。 launch.advice.different_platform=你正在使用 32 位 Java 启动游戏,建议更换至 64 位 Java。 -launch.advice.forge2760_liteloader=Forge 2760 与 LiteLoader 不兼容,请更新 Forge 至 2773 或更新的版本。 -launch.advice.forge28_2_2_optifine=Forge 28.2.2 或更高版本与 OptiFine 不兼容。请将 Forge 降级至 28.2.1 或更低版本。 -launch.advice.forge37_0_60=Forge 低于 37.0.60 的版本不兼容 Java 17。请更新 Forge 到 37.0.60 或更高版本,或者使用 Java 16 启动游戏。 -launch.advice.java8_1_13=Minecraft 1.13 及以上版本只能运行在 Java 8 或更高版本上,请使用 Java 8 或最新版本。 +launch.advice.forge2760_liteloader=Forge 14.23.5.2760 与 LiteLoader 不兼容,请更新 Forge 至 14.23.5.2773 或更高版本。 +launch.advice.forge28_2_2_optifine=Forge 28.2.2 及更高版本与 OptiFine 不兼容。请降级 Forge 至 28.2.1 或更低版本。 +launch.advice.forge37_0_60=Forge 37.0.59 及更低版本与 Java 17 不兼容。请更新 Forge 至 37.0.60 或更高版本,或使用 Java 16 启动游戏。 +launch.advice.java8_1_13=Minecraft 1.13 及更高版本只能在 Java 8 或更高版本上运行,请使用 Java 8 或最新版本。 launch.advice.java8_51_1_13=低于 1.8.0_51 的 Java 版本可能会导致 Minecraft 1.13 崩溃,建议更新 Java 至 1.8.0_51 或更高版本后再次启动。 -launch.advice.java9=低于 1.13 的有安装模组的 Minecraft 版本不支持 Java 9 或更高版本,请使用 Java 8。 +launch.advice.java9=低于 1.13 的有安装模组的 Minecraft 版本与 Java 9 或更高版本不兼容,请使用 Java 8。 launch.advice.modded_java=部分模组可能与高版本 Java 不兼容,建议使用 Java %s 启动 Minecraft %s。 -launch.advice.modlauncher8=您所使用的 Forge 版本与当前使用的 Java 不兼容,请更新 Forge。 +launch.advice.modlauncher8=你所使用的 Forge 版本与当前使用的 Java 不兼容,请更新 Forge。 launch.advice.newer_java=检测到你正在使用旧版本 Java 启动游戏,这可能导致部分模组引发游戏崩溃,建议更新至 Java 8 后再次启动。 -launch.advice.not_enough_space=你设置的内存大小过大,超过了系统内存容量 %dMB,可能导致游戏无法启动。 +launch.advice.not_enough_space=你设置的内存大小过大,超过了系统内存容量 %d MB,可能导致游戏无法启动。 launch.advice.require_newer_java_version=当前游戏版本需要 Java %s,但 HMCL 未能找到该 Java 版本,你可以点击“是”,HMCL 会自动下载他,是否下载?\n如遇到问题,你可以点击右上角帮助按钮进行求助。 -launch.advice.too_large_memory_for_32bit=您设置的内存大小过大,由于可能超过了 32 位 Java 的内存分配限制,所以可能无法启动游戏,请将内存调至 1024MB 或更小。\n如遇到问题,你可以点击右上角帮助按钮进行求助。 -launch.advice.vanilla_linux_java_8=对于 Linux x86-64 平台,Minecraft 1.12.2 及以下版本与 Java 9+ 不兼容,请使用 Java 8 启动游戏。\n如遇到问题,你可以点击右上角帮助按钮进行求助。 -launch.advice.vanilla_x86.translation=Minecraft 尚未为您的平台提供完善支持,所以可能影响游戏体验或无法启动游戏。\n你可以在 这里 下载 X86-64 架构的 Java 以获得更完整的体验。 +launch.advice.too_large_memory_for_32bit=你设置的内存大小过大,由于可能超过了 32 位 Java 的内存分配限制,所以可能无法启动游戏,请将内存调至 1024 MB 或更小。\n如遇到问题,你可以点击右上角帮助按钮进行求助。 +launch.advice.vanilla_linux_java_8=对于 Linux x86-64 平台,Minecraft 1.12.2 及更低版本与 Java 9+ 不兼容,请使用 Java 8 启动游戏。\n如遇到问题,你可以点击右上角帮助按钮进行求助。 +launch.advice.vanilla_x86.translation=Minecraft 尚未为你的平台提供完善支持,所以可能影响游戏体验或无法启动游戏。\n你可以在 这里 下载 x86-64 架构的 Java 以获得更完整的体验。 launch.advice.unknown=由于以下原因,无法继续启动游戏: launch.failed=启动失败 -launch.failed.cannot_create_jvm=截获到无法创建 Java 虚拟机,可能是 Java 参数有问题,可以在设置中开启无参数模式启动。 +launch.failed.cannot_create_jvm=无法创建 Java 虚拟机,可能是 Java 参数有问题,你可以在“(全局/版本特定) 游戏设置 → 高级设置 → Java 虚拟机设置”中移除所有 Java 虚拟机参数后,尝试再次启动游戏。 launch.failed.creating_process=启动失败,在创建新进程时发生错误,可能是 Java 路径错误。 -launch.failed.command_too_long=命令长度超过限制,无法创建 bat 脚本,请导出为 PowerShell 脚本。 +launch.failed.command_too_long=命令长度超过限制,无法创建批处理脚本,请导出为 PowerShell 脚本。 launch.failed.decompressing_natives=未能解压游戏本地库。 -launch.failed.download_library=未能下载游戏依赖 %s +launch.failed.download_library=未能下载游戏依赖“%s” launch.failed.executable_permission=未能为启动文件添加执行权限。 launch.failed.execution_policy=设置执行策略 launch.failed.execution_policy.failed_to_set=设置执行策略失败 -launch.failed.execution_policy.hint=当前执行策略阻止您执行 PowerShell 脚本。\n点击“确定”允许当前用户执行本地 PowerShell 脚本,或点击“取消”保持现状。 +launch.failed.execution_policy.hint=当前执行策略阻止你执行 PowerShell 脚本。\n点击“确定”允许当前用户执行本地 PowerShell 脚本,或点击“取消”保持现状。 launch.failed.exited_abnormally=游戏非正常退出,请查看日志文件,或联系他人寻求帮助。 launch.failed.java_version_too_low=你所指定的 Java 版本过低,请重新设置 Java 版本。 -launch.failed.no_accepted_java=找不到适合当前游戏使用的 Java,是否使用默认 Java 启动游戏?点击“是”使用默认 Java 继续启动游戏,\n或者请到全局(特定)游戏设置中选择一个合适的 Java 虚拟机版本。\n你可以点击右上角帮助按钮进行求助。 +launch.failed.no_accepted_java=找不到适合当前游戏使用的 Java,是否使用默认 Java 启动游戏?点击“是”使用默认 Java 继续启动游戏,\n或者在“(全局/版本特定) 游戏设置 → 游戏 Java”中选择一个合适的 Java 虚拟机版本。\n你可以点击右上角帮助按钮进行求助。 launch.failed.sigkill=游戏被用户或系统强制终止。 launch.state.dependencies=处理游戏依赖 launch.state.done=启动完成 @@ -612,26 +612,27 @@ launcher.agreement=用户协议与免责声明 launcher.agreement.accept=同意 launcher.agreement.decline=拒绝 launcher.agreement.hint=同意本软件的用户协议与免责声明以使用本软件。 -launcher.background=背景地址 -launcher.background.choose=选择背景路径 +launcher.background=背景图片 +launcher.background.choose=选择背景图片 launcher.background.classic=经典 -launcher.background.default=默认(自动检索启动器同目录下的 background.png/jpg/gif 及 bg 文件夹内的图片) +launcher.background.default=默认 +launcher.background.default.tooltip=自动检索启动器同文件夹下的“background.png/.jpg/.gif/.webp”及“bg”文件夹内的图片 launcher.background.network=网络 launcher.background.translucent=半透明 -launcher.cache_directory=文件下载缓存目录 +launcher.cache_directory=文件下载缓存文件夹 launcher.cache_directory.clean=清理缓存 -launcher.cache_directory.choose=选择文件下载缓存目录 -launcher.cache_directory.default=默认(%AppData%/.minecraft 或者 ~/.minecraft) -launcher.cache_directory.disabled=禁用(总是使用游戏路径) -launcher.cache_directory.invalid=无法创建自定义的缓存目录,恢复默认设置 +launcher.cache_directory.choose=选择文件下载缓存文件夹 +launcher.cache_directory.default=默认 ("%APPDATA%/.minecraft" 或 "~/.minecraft") +launcher.cache_directory.disabled=禁用 (总是使用游戏文件夹路径) +launcher.cache_directory.invalid=无法创建自定义的缓存文件夹,恢复默认设置 launcher.contact=联系我们 -launcher.crash=Hello Minecraft! Launcher 遇到了无法处理的错误,请复制下列内容并点击右下角的按钮反馈 bug。 +launcher.crash=Hello Minecraft! Launcher 遇到了无法处理的错误,请复制下列内容并点击右下角的按钮反馈问题。 launcher.crash.java_internal_error=Hello Minecraft! Launcher 由于当前 Java 损坏而无法继续运行,请卸载当前 Java,点击 此处 安装合适的 Java 版本。 -launcher.crash.hmcl_out_dated=Hello Minecraft! Launcher 遇到了无法处理的错误,已检测到您的启动器不是最新版本,请更新后再试。 -launcher.update_java=请更新您的 Java \n你可以访问\n https://docs.hmcl.net/help.html \n页面寻求帮助。 +launcher.crash.hmcl_out_dated=Hello Minecraft! Launcher 遇到了无法处理的错误,已检测到你的启动器不是最新版本,请更新后再试。 +launcher.update_java=请更新你的 Java。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。 login.empty_username=你还未设置用户名! -login.enter_password=请输入您的密码 +login.enter_password=请输入你的密码 logwindow.show_lines=显示行数 logwindow.terminate_game=结束游戏进程 @@ -640,20 +641,20 @@ logwindow.help=你可以前往 HMCL 社区,寻找他人帮助 logwindow.autoscroll=自动滚动 logwindow.export_game_crash_logs=导出游戏崩溃信息 logwindow.export_dump=导出游戏运行栈 -logwindow.export_dump.no_dependency=你的 Java 不包含用于创建游戏运行栈的依赖。请前往 HMCL QQ 群或 Discord 频道寻求帮助。 +logwindow.export_dump.no_dependency=你的 Java 不包含用于创建游戏运行栈的依赖。请前往 HMCL QQ 群或 Discord 寻求帮助。 main_page=主页 message.cancelled=操作被取消 message.confirm=提示 -message.copied=已拷贝至剪贴板 +message.copied=已复制到剪贴板 message.default=默认 message.doing=请耐心等待 message.downloading=正在下载 message.error=错误 message.failed=操作失败 message.info=提示 -message.success=已完成 +message.success=完成 message.unknown=未知 message.warning=警告 @@ -663,27 +664,27 @@ modpack.choose.local=导入本地整合包文件 modpack.choose.local.detail=你可以直接将整合包文件拖入本页面以安装 modpack.choose.remote=从互联网下载整合包 modpack.choose.remote.detail=需要提供整合包的下载链接 -modpack.choose.repository=从 Curseforge / Modrinth 下载整合包 +modpack.choose.repository=从 CurseForge/Modrinth 下载整合包 modpack.choose.repository.detail=下载后记得回到这个界面,把整合包拖进来哦 modpack.choose.remote.tooltip=要下载的整合包的链接 modpack.completion=下载整合包相关文件 -modpack.desc=描述你要制作的整合包,比如整合包注意事项和更新记录,支持 HTML(图片请用网络图) +modpack.desc=描述你要制作的整合包,比如整合包注意事项和更新记录,支持 Markdown (图片请用网络链接) modpack.description=整合包描述 modpack.download=下载整合包 modpack.enter_name=给游戏起个你喜欢的名字 modpack.export=导出整合包 -modpack.export.as=请选择整合包类型 (若无法决定,请选择我的世界中文论坛整合包标准) +modpack.export.as=请选择整合包类型 (若无法决定,请选择“我的世界中文论坛整合包标准”) modpack.file_api=整合包下载链接前缀 modpack.files.blueprints=BuildCraft 蓝图 modpack.files.config=模组配置文件 modpack.files.dumps=NEI 调试输出 modpack.files.hmclversion_cfg=启动器配置文件 -modpack.files.liteconfig=模组配置文件 +modpack.files.liteconfig=LiteLoader 相关文件 modpack.files.mods=模组 modpack.files.mods.voxelmods=VoxelMods 配置,如小地图 -modpack.files.options_txt=游戏设定 -modpack.files.optionsshaders_txt=光影设定 -modpack.files.resourcepacks=资源包(材质包) +modpack.files.options_txt=游戏设置 +modpack.files.optionsshaders_txt=光影设置 +modpack.files.resourcepacks=资源包 (纹理包) modpack.files.saves=游戏存档 modpack.files.scripts=MineTweaker 配置 modpack.files.servers_dat=多人游戏服务器列表 @@ -691,20 +692,18 @@ modpack.install=安装 %s 整合包 modpack.installing=正在安装整合包 modpack.introduction=支持 Curse、Modrinth、MultiMC、MCBBS 整合包。 modpack.invalid=无效的整合包升级文件,可能是下载时出现问题。 -modpack.mismatched_type=整合包类型不匹配,当前游戏是 %s 整合包,但是提供的整合包更新文件是 %s 整合包。\n如遇到问题,你可以点击右上角帮助按钮进行求助。 +modpack.mismatched_type=整合包类型不匹配,当前游戏为“%s”整合包,但是提供的整合包更新文件为“%s”整合包。\n如遇到问题,你可以点击右上角帮助按钮进行求助。 modpack.name=整合包名称 modpack.not_a_valid_name=不是一个有效的整合包名称 modpack.origin=来源 modpack.origin.url=官方网站 modpack.origin.mcbbs=MCBBS -modpack.origin.mcbbs.prompt=贴子 id +modpack.origin.mcbbs.prompt=帖子 ID modpack.scan=解析整合包 modpack.task.install=导入整合包 modpack.task.install.error=无法识别该整合包,目前仅支持导入 Curse、Modrinth、MultiMC、MCBBS 整合包。\n你可以点击右上角帮助按钮进行求助。 -modpack.task.install.will=导入的整合包文件位置: modpack.type.curse=Curse -modpack.type.curse.tolerable_error=但未能完成该整合包所需的依赖下载,您可以在启动该游戏版本时继续该整合包文件的下载。由于网络问题,您可能需要重试多次。\n你可以点击右上角帮助按钮进行求助。 -modpack.type.curse.error=未能完成该整合包所需的依赖下载,请多次重试或设置代理。\n你可以点击右上角帮助按钮进行求助。 +modpack.type.curse.error=未能完成该整合包所需的依赖下载,请再次尝试或设置代理。\n你可以点击右上角帮助按钮进行求助。 modpack.type.curse.not_found=部分必需文件已经在网络中被删除并且再也无法下载,请尝试该整合包的最新版本或者安装其他整合包。\n如遇到问题,你可以点击右上角帮助按钮进行求助。 modpack.type.manual.warning=该整合包由发布者手动打包,其中可能已经包含启动器,建议尝试解压后使用其自带的启动器运行游戏。\n如遇到问题,你可以点击右上角帮助按钮进行求助。\nHMCL 可以尝试导入该整合包,但不保证可用性,是否继续? modpack.type.mcbbs=我的世界中文论坛整合包标准 @@ -714,21 +713,21 @@ modpack.type.multimc=MultiMC modpack.type.multimc.export=可以被 Hello Minecraft! Launcher 和 MultiMC 导入 modpack.type.server=服务器自动更新整合包 modpack.type.server.export=允许服务器管理员远程更新游戏客户端 -modpack.type.server.malformed=服务器整合包配置格式错误,请联系服务器管理员解决此问题\n如遇到问题,你可以点击右上角帮助按钮进行求助。 +modpack.type.server.malformed=服务器整合包配置格式错误,请联系服务器管理员解决此问题。\n如遇到问题,你可以点击右上角帮助按钮进行求助。 modpack.unsupported=Hello Minecraft! Launcher 不支持该整合包格式 modpack.update=正在升级整合包 modpack.wizard=导出整合包向导 modpack.wizard.step.1=基本设置 modpack.wizard.step.1.title=设置整合包的主要信息 modpack.wizard.step.2=文件选择 -modpack.wizard.step.2.title=选中你想加到整合包中的文件或文件夹 +modpack.wizard.step.2.title=选中你想添加到整合包中的文件或文件夹 modpack.wizard.step.3=整合包类型 modpack.wizard.step.3.title=选择整合包导出类型 modpack.wizard.step.initialization.exported_version=要导出的游戏版本 -modpack.wizard.step.initialization.force_update=强制升级整合包至最新版本(需要自建服务器) +modpack.wizard.step.initialization.force_update=强制升级整合包至最新版本 (需要自建服务器) modpack.wizard.step.initialization.include_launcher=包含启动器 modpack.wizard.step.initialization.save=选择要导出到的游戏整合包位置 -modpack.wizard.step.initialization.warning=在制作整合包前,请您确认您选择的版本可以正常启动,\n并保证您的 Minecraft 是正式版而非快照版,\n而且不应当将不允许非官方途径传播的模组、材质包等纳入整合包。\n整合包会保存您目前的下载源设置\n如遇到问题,你可以点击右上角帮助按钮进行求助。 +modpack.wizard.step.initialization.warning=在制作整合包前,请你确认你选择的版本可以正常启动,\n并保证你的 Minecraft 是正式版而非快照,\n而且不应当将不允许非官方途径传播的模组、资源 (纹理) 包等纳入整合包。\n整合包会保存你目前的下载源设置。\n如遇到问题,你可以点击右上角帮助按钮进行求助。 modpack.wizard.step.initialization.server=点击此处查看有关服务器自动更新整合包的制作教程 modrinth.category.adventure=冒险 @@ -794,28 +793,32 @@ modrinth.category.folia=Folia mods=模组 mods.add=添加模组 -mods.add.failed=添加模组 %s 失败。\n如遇到问题,你可以点击右上角帮助按钮进行求助。 +mods.add.failed=添加模组“%s”失败。\n如遇到问题,你可以点击右上角帮助按钮进行求助。 mods.add.success=成功添加模组 %s。 mods.broken_dependency.title=损坏的前置模组 mods.broken_dependency.desc=该前置模组曾经在该模组仓库上存在过,但现在被删除了。换个下载源试试吧。 mods.category=类别 +mods.channel.alpha=快照版本 +mods.channel.beta=测试版本 +mods.channel.release=稳定版本 mods.check_updates=检查模组更新 mods.check_updates.current_version=当前版本 mods.check_updates.empty=没有需要更新的模组 -mods.check_updates.failed=部分文件下载失败 +mods.check_updates.failed_check=检查更新失败 +mods.check_updates.failed_download=部分文件下载失败 mods.check_updates.file=文件 mods.check_updates.source=来源 mods.check_updates.target_version=目标版本 mods.check_updates.update=更新 mods.choose_mod=选择模组 mods.curseforge=CurseForge -mods.dependency.embedded=内置的前置模组(已经由作者打包在模组文件中,无需另外下载) -mods.dependency.optional=可选的前置模组(若缺失游戏能够正常运行,但模组功能可能缺失) -mods.dependency.required=必须的前置模组(必须另外下载,缺失可能会导致游戏无法启动) -mods.dependency.tool=前置库(必须另外下载,缺失可能会导致游戏无法启动) -mods.dependency.include=内置的前置模组(已经由作者打包在模组文件中,无需另外下载) -mods.dependency.incompatible=不兼容的模组(同时安装该模组和正在下载的模组会导致游戏无法启动) -mods.dependency.broken=损坏的前置模组(该前置模组曾经在该模组仓库上存在过,但现在被删除了。换个下载源试试吧。) +mods.dependency.embedded=内置的前置模组 (已经由作者打包在模组文件中,无需另外下载) +mods.dependency.optional=可选的前置模组 (若缺失游戏能够正常运行,但模组功能可能缺失) +mods.dependency.required=必须的前置模组 (必须另外下载,缺失可能会导致游戏无法启动) +mods.dependency.tool=前置库 (必须另外下载,缺失可能会导致游戏无法启动) +mods.dependency.include=内置的前置模组 (已经由作者打包在模组文件中,无需另外下载) +mods.dependency.incompatible=不兼容的模组 (同时安装该模组和正在下载的模组会导致游戏无法启动) +mods.dependency.broken=损坏的前置模组 (该前置模组曾经在该模组仓库上存在过,但现在被删除了,换个下载源试试吧) mods.disable=禁用 mods.download=模组下载 mods.download.title=模组下载 - %1s @@ -828,12 +831,12 @@ mods.mcmod.page=MC 百科页面 mods.mcmod.search=MC 百科搜索 mods.modrinth=Modrinth mods.name=名称 -mods.not_modded=你需要先在自动安装页面安装 Fabric、Forge 或 LiteLoader 才能进行模组管理。 +mods.not_modded=你需要先在“自动安装”页面安装 Forge、NeoForge、Fabric、Quilt 或 LiteLoader 才能管理模组。 mods.restore=回退 mods.url=官方页面 mods.update_modpack_mod.warning=更新整合包中的模组可能导致整合包损坏,使整合包无法正常启动。该操作不可逆,确定要更新吗? mods.install=安装到当前版本 -mods.save_as=下载到本地目录 +mods.save_as=下载到本地文件夹 nbt.entries=%s 个条目 nbt.open.failed=打开文件失败 @@ -844,27 +847,31 @@ datapack=数据包 datapack.add=添加数据包 datapack.choose_datapack=选择要导入的数据包压缩包 datapack.extension=数据包 -datapack.title=世界 %s - 数据包 +datapack.title=世界 [%s] - 数据包 + +web.failed=加载页面失败 +web.open_in_browser=是否要在浏览器中打开此链接:\n%s +web.view_in_browser=在浏览器中查看 world=世界 world.add=添加世界 world.datapack=管理数据包 world.datapack.1_13=仅 Minecraft 1.13 及之后的版本支持数据包 -world.description=%s。上一次游戏时间: %s。游戏版本: %s。 +world.description=%s | 上一次游戏时间: %s | 游戏版本: %s world.download=存档下载 world.export=导出此世界 world.export.title=选择该世界的存储位置 world.export.location=保存到 -world.export.wizard=导出世界 %s +world.export.wizard=导出世界“%s” world.extension=世界压缩包 world.game_version=游戏版本 world.import.already_exists=此世界已经存在 world.import.choose=选择要导入的存档压缩包 world.import.failed=无法导入此世界:%s world.import.invalid=无法识别该存档压缩包 -world.info.title=世界 %s - 世界信息 +world.info.title=世界 [%s] - 世界信息 world.info.basic=基本信息 -world.info.allow_cheats=允许作弊 +world.info.allow_cheats=允许命令(作弊) world.info.dimension.the_nether=下界 world.info.dimension.the_end=末地 world.info.difficulty=难度 @@ -872,6 +879,7 @@ world.info.difficulty.peaceful=和平 world.info.difficulty.easy=简单 world.info.difficulty.normal=普通 world.info.difficulty.hard=困难 +world.info.failed=读取世界信息失败 world.info.game_version=游戏版本 world.info.last_played=上一次游戏时间 world.info.generate_features=生成建筑 @@ -880,7 +888,7 @@ world.info.player.food_level=饥饿值 world.info.player.game_type=游戏模式 world.info.player.game_type.adventure=冒险 world.info.player.game_type.creative=创造 -world.info.player.game_type.spectator=旁观 +world.info.player.game_type.spectator=旁观者 world.info.player.game_type.survival=生存 world.info.player.health=生命值 world.info.player.last_death_location=上次死亡位置 @@ -895,24 +903,26 @@ world.name=世界名称 world.name.enter=输入世界名称 world.reveal=打开文件夹 world.show_all=显示全部 -world.time=yyyy 年 MM 月 dd 日 HH:mm:ss +world.time=yyyy 年 MM 月 dd 日, HH:mm:ss -profile=游戏仓库 +profile=游戏文件夹 profile.already_exists=该名称已存在 -profile.default=当前目录仓库 -profile.home=官方启动器目录仓库 -profile.instance_directory=游戏路径 -profile.instance_directory.choose=选择游戏路径 +profile.default=当前文件夹 +profile.home=官方启动器文件夹 +profile.instance_directory=游戏文件夹路径 +profile.instance_directory.choose=选择游戏文件夹路径 +profile.manage=游戏文件夹列表 profile.name=名称 -profile.new=添加游戏仓库 -profile.title=游戏仓库 -profile.use_relative_path=若可能,游戏仓库使用相对路径 +profile.new=添加游戏文件夹 +profile.title=游戏文件夹 +profile.selected=已选中 +profile.use_relative_path=若可能,游戏文件夹使用相对路径 -repositories.custom=自定义 Maven 仓库(%s) -repositories.maven_central=全球(Maven Central) -repositories.tencentcloud_mirror=中国大陆(腾讯云镜像源 Maven 仓库) +repositories.custom=自定义 Maven 仓库 (%s) +repositories.maven_central=全球 (Maven Central) +repositories.tencentcloud_mirror=中国大陆 (腾讯云镜像源 Maven 仓库) repositories.chooser=缺少 JavaFX 运行环境,HMCL 需要 JavaFX 才能正常运行。\n点击“确认”从指定下载源下载 JavaFX 运行时组件并启动 HMCL,点击“取消”退出程序。\n选择下载源: -repositories.chooser.title=选择下载源下载 JavaFX +repositories.chooser.title=选择 JavaFX 下载源 resourcepack=资源包 @@ -934,34 +944,34 @@ selector.custom=自定义 settings=设置 settings.advanced=高级设置 -settings.advanced.modify=修改高级设置 +settings.advanced.modify=编辑高级设置 settings.advanced.title=高级设置 - %s settings.advanced.custom_commands=自定义命令 settings.advanced.custom_commands.hint=自定义命令被调用时将包含如下的环境变量:\n\ - \ - $INST_NAME: 版本名称\n\ - \ - $INST_ID: 版本名称\n\ - \ - $INST_DIR: 版本文件夹\n\ - \ - $INST_MC_DIR: 游戏运行路径\n\ - \ - $INST_JAVA: 游戏运行使用的 Java 路径\n\ - \ - $INST_FORGE: 若安装了 Forge,将会存在本环境变量\n\ - \ - $INST_NEOFORGE: 若安装了 NeoForge,将会存在本环境变量\n\ - \ - $INST_LITELOADER: 若安装了 LiteLoader,将会存在本环境变量\n\ - \ - $INST_OPTIFINE: 若安装了 OptiFine,将会存在本环境变量\n\ - \ - $INST_FABRIC: 若安装了 Fabric,将会存在本环境变量\n\ - \ - $INST_QUILT: 若安装了 Quilt,将会存在本环境变量 + \ · $INST_NAME: 版本名称;\n\ + \ · $INST_ID: 版本名称;\n\ + \ · $INST_DIR: 当前版本运行路径;\n\ + \ · $INST_MC_DIR: 当前游戏文件夹路径;\n\ + \ · $INST_JAVA: 游戏运行使用的 Java 路径;\n\ + \ · $INST_FORGE: 若安装了 Forge,将会存在本环境变量;\n\ + \ · $INST_NEOFORGE: 若安装了 NeoForge,将会存在本环境变量;\n\ + \ · $INST_LITELOADER: 若安装了 LiteLoader,将会存在本环境变量;\n\ + \ · $INST_OPTIFINE: 若安装了 OptiFine,将会存在本环境变量;\n\ + \ · $INST_FABRIC: 若安装了 Fabric,将会存在本环境变量;\n\ + \ · $INST_QUILT: 若安装了 Quilt,将会存在本环境变量。 settings.advanced.dont_check_game_completeness=不检查游戏完整性 settings.advanced.dont_check_jvm_validity=不检查 JVM 与游戏的兼容性 settings.advanced.dont_patch_natives=不尝试自动替换本地库 settings.advanced.environment_variables=环境变量 -settings.advanced.game_dir.default=默认(.minecraft/) -settings.advanced.game_dir.independent=各版本独立(存放在 .minecraft/versions/<版本名>/,除 assets、libraries 外) +settings.advanced.game_dir.default=默认 (".minecraft/") +settings.advanced.game_dir.independent=各版本独立 (存放在 ".minecraft/versions/<版本名>/",除 assets、libraries 外) settings.advanced.java_permanent_generation_space=内存永久保存区域 settings.advanced.java_permanent_generation_space.prompt=单位 MB settings.advanced.jvm=Java 虚拟机设置 settings.advanced.jvm_args=Java 虚拟机参数 -settings.advanced.jvm_args.prompt=- 若在“Java 虚拟机参数”输入的参数与默认参数相同,则不会添加\n\ -- 在“Java 虚拟机参数”输入任何 GC 参数,默认参数的 G1 参数会禁用\n\ -- 点击下方“不添加默认的 JVM 参数”可在启动游戏时不添加默认参数 +settings.advanced.jvm_args.prompt=\ · 若在“Java 虚拟机参数”输入的参数与默认参数相同,则不会添加;\n\ + \ · 在“Java 虚拟机参数”输入任何 GC 参数,默认参数的 G1 参数会禁用;\n\ + \ · 点击下方“不添加默认的 JVM 参数”可在启动游戏时不添加默认参数。 settings.advanced.launcher_visibility.close=游戏启动后结束启动器 settings.advanced.launcher_visibility.hide=游戏启动后隐藏启动器 settings.advanced.launcher_visibility.hide_and_reopen=隐藏启动器并在游戏结束后重新打开 @@ -969,35 +979,35 @@ settings.advanced.launcher_visibility.keep=保持启动器可见 settings.advanced.launcher_visible=启动器可见性 settings.advanced.minecraft_arguments=游戏参数 settings.advanced.minecraft_arguments.prompt=默认 -settings.advanced.natives_directory=本地库路径(LWJGL) +settings.advanced.natives_directory=本地库路径 (LWJGL) settings.advanced.natives_directory.choose=选择本地库路径 -settings.advanced.natives_directory.custom=自定义(由你提供游戏需要的本地库) -settings.advanced.natives_directory.default=预设(由启动器提供游戏本地库) -settings.advanced.natives_directory.hint=本选项提供给 Apple M1 等未受游戏官方支持的平台来自定义游戏本地库,如果你不知道本选项的含义,请你不要修改本选项,否则会导致游戏无法启动!\n如果你要修改本选项,你需要保证自定义目录下有游戏所需的本地库文件,如 lwjgl.dll(liblwjgl.so), openal.dll(libopenal.so) 等文件。启动器不会帮你补全缺少的本地库文件!\n注意:指定的本地库文件路径建议只包含英文大小写字母,数字,下划线,否则可能会导致启动游戏失败。 +settings.advanced.natives_directory.custom=自定义 (由你提供游戏需要的本地库) +settings.advanced.natives_directory.default=默认 (由启动器提供游戏本地库) +settings.advanced.natives_directory.hint=本选项提供给 Apple M1 等未受游戏官方支持的平台来自定义游戏本地库,如果你不知道本选项的含义,请不要修改本选项,否则会导致游戏无法启动!\n如果你要修改本选项,你需要保证自定义文件夹下有游戏所需的本地库文件,如 lwjgl.dll (liblwjgl.so)、openal.dll (libopenal.so) 等文件。启动器不会帮你补全缺少的本地库文件!\n注意:指定的本地库文件路径建议只包含英文大小写字母、数字和下划线,否则可能会导致启动游戏失败。 settings.advanced.no_jvm_args=不添加默认的 JVM 参数 settings.advanced.precall_command=游戏启动前执行命令 settings.advanced.precall_command.prompt=将在游戏启动前调用 settings.advanced.process_priority=进程优先级 -settings.advanced.process_priority.low=低(节省游戏占用资源,可能会造成游戏卡顿) -settings.advanced.process_priority.below_normal=较低(节省游戏占用资源,可能会造成游戏卡顿) -settings.advanced.process_priority.normal=中(平衡) -settings.advanced.process_priority.above_normal=较高(优先保证游戏运行,但可能会导致其他程序卡顿) -settings.advanced.process_priority.high=高(优先保证游戏运行,但可能会导致其他程序卡顿) +settings.advanced.process_priority.low=低 (节省游戏占用资源,可能会造成游戏卡顿) +settings.advanced.process_priority.below_normal=较低 (节省游戏占用资源,可能会造成游戏卡顿) +settings.advanced.process_priority.normal=中 (平衡) +settings.advanced.process_priority.above_normal=较高 (优先保证游戏运行,但可能会导致其他程序卡顿) +settings.advanced.process_priority.high=高 (优先保证游戏运行,但可能会导致其他程序卡顿) settings.advanced.post_exit_command=游戏结束后执行命令 settings.advanced.post_exit_command.prompt=将在游戏结束后调用 settings.advanced.renderer=渲染器 -settings.advanced.renderer.default=OpenGL(默认) -settings.advanced.renderer.d3d12=DirectX 12(性能与兼容性较差,用于调试) -settings.advanced.renderer.llvmpipe=软渲染器(性能较差,兼容性最好) -settings.advanced.renderer.zink=Vulkan(性能最好,兼容性较差) +settings.advanced.renderer.default=OpenGL (默认) +settings.advanced.renderer.d3d12=DirectX 12 (性能与兼容性较差,用于调试) +settings.advanced.renderer.llvmpipe=软渲染器 (性能较差,兼容性最好) +settings.advanced.renderer.zink=Vulkan (性能最好,兼容性较差) settings.advanced.server_ip=服务器地址 settings.advanced.server_ip.prompt=默认,启动游戏后可以直接进入对应服务器 -settings.advanced.use_native_glfw=使用系统 GLFW -settings.advanced.use_native_openal=使用系统 OpenAL +settings.advanced.use_native_glfw=[仅 Linux/FreeBSD] 使用系统 GLFW +settings.advanced.use_native_openal=[仅 Linux/FreeBSD] 使用系统 OpenAL settings.advanced.workaround=调试选项 settings.advanced.workaround.warning=调试选项仅提供给专业玩家使用。调试选项可能会导致游戏无法启动。除非你知道你在做什么,否则请不要修改这些选项! settings.advanced.wrapper_launcher=包装命令 -settings.advanced.wrapper_launcher.prompt=如填写 optirun 后,启动命令将从 "java ..." 变为 "optirun java ..." +settings.advanced.wrapper_launcher.prompt=如填写“optirun”后,启动命令将从“java ...”变为“optirun java ...” settings.custom=自定义 @@ -1006,30 +1016,30 @@ settings.game.current=游戏 settings.game.dimension=游戏窗口分辨率 settings.game.exploration=浏览 settings.game.fullscreen=全屏 -settings.game.java_directory=Java 路径 +settings.game.java_directory=游戏 Java settings.game.java_directory.auto=自动选择合适的 Java settings.game.java_directory.auto.not_found=没有合适的 Java settings.game.java_directory.bit=%s 位 -settings.game.java_directory.choose=选择 Java 路径 +settings.game.java_directory.choose=选择 Java settings.game.java_directory.invalid=Java 路径不正确 settings.game.java_directory.version=指定 Java 版本 -settings.game.java_directory.template=%s(%s) +settings.game.java_directory.template=%s (%s) settings.game.management=管理 -settings.game.working_directory=版本隔离(建议使用模组时开启“各版本隔离”,改后需移动存档模组等相关游戏文件) -settings.game.working_directory.choose=选择运行路径 -settings.game.working_directory.hint=在“运行路径(版本隔离)”选项中开启“各版本独立”使当前版本独立存放设置、存档、模组等数据,使用模组时建议开启此选项以避免不同版本模组冲突。修改此选项后请自行移动存档等文件。 +settings.game.working_directory=版本隔离 (建议使用模组时选择“各版本独立”,改后需移动存档、模组等相关游戏文件) +settings.game.working_directory.choose=选择运行文件夹 +settings.game.working_directory.hint=在“版本隔离”中选择“各版本独立”使当前版本独立存放设置、存档、模组等数据,使用模组时建议启用此选项以避免不同版本模组冲突。修改此选项后需自行移动存档等文件。 settings.game.working_directory.should_use_game_repo_feature=想要更改游戏的存储路径?进入 HMCL 主页 - 版本列表,点击【添加游戏仓库】 settings.icon=游戏图标 settings.launcher=启动器设置 settings.launcher.appearance=外观 -settings.launcher.common_path.tooltip=启动器将所有游戏资源及依赖库文件放于此集中管理,如果游戏文件夹内有现成的将不会使用公共库文件 +settings.launcher.common_path.tooltip=启动器将所有游戏资源及依赖库文件存放于此集中管理,如果游戏文件夹内有现成的将不会使用公共库文件。 settings.launcher.debug=调试 settings.launcher.download=下载 -settings.launcher.download.threads=并发数 -settings.launcher.download.threads.auto=自动选择并发数 -settings.launcher.download.threads.hint=并发数过大可能导致系统卡顿。你的下载速度会受到宽带运营商、服务器等方面的影响,调大下载并发数不一定能大幅提升总下载速度。 +settings.launcher.download.threads=线程数 +settings.launcher.download.threads.auto=自动选择线程数 +settings.launcher.download.threads.hint=线程数过高可能导致系统卡顿。你的下载速度会受到互联网运营商、下载源服务器等方面的影响,调高下载线程数不一定能大幅提升总下载速度。 settings.launcher.download_source=下载源 settings.launcher.download_source.auto=自动选择下载源 settings.launcher.enable_game_list=在主页内显示版本列表 @@ -1037,8 +1047,9 @@ settings.launcher.font=字体 settings.launcher.general=通用 settings.launcher.language=语言 (重启后生效) settings.launcher.launcher_log.export=导出启动器日志 +settings.launcher.launcher_log.reveal=打开日志文件夹 settings.launcher.launcher_log.export.failed=无法导出日志 -settings.launcher.launcher_log.export.success=日志已保存到 %s +settings.launcher.launcher_log.export.success=日志已保存到“%s” settings.launcher.log=日志 settings.launcher.log.font=日志字体 settings.launcher.proxy=代理 @@ -1066,20 +1077,20 @@ settings.memory.lower_bound=最低内存分配 settings.memory.used_per_total=设备中已使用 %1$.1f GB / 设备总内存 %2$.1f GB settings.physical_memory=物理内存大小 settings.show_log=查看日志 -settings.skin=现已支持离线账户更换皮肤,你可以到账户页面更改离线账户的皮肤和披风(多人游戏下其他玩家无法看到你的皮肤) +settings.skin=现已支持离线账户更换皮肤,你可以到账户页面更改离线账户的皮肤和披风 (多人游戏下其他玩家无法看到你的皮肤)。 settings.tabs.installers=自动安装 settings.take_effect_after_restart=重启后生效 -settings.type=版本设置类型 -settings.type.global=全局版本设置(未启用游戏特定设置的共用此设定) +settings.type=版本游戏设置类型 +settings.type.global=全局游戏设置 (未启用“版本特定游戏设置”的版本共用此设置) settings.type.global.manage=全局游戏设置 -settings.type.global.edit=编辑全局版本设置 -settings.type.special.enable=启用游戏特定设置(不影响其他游戏版本) -settings.type.special.edit=编辑游戏特定设置 -settings.type.special.edit.hint=当前游戏版本 %s 启用了游戏特定设置,因此本页面选项不对该游戏生效。点击链接前往该游戏版本的游戏特定设置页 +settings.type.global.edit=编辑全局游戏设置 +settings.type.special.enable=启用版本特定游戏设置 (不影响其他游戏版本) +settings.type.special.edit=编辑版本特定游戏设置 +settings.type.special.edit.hint=当前游戏版本“%s”启用了“版本特定游戏设置”,因此本页面选项不对该版本生效。点击链接前往该版本的“游戏设置”页。 sponsor=赞助 -sponsor.bmclapi=国内下载源由 BMCLAPI 提供高速下载服务。BMCLAPI 为公益服务,赞助 BMCLAPI 可以帮助作者更好的提供稳定高速的下载服务,[点击此处查阅详细信息] -sponsor.hmcl=Hello Minecraft! Launcher 是一个免费、自由、开放源代码的 Minecraft 启动器。[点击此处查阅更多详细信息] +sponsor.bmclapi=国内下载源由 BMCLAPI 提供高速下载服务。BMCLAPI 为公益服务,赞助 BMCLAPI 可以帮助作者更好地提供稳定高速的下载服务。[点击此处查阅详细信息] +sponsor.hmcl=Hello Minecraft! Launcher 是一个免费、自由、开放源代码的 Minecraft 启动器。[点击此处查阅详细信息] system.architecture=架构 system.operating_system=操作系统 @@ -1089,27 +1100,26 @@ unofficial.hint=你正在使用非官方构建的 HMCL,我们无法保证其 update=启动器更新 update.accept=更新 update.changelog=更新日志 -update.channel.dev=测试版 -update.channel.dev.hint=你正在使用测试版。测试版包含一些未在正式版中包含的测试性功能,仅用于体验新功能。\n\ - 测试版功能未受充分验证,使用起来可能不稳定!下载稳定版\n\ - 如果你遇到了使用问题,可以在设置的 反馈页面 中进行反馈,或加入反馈页面中提供的 DiscordQQ 群以反馈问题。欢迎关注 B 站账号 huanghongxun 以关注 HMCL 的开发进展。\n\n\ - 点击此处为当前版本隐藏该提示。 -update.channel.dev.title=测试版提示 +update.channel.dev=开发版 +update.channel.dev.hint=你正在使用 HMCL 开发版。开发版包含一些未在稳定版中包含的测试性功能,仅用于体验新功能。开发版功能未受充分验证,使用起来可能不稳定!下载稳定版\n\ + \n\ + 如果你使用时遇到了问题,可以通过设置中反馈页面提供的渠道进行反馈。欢迎关注 B 站账号 @huanghongxun 以关注 HMCL 的开发进展。 +update.channel.dev.title=开发版提示 update.channel.nightly=预览版 -update.channel.nightly.hint=你正在使用预览版。预览版可能会每天更新一次,包含一些未在正式版和测试版中包含的测试性功能,仅用于体验新功能。\n\ - 测试版功能未受充分验证,使用起来可能不稳定!下载稳定版\n\ - 如果你遇到了使用问题,可以在设置的 反馈页面 中进行反馈,或加入反馈页面中提供的 DiscordQQ 群以反馈问题。欢迎关注 B 站账号 huanghongxun 以关注 HMCL 的开发进展。 +update.channel.nightly.hint=你正在使用 HMCL 预览版。预览版更新较为频繁,包含一些未在稳定版和开发版中包含的测试性功能,仅用于体验新功能。预览版功能未受充分验证,使用起来可能不稳定!下载稳定版\n\ + \n\ + 如果你使用时遇到了问题,可以通过设置中反馈页面提供的渠道进行反馈。欢迎关注 B 站账号 @huanghongxun 以关注 HMCL 的开发进展。 update.channel.nightly.title=预览版提示 -update.channel.stable=推荐版本 +update.channel.stable=稳定版 update.checking=正在检查更新 update.failed=更新失败 update.found=发现更新 update.newest_version=最新版本为:%s update.bubble.title=发现更新:%s update.bubble.subtitle=点击此处进行升级 -update.note=测试版与开发版包含更多的功能以及错误修复,但也可能会包含其他的问题。 +update.note=开发版与预览版包含更多的功能以及错误修复,但也可能会包含其他的问题。 update.latest=当前版本为最新版本 -update.no_browser=无法打开浏览器,网址已经复制到剪贴板了,您可以手动粘贴网址打开页面 +update.no_browser=无法打开浏览器,网址已经复制到剪贴板,你可以手动粘贴网址打开页面。 update.tooltip=更新 version=游戏 @@ -1118,12 +1128,12 @@ version.cannot_read=读取游戏版本失败,无法进行自动安装 version.empty=没有游戏版本 version.empty.add=进入下载页安装游戏 version.empty.launch=没有可启动的游戏,你可以点击左侧边栏内的下载按钮安装游戏 -version.empty.hint=没有已安装的游戏,你可以切换其他游戏目录,或者点击此处进入游戏下载页面 +version.empty.hint=没有已安装的游戏,你可以切换其他游戏文件夹,或者点击此处进入游戏下载页面 version.game.old=远古版 version.game.release=正式版 version.game.releases=正式版 -version.game.snapshot=测试版 -version.game.snapshots=测试版 +version.game.snapshot=快照 +version.game.snapshots=快照 version.launch=启动游戏 version.launch.test=测试游戏 version.switch=切换版本 @@ -1132,27 +1142,31 @@ version.launch_script.failed=生成启动脚本失败 version.launch_script.save=保存启动脚本 version.launch_script.success=启动脚本已生成完毕:%s version.manage=版本列表 -version.manage.clean=清理游戏目录 -version.manage.clean.tooltip=清理 logs, crash-reports -version.manage.duplicate=复制游戏实例 +version.manage.clean=清理游戏文件夹 +version.manage.clean.tooltip=清理“logs”和“crash-reports”文件夹 +version.manage.duplicate=复制游戏版本 version.manage.duplicate.duplicate_save=复制存档 -version.manage.duplicate.prompt=请输入新游戏实例名称 -version.manage.duplicate.confirm=将锁定复制产生的新游戏实例:开启版本隔离、游戏设置并复制该版本产生的文件 -version.manage.manage=游戏管理 -version.manage.manage.title=游戏管理 - %1s +version.manage.duplicate.prompt=请输入新游戏名称 +version.manage.duplicate.confirm=新的游戏版本将复制该版本文件夹 (".minecraft/versions/<版本名>") 下的文件,并带有独立的运行文件夹和设置。 +version.manage.manage=版本管理 +version.manage.manage.title=版本管理 - %1s version.manage.redownload_assets_index=更新游戏资源文件 version.manage.remove=删除该版本 -version.manage.remove.confirm=真的要删除版本 %s 吗?你将无法找回被删除的文件!!! -version.manage.remove.confirm.trash=真的要删除版本 %s 吗?你可以在系统的回收站中恢复文件夹 %s 来找回该版本。 -version.manage.remove.confirm.independent=由于该游戏使用了版本隔离,所以删除该版本将导致该游戏的存档等数据一同被删除!真的要删除版本 %s 吗? +version.manage.remove.confirm=真的要删除版本“%s”吗?你将无法找回被删除的文件!!! +version.manage.remove.confirm.trash=真的要删除版本“%s”吗?你可以在系统的回收站中还原“%s”文件夹来找回该版本。 +version.manage.remove.confirm.independent=由于该游戏启用了“(全局/版本特定) 游戏设置 → 版本隔离 → 各版本独立”选项,所以删除该版本将导致该游戏的存档等数据一同被删除!真的要删除版本“%s”吗? version.manage.remove_assets=删除所有游戏资源文件 version.manage.remove_libraries=删除所有库文件 version.manage.rename=重命名该版本 version.manage.rename.message=请输入要修改的名称 -version.manage.rename.fail=重命名版本失败,可能文件被占用或者名字有特殊字符 +version.manage.rename.fail=重命名版本失败,可能文件被占用或者名字有特殊字符。 version.settings=游戏设置 version.update=更新整合包 +wiki.tooltip=Minecraft Wiki 页面 +wiki.version.game.release=https://zh.minecraft.wiki/w/Java版%s?variant=zh-cn +wiki.version.game.snapshot=https://zh.minecraft.wiki/w/%s?variant=zh-cn + wizard.prev=< 上一步 wizard.failed=失败 wizard.finish=完成 diff --git a/HMCL/src/main/resources/assets/natives.json b/HMCL/src/main/resources/assets/natives.json index 558d2bd3e2..13c2a1d74c 100644 --- a/HMCL/src/main/resources/assets/natives.json +++ b/HMCL/src/main/resources/assets/natives.json @@ -2265,6 +2265,226 @@ "org.lwjgl:lwjgl-glfw:3.3.1:natives-linux": null, "org.lwjgl:lwjgl-stb:3.3.1:natives-linux": null, "org.lwjgl:lwjgl-tinyfd:3.3.1:natives-linux": null, + "org.lwjgl:lwjgl:3.3.2": { + "name": "org.lwjgl:lwjgl:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar", + "sha1": "b86c3e4832426e8a6b466013b7cb34b40e9ce956", + "size": 800127 + } + } + }, + "org.lwjgl:lwjgl:3.3.2:natives-linux": { + "name": "org.glavo.hmcl:lwjgl3-natives:3.3.4-rc2", + "downloads": { + "classifiers": { + "linux-loongarch64": { + "path": "org/glavo/hmcl/lwjgl3-natives/3.3.4-rc2/lwjgl3-natives-3.3.4-rc2-linux-loongarch64.jar", + "url": "https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl3-natives/3.3.4-rc2-linux-loongarch64/lwjgl3-natives-3.3.4-rc2-linux-loongarch64.jar", + "sha1": "34a7f913c6750f2bede863f59c074cc4d540fb64", + "size": 12234234 + } + } + }, + "extract": { + "exclude": [ + "META-INF/" + ] + }, + "natives": { + "linux": "linux-loongarch64" + } + }, + "org.lwjgl:lwjgl-jemalloc:3.3.2": { + "name": "org.lwjgl:lwjgl-jemalloc:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar", + "sha1": "e3f5dcb8e13f3a5ed3f740fd30a114cee2a80bc4", + "size": 46430 + } + } + }, + "org.lwjgl:lwjgl-jemalloc:3.3.2:natives-linux": null, + "org.lwjgl:lwjgl-openal:3.3.2": { + "name": "org.lwjgl:lwjgl-openal:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar", + "sha1": "9b74d3ea380c83353d42af43ad9659e04dabe84a", + "size": 113103 + } + } + }, + "org.lwjgl:lwjgl-openal:3.3.2:natives-linux": null, + "org.lwjgl:lwjgl-opengl:3.3.2": { + "name": "org.lwjgl:lwjgl-opengl:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar", + "sha1": "2852ac7d9f6fc71349f1ce28e2708ff1977f18af", + "size": 931960 + } + } + }, + "org.lwjgl:lwjgl-opengl:3.3.2:natives-linux": null, + "org.lwjgl:lwjgl-glfw:3.3.2": { + "name": "org.lwjgl:lwjgl-glfw:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar", + "sha1": "7e46ecdec85db8738053cfde1414352cd62dab74", + "size": 147044 + } + } + }, + "org.lwjgl:lwjgl-glfw:3.3.2:natives-linux": null, + "org.lwjgl:lwjgl-stb:3.3.2": { + "name": "org.lwjgl:lwjgl-stb:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar", + "sha1": "5821735d5ef23f6da8542887344e57eb181b7cac", + "size": 143112 + } + } + }, + "org.lwjgl:lwjgl-stb:3.3.2:natives-linux": null, + "org.lwjgl:lwjgl-tinyfd:3.3.2": { + "name": "org.lwjgl:lwjgl-tinyfd:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar", + "sha1": "2d73789ffd8962b38d9d599cc38b2383ce818c7a", + "size": 15928 + } + } + }, + "org.lwjgl:lwjgl-tinyfd:3.3.2:natives-linux": null, + "org.lwjgl:lwjgl:3.3.3": { + "name": "org.lwjgl:lwjgl:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar", + "sha1": "b86c3e4832426e8a6b466013b7cb34b40e9ce956", + "size": 800127 + } + } + }, + "org.lwjgl:lwjgl:3.3.3:natives-linux": { + "name": "org.glavo.hmcl:lwjgl3-natives:3.3.4-rc2", + "downloads": { + "classifiers": { + "linux-loongarch64": { + "path": "org/glavo/hmcl/lwjgl3-natives/3.3.4-rc2/lwjgl3-natives-3.3.4-rc2-linux-loongarch64.jar", + "url": "https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl3-natives/3.3.4-rc2-linux-loongarch64/lwjgl3-natives-3.3.4-rc2-linux-loongarch64.jar", + "sha1": "34a7f913c6750f2bede863f59c074cc4d540fb64", + "size": 12234234 + } + } + }, + "extract": { + "exclude": [ + "META-INF/" + ] + }, + "natives": { + "linux": "linux-loongarch64" + } + }, + "org.lwjgl:lwjgl-jemalloc:3.3.3": { + "name": "org.lwjgl:lwjgl-jemalloc:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar", + "sha1": "e3f5dcb8e13f3a5ed3f740fd30a114cee2a80bc4", + "size": 46430 + } + } + }, + "org.lwjgl:lwjgl-jemalloc:3.3.3:natives-linux": null, + "org.lwjgl:lwjgl-openal:3.3.3": { + "name": "org.lwjgl:lwjgl-openal:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar", + "sha1": "9b74d3ea380c83353d42af43ad9659e04dabe84a", + "size": 113103 + } + } + }, + "org.lwjgl:lwjgl-openal:3.3.3:natives-linux": null, + "org.lwjgl:lwjgl-opengl:3.3.3": { + "name": "org.lwjgl:lwjgl-opengl:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar", + "sha1": "2852ac7d9f6fc71349f1ce28e2708ff1977f18af", + "size": 931960 + } + } + }, + "org.lwjgl:lwjgl-opengl:3.3.3:natives-linux": null, + "org.lwjgl:lwjgl-glfw:3.3.3": { + "name": "org.lwjgl:lwjgl-glfw:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar", + "sha1": "7e46ecdec85db8738053cfde1414352cd62dab74", + "size": 147044 + } + } + }, + "org.lwjgl:lwjgl-glfw:3.3.3:natives-linux": null, + "org.lwjgl:lwjgl-stb:3.3.3": { + "name": "org.lwjgl:lwjgl-stb:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar", + "sha1": "5821735d5ef23f6da8542887344e57eb181b7cac", + "size": 143112 + } + } + }, + "org.lwjgl:lwjgl-stb:3.3.3:natives-linux": null, + "org.lwjgl:lwjgl-tinyfd:3.3.3": { + "name": "org.lwjgl:lwjgl-tinyfd:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar", + "sha1": "2d73789ffd8962b38d9d599cc38b2383ce818c7a", + "size": 15928 + } + } + }, + "org.lwjgl:lwjgl-tinyfd:3.3.3:natives-linux": null, + "org.lwjgl:lwjgl-freetype:3.3.3": { + "name": "org.lwjgl:lwjgl-freetype:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-freetype/3.3.4/lwjgl-freetype-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-freetype/3.3.4/lwjgl-freetype-3.3.4.jar", + "sha1": "23f7bf165068ef2ca80ae1b79fd905af20498600", + "size": 453489 + } + } + }, + "org.lwjgl:lwjgl-freetype:3.3.3:natives-linux": null, "net.java.dev.jna:jna:5.8.0": { "name": "net.java.dev.jna:jna:5.13.0", "downloads": { @@ -3766,38 +3986,27 @@ "com.mojang:text2speech:1.13.9:natives-linux": null }, "windows-x86_64": { - "software-renderer-loader": { - "name": "org.glavo:llvmpipe-loader:1.0", - "downloads": { - "artifact": { - "path": "org/glavo/llvmpipe-loader/1.0/llvmpipe-loader-1.0.jar", - "url": "https://repo1.maven.org/maven2/org/glavo/llvmpipe-loader/1.0/llvmpipe-loader-1.0.jar", - "sha1": "ff255415e5c4b2a18970da0a8e552b557ca013ae", - "size": 12964773 - } - } - }, "mesa-loader": { - "name": "org.glavo:mesa-loader-windows:0.2.0:x64", + "name": "org.glavo:mesa-loader-windows:0.3.0:x64", "downloads": { "artifact": { - "path": "org/glavo/mesa-loader-windows/0.2.0/mesa-loader-windows-0.2.0-x64.jar", - "url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/0.2.0/mesa-loader-windows-0.2.0-x64.jar", - "sha1": "adb4a16ecb95c8944a016829b05d70f934ea1a29", - "size": 22823052 + "path": "org/glavo/mesa-loader-windows/0.3.0/mesa-loader-windows-0.3.0-x64.jar", + "url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/0.3.0/mesa-loader-windows-0.3.0-x64.jar", + "sha1": "629fca32417d6ec489cef8b2cbd0827131ec6801", + "size": 27174940 } } } }, "windows-x86": { "mesa-loader": { - "name": "org.glavo:mesa-loader-windows:0.2.0:x86", + "name": "org.glavo:mesa-loader-windows:0.3.0:x86", "downloads": { "artifact": { - "path": "org/glavo/mesa-loader-windows/0.2.0/mesa-loader-windows-0.2.0-x86.jar", - "url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/0.2.0/mesa-loader-windows-0.2.0-x86.jar", - "sha1": "93c0b9b7382d984e713d3aa299ceb337fde2897b", - "size": 18273286 + "path": "org/glavo/mesa-loader-windows/0.3.0/mesa-loader-windows-0.3.0-x86.jar", + "url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/0.3.0/mesa-loader-windows-0.3.0-x86.jar", + "sha1": "d25e0cdf5c5eb182acc9b93f700e0e6d8de36283", + "size": 22528549 } } } @@ -4178,7 +4387,18 @@ "com.mojang:text2speech:1.10.3:natives": null, "com.mojang:text2speech:1.11.3:natives": null, "com.mojang:text2speech:1.12.4:natives": null, - "com.mojang:text2speech:1.13.9:natives-windows": null + "com.mojang:text2speech:1.13.9:natives-windows": null, + "mesa-loader": { + "name": "org.glavo:mesa-loader-windows:0.3.0:arm64", + "downloads": { + "artifact": { + "path": "org/glavo/mesa-loader-windows/0.3.0/mesa-loader-windows-0.3.0-arm64.jar", + "url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/0.3.0/mesa-loader-windows-0.3.0-arm64.jar", + "sha1": "1986490c6fbe950e64018c2fb62c8ecf77a247ed", + "size": 24082103 + } + } + } }, "osx-arm64": { "org.lwjgl.lwjgl:lwjgl-platform:2.9.1-nightly-20130708-debug3:natives": { @@ -5054,7 +5274,7 @@ } } }, - "org.lwjgl:lwjgl:3.3.1:natives": { + "org.lwjgl:lwjgl:3.3.1:natives-linux": { "name": "org.lwjgl:lwjgl:3.3.4:natives-freebsd", "downloads": { "artifact": { @@ -5076,7 +5296,7 @@ } } }, - "org.lwjgl:lwjgl-jemalloc:3.3.1:natives": { + "org.lwjgl:lwjgl-jemalloc:3.3.1:natives-linux": { "name": "org.lwjgl:lwjgl-jemalloc:3.3.4:natives-freebsd", "downloads": { "artifact": { @@ -5098,7 +5318,7 @@ } } }, - "org.lwjgl:lwjgl-openal:3.3.1:natives": { + "org.lwjgl:lwjgl-openal:3.3.1:natives-linux": { "name": "org.lwjgl:lwjgl-openal:3.3.4:natives-freebsd", "downloads": { "artifact": { @@ -5120,7 +5340,7 @@ } } }, - "org.lwjgl:lwjgl-opengl:3.3.1:natives": { + "org.lwjgl:lwjgl-opengl:3.3.1:natives-linux": { "name": "org.lwjgl:lwjgl-opengl:3.3.4:natives-freebsd", "downloads": { "artifact": { @@ -5142,7 +5362,7 @@ } } }, - "org.lwjgl:lwjgl-glfw:3.3.1:natives": { + "org.lwjgl:lwjgl-glfw:3.3.1:natives-linux": { "name": "org.lwjgl:lwjgl-glfw:3.3.4:natives-freebsd", "downloads": { "artifact": { @@ -5164,7 +5384,7 @@ } } }, - "org.lwjgl:lwjgl-stb:3.3.1:natives": { + "org.lwjgl:lwjgl-stb:3.3.1:natives-linux": { "name": "org.lwjgl:lwjgl-stb:3.3.4:natives-freebsd", "downloads": { "artifact": { @@ -5186,7 +5406,7 @@ } } }, - "org.lwjgl:lwjgl-tinyfd:3.3.1:natives": { + "org.lwjgl:lwjgl-tinyfd:3.3.1:natives-linux": { "name": "org.lwjgl:lwjgl-tinyfd:3.3.4:natives-freebsd", "downloads": { "artifact": { @@ -5208,7 +5428,7 @@ } } }, - "org.lwjgl:lwjgl:3.3.2:natives": { + "org.lwjgl:lwjgl:3.3.2:natives-linux": { "name": "org.lwjgl:lwjgl:3.3.4:natives-freebsd", "downloads": { "artifact": { @@ -5230,7 +5450,7 @@ } } }, - "org.lwjgl:lwjgl-jemalloc:3.3.2:natives": { + "org.lwjgl:lwjgl-jemalloc:3.3.2:natives-linux": { "name": "org.lwjgl:lwjgl-jemalloc:3.3.4:natives-freebsd", "downloads": { "artifact": { @@ -5252,7 +5472,7 @@ } } }, - "org.lwjgl:lwjgl-openal:3.3.2:natives": { + "org.lwjgl:lwjgl-openal:3.3.2:natives-linux": { "name": "org.lwjgl:lwjgl-openal:3.3.4:natives-freebsd", "downloads": { "artifact": { @@ -5274,7 +5494,7 @@ } } }, - "org.lwjgl:lwjgl-opengl:3.3.2:natives": { + "org.lwjgl:lwjgl-opengl:3.3.2:natives-linux": { "name": "org.lwjgl:lwjgl-opengl:3.3.4:natives-freebsd", "downloads": { "artifact": { @@ -5296,7 +5516,7 @@ } } }, - "org.lwjgl:lwjgl-glfw:3.3.2:natives": { + "org.lwjgl:lwjgl-glfw:3.3.2:natives-linux": { "name": "org.lwjgl:lwjgl-glfw:3.3.4:natives-freebsd", "downloads": { "artifact": { @@ -5318,7 +5538,7 @@ } } }, - "org.lwjgl:lwjgl-stb:3.3.2:natives": { + "org.lwjgl:lwjgl-stb:3.3.2:natives-linux": { "name": "org.lwjgl:lwjgl-stb:3.3.4:natives-freebsd", "downloads": { "artifact": { @@ -5340,7 +5560,161 @@ } } }, - "org.lwjgl:lwjgl-tinyfd:3.3.2:natives": { + "org.lwjgl:lwjgl-tinyfd:3.3.2:natives-linux": { + "name": "org.lwjgl:lwjgl-tinyfd:3.3.4:natives-freebsd", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-freebsd.jar", + "sha1": "acd5e1b9b9b99ce4d21867058ee468ee45a859e5", + "size": 40104 + } + } + }, + "org.lwjgl:lwjgl:3.3.3": { + "name": "org.lwjgl:lwjgl:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar", + "sha1": "b86c3e4832426e8a6b466013b7cb34b40e9ce956", + "size": 800127 + } + } + }, + "org.lwjgl:lwjgl:3.3.3:natives-linux": { + "name": "org.lwjgl:lwjgl:3.3.4:natives-freebsd", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-freebsd.jar", + "sha1": "610d14530e637564d97d74af7cb98a737e70b77b", + "size": 96209 + } + } + }, + "org.lwjgl:lwjgl-jemalloc:3.3.3": { + "name": "org.lwjgl:lwjgl-jemalloc:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar", + "sha1": "e3f5dcb8e13f3a5ed3f740fd30a114cee2a80bc4", + "size": 46430 + } + } + }, + "org.lwjgl:lwjgl-jemalloc:3.3.3:natives-linux": { + "name": "org.lwjgl:lwjgl-jemalloc:3.3.4:natives-freebsd", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-freebsd.jar", + "sha1": "5ee27f3bad4715067cef0630682da4bb5a1b88ac", + "size": 157297 + } + } + }, + "org.lwjgl:lwjgl-openal:3.3.3": { + "name": "org.lwjgl:lwjgl-openal:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar", + "sha1": "9b74d3ea380c83353d42af43ad9659e04dabe84a", + "size": 113103 + } + } + }, + "org.lwjgl:lwjgl-openal:3.3.3:natives-linux": { + "name": "org.lwjgl:lwjgl-openal:3.3.4:natives-freebsd", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-freebsd.jar", + "sha1": "3863f8268f5515c27f1364257f8a018f0c6afa79", + "size": 597486 + } + } + }, + "org.lwjgl:lwjgl-opengl:3.3.3": { + "name": "org.lwjgl:lwjgl-opengl:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar", + "sha1": "2852ac7d9f6fc71349f1ce28e2708ff1977f18af", + "size": 931960 + } + } + }, + "org.lwjgl:lwjgl-opengl:3.3.3:natives-linux": { + "name": "org.lwjgl:lwjgl-opengl:3.3.4:natives-freebsd", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-freebsd.jar", + "sha1": "579071d2a3714f5662522f7d3edf58e941580587", + "size": 81028 + } + } + }, + "org.lwjgl:lwjgl-glfw:3.3.3": { + "name": "org.lwjgl:lwjgl-glfw:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar", + "sha1": "7e46ecdec85db8738053cfde1414352cd62dab74", + "size": 147044 + } + } + }, + "org.lwjgl:lwjgl-glfw:3.3.3:natives-linux": { + "name": "org.lwjgl:lwjgl-glfw:3.3.4:natives-freebsd", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-freebsd.jar", + "sha1": "f67b9b6c29451d8fea66db17aaba2f65e908c7e9", + "size": 104415 + } + } + }, + "org.lwjgl:lwjgl-stb:3.3.3": { + "name": "org.lwjgl:lwjgl-stb:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar", + "sha1": "5821735d5ef23f6da8542887344e57eb181b7cac", + "size": 143112 + } + } + }, + "org.lwjgl:lwjgl-stb:3.3.3:natives-linux": { + "name": "org.lwjgl:lwjgl-stb:3.3.4:natives-freebsd", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-freebsd.jar", + "sha1": "f5551338a1e2035ff747053f0e985dc93db1235c", + "size": 226093 + } + } + }, + "org.lwjgl:lwjgl-tinyfd:3.3.3": { + "name": "org.lwjgl:lwjgl-tinyfd:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar", + "sha1": "2d73789ffd8962b38d9d599cc38b2383ce818c7a", + "size": 15928 + } + } + }, + "org.lwjgl:lwjgl-tinyfd:3.3.3:natives-linux": { "name": "org.lwjgl:lwjgl-tinyfd:3.3.4:natives-freebsd", "downloads": { "artifact": { @@ -5351,6 +5725,28 @@ } } }, + "org.lwjgl:lwjgl-freetype:3.3.3": { + "name": "org.lwjgl:lwjgl-freetype:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-freetype/3.3.4/lwjgl-freetype-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-freetype/3.3.4/lwjgl-freetype-3.3.4.jar", + "sha1": "23f7bf165068ef2ca80ae1b79fd905af20498600", + "size": 453489 + } + } + }, + "org.lwjgl:lwjgl-freetype:3.3.3:natives-linux": { + "name": "org.lwjgl:lwjgl-freetype:3.3.4:natives-freebsd", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-freetype/3.3.4/lwjgl-freetype-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-freetype/3.3.4/lwjgl-freetype-3.3.4-natives-freebsd.jar", + "sha1": "67d6775292771087cb3d5ba1239bf9bf42fb3bd7", + "size": 1176759 + } + } + }, "net.java.jinput:jinput-platform:2.0.5:natives": null, "com.mojang:text2speech:1.10.3:natives": null, "com.mojang:text2speech:1.11.3:natives": null, diff --git a/HMCL/src/main/resources/assets/openjfx-dependencies.json b/HMCL/src/main/resources/assets/openjfx-dependencies.json new file mode 100644 index 0000000000..f0f732944a --- /dev/null +++ b/HMCL/src/main/resources/assets/openjfx-dependencies.json @@ -0,0 +1,314 @@ +{ + "windows-x86": [ + { + "module": "javafx.base", + "groupId": "org.openjfx", + "artifactId": "javafx-base", + "version": "19.0.2.1", + "classifier": "win-x86", + "sha1": "6c9ebafc7f9c4544d72fa5e306f4111e56b5db58" + }, + { + "module": "javafx.graphics", + "groupId": "org.openjfx", + "artifactId": "javafx-graphics", + "version": "19.0.2.1", + "classifier": "win-x86", + "sha1": "a14a1fbe3a0dca81d99c53fd7be8e7c784a68afe" + }, + { + "module": "javafx.controls", + "groupId": "org.openjfx", + "artifactId": "javafx-controls", + "version": "19.0.2.1", + "classifier": "win-x86", + "sha1": "7df1501701f9e9fbadab8ce55ef1dde128e2e88a" + } + ], + "windows-x86_64": [ + { + "module": "javafx.base", + "groupId": "org.openjfx", + "artifactId": "javafx-base", + "version": "17.0.13", + "classifier": "win", + "sha1": "ae86377359860449040372d3693bccf046148513" + }, + { + "module": "javafx.graphics", + "groupId": "org.openjfx", + "artifactId": "javafx-graphics", + "version": "17.0.13", + "classifier": "win", + "sha1": "bc0055a059b201d6c0be2293ccf72b6d2874fd1c" + }, + { + "module": "javafx.controls", + "groupId": "org.openjfx", + "artifactId": "javafx-controls", + "version": "17.0.13", + "classifier": "win", + "sha1": "bcd765dca36fa9e5fb4c997491a2d908947603f5" + } + ], + "windows-arm64": [ + { + "module": "javafx.base", + "groupId": "org.glavo.hmcl.openjfx", + "artifactId": "javafx-base", + "version": "18.0.2+1-arm64", + "classifier": "win", + "sha1": "4518a696b9d509dc09a7fe283452fce84a1686a8" + }, + { + "module": "javafx.graphics", + "groupId": "org.glavo.hmcl.openjfx", + "artifactId": "javafx-graphics", + "version": "18.0.2+1-arm64", + "classifier": "win", + "sha1": "e19ba9aefc4bba8ff86dcdf416620b93b7bdea39" + }, + { + "module": "javafx.controls", + "groupId": "org.glavo.hmcl.openjfx", + "artifactId": "javafx-controls", + "version": "18.0.2+1-arm64", + "classifier": "win", + "sha1": "0bf7380823bb8c420dd41837d2c71087b8953ec1" + } + ], + "osx-x86_64": [ + { + "module": "javafx.base", + "groupId": "org.openjfx", + "artifactId": "javafx-base", + "version": "19.0.2.1", + "classifier": "mac", + "sha1": "73e422d8426aaa23e8c712b9f4d9bebf70d3bfb9" + }, + { + "module": "javafx.graphics", + "groupId": "org.openjfx", + "artifactId": "javafx-graphics", + "version": "19.0.2.1", + "classifier": "mac", + "sha1": "6ab6f3e23421fcfa04e692d9d26a8f55a830c114" + }, + { + "module": "javafx.controls", + "groupId": "org.openjfx", + "artifactId": "javafx-controls", + "version": "19.0.2.1", + "classifier": "mac", + "sha1": "b7786b1b63e741c0e234829825fae5fef9d96c31" + } + ], + "osx-arm64": [ + { + "module": "javafx.base", + "groupId": "org.openjfx", + "artifactId": "javafx-base", + "version": "19.0.2.1", + "classifier": "mac-aarch64", + "sha1": "1d0d887c492330ed527b0614d115a4f32d2d05a4" + }, + { + "module": "javafx.graphics", + "groupId": "org.openjfx", + "artifactId": "javafx-graphics", + "version": "19.0.2.1", + "classifier": "mac-aarch64", + "sha1": "64db28799e61e0f8f51e471c732599b2a6961803" + }, + { + "module": "javafx.controls", + "groupId": "org.openjfx", + "artifactId": "javafx-controls", + "version": "19.0.2.1", + "classifier": "mac-aarch64", + "sha1": "3a14bd5f3ebe45d344c1f2bade0fe074e6ea2a83" + } + ], + "linux-x86_64": [ + { + "module": "javafx.base", + "groupId": "org.openjfx", + "artifactId": "javafx-base", + "version": "17.0.13", + "classifier": "linux", + "sha1": "875f02b498b00c9692f6ed0af3da373b1d07bf03" + }, + { + "module": "javafx.graphics", + "groupId": "org.openjfx", + "artifactId": "javafx-graphics", + "version": "17.0.13", + "classifier": "linux", + "sha1": "103f3bba5a177ba8912a92ef2ee0855de7ba050c" + }, + { + "module": "javafx.controls", + "groupId": "org.openjfx", + "artifactId": "javafx-controls", + "version": "17.0.13", + "classifier": "linux", + "sha1": "940e9e05a974a3e484d5ec241d88a0c77ad013f2" + } + ], + "linux-arm32": [ + { + "module": "javafx.base", + "groupId": "org.openjfx", + "artifactId": "javafx-base", + "version": "19.0.2.1", + "classifier": "linux-arm32-monocle", + "sha1": "452f455d6948788c1e5350a41259eb8101d3f82a" + }, + { + "module": "javafx.graphics", + "groupId": "org.openjfx", + "artifactId": "javafx-graphics", + "version": "19.0.2.1", + "classifier": "linux-arm32-monocle", + "sha1": "b45b33252e88263fe80a462a45828b4562c3c709" + }, + { + "module": "javafx.controls", + "groupId": "org.openjfx", + "artifactId": "javafx-controls", + "version": "19.0.2.1", + "classifier": "linux-arm32-monocle", + "sha1": "e606c619fc493ecd18d281b0ded3aa38ba15a9e5" + } + ], + "linux-arm64": [ + { + "module": "javafx.base", + "groupId": "org.openjfx", + "artifactId": "javafx-base", + "version": "19.0.2.1", + "classifier": "linux-aarch64", + "sha1": "1490bfe619e148b3d58746d43f549d697640c935" + }, + { + "module": "javafx.graphics", + "groupId": "org.openjfx", + "artifactId": "javafx-graphics", + "version": "19.0.2.1", + "classifier": "linux-aarch64", + "sha1": "cad8004a87f57d9813c52985894ef15e8011baee" + }, + { + "module": "javafx.controls", + "groupId": "org.openjfx", + "artifactId": "javafx-controls", + "version": "19.0.2.1", + "classifier": "linux-aarch64", + "sha1": "ccc33fc1fcbf46130346f4330d6d70b71bdec7d0" + } + ], + "linux-loongarch64": [ + { + "module": "javafx.base", + "groupId": "org.glavo.hmcl.openjfx", + "artifactId": "javafx-base", + "version": "17.0.8-loongarch64", + "classifier": "linux", + "sha1": "9d692dfc79eb334a7b4bae922aa57cb3a437345e" + }, + { + "module": "javafx.graphics", + "groupId": "org.glavo.hmcl.openjfx", + "artifactId": "javafx-graphics", + "version": "17.0.8-loongarch64", + "classifier": "linux", + "sha1": "7f241dc6adc8beddb776f453a3c63a5848d7b90b" + }, + { + "module": "javafx.controls", + "groupId": "org.glavo.hmcl.openjfx", + "artifactId": "javafx-controls", + "version": "17.0.8-loongarch64", + "classifier": "linux", + "sha1": "1c8b0141bec93ed21d7c0e9a2f69a1c7e3c734d2" + } + ], + "linux-loongarch64_ow": [ + { + "module": "javafx.base", + "groupId": "org.glavo.hmcl.openjfx", + "artifactId": "javafx-base", + "version": "19-ea+10-loongson64", + "classifier": "linux", + "sha1": "f90663ba93aef9f818236ce19ecfa33dca0ffe10" + }, + { + "module": "javafx.graphics", + "groupId": "org.glavo.hmcl.openjfx", + "artifactId": "javafx-graphics", + "version": "19-ea+10-loongson64", + "classifier": "linux", + "sha1": "6ff76304d08bba093abfe7f4d50ce6a49279c87f" + }, + { + "module": "javafx.controls", + "groupId": "org.glavo.hmcl.openjfx", + "artifactId": "javafx-controls", + "version": "19-ea+10-loongson64", + "classifier": "linux", + "sha1": "8a16096d42de70f2548f18840cdcd49a07fc1654" + } + ], + "linux-riscv64": [ + { + "module": "javafx.base", + "groupId": "org.glavo.hmcl.openjfx", + "artifactId": "javafx-base", + "version": "19.0.2.1-riscv64", + "classifier": "linux", + "sha1": "e33c7e0bac2931a8f7a752bb74e4e4e535eec4ad" + }, + { + "module": "javafx.graphics", + "groupId": "org.glavo.hmcl.openjfx", + "artifactId": "javafx-graphics", + "version": "19.0.2.1-riscv64", + "classifier": "linux", + "sha1": "ac4e5edd55d84da80f8fc2d81a4c9994296a6c09" + }, + { + "module": "javafx.controls", + "groupId": "org.glavo.hmcl.openjfx", + "artifactId": "javafx-controls", + "version": "19.0.2.1-riscv64", + "classifier": "linux", + "sha1": "efe6a87ea24972d45f1931528f87e64a53bd2232" + } + ], + "freebsd-x86_64": [ + { + "module": "javafx.base", + "groupId": "org.glavo.hmcl.openjfx", + "artifactId": "javafx-base", + "version": "14.0.2.1-freebsd", + "classifier": "freebsd", + "sha1": "7bac900f0ab0d4d6dcf178252cf37ee1e0470d40" + }, + { + "module": "javafx.graphics", + "groupId": "org.glavo.hmcl.openjfx", + "artifactId": "javafx-graphics", + "version": "14.0.2.1-freebsd", + "classifier": "freebsd", + "sha1": "7773ce02d1dc29160801e4e077ed2a26e93bed13" + }, + { + "module": "javafx.controls", + "groupId": "org.glavo.hmcl.openjfx", + "artifactId": "javafx-controls", + "version": "14.0.2.1-freebsd", + "classifier": "freebsd", + "sha1": "34a3dd3ccbc898bacfdcb3256cfb87ddc50621d3" + } + ] +} \ No newline at end of file diff --git a/HMCL/src/test/java/moe/mickey/minecraft/skin/fx/SkinCanvasSupport.java b/HMCL/src/test/java/org/jackhuang/hmcl/ui/skin/SkinCanvasSupport.java similarity index 98% rename from HMCL/src/test/java/moe/mickey/minecraft/skin/fx/SkinCanvasSupport.java rename to HMCL/src/test/java/org/jackhuang/hmcl/ui/skin/SkinCanvasSupport.java index 51f7826c52..51c00cab59 100644 --- a/HMCL/src/test/java/moe/mickey/minecraft/skin/fx/SkinCanvasSupport.java +++ b/HMCL/src/test/java/org/jackhuang/hmcl/ui/skin/SkinCanvasSupport.java @@ -1,4 +1,4 @@ -package moe.mickey.minecraft.skin.fx; +package org.jackhuang.hmcl.ui.skin; import javafx.application.Platform; import javafx.scene.Scene; @@ -8,7 +8,7 @@ import javafx.scene.input.ScrollEvent; import javafx.scene.input.TransferMode; import javafx.stage.Stage; -import moe.mickey.minecraft.skin.fx.test.Test; +import org.jackhuang.hmcl.ui.skin.test.Test; import java.io.File; import java.io.FileInputStream; diff --git a/HMCL/src/test/java/moe/mickey/minecraft/skin/fx/test/Test.java b/HMCL/src/test/java/org/jackhuang/hmcl/ui/skin/test/Test.java similarity index 76% rename from HMCL/src/test/java/moe/mickey/minecraft/skin/fx/test/Test.java rename to HMCL/src/test/java/org/jackhuang/hmcl/ui/skin/test/Test.java index f89d5b6aee..d311285bf4 100644 --- a/HMCL/src/test/java/moe/mickey/minecraft/skin/fx/test/Test.java +++ b/HMCL/src/test/java/org/jackhuang/hmcl/ui/skin/test/Test.java @@ -1,13 +1,13 @@ -package moe.mickey.minecraft.skin.fx.test; +package org.jackhuang.hmcl.ui.skin.test; import javafx.application.Application; import javafx.scene.Scene; import javafx.stage.Stage; -import moe.mickey.minecraft.skin.fx.FunctionHelper; -import moe.mickey.minecraft.skin.fx.SkinCanvas; -import moe.mickey.minecraft.skin.fx.SkinCanvasSupport; -import moe.mickey.minecraft.skin.fx.animation.SkinAniRunning; -import moe.mickey.minecraft.skin.fx.animation.SkinAniWavingArms; +import org.jackhuang.hmcl.ui.skin.FunctionHelper; +import org.jackhuang.hmcl.ui.skin.SkinCanvas; +import org.jackhuang.hmcl.ui.skin.SkinCanvasSupport; +import org.jackhuang.hmcl.ui.skin.animation.SkinAniRunning; +import org.jackhuang.hmcl.ui.skin.animation.SkinAniWavingArms; import org.jackhuang.hmcl.game.TexturesLoader; import java.util.function.Consumer; diff --git a/HMCLCore/build.gradle.kts b/HMCLCore/build.gradle.kts index c94473b502..ba4e103da1 100644 --- a/HMCLCore/build.gradle.kts +++ b/HMCLCore/build.gradle.kts @@ -4,13 +4,20 @@ plugins { dependencies { api("org.glavo:simple-png-javafx:0.3.0") - api("com.google.code.gson:gson:2.10.1") + api("com.google.code.gson:gson:2.11.0") api("com.moandjiezana.toml:toml4j:0.7.2") - api("org.tukaani:xz:1.9") + api("org.tukaani:xz:1.10") api("org.hildan.fxgson:fx-gson:5.0.0") api("org.jenkins-ci:constant-pool-scanner:1.2") api("com.github.steveice10:opennbt:1.5") api("org.nanohttpd:nanohttpd:2.3.1") api("org.apache.commons:commons-compress:1.25.0") - compileOnlyApi("org.jetbrains:annotations:24.1.0") + api("org.jsoup:jsoup:1.18.1") + compileOnlyApi("org.jetbrains:annotations:26.0.1") + + if (JavaVersion.current().isJava8) { + org.gradle.internal.jvm.Jvm.current().toolsJar?.let { + compileOnly(files(it)) + } + } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Account.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Account.java index 8ac98a2a98..3d40c983fe 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Account.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Account.java @@ -30,6 +30,7 @@ import org.jackhuang.hmcl.util.ToStringBuilder; import org.jackhuang.hmcl.util.javafx.ObservableHelper; +import java.nio.file.Path; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -69,6 +70,14 @@ public abstract class Account implements Observable { */ public abstract AuthInfo playOffline() throws AuthenticationException; + public boolean canUploadSkin() { + return false; + } + + public void uploadSkin(boolean isSlim, Path file) throws AuthenticationException, UnsupportedOperationException { + throw new UnsupportedOperationException("Unsupported Operation"); + } + public abstract Map toStorage(); public void clearCache() { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OAuth.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OAuth.java index 7ae6d51a04..c71b022168 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OAuth.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OAuth.java @@ -109,7 +109,7 @@ private Result authenticateDevice(Options options) throws IOException, Interrupt options.callback.openBrowser(deviceTokenResponse.verificationURI); long startTime = System.nanoTime(); - int interval = deviceTokenResponse.interval; + long interval = TimeUnit.MILLISECONDS.convert(deviceTokenResponse.interval, TimeUnit.SECONDS); while (true) { Thread.sleep(Math.max(interval, 1)); @@ -138,7 +138,7 @@ private Result authenticateDevice(Options options) throws IOException, Interrupt } if ("slow_down".equals(tokenResponse.error)) { - interval += 5; + interval += 5000; continue; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftAccount.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftAccount.java index 774178a725..ef9401630d 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftAccount.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftAccount.java @@ -24,6 +24,7 @@ import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService; import org.jackhuang.hmcl.util.javafx.BindingMapping; +import java.nio.file.Path; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -32,7 +33,7 @@ import static java.util.Objects.requireNonNull; import static org.jackhuang.hmcl.util.logging.Logger.LOG; -public class MicrosoftAccount extends OAuthAccount { +public final class MicrosoftAccount extends OAuthAccount { protected final MicrosoftService service; protected UUID characterUUID; @@ -125,6 +126,16 @@ public AuthInfo playOffline() { return session.toAuthInfo(); } + @Override + public boolean canUploadSkin() { + return true; + } + + @Override + public void uploadSkin(boolean isSlim, Path file) throws AuthenticationException, UnsupportedOperationException { + service.uploadSkin(session.getAccessToken(), isSlim, file); + } + @Override public Map toStorage() { return session.toStorage(); @@ -150,6 +161,7 @@ public ObjectBinding>> getTextures() { @Override public void clearCache() { authenticated = false; + service.getProfileRepository().invalidate(characterUUID); } @Override diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftService.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftService.java index 214f062de0..335e074d00 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftService.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftService.java @@ -25,19 +25,18 @@ import org.jackhuang.hmcl.auth.OAuth; import org.jackhuang.hmcl.auth.ServerDisconnectException; import org.jackhuang.hmcl.auth.ServerResponseMalformedException; -import org.jackhuang.hmcl.auth.yggdrasil.CompleteGameProfile; -import org.jackhuang.hmcl.auth.yggdrasil.RemoteAuthenticationException; -import org.jackhuang.hmcl.auth.yggdrasil.Texture; -import org.jackhuang.hmcl.auth.yggdrasil.TextureType; +import org.jackhuang.hmcl.auth.yggdrasil.*; +import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.gson.*; -import org.jackhuang.hmcl.util.io.HttpRequest; -import org.jackhuang.hmcl.util.io.NetworkUtils; -import org.jackhuang.hmcl.util.io.ResponseCodeException; +import org.jackhuang.hmcl.util.io.*; import org.jackhuang.hmcl.util.javafx.ObservableOptionalCache; import java.io.IOException; +import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.*; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -261,6 +260,33 @@ public Optional getCompleteGameProfile(UUID uuid) throws Au return Optional.ofNullable(GSON.fromJson(request(NetworkUtils.toURL("https://sessionserver.mojang.com/session/minecraft/profile/" + UUIDTypeAdapter.fromUUID(uuid)), null), CompleteGameProfile.class)); } + public void uploadSkin(String accessToken, boolean isSlim, Path file) throws AuthenticationException, UnsupportedOperationException { + try { + HttpURLConnection con = NetworkUtils.createHttpConnection(NetworkUtils.toURL("https://api.minecraftservices.com/minecraft/profile/skins")); + con.setRequestMethod("POST"); + con.setRequestProperty("Authorization", "Bearer " + accessToken); + con.setDoOutput(true); + try (HttpMultipartRequest request = new HttpMultipartRequest(con)) { + request.param("variant", isSlim ? "slim" : "classic"); + try (InputStream fis = Files.newInputStream(file)) { + request.file("file", FileUtils.getName(file), "image/" + FileUtils.getExtension(file), fis); + } + } + + String response = NetworkUtils.readData(con); + if (StringUtils.isBlank(response)) { + if (con.getResponseCode() / 100 != 2) + throw new ResponseCodeException(con.getURL(), con.getResponseCode()); + } else { + MinecraftErrorResponse profileResponse = GSON.fromJson(response, MinecraftErrorResponse.class); + if (StringUtils.isNotBlank(profileResponse.errorMessage) || con.getResponseCode() / 100 != 2) + throw new AuthenticationException("Failed to upload skin, response code: " + con.getResponseCode() + ", response: " + response); + } + } catch (IOException | JsonParseException e) { + throw new AuthenticationException(e); + } + } + private static String request(URL url, Object payload) throws AuthenticationException { try { if (payload == null) @@ -272,14 +298,6 @@ private static String request(URL url, Object payload) throws AuthenticationExce } } - private static T fromJson(String text, Class typeOfT) throws ServerResponseMalformedException { - try { - return GSON.fromJson(text, typeOfT); - } catch (JsonParseException e) { - throw new ServerResponseMalformedException(text, e); - } - } - public static class XboxAuthorizationException extends AuthenticationException { private final long errorCode; private final String redirect; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/YggdrasilServer.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/YggdrasilServer.java index f3deed3d4f..7e722fa6bb 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/YggdrasilServer.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/YggdrasilServer.java @@ -17,7 +17,6 @@ */ package org.jackhuang.hmcl.auth.offline; -import com.google.gson.reflect.TypeToken; import org.glavo.png.javafx.PNGJavaFXUtils; import org.jackhuang.hmcl.auth.yggdrasil.GameProfile; import org.jackhuang.hmcl.auth.yggdrasil.TextureModel; @@ -38,6 +37,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.jackhuang.hmcl.util.Lang.mapOf; import static org.jackhuang.hmcl.util.Pair.pair; +import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf; public class YggdrasilServer extends HttpServer { @@ -81,8 +81,7 @@ private Response status(Request request) { } private Response profiles(Request request) throws IOException { - List names = JsonUtils.fromNonNullJsonFully(request.getSession().getInputStream(), new TypeToken>() { - }.getType()); + List names = JsonUtils.fromNonNullJsonFully(request.getSession().getInputStream(), listTypeOf(String.class)); return ok(names.stream().distinct() .map(this::findCharacterByName) .flatMap(Lang::toStream) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.java index db0f06fdeb..f253eb40b3 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.java @@ -203,8 +203,14 @@ public ObjectBinding>> getTextures() { } - public void uploadSkin(String model, Path file) throws AuthenticationException, UnsupportedOperationException { - service.uploadSkin(characterUUID, session.getAccessToken(), model, file); + @Override + public boolean canUploadSkin() { + return true; + } + + @Override + public void uploadSkin(boolean isSlim, Path file) throws AuthenticationException, UnsupportedOperationException { + service.uploadSkin(characterUUID, session.getAccessToken(), isSlim, file); } private static String randomClientToken() { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilService.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilService.java index 3e3b71e5ee..ef3b695626 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilService.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilService.java @@ -148,14 +148,14 @@ public void invalidate(String accessToken, String clientToken) throws Authentica requireEmpty(request(provider.getInvalidationURL(), createRequestWithCredentials(accessToken, clientToken))); } - public void uploadSkin(UUID uuid, String accessToken, String model, Path file) throws AuthenticationException, UnsupportedOperationException { + public void uploadSkin(UUID uuid, String accessToken, boolean isSlim, Path file) throws AuthenticationException, UnsupportedOperationException { try { HttpURLConnection con = NetworkUtils.createHttpConnection(provider.getSkinUploadURL(uuid)); con.setRequestMethod("PUT"); con.setRequestProperty("Authorization", "Bearer " + accessToken); con.setDoOutput(true); try (HttpMultipartRequest request = new HttpMultipartRequest(con)) { - request.param("model", model); + request.param("model", isSlim ? "slim" : ""); try (InputStream fis = Files.newInputStream(file)) { request.file("file", FileUtils.getName(file), "image/" + FileUtils.getExtension(file), fis); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java index 71f963cfb9..e88e140397 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java @@ -170,6 +170,7 @@ public static boolean isModded(VersionProvider provider, Version version) { Version resolvedVersion = version.resolve(provider); String mainClass = resolvedVersion.getMainClass(); return mainClass != null && (LAUNCH_WRAPPER_MAIN.equals(mainClass) + || mainClass.startsWith("net.minecraftforge") || mainClass.startsWith("net.fabricmc") || mainClass.startsWith("org.quiltmc") || mainClass.startsWith("cpw.mods")); @@ -270,6 +271,13 @@ private String scanVersion(Version version) { private final Pattern group, artifact; private final ModLoaderType modLoaderType; + private static final Map PATCH_ID_MAP = new HashMap<>(); + static { + for (LibraryType type : values()) { + PATCH_ID_MAP.put(type.getPatchId(), type); + } + } + LibraryType(boolean modLoader, String patchId, Pattern group, Pattern artifact, ModLoaderType modLoaderType) { this.modLoader = modLoader; this.patchId = patchId; @@ -291,10 +299,7 @@ public ModLoaderType getModLoaderType() { } public static LibraryType fromPatchId(String patchId) { - for (LibraryType type : values()) - if (type.getPatchId().equals(patchId)) - return type; - return null; + return PATCH_ID_MAP.get(patchId); } protected boolean matchLibrary(Library library, List libraries) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricVersionList.java index 9837bc6791..8440f70b79 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricVersionList.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricVersionList.java @@ -17,7 +17,6 @@ */ package org.jackhuang.hmcl.download.fabric; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.download.DownloadProvider; import org.jackhuang.hmcl.download.VersionList; import org.jackhuang.hmcl.util.gson.JsonUtils; @@ -25,13 +24,13 @@ import org.jetbrains.annotations.Nullable; import java.io.IOException; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import static org.jackhuang.hmcl.util.Lang.wrap; +import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf; public final class FabricVersionList extends VersionList { private final DownloadProvider downloadProvider; @@ -69,8 +68,8 @@ public CompletableFuture refreshAsync() { private List getGameVersions(String metaUrl) throws IOException { String json = NetworkUtils.doGet(downloadProvider.injectURLWithCandidates(metaUrl)); - return JsonUtils.GSON.>fromJson(json, new TypeToken>() { - }.getType()).stream().map(GameVersion::getVersion).collect(Collectors.toList()); + return JsonUtils.GSON.fromJson(json, listTypeOf(GameVersion.class)) + .stream().map(GameVersion::getVersion).collect(Collectors.toList()); } private static String getLaunchMetaUrl(String gameVersion, String loaderVersion) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java index ef37315eee..c0d0fdc8b7 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.download.forge; import com.google.gson.JsonParseException; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.download.VersionList; import org.jackhuang.hmcl.util.Immutable; import org.jackhuang.hmcl.util.Lang; @@ -40,6 +39,7 @@ import static org.jackhuang.hmcl.util.Lang.mapOf; import static org.jackhuang.hmcl.util.Lang.wrap; import static org.jackhuang.hmcl.util.Pair.pair; +import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf; import static org.jackhuang.hmcl.util.logging.Logger.LOG; public final class ForgeBMCLVersionList extends VersionList { @@ -87,11 +87,9 @@ public CompletableFuture refreshAsync(String gameVersion) { String lookupVersion = toLookupVersion(gameVersion); return CompletableFuture.completedFuture(null) - .thenApplyAsync(wrap(unused -> HttpRequest.GET(apiRoot + "/forge/minecraft/" + lookupVersion).>getJson(new TypeToken>() { - }.getType()))) + .thenApplyAsync(wrap(unused -> HttpRequest.GET(apiRoot + "/forge/minecraft/" + lookupVersion).getJson(listTypeOf(ForgeVersion.class)))) .thenAcceptAsync(forgeVersions -> { lock.writeLock().lock(); - try { versions.clear(gameVersion); if (forgeVersions == null) return; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaDistribution.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaDistribution.java index f4032bebb7..96d02c95cc 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaDistribution.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaDistribution.java @@ -17,12 +17,7 @@ */ package org.jackhuang.hmcl.download.java; -import org.jackhuang.hmcl.download.DownloadProvider; -import org.jackhuang.hmcl.task.Task; -import org.jackhuang.hmcl.util.platform.Platform; - import java.util.Set; -import java.util.TreeMap; /** * @author Glavo @@ -31,6 +26,4 @@ public interface JavaDistribution { String getDisplayName(); Set getSupportedPackageTypes(); - - Task> getFetchJavaVersionsTask(DownloadProvider provider, Platform platform, JavaPackageType packageType); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaPackageType.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaPackageType.java index 42cc195d8f..8fcf4d3739 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaPackageType.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaPackageType.java @@ -21,10 +21,10 @@ * @author Glavo */ public enum JavaPackageType { - JDK(true, false), JRE(false, false), - JDKFX(true, true), - JREFX(false, true); + JDK(true, false), + JREFX(false, true), + JDKFX(true, true); private final boolean jdk; private final boolean javafx; @@ -34,6 +34,13 @@ public enum JavaPackageType { this.javafx = javafx; } + public static JavaPackageType of(boolean jdk, boolean javafx) { + if (jdk) + return javafx ? JDKFX : JDK; + else + return javafx ? JREFX : JRE; + } + public boolean isJDK() { return jdk; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/disco/DiscoFetchJavaListTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/disco/DiscoFetchJavaListTask.java index 036822ce57..df178fe576 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/disco/DiscoFetchJavaListTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/disco/DiscoFetchJavaListTask.java @@ -33,7 +33,7 @@ /** * @author Glavo */ -public final class DiscoFetchJavaListTask extends Task> { +public final class DiscoFetchJavaListTask extends Task>> { public static final String API_ROOT = System.getProperty("hmcl.discoapi.override", "https://api.foojay.io/disco/v3.0"); @@ -46,22 +46,21 @@ private static String getArchitectureName(Architecture arch) { } private final DiscoJavaDistribution distribution; + private final String archiveType; private final Task fetchPackagesTask; - public DiscoFetchJavaListTask(DownloadProvider downloadProvider, DiscoJavaDistribution distribution, Platform platform, JavaPackageType packageType) { + public DiscoFetchJavaListTask(DownloadProvider downloadProvider, DiscoJavaDistribution distribution, Platform platform) { this.distribution = distribution; + this.archiveType = platform.getOperatingSystem() == OperatingSystem.WINDOWS ? "zip" : "tar.gz"; HashMap params = new HashMap<>(); params.put("distribution", distribution.getApiParameter()); - params.put("package", packageType.isJDK() ? "jdk" : "jre"); - params.put("javafx_bundled", Boolean.toString(packageType.isJavaFXBundled())); params.put("operating_system", getOperatingSystemName(platform.getOperatingSystem())); params.put("architecture", getArchitectureName(platform.getArchitecture())); - params.put("archive_type", platform.getOperatingSystem() == OperatingSystem.WINDOWS ? "zip" : "tar.gz"); + params.put("archive_type", archiveType); params.put("directly_downloadable", "true"); - if (platform.getOperatingSystem() == OperatingSystem.LINUX) { + if (platform.getOperatingSystem() == OperatingSystem.LINUX) params.put("lib_c_type", "glibc"); - } this.fetchPackagesTask = new GetTask(downloadProvider.injectURLWithCandidates(NetworkUtils.withQuery(API_ROOT + "/packages", params))); } @@ -74,22 +73,28 @@ public Collection> getDependents() { @Override public void execute() throws Exception { String json = fetchPackagesTask.getResult(); - List result = JsonUtils.fromNonNullJson(json, DiscoResult.typeOf(DiscoJavaRemoteVersion.class)).getResult(); + List list = JsonUtils.fromNonNullJson(json, DiscoResult.typeOf(DiscoJavaRemoteVersion.class)).getResult(); + EnumMap> result = new EnumMap<>(JavaPackageType.class); - TreeMap map = new TreeMap<>(); + for (DiscoJavaRemoteVersion version : list) { + if (!distribution.getApiParameter().equals(version.getDistribution()) + || !version.isDirectlyDownloadable() + || !archiveType.equals(version.getArchiveType())) + continue; - for (DiscoJavaRemoteVersion version : result) { - if (!distribution.getApiParameter().equals(version.getDistribution())) + if (!distribution.testVersion(version)) continue; + JavaPackageType packageType = JavaPackageType.of("jdk".equals(version.getPackageType()), version.isJavaFXBundled()); + TreeMap map = result.computeIfAbsent(packageType, ignored -> new TreeMap<>()); + int jdkVersion = version.getJdkVersion(); DiscoJavaRemoteVersion oldVersion = map.get(jdkVersion); - if (oldVersion == null || VersionNumber.compare(version.getDistributionVersion(), oldVersion.getDistributionVersion()) > 0) { + if (oldVersion == null || VersionNumber.compare(version.getDistributionVersion(), oldVersion.getDistributionVersion()) > 0) map.put(jdkVersion, version); - } } - setResult(map); + setResult(result); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/disco/DiscoJavaDistribution.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/disco/DiscoJavaDistribution.java index 80611d674b..5a9c696beb 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/disco/DiscoJavaDistribution.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/disco/DiscoJavaDistribution.java @@ -17,10 +17,8 @@ */ package org.jackhuang.hmcl.download.java.disco; -import org.jackhuang.hmcl.download.DownloadProvider; import org.jackhuang.hmcl.download.java.JavaDistribution; import org.jackhuang.hmcl.download.java.JavaPackageType; -import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.util.Pair; import org.jackhuang.hmcl.util.platform.Architecture; import org.jackhuang.hmcl.util.platform.OperatingSystem; @@ -35,6 +33,7 @@ /** * @author Glavo + * @see discoapi */ public enum DiscoJavaDistribution implements JavaDistribution { TEMURIN("Eclipse Temurin", "temurin", "Adoptium", @@ -42,21 +41,42 @@ public enum DiscoJavaDistribution implements JavaDistribution> getFetchJavaVersionsTask(DownloadProvider provider, Platform platform, JavaPackageType packageType) { - return new DiscoFetchJavaListTask(provider, this, platform, packageType); + public boolean testVersion(DiscoJavaRemoteVersion version) { + return this.getApiParameter().equals(version.getDistribution()); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/disco/DiscoJavaRemoteVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/disco/DiscoJavaRemoteVersion.java index 3698183d86..ca65643db7 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/disco/DiscoJavaRemoteVersion.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/disco/DiscoJavaRemoteVersion.java @@ -171,6 +171,10 @@ public String getTermOfSupport() { return termOfSupport; } + public boolean isLTS() { + return "lts".equals(termOfSupport); + } + public String getOperatingSystem() { return operatingSystem; } @@ -191,7 +195,7 @@ public String getPackageType() { return packageType; } - public boolean isJavafxBundled() { + public boolean isJavaFXBundled() { return javafxBundled; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/mojang/MojangJavaDistribution.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/mojang/MojangJavaDistribution.java index b8de72f728..3f257264fd 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/mojang/MojangJavaDistribution.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/mojang/MojangJavaDistribution.java @@ -17,16 +17,12 @@ */ package org.jackhuang.hmcl.download.java.mojang; -import org.jackhuang.hmcl.download.DownloadProvider; import org.jackhuang.hmcl.download.java.JavaDistribution; import org.jackhuang.hmcl.download.java.JavaPackageType; import org.jackhuang.hmcl.download.java.JavaRemoteVersion; -import org.jackhuang.hmcl.task.Task; -import org.jackhuang.hmcl.util.platform.Platform; import java.util.Collections; import java.util.Set; -import java.util.TreeMap; /** * @author Glavo @@ -47,9 +43,4 @@ public String getDisplayName() { public Set getSupportedPackageTypes() { return Collections.singleton(JavaPackageType.JRE); } - - @Override - public Task> getFetchJavaVersionsTask(DownloadProvider provider, Platform platform, JavaPackageType packageType) { - return null; - } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/mojang/MojangJavaDownloads.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/mojang/MojangJavaDownloads.java index e549664676..cd1cf741cd 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/mojang/MojangJavaDownloads.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/mojang/MojangJavaDownloads.java @@ -19,7 +19,6 @@ import com.google.gson.*; import com.google.gson.annotations.JsonAdapter; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.game.DownloadInfo; import org.jackhuang.hmcl.util.Immutable; @@ -27,6 +26,9 @@ import java.util.List; import java.util.Map; +import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf; +import static org.jackhuang.hmcl.util.gson.JsonUtils.mapTypeOf; + @JsonAdapter(MojangJavaDownloads.Adapter.class) public class MojangJavaDownloads { @@ -49,8 +51,7 @@ public JsonElement serialize(MojangJavaDownloads src, Type typeOfSrc, JsonSerial @Override public MojangJavaDownloads deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { - return new MojangJavaDownloads(context.deserialize(json, new TypeToken>>>() { - }.getType())); + return new MojangJavaDownloads(context.deserialize(json, mapTypeOf(String.class, mapTypeOf(String.class, listTypeOf(JavaDownload.class))).getType())); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeBMCLVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeBMCLVersionList.java index d1d8c83407..a00f2eaebc 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeBMCLVersionList.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeBMCLVersionList.java @@ -19,18 +19,17 @@ import com.google.gson.JsonParseException; import com.google.gson.annotations.SerializedName; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.download.VersionList; import org.jackhuang.hmcl.util.Immutable; import org.jackhuang.hmcl.util.gson.Validation; import org.jackhuang.hmcl.util.io.HttpRequest; import java.util.Collections; -import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; import static org.jackhuang.hmcl.util.Lang.wrap; +import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf; public final class NeoForgeBMCLVersionList extends VersionList { private final String apiRoot; @@ -68,8 +67,7 @@ public Optional getVersion(String gameVersion, String rem @Override public CompletableFuture refreshAsync(String gameVersion) { return CompletableFuture.completedFuture((Void) null) - .thenApplyAsync(wrap(unused -> HttpRequest.GET(apiRoot + "/neoforge/list/" + gameVersion).>getJson(new TypeToken>() { - }.getType()))) + .thenApplyAsync(wrap(unused -> HttpRequest.GET(apiRoot + "/neoforge/list/" + gameVersion).getJson(listTypeOf(NeoForgeVersion.class)))) .thenAcceptAsync(neoForgeVersions -> { lock.writeLock().lock(); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineBMCLVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineBMCLVersionList.java index af5f8b7546..5e9c368235 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineBMCLVersionList.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineBMCLVersionList.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.download.optifine; import com.google.gson.annotations.SerializedName; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.download.VersionList; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.io.HttpRequest; @@ -26,10 +25,11 @@ import java.util.Collections; import java.util.HashSet; -import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; +import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf; + /** * @author huangyuhui */ @@ -72,8 +72,7 @@ private String toLookupVersion(String version) { @Override public CompletableFuture refreshAsync() { - return HttpRequest.GET(apiRoot + "/optifine/versionlist").>getJsonAsync(new TypeToken>() { - }.getType()).thenAcceptAsync(root -> { + return HttpRequest.GET(apiRoot + "/optifine/versionlist").getJsonAsync(listTypeOf(OptiFineVersion.class)).thenAcceptAsync(root -> { lock.writeLock().lock(); try { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/quilt/QuiltVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/quilt/QuiltVersionList.java index bc7bf88041..f7d6a2e6b2 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/quilt/QuiltVersionList.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/quilt/QuiltVersionList.java @@ -17,7 +17,6 @@ */ package org.jackhuang.hmcl.download.quilt; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.download.DownloadProvider; import org.jackhuang.hmcl.download.VersionList; import org.jackhuang.hmcl.util.gson.JsonUtils; @@ -25,13 +24,13 @@ import org.jetbrains.annotations.Nullable; import java.io.IOException; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import static org.jackhuang.hmcl.util.Lang.wrap; +import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf; public final class QuiltVersionList extends VersionList { private final DownloadProvider downloadProvider; @@ -69,8 +68,8 @@ public CompletableFuture refreshAsync() { private List getGameVersions(String metaUrl) throws IOException { String json = NetworkUtils.doGet(downloadProvider.injectURLWithCandidates(metaUrl)); - return JsonUtils.GSON.>fromJson(json, new TypeToken>() { - }.getType()).stream().map(GameVersion::getVersion).collect(Collectors.toList()); + return JsonUtils.GSON.fromJson(json, listTypeOf(GameVersion.class)) + .stream().map(GameVersion::getVersion).collect(Collectors.toList()); } private static String getLaunchMetaUrl(String gameVersion, String loaderVersion) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/CrashReportAnalyzer.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/CrashReportAnalyzer.java index 42e96367ed..22cce5a2aa 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/CrashReportAnalyzer.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/CrashReportAnalyzer.java @@ -113,7 +113,7 @@ public enum Rule { //https://github.com/HMCL-dev/HMCL/pull/2038 MODMIXIN_FAILURE(Pattern.compile("(MixinApplyError|Mixin prepare failed |Mixin apply failed |mixin\\.injection\\.throwables\\.|\\.mixins\\.json\\] FAILED during \\))")),//ModMixin失败 MIXIN_APPLY_MOD_FAILED(Pattern.compile("Mixin apply for mod (?.*) failed"), "id"),//Mixin应用失败 - FORGE_ERROR(Pattern.compile("An exception was thrown, the game will display an error screen and halt\\.(?(.*)[\\n\\r]*((.*)[\\n\\r]*)+)at "), "reason"),//Forge报错,Forge可能已经提供了错误信息 + FORGE_ERROR(Pattern.compile("An exception was thrown, the game will display an error screen and halt\\.\\R*(?.*\\R*(\\s*at .*\\R)+)"), "reason"),//Forge报错,Forge可能已经提供了错误信息 MOD_RESOLUTION0(Pattern.compile("(\tMod File:|-- MOD |\tFailure message:)")), FORGE_REPEAT_INSTALLATION(Pattern.compile("MultipleArgumentsForOptionException: Found multiple arguments for option (.*?), but you asked for only one")),//https://github.com/HMCL-dev/HMCL/issues/1880 OPTIFINE_REPEAT_INSTALLATION(Pattern.compile("ResolutionException: Module optifine reads another module named optifine")),//Optifine 重复安装(及Mod文件夹有,自动安装也有) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java index 115b79c332..21c63c60ef 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.game; import com.google.gson.JsonParseException; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.download.MaintainTask; import org.jackhuang.hmcl.download.game.VersionJsonSaveTask; import org.jackhuang.hmcl.event.*; @@ -499,18 +498,16 @@ public File getModpackConfiguration(String version) { * read modpack configuration for a version. * * @param version version installed as modpack - * @param manifest type of ModpackConfiguration * @return modpack configuration object, or null if this version is not a modpack. * @throws VersionNotFoundException if version does not exist. * @throws IOException if an i/o error occurs. */ @Nullable - public ModpackConfiguration readModpackConfiguration(String version) throws IOException, VersionNotFoundException { + public ModpackConfiguration readModpackConfiguration(String version) throws IOException, VersionNotFoundException { if (!hasVersion(version)) throw new VersionNotFoundException(version); File file = getModpackConfiguration(version); if (!file.exists()) return null; - return JsonUtils.GSON.fromJson(FileUtils.readText(file), new TypeToken>() { - }.getType()); + return JsonUtils.GSON.fromJson(FileUtils.readText(file), ModpackConfiguration.class); } public boolean isModpack(String version) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameJavaVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameJavaVersion.java index 5f5afda69c..e02f9eafe0 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameJavaVersion.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameJavaVersion.java @@ -33,7 +33,7 @@ public final class GameJavaVersion { public static final GameJavaVersion LATEST = JAVA_21; public static GameJavaVersion getMinimumJavaVersion(GameVersionNumber gameVersion) { - if (gameVersion.compareTo("1.21") >= 0) + if (gameVersion.compareTo("1.20.5") >= 0) return JAVA_21; if (gameVersion.compareTo("1.18") >= 0) return JAVA_17; @@ -59,21 +59,6 @@ public static GameJavaVersion get(int major) { } } - public static boolean isSupportedPlatform(Platform platform) { - OperatingSystem os = platform.getOperatingSystem(); - Architecture arch = platform.getArchitecture(); - switch (arch) { - case X86: - return os == OperatingSystem.WINDOWS || os == OperatingSystem.LINUX; - case X86_64: - return os == OperatingSystem.WINDOWS || os == OperatingSystem.LINUX || os == OperatingSystem.OSX; - case ARM64: - return os == OperatingSystem.WINDOWS || os == OperatingSystem.OSX; - default: - return false; - } - } - public static List getSupportedVersions(Platform platform) { OperatingSystem operatingSystem = platform.getOperatingSystem(); Architecture architecture = platform.getArchitecture(); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/JavaVersionConstraint.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/JavaVersionConstraint.java index dc7abb453c..ed1889fccc 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/JavaVersionConstraint.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/JavaVersionConstraint.java @@ -62,7 +62,14 @@ protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nul return analyzer != null && analyzer.has(LibraryAnalyzer.LibraryType.FORGE); } }, - MODDED_JAVA_17(false, GameVersionNumber.atLeast("1.18"), VersionNumber.between("17", "17.999")) { + MODDED_JAVA_17(false, GameVersionNumber.between("1.18", "1.20.4"), VersionNumber.between("17", "17.999")) { + @Override + protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version, + @Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) { + return analyzer != null && analyzer.has(LibraryAnalyzer.LibraryType.FORGE); + } + }, + MODDED_JAVA_21(false, GameVersionNumber.atLeast("1.20.5"), VersionNumber.between("21", "21.999")) { @Override protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version, @Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/RuledArgument.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/RuledArgument.java index d812258c02..b55937f15b 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/RuledArgument.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/RuledArgument.java @@ -19,13 +19,14 @@ import com.google.gson.*; import com.google.gson.annotations.JsonAdapter; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.util.Immutable; import java.lang.reflect.Type; import java.util.*; import java.util.stream.Collectors; +import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf; + /** * * @author huangyuhui @@ -86,8 +87,7 @@ public JsonElement serialize(RuledArgument src, Type typeOfSrc, JsonSerializatio public RuledArgument deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { JsonObject obj = json.getAsJsonObject(); - List rules = context.deserialize(obj.get("rules"), new TypeToken>() { - }.getType()); + List rules = context.deserialize(obj.get("rules"), listTypeOf(CompatibilityRule.class).getType()); JsonElement valuesElement; if (obj.has("values")) { @@ -102,8 +102,7 @@ public RuledArgument deserialize(JsonElement json, Type typeOfT, JsonDeserializa if (valuesElement.isJsonPrimitive()) { values = Collections.singletonList(valuesElement.getAsString()); } else { - values = context.deserialize(valuesElement, new TypeToken>() { - }.getType()); + values = context.deserialize(valuesElement, listTypeOf(String.class).getType()); } return new RuledArgument(rules, values); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java index 16bda75f0f..e7e808be8a 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java @@ -18,25 +18,13 @@ package org.jackhuang.hmcl.game; import com.google.gson.JsonParseException; -import org.jackhuang.hmcl.util.Constants; -import org.jackhuang.hmcl.util.Immutable; -import org.jackhuang.hmcl.util.Lang; -import org.jackhuang.hmcl.util.StringUtils; -import org.jackhuang.hmcl.util.ToStringBuilder; +import org.jackhuang.hmcl.util.*; import org.jackhuang.hmcl.util.gson.JsonMap; import org.jackhuang.hmcl.util.gson.Validation; import org.jetbrains.annotations.Nullable; import java.time.Instant; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; import static org.jackhuang.hmcl.util.logging.Logger.LOG; @@ -229,7 +217,42 @@ public DownloadInfo getDownloadInfo() { public AssetIndexInfo getAssetIndex() { String assetsId = assets == null ? "legacy" : assets; - return assetIndex == null ? new AssetIndexInfo(assetsId, Constants.DEFAULT_INDEX_URL + assetsId + ".json") : assetIndex; + + if (assetIndex == null) { + String hash; + switch (assetsId) { + case "1.8": + hash = "f6ad102bcaa53b1a58358f16e376d548d44933ec"; + break; + case "14w31a": + hash = "10a2a0e75b03cfb5a7196abbdf43b54f7fa61deb"; + break; + case "14w25a": + hash = "32ff354a3be1c4dd83027111e6d79ee4d701d2c0"; + break; + case "1.7.4": + hash = "545510a60f526b9aa8a38f9c0bc7a74235d21675"; + break; + case "1.7.10": + hash = "1863782e33ce7b584fc45b037325a1964e095d3e"; + break; + case "1.7.3": + hash = "f6cf726f4747128d13887010c2cbc44ba83504d9"; + break; + case "pre-1.6": + hash = "3d8e55480977e32acd9844e545177e69a52f594b"; + break; + case "legacy": + default: + assetsId = "legacy"; + hash = "770572e819335b6c0a053f8378ad88eda189fc14"; + } + + String url = Constants.DEFAULT_INDEX_URL + hash + "/" + assetsId + ".json"; + return new AssetIndexInfo(assetsId, url); + } else { + return assetIndex; + } } public boolean appliesToCurrentEnvironment() { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/java/JavaInfo.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/java/JavaInfo.java index 160a152bb7..d23f5a46a9 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/java/JavaInfo.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/java/JavaInfo.java @@ -126,9 +126,12 @@ public static String normalizeVendor(String vendor) { return "Azul"; case "IBM Corporation": case "International Business Machines Corporation": + case "Eclipse OpenJ9": return "IBM"; case "Eclipse Adoptium": return "Adoptium"; + case "Amazon.com Inc.": + return "Amazon"; default: return vendor; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java index 6bb0494739..1f4c13043a 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java @@ -496,6 +496,13 @@ private Map getEnvVars() { break; case ZINK: env.put("MESA_LOADER_DRIVER_OVERRIDE", "zink"); + /** + * The amdgpu DDX is missing support for modifiers, causing Zink to fail. + * Disable DRI3 to workaround this issue. + * + * Link: https://gitlab.freedesktop.org/mesa/mesa/-/issues/10093 + */ + env.put("LIBGL_KOPPER_DRI2", "1"); break; } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModpackConfiguration.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModpackConfiguration.java index e936db80d7..743f930454 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModpackConfiguration.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModpackConfiguration.java @@ -18,6 +18,7 @@ package org.jackhuang.hmcl.mod; import com.google.gson.JsonParseException; +import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.util.Immutable; import org.jackhuang.hmcl.util.gson.Validation; import org.jetbrains.annotations.Nullable; @@ -29,6 +30,11 @@ @Immutable public final class ModpackConfiguration implements Validation { + @SuppressWarnings("unchecked") + public static TypeToken> typeOf(Class clazz) { + return (TypeToken>) TypeToken.getParameterized(ModpackConfiguration.class, clazz); + } + private final T manifest; private final String type; private final String name; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java index 5bda19051e..3913ef7f0e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java @@ -38,6 +38,7 @@ import static org.jackhuang.hmcl.util.Lang.mapOf; import static org.jackhuang.hmcl.util.Pair.pair; +import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf; public final class CurseForgeRemoteModRepository implements RemoteModRepository { @@ -113,8 +114,7 @@ public SearchResult search(String gameVersion, @Nullable RemoteModRepository.Cat pair("index", Integer.toString(pageOffset * pageSize)), pair("pageSize", Integer.toString(pageSize))) .header("X-API-KEY", apiKey) - .getJson(new TypeToken>>() { - }.getType()); + .getJson(Response.typeOf(listTypeOf(CurseAddon.class))); if (searchFilter.isEmpty()) { return new SearchResult(response.getData().stream().map(CurseAddon::toMod), calculateTotalPages(response, pageSize)); } @@ -163,8 +163,7 @@ public Optional getRemoteVersionByLocalFile(LocalModFile loca Response response = HttpRequest.POST(PREFIX + "/v1/fingerprints/432") .json(mapOf(pair("fingerprints", Collections.singletonList(hash)))) .header("X-API-KEY", apiKey) - .getJson(new TypeToken>() { - }.getType()); + .getJson(Response.typeOf(FingerprintMatchesResult.class)); if (response.getData().getExactMatches() == null || response.getData().getExactMatches().isEmpty()) { return Optional.empty(); @@ -177,8 +176,7 @@ public Optional getRemoteVersionByLocalFile(LocalModFile loca public RemoteMod getModById(String id) throws IOException { Response response = HttpRequest.GET(PREFIX + "/v1/mods/" + id) .header("X-API-KEY", apiKey) - .getJson(new TypeToken>() { - }.getType()); + .getJson(Response.typeOf(CurseAddon.class)); return response.data.toMod(); } @@ -186,8 +184,7 @@ public RemoteMod getModById(String id) throws IOException { public RemoteMod.File getModFile(String modId, String fileId) throws IOException { Response response = HttpRequest.GET(String.format("%s/v1/mods/%s/files/%s", PREFIX, modId, fileId)) .header("X-API-KEY", apiKey) - .getJson(new TypeToken>() { - }.getType()); + .getJson(Response.typeOf(CurseAddon.LatestFile.class)); return response.getData().toVersion().getFile(); } @@ -196,16 +193,14 @@ public Stream getRemoteVersionsById(String id) throws IOExcep Response> response = HttpRequest.GET(PREFIX + "/v1/mods/" + id + "/files", pair("pageSize", "10000")) .header("X-API-KEY", apiKey) - .getJson(new TypeToken>>() { - }.getType()); + .getJson(Response.typeOf(listTypeOf(CurseAddon.LatestFile.class))); return response.getData().stream().map(CurseAddon.LatestFile::toVersion); } public List getCategoriesImpl() throws IOException { Response> categories = HttpRequest.GET(PREFIX + "/v1/categories", pair("gameId", "432")) .header("X-API-KEY", apiKey) - .getJson(new TypeToken>>() { - }.getType()); + .getJson(Response.typeOf(listTypeOf(CurseAddon.Category.class))); return reorganizeCategories(categories.getData(), section); } @@ -284,6 +279,17 @@ public int getTotalCount() { } public static class Response { + + @SuppressWarnings("unchecked") + public static TypeToken> typeOf(Class responseType) { + return (TypeToken>) TypeToken.getParameterized(Response.class, responseType); + } + + @SuppressWarnings("unchecked") + public static TypeToken> typeOf(TypeToken responseType) { + return (TypeToken>) TypeToken.getParameterized(Response.class, responseType.getType()); + } + private final T data; private final Pagination pagination; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseInstallTask.java index 6796cf3308..b40618c689 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseInstallTask.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.mod.curse; import com.google.gson.JsonParseException; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.GameBuilder; import org.jackhuang.hmcl.game.DefaultGameRepository; @@ -99,8 +98,7 @@ public CurseInstallTask(DefaultDependencyManager dependencyManager, File zipFile ModpackConfiguration config = null; try { if (json.exists()) { - config = JsonUtils.GSON.fromJson(FileUtils.readText(json), new TypeToken>() { - }.getType()); + config = JsonUtils.GSON.fromJson(FileUtils.readText(json), ModpackConfiguration.typeOf(CurseManifest.class)); if (!CurseModpackProvider.INSTANCE.getName().equals(config.getType())) throw new IllegalArgumentException("Version " + name + " is not a Curse modpack. Cannot update this version."); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackCompletionTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackCompletionTask.java index 93be7eb009..1df44c1ea7 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackCompletionTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackCompletionTask.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.mod.mcbbs; import com.google.gson.JsonParseException; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.game.DefaultGameRepository; import org.jackhuang.hmcl.mod.ModManager; @@ -88,8 +87,7 @@ public CompletableFuture getFuture(TaskCompletableFuture executor) { if (configuration == null) { // Load configuration from disk try { - configuration = JsonUtils.fromNonNullJson(FileUtils.readText(configurationFile), new TypeToken>() { - }.getType()); + configuration = JsonUtils.fromNonNullJson(FileUtils.readText(configurationFile), ModpackConfiguration.typeOf(McbbsModpackManifest.class)); } catch (IOException | JsonParseException e) { throw new IOException("Malformed modpack configuration"); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackLocalInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackLocalInstallTask.java index 3cee33c603..7bd81ae526 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackLocalInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackLocalInstallTask.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.mod.mcbbs; import com.google.gson.JsonParseException; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.GameBuilder; import org.jackhuang.hmcl.game.DefaultGameRepository; @@ -80,8 +79,7 @@ public McbbsModpackLocalInstallTask(DefaultDependencyManager dependencyManager, ModpackConfiguration config = null; try { if (json.exists()) { - config = JsonUtils.GSON.fromJson(FileUtils.readText(json), new TypeToken>() { - }.getType()); + config = JsonUtils.GSON.fromJson(FileUtils.readText(json), ModpackConfiguration.typeOf(McbbsModpackManifest.class)); if (!McbbsModpackProvider.INSTANCE.getName().equals(config.getType())) throw new IllegalArgumentException("Version " + name + " is not a Mcbbs modpack. Cannot update this version."); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackProvider.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackProvider.java index 2deaf72333..6a7ab07281 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackProvider.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackProvider.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.mod.mcbbs; import com.google.gson.JsonParseException; -import com.google.gson.reflect.TypeToken; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipFile; import org.jackhuang.hmcl.download.DefaultDependencyManager; @@ -56,8 +55,7 @@ public Task createUpdateTask(DefaultDependencyManager dependencyManager, Stri @Override public void injectLaunchOptions(String modpackConfigurationJson, LaunchOptions.Builder builder) { - ModpackConfiguration config = JsonUtils.GSON.fromJson(modpackConfigurationJson, new TypeToken>() { - }.getType()); + ModpackConfiguration config = JsonUtils.GSON.fromJson(modpackConfigurationJson, ModpackConfiguration.typeOf(McbbsModpackManifest.class)); if (!getName().equals(config.getType())) { throw new IllegalArgumentException("Incorrect manifest type, actual=" + config.getType() + ", expected=" + getName()); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackRemoteInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackRemoteInstallTask.java index 3e970980e6..4550273d8e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackRemoteInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackRemoteInstallTask.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.mod.mcbbs; import com.google.gson.JsonParseException; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.GameBuilder; import org.jackhuang.hmcl.game.DefaultGameRepository; @@ -66,8 +65,7 @@ public McbbsModpackRemoteInstallTask(DefaultDependencyManager dependencyManager, ModpackConfiguration config = null; try { if (json.exists()) { - config = JsonUtils.GSON.fromJson(FileUtils.readText(json), new TypeToken>() { - }.getType()); + config = JsonUtils.GSON.fromJson(FileUtils.readText(json), ModpackConfiguration.typeOf(McbbsModpackManifest.class)); if (!MODPACK_TYPE.equals(config.getType())) throw new IllegalArgumentException("Version " + name + " is not a Mcbbs modpack. Cannot update this version."); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeNewModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeNewModMetadata.java index 4a94eff56f..ec4efb22ed 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeNewModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeNewModMetadata.java @@ -160,12 +160,17 @@ private static LocalModFile fromFile0(String tomlPath, int loaderACC, ModLoaderT private static ModLoaderType analyzeLoader(Toml toml, String modID, int loaderACC, ModLoaderType defaultLoader) throws IOException { List> dependencies = toml.getList("dependencies." + modID); - if (dependencies != null) { - for (HashMap dependency : dependencies) { - switch ((String) dependency.get("modId")) { - case "forge": return checkLoaderACC(loaderACC, ACC_FORGE, ModLoaderType.FORGE); - case "neoforge": return checkLoaderACC(loaderACC, ACC_NEO_FORGED, ModLoaderType.NEO_FORGED); - } + if (dependencies == null) { + dependencies = toml.getList("dependencies"); // ??? I have no idea why some of the Forge mods use [[dependencies]] + if (dependencies == null) { + return defaultLoader; + } + } + + for (HashMap dependency : dependencies) { + switch ((String) dependency.get("modId")) { + case "forge": return checkLoaderACC(loaderACC, ACC_FORGE, ModLoaderType.FORGE); + case "neoforge": return checkLoaderACC(loaderACC, ACC_NEO_FORGED, ModLoaderType.NEO_FORGED); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeOldModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeOldModMetadata.java index 067c5b8c25..78b673ece9 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeOldModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeOldModMetadata.java @@ -19,7 +19,6 @@ import com.google.gson.JsonParseException; import com.google.gson.annotations.SerializedName; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.mod.LocalModFile; import org.jackhuang.hmcl.mod.ModLoaderType; import org.jackhuang.hmcl.mod.ModManager; @@ -34,6 +33,8 @@ import java.nio.file.Path; import java.util.List; +import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf; + /** * * @author huangyuhui @@ -125,9 +126,7 @@ public static LocalModFile fromFile(ModManager modManager, Path modFile, FileSys Path mcmod = fs.getPath("mcmod.info"); if (Files.notExists(mcmod)) throw new IOException("File " + modFile + " is not a Forge mod."); - List modList = JsonUtils.GSON.fromJson(FileUtils.readText(mcmod), - new TypeToken>() { - }.getType()); + List modList = JsonUtils.GSON.fromJson(FileUtils.readText(mcmod), listTypeOf(ForgeOldModMetadata.class)); if (modList == null || modList.isEmpty()) throw new IOException("Mod " + modFile + " `mcmod.info` is malformed.."); ForgeOldModMetadata metadata = modList.get(0); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthCompletionTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthCompletionTask.java index 3150a34578..c0fa257f64 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthCompletionTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthCompletionTask.java @@ -19,6 +19,7 @@ import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.game.DefaultGameRepository; +import org.jackhuang.hmcl.mod.ModManager; import org.jackhuang.hmcl.mod.ModpackCompletionException; import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.task.Task; @@ -27,6 +28,7 @@ import java.io.File; import java.io.FileNotFoundException; +import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; @@ -41,6 +43,7 @@ public class ModrinthCompletionTask extends Task { private final DefaultDependencyManager dependency; private final DefaultGameRepository repository; + private final ModManager modManager; private final String version; private ModrinthManifest manifest; private final List> dependencies = new ArrayList<>(); @@ -69,6 +72,7 @@ public ModrinthCompletionTask(DefaultDependencyManager dependencyManager, String public ModrinthCompletionTask(DefaultDependencyManager dependencyManager, String version, ModrinthManifest manifest) { this.dependency = dependencyManager; this.repository = dependencyManager.getGameRepository(); + this.modManager = repository.getModManager(version); this.version = version; this.manifest = manifest; @@ -99,18 +103,28 @@ public void execute() throws Exception { if (manifest == null) return; - Path runDirectory = repository.getRunDirectory(version).toPath(); + Path runDirectory = repository.getRunDirectory(version).toPath().toAbsolutePath().normalize(); + Path modsDirectory = runDirectory.resolve("mods"); for (ModrinthManifest.File file : manifest.getFiles()) { if (file.getEnv() != null && file.getEnv().getOrDefault("client", "required").equals("unsupported")) continue; - Path filePath = runDirectory.resolve(file.getPath()); - if (!Files.exists(filePath) && !file.getDownloads().isEmpty()) { - FileDownloadTask task = new FileDownloadTask(file.getDownloads().get(0), filePath.toFile()); - task.setCacheRepository(dependency.getCacheRepository()); - task.setCaching(true); - dependencies.add(task.withCounter("hmcl.modpack.download")); - } + if (file.getDownloads().isEmpty()) + continue; + + Path filePath = runDirectory.resolve(file.getPath()).toAbsolutePath().normalize(); + if (!filePath.startsWith(runDirectory)) + throw new IOException("Unsecure path: " + file.getPath()); + + if (Files.exists(filePath)) + continue; + if (modsDirectory.equals(filePath.getParent()) && this.modManager.hasSimpleMod(FileUtils.getName(filePath))) + continue; + + FileDownloadTask task = new FileDownloadTask(file.getDownloads(), filePath.toFile()); + task.setCacheRepository(dependency.getCacheRepository()); + task.setCaching(true); + dependencies.add(task.withCounter("hmcl.modpack.download")); } if (!dependencies.isEmpty()) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthInstallTask.java index 64ccc7c2f6..b796413d4b 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthInstallTask.java @@ -18,12 +18,10 @@ package org.jackhuang.hmcl.mod.modrinth; import com.google.gson.JsonParseException; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.GameBuilder; import org.jackhuang.hmcl.game.DefaultGameRepository; import org.jackhuang.hmcl.mod.*; -import org.jackhuang.hmcl.mod.curse.CurseManifest; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.io.FileUtils; @@ -95,8 +93,7 @@ public ModrinthInstallTask(DefaultDependencyManager dependencyManager, File zipF ModpackConfiguration config = null; try { if (json.exists()) { - config = JsonUtils.GSON.fromJson(FileUtils.readText(json), new TypeToken>() { - }.getType()); + config = JsonUtils.GSON.fromJson(FileUtils.readText(json), ModpackConfiguration.typeOf(ModrinthManifest.class)); if (!ModrinthModpackProvider.INSTANCE.getName().equals(config.getType())) throw new IllegalArgumentException("Version " + name + " is not a Modrinth modpack. Cannot update this version."); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java index d20a542ac9..c257a38816 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java @@ -39,6 +39,7 @@ import static org.jackhuang.hmcl.util.Lang.mapOf; import static org.jackhuang.hmcl.util.Pair.pair; +import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf; public final class ModrinthRemoteModRepository implements RemoteModRepository { public static final ModrinthRemoteModRepository MODS = new ModrinthRemoteModRepository("mod"); @@ -93,8 +94,7 @@ public SearchResult search(String gameVersion, @Nullable RemoteModRepository.Cat pair("index", convertSortType(sort)) ); Response response = HttpRequest.GET(NetworkUtils.withQuery(PREFIX + "/v2/search", query)) - .getJson(new TypeToken>() { - }.getType()); + .getJson(Response.typeOf(ProjectSearchResult.class)); return new SearchResult(response.getHits().stream().map(ProjectSearchResult::toMod), (int) Math.ceil((double) response.totalHits / pageSize)); } @@ -132,13 +132,12 @@ public RemoteMod.File getModFile(String modId, String fileId) throws IOException public Stream getRemoteVersionsById(String id) throws IOException { id = StringUtils.removePrefix(id, "local-"); List versions = HttpRequest.GET(PREFIX + "/v2/project/" + id + "/version") - .getJson(new TypeToken>() { - }.getType()); + .getJson(listTypeOf(ProjectVersion.class)); return versions.stream().map(ProjectVersion::toVersion).flatMap(Lang::toStream); } public List getCategoriesImpl() throws IOException { - List categories = HttpRequest.GET(PREFIX + "/v2/tag/category").getJson(new TypeToken>() {}.getType()); + List categories = HttpRequest.GET(PREFIX + "/v2/tag/category").getJson(listTypeOf(Category.class)); return categories.stream().filter(category -> category.getProjectType().equals(projectType)).collect(Collectors.toList()); } @@ -696,6 +695,12 @@ public RemoteMod toMod() { } public static class Response { + + @SuppressWarnings("unchecked") + public static TypeToken> typeOf(Class responseType) { + return (TypeToken>) TypeToken.getParameterized(Response.class, responseType); + } + private final int offset; private final int limit; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstanceConfiguration.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstanceConfiguration.java index 4cbc228c61..bd61ca231b 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstanceConfiguration.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstanceConfiguration.java @@ -58,43 +58,63 @@ public final class MultiMCInstanceConfiguration implements ModpackManifest { private final boolean overrideConsole; // OverrideConsole private final boolean overrideCommands; // OverrideCommands private final boolean overrideWindow; // OverrideWindow + private final String iconKey; private final MultiMCManifest mmcPack; MultiMCInstanceConfiguration(String defaultName, InputStream contentStream, MultiMCManifest mmcPack) throws IOException { + // instance.cfg is in .ini format. As it's like the .properties format, we use Properties to parse thie file. + // Maybe this will provide some problems (and it does, such as https://github.com/HMCL-dev/HMCL/issues/2991), but this workaround works, doesn't it? Properties p = new Properties(); p.load(new InputStreamReader(contentStream, StandardCharsets.UTF_8)); this.mmcPack = mmcPack; - instanceType = p.getProperty("InstanceType"); - autoCloseConsole = Boolean.parseBoolean(p.getProperty("AutoCloseConsole")); + instanceType = readValue(p, "InstanceType"); + autoCloseConsole = Boolean.parseBoolean(readValue(p, "AutoCloseConsole")); gameVersion = mmcPack != null ? mmcPack.getComponents().stream().filter(e -> "net.minecraft".equals(e.getUid())).findAny() - .orElseThrow(() -> new IOException("Malformed mmc-pack.json")).getVersion() : p.getProperty("IntendedVersion"); - javaPath = p.getProperty("JavaPath"); - jvmArgs = p.getProperty("JvmArgs"); - fullscreen = Boolean.parseBoolean(p.getProperty("LaunchMaximized")); - maxMemory = Lang.toIntOrNull(p.getProperty("MaxMemAlloc")); - minMemory = Lang.toIntOrNull(p.getProperty("MinMemAlloc")); - height = Lang.toIntOrNull(p.getProperty("MinecraftWinHeight")); - width = Lang.toIntOrNull(p.getProperty("MinecraftWinWidth")); - overrideCommands = Boolean.parseBoolean(p.getProperty("OverrideCommands")); - overrideConsole = Boolean.parseBoolean(p.getProperty("OverrideConsole")); - overrideJavaArgs = Boolean.parseBoolean(p.getProperty("OverrideJavaArgs")); - overrideJavaLocation = Boolean.parseBoolean(p.getProperty("OverrideJavaLocation")); - overrideMemory = Boolean.parseBoolean(p.getProperty("OverrideMemory")); - overrideWindow = Boolean.parseBoolean(p.getProperty("OverrideWindow")); - permGen = Lang.toIntOrNull(p.getProperty("PermGen")); - postExitCommand = p.getProperty("PostExitCommand"); - preLaunchCommand = p.getProperty("PreLaunchCommand"); - showConsole = Boolean.parseBoolean(p.getProperty("ShowConsole")); - showConsoleOnError = Boolean.parseBoolean(p.getProperty("ShowConsoleOnError")); - wrapperCommand = p.getProperty("WrapperCommand"); + .orElseThrow(() -> new IOException("Malformed mmc-pack.json")).getVersion() : readValue(p, "IntendedVersion"); + javaPath = readValue(p, "JavaPath"); + jvmArgs = readValue(p, "JvmArgs"); + fullscreen = Boolean.parseBoolean(readValue(p, "LaunchMaximized")); + maxMemory = Lang.toIntOrNull(readValue(p, "MaxMemAlloc")); + minMemory = Lang.toIntOrNull(readValue(p, "MinMemAlloc")); + height = Lang.toIntOrNull(readValue(p, "MinecraftWinHeight")); + width = Lang.toIntOrNull(readValue(p, "MinecraftWinWidth")); + overrideCommands = Boolean.parseBoolean(readValue(p, "OverrideCommands")); + overrideConsole = Boolean.parseBoolean(readValue(p, "OverrideConsole")); + overrideJavaArgs = Boolean.parseBoolean(readValue(p, "OverrideJavaArgs")); + overrideJavaLocation = Boolean.parseBoolean(readValue(p, "OverrideJavaLocation")); + overrideMemory = Boolean.parseBoolean(readValue(p, "OverrideMemory")); + overrideWindow = Boolean.parseBoolean(readValue(p, "OverrideWindow")); + permGen = Lang.toIntOrNull(readValue(p, "PermGen")); + postExitCommand = readValue(p, "PostExitCommand"); + preLaunchCommand = readValue(p, "PreLaunchCommand"); + showConsole = Boolean.parseBoolean(readValue(p, "ShowConsole")); + showConsoleOnError = Boolean.parseBoolean(readValue(p, "ShowConsoleOnError")); + wrapperCommand = readValue(p, "WrapperCommand"); name = defaultName; - notes = Optional.ofNullable(p.getProperty("notes")).orElse(""); + notes = Optional.ofNullable(readValue(p, "notes")).orElse(""); + iconKey = readValue(p, "iconKey"); } - public MultiMCInstanceConfiguration(String instanceType, String name, String gameVersion, Integer permGen, String wrapperCommand, String preLaunchCommand, String postExitCommand, String notes, String javaPath, String jvmArgs, boolean fullscreen, Integer width, Integer height, Integer maxMemory, Integer minMemory, boolean showConsole, boolean showConsoleOnError, boolean autoCloseConsole, boolean overrideMemory, boolean overrideJavaLocation, boolean overrideJavaArgs, boolean overrideConsole, boolean overrideCommands, boolean overrideWindow) { + /** + * A workaround for ... + */ + private String readValue(Properties p, String key) { + String value = p.getProperty(key); + if (value == null) { + return null; + } + + int l = value.length(); + if (l >= 2 && value.charAt(0) == '"' && value.charAt(l - 1) == ':') { + return value.substring(0, l - 1); + } + return value; + } + + public MultiMCInstanceConfiguration(String instanceType, String name, String gameVersion, Integer permGen, String wrapperCommand, String preLaunchCommand, String postExitCommand, String notes, String javaPath, String jvmArgs, boolean fullscreen, Integer width, Integer height, Integer maxMemory, Integer minMemory, boolean showConsole, boolean showConsoleOnError, boolean autoCloseConsole, boolean overrideMemory, boolean overrideJavaLocation, boolean overrideJavaArgs, boolean overrideConsole, boolean overrideCommands, boolean overrideWindow, String iconKey) { this.instanceType = instanceType; this.name = name; this.gameVersion = gameVersion; @@ -120,6 +140,7 @@ public MultiMCInstanceConfiguration(String instanceType, String name, String gam this.overrideCommands = overrideCommands; this.overrideWindow = overrideWindow; this.mmcPack = null; + this.iconKey = iconKey; } public String getInstanceType() { @@ -292,6 +313,10 @@ public boolean isOverrideWindow() { return overrideWindow; } + public String getIconKey() { + return iconKey; + } + public Properties toProperties() { Properties p = new Properties(); if (instanceType != null) p.setProperty("InstanceType", instanceType); @@ -318,6 +343,7 @@ public Properties toProperties() { if (wrapperCommand != null) p.setProperty("WrapperCommand", wrapperCommand); if (name != null) p.setProperty("name", name); if (notes != null) p.setProperty("notes", notes); + if (iconKey != null) p.setProperty("iconKey", iconKey); return p; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java index 017f722445..900f3a01a9 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.mod.multimc; import com.google.gson.JsonParseException; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.GameBuilder; import org.jackhuang.hmcl.game.Arguments; @@ -128,8 +127,7 @@ public void preExecute() throws Exception { ModpackConfiguration config = null; try { if (json.exists()) { - config = JsonUtils.GSON.fromJson(FileUtils.readText(json), new TypeToken>() { - }.getType()); + config = JsonUtils.GSON.fromJson(FileUtils.readText(json), ModpackConfiguration.typeOf(MultiMCInstanceConfiguration.class)); if (!MultiMCModpackProvider.INSTANCE.getName().equals(config.getType())) throw new IllegalArgumentException("Version " + name + " is not a MultiMC modpack. Cannot update this version."); @@ -201,6 +199,14 @@ public void execute() throws Exception { Path jarmods = root.resolve("jarmods"); if (Files.exists(jarmods)) FileUtils.copyDirectory(jarmods, repository.getVersionRoot(name).toPath().resolve("jarmods")); + + String iconKey = this.manifest.getIconKey(); + if (iconKey != null) { + Path iconFile = root.resolve(iconKey + ".png"); + if (Files.exists(iconFile)) { + FileUtils.copyFile(iconFile, repository.getVersionRoot(name).toPath().resolve("icon.png")); + } + } } dependencies.add(repository.saveAsync(version)); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackCompletionTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackCompletionTask.java index 9f7ddfb95c..3130f24e0e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackCompletionTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackCompletionTask.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.mod.server; import com.google.gson.JsonParseException; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.GameBuilder; import org.jackhuang.hmcl.game.DefaultGameRepository; @@ -66,8 +65,7 @@ public ServerModpackCompletionTask(DefaultDependencyManager dependencyManager, S try { File manifestFile = repository.getModpackConfiguration(version); if (manifestFile.exists()) { - this.manifest = JsonUtils.GSON.fromJson(FileUtils.readText(manifestFile), new TypeToken>() { - }.getType()); + this.manifest = JsonUtils.GSON.fromJson(FileUtils.readText(manifestFile), ModpackConfiguration.typeOf(ServerModpackManifest.class)); } } catch (Exception e) { LOG.warning("Unable to read Server modpack manifest.json", e); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackLocalInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackLocalInstallTask.java index b23753f304..fec92572d3 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackLocalInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackLocalInstallTask.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.mod.server; import com.google.gson.JsonParseException; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.GameBuilder; import org.jackhuang.hmcl.game.DefaultGameRepository; @@ -72,8 +71,7 @@ public ServerModpackLocalInstallTask(DefaultDependencyManager dependencyManager, ModpackConfiguration config = null; try { if (json.exists()) { - config = JsonUtils.GSON.fromJson(FileUtils.readText(json), new TypeToken>() { - }.getType()); + config = JsonUtils.GSON.fromJson(FileUtils.readText(json), ModpackConfiguration.typeOf(ServerModpackManifest.class)); if (!ServerModpackProvider.INSTANCE.getName().equals(config.getType())) throw new IllegalArgumentException("Version " + name + " is not a Server modpack. Cannot update this version."); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackRemoteInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackRemoteInstallTask.java index eddafb81ae..e6bb9b5565 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackRemoteInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackRemoteInstallTask.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.mod.server; import com.google.gson.JsonParseException; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.GameBuilder; import org.jackhuang.hmcl.game.DefaultGameRepository; @@ -63,11 +62,10 @@ public ServerModpackRemoteInstallTask(DefaultDependencyManager dependencyManager repository.removeVersionFromDisk(name); }); - ModpackConfiguration config = null; + ModpackConfiguration config; try { if (json.exists()) { - config = JsonUtils.GSON.fromJson(FileUtils.readText(json), new TypeToken>() { - }.getType()); + config = JsonUtils.GSON.fromJson(FileUtils.readText(json), ModpackConfiguration.typeOf(ServerModpackManifest.class)); if (!MODPACK_TYPE.equals(config.getType())) throw new IllegalArgumentException("Version " + name + " is not a Server modpack. Cannot update this version."); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/CacheRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/CacheRepository.java index 89082d4cc5..223ff4d631 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/CacheRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/CacheRepository.java @@ -19,9 +19,7 @@ import com.google.gson.JsonParseException; import com.google.gson.annotations.SerializedName; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.util.function.ExceptionalSupplier; -import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.io.IOUtils; @@ -46,6 +44,7 @@ import java.util.stream.Stream; import static java.nio.charset.StandardCharsets.UTF_8; +import static org.jackhuang.hmcl.util.gson.JsonUtils.*; import static org.jackhuang.hmcl.util.logging.Logger.LOG; public class CacheRepository { @@ -68,7 +67,7 @@ public void changeDirectory(Path commonDir) { } if (Files.isRegularFile(indexFile)) { - ETagIndex raw = JsonUtils.GSON.fromJson(FileUtils.readText(indexFile), ETagIndex.class); + ETagIndex raw = GSON.fromJson(FileUtils.readText(indexFile), ETagIndex.class); if (raw == null) index = new HashMap<>(); else @@ -289,10 +288,10 @@ public void saveETagIndex() throws IOException { try (FileChannel channel = FileChannel.open(indexFile, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE)) { FileLock lock = channel.lock(); try { - ETagIndex indexOnDisk = JsonUtils.fromMaybeMalformedJson(new String(IOUtils.readFullyWithoutClosing(Channels.newInputStream(channel)), UTF_8), ETagIndex.class); + ETagIndex indexOnDisk = fromMaybeMalformedJson(new String(IOUtils.readFullyWithoutClosing(Channels.newInputStream(channel)), UTF_8), ETagIndex.class); Map newIndex = joinETagIndexes(indexOnDisk == null ? null : indexOnDisk.eTag, index.values()); channel.truncate(0); - ByteBuffer writeTo = ByteBuffer.wrap(JsonUtils.GSON.toJson(new ETagIndex(newIndex.values())).getBytes(UTF_8)); + ByteBuffer writeTo = ByteBuffer.wrap(GSON.toJson(new ETagIndex(newIndex.values())).getBytes(UTF_8)); while (writeTo.hasRemaining()) { if (channel.write(writeTo) == 0) { throw new IOException("No value is written"); @@ -412,8 +411,7 @@ private void changeDirectory(Path cacheDirectory) { try { indexFile = cacheDirectory.resolve(name + ".json"); if (Files.isRegularFile(indexFile)) { - joinEntries(JsonUtils.fromNonNullJson(FileUtils.readText(indexFile), new TypeToken>() { - }.getType())); + joinEntries(fromNonNullJson(FileUtils.readText(indexFile), mapTypeOf(String.class, Object.class))); } } catch (IOException | JsonParseException e) { LOG.warning("Unable to read storage {" + name + "} file"); @@ -426,12 +424,11 @@ public void saveToFile() { try (FileChannel channel = FileChannel.open(indexFile, StandardOpenOption.READ, StandardOpenOption.WRITE)) { FileLock lock = channel.lock(); try { - Map indexOnDisk = JsonUtils.fromMaybeMalformedJson(new String(IOUtils.readFullyWithoutClosing(Channels.newInputStream(channel)), UTF_8), new TypeToken>() { - }.getType()); + Map indexOnDisk = fromMaybeMalformedJson(new String(IOUtils.readFullyWithoutClosing(Channels.newInputStream(channel)), UTF_8), mapTypeOf(String.class, Object.class)); if (indexOnDisk == null) indexOnDisk = new HashMap<>(); indexOnDisk.putAll(storage); channel.truncate(0); - channel.write(ByteBuffer.wrap(JsonUtils.GSON.toJson(storage).getBytes(UTF_8))); + channel.write(ByteBuffer.wrap(GSON.toJson(storage).getBytes(UTF_8))); this.storage = indexOnDisk; } finally { lock.release(); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Constants.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Constants.java index 55717ea90a..34da8d031d 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Constants.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Constants.java @@ -29,5 +29,5 @@ private Constants() { public static final String DEFAULT_LIBRARY_URL = "https://libraries.minecraft.net/"; public static final String DEFAULT_VERSION_DOWNLOAD_URL = "https://bmclapi2.bangbang93.com/versions/"; - public static final String DEFAULT_INDEX_URL = "https://launchermeta.mojang.com/mc-staging/assets/legacy/c0fd82e8ce9fbc93119e40d96d5a4e62cfa3f729/"; + public static final String DEFAULT_INDEX_URL = "https://launchermeta.mojang.com/v1/packages/"; } diff --git a/HMCL/src/main/resources/assets/css/custom.css b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/FXThread.java similarity index 55% rename from HMCL/src/main/resources/assets/css/custom.css rename to HMCLCore/src/main/java/org/jackhuang/hmcl/util/FXThread.java index 72c29e9aca..53974cb0a2 100644 --- a/HMCL/src/main/resources/assets/css/custom.css +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/FXThread.java @@ -1,6 +1,6 @@ -/** +/* * Hello Minecraft! Launcher - * Copyright (C) 2020 huangyuhui and contributors + * Copyright (C) 2024 huangyuhui 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 @@ -15,16 +15,18 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -.root { - -fx-base-color: %base-color%; - -fx-base-darker-color: derive(-fx-base-color, -10%); - -fx-base-check-color: derive(-fx-base-color, 30%); - -fx-rippler-color: rgba(%base-red%, %base-green%, %base-blue%, 0.3); - -fx-base-rippler-color: derive(%base-rippler-color%, 100%); - -fx-base-disabled-text-fill: %disabled-font-color%; - -fx-base-text-fill: %font-color%; +package org.jackhuang.hmcl.util; - -theme-thumb: rgba(%base-red%, %base-green%, %base-blue%, 0.7); +import java.lang.annotation.*; - %font% -} \ No newline at end of file +/** + * Mark a method that can only be called on the JavaFX Application Thread, + * or mark a field that can only be read/modified on the JavaFX Application Thread. + * + * @author Glavo + */ +@Documented +@Target({ElementType.METHOD, ElementType.FIELD}) +@Retention(RetentionPolicy.SOURCE) +public @interface FXThread { +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.java index b0be0988c0..85001ba8c0 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.java @@ -389,7 +389,7 @@ public static void forEachZipped(Iterable i1, Iterable i2, BiConsum public static synchronized Timer getTimer() { if (timer == null) { - timer = new Timer(); + timer = new Timer(true); } return timer; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/JsonUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/JsonUtils.java index bddf8d3f4a..bfd07080ac 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/JsonUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/JsonUtils.java @@ -27,14 +27,16 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; import java.time.Instant; +import java.util.List; +import java.util.Map; import java.util.UUID; /** * @author yushijinhun */ +@SuppressWarnings("unchecked") public final class JsonUtils { public static final Gson GSON = defaultGsonBuilder().create(); @@ -48,13 +50,29 @@ public final class JsonUtils { private JsonUtils() { } + public static TypeToken> listTypeOf(Class elementType) { + return (TypeToken>) TypeToken.getParameterized(List.class, elementType); + } + + public static TypeToken> listTypeOf(TypeToken elementType) { + return (TypeToken>) TypeToken.getParameterized(List.class, elementType.getType()); + } + + public static TypeToken> mapTypeOf(Class keyType, Class valueType) { + return (TypeToken>) TypeToken.getParameterized(Map.class, keyType, valueType); + } + + public static TypeToken> mapTypeOf(Class keyType, TypeToken valueType) { + return (TypeToken>) TypeToken.getParameterized(Map.class, keyType, valueType.getType()); + } + public static T fromJsonFully(InputStream json, Class classOfT) throws IOException, JsonParseException { try (InputStreamReader reader = new InputStreamReader(json, StandardCharsets.UTF_8)) { return GSON.fromJson(reader, classOfT); } } - public static T fromJsonFully(InputStream json, Type type) throws IOException, JsonParseException { + public static T fromJsonFully(InputStream json, TypeToken type) throws IOException, JsonParseException { try (InputStreamReader reader = new InputStreamReader(json, StandardCharsets.UTF_8)) { return GSON.fromJson(reader, type); } @@ -67,13 +85,6 @@ public static T fromNonNullJson(String json, Class classOfT) throws JsonP return parsed; } - public static T fromNonNullJson(String json, Type type) throws JsonParseException { - T parsed = GSON.fromJson(json, type); - if (parsed == null) - throw new JsonParseException("Json object cannot be null."); - return parsed; - } - public static T fromNonNullJson(String json, TypeToken type) throws JsonParseException { T parsed = GSON.fromJson(json, type); if (parsed == null) @@ -90,7 +101,7 @@ public static T fromNonNullJsonFully(InputStream json, Class classOfT) th } } - public static T fromNonNullJsonFully(InputStream json, Type type) throws IOException, JsonParseException { + public static T fromNonNullJsonFully(InputStream json, TypeToken type) throws IOException, JsonParseException { try (InputStreamReader reader = new InputStreamReader(json, StandardCharsets.UTF_8)) { T parsed = GSON.fromJson(reader, type); if (parsed == null) @@ -107,7 +118,7 @@ public static T fromMaybeMalformedJson(String json, Class classOfT) throw } } - public static T fromMaybeMalformedJson(String json, Type type) throws JsonParseException { + public static T fromMaybeMalformedJson(String json, TypeToken type) throws JsonParseException { try { return GSON.fromJson(json, type); } catch (JsonSyntaxException e) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/DirectoryStructurePrinter.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/DirectoryStructurePrinter.java new file mode 100644 index 0000000000..a07ba372b2 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/DirectoryStructurePrinter.java @@ -0,0 +1,99 @@ +package org.jackhuang.hmcl.util.io; + +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.FileVisitor; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; + +final class DirectoryStructurePrinter { + private DirectoryStructurePrinter() { + } + + public static String list(Path path, int maxDepth) throws IOException { + StringBuilder output = new StringBuilder(128); + list(path, maxDepth, output); + output.setLength(output.length() - 1); + return output.toString(); + } + + private static void list(Path path, int maxDepth, StringBuilder output) throws IOException { + output.append("Filesystem structure of: ").append(path).append('\n'); + + if (!Files.exists(path)) { + pushMessage(output, "nonexistent path", 1); + return; + } + if (Files.isRegularFile(path)) { + pushMessage(output, "regular file path", 1); + return; + } + if (Files.isDirectory(path)) { + Files.walkFileTree(path, new FileVisitor() { + private boolean isFolderEmpty; + + private int depth = 1; + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { + isFolderEmpty = true; + + pushFile(output, dir, depth); + if (depth == maxDepth) { + pushMessage(output, "too deep", depth); + return FileVisitResult.SKIP_SUBTREE; + } + + depth++; + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { + isFolderEmpty = false; + + pushFile(output, file, depth); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed(Path file, IOException exc) { + visitFile(file, null); + + pushMessage(output, exc.toString(), depth); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) { + if (isFolderEmpty) { + pushMessage(output, "empty directory", depth); + } + + depth--; + return FileVisitResult.CONTINUE; + } + }); + return; + } + + pushMessage(output, "unknown file type", 1); + } + + private static void pushFile(StringBuilder output, Path file, int depth) { + output.append("|"); + for (int i = 1; i < depth; i++) { + output.append(" |"); + } + output.append("-> ").append(FileUtils.getName(file)).append('\n'); + } + + private static void pushMessage(StringBuilder output, String message, int depth) { + output.append("| "); + for (int i = 1; i < depth; i++) { + output.append(" | "); + } + output.append('<').append(message).append(">\n"); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/FileUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/FileUtils.java index baa444003f..ba257a94cb 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/FileUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/FileUtils.java @@ -30,11 +30,7 @@ import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.Objects; -import java.util.Optional; +import java.util.*; import java.util.function.Predicate; import static java.nio.charset.StandardCharsets.UTF_8; @@ -199,7 +195,7 @@ public static void writeText(Path file, String text, Charset charset) throws IOE * It will create the file if it does not exist, or truncate the existing file to empty for rewriting. * All bytes in byte array will be written into the file in binary format. Existing data will be erased. * - * @param file the path to the file + * @param file the path to the file * @param data the data being written to file * @throws IOException if an I/O error occurs */ @@ -212,7 +208,7 @@ public static void writeBytes(File file, byte[] data) throws IOException { * It will create the file if it does not exist, or truncate the existing file to empty for rewriting. * All bytes in byte array will be written into the file in binary format. Existing data will be erased. * - * @param file the path to the file + * @param file the path to the file * @param data the data being written to file * @throws IOException if an I/O error occurs */ @@ -591,4 +587,8 @@ public static void saveSafely(Path file, ExceptionalConsumer T getJson(Class typeOfT) throws IOException, JsonParseException { return JsonUtils.fromNonNullJson(getString(), typeOfT); } - public T getJson(Type type) throws IOException, JsonParseException { + public T getJson(TypeToken type) throws IOException, JsonParseException { return JsonUtils.fromNonNullJson(getString(), type); } @@ -109,7 +109,7 @@ public CompletableFuture getJsonAsync(Class typeOfT) { return getStringAsync().thenApplyAsync(jsonString -> JsonUtils.fromNonNullJson(jsonString, typeOfT)); } - public CompletableFuture getJsonAsync(Type type) { + public CompletableFuture getJsonAsync(TypeToken type) { return getStringAsync().thenApplyAsync(jsonString -> JsonUtils.fromNonNullJson(jsonString, type)); } diff --git a/HMCLCore/src/main/resources/assets/game/versions.txt b/HMCLCore/src/main/resources/assets/game/versions.txt index 82cef053a6..363dc0b470 100644 --- a/HMCLCore/src/main/resources/assets/game/versions.txt +++ b/HMCLCore/src/main/resources/assets/game/versions.txt @@ -767,4 +767,33 @@ 1.21-pre3 1.21-pre4 1.21-rc1 -1.21 \ No newline at end of file +1.21 +1.21.1-rc1 +1.21.1 +24w33a +24w34a +24w35a +24w36a +24w37a +24w38a +24w39a +24w40a +1.21.2-pre1 +1.21.2-pre2 +1.21.2-pre3 +1.21.2-pre4 +1.21.2-pre5 +1.21.2-rc1 +1.21.2-rc2 +1.21.2 +1.21.3 +24w44a +24w45a +24w46a +1.21.4-pre1 +1.21.4-pre2 +1.21.4-pre3 +1.21.4-rc1 +1.21.4-rc2 +1.21.4-rc3 +1.21.4 diff --git a/HMCLCore/src/test/java/org/jackhuang/hmcl/game/CrashReportAnalyzerTest.java b/HMCLCore/src/test/java/org/jackhuang/hmcl/game/CrashReportAnalyzerTest.java index 289573c969..a47f263050 100644 --- a/HMCLCore/src/test/java/org/jackhuang/hmcl/game/CrashReportAnalyzerTest.java +++ b/HMCLCore/src/test/java/org/jackhuang/hmcl/game/CrashReportAnalyzerTest.java @@ -140,7 +140,8 @@ public void forgeEroor() throws IOException { "\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_131]\n" + "\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_131]\n" + "\tat java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_131]\n" + - "\tat oolloo.jlw.Wrapper.invokeMain(Wrapper.java:58) [JavaWrapper.jar:?]\n").replaceAll("\\s+", ""), + "\tat oolloo.jlw.Wrapper.invokeMain(Wrapper.java:58) [JavaWrapper.jar:?]\n" + + "\tat oolloo.jlw.Wrapper.main(Wrapper.java:51) [JavaWrapper.jar:?]").replaceAll("\\s+", ""), result.getMatcher().group("reason").replaceAll("\\s+", "")); } diff --git a/HMCLCore/src/test/java/org/jackhuang/hmcl/util/gson/JsonUtilsTest.java b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/gson/JsonUtilsTest.java new file mode 100644 index 0000000000..a3f0aaeed6 --- /dev/null +++ b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/gson/JsonUtilsTest.java @@ -0,0 +1,42 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui 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 . + */ +package org.jackhuang.hmcl.util.gson; + +import com.google.gson.reflect.TypeToken; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf; +import static org.jackhuang.hmcl.util.gson.JsonUtils.mapTypeOf; +import static org.junit.jupiter.api.Assertions.*; + +/** + * @author Glavo + */ +public class JsonUtilsTest { + + @Test + public void testGetTypeToken() { + assertEquals(new TypeToken>(){}, listTypeOf(Object.class)); + assertEquals(new TypeToken>(){}, listTypeOf(String.class)); + assertEquals(new TypeToken>>(){}, listTypeOf(mapTypeOf(String.class, Integer.class))); + assertEquals(new TypeToken>>>(){}, listTypeOf(mapTypeOf(String.class, listTypeOf(Integer.class)))); + } +} diff --git a/HMCLauncher/CMakeLists.txt b/HMCLauncher/CMakeLists.txt index c7afc7f3ba..1edf65cf65 100644 --- a/HMCLauncher/CMakeLists.txt +++ b/HMCLauncher/CMakeLists.txt @@ -1,7 +1,8 @@ cmake_minimum_required(VERSION 3.25) project(HMCLauncher) if(MSVC) - add_compile_options(/utf-8 /D_UNICODE /W4) + add_definitions(-DUNICODE -D_UNICODE) + add_compile_options(/utf-8 /W4) add_link_options(/ENTRY:wWinMainCRTStartup) else() add_compile_options(-municode -Wall -Wextra -Wpedantic) diff --git a/HMCLauncher/HMCL/HMCL.rc b/HMCLauncher/HMCL/HMCL.rc index 71fee26e9e..ecc8c147c4 100644 --- a/HMCLauncher/HMCL/HMCL.rc +++ b/HMCLauncher/HMCL/HMCL.rc @@ -82,8 +82,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 3,4,0,0 - PRODUCTVERSION 3,4,0,0 + FILEVERSION 3,5,0,0 + PRODUCTVERSION 3,5,0,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L diff --git a/HMCLauncher/HMCL/java-download.ps1 b/HMCLauncher/HMCL/java-download.ps1 deleted file mode 100644 index e2f0c6abeb..0000000000 --- a/HMCLauncher/HMCL/java-download.ps1 +++ /dev/null @@ -1,225 +0,0 @@ -param( - [string]$JavaDir, - [string]$Arch -) - -$chinese = [System.Globalization.CultureInfo]::CurrentCulture.Name -eq 'zh-CN' - -[Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") -[System.Windows.Forms.Application]::EnableVisualStyles() - -# Choose Source Dialog - -$dialog = New-Object System.Windows.Forms.Form -$dialog.AutoSize = $true -$dialog.AutoSizeMode = [System.Windows.Forms.AutoSizeMode]::GrowAndShrink -if ($chinese) { - $dialog.Text = '未能在这台电脑上找到 Java' -} else { - $dialog.Text = 'Java not found' -} - -$dialogLayout = New-Object System.Windows.Forms.FlowLayoutPanel -$dialogLayout.AutoSize = $true -$dialogLayout.FlowDirection = [System.Windows.Forms.FlowDirection]::TopDown -$dialogLayout.AutoSizeMode = [System.Windows.Forms.AutoSizeMode]::GrowAndShrink - -$messageLabel = New-Object System.Windows.Forms.Label -$messageLabel.AutoSize = $true -$messageLabel.Anchor = [System.Windows.Forms.AnchorStyles]::Bottom -if ($chinese) { - $messageLabel.Text = "HMCL 需要 Java 运行时环境才能正常运行,是否自动下载安装 Java?" -} else { - $messageLabel.Text = "Running HMCL requires a Java runtime environment. `nDo you want to download and install Java automatically?" -} - -$useMirrorCheckBox = New-Object System.Windows.Forms.CheckBox -$useMirrorCheckBox.AutoSize = $true -$useMirrorCheckBox.Anchor = [System.Windows.Forms.AnchorStyles]::Right -$useMirrorCheckBox.Checked = $false -if ($chinese) { - $useMirrorCheckBox.Text = '使用备用下载源(如果无法正常下载,请打开它再试一次)' -} else { - $useMirrorCheckBox.Text = 'Use the alternate download source' -} - -$selectButtonPanel = New-Object System.Windows.Forms.FlowLayoutPanel -$selectButtonPanel.AutoSize = $true -$selectButtonPanel.Anchor = [System.Windows.Forms.AnchorStyles]::Right -$selectButtonPanel.FlowDirection = [System.Windows.Forms.FlowDirection]::LeftToRight -$selectButtonPanel.AutoSizeMode = [System.Windows.Forms.AutoSizeMode]::GrowAndShrink - -$yesButton = New-Object System.Windows.Forms.Button -$noButton = New-Object System.Windows.Forms.Button -$yesButton.DialogResult = [System.Windows.Forms.DialogResult]::Yes -$noButton.DialogResult = [System.Windows.Forms.DialogResult]::No - -if ($chinese) { - $yesButton.Text = '是' - $noButton.Text = '否' -} else { - $yesButton.Text = 'Yes' - $noButton.Text = 'No' -} -$selectButtonPanel.Controls.Add($yesButton) -$selectButtonPanel.Controls.Add($noButton) - -$dialogLayout.Controls.Add($messageLabel) -$dialogLayout.Controls.Add($useMirrorCheckBox) -$dialogLayout.Controls.Add($selectButtonPanel) - -$dialog.Controls.Add($dialogLayout) - -$result = $dialog.ShowDialog() - -if ($result -ne [System.Windows.Forms.DialogResult]::Yes) { - exit 0 -} - -if ($useMirrorCheckBox.Checked) { - switch ($Arch) { - 'x86-64' { - $script:url = 'https://download.bell-sw.com/java/17.0.2+9/bellsoft-jre17.0.2+9-windows-amd64-full.zip' - } - 'x86' { - $script:url = 'https://download.bell-sw.com/java/17.0.2+9/bellsoft-jre17.0.2+9-windows-i586-full.zip' - } - default { exit 1 } - } -} else { - switch ($Arch) { - 'x86-64' { - $script:url = 'https://cdn.azul.com/zulu/bin/zulu17.32.13-ca-fx-jre17.0.2-win_x64.zip' - } - 'x86' { - $script:url = 'https://cdn.azul.com/zulu/bin/zulu17.32.13-ca-fx-jre17.0.2-win_i686.zip' - } - default { exit 1 } - } -} - -$securityProtocol = [System.Net.ServicePointManager]::SecurityProtocol.value__ -if (($securityProtocol -ne 0) -and (($securityProtocol -band 0x00000C00) -eq 0)) { # Try using HTTP when the platform does not support TLS 1.2 - $script:url = $script:url -replace '^https:', 'http:' -} - -# Download Winodw - -do { - $tempFileName = "hmcl-java-$(Get-Random).zip" - $script:tempFile = Join-Path ([System.IO.Path]::GetTempPath()) $tempFileName -} while (Test-Path $script:tempFile) - -$form = New-Object System.Windows.Forms.Form -$form.AutoSize = $true -$form.AutoSizeMode = [System.Windows.Forms.AutoSizeMode]::GrowAndShrink -if ($chinese) { - $form.Text = '正在下载 Java 中' -} else { - $form.Text = 'Downloading Java' -} - -$tip = New-Object System.Windows.Forms.Label -$tip.AutoSize = $true -if ($chinese) { - $tip.Text = '正在下载 Java。这需要一段时间,请耐心等待。' -} else { - $tip.Text = 'Downloading Java. Please wait patiently for the download to complete.' -} - -$layout = New-Object System.Windows.Forms.FlowLayoutPanel -$layout.AutoSize = $true -$layout.FlowDirection = [System.Windows.Forms.FlowDirection]::TopDown -$layout.AutoSizeMode = [System.Windows.Forms.AutoSizeMode]::GrowAndShrink - -$progressBar = New-Object System.Windows.Forms.ProgressBar -$progressBar.Maximum = 100 - -$label = New-Object System.Windows.Forms.Label -$label.Anchor = [System.Windows.Forms.AnchorStyles]::Bottom - -if ($chinese) { - $label.Text = '准备下载' -} else { - $label.Text = 'In preparation' -} - -$box = New-Object System.Windows.Forms.FlowLayoutPanel -$box.AutoSize = $true -$box.FlowDirection = [System.Windows.Forms.FlowDirection]::LeftToRight -$box.AutoSizeMode = [System.Windows.Forms.AutoSizeMode]::GrowAndShrink -$box.Controls.Add($progressBar) -$box.Controls.Add($label) - -$cancelButton = New-Object System.Windows.Forms.Button -$cancelButton.DialogResult = [System.Windows.Forms.DialogResult]::Cancel -$cancelButton.Anchor = [System.Windows.Forms.AnchorStyles]::Right -if ($chinese) { - $cancelButton.Text = '取消' -} else { - $cancelButton.Text = 'Cancel' -} - -$layout.Controls.Add($tip) -$layout.Controls.Add($box) -$box.Controls.Add($cancelButton) - -$form.Controls.Add($layout) - -[System.Net.DownloadProgressChangedEventHandler]$progressChangedEventHandler = { - param($sender, [System.Net.DownloadProgressChangedEventArgs]$ChangedEventArgs) - $bytesReceived = $ChangedEventArgs.BytesReceived - $totalBytes = $ChangedEventArgs.TotalBytesToReceive - - $percentage = ([double]$bytesReceived)/([double]$totalBytes) * 100 - - $progressBar.Value = [int][System.Math]::Truncate($percentage) - $label.Text = [string]::Format("{0:0.00}%", $percentage) -} - -[System.ComponentModel.AsyncCompletedEventHandler]$downloadFileCompletedEventHandler = { - param($sender, [System.ComponentModel.AsyncCompletedEventArgs]$CompletedEventArgs) - if (!$form.IsDisposed) { - $label.Refresh() - if ($CompletedEventArgs.Cancelled) { - $form.DialogResult = [System.Windows.Forms.DialogResult]::Cancel - } elseif ($CompletedEventArgs.Error -ne $null) { - if ($chinese) { - [System.Windows.Forms.MessageBox]::Show($CompletedEventArgs.Error.Message, '下载失败', [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Exclamation) - } else { - [System.Windows.Forms.MessageBox]::Show($CompletedEventArgs.Error.Message, 'Download failed', [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Exclamation) - } - $form.DialogResult = [System.Windows.Forms.DialogResult]::Cancel - } else { - $form.DialogResult = [System.Windows.Forms.DialogResult]::OK - } - } -} - -$client = New-Object System.Net.WebClient -$client.Headers.Add('User-Agent', 'Wget/1.20.3 (linux-gnu)') -$client.add_DownloadProgressChanged($progressChangedEventHandler) -$client.add_DownloadFileCompleted($downloadFileCompletedEventHandler) - -$client.DownloadFileAsync($script:url, $script:tempFile) - -$result = $form.ShowDialog() -$client.CancelAsync() -$form.Dispose() - -if ($result -eq [System.Windows.Forms.DialogResult]::OK) { - $null = New-Item -Type Directory -Force $JavaDir - $app = New-Object -ComObject Shell.Application - $items = $app.NameSpace($script:tempFile).items() - foreach ($item in $items) { - $app.NameSpace($JavaDir).copyHere($item) - } -} - -if ([System.IO.File]::Exists($script:tempFile)) { - try { - [System.IO.File]::Delete($script:tempFile) - } catch { - Write-Error $_ - } -} \ No newline at end of file diff --git a/HMCLauncher/HMCL/lang.h b/HMCLauncher/HMCL/lang.h index 8301c5e0ba..b6207594e2 100644 --- a/HMCLauncher/HMCL/lang.h +++ b/HMCLauncher/HMCL/lang.h @@ -6,9 +6,7 @@ #define ERROR_PROMPT L"The Java runtime environment is required to run HMCL and Minecraft,\n"\ L"Click 'OK' to start downloading java.\n"\ - L"Please restart HMCL after installing Java.\n"\ - L"Click 'Help' go for help." + L"Please restart HMCL after installing Java." #define ERROR_PROMPT_ZH L"运行 HMCL 以及 Minecraft 需要 Java 运行时环境,点击“确定”开始下载。\n"\ - L"请在安装 Java 完成后重新启动 HMCL。\n"\ - L"点击“帮助”寻求帮助。" + L"请在安装 Java 完成后重新启动 HMCL。" diff --git a/HMCLauncher/HMCL/main.cpp b/HMCLauncher/HMCL/main.cpp index 78bdc48f47..ba20558911 100644 --- a/HMCLauncher/HMCL/main.cpp +++ b/HMCLauncher/HMCL/main.cpp @@ -75,12 +75,11 @@ int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, bool useChinese = GetUserDefaultUILanguage() == 2052; // zh-CN - SYSTEM_INFO systemInfo; - GetNativeSystemInfo(&systemInfo); - // TODO: check whether the bundled JRE is valid. + MyArchitecture architecture = MyGetArchitecture(); + // First try the Java packaged together. - bool isX64 = (systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64); - bool isARM64 = (systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_ARM64); + bool isX64 = architecture == MyArchitecture::X86_64; + bool isARM64 = architecture == MyArchitecture::ARM64; if (isARM64) { RawLaunchJVM(L"jre-arm64\\bin\\javaw.exe", workdir, exeName, jvmOptions); @@ -143,7 +142,7 @@ int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, if (isARM64) { downloadLink = L"https://docs.hmcl.net/downloads/windows/arm64.html"; - } if (isX64) { + } else if (isX64) { downloadLink = L"https://docs.hmcl.net/downloads/windows/x86_64.html"; } else { downloadLink = L"https://docs.hmcl.net/downloads/windows/x86.html"; diff --git a/HMCLauncher/HMCL/os.cpp b/HMCLauncher/HMCL/os.cpp index d632b422c8..b10718586e 100644 --- a/HMCLauncher/HMCL/os.cpp +++ b/HMCLauncher/HMCL/os.cpp @@ -1,6 +1,41 @@ #include "stdafx.h" #include "os.h" +typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS2) (HANDLE, PUSHORT, PUSHORT); + +MyArchitecture MyGetArchitecture() { + LPFN_ISWOW64PROCESS2 fnIsWow64Process2 = (LPFN_ISWOW64PROCESS2)GetProcAddress( + GetModuleHandle(L"Kernel32.dll"), "IsWow64Process2"); + if (NULL != fnIsWow64Process2) { + USHORT uProcessMachine = 0; + USHORT uNativeMachine = 0; + if (fnIsWow64Process2(GetCurrentProcess(), &uProcessMachine, &uNativeMachine)) { + if (uNativeMachine == 0xAA64) { + return MyArchitecture::ARM64; + } + + if (uNativeMachine == 0x8664) { + return MyArchitecture::X86_64; + } + + return MyArchitecture::X86; + } + } + + SYSTEM_INFO systemInfo; + GetNativeSystemInfo(&systemInfo); + + if (systemInfo.wProcessorArchitecture == 12) { // PROCESSOR_ARCHITECTURE_ARM64 + return MyArchitecture::ARM64; + } + + if (systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) { + return MyArchitecture::X86_64; + } + + return MyArchitecture::X86; +} + LSTATUS MyRegQueryValue(HKEY hKey, LPCWSTR subKey, DWORD dwType, std::wstring &out) { DWORD dwSize = 0; diff --git a/HMCLauncher/HMCL/os.h b/HMCLauncher/HMCL/os.h index d0c2fcd63e..e44743a9f0 100644 --- a/HMCLauncher/HMCL/os.h +++ b/HMCLauncher/HMCL/os.h @@ -8,9 +8,13 @@ const int MAX_KEY_LENGTH = 255; const int MAX_VALUE_NAME = 16383; -#ifndef PROCESSOR_ARCHITECTURE_ARM64 - #define PROCESSOR_ARCHITECTURE_ARM64 12 -#endif +enum MyArchitecture { + X86, + X86_64, + ARM64 +}; + +MyArchitecture MyGetArchitecture(); // Query registry value of class root hKey, key path subKey, stores result in // parameter out. diff --git a/PLATFORM.md b/PLATFORM.md index 3826696f8e..8e21b74a9e 100644 --- a/PLATFORM.md +++ b/PLATFORM.md @@ -1,39 +1,38 @@ # Platform Support Status -English | [简体中文](PLATFORM_cn.md) - -| | Windows | Linux | Mac OS | FreeBSD | -|----------------------------|:--------------------------------------------------|:--------------------------|:------------------------------------------------------------------------|:-------------------------| -| x86-64 | ✅️ | ✅️ | ✅️ | 👌 (Minecraft 1.13~1.21) | -| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / | -| ARM64 | 👌 (Minecraft 1.8~1.18.2)
✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.20.6) | 👌 (Minecraft 1.6~1.18.2)
✅ (Minecraft 1.19+)
✅ (use Rosetta 2) | ❔ | -| ARM32 | /️ | 👌 (Minecraft 1.8~1.20.1) | / | / | -| MIPS64el | / | 👌 (Minecraft 1.8~1.20.1) | / | / | -| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21) | / | / | -| LoongArch64 | / | 👌 (Minecraft 1.6~1.20.1) | / | / | -| PowerPC-64 (Little-Endian) | / | ❔ | / | / | -| S390x | / | ❔ | / | / | +**English** | [简体中文](PLATFORM_cn.md) | [繁體中文](PLATFORM_tw.md) + +| | Windows | Linux | macOS | FreeBSD | +|----------------------------|:--------------------------------------------------|:---------------------------|:------------------------------------------------------------------------|:---------------------------| +| x86-64 | ✅️ | ✅️ | ✅️ | 👌 (Minecraft 1.13~1.21.4) | +| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / | +| ARM64 | 👌 (Minecraft 1.8~1.18.2)
✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21.4) | 👌 (Minecraft 1.6~1.18.2)
✅ (Minecraft 1.19+)
✅ (use Rosetta 2) | ❔ | +| ARM32 | /️ | 👌 (Minecraft 1.8~1.20.1) | / | / | +| MIPS64el | / | 👌 (Minecraft 1.8~1.20.1) | / | / | +| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.4) | / | / | +| LoongArch64 | / | 👌 (Minecraft 1.6~1.21.4) | / | / | +| LoongArch64 (Old World) | / | 👌 (Minecraft 1.6~1.20.1) | / | / | +| PowerPC-64 (Little-Endian) | / | ❔ | / | / | +| S390x | / | ❔ | / | / | Legend: -* ✅: Official supported platform. +* ✅: Officially supported platform. - Fully supported by Mojang official. Problems encountered in the game should be directly reported to the Mojang. + Fully supported by Mojang officials. Problems encountered in the game should be directly reported to the Mojang. * 👌: Supported platforms. - Support is provided by HMCL, tested to work, but may have more issues than a fully supported platform. - Support for versions below Minecraft 1.6 is not guaranteed. + Support is provided by HMCL, tested to work, but may have more problems than a fully supported platform. + Support for versions below Minecraft 1.6 is not guaranteed. If you encounter a problem that does not exist on fully supported platforms, you can report it to HMCL. -* ❔: Low level supported platforms. +* ❔: Low-level supported platforms. - HMCL can run on this platform and has some basic support. - However, launching the game directly is not yet available. - If you want to start the game, - you'll need to get the native libraries needed by Minecraft in other way and specify the native path in the instance settings. + HMCL can run on this platform and has some basic support. However, launching the game directly is not yet available. + If you want to start the game, you will need to get the native libraries needed by Minecraft in another way and specify the native path in the instance settings. -* /: Not applicable. +* `/`: Not applicable. - We have no plans to support these platforms at this time, mainly because we don't have the equipment to test them. - If you can help us adapt, please file a support request via issue. + We have no plans to support these platforms at this time, mainly because we do not have the equipment to test them. + If you can help us adapt, please file a support request via GitHub Issue. \ No newline at end of file diff --git a/PLATFORM_cn.md b/PLATFORM_cn.md index 0fe094933f..60c546af2f 100644 --- a/PLATFORM_cn.md +++ b/PLATFORM_cn.md @@ -1,39 +1,38 @@ # 平台支持状态 -[English](PLATFORM.md) | 简体中文 +[English](PLATFORM.md) | **简体中文** | [繁體中文](PLATFORM_tw.md) -| | Windows | Linux | Mac OS | FreeBSD | -|----------------------------|:--------------------------------------------------|:---------------------------|:-----------------------------------------------------------------------|:------------------------| -| x86-64 | ✅️ | ✅️ | ✅️ | 👌(Minecraft 1.13~1.21) | -| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / | -| ARM64 | 👌 (Minecraft 1.8~1.18.2)
✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21) | 👌 (Minecraft 1.6~1.18.2)
✅ (Minecraft 1.19+)
✅ (使用 Rosetta 2) | ❔ | -| ARM32 | /️ | 👌 (Minecraft 1.6~1.20.1) | / | / | -| MIPS64el | / | 👌 (Minecraft 1.6~1.20.1) | / | / | -| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21) | / | / | -| LoongArch64 | / | 👌 (Minecraft 1.6~1.20.1) | / | / | -| PowerPC-64 (Little-Endian) | / | ❔ | / | / | -| S390x | / | ❔ | / | / | +| | Windows | Linux | macOS | FreeBSD | +|----------------------------|:--------------------------------------------------|:---------------------------|:-----------------------------------------------------------------------|:--------------------------| +| x86-64 | ✅️ | ✅️ | ✅️ | 👌(Minecraft 1.13~1.21.4) | +| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / | +| ARM64 | 👌 (Minecraft 1.8~1.18.2)
✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21.4) | 👌 (Minecraft 1.6~1.18.2)
✅ (Minecraft 1.19+)
✅ (使用 Rosetta 2) | ❔ | +| ARM32 | /️ | 👌 (Minecraft 1.8~1.20.1) | / | / | +| MIPS64el | / | 👌 (Minecraft 1.8~1.20.1) | / | / | +| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.4) | / | / | +| LoongArch64 | / | 👌 (Minecraft 1.6~1.21.4) | / | / | +| LoongArch64 (旧世界) | / | 👌 (Minecraft 1.6~1.20.1) | / | / | +| PowerPC-64 (Little-Endian) | / | ❔ | / | / | +| S390x | / | ❔ | / | / | -图例: +图例: -* ✅: 官方支持的平台. +* ✅: 官方支持的平台 - 受 Mojang 官方支持. 在游戏中遇到的问题应该直接向 Mojang 反馈. + 受 Mojang 官方支持。在游戏中遇到的问题应该直接向 Mojang 反馈。 -* 👌: 支持的平台. +* 👌: 支持的平台 - 由 HMCL 提供支持, 经过测试可以正常运行, 但可能比得到全面支持的平台有更多问题. - 不保证支持 Minecraft 1.6 以下的版本. - 如果你遇到在得到全面支持的平台上不存在的问题, 可以向 HMCL 反馈. + 由 HMCL 提供支持,经过测试可以正常运行,但可能比得到全面支持的平台有更多问题。 + 不保证支持 Minecraft 1.6 以下的版本。 + 如果你遇到在得到全面支持的平台上不存在的问题,可以向 HMCL 反馈。 -* ❔: 低级别支持的平台. +* ❔: 低级别支持的平台 - HMCL 可以在这个平台上运行, 并且有一些基本的支持. - 但是, 还不能正常地启动游戏. - 如果你想正常启动游戏, - 则需要通过其他方式获得游戏所需的本地库(LWJGL), 并在版本设置中指定本地库路径. + HMCL 可以在这个平台上运行,并且有一些基本的支持。但是,还不能正常地启动游戏。 + 如果你想正常启动游戏,则需要通过其他方式获得游戏所需的本地库 (LWJGL),并在(全局)游戏设置中指定本地库路径。 -* /: 不支持的平台. +* `/`: 不支持的平台 - 我们目前还没有打算支持这些平台, 主要是因为我们没有测试这些平台的设备. - 如果你能帮助我们进行测试, 请通过提交 Issue 提出支持请求. + 我们目前还没有打算支持这些平台,主要是因为我们没有测试这些平台的设备。 + 如果你能帮助我们进行测试,请通过提交 Issue 提出支持请求。 \ No newline at end of file diff --git a/PLATFORM_tw.md b/PLATFORM_tw.md new file mode 100644 index 0000000000..8c81e7d67b --- /dev/null +++ b/PLATFORM_tw.md @@ -0,0 +1,38 @@ +# 平臺支援狀態 + +[English](PLATFORM.md) | [简体中文](PLATFORM_cn.md) | **繁體中文** + +| | Windows | Linux | macOS | FreeBSD | +|----------------------------|:--------------------------------------------------|:---------------------------|:-----------------------------------------------------------------------|:--------------------------| +| x86-64 | ✅️ | ✅️ | ✅️ | 👌(Minecraft 1.13~1.21.4) | +| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / | +| ARM64 | 👌 (Minecraft 1.8~1.18.2)
✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21.4) | 👌 (Minecraft 1.6~1.18.2)
✅ (Minecraft 1.19+)
✅ (使用 Rosetta 2) | ❔ | +| ARM32 | /️ | 👌 (Minecraft 1.8~1.20.1) | / | / | +| MIPS64el | / | 👌 (Minecraft 1.8~1.20.1) | / | / | +| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.4) | / | / | +| LoongArch64 | / | 👌 (Minecraft 1.6~1.21.4) | / | / | +| LoongArch64 (舊世界) | / | 👌 (Minecraft 1.6~1.20.1) | / | / | +| PowerPC-64 (Little-Endian) | / | ❔ | / | / | +| S390x | / | ❔ | / | / | + +圖例: + +* ✅: 官方支援的平臺 + + 受 Mojang 官方支援。在遊戲中遇到的問題應該直接向 Mojang 回報。 + +* 👌: 支援的平臺 + + 由 HMCL 提供支援,經過測試可以正常執行,但可能比得到全面支援的平臺有更多問題。 + 不保證支援 Minecraft 1.6 以下的版本。 + 如果你遇到在得到全面支援的平臺上不存在的問題,可以向 HMCL 回報。 + +* ❔: 低級別支援的平臺 + + HMCL 可以在這個平臺上執行,並且有一些基本的支援。但是,還不能正常地啟動遊戲。 + 如果你想正常啟動遊戲,則需要透過其他方式獲得遊戲所需的本機庫 (LWJGL),並在(全域)遊戲設定中指定本機庫路徑。 + +* `/`: 不支援的平臺 + + 我們目前還沒有打算支援這些平臺,主要是因為我們沒有測試這些平臺的裝置。 + 如果你能幫助我們進行測試,請透過提交 Issue 提出支援請求。 \ No newline at end of file diff --git a/README.md b/README.md index d46d9a0d27..e0174ae520 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,32 @@ # ⛏ Hello Minecraft! Launcher 💎 [![Build Status](https://ci.huangyuhui.net/job/HMCL/badge/icon?.svg)](https://ci.huangyuhui.net/job/HMCL) -![Downloads](https://img.shields.io/github/downloads/huanghongxun/HMCL/total) -![Stars](https://img.shields.io/github/stars/huanghongxun/HMCL) +![Downloads](https://img.shields.io/github/downloads/HMCL-dev/HMCL/total?style=flat) +![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) -[![QQ Group](https://img.shields.io/badge/QQ-HMCL-brightgreen)](https://docs.hmcl.net/groups.html) +[![QQ Group](https://img.shields.io/badge/QQ-HMCL-bright?label=&logo=qq&logoColor=ffffff&color=1EBAFC&labelColor=1DB0EF&logoSize=auto)](https://docs.hmcl.net/groups.html) -English | [中文](README_cn.md) +**English** | [简体中文](README_cn.md) | [繁體中文](README_tw.md) ## Introduction -HMCL is a cross-platform Minecraft launcher which supports Mod Management, Game Customizing, Auto Installing (Forge, NeoForge, Fabric, Quilt, LiteLoader and OptiFine), Modpack Creating, UI Customization, and more. +HMCL is an open-source, cross-platform Minecraft launcher that supports Mod Management, Game Customizing, ModLoader Installing (Forge, NeoForge, Fabric, Quilt, LiteLoader, and OptiFine), Modpack Creating, UI Customization, and more. -HMCL has amazing cross-platform capabilities. -It can not only run on different operating systems such as Windows, Linux, and macOS, -but also supports multiple CPU architectures such as x86, arm, mips, and loongarch. -You can easily play Minecraft on different platforms through HMCL. +HMCL has amazing cross-platform capabilities. Not only does it run on different operating systems like Windows, Linux, and macOS, but it also supports various CPU architectures such as x86, ARM, MIPS, and LoongArch. You can easily enjoy Minecraft across different platforms through HMCL. -For systems and CPU architectures supported by HMCL, see [this table](PLATFORM.md). +For systems and CPU architectures supported by HMCL, please refer to [this table](PLATFORM.md). ## Download -Download the latest version from [the official website](https://hmcl.huangyuhui.net/download). +Download the latest version from the [official website](https://hmcl.huangyuhui.net/download). -You can also download the latest version of HMCL in [GitHub Releases](https://github.com/HMCL-dev/HMCL/releases). +You can also find the latest version of HMCL in [GitHub Releases](https://github.com/HMCL-dev/HMCL/releases). -Although not necessary, it is recommended to download the ones from the official website. +Although not necessary, it is recommended only to download releases from the official websites listed above. ## License -The software is distributed under [GPLv3](https://www.gnu.org/licenses/gpl-3.0.html) with additional terms. +The software is distributed under [GPLv3](https://www.gnu.org/licenses/gpl-3.0.html) license with the following additional terms: ### Additional terms under GPLv3 Section 7 @@ -41,34 +38,34 @@ The software is distributed under [GPLv3](https://www.gnu.org/licenses/gpl-3.0.h ## Contribution -If you want to submit a pull request, there are some requirements: +If you want to submit a pull request, here are some requirements: -* IDE: Intellij IDEA +* IDE: IntelliJ IDEA * Compiler: Java 1.8 or Java 11+ * Do NOT modify `gradle` files ### Compilation -Simply execute the following command in project root directory: +Simply execute the following command in the project root directory: ```bash ./gradlew clean build ``` -Make sure you have Java installed with JavaFX 8 at least. Liberica Full JDK 8 or later is recommended. +Make sure you have at least JavaFX 8 installed. Liberica Full JDK 8 or later is recommended. ## JVM Options (for debugging) -| Parameter | Description | -|----------------------------------------------|------------------------------------------------------------------------------------------------| -| `-Dhmcl.home=` | Override HMCL directory. | -| `-Dhmcl.self_integrity_check.disable=true` | Bypass the self integrity check when checking for update. | -| `-Dhmcl.bmclapi.override=` | Override API Root of BMCLAPI download provider, defaults to `https://bmclapi2.bangbang93.com`. | -| `-Dhmcl.font.override=` | Override font family. | -| `-Dhmcl.version.override=` | Override the version number. | -| `-Dhmcl.update_source.override=` | Override the update source for HMCL itself. | -| `-Dhmcl.authlibinjector.location=` | Use specified authlib-injector (instead of downloading one). | -| `-Dhmcl.openjfx.repo=` | Add custom Maven repository for download OpenJFX. | -| `-Dhmcl.native.encoding=` | Override the native encoding. | -| `-Dhmcl.microsoft.auth.id=` | Override Microsoft OAuth App ID. | -| `-Dhmcl.microsoft.auth.secret=` | Override Microsoft OAuth App secret. | +| Parameter | Description | +| -------------------------------------------- | --------------------------------------------------------------------------------------------- | +| `-Dhmcl.home=` | Override HMCL directory | +| `-Dhmcl.self_integrity_check.disable=true` | Bypass the self integrity check when checking for updates | +| `-Dhmcl.bmclapi.override=` | Override API Root of BMCLAPI download provider. Defaults to `https://bmclapi2.bangbang93.com` | +| `-Dhmcl.font.override=` | Override font family | +| `-Dhmcl.version.override=` | Override the version number | +| `-Dhmcl.update_source.override=` | Override the update source for HMCL itself | +| `-Dhmcl.authlibinjector.location=` | Use the specified authlib-injector (instead of downloading one) | +| `-Dhmcl.openjfx.repo=` | Add custom Maven repository for downloading OpenJFX | +| `-Dhmcl.native.encoding=` | Override the native encoding | +| `-Dhmcl.microsoft.auth.id=` | Override Microsoft OAuth App ID | +| `-Dhmcl.microsoft.auth.secret=` | Override Microsoft OAuth App Secret | diff --git a/README_cn.md b/README_cn.md index bd9e1a9718..9b8928f360 100644 --- a/README_cn.md +++ b/README_cn.md @@ -1,72 +1,71 @@ # ⛏ Hello Minecraft! Launcher 💎 [![Build Status](https://ci.huangyuhui.net/job/HMCL/badge/icon?.svg)](https://ci.huangyuhui.net/job/HMCL) -![Downloads](https://img.shields.io/github/downloads/huanghongxun/HMCL/total) -![Stars](https://img.shields.io/github/stars/huanghongxun/HMCL) +![Downloads](https://img.shields.io/github/downloads/HMCL-dev/HMCL/total?style=flat) +![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) -[![QQ Group](https://img.shields.io/badge/QQ-HMCL-brightgreen)](https://docs.hmcl.net/groups.html) +[![QQ Group](https://img.shields.io/badge/QQ-HMCL-bright?label=&logo=qq&logoColor=ffffff&color=1EBAFC&labelColor=1DB0EF&logoSize=auto)](https://docs.hmcl.net/groups.html) -[English](README.md) | 中文 +[English](README.md) | **简体中文** | [繁體中文](README_tw.md) ## 简介 -HMCL 是一款跨平台 Minecraft 启动器, 支持 Mod 管理, 游戏自定义, 游戏自动安装 (Forge, NeoForge, Fabric, Quilt, LiteLoader 与 OptiFine), 整合包创建, 界面自定义等功能. +HMCL 是一款开源、跨平台的 Minecraft 启动器,支持模组管理、游戏自定义、游戏自动安装 (Forge、NeoForge、Fabric、Quilt、LiteLoader 和 OptiFine)、整合包创建、界面自定义等功能。 -HMCL 有着强大的跨平台能力. 它不仅支持 Windows、Linux、macOS 等常见的操作系统,同时也支持 x86、ARM、MIPS 和 LoongArch 等不同的 CPU 架构. -您可以使用 HMCL 在不同平台上轻松的游玩 Minecraft. +HMCL 有着强大的跨平台能力。它不仅支持 Windows、Linux、macOS 等常见的操作系统,同时也支持 x86、ARM、MIPS、LoongArch 等不同的 CPU 架构。你可以使用 HMCL 在不同平台上轻松地游玩 Minecraft。 -如果您想要了解 HMCL 对不同平台的支持程度,请参见[此表格](PLATFORM_cn.md). +如果你想要了解 HMCL 对不同平台的支持程度,请参见 [此表格](PLATFORM_cn.md)。 ## 下载 -请从 [HMCL 官网](https://hmcl.huangyuhui.net/download) 下载最新版本的 HMCL. +请从 [HMCL 官网](https://hmcl.huangyuhui.net/download) 下载最新版本的 HMCL。 -你也可以在 [GitHub Releases](https://github.com/HMCL-dev/HMCL/releases) 中下载最新版本的 HMCL. +你也可以在 [GitHub Releases](https://github.com/HMCL-dev/HMCL/releases) 中下载最新版本的 HMCL。 -虽然并不强制, 但仍建议通过 HMCL 官网下载启动器. +虽然并不强制,但仍建议通过 HMCL 官网下载启动器。 ## 开源协议 -该程序在 [GPLv3](https://www.gnu.org/licenses/gpl-3.0.html) 开源协议下发布, 同时附有附加条款. +该程序在 [GPLv3](https://www.gnu.org/licenses/gpl-3.0.html) 开源协议下发布,同时附有以下附加条款。 ### 附加条款 (依据 GPLv3 开源协议第七条) -1. 当您分发该程序的修改版本时, 您必须以一种合理的方式修改该程序的名称或版本号, 以示其与原始版本不同. (依据 [GPLv3, 7(c)](https://github.com/HMCL-dev/HMCL/blob/11820e31a85d8989e41d97476712b07e7094b190/LICENSE#L372-L374)) +1. 当你分发该程序的修改版本时,你必须以一种合理的方式修改该程序的名称或版本号,以示其与原始版本不同。(依据 [GPLv3, 7(c)](https://github.com/HMCL-dev/HMCL/blob/11820e31a85d8989e41d97476712b07e7094b190/LICENSE#L372-L374)) - 该程序的名称及版本号可在[此处](https://github.com/HMCL-dev/HMCL/blob/javafx/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java#L33-L35)修改. + 该程序的名称及版本号可在 [此处](https://github.com/HMCL-dev/HMCL/blob/javafx/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java#L33-L35) 修改。 -2. 您不得移除该程序所显示的版权声明. (依据 [GPLv3, 7(b)](https://github.com/HMCL-dev/HMCL/blob/11820e31a85d8989e41d97476712b07e7094b190/LICENSE#L368-L370)) +2. 你不得移除该程序所显示的版权声明。(依据 [GPLv3, 7(b)](https://github.com/HMCL-dev/HMCL/blob/11820e31a85d8989e41d97476712b07e7094b190/LICENSE#L368-L370)) ## 贡献 -如果您想提交一个 Pull Request, 必须遵守如下要求: +如果你想提交一个 Pull Request,必须遵守如下要求: -* IDE: Intellij IDEA -* 编译器: Java 1.8 或 Java 11+ +* IDE:IntelliJ IDEA +* 编译器:Java 1.8 或 Java 11+ * **不要**修改 `gradle` 相关文件 ### 编译 -于项目根目录执行以下命令: +于项目根文件夹执行以下命令: ```bash ./gradlew clean build ``` -请确保您至少安装了含有 JavaFX 8 的 Java. 建议使用 Liberica Full JDK 8 或更高版本. +请确保你至少安装了含有 JavaFX 8 的 Java。建议使用 Liberica Full JDK 8 或更高版本。 ## JVM 选项 (用于调试) -| 参数 | 简介 | -|----------------------------------------------|----------------------------------------------------------------| -| `-Dhmcl.home=` | 覆盖 HMCL 数据文件夹. | -| `-Dhmcl.self_integrity_check.disable=true` | 检查更新时绕过本体完整性检查. | -| `-Dhmcl.bmclapi.override=` | 覆盖 BMCLAPI 的 API Root, 默认值为 `https://bmclapi2.bangbang93.com`. | -| `-Dhmcl.font.override=` | 覆盖字族. | -| `-Dhmcl.version.override=` | 覆盖版本号. | -| `-Dhmcl.update_source.override=` | 覆盖 HMCL 更新源. | -| `-Dhmcl.authlibinjector.location=` | 使用指定的 authlib-injector (而非下载一个). | -| `-Dhmcl.openjfx.repo=` | 添加用于下载 OpenJFX 的自定义 Maven 仓库 | -| `-Dhmcl.native.encoding=` | 覆盖原生编码. | -| `-Dhmcl.microsoft.auth.id=` | 覆盖 Microsoft OAuth App ID. | -| `-Dhmcl.microsoft.auth.secret=` | 覆盖 Microsoft OAuth App 密钥. | +| 参数 | 简介 | +| -------------------------------------------- | -------------------------------------------------------------------- | +| `-Dhmcl.home=` | 覆盖 HMCL 数据文件夹 | +| `-Dhmcl.self_integrity_check.disable=true` | 检查更新时不检查本体完整性 | +| `-Dhmcl.bmclapi.override=` | 覆盖 BMCLAPI 的 API Root,默认值为 `https://bmclapi2.bangbang93.com` | +| `-Dhmcl.font.override=` | 覆盖字族 | +| `-Dhmcl.version.override=` | 覆盖版本号 | +| `-Dhmcl.update_source.override=` | 覆盖 HMCL 更新源 | +| `-Dhmcl.authlibinjector.location=` | 使用指定的 authlib-injector (而非下载一个) | +| `-Dhmcl.openjfx.repo=` | 添加用于下载 OpenJFX 的自定义 Maven 仓库 | +| `-Dhmcl.native.encoding=` | 覆盖原生编码 | +| `-Dhmcl.microsoft.auth.id=` | 覆盖 Microsoft OAuth App ID | +| `-Dhmcl.microsoft.auth.secret=` | 覆盖 Microsoft OAuth App 密钥 | \ No newline at end of file diff --git a/README_tw.md b/README_tw.md new file mode 100644 index 0000000000..6136081f88 --- /dev/null +++ b/README_tw.md @@ -0,0 +1,71 @@ +# ⛏ Hello Minecraft! Launcher 💎 + +[![Build Status](https://ci.huangyuhui.net/job/HMCL/badge/icon?.svg)](https://ci.huangyuhui.net/job/HMCL) +![Downloads](https://img.shields.io/github/downloads/HMCL-dev/HMCL/total?style=flat) +![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) +[![QQ Group](https://img.shields.io/badge/QQ-HMCL-bright?label=&logo=qq&logoColor=ffffff&color=1EBAFC&labelColor=1DB0EF&logoSize=auto)](https://docs.hmcl.net/groups.html) + +[English](README.md) | [简体中文](README_cn.md) | **繁體中文** + +## 簡介 + +HMCL 是一款開源、跨平臺的 Minecraft 啟動器,支援模組管理、遊戲客製化、遊戲自動安裝 (Forge、NeoForge、Fabric、Quilt、LiteLoader 和 OptiFine)、模組包建立、介面客製化等功能。 + +HMCL 有著強大的跨平臺能力。它不僅支援 Windows、Linux、macOS 等常見的作業系統,同時也支援 x86、ARM、MIPS、LoongArch 等不同的 CPU 架構。你可以使用 HMCL 在不同平臺上輕鬆地遊玩 Minecraft。 + +如果你想要了解 HMCL 對不同平臺的支援程度,請參見 [此表格](PLATFORM_tw.md)。 + +## 下載 + +請從 [HMCL 官網](https://hmcl.huangyuhui.net/download) 下載最新版本的 HMCL。 + +你也可以在 [GitHub Releases](https://github.com/HMCL-dev/HMCL/releases) 中下載最新版本的 HMCL。 + +雖然並不強制,但仍建議透過 HMCL 官網下載啟動器。 + +## 開源協議 + +該程式在 [GPLv3](https://www.gnu.org/licenses/gpl-3.0.html) 開源協議下發布,同時附有以下附加條款。 + +### 附加條款 (依據 GPLv3 開源協議第七條) + +1. 當你分發該程式的修改版本時,你必須以一種合理的方式修改該程式的名稱或版本號,以示其與原始版本不同。(依據 [GPLv3, 7(c)](https://github.com/HMCL-dev/HMCL/blob/11820e31a85d8989e41d97476712b07e7094b190/LICENSE#L372-L374)) + + 該程式的名稱及版本號可在 [此處](https://github.com/HMCL-dev/HMCL/blob/javafx/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java#L33-L35) 修改。 + +2. 你不得移除該程式所顯示的版權宣告。(依據 [GPLv3, 7(b)](https://github.com/HMCL-dev/HMCL/blob/11820e31a85d8989e41d97476712b07e7094b190/LICENSE#L368-L370)) + +## 貢獻 + +如果你想提交一個 Pull Request,必須遵守如下要求: + +* IDE:IntelliJ IDEA +* 編譯器:Java 1.8 或 Java 11+ +* **不要**修改 `gradle` 相關文件 + +### 編譯 + +於項目根目錄執行以下指令: + +```bash +./gradlew clean build +``` + +請確保你至少安裝了含有 JavaFX 8 的 Java。建議使用 Liberica Full JDK 8 或更高版本。 + +## JVM 選項 (用於除錯) + +| 參數 | 簡介 | +| -------------------------------------------- | -------------------------------------------------------------------- | +| `-Dhmcl.home=` | 覆蓋 HMCL 使用者目錄 | +| `-Dhmcl.self_integrity_check.disable=true` | 檢查更新時不檢查本體完整性 | +| `-Dhmcl.bmclapi.override=` | 覆蓋 BMCLAPI 的 API Root,預設值為 `https://bmclapi2.bangbang93.com` | +| `-Dhmcl.font.override=` | 覆蓋字族 | +| `-Dhmcl.version.override=` | 覆蓋版本號 | +| `-Dhmcl.update_source.override=` | 覆蓋 HMCL 更新來源 | +| `-Dhmcl.authlibinjector.location=` | 使用指定的 authlib-injector (而非下載一個) | +| `-Dhmcl.openjfx.repo=` | 添加用於下載 OpenJFX 的自訂 Maven 倉庫 | +| `-Dhmcl.native.encoding=` | 覆蓋原生編碼 | +| `-Dhmcl.microsoft.auth.id=` | 覆蓋 Microsoft OAuth App ID | +| `-Dhmcl.microsoft.auth.secret=` | 覆蓋 Microsoft OAuth App 金鑰 | \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index deccd9e5c6..2317dbc93c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -94,6 +94,13 @@ tasks.create("checkTranslations") { } } + zh_CN.forEach { + if (it.value.toString().contains("其它")) { + project.logger.warn("The misspelled '其它' in '${it.key}' should be replaced by '其他'") + success = false + } + } + if (!success) { throw GradleException("Part of the translation is missing") } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index d64cd49177..a4b76b9530 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a80b22ce5c..94913af205 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip -networkTimeout=10000 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +networkTimeout=120000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 1aa94a4269..f5feea6d6b 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,8 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 6689b85bee..9b42019c79 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/javafx.gradle.kts b/javafx.gradle.kts index 734639eeb3..686c1e32fd 100644 --- a/javafx.gradle.kts +++ b/javafx.gradle.kts @@ -4,21 +4,19 @@ buildscript { } dependencies { - classpath("com.google.code.gson:gson:2.10.1") + classpath("com.google.code.gson:gson:2.11.0") } } -val jfxVersion = "19.0.2.1" +val jfxVersion = "17.0.13" +val oldJfxVersion = "19.0.2.1" data class Platform( val name: String, val classifier: String, val groupId: String = "org.openjfx", - val version: String = jfxVersion, - val unsupportedModules: List = listOf() + val version: String = jfxVersion ) { - val modules: List = jfxModules.filter { it !in unsupportedModules } - fun fileUrl( module: String, classifier: String, ext: String, repo: String = "https://repo1.maven.org/maven2" @@ -28,22 +26,22 @@ data class Platform( ).toURL() } -val jfxModules = listOf("base", "graphics", "controls", "media", "web") +val jfxModules = listOf("base", "graphics", "controls") val jfxMirrorRepos = listOf("https://mirrors.cloud.tencent.com/nexus/repository/maven-public") -val jfxDependenciesFile = project("HMCL").layout.buildDirectory.file("openjfx-dependencies.json").get().asFile +val jfxDependenciesFile = project.file("HMCL/src/main/resources/assets/openjfx-dependencies.json") val jfxPlatforms = listOf( - Platform("windows-x86", "win-x86"), + Platform("windows-x86", "win-x86", version = oldJfxVersion), Platform("windows-x86_64", "win"), - Platform("windows-arm64", "win", groupId = "org.glavo.hmcl.openjfx", version = "18.0.2+1-arm64", unsupportedModules = listOf("media", "web")), - Platform("osx-x86_64", "mac"), - Platform("osx-arm64", "mac-aarch64"), + Platform("windows-arm64", "win", groupId = "org.glavo.hmcl.openjfx", version = "18.0.2+1-arm64"), + Platform("osx-x86_64", "mac", version = oldJfxVersion), + Platform("osx-arm64", "mac-aarch64", version = oldJfxVersion), Platform("linux-x86_64", "linux"), - Platform("linux-arm32", "linux-arm32-monocle", unsupportedModules = listOf("media", "web")), - Platform("linux-arm64", "linux-aarch64"), + Platform("linux-arm32", "linux-arm32-monocle", version = oldJfxVersion), + Platform("linux-arm64", "linux-aarch64", version = oldJfxVersion), Platform("linux-loongarch64", "linux", groupId = "org.glavo.hmcl.openjfx", version = "17.0.8-loongarch64"), - Platform("linux-loongarch64_ow", "linux", groupId = "org.glavo.hmcl.openjfx", version = "19-ea+10-loongson64", unsupportedModules = listOf("media", "web")), - Platform("linux-riscv64", "linux", groupId = "org.glavo.hmcl.openjfx", version = "19.0.2.1-riscv64", unsupportedModules = listOf("media", "web")), - Platform("freebsd-x86_64", "freebsd", groupId = "org.glavo.hmcl.openjfx", version = "14.0.2.1-freebsd", unsupportedModules = listOf("media", "web")), + Platform("linux-loongarch64_ow", "linux", groupId = "org.glavo.hmcl.openjfx", version = "19-ea+10-loongson64"), + Platform("linux-riscv64", "linux", groupId = "org.glavo.hmcl.openjfx", version = "19.0.2.1-riscv64"), + Platform("freebsd-x86_64", "freebsd", groupId = "org.glavo.hmcl.openjfx", version = "14.0.2.1-freebsd"), ) val jfxInClasspath = @@ -89,11 +87,9 @@ if (!jfxInClasspath && JavaVersion.current() >= JavaVersion.VERSION_11) { } rootProject.tasks.create("generateOpenJFXDependencies") { - outputs.file(jfxDependenciesFile) - doLast { val jfxDependencies = jfxPlatforms.associate { platform -> - platform.name to platform.modules.map { module -> + platform.name to jfxModules.map { module -> mapOf( "module" to "javafx.$module", "groupId" to platform.groupId, @@ -117,7 +113,7 @@ rootProject.tasks.create("preTouchOpenJFXDependencies") { doLast { for (repo in jfxMirrorRepos) { for (platform in jfxPlatforms) { - for (module in platform.modules) { + for (module in jfxModules) { val url = platform.fileUrl(module, platform.classifier, "jar", repo = repo) logger.quiet("Getting $url") try {