Compare commits
111
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17abfc1a55 | ||
|
|
caa98c8956 | ||
|
|
6124b6711f | ||
|
|
3b3cc54cb5 | ||
|
|
45a40ee675 | ||
|
|
4a75f1d462 | ||
|
|
e0805fc25f | ||
|
|
f223d2bc41 | ||
|
|
5ab75b6a84 | ||
|
|
362969db95 | ||
|
|
fb37e3b0df | ||
|
|
ad312a61fe | ||
|
|
58a4700a46 | ||
|
|
68c8a30bd7 | ||
|
|
bb37e3fafa | ||
|
|
942f7b703e | ||
|
|
267da46050 | ||
|
|
7c40dca3ea | ||
|
|
48e3f79965 | ||
|
|
c5f56eb5c4 | ||
|
|
0900ad3e5a | ||
|
|
d347b0c6cb | ||
|
|
2f3bf61d94 | ||
|
|
5479607597 | ||
|
|
4291ae5f16 | ||
|
|
1ac677365b | ||
|
|
b079e50916 | ||
|
|
f986df9654 | ||
|
|
c576c48f9d | ||
|
|
5bb035c884 | ||
|
|
973b7a9716 | ||
|
|
6e05b5ee58 | ||
|
|
76ed9353bd | ||
|
|
9c0f823705 | ||
|
|
b69602b735 | ||
|
|
7cdf4504d1 | ||
|
|
d95efc4cdc | ||
|
|
48ca2d6ee6 | ||
|
|
1da98d6d67 | ||
|
|
06a89dd6a9 | ||
|
|
4eee79da17 | ||
|
|
9788338b65 | ||
|
|
fb8da249b6 | ||
|
|
2d8b1df465 | ||
|
|
1ab7ab0750 | ||
|
|
f283254ca0 | ||
|
|
2ea96fdaa7 | ||
|
|
b32d3c354a | ||
|
|
cd57b163a3 | ||
|
|
cc5484f9ed | ||
|
|
bfa9c1a697 | ||
|
|
9a37d412a3 | ||
|
|
dc5ebcf64d | ||
|
|
038f56b048 | ||
|
|
528f79e008 | ||
|
|
75d0ec34f5 | ||
|
|
d70712a18f | ||
|
|
05c5b53c52 | ||
|
|
e98c7dfef1 | ||
|
|
8ba0e16dd9 | ||
|
|
77397f6722 | ||
|
|
c9ba789b7c | ||
|
|
91135b961e | ||
|
|
435790aa03 | ||
|
|
c252d7027e | ||
|
|
9d0c8da310 | ||
|
|
84b3312ebb | ||
|
|
f351d1d133 | ||
|
|
5ca1120223 | ||
|
|
6aa4019221 | ||
|
|
73fb8bda10 | ||
|
|
cc16f84992 | ||
|
|
b4945a150e | ||
|
|
8bcbe74d82 | ||
|
|
69c2429e92 | ||
|
|
88e83dc44a | ||
|
|
87c2126e5a | ||
|
|
abf1ec3eb8 | ||
|
|
139fb41149 | ||
|
|
6c986921d6 | ||
|
|
b8e6d6fc96 | ||
|
|
75e4aee8aa | ||
|
|
abda9f50e1 | ||
|
|
c33ef5170b | ||
|
|
a3cb871928 | ||
|
|
62e965c6fe | ||
|
|
99a031cea4 | ||
|
|
f97b9b9382 | ||
|
|
a8929560df | ||
|
|
22c8c59b5d | ||
|
|
78b043c19c | ||
|
|
fd51fb7d26 | ||
|
|
43c59dad9c | ||
|
|
81b0547610 | ||
|
|
997a16486b | ||
|
|
36ebdab698 | ||
|
|
a6471bca09 | ||
|
|
cb6320ffae | ||
|
|
7cf520cb48 | ||
|
|
7dc922d4bd | ||
|
|
8e57729349 | ||
|
|
66ed87eb1e | ||
|
|
27ffc8eec5 | ||
|
|
e2f50c92e9 | ||
|
|
e8ff42c4b2 | ||
|
|
44ca2cdb55 | ||
|
|
77bbee02f9 | ||
|
|
e4c7b137f1 | ||
|
|
efd088e014 | ||
|
|
2ae266491c | ||
|
|
0912613cfc |
No files matched your search
@@ -9,12 +9,12 @@ body:
|
||||
请确认 Issues 列表无重复的项目。
|
||||
Please make sure that no duplicate issues have already been submitted.
|
||||
- type: textarea
|
||||
id: description
|
||||
id: summary
|
||||
attributes:
|
||||
label: 描述 | Description
|
||||
label: 概述 | Summary
|
||||
description: |
|
||||
请详细描述你想加入的新功能。
|
||||
Please describe the new feature in detail.
|
||||
请介绍你想加入的新功能。
|
||||
Please describe the new feature.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
@@ -26,3 +26,10 @@ body:
|
||||
Please describe why you want to add the feature or enhancement to HMCL.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: 详情 | Description
|
||||
description: |
|
||||
在这里可以补充描述该功能的具体实现方式或建议。(可选)
|
||||
Describe implementation details or suggestions here. (Optional)
|
||||
@@ -1,46 +0,0 @@
|
||||
name: Build HMCLauncher
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'HMCLauncher/**'
|
||||
- '.github/workflows/build-launcher.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'HMCLauncher/**'
|
||||
- '.github/workflows/build-launcher.yml'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Add msbuild to PATH
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
with:
|
||||
msbuild-architecture: x86
|
||||
- name: Build HMCLauncher
|
||||
run: msbuild /p:Configuration=Release /t:Rebuild /verbosity:detailed .\HMCLauncher\
|
||||
- name: Copy HMCLauncher to assets
|
||||
run: Copy-Item .\HMCLauncher\Release\HMCLauncher.exe -Destination .\HMCL\src\main\resources\assets\HMCLauncher.exe
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: '11'
|
||||
java-package: 'jdk+fx'
|
||||
- name: Build with Gradle
|
||||
run: .\gradlew makeExecutables --no-daemon
|
||||
env:
|
||||
MICROSOFT_AUTH_ID: ${{ secrets.MICROSOFT_AUTH_ID }}
|
||||
MICROSOFT_AUTH_SECRET: ${{ secrets.MICROSOFT_AUTH_SECRET }}
|
||||
CURSEFORGE_API_KEY: ${{ secrets.CURSEFORGE_API_KEY }}
|
||||
- name: Get short SHA
|
||||
run: echo "SHORT_SHA=$("${{ github.sha }}".SubString(0, 7))" >> $env:GITHUB_ENV
|
||||
- name: Copy HMCLauncher to libs
|
||||
run: Copy-Item .\HMCLauncher\Release\HMCLauncher.exe -Destination .\HMCL\build\libs\HMCLauncher.exe
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: HMCLauncher-${{ env.SHORT_SHA }}
|
||||
path: HMCL/build/libs/*.exe
|
||||
@@ -5,7 +5,6 @@ on:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'HMCLauncher/**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -14,11 +13,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up JDK 8
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 8
|
||||
java-version: '11'
|
||||
java-package: 'jdk+fx'
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew build --no-daemon
|
||||
|
||||
+1
-1
@@ -42,7 +42,6 @@ minecraft-exported-crash-info*
|
||||
.nb-gradle
|
||||
|
||||
*.exe
|
||||
!/HMCL/src/main/resources/assets/HMCLauncher.exe
|
||||
|
||||
# macos
|
||||
.DS_Store
|
||||
@@ -53,3 +52,4 @@ minecraft-exported-crash-info*
|
||||
# test
|
||||
/hmcl.json
|
||||
/.hmcl.json
|
||||
/.hmcl/
|
||||
+63
-28
@@ -9,7 +9,7 @@ import java.security.spec.PKCS8EncodedKeySpec
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
plugins {
|
||||
id("com.github.johnrengelman.shadow") version "7.1.2"
|
||||
alias(libs.plugins.shadow)
|
||||
}
|
||||
|
||||
val isOfficial = System.getenv("HMCL_SIGNATURE_KEY") != null
|
||||
@@ -33,12 +33,18 @@ val microsoftAuthId = System.getenv("MICROSOFT_AUTH_ID") ?: ""
|
||||
val microsoftAuthSecret = System.getenv("MICROSOFT_AUTH_SECRET") ?: ""
|
||||
val curseForgeApiKey = System.getenv("CURSEFORGE_API_KEY") ?: ""
|
||||
|
||||
val launcherExe = System.getenv("HMCL_LAUNCHER_EXE")
|
||||
|
||||
version = "$versionRoot.$buildNumber"
|
||||
|
||||
dependencies {
|
||||
implementation(project(":HMCLCore"))
|
||||
implementation("libs:JFoenix")
|
||||
implementation("com.twelvemonkeys.imageio:imageio-webp:3.12.0")
|
||||
implementation(libs.twelvemonkeys.imageio.webp)
|
||||
|
||||
if (launcherExe == null) {
|
||||
implementation("org.glavo.hmcl:HMCLauncher:3.6.0.2")
|
||||
}
|
||||
}
|
||||
|
||||
fun digest(algorithm: String, bytes: ByteArray): ByteArray = MessageDigest.getInstance(algorithm).digest(bytes)
|
||||
@@ -89,11 +95,6 @@ val java11 = sourceSets.create("java11") {
|
||||
}
|
||||
|
||||
tasks.getByName<JavaCompile>(java11.compileJavaTaskName) {
|
||||
if (JavaVersion.current() < JavaVersion.VERSION_11) {
|
||||
javaCompiler.set(javaToolchains.compilerFor {
|
||||
languageVersion.set(JavaLanguageVersion.of(11))
|
||||
})
|
||||
}
|
||||
options.compilerArgs.add("--add-exports=java.base/jdk.internal.loader=ALL-UNNAMED")
|
||||
sourceCompatibility = "11"
|
||||
targetCompatibility = "11"
|
||||
@@ -106,7 +107,7 @@ tasks.jar {
|
||||
|
||||
val jarPath = tasks.jar.get().archiveFile.get().asFile
|
||||
|
||||
tasks.getByName<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("shadowJar") {
|
||||
tasks.shadowJar {
|
||||
archiveClassifier.set(null as String?)
|
||||
|
||||
exclude("**/package-info.class")
|
||||
@@ -115,8 +116,14 @@ tasks.getByName<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("sha
|
||||
exclude("META-INF/services/javax.imageio.spi.ImageReaderSpi")
|
||||
exclude("META-INF/services/javax.imageio.spi.ImageInputStreamSpi")
|
||||
|
||||
listOf(
|
||||
"aix-*", "sunos-*", "openbsd-*", "dragonflybsd-*","freebsd-*", "linux-*", "darwin-*",
|
||||
"*-ppc", "*-ppc64le", "*-s390x", "*-armel",
|
||||
).forEach { exclude("com/sun/jna/$it/**") }
|
||||
|
||||
minimize {
|
||||
exclude(dependency("com.google.code.gson:.*:.*"))
|
||||
exclude(dependency("net.java.dev.jna:jna:.*"))
|
||||
exclude(dependency("libs:JFoenix:.*"))
|
||||
}
|
||||
|
||||
@@ -144,8 +151,9 @@ tasks.getByName<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("sha
|
||||
"javafx.controls/com.sun.javafx.scene.control",
|
||||
"javafx.controls/com.sun.javafx.scene.control.behavior",
|
||||
"javafx.controls/javafx.scene.control.skin",
|
||||
"jdk.attach/sun.tools.attach"
|
||||
).joinToString(" ")
|
||||
"jdk.attach/sun.tools.attach",
|
||||
).joinToString(" "),
|
||||
"Enable-Native-Access" to "ALL-UNNAMED"
|
||||
)
|
||||
|
||||
System.getenv("GITHUB_SHA")?.also {
|
||||
@@ -153,23 +161,18 @@ tasks.getByName<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("sha
|
||||
}
|
||||
}
|
||||
|
||||
if (launcherExe != null) {
|
||||
into("assets") {
|
||||
from(file(launcherExe))
|
||||
}
|
||||
}
|
||||
|
||||
doLast {
|
||||
attachSignature(jarPath)
|
||||
createChecksum(jarPath)
|
||||
}
|
||||
}
|
||||
|
||||
fun createExecutable(suffix: String, header: String) {
|
||||
val output = File(jarPath.parentFile, jarPath.nameWithoutExtension + '.' + suffix)
|
||||
|
||||
output.outputStream().use {
|
||||
it.write(File(project.projectDir, header).readBytes())
|
||||
it.write(jarPath.readBytes())
|
||||
}
|
||||
|
||||
createChecksum(output)
|
||||
}
|
||||
|
||||
tasks.processResources {
|
||||
into("META-INF/versions/11") {
|
||||
from(sourceSets["java11"].output)
|
||||
@@ -177,11 +180,31 @@ tasks.processResources {
|
||||
dependsOn(tasks["java11Classes"])
|
||||
}
|
||||
|
||||
val makeExecutables = tasks.create("makeExecutables") {
|
||||
val makeExecutables by tasks.registering {
|
||||
val extensions = listOf("exe", "sh")
|
||||
|
||||
dependsOn(tasks.jar)
|
||||
|
||||
inputs.file(jarPath)
|
||||
outputs.files(extensions.map { File(jarPath.parentFile, jarPath.nameWithoutExtension + '.' + it) })
|
||||
|
||||
doLast {
|
||||
createExecutable("exe", "src/main/resources/assets/HMCLauncher.exe")
|
||||
createExecutable("sh", "src/main/resources/assets/HMCLauncher.sh")
|
||||
val jarContent = jarPath.readBytes()
|
||||
|
||||
ZipFile(jarPath).use { zipFile ->
|
||||
for (extension in extensions) {
|
||||
val output = File(jarPath.parentFile, jarPath.nameWithoutExtension + '.' + extension)
|
||||
val entry = zipFile.getEntry("assets/HMCLauncher.$extension")
|
||||
?: throw GradleException("HMCLauncher.$extension not found")
|
||||
|
||||
output.outputStream().use { outputStream ->
|
||||
zipFile.getInputStream(entry).use { it.copyTo(outputStream) }
|
||||
outputStream.write(jarContent)
|
||||
}
|
||||
|
||||
createChecksum(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,9 +212,9 @@ tasks.build {
|
||||
dependsOn(makeExecutables)
|
||||
}
|
||||
|
||||
fun parseToolOptions(options: String?): List<String> {
|
||||
fun parseToolOptions(options: String?): MutableList<String> {
|
||||
if (options == null)
|
||||
return listOf()
|
||||
return mutableListOf()
|
||||
|
||||
val builder = StringBuilder()
|
||||
val result = mutableListOf<String>()
|
||||
@@ -239,7 +262,7 @@ fun parseToolOptions(options: String?): List<String> {
|
||||
return result
|
||||
}
|
||||
|
||||
tasks.create<JavaExec>("run") {
|
||||
tasks.register<JavaExec>("run") {
|
||||
dependsOn(tasks.jar)
|
||||
|
||||
group = "application"
|
||||
@@ -248,10 +271,22 @@ tasks.create<JavaExec>("run") {
|
||||
workingDir = rootProject.rootDir
|
||||
|
||||
val vmOptions = parseToolOptions(System.getenv("HMCL_JAVA_OPTS"))
|
||||
if (vmOptions.none { it.startsWith("-Dhmcl.offline.auth.restricted=") })
|
||||
vmOptions += "-Dhmcl.offline.auth.restricted=false"
|
||||
|
||||
jvmArgs(vmOptions)
|
||||
|
||||
val hmclJavaHome = System.getenv("HMCL_JAVA_HOME")
|
||||
if (hmclJavaHome != null) {
|
||||
this.executable(
|
||||
file(hmclJavaHome).resolve("bin")
|
||||
.resolve(if (System.getProperty("os.name").lowercase().startsWith("windows")) "java.exe" else "java")
|
||||
)
|
||||
}
|
||||
|
||||
doFirst {
|
||||
logger.quiet("HMCL_JAVA_OPTS: $vmOptions")
|
||||
logger.quiet("HMCL_JAVA_OPTS: {}", vmOptions)
|
||||
logger.quiet("HMCL_JAVA_HOME: {}", hmclJavaHome ?: System.getProperty("java.home"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 16 KiB |
@@ -38,11 +38,14 @@ import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.JarUtils;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.CommandBuilder;
|
||||
import org.jackhuang.hmcl.util.platform.NativeUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.SystemInfo;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.MemoryPoolMXBean;
|
||||
import java.net.CookieHandler;
|
||||
import java.net.CookieManager;
|
||||
import java.nio.file.Files;
|
||||
@@ -53,6 +56,7 @@ import java.util.Collections;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.DataSizeUnit.MEGABYTES;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
@@ -86,7 +90,7 @@ public final class Launcher extends Application {
|
||||
}
|
||||
|
||||
// https://lapcatsoftware.com/articles/app-translocation.html
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS
|
||||
&& ConfigHolder.isNewlyCreated()
|
||||
&& System.getProperty("user.dir").startsWith("/private/var/folders/")) {
|
||||
if (showAlert(AlertType.WARNING, i18n("fatal.mac_app_translocation"), ButtonType.YES, ButtonType.NO) == ButtonType.NO)
|
||||
@@ -100,7 +104,7 @@ public final class Launcher extends Application {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Metadata.HMCL_DIRECTORY.toString().indexOf('=') >= 0) {
|
||||
if (Metadata.HMCL_CURRENT_DIRECTORY.toString().indexOf('=') >= 0) {
|
||||
Main.showWarningAndContinue(i18n("fatal.illegal_char"));
|
||||
}
|
||||
|
||||
@@ -148,7 +152,7 @@ public final class Launcher extends Application {
|
||||
|| configPath.startsWith("/var/cache/")
|
||||
|| configPath.startsWith("/dev/shm/")
|
||||
|| configPath.contains("/Trash/");
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX) {
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {
|
||||
return configPath.startsWith("/var/folders/")
|
||||
|| configPath.startsWith("/private/var/folders/")
|
||||
|| configPath.startsWith("/tmp/")
|
||||
@@ -186,8 +190,10 @@ public final class Launcher extends Application {
|
||||
|
||||
ArrayList<String> files = new ArrayList<>();
|
||||
files.add(ConfigHolder.configLocation().toString());
|
||||
if (Files.exists(Metadata.HMCL_DIRECTORY))
|
||||
files.add(Metadata.HMCL_DIRECTORY.toString());
|
||||
if (Files.exists(Metadata.HMCL_GLOBAL_DIRECTORY))
|
||||
files.add(Metadata.HMCL_GLOBAL_DIRECTORY.toString());
|
||||
if (Files.exists(Metadata.HMCL_CURRENT_DIRECTORY))
|
||||
files.add(Metadata.HMCL_CURRENT_DIRECTORY.toString());
|
||||
|
||||
Path mcDir = Paths.get(".minecraft").toAbsolutePath().normalize();
|
||||
if (Files.exists(mcDir))
|
||||
@@ -229,25 +235,39 @@ public final class Launcher extends Application {
|
||||
LOG.info("Processor Identifier: " + System.getenv("PROCESSOR_IDENTIFIER"));
|
||||
}
|
||||
LOG.info("System Architecture: " + Architecture.SYSTEM_ARCH.getDisplayName());
|
||||
LOG.info("Native Encoding: " + OperatingSystem.NATIVE_CHARSET);
|
||||
LOG.info("JNU Encoding: " + System.getProperty("sun.jnu.encoding"));
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
LOG.info("Code Page: " + OperatingSystem.CODE_PAGE);
|
||||
}
|
||||
LOG.info("Java Architecture: " + Architecture.CURRENT_ARCH.getDisplayName());
|
||||
LOG.info("Java Version: " + System.getProperty("java.version") + ", " + System.getProperty("java.vendor"));
|
||||
LOG.info("Java VM Version: " + System.getProperty("java.vm.name") + " (" + System.getProperty("java.vm.info") + "), " + System.getProperty("java.vm.vendor"));
|
||||
LOG.info("Java Home: " + System.getProperty("java.home"));
|
||||
LOG.info("Current Directory: " + System.getProperty("user.dir"));
|
||||
LOG.info("HMCL Directory: " + Metadata.HMCL_DIRECTORY);
|
||||
LOG.info("Current Directory: " + Metadata.CURRENT_DIRECTORY);
|
||||
LOG.info("HMCL Global Directory: " + Metadata.HMCL_GLOBAL_DIRECTORY);
|
||||
LOG.info("HMCL Current Directory: " + Metadata.HMCL_CURRENT_DIRECTORY);
|
||||
LOG.info("HMCL Jar Path: " + Lang.requireNonNullElse(JarUtils.thisJarPath(), "Not Found"));
|
||||
LOG.info("HMCL Log File: " + Lang.requireNonNullElse(LOG.getLogFile(), "In Memory"));
|
||||
LOG.info("Memory: " + Runtime.getRuntime().maxMemory() / 1024 / 1024 + "MB");
|
||||
LOG.info("Physical memory: " + OperatingSystem.TOTAL_MEMORY + " MB");
|
||||
LOG.info("Metaspace: " + ManagementFactory.getMemoryPoolMXBeans().stream()
|
||||
.filter(bean -> bean.getName().equals("Metaspace"))
|
||||
.findAny()
|
||||
.map(bean -> bean.getUsage().getUsed() / 1024 / 1024 + "MB")
|
||||
.orElse("Unknown"));
|
||||
LOG.info("JVM Max Memory: " + MEGABYTES.formatBytes(Runtime.getRuntime().maxMemory()));
|
||||
try {
|
||||
for (MemoryPoolMXBean bean : ManagementFactory.getMemoryPoolMXBeans()) {
|
||||
if ("Metaspace".equals(bean.getName())) {
|
||||
long bytes = bean.getUsage().getUsed();
|
||||
LOG.info("Metaspace: " + MEGABYTES.formatBytes(bytes));
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (NoClassDefFoundError ignored) {
|
||||
}
|
||||
LOG.info("Native Backend: " + (NativeUtils.USE_JNA ? "JNA" : "None"));
|
||||
if (OperatingSystem.CURRENT_OS.isLinuxOrBSD()) {
|
||||
LOG.info("XDG Session Type: " + System.getenv("XDG_SESSION_TYPE"));
|
||||
LOG.info("XDG Current Desktop: " + System.getenv("XDG_CURRENT_DESKTOP"));
|
||||
}
|
||||
|
||||
Lang.thread(SystemInfo::initialize, "Detection System Information", true);
|
||||
|
||||
launch(Launcher.class, args);
|
||||
} catch (Throwable e) { // Fucking JavaFX will suppress the exception and will break our crash reporter.
|
||||
CRASH_REPORTER.uncaughtException(Thread.currentThread(), e);
|
||||
@@ -276,7 +296,7 @@ public final class Launcher extends Application {
|
||||
Controllers.getStage().close();
|
||||
Schedulers.shutdown();
|
||||
Controllers.shutdown();
|
||||
Lang.executeDelayed(OperatingSystem::forceGC, TimeUnit.SECONDS, 5, true);
|
||||
Lang.executeDelayed(System::gc, TimeUnit.SECONDS, 5, true);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.Alert;
|
||||
import org.jackhuang.hmcl.ui.AwtUtils;
|
||||
import org.jackhuang.hmcl.util.ModuleHelper;
|
||||
import org.jackhuang.hmcl.util.SelfDependencyPatcher;
|
||||
import org.jackhuang.hmcl.ui.SwingUtils;
|
||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
@@ -31,6 +32,7 @@ import javax.net.ssl.TrustManagerFactory;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
@@ -56,7 +58,8 @@ public final class Main {
|
||||
System.getProperties().putIfAbsent("javafx.autoproxy.disable", "true");
|
||||
System.getProperties().putIfAbsent("http.agent", "HMCL/" + Metadata.VERSION);
|
||||
|
||||
LOG.start(Metadata.HMCL_DIRECTORY.resolve("logs"));
|
||||
createHMCLDirectories();
|
||||
LOG.start(Metadata.HMCL_CURRENT_DIRECTORY.resolve("logs"));
|
||||
|
||||
checkDirectoryPath();
|
||||
|
||||
@@ -64,11 +67,13 @@ public final class Main {
|
||||
// This environment check will take ~300ms
|
||||
thread(Main::fixLetsEncrypt, "CA Certificate Check", true);
|
||||
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX)
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS)
|
||||
initIcon();
|
||||
|
||||
checkJavaFX();
|
||||
verifyJavaFX();
|
||||
addEnableNativeAccess();
|
||||
enableUnsafeMemoryAccess();
|
||||
|
||||
Launcher.main(args);
|
||||
}
|
||||
@@ -78,8 +83,34 @@ public final class Main {
|
||||
System.exit(exitCode);
|
||||
}
|
||||
|
||||
private static void createHMCLDirectories() {
|
||||
if (!Files.isDirectory(Metadata.HMCL_CURRENT_DIRECTORY)) {
|
||||
try {
|
||||
Files.createDirectories(Metadata.HMCL_CURRENT_DIRECTORY);
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
try {
|
||||
Files.setAttribute(Metadata.HMCL_CURRENT_DIRECTORY, "dos:hidden", true);
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to set hidden attribute of " + Metadata.HMCL_CURRENT_DIRECTORY, e);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace(System.err);
|
||||
showErrorAndExit(i18n("fatal.create_hmcl_current_directory_failure", Metadata.HMCL_CURRENT_DIRECTORY));
|
||||
}
|
||||
}
|
||||
|
||||
if (!Files.isDirectory(Metadata.HMCL_GLOBAL_DIRECTORY)) {
|
||||
try {
|
||||
Files.createDirectories(Metadata.HMCL_GLOBAL_DIRECTORY);
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to create HMCL global directory " + Metadata.HMCL_GLOBAL_DIRECTORY, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void initIcon() {
|
||||
java.awt.Image image = java.awt.Toolkit.getDefaultToolkit().getImage(Main.class.getResource("/assets/img/icon@8x.png"));
|
||||
java.awt.Image image = java.awt.Toolkit.getDefaultToolkit().getImage(Main.class.getResource("/assets/img/icon-mac.png"));
|
||||
AwtUtils.setAppleIcon(image);
|
||||
}
|
||||
|
||||
@@ -116,10 +147,36 @@ public final class Main {
|
||||
Class.forName("javafx.stage.Stage"); // javafx.graphics
|
||||
Class.forName("javafx.scene.control.Skin"); // javafx.controls
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(System.err);
|
||||
showErrorAndExit(i18n("fatal.javafx.incomplete"));
|
||||
}
|
||||
}
|
||||
|
||||
private static void addEnableNativeAccess() {
|
||||
if (JavaRuntime.CURRENT_VERSION > 21) {
|
||||
try {
|
||||
ModuleHelper.addEnableNativeAccess(Class.forName("javafx.stage.Stage")); // javafx.graphics
|
||||
} catch (ClassNotFoundException e) {
|
||||
e.printStackTrace(System.err);
|
||||
showErrorAndExit(i18n("fatal.javafx.incomplete"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void enableUnsafeMemoryAccess() {
|
||||
// https://openjdk.org/jeps/498
|
||||
if (JavaRuntime.CURRENT_VERSION == 24 || JavaRuntime.CURRENT_VERSION == 25) {
|
||||
try {
|
||||
Class<?> clazz = Class.forName("sun.misc.Unsafe");
|
||||
Method trySetMemoryAccessWarned = clazz.getDeclaredMethod("trySetMemoryAccessWarned");
|
||||
trySetMemoryAccessWarned.setAccessible(true);
|
||||
trySetMemoryAccessWarned.invoke(null);
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that a fatal error has occurred, and that the application cannot start.
|
||||
*/
|
||||
|
||||
@@ -28,7 +28,8 @@ import java.nio.file.Paths;
|
||||
* Stores metadata about this application.
|
||||
*/
|
||||
public final class Metadata {
|
||||
private Metadata() {}
|
||||
private Metadata() {
|
||||
}
|
||||
|
||||
public static final String NAME = "HMCL";
|
||||
public static final String FULL_NAME = "Hello Minecraft! Launcher";
|
||||
@@ -37,18 +38,25 @@ public final class Metadata {
|
||||
public static final String TITLE = NAME + " " + VERSION;
|
||||
public static final String FULL_TITLE = FULL_NAME + " v" + VERSION;
|
||||
|
||||
public static final String HMCL_UPDATE_URL = System.getProperty("hmcl.update_source.override", "https://hmcl.huangyuhui.net/api/update_link");
|
||||
public static final String CONTACT_URL = "https://docs.hmcl.net/help.html";
|
||||
public static final String HELP_URL = "https://docs.hmcl.net";
|
||||
public static final String CHANGELOG_URL = "https://docs.hmcl.net/changelog/";
|
||||
public static final String PUBLISH_URL = "https://hmcl.huangyuhui.net";
|
||||
public static final String EULA_URL = "https://docs.hmcl.net/eula/hmcl.html";
|
||||
public static final String ABOUT_URL = PUBLISH_URL + "/about";
|
||||
public static final String DOWNLOAD_URL = PUBLISH_URL + "/download";
|
||||
public static final String HMCL_UPDATE_URL = System.getProperty("hmcl.update_source.override", PUBLISH_URL + "/api/update_link");
|
||||
|
||||
public static final String DOCS_URL = "https://docs.hmcl.net";
|
||||
public static final String CONTACT_URL = DOCS_URL + "/help.html";
|
||||
public static final String CHANGELOG_URL = DOCS_URL + "/changelog/";
|
||||
public static final String EULA_URL = DOCS_URL + "/eula/hmcl.html";
|
||||
public static final String GROUPS_URL = DOCS_URL + "/groups.html";
|
||||
|
||||
public static final String BUILD_CHANNEL = JarUtils.getManifestAttribute("Build-Channel", "nightly");
|
||||
public static final String GITHUB_SHA = JarUtils.getManifestAttribute("GitHub-SHA", null);
|
||||
|
||||
public static final Path CURRENT_DIRECTORY = Paths.get(System.getProperty("user.dir")).toAbsolutePath().normalize();
|
||||
public static final Path MINECRAFT_DIRECTORY = OperatingSystem.getWorkingDirectory("minecraft");
|
||||
public static final Path HMCL_DIRECTORY;
|
||||
public static final Path HMCL_GLOBAL_DIRECTORY;
|
||||
public static final Path HMCL_CURRENT_DIRECTORY;
|
||||
public static final Path DEPENDENCIES_DIRECTORY;
|
||||
|
||||
static {
|
||||
String hmclHome = System.getProperty("hmcl.home");
|
||||
@@ -56,16 +64,22 @@ public final class Metadata {
|
||||
if (OperatingSystem.CURRENT_OS.isLinuxOrBSD()) {
|
||||
String xdgData = System.getenv("XDG_DATA_HOME");
|
||||
if (StringUtils.isNotBlank(xdgData)) {
|
||||
HMCL_DIRECTORY = Paths.get(xdgData, "hmcl").toAbsolutePath();
|
||||
HMCL_GLOBAL_DIRECTORY = Paths.get(xdgData, "hmcl").toAbsolutePath().normalize();
|
||||
} else {
|
||||
HMCL_DIRECTORY = Paths.get(System.getProperty("user.home", "."), ".local", "share", "hmcl").toAbsolutePath();
|
||||
HMCL_GLOBAL_DIRECTORY = Paths.get(System.getProperty("user.home"), ".local", "share", "hmcl").toAbsolutePath().normalize();
|
||||
}
|
||||
} else {
|
||||
HMCL_DIRECTORY = OperatingSystem.getWorkingDirectory("hmcl");
|
||||
HMCL_GLOBAL_DIRECTORY = OperatingSystem.getWorkingDirectory("hmcl");
|
||||
}
|
||||
} else {
|
||||
HMCL_DIRECTORY = Paths.get(hmclHome).toAbsolutePath().normalize();
|
||||
HMCL_GLOBAL_DIRECTORY = Paths.get(hmclHome).toAbsolutePath().normalize();
|
||||
}
|
||||
|
||||
String hmclCurrentDir = System.getProperty("hmcl.dir");
|
||||
HMCL_CURRENT_DIRECTORY = hmclCurrentDir != null
|
||||
? Paths.get(hmclCurrentDir).toAbsolutePath().normalize()
|
||||
: CURRENT_DIRECTORY.resolve(".hmcl");
|
||||
DEPENDENCIES_DIRECTORY = HMCL_CURRENT_DIRECTORY.resolve("dependencies");
|
||||
}
|
||||
|
||||
public static boolean isStable() {
|
||||
|
||||
@@ -30,7 +30,6 @@ import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.mod.ModpackConfiguration;
|
||||
import org.jackhuang.hmcl.mod.ModpackProvider;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.ProxyManager;
|
||||
import org.jackhuang.hmcl.setting.VersionIconType;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
@@ -40,6 +39,7 @@ import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.SystemInfo;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -150,18 +150,24 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
|
||||
Version fromVersion = getVersion(srcId);
|
||||
|
||||
if (Files.exists(dstDir)) throw new IOException("Version exists");
|
||||
FileUtils.copyDirectory(srcDir, dstDir);
|
||||
List<String> blackList = new ArrayList<>(ModAdviser.MODPACK_BLACK_LIST);
|
||||
blackList.add(srcId + ".jar");
|
||||
blackList.add(srcId + ".json");
|
||||
if (!copySaves)
|
||||
blackList.add("saves");
|
||||
|
||||
Path fromJson = dstDir.resolve(srcId + ".json");
|
||||
Path fromJar = dstDir.resolve(srcId + ".jar");
|
||||
if (Files.exists(dstDir)) throw new IOException("Version exists");
|
||||
FileUtils.copyDirectory(srcDir, dstDir, path -> Modpack.acceptFile(path, blackList, null));
|
||||
|
||||
Path fromJson = srcDir.resolve(srcId + ".json");
|
||||
Path fromJar = srcDir.resolve(srcId + ".jar");
|
||||
Path toJson = dstDir.resolve(dstId + ".json");
|
||||
Path toJar = dstDir.resolve(dstId + ".jar");
|
||||
|
||||
if (Files.exists(fromJar)) {
|
||||
Files.move(fromJar, toJar);
|
||||
Files.copy(fromJar, toJar);
|
||||
}
|
||||
Files.move(fromJson, toJson);
|
||||
Files.copy(fromJson, toJson);
|
||||
|
||||
FileUtils.writeText(toJson.toFile(), JsonUtils.GSON.toJson(fromVersion.setId(dstId)));
|
||||
|
||||
@@ -175,12 +181,6 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
File srcGameDir = getRunDirectory(srcId);
|
||||
File dstGameDir = getRunDirectory(dstId);
|
||||
|
||||
List<String> blackList = new ArrayList<>(ModAdviser.MODPACK_BLACK_LIST);
|
||||
blackList.add(srcId + ".jar");
|
||||
blackList.add(srcId + ".json");
|
||||
if (!copySaves)
|
||||
blackList.add("saves");
|
||||
|
||||
if (originalGameDirType != GameDirectoryType.VERSION_FOLDER)
|
||||
FileUtils.copyDirectory(srcGameDir.toPath(), dstGameDir.toPath(), path -> Modpack.acceptFile(path, blackList, null));
|
||||
}
|
||||
@@ -203,6 +203,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
|
||||
/**
|
||||
* Create new version setting if version id has no version setting.
|
||||
*
|
||||
* @param id the version id.
|
||||
* @return new version setting, null if given version does not exist.
|
||||
*/
|
||||
@@ -225,7 +226,6 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
* Get the version setting for version id.
|
||||
*
|
||||
* @param id version id
|
||||
*
|
||||
* @return corresponding version setting, null if the version has no its own version setting.
|
||||
*/
|
||||
@Nullable
|
||||
@@ -349,6 +349,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
|
||||
/**
|
||||
* Make version use self version settings instead of the global one.
|
||||
*
|
||||
* @param id the version id.
|
||||
* @return specialized version setting, null if given version does not exist.
|
||||
*/
|
||||
@@ -383,7 +384,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
.setOverrideJavaArguments(StringUtils.tokenize(vs.getJavaArgs()))
|
||||
.setMaxMemory(vs.isNoJVMArgs() && vs.isAutoMemory() ? null : (int)(getAllocatedMemory(
|
||||
vs.getMaxMemory() * 1024L * 1024L,
|
||||
OperatingSystem.getPhysicalMemoryStatus().getAvailable(),
|
||||
SystemInfo.getPhysicalMemoryStatus().getAvailable(),
|
||||
vs.isAutoMemory()
|
||||
) / 1024 / 1024))
|
||||
.setMinMemory(vs.getMinMemory())
|
||||
@@ -415,8 +416,12 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
.setDaemon(!makeLaunchScript && vs.getLauncherVisibility().isDaemon())
|
||||
.setJavaAgents(javaAgents)
|
||||
.setJavaArguments(javaArguments);
|
||||
|
||||
if (config().hasProxy()) {
|
||||
builder.setProxy(ProxyManager.getProxy());
|
||||
builder.setProxyType(config().getProxyType());
|
||||
builder.setProxyHost(config().getProxyHost());
|
||||
builder.setProxyPort(config().getProxyPort());
|
||||
|
||||
if (config().hasProxyAuth()) {
|
||||
builder.setProxyUser(config().getProxyUser());
|
||||
builder.setProxyPass(config().getProxyPass());
|
||||
@@ -509,16 +514,16 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
|
||||
public static long getAllocatedMemory(long minimum, long available, boolean auto) {
|
||||
if (auto) {
|
||||
available -= 384 * 1024 * 1024; // Reserve 384MiB memory for off-heap memory and HMCL itself
|
||||
available -= 512 * 1024 * 1024; // Reserve 512 MiB memory for off-heap memory and HMCL itself
|
||||
if (available <= 0) {
|
||||
return minimum;
|
||||
}
|
||||
|
||||
final long threshold = 8L * 1024 * 1024 * 1024;
|
||||
final long threshold = 8L * 1024 * 1024 * 1024; // 8 GiB
|
||||
final long suggested = Math.min(available <= threshold
|
||||
? (long) (available * 0.8)
|
||||
: (long) (threshold * 0.8 + (available - threshold) * 0.2),
|
||||
16384L * 1024 * 1024);
|
||||
16L * 1024 * 1024 * 1024);
|
||||
return Math.max(minimum, suggested);
|
||||
} else {
|
||||
return minimum;
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import org.apache.commons.compress.archivers.zip.ZipFile;
|
||||
import kala.compress.archivers.zip.ZipArchiveReader;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.mod.MismatchedModpackTypeException;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
@@ -64,7 +64,7 @@ public final class HMCLModpackProvider implements ModpackProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Modpack readManifest(ZipFile file, Path path, Charset encoding) throws IOException, JsonParseException {
|
||||
public Modpack readManifest(ZipArchiveReader file, Path path, Charset encoding) throws IOException, JsonParseException {
|
||||
String manifestJson = CompressingUtils.readTextZipEntry(file, "modpack.json");
|
||||
Modpack manifest = JsonUtils.fromNonNullJson(manifestJson, HMCLModpack.class).setEncoding(encoding);
|
||||
String gameJson = CompressingUtils.readTextZipEntry(file, "minecraft/pack.json");
|
||||
|
||||
@@ -63,6 +63,7 @@ import java.util.stream.Collectors;
|
||||
import static javafx.application.Platform.runLater;
|
||||
import static javafx.application.Platform.setImplicitExit;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.DataSizeUnit.MEGABYTES;
|
||||
import static org.jackhuang.hmcl.util.Lang.resolveException;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
@@ -553,8 +554,9 @@ public final class LauncherHelper {
|
||||
}
|
||||
|
||||
// Cannot allocate too much memory exceeding free space.
|
||||
if (OperatingSystem.TOTAL_MEMORY > 0 && OperatingSystem.TOTAL_MEMORY < setting.getMaxMemory()) {
|
||||
suggestions.add(i18n("launch.advice.not_enough_space", OperatingSystem.TOTAL_MEMORY));
|
||||
long totalMemorySizeMB = (long) MEGABYTES.convertFromBytes(SystemInfo.getTotalMemorySize());
|
||||
if (totalMemorySizeMB > 0 && totalMemorySizeMB < setting.getMaxMemory()) {
|
||||
suggestions.add(i18n("launch.advice.not_enough_space", totalMemorySizeMB));
|
||||
}
|
||||
|
||||
VersionNumber forgeVersion = analyzer.getVersion(LibraryAnalyzer.LibraryType.FORGE)
|
||||
@@ -894,9 +896,6 @@ public final class LauncherHelper {
|
||||
|
||||
}
|
||||
|
||||
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<ManagedProcess> PROCESSES = new ConcurrentLinkedQueue<>();
|
||||
|
||||
public static void stopManagedProcesses() {
|
||||
|
||||
@@ -17,12 +17,13 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
import org.jackhuang.hmcl.util.logging.Logger;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.Zipper;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.lang.management.ManagementFactory;
|
||||
@@ -93,9 +94,8 @@ public final class LogExporter {
|
||||
if (Files.isRegularFile(file)) {
|
||||
FileTime time = Files.readAttributes(file, BasicFileAttributes.class).lastModifiedTime();
|
||||
if (time.toMillis() >= processStartTime) {
|
||||
try {
|
||||
String crashLog = Logger.filterForbiddenToken(FileUtils.readText(file, OperatingSystem.NATIVE_CHARSET));
|
||||
zipper.putTextFile(crashLog, file.getFileName().toString());
|
||||
try (BufferedReader reader = IOUtils.newBufferedReaderMaybeNativeEncoding(file)) {
|
||||
zipper.putLines(reader.lines().map(Logger::filterForbiddenToken), file.getFileName().toString());
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to read log file: " + file, e);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import org.apache.commons.compress.archivers.zip.ZipFile;
|
||||
import kala.compress.archivers.zip.ZipArchiveReader;
|
||||
import org.jackhuang.hmcl.mod.*;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseModpackProvider;
|
||||
import org.jackhuang.hmcl.mod.mcbbs.McbbsModpackManifest;
|
||||
@@ -83,7 +83,7 @@ public final class ModpackHelper {
|
||||
}
|
||||
|
||||
public static Modpack readModpackManifest(Path file, Charset charset) throws UnsupportedModpackException, ManuallyCreatedModpackException {
|
||||
try (ZipFile zipFile = CompressingUtils.openZipFile(file, charset)) {
|
||||
try (ZipArchiveReader zipFile = CompressingUtils.openZipFile(file, charset)) {
|
||||
// Order for trying detecting manifest is necessary here.
|
||||
// Do not change to iterating providers.
|
||||
for (ModpackProvider provider : new ModpackProvider[]{
|
||||
|
||||
@@ -87,7 +87,7 @@ public final class TexturesLoader {
|
||||
}
|
||||
|
||||
private static final ThreadPoolExecutor POOL = threadPool("TexturesDownload", true, 2, 10, TimeUnit.SECONDS);
|
||||
private static final Path TEXTURES_DIR = Metadata.HMCL_DIRECTORY.resolve("skins");
|
||||
private static final Path TEXTURES_DIR = Metadata.HMCL_GLOBAL_DIRECTORY.resolve("skins");
|
||||
|
||||
private static Path getTexturePath(Texture texture) {
|
||||
String url = texture.getUrl();
|
||||
|
||||
@@ -83,7 +83,7 @@ public final class HMCLJavaRepository implements JavaRepository {
|
||||
try {
|
||||
return JavaManager.getExecutable(javaDir).toRealPath();
|
||||
} catch (IOException ignored) {
|
||||
if (platform.getOperatingSystem() == OperatingSystem.OSX) {
|
||||
if (platform.getOperatingSystem() == OperatingSystem.MACOS) {
|
||||
try {
|
||||
return JavaManager.getMacExecutable(javaDir).toRealPath();
|
||||
} catch (IOException ignored1) {
|
||||
@@ -98,14 +98,8 @@ public final class HMCLJavaRepository implements JavaRepository {
|
||||
return getJavaExecutable(platform, MOJANG_JAVA_PREFIX + gameJavaVersion.getComponent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<JavaRuntime> getAllJava(Platform platform) {
|
||||
Path root = getPlatformRoot(platform);
|
||||
if (!Files.isDirectory(root))
|
||||
return Collections.emptyList();
|
||||
|
||||
ArrayList<JavaRuntime> list = new ArrayList<>();
|
||||
try (DirectoryStream<Path> stream = Files.newDirectoryStream(root)) {
|
||||
private static void getAllJava(List<JavaRuntime> list, Platform platform, Path platformRoot, boolean isManaged) {
|
||||
try (DirectoryStream<Path> stream = Files.newDirectoryStream(platformRoot)) {
|
||||
for (Path file : stream) {
|
||||
try {
|
||||
String name = file.getFileName().toString();
|
||||
@@ -115,7 +109,7 @@ public final class HMCLJavaRepository implements JavaRepository {
|
||||
try {
|
||||
executable = JavaManager.getExecutable(javaDir).toRealPath();
|
||||
} catch (IOException e) {
|
||||
if (platform.getOperatingSystem() == OperatingSystem.OSX)
|
||||
if (platform.getOperatingSystem() == OperatingSystem.MACOS)
|
||||
executable = JavaManager.getMacExecutable(javaDir).toRealPath();
|
||||
else
|
||||
throw e;
|
||||
@@ -127,7 +121,7 @@ public final class HMCLJavaRepository implements JavaRepository {
|
||||
manifest = JsonUtils.fromJsonFully(input, JavaManifest.class);
|
||||
}
|
||||
|
||||
list.add(JavaRuntime.of(executable, manifest.getInfo(), true));
|
||||
list.add(JavaRuntime.of(executable, manifest.getInfo(), isManaged));
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
@@ -137,6 +131,23 @@ public final class HMCLJavaRepository implements JavaRepository {
|
||||
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<JavaRuntime> getAllJava(Platform platform) {
|
||||
Path platformRoot = getPlatformRoot(platform);
|
||||
if (!Files.isDirectory(platformRoot))
|
||||
return Collections.emptyList();
|
||||
|
||||
ArrayList<JavaRuntime> list = new ArrayList<>();
|
||||
|
||||
getAllJava(list, platform, platformRoot, true);
|
||||
if (platform.getOperatingSystem() == OperatingSystem.MACOS) {
|
||||
platformRoot = root.resolve(platform.getOperatingSystem().getMojangName() + "-" + platform.getArchitecture().getCheckedName());
|
||||
if (Files.isDirectory(platformRoot))
|
||||
getAllJava(list, platform, platformRoot, false);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
@@ -149,7 +160,7 @@ public final class HMCLJavaRepository implements JavaRepository {
|
||||
try {
|
||||
executable = JavaManager.getExecutable(javaDir).toRealPath();
|
||||
} catch (IOException e) {
|
||||
if (platform.getOperatingSystem() == OperatingSystem.OSX)
|
||||
if (platform.getOperatingSystem() == OperatingSystem.MACOS)
|
||||
executable = JavaManager.getMacExecutable(javaDir).toRealPath();
|
||||
else
|
||||
throw e;
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.java;
|
||||
|
||||
import org.apache.commons.compress.archivers.ArchiveEntry;
|
||||
import kala.compress.archivers.ArchiveEntry;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.DigestUtils;
|
||||
import org.jackhuang.hmcl.util.Hex;
|
||||
|
||||
@@ -31,18 +31,18 @@ import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.util.CacheRepository;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.Platform;
|
||||
import org.jackhuang.hmcl.util.platform.UnsupportedPlatformException;
|
||||
import org.jackhuang.hmcl.util.platform.windows.WinReg;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
@@ -58,7 +58,7 @@ public final class JavaManager {
|
||||
private JavaManager() {
|
||||
}
|
||||
|
||||
public static final HMCLJavaRepository REPOSITORY = new HMCLJavaRepository(Metadata.HMCL_DIRECTORY.resolve("java"));
|
||||
public static final HMCLJavaRepository REPOSITORY = new HMCLJavaRepository(Metadata.HMCL_GLOBAL_DIRECTORY.resolve("java"));
|
||||
|
||||
public static String getMojangJavaPlatform(Platform platform) {
|
||||
if (platform.getOperatingSystem() == OperatingSystem.WINDOWS) {
|
||||
@@ -75,7 +75,7 @@ public final class JavaManager {
|
||||
} else if (Architecture.SYSTEM_ARCH == Architecture.X86_64) {
|
||||
return "linux";
|
||||
}
|
||||
} else if (platform.getOperatingSystem() == OperatingSystem.OSX) {
|
||||
} else if (platform.getOperatingSystem() == OperatingSystem.MACOS) {
|
||||
if (Architecture.SYSTEM_ARCH == Architecture.X86_64) {
|
||||
return "mac-os";
|
||||
} else if (Architecture.SYSTEM_ARCH == Architecture.ARM64) {
|
||||
@@ -113,7 +113,7 @@ public final class JavaManager {
|
||||
if (Architecture.SYSTEM_ARCH == Architecture.X86_64)
|
||||
return architecture == Architecture.X86;
|
||||
break;
|
||||
case OSX:
|
||||
case MACOS:
|
||||
if (Architecture.SYSTEM_ARCH == Architecture.ARM64)
|
||||
return architecture == Architecture.X86_64;
|
||||
break;
|
||||
@@ -279,7 +279,7 @@ public final class JavaManager {
|
||||
LibraryAnalyzer analyzer = version != null ? LibraryAnalyzer.analyze(version, gameVersion != null ? gameVersion.toString() : null) : null;
|
||||
|
||||
boolean forceX86 = Architecture.SYSTEM_ARCH == Architecture.ARM64
|
||||
&& (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS || OperatingSystem.CURRENT_OS == OperatingSystem.OSX)
|
||||
&& (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS || OperatingSystem.CURRENT_OS == OperatingSystem.MACOS)
|
||||
&& (gameVersion == null || gameVersion.compareTo("1.6") < 0);
|
||||
|
||||
GameJavaVersion suggestedJavaVersion =
|
||||
@@ -349,18 +349,18 @@ public final class JavaManager {
|
||||
searchAllJavaInRepository(javaRuntimes, Platform.WINDOWS_X86);
|
||||
}
|
||||
break;
|
||||
case OSX:
|
||||
case MACOS:
|
||||
if (Architecture.SYSTEM_ARCH == Architecture.ARM64)
|
||||
searchAllJavaInRepository(javaRuntimes, Platform.OSX_X86_64);
|
||||
searchAllJavaInRepository(javaRuntimes, Platform.MACOS_X86_64);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (OperatingSystem.CURRENT_OS) {
|
||||
case WINDOWS:
|
||||
queryJavaInRegistryKey(javaRuntimes, "HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Runtime Environment\\");
|
||||
queryJavaInRegistryKey(javaRuntimes, "HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\");
|
||||
queryJavaInRegistryKey(javaRuntimes, "HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\JRE\\");
|
||||
queryJavaInRegistryKey(javaRuntimes, "HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\JDK\\");
|
||||
queryJavaInRegistryKey(javaRuntimes, WinReg.HKEY.HKEY_LOCAL_MACHINE, "SOFTWARE\\JavaSoft\\Java Runtime Environment");
|
||||
queryJavaInRegistryKey(javaRuntimes, WinReg.HKEY.HKEY_LOCAL_MACHINE, "SOFTWARE\\JavaSoft\\Java Development Kit");
|
||||
queryJavaInRegistryKey(javaRuntimes, WinReg.HKEY.HKEY_LOCAL_MACHINE, "SOFTWARE\\JavaSoft\\JRE");
|
||||
queryJavaInRegistryKey(javaRuntimes, WinReg.HKEY.HKEY_LOCAL_MACHINE, "SOFTWARE\\JavaSoft\\JDK");
|
||||
|
||||
searchJavaInProgramFiles(javaRuntimes, "ProgramFiles", "C:\\Program Files");
|
||||
searchJavaInProgramFiles(javaRuntimes, "ProgramFiles(x86)", "C:\\Program Files (x86)");
|
||||
@@ -375,7 +375,7 @@ public final class JavaManager {
|
||||
searchAllJavaInDirectory(javaRuntimes, Paths.get("/usr/lib64/jvm")); // General locations
|
||||
searchAllJavaInDirectory(javaRuntimes, Paths.get(System.getProperty("user.home"), "/.sdkman/candidates/java")); // SDKMAN!
|
||||
break;
|
||||
case OSX:
|
||||
case MACOS:
|
||||
searchJavaInMacJavaVirtualMachines(javaRuntimes, Paths.get("/Library/Java/JavaVirtualMachines"));
|
||||
searchJavaInMacJavaVirtualMachines(javaRuntimes, Paths.get(System.getProperty("user.home"), "/Library/Java/JavaVirtualMachines"));
|
||||
tryAddJavaExecutable(javaRuntimes, Paths.get("/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java"));
|
||||
@@ -405,14 +405,14 @@ public final class JavaManager {
|
||||
.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"), ".minecraft/runtime"), false);
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX) {
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {
|
||||
searchAllOfficialJava(javaRuntimes, Paths.get(System.getProperty("user.home"), "Library/Application Support/minecraft/runtime"), false);
|
||||
}
|
||||
searchAllOfficialJava(javaRuntimes, CacheRepository.getInstance().getCacheDirectory().resolve("java"), true);
|
||||
|
||||
// Search in PATH.
|
||||
if (System.getenv("PATH") != null) {
|
||||
String[] paths = System.getenv("PATH").split(OperatingSystem.PATH_SEPARATOR);
|
||||
String[] paths = System.getenv("PATH").split(File.pathSeparator);
|
||||
for (String path : paths) {
|
||||
try {
|
||||
tryAddJavaExecutable(javaRuntimes, Paths.get(path, OperatingSystem.CURRENT_OS.getJavaExecutable()));
|
||||
@@ -422,7 +422,7 @@ public final class JavaManager {
|
||||
}
|
||||
|
||||
if (System.getenv("HMCL_JRES") != null) {
|
||||
String[] paths = System.getenv("HMCL_JRES").split(OperatingSystem.PATH_SEPARATOR);
|
||||
String[] paths = System.getenv("HMCL_JRES").split(File.pathSeparator);
|
||||
for (String path : paths) {
|
||||
try {
|
||||
tryAddJavaHome(javaRuntimes, Paths.get(path));
|
||||
@@ -548,7 +548,7 @@ public final class JavaManager {
|
||||
}
|
||||
}
|
||||
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX) {
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {
|
||||
Path macPath = dir.resolve("jre.bundle/Contents/Home");
|
||||
if (Files.exists(macPath)) {
|
||||
tryAddJavaHome(javaRuntimes, macPath);
|
||||
@@ -587,8 +587,8 @@ public final class JavaManager {
|
||||
}
|
||||
searchAllOfficialJava(javaRuntimes, directory, getMojangJavaPlatform(Platform.WINDOWS_X86), verify);
|
||||
}
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX && Architecture.CURRENT_ARCH == Architecture.ARM64) {
|
||||
searchAllOfficialJava(javaRuntimes, directory, getMojangJavaPlatform(Platform.OSX_X86_64), verify);
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS && Architecture.CURRENT_ARCH == Architecture.ARM64) {
|
||||
searchAllOfficialJava(javaRuntimes, directory, getMojangJavaPlatform(Platform.MACOS_X86_64), verify);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -646,67 +646,22 @@ public final class JavaManager {
|
||||
}
|
||||
|
||||
// ==== Windows Registry Support ====
|
||||
private static void queryJavaInRegistryKey(Map<Path, JavaRuntime> javaRuntimes, String location) {
|
||||
for (String java : querySubFolders(location)) {
|
||||
if (!querySubFolders(java).contains(java + "\\MSI"))
|
||||
private static void queryJavaInRegistryKey(Map<Path, JavaRuntime> javaRuntimes, WinReg.HKEY hkey, String location) {
|
||||
WinReg reg = WinReg.INSTANCE;
|
||||
if (reg == null)
|
||||
return;
|
||||
|
||||
for (String java : reg.querySubKeys(hkey, location)) {
|
||||
if (!reg.querySubKeys(hkey, java).contains(java + "\\MSI"))
|
||||
continue;
|
||||
String home = queryRegisterValue(java, "JavaHome");
|
||||
if (home != null) {
|
||||
Object home = reg.queryValue(hkey, java, "JavaHome");
|
||||
if (home instanceof String) {
|
||||
try {
|
||||
tryAddJavaHome(javaRuntimes, Paths.get(home));
|
||||
tryAddJavaHome(javaRuntimes, Paths.get((String) home));
|
||||
} catch (InvalidPathException e) {
|
||||
LOG.warning("Invalid Java path in system registry: " + home);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List<String> querySubFolders(String location) {
|
||||
List<String> res = new ArrayList<>();
|
||||
|
||||
try {
|
||||
Process process = Runtime.getRuntime().exec(new String[]{"cmd", "/c", "reg", "query", location});
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), OperatingSystem.NATIVE_CHARSET))) {
|
||||
for (String line; (line = reader.readLine()) != null; ) {
|
||||
if (line.startsWith(location) && !line.equals(location)) {
|
||||
res.add(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to query sub folders of " + location, e);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private static String queryRegisterValue(String location, String name) {
|
||||
boolean last = false;
|
||||
|
||||
try {
|
||||
Process process = Runtime.getRuntime().exec(new String[]{"cmd", "/c", "reg", "query", location, "/v", name});
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), OperatingSystem.NATIVE_CHARSET))) {
|
||||
for (String line; (line = reader.readLine()) != null; ) {
|
||||
if (StringUtils.isNotBlank(line)) {
|
||||
if (last && line.trim().startsWith(name)) {
|
||||
int begins = line.indexOf(name);
|
||||
if (begins > 0) {
|
||||
String s2 = line.substring(begins + name.length());
|
||||
begins = s2.indexOf("REG_SZ");
|
||||
if (begins > 0) {
|
||||
return s2.substring(begins + "REG_SZ".length()).trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (location.equals(line.trim())) {
|
||||
last = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to query register value of " + location, e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import javafx.beans.Observable;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.auth.*;
|
||||
@@ -50,6 +51,7 @@ import java.util.*;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static javafx.collections.FXCollections.observableArrayList;
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.globalConfig;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
||||
import static org.jackhuang.hmcl.util.Lang.immutableListOf;
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
@@ -173,7 +175,7 @@ public final class Accounts {
|
||||
}
|
||||
|
||||
private static void loadGlobalAccountStorages() {
|
||||
Path globalAccountsFile = Metadata.HMCL_DIRECTORY.resolve("accounts.json");
|
||||
Path globalAccountsFile = Metadata.HMCL_GLOBAL_DIRECTORY.resolve("accounts.json");
|
||||
if (Files.exists(globalAccountsFile)) {
|
||||
try (Reader reader = Files.newBufferedReader(globalAccountsFile)) {
|
||||
globalAccountStorages.setAll(Config.CONFIG_GSON.fromJson(reader, listTypeOf(mapTypeOf(Object.class, Object.class))));
|
||||
@@ -277,6 +279,30 @@ public final class Accounts {
|
||||
selected = accounts.get(0);
|
||||
}
|
||||
|
||||
if (!globalConfig().isEnableOfflineAccount())
|
||||
for (Account account : accounts) {
|
||||
if (account instanceof MicrosoftAccount) {
|
||||
globalConfig().setEnableOfflineAccount(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!globalConfig().isEnableOfflineAccount())
|
||||
accounts.addListener(new ListChangeListener<Account>() {
|
||||
@Override
|
||||
public void onChanged(Change<? extends Account> change) {
|
||||
while (change.next()) {
|
||||
for (Account account : change.getAddedSubList()) {
|
||||
if (account instanceof MicrosoftAccount) {
|
||||
accounts.removeListener(this);
|
||||
globalConfig().setEnableOfflineAccount(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
selectedAccount.set(selected);
|
||||
|
||||
InvalidationListener listener = o -> {
|
||||
@@ -362,7 +388,7 @@ public final class Accounts {
|
||||
String authlibinjectorLocation = System.getProperty("hmcl.authlibinjector.location");
|
||||
if (authlibinjectorLocation == null) {
|
||||
return new AuthlibInjectorDownloader(
|
||||
Metadata.HMCL_DIRECTORY.resolve("authlib-injector.jar"),
|
||||
Metadata.DEPENDENCIES_DIRECTORY.resolve("universal").resolve("authlib-injector.jar"),
|
||||
DownloadProviders::getDownloadProvider) {
|
||||
@Override
|
||||
public Optional<AuthlibInjectorArtifactInfo> getArtifactInfoImmediately() {
|
||||
@@ -371,7 +397,7 @@ public final class Accounts {
|
||||
return local;
|
||||
}
|
||||
// search authlib-injector.jar in current directory, it's used as a fallback
|
||||
return parseArtifact(Paths.get("authlib-injector.jar"));
|
||||
return parseArtifact(Metadata.CURRENT_DIRECTORY.resolve("authlib-injector.jar"));
|
||||
}
|
||||
};
|
||||
} else {
|
||||
@@ -463,6 +489,8 @@ public final class Accounts {
|
||||
return i18n("account.methods.microsoft.error.country_unavailable");
|
||||
} else if (errorCode == MicrosoftService.XboxAuthorizationException.MISSING_XBOX_ACCOUNT) {
|
||||
return i18n("account.methods.microsoft.error.missing_xbox_account");
|
||||
} else if (errorCode == MicrosoftService.XboxAuthorizationException.BANNED) {
|
||||
return i18n("account.methods.microsoft.error.banned");
|
||||
} else {
|
||||
return i18n("account.methods.microsoft.error.unknown", errorCode);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.setting;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.ToNumberPolicy;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
@@ -60,6 +61,7 @@ public final class Config implements Observable {
|
||||
.registerTypeAdapter(EnumBackgroundImage.class, new EnumOrdinalDeserializer<>(EnumBackgroundImage.class)) // backward compatibility for backgroundType
|
||||
.registerTypeAdapter(Proxy.Type.class, new EnumOrdinalDeserializer<>(Proxy.Type.class)) // backward compatibility for hasProxy
|
||||
.setPrettyPrinting()
|
||||
.setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE)
|
||||
.create();
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -39,7 +39,7 @@ public final class ConfigHolder {
|
||||
|
||||
public static final String CONFIG_FILENAME = "hmcl.json";
|
||||
public static final String CONFIG_FILENAME_LINUX = ".hmcl.json";
|
||||
public static final Path GLOBAL_CONFIG_PATH = Metadata.HMCL_DIRECTORY.resolve("config.json");
|
||||
public static final Path GLOBAL_CONFIG_PATH = Metadata.HMCL_GLOBAL_DIRECTORY.resolve("config.json");
|
||||
|
||||
private static Path configLocation;
|
||||
private static Config configInstance;
|
||||
@@ -93,19 +93,9 @@ public final class ConfigHolder {
|
||||
LOG.setLogRetention(globalConfig().getLogRetention());
|
||||
Settings.init();
|
||||
|
||||
if (newlyCreated) {
|
||||
if (newlyCreated)
|
||||
saveConfigSync();
|
||||
|
||||
// hide the config file on windows
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
try {
|
||||
Files.setAttribute(configLocation, "dos:hidden", true);
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to set hidden attribute of " + configLocation, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!Files.isWritable(configLocation)) {
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS
|
||||
&& configLocation.getFileSystem() == FileSystems.getDefault()
|
||||
@@ -121,12 +111,14 @@ public final class ConfigHolder {
|
||||
}
|
||||
|
||||
private static Path locateConfig() {
|
||||
Path exePath = Paths.get("").toAbsolutePath();
|
||||
Path defaultConfigFile = Metadata.HMCL_CURRENT_DIRECTORY.resolve(CONFIG_FILENAME);
|
||||
if (Files.isRegularFile(defaultConfigFile))
|
||||
return defaultConfigFile;
|
||||
|
||||
try {
|
||||
Path jarPath = JarUtils.thisJarPath();
|
||||
if (jarPath != null && Files.isRegularFile(jarPath) && Files.isWritable(jarPath)) {
|
||||
jarPath = jarPath.getParent();
|
||||
exePath = jarPath;
|
||||
|
||||
Path config = jarPath.resolve(CONFIG_FILENAME);
|
||||
if (Files.isRegularFile(config))
|
||||
@@ -149,7 +141,7 @@ public final class ConfigHolder {
|
||||
return dotConfig;
|
||||
|
||||
// create new
|
||||
return exePath.resolve(OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS ? CONFIG_FILENAME : CONFIG_FILENAME_LINUX);
|
||||
return defaultConfigFile;
|
||||
}
|
||||
|
||||
private static Config loadConfig() throws IOException {
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.text.Font;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
import org.jackhuang.hmcl.util.Lazy;
|
||||
import org.jackhuang.hmcl.util.io.JarUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.SystemUtils;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.net.MalformedURLException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class FontManager {
|
||||
|
||||
public static final String[] FONT_EXTENSIONS = {
|
||||
"ttf", "otf", "woff"
|
||||
};
|
||||
|
||||
public static final double DEFAULT_FONT_SIZE = 12.0f;
|
||||
|
||||
private static final Lazy<Font> DEFAULT_FONT = new Lazy<>(() -> {
|
||||
Font font = tryLoadDefaultFont(Metadata.HMCL_CURRENT_DIRECTORY);
|
||||
if (font != null)
|
||||
return font;
|
||||
|
||||
font = tryLoadDefaultFont(Metadata.CURRENT_DIRECTORY);
|
||||
if (font != null)
|
||||
return font;
|
||||
|
||||
font = tryLoadDefaultFont(Metadata.HMCL_GLOBAL_DIRECTORY);
|
||||
if (font != null)
|
||||
return font;
|
||||
|
||||
Path thisJar = JarUtils.thisJarPath();
|
||||
if (thisJar != null && thisJar.getParent() != null) {
|
||||
font = tryLoadDefaultFont(thisJar.getParent());
|
||||
if (font != null)
|
||||
return font;
|
||||
}
|
||||
|
||||
if (OperatingSystem.CURRENT_OS.isLinuxOrBSD()
|
||||
&& Locale.getDefault() != Locale.ROOT
|
||||
&& !"en".equals(Locale.getDefault().getLanguage()))
|
||||
return findByFcMatch();
|
||||
else
|
||||
return null;
|
||||
});
|
||||
|
||||
private static final ObjectProperty<Font> fontProperty;
|
||||
|
||||
static {
|
||||
String fontFamily = config().getLauncherFontFamily();
|
||||
if (fontFamily == null)
|
||||
fontFamily = System.getProperty("hmcl.font.override");
|
||||
if (fontFamily == null)
|
||||
fontFamily = System.getenv("HMCL_FONT");
|
||||
|
||||
Font font = fontFamily == null ? DEFAULT_FONT.get() : Font.font(fontFamily, DEFAULT_FONT_SIZE);
|
||||
fontProperty = new SimpleObjectProperty<>(font);
|
||||
|
||||
LOG.info("Font: " + (font != null ? font.getName() : Font.getDefault().getName()));
|
||||
fontProperty.addListener((obs, oldValue, newValue) -> {
|
||||
if (newValue != null)
|
||||
config().setLauncherFontFamily(newValue.getFamily());
|
||||
else
|
||||
config().setLauncherFontFamily(null);
|
||||
});
|
||||
}
|
||||
|
||||
private static Font tryLoadDefaultFont(Path dir) {
|
||||
for (String extension : FONT_EXTENSIONS) {
|
||||
Path path = dir.resolve("font." + extension);
|
||||
if (Files.isRegularFile(path)) {
|
||||
try {
|
||||
Font font = Font.loadFont(path.toUri().toURL().toExternalForm(), DEFAULT_FONT_SIZE);
|
||||
if (font != null) {
|
||||
return font;
|
||||
}
|
||||
} catch (MalformedURLException ignored) {
|
||||
}
|
||||
|
||||
LOG.warning("Failed to load font " + path);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Font findByFcMatch() {
|
||||
Path fcMatch = SystemUtils.which("fc-match");
|
||||
if (fcMatch == null)
|
||||
return null;
|
||||
|
||||
try {
|
||||
String result = SystemUtils.run(fcMatch.toString(),
|
||||
":lang=" + Locale.getDefault().toLanguageTag(),
|
||||
"--format", "%{family}\\n%{file}").trim();
|
||||
|
||||
String[] results = result.split("\\n");
|
||||
if (results.length != 2 || results[0].isEmpty() || results[1].isEmpty()) {
|
||||
LOG.warning("Unexpected output from fc-match: " + result);
|
||||
return null;
|
||||
}
|
||||
|
||||
String family = results[0].trim();
|
||||
String path = results[1];
|
||||
|
||||
Path file = Paths.get(path).toAbsolutePath().normalize();
|
||||
if (!Files.isRegularFile(file)) {
|
||||
LOG.warning("Font file does not exist: " + path);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (JavaRuntime.CURRENT_VERSION >= 9) {
|
||||
try {
|
||||
MethodHandle methodHandle = MethodHandles.publicLookup().findStatic(Font.class, "loadFonts",
|
||||
MethodType.methodType(Font[].class, String.class, double.class));
|
||||
|
||||
Font[] fonts = (Font[]) methodHandle.invokeExact(file.toUri().toURL().toExternalForm(), DEFAULT_FONT_SIZE);
|
||||
if (fonts == null) {
|
||||
LOG.warning("Failed to load font from " + path);
|
||||
return null;
|
||||
} else if (fonts.length == 0) {
|
||||
LOG.warning("No fonts loaded from " + path);
|
||||
return null;
|
||||
}
|
||||
|
||||
for (Font font : fonts) {
|
||||
if (font.getFamily().equalsIgnoreCase(family)) {
|
||||
return font;
|
||||
}
|
||||
}
|
||||
|
||||
LOG.warning(String.format("Family '%s' not found in font file '%s'", family, path));
|
||||
return fonts[0];
|
||||
} catch (NoSuchMethodException | IllegalAccessException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
Font font = Font.loadFont(file.toUri().toURL().toExternalForm(), DEFAULT_FONT_SIZE);
|
||||
if (font == null)
|
||||
LOG.warning("Failed to load font from " + path);
|
||||
return font;
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Failed to get default font with fc-match", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static ObjectProperty<Font> fontProperty() {
|
||||
return fontProperty;
|
||||
}
|
||||
|
||||
public static Font getFont() {
|
||||
return fontProperty.get();
|
||||
}
|
||||
|
||||
public static void setFont(Font font) {
|
||||
fontProperty.set(font);
|
||||
}
|
||||
|
||||
public static void setFontFamily(String fontFamily) {
|
||||
setFont(fontFamily != null ? Font.font(fontFamily, DEFAULT_FONT_SIZE) : null);
|
||||
}
|
||||
|
||||
private FontManager() {
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,9 @@ import com.google.gson.*;
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableSet;
|
||||
@@ -53,6 +55,8 @@ public final class GlobalConfig implements Observable {
|
||||
|
||||
private final IntegerProperty logRetention = new SimpleIntegerProperty();
|
||||
|
||||
private final BooleanProperty enableOfflineAccount = new SimpleBooleanProperty(false);
|
||||
|
||||
private final ObservableSet<String> userJava = FXCollections.observableSet(new LinkedHashSet<>());
|
||||
|
||||
private final ObservableSet<String> disabledJava = FXCollections.observableSet(new LinkedHashSet<>());
|
||||
@@ -115,6 +119,18 @@ public final class GlobalConfig implements Observable {
|
||||
this.logRetention.set(logRetention);
|
||||
}
|
||||
|
||||
public boolean isEnableOfflineAccount() {
|
||||
return enableOfflineAccount.get();
|
||||
}
|
||||
|
||||
public BooleanProperty enableOfflineAccountProperty() {
|
||||
return enableOfflineAccount;
|
||||
}
|
||||
|
||||
public void setEnableOfflineAccount(boolean value) {
|
||||
enableOfflineAccount.set(value);
|
||||
}
|
||||
|
||||
public ObservableSet<String> getUserJava() {
|
||||
return userJava;
|
||||
}
|
||||
@@ -129,7 +145,8 @@ public final class GlobalConfig implements Observable {
|
||||
"platformPromptVersion",
|
||||
"logRetention",
|
||||
"userJava",
|
||||
"disabledJava"
|
||||
"disabledJava",
|
||||
"enableOfflineAccount"
|
||||
));
|
||||
|
||||
@Override
|
||||
@@ -142,6 +159,9 @@ public final class GlobalConfig implements Observable {
|
||||
jsonObject.add("agreementVersion", context.serialize(src.getAgreementVersion()));
|
||||
jsonObject.add("platformPromptVersion", context.serialize(src.getPlatformPromptVersion()));
|
||||
jsonObject.add("logRetention", context.serialize(src.getLogRetention()));
|
||||
if (src.enableOfflineAccount.get())
|
||||
jsonObject.addProperty("enableOfflineAccount", true);
|
||||
|
||||
if (!src.getUserJava().isEmpty())
|
||||
jsonObject.add("userJava", context.serialize(src.getUserJava()));
|
||||
|
||||
@@ -165,6 +185,7 @@ public final class GlobalConfig implements Observable {
|
||||
config.setAgreementVersion(Optional.ofNullable(obj.get("agreementVersion")).map(JsonElement::getAsInt).orElse(0));
|
||||
config.setPlatformPromptVersion(Optional.ofNullable(obj.get("platformPromptVersion")).map(JsonElement::getAsInt).orElse(0));
|
||||
config.setLogRetention(Optional.ofNullable(obj.get("logRetention")).map(JsonElement::getAsInt).orElse(20));
|
||||
config.setEnableOfflineAccount(Optional.ofNullable(obj.get("enableOfflineAccount")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
|
||||
JsonElement userJava = obj.get("userJava");
|
||||
if (userJava != null && userJava.isJsonArray()) {
|
||||
|
||||
@@ -17,100 +17,118 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.beans.value.ObservableObjectValue;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.net.Authenticator;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.PasswordAuthentication;
|
||||
import java.net.Proxy;
|
||||
import java.net.Proxy.Type;
|
||||
import java.io.IOException;
|
||||
import java.net.*;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public final class ProxyManager {
|
||||
private ProxyManager() {
|
||||
|
||||
private static final ProxySelector NO_PROXY = new SimpleProxySelector(Proxy.NO_PROXY);
|
||||
private static final ProxySelector SYSTEM_DEFAULT = Lang.requireNonNullElse(ProxySelector.getDefault(), NO_PROXY);
|
||||
|
||||
private static ProxySelector getProxySelector() {
|
||||
if (config().hasProxy()) {
|
||||
Proxy.Type proxyType = config().getProxyType();
|
||||
String host = config().getProxyHost();
|
||||
int port = config().getProxyPort();
|
||||
|
||||
if (proxyType == Proxy.Type.DIRECT || StringUtils.isBlank(host)) {
|
||||
return NO_PROXY;
|
||||
} else if (port < 0 || port > 0xFFFF) {
|
||||
LOG.warning("Illegal proxy port: " + port);
|
||||
return NO_PROXY;
|
||||
} else {
|
||||
return new SimpleProxySelector(new Proxy(proxyType, new InetSocketAddress(host, port)));
|
||||
}
|
||||
} else {
|
||||
return ProxyManager.SYSTEM_DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
private static ObjectBinding<Proxy> proxyProperty;
|
||||
private static Authenticator getAuthenticator() {
|
||||
if (config().hasProxy() && config().hasProxyAuth()) {
|
||||
String username = config().getProxyUser();
|
||||
String password = config().getProxyPass();
|
||||
|
||||
public static Proxy getProxy() {
|
||||
return proxyProperty.get();
|
||||
}
|
||||
|
||||
public static ObservableObjectValue<Proxy> proxyProperty() {
|
||||
return proxyProperty;
|
||||
if (username != null || password != null)
|
||||
return new SimpleAuthenticator(username, password.toCharArray());
|
||||
else
|
||||
return null;
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
|
||||
static void init() {
|
||||
proxyProperty = Bindings.createObjectBinding(
|
||||
() -> {
|
||||
String host = config().getProxyHost();
|
||||
int port = config().getProxyPort();
|
||||
if (!config().hasProxy() || StringUtils.isBlank(host) || config().getProxyType() == Proxy.Type.DIRECT) {
|
||||
return Proxy.NO_PROXY;
|
||||
} else {
|
||||
if (port < 0 || port > 0xFFFF) {
|
||||
LOG.warning("Illegal proxy port: " + port);
|
||||
return Proxy.NO_PROXY;
|
||||
}
|
||||
return new Proxy(config().getProxyType(), new InetSocketAddress(host, port));
|
||||
}
|
||||
},
|
||||
config().proxyTypeProperty(),
|
||||
config().proxyHostProperty(),
|
||||
config().proxyPortProperty(),
|
||||
config().hasProxyProperty());
|
||||
ProxySelector.setDefault(getProxySelector());
|
||||
InvalidationListener updateProxySelector = observable -> ProxySelector.setDefault(getProxySelector());
|
||||
config().proxyTypeProperty().addListener(updateProxySelector);
|
||||
config().proxyHostProperty().addListener(updateProxySelector);
|
||||
config().proxyPortProperty().addListener(updateProxySelector);
|
||||
config().hasProxyProperty().addListener(updateProxySelector);
|
||||
|
||||
proxyProperty.addListener(any -> updateSystemProxy());
|
||||
updateSystemProxy();
|
||||
|
||||
Authenticator.setDefault(new Authenticator() {
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
if (config().hasProxyAuth()) {
|
||||
String username = config().getProxyUser();
|
||||
String password = config().getProxyPass();
|
||||
if (username != null && password != null) {
|
||||
return new PasswordAuthentication(username, password.toCharArray());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
Authenticator.setDefault(getAuthenticator());
|
||||
InvalidationListener updateAuthenticator = observable -> Authenticator.setDefault(getAuthenticator());
|
||||
config().hasProxyProperty().addListener(updateAuthenticator);
|
||||
config().hasProxyAuthProperty().addListener(updateAuthenticator);
|
||||
config().proxyUserProperty().addListener(updateAuthenticator);
|
||||
config().proxyPassProperty().addListener(updateAuthenticator);
|
||||
}
|
||||
|
||||
private static void updateSystemProxy() {
|
||||
Proxy proxy = proxyProperty.get();
|
||||
if (proxy.type() == Proxy.Type.DIRECT) {
|
||||
System.clearProperty("http.proxyHost");
|
||||
System.clearProperty("http.proxyPort");
|
||||
System.clearProperty("https.proxyHost");
|
||||
System.clearProperty("https.proxyPort");
|
||||
System.clearProperty("socksProxyHost");
|
||||
System.clearProperty("socksProxyPort");
|
||||
} else {
|
||||
InetSocketAddress address = (InetSocketAddress) proxy.address();
|
||||
String host = address.getHostString();
|
||||
String port = String.valueOf(address.getPort());
|
||||
if (proxy.type() == Type.HTTP) {
|
||||
System.clearProperty("socksProxyHost");
|
||||
System.clearProperty("socksProxyPort");
|
||||
System.setProperty("http.proxyHost", host);
|
||||
System.setProperty("http.proxyPort", port);
|
||||
System.setProperty("https.proxyHost", host);
|
||||
System.setProperty("https.proxyPort", port);
|
||||
} else if (proxy.type() == Type.SOCKS) {
|
||||
System.clearProperty("http.proxyHost");
|
||||
System.clearProperty("http.proxyPort");
|
||||
System.clearProperty("https.proxyHost");
|
||||
System.clearProperty("https.proxyPort");
|
||||
System.setProperty("socksProxyHost", host);
|
||||
System.setProperty("socksProxyPort", port);
|
||||
private static final class SimpleProxySelector extends ProxySelector {
|
||||
private final List<Proxy> proxies;
|
||||
|
||||
SimpleProxySelector(Proxy proxy) {
|
||||
this(Collections.singletonList(proxy));
|
||||
}
|
||||
|
||||
SimpleProxySelector(List<Proxy> proxies) {
|
||||
this.proxies = proxies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Proxy> select(URI uri) {
|
||||
if (uri == null)
|
||||
throw new IllegalArgumentException("URI can't be null.");
|
||||
|
||||
return proxies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
|
||||
if (uri == null || sa == null || ioe == null) {
|
||||
throw new IllegalArgumentException("Arguments can't be null.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SimpleProxySelector" + proxies;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class SimpleAuthenticator extends Authenticator {
|
||||
private final String username;
|
||||
private final char[] password;
|
||||
|
||||
private SimpleAuthenticator(String username, char[] password) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
return getRequestorType() == RequestorType.PROXY ? new PasswordAuthentication(username, password) : null;
|
||||
}
|
||||
}
|
||||
|
||||
private ProxyManager() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Font;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class StyleSheets {
|
||||
private static final int FONT_STYLE_SHEET_INDEX = 0;
|
||||
private static final int THEME_STYLE_SHEET_INDEX = 1;
|
||||
|
||||
private static final ObservableList<String> stylesheets;
|
||||
|
||||
static {
|
||||
String[] array = new String[]{
|
||||
getFontStyleSheet(),
|
||||
getThemeStyleSheet(),
|
||||
"/assets/css/root.css"
|
||||
};
|
||||
stylesheets = FXCollections.observableList(Arrays.asList(array));
|
||||
|
||||
FontManager.fontProperty().addListener(o -> stylesheets.set(FONT_STYLE_SHEET_INDEX, getFontStyleSheet()));
|
||||
config().themeProperty().addListener(o -> stylesheets.set(THEME_STYLE_SHEET_INDEX, getThemeStyleSheet()));
|
||||
}
|
||||
|
||||
private static String toStyleSheetUri(String styleSheet, String fallback) {
|
||||
if (FXUtils.JAVAFX_MAJOR_VERSION >= 17)
|
||||
// JavaFX 17+ support loading stylesheets from data URIs
|
||||
// https://bugs.openjdk.org/browse/JDK-8267554
|
||||
return "data:text/css;charset=UTF-8;base64," + Base64.getEncoder().encodeToString(styleSheet.getBytes(StandardCharsets.UTF_8));
|
||||
else
|
||||
try {
|
||||
Path temp = Files.createTempFile("hmcl", ".css");
|
||||
// For JavaFX 17 or earlier, CssParser uses the default charset
|
||||
// https://bugs.openjdk.org/browse/JDK-8279328
|
||||
FileUtils.writeText(temp, styleSheet, Charset.defaultCharset());
|
||||
temp.toFile().deleteOnExit();
|
||||
return temp.toUri().toString();
|
||||
} catch (IOException | NullPointerException e) {
|
||||
LOG.error("Unable to create stylesheet, fallback to " + fallback, e);
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
private static String getFontStyleSheet() {
|
||||
final String defaultCss = "/assets/css/font.css";
|
||||
final Font font = FontManager.getFont();
|
||||
|
||||
if (font == null || font == Font.getDefault())
|
||||
return defaultCss;
|
||||
|
||||
String fontFamily = font.getFamily();
|
||||
String style = font.getStyle();
|
||||
String weight = null;
|
||||
String posture = null;
|
||||
|
||||
if (style != null) {
|
||||
style = style.toLowerCase(Locale.ROOT);
|
||||
|
||||
if (style.contains("thin"))
|
||||
weight = "100";
|
||||
else if (style.contains("extralight") || style.contains("extra light") || style.contains("ultralight") | style.contains("ultra light"))
|
||||
weight = "200";
|
||||
else if (style.contains("medium"))
|
||||
weight = "500";
|
||||
else if (style.contains("semibold") || style.contains("semi bold") || style.contains("demibold") || style.contains("demi bold"))
|
||||
weight = "600";
|
||||
else if (style.contains("extrabold") || style.contains("extra bold") || style.contains("ultrabold") || style.contains("ultra bold"))
|
||||
weight = "800";
|
||||
else if (style.contains("black") || style.contains("heavy"))
|
||||
weight = "900";
|
||||
else if (style.contains("light"))
|
||||
weight = "lighter";
|
||||
else if (style.contains("bold"))
|
||||
weight = "bold";
|
||||
|
||||
posture = style.contains("italic") || style.contains("oblique") ? "italic" : null;
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append(".root {");
|
||||
if (fontFamily == null)
|
||||
// https://github.com/HMCL-dev/HMCL/pull/3423
|
||||
builder.append("-fx-font-family: -fx-base-font-family;");
|
||||
else
|
||||
builder.append("-fx-font-family:\"").append(fontFamily).append("\";");
|
||||
|
||||
if (weight != null)
|
||||
builder.append("-fx-font-weight:").append(weight).append(";");
|
||||
|
||||
if (posture != null)
|
||||
builder.append("-fx-font-style:").append(posture).append(";");
|
||||
|
||||
builder.append('}');
|
||||
|
||||
return toStyleSheetUri(builder.toString(), fontFamily);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private static String getThemeStyleSheet() {
|
||||
final String blueCss = "/assets/css/blue.css";
|
||||
|
||||
Theme theme = config().getTheme();
|
||||
if (theme == null || theme.getPaint().equals(Theme.BLUE.getPaint()))
|
||||
return blueCss;
|
||||
|
||||
return toStyleSheetUri(".root {" +
|
||||
"-fx-base-color:" + theme.getColor() + ';' +
|
||||
"-fx-base-darker-color: derive(-fx-base-color, -10%);" +
|
||||
"-fx-base-check-color: derive(-fx-base-color, 30%);" +
|
||||
"-fx-rippler-color:" + rgba(theme.getPaint(), 0.3) + ';' +
|
||||
"-fx-base-rippler-color: derive(" + rgba(theme.getPaint(), 0.3) + ", 100%);" +
|
||||
"-fx-base-disabled-text-fill:" + rgba(theme.getForegroundColor(), 0.7) + ";" +
|
||||
"-fx-base-text-fill:" + Theme.getColorDisplayName(theme.getForegroundColor()) + ";" +
|
||||
"-theme-thumb:" + rgba(theme.getPaint(), 0.7) + ";" +
|
||||
'}', blueCss);
|
||||
}
|
||||
|
||||
public static void init(Scene scene) {
|
||||
Bindings.bindContent(scene.getStylesheets(), stylesheets);
|
||||
}
|
||||
|
||||
private StyleSheets() {
|
||||
}
|
||||
}
|
||||
@@ -23,31 +23,16 @@ import com.google.gson.stream.JsonWriter;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Font;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.util.Lazy;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
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 static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
@JsonAdapter(Theme.TypeAdapter.class)
|
||||
public class Theme {
|
||||
public final class Theme {
|
||||
public static final Theme BLUE = new Theme("blue", "#5C6BC0");
|
||||
public static final Color BLACK = Color.web("#292929");
|
||||
public static final Color[] SUGGESTED_COLORS = new Color[]{
|
||||
@@ -59,42 +44,6 @@ public class Theme {
|
||||
Color.web("#B71C1C") // red
|
||||
};
|
||||
|
||||
private static final Lazy<Font> FONT = new Lazy<>(() -> {
|
||||
String[] fileNames = {"font.ttf", "font.otf", "font.woff"};
|
||||
|
||||
for (String fileName : fileNames) {
|
||||
Path path = Paths.get(fileName).toAbsolutePath();
|
||||
if (Files.isRegularFile(path)) {
|
||||
try {
|
||||
Font font = Font.loadFont(path.toUri().toURL().toExternalForm(), 0);
|
||||
if (font != null) {
|
||||
return font;
|
||||
}
|
||||
} catch (MalformedURLException ignored) {
|
||||
}
|
||||
|
||||
LOG.warning("Failed to load font " + path);
|
||||
}
|
||||
}
|
||||
|
||||
for (String fileName : fileNames) {
|
||||
Path path = Metadata.HMCL_DIRECTORY.resolve(fileName);
|
||||
if (Files.isRegularFile(path)) {
|
||||
try {
|
||||
Font font = Font.loadFont(path.toUri().toURL().toExternalForm(), 0);
|
||||
if (font != null) {
|
||||
return font;
|
||||
}
|
||||
} catch (MalformedURLException ignored) {
|
||||
}
|
||||
|
||||
LOG.warning("Failed to load font " + path);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
public static Theme getTheme() {
|
||||
Theme theme = config().getTheme();
|
||||
return theme == null ? BLUE : theme;
|
||||
@@ -118,6 +67,10 @@ public class Theme {
|
||||
return color;
|
||||
}
|
||||
|
||||
public Color getPaint() {
|
||||
return paint;
|
||||
}
|
||||
|
||||
public boolean isCustom() {
|
||||
return name.startsWith("#");
|
||||
}
|
||||
@@ -130,119 +83,6 @@ public class Theme {
|
||||
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);
|
||||
}
|
||||
|
||||
private static Pair<String, String> parseFontStyle(String style) {
|
||||
if (style == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
style = style.toLowerCase(Locale.ROOT);
|
||||
|
||||
String weight;
|
||||
String posture;
|
||||
|
||||
if (style.contains("thin")) {
|
||||
weight = "100";
|
||||
} else if (style.contains("extralight") || style.contains("extra light") || style.contains("ultralight") | style.contains("ultra light")) {
|
||||
weight = "200";
|
||||
} else if (style.contains("medium")) {
|
||||
weight = "500";
|
||||
} else if (style.contains("semibold") || style.contains("semi bold") || style.contains("demibold") || style.contains("demi bold")) {
|
||||
weight = "600";
|
||||
} else if (style.contains("extrabold") || style.contains("extra bold") || style.contains("ultrabold") || style.contains("ultra bold")) {
|
||||
weight = "800";
|
||||
} else if (style.contains("black") || style.contains("heavy")) {
|
||||
weight = "900";
|
||||
} else if (style.contains("light")) {
|
||||
weight = "lighter";
|
||||
} else if (style.contains("bold")) {
|
||||
weight = "bold";
|
||||
} else {
|
||||
weight = null;
|
||||
}
|
||||
|
||||
if (style.contains("italic") || style.contains("oblique")) {
|
||||
posture = "italic";
|
||||
} else {
|
||||
posture = null;
|
||||
}
|
||||
|
||||
return Pair.pair(weight, posture);
|
||||
}
|
||||
|
||||
public String[] getStylesheets(String overrideFontFamily) {
|
||||
String css = "/assets/css/blue.css";
|
||||
|
||||
String fontFamily = overrideFontFamily == null
|
||||
? System.getProperty("hmcl.font.override", System.getenv("HMCL_FONT"))
|
||||
: overrideFontFamily;
|
||||
|
||||
Pair<String, String> fontStyle = null;
|
||||
if (fontFamily == null) {
|
||||
Font font = FONT.get();
|
||||
if (font != null) {
|
||||
fontFamily = font.getFamily();
|
||||
fontStyle = parseFontStyle(font.getStyle());
|
||||
}
|
||||
}
|
||||
|
||||
if (fontFamily != null || !this.color.equalsIgnoreCase(BLUE.color)) {
|
||||
Color textFill = getForegroundColor();
|
||||
|
||||
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) {
|
||||
if (fontStyle.getKey() != null)
|
||||
themeBuilder.append("-fx-font-weight:").append(fontStyle.getKey()).append(";");
|
||||
|
||||
if (fontStyle.getValue() != null)
|
||||
themeBuilder.append("-fx-font-style:").append(fontStyle.getValue()).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"};
|
||||
}
|
||||
|
||||
public static Theme custom(String color) {
|
||||
if (!color.startsWith("#"))
|
||||
throw new IllegalArgumentException();
|
||||
|
||||
@@ -29,7 +29,7 @@ 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.platform.SystemInfo;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -39,6 +39,7 @@ import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.util.DataSizeUnit.MEGABYTES;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
/**
|
||||
@@ -47,7 +48,16 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
@JsonAdapter(VersionSetting.Serializer.class)
|
||||
public final class VersionSetting implements Cloneable, Observable {
|
||||
|
||||
private transient ObservableHelper helper = new ObservableHelper(this);
|
||||
private static final int SUGGESTED_MEMORY;
|
||||
|
||||
static {
|
||||
double totalMemoryMB = MEGABYTES.convertFromBytes(SystemInfo.getTotalMemorySize());
|
||||
SUGGESTED_MEMORY = totalMemoryMB >= 32768
|
||||
? 8192
|
||||
: Integer.max((int) (Math.round(totalMemoryMB / 4.0 / 128.0) * 128), 256);
|
||||
}
|
||||
|
||||
private final transient ObservableHelper helper = new ObservableHelper(this);
|
||||
|
||||
public VersionSetting() {
|
||||
PropertyUtils.attachListener(this, helper);
|
||||
@@ -219,7 +229,7 @@ public final class VersionSetting implements Cloneable, Observable {
|
||||
permSizeProperty.set(permSize);
|
||||
}
|
||||
|
||||
private final IntegerProperty maxMemoryProperty = new SimpleIntegerProperty(this, "maxMemory", OperatingSystem.SUGGESTED_MEMORY);
|
||||
private final IntegerProperty maxMemoryProperty = new SimpleIntegerProperty(this, "maxMemory", SUGGESTED_MEMORY);
|
||||
|
||||
public IntegerProperty maxMemoryProperty() {
|
||||
return maxMemoryProperty;
|
||||
@@ -734,7 +744,7 @@ public final class VersionSetting implements Cloneable, Observable {
|
||||
obj.addProperty("javaArgs", src.getJavaArgs());
|
||||
obj.addProperty("minecraftArgs", src.getMinecraftArgs());
|
||||
obj.addProperty("environmentVariables", src.getEnvironmentVariables());
|
||||
obj.addProperty("maxMemory", src.getMaxMemory() <= 0 ? OperatingSystem.SUGGESTED_MEMORY : src.getMaxMemory());
|
||||
obj.addProperty("maxMemory", src.getMaxMemory() <= 0 ? SUGGESTED_MEMORY : src.getMaxMemory());
|
||||
obj.addProperty("minMemory", src.getMinMemory());
|
||||
obj.addProperty("autoMemory", src.isAutoMemory());
|
||||
obj.addProperty("permSize", src.getPermSize());
|
||||
@@ -801,8 +811,8 @@ public final class VersionSetting implements Cloneable, Observable {
|
||||
return null;
|
||||
JsonObject obj = (JsonObject) json;
|
||||
|
||||
int maxMemoryN = parseJsonPrimitive(Optional.ofNullable(obj.get("maxMemory")).map(JsonElement::getAsJsonPrimitive).orElse(null), OperatingSystem.SUGGESTED_MEMORY);
|
||||
if (maxMemoryN <= 0) maxMemoryN = OperatingSystem.SUGGESTED_MEMORY;
|
||||
int maxMemoryN = parseJsonPrimitive(Optional.ofNullable(obj.get("maxMemory")).map(JsonElement::getAsJsonPrimitive).orElse(null), SUGGESTED_MEMORY);
|
||||
if (maxMemoryN <= 0) maxMemoryN = SUGGESTED_MEMORY;
|
||||
|
||||
VersionSetting vs = new VersionSetting();
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.game.ModpackHelper;
|
||||
import org.jackhuang.hmcl.java.JavaManager;
|
||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
import org.jackhuang.hmcl.setting.*;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
@@ -62,6 +63,7 @@ import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@@ -70,6 +72,8 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class Controllers {
|
||||
public static final String JAVA_VERSION_TIP = "javaVersion";
|
||||
|
||||
public static final int MIN_WIDTH = 800 + 2 + 16; // bg width + border width*2 + shadow width*2
|
||||
public static final int MIN_HEIGHT = 450 + 2 + 40 + 16; // bg height + border width*2 + toolbar height + shadow width*2
|
||||
public static final Screen SCREEN = Screen.getPrimary();
|
||||
@@ -265,7 +269,7 @@ public final class Controllers {
|
||||
stage.setMinHeight(MIN_HEIGHT);
|
||||
decorator.getDecorator().prefWidthProperty().bind(scene.widthProperty());
|
||||
decorator.getDecorator().prefHeightProperty().bind(scene.heightProperty());
|
||||
scene.getStylesheets().setAll(Theme.getTheme().getStylesheets(config().getLauncherFontFamily()));
|
||||
StyleSheets.init(scene);
|
||||
|
||||
FXUtils.setIcon(stage);
|
||||
stage.setTitle(Metadata.FULL_TITLE);
|
||||
@@ -293,8 +297,8 @@ public final class Controllers {
|
||||
if (!Architecture.SYSTEM_ARCH.isX86() && globalConfig().getPlatformPromptVersion() < 1) {
|
||||
Runnable continueAction = () -> globalConfig().setPlatformPromptVersion(1);
|
||||
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX && Architecture.SYSTEM_ARCH == Architecture.ARM64) {
|
||||
Controllers.dialog(i18n("fatal.unsupported_platform.osx_arm64"), null, MessageType.INFO, continueAction);
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS && Architecture.SYSTEM_ARCH == Architecture.ARM64) {
|
||||
Controllers.dialog(i18n("fatal.unsupported_platform.macos_arm64"), null, MessageType.INFO, continueAction);
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS && Architecture.SYSTEM_ARCH == Architecture.ARM64) {
|
||||
Controllers.dialog(i18n("fatal.unsupported_platform.windows_arm64"), null, MessageType.INFO, continueAction);
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX &&
|
||||
@@ -307,6 +311,51 @@ public final class Controllers {
|
||||
}
|
||||
}
|
||||
|
||||
if (JavaRuntime.CURRENT_VERSION < 10) {
|
||||
Integer shownTipVersion = null;
|
||||
|
||||
try {
|
||||
shownTipVersion = (Integer) config().getShownTips().get(JAVA_VERSION_TIP);
|
||||
} catch (ClassCastException e) {
|
||||
LOG.warning("Invalid type for shown tips key: " + JAVA_VERSION_TIP, e);
|
||||
}
|
||||
|
||||
if (shownTipVersion == null || shownTipVersion < 11) {
|
||||
String downloadLink = null;
|
||||
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX && Architecture.SYSTEM_ARCH == Architecture.LOONGARCH64_OW)
|
||||
downloadLink = "https://www.loongnix.cn/zh/api/java/downloads-jdk21/index.html";
|
||||
else {
|
||||
|
||||
EnumSet<Architecture> supportedArchitectures;
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS)
|
||||
supportedArchitectures = EnumSet.of(Architecture.X86_64, Architecture.X86, Architecture.ARM64);
|
||||
else if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX)
|
||||
supportedArchitectures = EnumSet.of(
|
||||
Architecture.X86_64, Architecture.X86,
|
||||
Architecture.ARM64, Architecture.ARM32,
|
||||
Architecture.RISCV64, Architecture.LOONGARCH64
|
||||
);
|
||||
else if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS)
|
||||
supportedArchitectures = EnumSet.of(Architecture.X86_64, Architecture.ARM64);
|
||||
else
|
||||
supportedArchitectures = EnumSet.noneOf(Architecture.class);
|
||||
|
||||
if (supportedArchitectures.contains(Architecture.SYSTEM_ARCH))
|
||||
downloadLink = String.format("https://docs.hmcl.net/downloads/%s/%s.html",
|
||||
OperatingSystem.CURRENT_OS.getCheckedName(),
|
||||
Architecture.SYSTEM_ARCH.getCheckedName()
|
||||
);
|
||||
}
|
||||
|
||||
MessageDialogPane.Builder builder = new MessageDialogPane.Builder(i18n("fatal.deprecated_java_version"), null, MessageType.WARNING);
|
||||
if (downloadLink != null)
|
||||
builder.addHyperLink(i18n("fatal.deprecated_java_version.download_link", 21), downloadLink);
|
||||
Controllers.dialog(builder
|
||||
.ok(() -> config().getShownTips().put(JAVA_VERSION_TIP, 11))
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
if (globalConfig().getAgreementVersion() < 1) {
|
||||
JFXDialogLayout agreementPane = new JFXDialogLayout();
|
||||
|
||||
@@ -83,7 +83,6 @@ import java.lang.ref.WeakReference;
|
||||
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;
|
||||
@@ -380,7 +379,7 @@ public final class FXUtils {
|
||||
String openCommand;
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS)
|
||||
openCommand = "explorer.exe";
|
||||
else if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX)
|
||||
else if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS)
|
||||
openCommand = "/usr/bin/open";
|
||||
else if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() && new File("/usr/bin/xdg-open").exists())
|
||||
openCommand = "/usr/bin/xdg-open";
|
||||
@@ -411,31 +410,15 @@ public final class FXUtils {
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void showFileInExplorer(Path file) {
|
||||
String path = file.toAbsolutePath().toString();
|
||||
|
||||
String[] openCommands;
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS)
|
||||
openCommands = new String[]{"explorer.exe", "/select,", path};
|
||||
else if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX)
|
||||
else if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS)
|
||||
openCommands = new String[]{"/usr/bin/open", "-R", path};
|
||||
else if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() && which("dbus-send") != null)
|
||||
else if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() && SystemUtils.which("dbus-send") != null)
|
||||
openCommands = new String[]{
|
||||
"dbus-send",
|
||||
"--print-reply",
|
||||
@@ -501,10 +484,10 @@ public final class FXUtils {
|
||||
}
|
||||
if (OperatingSystem.CURRENT_OS.isLinuxOrBSD()) {
|
||||
for (String browser : linuxBrowsers) {
|
||||
String path = which(browser);
|
||||
Path path = SystemUtils.which(browser);
|
||||
if (path != null) {
|
||||
try {
|
||||
Runtime.getRuntime().exec(new String[]{browser, link});
|
||||
Runtime.getRuntime().exec(new String[]{path.toString(), link});
|
||||
return;
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
@@ -515,7 +498,7 @@ public final class FXUtils {
|
||||
try {
|
||||
java.awt.Desktop.getDesktop().browse(new URI(link));
|
||||
} catch (Throwable e) {
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX)
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS)
|
||||
try {
|
||||
Runtime.getRuntime().exec(new String[]{"/usr/bin/open", link});
|
||||
} catch (IOException ex) {
|
||||
@@ -776,6 +759,8 @@ public final class FXUtils {
|
||||
String icon;
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
icon = "/assets/img/icon.png";
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {
|
||||
icon = "/assets/img/icon-mac.png";
|
||||
} else {
|
||||
icon = "/assets/img/icon@4x.png";
|
||||
}
|
||||
|
||||
@@ -37,20 +37,14 @@ import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.launch.ProcessListener;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.setting.StyleSheets;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Log4jLevel;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.logging.Logger;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.CommandBuilder;
|
||||
import org.jackhuang.hmcl.util.platform.ManagedProcess;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
@@ -64,7 +58,7 @@ import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.DataSizeUnit.MEGABYTES;
|
||||
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;
|
||||
@@ -98,7 +92,7 @@ public class GameCrashWindow extends Stage {
|
||||
|
||||
memory = Optional.ofNullable(launchOptions.getMaxMemory()).map(i -> i + " MB").orElse("-");
|
||||
|
||||
total_memory = OperatingSystem.TOTAL_MEMORY + " MB";
|
||||
total_memory = MEGABYTES.formatBytes(SystemInfo.getTotalMemorySize());
|
||||
|
||||
this.java = launchOptions.getJava().getArchitecture() == Architecture.SYSTEM_ARCH
|
||||
? launchOptions.getJava().getVersion()
|
||||
@@ -109,7 +103,7 @@ public class GameCrashWindow extends Stage {
|
||||
this.feedbackTextFlow.getChildren().addAll(FXUtils.parseSegment(i18n("game.crash.feedback"), Controllers::onHyperlinkAction));
|
||||
|
||||
setScene(new Scene(view, 800, 480));
|
||||
getScene().getStylesheets().addAll(Theme.getTheme().getStylesheets(config().getLauncherFontFamily()));
|
||||
StyleSheets.init(getScene());
|
||||
setTitle(i18n("game.crash.title"));
|
||||
FXUtils.setIcon(this);
|
||||
|
||||
@@ -142,7 +136,7 @@ public class GameCrashWindow extends Stage {
|
||||
|
||||
String log;
|
||||
try {
|
||||
log = FileUtils.readText(latestLog);
|
||||
log = FileUtils.readTextMaybeNativeEncoding(latestLog);
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to read logs/latest.log", e);
|
||||
return pair(new HashSet<CrashReportAnalyzer.Result>(), new HashSet<String>());
|
||||
@@ -271,7 +265,7 @@ public class GameCrashWindow extends Stage {
|
||||
Path logFile = Paths.get("minecraft-exported-crash-info-" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH-mm-ss")) + ".zip").toAbsolutePath();
|
||||
|
||||
CompletableFuture.supplyAsync(() ->
|
||||
logs.stream().map(Log::getLog).collect(Collectors.joining(OperatingSystem.LINE_SEPARATOR)))
|
||||
logs.stream().map(Log::getLog).collect(Collectors.joining("\n")))
|
||||
.thenComposeAsync(logs ->
|
||||
LogExporter.exportLogs(logFile, repository, launchOptions.getVersionName(), logs, new CommandBuilder().addAll(managedProcess.getCommands()).toString()))
|
||||
.handleAsync((result, exception) -> {
|
||||
@@ -422,7 +416,7 @@ public class GameCrashWindow extends Stage {
|
||||
logButton.setOnAction(e -> showLogWindow());
|
||||
|
||||
JFXButton helpButton = FXUtils.newRaisedButton(i18n("help"));
|
||||
helpButton.setOnAction(e -> FXUtils.openLink("https://docs.hmcl.net/help.html"));
|
||||
helpButton.setOnAction(e -> FXUtils.openLink(Metadata.CONTACT_URL));
|
||||
FXUtils.installFastTooltip(helpButton, i18n("logwindow.help"));
|
||||
|
||||
|
||||
|
||||
@@ -362,7 +362,9 @@ public class InstallerItem extends Control {
|
||||
}
|
||||
return i18n("install.installer.version", s.version);
|
||||
} else if (state instanceof InstallableState) {
|
||||
return i18n("install.installer.not_installed");
|
||||
return control.style == Style.CARD
|
||||
? i18n("install.installer.do_not_install")
|
||||
: i18n("install.installer.not_installed");
|
||||
} else if (state instanceof IncompatibleState) {
|
||||
return i18n("install.installer.incompatible", i18n("install.installer." + ((IncompatibleState) state).incompatibleItemName));
|
||||
} else {
|
||||
|
||||
@@ -40,7 +40,7 @@ import javafx.stage.Stage;
|
||||
import org.jackhuang.hmcl.game.GameDumpGenerator;
|
||||
import org.jackhuang.hmcl.game.LauncherHelper;
|
||||
import org.jackhuang.hmcl.game.Log;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.setting.StyleSheets;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.util.Holder;
|
||||
import org.jackhuang.hmcl.util.CircularArrayList;
|
||||
@@ -92,7 +92,7 @@ public final class LogWindow extends Stage {
|
||||
this.logs = logs;
|
||||
this.impl = new LogWindowImpl();
|
||||
setScene(new Scene(impl, 800, 480));
|
||||
getScene().getStylesheets().addAll(Theme.getTheme().getStylesheets(config().getLauncherFontFamily()));
|
||||
StyleSheets.init(getScene());
|
||||
setTitle(i18n("logwindow.title"));
|
||||
FXUtils.setIcon(this);
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ public enum SVG {
|
||||
CHECK_CIRCLE("M10.6 16.6 17.65 9.55 16.25 8.15 10.6 13.8 7.75 10.95 6.35 12.35 10.6 16.6ZM12 22Q9.925 22 8.1 21.2125T4.925 19.075Q3.575 17.725 2.7875 15.9T2 12Q2 9.925 2.7875 8.1T4.925 4.925Q6.275 3.575 8.1 2.7875T12 2Q14.075 2 15.9 2.7875T19.075 4.925Q20.425 6.275 21.2125 8.1T22 12Q22 14.075 21.2125 15.9T19.075 19.075Q17.725 20.425 15.9 21.2125T12 22ZM12 20Q15.35 20 17.675 17.675T20 12Q20 8.65 17.675 6.325T12 4Q8.65 4 6.325 6.325T4 12Q4 15.35 6.325 17.675T12 20ZM12 12Z"),
|
||||
CLOSE("M6.4 19 5 17.6 10.6 12 5 6.4 6.4 5 12 10.6 17.6 5 19 6.4 13.4 12 19 17.6 17.6 19 12 13.4 6.4 19Z"),
|
||||
CONTENT_COPY("M9 18Q8.175 18 7.5875 17.4125T7 16V4Q7 3.175 7.5875 2.5875T9 2H18Q18.825 2 19.4125 2.5875T20 4V16Q20 16.825 19.4125 17.4125T18 18H9ZM9 16H18V4H9V16ZM5 22Q4.175 22 3.5875 21.4125T3 20V6H5V20H16V22H5ZM9 16V4 16Z"),
|
||||
CREATE_NEW_FOLDER("M14 16h2V14h2V12H16V10H14v2H12v2h2v2ZM4 20q-.825 0-1.4125-.5875T2 18V6q0-.825.5875-1.4125T4 4h6l2 2h8q.825 0 1.4125.5875T22 8V18q0 .825-.5875 1.4125T20 20H4Zm0-2H20V8H11.175l-2-2H4V18ZV6 18Z"),
|
||||
DELETE("M7 21Q6.175 21 5.5875 20.4125T5 19V6H4V4H9V3H15V4H20V6H19V19Q19 19.825 18.4125 20.4125T17 21H7ZM17 6H7V19H17V6ZM9 17H11V8H9V17ZM13 17H15V8H13V17ZM7 6V19 6Z"),
|
||||
DELETE_FOREVER("M9.4 16.5 12 13.9 14.6 16.5 16 15.1 13.4 12.5 16 9.9 14.6 8.5 12 11.1 9.4 8.5 8 9.9 10.6 12.5 8 15.1 9.4 16.5ZM7 21Q6.175 21 5.5875 20.4125T5 19V6H4V4H9V3H15V4H20V6H19V19Q19 19.825 18.4125 20.4125T17 21H7ZM17 6H7V19H17V6ZM7 6V19 6Z"),
|
||||
DEPLOYED_CODE("M11 19.425V12.575L5 9.1V15.95L11 19.425ZM13 19.425 19 15.95V9.1L13 12.575V19.425ZM12 10.85 17.925 7.425 12 4 6.075 7.425 12 10.85ZM4 17.7Q3.525 17.425 3.2625 16.975T3 15.975V8.025Q3 7.475 3.2625 7.025T4 6.3L11 2.275Q11.475 2 12 2T13 2.275L20 6.3Q20.475 6.575 20.7375 7.025T21 8.025V15.975Q21 16.525 20.7375 16.975T20 17.7L13 21.725Q12.525 22 12 22T11 21.725L4 17.7ZM12 12Z"),
|
||||
@@ -91,6 +92,7 @@ public enum SVG {
|
||||
RELEASE_CIRCLE("M9,7H13A2,2 0 0,1 15,9V11C15,11.84 14.5,12.55 13.76,12.85L15,17H13L11.8,13H11V17H9V7M11,9V11H13V9H11M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12C4,16.41 7.58,20 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z"), // Not Material
|
||||
RESTORE("M12 21Q8.55 21 5.9875 18.7125T3.05 13H5.1Q5.45 15.6 7.4125 17.3T12 19Q14.925 19 16.9625 16.9625T19 12Q19 9.075 16.9625 7.0375T12 5Q10.275 5 8.775 5.8T6.25 8H9V10H3V4H5V6.35Q6.275 4.75 8.1125 3.875T12 3Q13.875 3 15.5125 3.7125T18.3625 5.6375Q19.575 6.85 20.2875 8.4875T21 12Q21 13.875 20.2875 15.5125T18.3625 18.3625Q17.15 19.575 15.5125 20.2875T12 21Z"), // Not Material
|
||||
ROCKET_LAUNCH("M5.65 10.025 7.6 10.85Q7.95 10.15 8.325 9.5T9.15 8.2L7.75 7.925 5.65 10.025ZM9.2 12.1 12.05 14.925Q13.1 14.525 14.3 13.7T16.55 11.825Q18.3 10.075 19.2875 7.9375T20.15 4Q18.35 3.875 16.2 4.8625T12.3 7.6Q11.25 8.65 10.425 9.85T9.2 12.1ZM13.65 10.475Q13.075 9.9 13.075 9.0625T13.65 7.65Q14.225 7.075 15.075 7.075T16.5 7.65Q17.075 8.225 17.075 9.0625T16.5 10.475Q15.925 11.05 15.075 11.05T13.65 10.475ZM14.125 18.5 16.225 16.4 15.95 15Q15.3 15.45 14.65 15.8125T13.3 16.525L14.125 18.5ZM21.95 2.175Q22.425 5.2 21.3625 8.0625T17.7 13.525L18.2 16Q18.3 16.5 18.15 16.975T17.65 17.8L13.45 22 11.35 17.075 7.075 12.8 2.15 10.7 6.325 6.5Q6.675 6.15 7.1625 6T8.15 5.95L10.625 6.45Q13.225 3.85 16.075 2.775T21.95 2.175ZM3.925 15.975Q4.8 15.1 6.0625 15.0875T8.2 15.95Q9.075 16.825 9.0625 18.0875T8.175 20.225Q7.55 20.85 6.0875 21.3T2.05 22.1Q2.4 19.525 2.85 18.0625T3.925 15.975ZM5.35 17.375Q5.1 17.625 4.85 18.2875T4.5 19.625Q5.175 19.525 5.8375 19.2875T6.75 18.8Q7.05 18.5 7.075 18.075T6.8 17.35Q6.5 17.05 6.075 17.0625T5.35 17.375Z"),
|
||||
SCHEMA("M4 23V17H6.5V15H4V9H6.5V7H4V1h7V7H8.5V9H11v2h3V9h7v6H14V13H11v2H8.5v2H11v6H4Zm2-2H9V19H6v2Zm0-8H9V11H6v2Zm10 0h3V11H16v2ZM6 5H9V3H6V5ZM7.5 4Zm0 8Zm10 0Zm-10 8Z"),
|
||||
SCREENSHOT_MONITOR("M15 16H19V12H17.5V14.5H15V16ZM5 10H6.5V7.5H9V6H5V10ZM8 21V19H4Q3.175 19 2.5875 18.4125T2 17V5Q2 4.175 2.5875 3.5875T4 3H20Q20.825 3 21.4125 3.5875T22 5V17Q22 17.825 21.4125 18.4125T20 19H16V21H8ZM4 17H20V5H4V17ZM4 17V5 17Z"),
|
||||
SCRIPT("M14,20A2,2 0 0,0 16,18V5H9A1,1 0 0,0 8,6V16H5V5A3,3 0 0,1 8,2H19A3,3 0 0,1 22,5V6H18V18L18,19A3,3 0 0,1 15,22H5A3,3 0 0,1 2,19V18H12A2,2 0 0,0 14,20Z"), // Not Material
|
||||
SEARCH("M19.6 21 13.3 14.7Q12.55 15.3 11.575 15.65T9.5 16Q6.775 16 4.8875 14.1125T3 9.5Q3 6.775 4.8875 4.8875T9.5 3Q12.225 3 14.1125 4.8875T16 9.5Q16 10.6 15.65 11.575T14.7 13.3L21 19.6 19.6 21ZM9.5 14Q11.375 14 12.6875 12.6875T14 9.5Q14 7.625 12.6875 6.3125T9.5 5Q7.625 5 6.3125 6.3125T5 9.5Q5 11.375 6.3125 12.6875T9.5 14Z"),
|
||||
|
||||
@@ -25,14 +25,11 @@ import javafx.animation.Animation.Status;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Bounds;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.input.ScrollEvent;
|
||||
import javafx.util.Duration;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Function;
|
||||
import org.jackhuang.hmcl.util.Holder;
|
||||
|
||||
/**
|
||||
* Utility class for ScrollPanes.
|
||||
@@ -142,23 +139,23 @@ final class ScrollUtils {
|
||||
smoothScroll(scrollPane, speed, trackPadAdjustment);
|
||||
}
|
||||
|
||||
private static final double[] FRICTIONS = {0.99, 0.1, 0.05, 0.04, 0.03, 0.02, 0.01, 0.04, 0.01, 0.008, 0.008, 0.008, 0.008, 0.0006, 0.0005, 0.00003, 0.00001};
|
||||
private static final Duration DURATION = Duration.millis(3);
|
||||
|
||||
private static void smoothScroll(ScrollPane scrollPane, double speed, double trackPadAdjustment) {
|
||||
final double[] frictions = {0.99, 0.1, 0.05, 0.04, 0.03, 0.02, 0.01, 0.04, 0.01, 0.008, 0.008, 0.008, 0.008, 0.0006, 0.0005, 0.00003, 0.00001};
|
||||
final double[] derivatives = new double[frictions.length];
|
||||
AtomicReference<Double> atomicSpeed = new AtomicReference<>(speed);
|
||||
final double[] derivatives = new double[FRICTIONS.length];
|
||||
|
||||
Timeline timeline = new Timeline();
|
||||
AtomicReference<ScrollDirection> scrollDirection = new AtomicReference<>();
|
||||
Holder<ScrollDirection> scrollDirectionHolder = new Holder<>();
|
||||
final EventHandler<MouseEvent> mouseHandler = event -> timeline.stop();
|
||||
final EventHandler<ScrollEvent> scrollHandler = event -> {
|
||||
if (event.getEventType() == ScrollEvent.SCROLL) {
|
||||
scrollDirection.set(determineScrollDirection(event));
|
||||
if (isTrackPad(event, scrollDirection.get())) {
|
||||
atomicSpeed.set(speed / trackPadAdjustment);
|
||||
} else {
|
||||
atomicSpeed.set(speed);
|
||||
}
|
||||
derivatives[0] += scrollDirection.get().intDirection * atomicSpeed.get();
|
||||
ScrollDirection scrollDirection = determineScrollDirection(event);
|
||||
scrollDirectionHolder.value = scrollDirection;
|
||||
|
||||
double currentSpeed = isTrackPad(event, scrollDirection) ? speed / trackPadAdjustment : speed;
|
||||
|
||||
derivatives[0] += scrollDirection.intDirection * currentSpeed;
|
||||
if (timeline.getStatus() == Status.STOPPED) {
|
||||
timeline.play();
|
||||
}
|
||||
@@ -180,28 +177,26 @@ final class ScrollUtils {
|
||||
}
|
||||
});
|
||||
|
||||
timeline.getKeyFrames().add(new KeyFrame(Duration.millis(3), event -> {
|
||||
timeline.getKeyFrames().add(new KeyFrame(DURATION, event -> {
|
||||
for (int i = 0; i < derivatives.length; i++) {
|
||||
derivatives[i] *= frictions[i];
|
||||
derivatives[i] *= FRICTIONS[i];
|
||||
}
|
||||
for (int i = 1; i < derivatives.length; i++) {
|
||||
derivatives[i] += derivatives[i - 1];
|
||||
}
|
||||
|
||||
double dy = derivatives[derivatives.length - 1];
|
||||
Function<Bounds, Double> sizeFunction = (scrollDirection.get() == ScrollDirection.UP || scrollDirection.get() == ScrollDirection.DOWN) ? Bounds::getHeight : Bounds::getWidth;
|
||||
double size = sizeFunction.apply(scrollPane.getContent().getLayoutBounds());
|
||||
double value;
|
||||
switch (scrollDirection.get()) {
|
||||
double size;
|
||||
switch (scrollDirectionHolder.value) {
|
||||
case LEFT:
|
||||
case RIGHT:
|
||||
value = Math.min(Math.max(scrollPane.hvalueProperty().get() + dy / size, 0), 1);
|
||||
scrollPane.hvalueProperty().set(value);
|
||||
size = scrollPane.getContent().getLayoutBounds().getWidth();
|
||||
scrollPane.setHvalue(Math.min(Math.max(scrollPane.getHvalue() + dy / size, 0), 1));
|
||||
break;
|
||||
case UP:
|
||||
case DOWN:
|
||||
value = Math.min(Math.max(scrollPane.vvalueProperty().get() + dy / size, 0), 1);
|
||||
scrollPane.vvalueProperty().set(value);
|
||||
size = scrollPane.getContent().getLayoutBounds().getHeight();
|
||||
scrollPane.setVvalue(Math.min(Math.max(scrollPane.getVvalue() + dy / size, 0), 1));
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.ui.account;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
@@ -42,16 +43,70 @@ import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
||||
import org.jackhuang.hmcl.util.platform.NativeUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.windows.Kernel32;
|
||||
import org.jackhuang.hmcl.util.platform.windows.WinConstants;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.globalConfig;
|
||||
import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.createSelectedItemPropertyFor;
|
||||
|
||||
public class AccountListPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
public final class AccountListPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
static final BooleanProperty RESTRICTED = new SimpleBooleanProperty(true);
|
||||
|
||||
private static boolean isExemptedRegion() {
|
||||
String zoneId = ZoneId.systemDefault().getId();
|
||||
if (Arrays.asList(
|
||||
"Asia/Shanghai",
|
||||
// Although Asia/Beijing is not a legal name, Deepin uses it
|
||||
"Asia/Beijing",
|
||||
"Asia/Chongqing",
|
||||
"Asia/Chungking",
|
||||
"Asia/Harbin"
|
||||
).contains(zoneId))
|
||||
return true;
|
||||
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS && NativeUtils.USE_JNA) {
|
||||
Kernel32 kernel32 = Kernel32.INSTANCE;
|
||||
|
||||
// https://learn.microsoft.com/windows/win32/intl/table-of-geographical-locations
|
||||
if (kernel32 != null && kernel32.GetUserGeoID(WinConstants.GEOCLASS_NATION) == 45) // China
|
||||
return true;
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX && "GMT+08:00".equals(zoneId))
|
||||
// Some Linux distributions may use invalid time zone ids (e.g., Asia/Beijing)
|
||||
// Java may not be able to resolve this name and use GMT+08:00 instead.
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static {
|
||||
String property = System.getProperty("hmcl.offline.auth.restricted", "auto");
|
||||
|
||||
if ("false".equals(property)
|
||||
|| "auto".equals(property) && isExemptedRegion()
|
||||
|| globalConfig().isEnableOfflineAccount())
|
||||
RESTRICTED.set(false);
|
||||
else
|
||||
globalConfig().enableOfflineAccountProperty().addListener(new ChangeListener<Boolean>() {
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Boolean> o, Boolean oldValue, Boolean newValue) {
|
||||
if (newValue) {
|
||||
globalConfig().enableOfflineAccountProperty().removeListener(this);
|
||||
RESTRICTED.set(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private final ObservableList<AccountListItem> items;
|
||||
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("account.manage")));
|
||||
private final ListProperty<Account> accounts = new SimpleListProperty<>(this, "accounts", FXCollections.observableArrayList());
|
||||
@@ -88,6 +143,7 @@ public class AccountListPage extends DecoratorAnimatedPage implements DecoratorP
|
||||
private static class AccountListPageSkin extends DecoratorAnimatedPageSkin<AccountListPage> {
|
||||
|
||||
private final ObservableList<AdvancedListItem> authServerItems;
|
||||
private ChangeListener<Boolean> holder;
|
||||
|
||||
public AccountListPageSkin(AccountListPage skinnable) {
|
||||
super(skinnable);
|
||||
@@ -96,24 +152,21 @@ public class AccountListPage extends DecoratorAnimatedPage implements DecoratorP
|
||||
VBox boxMethods = new VBox();
|
||||
{
|
||||
boxMethods.getStyleClass().add("advanced-list-box-content");
|
||||
boxMethods.getChildren().add(new ClassTitle(i18n("account.create").toUpperCase(Locale.ROOT)));
|
||||
FXUtils.setLimitWidth(boxMethods, 200);
|
||||
|
||||
AdvancedListItem offlineItem = new AdvancedListItem();
|
||||
offlineItem.getStyleClass().add("navigation-drawer-item");
|
||||
offlineItem.setActionButtonVisible(false);
|
||||
offlineItem.setTitle(i18n("account.methods.offline"));
|
||||
offlineItem.setLeftGraphic(wrap(SVG.PERSON));
|
||||
offlineItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_OFFLINE)));
|
||||
boxMethods.getChildren().add(offlineItem);
|
||||
|
||||
AdvancedListItem microsoftItem = new AdvancedListItem();
|
||||
microsoftItem.getStyleClass().add("navigation-drawer-item");
|
||||
microsoftItem.setActionButtonVisible(false);
|
||||
microsoftItem.setTitle(i18n("account.methods.microsoft"));
|
||||
microsoftItem.setLeftGraphic(wrap(SVG.MICROSOFT));
|
||||
microsoftItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_MICROSOFT)));
|
||||
boxMethods.getChildren().add(microsoftItem);
|
||||
|
||||
AdvancedListItem offlineItem = new AdvancedListItem();
|
||||
offlineItem.getStyleClass().add("navigation-drawer-item");
|
||||
offlineItem.setActionButtonVisible(false);
|
||||
offlineItem.setTitle(i18n("account.methods.offline"));
|
||||
offlineItem.setLeftGraphic(wrap(SVG.PERSON));
|
||||
offlineItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_OFFLINE)));
|
||||
|
||||
VBox boxAuthServers = new VBox();
|
||||
authServerItems = MappedObservableList.create(skinnable.authServersProperty(), server -> {
|
||||
@@ -149,7 +202,29 @@ public class AccountListPage extends DecoratorAnimatedPage implements DecoratorP
|
||||
return item;
|
||||
});
|
||||
Bindings.bindContent(boxAuthServers.getChildren(), authServerItems);
|
||||
boxMethods.getChildren().add(boxAuthServers);
|
||||
|
||||
ClassTitle title = new ClassTitle(i18n("account.create").toUpperCase(Locale.ROOT));
|
||||
if (RESTRICTED.get()) {
|
||||
VBox wrapper = new VBox(offlineItem, boxAuthServers);
|
||||
wrapper.setPadding(Insets.EMPTY);
|
||||
FXUtils.installFastTooltip(wrapper, i18n("account.login.restricted"));
|
||||
|
||||
offlineItem.setDisable(true);
|
||||
boxAuthServers.setDisable(true);
|
||||
|
||||
boxMethods.getChildren().setAll(title, microsoftItem, wrapper);
|
||||
|
||||
holder = FXUtils.onWeakChange(RESTRICTED, value -> {
|
||||
if (!value) {
|
||||
holder = null;
|
||||
offlineItem.setDisable(false);
|
||||
boxAuthServers.setDisable(false);
|
||||
boxMethods.getChildren().setAll(title, microsoftItem, offlineItem, boxAuthServers);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
boxMethods.getChildren().setAll(title, microsoftItem, offlineItem, boxAuthServers);
|
||||
}
|
||||
}
|
||||
|
||||
AdvancedListItem addAuthServerItem = new AdvancedListItem();
|
||||
|
||||
@@ -19,6 +19,9 @@ package org.jackhuang.hmcl.ui.account;
|
||||
|
||||
import com.jfoenix.controls.*;
|
||||
import com.jfoenix.validation.base.ValidatorBase;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.NamedArg;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
@@ -35,6 +38,9 @@ import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextInputControl;
|
||||
import javafx.scene.layout.*;
|
||||
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.auth.AccountFactory;
|
||||
import org.jackhuang.hmcl.auth.CharacterSelector;
|
||||
import org.jackhuang.hmcl.auth.NoSelectedCharacterException;
|
||||
@@ -106,12 +112,17 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
|
||||
public CreateAccountPane(AccountFactory<?> factory) {
|
||||
if (factory == null) {
|
||||
showMethodSwitcher = true;
|
||||
String preferred = config().getPreferredLoginType();
|
||||
try {
|
||||
factory = Accounts.getAccountFactory(preferred);
|
||||
} catch (IllegalArgumentException e) {
|
||||
factory = Accounts.FACTORY_OFFLINE;
|
||||
if (AccountListPage.RESTRICTED.get()) {
|
||||
showMethodSwitcher = false;
|
||||
factory = Accounts.FACTORY_MICROSOFT;
|
||||
} else {
|
||||
showMethodSwitcher = true;
|
||||
String preferred = config().getPreferredLoginType();
|
||||
try {
|
||||
factory = Accounts.getAccountFactory(preferred);
|
||||
} catch (IllegalArgumentException e) {
|
||||
factory = Accounts.FACTORY_OFFLINE;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
showMethodSwitcher = false;
|
||||
@@ -257,16 +268,33 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
};
|
||||
|
||||
if (factory instanceof OfflineAccountFactory && username != null && !USERNAME_CHECKER_PATTERN.matcher(username).matches()) {
|
||||
Controllers.confirm(
|
||||
JFXButton btnYes = new JFXButton(i18n("button.ok"));
|
||||
btnYes.getStyleClass().add("dialog-error");
|
||||
btnYes.setOnAction(e -> doCreate.run());
|
||||
btnYes.setDisable(true);
|
||||
|
||||
int countdown = 10;
|
||||
KeyFrame[] keyFrames = new KeyFrame[countdown + 1];
|
||||
for (int i = 0; i < countdown; i++) {
|
||||
keyFrames[i] = new KeyFrame(Duration.seconds(i),
|
||||
new KeyValue(btnYes.textProperty(), i18n("button.ok.countdown", countdown - i)));
|
||||
}
|
||||
keyFrames[countdown] = new KeyFrame(Duration.seconds(countdown),
|
||||
new KeyValue(btnYes.textProperty(), i18n("button.ok")),
|
||||
new KeyValue(btnYes.disableProperty(), false));
|
||||
|
||||
Timeline timeline = new Timeline(keyFrames);
|
||||
Controllers.confirmAction(
|
||||
i18n("account.methods.offline.name.invalid"), i18n("message.warning"),
|
||||
MessageDialogPane.MessageType.WARNING,
|
||||
doCreate,
|
||||
btnYes,
|
||||
() -> {
|
||||
lblErrorMessage.setText(i18n("account.methods.offline.name.invalid.tip"));
|
||||
timeline.stop();
|
||||
body.setDisable(false);
|
||||
spinner.hideSpinner();
|
||||
}
|
||||
);
|
||||
timeline.play();
|
||||
} else {
|
||||
doCreate.run();
|
||||
}
|
||||
@@ -320,7 +348,9 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
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);
|
||||
JFXHyperlink bannedQueryLink = new JFXHyperlink(i18n("account.methods.ban_query"));
|
||||
bannedQueryLink.setExternalLink("https://enforcement.xbox.com/enforcement/showenforcementhistory");
|
||||
box.getChildren().setAll(profileLink, birthLink, purchaseLink, deauthorizeLink, forgotpasswordLink, createProfileLink, bannedQueryLink);
|
||||
GridPane.setColumnSpan(box, 2);
|
||||
|
||||
if (!IntegrityChecker.isOfficial()) {
|
||||
@@ -337,7 +367,7 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
hintPane.setSegment(i18n("account.methods.microsoft.snapshot"));
|
||||
|
||||
JFXHyperlink officialWebsite = new JFXHyperlink(i18n("account.methods.microsoft.snapshot.website"));
|
||||
officialWebsite.setExternalLink("https://hmcl.huangyuhui.net");
|
||||
officialWebsite.setExternalLink(Metadata.PUBLISH_URL);
|
||||
|
||||
vbox.getChildren().setAll(hintPane, officialWebsite);
|
||||
btnAccept.setDisable(true);
|
||||
|
||||
@@ -154,7 +154,7 @@ public class OfflineAccountSkinPane extends StackPane {
|
||||
result.getCape() != null ? result.getCape().getImage() : null);
|
||||
}
|
||||
}).start();
|
||||
}, skinItem.selectedDataProperty(), cslApiField.textProperty(), skinSelector.valueProperty(), capeSelector.valueProperty());
|
||||
}, skinItem.selectedDataProperty(), cslApiField.textProperty(), modelCombobox.valueProperty(), skinSelector.valueProperty(), capeSelector.valueProperty());
|
||||
|
||||
FXUtils.onChangeAndOperate(skinItem.selectedDataProperty(), selectedData -> {
|
||||
GridPane gridPane = new GridPane();
|
||||
|
||||
@@ -30,7 +30,7 @@ import com.jfoenix.controls.JFXListCell;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.scene.text.Font;
|
||||
|
||||
public class FontComboBox extends JFXComboBox<String> {
|
||||
public final class FontComboBox extends JFXComboBox<String> {
|
||||
|
||||
private boolean loaded = false;
|
||||
|
||||
|
||||
@@ -160,6 +160,13 @@ public final class MessageDialogPane extends HBox {
|
||||
this.dialog = new MessageDialogPane(text, title, type);
|
||||
}
|
||||
|
||||
public Builder addHyperLink(String text, String externalLink) {
|
||||
JFXHyperlink link = new JFXHyperlink(text);
|
||||
link.setExternalLink(externalLink);
|
||||
dialog.actions.getChildren().add(link);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addAction(Node actionNode) {
|
||||
dialog.addButton(actionNode);
|
||||
actionNode.getStyleClass().add("dialog-accept");
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2022 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.validation.base.ValidatorBase;
|
||||
import javafx.beans.NamedArg;
|
||||
import javafx.scene.control.TextInputControl;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class ServerAddressValidator extends ValidatorBase {
|
||||
private final boolean nullable;
|
||||
|
||||
public ServerAddressValidator() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
public ServerAddressValidator(@NamedArg("nullable") boolean nullable) {
|
||||
this(i18n("input.url"), nullable);
|
||||
}
|
||||
|
||||
public ServerAddressValidator(@NamedArg("message") String message, @NamedArg("nullable") boolean nullable) {
|
||||
super(message);
|
||||
this.nullable = nullable;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void eval() {
|
||||
if (srcControl.get() instanceof TextInputControl) {
|
||||
evalTextInputField();
|
||||
}
|
||||
}
|
||||
|
||||
private static final Pattern PATTERN = Pattern.compile("[-a-zA-Z0-9@:%._+~#=]{1,256}(:\\d+)?");
|
||||
|
||||
private void evalTextInputField() {
|
||||
TextInputControl textField = ((TextInputControl) srcControl.get());
|
||||
|
||||
if (StringUtils.isBlank(textField.getText()))
|
||||
hasErrors.set(!nullable);
|
||||
else
|
||||
hasErrors.set(!PATTERN.matcher(textField.getText()).matches());
|
||||
}
|
||||
}
|
||||
@@ -98,17 +98,15 @@ public class TaskExecutorDialogPane extends BorderPane {
|
||||
double speed = speedEvent.getSpeed();
|
||||
if (speed > 1024) {
|
||||
speed /= 1024;
|
||||
unit = "KB/s";
|
||||
unit = "KiB/s";
|
||||
}
|
||||
if (speed > 1024) {
|
||||
speed /= 1024;
|
||||
unit = "MB/s";
|
||||
unit = "MiB/s";
|
||||
}
|
||||
double finalSpeed = speed;
|
||||
String finalUnit = unit;
|
||||
Platform.runLater(() -> {
|
||||
lblProgress.setText(String.format("%.1f %s", finalSpeed, finalUnit));
|
||||
});
|
||||
Platform.runLater(() -> lblProgress.setText(String.format("%.1f %s", finalSpeed, finalUnit)));
|
||||
};
|
||||
FileDownloadTask.speedEvent.channel(FileDownloadTask.SpeedEvent.class).registerWeak(speedEventHandler);
|
||||
|
||||
|
||||
@@ -147,15 +147,15 @@ public final class TaskListPane extends StackPane {
|
||||
} else if (task instanceof ModpackUpdateTask) {
|
||||
task.setName(i18n("modpack.update"));
|
||||
} else if (task instanceof CurseInstallTask) {
|
||||
task.setName(i18n("modpack.install", i18n("modpack.type.curse")));
|
||||
task.setName(i18n("modpack.installing.given", i18n("modpack.type.curse")));
|
||||
} else if (task instanceof MultiMCModpackInstallTask) {
|
||||
task.setName(i18n("modpack.install", i18n("modpack.type.multimc")));
|
||||
task.setName(i18n("modpack.installing.given", i18n("modpack.type.multimc")));
|
||||
} else if (task instanceof ModrinthInstallTask) {
|
||||
task.setName(i18n("modpack.install", i18n("modpack.type.modrinth")));
|
||||
task.setName(i18n("modpack.installing.given", i18n("modpack.type.modrinth")));
|
||||
} else if (task instanceof ServerModpackLocalInstallTask) {
|
||||
task.setName(i18n("modpack.install", i18n("modpack.type.server")));
|
||||
task.setName(i18n("install.installing") + ": " + i18n("modpack.type.server"));
|
||||
} else if (task instanceof HMCLModpackInstallTask) {
|
||||
task.setName(i18n("modpack.install", i18n("modpack.type.hmcl")));
|
||||
task.setName(i18n("modpack.installing.given", i18n("modpack.type.hmcl")));
|
||||
} else if (task instanceof McbbsModpackExportTask || task instanceof MultiMCModpackExportTask || task instanceof ServerModpackExportTask) {
|
||||
task.setName(i18n("modpack.export"));
|
||||
} else if (task instanceof MinecraftInstanceTask) {
|
||||
@@ -163,7 +163,7 @@ public final class TaskListPane extends StackPane {
|
||||
} else if (task instanceof MojangJavaDownloadTask) {
|
||||
task.setName(i18n("download.java"));
|
||||
} else if (task instanceof JavaInstallTask) {
|
||||
task.setName(i18n("java.install"));
|
||||
task.setName(i18n("java.installing"));
|
||||
}
|
||||
|
||||
Platform.runLater(() -> {
|
||||
|
||||
@@ -40,6 +40,7 @@ import javafx.scene.paint.Color;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDnD;
|
||||
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
@@ -203,7 +204,11 @@ public class DecoratorController {
|
||||
case CUSTOM:
|
||||
String backgroundImage = config().getBackgroundImage();
|
||||
if (backgroundImage != null)
|
||||
image = tryLoadImage(Paths.get(backgroundImage));
|
||||
try {
|
||||
image = tryLoadImage(Paths.get(backgroundImage));
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Couldn't load background image", e);
|
||||
}
|
||||
break;
|
||||
case NETWORK:
|
||||
String backgroundImageUrl = config().getBackgroundImageUrl();
|
||||
@@ -231,12 +236,22 @@ public class DecoratorController {
|
||||
* Load background image from bg/, background.png, background.jpg, background.gif
|
||||
*/
|
||||
private Image loadDefaultBackgroundImage() {
|
||||
Image image = randomImageIn(Paths.get("bg"));
|
||||
Image image = randomImageIn(Metadata.HMCL_CURRENT_DIRECTORY.resolve("background"));
|
||||
if (image != null)
|
||||
return image;
|
||||
|
||||
for (String extension : FXUtils.IMAGE_EXTENSIONS) {
|
||||
image = tryLoadImage(Paths.get("background." + extension));
|
||||
image = tryLoadImage(Metadata.HMCL_CURRENT_DIRECTORY.resolve("background." + extension));
|
||||
if (image != null)
|
||||
return image;
|
||||
}
|
||||
|
||||
image = randomImageIn(Metadata.CURRENT_DIRECTORY.resolve("bg"));
|
||||
if (image != null)
|
||||
return image;
|
||||
|
||||
for (String extension : FXUtils.IMAGE_EXTENSIONS) {
|
||||
image = tryLoadImage(Metadata.CURRENT_DIRECTORY.resolve("background." + extension));
|
||||
if (image != null)
|
||||
return image;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ import javafx.scene.layout.*;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
@@ -190,7 +192,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
btnHelp.setFocusTraversable(false);
|
||||
btnHelp.setGraphic(SVG.HELP.createIcon(Theme.foregroundFillBinding(), -1));
|
||||
btnHelp.getStyleClass().add("jfx-decorator-button");
|
||||
btnHelp.setOnAction(e -> FXUtils.openLink("https://docs.hmcl.net/help.html"));
|
||||
btnHelp.setOnAction(e -> FXUtils.openLink(Metadata.CONTACT_URL));
|
||||
|
||||
JFXButton btnMin = new JFXButton();
|
||||
btnMin.setFocusTraversable(false);
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.download;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.FlowPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.InstallerItem;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public abstract class AbstractInstallersPage extends Control implements WizardPage {
|
||||
protected final WizardController controller;
|
||||
|
||||
protected InstallerItem.InstallerItemGroup group;
|
||||
protected JFXTextField txtName = new JFXTextField();
|
||||
|
||||
protected BooleanProperty installable = new SimpleBooleanProperty();
|
||||
|
||||
public AbstractInstallersPage(WizardController controller, String gameVersion, DownloadProvider downloadProvider) {
|
||||
this.controller = controller;
|
||||
this.group = new InstallerItem.InstallerItemGroup(gameVersion, getInstallerItemStyle());
|
||||
|
||||
for (InstallerItem library : group.getLibraries()) {
|
||||
String libraryId = library.getLibraryId();
|
||||
if (libraryId.equals(LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId())) continue;
|
||||
library.setOnInstall(() -> {
|
||||
if (LibraryAnalyzer.LibraryType.FABRIC_API.getPatchId().equals(libraryId)) {
|
||||
Controllers.dialog(i18n("install.installer.fabric-api.warning"), i18n("message.warning"), MessageDialogPane.MessageType.WARNING);
|
||||
}
|
||||
|
||||
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.setOnRemove(() -> {
|
||||
controller.getSettings().remove(libraryId);
|
||||
reload();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected InstallerItem.Style getInstallerItemStyle() {
|
||||
return InstallerItem.Style.CARD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract String getTitle();
|
||||
|
||||
protected abstract void reload();
|
||||
|
||||
@Override
|
||||
public void onNavigate(Map<String, Object> settings) {
|
||||
reload();
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract void cleanup(Map<String, Object> settings);
|
||||
|
||||
protected abstract void onInstall();
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new InstallersPageSkin(this);
|
||||
}
|
||||
|
||||
protected static class InstallersPageSkin extends SkinBase<AbstractInstallersPage> {
|
||||
/**
|
||||
* Constructor for all SkinBase instances.
|
||||
*
|
||||
* @param control The control for which this Skin should attach to.
|
||||
*/
|
||||
protected InstallersPageSkin(AbstractInstallersPage control) {
|
||||
super(control);
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
root.setPadding(new Insets(16));
|
||||
|
||||
{
|
||||
HBox versionNamePane = new HBox(8);
|
||||
versionNamePane.getStyleClass().add("card-non-transparent");
|
||||
versionNamePane.setStyle("-fx-padding: 20 8 20 16");
|
||||
versionNamePane.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
control.txtName.setMaxWidth(300);
|
||||
versionNamePane.getChildren().setAll(new Label(i18n("version.name")), control.txtName);
|
||||
root.setTop(versionNamePane);
|
||||
}
|
||||
|
||||
{
|
||||
InstallerItem[] libraries = control.group.getLibraries();
|
||||
|
||||
FlowPane libraryPane = new FlowPane(libraries);
|
||||
libraryPane.setVgap(16);
|
||||
libraryPane.setHgap(16);
|
||||
|
||||
if (libraries.length <= 8) {
|
||||
BorderPane.setMargin(libraryPane, new Insets(16, 0, 16, 0));
|
||||
root.setCenter(libraryPane);
|
||||
} else {
|
||||
ScrollPane scrollPane = new ScrollPane(libraryPane);
|
||||
scrollPane.setFitToWidth(true);
|
||||
scrollPane.setFitToHeight(true);
|
||||
BorderPane.setMargin(scrollPane, new Insets(16, 0, 16, 0));
|
||||
root.setCenter(scrollPane);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
JFXButton installButton = FXUtils.newRaisedButton(i18n("button.install"));
|
||||
installButton.disableProperty().bind(control.installable.not());
|
||||
installButton.setPrefWidth(100);
|
||||
installButton.setPrefHeight(40);
|
||||
installButton.setOnAction(e -> control.onInstall());
|
||||
BorderPane.setAlignment(installButton, Pos.CENTER_RIGHT);
|
||||
root.setBottom(installButton);
|
||||
}
|
||||
|
||||
getChildren().setAll(root);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.download;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
@@ -36,26 +35,21 @@ import java.util.Optional;
|
||||
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.MINECRAFT;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
class AdditionalInstallersPage extends InstallersPage {
|
||||
class AdditionalInstallersPage extends AbstractInstallersPage {
|
||||
protected final BooleanProperty compatible = new SimpleBooleanProperty();
|
||||
protected final GameRepository repository;
|
||||
protected final String gameVersion;
|
||||
protected final Version version;
|
||||
|
||||
public AdditionalInstallersPage(String gameVersion, Version version, WizardController controller, HMCLGameRepository repository, DownloadProvider downloadProvider) {
|
||||
super(controller, repository, gameVersion, downloadProvider);
|
||||
super(controller, gameVersion, downloadProvider);
|
||||
this.gameVersion = gameVersion;
|
||||
this.version = version;
|
||||
this.repository = repository;
|
||||
|
||||
txtName.getValidators().clear();
|
||||
txtName.setText(version.getId());
|
||||
txtName.setEditable(false);
|
||||
|
||||
installable.bind(Bindings.createBooleanBinding(
|
||||
() -> compatible.get() && txtName.validate(),
|
||||
txtName.textProperty(), compatible));
|
||||
|
||||
for (InstallerItem library : group.getLibraries()) {
|
||||
String libraryId = library.getLibraryId();
|
||||
if (libraryId.equals("game")) continue;
|
||||
|
||||
@@ -43,7 +43,10 @@ import org.jackhuang.hmcl.ui.construct.TabControl;
|
||||
import org.jackhuang.hmcl.ui.construct.TabHeader;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.ui.versions.*;
|
||||
import org.jackhuang.hmcl.ui.versions.DownloadListPage;
|
||||
import org.jackhuang.hmcl.ui.versions.HMCLLocalizedDownloadListPage;
|
||||
import org.jackhuang.hmcl.ui.versions.VersionPage;
|
||||
import org.jackhuang.hmcl.ui.versions.Versions;
|
||||
import org.jackhuang.hmcl.ui.wizard.Navigation;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
@@ -54,8 +57,8 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@@ -76,10 +79,16 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
private WeakListenerHolder listenerHolder;
|
||||
|
||||
public DownloadPage() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public DownloadPage(String uploadVersion) {
|
||||
newGameTab.setNodeSupplier(loadVersionFor(() -> new VersionsPage(versionPageNavigator, i18n("install.installer.choose", i18n("install.installer.game")), "", DownloadProviders.getDownloadProvider(),
|
||||
"game", versionPageNavigator::onGameSelected)));
|
||||
modpackTab.setNodeSupplier(loadVersionFor(() -> {
|
||||
DownloadListPage page = HMCLLocalizedDownloadListPage.ofModPack(Versions::downloadModpackImpl, false);
|
||||
DownloadListPage page = HMCLLocalizedDownloadListPage.ofModPack((profile, __, file) -> {
|
||||
Versions.downloadModpackImpl(profile, uploadVersion, file);
|
||||
}, false);
|
||||
|
||||
JFXButton installLocalModpackButton = FXUtils.newRaisedButton(i18n("install.modpack"));
|
||||
installLocalModpackButton.setOnAction(e -> Versions.importModpack());
|
||||
@@ -130,7 +139,7 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
};
|
||||
}
|
||||
|
||||
private static void download(Profile profile, @Nullable String version, RemoteMod.Version file, String subdirectoryName) {
|
||||
public static void download(Profile profile, @Nullable String version, RemoteMod.Version file, String subdirectoryName) {
|
||||
if (version == null) version = profile.getSelectedVersion();
|
||||
|
||||
Path runDirectory = profile.getRepository().hasVersion(version) ? profile.getRepository().getRunDirectory(version).toPath() : profile.getRepository().getBaseDirectory().toPath();
|
||||
@@ -294,7 +303,7 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
|
||||
@Override
|
||||
public Object finish(Map<String, Object> settings) {
|
||||
settings.put("title", i18n("install.new_game"));
|
||||
settings.put("title", i18n("install.new_game.installation"));
|
||||
settings.put("success_message", i18n("install.success"));
|
||||
settings.put("failure_callback", (FailureCallback) (settings1, exception, next) -> UpdateInstallerWizardProvider.alertFailureMessage(exception, next));
|
||||
|
||||
|
||||
@@ -17,47 +17,29 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.download;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.FlowPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.InstallerItem;
|
||||
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.ui.wizard.WizardPage;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
|
||||
import static javafx.beans.binding.Bindings.createBooleanBinding;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class InstallersPage extends Control implements WizardPage {
|
||||
protected final WizardController controller;
|
||||
|
||||
protected InstallerItem.InstallerItemGroup group;
|
||||
protected JFXTextField txtName = new JFXTextField();
|
||||
protected BooleanProperty installable = new SimpleBooleanProperty();
|
||||
public class InstallersPage extends AbstractInstallersPage {
|
||||
|
||||
private boolean isNameModifiedByUser = false;
|
||||
|
||||
public InstallersPage(WizardController controller, HMCLGameRepository repository, String gameVersion, DownloadProvider downloadProvider) {
|
||||
this.controller = controller;
|
||||
this.group = new InstallerItem.InstallerItemGroup(gameVersion, getInstallerItemStyle());
|
||||
super(controller, gameVersion, downloadProvider);
|
||||
|
||||
txtName.getValidators().addAll(
|
||||
new RequiredValidator(),
|
||||
@@ -66,27 +48,6 @@ public class InstallersPage extends Control implements WizardPage {
|
||||
installable.bind(createBooleanBinding(txtName::validate, txtName.textProperty()));
|
||||
|
||||
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.setOnInstall(() -> {
|
||||
if (LibraryAnalyzer.LibraryType.FABRIC_API.getPatchId().equals(libraryId)) {
|
||||
Controllers.dialog(i18n("install.installer.fabric-api.warning"), i18n("message.warning"), MessageDialogPane.MessageType.WARNING);
|
||||
}
|
||||
|
||||
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.setOnRemove(() -> {
|
||||
controller.getSettings().remove(libraryId);
|
||||
reload();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected InstallerItem.Style getInstallerItemStyle() {
|
||||
return InstallerItem.Style.CARD;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -112,11 +73,6 @@ public class InstallersPage extends Control implements WizardPage {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNavigate(Map<String, Object> settings) {
|
||||
reload();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup(Map<String, Object> settings) {
|
||||
}
|
||||
@@ -125,7 +81,7 @@ public class InstallersPage extends Control implements WizardPage {
|
||||
String name = txtName.getText();
|
||||
|
||||
// Check for non-ASCII characters.
|
||||
if (!StandardCharsets.US_ASCII.newEncoder().canEncode(name)) {
|
||||
if (!StringUtils.isASCII(name)) {
|
||||
Controllers.dialog(new MessageDialogPane.Builder(
|
||||
i18n("install.name.invalid"),
|
||||
i18n("message.warning"),
|
||||
@@ -143,11 +99,6 @@ public class InstallersPage extends Control implements WizardPage {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new InstallersPageSkin(this);
|
||||
}
|
||||
|
||||
private void setTxtNameWithLoaders() {
|
||||
StringBuilder nameBuilder = new StringBuilder(group.getGame().versionProperty().get().getVersion());
|
||||
|
||||
@@ -190,62 +141,4 @@ public class InstallersPage extends Control implements WizardPage {
|
||||
txtName.setText(nameBuilder.toString());
|
||||
isNameModifiedByUser = false;
|
||||
}
|
||||
|
||||
protected static class InstallersPageSkin extends SkinBase<InstallersPage> {
|
||||
|
||||
/**
|
||||
* Constructor for all SkinBase instances.
|
||||
*
|
||||
* @param control The control for which this Skin should attach to.
|
||||
*/
|
||||
protected InstallersPageSkin(InstallersPage control) {
|
||||
super(control);
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
root.setPadding(new Insets(16));
|
||||
|
||||
{
|
||||
HBox versionNamePane = new HBox(8);
|
||||
versionNamePane.getStyleClass().add("card-non-transparent");
|
||||
versionNamePane.setStyle("-fx-padding: 20 8 20 16");
|
||||
versionNamePane.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
control.txtName.setMaxWidth(300);
|
||||
versionNamePane.getChildren().setAll(new Label(i18n("version.name")), control.txtName);
|
||||
root.setTop(versionNamePane);
|
||||
}
|
||||
|
||||
{
|
||||
InstallerItem[] libraries = control.group.getLibraries();
|
||||
|
||||
FlowPane libraryPane = new FlowPane(libraries);
|
||||
libraryPane.setVgap(16);
|
||||
libraryPane.setHgap(16);
|
||||
|
||||
if (libraries.length <= 8) {
|
||||
BorderPane.setMargin(libraryPane, new Insets(16, 0, 16, 0));
|
||||
root.setCenter(libraryPane);
|
||||
} else {
|
||||
ScrollPane scrollPane = new ScrollPane(libraryPane);
|
||||
scrollPane.setFitToWidth(true);
|
||||
scrollPane.setFitToHeight(true);
|
||||
BorderPane.setMargin(scrollPane, new Insets(16, 0, 16, 0));
|
||||
root.setCenter(scrollPane);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
JFXButton installButton = FXUtils.newRaisedButton(i18n("button.install"));
|
||||
installButton.disableProperty().bind(control.installable.not());
|
||||
installButton.setPrefWidth(100);
|
||||
installButton.setPrefHeight(40);
|
||||
installButton.setOnAction(e -> control.onInstall());
|
||||
BorderPane.setAlignment(installButton, Pos.CENTER_RIGHT);
|
||||
root.setBottom(installButton);
|
||||
}
|
||||
|
||||
getChildren().setAll(root);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,6 @@ 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;
|
||||
|
||||
@@ -153,7 +152,7 @@ public final class LocalModpackPage extends ModpackPage {
|
||||
String name = txtModpackName.getText();
|
||||
|
||||
// Check for non-ASCII characters.
|
||||
if (!StandardCharsets.US_ASCII.newEncoder().canEncode(name)) {
|
||||
if (!StringUtils.isASCII(name)) {
|
||||
Controllers.dialog(new MessageDialogPane.Builder(
|
||||
i18n("install.name.invalid"),
|
||||
i18n("message.warning"),
|
||||
|
||||
@@ -120,7 +120,7 @@ public class ModpackInstallWizardProvider implements WizardProvider {
|
||||
|
||||
@Override
|
||||
public Object finish(Map<String, Object> settings) {
|
||||
settings.put("title", i18n("install.modpack"));
|
||||
settings.put("title", i18n("install.modpack.installation"));
|
||||
settings.put("success_message", i18n("install.success"));
|
||||
settings.put("failure_callback", new FailureCallback() {
|
||||
@Override
|
||||
|
||||
@@ -50,6 +50,7 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.download.LocalModpackPage.MODPACK_FILE;
|
||||
import static org.jackhuang.hmcl.ui.download.LocalModpackPage.MODPACK_NAME;
|
||||
import static org.jackhuang.hmcl.ui.download.RemoteModpackPage.MODPACK_SERVER_MANIFEST;
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
@@ -170,7 +171,8 @@ public final class ModpackSelectionPage extends VBox implements WizardPage {
|
||||
}
|
||||
|
||||
public void onChooseRepository() {
|
||||
DownloadPage downloadPage = new DownloadPage();
|
||||
String modPackName = (String) controller.getSettings().get(MODPACK_NAME);
|
||||
DownloadPage downloadPage = new DownloadPage(modPackName);
|
||||
downloadPage.showModpackDownloads();
|
||||
Controllers.navigate(downloadPage);
|
||||
}
|
||||
|
||||
+1
-1
@@ -71,7 +71,7 @@ public final class UpdateInstallerWizardProvider implements WizardProvider {
|
||||
|
||||
@Override
|
||||
public Object finish(Map<String, Object> settings) {
|
||||
settings.put("title", i18n("install.change_version"));
|
||||
settings.put("title", i18n("install.change_version.process"));
|
||||
settings.put("success_message", i18n("install.success"));
|
||||
settings.put("failure_callback", (FailureCallback) (settings1, exception, next) -> alertFailureMessage(exception, next));
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ public final class VanillaInstallWizardProvider implements WizardProvider {
|
||||
|
||||
@Override
|
||||
public Object finish(Map<String, Object> settings) {
|
||||
settings.put("title", i18n("install.new_game"));
|
||||
settings.put("title", i18n("install.new_game.installation"));
|
||||
settings.put("success_message", i18n("install.success"));
|
||||
settings.put("failure_callback", (FailureCallback) (settings1, exception, next) -> UpdateInstallerWizardProvider.alertFailureMessage(exception, next));
|
||||
|
||||
|
||||
@@ -292,6 +292,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
switch (it.getVersionType()) {
|
||||
case RELEASE:
|
||||
return chkRelease.isSelected();
|
||||
case PENDING:
|
||||
case SNAPSHOT:
|
||||
return chkSnapshot.isSelected();
|
||||
case OLD:
|
||||
@@ -411,11 +412,10 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
content.setImage(VersionIconType.GRASS.getIcon());
|
||||
content.setExternalLink(i18n("wiki.version.game.release", remoteVersion.getGameVersion()));
|
||||
break;
|
||||
case PENDING:
|
||||
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:
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.ui.export;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.mod.ModAdviser;
|
||||
import org.jackhuang.hmcl.mod.ModpackExportInfo;
|
||||
import org.jackhuang.hmcl.mod.mcbbs.McbbsModpackExportTask;
|
||||
@@ -25,7 +26,7 @@ import org.jackhuang.hmcl.mod.multimc.MultiMCInstanceConfiguration;
|
||||
import org.jackhuang.hmcl.mod.multimc.MultiMCModpackExportTask;
|
||||
import org.jackhuang.hmcl.mod.server.ServerModpackExportTask;
|
||||
import org.jackhuang.hmcl.setting.Config;
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.setting.FontManager;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
@@ -123,19 +124,31 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
exported.setPreferredLoginType(config().getPreferredLoginType());
|
||||
exported.getAuthlibInjectorServers().setAll(config().getAuthlibInjectorServers());
|
||||
|
||||
zip.putTextFile(exported.toJson(), ConfigHolder.CONFIG_FILENAME);
|
||||
zip.putTextFile(exported.toJson(), ".hmcl/hmcl.json");
|
||||
zip.putFile(tempModpack, "modpack.zip");
|
||||
|
||||
File bg = new File("bg").getAbsoluteFile();
|
||||
if (bg.isDirectory())
|
||||
zip.putDirectory(bg.toPath(), "bg");
|
||||
Path bg = Metadata.HMCL_CURRENT_DIRECTORY.resolve("background");
|
||||
if (!Files.isDirectory(bg))
|
||||
bg = Metadata.CURRENT_DIRECTORY.resolve("bg");
|
||||
if (Files.isDirectory(bg))
|
||||
zip.putDirectory(bg, ".hmcl/bg");
|
||||
|
||||
for (String extension : FXUtils.IMAGE_EXTENSIONS) {
|
||||
String fileName = "background." + extension;
|
||||
Path background = Metadata.HMCL_CURRENT_DIRECTORY.resolve(fileName);
|
||||
if (!Files.isRegularFile(background))
|
||||
background = Metadata.CURRENT_DIRECTORY.resolve(fileName);
|
||||
if (Files.isRegularFile(background))
|
||||
zip.putFile(background, ".hmcl/" + fileName);
|
||||
}
|
||||
|
||||
File background = new File(fileName).getAbsoluteFile();
|
||||
if (background.isFile())
|
||||
zip.putFile(background, "background.png");
|
||||
for (String extension : FontManager.FONT_EXTENSIONS) {
|
||||
String fileName = "font." + extension;
|
||||
Path font = Metadata.HMCL_CURRENT_DIRECTORY.resolve(fileName);
|
||||
if (!Files.isRegularFile(font))
|
||||
font = Metadata.CURRENT_DIRECTORY.resolve(fileName);
|
||||
if (Files.isRegularFile(font))
|
||||
zip.putFile(font, ".hmcl/" + fileName);
|
||||
}
|
||||
|
||||
zip.putFile(launcherJar, launcherJar.getFileName().toString());
|
||||
|
||||
@@ -27,6 +27,8 @@ import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.stage.FileChooser;
|
||||
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
@@ -41,7 +43,7 @@ import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.JarUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.SystemInfo;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
@@ -52,6 +54,7 @@ import static org.jackhuang.hmcl.ui.FXUtils.jfxListCellFactory;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
|
||||
import static org.jackhuang.hmcl.ui.export.ModpackTypeSelectionPage.MODPACK_TYPE;
|
||||
import static org.jackhuang.hmcl.ui.export.ModpackTypeSelectionPage.MODPACK_TYPE_SERVER;
|
||||
import static org.jackhuang.hmcl.util.DataSizeUnit.MEGABYTES;
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
@@ -173,7 +176,7 @@ public final class ModpackInfoPage extends Control implements WizardPage {
|
||||
|
||||
if (skinnable.controller.getSettings().get(MODPACK_TYPE) == MODPACK_TYPE_SERVER) {
|
||||
Hyperlink hyperlink = new Hyperlink(i18n("modpack.wizard.step.initialization.server"));
|
||||
hyperlink.setOnAction(e -> FXUtils.openLink("https://docs.hmcl.net/modpack/serverpack.html"));
|
||||
hyperlink.setOnAction(e -> FXUtils.openLink(Metadata.DOCS_URL + "/modpack/serverpack.html"));
|
||||
borderPane.setTop(hyperlink);
|
||||
} else {
|
||||
HintPane pane = new HintPane(MessageDialogPane.MessageType.INFO);
|
||||
@@ -285,12 +288,12 @@ public final class ModpackInfoPage extends Control implements WizardPage {
|
||||
AtomicBoolean changedByTextField = new AtomicBoolean(false);
|
||||
FXUtils.onChangeAndOperate(skinnable.minMemory, minMemory -> {
|
||||
changedByTextField.set(true);
|
||||
slider.setValue(minMemory.intValue() * 1.0 / OperatingSystem.TOTAL_MEMORY);
|
||||
slider.setValue(minMemory.intValue() * 1.0 / MEGABYTES.convertFromBytes(SystemInfo.getTotalMemorySize()));
|
||||
changedByTextField.set(false);
|
||||
});
|
||||
slider.valueProperty().addListener((value, oldVal, newVal) -> {
|
||||
if (changedByTextField.get()) return;
|
||||
skinnable.minMemory.set((int) (value.getValue().doubleValue() * OperatingSystem.TOTAL_MEMORY));
|
||||
skinnable.minMemory.set((int) (value.getValue().doubleValue() * MEGABYTES.convertFromBytes(SystemInfo.getTotalMemorySize())));
|
||||
});
|
||||
|
||||
JFXTextField txtMinMemory = new JFXTextField();
|
||||
@@ -299,7 +302,7 @@ public final class ModpackInfoPage extends Control implements WizardPage {
|
||||
FXUtils.setLimitWidth(txtMinMemory, 60);
|
||||
validatingFields.add(txtMinMemory);
|
||||
|
||||
lowerBoundPane.getChildren().setAll(label, slider, txtMinMemory, new Label("MB"));
|
||||
lowerBoundPane.getChildren().setAll(label, slider, txtMinMemory, new Label("MiB"));
|
||||
}
|
||||
|
||||
pane.getChildren().setAll(title, lowerBoundPane);
|
||||
|
||||
@@ -43,12 +43,15 @@ public final class ModpackTypeSelectionPage extends VBox implements WizardPage {
|
||||
|
||||
public ModpackTypeSelectionPage(WizardController controller) {
|
||||
this.controller = controller;
|
||||
this.setPadding(new Insets(10));
|
||||
|
||||
Label title = new Label(i18n("modpack.export.as"));
|
||||
title.setPadding(new Insets(8));
|
||||
VBox.setMargin(title, new Insets(8, 0, 8, 12));
|
||||
|
||||
this.getStyleClass().add("jfx-list-view");
|
||||
this.setMaxSize(300, 150);
|
||||
this.setMaxSize(400, 150);
|
||||
this.setSpacing(8);
|
||||
|
||||
this.getChildren().setAll(
|
||||
title,
|
||||
createButton(MODPACK_TYPE_MCBBS, McbbsModpackExportTask.OPTION),
|
||||
@@ -59,6 +62,8 @@ public final class ModpackTypeSelectionPage extends VBox implements WizardPage {
|
||||
|
||||
private JFXButton createButton(String type, ModpackExportInfo.Options option) {
|
||||
JFXButton button = new JFXButton();
|
||||
|
||||
button.getStyleClass().add("card");
|
||||
button.setOnAction(e -> {
|
||||
controller.getSettings().put(MODPACK_TYPE, type);
|
||||
controller.getSettings().put(MODPACK_INFO_OPTION, option);
|
||||
|
||||
@@ -50,7 +50,7 @@ public final class AboutPage extends StackPane {
|
||||
launcher.setImage(FXUtils.newBuiltinImage("/assets/img/icon.png"));
|
||||
launcher.setTitle("Hello Minecraft! Launcher");
|
||||
launcher.setSubtitle(Metadata.VERSION);
|
||||
launcher.setExternalLink("https://hmcl.huangyuhui.net");
|
||||
launcher.setExternalLink(Metadata.PUBLISH_URL);
|
||||
|
||||
IconedTwoLineListItem author = new IconedTwoLineListItem();
|
||||
author.setImage(FXUtils.newBuiltinImage("/assets/img/yellow_fish.png"));
|
||||
@@ -70,7 +70,7 @@ public final class AboutPage extends StackPane {
|
||||
IconedTwoLineListItem copyright = new IconedTwoLineListItem();
|
||||
copyright.setTitle(i18n("about.copyright"));
|
||||
copyright.setSubtitle(i18n("about.copyright.statement"));
|
||||
copyright.setExternalLink("https://hmcl.huangyuhui.net/about/");
|
||||
copyright.setExternalLink(Metadata.ABOUT_URL);
|
||||
|
||||
IconedTwoLineListItem claim = new IconedTwoLineListItem();
|
||||
claim.setTitle(i18n("about.claim"));
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.ui.main;
|
||||
|
||||
import com.jfoenix.controls.*;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
@@ -28,6 +29,7 @@ import javafx.scene.layout.*;
|
||||
import org.jackhuang.hmcl.setting.DownloadProviders;
|
||||
import org.jackhuang.hmcl.task.FetchTask;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
|
||||
|
||||
@@ -37,11 +39,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
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.reversedSelectedPropertyFor;
|
||||
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.selectedItemPropertyFor;
|
||||
|
||||
public class DownloadSettingsPage extends StackPane {
|
||||
|
||||
private final WeakListenerHolder holder = new WeakListenerHolder();
|
||||
|
||||
public DownloadSettingsPage() {
|
||||
VBox content = new VBox(10);
|
||||
content.setPadding(new Insets(10));
|
||||
@@ -164,38 +167,76 @@ public class DownloadSettingsPage extends StackPane {
|
||||
VBox proxyList = new VBox(10);
|
||||
proxyList.getStyleClass().add("card-non-transparent");
|
||||
|
||||
VBox proxyPane = new VBox();
|
||||
HBox proxyTypePane = new HBox();
|
||||
{
|
||||
JFXCheckBox chkDisableProxy = new JFXCheckBox(i18n("settings.launcher.proxy.disable"));
|
||||
VBox.setMargin(chkDisableProxy, new Insets(8, 0, 0, 0));
|
||||
proxyList.getChildren().add(chkDisableProxy);
|
||||
reversedSelectedPropertyFor(chkDisableProxy).bindBidirectional(config().hasProxyProperty());
|
||||
proxyPane.disableProperty().bind(chkDisableProxy.selectedProperty());
|
||||
proxyTypePane.setPadding(new Insets(10, 0, 0, 0));
|
||||
|
||||
ToggleGroup proxyConfigurationGroup = new ToggleGroup();
|
||||
|
||||
JFXRadioButton chkProxyDefault = new JFXRadioButton(i18n("settings.launcher.proxy.default"));
|
||||
chkProxyDefault.setUserData(null);
|
||||
chkProxyDefault.setToggleGroup(proxyConfigurationGroup);
|
||||
|
||||
JFXRadioButton chkProxyNone = new JFXRadioButton(i18n("settings.launcher.proxy.none"));
|
||||
chkProxyNone.setUserData(Proxy.Type.DIRECT);
|
||||
chkProxyNone.setToggleGroup(proxyConfigurationGroup);
|
||||
|
||||
JFXRadioButton chkProxyHttp = new JFXRadioButton(i18n("settings.launcher.proxy.http"));
|
||||
chkProxyHttp.setUserData(Proxy.Type.HTTP);
|
||||
chkProxyHttp.setToggleGroup(proxyConfigurationGroup);
|
||||
|
||||
|
||||
JFXRadioButton chkProxySocks = new JFXRadioButton(i18n("settings.launcher.proxy.socks"));
|
||||
chkProxySocks.setUserData(Proxy.Type.SOCKS);
|
||||
chkProxySocks.setToggleGroup(proxyConfigurationGroup);
|
||||
|
||||
if (config().hasProxy()) {
|
||||
Proxy.Type proxyType = config().getProxyType();
|
||||
if (proxyType == Proxy.Type.DIRECT) {
|
||||
chkProxyNone.setSelected(true);
|
||||
} else if (proxyType == Proxy.Type.HTTP) {
|
||||
chkProxyHttp.setSelected(true);
|
||||
} else if (proxyType == Proxy.Type.SOCKS) {
|
||||
chkProxySocks.setSelected(true);
|
||||
} else {
|
||||
chkProxyNone.setSelected(true);
|
||||
}
|
||||
} else {
|
||||
chkProxyDefault.setSelected(true);
|
||||
}
|
||||
|
||||
holder.add(FXUtils.onWeakChange(proxyConfigurationGroup.selectedToggleProperty(), toggle -> {
|
||||
Proxy.Type proxyType = toggle != null ? (Proxy.Type) toggle.getUserData() : null;
|
||||
|
||||
if (proxyType == null) {
|
||||
config().setHasProxy(false);
|
||||
config().setProxyType(null);
|
||||
} else {
|
||||
config().setHasProxy(true);
|
||||
config().setProxyType(proxyType);
|
||||
}
|
||||
}));
|
||||
|
||||
proxyTypePane.getChildren().setAll(chkProxyDefault, chkProxyNone, chkProxyHttp, chkProxySocks);
|
||||
proxyList.getChildren().add(proxyTypePane);
|
||||
}
|
||||
|
||||
VBox proxyPane = new VBox();
|
||||
{
|
||||
proxyPane.setPadding(new Insets(0, 0, 0, 30));
|
||||
proxyPane.disableProperty().bind(
|
||||
Bindings.createBooleanBinding(() ->
|
||||
!config().hasProxy() || config().getProxyType() == null || config().getProxyType() == Proxy.Type.DIRECT,
|
||||
config().hasProxyProperty(),
|
||||
config().proxyTypeProperty()));
|
||||
|
||||
ColumnConstraints colHgrow = new ColumnConstraints();
|
||||
colHgrow.setHgrow(Priority.ALWAYS);
|
||||
|
||||
JFXRadioButton chkProxyNone;
|
||||
JFXRadioButton chkProxyHttp;
|
||||
JFXRadioButton chkProxySocks;
|
||||
{
|
||||
HBox hBox = new HBox();
|
||||
chkProxyNone = new JFXRadioButton(i18n("settings.launcher.proxy.none"));
|
||||
chkProxyHttp = new JFXRadioButton(i18n("settings.launcher.proxy.http"));
|
||||
chkProxySocks = new JFXRadioButton(i18n("settings.launcher.proxy.socks"));
|
||||
hBox.getChildren().setAll(chkProxyNone, chkProxyHttp, chkProxySocks);
|
||||
proxyPane.getChildren().add(hBox);
|
||||
}
|
||||
|
||||
{
|
||||
GridPane gridPane = new GridPane();
|
||||
gridPane.setPadding(new Insets(0, 0, 0, 30));
|
||||
gridPane.setHgap(20);
|
||||
gridPane.setVgap(10);
|
||||
gridPane.setStyle("-fx-padding: 0 0 0 15;");
|
||||
gridPane.getColumnConstraints().setAll(new ColumnConstraints(), colHgrow);
|
||||
gridPane.getRowConstraints().setAll(new RowConstraints(), new RowConstraints());
|
||||
|
||||
@@ -223,6 +264,8 @@ public class DownloadSettingsPage extends StackPane {
|
||||
|
||||
{
|
||||
JFXTextField txtProxyPort = new JFXTextField();
|
||||
GridPane.setFillWidth(txtProxyPort, false);
|
||||
txtProxyPort.setMaxWidth(200);
|
||||
GridPane.setRowIndex(txtProxyPort, 2);
|
||||
GridPane.setColumnIndex(txtProxyPort, 1);
|
||||
FXUtils.setValidateWhileTextChanged(txtProxyPort, true);
|
||||
@@ -236,25 +279,25 @@ public class DownloadSettingsPage extends StackPane {
|
||||
proxyPane.getChildren().add(gridPane);
|
||||
}
|
||||
|
||||
GridPane authPane = new GridPane();
|
||||
VBox chkProxyAuthenticationPane = new VBox();
|
||||
{
|
||||
VBox vBox = new VBox();
|
||||
vBox.setStyle("-fx-padding: 20 0 20 5;");
|
||||
chkProxyAuthenticationPane.setPadding(new Insets(20, 0, 20, 5));
|
||||
|
||||
JFXCheckBox chkProxyAuthentication = new JFXCheckBox(i18n("settings.launcher.proxy.authentication"));
|
||||
vBox.getChildren().setAll(chkProxyAuthentication);
|
||||
authPane.disableProperty().bind(chkProxyAuthentication.selectedProperty().not());
|
||||
chkProxyAuthenticationPane.getChildren().add(chkProxyAuthentication);
|
||||
chkProxyAuthentication.selectedProperty().bindBidirectional(config().hasProxyAuthProperty());
|
||||
|
||||
proxyPane.getChildren().add(vBox);
|
||||
proxyPane.getChildren().add(chkProxyAuthenticationPane);
|
||||
}
|
||||
|
||||
GridPane authPane = new GridPane();
|
||||
{
|
||||
authPane.setPadding(new Insets(0, 0, 0, 30));
|
||||
authPane.setHgap(20);
|
||||
authPane.setVgap(10);
|
||||
authPane.setStyle("-fx-padding: 0 0 0 15;");
|
||||
authPane.getColumnConstraints().setAll(new ColumnConstraints(), colHgrow);
|
||||
authPane.getRowConstraints().setAll(new RowConstraints(), new RowConstraints());
|
||||
authPane.disableProperty().bind(config().hasProxyAuthProperty().not());
|
||||
|
||||
{
|
||||
Label username = new Label(i18n("settings.launcher.proxy.username"));
|
||||
@@ -286,18 +329,9 @@ public class DownloadSettingsPage extends StackPane {
|
||||
txtProxyPassword.textProperty().bindBidirectional(config().proxyPassProperty());
|
||||
}
|
||||
|
||||
ToggleGroup proxyConfigurationGroup = new ToggleGroup();
|
||||
chkProxyNone.setUserData(Proxy.Type.DIRECT);
|
||||
chkProxyNone.setToggleGroup(proxyConfigurationGroup);
|
||||
chkProxyHttp.setUserData(Proxy.Type.HTTP);
|
||||
chkProxyHttp.setToggleGroup(proxyConfigurationGroup);
|
||||
chkProxySocks.setUserData(Proxy.Type.SOCKS);
|
||||
chkProxySocks.setToggleGroup(proxyConfigurationGroup);
|
||||
selectedItemPropertyFor(proxyConfigurationGroup, Proxy.Type.class).bindBidirectional(config().proxyTypeProperty());
|
||||
|
||||
proxyPane.getChildren().add(authPane);
|
||||
proxyList.getChildren().add(proxyPane);
|
||||
}
|
||||
proxyList.getChildren().add(proxyPane);
|
||||
}
|
||||
content.getChildren().addAll(ComponentList.createComponentListTitle(i18n("settings.launcher.proxy")), proxyList);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
|
||||
public class FeedbackPage extends SpinnerPane {
|
||||
|
||||
public FeedbackPage() {
|
||||
@@ -45,7 +47,7 @@ public class FeedbackPage extends SpinnerPane {
|
||||
users.setImage(FXUtils.newBuiltinImage("/assets/img/icon.png"));
|
||||
users.setTitle(i18n("feedback.qq_group"));
|
||||
users.setSubtitle(i18n("feedback.qq_group.statement"));
|
||||
users.setExternalLink("https://docs.hmcl.net/groups.html");
|
||||
users.setExternalLink(Metadata.GROUPS_URL);
|
||||
|
||||
IconedTwoLineListItem github = new IconedTwoLineListItem();
|
||||
github.setImage(FXUtils.newBuiltinImage("/assets/img/github.png"));
|
||||
|
||||
@@ -53,7 +53,7 @@ public class HelpPage extends SpinnerPane {
|
||||
IconedTwoLineListItem docPane = new IconedTwoLineListItem();
|
||||
docPane.setTitle(i18n("help.doc"));
|
||||
docPane.setSubtitle(i18n("help.detail"));
|
||||
docPane.setExternalLink(Metadata.HELP_URL);
|
||||
docPane.setExternalLink(Metadata.DOCS_URL);
|
||||
ComponentList doc = new ComponentList();
|
||||
doc.getContent().setAll(docPane);
|
||||
content.getChildren().add(doc);
|
||||
@@ -63,7 +63,7 @@ public class HelpPage extends SpinnerPane {
|
||||
|
||||
private void loadHelp() {
|
||||
showSpinner();
|
||||
Task.supplyAsync(() -> HttpRequest.GET("https://docs.hmcl.net/index.json").getJson(listTypeOf(HelpCategory.class)))
|
||||
Task.supplyAsync(() -> HttpRequest.GET(Metadata.DOCS_URL + "/index.json").getJson(listTypeOf(HelpCategory.class)))
|
||||
.thenAcceptAsync(Schedulers.javafx(), helpCategories -> {
|
||||
for (HelpCategory category : helpCategories) {
|
||||
ComponentList categoryPane = new ComponentList();
|
||||
|
||||
@@ -164,7 +164,7 @@ public final class JavaDownloadDialog extends StackPane {
|
||||
}), i18n("download.java"), TaskCancellationAction.NORMAL);
|
||||
}, null);
|
||||
else
|
||||
Controllers.taskDialog(downloadTask(javaVersion), i18n("download.java"), TaskCancellationAction.NORMAL);
|
||||
Controllers.taskDialog(downloadTask(javaVersion), i18n("download.java.process"), TaskCancellationAction.NORMAL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,10 +25,7 @@ import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
||||
import org.jackhuang.hmcl.ui.construct.PageAware;
|
||||
import org.jackhuang.hmcl.ui.construct.TabControl;
|
||||
import org.jackhuang.hmcl.ui.construct.TabHeader;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.ui.versions.VersionSettingsPage;
|
||||
@@ -62,7 +59,7 @@ public class LauncherSettingsPage extends DecoratorAnimatedPage implements Decor
|
||||
tab = new TabHeader(gameTab, javaManagementTab, settingsTab, personalizationTab, downloadTab, helpTab, feedbackTab, aboutTab);
|
||||
|
||||
tab.select(gameTab);
|
||||
gameTab.getNode().loadVersion(Profiles.getSelectedProfile(), null);
|
||||
addEventHandler(Navigator.NavigationEvent.NAVIGATED, event -> gameTab.getNode().loadVersion(Profiles.getSelectedProfile(), null));
|
||||
transitionPane.setContent(gameTab.getNode(), ContainerAnimations.NONE);
|
||||
FXUtils.onChange(tab.getSelectionModel().selectedItemProperty(), newValue -> {
|
||||
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE);
|
||||
|
||||
@@ -26,12 +26,15 @@ import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
@@ -76,7 +79,9 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>();
|
||||
|
||||
private final PopupMenu menu = new PopupMenu();
|
||||
private final JFXPopup popup = new JFXPopup(menu);
|
||||
|
||||
private final StackPane popupWrapper = new StackPane(menu);
|
||||
private final JFXPopup popup = new JFXPopup(popupWrapper);
|
||||
|
||||
private final StringProperty currentGame = new SimpleStringProperty(this, "currentGame");
|
||||
private final BooleanProperty showUpdate = new SimpleBooleanProperty(this, "showUpdate");
|
||||
@@ -254,6 +259,15 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
FXUtils.installFastTooltip(menuButton, i18n("version.switch"));
|
||||
menuButton.setGraphic(graphic);
|
||||
|
||||
EventHandler<MouseEvent> secondaryClickHandle = event -> {
|
||||
if (event.getButton() == MouseButton.SECONDARY && event.getClickCount() == 1) {
|
||||
menuButton.fire();
|
||||
event.consume();
|
||||
}
|
||||
};
|
||||
launchButton.addEventHandler(MouseEvent.MOUSE_CLICKED, secondaryClickHandle);
|
||||
menuButton.addEventHandler(MouseEvent.MOUSE_CLICKED, secondaryClickHandle);
|
||||
|
||||
launchPane.getChildren().setAll(launchButton, separator, menuButton);
|
||||
}
|
||||
|
||||
@@ -309,7 +323,27 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
}
|
||||
|
||||
private void onMenu() {
|
||||
popup.show(menuButton, JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.RIGHT, 0, -menuButton.getHeight());
|
||||
Node contentNode;
|
||||
if (menu.getContent().isEmpty()) {
|
||||
Label placeholder = new Label(i18n("version.empty"));
|
||||
placeholder.setStyle("-fx-padding: 10px; -fx-text-fill: gray; -fx-font-style: italic;");
|
||||
contentNode = placeholder;
|
||||
} else {
|
||||
contentNode = menu;
|
||||
}
|
||||
|
||||
popupWrapper.getChildren().setAll(contentNode);
|
||||
|
||||
if (popup.isShowing()) {
|
||||
popup.hide();
|
||||
}
|
||||
popup.show(
|
||||
menuButton,
|
||||
JFXPopup.PopupVPosition.BOTTOM,
|
||||
JFXPopup.PopupHPosition.RIGHT,
|
||||
0,
|
||||
-menuButton.getHeight()
|
||||
);
|
||||
}
|
||||
|
||||
private void onUpgrade() {
|
||||
|
||||
@@ -35,8 +35,8 @@ import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Font;
|
||||
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
|
||||
import org.jackhuang.hmcl.setting.FontManager;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
@@ -74,11 +74,8 @@ public class PersonalizationPage extends StackPane {
|
||||
|
||||
ColorPicker picker = new ColorPicker(Color.web(Theme.getTheme().getColor()));
|
||||
picker.getCustomColors().setAll(Theme.SUGGESTED_COLORS);
|
||||
picker.setOnAction(e -> {
|
||||
Theme theme = Theme.custom(Theme.getColorDisplayName(picker.getValue()));
|
||||
config().setTheme(theme);
|
||||
Controllers.getScene().getStylesheets().setAll(theme.getStylesheets(config().getLauncherFontFamily()));
|
||||
});
|
||||
picker.setOnAction(e ->
|
||||
config().setTheme(Theme.custom(Theme.getColorDisplayName(picker.getValue()))));
|
||||
themeColorPickerContainer.getChildren().setAll(picker);
|
||||
Platform.runLater(() -> JFXDepthManager.setDepth(picker, 0));
|
||||
}
|
||||
@@ -200,12 +197,13 @@ public class PersonalizationPage extends StackPane {
|
||||
hBox.setSpacing(8);
|
||||
|
||||
FontComboBox cboFont = new FontComboBox();
|
||||
cboFont.valueProperty().bindBidirectional(config().launcherFontFamilyProperty());
|
||||
cboFont.setValue(config().getLauncherFontFamily());
|
||||
FXUtils.onChange(cboFont.valueProperty(), FontManager::setFontFamily);
|
||||
|
||||
JFXButton clearButton = new JFXButton();
|
||||
clearButton.getStyleClass().add("toggle-icon4");
|
||||
clearButton.setGraphic(SVG.RESTORE.createIcon(Theme.blackFill(), -1));
|
||||
clearButton.setOnAction(e -> config().setLauncherFontFamily(null));
|
||||
clearButton.setOnAction(e -> cboFont.setValue(null));
|
||||
|
||||
hBox.getChildren().setAll(cboFont, clearButton);
|
||||
|
||||
@@ -213,15 +211,7 @@ public class PersonalizationPage extends StackPane {
|
||||
}
|
||||
}
|
||||
|
||||
Label lblFontDisplay = new Label("Hello Minecraft! Launcher");
|
||||
lblFontDisplay.fontProperty().bind(Bindings.createObjectBinding(
|
||||
() -> Font.font(config().getLauncherFontFamily(), 12),
|
||||
config().launcherFontFamilyProperty()));
|
||||
config().launcherFontFamilyProperty().addListener((a, b, newValue) -> {
|
||||
Controllers.getScene().getStylesheets().setAll(Theme.getTheme().getStylesheets(newValue));
|
||||
});
|
||||
|
||||
vbox.getChildren().add(lblFontDisplay);
|
||||
vbox.getChildren().add(new Label("Hello Minecraft! Launcher"));
|
||||
|
||||
fontPane.getContent().add(vbox);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
package org.jackhuang.hmcl.ui.main;
|
||||
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
@@ -179,7 +181,7 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
chatItem.setLeftGraphic(wrap(SVG.CHAT));
|
||||
chatItem.setActionButtonVisible(false);
|
||||
chatItem.setTitle(i18n("chat"));
|
||||
chatItem.setOnAction(e -> FXUtils.openLink("https://docs.hmcl.net/groups.html"));
|
||||
chatItem.setOnAction(e -> FXUtils.openLink(Metadata.GROUPS_URL));
|
||||
|
||||
// the left sidebar
|
||||
AdvancedListBox sideBar = new AdvancedListBox()
|
||||
|
||||
@@ -30,7 +30,6 @@ import javafx.scene.layout.*;
|
||||
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;
|
||||
@@ -46,6 +45,7 @@ import java.util.Arrays;
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public abstract class SettingsView extends StackPane {
|
||||
protected final JFXComboBox<SupportedLocale> cboLanguage;
|
||||
@@ -195,6 +195,8 @@ public abstract class SettingsView extends StackPane {
|
||||
JFXButton openLogFolderButton = new JFXButton(i18n("settings.launcher.launcher_log.reveal"));
|
||||
openLogFolderButton.setOnAction(e -> openLogFolder());
|
||||
openLogFolderButton.getStyleClass().add("jfx-button-border");
|
||||
if (LOG.getLogFile() == null)
|
||||
openLogFolderButton.setDisable(true);
|
||||
|
||||
JFXButton logButton = new JFXButton(i18n("settings.launcher.launcher_log.export"));
|
||||
logButton.setOnAction(e -> onExportLogs());
|
||||
@@ -216,7 +218,7 @@ public abstract class SettingsView extends StackPane {
|
||||
}
|
||||
|
||||
public void openLogFolder() {
|
||||
FXUtils.openFolder(Metadata.HMCL_DIRECTORY.resolve("logs").toFile());
|
||||
FXUtils.openFolder(LOG.getLogFile().getParent().toFile());
|
||||
}
|
||||
|
||||
protected abstract void onUpdate();
|
||||
|
||||
@@ -22,7 +22,7 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.IntTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.ListTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
import org.apache.commons.compress.utils.BoundedInputStream;
|
||||
import kala.compress.utils.BoundedInputStream;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
@@ -17,6 +17,7 @@ import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
@@ -197,9 +198,18 @@ public final class AdvancedVersionSettingPage extends StackPane implements Decor
|
||||
useNativeOpenALPane.setTitle(i18n("settings.advanced.use_native_openal"));
|
||||
|
||||
workaroundPane.getContent().setAll(
|
||||
nativesDirSublist, rendererPane,
|
||||
noJVMArgsPane, noGameCheckPane, noJVMCheckPane, noNativesPatchPane,
|
||||
useNativeGLFWPane, useNativeOpenALPane);
|
||||
nativesDirSublist, rendererPane, noJVMArgsPane, noGameCheckPane,
|
||||
noJVMCheckPane, noNativesPatchPane
|
||||
);
|
||||
|
||||
if (OperatingSystem.CURRENT_OS.isLinuxOrBSD()) {
|
||||
workaroundPane.getContent().addAll(useNativeGLFWPane, useNativeOpenALPane);
|
||||
} else {
|
||||
ComponentSublist unsupportedOptionsSublist = new ComponentSublist();
|
||||
unsupportedOptionsSublist.setTitle(i18n("settings.advanced.unsupported_system_options"));
|
||||
unsupportedOptionsSublist.getContent().addAll(useNativeGLFWPane, useNativeOpenALPane);
|
||||
workaroundPane.getContent().add(unsupportedOptionsSublist);
|
||||
}
|
||||
}
|
||||
|
||||
rootPane.getChildren().addAll(
|
||||
|
||||
@@ -60,10 +60,7 @@ import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.ignoreEvent;
|
||||
@@ -156,7 +153,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
FXUtils.runInFX(() -> selectedVersion.set(versionID));
|
||||
}
|
||||
|
||||
public void search(String userGameVersion, RemoteModRepository.Category category, int pageOffset, String searchFilter, RemoteModRepository.SortType sort) {
|
||||
private void search(String userGameVersion, RemoteModRepository.Category category, int pageOffset, String searchFilter, RemoteModRepository.SortType sort) {
|
||||
retrySearch = null;
|
||||
setLoading(true);
|
||||
setFailed(false);
|
||||
@@ -171,7 +168,9 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
? version.getProfile().getRepository().getGameVersion(version.getVersion()).orElse("")
|
||||
: "";
|
||||
}
|
||||
}).thenApplyAsync(gameVersion -> repository.search(gameVersion, category, pageOffset, 50, searchFilter, sort, RemoteModRepository.SortOrder.DESC)).whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
}).thenApplyAsync(
|
||||
gameVersion -> repository.search(gameVersion, category, pageOffset, 50, searchFilter, sort, RemoteModRepository.SortOrder.DESC)
|
||||
).whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
if (searchID != currentSearchID) {
|
||||
return;
|
||||
}
|
||||
@@ -312,7 +311,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
|
||||
StackPane categoryStackPane = new StackPane();
|
||||
JFXComboBox<CategoryIndented> categoryComboBox = new JFXComboBox<>();
|
||||
categoryComboBox.getItems().setAll(new CategoryIndented(0, null));
|
||||
categoryComboBox.getItems().setAll(CategoryIndented.ALL);
|
||||
categoryStackPane.getChildren().setAll(categoryComboBox);
|
||||
categoryComboBox.prefWidthProperty().bind(categoryStackPane.widthProperty());
|
||||
categoryComboBox.getStyleClass().add("fit-width");
|
||||
@@ -320,14 +319,22 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
categoryComboBox.getSelectionModel().select(0);
|
||||
categoryComboBox.setConverter(stringConverter(getSkinnable()::getLocalizedCategoryIndent));
|
||||
FXUtils.onChangeAndOperate(getSkinnable().downloadSource, downloadSource -> {
|
||||
categoryComboBox.getItems().setAll(CategoryIndented.ALL);
|
||||
categoryComboBox.getSelectionModel().select(0);
|
||||
|
||||
Task.supplyAsync(() -> getSkinnable().repository.getCategories())
|
||||
.thenAcceptAsync(Schedulers.javafx(), categories -> {
|
||||
if (!Objects.equals(getSkinnable().downloadSource.get(), downloadSource)) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<CategoryIndented> result = new ArrayList<>();
|
||||
result.add(new CategoryIndented(0, null));
|
||||
result.add(CategoryIndented.ALL);
|
||||
for (RemoteModRepository.Category category : Lang.toIterable(categories)) {
|
||||
resolveCategory(category, 0, result);
|
||||
}
|
||||
categoryComboBox.getItems().setAll(result);
|
||||
categoryComboBox.getSelectionModel().select(0);
|
||||
}).start();
|
||||
});
|
||||
|
||||
@@ -344,7 +351,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
IntegerProperty filterID = new SimpleIntegerProperty(this, "Filter ID", 0);
|
||||
IntegerProperty currentFilterID = new SimpleIntegerProperty(this, "Current Filter ID", -1);
|
||||
EventHandler<ActionEvent> searchAction = e -> {
|
||||
if (currentFilterID.get() != filterID.get()) {
|
||||
if (currentFilterID.get() != -1 && currentFilterID.get() != filterID.get()) {
|
||||
control.pageOffset.set(0);
|
||||
}
|
||||
currentFilterID.set(filterID.get());
|
||||
@@ -379,8 +386,8 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
JFXButton firstPageButton = FXUtils.newBorderButton(i18n("search.first_page"));
|
||||
firstPageButton.setOnAction(event -> {
|
||||
control.pageOffset.set(0);
|
||||
changeButton.value.run();
|
||||
searchAction.handle(event);
|
||||
changeButton.value.run();
|
||||
});
|
||||
|
||||
JFXButton previousPageButton = FXUtils.newBorderButton(i18n("search.previous_page"));
|
||||
@@ -388,8 +395,8 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
int pageOffset = control.pageOffset.get();
|
||||
if (pageOffset > 0) {
|
||||
control.pageOffset.set(pageOffset - 1);
|
||||
changeButton.value.run();
|
||||
searchAction.handle(event);
|
||||
changeButton.value.run();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -404,16 +411,16 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
int nv = control.pageOffset.get() + 1;
|
||||
if (nv < control.pageCount.get()) {
|
||||
control.pageOffset.set(nv);
|
||||
changeButton.value.run();
|
||||
searchAction.handle(event);
|
||||
changeButton.value.run();
|
||||
}
|
||||
});
|
||||
|
||||
JFXButton lastPageButton = FXUtils.newBorderButton(i18n("search.last_page"));
|
||||
lastPageButton.setOnAction(event -> {
|
||||
control.pageOffset.set(control.pageCount.get() - 1);
|
||||
changeButton.value.run();
|
||||
searchAction.handle(event);
|
||||
changeButton.value.run();
|
||||
});
|
||||
|
||||
firstPageButton.setDisable(true);
|
||||
@@ -446,6 +453,10 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
changeButton.value.run();
|
||||
});
|
||||
|
||||
FXUtils.onChange(control.pageOffset, pageOffsetN -> {
|
||||
changeButton.value.run();
|
||||
});
|
||||
|
||||
Pane placeholder = new Pane();
|
||||
HBox.setHgrow(placeholder, Priority.SOMETIMES);
|
||||
|
||||
@@ -529,6 +540,8 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
}
|
||||
|
||||
private static class CategoryIndented {
|
||||
private static final CategoryIndented ALL = new CategoryIndented(0, null);
|
||||
|
||||
private final int indent;
|
||||
private final RemoteModRepository.Category category;
|
||||
|
||||
|
||||
@@ -212,6 +212,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
scrollPane.setFitToHeight(true);
|
||||
|
||||
HBox descriptionPane = new HBox(8);
|
||||
descriptionPane.setMinHeight(Region.USE_PREF_SIZE);
|
||||
descriptionPane.setAlignment(Pos.CENTER);
|
||||
pane.getChildren().add(descriptionPane);
|
||||
descriptionPane.getStyleClass().add("card-non-transparent");
|
||||
@@ -439,9 +440,25 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
|
||||
private static final class ModVersion extends JFXDialogLayout {
|
||||
public ModVersion(RemoteMod.Version version, DownloadPage selfPage) {
|
||||
boolean isModpack = selfPage.repository.getType() == RemoteModRepository.Type.MODPACK;
|
||||
RemoteModRepository.Type type = selfPage.repository.getType();
|
||||
|
||||
this.setHeading(new HBox(new Label(i18n(isModpack ? "modpack.download.title" : "mods.download.title", version.getName()))));
|
||||
String title;
|
||||
switch (type) {
|
||||
case WORLD:
|
||||
title = "world.download.title";
|
||||
break;
|
||||
case MODPACK:
|
||||
title = "modpack.download.title";
|
||||
break;
|
||||
case RESOURCE_PACK:
|
||||
title = "resourcepack.download.title";
|
||||
break;
|
||||
case MOD:
|
||||
default:
|
||||
title = "mods.download.title";
|
||||
break;
|
||||
}
|
||||
this.setHeading(new HBox(new Label(i18n(title, version.getName()))));
|
||||
|
||||
VBox box = new VBox(8);
|
||||
box.setPadding(new Insets(8));
|
||||
@@ -463,14 +480,17 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
|
||||
this.setBody(box);
|
||||
|
||||
JFXButton downloadButton = new JFXButton(isModpack ? i18n("install.modpack") : i18n("mods.install"));
|
||||
downloadButton.getStyleClass().add("dialog-accept");
|
||||
downloadButton.setOnAction(e -> {
|
||||
if (isModpack || !spinnerPane.isLoading() && spinnerPane.getFailedReason() == null) {
|
||||
fireEvent(new DialogCloseEvent());
|
||||
}
|
||||
selfPage.download(version);
|
||||
});
|
||||
JFXButton downloadButton = null;
|
||||
if (selfPage.callback != null) {
|
||||
downloadButton = new JFXButton(type == RemoteModRepository.Type.MODPACK ? i18n("install.modpack") : i18n("mods.install"));
|
||||
downloadButton.getStyleClass().add("dialog-accept");
|
||||
downloadButton.setOnAction(e -> {
|
||||
if (type == RemoteModRepository.Type.MODPACK || !spinnerPane.isLoading() && spinnerPane.getFailedReason() == null) {
|
||||
fireEvent(new DialogCloseEvent());
|
||||
}
|
||||
selfPage.download(version);
|
||||
});
|
||||
}
|
||||
|
||||
JFXButton saveAsButton = new JFXButton(i18n("mods.save_as"));
|
||||
saveAsButton.getStyleClass().add("dialog-accept");
|
||||
@@ -485,7 +505,11 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
cancelButton.getStyleClass().add("dialog-cancel");
|
||||
cancelButton.setOnAction(e -> fireEvent(new DialogCloseEvent()));
|
||||
|
||||
this.setActions(downloadButton, saveAsButton, cancelButton);
|
||||
if (downloadButton == null) {
|
||||
this.setActions(saveAsButton, cancelButton);
|
||||
} else {
|
||||
this.setActions(downloadButton, saveAsButton, cancelButton);
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
+5
-5
@@ -55,11 +55,11 @@ public final class HMCLLocalizedDownloadListPage extends DownloadListPage {
|
||||
repository = new Repository(type, curseForge, modrinth);
|
||||
|
||||
supportChinese.set(true);
|
||||
downloadSources.get().setAll("mods.curseforge", "mods.modrinth");
|
||||
if (curseForge != null) {
|
||||
downloadSource.set("mods.curseforge");
|
||||
} else if (modrinth != null) {
|
||||
downloadSources.get().setAll("mods.modrinth", "mods.curseforge");
|
||||
if (modrinth != null) {
|
||||
downloadSource.set("mods.modrinth");
|
||||
} else if (curseForge != null) {
|
||||
downloadSource.set("mods.curseforge");
|
||||
} else {
|
||||
throw new AssertionError("Should not be here.");
|
||||
}
|
||||
@@ -110,7 +110,7 @@ public final class HMCLLocalizedDownloadListPage extends DownloadListPage {
|
||||
try {
|
||||
return I18n.getResourceBundle().getString(key);
|
||||
} catch (MissingResourceException e) {
|
||||
LOG.warning("Cannot find key " + key + " in resource bundle", e);
|
||||
LOG.warning("Cannot find key " + key + " in resource bundle");
|
||||
return category;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -446,7 +446,7 @@ class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
repository instanceof CurseForgeRemoteModRepository ? HMCLLocalizedDownloadListPage.ofCurseForgeMod(null, false) : HMCLLocalizedDownloadListPage.ofModrinthMod(null, false),
|
||||
remoteMod,
|
||||
new Profile.ProfileVersion(ModListPageSkin.this.getSkinnable().getProfile(), ModListPageSkin.this.getSkinnable().getVersionId()),
|
||||
null
|
||||
(profile, version, file) -> org.jackhuang.hmcl.ui.download.DownloadPage.download(profile, version, file, "mods")
|
||||
));
|
||||
});
|
||||
button.setDisable(false);
|
||||
|
||||
@@ -46,7 +46,6 @@ import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.io.CSVTable;
|
||||
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.LocalDateTime;
|
||||
@@ -157,7 +156,7 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
|
||||
Path path = Paths.get("hmcl-mod-update-list-" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH-mm-ss")) + ".csv").toAbsolutePath();
|
||||
|
||||
Controllers.taskDialog(Task.runAsync(() -> {
|
||||
CSVTable csvTable = CSVTable.createEmpty();
|
||||
CSVTable csvTable = new CSVTable();
|
||||
|
||||
csvTable.set(0, 0, "Source File Name");
|
||||
csvTable.set(1, 0, "Current Version");
|
||||
@@ -171,7 +170,7 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
|
||||
csvTable.set(3, i + 1, objects.get(i).source.get());
|
||||
}
|
||||
|
||||
csvTable.write(Files.newOutputStream(path));
|
||||
csvTable.write(path);
|
||||
|
||||
FXUtils.showFileInExplorer(path);
|
||||
}).whenComplete(Schedulers.javafx(), exception -> {
|
||||
|
||||
@@ -0,0 +1,618 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXDialogLayout;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.image.PixelWriter;
|
||||
import javafx.scene.image.WritableImage;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.schematic.LitematicFile;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.*;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class SchematicsPage extends ListPageBase<SchematicsPage.Item> implements VersionPage.VersionLoadable {
|
||||
|
||||
private static String translateAuthorName(String author) {
|
||||
if (I18n.isUseChinese() && "hsds".equals(author)) {
|
||||
return "黑山大叔";
|
||||
}
|
||||
return author;
|
||||
}
|
||||
|
||||
private Path schematicsDirectory;
|
||||
private DirItem currentDirectory;
|
||||
|
||||
public SchematicsPage() {
|
||||
FXUtils.applyDragListener(this,
|
||||
file -> currentDirectory != null && file.isFile() && file.getName().endsWith(".litematic"),
|
||||
files -> addFiles(files.stream().map(File::toPath).collect(Collectors.toList()))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new SchematicsPageSkin();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadVersion(Profile profile, String version) {
|
||||
this.schematicsDirectory = profile.getRepository().getSchematicsDirectory(version);
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
Path schematicsDirectory = this.schematicsDirectory;
|
||||
if (schematicsDirectory == null) return;
|
||||
|
||||
setLoading(true);
|
||||
Task.supplyAsync(() -> loadAll(schematicsDirectory, null))
|
||||
.whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
setLoading(false);
|
||||
if (exception == null) {
|
||||
DirItem target = result;
|
||||
if (currentDirectory != null) {
|
||||
loop:
|
||||
for (int i = 0; i < currentDirectory.relativePath.size(); i++) {
|
||||
String dirName = currentDirectory.relativePath.get(i);
|
||||
|
||||
for (Item child : target.children) {
|
||||
if (child instanceof DirItem && child.getName().equals(dirName)) {
|
||||
target = (DirItem) child;
|
||||
continue loop;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
navigateTo(target);
|
||||
} else {
|
||||
LOG.warning("Failed to load schematics", exception);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
public void addFiles(List<Path> files) {
|
||||
if (currentDirectory == null)
|
||||
return;
|
||||
|
||||
Path dir = currentDirectory.path;
|
||||
try {
|
||||
// Can be executed in the background, but be careful that users can call loadVersion during this time
|
||||
Files.createDirectories(dir);
|
||||
for (Path file : files) {
|
||||
Files.copy(file, dir.resolve(file.getFileName()));
|
||||
}
|
||||
refresh();
|
||||
} catch (FileAlreadyExistsException ignored) {
|
||||
} catch (IOException e) {
|
||||
Controllers.dialog(i18n("schematics.add.failed"), i18n("message.error"), MessageDialogPane.MessageType.ERROR);
|
||||
LOG.warning("Failed to add schematics to " + dir, e);
|
||||
}
|
||||
}
|
||||
|
||||
public void onAddFiles() {
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.setTitle(i18n("schematics.add"));
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(
|
||||
i18n("schematics"), "*.litematic"));
|
||||
List<File> files = fileChooser.showOpenMultipleDialog(Controllers.getStage());
|
||||
if (files != null && !files.isEmpty()) {
|
||||
addFiles(files.stream().map(File::toPath).collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
|
||||
public void onCreateDirectory() {
|
||||
if (currentDirectory == null)
|
||||
return;
|
||||
|
||||
Path parent = currentDirectory.path;
|
||||
Controllers.dialog(new InputDialogPane(
|
||||
i18n("schematics.create_directory.prompt"),
|
||||
"",
|
||||
(result, resolve, reject) -> {
|
||||
if (StringUtils.isBlank(result)) {
|
||||
reject.accept(i18n("schematics.create_directory.failed.empty_name"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.contains("/") || result.contains("\\") || !OperatingSystem.isNameValid(result)) {
|
||||
reject.accept(i18n("schematics.create_directory.failed.invalid_name"));
|
||||
return;
|
||||
}
|
||||
|
||||
Path targetDir = parent.resolve(result);
|
||||
if (Files.exists(targetDir)) {
|
||||
reject.accept(i18n("schematics.create_directory.failed.already_exists"));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Files.createDirectories(targetDir);
|
||||
resolve.run();
|
||||
refresh();
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to create directory: " + targetDir, e);
|
||||
reject.accept(i18n("schematics.create_directory.failed", targetDir));
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private DirItem loadAll(Path dir, @Nullable DirItem parent) {
|
||||
DirItem item = new DirItem(dir, parent);
|
||||
|
||||
try (Stream<Path> stream = Files.list(dir)) {
|
||||
for (Path path : Lang.toIterable(stream)) {
|
||||
if (Files.isDirectory(path)) {
|
||||
item.children.add(loadAll(path, item));
|
||||
} else if (path.getFileName().toString().endsWith(".litematic") && Files.isRegularFile(path)) {
|
||||
try {
|
||||
item.children.add(new LitematicFileItem(LitematicFile.load(path)));
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to load litematic file: " + path, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to load schematics in " + dir, e);
|
||||
}
|
||||
|
||||
item.children.sort(Comparator.naturalOrder());
|
||||
return item;
|
||||
}
|
||||
|
||||
private void navigateTo(DirItem item) {
|
||||
currentDirectory = item;
|
||||
getItems().clear();
|
||||
if (item.parent != null) {
|
||||
getItems().add(new BackItem(item.parent));
|
||||
}
|
||||
getItems().addAll(item.children);
|
||||
}
|
||||
|
||||
abstract class Item extends Control implements Comparable<Item> {
|
||||
|
||||
boolean isDirectory() {
|
||||
return this instanceof DirItem;
|
||||
}
|
||||
|
||||
abstract Path getPath();
|
||||
|
||||
abstract String getName();
|
||||
|
||||
abstract String getDescription();
|
||||
|
||||
abstract SVG getIcon();
|
||||
|
||||
Node getIcon(int size) {
|
||||
StackPane icon = new StackPane();
|
||||
icon.setPrefSize(size, size);
|
||||
icon.setMaxSize(size, size);
|
||||
icon.getChildren().add(getIcon().createIcon(Theme.blackFill(), size));
|
||||
return icon;
|
||||
}
|
||||
|
||||
abstract int order();
|
||||
|
||||
abstract void onClick();
|
||||
|
||||
abstract void onReveal();
|
||||
|
||||
abstract void onDelete();
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull SchematicsPage.Item o) {
|
||||
if (this.order() != o.order())
|
||||
return Integer.compare(this.order(), o.order());
|
||||
|
||||
return this.getName().compareTo(o.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new ItemSkin(this);
|
||||
}
|
||||
}
|
||||
|
||||
private final class BackItem extends Item {
|
||||
|
||||
private final DirItem parent;
|
||||
|
||||
BackItem(DirItem parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
int order() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
Path getPath() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
String getName() {
|
||||
return "..";
|
||||
}
|
||||
|
||||
@Override
|
||||
String getDescription() {
|
||||
return i18n("schematics.back_to", parent.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
SVG getIcon() {
|
||||
return SVG.FOLDER;
|
||||
}
|
||||
|
||||
@Override
|
||||
void onClick() {
|
||||
navigateTo(parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
void onReveal() {
|
||||
throw new UnsupportedOperationException("Unreachable");
|
||||
}
|
||||
|
||||
@Override
|
||||
void onDelete() {
|
||||
throw new UnsupportedOperationException("Unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
private final class DirItem extends Item {
|
||||
final Path path;
|
||||
final @Nullable DirItem parent;
|
||||
final List<Item> children = new ArrayList<>();
|
||||
final List<String> relativePath;
|
||||
|
||||
DirItem(Path path, @Nullable DirItem parent) {
|
||||
this.path = path;
|
||||
this.parent = parent;
|
||||
|
||||
if (parent != null) {
|
||||
this.relativePath = new ArrayList<>(parent.relativePath);
|
||||
relativePath.add(path.getFileName().toString());
|
||||
} else {
|
||||
this.relativePath = Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
int order() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
Path getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return path.getFileName().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
String getDescription() {
|
||||
return i18n("schematics.sub_items", children.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
SVG getIcon() {
|
||||
return SVG.FOLDER;
|
||||
}
|
||||
|
||||
@Override
|
||||
void onClick() {
|
||||
navigateTo(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
void onReveal() {
|
||||
FXUtils.openFolder(path.toFile());
|
||||
}
|
||||
|
||||
@Override
|
||||
void onDelete() {
|
||||
try {
|
||||
FileUtils.cleanDirectory(path.toFile());
|
||||
Files.deleteIfExists(path);
|
||||
refresh();
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to delete directory: " + path, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class LitematicFileItem extends Item {
|
||||
final LitematicFile file;
|
||||
final String name;
|
||||
final Image image;
|
||||
|
||||
private LitematicFileItem(LitematicFile file) {
|
||||
this.file = file;
|
||||
|
||||
String name = file.getName();
|
||||
if (name != null && !"Unnamed".equals(name)) {
|
||||
this.name = name;
|
||||
} else {
|
||||
this.name = StringUtils.removeSuffix(file.getFile().getFileName().toString(), ".litematic");
|
||||
}
|
||||
|
||||
WritableImage image = null;
|
||||
int[] previewImageData = file.getPreviewImageData();
|
||||
if (previewImageData != null && previewImageData.length > 0) {
|
||||
int size = (int) Math.sqrt(previewImageData.length);
|
||||
if ((size * size) == previewImageData.length) {
|
||||
image = new WritableImage(size, size);
|
||||
PixelWriter pixelWriter = image.getPixelWriter();
|
||||
|
||||
for (int y = 0, i = 0; y < size; ++y) {
|
||||
for (int x = 0; x < size; ++x) {
|
||||
pixelWriter.setArgb(x, y, previewImageData[i++]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
this.image = image;
|
||||
}
|
||||
|
||||
@Override
|
||||
int order() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
Path getPath() {
|
||||
return file.getFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
String getDescription() {
|
||||
return file.getFile().getFileName().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
SVG getIcon() {
|
||||
return SVG.SCHEMA;
|
||||
}
|
||||
|
||||
Node getIcon(int size) {
|
||||
if (image == null) {
|
||||
return super.getIcon(size);
|
||||
} else {
|
||||
ImageView imageView = new ImageView();
|
||||
imageView.setFitHeight(size);
|
||||
imageView.setFitWidth(size);
|
||||
imageView.setImage(image);
|
||||
return imageView;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void onClick() {
|
||||
Controllers.dialog(new LitematicInfoDialog());
|
||||
}
|
||||
|
||||
@Override
|
||||
void onReveal() {
|
||||
FXUtils.showFileInExplorer(file.getFile());
|
||||
}
|
||||
|
||||
@Override
|
||||
void onDelete() {
|
||||
try {
|
||||
Files.deleteIfExists(file.getFile());
|
||||
refresh();
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to delete litematic file: " + file.getFile(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private final class LitematicInfoDialog extends JFXDialogLayout {
|
||||
private final ComponentList details;
|
||||
|
||||
private void addDetailItem(String key, Object detail) {
|
||||
BorderPane borderPane = new BorderPane();
|
||||
borderPane.setLeft(new Label(key));
|
||||
borderPane.setRight(new Label(detail.toString()));
|
||||
details.getContent().add(borderPane);
|
||||
}
|
||||
|
||||
private void updateContent(LitematicFile file) {
|
||||
details.getContent().clear();
|
||||
addDetailItem(i18n("schematics.info.name"), file.getName());
|
||||
if (StringUtils.isNotBlank(file.getAuthor()))
|
||||
addDetailItem(i18n("schematics.info.schematic_author"), translateAuthorName(file.getAuthor()));
|
||||
if (file.getTimeCreated() != null)
|
||||
addDetailItem(i18n("schematics.info.time_created"), I18n.formatDateTime(file.getTimeCreated()));
|
||||
if (file.getTimeModified() != null && !file.getTimeModified().equals(file.getTimeCreated()))
|
||||
addDetailItem(i18n("schematics.info.time_modified"), I18n.formatDateTime(file.getTimeModified()));
|
||||
if (file.getRegionCount() > 0)
|
||||
addDetailItem(i18n("schematics.info.region_count"), String.valueOf(file.getRegionCount()));
|
||||
if (file.getTotalVolume() > 0)
|
||||
addDetailItem(i18n("schematics.info.total_volume"), file.getTotalVolume());
|
||||
if (file.getTotalBlocks() > 0)
|
||||
addDetailItem(i18n("schematics.info.total_blocks"), file.getTotalBlocks());
|
||||
if (file.getEnclosingSize() != null)
|
||||
addDetailItem(i18n("schematics.info.enclosing_size"),
|
||||
String.format("%d x %d x %d", (int) file.getEnclosingSize().getX(),
|
||||
(int) file.getEnclosingSize().getY(),
|
||||
(int) file.getEnclosingSize().getZ()));
|
||||
|
||||
addDetailItem(i18n("schematics.info.version"), file.getVersion());
|
||||
}
|
||||
|
||||
LitematicInfoDialog() {
|
||||
HBox titleBox = new HBox(8);
|
||||
{
|
||||
Node icon = getIcon(40);
|
||||
|
||||
TwoLineListItem title = new TwoLineListItem();
|
||||
title.setTitle(getName());
|
||||
title.setSubtitle(file.getFile().getFileName().toString());
|
||||
|
||||
titleBox.getChildren().setAll(icon, title);
|
||||
setHeading(titleBox);
|
||||
}
|
||||
|
||||
{
|
||||
this.details = new ComponentList();
|
||||
StackPane detailsContainer = new StackPane();
|
||||
detailsContainer.setPadding(new Insets(10, 0, 0, 0));
|
||||
detailsContainer.getChildren().add(details);
|
||||
setBody(detailsContainer);
|
||||
}
|
||||
|
||||
{
|
||||
JFXButton okButton = new JFXButton();
|
||||
okButton.getStyleClass().add("dialog-accept");
|
||||
okButton.setText(i18n("button.ok"));
|
||||
okButton.setOnAction(e -> fireEvent(new DialogCloseEvent()));
|
||||
getActions().add(okButton);
|
||||
|
||||
onEscPressed(this, okButton::fire);
|
||||
}
|
||||
|
||||
updateContent(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ItemSkin extends SkinBase<Item> {
|
||||
public ItemSkin(Item item) {
|
||||
super(item);
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
root.getStyleClass().add("md-list-cell");
|
||||
root.setPadding(new Insets(8));
|
||||
|
||||
{
|
||||
StackPane left = new StackPane();
|
||||
left.setMaxSize(32, 32);
|
||||
left.setPrefSize(32, 32);
|
||||
left.getChildren().add(item.getIcon(24));
|
||||
left.setPadding(new Insets(0, 8, 0, 0));
|
||||
|
||||
Path path = item.getPath();
|
||||
if (path != null) {
|
||||
FXUtils.installSlowTooltip(left, path.toAbsolutePath().normalize().toString());
|
||||
}
|
||||
|
||||
BorderPane.setAlignment(left, Pos.CENTER);
|
||||
root.setLeft(left);
|
||||
}
|
||||
|
||||
{
|
||||
TwoLineListItem center = new TwoLineListItem();
|
||||
center.setTitle(item.getName());
|
||||
center.setSubtitle(item.getDescription());
|
||||
|
||||
root.setCenter(center);
|
||||
}
|
||||
|
||||
if (!(item instanceof BackItem)) {
|
||||
HBox right = new HBox(8);
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
|
||||
JFXButton btnReveal = new JFXButton();
|
||||
FXUtils.installFastTooltip(btnReveal, i18n("reveal.in_explorer"));
|
||||
btnReveal.getStyleClass().add("toggle-icon4");
|
||||
btnReveal.setGraphic(SVG.FOLDER_OPEN.createIcon(Theme.blackFill(), -1));
|
||||
btnReveal.setOnAction(event -> item.onReveal());
|
||||
|
||||
JFXButton btnDelete = new JFXButton();
|
||||
btnDelete.getStyleClass().add("toggle-icon4");
|
||||
btnDelete.setGraphic(SVG.DELETE_FOREVER.createIcon(Theme.blackFill(), -1));
|
||||
btnDelete.setOnAction(event ->
|
||||
Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"),
|
||||
item::onDelete, null));
|
||||
|
||||
right.getChildren().setAll(btnReveal, btnDelete);
|
||||
root.setRight(right);
|
||||
}
|
||||
|
||||
RipplerContainer container = new RipplerContainer(root);
|
||||
FXUtils.onClicked(container, item::onClick);
|
||||
this.getChildren().add(container);
|
||||
}
|
||||
}
|
||||
|
||||
private final class SchematicsPageSkin extends ToolbarListPageSkin<SchematicsPage> {
|
||||
SchematicsPageSkin() {
|
||||
super(SchematicsPage.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Node> initializeToolbar(SchematicsPage skinnable) {
|
||||
return Arrays.asList(
|
||||
createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, skinnable::refresh),
|
||||
createToolbarButton2(i18n("schematics.add"), SVG.ADD, skinnable::onAddFiles),
|
||||
createToolbarButton2(i18n("schematics.create_directory"), SVG.CREATE_NEW_FOLDER, skinnable::onCreateDirectory)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
private final TabHeader.Tab<InstallerListPage> installerListTab = new TabHeader.Tab<>("installerListTab");
|
||||
private final TabHeader.Tab<ModListPage> modListTab = new TabHeader.Tab<>("modListTab");
|
||||
private final TabHeader.Tab<WorldListPage> worldListTab = new TabHeader.Tab<>("worldList");
|
||||
private final TabHeader.Tab<SchematicsPage> schematicsTab = new TabHeader.Tab<>("schematicsTab");
|
||||
private final TransitionPane transitionPane = new TransitionPane();
|
||||
private final BooleanProperty currentVersionUpgradable = new SimpleBooleanProperty();
|
||||
private final ObjectProperty<Profile.ProfileVersion> version = new SimpleObjectProperty<>();
|
||||
@@ -71,8 +72,9 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
installerListTab.setNodeSupplier(loadVersionFor(InstallerListPage::new));
|
||||
modListTab.setNodeSupplier(loadVersionFor(ModListPage::new));
|
||||
worldListTab.setNodeSupplier(loadVersionFor(WorldListPage::new));
|
||||
schematicsTab.setNodeSupplier(loadVersionFor(SchematicsPage::new));
|
||||
|
||||
tab = new TabHeader(versionSettingsTab, installerListTab, modListTab, worldListTab);
|
||||
tab = new TabHeader(versionSettingsTab, installerListTab, modListTab, worldListTab, schematicsTab);
|
||||
|
||||
addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onNavigated);
|
||||
|
||||
@@ -135,6 +137,8 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
modListTab.getNode().loadVersion(profile, version);
|
||||
if (worldListTab.isInitialized())
|
||||
worldListTab.getNode().loadVersion(profile, version);
|
||||
if (schematicsTab.isInitialized())
|
||||
schematicsTab.getNode().loadVersion(profile, version);
|
||||
currentVersionUpgradable.set(profile.getRepository().isModpack(version));
|
||||
}
|
||||
|
||||
@@ -271,11 +275,20 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
worldListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.worldListTab));
|
||||
worldListItem.setOnAction(e -> control.tab.select(control.worldListTab));
|
||||
|
||||
AdvancedListItem schematicsListItem = new AdvancedListItem();
|
||||
schematicsListItem.getStyleClass().add("navigation-drawer-item");
|
||||
schematicsListItem.setTitle(i18n("schematics.manage"));
|
||||
schematicsListItem.setLeftGraphic(wrap(SVG.SCHEMA));
|
||||
schematicsListItem.setActionButtonVisible(false);
|
||||
schematicsListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.schematicsTab));
|
||||
schematicsListItem.setOnAction(e -> control.tab.select(control.schematicsTab));
|
||||
|
||||
AdvancedListBox sideBar = new AdvancedListBox()
|
||||
.add(versionSettingsItem)
|
||||
.add(installerListItem)
|
||||
.add(modListItem)
|
||||
.add(worldListItem);
|
||||
.add(worldListItem)
|
||||
.add(schematicsListItem);
|
||||
VBox.setVgrow(sideBar, Priority.ALWAYS);
|
||||
|
||||
PopupMenu browseList = new PopupMenu();
|
||||
|
||||
@@ -45,12 +45,16 @@ import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.ServerAddress;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
import org.jackhuang.hmcl.util.javafx.PropertyUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.SystemInfo;
|
||||
import org.jackhuang.hmcl.util.platform.hardware.PhysicalMemoryStatus;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
@@ -58,13 +62,15 @@ import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
|
||||
import static org.jackhuang.hmcl.util.DataSizeUnit.GIGABYTES;
|
||||
import static org.jackhuang.hmcl.util.DataSizeUnit.MEGABYTES;
|
||||
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<OperatingSystem.PhysicalMemoryStatus> memoryStatus = new SimpleObjectProperty<>(OperatingSystem.PhysicalMemoryStatus.INVALID);
|
||||
private static final ObjectProperty<PhysicalMemoryStatus> memoryStatus = new SimpleObjectProperty<>(PhysicalMemoryStatus.INVALID);
|
||||
private static TimerTask memoryStatusUpdateTask;
|
||||
|
||||
private static void initMemoryStatusUpdateTask() {
|
||||
@@ -74,7 +80,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
memoryStatusUpdateTask = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
Platform.runLater(() -> memoryStatus.set(OperatingSystem.getPhysicalMemoryStatus()));
|
||||
Platform.runLater(() -> memoryStatus.set(SystemInfo.getPhysicalMemoryStatus()));
|
||||
}
|
||||
};
|
||||
getTimer().scheduleAtFixedRate(memoryStatusUpdateTask, 0, 1000);
|
||||
@@ -295,12 +301,12 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
AtomicBoolean changedByTextField = new AtomicBoolean(false);
|
||||
FXUtils.onChangeAndOperate(maxMemory, maxMemory -> {
|
||||
changedByTextField.set(true);
|
||||
slider.setValue(maxMemory.intValue() * 1.0 / OperatingSystem.TOTAL_MEMORY);
|
||||
slider.setValue(maxMemory.intValue() * 1.0 / MEGABYTES.convertFromBytes(SystemInfo.getTotalMemorySize()));
|
||||
changedByTextField.set(false);
|
||||
});
|
||||
slider.valueProperty().addListener((value, oldVal, newVal) -> {
|
||||
if (changedByTextField.get()) return;
|
||||
maxMemory.set((int) (value.getValue().doubleValue() * OperatingSystem.TOTAL_MEMORY));
|
||||
maxMemory.set((int) (value.getValue().doubleValue() * MEGABYTES.convertFromBytes(SystemInfo.getTotalMemorySize())));
|
||||
});
|
||||
|
||||
JFXTextField txtMaxMemory = new JFXTextField();
|
||||
@@ -309,7 +315,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
txtMaxMemory.textProperty().bindBidirectional(maxMemory, SafeStringConverter.fromInteger());
|
||||
txtMaxMemory.setValidators(new NumberValidator(i18n("input.number"), false));
|
||||
|
||||
lowerBoundPane.getChildren().setAll(label, slider, txtMaxMemory, new Label("MB"));
|
||||
lowerBoundPane.getChildren().setAll(label, slider, txtMaxMemory, new Label("MiB"));
|
||||
}
|
||||
|
||||
StackPane progressBarPane = new StackPane();
|
||||
@@ -343,9 +349,10 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
Label lblPhysicalMemory = new Label();
|
||||
lblPhysicalMemory.getStyleClass().add("memory-label");
|
||||
digitalPane.setLeft(lblPhysicalMemory);
|
||||
lblPhysicalMemory.textProperty().bind(Bindings.createStringBinding(() -> {
|
||||
return i18n("settings.memory.used_per_total", memoryStatus.get().getUsedGB(), memoryStatus.get().getTotalGB());
|
||||
}, memoryStatus));
|
||||
lblPhysicalMemory.textProperty().bind(Bindings.createStringBinding(() ->
|
||||
i18n("settings.memory.used_per_total",
|
||||
GIGABYTES.convertFromBytes(memoryStatus.get().getUsed()),
|
||||
GIGABYTES.convertFromBytes(memoryStatus.get().getTotal())), memoryStatus));
|
||||
|
||||
Label lblAllocateMemory = new Label();
|
||||
lblAllocateMemory.textProperty().bind(Bindings.createStringBinding(() -> {
|
||||
@@ -353,9 +360,9 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
return i18n(memoryStatus.get().hasAvailable() && maxMemory > memoryStatus.get().getAvailable()
|
||||
? (chkAutoAllocate.isSelected() ? "settings.memory.allocate.auto.exceeded" : "settings.memory.allocate.manual.exceeded")
|
||||
: (chkAutoAllocate.isSelected() ? "settings.memory.allocate.auto" : "settings.memory.allocate.manual"),
|
||||
OperatingSystem.PhysicalMemoryStatus.toGigaBytes(maxMemory),
|
||||
OperatingSystem.PhysicalMemoryStatus.toGigaBytes(HMCLGameRepository.getAllocatedMemory(maxMemory, memoryStatus.get().getAvailable(), chkAutoAllocate.isSelected())),
|
||||
OperatingSystem.PhysicalMemoryStatus.toGigaBytes(memoryStatus.get().getAvailable()));
|
||||
GIGABYTES.convertFromBytes(maxMemory),
|
||||
GIGABYTES.convertFromBytes(HMCLGameRepository.getAllocatedMemory(maxMemory, memoryStatus.get().getAvailable(), chkAutoAllocate.isSelected())),
|
||||
GIGABYTES.convertFromBytes(memoryStatus.get().getAvailable()));
|
||||
}, memoryStatus, maxMemory, chkAutoAllocate.selectedProperty()));
|
||||
lblAllocateMemory.getStyleClass().add("memory-label");
|
||||
digitalPane.setRight(lblAllocateMemory);
|
||||
@@ -446,6 +453,16 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
|
||||
txtServerIP = new JFXTextField();
|
||||
txtServerIP.setPromptText(i18n("settings.advanced.server_ip.prompt"));
|
||||
Validator.addTo(txtServerIP).accept(str -> {
|
||||
if (StringUtils.isBlank(str))
|
||||
return true;
|
||||
try {
|
||||
ServerAddress.parse(str);
|
||||
return true;
|
||||
} catch (Exception ignored) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
FXUtils.setLimitWidth(txtServerIP, 300);
|
||||
serverPane.addRow(0, new Label(i18n("settings.advanced.server_ip")), txtServerIP);
|
||||
}
|
||||
@@ -507,7 +524,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
cboProcessPriority.getItems().setAll(ProcessPriority.values());
|
||||
cboProcessPriority.setConverter(stringConverter(e -> i18n("settings.advanced.process_priority." + e.name().toLowerCase(Locale.ROOT))));
|
||||
|
||||
memoryStatus.set(OperatingSystem.getPhysicalMemoryStatus());
|
||||
memoryStatus.set(SystemInfo.getPhysicalMemoryStatus());
|
||||
componentList.disableProperty().bind(enableSpecificSettings.not());
|
||||
|
||||
initMemoryStatusUpdateTask();
|
||||
|
||||
@@ -90,7 +90,11 @@ public final class Versions {
|
||||
new FileDownloadTask(downloadURL, modpack.toFile())
|
||||
.whenComplete(Schedulers.javafx(), e -> {
|
||||
if (e == null) {
|
||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(profile, modpack.toFile()));
|
||||
if (version != null) {
|
||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(profile, modpack.toFile(), version));
|
||||
} else {
|
||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(profile, modpack.toFile()));
|
||||
}
|
||||
} else if (e instanceof CancellationException) {
|
||||
Controllers.showToast(i18n("message.cancelled"));
|
||||
} else {
|
||||
|
||||
@@ -140,7 +140,7 @@ public final class WorldBackupsPage extends ListPageBase<WorldBackupsPage.Backup
|
||||
}
|
||||
|
||||
void createBackup() {
|
||||
Controllers.taskDialog(new WorldBackupTask(world, backupsDir).setName(i18n("world.backup")).thenApplyAsync(path -> {
|
||||
Controllers.taskDialog(new WorldBackupTask(world, backupsDir).setName(i18n("world.backup.processing")).thenApplyAsync(path -> {
|
||||
Matcher matcher = backupFileNamePattern.matcher(path.getFileName().toString());
|
||||
if (!matcher.matches()) {
|
||||
throw new AssertionError("Wrong backup file name" + path);
|
||||
@@ -176,7 +176,7 @@ public final class WorldBackupsPage extends ListPageBase<WorldBackupsPage.Backup
|
||||
|
||||
@Override
|
||||
protected List<Node> initializeToolbar(WorldBackupsPage skinnable) {
|
||||
return Arrays.asList(createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, skinnable::refresh), createToolbarButton2(i18n("world.backup"), SVG.ARCHIVE, skinnable::createBackup));
|
||||
return Arrays.asList(createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, skinnable::refresh), createToolbarButton2(i18n("world.backup.create.new_one"), SVG.ARCHIVE, skinnable::createBackup));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.CancellationException;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
@@ -69,6 +70,12 @@ public abstract class TaskExecutorDialogWizardDisplayer extends AbstractWizardDi
|
||||
} else {
|
||||
if (executor.getException() == null)
|
||||
return;
|
||||
|
||||
if (executor.getException() instanceof CancellationException) {
|
||||
onEnd();
|
||||
return;
|
||||
}
|
||||
|
||||
String appendix = StringUtils.getStackTrace(executor.getException());
|
||||
if (settings.get("failure_callback") instanceof WizardProvider.FailureCallback)
|
||||
((WizardProvider.FailureCallback) settings.get("failure_callback")).onFail(settings, executor.getException(), () -> onEnd());
|
||||
|
||||
@@ -113,9 +113,8 @@ public final class UpdateHandler {
|
||||
|
||||
Task<?> task = new HMCLDownloadTask(version, downloaded);
|
||||
|
||||
TaskExecutor executor = task.executor(false);
|
||||
TaskExecutor executor = task.executor();
|
||||
Controllers.taskDialog(executor, i18n("message.downloading"), TaskCancellationAction.NORMAL);
|
||||
executor.start();
|
||||
thread(() -> {
|
||||
boolean success = executor.test();
|
||||
|
||||
@@ -253,7 +252,7 @@ public final class UpdateHandler {
|
||||
private static boolean isFirstLaunchAfterUpgrade() {
|
||||
Path currentPath = JarUtils.thisJarPath();
|
||||
if (currentPath != null) {
|
||||
Path updated = Metadata.HMCL_DIRECTORY.resolve("HMCL-" + Metadata.VERSION + ".jar");
|
||||
Path updated = Metadata.HMCL_GLOBAL_DIRECTORY.resolve("HMCL-" + Metadata.VERSION + ".jar");
|
||||
if (currentPath.equals(updated.toAbsolutePath())) {
|
||||
return true;
|
||||
}
|
||||
@@ -262,7 +261,7 @@ public final class UpdateHandler {
|
||||
}
|
||||
|
||||
private static void breakForceUpdateFeature() {
|
||||
Path hmclVersionJson = Metadata.HMCL_DIRECTORY.resolve("hmclver.json");
|
||||
Path hmclVersionJson = Metadata.HMCL_GLOBAL_DIRECTORY.resolve("hmclver.json");
|
||||
if (Files.isRegularFile(hmclVersionJson)) {
|
||||
try {
|
||||
Map<?, ?> content = new Gson().fromJson(FileUtils.readText(hmclVersionJson), Map.class);
|
||||
|
||||
@@ -121,7 +121,7 @@ public final class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
map.put("version", Metadata.VERSION);
|
||||
map.put("log", LOG.getLogs());
|
||||
try {
|
||||
String response = NetworkUtils.doPost(NetworkUtils.toURL("https://hmcl.huangyuhui.net/hmcl/crash.php"), map);
|
||||
String response = NetworkUtils.doPost(NetworkUtils.toURL(Metadata.PUBLISH_URL + "/hmcl/crash.php"), map);
|
||||
if (StringUtils.isNotBlank(response))
|
||||
LOG.error("Crash server response: " + response);
|
||||
} catch (IOException ex) {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class ModuleHelper {
|
||||
|
||||
public static void addEnableNativeAccess(Class<?> clazzInModule) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
private ModuleHelper() {
|
||||
}
|
||||
}
|
||||
@@ -116,10 +116,10 @@ public final class NativePatcher {
|
||||
if (settings.isNotPatchNatives())
|
||||
return version;
|
||||
|
||||
if (arch.isX86() && (os == OperatingSystem.WINDOWS || os == OperatingSystem.LINUX || os == OperatingSystem.OSX))
|
||||
if (arch.isX86() && (os == OperatingSystem.WINDOWS || os == OperatingSystem.LINUX || os == OperatingSystem.MACOS))
|
||||
return version;
|
||||
|
||||
if (arch == Architecture.ARM64 && (os == OperatingSystem.OSX || os == OperatingSystem.WINDOWS)
|
||||
if (arch == Architecture.ARM64 && (os == OperatingSystem.MACOS || os == OperatingSystem.WINDOWS)
|
||||
&& gameVersionNumber != null
|
||||
&& gameVersionNumber.compareTo("1.19") >= 0)
|
||||
return version;
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.ui.SwingUtils;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.ChecksumMismatchException;
|
||||
@@ -66,7 +67,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
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;
|
||||
@@ -102,14 +102,23 @@ public final class SelfDependencyPatcher {
|
||||
|
||||
private static final class DependencyDescriptor {
|
||||
private static final String DEPENDENCIES_LIST_FILE = "/assets/openjfx-dependencies.json";
|
||||
private static final Path DEPENDENCIES_DIR_PATH = HMCL_DIRECTORY.resolve("dependencies").resolve(Platform.getPlatform().toString()).resolve("openjfx");
|
||||
private static final Path DEPENDENCIES_DIR_PATH = Metadata.DEPENDENCIES_DIRECTORY.resolve(Platform.getPlatform().toString()).resolve("openjfx");
|
||||
|
||||
static List<DependencyDescriptor> readDependencies() {
|
||||
//noinspection ConstantConditions
|
||||
try (Reader reader = new InputStreamReader(SelfDependencyPatcher.class.getResourceAsStream(DEPENDENCIES_LIST_FILE), UTF_8)) {
|
||||
Map<String, List<DependencyDescriptor>> allDependencies =
|
||||
JsonUtils.GSON.fromJson(reader, mapTypeOf(String.class, listTypeOf(DependencyDescriptor.class)));
|
||||
return allDependencies.get(Platform.getPlatform().toString());
|
||||
Map<String, Map<String, List<DependencyDescriptor>>> allDependencies =
|
||||
JsonUtils.GSON.fromJson(reader, mapTypeOf(String.class, mapTypeOf(String.class, listTypeOf(DependencyDescriptor.class))));
|
||||
Map<String, List<DependencyDescriptor>> platform = allDependencies.get(Platform.getPlatform().toString());
|
||||
if (platform == null)
|
||||
return null;
|
||||
|
||||
if (JavaRuntime.CURRENT_VERSION >= 22) {
|
||||
List<DependencyDescriptor> modernDependencies = platform.get("modern");
|
||||
if (modernDependencies != null)
|
||||
return modernDependencies;
|
||||
}
|
||||
return platform.get("classic");
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class ModuleHelper {
|
||||
|
||||
public static void addEnableNativeAccess(Class<?> clazzInModule) {
|
||||
Module module = clazzInModule.getModule();
|
||||
if (module.isNamed()) {
|
||||
try {
|
||||
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(Module.class, MethodHandles.lookup());
|
||||
MethodHandle implAddEnableNativeAccess = lookup.findVirtual(Module.class, "implAddEnableNativeAccess", MethodType.methodType(Module.class));
|
||||
Module ignored = (Module) implAddEnableNativeAccess.invokeExact(module);
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
} else {
|
||||
System.err.println("TODO: Add enable native access for anonymous modules is not yet supported");
|
||||
}
|
||||
}
|
||||
|
||||
private ModuleHelper() {
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -14,7 +14,7 @@ case "$OSTYPE" in
|
||||
linux*)
|
||||
_HMCL_OS="linux";;
|
||||
darwin*)
|
||||
_HMCL_OS="osx";;
|
||||
_HMCL_OS="macos";;
|
||||
freebsd*)
|
||||
_HMCL_OS="freebsd";;
|
||||
msys*|cygwin*)
|
||||
@@ -159,7 +159,7 @@ case "$_HMCL_OS-$_HMCL_ARCH" in
|
||||
linux-x86|linux-x86_64|linux-arm64|linux-arm32|linux-riscv64|linux-loongarch64)
|
||||
_HMCL_JAVA_DOWNLOAD_PAGE="https://docs.hmcl.net/downloads/linux/$_HMCL_ARCH.html"
|
||||
;;
|
||||
osx-x86_64|osx-arm64)
|
||||
macos-x86_64|macos-arm64)
|
||||
_HMCL_JAVA_DOWNLOAD_PAGE="https://docs.hmcl.net/downloads/macos/$_HMCL_ARCH.html"
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -58,5 +58,10 @@
|
||||
"title" : "SimplePNG",
|
||||
"subtitle" : "Copyright © 2023 Glavo.\nLicensed under the Apache 2.0 License.",
|
||||
"externalLink" : "https://github.com/Glavo/SimplePNG"
|
||||
},
|
||||
{
|
||||
"title" : "Java Native Access",
|
||||
"subtitle" : "Licensed under the LGPL 2.1 License or the Apache 2.0 License.",
|
||||
"externalLink" : "https://github.com/java-native-access/jna"
|
||||
}
|
||||
]
|
||||
@@ -50,7 +50,8 @@
|
||||
{
|
||||
"image" : "/assets/img/red_lnn.png",
|
||||
"title" : "Red_lnn",
|
||||
"subtitleLocalized" : "about.thanks_to.red_lnn.statement"
|
||||
"subtitleLocalized" : "about.thanks_to.red_lnn.statement",
|
||||
"externalLink" : "https://redlnn.top"
|
||||
},
|
||||
{
|
||||
"image" : "/assets/img/mcmod.png",
|
||||
|
||||
@@ -24,8 +24,5 @@
|
||||
-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);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
.root {
|
||||
/* https://github.com/HMCL-dev/HMCL/pull/3423 */
|
||||
-fx-font-family: -fx-base-font-family;
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 39 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 46 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 122 KiB |
@@ -88,6 +88,7 @@ account.login.skip=Log in offline
|
||||
account.login.retry=Retry
|
||||
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.login.restricted=Sign in to your Microsoft account to enable this feature
|
||||
account.logout=Logout
|
||||
account.register=Register
|
||||
account.manage=Account List
|
||||
@@ -105,6 +106,7 @@ account.methods.microsoft.error.add_family_probably=Please check if the age indi
|
||||
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 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.banned=Your account may have been banned by Xbox Live.\nYou can click on "Ban Query" above for more details.
|
||||
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...
|
||||
@@ -123,19 +125,19 @@ account.methods.microsoft.snapshot=You are using an unofficial build of HMCL. Pl
|
||||
account.methods.microsoft.snapshot.website=Official Website
|
||||
account.methods.offline=Offline
|
||||
account.methods.offline.name.special_characters=Using only English letters, numbers, and underscores is recommended
|
||||
account.methods.offline.name.invalid.tip=It is recommended to use only English letters, numbers and underscores for the username, and the length should not exceed 16 characters.
|
||||
account.methods.offline.name.invalid=It is recommended to use only English letters, numbers and underscores for the username, and the length should not exceed 16 characters.\n\
|
||||
\n\
|
||||
\ · Legitimate: HuangYu, huang_Yu, Huang_Yu_123;\n\
|
||||
\ · Illegal: 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.
|
||||
Using the illegal username will prevent you from joining most servers and may conflict with some mods, causing the game to crash.
|
||||
account.methods.offline.uuid=UUID
|
||||
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.methods.ban_query=Ban Query
|
||||
account.missing=No Accounts
|
||||
account.missing.add=Click here to add one.
|
||||
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.
|
||||
@@ -180,6 +182,7 @@ button.install=Install
|
||||
button.export=Export
|
||||
button.no=No
|
||||
button.ok=OK
|
||||
button.ok.countdown=OK (%d)
|
||||
button.refresh=Refresh
|
||||
button.remove=Remove
|
||||
button.remove.confirm=Are you sure you want to permanently remove it? This action cannot be undone!
|
||||
@@ -274,6 +277,7 @@ curse.category.436=Food
|
||||
curse.category.4558=Redstone
|
||||
curse.category.4843=Automation
|
||||
curse.category.4906=MCreator
|
||||
curse.category.7669=Twilight Forest
|
||||
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/12
|
||||
curse.category.5244=Font Packs
|
||||
@@ -328,7 +332,7 @@ download=Download
|
||||
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 channel build does not support access to CurseForge. Please use Release or Beta channel builds to download.
|
||||
download.curseforge.unavailable=This HMCL build does not support access to CurseForge. Please use the official build to access CurseForge.
|
||||
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.
|
||||
@@ -343,6 +347,7 @@ 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.process=Java Download Process
|
||||
download.javafx=Downloading dependencies for the launcher...
|
||||
download.javafx.notes=We are currently downloading dependencies for HMCL from the Internet.\n\
|
||||
\n\
|
||||
@@ -364,6 +369,7 @@ extension.png=Image File
|
||||
extension.ps1=Windows PowerShell Script
|
||||
extension.sh=Shell Script
|
||||
|
||||
fatal.create_hmcl_current_directory_failure=Hello Minecraft! Launcher cannot create the HMCL directory (%s). Please move HMCL to another location and reopen it.
|
||||
fatal.javafx.incompatible=Missing JavaFX environment.\n\
|
||||
Hello Minecraft! Launcher cannot automatically install JavaFX on Java <11.\n\
|
||||
Please update your Java to version 11 or later.
|
||||
@@ -391,6 +397,12 @@ fatal.apply_update_failure=We are sorry, but Hello Minecraft! Launcher is unable
|
||||
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 launcher version from %s.
|
||||
fatal.deprecated_java_version=HMCL will require Java 11 or later to run in the future, but will still support launching games with Java 8.\n\
|
||||
\n\
|
||||
It is recommended to install the latest version of Java to ensure that HMCL works properly.\n\
|
||||
\n\
|
||||
You can continue to keep the old version of Java. HMCL can recognize and manage multiple Java versions and will automatically select the appropriate Java for you based on the game version.
|
||||
fatal.deprecated_java_version.download_link=Download Java %d
|
||||
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\
|
||||
@@ -398,7 +410,7 @@ fatal.unsupported_platform=Minecraft is not fully supported on your platform yet
|
||||
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\
|
||||
fatal.unsupported_platform.macos_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\
|
||||
@@ -550,7 +562,7 @@ game.crash.reason.jvm_32bit=The game crashed because the current memory allocati
|
||||
\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.
|
||||
Or, you can disable the "Automatically Allocate" option in "Global/Instance-specific Settings → Memory" and set the maximum memory allocation size to 1024 MiB 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.
|
||||
@@ -668,6 +680,7 @@ 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.change_version.process=Change Version Process
|
||||
install.failed=Failed to install
|
||||
install.failed.downloading=Failed to download some required files.
|
||||
install.failed.downloading.detail=Failed to download file: %s
|
||||
@@ -680,6 +693,7 @@ install.failed.version_mismatch=This loader requires the game version %s, but th
|
||||
install.installer.change_version=%s Incompatible
|
||||
install.installer.choose=Choose Your %s Version
|
||||
install.installer.depend=Requires %s
|
||||
install.installer.do_not_install=Do not install
|
||||
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 mod directory of the game instance. Please do not change the working directory of the game, or the Fabric API will not function. If you do want to change the directory, you should reinstall it.
|
||||
@@ -687,24 +701,27 @@ 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=Installing %s
|
||||
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 Forge, NeoForge, OptiFine, Fabric, Quilt, and LiteLoader.
|
||||
install.installer.liteloader=LiteLoader
|
||||
install.installer.not_installed=Do not install
|
||||
install.installer.not_installed=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 (Installed by external process, which cannot be configured)
|
||||
install.installing=Installing
|
||||
install.modpack=Install Modpack
|
||||
install.modpack.installation=Modpack Installation
|
||||
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.installation=Instance Installation
|
||||
install.new_game.malformed=Invalid name.
|
||||
install.select=Choose operation
|
||||
install.success=Successfully installed.
|
||||
@@ -736,6 +753,7 @@ java.install.failed.invalid=This archive is not a valid Java installation packag
|
||||
java.install.failed.unsupported_platform=This Java is not compatible with the current platform, so it cannot be installed.
|
||||
java.install.name=Name
|
||||
java.install.warning.invalid_character=Illegal character in name
|
||||
java.installing=Installing Java
|
||||
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!
|
||||
@@ -759,7 +777,7 @@ launch.advice.java9=You cannot launch Minecraft 1.12 or earlier with Java 9 or l
|
||||
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.not_enough_space=You have allocated a memory size larger than the actual %d MiB 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 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.
|
||||
@@ -869,8 +887,8 @@ modpack.files.resourcepacks=Resource/Texture Packs
|
||||
modpack.files.saves=Worlds
|
||||
modpack.files.scripts=MineTweaker Configuration File
|
||||
modpack.files.servers_dat=Server List File
|
||||
modpack.install=Install Modpack %s
|
||||
modpack.installing=Installing Modpack
|
||||
modpack.installing=Installing modpack
|
||||
modpack.installing.given=Installing %s modpack
|
||||
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(n) %s type, but the provided one is %s type.
|
||||
@@ -1047,17 +1065,20 @@ web.view_in_browser=View in browser
|
||||
|
||||
world=Worlds
|
||||
world.add=Add World
|
||||
world.backup=Backup World
|
||||
world.backup=World Backup
|
||||
world.backup.create.new_one=Create New Backup
|
||||
world.backup.create.failed=Failed to create backup.\n%s
|
||||
world.backup.create.locked=The world is currently in use. Please close the game and try again.
|
||||
world.backup.create.success=Successfully created a new backup: %s
|
||||
world.backup.delete=Delete this backup
|
||||
world.backup.processing=Backing up ...
|
||||
world.backup.reveal=Show in folder
|
||||
world.backup.title=World [%s] - Backups
|
||||
world.datapack=Manage Datapacks
|
||||
world.datapack.1_13=Only Minecraft 1.13 or later supports datapacks.
|
||||
world.datetime=Last played on %s
|
||||
world.download=Download World
|
||||
world.download.title=Download World - %1s
|
||||
world.export=Export the World
|
||||
world.export.title=Choose the directory for this exported world
|
||||
world.export.location=Save As
|
||||
@@ -1129,6 +1150,32 @@ repositories.chooser=HMCL requires JavaFX to work.\n\
|
||||
repositories.chooser.title=Choose download source for JavaFX
|
||||
|
||||
resourcepack=Resource Packs
|
||||
resourcepack.download.title=Download Resource Pack - %1s
|
||||
|
||||
reveal.in_explorer=Reveal in File Manager
|
||||
|
||||
schematics=Schematics
|
||||
schematics.add=Add Schematic Files
|
||||
schematics.add.failed=Failed to add schematic files
|
||||
schematics.back_to=Back to "%s"
|
||||
schematics.create_directory=Create Directory
|
||||
schematics.create_directory.prompt=Please enter the new directory name
|
||||
schematics.create_directory.failed=Failed to create directory
|
||||
schematics.create_directory.failed.already_exists=Directory already exists
|
||||
schematics.create_directory.failed.empty_name=Name cannot be empty
|
||||
schematics.create_directory.failed.invalid_name=Name contains invalid characters
|
||||
schematics.info.description=Description
|
||||
schematics.info.enclosing_size=Enclosing Size
|
||||
schematics.info.name=Name
|
||||
schematics.info.region_count=Regions
|
||||
schematics.info.schematic_author=Author
|
||||
schematics.info.time_created=Created Time
|
||||
schematics.info.time_modified=Modified Time
|
||||
schematics.info.total_blocks=Total Blocks
|
||||
schematics.info.total_volume=Total Volume
|
||||
schematics.info.version=Schematic Version
|
||||
schematics.manage=Schematics
|
||||
schematics.sub_items=%d sub-items
|
||||
|
||||
search=Search
|
||||
search.hint.chinese=Search in English and Chinese
|
||||
@@ -1170,7 +1217,7 @@ settings.advanced.environment_variables=Environment Variables
|
||||
settings.advanced.game_dir.default=Default (".minecraft/")
|
||||
settings.advanced.game_dir.independent=Isolated (".minecraft/versions/<instance name>/", except for assets and libraries)
|
||||
settings.advanced.java_permanent_generation_space=PermGen Space
|
||||
settings.advanced.java_permanent_generation_space.prompt=in MB
|
||||
settings.advanced.java_permanent_generation_space.prompt=in MiB
|
||||
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\
|
||||
@@ -1209,6 +1256,7 @@ settings.advanced.renderer.llvmpipe=Software (Poor performance, best compatibili
|
||||
settings.advanced.renderer.zink=Vulkan (Best performance, poor compatibility)
|
||||
settings.advanced.server_ip=Server Address
|
||||
settings.advanced.server_ip.prompt=Automatically join after launching the game
|
||||
settings.advanced.unsupported_system_options=Settings not applicable to the current system
|
||||
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
|
||||
@@ -1265,7 +1313,7 @@ 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.default=Use System Proxy
|
||||
settings.launcher.proxy.host=Host
|
||||
settings.launcher.proxy.http=HTTP
|
||||
settings.launcher.proxy.none=No Proxy
|
||||
@@ -1279,13 +1327,13 @@ settings.launcher.turn_off_animations=Disable Animation (Applies After Restart)
|
||||
settings.launcher.version_list_source=Version List
|
||||
|
||||
settings.memory=Memory
|
||||
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.allocate.auto=%1$.1f GiB Minimum / %2$.1f GiB Allocated
|
||||
settings.memory.allocate.auto.exceeded=%1$.1f GiB Minimum / %2$.1f GiB Allocated (%3$.1f GiB Available)
|
||||
settings.memory.allocate.manual=%1$.1f GiB Allocated
|
||||
settings.memory.allocate.manual.exceeded=%1$.1f GiB Allocated (%3$.1f GiB Available)
|
||||
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.memory.used_per_total=%1$.1f GiB Used / %2$.1f GiB Total
|
||||
settings.physical_memory=Physical Memory Size
|
||||
settings.show_log=Show Logs
|
||||
settings.tabs.installers=Loaders
|
||||
@@ -1313,12 +1361,16 @@ update.changelog=Changelog
|
||||
update.channel.dev=Beta
|
||||
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 submit feedback via the channels provided on the <a href="hmcl://settings/feedback">Feedback</a> page.
|
||||
If you encounter any bugs or problems, please submit feedback via the channels provided on the <a href="hmcl://settings/feedback">Feedback</a> page.\n\
|
||||
\n\
|
||||
Follow <a href="https://space.bilibili.com/1445341">@huanghongxun</a> on Bilibili to stay up to date on important HMCL news, or <a href="https://space.bilibili.com/20314891">@Glavo</a> to learn about HMCL development progress.
|
||||
update.channel.dev.title=Beta Channel Notice
|
||||
update.channel.nightly=Nightly
|
||||
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 submit feedback via the channels provided on the <a href="hmcl://settings/feedback">Feedback</a> page.
|
||||
If you encounter any bugs or problems, please submit feedback via the channels provided on the <a href="hmcl://settings/feedback">Feedback</a> page.\n\
|
||||
\n\
|
||||
Follow <a href="https://space.bilibili.com/1445341">@huanghongxun</a> on Bilibili to stay up to date on important HMCL news, or <a href="https://space.bilibili.com/20314891">@Glavo</a> to learn about HMCL development progress.
|
||||
update.channel.nightly.title=Nightly Channel Notice
|
||||
update.channel.stable=Release
|
||||
update.checking=Checking for Updates
|
||||
@@ -1337,8 +1389,8 @@ version.name=Instance Name
|
||||
version.cannot_read=Failed to parse the game instance, installation cannot continue.
|
||||
version.empty=No Instances
|
||||
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.empty.launch=No available instances. Clicking "OK" will take you to the "Download" page.\n\nYou can also download the game or switch game directories via the "Download" or "All Instances" buttons on the HMCL homepage.
|
||||
version.empty.hint=There are no Minecraft instances here.\nYou 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
|
||||
|
||||
@@ -73,7 +73,7 @@ account.failed.server_disconnected=No se ha podido conectar con el servidor de a
|
||||
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 "Iniciar sesión" y completar el proceso en la ventana abierta del navegador.
|
||||
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.
|
||||
@@ -88,6 +88,7 @@ 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.login.restricted=Iniciar sesión en su cuenta Microsoft para activar esta función
|
||||
account.logout=Salir
|
||||
account.register=Registrarse
|
||||
account.manage=Lista de cuentas
|
||||
@@ -102,21 +103,21 @@ account.methods.microsoft.close_page=La autorización de la cuenta de Microsoft
|
||||
account.methods.microsoft.makegameidsettings=Crear perfil / Editar nombre del perfil
|
||||
account.methods.microsoft.deauthorize=Desautorizar
|
||||
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.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. 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.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.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\
|
||||
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.
|
||||
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 <b>%1$s</b>. 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\
|
||||
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.
|
||||
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á utilizando una versión no oficial de HMCL. Por favor, descargue la versión oficial para iniciar sesión.
|
||||
@@ -129,7 +130,7 @@ account.methods.offline.name.invalid=Se recomienda utilizar sólo letras en ingl
|
||||
\ · 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.
|
||||
Usar un nombre de usuario ilegal te impedirá unirte a la mayoría de los servidores y puede entrar en conflicto con algunos mods, haciendo que el juego se cuelgue.
|
||||
account.methods.offline.uuid=UUID
|
||||
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\
|
||||
@@ -168,7 +169,7 @@ archive.version=Versión
|
||||
|
||||
assets.download=Descargando assets
|
||||
assets.download_all=Verificando la integridad de los archivos
|
||||
assets.index.malformed=Los archivos de índice de los activos descargados estaban dañados. Puede intentar utilizar "Actualizar activos del juego" en la configuración de su instancia del juego para solucionar este problema.
|
||||
assets.index.malformed=Los archivos de índice de los activos descargados estaban dañados. Puede intentar utilizar «Actualizar activos del juego» en la configuración de su instancia del juego para solucionar este problema.
|
||||
|
||||
button.cancel=Cancelar
|
||||
button.change_source=Cambiar fuente de descarga
|
||||
@@ -180,6 +181,7 @@ button.install=Instalar
|
||||
button.export=Exportar
|
||||
button.no=No
|
||||
button.ok=Aceptar
|
||||
button.ok.countdown=Aceptar (%d)
|
||||
button.refresh=Refrescar
|
||||
button.remove=Eliminar
|
||||
button.remove.confirm=¿Estás seguro de que deseas eliminarlo de forma permanente? ¡Esta acción no se puede deshacer!
|
||||
@@ -274,6 +276,7 @@ curse.category.436=Alimentos
|
||||
curse.category.4558=Redstone
|
||||
curse.category.4843=Automatización
|
||||
curse.category.4906=MCreator
|
||||
curse.category.7669=Twilight Forest
|
||||
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/12
|
||||
curse.category.5244=Paquete de fuentes
|
||||
@@ -328,8 +331,8 @@ download=Descargar
|
||||
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.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.curseforge.unavailable=Esta versión de HMCL no permite acceder a CurseForge. Utilice la versión oficial para acceder a CurseForge.
|
||||
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.empty=No hay versiones disponibles, por favor haga clic aquí para volver.
|
||||
@@ -343,17 +346,18 @@ download.provider.balanced=De la fuente más rápida disponible
|
||||
download.provider.mirror=Desde espejo
|
||||
download.java=Descargando Java
|
||||
download.java.override=Esta versión de Java ya existe. ¿Desea desinstalarla y volver a instalarla?
|
||||
download.java.process=Proceso de descarga Java
|
||||
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\nespejo de descarga o hacer clic en "Cancelar" para detener y salir.\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.\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.
|
||||
@@ -364,6 +368,7 @@ extension.png=Archivo de imagen
|
||||
extension.ps1=Windows PowerShell Script
|
||||
extension.sh=Shell Script
|
||||
|
||||
fatal.create_hmcl_current_directory_failure=El lanzador no puede crear el directorio HMCL (%s). Por favor, mueva el lanzador a otra ubicación y vuelva a abrirlo.
|
||||
fatal.javafx.incompatible=No se encontró un entorno JavaFX.\n\
|
||||
Hello Minecraft! Launcher no puede instalar automáticamente JavaFX con versiones de Java inferiores a la 11.\n\
|
||||
Por favor, actualice su Java a la versión 11 o posterior.
|
||||
@@ -377,9 +382,9 @@ fatal.config_in_temp_dir=Estás abriendo Hello Minecraft! Launcher en un directo
|
||||
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, 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\
|
||||
@@ -392,15 +397,21 @@ fatal.apply_update_failure=Lo sentimos, pero Hello Minecraft! Launcher no puede
|
||||
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.deprecated_java_version=HMCL requerirá Java 11 o posterior para funcionar en el futuro, pero seguirá siendo compatible con el lanzamiento de juegos con Java 8.\n\
|
||||
\n\
|
||||
Se recomienda instalar la última versión de Java para garantizar el correcto funcionamiento de HMCL.\n\
|
||||
\n\
|
||||
Puede seguir manteniendo la versión antigua de Java. HMCL puede reconocer y gestionar varias versiones de Java y elegirá automáticamente la adecuada para usted en función de la versión del juego.
|
||||
fatal.deprecated_java_version.download_link=Descargar Java %d
|
||||
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\
|
||||
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.
|
||||
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\
|
||||
fatal.unsupported_platform.macos_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\
|
||||
@@ -439,15 +450,15 @@ game.crash.reason.block=El juego se ha colgado debido a un bloqueo en el mundo.\
|
||||
\n\
|
||||
Tipo de bloque: %1$s\n\
|
||||
Ubicación: %2$s
|
||||
game.crash.reason.bootstrap_failed=El juego se ha colgado debido al mod "%1$s".\n\
|
||||
game.crash.reason.bootstrap_failed=El juego se ha colgado debido al mod «%1$s».\n\
|
||||
\n\
|
||||
Puedes intentar borrarlo o actualizarlo.
|
||||
game.crash.reason.config=El juego se ha colgado debido a que el mod "%1$s" no ha podido analizar su archivo de configuración "%2$s".
|
||||
game.crash.reason.config=El juego se ha colgado debido a que el mod «%1$s» no ha podido analizar su archivo de configuración «%2$s».
|
||||
game.crash.reason.debug_crash=El juego se ha colgado porque lo has activado manualmente. Así que probablemente sepas por qué :)
|
||||
game.crash.reason.mixin_apply_mod_failed=El juego se ha colgado porque no se ha podido aplicar el mixin al mod "%1$s".\n\
|
||||
game.crash.reason.mixin_apply_mod_failed=El juego se ha colgado porque no se ha podido aplicar el mixin al mod «%1$s».\n\
|
||||
\n\
|
||||
Puedes probar a borrar o actualizar el mod para resolver el problema.
|
||||
game.crash.reason.duplicated_mod=El juego no puede iniciarse debido a la existencia de mods duplicados: "%1$s".\n\
|
||||
game.crash.reason.duplicated_mod=El juego no puede iniciarse debido a la existencia de mods duplicados: «%1$s».\n\
|
||||
\n\
|
||||
%2$s\n\
|
||||
\n\
|
||||
@@ -462,7 +473,7 @@ game.crash.reason.fabric_version_0_12=La versión de Fabric Loader 0.12 o poster
|
||||
game.crash.reason.fabric_warnings=El cargador de Fabric Loader ha advertido:\n\
|
||||
\n\
|
||||
%1$s
|
||||
game.crash.reason.file_already_exists=El juego se ha colgado porque el archivo "%1$s" ya existe.\n\
|
||||
game.crash.reason.file_already_exists=El juego se ha colgado porque el archivo «%1$s» ya existe.\n\
|
||||
\n\
|
||||
Puedes intentar hacer una copia de seguridad y eliminar ese archivo, y luego volver a ejecutar el juego.
|
||||
game.crash.reason.file_changed=El juego se ha colgado porque ha fallado la verificación de archivos.\n\
|
||||
@@ -489,27 +500,27 @@ game.crash.reason.install_mixinbootstrap=El juego se ha colgado debido a la falt
|
||||
Puedes intentar instalar <a href="https://www.curseforge.com/minecraft/mc-mods/mixinbootstrap">MixinBootstrap</a> para resolver el problema. Si se cuelga después de la instalación, prueba a añadir un signo de exclamación (!) delante del nombre de archivo de este mod para intentar resolver el problema.
|
||||
game.crash.reason.jdk_9=El juego se ha colgado porque la versión de Java es demasiado nueva para esta instancia.\n\
|
||||
\n\
|
||||
Tienes que descargar e instalar Java 8 y elegirlo en "Config. Global/Específica de instancia → Java".
|
||||
Tienes que descargar e instalar Java 8 y elegirlo en «Config. Global/Específica de instancia → Java».
|
||||
game.crash.reason.need_jdk11=El juego se ha colgado debido a una versión inadecuada de Java.\n\
|
||||
\n\
|
||||
Debes descargar e instalar Java 11 y configurarlo en "Config. Global/Específica de instancia → Java".
|
||||
Debes descargar e instalar Java 11 y configurarlo en «Config. Global/Específica de instancia → Java».
|
||||
game.crash.reason.jvm_32bit=El juego se ha colgado porque la asignación de memoria actual excedía el límite de 32 bits JVM.\n\
|
||||
\n\
|
||||
Si su sistema operativo es de 64 bits, instale y utilice una versión de Java de 64 bits. De lo contrario, es posible que tenga que volver a instalar un sistema operativo de 64 bits o adquirir un ordenador más moderno.\n\
|
||||
\n\
|
||||
O bien, puede desactivar la opción "Asignar automáticamente" en "Config. Global/Específica de instancia → Memoria" y establecer el tamaño máximo de asignación de memoria en 1024 MB o menos.
|
||||
game.crash.reason.loading_crashed_forge=El juego se ha colgado debido al mod "%1$s" (%2$s).\n\
|
||||
O bien, puede desactivar la opción «Asignar automáticamente» en «Config. Global/Específica de instancia → Memoria» y establecer el tamaño máximo de asignación de memoria en 1024 MiB o menos.
|
||||
game.crash.reason.loading_crashed_forge=El juego se ha colgado debido al mod «%1$s» (%2$s).\n\
|
||||
\n\
|
||||
Puedes intentar borrarlo o actualizarlo.
|
||||
game.crash.reason.loading_crashed_fabric=El juego se ha colgado debido al mod "%1$s".\n\
|
||||
game.crash.reason.loading_crashed_fabric=El juego se ha colgado debido al mod «%1$s».\n\
|
||||
\n\
|
||||
Puedes intentar borrarlo o actualizarlo.
|
||||
game.crash.reason.memory_exceeded=El juego se ha colgado debido a que se ha asignado demasiada memoria a un pequeño archivo de paginación.\n\
|
||||
\n\
|
||||
Puedes probar a desactivar la opción "Asignar automáticamente" en "Config. Global/Específica de instancia → Memoria" y ajustar el valor hasta que se inicie el juego.\n\
|
||||
Puedes probar a desactivar la opción «Asignar automáticamente» en «Config. Global/Específica de instancia → Memoria» y ajustar el valor hasta que se inicie el juego.\n\
|
||||
\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 colgado debido al mod "%1$s".\n\
|
||||
game.crash.reason.mod=El juego se ha colgado debido al mod «%1$s».\n\
|
||||
\n\
|
||||
Puedes actualizar o eliminar el mod y volver a intentarlo.
|
||||
game.crash.reason.mod_resolution=El juego no puede seguir ejecutándose debido a problemas de dependencia de mods.\n\
|
||||
@@ -532,20 +543,20 @@ game.crash.reason.night_config_fixes=El juego se colgó debido a algunos problem
|
||||
Para obtener más información, visite el <a href="https://www.github.com/Fuzss/nightconfigfixes">repositorio de GitHub</a> del mod.
|
||||
game.crash.reason.mod_resolution_collection=El juego se colgó porque la versión del mod no es compatible.\n\
|
||||
\n\
|
||||
"%1$s" requiere mod "%2$s".\n\
|
||||
«%1$s» requiere mod «%2$s».\n\
|
||||
\n\
|
||||
Necesitas actualizar o degradar "%3$s" antes de continuar.
|
||||
Necesitas actualizar o degradar «%3$s» antes de continuar.
|
||||
game.crash.reason.mod_resolution_conflict=El juego se ha colgado debido a mods conflictivos.\n\
|
||||
\n\
|
||||
"%1$s" es incompatible con "%2$s".
|
||||
«%1$s» es incompatible con «%2$s».
|
||||
game.crash.reason.mod_resolution_missing=El juego se ha colgado porque algunos mods dependientes no estaban instalados.\n\
|
||||
\n\
|
||||
"%1$s" requiere mod "%2$s".\n\
|
||||
«%1$s» requiere mod «%2$s».\n\
|
||||
\n\
|
||||
Esto significa que tienes que descargar e instalar "%2$s" primero para continuar jugando.
|
||||
Esto significa que tienes que descargar e instalar «%2$s» primero para continuar jugando.
|
||||
game.crash.reason.mod_resolution_missing_minecraft=El juego se ha colgado porque un mod es incompatible con la versión actual de Minecraft.\n\
|
||||
\n\
|
||||
"%1$s" requiere la versión de Minecraft %2$s.\n\
|
||||
«%1$s» requiere la versión de Minecraft %2$s.\n\
|
||||
\n\
|
||||
Si quieres jugar con esta versión del mod instalada, debes cambiar la versión del juego de tu instancia.\n\
|
||||
\n\
|
||||
@@ -556,10 +567,10 @@ game.crash.reason.forge_repeat_installation=El juego se ha colgado debido a una
|
||||
\n\
|
||||
Se recomienda enviar comentarios en GitHub junto con este registro para que podamos encontrar más pistas y resolver el problema.\n\
|
||||
\n\
|
||||
Actualmente puede desinstalar Forge y volver a instalarlo en "Editar Instancia → Cargadores".
|
||||
Actualmente puede desinstalar Forge y volver a instalarlo en «Editar Instancia → Cargadores».
|
||||
game.crash.reason.optifine_repeat_installation=El juego se ha colgado debido a una instalación duplicada de OptiFine.\n\
|
||||
\n\
|
||||
Elimine OptiFine del directorio "mods" o desinstálelo en "Editar instancia → Cargadores".
|
||||
Elimine OptiFine del directorio «mods» o desinstálelo en «Editar instancia → Cargadores».
|
||||
game.crash.reason.modmixin_failure=El juego se ha colgado debido a que algunos mods no se han inyectado.\n\
|
||||
\n\
|
||||
Esto generalmente significa que el mod tiene un error o no es compatible con el entorno actual. \n\
|
||||
@@ -578,21 +589,21 @@ game.crash.reason.forge_error=Forge/NeoForge puede haber proporcionado informaci
|
||||
game.crash.reason.mod_resolution0=El juego se ha colgado debido a algunos problemas con el mod. Puede consultar el registro para ver si hay un mod incorrecto.
|
||||
game.crash.reason.java_version_is_too_high=El juego se ha colgado debido a que la versión de Java es demasiado nueva para seguir ejecutándose.\n\
|
||||
\n\
|
||||
Cambia la versión principal anterior de Java en "Config. Global/Específica de instancia → Java" y, a continuación, inicia el juego.\n\
|
||||
Cambia la versión principal anterior de Java en «Config. Global/Específica de instancia → Java» y, a continuación, inicia el juego.\n\
|
||||
\n\
|
||||
Si no es así, puede descargarlo desde <a href="https://www.java.com/download/">java.com (Java 8)</a> o <a href="https://bell-sw.com/pages/downloads/#downloads">BellSoft Liberica Full JRE (Java 17)</a> y otras plataformas para descargar e instalar una (reinicie el iniciador después de la instalación).
|
||||
game.crash.reason.mod_name=El juego se ha colgado debido a problemas con el nombre de archivo del mod.\n\
|
||||
\n\
|
||||
Los nombres de los archivos Mod deben usar sólo letras inglesas (A~Z, a~z), números (0~9), líneas horizontales(-), subrayado (_) y puntos (.) a media altura.\n\
|
||||
\n\
|
||||
Por favor, vaya al directorio "mods" y cambie todos los nombres de archivo de mods no conformes utilizando los caracteres conformes anteriores.
|
||||
Por favor, vaya al directorio «mods» y cambie todos los nombres de archivo de mods no conformes utilizando los caracteres conformes anteriores.
|
||||
game.crash.reason.incomplete_forge_installation=O jogo atual não pode continuar devido a uma instalação incompleta do Forge / NeoForge.\n\
|
||||
\n\
|
||||
Reinstale o Forge/NeoForge em "Editar Instância → Carregadores".
|
||||
Reinstale o Forge/NeoForge em «Editar Instância → Carregadores».
|
||||
game.crash.reason.modlauncher_8=El juego se ha colgado debido a que la versión actual de Forge no es compatible con tu instalación de Java. Intenta actualizar Forge.
|
||||
game.crash.reason.no_class_def_found_error=El juego no puede ejecutarse debido a un código incompleto.\n\
|
||||
\n\
|
||||
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\
|
||||
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_such_method_error=El juego no puede ejecutarse debido a un código incompleto.\n\
|
||||
@@ -606,10 +617,10 @@ game.crash.reason.opengl_not_supported=El juego se ha colgado porque tu controla
|
||||
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 JVM OpenJ9. Por favor, cambia a un Java que utilice la JVM Hotspot en "Config. Global/Específica de instancia → Java" y vuelve a ejecutar el juego. Si no dispones de una, puedes descargarla.
|
||||
game.crash.reason.openj9=El juego no puede ejecutarse en una JVM OpenJ9. Por favor, cambia a un Java que utilice la JVM Hotspot en «Config. Global/Específica de instancia → Java» y vuelve a ejecutar el juego. Si no dispones de una, puedes descargarla.
|
||||
game.crash.reason.out_of_memory=El juego se ha colgado porque el ordenador se ha quedado sin memoria.\n\
|
||||
\n\
|
||||
Puede que no haya suficiente memoria disponible o que haya demasiados mods instalados. Puedes intentar resolverlo aumentando la memoria asignada en "Config. Global/Específica de instancia → Memoria".\n\
|
||||
Puede que no haya suficiente memoria disponible o que haya demasiados mods instalados. Puedes intentar resolverlo aumentando la memoria asignada en «Config. Global/Específica de instancia → Memoria».\n\
|
||||
\n\
|
||||
Si sigues encontrando estos problemas, es posible que necesites una mejor computadora.
|
||||
game.crash.reason.resolution_too_high=El juego se ha colgado porque la resolución del paquete de recursos/texturas es demasiado alta.\n\
|
||||
@@ -621,14 +632,14 @@ game.crash.reason.shaders_mod=El juego se ha colgado porque OptiFine y Shaders m
|
||||
game.crash.reason.rtss_forest_sodium=El juego se colgó porque el RivaTuner Statistical Server (RTSS) no es compatible con Sodium.\n\
|
||||
\n\
|
||||
Haz clic <a href="https://github.com/CaffeineMC/sodium-fabric/wiki/Known-Issues#rtss-incompatible">aquí</a> para obtener más detalles.
|
||||
game.crash.reason.stacktrace=El motivo del cuelgue es desconocido. Puedes ver los detalles haciendo clic en el botón "Registros".\n\
|
||||
game.crash.reason.stacktrace=El motivo del cuelgue es desconocido. Puedes ver los detalles haciendo clic en el botón «Registros».\n\
|
||||
\n\
|
||||
Hay algunas palabras clave que pueden contener algunos nombres de mods. Puedes buscarlas en Internet para averiguar el problema tú mismo.\n\
|
||||
\n\
|
||||
%s
|
||||
game.crash.reason.too_old_java=El juego se ha colgado porque estás utilizando una versión de Java VM obsoleta.\n\
|
||||
\n\
|
||||
Tienes que cambiar a una versión de Java más reciente (%1$s) en "Config. Global/Específica de instancia → Java" y, a continuación, volver a ejecutar el juego. Puede descargar Java desde <a href="https://learn.microsoft.com/java/openjdk/download">aquí</a>.
|
||||
Tienes que cambiar a una versión de Java más reciente (%1$s) en «Config. Global/Específica de instancia → Java» y, a continuación, volver a ejecutar el juego. Puede descargar Java desde <a href="https://learn.microsoft.com/java/openjdk/download">aquí</a>.
|
||||
game.crash.reason.unknown=No somos capaces de averiguar por qué se ha colgado el juego. Por favor, refiérase a los registros del juego.
|
||||
game.crash.reason.unsatisfied_link_error=No se puede iniciar Minecraft porque faltan bibliotecas: %1$s.\n\
|
||||
\n\
|
||||
@@ -647,10 +658,10 @@ game.crash.reason.mod_files_are_decompressed=El juego se ha colgado porque se ha
|
||||
Si la extracción provoca errores en el juego, elimine el mod extraído en el directorio de mods y, a continuación, inicie el juego.
|
||||
game.crash.reason.optifine_causes_the_world_to_fail_to_load=Es posible que el juego no siga ejecutándose debido al OptiFine.\n\
|
||||
\n\
|
||||
Este problema solo se produce en una versión específica de OptiFine. Puede probar a cambiar la versión de OptiFine en "Editar instancia → Cargadores".
|
||||
Este problema solo se produce en una versión específica de OptiFine. Puede probar a cambiar la versión de OptiFine en «Editar instancia → Cargadores».
|
||||
game.crash.reason.optifine_is_not_compatible_with_forge=El juego se ha colgado porque OptiFine no es compatible con la instalación actual de Forge.\n\
|
||||
\n\
|
||||
Por favor, navegue hasta la <a href="https://optifine.net/downloads">página oficial de OptiFine</a>, compruebe si la versión de Forge es compatible con OptiFine, y reinstale la instancia en estricta conformidad con la versión correspondiente, o cambie la versión de OptiFine en "Editar Instancia → Cargadores".\n\
|
||||
Por favor, navegue hasta la <a href="https://optifine.net/downloads">página oficial de OptiFine</a>, compruebe si la versión de Forge es compatible con OptiFine, y reinstale la instancia en estricta conformidad con la versión correspondiente, o cambie la versión de OptiFine en «Editar Instancia → Cargadores».\n\
|
||||
\n\
|
||||
Tras realizar pruebas, creemos que las versiones de OptiFine demasiado altas o demasiado bajas pueden provocar fallos.
|
||||
game.crash.reason.too_many_mods_lead_to_exceeding_the_id_limit=El juego se ha colgado porque has instalado demasiados mods y has superado el límite de ID del juego.\n\
|
||||
@@ -672,18 +683,20 @@ input.url=La entrada debe ser una URL válida.
|
||||
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.change_version.process=Proceso de cambio de versión
|
||||
install.failed=Fallo en la instalación
|
||||
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 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 resolver este problema cambiando a otra fuente de descarga en "Ajustes → Descarga → Fuente de descarga".
|
||||
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 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.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.do_not_install=No instalar
|
||||
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.
|
||||
@@ -691,24 +704,27 @@ 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=Instalando %s
|
||||
install.installer.install_offline=Instalar/actualizar desde archivo local
|
||||
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 Forge, NeoForge, OptiFine, Fabric, Quilt y LiteLoader.
|
||||
install.installer.liteloader=LiteLoader
|
||||
install.installer.not_installed=No instalar
|
||||
install.installer.not_installed=No está instalado
|
||||
install.installer.optifine=OptiFine
|
||||
install.installer.quilt=Quilt
|
||||
install.installer.quilt-api=QSL/QFAPI
|
||||
install.installer.version=%s
|
||||
install.installer.external_version=%s (Instalado por un proceso externo, que no se puede configurar)
|
||||
install.installing=Instalando
|
||||
install.modpack=Añadir modpack
|
||||
install.modpack.installation=Instalación de 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.installation=Instalación de instancia
|
||||
install.new_game.malformed=Nombre no válido.
|
||||
install.select=Elegir una operación
|
||||
install.success=Instalado con éxito.
|
||||
@@ -740,6 +756,7 @@ java.install.failed.invalid=Este archivo no es un paquete de instalación de Jav
|
||||
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.installing=Instalando Java
|
||||
java.reveal=Abrir el directorio Java
|
||||
java.uninstall=Desinstalar Java
|
||||
java.uninstall.confirm=¿Está seguro de que desea desinstalar este Java? ¡Esta acción no se puede deshacer!
|
||||
@@ -749,10 +766,10 @@ lang.default=Usar idioma del sistema
|
||||
|
||||
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.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 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.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 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.
|
||||
@@ -763,7 +780,7 @@ launch.advice.java9=No puedes ejecutar Minecraft 1.12 o anterior con Java 9 o m
|
||||
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.not_enough_space=Has asignado un tamaño de memoria mayor que los %d MiB 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\nPor favor, descárguelo de java.com, o instale OpenJDK 8.
|
||||
@@ -778,7 +795,7 @@ 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\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.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.
|
||||
@@ -800,13 +817,13 @@ launcher.background=Imagen de fondo
|
||||
launcher.background.choose=Elige una imagen de fondo
|
||||
launcher.background.classic=Clásico
|
||||
launcher.background.default=Por defecto
|
||||
launcher.background.default.tooltip=O "background.png/.jpg/.gif/.webp" y las imágenes en el directorio "bg".
|
||||
launcher.background.default.tooltip=O «background.png/.jpg/.gif/.webp» y las imágenes en el directorio «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
|
||||
@@ -873,8 +890,8 @@ modpack.files.resourcepacks=Paquetes de recursos/texturas
|
||||
modpack.files.saves=Mundos
|
||||
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=Instalando Modpack
|
||||
modpack.installing=Instalando modpack
|
||||
modpack.installing.given=Instalando %s 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.
|
||||
@@ -1052,16 +1069,19 @@ web.view_in_browser=Ver en navegador
|
||||
world=Mundos
|
||||
world.add=Añadir mundo
|
||||
world.backup=Copia de seguridad
|
||||
world.backup.create.new_one=Crear uno nuevo
|
||||
world.backup.create.failed=No se ha podido crear la copia de seguridad.\n%s
|
||||
world.backup.create.locked=El mundo está actualmente en uso. Por favor, cierra el juego e inténtalo de nuevo.
|
||||
world.backup.create.success=Creada con éxito una nueva copia de seguridad: %s
|
||||
world.backup.delete=Eliminar esta copia de seguridad
|
||||
world.backup.processing=Creando nueva copia de seguridad ...
|
||||
world.backup.reveal=Abrir el directorio
|
||||
world.backup.title=Mundo [%s] - Copias de seguridad
|
||||
world.datapack=Gestionar paquetes de datos
|
||||
world.datapack.1_13=Sólo Minecraft 1.13 o posterior soporta paquetes de datos.
|
||||
world.datetime=Jugado por última vez en %s
|
||||
world.download=Descargar Mundo
|
||||
world.download.title=Descargar mundo - %1s
|
||||
world.export=Exportar el mundo
|
||||
world.export.title=Elija el directorio para este mundo exportado
|
||||
world.export.location=Guardar como
|
||||
@@ -1127,12 +1147,13 @@ 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\
|
||||
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=Paquetes de recursos
|
||||
resourcepack.download.title=Descargar paquete de recursos - %1s
|
||||
|
||||
search=Búsqueda
|
||||
search.hint.chinese=Buscar en inglés y chino
|
||||
@@ -1171,15 +1192,15 @@ 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.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/<nombre de la instancia>/", excepto para los activos y las bibliotecas)
|
||||
settings.advanced.game_dir.default=Por defecto («.minecraft/»)
|
||||
settings.advanced.game_dir.independent=Aislar («.minecraft/versions/<nombre de la instancia>/», 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.java_permanent_generation_space.prompt=en MiB
|
||||
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=\ · 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.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.
|
||||
@@ -1212,6 +1233,7 @@ settings.advanced.renderer.llvmpipe=Software (Bajo rendimiento, máxima compatib
|
||||
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.unsupported_system_options=Configuración no aplicable al sistema actual
|
||||
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
|
||||
@@ -1240,7 +1262,7 @@ 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=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\
|
||||
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.
|
||||
|
||||
@@ -1262,13 +1284,13 @@ 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=Abrir el directorio
|
||||
settings.launcher.log=Registro
|
||||
settings.launcher.log.font=Fuente
|
||||
settings.launcher.proxy=Proxy
|
||||
settings.launcher.proxy.authentication=Requiere autenticación
|
||||
settings.launcher.proxy.disable=Usar proxy del sistema
|
||||
settings.launcher.proxy.default=Usar proxy del sistema
|
||||
settings.launcher.proxy.host=Host
|
||||
settings.launcher.proxy.http=HTTP
|
||||
settings.launcher.proxy.none=Sin proxy
|
||||
@@ -1282,13 +1304,13 @@ settings.launcher.turn_off_animations=Desactivar animación (Se aplica después
|
||||
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.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.allocate.auto=%1$.1f GiB Mínimo / %2$.1f GiB Asignado
|
||||
settings.memory.allocate.auto.exceeded=%1$.1f GiB Mínimo / %2$.1f GiB Asignado (%3$.1f GiB Disponible)
|
||||
settings.memory.allocate.manual=%1$.1f GiB Asignados
|
||||
settings.memory.allocate.manual.exceeded=%1$.1f GiB Asignados (%3$.1f GiB 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 GiB Utilizados / %2$.1f GiB Totales
|
||||
settings.physical_memory=Tamaño de la memoria física
|
||||
settings.show_log=Mostrar registros
|
||||
settings.tabs.installers=Cargadores
|
||||
@@ -1299,7 +1321,7 @@ settings.type.global.manage=Config. Global
|
||||
settings.type.global.edit=Editar Config. Global
|
||||
settings.type.special.enable=Activar Config. Específica de la instancia
|
||||
settings.type.special.edit=Editar configuración de la instancia actual
|
||||
settings.type.special.edit.hint=La instancia actual "%s" ha activado la "Configuración específica de la instancia". Todas las configuraciones de esta página NO afectarán a esa instancia. Haga clic aquí para editar la configuración de la instancia.
|
||||
settings.type.special.edit.hint=La instancia actual «%s» ha activado la «Configuración específica de la instancia». Todas las configuraciones de esta página NO afectarán a esa instancia. Haga clic aquí para editar la configuración de la instancia.
|
||||
|
||||
sponsor=Donantes
|
||||
sponsor.bmclapi=Las descargas de China continental son proporcionadas por BMCLAPI. Haga clic aquí para obtener más información.
|
||||
@@ -1340,8 +1362,8 @@ 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 "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.empty.launch=No hay instancias disponibles. Si haces clic en «Aceptar», accederás a la página «Descargar». También puedes descargar el juego o cambiar de directorio de juego con los botones «Descargar» o «Todas las instancias» de la página principal de HMCL.
|
||||
version.empty.hint=No hay instancias de Minecraft aquí.\nPuedes intentar cambiar a otro directorio del juego o hacer clic aquí para descargar una.
|
||||
version.game.old=Histórico
|
||||
version.game.release=Lanzamiento
|
||||
version.game.releases=Lanzamientos
|
||||
@@ -1356,7 +1378,7 @@ 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=Eliminar archivos de registros
|
||||
version.manage.clean.tooltip=Eliminar los archivos de los directorios "logs" y "crash-reports".
|
||||
version.manage.clean.tooltip=Eliminar los archivos de los directorios «logs» y «crash-reports».
|
||||
version.manage.duplicate=Duplicar instancia
|
||||
version.manage.duplicate.duplicate_save=Duplicar mundos
|
||||
version.manage.duplicate.prompt=Introduzca el nombre de la nueva instancia
|
||||
@@ -1365,8 +1387,8 @@ version.manage.manage=Editar instancia
|
||||
version.manage.manage.title=Editar instancia - %1s
|
||||
version.manage.redownload_assets_index=Actualizar activos del juego
|
||||
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=¿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
|
||||
|
||||
@@ -379,7 +379,7 @@ game.crash.reason.macos_failed_to_find_service_port_for_display=現在のゲー
|
||||
game.crash.reason.illegal_access_error=一部のmodが原因でゲームがクラッシュしました。\n認識している場合:%1$s、modを更新または削除して、再試行してください。
|
||||
game.crash.reason.install_mixinbootstrap=MixinBootstrapが見つからないため、現在のゲームを続行できません。\n<a href="https://www.curseforge.com/minecraft/mc-mods/mixinbootstrap">MixinBootstrap</a>をインストールしてみてください。インストール後にクラッシュする場合は、モジュールのファイル名の前に英語の「! をモジュールのファイル名の前につけてみてください。
|
||||
game.crash.reason.jdk_9=Javaのバージョンが高すぎるため、ゲームを実行できません。\nJava 8をダウンロードしてインストールし、ゲーム設定で新しくインストールしたJavaを選択する必要があります。
|
||||
game.crash.reason.jvm_32bit=メモリ割り当てが32ビットJavaVMの制限を超えたため、ゲームがクラッシュしました。\nOSが64ビットの場合は、64ビットJavaをインストールして使用してください。OSが32ビットの場合は、64ビットOSを再インストールするか、新しいコンピュータを変更できます。\nまたは、自動メモリ割り当てを無効にして、最大メモリサイズを1024MB以下に設定できます。
|
||||
game.crash.reason.jvm_32bit=メモリ割り当てが32ビットJavaVMの制限を超えたため、ゲームがクラッシュしました。\nOSが64ビットの場合は、64ビットJavaをインストールして使用してください。OSが32ビットの場合は、64ビットOSを再インストールするか、新しいコンピュータを変更できます。\nまたは、自動メモリ割り当てを無効にして、最大メモリサイズを1024MiB以下に設定できます。
|
||||
game.crash.reason.loading_crashed_forge=mod %1$s(%2$s)がクラッシュしたため、ゲームがクラッシュしました。\n削除または更新を試みることができます。
|
||||
game.crash.reason.loading_crashed_fabric=mod %1$s がクラッシュしたため、ゲームがクラッシュしました。\n削除または更新を試みることができます。
|
||||
game.crash.reason.memory_exceeded=JVMが割り当てるのに十分なメモリがないため、ゲームがクラッシュしました。\nこの問題は、ページサイズが小さすぎることが原因です。\nゲーム設定で自動メモリ割り当てをオフにし、メモリ割り当てを次のように調整する必要があります。システムが処理できる値。\nシステムのページサイズを十分な大きさに調整することもできます。
|
||||
@@ -506,8 +506,8 @@ 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以前を起動することはできません。ゲームを高速化するには、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.not_enough_space=割り当てたメモリが多すぎます。物理メモリサイズが%dMiBであるため、ゲームがクラッシュする可能性があります。
|
||||
launch.advice.too_large_memory_for_32bit=32ビットJavaランタイム環境が原因で、割り当てたメモリが多すぎるため、ゲームがクラッシュする可能性があります。32ビットシステムの最大メモリ容量は1024MiBです。
|
||||
launch.advice.vanilla_linux_java_8=Linux x86-64の場合、Minecraft1.12.2以下はJava8でのみ実行できます。\nJava9以降のバージョンでは、liblwjgl.soなどの32ビットネイティブライブラリをロードできません。
|
||||
launch.advice.vanilla_x86.translation=Minecraftは現在、x86およびx86-64以外のアーキテクチャの公式サポートを提供していません。\nJava for x86-64を使用して、トランスレータを介してminecraftを実行するか、プラットフォームの対応するネイティブライブラリをダウンロードして指定してくださいその配置パス。
|
||||
launch.failed=起動できません
|
||||
@@ -772,8 +772,8 @@ settings.advanced.dont_check_game_completeness=ゲームファイルをスキャ
|
||||
settings.advanced.dont_check_jvm_validity=JVMがゲームを起動できるかどうかを確認しないでください
|
||||
settings.advanced.game_dir.default=標準(.minecraft /)
|
||||
settings.advanced.game_dir.independent=バージョン独立(.minecraft / versions / <バージョン名> /、アセットとライブラリを除く)
|
||||
settings.advanced.java_permanent_generation_space=PermGen Space / MB
|
||||
settings.advanced.java_permanent_generation_space.prompt=Java 8以降のメタスペース、MB
|
||||
settings.advanced.java_permanent_generation_space=PermGen Space / MiB
|
||||
settings.advanced.java_permanent_generation_space.prompt=Java 8以降のメタスペース、MiB
|
||||
settings.advanced.jvm=Java仮想マシンの設定
|
||||
settings.advanced.jvm_args=JavaVM引数
|
||||
settings.advanced.jvm_args.prompt=- 入力パラメータがデフォルトパラメータと同じである場合、追加されません\n\
|
||||
@@ -804,6 +804,7 @@ settings.advanced.post_exit_command=終了後のコマンド
|
||||
settings.advanced.post_exit_command.prompt=ゲーム終了後に実行されます
|
||||
settings.advanced.server_ip=サーバーアドレス
|
||||
settings.advanced.server_ip.prompt=ゲームの起動時にサーバーに参加する
|
||||
settings.advanced.unsupported_system_options=サポートされていないシステムオプション
|
||||
settings.advanced.use_native_glfw=[Linux/FreeBSDのみ]システムGLFWを使用する
|
||||
settings.advanced.use_native_openal=[Linux/FreeBSDのみ]システムOpenALを使用する
|
||||
settings.advanced.workaround=デバッグ用オプション
|
||||
@@ -852,7 +853,7 @@ settings.launcher.log=ログ
|
||||
settings.launcher.log.font=ログフォント
|
||||
settings.launcher.proxy=プロキシ
|
||||
settings.launcher.proxy.authentication=プロキシ認証
|
||||
settings.launcher.proxy.disable=システムプロキシを使用する
|
||||
settings.launcher.proxy.default=システムプロキシを使用する
|
||||
settings.launcher.proxy.host=Host
|
||||
settings.launcher.proxy.http=HTTP
|
||||
settings.launcher.proxy.none=プロキシなし
|
||||
|
||||
@@ -91,6 +91,7 @@ account.login.skip=Войти в режиме офлайн
|
||||
account.login.retry=Повторить снова
|
||||
account.login.refresh=Войти снова
|
||||
account.login.refresh.microsoft.hint=Вам необходимо снова войти в аккаунт Microsoft, поскольку авторизация аккаунта недействительна.
|
||||
account.login.restricted=Войти в свою аккаунт Microsoft, чтобы включить эту функцию
|
||||
account.logout=Выйти
|
||||
account.register=Зарегестрироваться
|
||||
account.manage=Список аккаунтов
|
||||
@@ -128,11 +129,11 @@ account.methods.offline=Офлайн
|
||||
account.methods.offline.name.special_characters=Рекомендуется использовать только английские буквы, цифры и символы подчёркивания.
|
||||
account.methods.offline.name.invalid.tip=Для имени пользователя рекомендуется использовать только английские буквы, цифры и подчеркивания, а его длина не должна превышать 16 символов.
|
||||
account.methods.offline.name.invalid=Для имени пользователя рекомендуется использовать только английские буквы, цифры и подчеркивания, а его длина не должна превышать 16 символов.\n\
|
||||
\n\
|
||||
\ · Законный: HuangYu, huang_Yu, Huang_Yu_123;\n\
|
||||
\ · Незаконный: Huang Yu, Huang-Yu_%%%, Huang_Yu_hello_world_hello_world.\n\
|
||||
\n\
|
||||
Если вы считаете, что на стороне сервера есть соответствующий мод или плагин, снимающий это ограничение, вы можете проигнорировать это предупреждение.
|
||||
\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 на тот, который генерируется другим лаунчером гарантирует, что игровые блоки/предметы в рюкзаке вашего офлайн аккаунта останутся. Этот параметр предназначен для экспертов. Если вы не знаете что делаете, мы не советуем вам изменять этот параметр. Эта опция не требуется для присоединения к серверам.
|
||||
account.methods.offline.uuid.malformed=Недопустимый формат.
|
||||
@@ -181,6 +182,7 @@ button.install=Установить
|
||||
button.export=Экспорт
|
||||
button.no=Нет
|
||||
button.ok=ОК
|
||||
button.ok.countdown=ОК (%d)
|
||||
button.refresh=Обновить
|
||||
button.remove=Убрать
|
||||
button.remove.confirm=Удалить навсегда? Это действие невозможно отменить!
|
||||
@@ -275,6 +277,7 @@ curse.category.436=Еда
|
||||
curse.category.4558=Редстоун
|
||||
curse.category.4843=Автоматизация
|
||||
curse.category.4906=MCreator
|
||||
curse.category.7669=Twilight Forest
|
||||
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/12
|
||||
curse.category.5244=Пакеты шрифтов
|
||||
@@ -329,7 +332,7 @@ download=Скачать
|
||||
download.hint=Установить игры и модпаки или скачать моды, пакеты ресурсов и миры.
|
||||
download.code.404=Файл «%s» не найден на удаленном сервере.
|
||||
download.content=Аддоны
|
||||
download.curseforge.unavailable=Альфа-версия HMCL не поддерживает доступ к CurseForge. Для скачивания используйте версии релиза или бета.
|
||||
download.curseforge.unavailable=Эта сборка HMCL не поддерживает доступ к CurseForge. Пожалуйста, используйте официальную сборку для доступа к CurseForge.
|
||||
download.existing=Файл существует и по этому не может быть сохранён. Можно использовать «Сохранить как», чтобы сохранить файл в другом месте.
|
||||
download.external_link=Открыть сайт
|
||||
download.failed=Не удалось скачать «%1$s», код ответа: %2$d.
|
||||
@@ -344,6 +347,7 @@ download.provider.balanced=Из самых быстрых доступных
|
||||
download.provider.mirror=Из зеркала
|
||||
download.java=Скачивание Java
|
||||
download.java.override=Эта версия Java уже существует. Вы хотите удалить и установить ее заново?
|
||||
download.java.process=Процесс скачивания Java
|
||||
download.javafx=Скачивание зависимостей лаунчера...
|
||||
download.javafx.notes=Скачивание зависимостей для лаунчер из Интернета.\n\
|
||||
\n\
|
||||
@@ -365,6 +369,7 @@ extension.png=Файл изображения
|
||||
extension.ps1=Сценарий Windows PowerShell
|
||||
extension.sh=Сценарий оболочки Bash
|
||||
|
||||
fatal.create_hmcl_current_directory_failure=Лаунчер не может создать папку HMCL (%s). Пожалуйста, переместите лаунчер в другое место и откройте его снова.
|
||||
fatal.javafx.incompatible=Отсутствует среда JavaFX.\n\
|
||||
Лаунчер не может автоматически установить JavaFX на Java <11.\n\
|
||||
Обновите Java до версии 11 или более поздней.
|
||||
@@ -390,6 +395,12 @@ fatal.apply_update_failure=Мы сожалеем, лаунчер не смог
|
||||
Вы можете обновить программу вручную, скачав более новую версию с %s.
|
||||
fatal.apply_update_need_win7=Лаунчер не может автоматически обновляться на Windows XP/Vista.\n\
|
||||
Вы можете обновить программу вручную, скачав более новую версию с %s.
|
||||
fatal.deprecated_java_version=В будущем для работы HMCL потребуется Java 11 или более поздняя версия, однако запуск игр будет поддерживаться и на Java 8.\n\
|
||||
\n\
|
||||
Рекомендуется установить последнюю версию Java, чтобы обеспечить правильную работу HMCL.\n\
|
||||
\n\
|
||||
Вы можете продолжать использовать старую версию Java. HMCL может распознавать и управлять несколькими версиями Java и автоматически выбирать подходящую Java в зависимости от версии игры.
|
||||
fatal.deprecated_java_version.download_link=Скачать Java %d
|
||||
fatal.samba=Если вы пытаетесь открыть лаунчер в общей папке Samba, он может не работать, попробуйте обновить Java или запустить лаунчер из локальной папки.
|
||||
fatal.illegal_char=Недопустимый символ «=» в пути к папке пользователя. Лаунчер может работать, но некоторые функции не будут работать.\n\
|
||||
Вы не сможете использовать аккаунт authlib-injector или изменить скин для аккаунта в режиме офлайн.
|
||||
@@ -398,7 +409,7 @@ fatal.unsupported_platform=Minecraft еще не полностью поддер
|
||||
Если вы не можете запустить Minecraft 1.17 и более поздние версии, попробуйте переключить «Рендерер» на «Программный» в разделе «Глобальные настройки / Раздельные настройки для сборки → Расширенные настройки», чтобы использовать рендеринг процессора для лучшей совместимости.
|
||||
fatal.unsupported_platform.loongarch=Лаунчер обеспечил поддержку платформы Loongson.\n\
|
||||
Если у вас возникнут проблемы во время игры, вы можете обратиться за помощью на сайт https://docs.hmcl.net/groups.html.
|
||||
fatal.unsupported_platform.osx_arm64=Лаунчер обеспечил поддержку платформы Apple silicon, используя родную ARM Java для запуска игр, чтобы получить более плавный игровой опыт.\n\
|
||||
fatal.unsupported_platform.macos_arm64=Лаунчер обеспечил поддержку платформы Apple silicon, используя родную ARM Java для запуска игр, чтобы получить более плавный игровой опыт.\n\
|
||||
Если вы столкнулись с проблемами во время игры, запуск игры с Java на базе архитектуры x86-64 может обеспечить лучшую совместимость.
|
||||
fatal.unsupported_platform.windows_arm64=Лаунчер обеспечил нативную поддержку платформы Windows на архитектуре Arm. Если у вам возникли проблемы во время игры, попробуйте запустить игру с Java на базе архитектуры x86.\n\
|
||||
\n\
|
||||
@@ -668,6 +679,7 @@ input.url=Ввод должен быть действительным URL-адр
|
||||
install=Новый сборник
|
||||
install.change_version=Изменить версию
|
||||
install.change_version.confirm=Переключить %s с версии %s на %s?
|
||||
install.change_version.process=Процесс изменения версии
|
||||
install.failed=Не удалось установить
|
||||
install.failed.downloading=Не удалось скачать некоторые необходимые файлы.
|
||||
install.failed.downloading.detail=Не удалось скачать файл: %s
|
||||
@@ -680,6 +692,7 @@ install.failed.version_mismatch=Этот загрузчик требует ве
|
||||
install.installer.change_version=%s (Несовместимо)
|
||||
install.installer.choose=Выберите версию %s
|
||||
install.installer.depend=Требуется %s
|
||||
install.installer.do_not_install=Не устанавливать
|
||||
install.installer.fabric=Fabric
|
||||
install.installer.fabric-api=Fabric API
|
||||
install.installer.fabric-api.warning=Предупреждение: Fabric API является модом и будет установлен в папку mod в сборке игры. Пожалуйста, не меняйте рабочую папку сборки, иначе Fabric API не будет работать. Если вы все же хотите изменить папку, вам следует переустановить сборку.
|
||||
@@ -694,17 +707,20 @@ install.installer.install_offline.tooltip=Поддерживаются уста
|
||||
install.installer.install_online=Установить с интернета
|
||||
install.installer.install_online.tooltip=Поддерживаются Forge, NeoForge, OptiFine, Fabric, Quilt и LiteLoader.
|
||||
install.installer.liteloader=LiteLoader
|
||||
install.installer.not_installed=Не устанавливать
|
||||
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.installing=Установка
|
||||
install.modpack=Установить модпак
|
||||
install.modpack.installation=Установка модпак
|
||||
install.name.invalid=Имя содержит символы, отличные от ASCII (например, эмодзи или символы CJK).\nРекомендуется изменить имя, включив в него только английские буквы, цифры и подчеркивание, чтобы избежать возможных проблем при запуске игры.\nВы хотите продолжить процесс?
|
||||
install.new_game=Установить сборку
|
||||
install.new_game.already_exists=Это имя сборки уже существует. Пожалуйста, используйте другое имя.
|
||||
install.new_game.current_game_version=Текущая версия сборки
|
||||
install.new_game.installation=Установка экземпляра
|
||||
install.new_game.malformed=Недопустимое имя
|
||||
install.select=Выберите операцию
|
||||
install.success=Успешно установлено.
|
||||
@@ -736,6 +752,7 @@ java.install.failed.invalid=Этот архив не является допус
|
||||
java.install.failed.unsupported_platform=Эта Java несовместима с текущей платформой, поэтому ее невозможно установить.
|
||||
java.install.name=Название
|
||||
java.install.warning.invalid_character=Неправильный символ в названии
|
||||
java.installing=Установка Java
|
||||
java.reveal=Откройте каталог Java
|
||||
java.uninstall=Удалить Java
|
||||
java.uninstall.confirm=Вы уверены, что хотите удалить эту Java? Это действие нельзя отменить!
|
||||
@@ -872,8 +889,8 @@ modpack.files.resourcepacks=Пакеты ресурсов/текстур
|
||||
modpack.files.saves=Миры
|
||||
modpack.files.scripts=Файл конфигурации MineTweaker
|
||||
modpack.files.servers_dat=Файл списка серверов
|
||||
modpack.install=Установить модпак %s
|
||||
modpack.installing=Установка модпака
|
||||
modpack.installing=Установка модпак
|
||||
modpack.installing.given=Установка модпак %s
|
||||
modpack.introduction=Поддерживаются модпаки CurseForge, Modrinth, MultiMC и MCBBS.
|
||||
modpack.invalid=Неверный модпак, попробуйте скачать его заново.
|
||||
modpack.mismatched_type=Несоответствие типа модпака, текущий сборка имеет тип %s, но предоставленный сборка имеет тип %s.
|
||||
@@ -1051,16 +1068,19 @@ web.view_in_browser=Смотреть в браузере
|
||||
world=Миры
|
||||
world.add=Добавить мир
|
||||
world.backup=Резервный мир
|
||||
world.backup.create.new_one=Создать новый
|
||||
world.backup.create.failed=Не удалось создать резервную копию.\n%s
|
||||
world.backup.create.locked=В настоящее время мир находится в эксплуатации. Закройте игру и попробуйте снова.
|
||||
world.backup.create.success=Успешно создано новое резервное копирование: %s
|
||||
world.backup.delete=Удалить эту резервную копию
|
||||
world.backup.processing=Создание новой резервной копии ...
|
||||
world.backup.reveal=Показать в диспетчере файлов
|
||||
world.backup.title=Мир [%s] - Резервные копии
|
||||
world.datapack=Управлять наборами данных
|
||||
world.datapack.1_13=Только Minecraft 1.13 или новее поддерживает наборы данных.
|
||||
world.datetime=Последний запуск игры %s
|
||||
world.download=Скачать мир
|
||||
world.download.title=Скачать мир - %1s
|
||||
world.export=Экспорт мира
|
||||
world.export.title=Выберите папку для экспорта мира
|
||||
world.export.location=Экспорт в
|
||||
@@ -1132,6 +1152,7 @@ repositories.chooser=Для работы HMCL требуется JavaFX.\n\
|
||||
repositories.chooser.title=Выберите зеркало для скачивания JavaFX
|
||||
|
||||
resourcepack=Пакеты ресурсов
|
||||
resourcepack.download.title=Скачать пакет ресурсов - %1s
|
||||
|
||||
search=Поиск
|
||||
search.hint.chinese=Поиск на китайском и английском языках
|
||||
@@ -1212,6 +1233,7 @@ settings.advanced.renderer.llvmpipe=ПО (Низкая производител
|
||||
settings.advanced.renderer.zink=Vulkan (Лучшая производительность, низкая совместимость)
|
||||
settings.advanced.server_ip=Адрес сервера
|
||||
settings.advanced.server_ip.prompt=Присоединяться к серверу при запуске игры
|
||||
settings.advanced.unsupported_system_options=Настройки, не применимые к текущей системе
|
||||
settings.advanced.use_native_glfw=[Только для Linux/FreeBSD] Использовать системный GLFW
|
||||
settings.advanced.use_native_openal=[Только для Linux/FreeBSD] Использовать системный OpenAL
|
||||
settings.advanced.workaround=Обходные пути
|
||||
@@ -1268,7 +1290,7 @@ settings.launcher.log=Запись логов
|
||||
settings.launcher.log.font=Шрифт
|
||||
settings.launcher.proxy=Прокси
|
||||
settings.launcher.proxy.authentication=Требуется авторизация
|
||||
settings.launcher.proxy.disable=Использовать прокси системы
|
||||
settings.launcher.proxy.default=Использовать прокси системы
|
||||
settings.launcher.proxy.host=Хост
|
||||
settings.launcher.proxy.http=HTTP
|
||||
settings.launcher.proxy.none=Без прокси
|
||||
@@ -1340,8 +1362,8 @@ version.name=Название сборки
|
||||
version.cannot_read=Невозможно найти версию игры. Невозможно продолжить автоматическую установку.
|
||||
version.empty=Нет сборок
|
||||
version.empty.add=Установить новую сборок
|
||||
version.empty.launch=Нет версии для запуска, установите версию через страницу скачивания.
|
||||
version.empty.hint=Нет установленной версии игры. Кликните здесь, чтобы скачать новую игру.
|
||||
version.empty.launch=Нет доступных сборок. Нажав «ОК», вы перейдете на страницу «Скачать»\n\nВы также можете скачать игру или переключить директорию игры с помощью кнопок «Скачать» или «Все сборок» на главной странице HMCL.
|
||||
version.empty.hint=Нет сборок.\nВы можете попробовать перейти в другую папку с игрой или нажать здесь, чтобы скачать игру.
|
||||
version.game.old=Исторические
|
||||
version.game.release=Релиз
|
||||
version.game.releases=Релизы
|
||||
|
||||
@@ -91,6 +91,7 @@ account.login.skip=跳過重新整理帳戶
|
||||
account.login.retry=再次重新整理帳戶
|
||||
account.login.refresh=重新登入
|
||||
account.login.refresh.microsoft.hint=由於帳戶授權失效,你需要重新加入 Microsoft 帳戶
|
||||
account.login.restricted=登入微軟帳戶以啟用此功能
|
||||
account.logout=登出
|
||||
account.register=註冊
|
||||
account.manage=帳戶清單
|
||||
@@ -106,6 +107,7 @@ account.methods.microsoft.error.add_family_probably=請檢查你的帳戶設定
|
||||
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.banned=你的帳戶可能被 Xbox Live 封禁。\n你可以點擊上方「查詢帳戶是否被封禁」查看帳戶狀態。
|
||||
account.methods.microsoft.error.unknown=登入失敗,錯誤碼:%d
|
||||
account.methods.microsoft.error.wrong_verify_method=請在 Microsoft 帳戶登入頁面使用密碼登入,不要使用驗證碼登入。
|
||||
account.methods.microsoft.logging_in=登入中……
|
||||
@@ -125,17 +127,17 @@ account.methods.microsoft.manual=你的代碼為 <b>%1$s</b>,請點擊此處
|
||||
account.methods.microsoft.profile=編輯帳戶配置檔
|
||||
account.methods.microsoft.purchase=購買 Minecraft
|
||||
account.methods.forgot_password=忘記密碼
|
||||
account.methods.ban_query=查詢帳戶是否被封禁
|
||||
account.methods.microsoft.snapshot=你正在使用第三方提供的 HMCL,請下載官方版本進行登入。
|
||||
account.methods.microsoft.snapshot.website=官方網站
|
||||
account.methods.offline=離線模式
|
||||
account.methods.offline.name.special_characters=建議使用英文字母、數字以及底線命名
|
||||
account.methods.offline.name.invalid.tip=遊戲使用者名稱建議僅使用英文字母、數字及底線,且長度不超過 16 個字元。
|
||||
account.methods.offline.name.invalid=遊戲使用者名稱建議僅使用英文字母、數字及底線,且長度不超過 16 個字元。\n\
|
||||
\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 的方式可能不同。\n透過修改 UUID 選項至原啟動器所生成的 UUID,你可以保證在切換啟動器後,遊戲還能將你的遊戲角色識別為給定 UUID 所對應的角色,從而保留原來角色的背包物品。\nUUID 選項為進階選項,除非你知道你在做什麼,否則你不需要調整該選項。
|
||||
account.methods.offline.uuid.malformed=格式錯誤
|
||||
@@ -183,6 +185,7 @@ button.install=安裝
|
||||
button.export=匯出
|
||||
button.no=否
|
||||
button.ok=確定
|
||||
button.ok.countdown=確定 (%d)
|
||||
button.refresh=重新整理
|
||||
button.remove=刪除
|
||||
button.remove.confirm=你確認要刪除嗎?該操作無法復原!
|
||||
@@ -276,6 +279,7 @@ curse.category.436=食物
|
||||
curse.category.4558=紅石
|
||||
curse.category.4843=自動化
|
||||
curse.category.4906=MCreator
|
||||
curse.category.7669=暮色森林
|
||||
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/6
|
||||
curse.category.5244=字體包
|
||||
@@ -330,7 +334,7 @@ download=下載
|
||||
download.hint=安裝遊戲和模組包或下載模組、資源包和地圖
|
||||
download.code.404=遠端伺服器沒有需要下載的檔案:%s
|
||||
download.content=遊戲內容
|
||||
download.curseforge.unavailable=HMCL 預覽版暫不支援訪問 CurseForge,請使用穩定版或開發版進行下載。
|
||||
download.curseforge.unavailable=這個 HMCL 版本不支援訪問 CurseForge。請使用官方版本進行下載。
|
||||
download.existing=檔案已存在,無法儲存。你可以將檔案儲存至其他地方。
|
||||
download.external_link=打開下載網站
|
||||
download.failed=下載失敗:%1$s\n錯誤碼:%2$d
|
||||
@@ -345,6 +349,7 @@ download.provider.balanced=選取載入速度快的下載源 (平衡,但可能
|
||||
download.provider.mirror=盡量使用鏡像源 (載入快,但可能不是最新)
|
||||
download.java=下載 Java
|
||||
download.java.override=此 Java 版本已經存在,是否移除並重新安裝?
|
||||
download.java.process=下載 Java
|
||||
download.javafx=正在下載必要的執行時元件
|
||||
download.javafx.notes=正在透過網路下載 HMCL 必要的執行時元件。\n點擊「切換下載源」按鈕查看詳情以及選取下載源,點擊「取消」按鈕停止並退出。\n注意:如果下載速度過慢,請嘗試切換下載源。
|
||||
download.javafx.component=正在下載元件「%s」
|
||||
@@ -362,6 +367,7 @@ extension.png=圖片檔案
|
||||
extension.ps1=PowerShell 指令碼
|
||||
extension.sh=Bash 指令碼
|
||||
|
||||
fatal.create_hmcl_current_directory_failure=Hello Minecraft! Launcher 無法建立 HMCL 資料夾 (%s),請將 HMCL 移動至其他位置再開啟。
|
||||
fatal.javafx.incompatible=缺少 JavaFX 執行環境。\nHMCL 無法在低於 Java 11 的 Java 環境上自行補全 JavaFX 執行環境,請更新到 Java 11 或更高版本。
|
||||
fatal.javafx.incomplete=JavaFX 執行環境不完整,請嘗試更換你的 Java 或者重新安裝 OpenJFX。
|
||||
fatal.javafx.missing=缺少 JavaFX 執行環境,請使用包含 OpenJFX 的 Java 執行環境開啟 Hello Minecraft! Launcher。
|
||||
@@ -373,11 +379,13 @@ fatal.mac_app_translocation=由於 macOS 的安全機制,Hello Minecraft! Laun
|
||||
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.deprecated_java_version=HMCL 未來需要 Java 11 或更高版本才能執行,但依然支援使用 Java 8 啟動遊戲。建議安裝最新版本的 Java 以確保 HMCL 正常執行。\n你可以繼續保留舊版本 Java。HMCL 能夠識別與管理多個 Java,並會自動根據遊戲版本為你選取合適的 Java。
|
||||
fatal.deprecated_java_version.download_link=下載 Java %d
|
||||
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.macos_arm64=Hello Minecraft! Launcher 已為 Apple Silicon 平臺提供支援,使用 ARM 原生 Java 啟動遊戲以獲得更流暢的遊戲體驗。\n如果你在遊戲中遭遇問題,使用 x86-64 架構的 Java 啟動遊戲可能有更好的相容性。
|
||||
fatal.unsupported_platform.windows_arm64=Hello Minecraft! Launcher 已為 Windows on Arm 平臺提供原生支援。如果你在遊戲中遭遇問題,請嘗試使用 x86 架構的 Java 啟動遊戲。\n\n如果你正在使用<b>高通</b>平臺,你可能需要安裝 <a href="ms-windows-store://pdp/?productid=9NQPSL29BFFF">OpenGL 相容包</a>後才能進行遊戲。點擊連結前往 Microsoft Store 安裝相容包。
|
||||
|
||||
feedback=回報
|
||||
@@ -433,7 +441,7 @@ game.crash.reason.macos_failed_to_find_service_port_for_display=目前遊戲由
|
||||
game.crash.reason.illegal_access_error=目前遊戲由於某些模組的問題,無法繼續執行。\n如果你認識「%1$s」,你可以更新或刪除對應模組再試。
|
||||
game.crash.reason.install_mixinbootstrap=目前遊戲由於缺失 MixinBootstrap,無法繼續執行。\n你可以嘗試安裝 <a href="https://www.curseforge.com/minecraft/mc-mods/mixinbootstrap">MixinBootstrap</a> 解決該問題。若安裝後崩潰,嘗試在該模組的檔案名前加入半形驚嘆號 (!) 嘗試解決。
|
||||
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.jvm_32bit=目前遊戲由於記憶體分配過大,超過了 32 位 Java 記憶體限制,無法繼續執行。\n如果你的電腦是 64 位系統,請下載安裝並更換 64 位 Java。\n如果你的電腦是 32 位系統,你或許可以重新安裝 64 位系統,或換一台新電腦。\n或者,你可以關閉「(全域/實例特定) 遊戲設定 → 遊戲記憶體」中的「自動分配」,並且把記憶體限制調節為 1024 MiB 或以下。
|
||||
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 及更早版本啟動。
|
||||
@@ -484,6 +492,7 @@ input.url=必須是有效連結
|
||||
install=新增實例
|
||||
install.change_version=變更版本
|
||||
install.change_version.confirm=你確定要將 %s 從 %s 更新到 %s 嗎?
|
||||
install.change_version.process=變更版本
|
||||
install.failed=安裝失敗
|
||||
install.failed.downloading=安裝失敗,部分檔案未能完成下載
|
||||
install.failed.downloading.detail=未能下載檔案: %s
|
||||
@@ -496,6 +505,7 @@ install.failed.version_mismatch=該載入器需要的遊戲版本為 %s,但實
|
||||
install.installer.change_version=%s 與目前遊戲不相容,請更換版本
|
||||
install.installer.choose=選取 %s 版本
|
||||
install.installer.depend=需要先安裝 %s
|
||||
install.installer.do_not_install=不安裝
|
||||
install.installer.fabric=Fabric
|
||||
install.installer.fabric-api=Fabric API
|
||||
install.installer.fabric-api.warning=警告:Fabric API 是模組,將會被安裝到新遊戲的模組目錄,請你在安裝遊戲後不要修改目前遊戲的「執行路徑」設定,如果你在之後修改了相關設定,則需要重新安裝 Fabric API。
|
||||
@@ -510,17 +520,20 @@ install.installer.install_offline.tooltip=支援匯入已經下載好的 (Neo)Fo
|
||||
install.installer.install_online=線上安裝
|
||||
install.installer.install_online.tooltip=支援安裝 Forge、NeoForge、Fabric、Quilt、LiteLoader 和 OptiFine
|
||||
install.installer.liteloader=LiteLoader
|
||||
install.installer.not_installed=不安裝
|
||||
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.installing=安裝
|
||||
install.modpack=安裝模組包
|
||||
install.modpack.installation=安裝模組包
|
||||
install.name.invalid=名稱中包含非 ASCII 字元(如 Emoji 表情或中文字元)。\n建議修改名稱,名稱建議僅包含英文字母、數字和底線,以防啟動遊戲時出現問題。是否繼續安裝?
|
||||
install.new_game=安裝新實例
|
||||
install.new_game.already_exists=此實例已經存在,請重新命名
|
||||
install.new_game.current_game_version=目前遊戲實例
|
||||
install.new_game.installation=安裝新實例
|
||||
install.new_game.malformed=名稱無效
|
||||
install.select=請選取安裝方式
|
||||
install.success=安裝成功
|
||||
@@ -552,6 +565,7 @@ java.install.failed.invalid=該檔案不是合法的 Java 安裝包,無法繼
|
||||
java.install.failed.unsupported_platform=此 Java 與目前平臺不相容,無法安裝。
|
||||
java.install.name=名稱
|
||||
java.install.warning.invalid_character=名稱中包含無效字元
|
||||
java.installing=安裝 Java
|
||||
java.reveal=瀏覽 Java 目錄
|
||||
java.uninstall=移除此 Java
|
||||
java.uninstall.confirm=你確定要移除此 Java 嗎?此操作無法復原!
|
||||
@@ -575,9 +589,9 @@ launch.advice.java9=低於 (包含) 1.13 的有安裝模組的 Minecraft 版本
|
||||
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.not_enough_space=你設定的記憶體大小過大,由於超過了系統記憶體大小 %d MiB,所以可能影響遊戲體驗或無法啟動遊戲。
|
||||
launch.advice.require_newer_java_version=目前遊戲版本需要 Java %s,但 HMCL 未能找到該 Java 版本,你可以點擊「是」,HMCL 會自動下載他,是否下載?
|
||||
launch.advice.too_large_memory_for_32bit=你設定的記憶體大小過大,由於可能超過了 32 位元 Java 的記憶體分配限制,所以可能無法啟動遊戲,請將記憶體調至低於 1024 MB 的值。
|
||||
launch.advice.too_large_memory_for_32bit=你設定的記憶體大小過大,由於可能超過了 32 位元 Java 的記憶體分配限制,所以可能無法啟動遊戲,請將記憶體調至低於 1024 MiB 的值。
|
||||
launch.advice.vanilla_linux_java_8=對於 Linux x86-64 平台,Minecraft 1.12.2 及更低版本與 Java 9+ 不相容,請使用 Java 8 啟動遊戲。
|
||||
launch.advice.vanilla_x86.translation=Minecraft 尚未為你的平臺提供完善支援,所以可能影響遊戲體驗或無法啟動遊戲。\n你可以在 <a href="https://learn.microsoft.com/java/openjdk/download">這裡</a> 下載 <b>x86-64</b> 架構的 Java 以獲得更完整的體驗。\n是否繼續啟動?
|
||||
launch.advice.unknown=由於以下原因,無法繼續啟動遊戲:
|
||||
@@ -670,7 +684,7 @@ modpack.download=下載模組包
|
||||
modpack.download.title=模組包下載 - %1s
|
||||
modpack.enter_name=給遊戲取個你喜歡的名稱
|
||||
modpack.export=匯出模組包
|
||||
modpack.export.as=請選取模組包類型。若你無法決定,請選取 MCBBS 類型。
|
||||
modpack.export.as=請選取模組包類型
|
||||
modpack.file_api=模組包下載連結前綴
|
||||
modpack.files.blueprints=BuildCraft 藍圖
|
||||
modpack.files.config=模組設定檔案
|
||||
@@ -685,8 +699,8 @@ modpack.files.resourcepacks=資源包 (紋理包)
|
||||
modpack.files.saves=遊戲存檔
|
||||
modpack.files.scripts=MineTweaker 設定
|
||||
modpack.files.servers_dat=多人遊戲伺服器清單
|
||||
modpack.install=安裝 %s 模組包
|
||||
modpack.installing=正在安裝模組包
|
||||
modpack.installing=安裝模組包
|
||||
modpack.installing.given=安裝 %s 模組包
|
||||
modpack.introduction=支援 Curse、Modrinth、MultiMC、MCBBS 模組包。
|
||||
modpack.invalid=無效的模組包升級檔案,可能是下載時出現問題。
|
||||
modpack.mismatched_type=模組包類型不符,目前遊戲是「%s」模組包,但是提供的模組包更新檔案是「%s」模組包。
|
||||
@@ -703,7 +717,7 @@ modpack.type.curse=Curse
|
||||
modpack.type.curse.error=無法完成該模組包所需的相依元件下載,請多次重試或設定代理……
|
||||
modpack.type.curse.not_found=部分必需檔案已經從網路中被刪除並且再也無法下載,請嘗試該模組包的最新版本或者安裝其他模組包。
|
||||
modpack.type.manual.warning=該模組包由發佈者手動打包,其中可能已經包含啟動器,建議嘗試解壓後使用其內建的啟動器執行遊戲。\nHMCL 可以嘗試匯入該模組包,但不保證可用性,是否繼續?
|
||||
modpack.type.mcbbs=MCBBS
|
||||
modpack.type.mcbbs=MCBBS 模組包
|
||||
modpack.type.mcbbs.export=可以被 Hello Minecraft! Launcher 匯入
|
||||
modpack.type.modrinth=Modrinth
|
||||
modpack.type.multimc=MultiMC
|
||||
@@ -853,16 +867,19 @@ web.view_in_browser=在瀏覽器中查看
|
||||
|
||||
world=世界
|
||||
world.add=加入世界
|
||||
world.backup=備份世界
|
||||
world.backup=世界備份
|
||||
world.backup.create.new_one=建立新備份
|
||||
world.backup.create.failed=創建備份失敗。\n%s
|
||||
world.backup.create.locked=該世界正在使用中,請關閉遊戲後重試。
|
||||
world.backup.create.success=成功創建新備份:%s
|
||||
world.backup.delete=删除此備份
|
||||
world.backup.processing=正在備份中……
|
||||
world.backup.title=世界 [%s] - 備份
|
||||
world.datapack=管理資料包
|
||||
world.datapack.1_13=僅 Minecraft 1.13 及之後的版本支援資料包
|
||||
world.datetime=上一次遊戲時間: %s
|
||||
world.download=下載世界
|
||||
world.download.title=世界下載 - %1s
|
||||
world.export=匯出此世界
|
||||
world.export.title=選取該世界的儲存位置
|
||||
world.export.location=儲存到
|
||||
@@ -930,6 +947,32 @@ repositories.chooser=缺少 JavaFX 執行環境,HMCL 需要 JavaFX 才能正
|
||||
repositories.chooser.title=選取 JavaFX 下載源
|
||||
|
||||
resourcepack=資源包
|
||||
resourcepack.download.title=資源包下載 - %1s
|
||||
|
||||
reveal.in_explorer=在檔案管理員中查看
|
||||
|
||||
schematics=原理圖
|
||||
schematics.add=添加原理圖
|
||||
schematics.add.failed=添加原理圖失敗
|
||||
schematics.back_to=返回到「%s」
|
||||
schematics.create_directory=建立目錄
|
||||
schematics.create_directory.prompt=請輸入新目錄名稱
|
||||
schematics.create_directory.failed=建立目錄失敗
|
||||
schematics.create_directory.failed.already_exists=目錄已存在
|
||||
schematics.create_directory.failed.empty_name=名稱不能為空
|
||||
schematics.create_directory.failed.invalid_name=名稱中包含非法字元
|
||||
schematics.info.description=描述
|
||||
schematics.info.enclosing_size=框大小
|
||||
schematics.info.name=名稱
|
||||
schematics.info.region_count=區域數量
|
||||
schematics.info.schematic_author=作者
|
||||
schematics.info.time_created=建立時間
|
||||
schematics.info.time_modified=修改時間
|
||||
schematics.info.total_blocks=總方塊數
|
||||
schematics.info.total_volume=總體積
|
||||
schematics.info.version=原理圖版本
|
||||
schematics.manage=原理圖管理
|
||||
schematics.sub_items=%d 個子項
|
||||
|
||||
search=搜尋
|
||||
search.hint.chinese=支援中英文搜尋
|
||||
@@ -971,7 +1014,7 @@ settings.advanced.environment_variables=環境變數
|
||||
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=單位 MiB
|
||||
settings.advanced.jvm=Java 虛擬機設定
|
||||
settings.advanced.jvm_args=Java 虛擬機參數
|
||||
settings.advanced.jvm_args.prompt=\ · 若在「Java 虛擬機參數」中輸入的參數與預設參數相同,則不會加入;\n\
|
||||
@@ -1007,6 +1050,7 @@ settings.advanced.renderer.llvmpipe=軟繪製器 (效能較差,相容性最好
|
||||
settings.advanced.renderer.zink=Vulkan (效能最好,相容性較差)
|
||||
settings.advanced.server_ip=伺服器位址
|
||||
settings.advanced.server_ip.prompt=預設,啟動遊戲後直接進入對應伺服器
|
||||
settings.advanced.unsupported_system_options=不適用於目前系統的選項
|
||||
settings.advanced.use_native_glfw=[僅限 Linux/FreeBSD] 使用系統 GLFW
|
||||
settings.advanced.use_native_openal=[僅限 Linux/FreeBSD] 使用系統 OpenAL
|
||||
settings.advanced.workaround=除錯選項
|
||||
@@ -1061,7 +1105,7 @@ settings.launcher.log=日誌
|
||||
settings.launcher.log.font=日誌字體
|
||||
settings.launcher.proxy=代理
|
||||
settings.launcher.proxy.authentication=身份驗證
|
||||
settings.launcher.proxy.disable=使用系統代理
|
||||
settings.launcher.proxy.default=使用系統代理
|
||||
settings.launcher.proxy.host=IP 位址
|
||||
settings.launcher.proxy.http=HTTP
|
||||
settings.launcher.proxy.none=不使用代理
|
||||
@@ -1075,13 +1119,13 @@ settings.launcher.turn_off_animations=關閉動畫 (重啟後生效)
|
||||
settings.launcher.version_list_source=版本清單來源
|
||||
|
||||
settings.memory=遊戲記憶體
|
||||
settings.memory.allocate.auto=最低分配 %1$.1f GB / 實際分配 %2$.1f GB
|
||||
settings.memory.allocate.auto.exceeded=最低分配 %1$.1f GB / 實際分配 %2$.1f GB (%3$.1f GB 可用)
|
||||
settings.memory.allocate.manual=遊戲分配 %1$.1f GB
|
||||
settings.memory.allocate.manual.exceeded=遊戲分配 %1$.1f GB (%3$.1f GB 可用)
|
||||
settings.memory.allocate.auto=最低分配 %1$.1f GiB / 實際分配 %2$.1f GiB
|
||||
settings.memory.allocate.auto.exceeded=最低分配 %1$.1f GiB / 實際分配 %2$.1f GiB (%3$.1f GiB 可用)
|
||||
settings.memory.allocate.manual=遊戲分配 %1$.1f GiB
|
||||
settings.memory.allocate.manual.exceeded=遊戲分配 %1$.1f GiB (%3$.1f GiB 可用)
|
||||
settings.memory.auto_allocate=自動分配
|
||||
settings.memory.lower_bound=最低分配
|
||||
settings.memory.used_per_total=已使用 %1$.1f GB / 總記憶體 %2$.1f GB
|
||||
settings.memory.used_per_total=已使用 %1$.1f GiB / 總記憶體 %2$.1f GiB
|
||||
settings.physical_memory=實體記憶體大小
|
||||
settings.show_log=查看日誌
|
||||
settings.tabs.installers=自動安裝
|
||||
@@ -1109,12 +1153,12 @@ update.changelog=更新日誌
|
||||
update.channel.dev=開發版
|
||||
update.channel.dev.hint=你正在使用 HMCL 開發版。開發版包含一些未在穩定版中包含的測試性功能,僅用於體驗新功能。開發版功能未受充分驗證,使用起來可能不穩定!\n\
|
||||
\n\
|
||||
如果你遇到了使用問題,可以透過設定中 <a href="hmcl://settings/feedback">回報頁面</a> 提供的管道進行回報。
|
||||
如果你遇到了使用問題,可以透過設定中 <a href="hmcl://settings/feedback">回報頁面</a> 提供的管道進行回報。歡迎關注 Bilibili <a href="https://space.bilibili.com/1445341">@huanghongxun</a> 以關注 HMCL 的重要動態,或關注 <a href="https://space.bilibili.com/20314891">@Glavo</a> 以瞭解 HMCL 的開發進展。
|
||||
update.channel.dev.title=開發版提示
|
||||
update.channel.nightly=預覽版
|
||||
update.channel.nightly.hint=你正在使用 HMCL 預覽版。預覽版更新較為頻繁,包含一些未在穩定版和開發版中包含的測試性功能,僅用於體驗新功能。預覽版功能未受充分驗證,使用起來可能不穩定!\n\
|
||||
\n\
|
||||
如果你遇到了使用問題,可以透過設定中 <a href="hmcl://settings/feedback">回報頁面</a> 提供的管道進行回報。
|
||||
如果你遇到了使用問題,可以透過設定中 <a href="hmcl://settings/feedback">回報頁面</a> 提供的管道進行回報。歡迎關注 Bilibili <a href="https://space.bilibili.com/1445341">@huanghongxun</a> 以關注 HMCL 的重要動態,或關注 <a href="https://space.bilibili.com/20314891">@Glavo</a> 以瞭解 HMCL 的開發進展。
|
||||
update.channel.nightly.title=預覽版提示
|
||||
update.channel.stable=穩定版
|
||||
update.checking=正在檢查更新
|
||||
@@ -1133,8 +1177,8 @@ version.name=遊戲實例名稱
|
||||
version.cannot_read=讀取遊戲版本失敗,無法進行自動安裝
|
||||
version.empty=沒有遊戲版本
|
||||
version.empty.add=進入下載頁安裝遊戲
|
||||
version.empty.launch=沒有可啟動的遊戲,你可以點擊左側邊欄內的下載按鈕安裝遊戲
|
||||
version.empty.hint=沒有已安裝的遊戲,你可以切換其他遊戲目錄,或者點擊此處進入遊戲下載頁面
|
||||
version.empty.launch=沒有可啟動的遊戲。點擊「確定」將進入「下載」頁面。\n你也可以點擊 HMCL 主介面左側的「下載」按鈕安裝遊戲,或在「實例清單」切換遊戲目錄。
|
||||
version.empty.hint=沒有已安裝的遊戲。\n你可以切換其他遊戲目錄,或者點擊此處進入遊戲下載頁面。
|
||||
version.game.old=遠古版
|
||||
version.game.release=正式版
|
||||
version.game.releases=正式版
|
||||
@@ -1170,7 +1214,7 @@ 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.release=https://zh.minecraft.wiki/w/Java%%E7%%89%%88%s?variant=zh-tw
|
||||
wiki.version.game.snapshot=https://zh.minecraft.wiki/w/%s?variant=zh-tw
|
||||
|
||||
wizard.prev=< 上一步
|
||||
|
||||
Loaded 100 of 230 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user