diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 9e7553d9a3..331b5e472b 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -9,7 +9,7 @@ body: value: | 提交前请确认: * 该问题确实是 **HMCL 的错误**,而**不是 Minecraft 非正常退出**,如果你的 Minecraft 非正常退出,请前往 [QQ 群](https://docs.hmcl.net/groups.html)/[Discord 频道](https://discord.gg/jVvC7HfM6U) 中获取帮助。 - * 你的启动器版本是**最新的快照版本**,可以点击 [此处](https://github.com/burningtnt/HMCL-Snapshot-Update/raw/master/datas/HMCL-dev.jar) 下载最新快照版本。 + * 你的启动器版本是**最新的快照版本**,可以点击 [此处](https://zkitefly.github.io/HMCL-Snapshot-Update/) 下载最新快照版本。 如果你的问题并不属于上述两类,你可以选取另一种 Issue 类型,或者直接前往 [QQ 群](https://docs.hmcl.net/groups.html)/[Discord 频道](https://discord.gg/jVvC7HfM6U) 中获取帮助。 diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index fe45a94b22..acc107f824 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -14,11 +14,11 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up JDK 11 + - name: Set up JDK 8 uses: actions/setup-java@v4 with: distribution: 'zulu' - java-version: '11' + java-version: 8 java-package: 'jdk+fx' - name: Build with Gradle run: ./gradlew build --no-daemon diff --git a/HMCL/build.gradle.kts b/HMCL/build.gradle.kts index 25b2687df0..5c42b7814f 100644 --- a/HMCL/build.gradle.kts +++ b/HMCL/build.gradle.kts @@ -189,11 +189,6 @@ tasks.processResources { from(sourceSets["java11"].output) } dependsOn(tasks["java11Classes"]) - - into("assets") { - from(project.layout.buildDirectory.file("openjfx-dependencies.json")) - } - dependsOn(rootProject.tasks["generateOpenJFXDependencies"]) } val makeExecutables = tasks.create("makeExecutables") { @@ -208,6 +203,56 @@ tasks.build { dependsOn(makeExecutables) } +fun parseToolOptions(options: String?): List { + if (options == null) + return listOf() + + val builder = StringBuilder() + val result = mutableListOf() + + var offset = 0 + + loop@ while (offset < options.length) { + val ch = options[offset] + if (Character.isWhitespace(ch)) { + if (builder.isNotEmpty()) { + result += builder.toString() + builder.clear() + } + + while (offset < options.length && Character.isWhitespace(options[offset])) { + offset++ + } + + continue@loop + } + + if (ch == '\'' || ch == '"') { + offset++ + + while (offset < options.length) { + val ch2 = options[offset++] + if (ch2 != ch) { + builder.append(ch2) + } else { + continue@loop + } + } + + throw GradleException("Unmatched quote in $options") + } + + builder.append(ch) + offset++ + } + + if (builder.isNotEmpty()) { + result += builder.toString() + } + + return result +} + tasks.create("run") { dependsOn(tasks.jar) @@ -215,4 +260,11 @@ tasks.create("run") { classpath = files(jarPath) workingDir = rootProject.rootDir + + val vmOptions = parseToolOptions(System.getenv("HMCL_JAVA_OPTS")) + jvmArgs(vmOptions) + + doFirst { + logger.quiet("HMCL_JAVA_OPTS: $vmOptions") + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/Launcher.java b/HMCL/src/main/java/org/jackhuang/hmcl/Launcher.java index 494dda7b3f..4bd9c28385 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/Launcher.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/Launcher.java @@ -222,7 +222,9 @@ public static void main(String[] args) { try { LOG.info("*** " + Metadata.TITLE + " ***"); - LOG.info("Operating System: " + OperatingSystem.SYSTEM_NAME + ' ' + OperatingSystem.SYSTEM_VERSION); + LOG.info("Operating System: " + (OperatingSystem.OS_RELEASE_PRETTY_NAME == null + ? OperatingSystem.SYSTEM_NAME + ' ' + OperatingSystem.SYSTEM_VERSION + : OperatingSystem.OS_RELEASE_PRETTY_NAME + " (" + OperatingSystem.SYSTEM_NAME + ' ' + OperatingSystem.SYSTEM_VERSION + ')')); LOG.info("System Architecture: " + Architecture.SYSTEM_ARCH_NAME); LOG.info("Java Architecture: " + Architecture.CURRENT_ARCH_NAME); LOG.info("Java Version: " + System.getProperty("java.version") + ", " + System.getProperty("java.vendor")); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/Main.java b/HMCL/src/main/java/org/jackhuang/hmcl/Main.java index 96ea379797..ff39601f7b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/Main.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/Main.java @@ -20,10 +20,9 @@ import javafx.application.Platform; import javafx.scene.control.Alert; import org.jackhuang.hmcl.ui.AwtUtils; -import org.jackhuang.hmcl.util.FractureiserDetector; import org.jackhuang.hmcl.util.SelfDependencyPatcher; import org.jackhuang.hmcl.ui.SwingUtils; -import org.jackhuang.hmcl.util.platform.JavaVersion; +import org.jackhuang.hmcl.java.JavaRuntime; import org.jackhuang.hmcl.util.platform.OperatingSystem; import javax.net.ssl.HttpsURLConnection; @@ -61,7 +60,7 @@ public static void main(String[] args) { checkDirectoryPath(); - if (JavaVersion.CURRENT_JAVA.getParsedVersion() < 9) + if (JavaRuntime.CURRENT_VERSION < 9) // This environment check will take ~300ms thread(Main::fixLetsEncrypt, "CA Certificate Check", true); @@ -70,7 +69,6 @@ public static void main(String[] args) { checkJavaFX(); verifyJavaFX(); - detectFractureiser(); Launcher.main(args); } @@ -94,13 +92,6 @@ private static void checkDirectoryPath() { } } - private static void detectFractureiser() { - if (FractureiserDetector.detect()) { - LOG.error("Detected that this computer is infected by fractureiser"); - showErrorAndExit(i18n("fatal.fractureiser")); - } - } - private static void checkJavaFX() { try { SelfDependencyPatcher.patch(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/countly/Countly.java b/HMCL/src/main/java/org/jackhuang/hmcl/countly/Countly.java deleted file mode 100644 index f18b196859..0000000000 --- a/HMCL/src/main/java/org/jackhuang/hmcl/countly/Countly.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Hello Minecraft! Launcher - * Copyright (C) 2020 huangyuhui and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.jackhuang.hmcl.countly; - -import org.jackhuang.hmcl.util.io.HttpRequest; - -import java.io.IOException; -import java.time.ZonedDateTime; -import java.util.Calendar; -import java.util.Locale; - -import static org.jackhuang.hmcl.util.Pair.pair; - -public class Countly { - - private String deviceId; - private String endpoint; - private String serverURL; - - public void sendMetric(String metrics) throws IOException { - HttpRequest.GET(serverURL + endpoint, - pair("begin_session", "1"), - pair("session_id", "1"), - pair("metrics", metrics), - pair("device_id", deviceId), - pair("timestamp", Long.toString(System.currentTimeMillis())), - pair("tz", Integer.toString(getTimezoneOffset())), - pair("hour", Integer.toString(currentHour())), - pair("dow", Integer.toString(currentDayOfWeek())), - pair("app_key", APP_KEY), - pair("sdk_name", "java-native"), - pair("sdk_version", "20.11.1")) - .getString(); - } - - private static int getTimezoneOffset() { - return ZonedDateTime.now().getOffset().getTotalSeconds() / 60; - } - - private static String getLocale() { - final Locale locale = Locale.getDefault(); - return locale.getLanguage() + "_" + locale.getCountry(); - } - - private static int currentHour() { - return Calendar.getInstance().get(Calendar.HOUR_OF_DAY); - } - - private int currentDayOfWeek() { - int day = Calendar.getInstance().get(Calendar.DAY_OF_WEEK); - switch (day) { - case Calendar.SUNDAY: - return 0; - case Calendar.MONDAY: - return 1; - case Calendar.TUESDAY: - return 2; - case Calendar.WEDNESDAY: - return 3; - case Calendar.THURSDAY: - return 4; - case Calendar.FRIDAY: - return 5; - case Calendar.SATURDAY: - return 6; - } - return 0; - } - - private static final String APP_KEY = ""; -} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/countly/CrashReport.java b/HMCL/src/main/java/org/jackhuang/hmcl/countly/CrashReport.java index a18d405f7a..6c3847e587 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/countly/CrashReport.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/countly/CrashReport.java @@ -5,14 +5,8 @@ import org.jackhuang.hmcl.util.platform.Architecture; import org.jackhuang.hmcl.util.platform.OperatingSystem; -import java.io.File; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; -import java.util.Map; - -import static org.jackhuang.hmcl.util.Lang.mapOf; -import static org.jackhuang.hmcl.util.Pair.pair; -import static org.jackhuang.hmcl.util.logging.Logger.LOG; public class CrashReport { @@ -20,24 +14,16 @@ public class CrashReport { private final Throwable throwable; private final String stackTrace; - private boolean nonFatal; - public CrashReport(Thread thread, Throwable throwable) { this.thread = thread; this.throwable = throwable; stackTrace = StringUtils.getStackTrace(throwable); - nonFatal = false; } public Throwable getThrowable() { return this.throwable; } - public CrashReport setNonFatal() { - nonFatal = true; - return this; - } - public boolean shouldBeReport() { if (!stackTrace.contains("org.jackhuang")) return false; @@ -48,23 +34,6 @@ public boolean shouldBeReport() { return true; } - public Map getMetrics(long runningTime) { - return mapOf( - pair("_run", runningTime), - pair("_app_version", Metadata.VERSION), - pair("_os", OperatingSystem.SYSTEM_NAME), - pair("_os_version", OperatingSystem.SYSTEM_VERSION), - pair("_disk_current", getDiskAvailable()), - pair("_disk_total", getDiskTotal()), - pair("_ram_current", getMemoryAvailable()), - pair("_ram_total", Runtime.getRuntime().maxMemory() / BYTES_IN_MB), - pair("_error", stackTrace), - pair("_logs", LOG.getLogs()), - pair("_name", throwable.getLocalizedMessage()), - pair("_nonfatal", nonFatal) - ); - } - public String getDisplayText() { return "---- Hello Minecraft! Crash Report ----\n" + " Version: " + Metadata.VERSION + "\n" + @@ -82,29 +51,4 @@ public String getDisplayText() { " JVM Total Memory: " + Runtime.getRuntime().totalMemory() + "\n" + " JVM Free Memory: " + Runtime.getRuntime().freeMemory() + "\n"; } - - private static final Long BYTES_IN_MB = 1024L * 1024; - - private static long getMemoryAvailable() { - Long total = Runtime.getRuntime().totalMemory(); - Long availMem = Runtime.getRuntime().freeMemory(); - return (total - availMem) / BYTES_IN_MB; - } - - private static long getDiskAvailable() { - long total = 0, free = 0; - for (File f : File.listRoots()) { - total += f.getTotalSpace(); - free += f.getUsableSpace(); - } - return (total - free) / BYTES_IN_MB; - } - - private static long getDiskTotal() { - long total = 0; - for (File f : File.listRoots()) { - total += f.getTotalSpace(); - } - return total / BYTES_IN_MB; - } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java index 4cbff8d75e..db71cb536d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java @@ -37,7 +37,7 @@ import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.io.FileUtils; -import org.jackhuang.hmcl.util.platform.JavaVersion; +import org.jackhuang.hmcl.java.JavaRuntime; import org.jackhuang.hmcl.util.platform.OperatingSystem; import org.jackhuang.hmcl.util.versioning.VersionNumber; import org.jetbrains.annotations.Nullable; @@ -383,7 +383,7 @@ public void globalizeVersionSetting(String id) { vs.setUsesGlobal(true); } - public LaunchOptions getLaunchOptions(String version, JavaVersion javaVersion, File gameDir, List javaAgents, boolean makeLaunchScript) { + public LaunchOptions getLaunchOptions(String version, JavaRuntime javaVersion, File gameDir, List javaAgents, boolean makeLaunchScript) { VersionSetting vs = getVersionSetting(version); LaunchOptions.Builder builder = new LaunchOptions.Builder() @@ -396,7 +396,7 @@ public LaunchOptions getLaunchOptions(String version, JavaVersion javaVersion, F .setOverrideJavaArguments(StringUtils.tokenize(vs.getJavaArgs())) .setMaxMemory(vs.isNoJVMArgs() && vs.isAutoMemory() ? null : (int)(getAllocatedMemory( vs.getMaxMemory() * 1024L * 1024L, - OperatingSystem.getPhysicalMemoryStatus().orElse(OperatingSystem.PhysicalMemoryStatus.INVALID).getAvailable(), + OperatingSystem.getPhysicalMemoryStatus().getAvailable(), vs.isAutoMemory() ) / 1024 / 1024)) .setMinMemory(vs.getMinMemory()) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackInstallTask.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackInstallTask.java index 19c8147ad1..494e9b6490 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackInstallTask.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackInstallTask.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.game; import com.google.gson.JsonParseException; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.LibraryAnalyzer; import org.jackhuang.hmcl.mod.MinecraftInstanceTask; @@ -67,8 +66,7 @@ public HMCLModpackInstallTask(Profile profile, File zipFile, Modpack modpack, St ModpackConfiguration config = null; try { if (json.exists()) { - config = JsonUtils.GSON.fromJson(FileUtils.readText(json), new TypeToken>() { - }.getType()); + config = JsonUtils.GSON.fromJson(FileUtils.readText(json), ModpackConfiguration.typeOf(Modpack.class)); if (!HMCLModpackProvider.INSTANCE.getName().equals(config.getType())) throw new IllegalArgumentException("Version " + name + " is not a HMCL modpack. Cannot update this version."); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java index 960a983660..61725887c7 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.game; import com.jfoenix.controls.JFXButton; -import javafx.application.Platform; import javafx.stage.Stage; import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.auth.*; @@ -28,15 +27,13 @@ import org.jackhuang.hmcl.download.LibraryAnalyzer; import org.jackhuang.hmcl.download.MaintainTask; import org.jackhuang.hmcl.download.game.*; -import org.jackhuang.hmcl.download.java.JavaRepository; +import org.jackhuang.hmcl.java.JavaManager; +import org.jackhuang.hmcl.java.JavaRuntime; import org.jackhuang.hmcl.launch.*; import org.jackhuang.hmcl.mod.ModpackCompletionException; import org.jackhuang.hmcl.mod.ModpackConfiguration; import org.jackhuang.hmcl.mod.ModpackProvider; -import org.jackhuang.hmcl.setting.DownloadProviders; -import org.jackhuang.hmcl.setting.LauncherVisibility; -import org.jackhuang.hmcl.setting.Profile; -import org.jackhuang.hmcl.setting.VersionSetting; +import org.jackhuang.hmcl.setting.*; import org.jackhuang.hmcl.task.*; import org.jackhuang.hmcl.ui.*; import org.jackhuang.hmcl.ui.construct.*; @@ -55,19 +52,17 @@ import java.net.URL; import java.nio.file.AccessDeniedException; import java.util.*; -import java.util.concurrent.CancellationException; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.CountDownLatch; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; -import static org.jackhuang.hmcl.setting.ConfigHolder.config; +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.Lang.resolveException; import static org.jackhuang.hmcl.util.logging.Logger.LOG; -import static org.jackhuang.hmcl.util.Pair.pair; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; +import static org.jackhuang.hmcl.util.platform.Platform.*; public final class LauncherHelper { @@ -132,12 +127,12 @@ private void launch0() { CountDownLatch launchingLatch = new CountDownLatch(1); List javaAgents = new ArrayList<>(0); - AtomicReference javaVersionRef = new AtomicReference<>(); + AtomicReference javaVersionRef = new AtomicReference<>(); TaskExecutor executor = checkGameState(profile, setting, version.get()) - .thenComposeAsync(javaVersion -> { - javaVersionRef.set(Objects.requireNonNull(javaVersion)); - version.set(NativePatcher.patchNative(version.get(), gameVersion.orElse(null), javaVersion, setting)); + .thenComposeAsync(java -> { + javaVersionRef.set(Objects.requireNonNull(java)); + version.set(NativePatcher.patchNative(version.get(), gameVersion.orElse(null), java, setting)); if (setting.isNotCheckGame()) return null; return Task.allOf( @@ -155,7 +150,7 @@ private void launch0() { Task.composeAsync(() -> { Renderer renderer = setting.getRenderer(); if (renderer != Renderer.DEFAULT && OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) { - Library lib = NativePatcher.getMesaLoader(javaVersion, renderer); + Library lib = NativePatcher.getMesaLoader(java, renderer); if (lib == null) return null; File file = dependencyManager.getGameRepository().getLibraryFile(version.get(), lib); @@ -179,9 +174,7 @@ private void launch0() { }) ); }).withStage("launch.state.dependencies") - .thenComposeAsync(() -> { - return gameVersion.map(s -> new GameVerificationFixTask(dependencyManager, s, version.get())).orElse(null); - }) + .thenComposeAsync(() -> gameVersion.map(s -> new GameVerificationFixTask(dependencyManager, s, version.get())).orElse(null)) .thenComposeAsync(() -> logIn(account).withStage("launch.state.logging_in")) .thenComposeAsync(authInfo -> Task.supplyAsync(() -> { LaunchOptions launchOptions = repository.getLaunchOptions(selectedVersion, javaVersionRef.get(), profile.getGameDir(), javaAgents, scriptFile != null); @@ -214,7 +207,7 @@ private void launch0() { it.fireEvent(new DialogCloseEvent()); })); } else { - Platform.runLater(() -> { + runLater(() -> { launchingStepsPane.fireEvent(new DialogCloseEvent()); Controllers.dialog(i18n("version.launch_script.success", scriptFile.getAbsolutePath())); }); @@ -234,14 +227,14 @@ private void launch0() { @Override public void onStop(boolean success, TaskExecutor executor) { - Platform.runLater(() -> { + runLater(() -> { // Check if the application has stopped // because onStop will be invoked if tasks fail when the executor service shut down. if (!Controllers.isStopped()) { launchingStepsPane.fireEvent(new DialogCloseEvent()); if (!success) { Exception ex = executor.getException(); - if (!(ex instanceof CancellationException)) { + if (ex != null && !(ex instanceof CancellationException)) { String message; if (ex instanceof ModpackCompletionException) { if (ex.getCause() instanceof FileNotFoundException) @@ -251,9 +244,9 @@ public void onStop(boolean success, TaskExecutor executor) { } else if (ex instanceof PermissionException) { message = i18n("launch.failed.executable_permission"); } else if (ex instanceof ProcessCreationException) { - message = i18n("launch.failed.creating_process") + ex.getLocalizedMessage(); + message = i18n("launch.failed.creating_process") + "\n" + ex.getLocalizedMessage(); } else if (ex instanceof NotDecompressingNativesException) { - message = i18n("launch.failed.decompressing_natives") + ex.getLocalizedMessage(); + message = i18n("launch.failed.decompressing_natives") + "\n" + ex.getLocalizedMessage(); } else if (ex instanceof LibraryDownloadException) { message = i18n("launch.failed.download_library", ((LibraryDownloadException) ex).getLibrary().getName()) + "\n"; if (ex.getCause() instanceof ResponseCodeException) { @@ -331,178 +324,181 @@ public void onStop(boolean success, TaskExecutor executor) { executor.start(); } - private static Task checkGameState(Profile profile, VersionSetting setting, Version version) { + private static Task checkGameState(Profile profile, VersionSetting setting, Version version) { LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version, profile.getRepository().getGameVersion(version).orElse(null)); GameVersionNumber gameVersion = GameVersionNumber.asGameVersion(analyzer.getVersion(LibraryAnalyzer.LibraryType.MINECRAFT)); + Task getJavaTask = Task.supplyAsync(() -> { + try { + return setting.getJava(gameVersion, version); + } catch (InterruptedException e) { + throw new CancellationException(); + } + }); + Task task; if (setting.isNotCheckJVM()) { - return Task.composeAsync(() -> setting.getJavaVersion(gameVersion, version)) - .thenApplyAsync(javaVersion -> Optional.ofNullable(javaVersion).orElseGet(JavaVersion::fromCurrentEnvironment)) - .withStage("launch.state.java"); - } - - return Task.composeAsync(() -> { - return setting.getJavaVersion(gameVersion, version); - }).thenComposeAsync(Schedulers.javafx(), javaVersion -> { - // Reset invalid java version - if (javaVersion == null) { - CompletableFuture future = new CompletableFuture<>(); - Runnable continueAction = () -> future.complete(JavaVersion.fromCurrentEnvironment()); - - if (setting.isJavaAutoSelected()) { - GameJavaVersion targetJavaVersion = null; - - if (org.jackhuang.hmcl.util.platform.Platform.isCompatibleWithX86Java()) { - JavaVersionConstraint.VersionRanges range = JavaVersionConstraint.findSuitableJavaVersionRange(gameVersion, version); - if (range.getMandatory().contains(VersionNumber.asVersion("21.0.3"))) { - targetJavaVersion = GameJavaVersion.JAVA_21; - } else if (range.getMandatory().contains(VersionNumber.asVersion("17.0.1"))) { - targetJavaVersion = GameJavaVersion.JAVA_17; - } else if (range.getMandatory().contains(VersionNumber.asVersion("16.0.1"))) { - targetJavaVersion = GameJavaVersion.JAVA_16; - } else { - String java8Version; - - if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) { - java8Version = "1.8.0_51"; - } else if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX) { - java8Version = "1.8.0_202"; - } else if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX) { - java8Version = "1.8.0_74"; - } else { - java8Version = null; - } + task = getJavaTask.thenApplyAsync(java -> Lang.requireNonNullElse(java, JavaRuntime.getDefault())); + } else if (setting.getJavaVersionType() == JavaVersionType.AUTO || setting.getJavaVersionType() == JavaVersionType.VERSION) { + task = getJavaTask.thenComposeAsync(Schedulers.javafx(), java -> { + if (java != null) { + return Task.completed(java); + } - if (java8Version != null && range.getMandatory().contains(VersionNumber.asVersion(java8Version))) - targetJavaVersion = GameJavaVersion.JAVA_8; - else - targetJavaVersion = null; + // Reset invalid java version + CompletableFuture future = new CompletableFuture<>(); + Task result = Task.fromCompletableFuture(future); + Runnable breakAction = () -> future.completeExceptionally(new CancellationException("No accepted java")); + List supportedVersions = GameJavaVersion.getSupportedVersions(SYSTEM_PLATFORM); + + GameJavaVersion targetJavaVersion = null; + if (setting.getJavaVersionType() == JavaVersionType.VERSION) { + try { + int targetJavaVersionMajor = Integer.parseInt(setting.getJavaVersion()); + GameJavaVersion minimumJavaVersion = GameJavaVersion.getMinimumJavaVersion(gameVersion); + + if (minimumJavaVersion != null && targetJavaVersionMajor < minimumJavaVersion.getMajorVersion()) { + Controllers.dialog( + i18n("launch.failed.java_version_too_low"), + i18n("message.error"), + MessageType.ERROR, + breakAction + ); + return result; } - } - if (targetJavaVersion == null) { - Controllers.confirm(i18n("launch.failed.no_accepted_java"), i18n("message.warning"), MessageType.WARNING, continueAction, () -> { - future.completeExceptionally(new CancellationException("No accepted java")); - }); - } else { - downloadJava(gameVersion.toString(), targetJavaVersion, profile) - .thenAcceptAsync(downloadedJavaVersion -> { - future.complete(downloadedJavaVersion); - }) - .exceptionally(throwable -> { - LOG.warning("Failed to download java", throwable); - Controllers.confirm(i18n("launch.failed.no_accepted_java"), i18n("message.warning"), MessageType.WARNING, continueAction, () -> { - future.completeExceptionally(new CancellationException("No accepted java")); - }); - return null; - }); + targetJavaVersion = GameJavaVersion.get(targetJavaVersionMajor); + } catch (NumberFormatException ignored) { } + } else + targetJavaVersion = version.getJavaVersion(); + + if (targetJavaVersion != null && supportedVersions.contains(targetJavaVersion)) { + downloadJava(targetJavaVersion, profile) + .whenCompleteAsync((downloadedJava, exception) -> { + if (exception == null) { + future.complete(downloadedJava); + } else { + LOG.warning("Failed to download java", exception); + Controllers.confirm(i18n("launch.failed.no_accepted_java"), i18n("message.warning"), MessageType.WARNING, + () -> future.complete(JavaRuntime.getDefault()), + breakAction); + } + }, Schedulers.javafx()); } else { - Controllers.dialog(i18n("launch.wrong_javadir"), i18n("message.warning"), MessageType.WARNING, continueAction); - - setting.setJava(null); - setting.setDefaultJavaPath(null); - setting.setJavaVersion(JavaVersion.fromCurrentEnvironment()); + Controllers.confirm(i18n("launch.failed.no_accepted_java"), i18n("message.warning"), MessageType.WARNING, + () -> future.complete(JavaRuntime.getDefault()), + breakAction); } - return Task.fromCompletableFuture(future); - } else { - return Task.completed(javaVersion); - } - }).thenComposeAsync(javaVersion -> { - return Task.allOf(Task.completed(javaVersion), Task.supplyAsync(() -> JavaVersionConstraint.findSuitableJavaVersion(gameVersion, version))); - }).thenComposeAsync(Schedulers.javafx(), javaVersions -> { - JavaVersion javaVersion = (JavaVersion) javaVersions.get(0); - JavaVersion suggestedJavaVersion = (JavaVersion) javaVersions.get(1); - if (setting.isJavaAutoSelected()) return Task.completed(javaVersion); - - JavaVersionConstraint violatedMandatoryConstraint = null; - List violatedSuggestedConstraints = null; - - for (JavaVersionConstraint constraint : JavaVersionConstraint.ALL) { - if (constraint.appliesToVersion(gameVersion, version, javaVersion, analyzer)) { - if (!constraint.checkJava(gameVersion, version, javaVersion)) { - if (constraint.getType() == JavaVersionConstraint.RULE_MANDATORY) { - violatedMandatoryConstraint = constraint; - } else if (constraint.getType() == JavaVersionConstraint.RULE_SUGGESTED) { - if (violatedSuggestedConstraints == null) - violatedSuggestedConstraints = new ArrayList<>(1); - violatedSuggestedConstraints.add(constraint); + return result; + }); + } else { + task = getJavaTask.thenComposeAsync(java -> { + Set violatedMandatoryConstraints = EnumSet.noneOf(JavaVersionConstraint.class); + Set violatedSuggestedConstraints = EnumSet.noneOf(JavaVersionConstraint.class); + + if (java != null) { + for (JavaVersionConstraint constraint : JavaVersionConstraint.ALL) { + if (constraint.appliesToVersion(gameVersion, version, java, analyzer)) { + if (!constraint.checkJava(gameVersion, version, java)) { + if (constraint.isMandatory()) { + violatedMandatoryConstraints.add(constraint); + } else { + violatedSuggestedConstraints.add(constraint); + } + } } } - } - } - CompletableFuture future = new CompletableFuture<>(); - Runnable breakAction = () -> future.completeExceptionally(new CancellationException("Launch operation was cancelled by user")); + CompletableFuture future = new CompletableFuture<>(); + Task result = Task.fromCompletableFuture(future); + Runnable breakAction = () -> future.completeExceptionally(new CancellationException("Launch operation was cancelled by user")); + + if (java == null || !violatedMandatoryConstraints.isEmpty()) { + JavaRuntime suggestedJava = JavaManager.findSuitableJava(gameVersion, version); + if (suggestedJava != null) { + FXUtils.runInFX(() -> { + Controllers.confirm(i18n("launch.advice.java.auto"), i18n("message.warning"), () -> { + setting.setJavaAutoSelected(); + future.complete(suggestedJava); + }, breakAction); + }); + return result; + } else if (java == null) { + FXUtils.runInFX(() -> Controllers.dialog( + i18n("launch.invalid_java"), + i18n("message.error"), + MessageType.ERROR, + breakAction + )); + return result; + } else { + GameJavaVersion gameJavaVersion; + if (violatedMandatoryConstraints.contains(JavaVersionConstraint.GAME_JSON)) + gameJavaVersion = version.getJavaVersion(); + else if (violatedMandatoryConstraints.contains(JavaVersionConstraint.VANILLA)) + gameJavaVersion = GameJavaVersion.getMinimumJavaVersion(gameVersion); + else + gameJavaVersion = null; - if (violatedMandatoryConstraint != null) { - if (suggestedJavaVersion != null) { - Controllers.confirm(i18n("launch.advice.java.auto"), i18n("message.warning"), () -> { - setting.setJavaAutoSelected(); - future.complete(suggestedJavaVersion); - }, breakAction); - return Task.fromCompletableFuture(future); - } else { - switch (violatedMandatoryConstraint) { - case GAME_JSON: - downloadJava(gameVersion.toString(), version.getJavaVersion(), profile) - .thenAcceptAsync(downloadedJavaVersion -> { - setting.setJavaVersion(downloadedJavaVersion); - future.complete(downloadedJavaVersion); - }, Schedulers.javafx()) - .whenCompleteAsync((result, throwable) -> { - LOG.warning("Failed to download java", throwable); - breakAction.run(); - }, Schedulers.javafx()); - return Task.fromCompletableFuture(future); - case VANILLA_JAVA_16: - Controllers.confirm(i18n("launch.advice.require_newer_java_version", gameVersion.toString(), 16), i18n("message.warning"), - () -> FXUtils.openLink(OPENJDK_DOWNLOAD_LINK), null); - breakAction.run(); - return Task.fromCompletableFuture(future); - case VANILLA_JAVA_17: - Controllers.confirm(i18n("launch.advice.require_newer_java_version", gameVersion.toString(), 17), i18n("message.warning"), - () -> FXUtils.openLink(OPENJDK_DOWNLOAD_LINK), null); - breakAction.run(); - return Task.fromCompletableFuture(future); - case VANILLA_JAVA_21: - Controllers.confirm(i18n("launch.advice.require_newer_java_version", gameVersion.toString(), 21), i18n("message.warning"), - () -> FXUtils.openLink(OPENJDK_DOWNLOAD_LINK), null); - breakAction.run(); - return Task.fromCompletableFuture(future); - case VANILLA_JAVA_8: - Controllers.dialog(i18n("launch.advice.java8_1_13"), i18n("message.error"), MessageType.ERROR, breakAction); - return Task.fromCompletableFuture(future); - case VANILLA_LINUX_JAVA_8: + if (gameJavaVersion != null) { + FXUtils.runInFX(() -> downloadJava(gameJavaVersion, profile).whenCompleteAsync((downloadedJava, throwable) -> { + if (throwable == null) { + setting.setJavaAutoSelected(); + future.complete(downloadedJava); + } else { + LOG.warning("Failed to download java", throwable); + breakAction.run(); + } + }, Schedulers.javafx())); + return result; + } + + if (violatedMandatoryConstraints.contains(JavaVersionConstraint.VANILLA_LINUX_JAVA_8)) { if (setting.getNativesDirType() == NativesDirectoryType.VERSION_FOLDER) { - Controllers.dialog(i18n("launch.advice.vanilla_linux_java_8"), i18n("message.error"), MessageType.ERROR, breakAction); - return Task.fromCompletableFuture(future); + FXUtils.runInFX(() -> Controllers.dialog(i18n("launch.advice.vanilla_linux_java_8"), i18n("message.error"), MessageType.ERROR, breakAction)); + return result; } else { - break; + violatedMandatoryConstraints.remove(JavaVersionConstraint.VANILLA_LINUX_JAVA_8); } - case LAUNCH_WRAPPER: - Controllers.dialog(i18n("launch.advice.java9") + "\n" + i18n("launch.advice.uncorrected"), i18n("message.error"), MessageType.ERROR, breakAction); - return Task.fromCompletableFuture(future); + } + + if (violatedMandatoryConstraints.contains(JavaVersionConstraint.LAUNCH_WRAPPER)) { + FXUtils.runInFX(() -> Controllers.dialog( + i18n("launch.advice.java9") + "\n" + i18n("launch.advice.uncorrected"), + i18n("message.error"), + MessageType.ERROR, + breakAction + )); + return result; + } + + if (!violatedMandatoryConstraints.isEmpty()) { + FXUtils.runInFX(() -> Controllers.dialog( + i18n("launch.advice.unknown") + "\n" + violatedMandatoryConstraints, + i18n("message.error"), + MessageType.ERROR, + breakAction + )); + return result; + } } } - } - List suggestions = new ArrayList<>(0); + List suggestions = new ArrayList<>(); - if (Architecture.SYSTEM_ARCH == Architecture.X86_64 && javaVersion.getPlatform().getArchitecture() == Architecture.X86) { - suggestions.add(i18n("launch.advice.different_platform")); - } + if (Architecture.SYSTEM_ARCH == Architecture.X86_64 && java.getPlatform().getArchitecture() == Architecture.X86) { + suggestions.add(i18n("launch.advice.different_platform")); + } - // 32-bit JVM cannot make use of too much memory. - if (javaVersion.getBits() == Bits.BIT_32 && setting.getMaxMemory() > 1.5 * 1024) { - // 1.5 * 1024 is an inaccurate number. - // Actual memory limit depends on operating system and memory. - suggestions.add(i18n("launch.advice.too_large_memory_for_32bit")); - } + // 32-bit JVM cannot make use of too much memory. + if (java.getBits() == Bits.BIT_32 && setting.getMaxMemory() > 1.5 * 1024) { + // 1.5 * 1024 is an inaccurate number. + // Actual memory limit depends on operating system and memory. + suggestions.add(i18n("launch.advice.too_large_memory_for_32bit")); + } - if (violatedSuggestedConstraints != null) { for (JavaVersionConstraint violatedSuggestedConstraint : violatedSuggestedConstraints) { switch (violatedSuggestedConstraint) { case MODDED_JAVA_7: @@ -510,7 +506,7 @@ private static Task checkGameState(Profile profile, VersionSetting break; case MODDED_JAVA_8: // Minecraft>=1.7.10+Forge accepts Java 8 - if (javaVersion.getParsedVersion() < 8) + if (java.getParsedVersion() < 8) suggestions.add(i18n("launch.advice.newer_java")); else suggestions.add(i18n("launch.advice.modded_java", 8, gameVersion)); @@ -534,117 +530,91 @@ private static Task checkGameState(Profile profile, VersionSetting break; case VANILLA_X86: if (setting.getNativesDirType() == NativesDirectoryType.VERSION_FOLDER - && org.jackhuang.hmcl.util.platform.Platform.isCompatibleWithX86Java()) { + && isCompatibleWithX86Java()) { suggestions.add(i18n("launch.advice.vanilla_x86.translation")); } break; + default: + suggestions.add(violatedSuggestedConstraint.name()); } } - } - // 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)); - } + // 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)); + } - VersionNumber forgeVersion = version.getLibraries().stream() - .filter(it -> it.is("net.minecraftforge", "forge")) - .findFirst() - .map(library -> VersionNumber.asVersion(library.getVersion())) - .orElse(null); - - // Forge 2760~2773 will crash game with LiteLoader. - boolean hasForge2760 = forgeVersion != null && (forgeVersion.compareTo("1.12.2-14.23.5.2760") >= 0) && (forgeVersion.compareTo("1.12.2-14.23.5.2773") < 0); - boolean hasLiteLoader = version.getLibraries().stream().anyMatch(it -> it.is("com.mumfrey", "liteloader")); - if (hasForge2760 && hasLiteLoader && gameVersion.compareTo("1.12.2") == 0) { - suggestions.add(i18n("launch.advice.forge2760_liteloader")); - } + VersionNumber forgeVersion = analyzer.getVersion(LibraryAnalyzer.LibraryType.FORGE) + .map(VersionNumber::asVersion) + .orElse(null); - // OptiFine 1.14.4 is not compatible with Forge 28.2.2 and later versions. - boolean hasForge28_2_2 = forgeVersion != null && (forgeVersion.compareTo("1.14.4-28.2.2") >= 0); - boolean hasOptiFine = version.getLibraries().stream().anyMatch(it -> it.is("optifine", "OptiFine")); - if (hasForge28_2_2 && hasOptiFine && gameVersion.compareTo("1.14.4") == 0) { - suggestions.add(i18n("launch.advice.forge28_2_2_optifine")); - } + // Forge 2760~2773 will crash game with LiteLoader. + boolean hasForge2760 = forgeVersion != null && (forgeVersion.compareTo("1.12.2-14.23.5.2760") >= 0) && (forgeVersion.compareTo("1.12.2-14.23.5.2773") < 0); + boolean hasLiteLoader = version.getLibraries().stream().anyMatch(it -> it.is("com.mumfrey", "liteloader")); + if (hasForge2760 && hasLiteLoader && gameVersion.compareTo("1.12.2") == 0) { + suggestions.add(i18n("launch.advice.forge2760_liteloader")); + } - if (suggestions.isEmpty()) { - if (!future.isDone()) { - future.complete(javaVersion); + // OptiFine 1.14.4 is not compatible with Forge 28.2.2 and later versions. + boolean hasForge28_2_2 = forgeVersion != null && (forgeVersion.compareTo("1.14.4-28.2.2") >= 0); + boolean hasOptiFine = version.getLibraries().stream().anyMatch(it -> it.is("optifine", "OptiFine")); + if (hasForge28_2_2 && hasOptiFine && gameVersion.compareTo("1.14.4") == 0) { + suggestions.add(i18n("launch.advice.forge28_2_2_optifine")); } - } else { - String message; - if (suggestions.size() == 1) { - message = i18n("launch.advice", suggestions.get(0)); + + if (suggestions.isEmpty()) { + if (!future.isDone()) { + future.complete(java); + } } else { - message = i18n("launch.advice.multi", suggestions.stream().map(it -> "→ " + it).collect(Collectors.joining("\n"))); + String message; + if (suggestions.size() == 1) { + message = i18n("launch.advice", suggestions.get(0)); + } else { + message = i18n("launch.advice.multi", suggestions.stream().map(it -> "→ " + it).collect(Collectors.joining("\n"))); + } + + FXUtils.runInFX(() -> Controllers.confirm( + message, + i18n("message.warning"), + MessageType.WARNING, + () -> future.complete(java), + breakAction)); } - Controllers.confirm(message, i18n("message.warning"), MessageType.WARNING, () -> future.complete(javaVersion), breakAction); - } + return result; + }); + } - return Task.fromCompletableFuture(future); - }).withStage("launch.state.java"); + return task.withStage("launch.state.java"); } - private static CompletableFuture downloadJava(String gameVersion, GameJavaVersion javaVersion, Profile profile) { - CompletableFuture future = new CompletableFuture<>(); - - JFXHyperlink link = new JFXHyperlink(i18n("download.external_link")); - link.setOnAction(e -> { - if (javaVersion.getMajorVersion() == JavaVersion.JAVA_8) { - FXUtils.openLink(ORACLEJDK_DOWNLOAD_LINK); - } else { - FXUtils.openLink(OPENJDK_DOWNLOAD_LINK); - } - future.completeExceptionally(new CancellationException()); - }); - + private static CompletableFuture downloadJava(GameJavaVersion javaVersion, Profile profile) { + CompletableFuture future = new CompletableFuture<>(); Controllers.dialog(new MessageDialogPane.Builder( - i18n("launch.advice.require_newer_java_version", - gameVersion, - javaVersion.getMajorVersion()), + i18n("launch.advice.require_newer_java_version", javaVersion.getMajorVersion()), i18n("message.warning"), MessageType.QUESTION) - .addAction(link) .yesOrNo(() -> { - downloadJavaImpl(javaVersion, profile.getDependency().getDownloadProvider()) - .thenAcceptAsync(future::complete) - .exceptionally(throwable -> { - Throwable resolvedException = resolveException(throwable); - LOG.warning("Failed to download java", throwable); - if (!(resolvedException instanceof CancellationException)) { - Controllers.dialog(DownloadProviders.localizeErrorMessage(resolvedException), i18n("install.failed")); + DownloadProvider downloadProvider = profile.getDependency().getDownloadProvider(); + Controllers.taskDialog(JavaManager.getDownloadJavaTask(downloadProvider, SYSTEM_PLATFORM, javaVersion) + .whenComplete(Schedulers.javafx(), (result, exception) -> { + if (exception == null) { + future.complete(result); + } else { + Throwable resolvedException = resolveException(exception); + LOG.warning("Failed to download java", exception); + if (!(resolvedException instanceof CancellationException)) { + Controllers.dialog(DownloadProviders.localizeErrorMessage(resolvedException), i18n("install.failed")); + } + future.completeExceptionally(new CancellationException()); } - future.completeExceptionally(new CancellationException()); - return null; - }); + }), i18n("download.java"), new TaskCancellationAction(() -> future.completeExceptionally(new CancellationException()))); }, () -> future.completeExceptionally(new CancellationException())).build()); return future; } - /** - * Directly start java downloading. - * - * @param javaVersion target Java version - * @param downloadProvider download provider - * @return JavaVersion, null if we failed to download java, failed if an error occurred when downloading. - */ - private static CompletableFuture downloadJavaImpl(GameJavaVersion javaVersion, DownloadProvider downloadProvider) { - CompletableFuture future = new CompletableFuture<>(); - - Controllers.taskDialog(JavaRepository.downloadJava(javaVersion, downloadProvider) - .whenComplete(Schedulers.javafx(), (downloadedJava, exception) -> { - if (exception != null) { - future.completeExceptionally(exception); - } else { - future.complete(downloadedJava); - } - }), i18n("download.java"), TaskCancellationAction.NORMAL); - - return future; - } - private static Task logIn(Account account) { return Task.composeAsync(() -> { try { @@ -685,7 +655,7 @@ private static Task logIn(Account account) { private void checkExit() { switch (launcherVisibility) { case HIDE_AND_REOPEN: - Platform.runLater(() -> { + runLater(() -> { Optional.ofNullable(Controllers.getStage()) .ifPresent(Stage::show); }); @@ -696,9 +666,9 @@ private void checkExit() { case CLOSE: throw new Error("Never get to here"); case HIDE: - Platform.runLater(() -> { + runLater(() -> { // Shut down the platform when user closed log window. - Platform.setImplicitExit(true); + setImplicitExit(true); // If we use Launcher.stop(), log window will be halt immediately. Launcher.stopWithoutPlatform(); }); @@ -717,14 +687,14 @@ private final class HMCLProcessListener implements ProcessListener { private final Version version; private final LaunchOptions launchOptions; private ManagedProcess process; - private boolean lwjgl; + private volatile boolean lwjgl; private LogWindow logWindow; private final boolean detectWindow; - private final ArrayDeque logs; - private final ArrayDeque levels; - private final CountDownLatch logWindowLatch = new CountDownLatch(1); + private final CircularArrayList logs; private final CountDownLatch launchingLatch; private final String forbiddenAccessToken; + private Thread submitLogThread; + private LinkedBlockingQueue logBuffer; public HMCLProcessListener(HMCLGameRepository repository, Version version, AuthInfo authInfo, LaunchOptions launchOptions, CountDownLatch launchingLatch, boolean detectWindow) { this.repository = repository; @@ -733,10 +703,7 @@ public HMCLProcessListener(HMCLGameRepository repository, Version version, AuthI this.launchingLatch = launchingLatch; this.detectWindow = detectWindow; this.forbiddenAccessToken = authInfo != null ? authInfo.getAccessToken() : null; - - final int numLogs = config().getLogLines() + 1; - this.logs = new ArrayDeque<>(numLogs); - this.levels = new ArrayDeque<>(numLogs); + this.logs = new CircularArrayList<>(Log.getLogLines() + 1); } @Override @@ -752,18 +719,66 @@ public void setProcess(ManagedProcess process) { LOG.info("Process ClassPath: " + classpath); } - if (showLogs) - Platform.runLater(() -> { - logWindow = new LogWindow(process); - logWindow.showNormal(); + if (showLogs) { + CountDownLatch logWindowLatch = new CountDownLatch(1); + runLater(() -> { + logWindow = new LogWindow(process, logs); + logWindow.show(); logWindowLatch.countDown(); }); + + logBuffer = new LinkedBlockingQueue<>(); + submitLogThread = Lang.thread(new Runnable() { + private final ArrayList currentLogs = new ArrayList<>(); + private final Semaphore semaphore = new Semaphore(0); + + private void submitLogs() { + if (currentLogs.size() == 1) { + Log log = currentLogs.get(0); + runLater(() -> logWindow.logLine(log)); + } else { + runLater(() -> { + logWindow.logLines(currentLogs); + semaphore.release(); + }); + semaphore.acquireUninterruptibly(); + } + currentLogs.clear(); + } + + @Override + public void run() { + while (true) { + try { + currentLogs.add(logBuffer.take()); + //noinspection BusyWait + Thread.sleep(200); // Wait for more logs + } catch (InterruptedException e) { + break; + } + + logBuffer.drainTo(currentLogs); + submitLogs(); + } + + do { + submitLogs(); + } while (logBuffer.drainTo(currentLogs) > 0); + } + }, "Game Log Submitter", true); + + try { + logWindowLatch.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } } private void finishLaunch() { switch (launcherVisibility) { case HIDE_AND_REOPEN: - Platform.runLater(() -> { + runLater(() -> { // If application was stopped and execution services did not finish termination, // these codes will be executed. if (Controllers.getStage() != null) { @@ -776,11 +791,11 @@ private void finishLaunch() { // Never come to here. break; case KEEP: - Platform.runLater(launchingLatch::countDown); + runLater(launchingLatch::countDown); break; case HIDE: launchingLatch.countDown(); - Platform.runLater(() -> { + runLater(() -> { // If application was stopped and execution services did not finish termination, // these codes will be executed. if (Controllers.getStage() != null) { @@ -796,44 +811,37 @@ private void finishLaunch() { @Override public void onLog(String log, boolean isErrorStream) { - String filteredLog = forbiddenAccessToken == null ? log : log.replace(forbiddenAccessToken, ""); - if (isErrorStream) - System.err.println(filteredLog); + System.err.println(log); else - System.out.println(filteredLog); + System.out.println(log); - Log4jLevel level; - if (isErrorStream) - level = Log4jLevel.ERROR; - else - level = showLogs ? Optional.ofNullable(Log4jLevel.guessLevel(filteredLog)).orElse(Log4jLevel.INFO) : null; - - synchronized (this) { - logs.add(filteredLog); - levels.add(level != null ? level : Optional.empty()); // Use 'Optional.empty()' as hole - if (logs.size() > config().getLogLines()) { - logs.removeFirst(); - levels.removeFirst(); - } - } + log = StringUtils.parseEscapeSequence(log); + if (forbiddenAccessToken != null) + log = log.replace(forbiddenAccessToken, ""); + Log4jLevel level = isErrorStream && !log.startsWith("[authlib-injector]") ? Log4jLevel.ERROR : null; if (showLogs) { - try { - logWindowLatch.await(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - return; + if (level == null) + level = Lang.requireNonNullElse(Log4jLevel.guessLevel(log), Log4jLevel.INFO); + logBuffer.add(new Log(log, level)); + } else { + synchronized (this) { + logs.addLast(new Log(log, level)); + if (logs.size() > Log.getLogLines()) + logs.removeFirst(); } - - Platform.runLater(() -> logWindow.logLine(filteredLog, level)); } if (!lwjgl) { - String lowerCaseLog = filteredLog.toLowerCase(Locale.ROOT); + String lowerCaseLog = log.toLowerCase(Locale.ROOT); if (!detectWindow || lowerCaseLog.contains("lwjgl version") || lowerCaseLog.contains("lwjgl openal")) { - lwjgl = true; - finishLaunch(); + synchronized (this) { + if (!lwjgl) { + lwjgl = true; + finishLaunch(); + } + } } } } @@ -841,7 +849,13 @@ public void onLog(String log, boolean isErrorStream) { @Override public void onExit(int exitCode, ExitType exitType) { if (showLogs) { - Platform.runLater(() -> logWindow.logLine(String.format("[HMCL ProcessListener] Minecraft exit with code %d(0x%x), type is %s.", exitCode, exitCode, exitType), Log4jLevel.INFO)); + logBuffer.add(new Log(String.format("[HMCL ProcessListener] Minecraft exit with code %d(0x%x), type is %s.", exitCode, exitCode, exitType), Log4jLevel.INFO)); + submitLogThread.interrupt(); + try { + submitLogThread.join(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } } launchingLatch.countDown(); @@ -850,14 +864,16 @@ public void onExit(int exitCode, ExitType exitType) { return; // Game crashed before opening the game window. - if (!lwjgl) finishLaunch(); + if (!lwjgl) { + synchronized (this) { + if (!lwjgl) + finishLaunch(); + } + } if (exitType != ExitType.NORMAL) { - ArrayList> pairs = new ArrayList<>(logs.size()); - Lang.forEachZipped(logs, levels, - (log, l) -> pairs.add(pair(log, l instanceof Log4jLevel ? ((Log4jLevel) l) : Optional.ofNullable(Log4jLevel.guessLevel(log)).orElse(Log4jLevel.INFO)))); repository.markVersionLaunchedAbnormally(version.getId()); - Platform.runLater(() -> new GameCrashWindow(process, exitType, repository, version, launchOptions, pairs).show()); + runLater(() -> new GameCrashWindow(process, exitType, repository, version, launchOptions, logs).show()); } checkExit(); @@ -865,8 +881,8 @@ public void onExit(int exitCode, ExitType exitType) { } - private static final String ORACLEJDK_DOWNLOAD_LINK = "https://www.java.com/download"; - private static final String OPENJDK_DOWNLOAD_LINK = "https://docs.microsoft.com/java/openjdk/download"; + private static final String ORACLEJDK_DOWNLOAD_LINK = "https://www.java.com/download/"; + private static final String OPENJDK_DOWNLOAD_LINK = "https://learn.microsoft.com/java/openjdk/download"; public static final Queue PROCESSES = new ConcurrentLinkedQueue<>(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/Log.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/Log.java new file mode 100644 index 0000000000..ea01aee3ec --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/Log.java @@ -0,0 +1,72 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.game; + +import org.jackhuang.hmcl.util.Log4jLevel; + +import static org.jackhuang.hmcl.setting.ConfigHolder.config; + +public final class Log { + public static final int DEFAULT_LOG_LINES = 2000; + + public static int getLogLines() { + Integer lines = config().getLogLines(); + return lines != null && lines > 0 ? lines : DEFAULT_LOG_LINES; + } + + private final String log; + private Log4jLevel level; + private boolean selected = false; + + public Log(String log) { + this.log = log; + } + + public Log(String log, Log4jLevel level) { + this.log = log; + this.level = level; + } + + public String getLog() { + return log; + } + + public Log4jLevel getLevel() { + Log4jLevel level = this.level; + if (level == null) { + level = Log4jLevel.guessLevel(log); + if (level == null) + level = Log4jLevel.INFO; + this.level = level; + } + return level; + } + + public boolean isSelected() { + return selected; + } + + public void setSelected(boolean selected) { + this.selected = selected; + } + + @Override + public String toString() { + return log; + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/ModpackHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/ModpackHelper.java index c076765976..78b96db501 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/ModpackHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/ModpackHelper.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.game; import com.google.gson.JsonParseException; -import com.google.gson.reflect.TypeToken; import org.apache.commons.compress.archivers.zip.ZipFile; import org.jackhuang.hmcl.mod.*; import org.jackhuang.hmcl.mod.curse.CurseModpackProvider; @@ -141,8 +140,7 @@ public static ModpackConfiguration readModpackConfiguration(File file) throws throw new FileNotFoundException(file.getPath()); else try { - return JsonUtils.GSON.fromJson(FileUtils.readText(file), new TypeToken>() { - }.getType()); + return JsonUtils.GSON.fromJson(FileUtils.readText(file), ModpackConfiguration.class); } catch (JsonParseException e) { throw new IOException("Malformed modpack configuration"); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/java/HMCLJavaRepository.java b/HMCL/src/main/java/org/jackhuang/hmcl/java/HMCLJavaRepository.java new file mode 100644 index 0000000000..75d8d3f890 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/java/HMCLJavaRepository.java @@ -0,0 +1,221 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.java; + +import org.jackhuang.hmcl.download.DownloadProvider; +import org.jackhuang.hmcl.download.java.mojang.MojangJavaDownloadTask; +import org.jackhuang.hmcl.download.java.mojang.MojangJavaRemoteFiles; +import org.jackhuang.hmcl.game.DownloadInfo; +import org.jackhuang.hmcl.game.GameJavaVersion; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.gson.JsonUtils; +import org.jackhuang.hmcl.util.io.FileUtils; +import org.jackhuang.hmcl.util.platform.OperatingSystem; +import org.jackhuang.hmcl.util.platform.Platform; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.*; +import java.util.*; + +import static org.jackhuang.hmcl.util.logging.Logger.LOG; + +/** + * @author Glavo + */ +public final class HMCLJavaRepository implements JavaRepository { + public static final String MOJANG_JAVA_PREFIX = "mojang-"; + + private final Path root; + + public HMCLJavaRepository(Path root) { + this.root = root; + } + + public Path getPlatformRoot(Platform platform) { + return root.resolve(platform.toString()); + } + + @Override + public Path getJavaDir(Platform platform, String name) { + return getPlatformRoot(platform).resolve(name); + } + + public Path getJavaDir(Platform platform, GameJavaVersion gameJavaVersion) { + return getJavaDir(platform, MOJANG_JAVA_PREFIX + gameJavaVersion.getComponent()); + } + + @Override + public Path getManifestFile(Platform platform, String name) { + return getPlatformRoot(platform).resolve(name + ".json"); + } + + public Path getManifestFile(Platform platform, GameJavaVersion gameJavaVersion) { + return getManifestFile(platform, MOJANG_JAVA_PREFIX + gameJavaVersion.getComponent()); + } + + public boolean isInstalled(Platform platform, String name) { + return Files.exists(getManifestFile(platform, name)); + } + + public boolean isInstalled(Platform platform, GameJavaVersion gameJavaVersion) { + return isInstalled(platform, MOJANG_JAVA_PREFIX + gameJavaVersion.getComponent()); + } + + public @Nullable Path getJavaExecutable(Platform platform, String name) { + Path javaDir = getJavaDir(platform, name); + try { + return JavaManager.getExecutable(javaDir).toRealPath(); + } catch (IOException ignored) { + if (platform.getOperatingSystem() == OperatingSystem.OSX) { + try { + return JavaManager.getMacExecutable(javaDir).toRealPath(); + } catch (IOException ignored1) { + } + } + } + + return null; + } + + public @Nullable Path getJavaExecutable(Platform platform, GameJavaVersion gameJavaVersion) { + return getJavaExecutable(platform, MOJANG_JAVA_PREFIX + gameJavaVersion.getComponent()); + } + + @Override + public Collection getAllJava(Platform platform) { + Path root = getPlatformRoot(platform); + if (!Files.isDirectory(root)) + return Collections.emptyList(); + + ArrayList list = new ArrayList<>(); + try (DirectoryStream stream = Files.newDirectoryStream(root)) { + for (Path file : stream) { + try { + String name = file.getFileName().toString(); + if (name.endsWith(".json") && Files.isRegularFile(file)) { + Path javaDir = file.resolveSibling(name.substring(0, name.length() - ".json".length())); + Path executable; + try { + executable = JavaManager.getExecutable(javaDir).toRealPath(); + } catch (IOException e) { + if (platform.getOperatingSystem() == OperatingSystem.OSX) + executable = JavaManager.getMacExecutable(javaDir).toRealPath(); + else + throw e; + } + + if (Files.isDirectory(javaDir)) { + JavaManifest manifest; + try (InputStream input = Files.newInputStream(file)) { + manifest = JsonUtils.fromJsonFully(input, JavaManifest.class); + } + + list.add(JavaRuntime.of(executable, manifest.getInfo(), true)); + } + } + } catch (Throwable e) { + LOG.warning("Failed to parse " + file, e); + } + } + + } catch (IOException ignored) { + } + return list; + } + + @Override + public Task getDownloadJavaTask(DownloadProvider downloadProvider, Platform platform, GameJavaVersion gameJavaVersion) { + Path javaDir = getJavaDir(platform, gameJavaVersion); + + return new MojangJavaDownloadTask(downloadProvider, javaDir, gameJavaVersion, JavaManager.getMojangJavaPlatform(platform)).thenApplyAsync(result -> { + Path executable; + try { + executable = JavaManager.getExecutable(javaDir).toRealPath(); + } catch (IOException e) { + if (platform.getOperatingSystem() == OperatingSystem.OSX) + executable = JavaManager.getMacExecutable(javaDir).toRealPath(); + else + throw e; + } + + JavaInfo info; + if (JavaManager.isCompatible(platform)) + info = JavaInfo.fromExecutable(executable, false); + else + info = new JavaInfo(platform, result.download.getVersion().getName(), null); + + Map update = new LinkedHashMap<>(); + update.put("provider", "mojang"); + update.put("component", gameJavaVersion.getComponent()); + + Map files = new LinkedHashMap<>(); + result.remoteFiles.getFiles().forEach((path, file) -> { + if (file instanceof MojangJavaRemoteFiles.RemoteFile) { + DownloadInfo downloadInfo = ((MojangJavaRemoteFiles.RemoteFile) file).getDownloads().get("raw"); + if (downloadInfo != null) { + files.put(path, new JavaLocalFiles.LocalFile(downloadInfo.getSha1(), downloadInfo.getSize())); + } + } else if (file instanceof MojangJavaRemoteFiles.RemoteDirectory) { + files.put(path, new JavaLocalFiles.LocalDirectory()); + } else if (file instanceof MojangJavaRemoteFiles.RemoteLink) { + files.put(path, new JavaLocalFiles.LocalLink(((MojangJavaRemoteFiles.RemoteLink) file).getTarget())); + } + }); + + JavaManifest manifest = new JavaManifest(info, update, files); + FileUtils.writeText(getManifestFile(platform, gameJavaVersion), JsonUtils.GSON.toJson(manifest)); + return JavaRuntime.of(executable, info, true); + }); + } + + public Task getInstallJavaTask(Platform platform, String name, Map update, Path archiveFile) { + Path javaDir = getJavaDir(platform, name); + return new JavaInstallTask(javaDir, update, archiveFile).thenApplyAsync(result -> { + if (!result.getInfo().getPlatform().equals(platform)) + throw new IOException("Platform is mismatch: expected " + platform + " but got " + result.getInfo().getPlatform()); + + Path executable = javaDir.resolve("bin").resolve(platform.getOperatingSystem().getJavaExecutable()).toRealPath(); + FileUtils.writeText(getManifestFile(platform, name), JsonUtils.GSON.toJson(result)); + return JavaRuntime.of(executable, result.getInfo(), true); + }); + } + + @Override + public Task getUninstallJavaTask(Platform platform, String name) { + return Task.runAsync(() -> { + Files.deleteIfExists(getManifestFile(platform, name)); + FileUtils.deleteDirectory(getJavaDir(platform, name).toFile()); + }); + } + + @Override + public Task getUninstallJavaTask(JavaRuntime java) { + return Task.runAsync(() -> { + Path root = getPlatformRoot(java.getPlatform()); + Path relativized = root.relativize(java.getBinary()); + + if (relativized.getNameCount() > 1) { + String name = relativized.getName(0).toString(); + Files.deleteIfExists(getManifestFile(java.getPlatform(), name)); + FileUtils.deleteDirectory(getJavaDir(java.getPlatform(), name).toFile()); + } + }); + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/java/JavaInstallTask.java b/HMCL/src/main/java/org/jackhuang/hmcl/java/JavaInstallTask.java new file mode 100644 index 0000000000..b26c674d5c --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/java/JavaInstallTask.java @@ -0,0 +1,116 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.java; + +import org.apache.commons.compress.archivers.ArchiveEntry; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.DigestUtils; +import org.jackhuang.hmcl.util.Hex; +import org.jackhuang.hmcl.util.io.IOUtils; +import org.jackhuang.hmcl.util.tree.ArchiveFileTree; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * @author Glavo + */ +public final class JavaInstallTask extends Task { + + private final Path targetDir; + private final Map update; + private final Path archiveFile; + + private final Map files = new LinkedHashMap<>(); + private final ArrayList nameStack = new ArrayList<>(); + private final byte[] buffer = new byte[IOUtils.DEFAULT_BUFFER_SIZE]; + private final MessageDigest messageDigest = DigestUtils.getDigest("SHA-1"); + + public JavaInstallTask(Path targetDir, Map update, Path archiveFile) { + this.targetDir = targetDir; + this.update = update; + this.archiveFile = archiveFile; + } + + @Override + public void execute() throws Exception { + JavaInfo info; + + try (ArchiveFileTree tree = ArchiveFileTree.open(archiveFile)) { + info = JavaInfo.fromArchive(tree); + copyDirContent(tree, targetDir); + } + + setResult(new JavaManifest(info, update, files)); + } + + private void copyDirContent(ArchiveFileTree tree, Path targetDir) throws IOException { + copyDirContent(tree, tree.getRoot().getSubDirs().values().iterator().next(), targetDir); + } + + private void copyDirContent(ArchiveFileTree tree, ArchiveFileTree.Dir dir, Path targetDir) throws IOException { + Files.createDirectories(targetDir); + + for (Map.Entry pair : dir.getFiles().entrySet()) { + Path path = targetDir.resolve(pair.getKey()); + E entry = pair.getValue(); + + nameStack.add(pair.getKey()); + if (tree.isLink(entry)) { + String linkTarget = tree.getLink(entry); + files.put(String.join("/", nameStack), new JavaLocalFiles.LocalLink(linkTarget)); + Files.createSymbolicLink(path, Paths.get(linkTarget)); + } else { + long size = 0L; + + try (InputStream input = tree.getInputStream(entry); + OutputStream output = Files.newOutputStream(path, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) { + messageDigest.reset(); + + int c; + while ((c = input.read(buffer)) > 0) { + size += c; + output.write(buffer, 0, c); + messageDigest.update(buffer, 0, c); + } + } + + if (tree.isExecutable(entry)) + //noinspection ResultOfMethodCallIgnored + path.toFile().setExecutable(true); + + files.put(String.join("/", nameStack), new JavaLocalFiles.LocalFile(Hex.encodeHex(messageDigest.digest()), size)); + } + nameStack.remove(nameStack.size() - 1); + } + + for (Map.Entry> pair : dir.getSubDirs().entrySet()) { + nameStack.add(pair.getKey()); + files.put(String.join("/", nameStack), new JavaLocalFiles.LocalDirectory()); + copyDirContent(tree, pair.getValue(), targetDir.resolve(pair.getKey())); + nameStack.remove(nameStack.size() - 1); + } + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/java/JavaLocalFiles.java b/HMCL/src/main/java/org/jackhuang/hmcl/java/JavaLocalFiles.java new file mode 100644 index 0000000000..592acec56e --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/java/JavaLocalFiles.java @@ -0,0 +1,128 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2020 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.java; + +import com.google.gson.*; +import com.google.gson.annotations.JsonAdapter; + +import java.lang.reflect.Type; + +/** + * @author Glavo + */ +public final class JavaLocalFiles { + @JsonAdapter(Serializer.class) + public abstract static class Local { + private final String type; + + Local(String type) { + this.type = type; + } + + public String getType() { + return type; + } + } + + public static final class LocalFile extends Local { + private final String sha1; + private final long size; + + public LocalFile(String sha1, long size) { + super("file"); + this.sha1 = sha1; + this.size = size; + } + + public String getSha1() { + return sha1; + } + + public long getSize() { + return size; + } + } + + public static final class LocalDirectory extends Local { + public LocalDirectory() { + super("directory"); + } + } + + public static final class LocalLink extends Local { + private final String target; + + public LocalLink(String target) { + super("link"); + this.target = target; + } + + public String getTarget() { + return target; + } + } + + public static class Serializer implements JsonSerializer, JsonDeserializer { + + @Override + public JsonElement serialize(Local src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject obj = new JsonObject(); + obj.addProperty("type", src.getType()); + if (src instanceof LocalFile) { + obj.addProperty("sha1", ((LocalFile) src).getSha1()); + obj.addProperty("size", ((LocalFile) src).getSize()); + } else if (src instanceof LocalLink) { + obj.addProperty("target", ((LocalLink) src).getTarget()); + } + return obj; + } + + @Override + public Local deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + if (!json.isJsonObject()) + throw new JsonParseException(json.toString()); + + JsonObject obj = json.getAsJsonObject(); + if (!obj.has("type")) + throw new JsonParseException(json.toString()); + + String type = obj.getAsJsonPrimitive("type").getAsString(); + + try { + switch (type) { + case "file": { + String sha1 = obj.getAsJsonPrimitive("sha1").getAsString(); + long size = obj.getAsJsonPrimitive("size").getAsLong(); + return new LocalFile(sha1, size); + } + case "directory": { + return new LocalDirectory(); + } + case "link": { + String target = obj.getAsJsonPrimitive("target").getAsString(); + return new LocalLink(target); + } + default: + throw new AssertionError("unknown type: " + type); + } + } catch (Throwable e) { + throw new JsonParseException(json.toString()); + } + } + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/java/JavaManager.java b/HMCL/src/main/java/org/jackhuang/hmcl/java/JavaManager.java new file mode 100644 index 0000000000..a5592e6a9b --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/java/JavaManager.java @@ -0,0 +1,698 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.java; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import org.jackhuang.hmcl.Metadata; +import org.jackhuang.hmcl.download.DownloadProvider; +import org.jackhuang.hmcl.download.LibraryAnalyzer; +import org.jackhuang.hmcl.game.GameJavaVersion; +import org.jackhuang.hmcl.game.JavaVersionConstraint; +import org.jackhuang.hmcl.game.Version; +import org.jackhuang.hmcl.setting.ConfigHolder; +import org.jackhuang.hmcl.task.Schedulers; +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.versioning.GameVersionNumber; +import org.jetbrains.annotations.Nullable; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.*; +import java.util.*; +import java.util.concurrent.CountDownLatch; +import java.util.stream.Collectors; + +import static org.jackhuang.hmcl.util.logging.Logger.LOG; + +/** + * @author Glavo + */ +public final class JavaManager { + + private JavaManager() { + } + + public static final HMCLJavaRepository REPOSITORY = new HMCLJavaRepository(Metadata.HMCL_DIRECTORY.resolve("java")); + + public static String getMojangJavaPlatform(Platform platform) { + if (platform.getOperatingSystem() == OperatingSystem.WINDOWS) { + if (Architecture.SYSTEM_ARCH == Architecture.X86) { + return "windows-x86"; + } else if (Architecture.SYSTEM_ARCH == Architecture.X86_64) { + return "windows-x64"; + } else if (Architecture.SYSTEM_ARCH == Architecture.ARM64) { + return "windows-arm64"; + } + } else if (platform.getOperatingSystem() == OperatingSystem.LINUX) { + if (Architecture.SYSTEM_ARCH == Architecture.X86) { + return "linux-i386"; + } else if (Architecture.SYSTEM_ARCH == Architecture.X86_64) { + return "linux"; + } + } else if (platform.getOperatingSystem() == OperatingSystem.OSX) { + if (Architecture.SYSTEM_ARCH == Architecture.X86_64) { + return "mac-os"; + } else if (Architecture.SYSTEM_ARCH == Architecture.ARM64) { + return "mac-os-arm64"; + } + } + + return null; + } + + public static Path getExecutable(Path javaHome) { + return javaHome.resolve("bin").resolve(OperatingSystem.CURRENT_OS.getJavaExecutable()); + } + + public static Path getMacExecutable(Path javaHome) { + return javaHome.resolve("jre.bundle/Contents/Home/bin/java"); + } + + public static boolean isCompatible(Platform platform) { + if (platform.getOperatingSystem() != OperatingSystem.CURRENT_OS) + return false; + + Architecture architecture = platform.getArchitecture(); + if (architecture == Architecture.SYSTEM_ARCH || architecture == Architecture.CURRENT_ARCH) + return true; + + switch (OperatingSystem.CURRENT_OS) { + case WINDOWS: + if (Architecture.SYSTEM_ARCH == Architecture.X86_64) + return architecture == Architecture.X86; + if (Architecture.SYSTEM_ARCH == Architecture.ARM64) + return OperatingSystem.SYSTEM_BUILD_NUMBER >= 21277 && architecture == Architecture.X86_64 || architecture == Architecture.X86; + break; + case LINUX: + if (Architecture.SYSTEM_ARCH == Architecture.X86_64) + return architecture == Architecture.X86; + break; + case OSX: + if (Architecture.SYSTEM_ARCH == Architecture.ARM64) + return architecture == Architecture.X86_64; + break; + } + + return false; + } + + private static volatile Map allJava; + private static final CountDownLatch LATCH = new CountDownLatch(1); + + private static final ObjectProperty> allJavaProperty = new SimpleObjectProperty<>(); + + private static Map getAllJavaMap() throws InterruptedException { + Map map = allJava; + if (map == null) { + LATCH.await(); + map = allJava; + } + return map; + } + + private static void updateAllJavaProperty(Map javaRuntimes) { + JavaRuntime[] array = javaRuntimes.values().toArray(new JavaRuntime[0]); + Arrays.sort(array); + allJavaProperty.set(Arrays.asList(array)); + } + + public static boolean isInitialized() { + return allJava != null; + } + + public static Collection getAllJava() throws InterruptedException { + return getAllJavaMap().values(); + } + + public static ObjectProperty> getAllJavaProperty() { + return allJavaProperty; + } + + public static JavaRuntime getJava(Path executable) throws IOException, InterruptedException { + executable = executable.toRealPath(); + + JavaRuntime javaRuntime = getAllJavaMap().get(executable); + if (javaRuntime != null) { + return javaRuntime; + } + + JavaInfo info = JavaInfo.fromExecutable(executable); + return JavaRuntime.of(executable, info, false); + } + + public static void refresh() { + Task.supplyAsync(JavaManager::searchPotentialJavaExecutables).whenComplete(Schedulers.javafx(), (result, exception) -> { + if (result != null) { + LATCH.await(); + allJava = result; + updateAllJavaProperty(result); + } + }).start(); + } + + public static Task getAddJavaTask(Path binary) { + return Task.supplyAsync("Get Java", () -> JavaManager.getJava(binary)) + .thenApplyAsync(Schedulers.javafx(), javaRuntime -> { + if (!JavaManager.isCompatible(javaRuntime.getPlatform())) { + throw new UnsupportedPlatformException("Incompatible platform: " + javaRuntime.getPlatform()); + } + + String pathString = javaRuntime.getBinary().toString(); + + ConfigHolder.globalConfig().getDisabledJava().remove(pathString); + if (ConfigHolder.globalConfig().getUserJava().add(pathString)) { + addJava(javaRuntime); + } + return javaRuntime; + }); + } + + public static Task getDownloadJavaTask(DownloadProvider downloadProvider, Platform platform, GameJavaVersion gameJavaVersion) { + return REPOSITORY.getDownloadJavaTask(downloadProvider, platform, gameJavaVersion) + .thenApplyAsync(Schedulers.javafx(), java -> { + addJava(java); + return java; + }); + } + + public static Task getInstallJavaTask(Platform platform, String name, Map update, Path archiveFile) { + return REPOSITORY.getInstallJavaTask(platform, name, update, archiveFile) + .thenApplyAsync(Schedulers.javafx(), java -> { + addJava(java); + return java; + }); + } + + public static Task getUninstallJavaTask(JavaRuntime java) { + assert java.isManaged(); + Path root = REPOSITORY.getPlatformRoot(java.getPlatform()); + Path relativized = root.relativize(java.getBinary()); + + if (relativized.getNameCount() > 1) { + FXUtils.runInFX(() -> { + try { + removeJava(java); + } catch (InterruptedException e) { + throw new AssertionError("Unreachable code", e); + } + }); + + String name = relativized.getName(0).toString(); + return REPOSITORY.getUninstallJavaTask(java.getPlatform(), name); + } else { + return Task.completed(null); + } + } + + // FXThread + public static void addJava(JavaRuntime java) throws InterruptedException { + Map oldMap = getAllJavaMap(); + if (!oldMap.containsKey(java.getBinary())) { + HashMap newMap = new HashMap<>(oldMap); + newMap.put(java.getBinary(), java); + allJava = newMap; + updateAllJavaProperty(newMap); + } + } + + // FXThread + public static void removeJava(JavaRuntime java) throws InterruptedException { + removeJava(java.getBinary()); + } + + // FXThread + public static void removeJava(Path realPath) throws InterruptedException { + Map oldMap = getAllJavaMap(); + if (oldMap.containsKey(realPath)) { + HashMap newMap = new HashMap<>(oldMap); + newMap.remove(realPath); + allJava = newMap; + updateAllJavaProperty(newMap); + } + } + + private static int compareJavaVersion(JavaRuntime java1, JavaRuntime java2, GameJavaVersion suggestedJavaVersion) { + if (suggestedJavaVersion != null) { + boolean b1 = java1.getParsedVersion() == suggestedJavaVersion.getMajorVersion(); + boolean b2 = java2.getParsedVersion() == suggestedJavaVersion.getMajorVersion(); + + if (b1 != b2) + return b1 ? 1 : -1; + } + + return java1.getVersionNumber().compareTo(java2.getVersionNumber()); + } + + @Nullable + public static JavaRuntime findSuitableJava(GameVersionNumber gameVersion, Version version) throws InterruptedException { + return findSuitableJava(getAllJava(), gameVersion, version); + } + + @Nullable + public static JavaRuntime findSuitableJava(Collection javaRuntimes, GameVersionNumber gameVersion, Version version) { + 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) + && (gameVersion == null || gameVersion.compareTo("1.6") < 0); + + GameJavaVersion suggestedJavaVersion = + (version != null && gameVersion != null && gameVersion.compareTo("1.7.10") >= 0) ? version.getJavaVersion() : null; + + JavaRuntime mandatory = null; + JavaRuntime suggested = null; + for (JavaRuntime java : javaRuntimes) { + if (forceX86) { + if (!java.getArchitecture().isX86()) + continue; + } else { + if (java.getArchitecture() != Architecture.SYSTEM_ARCH) + continue; + } + + boolean violationMandatory = false; + boolean violationSuggested = false; + + for (JavaVersionConstraint constraint : JavaVersionConstraint.ALL) { + if (constraint.appliesToVersion(gameVersion, version, java, analyzer)) { + if (!constraint.checkJava(gameVersion, version, java)) { + if (constraint.isMandatory()) { + violationMandatory = true; + } else { + violationSuggested = true; + } + } + } + } + + if (!violationMandatory) { + if (mandatory == null) mandatory = java; + else if (compareJavaVersion(java, mandatory, suggestedJavaVersion) > 0) + mandatory = java; + + if (!violationSuggested) { + if (suggested == null) suggested = java; + else if (compareJavaVersion(java, suggested, suggestedJavaVersion) > 0) + suggested = java; + } + } + } + + return suggested != null ? suggested : mandatory; + } + + public static void initialize() { + Map allJava = searchPotentialJavaExecutables(); + JavaManager.allJava = allJava; + LATCH.countDown(); + FXUtils.runInFX(() -> updateAllJavaProperty(allJava)); + } + + // search java + + private static Map searchPotentialJavaExecutables() { + Map javaRuntimes = new HashMap<>(); + searchAllJavaInRepository(javaRuntimes, Platform.SYSTEM_PLATFORM); + switch (OperatingSystem.CURRENT_OS) { + case WINDOWS: + if (Architecture.SYSTEM_ARCH == Architecture.X86_64) + searchAllJavaInRepository(javaRuntimes, Platform.WINDOWS_X86); + if (Architecture.SYSTEM_ARCH == Architecture.ARM64) { + if (OperatingSystem.SYSTEM_BUILD_NUMBER >= 21277) + searchAllJavaInRepository(javaRuntimes, Platform.WINDOWS_X86_64); + searchAllJavaInRepository(javaRuntimes, Platform.WINDOWS_X86); + } + break; + case OSX: + if (Architecture.SYSTEM_ARCH == Architecture.ARM64) + searchAllJavaInRepository(javaRuntimes, Platform.OSX_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\\"); + + searchJavaInProgramFiles(javaRuntimes, "ProgramFiles", "C:\\Program Files"); + searchJavaInProgramFiles(javaRuntimes, "ProgramFiles(x86)", "C:\\Program Files (x86)"); + if (Architecture.SYSTEM_ARCH == Architecture.ARM64) { + searchJavaInProgramFiles(javaRuntimes, "ProgramFiles(ARM)", "C:\\Program Files (ARM)"); + } + break; + case LINUX: + searchAllJavaInDirectory(javaRuntimes, Paths.get("/usr/java")); // Oracle RPMs + searchAllJavaInDirectory(javaRuntimes, Paths.get("/usr/lib/jvm")); // General locations + searchAllJavaInDirectory(javaRuntimes, Paths.get("/usr/lib32/jvm")); // General locations + searchAllJavaInDirectory(javaRuntimes, Paths.get("/usr/lib64/jvm")); // General locations + searchAllJavaInDirectory(javaRuntimes, Paths.get(System.getProperty("user.home"), "/.sdkman/candidates/java")); // SDKMAN! + break; + case OSX: + tryAddJavaHome(javaRuntimes, Paths.get("/Library/Java/JavaVirtualMachines/Contents/Home")); + tryAddJavaHome(javaRuntimes, Paths.get(System.getProperty("user.home"), "/Library/Java/JavaVirtualMachines/Contents/Home")); + tryAddJavaExecutable(javaRuntimes, Paths.get("/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java")); + tryAddJavaExecutable(javaRuntimes, Paths.get("/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/MacOS/itms/java/bin/java")); + // Homebrew + tryAddJavaExecutable(javaRuntimes, Paths.get("/opt/homebrew/opt/java/bin/java")); + searchAllJavaInDirectory(javaRuntimes, Paths.get("/opt/homebrew/Cellar/openjdk")); + try (DirectoryStream dirs = Files.newDirectoryStream(Paths.get("/opt/homebrew/Cellar"), "openjdk@*")) { + for (Path dir : dirs) { + searchAllJavaInDirectory(javaRuntimes, dir); + } + } catch (IOException e) { + LOG.warning("Failed to get subdirectories of /opt/homebrew/Cellar"); + } + break; + + default: + break; + } + + // Search Minecraft bundled runtimes + if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS && Architecture.SYSTEM_ARCH.isX86()) { + FileUtils.tryGetPath(System.getenv("localappdata"), "Packages\\Microsoft.4297127D64EC6_8wekyb3d8bbwe\\LocalCache\\Local\\runtime") + .ifPresent(it -> searchAllOfficialJava(javaRuntimes, it, false)); + + FileUtils.tryGetPath(Lang.requireNonNullElse(System.getenv("ProgramFiles(x86)"), "C:\\Program Files (x86)"), "Minecraft Launcher\\runtime") + .ifPresent(it -> searchAllOfficialJava(javaRuntimes, it, false)); + } else if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX && Architecture.SYSTEM_ARCH == Architecture.X86_64) { + searchAllOfficialJava(javaRuntimes, Paths.get(System.getProperty("user.home"), ".minecraft/runtime"), false); + } else if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX) { + 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); + for (String path : paths) { + try { + tryAddJavaExecutable(javaRuntimes, Paths.get(path, OperatingSystem.CURRENT_OS.getJavaExecutable())); + } catch (InvalidPathException ignored) { + } + } + } + + if (System.getenv("HMCL_JRES") != null) { + String[] paths = System.getenv("HMCL_JRES").split(OperatingSystem.PATH_SEPARATOR); + for (String path : paths) { + try { + tryAddJavaHome(javaRuntimes, Paths.get(path)); + } catch (InvalidPathException ignored) { + } + } + } + searchAllJavaInDirectory(javaRuntimes, Paths.get(System.getProperty("user.home"), ".jdks")); + + for (String javaPath : ConfigHolder.globalConfig().getUserJava()) { + try { + tryAddJavaExecutable(javaRuntimes, Paths.get(javaPath)); + } catch (InvalidPathException e) { + LOG.warning("Invalid Java path: " + javaPath); + } + } + + JavaRuntime currentJava = JavaRuntime.CURRENT_JAVA; + if (currentJava != null + && !javaRuntimes.containsKey(currentJava.getBinary()) + && !ConfigHolder.globalConfig().getDisabledJava().contains(currentJava.getBinary().toString())) { + javaRuntimes.put(currentJava.getBinary(), currentJava); + } + + LOG.trace(javaRuntimes.values().stream().sorted() + .map(it -> String.format(" - %s %s (%s, %s): %s", + it.isJDK() ? "JDK" : "JRE", + it.getVersion(), + it.getPlatform().getArchitecture().getDisplayName(), + Lang.requireNonNullElse(it.getVendor(), "Unknown"), + it.getBinary())) + .collect(Collectors.joining("\n", "Finished Java lookup, found " + javaRuntimes.size() + "\n", ""))); + + return javaRuntimes; + } + + private static void tryAddJavaHome(Map javaRuntimes, Path javaHome) { + Path executable = getExecutable(javaHome); + if (!Files.isRegularFile(executable)) { + return; + } + + try { + executable = executable.toRealPath(); + } catch (IOException e) { + LOG.warning("Failed to resolve path " + executable, e); + return; + } + + if (javaRuntimes.containsKey(executable) || ConfigHolder.globalConfig().getDisabledJava().contains(executable.toString())) { + return; + } + + JavaInfo info = null; + + Path releaseFile = javaHome.resolve("release"); + if (Files.exists(releaseFile)) { + try { + info = JavaInfo.fromReleaseFile(releaseFile); + } catch (IOException e) { + try { + info = JavaInfo.fromExecutable(executable, false); + } catch (IOException e2) { + e2.addSuppressed(e); + LOG.warning("Failed to lookup Java executable at " + executable, e2); + } + } + } + + if (info != null && isCompatible(info.getPlatform())) + javaRuntimes.put(executable, JavaRuntime.of(executable, info, false)); + } + + private static void tryAddJavaExecutable(Map javaRuntimes, Path executable) { + try { + executable = executable.toRealPath(); + } catch (IOException e) { + return; + } + + if (javaRuntimes.containsKey(executable) || ConfigHolder.globalConfig().getDisabledJava().contains(executable.toString())) { + return; + } + + JavaInfo info = null; + try { + info = JavaInfo.fromExecutable(executable); + } catch (IOException e) { + LOG.warning("Failed to lookup Java executable at " + executable, e); + } + + if (info != null && isCompatible(info.getPlatform())) { + javaRuntimes.put(executable, JavaRuntime.of(executable, info, false)); + } + } + + private static void tryAddJavaInComponentDir(Map javaRuntimes, String platform, Path component, boolean verify) { + Path sha1File = component.resolve(platform).resolve(component.getFileName() + ".sha1"); + if (!Files.isRegularFile(sha1File)) + return; + + Path dir = component.resolve(platform).resolve(component.getFileName()); + + if (verify) { + try (BufferedReader reader = Files.newBufferedReader(sha1File)) { + String line; + while ((line = reader.readLine()) != null) { + if (line.isEmpty()) continue; + + int idx = line.indexOf(" /#//"); + if (idx <= 0) + throw new IOException("Illegal line: " + line); + + Path file = dir.resolve(line.substring(0, idx)); + + // Should we check the sha1 of files? This will take a lot of time. + if (Files.notExists(file)) + throw new NoSuchFileException(file.toAbsolutePath().toString()); + } + } catch (IOException e) { + LOG.warning("Failed to verify Java in " + component, e); + return; + } + } + + if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX) { + Path macPath = dir.resolve("jre.bundle/Contents/Home"); + if (Files.exists(macPath)) { + tryAddJavaHome(javaRuntimes, macPath); + return; + } else + LOG.warning("The Java is not in 'jre.bundle/Contents/Home'"); + } + + tryAddJavaHome(javaRuntimes, dir); + } + + private static void searchAllJavaInRepository(Map javaRuntimes, Platform platform) { + for (JavaRuntime java : REPOSITORY.getAllJava(platform)) { + javaRuntimes.put(java.getBinary(), java); + } + } + + private static void searchAllOfficialJava(Map javaRuntimes, Path directory, boolean verify) { + if (!Files.isDirectory(directory)) + return; + // Examples: + // $HOME/Library/Application Support/minecraft/runtime/java-runtime-beta/mac-os/java-runtime-beta/jre.bundle/Contents/Home + // $HOME/.minecraft/runtime/java-runtime-beta/linux/java-runtime-beta + + String javaPlatform = getMojangJavaPlatform(Platform.SYSTEM_PLATFORM); + if (javaPlatform != null) { + searchAllOfficialJava(javaRuntimes, directory, javaPlatform, verify); + } + + if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) { + if (Architecture.SYSTEM_ARCH == Architecture.X86_64) { + searchAllOfficialJava(javaRuntimes, directory, getMojangJavaPlatform(Platform.WINDOWS_X86), verify); + } else if (Architecture.SYSTEM_ARCH == Architecture.ARM64) { + if (OperatingSystem.SYSTEM_BUILD_NUMBER >= 21277) { + searchAllOfficialJava(javaRuntimes, directory, getMojangJavaPlatform(Platform.WINDOWS_X86_64), verify); + } + 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); + } + } + + private static void searchAllOfficialJava(Map javaRuntimes, Path directory, String platform, boolean verify) { + try (DirectoryStream dir = Files.newDirectoryStream(directory)) { + // component can be jre-legacy, java-runtime-alpha, java-runtime-beta, java-runtime-gamma or any other being added in the future. + for (Path component : dir) { + tryAddJavaInComponentDir(javaRuntimes, platform, component, verify); + } + } catch (IOException e) { + LOG.warning("Failed to list java-runtime directory " + directory, e); + } + } + + private static void searchAllJavaInDirectory(Map javaRuntimes, Path directory) { + if (!Files.isDirectory(directory)) { + return; + } + + try (DirectoryStream stream = Files.newDirectoryStream(directory)) { + for (Path subDir : stream) { + tryAddJavaHome(javaRuntimes, subDir); + } + } catch (IOException e) { + LOG.warning("Failed to find Java in " + directory, e); + } + } + + private static void searchJavaInProgramFiles(Map javaRuntimes, String env, String defaultValue) { + String programFiles = Lang.requireNonNullElse(System.getenv(env), defaultValue); + Path path; + try { + path = Paths.get(programFiles); + } catch (InvalidPathException ignored) { + return; + } + + for (String vendor : new String[]{"Java", "BellSoft", "AdoptOpenJDK", "Zulu", "Microsoft", "Eclipse Foundation", "Semeru"}) { + searchAllJavaInDirectory(javaRuntimes, path.resolve(vendor)); + } + } + + // ==== Windows Registry Support ==== + private static void queryJavaInRegistryKey(Map javaRuntimes, String location) { + for (String java : querySubFolders(location)) { + if (!querySubFolders(java).contains(java + "\\MSI")) + continue; + String home = queryRegisterValue(java, "JavaHome"); + if (home != null) { + try { + tryAddJavaHome(javaRuntimes, Paths.get(home)); + } catch (InvalidPathException e) { + LOG.warning("Invalid Java path in system registry: " + home); + } + } + } + } + + private static List querySubFolders(String location) { + List 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; + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/java/JavaManifest.java b/HMCL/src/main/java/org/jackhuang/hmcl/java/JavaManifest.java new file mode 100644 index 0000000000..856b2a7195 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/java/JavaManifest.java @@ -0,0 +1,112 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.java; + +import com.google.gson.*; +import com.google.gson.annotations.JsonAdapter; +import org.jackhuang.hmcl.util.platform.Architecture; +import org.jackhuang.hmcl.util.platform.OperatingSystem; +import org.jackhuang.hmcl.util.platform.Platform; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Type; +import java.util.Map; +import java.util.Optional; + +import static org.jackhuang.hmcl.util.gson.JsonUtils.mapTypeOf; + +/** + * @author Glavo + */ +@JsonAdapter(JavaManifest.Serializer.class) +public final class JavaManifest { + + private final JavaInfo info; + + @Nullable + private final Map update; + + @Nullable + private final Map files; + + public JavaManifest(JavaInfo info, @Nullable Map update, @Nullable Map files) { + this.info = info; + this.update = update; + this.files = files; + } + + public JavaInfo getInfo() { + return info; + } + + public Map getUpdate() { + return update; + } + + public Map getFiles() { + return files; + } + + public static final class Serializer implements JsonSerializer, JsonDeserializer { + + private static final Type LOCAL_FILES_TYPE = mapTypeOf(String.class, JavaLocalFiles.Local.class).getType(); + + @Override + public JsonElement serialize(JavaManifest src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject res = new JsonObject(); + res.addProperty("os.name", src.getInfo().getPlatform().getOperatingSystem().getCheckedName()); + res.addProperty("os.arch", src.getInfo().getPlatform().getArchitecture().getCheckedName()); + res.addProperty("java.version", src.getInfo().getVersion()); + res.addProperty("java.vendor", src.getInfo().getVendor()); + + if (src.getUpdate() != null) + res.add("update", context.serialize(src.getUpdate())); + + if (src.getFiles() != null) + res.add("files", context.serialize(src.getFiles(), LOCAL_FILES_TYPE)); + + return res; + } + + @Override + public JavaManifest deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + if (!json.isJsonObject()) + throw new JsonParseException(json.toString()); + + try { + JsonObject jsonObject = json.getAsJsonObject(); + OperatingSystem osName = OperatingSystem.parseOSName(jsonObject.getAsJsonPrimitive("os.name").getAsString()); + Architecture osArch = Architecture.parseArchName(jsonObject.getAsJsonPrimitive("os.arch").getAsString()); + String javaVersion = jsonObject.getAsJsonPrimitive("java.version").getAsString(); + String javaVendor = Optional.ofNullable(jsonObject.get("java.vendor")).map(JsonElement::getAsString).orElse(null); + + Map update = jsonObject.has("update") ? context.deserialize(jsonObject.get("update"), Map.class) : null; + Map files = jsonObject.has("files") ? context.deserialize(jsonObject.get("files"), LOCAL_FILES_TYPE) : null; + + if (osName == null || osArch == null || javaVersion == null) + throw new JsonParseException(json.toString()); + + return new JavaManifest(new JavaInfo(Platform.getPlatform(osName, osArch), javaVersion, javaVendor), update, files); + } catch (JsonParseException e) { + throw e; + } catch (Throwable e) { + throw new JsonParseException(e); + } + } + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java index c96b56cfa4..009cf6b40e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java @@ -17,7 +17,6 @@ */ package org.jackhuang.hmcl.setting; -import com.google.gson.reflect.TypeToken; import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.property.ObjectProperty; @@ -54,17 +53,21 @@ import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating; import static org.jackhuang.hmcl.util.Lang.immutableListOf; import static org.jackhuang.hmcl.util.Lang.mapOf; -import static org.jackhuang.hmcl.util.logging.Logger.LOG; import static org.jackhuang.hmcl.util.Pair.pair; +import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf; +import static org.jackhuang.hmcl.util.gson.JsonUtils.mapTypeOf; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; /** * @author huangyuhui */ public final class Accounts { - private Accounts() {} + private Accounts() { + } private static final AuthlibInjectorArtifactProvider AUTHLIB_INJECTOR_DOWNLOADER = createAuthlibInjectorArtifactProvider(); + private static void triggerAuthlibInjectorUpdateCheck() { if (AUTHLIB_INJECTOR_DOWNLOADER instanceof AuthlibInjectorDownloader) { Schedulers.io().execute(() -> { @@ -87,6 +90,7 @@ private static void triggerAuthlibInjectorUpdateCheck() { // ==== login type / account factory mapping ==== private static final Map> type2factory = new HashMap<>(); private static final Map, String> factory2type = new HashMap<>(); + static { type2factory.put("offline", FACTORY_OFFLINE); type2factory.put("authlibInjector", FACTORY_AUTHLIB_INJECTOR); @@ -130,7 +134,7 @@ else if (account instanceof MicrosoftAccount) private static final String GLOBAL_PREFIX = "$GLOBAL:"; private static final ObservableList> globalAccountStorages = FXCollections.observableArrayList(); - private static final ObservableList accounts = observableArrayList(account -> new Observable[] { account }); + private static final ObservableList accounts = observableArrayList(account -> new Observable[]{account}); private static final ObjectProperty selectedAccount = new SimpleObjectProperty<>(Accounts.class, "selectedAccount"); /** @@ -168,14 +172,11 @@ private static void updateAccountStorages() { config().getAccountStorages().setAll(portable); } - @SuppressWarnings("unchecked") private static void loadGlobalAccountStorages() { Path globalAccountsFile = Metadata.HMCL_DIRECTORY.resolve("accounts.json"); if (Files.exists(globalAccountsFile)) { try (Reader reader = Files.newBufferedReader(globalAccountsFile)) { - globalAccountStorages.setAll((List>) - Config.CONFIG_GSON.fromJson(reader, new TypeToken>>() { - }.getType())); + globalAccountStorages.setAll(Config.CONFIG_GSON.fromJson(reader, listTypeOf(mapTypeOf(Object.class, Object.class)))); } catch (Throwable e) { LOG.warning("Failed to load global accounts", e); } @@ -465,6 +466,8 @@ public static String localizeErrorMessage(Exception exception) { } else { return i18n("account.methods.microsoft.error.unknown", errorCode); } + } else if (exception instanceof MicrosoftService.XBox400Exception) { + return i18n("account.methods.microsoft.error.wrong_verify_method"); } else if (exception instanceof MicrosoftService.NoMinecraftJavaEditionProfileException) { return i18n("account.methods.microsoft.error.no_character"); } else if (exception instanceof MicrosoftService.NoXuiException) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java index f9611ed483..b124c509a5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java @@ -164,7 +164,7 @@ public static Config fromJson(String json) throws JsonParseException { private StringProperty launcherFontFamily = new SimpleStringProperty(); @SerializedName("logLines") - private IntegerProperty logLines = new SimpleIntegerProperty(1000); + private ObjectProperty logLines = new SimpleObjectProperty<>(); @SerializedName("titleTransparent") private BooleanProperty titleTransparent = new SimpleBooleanProperty(false); @@ -573,15 +573,15 @@ public void setLauncherFontFamily(String launcherFontFamily) { this.launcherFontFamily.set(launcherFontFamily); } - public int getLogLines() { + public Integer getLogLines() { return logLines.get(); } - public void setLogLines(int logLines) { + public void setLogLines(Integer logLines) { this.logLines.set(logLines); } - public IntegerProperty logLinesProperty() { + public ObjectProperty logLinesProperty() { return logLines; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/GlobalConfig.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/GlobalConfig.java index 07153f3996..7979f17fde 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/GlobalConfig.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/GlobalConfig.java @@ -23,6 +23,8 @@ import javafx.beans.Observable; import javafx.beans.property.IntegerProperty; import javafx.beans.property.SimpleIntegerProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableSet; import org.jackhuang.hmcl.util.javafx.ObservableHelper; import org.jackhuang.hmcl.util.javafx.PropertyUtils; import org.jetbrains.annotations.Nullable; @@ -51,6 +53,10 @@ public static GlobalConfig fromJson(String json) throws JsonParseException { private final IntegerProperty logRetention = new SimpleIntegerProperty(); + private final ObservableSet userJava = FXCollections.observableSet(new LinkedHashSet<>()); + + private final ObservableSet disabledJava = FXCollections.observableSet(new LinkedHashSet<>()); + private final Map unknownFields = new HashMap<>(); private final transient ObservableHelper helper = new ObservableHelper(this); @@ -114,11 +120,21 @@ public void setLogRetention(int logRetention) { this.logRetention.set(logRetention); } + public ObservableSet getUserJava() { + return userJava; + } + + public ObservableSet getDisabledJava() { + return disabledJava; + } + public static final class Serializer implements JsonSerializer, JsonDeserializer { private static final Set knownFields = new HashSet<>(Arrays.asList( "agreementVersion", "platformPromptVersion", - "logRetention" + "logRetention", + "userJava", + "disabledJava" )); @Override @@ -131,6 +147,12 @@ public JsonElement serialize(GlobalConfig src, Type typeOfSrc, JsonSerialization jsonObject.add("agreementVersion", context.serialize(src.getAgreementVersion())); jsonObject.add("platformPromptVersion", context.serialize(src.getPlatformPromptVersion())); jsonObject.add("logRetention", context.serialize(src.getLogRetention())); + if (!src.getUserJava().isEmpty()) + jsonObject.add("userJava", context.serialize(src.getUserJava())); + + if (!src.getDisabledJava().isEmpty()) + jsonObject.add("disabledJava", context.serialize(src.getDisabledJava())); + for (Map.Entry entry : src.unknownFields.entrySet()) { jsonObject.add(entry.getKey(), context.serialize(entry.getValue())); } @@ -149,6 +171,20 @@ public GlobalConfig deserialize(JsonElement json, Type typeOfT, JsonDeserializat config.setPlatformPromptVersion(Optional.ofNullable(obj.get("platformPromptVersion")).map(JsonElement::getAsInt).orElse(0)); config.setLogRetention(Optional.ofNullable(obj.get("logRetention")).map(JsonElement::getAsInt).orElse(20)); + JsonElement userJava = obj.get("userJava"); + if (userJava != null && userJava.isJsonArray()) { + for (JsonElement element : userJava.getAsJsonArray()) { + config.userJava.add(element.getAsString()); + } + } + + JsonElement disabledJava = obj.get("disabledJava"); + if (disabledJava != null && disabledJava.isJsonArray()) { + for (JsonElement element : disabledJava.getAsJsonArray()) { + config.disabledJava.add(element.getAsString()); + } + } + for (Map.Entry entry : obj.entrySet()) { if (!knownFields.contains(entry.getKey())) { config.unknownFields.put(entry.getKey(), context.deserialize(entry.getValue(), Object.class)); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/JavaVersionType.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/JavaVersionType.java new file mode 100644 index 0000000000..e56df50da6 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/JavaVersionType.java @@ -0,0 +1,25 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.setting; + +/** + * @author Glavo + */ +public enum JavaVersionType { + DEFAULT, AUTO, VERSION, DETECTED, CUSTOM +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Theme.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Theme.java index a37f0cef2b..e5b116ad85 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Theme.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Theme.java @@ -24,9 +24,8 @@ import javafx.beans.binding.ObjectBinding; import javafx.scene.paint.Color; import javafx.scene.text.Font; -import org.jackhuang.hmcl.util.Lang; +import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.util.io.FileUtils; -import org.jackhuang.hmcl.util.io.IOUtils; import java.io.File; import java.io.IOException; @@ -36,11 +35,10 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Base64; import java.util.Locale; import java.util.Objects; import java.util.Optional; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import static org.jackhuang.hmcl.setting.ConfigHolder.config; import static org.jackhuang.hmcl.util.logging.Logger.LOG; @@ -58,34 +56,6 @@ public class Theme { Color.web("#B71C1C") // red }; - private static Charset cssCharset; - - private static Charset getCssCharset() { - if (cssCharset != null) - return cssCharset; - - Charset defaultCharset = Charset.defaultCharset(); - if (defaultCharset != StandardCharsets.UTF_8) { - // https://bugs.openjdk.org/browse/JDK-8279328 - // For JavaFX 17 or earlier, native encoding should be used - String jfxVersion = System.getProperty("javafx.version"); - if (jfxVersion != null) { - Matcher matcher = Pattern.compile("^(?[0-9]+)").matcher(jfxVersion); - if (matcher.find()) { - int v = Lang.parseInt(matcher.group(), -1); - if (v >= 18) { - cssCharset = StandardCharsets.UTF_8; - } - } - } - } - - if (cssCharset == null) - cssCharset = defaultCharset; - - return cssCharset; - } - @SuppressWarnings("OptionalUsedAsFieldOrParameterType") private static Optional font; @@ -145,10 +115,21 @@ public Color getForegroundColor() { return isLight() ? Color.BLACK : Color.WHITE; } + private static String rgba(Color color, double opacity) { + return String.format("rgba(%d, %d, %d, %.1f)", + (int) Math.ceil(color.getRed() * 256), + (int) Math.ceil(color.getGreen() * 256), + (int) Math.ceil(color.getBlue() * 256), + opacity); + } + public String[] getStylesheets(String overrideFontFamily) { String css = "/assets/css/blue.css"; - String fontFamily = System.getProperty("hmcl.font.override", overrideFontFamily); + String fontFamily = overrideFontFamily == null + ? System.getProperty("hmcl.font.override", System.getenv("HMCL_FONT")) + : overrideFontFamily; + String fontStyle = null; if (fontFamily == null) { Optional font = tryLoadFont(); @@ -160,30 +141,41 @@ public String[] getStylesheets(String overrideFontFamily) { if (fontFamily != null || !this.color.equalsIgnoreCase(BLUE.color)) { Color textFill = getForegroundColor(); - String fontCss = ""; + + 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) { - fontCss = "-fx-font-family: \"" + fontFamily + "\";"; + themeBuilder.append("-fx-font-family:\"").append(fontFamily).append("\";"); if (fontStyle != null && !fontStyle.isEmpty()) - fontCss += " -fx-font-style: \"" + fontStyle + "\";"; + themeBuilder.append("-fx-font-style:\"").append(fontStyle).append("\";"); } - try { - File temp = File.createTempFile("hmcl", ".css"); - String themeText = IOUtils.readFullyAsString(Theme.class.getResourceAsStream("/assets/css/custom.css")) - .replace("%base-color%", color) - .replace("%base-red%", Integer.toString((int) Math.ceil(paint.getRed() * 256))) - .replace("%base-green%", Integer.toString((int) Math.ceil(paint.getGreen() * 256))) - .replace("%base-blue%", Integer.toString((int) Math.ceil(paint.getBlue() * 256))) - .replace("%base-rippler-color%", String.format("rgba(%d, %d, %d, 0.3)", (int) Math.ceil(paint.getRed() * 256), (int) Math.ceil(paint.getGreen() * 256), (int) Math.ceil(paint.getBlue() * 256))) - .replace("%disabled-font-color%", String.format("rgba(%d, %d, %d, 0.7)", (int) Math.ceil(textFill.getRed() * 256), (int) Math.ceil(textFill.getGreen() * 256), (int) Math.ceil(textFill.getBlue() * 256))) - .replace("%font-color%", getColorDisplayName(getForegroundColor())) - .replace("%font%", fontCss); - FileUtils.writeText(temp, themeText, getCssCharset()); - temp.deleteOnExit(); - css = temp.toURI().toString(); - } catch (IOException | NullPointerException e) { - LOG.error("Unable to create theme stylesheet. Fallback to blue theme.", e); - } + 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"}; @@ -232,7 +224,7 @@ else if (name.startsWith("#")) } public static String getColorDisplayName(Color c) { - return c != null ? String.format("#%02x%02x%02x", Math.round(c.getRed() * 255.0D), Math.round(c.getGreen() * 255.0D), Math.round(c.getBlue() * 255.0D)).toUpperCase(Locale.ROOT) : null; + return c != null ? String.format("#%02X%02X%02X", Math.round(c.getRed() * 255.0D), Math.round(c.getGreen() * 255.0D), Math.round(c.getBlue() * 255.0D)) : null; } private static ObjectBinding FOREGROUND_FILL; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.java index 0d4feb2f8d..aa084d119b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.java @@ -22,29 +22,23 @@ import javafx.beans.InvalidationListener; import javafx.beans.property.*; import org.jackhuang.hmcl.game.*; -import org.jackhuang.hmcl.task.Schedulers; -import org.jackhuang.hmcl.task.Task; -import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.java.JavaManager; import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.StringUtils; -import org.jackhuang.hmcl.util.platform.Architecture; -import org.jackhuang.hmcl.util.platform.JavaVersion; +import org.jackhuang.hmcl.java.JavaRuntime; import org.jackhuang.hmcl.util.platform.OperatingSystem; -import org.jackhuang.hmcl.util.platform.Platform; import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import java.io.IOException; import java.lang.reflect.Type; import java.nio.file.InvalidPathException; import java.nio.file.Paths; -import java.util.List; -import java.util.Locale; -import java.util.Optional; -import java.util.concurrent.CancellationException; +import java.util.*; import java.util.stream.Collectors; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; + /** - * * @author huangyuhui */ @JsonAdapter(VersionSetting.Serializer.class) @@ -84,38 +78,43 @@ public void setUsesGlobal(boolean usesGlobal) { // java - private final StringProperty javaProperty = new SimpleStringProperty(this, "java", ""); + private final ObjectProperty javaVersionTypeProperty = new SimpleObjectProperty<>(this, "javaVersionType", JavaVersionType.AUTO); - public StringProperty javaProperty() { - return javaProperty; + public ObjectProperty javaVersionTypeProperty() { + return javaVersionTypeProperty; } - /** - * Java version or "Custom" if user customizes java directory, "Default" if the jvm that this app relies on. - */ - public String getJava() { - return javaProperty.get(); + public JavaVersionType getJavaVersionType() { + return javaVersionTypeProperty.get(); } - public void setJava(String java) { - javaProperty.set(java); + public void setJavaVersionType(JavaVersionType javaVersionType) { + javaVersionTypeProperty.set(javaVersionType); } - public boolean isUsesCustomJavaDir() { - return "Custom".equals(getJava()); + private final StringProperty javaVersionProperty = new SimpleStringProperty(this, "javaVersion", ""); + + public StringProperty javaVersionProperty() { + return javaVersionProperty; } - public void setUsesCustomJavaDir() { - setJava("Custom"); - setDefaultJavaPath(null); + public String getJavaVersion() { + return javaVersionProperty.get(); + } + + public void setJavaVersion(String java) { + javaVersionProperty.set(java); } - public boolean isJavaAutoSelected() { - return "Auto".equals(getJava()); + public void setUsesCustomJavaDir() { + setJavaVersionType(JavaVersionType.CUSTOM); + setJavaVersion(""); + setDefaultJavaPath(null); } public void setJavaAutoSelected() { - setJava("Auto"); + setJavaVersionType(JavaVersionType.AUTO); + setJavaVersion(""); setDefaultJavaPath(null); } @@ -644,58 +643,74 @@ public void setLauncherVisibility(LauncherVisibility launcherVisibility) { launcherVisibilityProperty.set(launcherVisibility); } - public Task getJavaVersion(GameVersionNumber gameVersion, Version version) { - return getJavaVersion(gameVersion, version, true); - } - - public Task getJavaVersion(GameVersionNumber gameVersion, Version version, boolean checkJava) { - return Task.runAsync(Schedulers.javafx(), () -> { - if (StringUtils.isBlank(getJava())) { - setJava(StringUtils.isBlank(getJavaDir()) ? "Auto" : "Custom"); + public JavaRuntime getJava(GameVersionNumber gameVersion, Version version) throws InterruptedException { + switch (getJavaVersionType()) { + case DEFAULT: + return JavaRuntime.getDefault(); + case AUTO: + return JavaManager.findSuitableJava(gameVersion, version); + case CUSTOM: + try { + return JavaManager.getJava(Paths.get(getJavaDir())); + } catch (IOException | InvalidPathException e) { + return null; // Custom Java not found + } + case VERSION: { + String javaVersion = getJavaVersion(); + if (StringUtils.isBlank(javaVersion)) { + return JavaManager.findSuitableJava(gameVersion, version); + } + + int majorVersion = -1; + try { + majorVersion = Integer.parseInt(javaVersion); + } catch (NumberFormatException ignored) { + } + + if (majorVersion < 0) { + LOG.warning("Invalid Java version: " + javaVersion); + return null; + } + + final int finalMajorVersion = majorVersion; + Collection allJava = JavaManager.getAllJava().stream() + .filter(it -> it.getParsedVersion() == finalMajorVersion) + .collect(Collectors.toList()); + return JavaManager.findSuitableJava(allJava, gameVersion, version); } - }).thenSupplyAsync(() -> { - try { - if ("Default".equals(getJava())) { - return JavaVersion.fromCurrentEnvironment(); - } else if (isJavaAutoSelected()) { - return JavaVersionConstraint.findSuitableJavaVersion(gameVersion, version); - } else if (isUsesCustomJavaDir()) { - try { - if (checkJava) - return JavaVersion.fromExecutable(Paths.get(getJavaDir())); - else - return new JavaVersion(Paths.get(getJavaDir()), "", Platform.getPlatform(OperatingSystem.CURRENT_OS, Architecture.UNKNOWN)); - } catch (IOException | InvalidPathException e) { - return null; // Custom Java Directory not found, + case DETECTED: { + String javaVersion = getJavaVersion(); + if (StringUtils.isBlank(javaVersion)) { + return JavaManager.findSuitableJava(gameVersion, version); + } + + try { + String defaultJavaPath = getDefaultJavaPath(); + if (StringUtils.isNotBlank(defaultJavaPath)) { + JavaRuntime java = JavaManager.getJava(Paths.get(defaultJavaPath).toRealPath()); + if (java != null && java.getVersion().equals(javaVersion)) { + return java; + } } - } else if (StringUtils.isNotBlank(getJava())) { - List matchedJava = JavaVersion.getJavas().stream() - .filter(java -> java.getVersion().equals(getJava())) - .collect(Collectors.toList()); - if (matchedJava.isEmpty()) { - FXUtils.runInFX(() -> setJava("Auto")); - return JavaVersion.fromCurrentEnvironment(); - } else { - return matchedJava.stream() - .filter(java -> java.getBinary().toString().equals(getDefaultJavaPath())) - .findFirst() - .orElse(matchedJava.get(0)); + } catch (IOException | InvalidPathException ignored) { + } + + for (JavaRuntime java : JavaManager.getAllJava()) { + if (java.getVersion().equals(javaVersion)) { + return java; } - } else throw new Error(); - } catch (InterruptedException e) { - throw new CancellationException(); - } - }); - } + } - public void setJavaVersion(JavaVersion java) { - setJava(java.getVersion()); - setDefaultJavaPath(java.getBinary().toString()); + return null; + } + default: + throw new AssertionError("JavaVersionType: " + getJavaVersionType()); + } } public void addPropertyChangedListener(InvalidationListener listener) { usesGlobalProperty.addListener(listener); - javaProperty.addListener(listener); + javaVersionProperty.addListener(listener); javaDirProperty.addListener(listener); wrapperProperty.addListener(listener); permSizeProperty.addListener(listener); @@ -733,7 +748,8 @@ public void addPropertyChangedListener(InvalidationListener listener) { public VersionSetting clone() { VersionSetting versionSetting = new VersionSetting(); versionSetting.setUsesGlobal(isUsesGlobal()); - versionSetting.setJava(getJava()); + versionSetting.setJavaVersionType(getJavaVersionType()); + versionSetting.setJavaVersion(getJavaVersion()); versionSetting.setDefaultJavaPath(getDefaultJavaPath()); versionSetting.setJavaDir(getJavaDir()); versionSetting.setWrapper(getWrapper()); @@ -787,7 +803,6 @@ public JsonElement serialize(VersionSetting src, Type typeOfSrc, JsonSerializati obj.addProperty("precalledCommand", src.getPreLaunchCommand()); obj.addProperty("postExitCommand", src.getPostExitCommand()); obj.addProperty("serverIp", src.getServerIp()); - obj.addProperty("java", src.getJava()); obj.addProperty("wrapper", src.getWrapper()); obj.addProperty("fullscreen", src.isFullscreen()); obj.addProperty("noJVMArgs", src.isNoJVMArgs()); @@ -806,6 +821,24 @@ public JsonElement serialize(VersionSetting src, Type typeOfSrc, JsonSerializati obj.addProperty("nativesDirType", src.getNativesDirType().ordinal()); obj.addProperty("versionIcon", src.getVersionIcon().ordinal()); + obj.addProperty("javaVersionType", src.getJavaVersionType().name()); + String java; + switch (src.getJavaVersionType()) { + case DEFAULT: + java = "Default"; + break; + case AUTO: + java = "Auto"; + break; + case CUSTOM: + java = "Custom"; + break; + default: + java = src.getJavaVersion(); + break; + } + obj.addProperty("java", java); + obj.addProperty("renderer", src.getRenderer().name()); if (src.getRenderer() == Renderer.LLVMPIPE) obj.addProperty("useSoftwareRenderer", true); @@ -846,7 +879,6 @@ public VersionSetting deserialize(JsonElement json, Type typeOfT, JsonDeserializ vs.setPreLaunchCommand(Optional.ofNullable(obj.get("precalledCommand")).map(JsonElement::getAsString).orElse("")); vs.setPostExitCommand(Optional.ofNullable(obj.get("postExitCommand")).map(JsonElement::getAsString).orElse("")); vs.setServerIp(Optional.ofNullable(obj.get("serverIp")).map(JsonElement::getAsString).orElse("")); - vs.setJava(Optional.ofNullable(obj.get("java")).map(JsonElement::getAsString).orElse("")); vs.setWrapper(Optional.ofNullable(obj.get("wrapper")).map(JsonElement::getAsString).orElse("")); vs.setGameDir(Optional.ofNullable(obj.get("gameDir")).map(JsonElement::getAsString).orElse("")); vs.setNativesDir(Optional.ofNullable(obj.get("nativesDir")).map(JsonElement::getAsString).orElse("")); @@ -865,6 +897,27 @@ public VersionSetting deserialize(JsonElement json, Type typeOfT, JsonDeserializ vs.setNativesDirType(getOrDefault(NativesDirectoryType.values(), obj.get("nativesDirType"), NativesDirectoryType.VERSION_FOLDER)); vs.setVersionIcon(getOrDefault(VersionIconType.values(), obj.get("versionIcon"), VersionIconType.DEFAULT)); + if (obj.get("javaVersionType") != null) { + JavaVersionType javaVersionType = parseJsonPrimitive(obj.getAsJsonPrimitive("javaVersionType"), JavaVersionType.class, JavaVersionType.AUTO); + vs.setJavaVersionType(javaVersionType); + vs.setJavaVersion(Optional.ofNullable(obj.get("java")).map(JsonElement::getAsString).orElse(null)); + } else { + String java = Optional.ofNullable(obj.get("java")).map(JsonElement::getAsString).orElse(""); + switch (java) { + case "Default": + vs.setJavaVersionType(JavaVersionType.DEFAULT); + break; + case "Auto": + vs.setJavaVersionType(JavaVersionType.AUTO); + break; + case "Custom": + vs.setJavaVersionType(JavaVersionType.CUSTOM); + break; + default: + vs.setJavaVersion(java); + } + } + vs.setRenderer(Optional.ofNullable(obj.get("renderer")).map(JsonElement::getAsString) .flatMap(name -> { try { @@ -892,5 +945,25 @@ else if (primitive.isNumber()) else return Lang.parseInt(primitive.getAsString(), defaultValue); } + + private > E parseJsonPrimitive(JsonPrimitive primitive, Class clazz, E defaultValue) { + if (primitive == null) + return defaultValue; + else { + E[] enumConstants = clazz.getEnumConstants(); + if (primitive.isNumber()) { + int index = primitive.getAsInt(); + return index >= 0 && index < enumConstants.length ? enumConstants[index] : defaultValue; + } else { + String name = primitive.getAsString(); + for (E enumConstant : enumConstants) { + if (enumConstant.name().equalsIgnoreCase(name)) { + return enumConstant; + } + } + return defaultValue; + } + } + } } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java index af9c01d4ad..44e208ac58 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java @@ -37,6 +37,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.setting.*; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.TaskExecutor; @@ -53,7 +54,6 @@ import org.jackhuang.hmcl.util.*; import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.platform.Architecture; -import org.jackhuang.hmcl.util.platform.JavaVersion; import org.jackhuang.hmcl.util.platform.OperatingSystem; import java.io.File; @@ -252,7 +252,7 @@ public static void initialize(Stage stage) { dialog(i18n("launcher.cache_directory.invalid")); } - Task.runAsync(JavaVersion::initialize).start(); + Lang.thread(JavaManager::initialize, "Search Java", true); scene = new Scene(decorator.getDecorator()); scene.setFill(Color.TRANSPARENT); @@ -391,9 +391,6 @@ public static void onHyperlinkAction(String href) { Controllers.getSettingsPage().showFeedback(); Controllers.navigate(Controllers.getSettingsPage()); break; - case "hmcl://hide-announcement": - Controllers.getRootPage().getMainPage().hideAnnouncementPane(); - break; } } else { FXUtils.openLink(href); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/CrashWindow.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/CrashWindow.java index c1f7158d44..5af27b6a60 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/CrashWindow.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/CrashWindow.java @@ -53,7 +53,7 @@ else if (UpdateChecker.isOutdated()) Button btnContact = new Button(); btnContact.setText(i18n("launcher.contact")); - btnContact.setOnMouseClicked(event -> FXUtils.openLink(Metadata.CONTACT_URL)); + btnContact.setOnAction(event -> FXUtils.openLink(Metadata.CONTACT_URL)); HBox box = new HBox(); box.setStyle("-fx-padding: 8px;"); box.getChildren().add(btnContact); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java index 0cb5c433f0..5d0d684731 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -23,14 +23,14 @@ import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.WeakInvalidationListener; +import javafx.beans.property.BooleanProperty; import javafx.beans.property.Property; -import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; -import javafx.beans.value.WeakChangeListener; -import javafx.beans.value.WritableValue; +import javafx.beans.value.*; import javafx.geometry.Insets; import javafx.geometry.Pos; +import javafx.scene.Cursor; import javafx.scene.Node; +import javafx.scene.control.Label; import javafx.scene.control.ScrollPane; import javafx.scene.control.*; import javafx.scene.image.Image; @@ -40,6 +40,7 @@ import javafx.scene.layout.Priority; import javafx.scene.layout.Region; import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import javafx.scene.text.Text; import javafx.scene.text.TextFlow; @@ -50,12 +51,12 @@ import org.glavo.png.PNGType; import org.glavo.png.PNGWriter; import org.glavo.png.javafx.PNGJavaFXUtils; -import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.animation.AnimationUtils; -import org.jackhuang.hmcl.ui.construct.JFXHyperlink; import org.jackhuang.hmcl.util.Holder; +import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.ResourceNotFoundError; +import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.javafx.ExtendedProperties; import org.jackhuang.hmcl.util.javafx.SafeStringConverter; @@ -67,26 +68,24 @@ import org.xml.sax.InputSource; import org.xml.sax.SAXException; -import javax.swing.*; -import javax.swing.event.HyperlinkEvent; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; -import java.awt.*; import java.io.*; import java.lang.ref.WeakReference; -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; import java.net.URI; import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.List; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BooleanSupplier; import java.util.function.Consumer; import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; import static org.jackhuang.hmcl.util.Lang.thread; @@ -98,6 +97,20 @@ public final class FXUtils { private FXUtils() { } + public static final int JAVAFX_MAJOR_VERSION; + + static { + String jfxVersion = System.getProperty("javafx.version"); + int majorVersion = -1; + if (jfxVersion != null) { + Matcher matcher = Pattern.compile("^(?[0-9]+)").matcher(jfxVersion); + if (matcher.find()) { + majorVersion = Lang.parseInt(matcher.group(), -1); + } + } + JAVAFX_MAJOR_VERSION = majorVersion; + } + public static final String DEFAULT_MONOSPACE_FONT = OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS ? "Consolas" : "Monospace"; private static final Map builtinImageCache = new ConcurrentHashMap<>(); @@ -300,8 +313,12 @@ public static void smoothScrolling(ScrollPane scrollPane) { ScrollUtils.addSmoothScrolling(scrollPane); } + private static final Duration TOOLTIP_FAST_SHOW_DELAY = Duration.millis(50); + private static final Duration TOOLTIP_SLOW_SHOW_DELAY = Duration.millis(500); + private static final Duration TOOLTIP_SHOW_DURATION = Duration.millis(5000); + public static void installFastTooltip(Node node, Tooltip tooltip) { - installTooltip(node, 50, 5000, 0, tooltip); + runInFX(() -> TooltipInstaller.INSTALLER.installTooltip(node, TOOLTIP_FAST_SHOW_DELAY, TOOLTIP_SHOW_DURATION, Duration.ZERO, tooltip)); } public static void installFastTooltip(Node node, String tooltip) { @@ -309,39 +326,13 @@ public static void installFastTooltip(Node node, String tooltip) { } public static void installSlowTooltip(Node node, Tooltip tooltip) { - installTooltip(node, 500, 5000, 0, tooltip); + runInFX(() -> TooltipInstaller.INSTALLER.installTooltip(node, TOOLTIP_SLOW_SHOW_DELAY, TOOLTIP_SHOW_DURATION, Duration.ZERO, tooltip)); } public static void installSlowTooltip(Node node, String tooltip) { installSlowTooltip(node, new Tooltip(tooltip)); } - public static void installTooltip(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip) { - runInFX(() -> { - try { - // Java 8 - Class behaviorClass = Class.forName("javafx.scene.control.Tooltip$TooltipBehavior"); - Constructor behaviorConstructor = behaviorClass.getDeclaredConstructor(Duration.class, Duration.class, Duration.class, boolean.class); - behaviorConstructor.setAccessible(true); - Object behavior = behaviorConstructor.newInstance(new Duration(openDelay), new Duration(visibleDelay), new Duration(closeDelay), false); - Method installMethod = behaviorClass.getDeclaredMethod("install", Node.class, Tooltip.class); - installMethod.setAccessible(true); - installMethod.invoke(behavior, node, tooltip); - } catch (ReflectiveOperationException e) { - try { - // Java 9 - Tooltip.class.getMethod("setShowDelay", Duration.class).invoke(tooltip, new Duration(openDelay)); - Tooltip.class.getMethod("setShowDuration", Duration.class).invoke(tooltip, new Duration(visibleDelay)); - Tooltip.class.getMethod("setHideDelay", Duration.class).invoke(tooltip, new Duration(closeDelay)); - } catch (ReflectiveOperationException e2) { - e.addSuppressed(e2); - LOG.error("Cannot install tooltip", e); - } - Tooltip.install(node, tooltip); - } - }); - } - public static void playAnimation(Node node, String animationKey, Timeline timeline) { animationKey = "FXUTILS.ANIMATION." + animationKey; Object oldTimeline = node.getProperties().get(animationKey); @@ -408,6 +399,22 @@ else if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() && new File("/usr/bin/xdg-ope }); } + 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(); @@ -416,6 +423,16 @@ public static void showFileInExplorer(Path file) { openCommands = new String[]{"explorer.exe", "/select,", path}; else if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX) openCommands = new String[]{"/usr/bin/open", "-R", path}; + else if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() && which("dbus-send") != null) + openCommands = new String[]{ + "dbus-send", + "--print-reply", + "--dest=org.freedesktop.FileManager1", + "/org/freedesktop/FileManager1", + "org.freedesktop.FileManager1.ShowItems", + "array:string:" + file.toAbsolutePath().toUri(), + "string:" + }; else openCommands = null; @@ -472,12 +489,13 @@ public static void openLink(String link) { } if (OperatingSystem.CURRENT_OS.isLinuxOrBSD()) { for (String browser : linuxBrowsers) { - try (final InputStream is = Runtime.getRuntime().exec(new String[]{"which", browser}).getInputStream()) { - if (is.read() != -1) { + String path = which(browser); + if (path != null) { + try { Runtime.getRuntime().exec(new String[]{browser, link}); return; + } catch (Throwable ignored) { } - } catch (Throwable ignored) { } LOG.warning("No known browser found"); } @@ -496,52 +514,6 @@ public static void openLink(String link) { }); } - public static void showWebDialog(String title, String content) { - showWebDialog(title, content, 800, 480); - } - - public static void showWebDialog(String title, String content, int width, int height) { - try { - WebStage stage = new WebStage(width, height); - stage.getWebView().getEngine().loadContent(content); - stage.setTitle(title); - stage.showAndWait(); - } catch (NoClassDefFoundError | UnsatisfiedLinkError e) { - LOG.warning("WebView is missing or initialization failed, use JEditorPane replaced", e); - - SwingUtils.initLookAndFeel(); - SwingUtilities.invokeLater(() -> { - final JFrame frame = new JFrame(title); - frame.setSize(width, height); - frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); - frame.setLocationByPlatform(true); - frame.setIconImage(new ImageIcon(FXUtils.class.getResource("/assets/img/icon.png")).getImage()); - frame.setLayout(new BorderLayout()); - - final JProgressBar progressBar = new JProgressBar(); - progressBar.setIndeterminate(true); - frame.add(progressBar, BorderLayout.PAGE_START); - - Schedulers.defaultScheduler().execute(() -> { - final JEditorPane pane = new JEditorPane("text/html", content); - pane.setEditable(false); - pane.addHyperlinkListener(event -> { - if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { - openLink(event.getURL().toExternalForm()); - } - }); - SwingUtilities.invokeLater(() -> { - progressBar.setVisible(false); - frame.add(new JScrollPane(pane), BorderLayout.CENTER); - }); - }); - - frame.setVisible(true); - frame.toFront(); - }); - } - } - public static void bind(JFXTextField textField, Property property, StringConverter converter) { textField.setText(converter == null ? (String) property.getValue() : converter.toString(property.getValue())); TextFieldBindingListener listener = new TextFieldBindingListener<>(textField, property, converter); @@ -676,6 +648,61 @@ public static void unbindEnum(JFXComboBox> comboBox) { comboBox.getSelectionModel().selectedIndexProperty().removeListener(listener); } + public static void bindAllEnabled(BooleanProperty allEnabled, BooleanProperty... children) { + int itemCount = children.length; + int childSelectedCount = 0; + for (BooleanProperty child : children) { + if (child.get()) + childSelectedCount++; + } + + allEnabled.set(childSelectedCount == itemCount); + + class Listener implements InvalidationListener { + private int childSelectedCount; + private boolean updating = false; + + public Listener(int childSelectedCount) { + this.childSelectedCount = childSelectedCount; + } + + @Override + public void invalidated(Observable observable) { + if (updating) + return; + + updating = true; + try { + boolean value = ((BooleanProperty) observable).get(); + + if (observable == allEnabled) { + for (BooleanProperty child : children) { + child.setValue(value); + } + childSelectedCount = value ? itemCount : 0; + } else { + if (value) + childSelectedCount++; + else + childSelectedCount--; + + allEnabled.set(childSelectedCount == itemCount); + } + } finally { + updating = false; + } + } + } + + InvalidationListener listener = new Listener(childSelectedCount); + + WeakInvalidationListener weakListener = new WeakInvalidationListener(listener); + allEnabled.addListener(listener); + for (BooleanProperty child : children) { + child.addListener(weakListener); + } + } + public static void setIcon(Stage stage) { String icon; if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) { @@ -815,6 +842,17 @@ public static JFXButton newBorderButton(String text) { return button; } + public static Label truncatedLabel(String text, int limit) { + Label label = new Label(); + if (text.length() <= limit) { + label.setText(text); + } else { + label.setText(StringUtils.truncate(text, limit)); + installFastTooltip(label, text); + } + return label; + } + public static void applyDragListener(Node node, FileFilter filter, Consumer> callback) { applyDragListener(node, filter, callback, null); } @@ -912,6 +950,15 @@ public static void onEscPressed(Node node, Runnable action) { }); } + public static void onClicked(Node node, Runnable action) { + node.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> { + if (e.getButton() == MouseButton.PRIMARY && e.getClickCount() == 1) { + action.run(); + e.consume(); + } + }); + } + public static void copyText(String text) { ClipboardContent content = new ClipboardContent(); content.putString(text); @@ -933,7 +980,7 @@ public static List parseSegment(String segment, Consumer hyperlink Element r = doc.getDocumentElement(); NodeList children = r.getChildNodes(); - List texts = new ArrayList<>(); + List texts = new ArrayList<>(); for (int i = 0; i < children.getLength(); i++) { org.w3c.dom.Node node = children.item(i); @@ -941,8 +988,8 @@ public static List parseSegment(String segment, Consumer hyperlink Element element = (Element) node; if ("a".equals(element.getTagName())) { String href = element.getAttribute("href"); - JFXHyperlink hyperlink = new JFXHyperlink(element.getTextContent()); - hyperlink.setOnAction(e -> { + Text text = new Text(element.getTextContent()); + onClicked(text, () -> { String link = href; try { link = new URI(href).toASCIIString(); @@ -950,7 +997,10 @@ public static List parseSegment(String segment, Consumer hyperlink } hyperlinkAction.accept(link); }); - texts.add(hyperlink); + text.setCursor(Cursor.HAND); + text.setFill(Color.web("#0070E0")); + text.setUnderline(true); + texts.add(text); } else if ("b".equals(element.getTagName())) { Text text = new Text(element.getTextContent()); text.getStyleClass().add("bold"); @@ -977,4 +1027,11 @@ public static TextFlow segmentToTextFlow(final String segment, Consumer return tf; } + public static String toWeb(Color color) { + int r = (int) Math.round(color.getRed() * 255.0); + int g = (int) Math.round(color.getGreen() * 255.0); + int b = (int) Math.round(color.getBlue() * 255.0); + + return String.format("#%02x%02x%02x", r, g, b); + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/GameCrashWindow.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/GameCrashWindow.java index 327d17a308..d3d74d24b0 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/GameCrashWindow.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/GameCrashWindow.java @@ -20,8 +20,6 @@ import com.jfoenix.controls.JFXButton; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; -import javafx.beans.property.SimpleStringProperty; -import javafx.beans.property.StringProperty; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Node; @@ -43,6 +41,7 @@ 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.logging.Logger; import org.jackhuang.hmcl.util.Pair; @@ -66,7 +65,6 @@ import java.util.stream.Collectors; import static org.jackhuang.hmcl.setting.ConfigHolder.config; -import static org.jackhuang.hmcl.ui.FXUtils.runInFX; import static org.jackhuang.hmcl.util.logging.Logger.LOG; import static org.jackhuang.hmcl.util.Pair.pair; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; @@ -77,8 +75,6 @@ public class GameCrashWindow extends Stage { private final String total_memory; private final String java; private final LibraryAnalyzer analyzer; - private final StringProperty os = new SimpleStringProperty(OperatingSystem.SYSTEM_NAME); - private final StringProperty arch = new SimpleStringProperty(Architecture.SYSTEM_ARCH.getDisplayName()); private final TextFlow reasonTextFlow = new TextFlow(new Text(i18n("game.crash.reason.unknown"))); private final BooleanProperty loading = new SimpleBooleanProperty(); private final TextFlow feedbackTextFlow = new TextFlow(); @@ -89,9 +85,9 @@ public class GameCrashWindow extends Stage { private final LaunchOptions launchOptions; private final View view; - private final Collection> logs; + private final List logs; - public GameCrashWindow(ManagedProcess managedProcess, ProcessListener.ExitType exitType, DefaultGameRepository repository, Version version, LaunchOptions launchOptions, Collection> logs) { + public GameCrashWindow(ManagedProcess managedProcess, ProcessListener.ExitType exitType, DefaultGameRepository repository, Version version, LaunchOptions launchOptions, List logs) { this.managedProcess = managedProcess; this.exitType = exitType; this.repository = repository; @@ -124,7 +120,7 @@ public GameCrashWindow(ManagedProcess managedProcess, ProcessListener.ExitType e private void analyzeCrashReport() { loading.set(true); Task.allOf(Task.supplyAsync(() -> { - String rawLog = logs.stream().map(Pair::getKey).collect(Collectors.joining("\n")); + String rawLog = logs.stream().map(Log::getLog).collect(Collectors.joining("\n")); // Get the crash-report from the crash-reports/xxx, or the output of console. String crashReport = null; @@ -264,20 +260,18 @@ private String parseFabricModId(String modName) { private void showLogWindow() { LogWindow logWindow = new LogWindow(managedProcess); - logWindow.logLine(Logger.filterForbiddenToken("Command: " + new CommandBuilder().addAll(managedProcess.getCommands())), Log4jLevel.INFO); + logWindow.logLine(new Log(Logger.filterForbiddenToken("Command: " + new CommandBuilder().addAll(managedProcess.getCommands())), Log4jLevel.INFO)); if (managedProcess.getClasspath() != null) - logWindow.logLine("ClassPath: " + managedProcess.getClasspath(), Log4jLevel.INFO); - for (Map.Entry entry : logs) - logWindow.logLine(entry.getKey(), entry.getValue()); - - logWindow.showNormal(); + logWindow.logLine(new Log("ClassPath: " + managedProcess.getClasspath(), Log4jLevel.INFO)); + logWindow.logLines(logs); + logWindow.show(); } private void exportGameCrashInfo() { 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(Pair::getKey).collect(Collectors.joining(OperatingSystem.LINE_SEPARATOR))) + logs.stream().map(Log::getLog).collect(Collectors.joining(OperatingSystem.LINE_SEPARATOR))) .thenComposeAsync(logs -> LogExporter.exportLogs(logFile, repository, launchOptions.getVersionName(), logs, new CommandBuilder().addAll(managedProcess.getCommands()).toString())) .handleAsync((result, exception) -> { @@ -337,7 +331,7 @@ private final class View extends VBox { TwoLineListItem version = new TwoLineListItem(); version.getStyleClass().setAll("two-line-item-second-large"); - version.setTitle(i18n("archive.game_version")); + version.setTitle(i18n("game.version")); version.setSubtitle(GameCrashWindow.this.version.getId()); TwoLineListItem total_memory = new TwoLineListItem(); @@ -358,12 +352,12 @@ private final class View extends VBox { TwoLineListItem os = new TwoLineListItem(); os.getStyleClass().setAll("two-line-item-second-large"); os.setTitle(i18n("system.operating_system")); - os.subtitleProperty().bind(GameCrashWindow.this.os); + os.setSubtitle(Lang.requireNonNullElse(OperatingSystem.OS_RELEASE_NAME, OperatingSystem.SYSTEM_NAME)); TwoLineListItem arch = new TwoLineListItem(); arch.getStyleClass().setAll("two-line-item-second-large"); arch.setTitle(i18n("system.architecture")); - arch.subtitleProperty().bind(GameCrashWindow.this.arch); + arch.setSubtitle(Architecture.SYSTEM_ARCH.getDisplayName()); infoPane.getChildren().setAll(launcher, version, total_memory, memory, java, os, arch); } @@ -392,13 +386,13 @@ private final class View extends VBox { gameDir.getStyleClass().setAll("two-line-item-second-large"); gameDir.setTitle(i18n("game.directory")); gameDir.setSubtitle(launchOptions.getGameDir().getAbsolutePath()); - runInFX(() -> FXUtils.installFastTooltip(gameDir, i18n("game.directory"))); + FXUtils.installFastTooltip(gameDir, i18n("game.directory")); TwoLineListItem javaDir = new TwoLineListItem(); javaDir.getStyleClass().setAll("two-line-item-second-large"); javaDir.setTitle(i18n("settings.game.java_directory")); javaDir.setSubtitle(launchOptions.getJava().getBinary().toAbsolutePath().toString()); - runInFX(() -> FXUtils.installFastTooltip(javaDir, i18n("settings.game.java_directory"))); + FXUtils.installFastTooltip(javaDir, i18n("settings.game.java_directory")); Label reasonTitle = new Label(i18n("game.crash.reason")); reasonTitle.getStyleClass().add("two-line-item-second-large-title"); @@ -422,14 +416,14 @@ private final class View extends VBox { HBox toolBar = new HBox(); { JFXButton exportGameCrashInfoButton = FXUtils.newRaisedButton(i18n("logwindow.export_game_crash_logs")); - exportGameCrashInfoButton.setOnMouseClicked(e -> exportGameCrashInfo()); + exportGameCrashInfoButton.setOnAction(e -> exportGameCrashInfo()); JFXButton logButton = FXUtils.newRaisedButton(i18n("logwindow.title")); - logButton.setOnMouseClicked(e -> showLogWindow()); + logButton.setOnAction(e -> showLogWindow()); JFXButton helpButton = FXUtils.newRaisedButton(i18n("help")); helpButton.setOnAction(e -> FXUtils.openLink("https://docs.hmcl.net/help.html")); - runInFX(() -> FXUtils.installFastTooltip(helpButton, i18n("logwindow.help"))); + FXUtils.installFastTooltip(helpButton, i18n("logwindow.help")); toolBar.setPadding(new Insets(8)); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/HTMLRenderer.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/HTMLRenderer.java new file mode 100644 index 0000000000..dccedf77f2 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/HTMLRenderer.java @@ -0,0 +1,266 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.ui; + +import javafx.scene.Cursor; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.text.Text; +import javafx.scene.text.TextFlow; +import org.jsoup.nodes.Node; +import org.jsoup.nodes.TextNode; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import static org.jackhuang.hmcl.util.logging.Logger.LOG; + +/** + * @author Glavo + */ +public final class HTMLRenderer { + private static URI resolveLink(Node linkNode) { + String href = linkNode.absUrl("href"); + if (href.isEmpty()) + return null; + + try { + return new URI(href); + } catch (Throwable e) { + return null; + } + } + + private final List children = new ArrayList<>(); + private final List stack = new ArrayList<>(); + + private boolean bold; + private boolean italic; + private boolean underline; + private boolean strike; + private boolean highlight; + private String headerLevel; + private Node hyperlink; + + private final Consumer onClickHyperlink; + + public HTMLRenderer(Consumer onClickHyperlink) { + this.onClickHyperlink = onClickHyperlink; + } + + private void updateStyle() { + bold = false; + italic = false; + underline = false; + strike = false; + highlight = false; + headerLevel = null; + hyperlink = null; + + for (Node node : stack) { + String nodeName = node.nodeName(); + switch (nodeName) { + case "b": + case "strong": + bold = true; + break; + case "i": + case "em": + italic = true; + break; + case "ins": + underline = true; + break; + case "del": + strike = true; + break; + case "mark": + highlight = true; + break; + case "a": + hyperlink = node; + break; + case "h1": + case "h2": + case "h3": + case "h4": + case "h5": + case "h6": + headerLevel = nodeName; + break; + } + } + } + + private void pushNode(Node node) { + stack.add(node); + updateStyle(); + } + + private void popNode() { + stack.remove(stack.size() - 1); + updateStyle(); + } + + private void applyStyle(Text text) { + if (hyperlink != null) { + URI target = resolveLink(hyperlink); + if (target != null) { + FXUtils.onClicked(text, () -> onClickHyperlink.accept(target)); + text.setCursor(Cursor.HAND); + } + text.getStyleClass().add("html-hyperlink"); + } + + if (hyperlink != null || underline) + text.setUnderline(true); + + if (strike) + text.setStrikethrough(true); + + if (bold || highlight) + text.getStyleClass().add("html-bold"); + + if (italic) + text.getStyleClass().add("html-italic"); + + if (headerLevel != null) + text.getStyleClass().add("html-" + headerLevel); + } + + private void appendText(String text) { + Text textNode = new Text(text); + applyStyle(textNode); + children.add(textNode); + } + + private void appendImage(Node node) { + String src = node.absUrl("src"); + URI imageUri = null; + try { + if (!src.isEmpty()) + imageUri = URI.create(src); + } catch (Exception ignored) { + } + + String alt = node.attr("alt"); + + if (imageUri != null) { + URI uri = URI.create(src); + + String widthAttr = node.attr("width"); + String heightAttr = node.attr("height"); + + double width = 0; + double height = 0; + + if (!widthAttr.isEmpty() && !heightAttr.isEmpty()) { + try { + width = Double.parseDouble(widthAttr); + height = Double.parseDouble(heightAttr); + } catch (NumberFormatException ignored) { + } + + if (width <= 0 || height <= 0) { + width = 0; + height = 0; + } + } + + Image image = FXUtils.newRemoteImage(uri.toString(), width, height, true, true, false); + if (image.isError()) { + LOG.warning("Failed to load image: " + uri, image.getException()); + } else { + ImageView imageView = new ImageView(image); + if (hyperlink != null) { + URI target = resolveLink(hyperlink); + if (target != null) { + FXUtils.onClicked(imageView, () -> onClickHyperlink.accept(target)); + imageView.setCursor(Cursor.HAND); + } + } + children.add(imageView); + return; + } + } + + if (!alt.isEmpty()) + appendText(alt); + } + + public void appendNode(Node node) { + if (node instanceof TextNode) { + appendText(((TextNode) node).text()); + } + + String name = node.nodeName(); + switch (name) { + case "img": + appendImage(node); + break; + case "li": + appendText("\n \u2022 "); + break; + case "dt": + appendText(" "); + break; + case "p": + case "h1": + case "h2": + case "h3": + case "h4": + case "h5": + case "h6": + case "tr": + if (!children.isEmpty()) + appendText("\n\n"); + break; + } + + if (node.childNodeSize() > 0) { + pushNode(node); + for (Node childNode : node.childNodes()) { + appendNode(childNode); + } + popNode(); + } + + switch (name) { + case "br": + case "dd": + case "p": + case "h1": + case "h2": + case "h3": + case "h4": + case "h5": + case "h6": + appendText("\n"); + break; + } + } + + public TextFlow render() { + TextFlow textFlow = new TextFlow(); + textFlow.getStyleClass().add("html"); + textFlow.getChildren().setAll(children); + return textFlow; + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java index b6cf2a2de9..17224a8823 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java @@ -18,11 +18,11 @@ package org.jackhuang.hmcl.ui; import com.jfoenix.controls.JFXButton; -import javafx.beans.InvalidationListener; +import javafx.beans.Observable; import javafx.beans.binding.Bindings; -import javafx.beans.property.*; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; import javafx.css.PseudoClass; -import javafx.event.EventHandler; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Cursor; @@ -32,7 +32,7 @@ import javafx.scene.control.Skin; import javafx.scene.control.SkinBase; import javafx.scene.image.ImageView; -import javafx.scene.input.MouseEvent; +import javafx.scene.input.MouseButton; import javafx.scene.layout.*; import org.jackhuang.hmcl.download.LibraryAnalyzer; import org.jackhuang.hmcl.setting.Theme; @@ -55,29 +55,77 @@ public class InstallerItem extends Control { private final String id; private final VersionIconType iconType; - public final StringProperty libraryVersion = new SimpleStringProperty(); - public final StringProperty incompatibleLibraryName = new SimpleStringProperty(); - public final StringProperty dependencyName = new SimpleStringProperty(); - public final BooleanProperty incompatibleWithGame = new SimpleBooleanProperty(); - public final BooleanProperty removable = new SimpleBooleanProperty(); - public final BooleanProperty upgradable = new SimpleBooleanProperty(false); - public final BooleanProperty installable = new SimpleBooleanProperty(true); - public final ObjectProperty> removeAction = new SimpleObjectProperty<>(); - public final ObjectProperty> action = new SimpleObjectProperty<>(); - - private Style style = Style.LIST_ITEM; + private final Style style; + private final ObjectProperty versionProperty = new SimpleObjectProperty<>(this, "version", null); + private final ObjectProperty resolvedStateProperty = new SimpleObjectProperty<>(this, "resolvedState", InstallableState.INSTANCE); + + private final ObjectProperty onInstall = new SimpleObjectProperty<>(this, "onInstall"); + private final ObjectProperty onRemove = new SimpleObjectProperty<>(this, "onRemove"); + + public interface State { + } + + public static final class InstallableState implements State { + public static final InstallableState INSTANCE = new InstallableState(); + + private InstallableState() { + } + } + + public static final class IncompatibleState implements State { + private final String incompatibleItemName; + private final String incompatibleItemVersion; + + public IncompatibleState(String incompatibleItemName, String incompatibleItemVersion) { + this.incompatibleItemName = incompatibleItemName; + this.incompatibleItemVersion = incompatibleItemVersion; + } + + public String getIncompatibleItemName() { + return incompatibleItemName; + } + + public String getIncompatibleItemVersion() { + return incompatibleItemVersion; + } + } + + public static final class InstalledState implements State { + private final String version; + private final boolean external; + private final boolean incompatibleWithGame; + + public InstalledState(String version, boolean external, boolean incompatibleWithGame) { + this.version = version; + this.external = external; + this.incompatibleWithGame = incompatibleWithGame; + } + + public String getVersion() { + return version; + } + + public boolean isExternal() { + return external; + } + + public boolean isIncompatibleWithGame() { + return incompatibleWithGame; + } + } public enum Style { LIST_ITEM, CARD, } - public InstallerItem(LibraryAnalyzer.LibraryType id) { - this(id.getPatchId()); + public InstallerItem(LibraryAnalyzer.LibraryType id, Style style) { + this(id.getPatchId(), style); } - public InstallerItem(String id) { + public InstallerItem(String id, Style style) { this.id = id; + this.style = style; switch (id) { case "game": @@ -109,18 +157,40 @@ public InstallerItem(String id) { } } - public void setStyleMode(Style style) { - this.style = style; + public String getLibraryId() { + return id; } - public void setState(String libraryVersion, boolean incompatibleWithGame, boolean removable) { - this.libraryVersion.set(libraryVersion); - this.incompatibleWithGame.set(incompatibleWithGame); - this.removable.set(removable); + public ObjectProperty versionProperty() { + return versionProperty; } - public String getLibraryId() { - return id; + public ObjectProperty resolvedStateProperty() { + return resolvedStateProperty; + } + + public ObjectProperty onInstallProperty() { + return onInstall; + } + + public Runnable getOnInstall() { + return onInstall.get(); + } + + public void setOnInstall(Runnable onInstall) { + this.onInstall.set(onInstall); + } + + public ObjectProperty onRemoveProperty() { + return onRemove; + } + + public Runnable getOnRemove() { + return onRemove.get(); + } + + public void setOnRemove(Runnable onRemove) { + this.onRemove.set(onRemove); } @Override @@ -129,35 +199,25 @@ protected Skin createDefaultSkin() { } public final static class InstallerItemGroup { - public final InstallerItem game = new InstallerItem(MINECRAFT); - public final InstallerItem fabric = new InstallerItem(FABRIC); - public final InstallerItem fabricApi = new InstallerItem(FABRIC_API); - public final InstallerItem forge = new InstallerItem(FORGE); - public final InstallerItem neoForge = new InstallerItem(NEO_FORGE); - public final InstallerItem liteLoader = new InstallerItem(LITELOADER); - public final InstallerItem optiFine = new InstallerItem(OPTIFINE); - public final InstallerItem quilt = new InstallerItem(QUILT); - public final InstallerItem quiltApi = new InstallerItem(QUILT_API); + private final InstallerItem game; private final InstallerItem[] libraries; - private final HashMap> incompatibleMap = new HashMap<>(); - - private Set getIncompatibles(InstallerItem item) { + private Set getIncompatibles(Map> incompatibleMap, InstallerItem item) { return incompatibleMap.computeIfAbsent(item, it -> new HashSet<>()); } - private void addIncompatibles(InstallerItem item, InstallerItem... others) { - Set set = getIncompatibles(item); + private void addIncompatibles(Map> incompatibleMap, InstallerItem item, InstallerItem... others) { + Set set = getIncompatibles(incompatibleMap, item); for (InstallerItem other : others) { set.add(other); - getIncompatibles(other).add(item); + getIncompatibles(incompatibleMap, other).add(item); } } - private void mutualIncompatible(InstallerItem... items) { + private void mutualIncompatible(Map> incompatibleMap, InstallerItem... items) { for (InstallerItem item : items) { - Set set = getIncompatibles(item); + Set set = getIncompatibles(incompatibleMap, item); for (InstallerItem item2 : items) { if (item2 != item) { @@ -167,43 +227,66 @@ private void mutualIncompatible(InstallerItem... items) { } } - public InstallerItemGroup(String gameVersion) { - mutualIncompatible(forge, fabric, quilt, neoForge, liteLoader); - addIncompatibles(optiFine, fabric, quilt, neoForge); - addIncompatibles(fabricApi, forge, quiltApi, neoForge, liteLoader, optiFine); - addIncompatibles(quiltApi, forge, fabric, fabricApi, neoForge, liteLoader, optiFine); - - InvalidationListener listener = o -> { - for (Map.Entry> entry : incompatibleMap.entrySet()) { - InstallerItem item = entry.getKey(); - - String incompatibleId = null; - for (InstallerItem other : entry.getValue()) { - if (other.libraryVersion.get() != null) { - incompatibleId = other.id; - break; + public InstallerItemGroup(String gameVersion, Style style) { + game = new InstallerItem(MINECRAFT, style); + InstallerItem fabric = new InstallerItem(FABRIC, style); + InstallerItem fabricApi = new InstallerItem(FABRIC_API, style); + InstallerItem forge = new InstallerItem(FORGE, style); + InstallerItem neoForge = new InstallerItem(NEO_FORGE, style); + InstallerItem liteLoader = new InstallerItem(LITELOADER, style); + InstallerItem optiFine = new InstallerItem(OPTIFINE, style); + InstallerItem quilt = new InstallerItem(QUILT, style); + InstallerItem quiltApi = new InstallerItem(QUILT_API, style); + + Map> incompatibleMap = new HashMap<>(); + mutualIncompatible(incompatibleMap, forge, fabric, quilt, neoForge); + addIncompatibles(incompatibleMap, liteLoader, fabric, quilt, neoForge); + addIncompatibles(incompatibleMap, optiFine, fabric, quilt, neoForge); + addIncompatibles(incompatibleMap, fabricApi, forge, quiltApi, neoForge, liteLoader, optiFine); + addIncompatibles(incompatibleMap, quiltApi, forge, fabric, fabricApi, neoForge, liteLoader, optiFine); + + for (Map.Entry> entry : incompatibleMap.entrySet()) { + InstallerItem item = entry.getKey(); + Set incompatibleItems = entry.getValue(); + + Observable[] bindings = new Observable[incompatibleItems.size() + 1]; + bindings[0] = item.versionProperty; + int i = 1; + for (InstallerItem other : incompatibleItems) { + bindings[i++] = other.versionProperty; + } + + item.resolvedStateProperty.bind(Bindings.createObjectBinding(() -> { + InstalledState itemVersion = item.versionProperty.get(); + if (itemVersion != null) { + return itemVersion; + } + + for (InstallerItem other : incompatibleItems) { + InstalledState otherVersion = other.versionProperty.get(); + if (otherVersion != null) { + return new IncompatibleState(other.id, otherVersion.version); } } - item.incompatibleLibraryName.set(incompatibleId); - } - }; - for (InstallerItem item : incompatibleMap.keySet()) { - item.libraryVersion.addListener(listener); + return InstallableState.INSTANCE; + }, bindings)); + } + + if (gameVersion != null) { + game.versionProperty.set(new InstalledState(gameVersion, false, false)); } - fabricApi.dependencyName.bind(Bindings.createStringBinding(() -> { - if (fabric.libraryVersion.get() == null) return FABRIC.getPatchId(); - else return null; - }, fabric.libraryVersion)); + InstallerItem[] all = {game, forge, neoForge, liteLoader, optiFine, fabric, fabricApi, quilt, quiltApi}; - quiltApi.dependencyName.bind(Bindings.createStringBinding(() -> { - if (quilt.libraryVersion.get() == null) return QUILT.getPatchId(); - else return null; - }, quilt.libraryVersion)); + for (InstallerItem item : all) { + if (!item.resolvedStateProperty.isBound()) { + item.resolvedStateProperty.bind(item.versionProperty); + } + } if (gameVersion == null) { - this.libraries = new InstallerItem[]{game, forge, neoForge, liteLoader, optiFine, fabric, fabricApi, quilt, quiltApi}; + this.libraries = all; } else if (GameVersionNumber.compare(gameVersion, "1.13") < 0) { this.libraries = new InstallerItem[]{game, forge, liteLoader, optiFine}; } else { @@ -211,15 +294,19 @@ public InstallerItemGroup(String gameVersion) { } } + public InstallerItem getGame() { + return game; + } + public InstallerItem[] getLibraries() { return libraries; } } - public static class InstallerItemSkin extends SkinBase { - + private static final class InstallerItemSkin extends SkinBase { private static final PseudoClass LIST_ITEM = PseudoClass.getPseudoClass("list-item"); private static final PseudoClass CARD = PseudoClass.getPseudoClass("card"); + private static final WeakListenerHolder holder = new WeakListenerHolder(); InstallerItemSkin(InstallerItem control) { super(control); @@ -227,6 +314,7 @@ public static class InstallerItemSkin extends SkinBase { Pane pane; if (control.style == Style.CARD) { pane = new VBox(); + holder.add(FXUtils.onWeakChange(pane.widthProperty(), v -> FXUtils.setLimitHeight(pane, v.doubleValue() * 0.7))); } else { pane = new HBox(); } @@ -262,20 +350,25 @@ public static class InstallerItemSkin extends SkinBase { pane.getChildren().add(statusLabel); HBox.setHgrow(statusLabel, Priority.ALWAYS); statusLabel.textProperty().bind(Bindings.createStringBinding(() -> { - String incompatibleWith = control.incompatibleLibraryName.get(); - String version = control.libraryVersion.get(); - if (control.incompatibleWithGame.get()) { - return i18n("install.installer.change_version", version); - } else if (incompatibleWith != null) { - return i18n("install.installer.incompatible", i18n("install.installer." + incompatibleWith)); - } else if (version == null) { + State state = control.resolvedStateProperty.get(); + + if (state instanceof InstalledState) { + InstalledState s = (InstalledState) state; + if (s.incompatibleWithGame) { + return i18n("install.installer.change_version", s.version); + } + if (s.external) { + return i18n("install.installer.external_version", s.version); + } + return i18n("install.installer.version", s.version); + } else if (state instanceof InstallableState) { return i18n("install.installer.not_installed"); - } else if (control.id.equals(MINECRAFT.getPatchId()) || control.removable.get() || control.upgradable.get()) { - return i18n("install.installer.version", version); + } else if (state instanceof IncompatibleState) { + return i18n("install.installer.incompatible", i18n("install.installer." + ((IncompatibleState) state).incompatibleItemName)); } else { - return i18n("install.installer.external_version", version); + throw new AssertionError("Unknown state type: " + state.getClass()); } - }, control.incompatibleLibraryName, control.incompatibleWithGame, control.libraryVersion, control.installable, control.removable, control.upgradable)); + }, control.resolvedStateProperty)); BorderPane.setMargin(statusLabel, new Insets(0, 0, 0, 8)); BorderPane.setAlignment(statusLabel, Pos.CENTER_LEFT); @@ -284,35 +377,65 @@ public static class InstallerItemSkin extends SkinBase { buttonsContainer.setAlignment(Pos.CENTER); pane.getChildren().add(buttonsContainer); - JFXButton closeButton = new JFXButton(); - closeButton.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), -1, -1)); - closeButton.getStyleClass().add("toggle-icon4"); - closeButton.visibleProperty().bind(control.removable); - closeButton.managedProperty().bind(closeButton.visibleProperty()); - closeButton.onMouseClickedProperty().bind(control.removeAction); - buttonsContainer.getChildren().add(closeButton); - - JFXButton arrowButton = new JFXButton(); - arrowButton.graphicProperty().bind(Bindings.createObjectBinding(() -> control.upgradable.get() - ? SVG.UPDATE.createIcon(Theme.blackFill(), -1, -1) - : SVG.ARROW_RIGHT.createIcon(Theme.blackFill(), -1, -1), - control.upgradable)); - arrowButton.getStyleClass().add("toggle-icon4"); - arrowButton.visibleProperty().bind(Bindings.createBooleanBinding( - () -> control.installable.get() && control.libraryVersion.get() == null && control.incompatibleLibraryName.get() == null, - control.installable, control.libraryVersion, control.incompatibleLibraryName + JFXButton removeButton = new JFXButton(); + removeButton.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), -1, -1)); + removeButton.getStyleClass().add("toggle-icon4"); + if (control.id.equals(MINECRAFT.getPatchId())) { + removeButton.setVisible(false); + } else { + removeButton.visibleProperty().bind(Bindings.createBooleanBinding(() -> control.resolvedStateProperty.get() instanceof InstalledState, control.resolvedStateProperty)); + } + removeButton.managedProperty().bind(removeButton.visibleProperty()); + removeButton.setOnAction(e -> { + Runnable onRemove = control.getOnRemove(); + if (onRemove != null) + onRemove.run(); + }); + buttonsContainer.getChildren().add(removeButton); + + JFXButton installButton = new JFXButton(); + installButton.graphicProperty().bind(Bindings.createObjectBinding(() -> + control.resolvedStateProperty.get() instanceof InstallableState ? + SVG.ARROW_RIGHT.createIcon(Theme.blackFill(), -1, -1) : + SVG.UPDATE.createIcon(Theme.blackFill(), -1, -1), + control.resolvedStateProperty )); - arrowButton.managedProperty().bind(arrowButton.visibleProperty()); - arrowButton.onMouseClickedProperty().bind(control.action); - buttonsContainer.getChildren().add(arrowButton); + installButton.getStyleClass().add("toggle-icon4"); + installButton.visibleProperty().bind(Bindings.createBooleanBinding(() -> { + if (control.getOnInstall() == null) { + return false; + } + + State state = control.resolvedStateProperty.get(); + if (state instanceof InstallableState) { + return true; + } + if (state instanceof InstalledState) { + return !((InstalledState) state).external; + } + + return false; + }, control.resolvedStateProperty, control.onInstall)); + installButton.managedProperty().bind(installButton.visibleProperty()); + installButton.setOnAction(e -> { + Runnable onInstall = control.getOnInstall(); + if (onInstall != null) + onInstall.run(); + }); + buttonsContainer.getChildren().add(installButton); - FXUtils.onChangeAndOperate(arrowButton.visibleProperty(), clickable -> { + FXUtils.onChangeAndOperate(installButton.visibleProperty(), clickable -> { if (clickable) { - container.onMouseClickedProperty().bind(control.action); + container.setOnMouseClicked(event -> { + Runnable onInstall = control.getOnInstall(); + if (onInstall != null && event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 1) { + onInstall.run(); + event.consume(); + } + }); pane.setCursor(Cursor.HAND); } else { - container.onMouseClickedProperty().unbind(); - container.onMouseClickedProperty().set(null); + container.setOnMouseClicked(null); pane.setCursor(Cursor.DEFAULT); } }); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPageSkin.java index ffbe90d466..89fe706518 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPageSkin.java @@ -70,7 +70,7 @@ public ListPageSkin(ListPage skinnable) { btnAdd.getStyleClass().add("jfx-button-raised-round"); btnAdd.setButtonType(JFXButton.ButtonType.RAISED); btnAdd.setGraphic(SVG.PLUS.createIcon(Theme.whiteFill(), -1, -1)); - btnAdd.setOnMouseClicked(e -> skinnable.add()); + btnAdd.setOnAction(e -> skinnable.add()); JFXButton btnRefresh = new JFXButton(); FXUtils.setLimitWidth(btnRefresh, 40); @@ -78,7 +78,7 @@ public ListPageSkin(ListPage skinnable) { btnRefresh.getStyleClass().add("jfx-button-raised-round"); btnRefresh.setButtonType(JFXButton.ButtonType.RAISED); btnRefresh.setGraphic(SVG.REFRESH.createIcon(Theme.whiteFill(), -1, -1)); - btnRefresh.setOnMouseClicked(e -> skinnable.refresh()); + btnRefresh.setOnAction(e -> skinnable.refresh()); vBox.getChildren().setAll(btnAdd); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/LogWindow.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/LogWindow.java index c216bb74a2..bcefd16e04 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/LogWindow.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/LogWindow.java @@ -25,8 +25,8 @@ import javafx.beans.InvalidationListener; import javafx.beans.binding.Bindings; import javafx.beans.property.*; -import javafx.beans.value.ChangeListener; import javafx.collections.FXCollections; +import javafx.collections.ObservableList; import javafx.css.PseudoClass; import javafx.geometry.Insets; import javafx.geometry.Pos; @@ -34,17 +34,19 @@ import javafx.scene.control.Label; import javafx.scene.control.*; import javafx.scene.input.KeyCode; +import javafx.scene.input.MouseButton; import javafx.scene.layout.*; import javafx.stage.Stage; import org.jackhuang.hmcl.game.GameDumpGenerator; import org.jackhuang.hmcl.game.LauncherHelper; +import org.jackhuang.hmcl.game.Log; import org.jackhuang.hmcl.setting.Theme; +import org.jackhuang.hmcl.ui.construct.SpinnerPane; import org.jackhuang.hmcl.util.Holder; import org.jackhuang.hmcl.util.CircularArrayList; import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.Log4jLevel; import org.jackhuang.hmcl.util.platform.ManagedProcess; -import org.jackhuang.hmcl.util.platform.OperatingSystem; import org.jackhuang.hmcl.util.platform.SystemUtils; import java.io.IOException; @@ -54,14 +56,11 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; -import java.util.function.Consumer; import java.util.stream.Collectors; -import java.util.stream.IntStream; import static org.jackhuang.hmcl.setting.ConfigHolder.config; import static org.jackhuang.hmcl.util.Lang.thread; import static org.jackhuang.hmcl.util.logging.Logger.LOG; -import static org.jackhuang.hmcl.util.StringUtils.parseEscapeSequence; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; /** @@ -69,128 +68,120 @@ */ public final class LogWindow extends Stage { - private final ArrayDeque logs = new ArrayDeque<>(); - private final Map levelCountMap = new EnumMap(Log4jLevel.class) { - { - for (Log4jLevel level : Log4jLevel.values()) put(level, new SimpleIntegerProperty()); - } - }; - private final Map levelShownMap = new EnumMap(Log4jLevel.class) { - { - for (Log4jLevel level : Log4jLevel.values()) { - SimpleBooleanProperty property = new SimpleBooleanProperty(true); - put(level, property); - } - } - }; - private final LogWindowImpl impl = new LogWindowImpl(); - private final ChangeListener logLinesListener = FXUtils.onWeakChange(config().logLinesProperty(), logLines -> checkLogCount()); + private static final Log4jLevel[] LEVELS = {Log4jLevel.FATAL, Log4jLevel.ERROR, Log4jLevel.WARN, Log4jLevel.INFO, Log4jLevel.DEBUG}; - private Consumer exportGameCrashInfoCallback; + private final CircularArrayList logs; + private final Map levelCountMap = new EnumMap<>(Log4jLevel.class); + private final Map levelShownMap = new EnumMap<>(Log4jLevel.class); - private boolean stopCheckLogCount = false; + { + for (Log4jLevel level : Log4jLevel.values()) { + levelCountMap.put(level, new SimpleIntegerProperty()); + levelShownMap.put(level, new SimpleBooleanProperty(true)); + } + } + private final LogWindowImpl impl; private final ManagedProcess gameProcess; public LogWindow(ManagedProcess gameProcess) { + this(gameProcess, new CircularArrayList<>()); + } + + public LogWindow(ManagedProcess gameProcess, CircularArrayList logs) { + this.logs = logs; + this.impl = new LogWindowImpl(); setScene(new Scene(impl, 800, 480)); getScene().getStylesheets().addAll(Theme.getTheme().getStylesheets(config().getLauncherFontFamily())); setTitle(i18n("logwindow.title")); FXUtils.setIcon(this); - levelShownMap.values().forEach(property -> property.addListener((a, b, newValue) -> shakeLogs())); + for (SimpleBooleanProperty property : levelShownMap.values()) { + property.addListener(o -> shakeLogs()); + } this.gameProcess = gameProcess; } - public void logLine(String filteredLine, Log4jLevel level) { - Log log = new Log(parseEscapeSequence(filteredLine), level); + public void logLine(Log log) { + Log4jLevel level = log.getLevel(); logs.add(log); if (levelShownMap.get(level).get()) impl.listView.getItems().add(log); - levelCountMap.get(level).setValue(levelCountMap.get(level).getValue() + 1); - if (!stopCheckLogCount) checkLogCount(); + SimpleIntegerProperty property = levelCountMap.get(log.getLevel()); + property.set(property.get() + 1); + checkLogCount(); + autoScroll(); } - public void showGameCrashReport(Consumer exportGameCrashInfoCallback) { - this.exportGameCrashInfoCallback = exportGameCrashInfoCallback; - this.impl.showCrashReport.set(true); - stopCheckLogCount = true; - for (Log log : impl.listView.getItems()) { - if (log.log.contains("Minecraft Crash Report")) { - Platform.runLater(() -> { - impl.listView.scrollTo(log); - }); - break; - } - } - show(); - } + public void logLines(List logs) { + for (Log log : logs) { + Log4jLevel level = log.getLevel(); + this.logs.add(log); + if (levelShownMap.get(level).get()) + impl.listView.getItems().add(log); - public void showNormal() { - this.impl.showCrashReport.set(false); - show(); + SimpleIntegerProperty property = levelCountMap.get(log.getLevel()); + property.set(property.get() + 1); + } + checkLogCount(); + autoScroll(); } private void shakeLogs() { - impl.listView.getItems().setAll(logs.stream().filter(log -> levelShownMap.get(log.level).get()).collect(Collectors.toList())); + impl.listView.getItems().setAll(logs.stream().filter(log -> levelShownMap.get(log.getLevel()).get()).collect(Collectors.toList())); + autoScroll(); } private void checkLogCount() { - while (logs.size() > config().getLogLines()) { + int nRemove = logs.size() - Log.getLogLines(); + if (nRemove <= 0) + return; + + ObservableList items = impl.listView.getItems(); + int itemsSize = items.size(); + int count = 0; + + for (int i = 0; i < nRemove; i++) { Log removedLog = logs.removeFirst(); - if (!impl.listView.getItems().isEmpty() && impl.listView.getItems().get(0) == removedLog) { - impl.listView.getItems().remove(0); - } + if (itemsSize > count && items.get(count) == removedLog) + count++; } - } - private static class Log { - private final String log; - private final Log4jLevel level; - private boolean selected = false; + items.remove(0, count); + } - public Log(String log, Log4jLevel level) { - this.log = log; - this.level = level; - } + private void autoScroll() { + if (!impl.listView.getItems().isEmpty() && impl.autoScroll.get()) + impl.listView.scrollTo(impl.listView.getItems().size() - 1); } - public class LogWindowImpl extends Control { + private final class LogWindowImpl extends Control { private final ListView listView = new JFXListView<>(); private final BooleanProperty autoScroll = new SimpleBooleanProperty(); - private final List buttonText = IntStream.range(0, 5).mapToObj(x -> new SimpleStringProperty()).collect(Collectors.toList()); - private final List showLevel = IntStream.range(0, 5).mapToObj(x -> new SimpleBooleanProperty(true)).collect(Collectors.toList()); - private final JFXComboBox cboLines = new JFXComboBox<>(); - private final BooleanProperty showCrashReport = new SimpleBooleanProperty(); + private final StringProperty[] buttonText = new StringProperty[LEVELS.length]; + private final BooleanProperty[] showLevel = new BooleanProperty[LEVELS.length]; + private final JFXComboBox cboLines = new JFXComboBox<>(); LogWindowImpl() { getStyleClass().add("log-window"); - listView.setItems(FXCollections.observableList(new CircularArrayList<>(config().getLogLines() + 1))); + listView.setItems(FXCollections.observableList(new CircularArrayList<>(logs.size()))); - boolean flag = false; - cboLines.getItems().setAll("500", "2000", "5000", "10000"); - for (String i : cboLines.getItems()) - if (Integer.toString(config().getLogLines()).equals(i)) { - cboLines.getSelectionModel().select(i); - flag = true; - } + for (int i = 0; i < LEVELS.length; i++) { + buttonText[i] = new SimpleStringProperty(); + showLevel[i] = new SimpleBooleanProperty(true); + } - if (!flag) - cboLines.getSelectionModel().select(2); + cboLines.getItems().setAll(500, 2000, 5000, 10000); + cboLines.setValue(Log.getLogLines()); + cboLines.getSelectionModel().selectedItemProperty().addListener((a, b, newValue) -> config().setLogLines(newValue)); - cboLines.getSelectionModel().selectedItemProperty().addListener((a, b, newValue) -> { - config().setLogLines(newValue == null ? 1000 : Integer.parseInt(newValue)); - }); - - Log4jLevel[] levels = new Log4jLevel[]{Log4jLevel.FATAL, Log4jLevel.ERROR, Log4jLevel.WARN, Log4jLevel.INFO, Log4jLevel.DEBUG}; - String[] suffix = new String[]{"fatals", "errors", "warns", "infos", "debugs"}; - for (int i = 0; i < 5; ++i) { - buttonText.get(i).bind(Bindings.concat(levelCountMap.get(levels[i]), " " + suffix[i])); - levelShownMap.get(levels[i]).bind(showLevel.get(i)); + for (int i = 0; i < LEVELS.length; ++i) { + buttonText[i].bind(Bindings.concat(levelCountMap.get(LEVELS[i]), " " + LEVELS[i].name().toLowerCase(Locale.ROOT) + "s")); + levelShownMap.get(LEVELS[i]).bind(showLevel[i]); } } @@ -207,7 +198,7 @@ private void onExportLogs() { thread(() -> { Path logFile = Paths.get("minecraft-exported-logs-" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH-mm-ss")) + ".log").toAbsolutePath(); try { - Files.write(logFile, logs.stream().map(x -> x.log).collect(Collectors.toList())); + Files.write(logFile, logs.stream().map(Log::getLog).collect(Collectors.toList())); } catch (IOException e) { LOG.warning("Failed to export logs", e); return; @@ -223,38 +214,31 @@ private void onExportLogs() { }); } - private void onExportDump(JFXButton button) { - thread(() -> { - if (button.getText().equals(i18n("logwindow.export_dump.dependency_ok.button"))) { - if (SystemUtils.supportJVMAttachment()) { - Platform.runLater(() -> button.setText(i18n("logwindow.export_dump.dependency_ok.doing_button"))); + private void onExportDump(SpinnerPane pane) { + assert SystemUtils.supportJVMAttachment(); - Path dumpFile = Paths.get("minecraft-exported-jstack-dump-" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH-mm-ss")) + ".log").toAbsolutePath(); + pane.setLoading(true); - try { - if (gameProcess.isRunning()) { - GameDumpGenerator.writeDumpTo(gameProcess.getPID(), dumpFile); - FXUtils.showFileInExplorer(dumpFile); - } - } catch (Throwable e) { - LOG.warning("Failed to create minecraft jstack dump", e); - - Platform.runLater(() -> { - Alert alert = new Alert(Alert.AlertType.ERROR, i18n("logwindow.export_dump.dependency_ok.button")); - alert.setTitle(i18n("message.error")); - alert.showAndWait(); - }); - } + thread(() -> { + Path dumpFile = Paths.get("minecraft-exported-jstack-dump-" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH-mm-ss")) + ".log").toAbsolutePath(); - Platform.runLater(() -> button.setText(i18n("logwindow.export_dump.dependency_ok.button"))); + try { + if (gameProcess.isRunning()) { + GameDumpGenerator.writeDumpTo(gameProcess.getPID(), dumpFile); + FXUtils.showFileInExplorer(dumpFile); } + } catch (Throwable e) { + LOG.warning("Failed to create minecraft jstack dump", e); + + Platform.runLater(() -> { + Alert alert = new Alert(Alert.AlertType.ERROR, i18n("logwindow.export_dump")); + alert.setTitle(i18n("message.error")); + alert.showAndWait(); + }); } - }); - } - private void onExportGameCrashInfo() { - if (exportGameCrashInfoCallback == null) return; - exportGameCrashInfoCallback.accept(logs.stream().map(x -> x.log).collect(Collectors.joining(OperatingSystem.LINE_SEPARATOR))); + Platform.runLater(() -> pane.setLoading(false)); + }); } @Override @@ -263,7 +247,7 @@ protected Skin createDefaultSkin() { } } - private static class LogWindowSkin extends SkinBase { + private static final class LogWindowSkin extends SkinBase { private static final PseudoClass EMPTY = PseudoClass.getPseudoClass("empty"); private static final PseudoClass FATAL = PseudoClass.getPseudoClass("fatal"); private static final PseudoClass ERROR = PseudoClass.getPseudoClass("error"); @@ -275,17 +259,7 @@ private static class LogWindowSkin extends SkinBase { private final Set> selected = new HashSet<>(); - private static ToggleButton createToggleButton(String backgroundColor, StringProperty buttonText, BooleanProperty showLevel) { - ToggleButton button = new ToggleButton(); - button.setStyle("-fx-background-color: " + backgroundColor + ";"); - button.getStyleClass().add("log-toggle"); - button.textProperty().bind(buttonText); - button.setSelected(true); - showLevel.bind(button.selectedProperty()); - return button; - } - - protected LogWindowSkin(LogWindowImpl control) { + LogWindowSkin(LogWindowImpl control) { super(control); VBox vbox = new VBox(3); @@ -310,13 +284,16 @@ protected LogWindowSkin(LogWindowImpl control) { { HBox hBox = new HBox(3); - hBox.getChildren().setAll( - createToggleButton("#F7A699", control.buttonText.get(0), control.showLevel.get(0)), - createToggleButton("#FFCCBB", control.buttonText.get(1), control.showLevel.get(1)), - createToggleButton("#FFEECC", control.buttonText.get(2), control.showLevel.get(2)), - createToggleButton("#FBFBFB", control.buttonText.get(3), control.showLevel.get(3)), - createToggleButton("#EEE9E0", control.buttonText.get(4), control.showLevel.get(4)) - ); + for (int i = 0; i < LEVELS.length; i++) { + ToggleButton button = new ToggleButton(); + button.setStyle("-fx-background-color: " + FXUtils.toWeb(LEVELS[i].getColor()) + ";"); + button.getStyleClass().add("log-toggle"); + button.textProperty().bind(control.buttonText[i]); + button.setSelected(true); + control.showLevel[i].bind(button.selectedProperty()); + hBox.getChildren().add(button); + } + borderPane.setRight(hBox); } @@ -346,12 +323,15 @@ protected LogWindowSkin(LogWindowImpl control) { setGraphic(null); setOnMouseClicked(event -> { + if (event.getButton() != MouseButton.PRIMARY) + return; + if (!event.isControlDown()) { - for (ListCell logListCell: selected) { + for (ListCell logListCell : selected) { if (logListCell != this) { logListCell.pseudoClassStateChanged(SELECTED, false); if (logListCell.getItem() != null) { - logListCell.getItem().selected = false; + logListCell.getItem().setSelected(false); } } } @@ -362,8 +342,10 @@ protected LogWindowSkin(LogWindowImpl control) { selected.add(this); pseudoClassStateChanged(SELECTED, true); if (getItem() != null) { - getItem().selected = true; + getItem().setSelected(true); } + + event.consume(); }); } @@ -377,18 +359,18 @@ protected void updateItem(Log item, boolean empty) { lastCell.value = this; pseudoClassStateChanged(EMPTY, empty); - pseudoClassStateChanged(FATAL, !empty && item.level == Log4jLevel.FATAL); - pseudoClassStateChanged(ERROR, !empty && item.level == Log4jLevel.ERROR); - pseudoClassStateChanged(WARN, !empty && item.level == Log4jLevel.WARN); - pseudoClassStateChanged(INFO, !empty && item.level == Log4jLevel.INFO); - pseudoClassStateChanged(DEBUG, !empty && item.level == Log4jLevel.DEBUG); - pseudoClassStateChanged(TRACE, !empty && item.level == Log4jLevel.TRACE); - pseudoClassStateChanged(SELECTED, !empty && item.selected); + pseudoClassStateChanged(FATAL, !empty && item.getLevel() == Log4jLevel.FATAL); + pseudoClassStateChanged(ERROR, !empty && item.getLevel() == Log4jLevel.ERROR); + pseudoClassStateChanged(WARN, !empty && item.getLevel() == Log4jLevel.WARN); + pseudoClassStateChanged(INFO, !empty && item.getLevel() == Log4jLevel.INFO); + pseudoClassStateChanged(DEBUG, !empty && item.getLevel() == Log4jLevel.DEBUG); + pseudoClassStateChanged(TRACE, !empty && item.getLevel() == Log4jLevel.TRACE); + pseudoClassStateChanged(SELECTED, !empty && item.isSelected()); if (empty) { setText(null); } else { - setText(item.log); + setText(item.getLog()); } } }); @@ -398,10 +380,9 @@ protected void updateItem(Log item, boolean empty) { StringBuilder stringBuilder = new StringBuilder(); for (Log item : listView.getItems()) { - if (item != null && item.selected) { - if (item.log != null) { - stringBuilder.append(item.log); - } + if (item != null && item.isSelected()) { + if (item.getLog() != null) + stringBuilder.append(item.getLog()); stringBuilder.append('\n'); } } @@ -417,11 +398,6 @@ protected void updateItem(Log item, boolean empty) { { BorderPane bottom = new BorderPane(); - JFXButton exportGameCrashInfoButton = new JFXButton(i18n("logwindow.export_game_crash_logs")); - exportGameCrashInfoButton.setOnMouseClicked(e -> getSkinnable().onExportGameCrashInfo()); - exportGameCrashInfoButton.visibleProperty().bind(getSkinnable().showCrashReport); - bottom.setLeft(exportGameCrashInfoButton); - HBox hBox = new HBox(3); bottom.setRight(hBox); hBox.setAlignment(Pos.CENTER_RIGHT); @@ -432,24 +408,24 @@ protected void updateItem(Log item, boolean empty) { control.autoScroll.bind(autoScrollCheckBox.selectedProperty()); JFXButton exportLogsButton = new JFXButton(i18n("button.export")); - exportLogsButton.setOnMouseClicked(e -> getSkinnable().onExportLogs()); + exportLogsButton.setOnAction(e -> getSkinnable().onExportLogs()); JFXButton terminateButton = new JFXButton(i18n("logwindow.terminate_game")); - terminateButton.setOnMouseClicked(e -> getSkinnable().onTerminateGame()); + terminateButton.setOnAction(e -> getSkinnable().onTerminateGame()); - JFXButton exportDumpButton = new JFXButton(); + SpinnerPane exportDumpPane = new SpinnerPane(); + JFXButton exportDumpButton = new JFXButton(i18n("logwindow.export_dump")); if (SystemUtils.supportJVMAttachment()) { - exportDumpButton.setText(i18n("logwindow.export_dump.dependency_ok.button")); - exportDumpButton.setOnAction(e -> getSkinnable().onExportDump(exportDumpButton)); + exportDumpButton.setOnAction(e -> getSkinnable().onExportDump(exportDumpPane)); } else { - exportDumpButton.setText(i18n("logwindow.export_dump.no_dependency.button")); - exportDumpButton.setTooltip(new Tooltip(i18n("logwindow.export_dump.no_dependency.tooltip"))); + exportDumpButton.setTooltip(new Tooltip(i18n("logwindow.export_dump.no_dependency"))); exportDumpButton.setDisable(true); } + exportDumpPane.setContent(exportDumpButton); JFXButton clearButton = new JFXButton(i18n("button.clear")); - clearButton.setOnMouseClicked(e -> getSkinnable().onClear()); - hBox.getChildren().setAll(autoScrollCheckBox, exportLogsButton, terminateButton, exportDumpButton, clearButton); + clearButton.setOnAction(e -> getSkinnable().onClear()); + hBox.getChildren().setAll(autoScrollCheckBox, exportLogsButton, terminateButton, exportDumpPane, clearButton); vbox.getChildren().add(bottom); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java index fa80d8cedf..618eb270ad 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java @@ -32,7 +32,7 @@ public enum SVG { CANCEL("M12 2C17.5 2 22 6.5 22 12S17.5 22 12 22 2 17.5 2 12 6.5 2 12 2M12 4C10.1 4 8.4 4.6 7.1 5.7L18.3 16.9C19.3 15.5 20 13.8 20 12C20 7.6 16.4 4 12 4M16.9 18.3L5.7 7.1C4.6 8.4 4 10.1 4 12C4 16.4 7.6 20 12 20C13.9 20 15.6 19.4 16.9 18.3Z"), CHAT("M20,2A2,2 0 0,1 22,4V16A2,2 0 0,1 20,18H6L2,22V4C2,2.89 2.9,2 4,2H20M4,4V17.17L5.17,16H20V4H4M6,7H18V9H6V7M6,11H15V13H6V11Z"), CLOSE("M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z"), - COPY("M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"), + COPY("M15 16H7V6h8m.4-2H6.6A1.6 1.6 90 005 5.6V16.4a1.6 1.6 90 001.6 1.6h8.8A1.6 1.6 90 0017 16.4V5.6A1.6 1.6 90 0015.4 4M13 1H3.6A1.6 1.6 90 002 2.6V13h2V3H13V1Z"), DOTS_VERTICAL("M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z"), DOTS_HORIZONTAL("M16,12A2,2 0 0,1 18,10A2,2 0 0,1 20,12A2,2 0 0,1 18,14A2,2 0 0,1 16,12M10,12A2,2 0 0,1 12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12M4,12A2,2 0 0,1 6,10A2,2 0 0,1 8,12A2,2 0 0,1 6,14A2,2 0 0,1 4,12Z"), DELETE("M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z"), @@ -110,7 +110,8 @@ public enum SVG { LAN("M10,2C8.89,2 8,2.89 8,4V7C8,8.11 8.89,9 10,9H11V11H2V13H6V15H5C3.89,15 3,15.89 3,17V20C3,21.11 3.89,22 5,22H9C10.11,22 11,21.11 11,20V17C11,15.89 10.11,15 9,15H8V13H16V15H15C13.89,15 13,15.89 13,17V20C13,21.11 13.89,22 15,22H19C20.11,22 21,21.11 21,20V17C21,15.89 20.11,15 19,15H18V13H22V11H13V9H14C15.11,9 16,8.11 16,7V4C16,2.89 15.11,2 14,2H10M10,4H14V7H10V4M5,17H9V20H5V17M15,17H19V20H15V17Z"), THUMB_UP_OUTLINE("M5,9V21H1V9H5M9,21A2,2 0 0,1 7,19V9C7,8.45 7.22,7.95 7.59,7.59L14.17,1L15.23,2.06C15.5,2.33 15.67,2.7 15.67,3.11L15.64,3.43L14.69,8H21C22.11,8 23,8.9 23,10V12C23,12.26 22.95,12.5 22.86,12.73L19.84,19.78C19.54,20.5 18.83,21 18,21H9M9,19H18.03L21,12V10H12.21L13.34,4.68L9,9.03V19Z"), THUMB_DOWN_OUTLINE("M19,15V3H23V15H19M15,3A2,2 0 0,1 17,5V15C17,15.55 16.78,16.05 16.41,16.41L9.83,23L8.77,21.94C8.5,21.67 8.33,21.3 8.33,20.88L8.36,20.57L9.31,16H3C1.89,16 1,15.1 1,14V12C1,11.74 1.05,11.5 1.14,11.27L4.16,4.22C4.46,3.5 5.17,3 6,3H15M15,5H5.97L3,12V14H11.78L10.65,19.32L15,14.97V5Z"), - SELECT_ALL("M9,9H15V15H9M7,17H17V7H7M15,5H17V3H15M15,21H17V19H15M19,17H21V15H19M19,9H21V7H19M19,21A2,2 0 0,0 21,19H19M19,13H21V11H19M11,21H13V19H11M9,3H7V5H9M3,17H5V15H3M5,21V19H3A2,2 0 0,0 5,21M19,3V5H21A2,2 0 0,0 19,3M13,3H11V5H13M3,9H5V7H3M7,21H9V19H7M3,13H5V11H3M3,5H5V3A2,2 0 0,0 3,5Z"); + SELECT_ALL("M9,9H15V15H9M7,17H17V7H7M15,5H17V3H15M15,21H17V19H15M19,17H21V15H19M19,9H21V7H19M19,21A2,2 0 0,0 21,19H19M19,13H21V11H19M11,21H13V19H11M9,3H7V5H9M3,17H5V15H3M5,21V19H3A2,2 0 0,0 5,21M19,3V5H21A2,2 0 0,0 19,3M13,3H11V5H13M3,9H5V7H3M7,21H9V19H7M3,13H5V11H3M3,5H5V3A2,2 0 0,0 3,5Z"), + JAVA("m 12.400746,23.498132 c 0,0 -1.047682,0.609185 0.745753,0.816159 2.173248,0.247515 3.284943,0.212301 5.680103,-0.241112 0,0 0.629462,0.394742 1.509642,0.737207 -5.369636,2.301245 -12.1518319,-0.133351 -7.934431,-1.312254 z m -0.656135,-3.003246 c 0,0 -1.175708,0.870569 0.619861,1.056206 2.321545,0.238977 4.155521,0.25925 7.32844,-0.35207 0,0 0.43849,0.444887 1.128765,0.688135 -6.492002,1.897965 -13.7233551,0.149351 -9.077066,-1.392271 z m 5.531806,-5.094318 c 1.322937,1.523496 -0.347807,2.894427 -0.347807,2.894427 0,0 3.359626,-1.733668 1.816908,-3.905821 -1.441364,-2.024926 -2.545591,-3.030987 3.43644,-6.5004569 0,0 -9.389665,2.3449859 -4.905541,7.5129179 z m 7.101193,10.318794 c 0,0 0.775626,0.639057 -0.854578,1.133019 C 20.42373,27.79123 10.623314,28.075018 7.9006204,26.88973 6.9222859,26.464048 8.75733,25.873001 9.3345153,25.749244 9.9362392,25.619093 10.280844,25.642568 10.280844,25.642568 9.1926195,24.875486 3.2457961,27.147927 7.2604887,27.798718 18.208875,29.573994 27.21766,26.999632 24.37761,25.718316 Z M 12.904316,17.383886 c 0,0 -4.9855587,1.184229 -1.765696,1.614178 1.359213,0.182426 4.069103,0.140825 6.594422,-0.07042 2.063359,-0.173899 4.135252,-0.544104 4.135252,-0.544104 0,0 -0.727616,0.311527 -1.253592,0.671064 -5.062375,1.331456 -14.8414526,0.711604 -12.02594,-0.649727 2.38129,-1.151156 4.316621,-1.019929 4.316621,-1.019929 z m 8.943706,4.998298 c 5.146658,-2.673583 2.766435,-5.24368 1.106361,-4.898013 -0.407551,0.08428 -0.58892,0.1579 -0.58892,0.1579 0,0 0.151501,-0.236845 0.439557,-0.339265 3.284942,-1.155424 5.812393,3.406524 -1.060485,5.213807 0,0 0.08003,-0.07147 0.103478,-0.134425 z M 18.744451,2.2855 c 0,0 2.849653,2.8506848 -2.703489,7.2344656 -4.453183,3.5164124 -1.015676,5.5221324 -0.0021,7.8127104 C 13.439893,14.987688 11.532301,12.92329 12.811497,11.001852 14.690284,8.1810394 19.893487,6.8143757 18.743384,2.2855 Z M 13.41002,29.628384 c 4.939684,0.315794 12.525242,-0.174974 12.70448,-2.512485 0,0 -0.345672,0.886571 -4.081906,1.589641 -4.216334,0.793754 -9.416337,0.700936 -12.4996379,0.192025 0,0 0.6315969,0.522768 3.8781309,0.730807 z"); private final String path; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ToolbarListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ToolbarListPageSkin.java index 518ceb2f0e..ba4d42c814 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ToolbarListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ToolbarListPageSkin.java @@ -91,7 +91,7 @@ public static JFXButton createToolbarButton(String text, SVG svg, Runnable onCli ret.textFillProperty().bind(Theme.foregroundFillBinding()); ret.setGraphic(wrap(svg.createIcon(Theme.foregroundFillBinding(), -1, -1))); ret.setText(text); - ret.setOnMouseClicked(e -> onClick.run()); + ret.setOnAction(e -> onClick.run()); return ret; } @@ -100,7 +100,7 @@ public static JFXButton createToolbarButton2(String text, SVG svg, Runnable onCl ret.getStyleClass().add("jfx-tool-bar-button"); ret.setGraphic(wrap(svg.createIcon(Theme.blackFill(), -1, -1))); ret.setText(text); - ret.setOnMouseClicked(e -> onClick.run()); + ret.setOnAction(e -> onClick.run()); return ret; } @@ -110,7 +110,7 @@ public static JFXButton createDecoratorButton(String tooltip, SVG svg, Runnable ret.textFillProperty().bind(Theme.foregroundFillBinding()); ret.setGraphic(wrap(svg.createIcon(Theme.foregroundFillBinding(), -1, -1))); FXUtils.installFastTooltip(ret, tooltip); - ret.setOnMouseClicked(e -> onClick.run()); + ret.setOnAction(e -> onClick.run()); return ret; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/TooltipInstaller.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/TooltipInstaller.java new file mode 100644 index 0000000000..acc1530d12 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/TooltipInstaller.java @@ -0,0 +1,123 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.ui; + +import javafx.scene.Node; +import javafx.scene.control.Tooltip; +import javafx.util.Duration; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +import static org.jackhuang.hmcl.util.logging.Logger.LOG; + +/** + * @author Glavo + */ +public class TooltipInstaller { + public static final TooltipInstaller INSTALLER; + + static { + TooltipInstaller installer = null; + + try { + installer = new NewInstaller(); + } catch (Throwable e) { + try { + installer = new OldInstaller(); + } catch (Throwable e2) { + e2.addSuppressed(e); + LOG.warning("Failed to initialize TooltipInstaller", e2); + } + } + + INSTALLER = installer != null ? installer : new TooltipInstaller(); + } + + TooltipInstaller() { + } + + public void installTooltip(Node node, Duration showDelay, Duration showDuration, Duration hideDelay, Tooltip tooltip) { + Tooltip.install(node, tooltip); + } + + // For Java 8 + private static final class OldInstaller extends TooltipInstaller { + private static final Constructor createTooltipBehavior; + private static final Method installTooltipBehavior; + + static { + try { + Class behaviorClass = Class.forName("javafx.scene.control.Tooltip$TooltipBehavior"); + createTooltipBehavior = behaviorClass.getDeclaredConstructor(Duration.class, Duration.class, Duration.class, boolean.class); + createTooltipBehavior.setAccessible(true); + installTooltipBehavior = behaviorClass.getDeclaredMethod("install", Node.class, Tooltip.class); + installTooltipBehavior.setAccessible(true); + } catch (Exception e) { + throw new ExceptionInInitializerError(e); + } + } + + @Override + public void installTooltip(Node node, Duration showDelay, Duration showDuration, Duration hideDelay, Tooltip tooltip) { + try { + Object behavior = createTooltipBehavior.newInstance(showDelay, showDuration, hideDelay, false); + installTooltipBehavior.invoke(behavior, node, tooltip); + } catch (ReflectiveOperationException e) { + LOG.warning("Failed to set tooltip show delay", e); + Tooltip.install(node, tooltip); + } + } + } + + // For Java 9+ + private static final class NewInstaller extends TooltipInstaller { + private static final MethodHandle setTooltipShowDelay; + private static final MethodHandle setTooltipShowDuration; + private static final MethodHandle setTooltipHideDelay; + + static { + try { + MethodHandles.Lookup lookup = MethodHandles.publicLookup(); + MethodType methodType = MethodType.methodType(void.class, Duration.class); + + setTooltipShowDelay = lookup.findVirtual(Tooltip.class, "setShowDelay", methodType); + setTooltipShowDuration = lookup.findVirtual(Tooltip.class, "setShowDuration", methodType); + setTooltipHideDelay = lookup.findVirtual(Tooltip.class, "setHideDelay", methodType); + } catch (Exception e) { + throw new ExceptionInInitializerError(e); + } + } + + @Override + public void installTooltip(Node node, Duration showDelay, Duration showDuration, Duration hideDelay, Tooltip tooltip) { + try { + setTooltipShowDelay.invokeExact(tooltip, showDelay); + setTooltipShowDuration.invokeExact(tooltip, showDuration); + setTooltipHideDelay.invokeExact(tooltip, hideDelay); + } catch (Throwable e) { + LOG.warning("Failed to set tooltip show delay", e); + } + + Tooltip.install(node, tooltip); + } + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/UpgradeDialog.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/UpgradeDialog.java index e8602e394d..e6b1e99255 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/UpgradeDialog.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/UpgradeDialog.java @@ -19,58 +19,73 @@ import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXDialogLayout; -import javafx.concurrent.Worker; import javafx.scene.control.Label; -import javafx.scene.web.WebEngine; -import javafx.scene.web.WebView; -import org.jackhuang.hmcl.Metadata; +import javafx.scene.control.ProgressIndicator; +import javafx.scene.control.ScrollPane; +import org.jackhuang.hmcl.task.Schedulers; +import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.construct.DialogCloseEvent; +import org.jackhuang.hmcl.ui.construct.JFXHyperlink; import org.jackhuang.hmcl.upgrade.RemoteVersion; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Node; +import java.io.IOException; +import java.net.URL; import static org.jackhuang.hmcl.Metadata.CHANGELOG_URL; import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed; -import static org.jackhuang.hmcl.util.logging.Logger.LOG; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; -public class UpgradeDialog extends JFXDialogLayout { +public final class UpgradeDialog extends JFXDialogLayout { public UpgradeDialog(RemoteVersion remoteVersion, Runnable updateRunnable) { - { - setHeading(new Label(i18n("update.changelog"))); - } + setHeading(new Label(i18n("update.changelog"))); + setBody(new ProgressIndicator()); + + String url = CHANGELOG_URL + remoteVersion.getChannel().channelName + ".html"; + Task.supplyAsync(Schedulers.io(), () -> { + Document document = Jsoup.parse(new URL(url), 30 * 1000); + Node node = document.selectFirst("#nowchange"); + if (node == null) + throw new IOException("Cannot find #nowchange in document"); - { - String url = CHANGELOG_URL + remoteVersion.getChannel().channelName + ".html#nowchange"; - try { - WebView webView = new WebView(); - webView.getEngine().setUserDataDirectory(Metadata.HMCL_DIRECTORY.toFile()); - WebEngine engine = webView.getEngine(); - engine.load(url); - engine.getLoadWorker().stateProperty().addListener((observable, oldValue, newValue) -> { - if (newValue == Worker.State.FAILED) { - LOG.warning("Failed to load update log, trying to open it in browser"); - FXUtils.openLink(url); - setBody(); - } - }); - setBody(webView); - } catch (NoClassDefFoundError | UnsatisfiedLinkError e) { - LOG.warning("WebView is missing or initialization failed", e); + HTMLRenderer renderer = new HTMLRenderer(uri -> { + LOG.info("Open link: " + uri); + FXUtils.openLink(uri.toString()); + }); + + do { + renderer.appendNode(node); + node = node.nextSibling(); + } while (node != null); + + return renderer.render(); + }).whenComplete(Schedulers.javafx(), (result, exception) -> { + if (exception == null) { + ScrollPane scrollPane = new ScrollPane(result); + scrollPane.setFitToWidth(true); + setBody(scrollPane); + } else { + LOG.warning("Failed to load update log, trying to open it in browser"); FXUtils.openLink(url); + setBody(); } - } + }).start(); + + JFXHyperlink openInBrowser = new JFXHyperlink(i18n("web.view_in_browser")); + openInBrowser.setExternalLink(url); - { - JFXButton updateButton = new JFXButton(i18n("update.accept")); - updateButton.getStyleClass().add("dialog-accept"); - updateButton.setOnMouseClicked(e -> updateRunnable.run()); + JFXButton updateButton = new JFXButton(i18n("update.accept")); + updateButton.getStyleClass().add("dialog-accept"); + updateButton.setOnAction(e -> updateRunnable.run()); - JFXButton cancelButton = new JFXButton(i18n("button.cancel")); - cancelButton.getStyleClass().add("dialog-cancel"); - cancelButton.setOnMouseClicked(e -> fireEvent(new DialogCloseEvent())); + JFXButton cancelButton = new JFXButton(i18n("button.cancel")); + cancelButton.getStyleClass().add("dialog-cancel"); + cancelButton.setOnAction(e -> fireEvent(new DialogCloseEvent())); - setActions(updateButton, cancelButton); - onEscPressed(this, cancelButton::fire); - } + setActions(openInBrowser, updateButton, cancelButton); + onEscPressed(this, cancelButton::fire); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/WebPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/WebPage.java new file mode 100644 index 0000000000..96c23f51af --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/WebPage.java @@ -0,0 +1,75 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.ui; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.Background; +import javafx.scene.layout.BackgroundFill; +import javafx.scene.paint.Color; +import org.jackhuang.hmcl.task.Schedulers; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.ui.construct.SpinnerPane; +import org.jackhuang.hmcl.ui.decorator.DecoratorPage; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; + +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; + +/** + * @author Glavo + */ +public final class WebPage extends SpinnerPane implements DecoratorPage { + + private final ObjectProperty stateProperty; + + public WebPage(String title, String content) { + this.stateProperty = new SimpleObjectProperty<>(DecoratorPage.State.fromTitle(title)); + this.setBackground(new Background(new BackgroundFill(Color.WHITE, null, null))); + + Task.supplyAsync(() -> { + Document document = Jsoup.parseBodyFragment(content); + HTMLRenderer renderer = new HTMLRenderer(uri -> { + Controllers.confirm(i18n("web.open_in_browser", uri), i18n("message.confirm"), () -> { + FXUtils.openLink(uri.toString()); + }, null); + }); + renderer.appendNode(document); + return renderer.render(); + }).whenComplete(Schedulers.javafx(), ((result, exception) -> { + if (exception == null) { + ScrollPane scrollPane = new ScrollPane(); + scrollPane.setFitToWidth(true); + scrollPane.setContent(result); + setContent(scrollPane); + setFailedReason(null); + } else { + LOG.warning("Failed to load content", exception); + setFailedReason(i18n("web.failed")); + } + })).start(); + } + + @Override + public ReadOnlyObjectProperty stateProperty() { + return stateProperty; + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/WebStage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/WebStage.java deleted file mode 100644 index 1b57ca0aa3..0000000000 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/WebStage.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Hello Minecraft! Launcher - * Copyright (C) 2020 huangyuhui and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.jackhuang.hmcl.ui; - -import com.jfoenix.controls.JFXProgressBar; -import javafx.beans.binding.Bindings; -import javafx.scene.Scene; -import javafx.scene.layout.BorderPane; -import javafx.scene.layout.StackPane; -import javafx.scene.web.WebEngine; -import javafx.scene.web.WebView; -import javafx.stage.Stage; -import org.jackhuang.hmcl.Metadata; -import org.jackhuang.hmcl.setting.Theme; - -import static org.jackhuang.hmcl.setting.ConfigHolder.config; - -public class WebStage extends Stage { - protected final StackPane pane = new StackPane(); - protected final JFXProgressBar progressBar = new JFXProgressBar(); - protected final WebView webView = new WebView(); - protected final WebEngine webEngine = webView.getEngine(); - - public WebStage() { - this(800, 480); - } - - public WebStage(int width, int height) { - setScene(new Scene(pane, width, height)); - getScene().getStylesheets().addAll(Theme.getTheme().getStylesheets(config().getLauncherFontFamily())); - FXUtils.setIcon(this); - webView.getEngine().setUserDataDirectory(Metadata.HMCL_DIRECTORY.toFile()); - webView.setContextMenuEnabled(false); - progressBar.progressProperty().bind(webView.getEngine().getLoadWorker().progressProperty()); - - progressBar.visibleProperty().bind(Bindings.createBooleanBinding(() -> { - switch (webView.getEngine().getLoadWorker().getState()) { - case SUCCEEDED: - case FAILED: - case CANCELLED: - return false; - default: - return true; - } - }, webEngine.getLoadWorker().stateProperty())); - - BorderPane borderPane = new BorderPane(); - borderPane.setPickOnBounds(false); - borderPane.setTop(progressBar); - progressBar.prefWidthProperty().bind(borderPane.widthProperty()); - pane.getChildren().setAll(webView, borderPane); - } - - public WebView getWebView() { - return webView; - } -} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItem.java index 22a0b39f9e..6fc255dd9b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItem.java @@ -32,17 +32,14 @@ import org.jackhuang.hmcl.auth.CredentialExpiredException; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer; -import org.jackhuang.hmcl.auth.microsoft.MicrosoftAccount; import org.jackhuang.hmcl.auth.offline.OfflineAccount; import org.jackhuang.hmcl.auth.yggdrasil.CompleteGameProfile; import org.jackhuang.hmcl.auth.yggdrasil.TextureType; -import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount; import org.jackhuang.hmcl.setting.Accounts; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.DialogController; -import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType; import org.jackhuang.hmcl.util.skin.InvalidSkinException; import org.jackhuang.hmcl.util.skin.NormalizedSkin; @@ -128,7 +125,7 @@ public ObservableBooleanValue canUploadSkin() { .orElse(emptySet()); return uploadableTextures.contains(TextureType.SKIN); }, profile); - } else if (account instanceof OfflineAccount || account instanceof MicrosoftAccount) { + } else if (account instanceof OfflineAccount || account.canUploadSkin()) { return createBooleanBinding(() -> true); } else { return createBooleanBinding(() -> false); @@ -144,11 +141,7 @@ public Task uploadSkin() { Controllers.dialog(new OfflineAccountSkinPane((OfflineAccount) account)); return null; } - if (account instanceof MicrosoftAccount) { - FXUtils.openLink("https://www.minecraft.net/msaprofile/mygames/editskin"); - return null; - } - if (!(account instanceof YggdrasilAccount)) { + if (!account.canUploadSkin()) { return null; } @@ -174,7 +167,7 @@ public Task uploadSkin() { NormalizedSkin skin = new NormalizedSkin(skinImg); String model = skin.isSlim() ? "slim" : ""; LOG.info("Uploading skin [" + selectedFile + "], model [" + model + "]"); - ((YggdrasilAccount) account).uploadSkin(model, selectedFile.toPath()); + account.uploadSkin(skin.isSlim(), selectedFile.toPath()); }) .thenComposeAsync(refreshAsync()) .whenComplete(Schedulers.javafx(), e -> { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java index 2e69fbc17e..ea6b28dbfa 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java @@ -42,10 +42,9 @@ import org.jackhuang.hmcl.ui.construct.SpinnerPane; import org.jackhuang.hmcl.util.javafx.BindingMapping; -import static org.jackhuang.hmcl.ui.FXUtils.runInFX; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; -public class AccountListItemSkin extends SkinBase { +public final class AccountListItemSkin extends SkinBase { public AccountListItemSkin(AccountListItem skinnable) { super(skinnable); @@ -94,7 +93,7 @@ public void fire() { JFXButton btnMove = new JFXButton(); SpinnerPane spinnerMove = new SpinnerPane(); spinnerMove.getStyleClass().add("small-spinner-pane"); - btnMove.setOnMouseClicked(e -> { + btnMove.setOnAction(e -> { Account account = skinnable.getAccount(); Accounts.getAccounts().remove(account); if (account.isPortable()) { @@ -118,10 +117,10 @@ public void fire() { btnMove.getStyleClass().add("toggle-icon4"); if (skinnable.getAccount().isPortable()) { btnMove.setGraphic(SVG.EARTH.createIcon(Theme.blackFill(), -1, -1)); - runInFX(() -> FXUtils.installFastTooltip(btnMove, i18n("account.move_to_global"))); + FXUtils.installFastTooltip(btnMove, i18n("account.move_to_global")); } else { btnMove.setGraphic(SVG.EXPORT.createIcon(Theme.blackFill(), -1, -1)); - runInFX(() -> FXUtils.installFastTooltip(btnMove, i18n("account.move_to_portable"))); + FXUtils.installFastTooltip(btnMove, i18n("account.move_to_portable")); } spinnerMove.setContent(btnMove); right.getChildren().add(spinnerMove); @@ -129,7 +128,7 @@ public void fire() { JFXButton btnRefresh = new JFXButton(); SpinnerPane spinnerRefresh = new SpinnerPane(); spinnerRefresh.getStyleClass().setAll("small-spinner-pane"); - btnRefresh.setOnMouseClicked(e -> { + btnRefresh.setOnAction(e -> { spinnerRefresh.showSpinner(); skinnable.refreshAsync() .whenComplete(Schedulers.javafx(), ex -> { @@ -143,13 +142,13 @@ public void fire() { }); btnRefresh.getStyleClass().add("toggle-icon4"); btnRefresh.setGraphic(SVG.REFRESH.createIcon(Theme.blackFill(), -1, -1)); - runInFX(() -> FXUtils.installFastTooltip(btnRefresh, i18n("button.refresh"))); + FXUtils.installFastTooltip(btnRefresh, i18n("button.refresh")); spinnerRefresh.setContent(btnRefresh); right.getChildren().add(spinnerRefresh); JFXButton btnUpload = new JFXButton(); SpinnerPane spinnerUpload = new SpinnerPane(); - btnUpload.setOnMouseClicked(e -> { + btnUpload.setOnAction(e -> { Task uploadTask = skinnable.uploadSkin(); if (uploadTask != null) { spinnerUpload.showSpinner(); @@ -160,7 +159,7 @@ public void fire() { }); btnUpload.getStyleClass().add("toggle-icon4"); btnUpload.setGraphic(SVG.HANGER.createIcon(Theme.blackFill(), -1, -1)); - runInFX(() -> FXUtils.installFastTooltip(btnUpload, i18n("account.skin.upload"))); + FXUtils.installFastTooltip(btnUpload, i18n("account.skin.upload")); spinnerUpload.managedProperty().bind(spinnerUpload.visibleProperty()); spinnerUpload.visibleProperty().bind(skinnable.canUploadSkin()); spinnerUpload.setContent(btnUpload); @@ -170,18 +169,19 @@ public void fire() { JFXButton btnCopyUUID = new JFXButton(); SpinnerPane spinnerCopyUUID = new SpinnerPane(); spinnerCopyUUID.getStyleClass().add("small-spinner-pane"); - btnCopyUUID.setOnMouseClicked(e -> FXUtils.copyText(skinnable.getAccount().getUUID().toString())); + btnUpload.getStyleClass().add("toggle-icon4"); + btnCopyUUID.setOnAction(e -> FXUtils.copyText(skinnable.getAccount().getUUID().toString())); btnCopyUUID.setGraphic(SVG.COPY.createIcon(Theme.blackFill(), -1, -1)); - runInFX(() -> FXUtils.installFastTooltip(btnCopyUUID, i18n("account.copy_uuid"))); + FXUtils.installFastTooltip(btnCopyUUID, i18n("account.copy_uuid")); spinnerCopyUUID.setContent(btnCopyUUID); right.getChildren().add(spinnerCopyUUID); JFXButton btnRemove = new JFXButton(); - btnRemove.setOnMouseClicked(e -> skinnable.remove()); + btnRemove.setOnAction(e -> Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), skinnable::remove, null)); btnRemove.getStyleClass().add("toggle-icon4"); BorderPane.setAlignment(btnRemove, Pos.CENTER); - btnRemove.setGraphic(SVG.DELETE.createIcon(Theme.blackFill(), -1, -1)); - runInFX(() -> FXUtils.installFastTooltip(btnRemove, i18n("button.delete"))); + btnRemove.setGraphic(SVG.DELETE_OUTLINE.createIcon(Theme.blackFill(), -1, -1)); + FXUtils.installFastTooltip(btnRemove, i18n("button.delete")); right.getChildren().add(btnRemove); root.setRight(right); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java index ddfcfa056a..ae970c8643 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java @@ -44,6 +44,7 @@ import org.jackhuang.hmcl.util.javafx.MappedObservableList; import java.net.URI; +import java.util.Locale; import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap; import static org.jackhuang.hmcl.util.logging.Logger.LOG; @@ -95,7 +96,7 @@ public AccountListPageSkin(AccountListPage skinnable) { VBox boxMethods = new VBox(); { boxMethods.getStyleClass().add("advanced-list-box-content"); - boxMethods.getChildren().add(new ClassTitle(i18n("account.create"))); + boxMethods.getChildren().add(new ClassTitle(i18n("account.create").toUpperCase(Locale.ROOT))); FXUtils.setLimitWidth(boxMethods, 200); AdvancedListItem offlineItem = new AdvancedListItem(); @@ -123,7 +124,9 @@ public AccountListPageSkin(AccountListPage skinnable) { JFXButton btnRemove = new JFXButton(); btnRemove.setOnAction(e -> { - skinnable.authServersProperty().remove(server); + Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), () -> { + skinnable.authServersProperty().remove(server); + }, null); e.consume(); }); btnRemove.getStyleClass().add("toggle-icon4"); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AddAuthlibInjectorServerPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AddAuthlibInjectorServerPane.java index 914046649d..c19c80131c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AddAuthlibInjectorServerPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AddAuthlibInjectorServerPane.java @@ -147,7 +147,7 @@ public AddAuthlibInjectorServerPane() { confirmServerPane.setActions(prevButton, cancelButton, finishButton); } - this.setContent(addServerPane, ContainerAnimations.NONE.getAnimationProducer()); + this.setContent(addServerPane, ContainerAnimations.NONE); lblCreationWarning.maxWidthProperty().bind(((FlowPane) lblCreationWarning.getParent()).widthProperty()); btnAddNext.disableProperty().bind(txtServerUrl.textProperty().isEmpty()); @@ -198,7 +198,7 @@ private void onAddNext() { lblServerWarning.setVisible("http".equals(NetworkUtils.toURL(serverBeingAdded.getUrl()).getProtocol())); - this.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT.getAnimationProducer()); + this.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT); } else { LOG.warning("Failed to resolve auth server: " + url, exception); lblCreationWarning.setText(resolveFetchExceptionMessage(exception)); @@ -208,7 +208,7 @@ private void onAddNext() { } private void onAddPrev() { - this.setContent(addServerPane, ContainerAnimations.SWIPE_RIGHT.getAnimationProducer()); + this.setContent(addServerPane, ContainerAnimations.SWIPE_RIGHT); } private void onAddFinish() { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java index 9a17ba55cc..a5d8bee8b9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java @@ -65,6 +65,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.UUID; import java.util.concurrent.CountDownLatch; @@ -296,7 +297,7 @@ private void initDetailsPane() { hintPane.setSegment(i18n("account.methods.microsoft.hint")); } }); - hintPane.setOnMouseClicked(e -> { + FXUtils.onClicked(hintPane, () -> { if (deviceCode.get() != null) { FXUtils.copyText(deviceCode.get().getUserCode()); } @@ -316,7 +317,7 @@ private void initDetailsPane() { JFXHyperlink deauthorizeLink = new JFXHyperlink(i18n("account.methods.microsoft.deauthorize")); deauthorizeLink.setExternalLink("https://account.live.com/consent/Edit?client_id=000000004C794E0A"); JFXHyperlink forgotpasswordLink = new JFXHyperlink(i18n("account.methods.forgot_password")); - forgotpasswordLink.setExternalLink("https://www.minecraft.net/password/forgot"); + forgotpasswordLink.setExternalLink("https://account.live.com/ResetPassword.aspx"); JFXHyperlink createProfileLink = new JFXHyperlink(i18n("account.methods.microsoft.makegameidsettings")); createProfileLink.setExternalLink("https://www.minecraft.net/msaprofile/mygames/editprofile"); box.getChildren().setAll(profileLink, birthLink, purchaseLink, deauthorizeLink, forgotpasswordLink, createProfileLink); @@ -509,7 +510,7 @@ public AccountDetailsInputPane(AccountFactory factory, Runnable onAction) { if (factory instanceof OfflineAccountFactory) { txtUsername.setPromptText(i18n("account.methods.offline.name.special_characters")); - runInFX(() -> FXUtils.installFastTooltip(txtUsername, i18n("account.methods.offline.name.special_characters"))); + FXUtils.installFastTooltip(txtUsername, i18n("account.methods.offline.name.special_characters")); JFXHyperlink purchaseLink = new JFXHyperlink(i18n("account.methods.microsoft.purchase")); purchaseLink.setExternalLink(YggdrasilService.PURCHASE_URL); @@ -637,7 +638,7 @@ public DialogCharacterSelector() { StackPane.setAlignment(cancel, Pos.BOTTOM_RIGHT); cancel.setOnAction(e -> latch.countDown()); - listBox.startCategory(i18n("account.choose")); + listBox.startCategory(i18n("account.choose").toUpperCase(Locale.ROOT)); setCenter(listBox); @@ -657,7 +658,7 @@ public GameProfile select(YggdrasilService service, List profiles) TexturesLoader.bindAvatar(portraitCanvas, service, profile.getId()); IconedItem accountItem = new IconedItem(portraitCanvas, profile.getName()); - accountItem.setOnMouseClicked(e -> { + FXUtils.onClicked(accountItem, () -> { selectedProfile = profile; latch.countDown(); }); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/OAuthAccountLoginDialog.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/OAuthAccountLoginDialog.java index bf9f98bce4..36780c3d47 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/OAuthAccountLoginDialog.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/OAuthAccountLoginDialog.java @@ -58,7 +58,7 @@ public OAuthAccountLoginDialog(OAuthAccount account, Consumer success, ); } }); - hintPane.setOnMouseClicked(e -> { + FXUtils.onClicked(hintPane, () -> { if (deviceCode.get() != null) { FXUtils.copyText(deviceCode.get().getUserCode()); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/ContainerAnimations.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/ContainerAnimations.java index 20bca631dd..281ac618db 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/ContainerAnimations.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/ContainerAnimations.java @@ -1,6 +1,6 @@ /* * Hello Minecraft! Launcher - * Copyright (C) 2020 huangyuhui and contributors + * Copyright (C) 2020 huangyuhui and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -27,165 +27,225 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.function.Consumer; -import java.util.function.Function; -public enum ContainerAnimations { - NONE(c -> { - c.getPreviousNode().setTranslateX(0); - c.getPreviousNode().setTranslateY(0); - c.getPreviousNode().setScaleX(1); - c.getPreviousNode().setScaleY(1); - c.getPreviousNode().setOpacity(1); - c.getCurrentNode().setTranslateX(0); - c.getCurrentNode().setTranslateY(0); - c.getCurrentNode().setScaleX(1); - c.getCurrentNode().setScaleY(1); - c.getCurrentNode().setOpacity(1); - }, c -> Collections.emptyList()), +public enum ContainerAnimations implements AnimationProducer { + NONE { + @Override + public void init(AnimationHandler c) { + c.getPreviousNode().setTranslateX(0); + c.getPreviousNode().setTranslateY(0); + c.getPreviousNode().setScaleX(1); + c.getPreviousNode().setScaleY(1); + c.getPreviousNode().setOpacity(1); + c.getCurrentNode().setTranslateX(0); + c.getCurrentNode().setTranslateY(0); + c.getCurrentNode().setScaleX(1); + c.getCurrentNode().setScaleY(1); + c.getCurrentNode().setOpacity(1); + } + + @Override + public List animate(AnimationHandler c) { + return Collections.emptyList(); + } + }, /** * A fade between the old and new view */ - FADE(c -> { - c.getPreviousNode().setTranslateX(0); - c.getPreviousNode().setTranslateY(0); - c.getPreviousNode().setScaleX(1); - c.getPreviousNode().setScaleY(1); - c.getPreviousNode().setOpacity(1); - c.getCurrentNode().setTranslateX(0); - c.getCurrentNode().setTranslateY(0); - c.getCurrentNode().setScaleX(1); - c.getCurrentNode().setScaleY(1); - c.getCurrentNode().setOpacity(0); - }, c -> - Arrays.asList(new KeyFrame(Duration.ZERO, + FADE { + @Override + public void init(AnimationHandler c) { + c.getPreviousNode().setTranslateX(0); + c.getPreviousNode().setTranslateY(0); + c.getPreviousNode().setScaleX(1); + c.getPreviousNode().setScaleY(1); + c.getPreviousNode().setOpacity(1); + c.getCurrentNode().setTranslateX(0); + c.getCurrentNode().setTranslateY(0); + c.getCurrentNode().setScaleX(1); + c.getCurrentNode().setScaleY(1); + c.getCurrentNode().setOpacity(0); + } + + @Override + public List animate(AnimationHandler c) { + return Arrays.asList(new KeyFrame(Duration.ZERO, new KeyValue(c.getPreviousNode().opacityProperty(), 1, Interpolator.EASE_BOTH), new KeyValue(c.getCurrentNode().opacityProperty(), 0, Interpolator.EASE_BOTH)), new KeyFrame(c.getDuration(), new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH), - new KeyValue(c.getCurrentNode().opacityProperty(), 1, Interpolator.EASE_BOTH)))), + new KeyValue(c.getCurrentNode().opacityProperty(), 1, Interpolator.EASE_BOTH))); + } + }, /** * A fade between the old and new view */ - FADE_IN(c -> { - c.getCurrentNode().setTranslateX(0); - c.getCurrentNode().setTranslateY(0); - c.getCurrentNode().setScaleX(1); - c.getCurrentNode().setScaleY(1); - c.getCurrentNode().setOpacity(0); - }, c -> - Arrays.asList(new KeyFrame(Duration.ZERO, + FADE_IN { + @Override + public void init(AnimationHandler c) { + c.getCurrentNode().setTranslateX(0); + c.getCurrentNode().setTranslateY(0); + c.getCurrentNode().setScaleX(1); + c.getCurrentNode().setScaleY(1); + c.getCurrentNode().setOpacity(0); + } + + @Override + public List animate(AnimationHandler c) { + return Arrays.asList(new KeyFrame(Duration.ZERO, new KeyValue(c.getCurrentNode().opacityProperty(), 0, FXUtils.SINE)), new KeyFrame(c.getDuration(), - new KeyValue(c.getCurrentNode().opacityProperty(), 1, FXUtils.SINE)))), + new KeyValue(c.getCurrentNode().opacityProperty(), 1, FXUtils.SINE))); + } + }, /** * A fade between the old and new view */ - FADE_OUT(c -> { - c.getCurrentNode().setTranslateX(0); - c.getCurrentNode().setTranslateY(0); - c.getCurrentNode().setScaleX(1); - c.getCurrentNode().setScaleY(1); - c.getCurrentNode().setOpacity(1); - }, c -> - Arrays.asList(new KeyFrame(Duration.ZERO, + FADE_OUT { + @Override + public void init(AnimationHandler c) { + c.getCurrentNode().setTranslateX(0); + c.getCurrentNode().setTranslateY(0); + c.getCurrentNode().setScaleX(1); + c.getCurrentNode().setScaleY(1); + c.getCurrentNode().setOpacity(1); + } + + @Override + public List animate(AnimationHandler c) { + return Arrays.asList(new KeyFrame(Duration.ZERO, new KeyValue(c.getCurrentNode().opacityProperty(), 1, FXUtils.SINE)), new KeyFrame(c.getDuration(), - new KeyValue(c.getCurrentNode().opacityProperty(), 0, FXUtils.SINE)))), + new KeyValue(c.getCurrentNode().opacityProperty(), 0, FXUtils.SINE))); + } + }, /** * A zoom effect */ - ZOOM_IN(c -> { - c.getPreviousNode().setTranslateX(0); - c.getPreviousNode().setTranslateY(0); - c.getPreviousNode().setScaleX(1); - c.getPreviousNode().setScaleY(1); - c.getPreviousNode().setOpacity(1); - c.getCurrentNode().setTranslateX(0); - c.getCurrentNode().setTranslateY(0); - }, c -> - Arrays.asList(new KeyFrame(Duration.ZERO, + ZOOM_IN { + @Override + public void init(AnimationHandler c) { + c.getPreviousNode().setTranslateX(0); + c.getPreviousNode().setTranslateY(0); + c.getPreviousNode().setScaleX(1); + c.getPreviousNode().setScaleY(1); + c.getPreviousNode().setOpacity(1); + c.getCurrentNode().setTranslateX(0); + c.getCurrentNode().setTranslateY(0); + } + + @Override + public List animate(AnimationHandler c) { + return Arrays.asList(new KeyFrame(Duration.ZERO, new KeyValue(c.getPreviousNode().scaleXProperty(), 1, Interpolator.EASE_BOTH), new KeyValue(c.getPreviousNode().scaleYProperty(), 1, Interpolator.EASE_BOTH), new KeyValue(c.getPreviousNode().opacityProperty(), 1, Interpolator.EASE_BOTH)), new KeyFrame(c.getDuration(), new KeyValue(c.getPreviousNode().scaleXProperty(), 4, Interpolator.EASE_BOTH), new KeyValue(c.getPreviousNode().scaleYProperty(), 4, Interpolator.EASE_BOTH), - new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH)))), + new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH))); + } + }, /** * A zoom effect */ - ZOOM_OUT(c -> { - c.getPreviousNode().setTranslateX(0); - c.getPreviousNode().setTranslateY(0); - c.getPreviousNode().setScaleX(1); - c.getPreviousNode().setScaleY(1); - c.getPreviousNode().setOpacity(1); - c.getCurrentNode().setTranslateX(0); - c.getCurrentNode().setTranslateY(0); - }, c -> - (Arrays.asList(new KeyFrame(Duration.ZERO, + ZOOM_OUT { + @Override + public void init(AnimationHandler c) { + c.getPreviousNode().setTranslateX(0); + c.getPreviousNode().setTranslateY(0); + c.getPreviousNode().setScaleX(1); + c.getPreviousNode().setScaleY(1); + c.getPreviousNode().setOpacity(1); + c.getCurrentNode().setTranslateX(0); + c.getCurrentNode().setTranslateY(0); + } + + @Override + public List animate(AnimationHandler c) { + return Arrays.asList(new KeyFrame(Duration.ZERO, new KeyValue(c.getPreviousNode().scaleXProperty(), 1, Interpolator.EASE_BOTH), new KeyValue(c.getPreviousNode().scaleYProperty(), 1, Interpolator.EASE_BOTH), new KeyValue(c.getPreviousNode().opacityProperty(), 1, Interpolator.EASE_BOTH)), new KeyFrame(c.getDuration(), new KeyValue(c.getPreviousNode().scaleXProperty(), 0, Interpolator.EASE_BOTH), new KeyValue(c.getPreviousNode().scaleYProperty(), 0, Interpolator.EASE_BOTH), - new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH))))), + new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH))); + } + }, /** * A swipe effect */ - SWIPE_LEFT(c -> { - c.getPreviousNode().setScaleX(1); - c.getPreviousNode().setScaleY(1); - c.getPreviousNode().setOpacity(0); - c.getPreviousNode().setTranslateX(0); - c.getCurrentNode().setScaleX(1); - c.getCurrentNode().setScaleY(1); - c.getCurrentNode().setOpacity(1); - c.getCurrentNode().setTranslateX(c.getCurrentRoot().getWidth()); - }, c -> - Arrays.asList(new KeyFrame(Duration.ZERO, + SWIPE_LEFT { + @Override + public void init(AnimationHandler c) { + c.getPreviousNode().setScaleX(1); + c.getPreviousNode().setScaleY(1); + c.getPreviousNode().setOpacity(0); + c.getPreviousNode().setTranslateX(0); + c.getCurrentNode().setScaleX(1); + c.getCurrentNode().setScaleY(1); + c.getCurrentNode().setOpacity(1); + c.getCurrentNode().setTranslateX(c.getCurrentRoot().getWidth()); + } + + @Override + public List animate(AnimationHandler c) { + return Arrays.asList(new KeyFrame(Duration.ZERO, new KeyValue(c.getCurrentNode().translateXProperty(), c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH), new KeyValue(c.getPreviousNode().translateXProperty(), 0, Interpolator.EASE_BOTH)), new KeyFrame(c.getDuration(), new KeyValue(c.getCurrentNode().translateXProperty(), 0, Interpolator.EASE_BOTH), - new KeyValue(c.getPreviousNode().translateXProperty(), -c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH)))), + new KeyValue(c.getPreviousNode().translateXProperty(), -c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH))); + } + }, /** * A swipe effect */ - SWIPE_RIGHT(c -> { - c.getPreviousNode().setScaleX(1); - c.getPreviousNode().setScaleY(1); - c.getPreviousNode().setOpacity(0); - c.getPreviousNode().setTranslateX(0); - c.getCurrentNode().setScaleX(1); - c.getCurrentNode().setScaleY(1); - c.getCurrentNode().setOpacity(1); - c.getCurrentNode().setTranslateX(-c.getCurrentRoot().getWidth()); - }, c -> - Arrays.asList(new KeyFrame(Duration.ZERO, + SWIPE_RIGHT { + @Override + public void init(AnimationHandler c) { + c.getPreviousNode().setScaleX(1); + c.getPreviousNode().setScaleY(1); + c.getPreviousNode().setOpacity(0); + c.getPreviousNode().setTranslateX(0); + c.getCurrentNode().setScaleX(1); + c.getCurrentNode().setScaleY(1); + c.getCurrentNode().setOpacity(1); + c.getCurrentNode().setTranslateX(-c.getCurrentRoot().getWidth()); + } + + @Override + public List animate(AnimationHandler c) { + return Arrays.asList(new KeyFrame(Duration.ZERO, new KeyValue(c.getCurrentNode().translateXProperty(), -c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH), new KeyValue(c.getPreviousNode().translateXProperty(), 0, Interpolator.EASE_BOTH)), new KeyFrame(c.getDuration(), new KeyValue(c.getCurrentNode().translateXProperty(), 0, Interpolator.EASE_BOTH), - new KeyValue(c.getPreviousNode().translateXProperty(), c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH)))), + new KeyValue(c.getPreviousNode().translateXProperty(), c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH))); + } + }, + + SWIPE_LEFT_FADE_SHORT { + @Override + public void init(AnimationHandler c) { + c.getPreviousNode().setScaleX(1); + c.getPreviousNode().setScaleY(1); + c.getPreviousNode().setOpacity(0); + c.getPreviousNode().setTranslateX(0); + c.getCurrentNode().setScaleX(1); + c.getCurrentNode().setScaleY(1); + c.getCurrentNode().setOpacity(1); + c.getCurrentNode().setTranslateX(c.getCurrentRoot().getWidth()); + } - SWIPE_LEFT_FADE_SHORT(c -> { - c.getPreviousNode().setScaleX(1); - c.getPreviousNode().setScaleY(1); - c.getPreviousNode().setOpacity(0); - c.getPreviousNode().setTranslateX(0); - c.getCurrentNode().setScaleX(1); - c.getCurrentNode().setScaleY(1); - c.getCurrentNode().setOpacity(1); - c.getCurrentNode().setTranslateX(c.getCurrentRoot().getWidth()); - }, c -> - Arrays.asList(new KeyFrame(Duration.ZERO, + @Override + public List animate(AnimationHandler c) { + return Arrays.asList(new KeyFrame(Duration.ZERO, new KeyValue(c.getCurrentNode().translateXProperty(), 50, Interpolator.EASE_BOTH), new KeyValue(c.getPreviousNode().translateXProperty(), 0, Interpolator.EASE_BOTH), new KeyValue(c.getCurrentNode().opacityProperty(), 0, Interpolator.EASE_BOTH), @@ -194,19 +254,26 @@ public enum ContainerAnimations { new KeyValue(c.getCurrentNode().translateXProperty(), 0, Interpolator.EASE_BOTH), new KeyValue(c.getPreviousNode().translateXProperty(), -50, Interpolator.EASE_BOTH), new KeyValue(c.getCurrentNode().opacityProperty(), 1, Interpolator.EASE_BOTH), - new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH)))), + new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH))); + } + }, - SWIPE_RIGHT_FADE_SHORT(c -> { - c.getPreviousNode().setScaleX(1); - c.getPreviousNode().setScaleY(1); - c.getPreviousNode().setOpacity(0); - c.getPreviousNode().setTranslateX(0); - c.getCurrentNode().setScaleX(1); - c.getCurrentNode().setScaleY(1); - c.getCurrentNode().setOpacity(1); - c.getCurrentNode().setTranslateX(c.getCurrentRoot().getWidth()); - }, c -> - Arrays.asList(new KeyFrame(Duration.ZERO, + SWIPE_RIGHT_FADE_SHORT { + @Override + public void init(AnimationHandler c) { + c.getPreviousNode().setScaleX(1); + c.getPreviousNode().setScaleY(1); + c.getPreviousNode().setOpacity(0); + c.getPreviousNode().setTranslateX(0); + c.getCurrentNode().setScaleX(1); + c.getCurrentNode().setScaleY(1); + c.getCurrentNode().setOpacity(1); + c.getCurrentNode().setTranslateX(c.getCurrentRoot().getWidth()); + } + + @Override + public List animate(AnimationHandler c) { + return Arrays.asList(new KeyFrame(Duration.ZERO, new KeyValue(c.getCurrentNode().translateXProperty(), -50, Interpolator.EASE_BOTH), new KeyValue(c.getPreviousNode().translateXProperty(), 0, Interpolator.EASE_BOTH), new KeyValue(c.getCurrentNode().opacityProperty(), 0, Interpolator.EASE_BOTH), @@ -215,38 +282,12 @@ public enum ContainerAnimations { new KeyValue(c.getCurrentNode().translateXProperty(), 0, Interpolator.EASE_BOTH), new KeyValue(c.getPreviousNode().translateXProperty(), 50, Interpolator.EASE_BOTH), new KeyValue(c.getCurrentNode().opacityProperty(), 1, Interpolator.EASE_BOTH), - new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH)))); + new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH))); + } + }; - private final AnimationProducer animationProducer; private ContainerAnimations opposite; - ContainerAnimations(Consumer init, Function> animationProducer) { - this.animationProducer = new AnimationProducer() { - @Override - public void init(AnimationHandler handler) { - init.accept(handler); - } - - @Override - public List animate(AnimationHandler handler) { - return animationProducer.apply(handler); - } - - @Override - public @Nullable AnimationProducer opposite() { - return opposite != null ? opposite.getAnimationProducer() : null; - } - }; - } - - public AnimationProducer getAnimationProducer() { - return animationProducer; - } - - public ContainerAnimations getOpposite() { - return opposite; - } - static { NONE.opposite = NONE; FADE.opposite = FADE; @@ -257,4 +298,15 @@ public ContainerAnimations getOpposite() { ZOOM_IN.opposite = ZOOM_OUT; ZOOM_OUT.opposite = ZOOM_IN; } + + @Override + public abstract void init(AnimationHandler handler); + + @Override + public abstract List animate(AnimationHandler handler); + + @Override + public @Nullable ContainerAnimations opposite() { + return opposite; + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AdvancedListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AdvancedListItem.java index 8464699081..cdbedba2f3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AdvancedListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AdvancedListItem.java @@ -25,7 +25,6 @@ import javafx.scene.control.Skin; import javafx.scene.image.Image; import javafx.scene.image.ImageView; -import javafx.scene.input.MouseEvent; import javafx.scene.layout.StackPane; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.util.Pair; @@ -42,7 +41,7 @@ public class AdvancedListItem extends Control { public AdvancedListItem() { getStyleClass().add("advanced-list-item"); - addEventHandler(MouseEvent.MOUSE_CLICKED, e -> fireEvent(new ActionEvent())); + FXUtils.onClicked(this, () -> fireEvent(new ActionEvent())); } public Node getLeftGraphic() { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AnnouncementCard.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AnnouncementCard.java index 7262bcadd3..5e80222c4d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AnnouncementCard.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AnnouncementCard.java @@ -17,21 +17,36 @@ */ package org.jackhuang.hmcl.ui.construct; +import javafx.scene.Cursor; +import javafx.scene.Node; import javafx.scene.control.Label; +import javafx.scene.layout.BorderPane; import javafx.scene.layout.VBox; import javafx.scene.text.TextFlow; +import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.SVG; -public class AnnouncementCard extends VBox { +public final class AnnouncementCard extends VBox { - public AnnouncementCard(String title, String content) { - TextFlow tf = FXUtils.segmentToTextFlow(content, Controllers::onHyperlinkAction); + public AnnouncementCard(String title, String content, Runnable onClose) { + TextFlow body = FXUtils.segmentToTextFlow(content, Controllers::onHyperlinkAction); + body.setLineSpacing(4); - Label label = new Label(title); - label.getStyleClass().add("title"); - getChildren().setAll(label, tf); - setSpacing(14); + BorderPane titleBar = new BorderPane(); + titleBar.getStyleClass().add("title"); + titleBar.setLeft(new Label(title)); + + if (onClose != null) { + Node hideNode = SVG.CLOSE.createIcon(Theme.blackFill(), 20, 20); + hideNode.setOnMouseClicked(e -> onClose.run()); + hideNode.setCursor(Cursor.HAND); + titleBar.setRight(hideNode); + } + + getChildren().setAll(titleBar, body); + setSpacing(16); getStyleClass().addAll("card", "announcement"); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java index d6f2a5f5c0..bd97fba095 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentListCell.java @@ -25,9 +25,6 @@ import javafx.application.Platform; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; -import javafx.event.ActionEvent; -import javafx.event.Event; -import javafx.event.EventHandler; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Node; @@ -139,7 +136,7 @@ private void updateLayout() { container.getChildren().setAll(content); groupNode.getChildren().add(container); - EventHandler onExpand = e -> { + Runnable onExpand = () -> { if (expandAnimation != null && expandAnimation.getStatus() == Animation.Status.RUNNING) { expandAnimation.stop(); } @@ -182,8 +179,8 @@ private void updateLayout() { }); }; - headerRippler.setOnMouseClicked(onExpand); - expandButton.setOnAction((EventHandler) (Object) onExpand); + FXUtils.onClicked(headerRippler, onExpand); + expandButton.setOnAction(e -> onExpand.run()); expandedProperty().addListener((a, b, newValue) -> expandIcon.setRotate(newValue ? 180 : 0)); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FileItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FileItem.java index 7a636c58ad..1df0657605 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FileItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FileItem.java @@ -60,7 +60,7 @@ public FileItem() { JFXButton right = new JFXButton(); right.setGraphic(SVG.PENCIL.createIcon(Theme.blackFill(), 15, 15)); right.getStyleClass().add("toggle-icon4"); - right.setOnMouseClicked(e -> onExplore()); + right.setOnAction(e -> onExplore()); FXUtils.installFastTooltip(right, i18n("button.edit")); setRight(right); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FontComboBox.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FontComboBox.java index 58b0923beb..ae50156606 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FontComboBox.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FontComboBox.java @@ -21,6 +21,7 @@ import static javafx.collections.FXCollections.observableList; import static javafx.collections.FXCollections.singletonObservableList; +import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.util.javafx.BindingMapping; import com.jfoenix.controls.JFXComboBox; @@ -51,7 +52,7 @@ public void updateItem(String item, boolean empty) { itemsProperty().bind(BindingMapping.of(valueProperty()) .map(value -> value == null ? emptyObservableList() : singletonObservableList(value))); - setOnMouseClicked(e -> { + FXUtils.onClicked(this, () -> { if (loaded) return; itemsProperty().unbind(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedMenuItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedMenuItem.java index b54c6f70b3..dcc1d86aa8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedMenuItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedMenuItem.java @@ -30,9 +30,9 @@ public IconedMenuItem(SVG icon, String text, Runnable action, JFXPopup popup) { getStyleClass().setAll("iconed-menu-item"); if (popup == null) { - setOnMouseClicked(e -> action.run()); + FXUtils.onClicked(this, action); } else { - setOnMouseClicked(e -> { + FXUtils.onClicked(this, () -> { action.run(); popup.hide(); }); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ImagePickerItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ImagePickerItem.java index e7a0a6d00d..810256c7c5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ImagePickerItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ImagePickerItem.java @@ -23,12 +23,12 @@ import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; +import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Pos; import javafx.scene.control.Label; import javafx.scene.image.Image; import javafx.scene.image.ImageView; -import javafx.scene.input.MouseEvent; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; @@ -44,8 +44,8 @@ public final class ImagePickerItem extends BorderPane { private final ImageView imageView; private final StringProperty title = new SimpleStringProperty(this, "title"); - private final ObjectProperty> onSelectButtonClicked = new SimpleObjectProperty<>(this, "onSelectButtonClicked"); - private final ObjectProperty> onDeleteButtonClicked = new SimpleObjectProperty<>(this, "onDeleteButtonClicked"); + private final ObjectProperty> onSelectButtonClicked = new SimpleObjectProperty<>(this, "onSelectButtonClicked"); + private final ObjectProperty> onDeleteButtonClicked = new SimpleObjectProperty<>(this, "onDeleteButtonClicked"); private final ObjectProperty image = new SimpleObjectProperty<>(this, "image"); public ImagePickerItem() { @@ -55,12 +55,12 @@ public ImagePickerItem() { JFXButton selectButton = new JFXButton(); selectButton.setGraphic(SVG.PENCIL.createIcon(Theme.blackFill(), 20, 20)); - selectButton.onMouseClickedProperty().bind(onSelectButtonClicked); + selectButton.onActionProperty().bind(onSelectButtonClicked); selectButton.getStyleClass().add("toggle-icon4"); JFXButton deleteButton = new JFXButton(); deleteButton.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), 20, 20)); - deleteButton.onMouseClickedProperty().bind(onDeleteButtonClicked); + deleteButton.onActionProperty().bind(onDeleteButtonClicked); deleteButton.getStyleClass().add("toggle-icon4"); FXUtils.installFastTooltip(selectButton, i18n("button.edit")); @@ -93,27 +93,27 @@ public void setTitle(String title) { this.title.set(title); } - public EventHandler getOnSelectButtonClicked() { + public EventHandler getOnSelectButtonClicked() { return onSelectButtonClicked.get(); } - public ObjectProperty> onSelectButtonClickedProperty() { + public ObjectProperty> onSelectButtonClickedProperty() { return onSelectButtonClicked; } - public void setOnSelectButtonClicked(EventHandler onSelectButtonClicked) { + public void setOnSelectButtonClicked(EventHandler onSelectButtonClicked) { this.onSelectButtonClicked.set(onSelectButtonClicked); } - public EventHandler getOnDeleteButtonClicked() { + public EventHandler getOnDeleteButtonClicked() { return onDeleteButtonClicked.get(); } - public ObjectProperty> onDeleteButtonClickedProperty() { + public ObjectProperty> onDeleteButtonClickedProperty() { return onDeleteButtonClicked; } - public void setOnDeleteButtonClicked(EventHandler onDeleteButtonClicked) { + public void setOnDeleteButtonClicked(EventHandler onDeleteButtonClicked) { this.onDeleteButtonClicked.set(onDeleteButtonClicked); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java index dd70b735b6..a709305f1a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java @@ -166,6 +166,16 @@ public Builder addAction(Node actionNode) { return this; } + public Builder addAction(String text, @Nullable Runnable action) { + JFXButton btnAction = new JFXButton(text); + btnAction.getStyleClass().add("dialog-accept"); + if (action != null) { + btnAction.setOnAction(e -> action.run()); + } + dialog.addButton(btnAction); + return this; + } + public Builder ok(@Nullable Runnable ok) { JFXButton btnOk = new JFXButton(i18n("button.ok")); btnOk.getStyleClass().add("dialog-accept"); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MultiFileItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MultiFileItem.java index cd6506fb15..d7e224baa0 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MultiFileItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MultiFileItem.java @@ -57,7 +57,7 @@ public MultiFileItem() { if (toggleSelectedListener != null) toggleSelectedListener.accept(newValue); - selectedData.set((T) newValue.getUserData()); + selectedData.set(newValue != null ? (T) newValue.getUserData() : null); }); selectedData.addListener((a, b, newValue) -> { Optional selecting = group.getToggles().stream() @@ -183,6 +183,10 @@ public StringOption(String title, T data) { super(title, data); } + public JFXTextField getCustomField() { + return customField; + } + public String getValue() { return customField.getText(); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/Navigator.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/Navigator.java index 209c893f7e..4ec4989929 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/Navigator.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/Navigator.java @@ -126,7 +126,7 @@ public void close(Node from) { if (obj instanceof AnimationProducer) { setContent(node, (AnimationProducer) obj); } else { - setContent(node, ContainerAnimations.NONE.getAnimationProducer()); + setContent(node, ContainerAnimations.NONE); } NavigationEvent navigated = new NavigationEvent(this, node, Navigation.NavigationDirection.PREVIOUS, NavigationEvent.NAVIGATED); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/OptionToggleButton.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/OptionToggleButton.java index fd9a6359dc..7883bb37c2 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/OptionToggleButton.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/OptionToggleButton.java @@ -61,9 +61,7 @@ public OptionToggleButton() { toggleButton.setSize(8); FXUtils.setLimitHeight(toggleButton, 30); - container.setOnMouseClicked(e -> { - toggleButton.setSelected(!toggleButton.isSelected()); - }); + FXUtils.onClicked(container, () -> toggleButton.setSelected(!toggleButton.isSelected())); FXUtils.onChangeAndOperate(subtitleProperty(), subtitle -> { if (StringUtils.isNotBlank(subtitle)) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/SpinnerPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/SpinnerPane.java index 8035df0894..2d6ee81f89 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/SpinnerPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/SpinnerPane.java @@ -32,7 +32,6 @@ import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.animation.ContainerAnimations; import org.jackhuang.hmcl.ui.animation.TransitionPane; -import org.jackhuang.hmcl.util.javafx.BindingMapping; @DefaultProperty("content") public class SpinnerPane extends Control { @@ -101,18 +100,19 @@ public final EventHandler getOnFailedAction() { return onFailedActionProperty().get(); } - private ObjectProperty> onFailedAction = new SimpleObjectProperty>(this, "onFailedAction") { + private final ObjectProperty> onFailedAction = new SimpleObjectProperty>(this, "onFailedAction") { @Override protected void invalidated() { setEventHandler(FAILED_ACTION, get()); } }; + @Override - protected Skin createDefaultSkin() { + protected SkinBase createDefaultSkin() { return new Skin(this); } - private static class Skin extends SkinBase { + private static final class Skin extends SkinBase { private final JFXSpinner spinner = new JFXSpinner(); private final StackPane contentPane = new StackPane(); private final StackPane topPane = new StackPane(); @@ -122,20 +122,18 @@ private static class Skin extends SkinBase { @SuppressWarnings("FieldCanBeLocal") // prevent from gc. private final InvalidationListener observer; - protected Skin(SpinnerPane control) { + Skin(SpinnerPane control) { super(control); topPane.getChildren().setAll(spinner); topPane.getStyleClass().add("notice-pane"); failedPane.getStyleClass().add("notice-pane"); failedPane.getChildren().setAll(failedReasonLabel); - failedPane.onMouseClickedProperty().bind( - BindingMapping.of(control.onFailedAction) - .map(actionHandler -> (e -> { - if (actionHandler != null) { - actionHandler.handle(new Event(FAILED_ACTION)); - } - }))); + FXUtils.onClicked(failedPane, () -> { + EventHandler action = control.getOnFailedAction(); + if (action != null) + action.handle(new Event(FAILED_ACTION)); + }); FXUtils.onChangeAndOperate(getSkinnable().content, newValue -> { if (newValue == null) { @@ -148,12 +146,12 @@ protected Skin(SpinnerPane control) { observer = FXUtils.observeWeak(() -> { if (getSkinnable().getFailedReason() != null) { - root.setContent(failedPane, ContainerAnimations.FADE.getAnimationProducer()); + root.setContent(failedPane, ContainerAnimations.FADE); failedReasonLabel.setText(getSkinnable().getFailedReason()); } else if (getSkinnable().isLoading()) { - root.setContent(topPane, ContainerAnimations.FADE.getAnimationProducer()); + root.setContent(topPane, ContainerAnimations.FADE); } else { - root.setContent(contentPane, ContainerAnimations.FADE.getAnimationProducer()); + root.setContent(contentPane, ContainerAnimations.FADE); } }, getSkinnable().loadingProperty(), getSkinnable().failedReasonProperty()); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TabHeader.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TabHeader.java index 8f11574995..c91ac1c82f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TabHeader.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TabHeader.java @@ -30,7 +30,6 @@ import javafx.geometry.Side; import javafx.scene.Node; import javafx.scene.control.*; -import javafx.scene.input.MouseButton; import javafx.scene.layout.*; import javafx.scene.paint.Color; import javafx.scene.transform.Rotate; @@ -594,11 +593,9 @@ public TabHeaderContainer(Tab tab) { FXUtils.onChangeAndOperate(tab.selectedProperty(), selected -> inner.pseudoClassStateChanged(SELECTED_PSEUDOCLASS_STATE, selected)); - this.setOnMouseClicked(event -> { - if (event.getButton() == MouseButton.PRIMARY) { - this.setOpacity(1); - getSkinnable().getSelectionModel().select(tab); - } + FXUtils.onClicked(this, () -> { + this.setOpacity(1); + getSkinnable().getSelectionModel().select(tab); }); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java index 0a846af836..9260cbf778 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java @@ -34,7 +34,7 @@ import org.jackhuang.hmcl.download.forge.ForgeOldInstallTask; import org.jackhuang.hmcl.download.game.GameAssetDownloadTask; import org.jackhuang.hmcl.download.game.GameInstallTask; -import org.jackhuang.hmcl.download.java.JavaDownloadTask; +import org.jackhuang.hmcl.download.java.mojang.MojangJavaDownloadTask; import org.jackhuang.hmcl.download.liteloader.LiteLoaderInstallTask; import org.jackhuang.hmcl.download.neoforge.NeoForgeInstallTask; import org.jackhuang.hmcl.download.neoforge.NeoForgeOldInstallTask; @@ -42,6 +42,7 @@ import org.jackhuang.hmcl.download.quilt.QuiltAPIInstallTask; import org.jackhuang.hmcl.download.quilt.QuiltInstallTask; import org.jackhuang.hmcl.game.HMCLModpackInstallTask; +import org.jackhuang.hmcl.java.JavaInstallTask; import org.jackhuang.hmcl.mod.MinecraftInstanceTask; import org.jackhuang.hmcl.mod.ModpackInstallTask; import org.jackhuang.hmcl.mod.ModpackUpdateTask; @@ -159,8 +160,10 @@ public void onRunning(Task task) { task.setName(i18n("modpack.export")); } else if (task instanceof MinecraftInstanceTask) { task.setName(i18n("modpack.scan")); - } else if (task instanceof JavaDownloadTask) { + } else if (task instanceof MojangJavaDownloadTask) { task.setName(i18n("download.java")); + } else if (task instanceof JavaInstallTask) { + task.setName(i18n("java.install")); } Platform.runLater(() -> { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java index 9b07239994..7b7b552292 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java @@ -30,6 +30,8 @@ import javafx.scene.image.Image; import javafx.scene.input.DragEvent; import javafx.scene.input.KeyEvent; +import javafx.scene.input.MouseButton; +import javafx.scene.input.MouseEvent; import javafx.scene.layout.*; import javafx.scene.paint.Color; import javafx.stage.Stage; @@ -144,6 +146,18 @@ public DecoratorController(Stage stage, Node mainPage) { // press ESC to go back onEscPressed(navigator, this::back); + + try { + // For JavaFX 12+ + MouseButton button = MouseButton.valueOf("BACK"); + navigator.addEventFilter(MouseEvent.MOUSE_CLICKED, e -> { + if (e.getButton() == button) { + back(); + e.consume(); + } + }); + } catch (IllegalArgumentException ignored) { + } } public Decorator getDecorator() { @@ -443,7 +457,7 @@ public void startWizard(WizardProvider wizardProvider) { public void startWizard(WizardProvider wizardProvider, String category) { FXUtils.checkFxUserThread(); - navigator.navigate(new DecoratorWizardDisplayer(wizardProvider, category), ContainerAnimations.FADE.getAnimationProducer()); + navigator.navigate(new DecoratorWizardDisplayer(wizardProvider, category), ContainerAnimations.FADE); } // ==== Authlib Injector DnD ==== diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorSkin.java index 21b33277a8..161bb51be0 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorSkin.java @@ -174,11 +174,11 @@ public DecoratorSkin(Decorator control) { if (s.isAnimate()) { AnimationProducer animation; if (skinnable.getNavigationDirection() == Navigation.NavigationDirection.NEXT) { - animation = ContainerAnimations.SWIPE_LEFT_FADE_SHORT.getAnimationProducer(); + animation = ContainerAnimations.SWIPE_LEFT_FADE_SHORT; } else if (skinnable.getNavigationDirection() == Navigation.NavigationDirection.PREVIOUS) { - animation = ContainerAnimations.SWIPE_RIGHT_FADE_SHORT.getAnimationProducer(); + animation = ContainerAnimations.SWIPE_RIGHT_FADE_SHORT; } else { - animation = ContainerAnimations.FADE.getAnimationProducer(); + animation = ContainerAnimations.FADE; } skinnable.setNavigationDirection(Navigation.NavigationDirection.START); navBarPane.setContent(node, animation); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorTabPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorTabPage.java index 325edd2691..e7cf6b0339 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorTabPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorTabPage.java @@ -36,7 +36,7 @@ public DecoratorTabPage() { if (newValue.getNode() != null) { onNavigating(getCurrentPage()); if (getCurrentPage() != null) getCurrentPage().fireEvent(new Navigator.NavigationEvent(null, getCurrentPage(), Navigation.NavigationDirection.NEXT, Navigator.NavigationEvent.NAVIGATING)); - navigate(newValue.getNode(), ContainerAnimations.FADE.getAnimationProducer()); + navigate(newValue.getNode(), ContainerAnimations.FADE); onNavigated(getCurrentPage()); if (getCurrentPage() != null) getCurrentPage().fireEvent(new Navigator.NavigationEvent(null, getCurrentPage(), Navigation.NavigationDirection.NEXT, Navigator.NavigationEvent.NAVIGATED)); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorWizardDisplayer.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorWizardDisplayer.java index ed6164e7fa..e6a6b5b997 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorWizardDisplayer.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorWizardDisplayer.java @@ -74,7 +74,7 @@ public void onEnd() { @Override public void navigateTo(Node page, Navigation.NavigationDirection nav) { displayer.navigateTo(page, nav); - navigate(page, nav.getAnimation().getAnimationProducer()); + navigate(page, nav.getAnimation()); String prefix = category == null ? "" : category + " - "; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/AdditionalInstallersPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/AdditionalInstallersPage.java index 0bbb02d654..0e7d1ce649 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/AdditionalInstallersPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/AdditionalInstallersPage.java @@ -33,7 +33,7 @@ import java.util.Map; import java.util.Optional; -import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.*; +import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.MINECRAFT; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; class AdditionalInstallersPage extends InstallersPage { @@ -59,7 +59,7 @@ public AdditionalInstallersPage(String gameVersion, Version version, WizardContr for (InstallerItem library : group.getLibraries()) { String libraryId = library.getLibraryId(); if (libraryId.equals("game")) continue; - library.removeAction.set(e -> { + library.setOnRemove(() -> { controller.getSettings().put(libraryId, new UpdateInstallerWizardProvider.RemoveVersionAction(libraryId)); reload(); }); @@ -99,12 +99,12 @@ protected void reload() { if (!"game".equals(libraryId) && currentGameVersion != null && !currentGameVersion.equals(game) && getVersion(libraryId) == null && alreadyInstalled) { // For third-party libraries, if game version is being changed, and the library is not being reinstalled, // warns the user that we should update the library. - library.setState(libraryVersion, /* incompatibleWithGame */ true, /* removable */ true); + library.versionProperty().set(new InstallerItem.InstalledState(libraryVersion, false, true)); compatible = false; } else if (alreadyInstalled || getVersion(libraryId) != null) { - library.setState(libraryVersion, /* incompatibleWithGame */ false, /* removable */ true); + library.versionProperty().set(new InstallerItem.InstalledState(libraryVersion, false, false)); } else { - library.setState(/* libraryVersion */ null, /* incompatibleWithGame */ false, /* removable */ false); + library.versionProperty().set(null); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java index 6ad9798334..74e447abe8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java @@ -55,6 +55,7 @@ import java.nio.file.Path; import java.util.HashMap; import java.util.Map; +import java.util.Locale; import java.util.concurrent.CancellationException; import java.util.function.Supplier; @@ -69,7 +70,6 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage private final TabHeader.Tab modTab = new TabHeader.Tab<>("modTab"); private final TabHeader.Tab modpackTab = new TabHeader.Tab<>("modpackTab"); private final TabHeader.Tab resourcePackTab = new TabHeader.Tab<>("resourcePackTab"); - private final TabHeader.Tab customizationTab = new TabHeader.Tab<>("customizationTab"); private final TabHeader.Tab worldTab = new TabHeader.Tab<>("worldTab"); private final TransitionPane transitionPane = new TransitionPane(); private final DownloadNavigator versionPageNavigator = new DownloadNavigator(); @@ -80,7 +80,7 @@ public DownloadPage() { newGameTab.setNodeSupplier(loadVersionFor(() -> new VersionsPage(versionPageNavigator, i18n("install.installer.choose", i18n("install.installer.game")), "", DownloadProviders.getDownloadProvider(), "game", versionPageNavigator::onGameSelected))); modpackTab.setNodeSupplier(loadVersionFor(() -> { - ModpackDownloadListPage page = new ModpackDownloadListPage(Versions::downloadModpackImpl, false); + DownloadListPage page = HMCLLocalizedDownloadListPage.ofModPack(Versions::downloadModpackImpl, false); JFXButton installLocalModpackButton = FXUtils.newRaisedButton(i18n("install.modpack")); installLocalModpackButton.setOnAction(e -> Versions.importModpack()); @@ -88,9 +88,8 @@ public DownloadPage() { page.getActions().add(installLocalModpackButton); return page; })); - modTab.setNodeSupplier(loadVersionFor(() -> new ModDownloadListPage((profile, version, file) -> download(profile, version, file, "mods"), true))); - resourcePackTab.setNodeSupplier(loadVersionFor(() -> new ResourcePackDownloadListPage((profile, version, file) -> download(profile, version, file, "resourcepacks"), true))); - customizationTab.setNodeSupplier(loadVersionFor(() -> new DownloadListPage(CurseForgeRemoteModRepository.CUSTOMIZATIONS))); + modTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofMod((profile, version, file) -> download(profile, version, file, "mods"), true))); + resourcePackTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofResourcePack((profile, version, file) -> download(profile, version, file, "resourcepacks"), true))); worldTab.setNodeSupplier(loadVersionFor(() -> new DownloadListPage(CurseForgeRemoteModRepository.WORLDS))); tab = new TabHeader(newGameTab, modpackTab, modTab, resourcePackTab, worldTab); @@ -98,12 +97,12 @@ public DownloadPage() { tab.select(newGameTab); FXUtils.onChangeAndOperate(tab.getSelectionModel().selectedItemProperty(), newValue -> { - transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE.getAnimationProducer()); + transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE); }); { AdvancedListBox sideBar = new AdvancedListBox() - .startCategory(i18n("download.game")) + .startCategory(i18n("download.game").toUpperCase(Locale.ROOT)) .addNavigationDrawerItem(item -> { item.setTitle(i18n("game")); item.setLeftGraphic(wrap(SVG.GAMEPAD)); @@ -116,7 +115,7 @@ public DownloadPage() { settingsItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(modpackTab)); settingsItem.setOnAction(e -> tab.select(modpackTab)); }) - .startCategory(i18n("download.content")) + .startCategory(i18n("download.content").toUpperCase(Locale.ROOT)) .addNavigationDrawerItem(item -> { item.setTitle(i18n("mods")); item.setLeftGraphic(wrap(SVG.PUZZLE)); @@ -129,12 +128,6 @@ public DownloadPage() { item.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(resourcePackTab)); item.setOnAction(e -> tab.select(resourcePackTab)); }) -// .addNavigationDrawerItem(item -> { -// item.setTitle(i18n("download.curseforge.customization")); -// item.setLeftGraphic(wrap(SVG::script)); -// item.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(customizationTab)); -// item.setOnAction(e -> selectTabIfCurseForgeAvailable(customizationTab)); -// }) .addNavigationDrawerItem(item -> { item.setTitle(i18n("world")); item.setLeftGraphic(wrap(SVG.EARTH)); @@ -170,7 +163,7 @@ private static void download(Profile profile, @Nullable String version, RemoteMo Path runDirectory = profile.getRepository().hasVersion(version) ? profile.getRepository().getRunDirectory(version).toPath() : profile.getRepository().getBaseDirectory().toPath(); - Controllers.prompt(i18n("archive.name"), (result, resolve, reject) -> { + Controllers.prompt(i18n("archive.file.name"), (result, resolve, reject) -> { if (!OperatingSystem.isNameValid(result)) { reject.accept(i18n("install.new_game.malformed")); return; @@ -212,9 +205,6 @@ private void loadVersions(Profile profile) { if (resourcePackTab.isInitialized()) { resourcePackTab.getNode().loadVersion(profile, null); } - if (customizationTab.isInitialized()) { - customizationTab.getNode().loadVersion(profile, null); - } if (worldTab.isInitialized()) { worldTab.getNode().loadVersion(profile, null); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java index aa6cc4fa81..e5e935eb1a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java @@ -52,44 +52,45 @@ public class InstallersPage extends Control implements WizardPage { protected JFXTextField txtName = new JFXTextField(); protected BooleanProperty installable = new SimpleBooleanProperty(); + private boolean isNameModifiedByUser = false; + public InstallersPage(WizardController controller, HMCLGameRepository repository, String gameVersion, DownloadProvider downloadProvider) { this.controller = controller; - this.group = new InstallerItem.InstallerItemGroup(gameVersion); + this.group = new InstallerItem.InstallerItemGroup(gameVersion, getInstallerItemStyle()); txtName.getValidators().addAll( new RequiredValidator(), new Validator(i18n("install.new_game.already_exists"), str -> !repository.versionIdConflicts(str)), new Validator(i18n("install.new_game.malformed"), HMCLGameRepository::isValidVersionId)); installable.bind(createBooleanBinding(txtName::validate, txtName.textProperty())); - txtName.setText(gameVersion); - - group.game.installable.setValue(false); - for (InstallerItem item : group.getLibraries()) { - item.setStyleMode(InstallerItem.Style.CARD); - } + 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.action.set(e -> { + library.setOnInstall(() -> { if (LibraryAnalyzer.LibraryType.FABRIC_API.getPatchId().equals(libraryId)) { Controllers.dialog(i18n("install.installer.fabric-api.warning"), i18n("message.warning"), MessageDialogPane.MessageType.WARNING); } - if (library.incompatibleLibraryName.get() == null) + 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.removeAction.set(e -> { + library.setOnRemove(() -> { controller.getSettings().remove(libraryId); reload(); }); } } + protected InstallerItem.Style getInstallerItemStyle() { + return InstallerItem.Style.CARD; + } + @Override public String getTitle() { - return i18n("install.new_game"); + return group.getGame().versionProperty().get().getVersion(); } private String getVersion(String id) { @@ -100,13 +101,14 @@ protected void reload() { for (InstallerItem library : group.getLibraries()) { String libraryId = library.getLibraryId(); if (controller.getSettings().containsKey(libraryId)) { - library.libraryVersion.set(getVersion(libraryId)); - library.removable.set(true); + library.versionProperty().set(new InstallerItem.InstalledState(getVersion(libraryId), false, false)); } else { - library.libraryVersion.set(null); - library.removable.set(false); + library.versionProperty().set(null); } } + if (!isNameModifiedByUser) { + setTxtNameWithLoaders(); + } } @Override @@ -128,6 +130,49 @@ protected Skin createDefaultSkin() { return new InstallersPageSkin(this); } + private void setTxtNameWithLoaders() { + StringBuilder nameBuilder = new StringBuilder(group.getGame().versionProperty().get().getVersion()); + + for (InstallerItem library : group.getLibraries()) { + String libraryId = library.getLibraryId().replace(LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId(), ""); + if (!controller.getSettings().containsKey(libraryId)) { + continue; + } + + LibraryAnalyzer.LibraryType libraryType = LibraryAnalyzer.LibraryType.fromPatchId(libraryId); + if (libraryType != null) { + String loaderName; + switch (libraryType) { + case FORGE: + loaderName = i18n("install.installer.forge"); + break; + case NEO_FORGE: + loaderName = i18n("install.installer.neoforge"); + break; + case FABRIC: + loaderName = i18n("install.installer.fabric"); + break; + case LITELOADER: + loaderName = i18n("install.installer.liteloader"); + break; + case QUILT: + loaderName = i18n("install.installer.quilt"); + break; + case OPTIFINE: + loaderName = i18n("install.installer.optifine"); + break; + default: + continue; + } + + nameBuilder.append("-").append(loaderName); + } + } + + txtName.setText(nameBuilder.toString()); + isNameModifiedByUser = false; + } + protected static class InstallersPageSkin extends SkinBase { /** @@ -148,7 +193,7 @@ protected InstallersPageSkin(InstallersPage control) { versionNamePane.setAlignment(Pos.CENTER_LEFT); control.txtName.setMaxWidth(300); - versionNamePane.getChildren().setAll(new Label(i18n("archive.name")), control.txtName); + versionNamePane.getChildren().setAll(new Label(i18n("version.name")), control.txtName); root.setTop(versionNamePane); } @@ -177,7 +222,7 @@ protected InstallersPageSkin(InstallersPage control) { installButton.disableProperty().bind(control.installable.not()); installButton.setPrefWidth(100); installButton.setPrefHeight(40); - installButton.setOnMouseClicked(e -> control.onInstall()); + installButton.setOnAction(e -> control.onInstall()); BorderPane.setAlignment(installButton, Pos.CENTER_RIGHT); root.setBottom(installButton); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/LocalModpackPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/LocalModpackPage.java index 54480e5524..eb9e3f441a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/LocalModpackPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/LocalModpackPage.java @@ -31,10 +31,12 @@ import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.WebPage; import org.jackhuang.hmcl.ui.construct.MessageDialogPane; import org.jackhuang.hmcl.ui.construct.RequiredValidator; import org.jackhuang.hmcl.ui.construct.Validator; import org.jackhuang.hmcl.ui.wizard.WizardController; +import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.io.FileUtils; @@ -78,6 +80,8 @@ public LocalModpackPage(WizardController controller) { }); } + btnDescription.setVisible(false); + File selectedFile; Optional filePath = tryCast(controller.getSettings().get(MODPACK_FILE), File.class); if (filePath.isPresent()) { @@ -107,7 +111,6 @@ public LocalModpackPage(WizardController controller) { hideSpinner(); lblName.setText(selectedFile.getName()); installAsVersion.set(false); - lblModpackLocation.setText(selectedFile.getAbsolutePath()); if (!name.isPresent()) { // trim: https://github.com/HMCL-dev/HMCL/issues/962 @@ -130,12 +133,12 @@ public LocalModpackPage(WizardController controller) { lblVersion.setText(manifest.getVersion()); lblAuthor.setText(manifest.getAuthor()); - lblModpackLocation.setText(selectedFile.getAbsolutePath()); - if (!name.isPresent()) { // trim: https://github.com/HMCL-dev/HMCL/issues/962 txtModpackName.setText(manifest.getName().trim()); } + + btnDescription.setVisible(StringUtils.isNotBlank(manifest.getDescription())); } }).start(); } @@ -153,9 +156,8 @@ protected void onInstall() { } protected void onDescribe() { - if (manifest != null) { - FXUtils.showWebDialog(i18n("modpack.description"), manifest.getDescription()); - } + if (manifest != null) + Controllers.navigate(new WebPage(i18n("modpack.description"), manifest.getDescription())); } public static final String MODPACK_FILE = "MODPACK_FILE"; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackPage.java index 0f93bd0ce0..107429813b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackPage.java @@ -22,9 +22,9 @@ public abstract class ModpackPage extends SpinnerPane implements WizardPage { protected final Label lblName; protected final Label lblVersion; protected final Label lblAuthor; - protected final Label lblModpackLocation; protected final JFXTextField txtModpackName; protected final JFXButton btnInstall; + protected final JFXButton btnDescription; protected ModpackPage(WizardController controller) { this.controller = controller; @@ -37,18 +37,9 @@ protected ModpackPage(WizardController controller) { ComponentList componentList = new ComponentList(); { - BorderPane locationPane = new BorderPane(); - { - locationPane.setLeft(new Label(i18n("modpack.task.install.will"))); - - lblModpackLocation = new Label(); - BorderPane.setAlignment(lblModpackLocation, Pos.CENTER_RIGHT); - locationPane.setCenter(lblModpackLocation); - } - BorderPane archiveNamePane = new BorderPane(); { - Label label = new Label(i18n("archive.name")); + Label label = new Label(i18n("archive.file.name")); BorderPane.setAlignment(label, Pos.CENTER_LEFT); archiveNamePane.setLeft(label); @@ -87,7 +78,7 @@ protected ModpackPage(WizardController controller) { BorderPane descriptionPane = new BorderPane(); { - JFXButton btnDescription = new JFXButton(i18n("modpack.description")); + btnDescription = new JFXButton(i18n("modpack.description")); btnDescription.getStyleClass().add("jfx-button-border"); btnDescription.setOnAction(e -> onDescribe()); descriptionPane.setLeft(btnDescription); @@ -99,7 +90,7 @@ protected ModpackPage(WizardController controller) { } componentList.getContent().setAll( - locationPane, archiveNamePane, modpackNamePane, versionPane, authorPane, descriptionPane); + archiveNamePane, modpackNamePane, versionPane, authorPane, descriptionPane); } borderPane.getChildren().setAll(componentList); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/RemoteModpackPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/RemoteModpackPage.java index b38c663462..1123f3fd8a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/RemoteModpackPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/RemoteModpackPage.java @@ -22,11 +22,12 @@ import org.jackhuang.hmcl.mod.server.ServerModpackManifest; import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.ui.Controllers; -import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.WebPage; import org.jackhuang.hmcl.ui.construct.MessageDialogPane; import org.jackhuang.hmcl.ui.construct.RequiredValidator; import org.jackhuang.hmcl.ui.construct.Validator; import org.jackhuang.hmcl.ui.wizard.WizardController; +import org.jackhuang.hmcl.util.StringUtils; import java.io.IOException; import java.util.Map; @@ -43,7 +44,6 @@ public RemoteModpackPage(WizardController controller) { manifest = tryCast(controller.getSettings().get(MODPACK_SERVER_MANIFEST), ServerModpackManifest.class) .orElseThrow(() -> new IllegalStateException("MODPACK_SERVER_MANIFEST should exist")); - lblModpackLocation.setText(manifest.getFileApi()); try { controller.getSettings().put(MODPACK_MANIFEST, manifest.toModpack(null)); @@ -70,6 +70,8 @@ public RemoteModpackPage(WizardController controller) { new Validator(i18n("install.new_game.already_exists"), str -> !profile.getRepository().versionIdConflicts(str)), new Validator(i18n("install.new_game.malformed"), HMCLGameRepository::isValidVersionId)); } + + btnDescription.setVisible(StringUtils.isNotBlank(manifest.getDescription())); } @Override @@ -84,7 +86,7 @@ protected void onInstall() { } protected void onDescribe() { - FXUtils.showWebDialog(i18n("modpack.description"), manifest.getDescription()); + Controllers.navigate(new WebPage(i18n("modpack.description"), manifest.getDescription())); } public static final String MODPACK_SERVER_MANIFEST = "MODPACK_SERVER_MANIFEST"; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java index e919424ed3..3d4a3c3efe 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java @@ -95,7 +95,7 @@ public VersionsPage(Navigation navigation, String title, String gameVersion, Dow HintPane hintPane = new HintPane(); hintPane.setText(i18n("sponsor.bmclapi")); hintPane.getStyleClass().add("sponsor-pane"); - hintPane.setOnMouseClicked(e -> onSponsor()); + FXUtils.onClicked(hintPane, this::onSponsor); BorderPane.setMargin(hintPane, new Insets(10, 10, 0, 10)); this.setTop(hintPane); @@ -146,7 +146,7 @@ public VersionsPage(Navigation navigation, String title, String gameVersion, Dow failedPane.getStyleClass().add("notice-pane"); { Label label = new Label(i18n("download.failed.refresh")); - label.setOnMouseClicked(e -> onRefresh()); + FXUtils.onClicked(label, this::onRefresh); failedPane.getChildren().setAll(label); } @@ -155,7 +155,7 @@ public VersionsPage(Navigation navigation, String title, String gameVersion, Dow emptyPane.getStyleClass().add("notice-pane"); { Label label = new Label(i18n("download.failed.empty")); - label.setOnMouseClicked(e -> onBack()); + FXUtils.onClicked(label, this::onBack); emptyPane.getChildren().setAll(label); } @@ -180,7 +180,7 @@ public VersionsPage(Navigation navigation, String title, String gameVersion, Dow Holder lastCell = new Holder<>(); list.setCellFactory(listView -> new RemoteVersionListCell(lastCell)); - list.setOnMouseClicked(e -> { + FXUtils.onClicked(list, () -> { if (list.getSelectionModel().getSelectedIndex() < 0) return; navigation.getSettings().put(libraryId, list.getSelectionModel().getSelectedItem()); @@ -210,7 +210,7 @@ private List loadVersions() { @Override public void refresh() { VersionList currentVersionList = versionList; - root.setContent(spinner, ContainerAnimations.FADE.getAnimationProducer()); + root.setContent(spinner, ContainerAnimations.FADE); executor = currentVersionList.refreshAsync(gameVersion).whenComplete((result, exception) -> { if (exception == null) { List items = loadVersions(); @@ -218,7 +218,7 @@ public void refresh() { Platform.runLater(() -> { if (versionList != currentVersionList) return; if (currentVersionList.getVersions(gameVersion).isEmpty()) { - root.setContent(emptyPane, ContainerAnimations.FADE.getAnimationProducer()); + root.setContent(emptyPane, ContainerAnimations.FADE); } else { if (items.isEmpty()) { chkRelease.setSelected(true); @@ -227,14 +227,14 @@ public void refresh() { } else { list.getItems().setAll(items); } - root.setContent(center, ContainerAnimations.FADE.getAnimationProducer()); + root.setContent(center, ContainerAnimations.FADE); } }); } else { LOG.warning("Failed to fetch versions list", exception); Platform.runLater(() -> { if (versionList != currentVersionList) return; - root.setContent(failedPane, ContainerAnimations.FADE.getAnimationProducer()); + root.setContent(failedPane, ContainerAnimations.FADE); }); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackInfoPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackInfoPage.java index e3908c5b91..024c0f40b9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackInfoPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackInfoPage.java @@ -21,12 +21,10 @@ import javafx.beans.binding.Bindings; import javafx.beans.property.*; import javafx.collections.ObservableList; -import javafx.event.EventHandler; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.*; -import javafx.scene.input.MouseEvent; import javafx.scene.layout.*; import javafx.stage.FileChooser; import org.jackhuang.hmcl.auth.Account; @@ -78,7 +76,6 @@ public final class ModpackInfoPage extends Control implements WizardPage { private final SimpleStringProperty authlibInjectorServer = new SimpleStringProperty(); private final SimpleStringProperty launchArguments = new SimpleStringProperty(""); private final SimpleStringProperty javaArguments = new SimpleStringProperty(""); - private final ObjectProperty> next = new SimpleObjectProperty<>(); private final SimpleStringProperty mcbbsThreadId = new SimpleStringProperty(""); public ModpackInfoPage(WizardController controller, HMCLGameRepository gameRepository, String version) { @@ -97,8 +94,6 @@ public ModpackInfoPage(WizardController controller, HMCLGameRepository gameRepos javaArguments.set(versionSetting.getJavaArgs()); canIncludeLauncher = JarUtils.thisJarPath() != null; - - next.set(e -> onNext()); } private void onNext() { @@ -178,9 +173,7 @@ public ModpackInfoPageSkin(ModpackInfoPage skinnable) { if (skinnable.controller.getSettings().get(MODPACK_TYPE) == MODPACK_TYPE_SERVER) { Hyperlink hyperlink = new Hyperlink(i18n("modpack.wizard.step.initialization.server")); - hyperlink.setOnMouseClicked(e -> { - FXUtils.openLink("https://docs.hmcl.net/modpack/serverpack.html"); - }); + hyperlink.setOnAction(e -> FXUtils.openLink("https://docs.hmcl.net/modpack/serverpack.html")); borderPane.setTop(hyperlink); } else { HintPane pane = new HintPane(MessageDialogPane.MessageType.INFO); @@ -378,7 +371,7 @@ public ModpackInfoPageSkin(ModpackInfoPage skinnable) { borderPane.setBottom(hbox); JFXButton nextButton = FXUtils.newRaisedButton(i18n("wizard.next")); - nextButton.onMouseClickedProperty().bind(skinnable.next); + nextButton.setOnAction(e -> skinnable.onNext()); nextButton.setPrefWidth(100); nextButton.setPrefHeight(40); nextButton.disableProperty().bind( diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java index 0c3e8c78ff..9d84d53996 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java @@ -17,18 +17,30 @@ */ package org.jackhuang.hmcl.ui.main; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; import javafx.geometry.Insets; import javafx.scene.control.ScrollPane; +import javafx.scene.image.Image; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.construct.ComponentList; import org.jackhuang.hmcl.ui.construct.IconedTwoLineListItem; +import org.jackhuang.hmcl.util.gson.JsonUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; -public class AboutPage extends StackPane { +public final class AboutPage extends StackPane { public AboutPage() { ComponentList about = new ComponentList(); @@ -48,131 +60,9 @@ public AboutPage() { about.getContent().setAll(launcher, author); } - ComponentList thanks = new ComponentList(); - { - IconedTwoLineListItem yushijinhun = new IconedTwoLineListItem(); - yushijinhun.setImage(FXUtils.newBuiltinImage("/assets/img/yushijinhun.png")); - yushijinhun.setTitle("yushijinhun"); - yushijinhun.setSubtitle(i18n("about.thanks_to.yushijinhun.statement")); - yushijinhun.setExternalLink("https://yushi.moe/"); - - IconedTwoLineListItem bangbang93 = new IconedTwoLineListItem(); - bangbang93.setImage(FXUtils.newBuiltinImage("/assets/img/bangbang93.png")); - bangbang93.setTitle("bangbang93"); - bangbang93.setSubtitle(i18n("about.thanks_to.bangbang93.statement")); - bangbang93.setExternalLink("https://bmclapi2.bangbang93.com/"); - - IconedTwoLineListItem glavo = new IconedTwoLineListItem(); - glavo.setImage(FXUtils.newBuiltinImage("/assets/img/glavo.png")); - glavo.setTitle("Glavo"); - glavo.setSubtitle(i18n("about.thanks_to.glavo.statement")); - glavo.setExternalLink("https://github.com/Glavo"); - - IconedTwoLineListItem zekerzhayard = new IconedTwoLineListItem(); - zekerzhayard.setImage(FXUtils.newBuiltinImage("/assets/img/zekerzhayard.png")); - zekerzhayard.setTitle("ZekerZhayard"); - zekerzhayard.setSubtitle(i18n("about.thanks_to.zekerzhayard.statement")); - zekerzhayard.setExternalLink("https://github.com/ZekerZhayard"); - - IconedTwoLineListItem zkitefly = new IconedTwoLineListItem(); - zkitefly.setImage(FXUtils.newBuiltinImage("/assets/img/zkitefly.png")); - zkitefly.setTitle("Zkitefly"); - zkitefly.setSubtitle(i18n("about.thanks_to.zkitefly.statement")); - zkitefly.setExternalLink("https://github.com/zkitefly"); - - IconedTwoLineListItem burningtnt = new IconedTwoLineListItem(); - burningtnt.setImage(FXUtils.newBuiltinImage("/assets/img/burningtnt.png")); - burningtnt.setTitle("Burning_TNT"); - burningtnt.setSubtitle(i18n("about.thanks_to.burningtnt.statement")); - burningtnt.setExternalLink("https://github.com/burningtnt"); - - IconedTwoLineListItem shulkerSakura = new IconedTwoLineListItem(); - shulkerSakura.setTitle("ShulkerSakura"); - shulkerSakura.setImage(FXUtils.newBuiltinImage("/assets/img/ShulkerSakura.png")); - shulkerSakura.setSubtitle(i18n("about.thanks_to.shulkersakura.statement")); - shulkerSakura.setExternalLink("https://github.com/ShulkerSakura"); - - IconedTwoLineListItem gamerteam = new IconedTwoLineListItem(); - gamerteam.setTitle("gamerteam"); - gamerteam.setImage(FXUtils.newBuiltinImage("/assets/img/gamerteam.png")); - gamerteam.setSubtitle(i18n("about.thanks_to.gamerteam.statement")); - gamerteam.setExternalLink("http://www.zhaisoul.com/"); - - IconedTwoLineListItem redLnn = new IconedTwoLineListItem(); - redLnn.setTitle("Red_lnn"); - redLnn.setImage(FXUtils.newBuiltinImage("/assets/img/red_lnn.png")); - redLnn.setSubtitle(i18n("about.thanks_to.red_lnn.statement")); - - IconedTwoLineListItem mcmod = new IconedTwoLineListItem(); - mcmod.setImage(FXUtils.newBuiltinImage("/assets/img/mcmod.png")); - mcmod.setTitle(i18n("about.thanks_to.mcmod")); - mcmod.setSubtitle(i18n("about.thanks_to.mcmod.statement")); - mcmod.setExternalLink("https://www.mcmod.cn/"); - - IconedTwoLineListItem mcbbs = new IconedTwoLineListItem(); - mcbbs.setImage(FXUtils.newBuiltinImage("/assets/img/chest.png")); - mcbbs.setTitle(i18n("about.thanks_to.mcbbs")); - mcbbs.setSubtitle(i18n("about.thanks_to.mcbbs.statement")); - - IconedTwoLineListItem contributors = new IconedTwoLineListItem(); - contributors.setImage(FXUtils.newBuiltinImage("/assets/img/github.png")); - contributors.setTitle(i18n("about.thanks_to.contributors")); - contributors.setSubtitle(i18n("about.thanks_to.contributors.statement")); - contributors.setExternalLink("https://github.com/HMCL-dev/HMCL/graphs/contributors"); - - IconedTwoLineListItem users = new IconedTwoLineListItem(); - users.setImage(FXUtils.newBuiltinImage("/assets/img/icon.png")); - users.setTitle(i18n("about.thanks_to.users")); - users.setSubtitle(i18n("about.thanks_to.users.statement")); - users.setExternalLink("https://docs.hmcl.net/groups.html"); - - thanks.getContent().setAll(yushijinhun, bangbang93, glavo, zekerzhayard, zkitefly, burningtnt, mcmod, mcbbs, shulkerSakura, gamerteam, redLnn, contributors, users); - } + ComponentList thanks = loadIconedTwoLineList("/assets/about/thanks.json"); - ComponentList dep = new ComponentList(); - { - IconedTwoLineListItem javafx = new IconedTwoLineListItem(); - javafx.setTitle("JavaFX"); - javafx.setSubtitle("Copyright © 2013, 2024, Oracle and/or its affiliates.\nLicensed under the GPL 2 with Classpath Exception."); - javafx.setExternalLink("https://openjfx.io/"); - - IconedTwoLineListItem jfoenix = new IconedTwoLineListItem(); - jfoenix.setTitle("JFoenix"); - jfoenix.setSubtitle("Copyright © 2016 JFoenix.\nLicensed under the MIT License."); - jfoenix.setExternalLink("https://github.com/sshahine/JFoenix"); - - IconedTwoLineListItem gson = new IconedTwoLineListItem(); - gson.setTitle("Gson"); - gson.setSubtitle("Copyright © 2008 Google Inc.\nLicensed under the Apache 2.0 License."); - gson.setExternalLink("https://github.com/google/gson"); - - IconedTwoLineListItem xz = new IconedTwoLineListItem(); - xz.setTitle("XZ for Java"); - xz.setSubtitle("Lasse Collin, Igor Pavlov, and/or Brett Okken.\nPublic Domain."); - xz.setExternalLink("https://tukaani.org/xz/java.html"); - - IconedTwoLineListItem fxgson = new IconedTwoLineListItem(); - fxgson.setTitle("fx-gson"); - fxgson.setSubtitle("Copyright © 2016 Joffrey Bion.\nLicensed under the MIT License."); - fxgson.setExternalLink("https://github.com/joffrey-bion/fx-gson"); - - IconedTwoLineListItem constantPoolScanner = new IconedTwoLineListItem(); - constantPoolScanner.setTitle("Constant Pool Scanner"); - constantPoolScanner.setSubtitle("Copyright © 1997-2010 Oracle and/or its affiliates.\nLicensed under the GPL 2 or the CDDL."); - constantPoolScanner.setExternalLink("https://github.com/jenkinsci/constant-pool-scanner"); - - IconedTwoLineListItem openNBT = new IconedTwoLineListItem(); - openNBT.setTitle("OpenNBT"); - openNBT.setSubtitle("Copyright © 2013-2021 Steveice10.\nLicensed under the MIT License."); - openNBT.setExternalLink("https://github.com/GeyserMC/OpenNBT"); - - IconedTwoLineListItem minecraftJFXSkin = new IconedTwoLineListItem(); - minecraftJFXSkin.setTitle("minecraft-jfx-skin"); - minecraftJFXSkin.setSubtitle("Copyright © 2016 InfinityStudio.\nLicensed under the GPL 3."); - minecraftJFXSkin.setExternalLink("https://github.com/InfinityStudio/minecraft-jfx-skin"); - - dep.getContent().setAll(javafx, jfoenix, gson, xz, fxgson, constantPoolScanner, openNBT, minecraftJFXSkin); - } + ComponentList deps = loadIconedTwoLineList("/assets/about/deps.json"); ComponentList legal = new ComponentList(); { @@ -204,7 +94,7 @@ public AboutPage() { thanks, ComponentList.createComponentListTitle(i18n("about.dependency")), - dep, + deps, ComponentList.createComponentListTitle(i18n("about.legal")), legal @@ -216,4 +106,49 @@ public AboutPage() { FXUtils.smoothScrolling(scrollPane); getChildren().setAll(scrollPane); } + + private static ComponentList loadIconedTwoLineList(String path) { + ComponentList componentList = new ComponentList(); + + InputStream input = FXUtils.class.getResourceAsStream(path); + if (input == null) { + LOG.warning("Resources not found: " + path); + return componentList; + } + + try (Reader reader = new InputStreamReader(input)) { + JsonArray array = JsonUtils.GSON.fromJson(reader, JsonArray.class); + + for (JsonElement element : array) { + JsonObject obj = element.getAsJsonObject(); + IconedTwoLineListItem item = new IconedTwoLineListItem(); + + if (obj.has("image")) { + String image = obj.get("image").getAsString(); + item.setImage(image.startsWith("/") + ? FXUtils.newBuiltinImage(image) + : new Image(image)); + } + + if (obj.has("title")) + item.setTitle(obj.get("title").getAsString()); + else if (obj.has("titleLocalized")) + item.setTitle(i18n(obj.get("titleLocalized").getAsString())); + + if (obj.has("subtitle")) + item.setSubtitle(obj.get("subtitle").getAsString()); + else if (obj.has("subtitleLocalized")) + item.setSubtitle(i18n(obj.get("subtitleLocalized").getAsString())); + + if (obj.has("externalLink")) + item.setExternalLink(obj.get("externalLink").getAsString()); + + componentList.getContent().add(item); + } + } catch (IOException | JsonParseException e) { + LOG.warning("Failed to load list: " + path, e); + } + + return componentList; + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/DownloadSettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/DownloadSettingsPage.java index 23abc00563..695c89ddfa 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/DownloadSettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/DownloadSettingsPage.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.ui.main; import com.jfoenix.controls.*; -import javafx.geometry.HPos; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.Label; @@ -100,7 +99,7 @@ public DownloadSettingsPage() { downloadSource.getChildren().setAll(chooseWrapper, versionListSourcePane, downloadSourcePane); } - content.getChildren().addAll(ComponentList.createComponentListTitle(i18n("settings.launcher.version_list_source")), downloadSource); + content.getChildren().addAll(ComponentList.createComponentListTitle(i18n("settings.launcher.download_source")), downloadSource); } { @@ -204,7 +203,6 @@ public DownloadSettingsPage() { Label host = new Label(i18n("settings.launcher.proxy.host")); GridPane.setRowIndex(host, 1); GridPane.setColumnIndex(host, 0); - GridPane.setHalignment(host, HPos.RIGHT); gridPane.getChildren().add(host); } @@ -220,7 +218,6 @@ public DownloadSettingsPage() { Label port = new Label(i18n("settings.launcher.proxy.port")); GridPane.setRowIndex(port, 2); GridPane.setColumnIndex(port, 0); - GridPane.setHalignment(port, HPos.RIGHT); gridPane.getChildren().add(port); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/HelpPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/HelpPage.java index 33f7935e30..75223967c3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/HelpPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/HelpPage.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.ui.main; import com.google.gson.annotations.SerializedName; -import com.google.gson.reflect.TypeToken; import javafx.geometry.Insets; import javafx.scene.control.ScrollPane; import javafx.scene.layout.VBox; @@ -34,6 +33,7 @@ import java.util.Collections; import java.util.List; +import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public class HelpPage extends SpinnerPane { @@ -63,8 +63,7 @@ public HelpPage() { private void loadHelp() { showSpinner(); - Task.>supplyAsync(() -> HttpRequest.GET("https://docs.hmcl.net/index.json").getJson(new TypeToken>() { - }.getType())) + Task.supplyAsync(() -> HttpRequest.GET("https://docs.hmcl.net/index.json").getJson(listTypeOf(HelpCategory.class))) .thenAcceptAsync(Schedulers.javafx(), helpCategories -> { for (HelpCategory category : helpCategories) { ComponentList categoryPane = new ComponentList(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaDownloadDialog.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaDownloadDialog.java new file mode 100644 index 0000000000..231be4b2b5 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaDownloadDialog.java @@ -0,0 +1,426 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.ui.main; + +import com.jfoenix.controls.*; +import javafx.beans.binding.Bindings; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.scene.control.Label; +import javafx.scene.control.ToggleGroup; +import javafx.scene.layout.ColumnConstraints; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; +import org.jackhuang.hmcl.download.DownloadProvider; +import org.jackhuang.hmcl.download.java.JavaDistribution; +import org.jackhuang.hmcl.download.java.JavaPackageType; +import org.jackhuang.hmcl.download.java.JavaRemoteVersion; +import org.jackhuang.hmcl.download.java.disco.*; +import org.jackhuang.hmcl.game.GameJavaVersion; +import org.jackhuang.hmcl.java.JavaInfo; +import org.jackhuang.hmcl.java.JavaManager; +import org.jackhuang.hmcl.setting.DownloadProviders; +import org.jackhuang.hmcl.task.*; +import org.jackhuang.hmcl.ui.Controllers; +import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.construct.DialogCloseEvent; +import org.jackhuang.hmcl.ui.construct.DialogPane; +import org.jackhuang.hmcl.ui.construct.JFXHyperlink; +import org.jackhuang.hmcl.ui.wizard.SinglePageWizardProvider; +import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.TaskCancellationAction; +import org.jackhuang.hmcl.util.gson.JsonUtils; +import org.jackhuang.hmcl.util.platform.Platform; + +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.CancellationException; +import java.util.function.Consumer; + +import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed; +import static org.jackhuang.hmcl.util.Lang.resolveException; +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; + +/** + * @author Glavo + */ +public final class JavaDownloadDialog extends StackPane { + + public static Runnable showDialogAction(DownloadProvider downloadProvider) { + Platform platform = Platform.SYSTEM_PLATFORM; + + List supportedVersions = GameJavaVersion.getSupportedVersions(platform); + + EnumSet distributions = EnumSet.noneOf(DiscoJavaDistribution.class); + for (DiscoJavaDistribution distribution : DiscoJavaDistribution.values()) { + if (distribution.isSupport(platform)) { + distributions.add(distribution); + } + } + + return supportedVersions.isEmpty() && distributions.isEmpty() + ? null + : () -> Controllers.dialog(new JavaDownloadDialog(downloadProvider, platform, supportedVersions, distributions)); + } + + private final DownloadProvider downloadProvider; + private final Platform platform; + private final List supportedGameJavaVersions; + private final EnumSet distributions; + + private JavaDownloadDialog(DownloadProvider downloadProvider, Platform platform, List supportedGameJavaVersions, EnumSet distributions) { + this.downloadProvider = downloadProvider; + this.platform = platform; + this.supportedGameJavaVersions = supportedGameJavaVersions; + this.distributions = distributions; + + if (!supportedGameJavaVersions.isEmpty()) { + this.getChildren().add(new DownloadMojangJava()); + } else { + this.getChildren().add(new DownloadDiscoJava()); + } + } + + private final class DownloadMojangJava extends DialogPane { + private final ToggleGroup toggleGroup = new ToggleGroup(); + + DownloadMojangJava() { + setTitle(i18n("java.download")); + validProperty().bind(toggleGroup.selectedToggleProperty().isNotNull()); + + VBox vbox = new VBox(16); + Label prompt = new Label(i18n("java.download.prompt")); + vbox.getChildren().add(prompt); + + for (GameJavaVersion version : supportedGameJavaVersions) { + JFXRadioButton button = new JFXRadioButton("Java " + version.getMajorVersion()); + button.setUserData(version); + vbox.getChildren().add(button); + toggleGroup.getToggles().add(button); + if (JavaManager.REPOSITORY.isInstalled(platform, version)) + button.setDisable(true); + } + + setBody(vbox); + + if (!distributions.isEmpty()) { + JFXHyperlink more = new JFXHyperlink(i18n("java.download.more")); + more.setOnAction(event -> JavaDownloadDialog.this.getChildren().setAll(new DownloadDiscoJava())); + setActions(warningLabel, more, acceptPane, cancelButton); + } else + setActions(warningLabel, acceptPane, cancelButton); + } + + private Task downloadTask(GameJavaVersion javaVersion) { + return JavaManager.getDownloadJavaTask(downloadProvider, platform, javaVersion).whenComplete(Schedulers.javafx(), (result, exception) -> { + if (exception != null) { + Throwable resolvedException = resolveException(exception); + LOG.warning("Failed to download java", exception); + if (!(resolvedException instanceof CancellationException)) { + Controllers.dialog(DownloadProviders.localizeErrorMessage(resolvedException), i18n("install.failed")); + } + } + }); + } + + @Override + protected void onAccept() { + fireEvent(new DialogCloseEvent()); + + GameJavaVersion javaVersion = (GameJavaVersion) toggleGroup.getSelectedToggle().getUserData(); + if (javaVersion == null) + return; + + if (JavaManager.REPOSITORY.isInstalled(platform, javaVersion)) + Controllers.confirm(i18n("download.java.override"), null, () -> { + Controllers.taskDialog(Task.supplyAsync(() -> JavaManager.REPOSITORY.getJavaExecutable(platform, javaVersion)) + .thenComposeAsync(Schedulers.javafx(), realPath -> { + if (realPath != null) { + JavaManager.removeJava(realPath); + } + return downloadTask(javaVersion); + }), i18n("download.java"), TaskCancellationAction.NORMAL); + }, null); + else + Controllers.taskDialog(downloadTask(javaVersion), i18n("download.java"), TaskCancellationAction.NORMAL); + } + } + + private final class DownloadDiscoJava extends JFXDialogLayout { + private final JFXComboBox distributionBox; + private final JFXComboBox remoteVersionBox; + private final JFXComboBox packageTypeBox; + private final Label warningLabel = new Label(); + + private final JFXButton downloadButton; + private final StackPane downloadButtonPane = new StackPane(); + + private final DownloadProvider downloadProvider = DownloadProviders.getDownloadProvider(); + + private final Map javaVersionLists = new HashMap<>(); + private final ObjectProperty currentJavaVersionList = new SimpleObjectProperty<>(); + + DownloadDiscoJava() { + assert !distributions.isEmpty(); + + this.distributionBox = new JFXComboBox<>(); + this.distributionBox.setConverter(FXUtils.stringConverter(JavaDistribution::getDisplayName)); + + this.remoteVersionBox = new JFXComboBox<>(); + this.remoteVersionBox.setConverter(FXUtils.stringConverter(JavaRemoteVersion::getDistributionVersion)); + + this.packageTypeBox = new JFXComboBox<>(FXCollections.observableArrayList()); + + this.downloadButton = new JFXButton(i18n("download")); + downloadButton.setOnAction(e -> onDownload()); + downloadButton.getStyleClass().add("dialog-accept"); + downloadButton.disableProperty().bind(Bindings.isNull(remoteVersionBox.getSelectionModel().selectedItemProperty())); + downloadButtonPane.getChildren().setAll(downloadButton); + + JFXButton cancelButton = new JFXButton(i18n("button.cancel")); + cancelButton.setOnAction(e -> fireEvent(new DialogCloseEvent())); + cancelButton.getStyleClass().add("dialog-cancel"); + onEscPressed(this, cancelButton::fire); + + GridPane body = new GridPane(); + body.getColumnConstraints().setAll(new ColumnConstraints(), FXUtils.getColumnHgrowing()); + body.setVgap(8); + body.setHgap(16); + + body.addRow(0, new Label(i18n("java.download.distribution")), distributionBox); + body.addRow(1, new Label(i18n("java.download.version")), remoteVersionBox); + body.addRow(2, new Label(i18n("java.download.packageType")), packageTypeBox); + + distributionBox.setItems(FXCollections.observableList(new ArrayList<>(distributions))); + + FXUtils.onChange(packageTypeBox.getSelectionModel().selectedItemProperty(), packageType -> { + ObservableList versions; + if (packageType == null + || currentJavaVersionList.get() == null + || (versions = currentJavaVersionList.get().versions.get(packageType)) == null) { + remoteVersionBox.setItems(null); + return; + } + + remoteVersionBox.setItems(versions); + + for (int i = 0; i < versions.size(); i++) { + DiscoJavaRemoteVersion version = versions.get(i); + if (version.getJdkVersion() == GameJavaVersion.LATEST.getMajorVersion()) { + remoteVersionBox.getSelectionModel().select(i); + return; + } + } + + for (int i = 0; i < versions.size(); i++) { + DiscoJavaRemoteVersion version = versions.get(i); + if (version.isLTS()) { + remoteVersionBox.getSelectionModel().select(i); + return; + } + } + + remoteVersionBox.getSelectionModel().selectFirst(); + }); + + Consumer updateListStatus = list -> { + remoteVersionBox.setItems(null); + packageTypeBox.getItems().clear(); + remoteVersionBox.setDisable(true); + packageTypeBox.setDisable(true); + warningLabel.setText(null); + + if (list == null || (list.versions != null && list.versions.isEmpty())) + downloadButtonPane.getChildren().setAll(downloadButton); + else if (list.status == DiscoJavaVersionList.Status.LOADING) + downloadButtonPane.getChildren().setAll(new JFXSpinner()); + else { + downloadButtonPane.getChildren().setAll(downloadButton); + + if (list.status == DiscoJavaVersionList.Status.SUCCESS) { + packageTypeBox.getItems().setAll(list.versions.keySet()); + packageTypeBox.getSelectionModel().selectFirst(); + + remoteVersionBox.setDisable(false); + packageTypeBox.setDisable(false); + } else + warningLabel.setText(i18n("java.download.load_list.failed")); + } + }; + + currentJavaVersionList.addListener((observable, oldValue, newValue) -> { + if (oldValue != null) + oldValue.listener = null; + + if (newValue != null) { + updateListStatus.accept(newValue); + + if (newValue.status == DiscoJavaVersionList.Status.LOADING) + newValue.listener = updateListStatus; + } else { + currentJavaVersionList.set(null); + updateListStatus.accept(null); + } + }); + + FXUtils.onChange(distributionBox.getSelectionModel().selectedItemProperty(), + it -> currentJavaVersionList.set(getJavaVersionList(it))); + + setHeading(new Label(i18n("java.download"))); + setBody(body); + setActions(warningLabel, downloadButtonPane, cancelButton); + } + + private void onDownload() { + fireEvent(new DialogCloseEvent()); + + DiscoJavaDistribution distribution = distributionBox.getSelectionModel().getSelectedItem(); + DiscoJavaRemoteVersion version = remoteVersionBox.getSelectionModel().getSelectedItem(); + JavaPackageType packageType = packageTypeBox.getSelectionModel().getSelectedItem(); + + if (version == null) + return; + + Controllers.taskDialog(new GetTask(downloadProvider.injectURLWithCandidates(version.getLinks().getPkgInfoUri())) + .setExecutor(Schedulers.io()) + .thenComposeAsync(json -> { + DiscoResult result = JsonUtils.fromNonNullJson(json, DiscoResult.typeOf(DiscoRemoteFileInfo.class)); + if (result.getResult().size() != 1) + throw new IOException("Illegal result: " + json); + + DiscoRemoteFileInfo fileInfo = result.getResult().get(0); + if (!fileInfo.getChecksumType().equals("sha1") && !fileInfo.getChecksumType().equals("sha256")) + throw new IOException("Unsupported checksum type: " + fileInfo.getChecksumType()); + if (StringUtils.isBlank(fileInfo.getDirectDownloadUri())) + throw new IOException("Missing download URI: " + json); + + File targetFile = File.createTempFile("hmcl-java-", "." + version.getArchiveType()); + targetFile.deleteOnExit(); + + Task getIntegrityCheck; + if (StringUtils.isNotBlank(fileInfo.getChecksum())) + getIntegrityCheck = Task.completed(new FileDownloadTask.IntegrityCheck(fileInfo.getChecksumType(), fileInfo.getChecksum())); + else if (StringUtils.isNotBlank(fileInfo.getChecksumUri())) + getIntegrityCheck = new GetTask(downloadProvider.injectURLWithCandidates(fileInfo.getChecksumUri())) + .thenApplyAsync(checksum -> { + checksum = checksum.trim(); + + int idx = checksum.indexOf(' '); + if (idx > 0) + checksum = checksum.substring(0, idx); + + return new FileDownloadTask.IntegrityCheck(fileInfo.getChecksumType(), checksum); + }); + else + throw new IOException("Unable to get checksum for file"); + + return getIntegrityCheck + .thenComposeAsync(integrityCheck -> + new FileDownloadTask(downloadProvider.injectURLWithCandidates(fileInfo.getDirectDownloadUri()), + targetFile, integrityCheck).setName(fileInfo.getFileName())) + .thenSupplyAsync(targetFile::toPath); + }) + .whenComplete(Schedulers.javafx(), ((result, exception) -> { + if (exception == null) { + String javaVersion = version.getJavaVersion(); + JavaInfo info = new JavaInfo(platform, javaVersion, distribution.getVendor()); + + Map updateInfo = new LinkedHashMap<>(); + updateInfo.put("type", "disco"); + updateInfo.put("info", version); + + int idx = javaVersion.indexOf('+'); + if (idx > 0) { + javaVersion = javaVersion.substring(0, idx); + } + String defaultName = distribution.getApiParameter() + "-" + javaVersion + "-" + packageType.name().toLowerCase(Locale.ROOT); + Controllers.getDecorator().startWizard(new SinglePageWizardProvider(controller -> + new JavaInstallPage(controller::onFinish, info, version, updateInfo, defaultName, result))); + } else { + LOG.warning("Failed to download java", exception); + Throwable resolvedException = resolveException(exception); + if (!(resolvedException instanceof CancellationException)) { + Controllers.dialog(DownloadProviders.localizeErrorMessage(resolvedException), i18n("install.failed")); + } + } + })), i18n("java.download"), TaskCancellationAction.NORMAL); + + } + + private DiscoJavaVersionList getJavaVersionList(DiscoJavaDistribution distribution) { + if (distribution == null) + return null; + return javaVersionLists.computeIfAbsent(distribution, it -> { + DiscoJavaVersionList versionList = new DiscoJavaVersionList(it); + new DiscoFetchJavaListTask(downloadProvider, it, platform).setExecutor(Schedulers.io()).thenApplyAsync(versions -> { + EnumMap> result = new EnumMap<>(JavaPackageType.class); + if (versions.isEmpty()) + return result; + + for (Map.Entry> entry : versions.entrySet()) + for (DiscoJavaRemoteVersion version : entry.getValue().values()) + if (version.isLTS() + || version.getJdkVersion() == entry.getValue().lastKey() // latest version + || version.getJdkVersion() == 16) + result.computeIfAbsent(entry.getKey(), ignored -> FXCollections.observableArrayList()) + .add(version); + + for (List l : result.values()) + Collections.reverse(l); + return result; + }).whenComplete(Schedulers.javafx(), ((result, exception) -> { + if (exception == null) { + versionList.status = DiscoJavaVersionList.Status.SUCCESS; + versionList.versions = result; + } else { + LOG.warning("Failed to load java list", exception); + versionList.status = DiscoJavaVersionList.Status.FAILED; + } + versionList.invalidate(); + })).start(); + return versionList; + }); + } + } + + private static final class DiscoJavaVersionList { + enum Status { + LOADING, SUCCESS, FAILED + } + + final DiscoJavaDistribution distribution; + + Status status = Status.LOADING; + EnumMap> versions; + Consumer listener; + + DiscoJavaVersionList(DiscoJavaDistribution distribution) { + this.distribution = distribution; + } + + void invalidate() { + if (listener != null) + listener.accept(this); + } + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaInstallPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaInstallPage.java new file mode 100644 index 0000000000..90018444d1 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaInstallPage.java @@ -0,0 +1,190 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.ui.main; + +import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXTextField; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.control.SkinBase; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.VBox; +import org.jackhuang.hmcl.download.java.JavaRemoteVersion; +import org.jackhuang.hmcl.download.java.disco.DiscoJavaDistribution; +import org.jackhuang.hmcl.download.java.disco.DiscoJavaRemoteVersion; +import org.jackhuang.hmcl.java.HMCLJavaRepository; +import org.jackhuang.hmcl.java.JavaInfo; +import org.jackhuang.hmcl.java.JavaManager; +import org.jackhuang.hmcl.java.JavaRuntime; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.ui.Controllers; +import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.construct.*; +import org.jackhuang.hmcl.ui.wizard.WizardSinglePage; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; + +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; + +public final class JavaInstallPage extends WizardSinglePage { + + private static final Pattern NAME_PATTERN = Pattern.compile("[a-zA-Z0-9.\\-_]+"); + + private final Path file; + + private final JavaInfo info; + private final JavaRemoteVersion remoteVersion; + private final Map update; + private final StringProperty nameProperty = new SimpleStringProperty(); + + public JavaInstallPage(Runnable onFinish, JavaInfo info, JavaRemoteVersion remoteVersion, Map update, String defaultName, Path file) { + super(onFinish); + this.info = info; + this.remoteVersion = remoteVersion; + this.update = update; + this.file = file; + this.nameProperty.set(defaultName); + } + + @Override + protected SkinBase createDefaultSkin() { + return new Skin(this); + } + + @Override + protected Object finish() { + Task installTask = JavaManager.getInstallJavaTask(info.getPlatform(), nameProperty.get(), update, file); + return remoteVersion == null ? installTask : installTask.whenComplete(exception -> { + try { + Files.delete(file); + } catch (IOException e) { + LOG.warning("Failed to delete file: " + file, e); + } + }); + } + + @Override + public String getTitle() { + return i18n("java.install"); + } + + private static final class Skin extends SkinBase { + + private final ComponentList componentList = new ComponentList(); + + private final JFXTextField nameField; + + private final Set usedNames = new HashSet<>(); + + Skin(JavaInstallPage control) { + super(control); + + VBox borderPane = new VBox(); + borderPane.setAlignment(Pos.CENTER); + FXUtils.setLimitWidth(borderPane, 500); + + + { + BorderPane namePane = new BorderPane(); + { + Label label = new Label(i18n("java.install.name")); + BorderPane.setAlignment(label, Pos.CENTER_LEFT); + namePane.setLeft(label); + + nameField = new JFXTextField(); + nameField.textProperty().bindBidirectional(control.nameProperty); + FXUtils.setLimitWidth(nameField, 200); + BorderPane.setAlignment(nameField, Pos.CENTER_RIGHT); + BorderPane.setMargin(nameField, new Insets(0, 0, 12, 0)); + namePane.setRight(nameField); + nameField.setValidators( + new RequiredValidator(), + new Validator(i18n("java.install.warning.invalid_character"), + text -> !text.startsWith(HMCLJavaRepository.MOJANG_JAVA_PREFIX) && NAME_PATTERN.matcher(text).matches()), + new Validator(i18n("java.install.failed.exists"), text -> !usedNames.contains(text)) + ); + String defaultName = control.nameProperty.get(); + if (JavaManager.REPOSITORY.isInstalled(control.info.getPlatform(), defaultName)) { + usedNames.add(defaultName); + } + nameField.textProperty().addListener(o -> nameField.validate()); + nameField.validate(); + + componentList.getContent().add(namePane); + } + + String vendor = JavaInfo.normalizeVendor(control.info.getVendor()); + if (vendor != null) + addInfo(i18n("java.info.vendor"), vendor); + + if (control.remoteVersion instanceof DiscoJavaRemoteVersion) { + String distributionName = ((DiscoJavaRemoteVersion) control.remoteVersion).getDistribution(); + DiscoJavaDistribution distribution = DiscoJavaDistribution.of(distributionName); + addInfo(i18n("java.info.disco.distribution"), distribution != null ? distribution.getDisplayName() : distributionName); + } else + addInfo(i18n("java.install.archive"), control.file.toAbsolutePath().toString()); + + addInfo(i18n("java.info.version"), control.info.getVersion()); + addInfo(i18n("java.info.architecture"), control.info.getPlatform().getArchitecture().getDisplayName()); + + BorderPane installPane = new BorderPane(); + { + JFXButton installButton = FXUtils.newRaisedButton(i18n("button.install")); + installButton.setOnAction(e -> { + String name = control.nameProperty.get(); + if (JavaManager.REPOSITORY.isInstalled(control.info.getPlatform(), name)) { + Controllers.dialog(i18n("java.install.failed.exists"), null, MessageDialogPane.MessageType.WARNING); + usedNames.add(name); + nameField.validate(); + } else + control.onFinish.run(); + }); + installButton.disableProperty().bind(nameField.activeValidatorProperty().isNotNull()); + installPane.setRight(installButton); + + componentList.getContent().add(installPane); + } + } + + borderPane.getChildren().setAll(componentList); + this.getChildren().setAll(borderPane); + } + + private void addInfo(String name, String value) { + BorderPane pane = new BorderPane(); + + pane.setLeft(new Label(name)); + + Label valueLabel = FXUtils.truncatedLabel(value, 60); + BorderPane.setAlignment(valueLabel, Pos.CENTER_RIGHT); + pane.setCenter(valueLabel); + + this.componentList.getContent().add(pane); + } + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaManagementPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaManagementPage.java new file mode 100644 index 0000000000..6eb9c2d450 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaManagementPage.java @@ -0,0 +1,321 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.ui.main; + +import com.jfoenix.controls.JFXButton; +import javafx.beans.binding.Bindings; +import javafx.beans.value.ChangeListener; +import javafx.collections.FXCollections; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Control; +import javafx.scene.control.Skin; +import javafx.scene.control.SkinBase; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.stage.FileChooser; +import org.jackhuang.hmcl.java.JavaInfo; +import org.jackhuang.hmcl.java.JavaManager; +import org.jackhuang.hmcl.java.JavaRuntime; +import org.jackhuang.hmcl.setting.ConfigHolder; +import org.jackhuang.hmcl.setting.DownloadProviders; +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.MessageDialogPane; +import org.jackhuang.hmcl.ui.construct.RipplerContainer; +import org.jackhuang.hmcl.ui.construct.TwoLineListItem; +import org.jackhuang.hmcl.ui.wizard.SinglePageWizardProvider; +import org.jackhuang.hmcl.util.Pair; +import org.jackhuang.hmcl.util.TaskCancellationAction; +import org.jackhuang.hmcl.util.platform.UnsupportedPlatformException; +import org.jackhuang.hmcl.util.tree.ArchiveFileTree; +import org.jackhuang.hmcl.util.platform.Architecture; +import org.jackhuang.hmcl.util.platform.OperatingSystem; +import org.jackhuang.hmcl.util.platform.Platform; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; + +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; + +/** + * @author Glavo + */ +public final class JavaManagementPage extends ListPageBase { + + @SuppressWarnings("FieldCanBeLocal") + private final ChangeListener> listener; + + private final Runnable onInstallJava; + + public JavaManagementPage() { + this.listener = FXUtils.onWeakChangeAndOperate(JavaManager.getAllJavaProperty(), this::loadJava); + + if (Platform.SYSTEM_PLATFORM.equals(OperatingSystem.LINUX, Architecture.LOONGARCH64_OW)) { + onInstallJava = () -> FXUtils.openLink("https://www.loongnix.cn/zh/api/java/"); + } else { + onInstallJava = JavaDownloadDialog.showDialogAction(DownloadProviders.getDownloadProvider()); + } + + FXUtils.applyDragListener(this, it -> { + String name = it.getName(); + return it.isDirectory() || name.endsWith(".zip") || name.endsWith(".tar.gz") || name.equals(OperatingSystem.CURRENT_OS.getJavaExecutable()); + }, files -> { + for (File file : files) { + if (file.isDirectory()) { + onAddJavaHome(file.toPath()); + } else { + String fileName = file.getName(); + + if (fileName.equals(OperatingSystem.CURRENT_OS.getJavaExecutable())) { + onAddJavaBinary(file.toPath()); + } else if (fileName.endsWith(".zip") || fileName.endsWith(".tar.gz")) { + onInstallArchive(file.toPath()); + } else { + throw new AssertionError("Unreachable code"); + } + } + } + }); + } + + @Override + protected Skin createDefaultSkin() { + return new JavaPageSkin(this); + } + + void onAddJava() { + FileChooser chooser = new FileChooser(); + if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) + chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Java", "java.exe")); + chooser.setTitle(i18n("settings.game.java_directory.choose")); + File file = chooser.showOpenDialog(Controllers.getStage()); + if (file != null) { + JavaManager.getAddJavaTask(file.toPath()).whenComplete(Schedulers.javafx(), exception -> { + if (exception != null) { + LOG.warning("Failed to add java", exception); + Controllers.dialog(i18n("java.add.failed"), i18n("message.error"), MessageDialogPane.MessageType.ERROR); + } + }).start(); + } + } + + void onShowRestoreJavaPage() { + Controllers.navigate(new JavaRestorePage(ConfigHolder.globalConfig().getDisabledJava())); + } + + private void onAddJavaBinary(Path file) { + JavaManager.getAddJavaTask(file).whenComplete(Schedulers.javafx(), exception -> { + if (exception != null) { + LOG.warning("Failed to add java", exception); + Controllers.dialog(i18n("java.add.failed"), i18n("message.error"), MessageDialogPane.MessageType.ERROR); + } + }).start(); + } + + private void onAddJavaHome(Path file) { + Task.composeAsync(() -> { + Path releaseFile = file.resolve("release"); + if (Files.notExists(releaseFile)) + throw new IOException("Missing release file " + releaseFile); + return JavaManager.getAddJavaTask(file.resolve("bin").resolve(OperatingSystem.CURRENT_OS.getJavaExecutable())); + }).whenComplete(Schedulers.javafx(), exception -> { + if (exception != null) { + LOG.warning("Failed to add java", exception); + Controllers.dialog(i18n("java.add.failed"), i18n("message.error"), MessageDialogPane.MessageType.ERROR); + } + }).start(); + } + + private void onInstallArchive(Path file) { + Task.supplyAsync(() -> { + try (ArchiveFileTree tree = ArchiveFileTree.open(file)) { + JavaInfo info = JavaInfo.fromArchive(tree); + + if (!JavaManager.isCompatible(info.getPlatform())) + throw new UnsupportedPlatformException(info.getPlatform().toString()); + + return Pair.pair(tree.getRoot().getSubDirs().keySet().iterator().next(), info); + } + }).whenComplete(Schedulers.javafx(), (result, exception) -> { + if (exception == null) { + Controllers.getDecorator().startWizard(new SinglePageWizardProvider(controller -> + new JavaInstallPage(controller::onFinish, result.getValue(), null, null, result.getKey(), file))); + } else { + if (exception instanceof UnsupportedPlatformException) { + Controllers.dialog(i18n("java.install.failed.unsupported_platform"), null, MessageDialogPane.MessageType.WARNING); + } else { + Controllers.dialog(i18n("java.install.failed.invalid"), null, MessageDialogPane.MessageType.WARNING); + } + } + }).start(); + } + + // FXThread + private void loadJava(Collection javaRuntimes) { + if (javaRuntimes != null) { + List items = new ArrayList<>(); + for (JavaRuntime java : javaRuntimes) { + items.add(new JavaItem(java)); + } + this.setItems(FXCollections.observableList(items)); + this.setLoading(false); + } else + this.setLoading(true); + } + + static final class JavaItem extends Control { + private final JavaRuntime java; + + public JavaItem(JavaRuntime java) { + this.java = java; + } + + public JavaRuntime getJava() { + return java; + } + + public void onReveal() { + Path target; + Path parent = java.getBinary().getParent(); + if (parent != null + && parent.getParent() != null + && parent.getFileName() != null + && parent.getFileName().toString().equals("bin") + && Files.exists(parent.getParent().resolve("release"))) { + target = parent.getParent(); + } else { + target = java.getBinary(); + } + + FXUtils.showFileInExplorer(target); + } + + public void onRemove() { + if (java.isManaged()) { + Controllers.taskDialog(JavaManager.getUninstallJavaTask(java), i18n("java.uninstall"), TaskCancellationAction.NORMAL); + } else { + String path = java.getBinary().toString(); + ConfigHolder.globalConfig().getUserJava().remove(path); + ConfigHolder.globalConfig().getDisabledJava().add(path); + try { + JavaManager.removeJava(java); + } catch (InterruptedException ignored) { + } + } + } + + @Override + protected Skin createDefaultSkin() { + return new JavaRuntimeItemSkin(this); + } + + } + + private static final class JavaRuntimeItemSkin extends SkinBase { + + JavaRuntimeItemSkin(JavaItem control) { + super(control); + JavaRuntime java = control.getJava(); + String vendor = JavaInfo.normalizeVendor(java.getVendor()); + + BorderPane root = new BorderPane(); + + HBox center = new HBox(); + center.setMouseTransparent(true); + center.setSpacing(8); + center.setAlignment(Pos.CENTER_LEFT); + + TwoLineListItem item = new TwoLineListItem(); + item.setTitle((java.isJDK() ? "JDK" : "JRE") + " " + java.getVersion()); + item.setSubtitle(java.getBinary().toString()); + item.getTags().add(i18n("java.info.architecture") + ": " + java.getArchitecture().getDisplayName()); + if (vendor != null) + item.getTags().add(i18n("java.info.vendor") + ": " + vendor); + BorderPane.setAlignment(item, Pos.CENTER); + center.getChildren().setAll(item); + root.setCenter(center); + + HBox right = new HBox(); + right.setAlignment(Pos.CENTER_RIGHT); + { + JFXButton revealButton = new JFXButton(); + revealButton.getStyleClass().add("toggle-icon4"); + revealButton.setGraphic(FXUtils.limitingSize(SVG.FOLDER_OUTLINE.createIcon(Theme.blackFill(), 24, 24), 24, 24)); + revealButton.setOnAction(e -> control.onReveal()); + FXUtils.installFastTooltip(revealButton, i18n("java.reveal")); + + JFXButton removeButton = new JFXButton(); + removeButton.getStyleClass().add("toggle-icon4"); + removeButton.setOnAction(e -> Controllers.confirm( + java.isManaged() ? i18n("java.uninstall.confirm") : i18n("java.disable.confirm"), + i18n("message.warning"), + control::onRemove, + null + )); + if (java.isManaged()) { + removeButton.setGraphic(FXUtils.limitingSize(SVG.DELETE_OUTLINE.createIcon(Theme.blackFill(), 24, 24), 24, 24)); + FXUtils.installFastTooltip(removeButton, i18n("java.uninstall")); + if (JavaRuntime.CURRENT_JAVA != null && java.getBinary().equals(JavaRuntime.CURRENT_JAVA.getBinary())) + removeButton.setDisable(true); + } else { + removeButton.setGraphic(FXUtils.limitingSize(SVG.CLOSE.createIcon(Theme.blackFill(), 24, 24), 24, 24)); + FXUtils.installFastTooltip(removeButton, i18n("java.disable")); + } + + right.getChildren().setAll(revealButton, removeButton); + } + root.setRight(right); + + root.getStyleClass().add("md-list-cell"); + root.setPadding(new Insets(8)); + + getChildren().setAll(new RipplerContainer(root)); + } + } + + private static final class JavaPageSkin extends ToolbarListPageSkin { + + JavaPageSkin(JavaManagementPage skinnable) { + super(skinnable); + } + + @Override + protected List initializeToolbar(JavaManagementPage skinnable) { + ArrayList res = new ArrayList<>(4); + + res.add(createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, JavaManager::refresh)); + if (skinnable.onInstallJava != null) { + res.add(createToolbarButton2(i18n("java.download"), SVG.DOWNLOAD_OUTLINE, skinnable.onInstallJava)); + } + res.add(createToolbarButton2(i18n("java.add"), SVG.PLUS, skinnable::onAddJava)); + + JFXButton disableJava = createToolbarButton2(i18n("java.disabled.management"), SVG.VIEW_LIST, skinnable::onShowRestoreJavaPage); + disableJava.disableProperty().bind(Bindings.isEmpty(ConfigHolder.globalConfig().getDisabledJava())); + res.add(disableJava); + + return res; + } + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaRestorePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaRestorePage.java new file mode 100644 index 0000000000..7a09b8d022 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaRestorePage.java @@ -0,0 +1,206 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.ui.main; + +import com.jfoenix.controls.JFXButton; +import javafx.beans.InvalidationListener; +import javafx.beans.WeakInvalidationListener; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableSet; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.*; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import org.jackhuang.hmcl.java.JavaManager; +import org.jackhuang.hmcl.setting.Theme; +import org.jackhuang.hmcl.task.Schedulers; +import org.jackhuang.hmcl.ui.*; +import org.jackhuang.hmcl.ui.construct.MessageDialogPane; +import org.jackhuang.hmcl.ui.construct.RipplerContainer; +import org.jackhuang.hmcl.ui.decorator.DecoratorPage; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; + +/** + * @author Glavo + */ +public final class JavaRestorePage extends ListPageBase implements DecoratorPage { + + private final ObjectProperty state = new SimpleObjectProperty<>(State.fromTitle(i18n("java.disabled.management"))); + + @SuppressWarnings("FieldCanBeLocal") + private final InvalidationListener listener; + + public JavaRestorePage(ObservableSet disabledJava) { + this.listener = o -> { + ArrayList result = new ArrayList<>(disabledJava.size()); + for (String path : disabledJava) { + Path realPath = null; + + try { + realPath = Paths.get(path).toRealPath(); + } catch (IOException ignored) { + } + + result.add(new DisabledJavaItem(disabledJava, path, realPath)); + } + result.sort((a, b) -> { + if (a.realPath == null && b.realPath != null) + return -1; + if (a.realPath != null && b.realPath == null) + return 1; + return a.path.compareTo(b.path); + }); + this.setItems(FXCollections.observableList(result)); + }; + disabledJava.addListener(new WeakInvalidationListener(listener)); + listener.invalidated(disabledJava); + } + + @Override + protected Skin createDefaultSkin() { + return new JavaRestorePageSkin(this); + } + + @Override + public ReadOnlyObjectProperty stateProperty() { + return state; + } + + static final class DisabledJavaItem extends Control { + final ObservableSet disabledJava; + final String path; + final Path realPath; + + DisabledJavaItem(ObservableSet disabledJava, String path, Path realPath) { + this.disabledJava = disabledJava; + this.path = path; + this.realPath = realPath; + } + + @Override + protected Skin createDefaultSkin() { + return new DisabledJavaItemSkin(this); + } + + void onReveal() { + if (realPath != null) { + Path target; + Path parent = realPath.getParent(); + if (parent != null + && parent.getParent() != null + && parent.getFileName() != null + && parent.getFileName().toString().equals("bin") + && Files.exists(parent.getParent().resolve("release"))) { + target = parent.getParent(); + } else { + target = realPath; + } + + FXUtils.showFileInExplorer(target); + } + } + + void onRestore() { + disabledJava.remove(path); + JavaManager.getAddJavaTask(realPath).whenComplete(Schedulers.javafx(), exception -> { + if (exception != null) { + LOG.warning("Failed to add java", exception); + Controllers.dialog(i18n("java.add.failed"), i18n("message.error"), MessageDialogPane.MessageType.ERROR); + } + }).start(); + } + + void onRemove() { + disabledJava.remove(path); + } + } + + private static final class DisabledJavaItemSkin extends SkinBase { + DisabledJavaItemSkin(DisabledJavaItem skinnable) { + super(skinnable); + + BorderPane root = new BorderPane(); + + Label label = new Label(skinnable.path); + BorderPane.setAlignment(label, Pos.CENTER_LEFT); + root.setCenter(label); + + HBox right = new HBox(); + right.setAlignment(Pos.CENTER_RIGHT); + { + JFXButton revealButton = new JFXButton(); + revealButton.getStyleClass().add("toggle-icon4"); + revealButton.setGraphic(FXUtils.limitingSize(SVG.FOLDER_OUTLINE.createIcon(Theme.blackFill(), 24, 24), 24, 24)); + revealButton.setOnAction(e -> skinnable.onReveal()); + FXUtils.installFastTooltip(revealButton, i18n("java.reveal")); + + if (skinnable.realPath == null) { + revealButton.setDisable(true); + + JFXButton removeButton = new JFXButton(); + removeButton.getStyleClass().add("toggle-icon4"); + removeButton.setGraphic(FXUtils.limitingSize(SVG.DELETE_OUTLINE.createIcon(Theme.blackFill(), 24, 24), 24, 24)); + removeButton.setOnAction(e -> skinnable.onRemove()); + FXUtils.installFastTooltip(removeButton, i18n("java.disabled.management.remove")); + + right.getChildren().setAll(revealButton, removeButton); + } else { + JFXButton restoreButton = new JFXButton(); + restoreButton.getStyleClass().add("toggle-icon4"); + restoreButton.setGraphic(FXUtils.limitingSize(SVG.RESTORE.createIcon(Theme.blackFill(), 24, 24), 24, 24)); + restoreButton.setOnAction(e -> skinnable.onRestore()); + FXUtils.installFastTooltip(restoreButton, i18n("java.disabled.management.restore")); + + right.getChildren().setAll(revealButton, restoreButton); + } + } + root.setRight(right); + + root.getStyleClass().add("md-list-cell"); + root.setPadding(new Insets(8)); + + this.getChildren().setAll(new RipplerContainer(root)); + } + } + + private static final class JavaRestorePageSkin extends ToolbarListPageSkin { + JavaRestorePageSkin(JavaRestorePage skinnable) { + super(skinnable); + } + + @Override + protected List initializeToolbar(JavaRestorePage skinnable) { + return Collections.emptyList(); + } + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java index dbcba03c0c..8e59718feb 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java @@ -27,19 +27,22 @@ 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.decorator.DecoratorAnimatedPage; import org.jackhuang.hmcl.ui.decorator.DecoratorPage; import org.jackhuang.hmcl.ui.versions.VersionSettingsPage; +import java.util.Locale; + import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap; -import static org.jackhuang.hmcl.ui.FXUtils.runInFX; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public class LauncherSettingsPage extends DecoratorAnimatedPage implements DecoratorPage, PageAware { private final ReadOnlyObjectWrapper state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("settings"))); private final TabHeader tab; private final TabHeader.Tab gameTab = new TabHeader.Tab<>("versionSettingsPage"); + private final TabControl.Tab javaManagementTab = new TabControl.Tab<>("javaManagementPage"); private final TabHeader.Tab settingsTab = new TabHeader.Tab<>("settingsPage"); private final TabHeader.Tab personalizationTab = new TabHeader.Tab<>("personalizationPage"); private final TabHeader.Tab downloadTab = new TabHeader.Tab<>("downloadSettingsPage"); @@ -50,19 +53,20 @@ public class LauncherSettingsPage extends DecoratorAnimatedPage implements Decor public LauncherSettingsPage() { gameTab.setNodeSupplier(() -> new VersionSettingsPage(true)); + javaManagementTab.setNodeSupplier(JavaManagementPage::new); settingsTab.setNodeSupplier(SettingsPage::new); personalizationTab.setNodeSupplier(PersonalizationPage::new); downloadTab.setNodeSupplier(DownloadSettingsPage::new); helpTab.setNodeSupplier(HelpPage::new); feedbackTab.setNodeSupplier(FeedbackPage::new); aboutTab.setNodeSupplier(AboutPage::new); - tab = new TabHeader(gameTab, settingsTab, personalizationTab, downloadTab, helpTab, feedbackTab, aboutTab); + tab = new TabHeader(gameTab, javaManagementTab, settingsTab, personalizationTab, downloadTab, helpTab, feedbackTab, aboutTab); tab.select(gameTab); gameTab.initializeIfNeeded(); gameTab.getNode().loadVersion(Profiles.getSelectedProfile(), null); FXUtils.onChangeAndOperate(tab.getSelectionModel().selectedItemProperty(), newValue -> { - transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE.getAnimationProducer()); + transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE); }); { @@ -71,10 +75,15 @@ public LauncherSettingsPage() { settingsItem.setTitle(i18n("settings.type.global.manage")); settingsItem.setLeftGraphic(wrap(SVG.GAMEPAD)); settingsItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(gameTab)); - runInFX(() -> FXUtils.installFastTooltip(settingsItem, i18n("settings.type.global.manage"))); settingsItem.setOnAction(e -> tab.select(gameTab)); }) - .startCategory(i18n("launcher")) + .addNavigationDrawerItem(javaItem -> { + javaItem.setTitle(i18n("java.management")); + javaItem.setLeftGraphic(wrap(SVG.WRENCH_OUTLINE)); + javaItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(javaManagementTab)); + javaItem.setOnAction(e -> tab.select(javaManagementTab)); + }) + .startCategory(i18n("launcher").toUpperCase(Locale.ROOT)) .addNavigationDrawerItem(settingsItem -> { settingsItem.setTitle(i18n("settings.launcher.general")); settingsItem.setLeftGraphic(wrap(SVG.APPLICATION_OUTLINE)); @@ -93,7 +102,7 @@ public LauncherSettingsPage() { downloadItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(downloadTab)); downloadItem.setOnAction(e -> tab.select(downloadTab)); }) - .startCategory(i18n("help")) + .startCategory(i18n("help").toUpperCase(Locale.ROOT)) .addNavigationDrawerItem(helpItem -> { helpItem.setTitle(i18n("help")); helpItem.setLeftGraphic(wrap(SVG.HELP_CIRCLE_OUTLINE)); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java index 6f1cf7f176..7cded1d271 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java @@ -32,7 +32,6 @@ import javafx.scene.control.Label; import javafx.scene.image.ImageView; import javafx.scene.layout.HBox; -import javafx.scene.layout.Pane; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import javafx.scene.shape.Rectangle; @@ -46,6 +45,8 @@ import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.animation.AnimationUtils; +import org.jackhuang.hmcl.ui.animation.ContainerAnimations; +import org.jackhuang.hmcl.ui.animation.TransitionPane; import org.jackhuang.hmcl.ui.construct.AnnouncementCard; import org.jackhuang.hmcl.ui.construct.MessageDialogPane; import org.jackhuang.hmcl.ui.construct.PopupMenu; @@ -65,7 +66,6 @@ import static org.jackhuang.hmcl.setting.ConfigHolder.config; import static org.jackhuang.hmcl.ui.FXUtils.SINE; -import static org.jackhuang.hmcl.ui.FXUtils.runInFX; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public final class MainPage extends StackPane implements DecoratorPage { @@ -83,7 +83,7 @@ public final class MainPage extends StackPane implements DecoratorPage { private final ObservableList versionNodes; private Profile profile; - private VBox announcementPane; + private TransitionPane announcementPane; private final StackPane updatePane; private final JFXButton menuButton; @@ -102,13 +102,23 @@ public final class MainPage extends StackPane implements DecoratorPage { setPadding(new Insets(20)); if (Metadata.isNightly() || (Metadata.isDev() && !Objects.equals(Metadata.VERSION, config().getShownTips().get(ANNOUNCEMENT)))) { - announcementPane = new VBox(16); + AnnouncementCard announcementCard = null; + if (Metadata.isNightly()) { - announcementPane.getChildren().add(new AnnouncementCard(i18n("update.channel.nightly.title"), i18n("update.channel.nightly.hint"))); + announcementCard = new AnnouncementCard(i18n("update.channel.nightly.title"), i18n("update.channel.nightly.hint"), null); } else if (Metadata.isDev()) { - announcementPane.getChildren().add(new AnnouncementCard(i18n("update.channel.dev.title"), i18n("update.channel.dev.hint"))); + announcementCard = new AnnouncementCard(i18n("update.channel.dev.title"), i18n("update.channel.dev.hint"), this::hideAnnouncementPane); + } + + if (announcementCard != null) { + VBox announcementBox = new VBox(16); + announcementBox.getChildren().add(announcementCard); + + announcementPane = new TransitionPane(); + announcementPane.setContent(announcementBox, ContainerAnimations.NONE); + + getChildren().add(announcementPane); } - getChildren().add(announcementPane); } updatePane = new StackPane(); @@ -117,7 +127,7 @@ public final class MainPage extends StackPane implements DecoratorPage { FXUtils.setLimitWidth(updatePane, 230); FXUtils.setLimitHeight(updatePane, 55); StackPane.setAlignment(updatePane, Pos.TOP_RIGHT); - updatePane.setOnMouseClicked(e -> onUpgrade()); + FXUtils.onClicked(updatePane, this::onUpgrade); FXUtils.onChange(showUpdateProperty(), this::showUpdate); { @@ -144,7 +154,7 @@ public final class MainPage extends StackPane implements DecoratorPage { StackPane.setAlignment(closeUpdateButton, Pos.TOP_RIGHT); closeUpdateButton.getStyleClass().add("toggle-icon-tiny"); StackPane.setMargin(closeUpdateButton, new Insets(5)); - closeUpdateButton.setOnMouseClicked(e -> closeUpdateBubble()); + closeUpdateButton.setOnAction(e -> closeUpdateBubble()); updatePane.getChildren().setAll(hBox, closeUpdateButton); } @@ -206,14 +216,14 @@ public final class MainPage extends StackPane implements DecoratorPage { menuButton.setPrefWidth(230); //menuButton.setButtonType(JFXButton.ButtonType.RAISED); menuButton.setStyle("-fx-font-size: 15px;"); - menuButton.setOnMouseClicked(e -> onMenu()); + menuButton.setOnAction(e -> onMenu()); menuButton.setClip(new Rectangle(211, -100, 100, 200)); StackPane graphic = new StackPane(); Node svg = SVG.TRIANGLE.createIcon(Theme.foregroundFillBinding(), 10, 10); StackPane.setAlignment(svg, Pos.CENTER_RIGHT); graphic.getChildren().setAll(svg); graphic.setTranslateX(12); - runInFX(() -> FXUtils.installFastTooltip(menuButton, i18n("version.switch"))); + FXUtils.installFastTooltip(menuButton, i18n("version.switch")); menuButton.setGraphic(graphic); launchPane.getChildren().setAll(launchButton, separator, menuButton); @@ -224,10 +234,10 @@ public final class MainPage extends StackPane implements DecoratorPage { menu.setMaxHeight(365); menu.setMaxWidth(545); menu.setAlwaysShowingVBar(true); - menu.setOnMouseClicked(e -> popup.hide()); + FXUtils.onClicked(menu, popup::hide); versionNodes = MappedObservableList.create(versions, version -> { Node node = PopupMenu.wrapPopupMenuItem(new GameItem(profile, version.getId())); - node.setOnMouseClicked(e -> profile.setSelectedVersion(version.getId())); + FXUtils.onClicked(node, () -> profile.setSelectedVersion(version.getId())); return node; }); Bindings.bindContent(menu.getContent(), versionNodes); @@ -237,10 +247,13 @@ private void showUpdate(boolean show) { doAnimation(show); if (show && getLatestVersion() != null && !Objects.equals(config().getPromptedVersion(), getLatestVersion().getVersion())) { - Controllers.dialog("", i18n("update.bubble.title", getLatestVersion().getVersion()), MessageDialogPane.MessageType.INFO, () -> { - config().setPromptedVersion(getLatestVersion().getVersion()); - onUpgrade(); - }); + Controllers.dialog(new MessageDialogPane.Builder("", i18n("update.bubble.title", getLatestVersion().getVersion()), MessageDialogPane.MessageType.INFO) + .addAction(i18n("button.view"), () -> { + config().setPromptedVersion(getLatestVersion().getVersion()); + onUpgrade(); + }) + .addCancel(null) + .build()); } } @@ -287,10 +300,7 @@ private void closeUpdateBubble() { public void hideAnnouncementPane() { if (announcementPane != null) { config().getShownTips().put(ANNOUNCEMENT, Metadata.VERSION); - Pane parent = (Pane) announcementPane.getParent(); - if (parent != null) - parent.getChildren().remove(announcementPane); - announcementPane = null; + announcementPane.setContent(new StackPane(), ContainerAnimations.FADE); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java index abf24d6ce0..21301dee70 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java @@ -165,7 +165,7 @@ protected Skin(RootPage control) { downloadItem.setActionButtonVisible(false); downloadItem.setTitle(i18n("download")); downloadItem.setOnAction(e -> Controllers.navigate(Controllers.getDownloadPage())); - runInFX(() -> FXUtils.installFastTooltip(downloadItem, i18n("download.hint"))); + FXUtils.installFastTooltip(downloadItem, i18n("download.hint")); // fifth item in left sidebar AdvancedListItem launcherSettingsItem = new AdvancedListItem(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsView.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsView.java index 39bd0ae17f..0bfc1ba6a6 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsView.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsView.java @@ -30,6 +30,7 @@ import javafx.scene.text.Text; import javafx.scene.text.TextAlignment; import javafx.scene.text.TextFlow; +import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.setting.EnumCommonDirectory; import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.ui.FXUtils; @@ -72,7 +73,7 @@ public SettingsView() { { StackPane sponsorPane = new StackPane(); sponsorPane.setCursor(Cursor.HAND); - sponsorPane.setOnMouseClicked(e -> onSponsor()); + FXUtils.onClicked(sponsorPane, this::onSponsor); sponsorPane.setPadding(new Insets(8, 0, 8, 0)); GridPane gridPane = new GridPane(); @@ -120,7 +121,7 @@ public SettingsView() { { btnUpdate = new JFXButton(); - btnUpdate.setOnMouseClicked(e -> onUpdate()); + btnUpdate.setOnAction(e -> onUpdate()); btnUpdate.getStyleClass().add("toggle-icon4"); btnUpdate.setGraphic(SVG.UPDATE.createIcon(Theme.blackFill(), 20, 20)); @@ -160,7 +161,7 @@ public SettingsView() { { JFXButton cleanButton = new JFXButton(i18n("launcher.cache_directory.clean")); - cleanButton.setOnMouseClicked(e -> clearCacheDirectory()); + cleanButton.setOnAction(e -> clearCacheDirectory()); cleanButton.getStyleClass().add("jfx-button-border"); fileCommonLocationSublist.setHeaderRight(cleanButton); @@ -191,10 +192,19 @@ public SettingsView() { BorderPane.setAlignment(left, Pos.CENTER_LEFT); debugPane.setLeft(left); + JFXButton openLogFolderButton = new JFXButton(i18n("settings.launcher.launcher_log.reveal")); + openLogFolderButton.setOnAction(e -> openLogFolder()); + openLogFolderButton.getStyleClass().add("jfx-button-border"); + JFXButton logButton = new JFXButton(i18n("settings.launcher.launcher_log.export")); - logButton.setOnMouseClicked(e -> onExportLogs()); + logButton.setOnAction(e -> onExportLogs()); logButton.getStyleClass().add("jfx-button-border"); - debugPane.setRight(logButton); + + HBox buttonBox = new HBox(); + buttonBox.setSpacing(10); + buttonBox.getChildren().addAll(openLogFolderButton, logButton); + BorderPane.setAlignment(buttonBox, Pos.CENTER_RIGHT); + debugPane.setRight(buttonBox); settingsPane.getContent().add(debugPane); } @@ -205,6 +215,10 @@ public SettingsView() { } } + public void openLogFolder() { + FXUtils.openFolder(Metadata.HMCL_DIRECTORY.resolve("logs").toFile()); + } + protected abstract void onUpdate(); protected abstract void onExportLogs(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/profile/ProfileListItemSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/profile/ProfileListItemSkin.java index 83b5ed3367..e17047341c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/profile/ProfileListItemSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/profile/ProfileListItemSkin.java @@ -22,7 +22,6 @@ import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.SkinBase; -import javafx.scene.input.MouseEvent; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import org.jackhuang.hmcl.setting.Theme; @@ -47,9 +46,7 @@ public ProfileListItemSkin(ProfileListItem skinnable) { skinnable.pseudoClassStateChanged(SELECTED, active); }); - getSkinnable().addEventHandler(MouseEvent.MOUSE_CLICKED, e -> { - getSkinnable().setSelected(true); - }); + FXUtils.onClicked(getSkinnable(), () -> getSkinnable().setSelected(true)); Node left = VersionPage.wrap(SVG.FOLDER_OUTLINE); root.setLeft(left); @@ -64,7 +61,7 @@ public ProfileListItemSkin(ProfileListItem skinnable) { right.setAlignment(Pos.CENTER_RIGHT); JFXButton btnRemove = new JFXButton(); - btnRemove.setOnMouseClicked(e -> skinnable.remove()); + btnRemove.setOnAction(e -> skinnable.remove()); btnRemove.getStyleClass().add("toggle-icon4"); BorderPane.setAlignment(btnRemove, Pos.CENTER); btnRemove.setGraphic(SVG.CLOSE.createIcon(Theme.blackFill(), 14, 14)); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java index 46df504b17..dffe893a3f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java @@ -43,7 +43,6 @@ import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; -import org.jackhuang.hmcl.task.TaskExecutor; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.WeakListenerHolder; @@ -52,6 +51,7 @@ import org.jackhuang.hmcl.ui.construct.TwoLineListItem; import org.jackhuang.hmcl.ui.decorator.DecoratorPage; import org.jackhuang.hmcl.util.AggregatedObservableList; +import org.jackhuang.hmcl.util.Holder; import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.i18n.I18n; @@ -67,7 +67,6 @@ import static org.jackhuang.hmcl.ui.FXUtils.stringConverter; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.selectedItemPropertyFor; -import static org.jackhuang.hmcl.ui.FXUtils.runInFX; public class DownloadListPage extends Control implements DecoratorPage, VersionPage.VersionLoadable { protected final ReadOnlyObjectWrapper state = new ReadOnlyObjectWrapper<>(); @@ -87,7 +86,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP protected final ListProperty downloadSources = new SimpleListProperty<>(this, "downloadSources", FXCollections.observableArrayList()); protected final StringProperty downloadSource = new SimpleStringProperty(); private final WeakListenerHolder listenerHolder = new WeakListenerHolder(); - private TaskExecutor executor; + private int searchID = 0; protected RemoteModRepository repository; private Runnable retrySearch; @@ -163,11 +162,8 @@ public void search(String userGameVersion, RemoteModRepository.Category category setLoading(true); setFailed(false); - if (executor != null && !executor.isCancelled()) { - executor.cancel(); - } - - executor = Task.supplyAsync(() -> { + int currentSearchID = searchID = searchID + 1; + Task.supplyAsync(() -> { Profile.ProfileVersion version = this.version.get(); if (StringUtils.isBlank(version.getVersion())) { return userGameVersion; @@ -176,9 +172,11 @@ public void search(String userGameVersion, RemoteModRepository.Category category ? 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; + } + setLoading(false); if (exception == null) { items.setAll(result.getResults().collect(Collectors.toList())); @@ -196,7 +194,7 @@ protected String getLocalizedCategory(String category) { return i18n("curse.category." + category); } - protected String getLocalizedCategoryIndent(ModDownloadListPageSkin.CategoryIndented category) { + private String getLocalizedCategoryIndent(ModDownloadListPageSkin.CategoryIndented category) { return StringUtils.repeats(' ', category.indent * 4) + (category.getCategory() == null ? i18n("curse.category.0") @@ -303,7 +301,7 @@ protected ModDownloadListPageSkin(DownloadListPage control) { lblGameVersion.visibleProperty().bind(hasVersion); gameVersionField.managedProperty().bind(hasVersion); gameVersionField.visibleProperty().bind(hasVersion); - runInFX(() -> FXUtils.installFastTooltip(gameVersionField, i18n("search.enter"))); + FXUtils.installFastTooltip(gameVersionField, i18n("search.enter")); FXUtils.onChangeAndOperate(getSkinnable().version, version -> { if (StringUtils.isNotBlank(version.getVersion())) { @@ -344,13 +342,14 @@ protected ModDownloadListPageSkin(DownloadListPage control) { sortComboBox.getSelectionModel().select(0); searchPane.addRow(rowIndex++, new Label(i18n("mods.category")), categoryStackPane, new Label(i18n("search.sort")), sortStackPane); - StringProperty previousSearchFilter = new SimpleStringProperty(this, "Previous Seach Filter", ""); + IntegerProperty filterID = new SimpleIntegerProperty(this, "Filter ID", 0); + IntegerProperty currentFilterID = new SimpleIntegerProperty(this, "Current Filter ID", -1); EventHandler searchAction = e -> { - if (!previousSearchFilter.get().equals(nameField.getText())) { + if (currentFilterID.get() != filterID.get()) { control.pageOffset.set(0); } + currentFilterID.set(filterID.get()); - previousSearchFilter.set(nameField.getText()); getSkinnable().search(gameVersionField.getSelectionModel().getSelectedItem(), Optional.ofNullable(categoryComboBox.getSelectionModel().getSelectedItem()) .map(CategoryIndented::getCategory) @@ -360,60 +359,93 @@ protected ModDownloadListPageSkin(DownloadListPage control) { sortComboBox.getSelectionModel().getSelectedItem()); }; + control.listenerHolder.add(FXUtils.observeWeak( + () -> filterID.set(filterID.get() + 1), + + control.downloadSource, + gameVersionField.getSelectionModel().selectedItemProperty(), + categoryComboBox.getSelectionModel().selectedItemProperty(), + nameField.textProperty(), + sortComboBox.getSelectionModel().selectedItemProperty() + )); + HBox actionsBox = new HBox(8); GridPane.setColumnSpan(actionsBox, 4); actionsBox.setAlignment(Pos.CENTER); { AggregatedObservableList actions = new AggregatedObservableList<>(); + Holder changeButton = new Holder<>(); + JFXButton firstPageButton = FXUtils.newBorderButton(i18n("search.first_page")); firstPageButton.setOnAction(event -> { control.pageOffset.set(0); + changeButton.value.run(); searchAction.handle(event); }); - firstPageButton.setDisable(true); - control.pageCount.addListener((observable, oldValue, newValue) -> firstPageButton.setDisable(control.pageCount.get() == -1)); JFXButton previousPageButton = FXUtils.newBorderButton(i18n("search.previous_page")); previousPageButton.setOnAction(event -> { - if (control.pageOffset.get() > 0) { - control.pageOffset.set(control.pageOffset.get() - 1); + int pageOffset = control.pageOffset.get(); + if (pageOffset > 0) { + control.pageOffset.set(pageOffset - 1); + changeButton.value.run(); searchAction.handle(event); } }); - previousPageButton.setDisable(true); - control.pageOffset.addListener((observable, oldValue, newValue) -> previousPageButton.setDisable( - control.pageCount.get() == -1 || control.pageOffset.get() == 0 - )); - - Label pageOffset = new Label(i18n("search.page_n", 0, "-")); - control.pageOffset.addListener((observable, oldValue, newValue) -> pageOffset.setText(i18n( - "search.page_n", control.pageOffset.get() + 1, control.pageCount.get() == -1 ? "-" : control.pageCount.getValue().toString() - ))); - control.pageCount.addListener((observable, oldValue, newValue) -> pageOffset.setText(i18n( - "search.page_n", control.pageOffset.get() + 1, control.pageCount.get() == -1 ? "-" : control.pageCount.getValue().toString() - ))); + + Label pageDescription = new Label(); + pageDescription.textProperty().bind(Bindings.createStringBinding(() -> { + int pageCount = control.pageCount.get(); + return i18n("search.page_n", control.pageOffset.get() + 1, pageCount == -1 ? "-" : String.valueOf(pageCount)); + }, control.pageOffset, control.pageCount)); JFXButton nextPageButton = FXUtils.newBorderButton(i18n("search.next_page")); nextPageButton.setOnAction(event -> { - control.pageOffset.set(control.pageOffset.get() + 1); - searchAction.handle(event); + int nv = control.pageOffset.get() + 1; + if (nv < control.pageCount.get()) { + control.pageOffset.set(nv); + changeButton.value.run(); + searchAction.handle(event); + } }); - nextPageButton.setDisable(true); - control.pageOffset.addListener((observable, oldValue, newValue) -> nextPageButton.setDisable( - control.pageCount.get() == -1 || control.pageOffset.get() >= control.pageCount.get() - 1 - )); - control.pageCount.addListener((observable, oldValue, newValue) -> nextPageButton.setDisable( - control.pageCount.get() == -1 || control.pageOffset.get() >= control.pageCount.get() - 1 - )); JFXButton lastPageButton = FXUtils.newBorderButton(i18n("search.last_page")); lastPageButton.setOnAction(event -> { control.pageOffset.set(control.pageCount.get() - 1); + changeButton.value.run(); searchAction.handle(event); }); + + firstPageButton.setDisable(true); + previousPageButton.setDisable(true); lastPageButton.setDisable(true); - control.pageCount.addListener((observable, oldValue, newValue) -> lastPageButton.setDisable(control.pageCount.get() == -1 || control.pageOffset.get() >= control.pageCount.get() - 1)); + nextPageButton.setDisable(true); + + changeButton.value = () -> { + int pageOffset = control.pageOffset.get(); + int pageCount = control.pageCount.get(); + + boolean disablePrevious = pageOffset == 0; + firstPageButton.setDisable(disablePrevious); + previousPageButton.setDisable(disablePrevious); + + boolean disableNext = pageOffset == pageCount - 1; + nextPageButton.setDisable(disableNext); + lastPageButton.setDisable(disableNext || pageCount == -1); + }; + + FXUtils.onChange(control.pageCount, pageCountN -> { + int pageCount = pageCountN.intValue(); + + if (pageCount != -1) { + if (control.pageOffset.get() + 1 >= pageCount) { + control.pageOffset.set(pageCount - 1); + } + } + + changeButton.value.run(); + }); Pane placeholder = new Pane(); HBox.setHgrow(placeholder, Priority.SOMETIMES); @@ -421,13 +453,14 @@ protected ModDownloadListPageSkin(DownloadListPage control) { JFXButton searchButton = FXUtils.newRaisedButton(i18n("search")); searchButton.setOnAction(searchAction); - actions.appendList(FXCollections.observableArrayList(firstPageButton, previousPageButton, pageOffset, nextPageButton, lastPageButton, placeholder, searchButton)); + actions.appendList(FXCollections.observableArrayList(firstPageButton, previousPageButton, pageDescription, nextPageButton, lastPageButton, placeholder, searchButton)); actions.appendList(control.actions); Bindings.bindContent(actionsBox.getChildren(), actions.getAggregatedList()); } searchPane.addRow(rowIndex++, actionsBox); + FXUtils.onChange(control.downloadSource, v -> searchAction.handle(null)); nameField.setOnAction(searchAction); gameVersionField.setOnAction(searchAction); categoryComboBox.setOnAction(searchAction); @@ -454,7 +487,7 @@ protected ModDownloadListPageSkin(DownloadListPage control) { JFXListView listView = new JFXListView<>(); spinnerPane.setContent(listView); Bindings.bindContent(listView.getItems(), getSkinnable().items); - listView.setOnMouseClicked(e -> { + FXUtils.onClicked(listView, () -> { if (listView.getSelectionModel().getSelectedIndex() < 0) return; RemoteMod selectedItem = listView.getSelectionModel().getSelectedItem(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java index a8244c509b..86e1a737b7 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java @@ -62,7 +62,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static org.jackhuang.hmcl.ui.FXUtils.runInFX; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public class DownloadPage extends Control implements DecoratorPage { @@ -241,14 +240,14 @@ protected ModDownloadPageSkin(DownloadPage control) { openMcmodButton.setExternalLink(getSkinnable().translations.getMcmodUrl(getSkinnable().mod)); descriptionPane.getChildren().add(openMcmodButton); openMcmodButton.setMinWidth(Region.USE_PREF_SIZE); - runInFX(() -> FXUtils.installFastTooltip(openMcmodButton, i18n("mods.mcmod"))); + FXUtils.installFastTooltip(openMcmodButton, i18n("mods.mcmod")); } JFXHyperlink openUrlButton = new JFXHyperlink(control.page.getLocalizedOfficialPage()); openUrlButton.setExternalLink(getSkinnable().addon.getPageUrl()); descriptionPane.getChildren().add(openUrlButton); openUrlButton.setMinWidth(Region.USE_PREF_SIZE); - runInFX(() -> FXUtils.installFastTooltip(openUrlButton, control.page.getLocalizedOfficialPage())); + FXUtils.installFastTooltip(openUrlButton, control.page.getLocalizedOfficialPage()); } SpinnerPane spinnerPane = new SpinnerPane(); @@ -301,10 +300,18 @@ protected ModDownloadPageSkin(DownloadPage control) { for (String gameVersion : control.versions.keys().stream() .sorted(Collections.reverseOrder(GameVersionNumber::compare)) .collect(Collectors.toList())) { - ComponentList sublist = new ComponentList(() -> - control.versions.get(gameVersion).stream() - .map(version -> new ModItem(version, control)) - .collect(Collectors.toList())); + List versions = control.versions.get(gameVersion); + if (versions == null || versions.isEmpty()) { + continue; + } + + ComponentList sublist = new ComponentList(() -> { + ArrayList items = new ArrayList<>(versions.size()); + for (RemoteMod.Version v: versions) { + items.add(new ModItem(v, control)); + } + return items; + }); sublist.getStyleClass().add("no-padding"); sublist.setTitle("Minecraft " + gameVersion); @@ -338,7 +345,7 @@ private static final class DependencyModItem extends StackPane { pane.getChildren().setAll(FXUtils.limitingSize(imageView, 40, 40), content); RipplerContainer container = new RipplerContainer(pane); - container.setOnMouseClicked(e -> Controllers.navigate(new DownloadPage(page, addon, version, callback))); + FXUtils.onClicked(container, () -> Controllers.navigate(new DownloadPage(page, addon, version, callback))); getChildren().setAll(container); if (addon != RemoteMod.BROKEN) { @@ -373,8 +380,6 @@ private static final class ModItem extends StackPane { { StackPane graphicPane = new StackPane(); - graphicPane.getChildren().setAll(SVG.RELEASE_CIRCLE_OUTLINE.createIcon(Theme.blackFill(), 24, 24)); - TwoLineListItem content = new TwoLineListItem(); HBox.setHgrow(content, Priority.ALWAYS); content.setTitle(dataItem.getName()); @@ -382,11 +387,16 @@ private static final class ModItem extends StackPane { switch (dataItem.getVersionType()) { case Alpha: + content.getTags().add(i18n("mods.channel.alpha")); + graphicPane.getChildren().setAll(SVG.ALPHA_CIRCLE_OUTLINE.createIcon(Theme.blackFill(), 24, 24)); + break; case Beta: - content.getTags().add(i18n("version.game.snapshot")); + content.getTags().add(i18n("mods.channel.beta")); + graphicPane.getChildren().setAll(SVG.BETA_CIRCLE_OUTLINE.createIcon(Theme.blackFill(), 24, 24)); break; case Release: - content.getTags().add(i18n("version.game.release")); + content.getTags().add(i18n("mods.channel.release")); + graphicPane.getChildren().setAll(SVG.RELEASE_CIRCLE_OUTLINE.createIcon(Theme.blackFill(), 24, 24)); break; } @@ -417,7 +427,7 @@ private static final class ModItem extends StackPane { } RipplerContainer container = new RipplerContainer(pane); - container.setOnMouseClicked(e -> Controllers.dialog(new ModVersion(dataItem, selfPage))); + FXUtils.onClicked(container, () -> Controllers.dialog(new ModVersion(dataItem, selfPage))); getChildren().setAll(container); // Workaround for https://github.com/HMCL-dev/HMCL/issues/2129 @@ -432,7 +442,7 @@ public ModVersion(RemoteMod.Version version, DownloadPage selfPage) { VBox box = new VBox(8); box.setPadding(new Insets(8)); ModItem modItem = new ModItem(version, selfPage); - modItem.setOnMouseClicked(e -> fireEvent(new DialogCloseEvent())); + FXUtils.onClicked(modItem, () -> fireEvent(new DialogCloseEvent())); box.getChildren().setAll(modItem); SpinnerPane spinnerPane = new SpinnerPane(); ScrollPane scrollPane = new ScrollPane(); @@ -449,7 +459,7 @@ public ModVersion(RemoteMod.Version version, DownloadPage selfPage) { this.setBody(box); - JFXButton downloadButton = new JFXButton(i18n("download")); + JFXButton downloadButton = new JFXButton(i18n("mods.install")); downloadButton.getStyleClass().add("dialog-accept"); downloadButton.setOnAction(e -> { if (!spinnerPane.isLoading() && spinnerPane.getFailedReason() == null) { @@ -458,7 +468,7 @@ public ModVersion(RemoteMod.Version version, DownloadPage selfPage) { selfPage.download(version); }); - JFXButton saveAsButton = new JFXButton(i18n("button.save_as")); + JFXButton saveAsButton = new JFXButton(i18n("mods.save_as")); saveAsButton.getStyleClass().add("dialog-accept"); saveAsButton.setOnAction(e -> { if (!spinnerPane.isLoading() && spinnerPane.getFailedReason() == null) { @@ -494,7 +504,7 @@ private void loadDependencies(RemoteMod.Version version, DownloadPage selfPage, dependencies.put(dependency.getType(), list); } DependencyModItem dependencyModItem = new DependencyModItem(selfPage.page, dependency.load(), selfPage.version, selfPage.callback); - dependencyModItem.setOnMouseClicked(e -> fireEvent(new DialogCloseEvent())); + FXUtils.onClicked(dependencyModItem, () -> fireEvent(new DialogCloseEvent())); dependencies.get(dependency.getType()).add(dependencyModItem); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameAdvancedListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameAdvancedListItem.java index da7306d5ac..29743853bf 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameAdvancedListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameAdvancedListItem.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.ui.versions; import javafx.scene.Node; -import javafx.scene.control.Tooltip; import javafx.scene.image.ImageView; import org.jackhuang.hmcl.event.Event; import org.jackhuang.hmcl.setting.Profile; @@ -34,15 +33,13 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public class GameAdvancedListItem extends AdvancedListItem { - private final Tooltip tooltip; private final ImageView imageView; private final WeakListenerHolder holder = new WeakListenerHolder(); private Profile profile; + @SuppressWarnings("unused") private Consumer onVersionIconChangedListener; public GameAdvancedListItem() { - tooltip = new Tooltip(); - Pair view = createImageView(null); setLeftGraphic(view.getKey()); imageView = view.getValue(); @@ -63,17 +60,13 @@ private void loadVersion(String version) { } if (version != null && Profiles.getSelectedProfile() != null && Profiles.getSelectedProfile().getRepository().hasVersion(version)) { - FXUtils.installFastTooltip(this, tooltip); - setTitle(version); - setSubtitle(null); + setTitle(i18n("version.manage.manage")); + setSubtitle(version); imageView.setImage(Profiles.getSelectedProfile().getRepository().getVersionIconImage(version)); - tooltip.setText(version); } else { - Tooltip.uninstall(this,tooltip); setTitle(i18n("version.empty")); setSubtitle(i18n("version.empty.add")); imageView.setImage(VersionIconType.DEFAULT.getIcon()); - tooltip.setText(""); } } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListItemSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListItemSkin.java index e4e767214f..15b8264722 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListItemSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListItemSkin.java @@ -35,7 +35,6 @@ import org.jackhuang.hmcl.ui.construct.RipplerContainer; import org.jackhuang.hmcl.util.Lazy; -import static org.jackhuang.hmcl.ui.FXUtils.runInFX; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public class GameListItemSkin extends SkinBase { @@ -79,33 +78,33 @@ public GameListItemSkin(GameListItem skinnable) { right.setAlignment(Pos.CENTER_RIGHT); if (skinnable.canUpdate()) { JFXButton btnUpgrade = new JFXButton(); - btnUpgrade.setOnMouseClicked(e -> skinnable.update()); + btnUpgrade.setOnAction(e -> skinnable.update()); btnUpgrade.getStyleClass().add("toggle-icon4"); btnUpgrade.setGraphic(FXUtils.limitingSize(SVG.UPDATE.createIcon(Theme.blackFill(), 24, 24), 24, 24)); - runInFX(() -> FXUtils.installFastTooltip(btnUpgrade, i18n("version.update"))); + FXUtils.installFastTooltip(btnUpgrade, i18n("version.update")); right.getChildren().add(btnUpgrade); } { JFXButton btnLaunch = new JFXButton(); - btnLaunch.setOnMouseClicked(e -> skinnable.launch()); + btnLaunch.setOnAction(e -> skinnable.launch()); btnLaunch.getStyleClass().add("toggle-icon4"); BorderPane.setAlignment(btnLaunch, Pos.CENTER); btnLaunch.setGraphic(FXUtils.limitingSize(SVG.ROCKET_LAUNCH_OUTLINE.createIcon(Theme.blackFill(), 24, 24), 24, 24)); - runInFX(() -> FXUtils.installFastTooltip(btnLaunch, i18n("version.launch.test"))); + FXUtils.installFastTooltip(btnLaunch, i18n("version.launch.test")); right.getChildren().add(btnLaunch); } { JFXButton btnManage = new JFXButton(); - btnManage.setOnMouseClicked(e -> { + btnManage.setOnAction(e -> { currentSkinnable = skinnable; popup.get().show(root, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, root.getHeight()); }); btnManage.getStyleClass().add("toggle-icon4"); BorderPane.setAlignment(btnManage, Pos.CENTER); btnManage.setGraphic(FXUtils.limitingSize(SVG.DOTS_VERTICAL.createIcon(Theme.blackFill(), 24, 24), 24, 24)); - runInFX(() -> FXUtils.installFastTooltip(btnManage, i18n("settings.game.management"))); + FXUtils.installFastTooltip(btnManage, i18n("settings.game.management")); right.getChildren().add(btnManage); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListPage.java index 1482b510a5..26216e5463 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListPage.java @@ -91,13 +91,11 @@ public GameListPage() { installNewGameItem.setTitle(i18n("install.new_game")); installNewGameItem.setLeftGraphic(VersionPage.wrap(SVG.PLUS_CIRCLE_OUTLINE)); installNewGameItem.setOnAction(e -> Versions.addNewGame()); - runInFX(() -> FXUtils.installFastTooltip(installNewGameItem, i18n("install.new_game"))); }) .addNavigationDrawerItem(installModpackItem -> { installModpackItem.setTitle(i18n("install.modpack")); installModpackItem.setLeftGraphic(VersionPage.wrap(SVG.PACK)); installModpackItem.setOnAction(e -> Versions.importModpack()); - runInFX(() -> FXUtils.installFastTooltip(installModpackItem, i18n("install.modpack"))); }) .addNavigationDrawerItem(refreshItem -> { refreshItem.setTitle(i18n("button.refresh")); @@ -108,7 +106,6 @@ public GameListPage() { globalManageItem.setTitle(i18n("settings.type.global.manage")); globalManageItem.setLeftGraphic(VersionPage.wrap(SVG.GEAR_OUTLINE)); globalManageItem.setOnAction(e -> modifyGlobalGameSettings()); - runInFX(() -> FXUtils.installFastTooltip(globalManageItem, i18n("settings.type.global.manage"))); }); FXUtils.setLimitHeight(bottomLeftCornerList, 40 * 4 + 12 * 2); setLeft(pane, bottomLeftCornerList); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ResourcePackDownloadListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/HMCLLocalizedDownloadListPage.java similarity index 50% rename from HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ResourcePackDownloadListPage.java rename to HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/HMCLLocalizedDownloadListPage.java index cf430a476f..e00db972b9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ResourcePackDownloadListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/HMCLLocalizedDownloadListPage.java @@ -28,28 +28,60 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; -public class ResourcePackDownloadListPage extends DownloadListPage { - public ResourcePackDownloadListPage(DownloadPage.DownloadCallback callback, boolean versionSelection) { +public final class HMCLLocalizedDownloadListPage extends DownloadListPage { + public static DownloadListPage ofMod(DownloadPage.DownloadCallback callback, boolean versionSelection) { + return new HMCLLocalizedDownloadListPage(callback, versionSelection, RemoteModRepository.Type.MOD, CurseForgeRemoteModRepository.MODS, ModrinthRemoteModRepository.MODS); + } + + public static DownloadListPage ofCurseForgeMod(DownloadPage.DownloadCallback callback, boolean versionSelection) { + return new HMCLLocalizedDownloadListPage(callback, versionSelection, RemoteModRepository.Type.MOD, CurseForgeRemoteModRepository.MODS, null); + } + + public static DownloadListPage ofModrinthMod(DownloadPage.DownloadCallback callback, boolean versionSelection) { + return new HMCLLocalizedDownloadListPage(callback, versionSelection, RemoteModRepository.Type.MOD, null, ModrinthRemoteModRepository.MODS); + } + + public static DownloadListPage ofModPack(DownloadPage.DownloadCallback callback, boolean versionSelection) { + return new HMCLLocalizedDownloadListPage(callback, versionSelection, RemoteModRepository.Type.MODPACK, CurseForgeRemoteModRepository.MODPACKS, ModrinthRemoteModRepository.MODPACKS); + } + + public static DownloadListPage ofResourcePack(DownloadPage.DownloadCallback callback, boolean versionSelection) { + return new HMCLLocalizedDownloadListPage(callback, versionSelection, RemoteModRepository.Type.RESOURCE_PACK, CurseForgeRemoteModRepository.RESOURCE_PACKS, ModrinthRemoteModRepository.RESOURCE_PACKS); + } + + private HMCLLocalizedDownloadListPage(DownloadPage.DownloadCallback callback, boolean versionSelection, RemoteModRepository.Type type, CurseForgeRemoteModRepository curseForge, ModrinthRemoteModRepository modrinth) { super(null, callback, versionSelection); - repository = new Repository(); + repository = new Repository(type, curseForge, modrinth); supportChinese.set(true); downloadSources.get().setAll("mods.curseforge", "mods.modrinth"); - if (CurseForgeRemoteModRepository.isAvailable()) + if (curseForge != null) { downloadSource.set("mods.curseforge"); - else + } else if (modrinth != null) { downloadSource.set("mods.modrinth"); + } else { + throw new AssertionError("Should not be here."); + } } private class Repository extends LocalizedRemoteModRepository { + private final RemoteModRepository.Type type; + private final CurseForgeRemoteModRepository curseForge; + private final ModrinthRemoteModRepository modrinth; + + public Repository(Type type, CurseForgeRemoteModRepository curseForge, ModrinthRemoteModRepository modrinth) { + this.type = type; + this.curseForge = curseForge; + this.modrinth = modrinth; + } @Override protected RemoteModRepository getBackedRemoteModRepository() { if ("mods.modrinth".equals(downloadSource.get())) { - return ModrinthRemoteModRepository.RESOURCE_PACKS; + return modrinth; } else { - return CurseForgeRemoteModRepository.RESOURCE_PACKS; + return curseForge; } } @@ -64,19 +96,17 @@ protected SortType getBackedRemoteModRepositorySortOrder() { @Override public Type getType() { - return Type.MOD; + return type; } } @Override protected String getLocalizedCategory(String category) { - String key; - if ("mods.modrinth".equals(downloadSource.get())) { - key = "modrinth.category." + category; - } else { - key = "curse.category." + category; + if (category.isEmpty()) { + return ""; } + String key = ("mods.modrinth".equals(downloadSource.get()) ? "modrinth" : "curse") + ".category." + category; try { return I18n.getResourceBundle().getString(key); } catch (MissingResourceException e) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java index d65df18fc6..02a6d121f0 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java @@ -38,7 +38,6 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.CompletableFuture; -import java.util.function.Function; import static org.jackhuang.hmcl.ui.FXUtils.runInFX; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; @@ -73,37 +72,42 @@ public void loadVersion(Profile profile, String versionId) { return LibraryAnalyzer.analyze(profile.getRepository().getResolvedPreservingPatchesVersion(versionId), gameVersion); }).thenAcceptAsync(analyzer -> { - Function removeAction = libraryId -> () -> { - profile.getDependency().removeLibraryAsync(version, libraryId) - .thenComposeAsync(profile.getRepository()::saveAsync) - .withComposeAsync(profile.getRepository().refreshVersionsAsync()) - .withRunAsync(Schedulers.javafx(), () -> loadVersion(this.profile, this.versionId)) - .start(); - }; - itemsProperty().clear(); - InstallerItem.InstallerItemGroup group = new InstallerItem.InstallerItemGroup(gameVersion); + InstallerItem.InstallerItemGroup group = new InstallerItem.InstallerItemGroup(gameVersion, InstallerItem.Style.LIST_ITEM); // Conventional libraries: game, fabric, forge, neoforge, liteloader, optifine - for (InstallerItem installerItem : group.getLibraries()) { - String libraryId = installerItem.getLibraryId(); + for (InstallerItem item : group.getLibraries()) { + String libraryId = item.getLibraryId(); + + // Skip fabric-api and quilt-api + if (libraryId.contains("fabric-api") || libraryId.contains("quilt-api")) { + continue; + } + String libraryVersion = analyzer.getVersion(libraryId).orElse(null); - boolean libraryConfigurable = libraryVersion != null && analyzer.getLibraryStatus(libraryId) == LibraryAnalyzer.LibraryMark.LibraryStatus.CLEAR; - installerItem.libraryVersion.set(libraryVersion); - installerItem.upgradable.set(libraryConfigurable); - installerItem.installable.set(true); - installerItem.action.set(e -> { + if (libraryVersion != null) { + item.versionProperty().set(new InstallerItem.InstalledState( + libraryVersion, + analyzer.getLibraryStatus(libraryId) != LibraryAnalyzer.LibraryMark.LibraryStatus.CLEAR, + false + )); + } else { + item.versionProperty().set(null); + } + + item.setOnInstall(() -> { Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, libraryId, libraryVersion)); }); - boolean removable = !LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId().equals(libraryId) && libraryConfigurable; - installerItem.removable.set(removable); - if (removable) { - Runnable action = removeAction.apply(libraryId); - installerItem.removeAction.set(e -> action.run()); - } - itemsProperty().add(installerItem); + + item.setOnRemove(() -> profile.getDependency().removeLibraryAsync(version, libraryId) + .thenComposeAsync(profile.getRepository()::saveAsync) + .withComposeAsync(profile.getRepository().refreshVersionsAsync()) + .withRunAsync(Schedulers.javafx(), () -> loadVersion(this.profile, this.versionId)) + .start()); + + itemsProperty().add(item); } // other third-party libraries which are unable to manage. @@ -115,14 +119,13 @@ public void loadVersion(Profile profile, String versionId) { if (LibraryAnalyzer.LibraryType.fromPatchId(libraryId) != null) continue; - Runnable action = removeAction.apply(libraryId); - - InstallerItem installerItem = new InstallerItem(libraryId); - installerItem.libraryVersion.set(libraryVersion); - installerItem.installable.set(false); - installerItem.upgradable.set(false); - installerItem.removable.set(true); - installerItem.removeAction.set(e -> action.run()); + InstallerItem installerItem = new InstallerItem(libraryId, InstallerItem.Style.LIST_ITEM); + installerItem.versionProperty().set(new InstallerItem.InstalledState(libraryVersion, false, false)); + installerItem.setOnRemove(() -> profile.getDependency().removeLibraryAsync(version, libraryId) + .thenComposeAsync(profile.getRepository()::saveAsync) + .withComposeAsync(profile.getRepository().refreshVersionsAsync()) + .withRunAsync(Schedulers.javafx(), () -> loadVersion(this.profile, this.versionId)) + .start()); itemsProperty().add(installerItem); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModDownloadListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModDownloadListPage.java deleted file mode 100644 index 5deea40292..0000000000 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModDownloadListPage.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Hello Minecraft! Launcher - * Copyright (C) 2021 huangyuhui and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.jackhuang.hmcl.ui.versions; - -import org.jackhuang.hmcl.game.LocalizedRemoteModRepository; -import org.jackhuang.hmcl.mod.RemoteModRepository; -import org.jackhuang.hmcl.mod.curse.CurseForgeRemoteModRepository; -import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository; - -import static org.jackhuang.hmcl.util.i18n.I18n.i18n; - -public class ModDownloadListPage extends DownloadListPage { - public ModDownloadListPage(DownloadPage.DownloadCallback callback, boolean versionSelection) { - super(null, callback, versionSelection); - - repository = new Repository(); - - supportChinese.set(true); - downloadSources.get().setAll("mods.curseforge", "mods.modrinth"); - if (CurseForgeRemoteModRepository.isAvailable()) - downloadSource.set("mods.curseforge"); - else - downloadSource.set("mods.modrinth"); - } - - private class Repository extends LocalizedRemoteModRepository { - - @Override - protected RemoteModRepository getBackedRemoteModRepository() { - if ("mods.modrinth".equals(downloadSource.get())) { - return ModrinthRemoteModRepository.MODS; - } else { - return CurseForgeRemoteModRepository.MODS; - } - } - - @Override - protected SortType getBackedRemoteModRepositorySortOrder() { - if ("mods.modrinth".equals(downloadSource.get())) { - return SortType.NAME; - } else { - return SortType.POPULARITY; - } - } - - @Override - public Type getType() { - return Type.MOD; - } - } - - @Override - protected String getLocalizedCategory(String category) { - if ("mods.modrinth".equals(downloadSource.get())) { - return i18n("modrinth.category." + category); - } else { - return i18n("curse.category." + category); - } - } - - @Override - protected String getLocalizedOfficialPage() { - if ("mods.modrinth".equals(downloadSource.get())) { - return i18n("mods.modrinth"); - } else { - return i18n("mods.curseforge"); - } - } -} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java index 497a383cca..028e74883b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java @@ -19,6 +19,7 @@ import com.jfoenix.controls.*; import com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject; +import javafx.animation.PauseTransition; import javafx.beans.binding.Bindings; import javafx.beans.property.BooleanProperty; import javafx.collections.ListChangeListener; @@ -33,7 +34,11 @@ import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.StackPane; -import org.jackhuang.hmcl.mod.*; +import javafx.util.Duration; +import org.jackhuang.hmcl.mod.LocalModFile; +import org.jackhuang.hmcl.mod.ModLoaderType; +import org.jackhuang.hmcl.mod.RemoteMod; +import org.jackhuang.hmcl.mod.RemoteModRepository; import org.jackhuang.hmcl.mod.curse.CurseForgeRemoteModRepository; import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository; import org.jackhuang.hmcl.setting.Profile; @@ -46,20 +51,21 @@ import org.jackhuang.hmcl.ui.animation.ContainerAnimations; import org.jackhuang.hmcl.ui.animation.TransitionPane; import org.jackhuang.hmcl.ui.construct.*; -import org.jackhuang.hmcl.util.*; +import org.jackhuang.hmcl.util.Holder; +import org.jackhuang.hmcl.util.Lazy; +import org.jackhuang.hmcl.util.Pair; +import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.i18n.I18n; import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.io.NetworkUtils; import org.jetbrains.annotations.NotNull; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; +import java.io.InputStream; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Locale; -import java.util.Optional; +import java.util.*; import java.util.function.Predicate; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -67,10 +73,10 @@ import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed; import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2; import static org.jackhuang.hmcl.util.Lang.mapOf; -import static org.jackhuang.hmcl.util.logging.Logger.LOG; import static org.jackhuang.hmcl.util.Pair.pair; import static org.jackhuang.hmcl.util.StringUtils.isNotBlank; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; class ModListPageSkin extends SkinBase { @@ -109,7 +115,12 @@ class ModListPageSkin extends SkinBase { searchField = new JFXTextField(); searchField.setPromptText(i18n("search")); HBox.setHgrow(searchField, Priority.ALWAYS); - searchField.setOnAction(e -> search()); + PauseTransition pause = new PauseTransition(Duration.millis(100)); + pause.setOnFinished(e -> search()); + searchField.textProperty().addListener((observable, oldValue, newValue) -> { + pause.setRate(1); + pause.playFromStart(); + }); JFXButton closeSearchBar = createToolbarButton2(null, SVG.CLOSE, () -> { @@ -175,6 +186,14 @@ class ModListPageSkin extends SkinBase { } }); + listView.setOnContextMenuRequested(event -> { + ModInfoObject selectedItem = listView.getSelectionModel().getSelectedItem(); + if (selectedItem != null && listView.getSelectionModel().getSelectedItems().size() == 1) { + listView.getSelectionModel().clearSelection(); + Controllers.dialog(new ModInfoDialog(selectedItem)); + } + }); + center.setContent(listView); root.getContent().add(center); } @@ -193,7 +212,7 @@ class ModListPageSkin extends SkinBase { private void changeToolbar(HBox newToolbar) { Node oldToolbar = toolbarPane.getCurrentNode(); if (newToolbar != oldToolbar) { - toolbarPane.setContent(newToolbar, ContainerAnimations.FADE.getAnimationProducer()); + toolbarPane.setContent(newToolbar, ContainerAnimations.FADE); if (newToolbar == searchBar) { searchField.requestFocus(); } @@ -252,7 +271,7 @@ static class ModInfoObject extends RecursiveTreeObject implements StringBuilder message = new StringBuilder(localModFile.getFileName()); if (isNotBlank(localModFile.getGameVersion())) - message.append(", ").append(i18n("archive.game_version")).append(": ").append(localModFile.getGameVersion()); + message.append(", ").append(i18n("game.version")).append(": ").append(localModFile.getGameVersion()); if (isNotBlank(localModFile.getAuthors())) message.append(", ").append(i18n("archive.author")).append(": ").append(localModFile.getAuthors()); this.message = message.toString(); @@ -289,25 +308,65 @@ class ModInfoDialog extends JFXDialogLayout { titleContainer.setSpacing(8); ImageView imageView = new ImageView(); - if (StringUtils.isNotBlank(modInfo.getModInfo().getLogoPath())) { - Task.supplyAsync(() -> { - try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(modInfo.getModInfo().getFile())) { - Path iconPath = fs.getPath(modInfo.getModInfo().getLogoPath()); + Task.supplyAsync(() -> { + try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(modInfo.getModInfo().getFile())) { + String logoPath = modInfo.getModInfo().getLogoPath(); + if (StringUtils.isNotBlank(logoPath)) { + Path iconPath = fs.getPath(logoPath); if (Files.exists(iconPath)) { - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - Files.copy(iconPath, stream); - return new ByteArrayInputStream(stream.toByteArray()); + try (InputStream stream = Files.newInputStream(iconPath)) { + Image image = new Image(stream, 40, 40, true, true); + if (!image.isError() && image.getWidth() == image.getHeight()) + return image; + } catch (Throwable e) { + LOG.warning("Failed to load image " + logoPath, e); + } } } - return null; - }).whenComplete(Schedulers.javafx(), (stream, exception) -> { - if (stream != null) { - imageView.setImage(new Image(stream, 40, 40, true, true)); - } else { - imageView.setImage(FXUtils.newBuiltinImage("/assets/img/command.png", 40, 40, true, true)); + + List defaultPaths = new ArrayList<>(Arrays.asList( + "icon.png", + "logo.png", + "mod_logo.png", + "pack.png", + "logoFile.png" + )); + + String id = modInfo.getModInfo().getId(); + if (StringUtils.isNotBlank(id)) { + defaultPaths.addAll(Arrays.asList( + "assets/" + id + "/icon.png", + "assets/" + id.replace("-", "") + "/icon.png", + id + ".png", + id + "-logo.png", + id + "-icon.png", + id + "_logo.png", + id + "_icon.png" + )); } - }).start(); - } + + for (String path : defaultPaths) { + Path iconPath = fs.getPath(path); + if (Files.exists(iconPath)) { + try (InputStream stream = Files.newInputStream(iconPath)) { + Image image = new Image(stream, 40, 40, true, true); + if (!image.isError() && image.getWidth() == image.getHeight()) + return image; + } + } + } + } catch (Exception e) { + LOG.warning("Failed to load icon", e); + } + + return null; + }).whenComplete(Schedulers.javafx(), (image, exception) -> { + if (image != null) { + imageView.setImage(image); + } else { + imageView.setImage(FXUtils.newBuiltinImage("/assets/img/command.png", 40, 40, true, true)); + } + }).start(); TwoLineListItem title = new TwoLineListItem(); title.setTitle(modInfo.getModInfo().getName()); @@ -323,18 +382,16 @@ class ModInfoDialog extends JFXDialogLayout { setBody(description); if (StringUtils.isNotBlank(modInfo.getModInfo().getId())) { - Lang.>immutableListOf( + for (Pair item : Arrays.asList( pair("mods.curseforge", CurseForgeRemoteModRepository.MODS), pair("mods.modrinth", ModrinthRemoteModRepository.MODS) - ).forEach(item -> { - String text = item.getKey(); - RemoteModRepository remoteModRepository = item.getValue(); - - JFXHyperlink button = new JFXHyperlink(i18n(text)); + )) { + RemoteModRepository repository = item.getValue(); + JFXHyperlink button = new JFXHyperlink(i18n(item.getKey())); Task.runAsync(() -> { - Optional versionOptional = remoteModRepository.getRemoteVersionByLocalFile(modInfo.getModInfo(), modInfo.getModInfo().getFile()); + Optional versionOptional = repository.getRemoteVersionByLocalFile(modInfo.getModInfo(), modInfo.getModInfo().getFile()); if (versionOptional.isPresent()) { - RemoteMod remoteMod = remoteModRepository.getModById(versionOptional.get().getModid()); + RemoteMod remoteMod = repository.getModById(versionOptional.get().getModid()); FXUtils.runInFX(() -> { for (ModLoaderType modLoaderType : versionOptional.get().getLoaders()) { String loaderName; @@ -357,15 +414,16 @@ class ModInfoDialog extends JFXDialogLayout { default: continue; } - if (!title.getTags().contains(loaderName)) { - title.getTags().add(loaderName); + List tags = title.getTags(); + if (!tags.contains(loaderName)) { + tags.add(loaderName); } } button.setOnAction(e -> { fireEvent(new DialogCloseEvent()); Controllers.navigate(new DownloadPage( - new DownloadListPage(remoteModRepository), + repository instanceof CurseForgeRemoteModRepository ? HMCLLocalizedDownloadListPage.ofCurseForgeMod(null, false) : HMCLLocalizedDownloadListPage.ofModrinthMod(null, false), remoteMod, new Profile.ProfileVersion(ModListPageSkin.this.getSkinnable().getProfile(), ModListPageSkin.this.getSkinnable().getVersionId()), null @@ -377,7 +435,7 @@ class ModInfoDialog extends JFXDialogLayout { }).start(); button.setDisable(true); getActions().add(button); - }); + } } if (StringUtils.isNotBlank(modInfo.getModInfo().getUrl())) { @@ -489,7 +547,7 @@ protected void updateControl(ModInfoObject dataItem, boolean empty) { } checkBox.selectedProperty().bindBidirectional(booleanProperty = dataItem.active); restoreButton.setVisible(!dataItem.getModInfo().getMod().getOldFiles().isEmpty()); - restoreButton.setOnMouseClicked(e -> { + restoreButton.setOnAction(e -> { menu.get().getContent().setAll(dataItem.getModInfo().getMod().getOldFiles().stream() .map(localModFile -> new IconedMenuItem(null, localModFile.getVersion(), () -> getSkinnable().rollback(dataItem.getModInfo(), localModFile), @@ -499,12 +557,8 @@ protected void updateControl(ModInfoObject dataItem, boolean empty) { popup.get().show(restoreButton, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, restoreButton.getHeight()); }); - revealButton.setOnMouseClicked(e -> { - FXUtils.showFileInExplorer(dataItem.getModInfo().getFile()); - }); - infoButton.setOnMouseClicked(e -> { - Controllers.dialog(new ModInfoDialog(dataItem)); - }); + revealButton.setOnAction(e -> FXUtils.showFileInExplorer(dataItem.getModInfo().getFile())); + infoButton.setOnAction(e -> Controllers.dialog(new ModInfoDialog(dataItem))); } } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModUpdatesPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModUpdatesPage.java index 0d080f731f..791ba21c4d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModUpdatesPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModUpdatesPage.java @@ -24,6 +24,7 @@ import javafx.collections.ObservableList; import javafx.geometry.Insets; import javafx.geometry.Pos; +import javafx.scene.control.CheckBox; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.cell.CheckBoxTableCell; @@ -73,6 +74,8 @@ public ModUpdatesPage(ModManager modManager, List update getStyleClass().add("gray-background"); TableColumn enabledColumn = new TableColumn<>(); + CheckBox allEnabledBox = new CheckBox(); + enabledColumn.setGraphic(allEnabledBox); enabledColumn.setCellFactory(CheckBoxTableCell.forTableColumn(enabledColumn)); setupCellValueFactory(enabledColumn, ModUpdateObject::enabledProperty); enabledColumn.setEditable(true); @@ -95,6 +98,7 @@ public ModUpdatesPage(ModManager modManager, List update setupCellValueFactory(sourceColumn, ModUpdateObject::sourceProperty); objects = FXCollections.observableList(updates.stream().map(ModUpdateObject::new).collect(Collectors.toList())); + FXUtils.bindAllEnabled(allEnabledBox.selectedProperty(), objects.stream().map(o -> o.enabled).toArray(BooleanProperty[]::new)); TableView table = new TableView<>(objects); table.setEditable(true); @@ -170,7 +174,7 @@ private void exportList() { csvTable.write(Files.newOutputStream(path)); FXUtils.showFileInExplorer(path); - }).whenComplete(Schedulers.javafx() ,exception -> { + }).whenComplete(Schedulers.javafx(), exception -> { if (exception == null) { Controllers.dialog(path.toString(), i18n("message.success")); } else { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModpackDownloadListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModpackDownloadListPage.java deleted file mode 100644 index 91ee40d05c..0000000000 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModpackDownloadListPage.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Hello Minecraft! Launcher - * Copyright (C) 2022 huangyuhui and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.jackhuang.hmcl.ui.versions; - -import org.jackhuang.hmcl.game.LocalizedRemoteModRepository; -import org.jackhuang.hmcl.mod.RemoteModRepository; -import org.jackhuang.hmcl.mod.curse.CurseForgeRemoteModRepository; -import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository; - -import static org.jackhuang.hmcl.util.i18n.I18n.i18n; - -public class ModpackDownloadListPage extends DownloadListPage { - public ModpackDownloadListPage(DownloadPage.DownloadCallback callback, boolean versionSelection) { - super(null, callback, versionSelection); - - repository = new Repository(); - - supportChinese.set(true); - downloadSources.get().setAll("mods.curseforge", "mods.modrinth"); - if (CurseForgeRemoteModRepository.isAvailable()) - downloadSource.set("mods.curseforge"); - else - downloadSource.set("mods.modrinth"); - } - - private class Repository extends LocalizedRemoteModRepository { - - @Override - protected RemoteModRepository getBackedRemoteModRepository() { - if ("mods.modrinth".equals(downloadSource.get())) { - return ModrinthRemoteModRepository.MODPACKS; - } else { - return CurseForgeRemoteModRepository.MODPACKS; - } - } - - @Override - protected SortType getBackedRemoteModRepositorySortOrder() { - if ("mods.modrinth".equals(downloadSource.get())) { - return SortType.NAME; - } else { - return SortType.POPULARITY; - } - } - - @Override - public Type getType() { - return Type.MODPACK; - } - } - - @Override - protected String getLocalizedCategory(String category) { - if ("mods.modrinth".equals(downloadSource.get())) { - return i18n("modrinth.category." + category); - } else { - return i18n("curse.category." + category); - } - } - - @Override - protected String getLocalizedOfficialPage() { - if ("mods.modrinth".equals(downloadSource.get())) { - return i18n("mods.modrinth"); - } else { - return i18n("mods.curseforge"); - } - } -} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java index 106dfcc114..a19ab6ac50 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java @@ -95,9 +95,7 @@ private Node createCustomIcon() { RipplerContainer container = new RipplerContainer(shape); FXUtils.setLimitWidth(container, 36); FXUtils.setLimitHeight(container, 36); - container.setOnMouseClicked(e -> { - exploreIcon(); - }); + FXUtils.onClicked(container, this::exploreIcon); return container; } @@ -107,7 +105,7 @@ private Node createIcon(VersionIconType type) { RipplerContainer container = new RipplerContainer(imageView); FXUtils.setLimitWidth(container, 36); FXUtils.setLimitHeight(container, 36); - container.setOnMouseClicked(e -> { + FXUtils.onClicked(container, () -> { if (vs != null) { vs.setVersionIcon(type); onAccept(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java index bb328b7eaf..6d36a9051e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java @@ -56,8 +56,8 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage private final ReadOnlyObjectWrapper state = new ReadOnlyObjectWrapper<>(); private final TabHeader tab; private final TabHeader.Tab versionSettingsTab = new TabHeader.Tab<>("versionSettingsTab"); - private final TabHeader.Tab modListTab = new TabHeader.Tab<>("modListTab"); private final TabHeader.Tab installerListTab = new TabHeader.Tab<>("installerListTab"); + private final TabHeader.Tab modListTab = new TabHeader.Tab<>("modListTab"); private final TabHeader.Tab worldListTab = new TabHeader.Tab<>("worldList"); private final TransitionPane transitionPane = new TransitionPane(); private final BooleanProperty currentVersionUpgradable = new SimpleBooleanProperty(); @@ -68,17 +68,17 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage { versionSettingsTab.setNodeSupplier(loadVersionFor(() -> new VersionSettingsPage(false))); - modListTab.setNodeSupplier(loadVersionFor(ModListPage::new)); installerListTab.setNodeSupplier(loadVersionFor(InstallerListPage::new)); + modListTab.setNodeSupplier(loadVersionFor(ModListPage::new)); worldListTab.setNodeSupplier(loadVersionFor(WorldListPage::new)); - tab = new TabHeader(versionSettingsTab, modListTab, installerListTab, worldListTab); + tab = new TabHeader(versionSettingsTab, installerListTab, modListTab, worldListTab); addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onNavigated); tab.select(versionSettingsTab); FXUtils.onChangeAndOperate(tab.getSelectionModel().selectedItemProperty(), newValue -> { - transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE.getAnimationProducer()); + transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE); }); listenerHolder.add(EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).registerWeak(event -> checkSelectedVersion(), EventPriority.HIGHEST)); @@ -128,10 +128,10 @@ public void loadVersion(String version, Profile profile) { if (versionSettingsTab.isInitialized()) versionSettingsTab.getNode().loadVersion(profile, version); - if (modListTab.isInitialized()) - modListTab.getNode().loadVersion(profile, version); if (installerListTab.isInitialized()) installerListTab.getNode().loadVersion(profile, version); + if (modListTab.isInitialized()) + modListTab.getNode().loadVersion(profile, version); if (worldListTab.isInitialized()) worldListTab.getNode().loadVersion(profile, version); currentVersionUpgradable.set(profile.getRepository().isModpack(version)); @@ -244,40 +244,36 @@ protected Skin(VersionPage control) { versionSettingsItem.setLeftGraphic(wrap(SVG.GEAR_OUTLINE)); versionSettingsItem.setActionButtonVisible(false); versionSettingsItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.versionSettingsTab)); - runInFX(() -> FXUtils.installFastTooltip(versionSettingsItem, i18n("settings.game"))); versionSettingsItem.setOnAction(e -> control.tab.select(control.versionSettingsTab)); - AdvancedListItem modListItem = new AdvancedListItem(); - modListItem.getStyleClass().add("navigation-drawer-item"); - modListItem.setTitle(i18n("mods.manage")); - modListItem.setLeftGraphic(wrap(SVG.PUZZLE)); - modListItem.setActionButtonVisible(false); - modListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.modListTab)); - runInFX(() -> FXUtils.installFastTooltip(modListItem, i18n("mods.manage"))); - modListItem.setOnAction(e -> control.tab.select(control.modListTab)); - AdvancedListItem installerListItem = new AdvancedListItem(); installerListItem.getStyleClass().add("navigation-drawer-item"); installerListItem.setTitle(i18n("settings.tabs.installers")); installerListItem.setLeftGraphic(wrap(SVG.CUBE)); installerListItem.setActionButtonVisible(false); installerListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.installerListTab)); - runInFX(() -> FXUtils.installFastTooltip(installerListItem, i18n("settings.tabs.installers"))); installerListItem.setOnAction(e -> control.tab.select(control.installerListTab)); + AdvancedListItem modListItem = new AdvancedListItem(); + modListItem.getStyleClass().add("navigation-drawer-item"); + modListItem.setTitle(i18n("mods.manage")); + modListItem.setLeftGraphic(wrap(SVG.PUZZLE)); + modListItem.setActionButtonVisible(false); + modListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.modListTab)); + modListItem.setOnAction(e -> control.tab.select(control.modListTab)); + AdvancedListItem worldListItem = new AdvancedListItem(); worldListItem.getStyleClass().add("navigation-drawer-item"); worldListItem.setTitle(i18n("world.manage")); worldListItem.setLeftGraphic(wrap(SVG.EARTH)); worldListItem.setActionButtonVisible(false); worldListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.worldListTab)); - runInFX(() -> FXUtils.installFastTooltip(worldListItem, i18n("world.manage"))); worldListItem.setOnAction(e -> control.tab.select(control.worldListTab)); AdvancedListBox sideBar = new AdvancedListBox() .add(versionSettingsItem) - .add(modListItem) .add(installerListItem) + .add(modListItem) .add(worldListItem); VBox.setVgrow(sideBar, Priority.ALWAYS); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java index 42ffc4c3d8..6fcf3c0de0 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java @@ -22,20 +22,22 @@ import javafx.beans.InvalidationListener; import javafx.beans.binding.Bindings; import javafx.beans.property.*; +import javafx.beans.value.ChangeListener; import javafx.geometry.HPos; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.Label; import javafx.scene.control.ScrollPane; +import javafx.scene.control.Toggle; import javafx.scene.layout.*; import javafx.scene.text.Text; import javafx.stage.FileChooser; import org.jackhuang.hmcl.game.GameDirectoryType; import org.jackhuang.hmcl.game.HMCLGameRepository; import org.jackhuang.hmcl.game.ProcessPriority; +import org.jackhuang.hmcl.game.*; +import org.jackhuang.hmcl.java.JavaManager; import org.jackhuang.hmcl.setting.*; -import org.jackhuang.hmcl.task.Schedulers; -import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.WeakListenerHolder; @@ -46,18 +48,13 @@ import org.jackhuang.hmcl.util.javafx.BindingMapping; import org.jackhuang.hmcl.util.javafx.SafeStringConverter; import org.jackhuang.hmcl.util.platform.Architecture; -import org.jackhuang.hmcl.util.platform.JavaVersion; +import org.jackhuang.hmcl.java.JavaRuntime; import org.jackhuang.hmcl.util.platform.OperatingSystem; import org.jackhuang.hmcl.util.versioning.GameVersionNumber; -import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Arrays; -import java.util.List; -import java.util.Locale; -import java.util.Optional; +import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.Collectors; import static org.jackhuang.hmcl.ui.FXUtils.stringConverter; import static org.jackhuang.hmcl.util.Pair.pair; @@ -72,7 +69,6 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag private Profile profile; private WeakListenerHolder listenerHolder; private String versionId; - private boolean javaItemsLoaded; private final VBox rootPane; private final JFXTextField txtWidth; @@ -83,9 +79,11 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag private final JFXCheckBox chkAutoAllocate; private final JFXCheckBox chkFullscreen; private final ComponentSublist javaSublist; - private final MultiFileItem> javaItem; - private final MultiFileItem.Option> javaAutoDeterminedOption; - private final MultiFileItem.FileOption> javaCustomOption; + private final MultiFileItem> javaItem; + private final MultiFileItem.Option> javaAutoDeterminedOption; + private final MultiFileItem.StringOption> javaVersionOption; + private final MultiFileItem.FileOption> javaCustomOption; + private final ComponentSublist gameDirSublist; private final MultiFileItem gameDirItem; private final MultiFileItem.FileOption gameDirCustomOption; @@ -93,9 +91,11 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag private final OptionToggleButton showLogsPane; private final ImagePickerItem iconPickerItem; + private final ChangeListener> javaListChangeListener; private final InvalidationListener specificSettingsListener; - private final InvalidationListener javaListener = any -> initJavaSubtitle(); + private boolean updatingJavaSetting = false; + private boolean updatingSelectedJava = false; private final StringProperty selectedVersion = new SimpleStringProperty(); private final BooleanProperty navigateToSpecificSettings = new SimpleBooleanProperty(false); @@ -179,8 +179,36 @@ public VersionSettingsPage(boolean globalSetting) { javaSublist.setTitle(i18n("settings.game.java_directory")); javaSublist.setHasSubtitle(true); javaAutoDeterminedOption = new MultiFileItem.Option<>(i18n("settings.game.java_directory.auto"), pair(JavaVersionType.AUTO, null)); - javaCustomOption = new MultiFileItem.FileOption>(i18n("settings.custom"), pair(JavaVersionType.CUSTOM, null)) + javaVersionOption = new MultiFileItem.StringOption<>(i18n("settings.game.java_directory.version"), pair(JavaVersionType.VERSION, null)); + javaVersionOption.setValidators(new NumberValidator(true)); + FXUtils.setLimitWidth(javaVersionOption.getCustomField(), 40); + javaCustomOption = new MultiFileItem.FileOption>(i18n("settings.custom"), pair(JavaVersionType.CUSTOM, null)) .setChooserTitle(i18n("settings.game.java_directory.choose")); + if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) + javaCustomOption.getExtensionFilters().add(new FileChooser.ExtensionFilter("Java", "java.exe")); + + javaListChangeListener = FXUtils.onWeakChangeAndOperate(JavaManager.getAllJavaProperty(), allJava -> { + List>> options = new ArrayList<>(); + options.add(javaAutoDeterminedOption); + options.add(javaVersionOption); + if (allJava != null) { + boolean isX86 = Architecture.SYSTEM_ARCH.isX86() && allJava.stream().allMatch(java -> java.getArchitecture().isX86()); + + for (JavaRuntime java : allJava) { + options.add(new MultiFileItem.Option<>( + i18n("settings.game.java_directory.template", + java.getVersion(), + isX86 ? i18n("settings.game.java_directory.bit", java.getBits().getBit()) + : java.getPlatform().getArchitecture().getDisplayName()), + pair(JavaVersionType.DETECTED, java)) + .setSubtitle(java.getBinary().toString())); + } + } + + options.add(javaCustomOption); + javaItem.loadChildren(options); + initializeSelectedJava(); + }); gameDirItem = new MultiFileItem<>(); gameDirSublist = new ComponentSublist(); @@ -429,31 +457,7 @@ public VersionSettingsPage(boolean globalSetting) { } private void initialize() { - memoryStatus.set(OperatingSystem.getPhysicalMemoryStatus().orElse(OperatingSystem.PhysicalMemoryStatus.INVALID)); - - Task.supplyAsync(JavaVersion::getJavas).thenAcceptAsync(Schedulers.javafx(), list -> { - boolean isX86 = Architecture.SYSTEM_ARCH.isX86() && list.stream().allMatch(java -> java.getArchitecture().isX86()); - - List>> options = list.stream() - .map(javaVersion -> new MultiFileItem.Option<>( - i18n("settings.game.java_directory.template", - javaVersion.getVersion(), - isX86 ? i18n("settings.game.java_directory.bit", javaVersion.getBits().getBit()) - : javaVersion.getPlatform().getArchitecture().getDisplayName()), - pair(JavaVersionType.DETECTED, javaVersion)) - .setSubtitle(javaVersion.getBinary().toString())) - .collect(Collectors.toList()); - options.add(0, javaAutoDeterminedOption); - options.add(javaCustomOption); - javaItem.loadChildren(options); - javaItemsLoaded = true; - initializeSelectedJava(); - }).start(); - - javaItem.setSelectedData(null); - if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) - javaCustomOption.getExtensionFilters().add(new FileChooser.ExtensionFilter("Java", "java.exe")); - + memoryStatus.set(OperatingSystem.getPhysicalMemoryStatus()); enableSpecificSettings.addListener((a, b, newValue) -> { if (versionId == null) return; @@ -472,7 +476,6 @@ private void initialize() { } @Override - @SuppressWarnings("unchecked") public void loadVersion(Profile profile, String versionId) { this.profile = profile; this.versionId = versionId; @@ -517,8 +520,10 @@ public void loadVersion(Profile profile, String versionId) { FXUtils.unbindEnum(cboProcessPriority); lastVersionSetting.usesGlobalProperty().removeListener(specificSettingsListener); + lastVersionSetting.javaVersionTypeProperty().removeListener(javaListener); lastVersionSetting.javaDirProperty().removeListener(javaListener); - lastVersionSetting.javaProperty().removeListener(javaListener); + lastVersionSetting.defaultJavaPathPropertyProperty().removeListener(javaListener); + lastVersionSetting.javaVersionProperty().removeListener(javaListener); gameDirItem.selectedDataProperty().unbindBidirectional(lastVersionSetting.gameDirTypeProperty()); gameDirSublist.subtitleProperty().unbind(); @@ -531,6 +536,7 @@ public void loadVersion(Profile profile, String versionId) { // unbind data fields javaItem.setToggleSelectedListener(null); + javaVersionOption.valueProperty().unbind(); // bind new data fields FXUtils.bindInt(txtWidth, versionSetting.widthProperty()); @@ -551,18 +557,42 @@ public void loadVersion(Profile profile, String versionId) { enableSpecificSettings.set(!versionSetting.isUsesGlobal()); javaItem.setToggleSelectedListener(newValue -> { + if (javaItem.getSelectedData() == null || updatingSelectedJava) + return; + + updatingJavaSetting = true; + + if (javaVersionOption.isSelected()) { + javaVersionOption.valueProperty().bindBidirectional(versionSetting.javaVersionProperty()); + } else { + javaVersionOption.valueProperty().unbind(); + javaVersionOption.setValue(""); + } + if (javaCustomOption.isSelected()) { versionSetting.setUsesCustomJavaDir(); } else if (javaAutoDeterminedOption.isSelected()) { versionSetting.setJavaAutoSelected(); + } else if (javaVersionOption.isSelected()) { + if (versionSetting.getJavaVersionType() != JavaVersionType.VERSION) + versionSetting.setJavaVersion(""); + versionSetting.setJavaVersionType(JavaVersionType.VERSION); + versionSetting.setDefaultJavaPath(null); } else { - versionSetting.setJavaVersion(((Pair) newValue.getUserData()).getValue()); + @SuppressWarnings("unchecked") + JavaRuntime java = ((Pair) newValue.getUserData()).getValue(); + versionSetting.setJavaVersionType(JavaVersionType.DETECTED); + versionSetting.setJavaVersion(java.getVersion()); + versionSetting.setDefaultJavaPath(java.getBinary().toString()); } + + updatingJavaSetting = false; }); + versionSetting.javaVersionTypeProperty().addListener(javaListener); versionSetting.javaDirProperty().addListener(javaListener); versionSetting.defaultJavaPathPropertyProperty().addListener(javaListener); - versionSetting.javaProperty().addListener(javaListener); + versionSetting.javaVersionProperty().addListener(javaListener); gameDirItem.selectedDataProperty().bindBidirectional(versionSetting.gameDirTypeProperty()); gameDirSublist.subtitleProperty().bind(Bindings.createStringBinding(() -> Paths.get(profile.getRepository().getRunDirectory(versionId).getAbsolutePath()).normalize().toString(), @@ -576,52 +606,101 @@ public void loadVersion(Profile profile, String versionId) { } private void initializeSelectedJava() { - if (lastVersionSetting == null - || !javaItemsLoaded /* JREs are still being loaded */) { + if (lastVersionSetting == null || updatingJavaSetting) return; - } - if (lastVersionSetting.isUsesCustomJavaDir()) { - javaCustomOption.setSelected(true); - } else if (lastVersionSetting.isJavaAutoSelected()) { - javaAutoDeterminedOption.setSelected(true); - } else { -// javaLoading.set(true); - lastVersionSetting.getJavaVersion(null, null) - .thenAcceptAsync(Schedulers.javafx(), javaVersion -> { - javaItem.setSelectedData(pair(JavaVersionType.DETECTED, javaVersion)); -// javaLoading.set(false); - }).start(); + updatingSelectedJava = true; + switch (lastVersionSetting.getJavaVersionType()) { + case CUSTOM: + javaCustomOption.setSelected(true); + break; + case VERSION: + javaVersionOption.setSelected(true); + javaVersionOption.setValue(lastVersionSetting.getJavaVersion()); + break; + case AUTO: + javaAutoDeterminedOption.setSelected(true); + break; + default: + Toggle toggle = null; + if (JavaManager.isInitialized()) { + try { + JavaRuntime java = lastVersionSetting.getJava(null, null); + if (java != null) { + for (Toggle t : javaItem.getGroup().getToggles()) { + if (t.getUserData() != null) { + @SuppressWarnings("unchecked") + Pair userData = (Pair) t.getUserData(); + if (userData.getValue() != null && java.getBinary().equals(userData.getValue().getBinary())) { + toggle = t; + break; + + } + } + } + } + } catch (InterruptedException ignored) { + } + } + + if (toggle != null) { + toggle.setSelected(true); + } else { + Toggle selectedToggle = javaItem.getGroup().getSelectedToggle(); + if (selectedToggle != null) { + selectedToggle.setSelected(false); + } + } + break; } + updatingSelectedJava = false; } private void initJavaSubtitle() { FXUtils.checkFxUserThread(); - initializeSelectedJava(); - VersionSetting versionSetting = lastVersionSetting; - if (versionSetting == null) + if (lastVersionSetting == null) return; - Profile profile = this.profile; + initializeSelectedJava(); + HMCLGameRepository repository = this.profile.getRepository(); String versionId = this.versionId; - boolean autoSelected = versionSetting.isJavaAutoSelected(); + JavaVersionType javaVersionType = lastVersionSetting.getJavaVersionType(); + boolean autoSelected = javaVersionType == JavaVersionType.AUTO || javaVersionType == JavaVersionType.VERSION; - if (autoSelected && versionId == null) { + if (versionId == null && autoSelected) { javaSublist.setSubtitle(i18n("settings.game.java_directory.auto")); return; } - Task.composeAsync(Schedulers.javafx(), () -> { + Pair selectedData = javaItem.getSelectedData(); + if (selectedData != null && selectedData.getValue() != null) { + javaSublist.setSubtitle(selectedData.getValue().getBinary().toString()); + return; + } + + if (JavaManager.isInitialized()) { + GameVersionNumber gameVersionNumber; + Version version; if (versionId == null) { - return versionSetting.getJavaVersion(GameVersionNumber.unknown(), null); + gameVersionNumber = GameVersionNumber.unknown(); + version = null; } else { - return versionSetting.getJavaVersion( - GameVersionNumber.asGameVersion(profile.getRepository().getGameVersion(versionId)), - profile.getRepository().getVersion(versionId)); + gameVersionNumber = GameVersionNumber.asGameVersion(repository.getGameVersion(versionId)); + version = repository.getVersion(versionId); } - }).thenAcceptAsync(Schedulers.javafx(), javaVersion -> javaSublist.setSubtitle(Optional.ofNullable(javaVersion) - .map(JavaVersion::getBinary).map(Path::toString).orElseGet(() -> - autoSelected ? i18n("settings.game.java_directory.auto.not_found") : i18n("settings.game.java_directory.invalid")))) - .start(); + + try { + JavaRuntime java = lastVersionSetting.getJava(gameVersionNumber, version); + if (java != null) { + javaSublist.setSubtitle(java.getBinary().toString()); + } else { + javaSublist.setSubtitle(autoSelected ? i18n("settings.game.java_directory.auto.not_found") : i18n("settings.game.java_directory.invalid")); + } + return; + } catch (InterruptedException ignored) { + } + } + + javaSublist.setSubtitle(""); } private void editSpecificSettings() { @@ -664,10 +743,4 @@ private void loadIcon() { public ReadOnlyObjectProperty stateProperty() { return state.getReadOnlyProperty(); } - - private enum JavaVersionType { - DETECTED, - CUSTOM, - AUTO, - } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldExportPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldExportPageSkin.java index 38af59549b..98226cbb28 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldExportPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldExportPageSkin.java @@ -83,7 +83,7 @@ public WorldExportPageSkin(WorldExportPage skinnable) { JFXButton btnExport = FXUtils.newRaisedButton(i18n("button.export")); btnExport.disableProperty().bind(Bindings.createBooleanBinding(() -> txtWorldName.getText().isEmpty() || Files.exists(Paths.get(fileItem.getPath())), txtWorldName.textProperty().isEmpty(), fileItem.pathProperty())); - btnExport.setOnMouseClicked(e -> skinnable.export()); + btnExport.setOnAction(e -> skinnable.export()); HBox bottom = new HBox(); bottom.setAlignment(Pos.CENTER_RIGHT); bottom.getChildren().setAll(btnExport); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java index d7ab4e1863..a8d7e2c5bd 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java @@ -1,3 +1,20 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package org.jackhuang.hmcl.ui.versions; import com.github.steveice10.opennbt.tag.builtin.*; @@ -10,11 +27,14 @@ import javafx.collections.ObservableList; import javafx.geometry.Pos; import javafx.scene.control.Label; +import javafx.scene.control.ProgressIndicator; import javafx.scene.control.ScrollPane; import javafx.scene.layout.BorderPane; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import org.jackhuang.hmcl.game.World; +import org.jackhuang.hmcl.task.Schedulers; +import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.construct.ComponentList; import org.jackhuang.hmcl.ui.construct.DoubleValidator; @@ -32,22 +52,36 @@ import static org.jackhuang.hmcl.util.i18n.I18n.formatDateTime; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; +/** + * @author Glavo + */ public final class WorldInfoPage extends StackPane implements DecoratorPage { private final World world; - private final CompoundTag levelDat; - private final CompoundTag dataTag; + private CompoundTag levelDat; - private final ObjectProperty stateProperty = new SimpleObjectProperty<>(); + private final ObjectProperty stateProperty; - public WorldInfoPage(World world) throws IOException { + public WorldInfoPage(World world) { this.world = world; - this.levelDat = world.readLevelDat(); - this.dataTag = levelDat.get("Data"); + this.stateProperty = new SimpleObjectProperty<>(State.fromTitle(i18n("world.info.title", world.getWorldName()))); + + this.getChildren().add(new ProgressIndicator()); + Task.supplyAsync(world::readLevelDat) + .whenComplete(Schedulers.javafx(), ((result, exception) -> { + if (exception == null) { + this.levelDat = result; + loadWorldInfo(); + } else { + LOG.warning("Failed to load level.dat", exception); + this.getChildren().setAll(new Label(i18n("world.info.failed"))); + } + })).start(); + } + private void loadWorldInfo() { + CompoundTag dataTag = levelDat.get("Data"); CompoundTag worldGenSettings = dataTag.get("WorldGenSettings"); - stateProperty.set(State.fromTitle(i18n("world.info.title", world.getWorldName()))); - ScrollPane scrollPane = new ScrollPane(); scrollPane.setFitToHeight(true); scrollPane.setFitToWidth(true); @@ -483,7 +517,6 @@ String formatPosition(Tag tag) { Tag z = listTag.get(2); if (x instanceof DoubleTag && y instanceof DoubleTag && z instanceof DoubleTag) { - //noinspection MalformedFormatString return this == OVERWORLD ? String.format("(%.2f, %.2f, %.2f)", x.getValue(), y.getValue(), z.getValue()) : String.format("%s (%.2f, %.2f, %.2f)", name, x.getValue(), y.getValue(), z.getValue()); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListItem.java index 5f98c22554..4a45486ced 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListItem.java @@ -51,7 +51,7 @@ public WorldListItem(World world) { subtitle.set(i18n("world.description", world.getFileName(), formatDateTime(Instant.ofEpochMilli(world.getLastPlayed())), world.getGameVersion() == null ? i18n("message.unknown") : world.getGameVersion())); - setOnMouseClicked(event -> showInfo()); + FXUtils.onClicked(this, this::showInfo); } @Override @@ -102,10 +102,6 @@ public void manageDatapacks() { } public void showInfo() { - try { - Controllers.navigate(new WorldInfoPage(world)); - } catch (Exception e) { - // TODO - } + Controllers.navigate(new WorldInfoPage(world)); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListItemSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListItemSkin.java index e83c7f962f..27a8f3dbd1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListItemSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListItemSkin.java @@ -76,9 +76,7 @@ public WorldListItemSkin(WorldListItem skinnable) { right.setAlignment(Pos.CENTER_RIGHT); JFXButton btnManage = new JFXButton(); - btnManage.setOnMouseClicked(e -> { - popup.show(root, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, root.getHeight()); - }); + btnManage.setOnAction(e -> popup.show(root, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, root.getHeight())); btnManage.getStyleClass().add("toggle-icon4"); BorderPane.setAlignment(btnManage, Pos.CENTER); btnManage.setGraphic(SVG.DOTS_VERTICAL.createIcon(Theme.blackFill(), -1, -1)); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardPage.java index cf5c2438b7..b9d04ba341 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardPage.java @@ -23,7 +23,8 @@ public interface WizardPage { default void onNavigate(Map settings) { } - void cleanup(Map settings); + default void cleanup(Map settings) { + } String getTitle(); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateHandler.java b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateHandler.java index d0dccc4f7b..ddcd5342fa 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateHandler.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateHandler.java @@ -33,7 +33,7 @@ import org.jackhuang.hmcl.util.TaskCancellationAction; import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.io.JarUtils; -import org.jackhuang.hmcl.util.platform.JavaVersion; +import org.jackhuang.hmcl.java.JavaRuntime; import org.jackhuang.hmcl.util.platform.OperatingSystem; import java.io.IOException; @@ -177,7 +177,7 @@ private static void requestUpdate(Path updateTo, Path self) throws IOException { private static void startJava(Path jar, String... appArgs) throws IOException { List commandline = new ArrayList<>(); - commandline.add(JavaVersion.fromCurrentEnvironment().getBinary().toString()); + commandline.add(JavaRuntime.getDefault().getBinary().toString()); for (Map.Entry entry : System.getProperties().entrySet()) { Object key = entry.getKey(); if (key instanceof String && ((String) key).startsWith("hmcl.")) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/util/FractureiserDetector.java b/HMCL/src/main/java/org/jackhuang/hmcl/util/FractureiserDetector.java deleted file mode 100644 index 01a4370303..0000000000 --- a/HMCL/src/main/java/org/jackhuang/hmcl/util/FractureiserDetector.java +++ /dev/null @@ -1,57 +0,0 @@ -package org.jackhuang.hmcl.util; - -import org.jackhuang.hmcl.util.platform.OperatingSystem; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; - -/** - * @see fractureiser-investigation/fractureiser - * @see [MALWARE WARNING] "fractureiser" malware in many popular Minecraft mods and modpacks - */ -public final class FractureiserDetector { - private FractureiserDetector() { - } - - private static final class FractureiserException extends Exception { - } - - public static boolean detect() { - try { - if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) { - Path appdata = Paths.get(System.getProperty("user.home"), "AppData"); - if (Files.isDirectory(appdata)) { - check(appdata.resolve("Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\run.bat")); - - Path falseEdgePath = appdata.resolve("Local\\Microsoft Edge"); - if (Files.exists(falseEdgePath)) { - check(falseEdgePath.resolve(".ref")); - check(falseEdgePath.resolve("client.jar")); - check(falseEdgePath.resolve("lib.dll")); - check(falseEdgePath.resolve("libWebGL64.jar")); - check(falseEdgePath.resolve("run.bat")); - } - } - } else if (OperatingSystem.CURRENT_OS.isLinuxOrBSD()) { - Path dataDir = Paths.get(System.getProperty("user.home"), ".config", ".data"); - if (Files.exists(dataDir)) { - check(dataDir.resolve(".ref")); - check(dataDir.resolve("client.jar")); - check(dataDir.resolve("lib.jar")); - } - } - } catch (FractureiserException e) { - return true; - } catch (Throwable ignored) { - } - - return false; - } - - private static void check(Path path) throws FractureiserException { - if (Files.isRegularFile(path)) { - throw new FractureiserException(); - } - } -} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/util/NativePatcher.java b/HMCL/src/main/java/org/jackhuang/hmcl/util/NativePatcher.java index 58a1fd9aad..82ccd9fc3b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/util/NativePatcher.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/util/NativePatcher.java @@ -1,11 +1,27 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package org.jackhuang.hmcl.util; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.game.*; import org.jackhuang.hmcl.setting.VersionSetting; import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.platform.Architecture; -import org.jackhuang.hmcl.util.platform.JavaVersion; +import org.jackhuang.hmcl.java.JavaRuntime; import org.jackhuang.hmcl.util.platform.OperatingSystem; import org.jackhuang.hmcl.util.platform.Platform; import org.jackhuang.hmcl.util.versioning.GameVersionNumber; @@ -17,8 +33,12 @@ import java.util.*; import java.util.stream.Collectors; +import static org.jackhuang.hmcl.util.gson.JsonUtils.mapTypeOf; import static org.jackhuang.hmcl.util.logging.Logger.LOG; +/** + * @author Glavo + */ public final class NativePatcher { private NativePatcher() { } @@ -31,9 +51,7 @@ private static Map getNatives(Platform platform) { return natives.computeIfAbsent(platform, p -> { //noinspection ConstantConditions try (Reader reader = new InputStreamReader(NativePatcher.class.getResourceAsStream("/assets/natives.json"), StandardCharsets.UTF_8)) { - Map> natives = JsonUtils.GSON.fromJson(reader, new TypeToken>>() { - }.getType()); - + Map> natives = JsonUtils.GSON.fromJson(reader, mapTypeOf(String.class, mapTypeOf(String.class, Library.class))); return natives.getOrDefault(p.toString(), Collections.emptyMap()); } catch (IOException e) { LOG.warning("Failed to load native library list", e); @@ -42,7 +60,7 @@ private static Map getNatives(Platform platform) { }); } - public static Version patchNative(Version version, String gameVersion, JavaVersion javaVersion, VersionSetting settings) { + public static Version patchNative(Version version, String gameVersion, JavaRuntime javaVersion, VersionSetting settings) { if (settings.getNativesDirType() == NativesDirectoryType.CUSTOM) { if (gameVersion != null && GameVersionNumber.compare(gameVersion, "1.19") < 0) return version; @@ -114,9 +132,10 @@ public static Version patchNative(Version version, String gameVersion, JavaVersi if (library.isNative()) { Library replacement = replacements.getOrDefault(library.getName() + ":natives", NONEXISTENT_LIBRARY); if (replacement == NONEXISTENT_LIBRARY) { - LOG.warning("No alternative native library " + library.getName() + " provided for platform " + javaVersion.getPlatform()); + LOG.warning("No alternative native library " + library.getName() + ":natives provided for platform " + javaVersion.getPlatform()); newLibraries.add(library); } else if (replacement != null) { + LOG.info("Replace " + library.getName() + ":natives with " + replacement.getName()); newLibraries.add(replacement); } } else { @@ -124,6 +143,7 @@ public static Version patchNative(Version version, String gameVersion, JavaVersi if (replacement == NONEXISTENT_LIBRARY) { newLibraries.add(library); } else if (replacement != null) { + LOG.info("Replace " + library.getName() + " with " + replacement.getName()); newLibraries.add(replacement); } } @@ -132,7 +152,7 @@ public static Version patchNative(Version version, String gameVersion, JavaVersi return version.setLibraries(newLibraries); } - public static Library getMesaLoader(JavaVersion javaVersion, Renderer renderer) { + public static Library getMesaLoader(JavaRuntime javaVersion, Renderer renderer) { return getNatives(javaVersion.getPlatform()).get(renderer == Renderer.LLVMPIPE ? "software-renderer-loader" : "mesa-loader"); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/util/SelfDependencyPatcher.java b/HMCL/src/main/java/org/jackhuang/hmcl/util/SelfDependencyPatcher.java index d6a8aa77ed..8d88115fde 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/util/SelfDependencyPatcher.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/util/SelfDependencyPatcher.java @@ -41,13 +41,13 @@ */ package org.jackhuang.hmcl.util; -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.Main; import org.jackhuang.hmcl.ui.SwingUtils; +import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.io.ChecksumMismatchException; import org.jackhuang.hmcl.util.io.IOUtils; import org.jackhuang.hmcl.util.io.JarUtils; +import org.jackhuang.hmcl.java.JavaRuntime; import org.jackhuang.hmcl.util.platform.Platform; import javax.swing.*; @@ -67,9 +67,10 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.stream.Collectors.toSet; import static org.jackhuang.hmcl.Metadata.HMCL_DIRECTORY; +import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf; +import static org.jackhuang.hmcl.util.gson.JsonUtils.mapTypeOf; import static org.jackhuang.hmcl.util.logging.Logger.LOG; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; -import static org.jackhuang.hmcl.util.platform.JavaVersion.CURRENT_JAVA; // From: https://github.com/Col-E/Recaf/blob/7378b397cee664ae81b7963b0355ef8ff013c3a7/src/main/java/me/coley/recaf/util/self/SelfDependencyPatcher.java public final class SelfDependencyPatcher { @@ -104,28 +105,14 @@ private static final class DependencyDescriptor { private static final Path DEPENDENCIES_DIR_PATH = HMCL_DIRECTORY.resolve("dependencies").resolve(Platform.getPlatform().toString()).resolve("openjfx"); static List readDependencies() { - ArrayList dependencies; //noinspection ConstantConditions try (Reader reader = new InputStreamReader(SelfDependencyPatcher.class.getResourceAsStream(DEPENDENCIES_LIST_FILE), UTF_8)) { - Map> allDependencies = - new Gson().fromJson(reader, new TypeToken>>(){}.getType()); - dependencies = allDependencies.get(Platform.getPlatform().toString()); + Map> allDependencies = + JsonUtils.GSON.fromJson(reader, mapTypeOf(String.class, listTypeOf(DependencyDescriptor.class))); + return allDependencies.get(Platform.getPlatform().toString()); } catch (IOException e) { throw new UncheckedIOException(e); } - - if (dependencies == null) return null; - - try { - ClassLoader classLoader = SelfDependencyPatcher.class.getClassLoader(); - Class.forName("netscape.javascript.JSObject", false, classLoader); - Class.forName("org.w3c.dom.html.HTMLDocument", false, classLoader); - } catch (Throwable e) { - LOG.warning("Disable javafx.web because JRE is incomplete", e); - dependencies.removeIf(it -> "javafx.web".equals(it.module) || "javafx.media".equals(it.module)); - } - - return dependencies; } public String module; @@ -188,7 +175,7 @@ public static void patch() throws PatchException, IncompatibleVersionException, // So the problem with Java 8 is that some distributions DO NOT BUNDLE JAVAFX // Why is this a problem? OpenJFX does not come in public bundles prior to Java 11 // So you're out of luck unless you change your JDK or update Java. - if (CURRENT_JAVA.getParsedVersion() < 11) { + if (JavaRuntime.CURRENT_VERSION < 11) { throw new IncompatibleVersionException(); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/Locales.java b/HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/Locales.java index 3da9e612cd..868371dce8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/Locales.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/Locales.java @@ -20,8 +20,8 @@ import com.google.gson.annotations.JsonAdapter; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; +import org.jackhuang.hmcl.java.JavaInfo; import org.jackhuang.hmcl.util.Lang; -import org.jackhuang.hmcl.util.platform.JavaVersion; import java.io.IOException; import java.util.List; @@ -39,16 +39,6 @@ private Locales() { */ public static final SupportedLocale EN = new SupportedLocale(Locale.ROOT); - /** - * Traditional Chinese - */ - public static final SupportedLocale ZH = new SupportedLocale(Locale.TRADITIONAL_CHINESE); - - /** - * Simplified Chinese - */ - public static final SupportedLocale ZH_CN = new SupportedLocale(Locale.SIMPLIFIED_CHINESE); - /** * Spanish */ @@ -64,23 +54,33 @@ private Locales() { */ public static final SupportedLocale JA = new SupportedLocale(Locale.JAPANESE); - public static final List LOCALES = Lang.immutableListOf(DEFAULT, EN, ZH_CN, ZH, ES, RU, JA); + /** + * Traditional Chinese + */ + public static final SupportedLocale ZH = new SupportedLocale(Locale.TRADITIONAL_CHINESE); + + /** + * Simplified Chinese + */ + public static final SupportedLocale ZH_CN = new SupportedLocale(Locale.SIMPLIFIED_CHINESE); + + public static final List LOCALES = Lang.immutableListOf(DEFAULT, EN, ES, JA, RU, ZH_CN, ZH); public static SupportedLocale getLocaleByName(String name) { if (name == null) return DEFAULT; switch (name.toLowerCase(Locale.ROOT)) { case "en": return EN; - case "zh": - return ZH; - case "zh_cn": - return ZH_CN; case "es": return ES; - case "ru": - return RU; case "ja": return JA; + case "ru": + return RU; + case "zh": + return ZH; + case "zh_cn": + return ZH_CN; default: return DEFAULT; } @@ -88,11 +88,11 @@ public static SupportedLocale getLocaleByName(String name) { public static String getNameByLocale(SupportedLocale locale) { if (locale == EN) return "en"; - else if (locale == ZH) return "zh"; - else if (locale == ZH_CN) return "zh_CN"; else if (locale == ES) return "es"; else if (locale == RU) return "ru"; else if (locale == JA) return "ja"; + else if (locale == ZH) return "zh"; + else if (locale == ZH_CN) return "zh_CN"; else if (locale == DEFAULT) return "def"; else throw new IllegalArgumentException("Unknown locale: " + locale); } @@ -116,7 +116,7 @@ public ResourceBundle getResourceBundle() { if (resourceBundle == null) { if (this != DEFAULT && this.locale == DEFAULT.locale) { bundle = DEFAULT.getResourceBundle(); - } else if (JavaVersion.CURRENT_JAVA.getParsedVersion() < 9) { + } else if (JavaInfo.CURRENT_ENVIRONMENT.getParsedVersion() < 9) { bundle = ResourceBundle.getBundle("assets.lang.I18N", locale, UTF8Control.INSTANCE); } else { // Java 9+ uses UTF-8 as the default encoding for resource bundles diff --git a/HMCL/src/main/java11/org/jackhuang/hmcl/util/logging/CallerFinder.java b/HMCL/src/main/java11/org/jackhuang/hmcl/util/logging/CallerFinder.java index 89547137f8..8c934cdaab 100644 --- a/HMCL/src/main/java11/org/jackhuang/hmcl/util/logging/CallerFinder.java +++ b/HMCL/src/main/java11/org/jackhuang/hmcl/util/logging/CallerFinder.java @@ -9,13 +9,13 @@ * @author Glavo */ final class CallerFinder { - private static final StackWalker WALKER = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); private static final String PACKAGE_PREFIX = CallerFinder.class.getPackageName() + "."; private static final Predicate PREDICATE = stackFrame -> !stackFrame.getClassName().startsWith(PACKAGE_PREFIX); private static final Function, Optional> FUNCTION = stream -> stream.filter(PREDICATE).findFirst(); + private static final Function FRAME_MAPPING = frame -> frame.getClassName() + "." + frame.getMethodName(); static String getCaller() { - return WALKER.walk(FUNCTION).map(it -> it.getClassName() + "." + it.getMethodName()).orElse(null); + return StackWalker.getInstance().walk(FUNCTION).map(FRAME_MAPPING).orElse(null); } private CallerFinder() { diff --git a/HMCL/src/main/resources/assets/about/deps.json b/HMCL/src/main/resources/assets/about/deps.json new file mode 100644 index 0000000000..5b0cf68475 --- /dev/null +++ b/HMCL/src/main/resources/assets/about/deps.json @@ -0,0 +1,62 @@ +[ + { + "title" : "OpenJFX", + "subtitle" : "Copyright © 2013, 2024, Oracle and/or its affiliates.\nLicensed under the GPL 2 with Classpath Exception.", + "externalLink" : "https://openjfx.io" + }, + { + "title" : "JFoenix", + "subtitle" : "Copyright © 2016 JFoenix.\nLicensed under the MIT License.", + "externalLink" : "https://github.com/sshahine/JFoenix" + }, + { + "title" : "Gson", + "subtitle" : "Copyright © 2008 Google Inc.\nLicensed under the Apache 2.0 License.", + "externalLink" : "https://github.com/google/gson" + }, + { + "title" : "Apache Commons Compress", + "subtitle" : "Licensed under the Apache 2.0 License.", + "externalLink" : "https://commons.apache.org/proper/commons-compress/" + }, + { + "title" : "XZ for Java", + "subtitle" : "Lasse Collin, Igor Pavlov, and/or Brett Okken.\nPublic Domain.", + "externalLink" : "https://tukaani.org/xz/java.html" + }, + { + "title" : "FX Gson", + "subtitle" : "Copyright © 2016 Joffrey Bion.\nLicensed under the MIT License.", + "externalLink" : "https://github.com/joffrey-bion/fx-gson" + }, + { + "title" : "jsoup", + "subtitle" : "Copyright © 2009 - 2024 Jonathan Hedley (https://jsoup.org/)\nLicensed under the MIT License.", + "externalLink" : "https://jsoup.org/" + }, + { + "title" : "NanoHTTPD", + "subtitle" : "Copyright © 2012 - 2015 nanohttpd.\nLicensed under the BSD 3-clause License.", + "externalLink" : "https://github.com/NanoHttpd/nanohttpd" + }, + { + "title" : "Constant Pool Scanner", + "subtitle" : "Copyright © 1997-2010 Oracle and/or its affiliates.\nLicensed under the GPL 2 or the CDDL.", + "externalLink" : "https://github.com/jenkinsci/constant-pool-scanner" + }, + { + "title" : "OpenNBT", + "subtitle" : "Copyright © 2013-2021 Steveice10.\nLicensed under the MIT License.", + "externalLink" : "https://github.com/GeyserMC/OpenNBT" + }, + { + "title" : "minecraft-jfx-skin", + "subtitle" : "Copyright © 2016 InfinityStudio.\nLicensed under the GPL 3.", + "externalLink" : "https://github.com/InfinityStudio/minecraft-jfx-skin" + }, + { + "title" : "SimplePNG", + "subtitle" : "Copyright © 2023 Glavo.\nLicensed under the Apache 2.0 License.", + "externalLink" : "https://github.com/Glavo/SimplePNG" + } +] \ No newline at end of file diff --git a/HMCL/src/main/resources/assets/about/thanks.json b/HMCL/src/main/resources/assets/about/thanks.json new file mode 100644 index 0000000000..189ff30129 --- /dev/null +++ b/HMCL/src/main/resources/assets/about/thanks.json @@ -0,0 +1,78 @@ +[ + { + "image" : "/assets/img/yushijinhun.png", + "title" : "yushijinhun", + "subtitleLocalized" : "about.thanks_to.yushijinhun.statement", + "externalLink" : "https://github.com/yushijinhun" + }, + { + "image" : "/assets/img/bangbang93.png", + "title" : "bangbang93", + "subtitleLocalized" : "about.thanks_to.bangbang93.statement", + "externalLink" : "https://www.bangbang93.com" + }, + { + "image" : "/assets/img/glavo.png", + "title" : "Glavo", + "subtitleLocalized" : "about.thanks_to.glavo.statement", + "externalLink" : "https://github.com/Glavo" + }, + { + "image" : "/assets/img/zekerzhayard.png", + "title" : "ZekerZhayard", + "subtitleLocalized" : "about.thanks_to.zekerzhayard.statement", + "externalLink" : "https://github.com/ZekerZhayard" + }, + { + "image" : "/assets/img/zkitefly.png", + "title" : "Zkitefly", + "subtitleLocalized" : "about.thanks_to.zkitefly.statement", + "externalLink" : "https://github.com/zkitefly" + }, + { + "image" : "/assets/img/burningtnt.png", + "title" : "Burning_TNT", + "subtitleLocalized" : "about.thanks_to.burningtnt.statement", + "externalLink" : "https://github.com/burningtnt" + }, + { + "image" : "/assets/img/ShulkerSakura.png", + "title" : "ShulkerSakura", + "subtitleLocalized" : "about.thanks_to.shulkersakura.statement", + "externalLink" : "https://github.com/ShulkerSakura" + }, + { + "image" : "/assets/img/gamerteam.png", + "title" : "gamerteam", + "subtitleLocalized" : "about.thanks_to.gamerteam.statement", + "externalLink" : "https://github.com/ZhaiSoul" + }, + { + "image" : "/assets/img/red_lnn.png", + "title" : "Red_lnn", + "subtitleLocalized" : "about.thanks_to.red_lnn.statement" + }, + { + "image" : "/assets/img/mcmod.png", + "titleLocalized" : "about.thanks_to.mcmod", + "subtitleLocalized" : "about.thanks_to.mcmod.statement", + "externalLink" : "https://www.mcmod.cn" + }, + { + "image" : "/assets/img/chest.png", + "titleLocalized" : "about.thanks_to.mcbbs", + "subtitleLocalized" : "about.thanks_to.mcbbs.statement" + }, + { + "image" : "/assets/img/github.png", + "titleLocalized" : "about.thanks_to.contributors", + "subtitleLocalized" : "about.thanks_to.contributors.statement", + "externalLink" : "https://github.com/HMCL-dev/HMCL/graphs/contributors" + }, + { + "image" : "/assets/img/icon.png", + "titleLocalized" : "about.thanks_to.users", + "subtitleLocalized" : "about.thanks_to.users.statement", + "externalLink" : "https://docs.hmcl.net/groups.html" + } +] \ No newline at end of file diff --git a/HMCL/src/main/resources/assets/css/root.css b/HMCL/src/main/resources/assets/css/root.css index 36c589bbe3..6ee1010484 100644 --- a/HMCL/src/main/resources/assets/css/root.css +++ b/HMCL/src/main/resources/assets/css/root.css @@ -431,7 +431,6 @@ .jfx-layout-heading { -fx-font-size: 20.0px; - -fx-font-family: -fx-base-font-family; -fx-alignment: center-left; -fx-padding: 5.0 0.0 5.0 0.0; } @@ -449,18 +448,15 @@ .dialog-error { -fx-text-fill: #d32f2f; - -fx-font-family: -fx-base-font-family; -fx-padding: 0.7em 0.8em; } .dialog-accept { -fx-text-fill: #03A9F4; - -fx-font-family: -fx-base-font-family; -fx-padding: 0.7em 0.8em; } .dialog-cancel { - -fx-font-family: -fx-base-font-family; -fx-padding: 0.7em 0.8em; } @@ -1438,3 +1434,37 @@ .fit-width { -fx-pref-width: 100%; } + +/******************************************************************************* +* * +* HTML Renderer * +* * +*******************************************************************************/ + +.html { + -fx-font-size: 16; +} + +.html-hyperlink { + -fx-fill: blue; +} + +.html-h1 { + -fx-font-size: 22; +} + +.html-h2 { + -fx-font-size: 20; +} + +.html-h3 { + -fx-font-size: 18; +} + +.html-bold { + -fx-font-weight: bold; +} + +.html-italic { + -fx-font-style: italic; +} diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 77ed7f4190..9e8364c102 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -1,6 +1,6 @@ # # Hello Minecraft! Launcher -# Copyright (C) 2023 huangyuhui and contributors +# Copyright (C) 2024 huangyuhui and contributors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -21,62 +21,61 @@ about=About about.copyright=Copyright -about.copyright.statement=Copyright © 2024 huangyuhui. +about.copyright.statement=Copyright © 2024 huangyuhui about.author=Author -about.author.statement=@huanghongxun on bilibili +about.author.statement=bilibili @huanghongxun about.claim=EULA -about.claim.statement=Click on this link for full text. +about.claim.statement=Click this link for full text. about.dependency=Third-party Libraries about.legal=Legal Acknowledgement about.thanks_to=Thanks to -about.thanks_to.bangbang93.statement=For providing BMCLAPI download API, please consider donating. +about.thanks_to.bangbang93.statement=For providing the BMCLAPI download mirror, please consider donating! about.thanks_to.burningtnt.statement=Contribute a lot of technical support to HMCL. about.thanks_to.contributors=All contributors on GitHub -about.thanks_to.contributors.statement=Without the awesome open-source community, Hello Minecraft! Launcher would not make it so far. +about.thanks_to.contributors.statement=Without the awesome open-source community, HMCL would not make it so far. about.thanks_to.gamerteam.statement=For providing the default background image. about.thanks_to.glavo.statement=Responsible for maintaining HMCL. about.thanks_to.zekerzhayard.statement=Contribute a lot of technical support to HMCL. -about.thanks_to.zkitefly.statement=Responsible for maintaining documentation of HMCL. +about.thanks_to.zkitefly.statement=Responsible for maintaining the documentation of HMCL. about.thanks_to.mcbbs=MCBBS (Minecraft Chinese Forum) -about.thanks_to.mcbbs.statement=or providing mcbbs.net download mirror for Mainland China users. -about.thanks_to.mcmod=mcmod.cn -about.thanks_to.mcmod.statement=For providing Chinese translations and wiki for various mods. +about.thanks_to.mcbbs.statement=For providing the mcbbs.net download mirror for Chinese Mainland users. (No longer available) +about.thanks_to.mcmod=MCMod (mcmod.cn) +about.thanks_to.mcmod.statement=For providing the Simplified Chinese translations and wiki for various mods. about.thanks_to.red_lnn.statement=For providing the default background image. -about.thanks_to.shulkersakura.statement=For providing the icon for HMCL +about.thanks_to.shulkersakura.statement=For providing the logo for HMCL. about.thanks_to.users=HMCL User Group Members about.thanks_to.users.statement=Thanks for donations, bug reports, and so on. -about.thanks_to.yushijinhun.statement=For providing authlib-injector related support. +about.thanks_to.yushijinhun.statement=For providing the authlib-injector related support. about.open_source=Open Source about.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL) account=Accounts account.cape=Cape -account.character=character -account.choose=Choose a Character -account.create=Add an account -account.create.microsoft=Add a Microsoft account -account.create.offline=Add an offline account -account.create.authlibInjector=Create an authlib-injector account +account.character=Player +account.choose=Choose a Player +account.create=Add Account +account.create.microsoft=Add a Microsoft Account +account.create.offline=Add an Offline Account +account.create.authlibInjector=Add an authlib-injector Account account.email=Email -account.failed=Account refresh failed -account.failed.character_deleted=The character has already been deleted. -account.failed.connect_authentication_server=Unable to contact authentication servers, your Internet connection may be down. +account.failed=Account refresh failed. +account.failed.character_deleted=The player has already been deleted. +account.failed.connect_authentication_server=Unable to connect to the authentication server, your network connection may be down. account.failed.connect_injector_server=Unable to connect to the authentication server. Please check your network and make sure you entered the correct URL. -account.failed.injector_download_failure=Unable to download authlib-injector. Please check your network, or try switching to a different download mirror. +account.failed.injector_download_failure=Unable to download the authlib-injector. Please check your network, or try switching to a different download source. account.failed.invalid_credentials=Incorrect password or rate limited, please try again later. -account.failed.invalid_password=Invalid password +account.failed.invalid_password=Invalid password. account.failed.invalid_token=Please try logging in again. account.failed.migration=Your account needs to be migrated to a Microsoft account. If you already did, you should re-login to your migrated Microsoft account instead. account.failed.no_character=There are no characters linked to this account. -account.failed.server_disconnected=Cannot access authentication server. You can log in offline or try logging in again.\n\ - If you try multiple times and still fail, please try adding the account again. +account.failed.server_disconnected=Unable to connect to the authentication server. You can log in offline mode or try logging in again.\n\ + If you try several times and still fail, please try adding the account again. account.failed.server_response_malformed=Invalid server response, the authentication server may not be working. account.failed.ssl=An SSL error occurred while connecting to the server. Please try updating your Java. account.failed.wrong_account=You have logged in to the wrong account. -account.hmcl.hint=You need to click on "Login" and complete the process in the opened tab in your browser. -# avoid too long sequence. +account.hmcl.hint=You need to click "Login" and complete the process in the opened browser window. account.injector.add=New Auth Server -account.injector.empty=None (You can click on the plus button on the right to add one) +account.injector.empty=None (You can click "+" to add one) account.injector.http=Warning: This server uses the unsafe HTTP protocol, anyone between your connection will be able to see your credentials in cleartext. account.injector.link.homepage=Homepage account.injector.link.register=Register @@ -84,59 +83,62 @@ account.injector.server=Authentication Server account.injector.server_url=Server URL account.injector.server_name=Server Name account.login=Login -account.login.hint=We will not store your password. +account.login.hint=We never store your password. account.login.skip=Login offline account.login.retry=Retry account.login.refresh=Re-login -account.login.refresh.microsoft.hint=Because the account authorization is invalid, you need to re-add your Microsoft account +account.login.refresh.microsoft.hint=You need to re-add your Microsoft account due to the account authorization is invalid. account.logout=Logout account.register=Register account.manage=Account List -account.copy_uuid=Copy the UUID of the account. +account.copy_uuid=Copy UUID of the Account account.methods=Login Type account.methods.authlib_injector=authlib-injector -account.methods.microsoft=Microsoft Account -account.methods.microsoft.birth=How to update your account birthday +account.methods.microsoft=Microsoft +account.methods.microsoft.birth=How to Change Your Account Birthday account.methods.microsoft.close_page=Microsoft account authorization is now completed.\n\ -\n\ -There are some extra works for us, but you can safely close this tab for now. + \n\ + There are some extra works for us, but you can safely close this tab for now. account.methods.microsoft.deauthorize=Deauthorize -account.methods.microsoft.error.add_family=Since you are not yet 18 years old, an adult must add you to a family in order for you to play Minecraft. -account.methods.microsoft.error.add_family_probably=Please check if the age indicated in your account settings is at least 18 years old. If not and you believe this is an error, you can click on the above link to change it. +account.methods.microsoft.error.add_family=An adult must add you to a family in order for you to play Minecraft due to you are not yet 18 years old. +account.methods.microsoft.error.add_family_probably=Please check if the age indicated in your account settings is at least 18 years old. If not and you believe this is an error, you can click "How to Change Your Account Birthday" to learn how to change it. account.methods.microsoft.error.country_unavailable=Xbox Live is not available in your current country/region. -account.methods.microsoft.error.missing_xbox_account=Your Microsoft account does not have a linked Xbox account yet. Please create one before continuing. -account.methods.microsoft.error.no_character=Your account does not own the Minecraft Java Edition.\nThe game profile may not have been created,\nplease click the link above to create it. -account.methods.microsoft.error.unknown=Failed to log in, error: %d. +account.methods.microsoft.error.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=Your account does not own the Minecraft Java Edition.\nThe game profile may not have been created\nplease click "Create Profile / Edit Profile Name" to create it. +account.methods.microsoft.error.unknown=Failed to log in, error code: %d. +account.methods.microsoft.error.wrong_verify_method=Please log in using your password on the Microsoft account login page, and do not use a verification code to log in. account.methods.microsoft.logging_in=Logging in... -account.methods.microsoft.hint=Please click on the "login" button, and copy the code shown here later to finish the login process in the opened browser window.\n\ -\n\ -If the token used to log in to the Microsoft account is leaked, you can click on "Deauthorize" to deauthorize it.\n\ -If you encounter any problems, you can click the help button in the upper right corner for help. -account.methods.microsoft.manual=Your device code is %1$s, please click here to copy. After clicking on the "Login" button, you should finish the login process in the opened browser window. If it did not show, you can go to %2$s manually.\n\ -\n\ -If the token used to log in to the Microsoft account is leaked, you can click on "Deauthorize" to deauthorize it.\n\ -If you encounter any problems, you can click the help button in the upper right corner for help. +account.methods.microsoft.hint=Please click "Login" and copy the code displayed here to complete the login process in the browser window that opens.\n\ + \n\ + If the token used to log in to the Microsoft account is leaked, you can click "Deauthorize" to deauthorize it. +account.methods.microsoft.manual=Your device code is %1$s, please click here to copy.\n\ + \n\ + After clicking "Login", you should complete the login process in the opened browser window. If it did not show, you can navigate to %2$s manually.\n\ + \n\ + If the token used to log in to the Microsoft account is leaked, you can click "Deauthorize" to deauthorize it. account.methods.microsoft.makegameidsettings=Create Profile / Edit Profile Name account.methods.microsoft.profile=Account Profile account.methods.microsoft.purchase=Buy Minecraft account.methods.microsoft.snapshot=You are using an unofficial build of HMCL. Please download the official build for login. account.methods.microsoft.snapshot.website=Official Website account.methods.offline=Offline -account.methods.offline.name.special_characters=Recommended to use letters, numbers and underscores for naming -account.methods.offline.name.invalid=Under normal circumstances, the game username can only contain English characters, numbers and underscores, and the length cannot exceed 16 characters.\n\ - Some legitimate usernames: HuangYu, huang_Yu, Huang_Yu_123;\n\ - Some illegal usernames: Huang Yu, Huang-Yu_%%%, Huang_Yu_hello_world_hello_world.\n\ +account.methods.offline.name.special_characters=Recommended to use English letters, numbers and underscores +account.methods.offline.name.invalid=Generally, game usernames only allow English letters, numbers, and underscores, and cannot exceed 16 characters in length.\n\ + \n\ + \ · Some legitimate usernames: HuangYu, huang_Yu, Huang_Yu_123;\n\ + \ · Some illegal usernames: Huang Yu, Huang-Yu_%%%, Huang_Yu_hello_world_hello_world.\n\ + \n\ If you believe that there is a corresponding mod or plugin on the server side to remove this restriction, you can ignore this warning. account.methods.offline.uuid=UUID -account.methods.offline.uuid.hint=UUID is the unique identifier for the game character in Minecraft. The way that it is generated might vary between different game launchers. Changing it to the one generated by other launchers allows you to keep your items in your offline account inventory.\n\ -\n\ -This option is for advanced users only. We do not recommend modifying this option unless you know what you are doing. -account.methods.offline.uuid.malformed=Invalid Format +account.methods.offline.uuid.hint=UUID is a unique identifier for Minecraft players, and each launcher may generate UUIDs differently. Editing 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 editing this option unless you know what you are doing. +account.methods.offline.uuid.malformed=Invalid format. account.methods.forgot_password=Forgot Password account.missing=No Accounts account.missing.add=Click here to add one. -account.move_to_global=Convert to global account -account.move_to_portable=Convert to portable account +account.move_to_global=Convert to Global Account\nThe account information will be saved in a config file of the user directory. +account.move_to_portable=Convert to Portable Account\nThe account information will be saved in a config file in the same directory as HMCL. account.not_logged_in=Not Logged in account.password=Password account.portable=Portable Account @@ -149,22 +151,21 @@ account.skin.type.csl_api=Blessing Skin account.skin.type.csl_api.location=Address account.skin.type.csl_api.location.hint=CustomSkinAPI URL account.skin.type.little_skin=LittleSkin -account.skin.type.little_skin.hint=You need to create a player with the same name as your offline account on your skin provider website. Your skin will then be your uploaded skin to that site. +account.skin.type.little_skin.hint=You need to create a player with the same player name as your offline account on your skin provider site. Your skin will now be set to the skin assigned to your player on the skin provider site. account.skin.type.local_file=Local Skin File -account.skin.upload=Upload Skin -account.skin.upload.failed=Unable to upload skin -account.skin.invalid_skin=Invalid skin file +account.skin.upload=Upload/Edit Skin +account.skin.upload.failed=Unable to upload skin. +account.skin.invalid_skin=Invalid skin file. account.username=Username archive.author=Author(s) archive.date=Publish Date -archive.game_version=Game Version -archive.name=Filename +archive.file.name=File Name archive.version=Version assets.download=Downloading Assets -assets.download_all=Validating assets integrity -assets.index.malformed=Index files of downloaded assets were corrupted. You can try using 'Update Game Assets' in its game instance settings to fix this issue. +assets.download_all=Validating Assets Integrity +assets.index.malformed=Index files of downloaded assets were corrupted. You can resolve this problem by clicking "Manage → Update Game Assets" on the "Edit Instance" page. button.cancel=Cancel button.change_source=Change Download Source @@ -183,14 +184,16 @@ button.retry=Retry button.save=Save button.save_as=Save As button.select_all=Select All +button.view=View button.yes=Yes chat=Join Group Chat color.recent=Recommended color.custom=Custom Color + crash.NoClassDefFound=Please verify the integrity of this software, or try updating your Java. -crash.user_fault=The launcher crashed due to a corrupted Java or system environment. Please make sure your Java or operating system is installed properly. +crash.user_fault=The launcher crashed due to a corrupted Java or system environment. Please make sure your Java or OS is installed properly. curse.category.0=All @@ -211,6 +214,7 @@ curse.category.4482=Extra Large curse.category.4472=Tech curse.category.4473=Magic curse.category.5128=Vanilla+ +curse.category.7418=Horror # https://addons-ecs.forgesvc.net/api/v2/category/section/6 curse.category.5299=Education @@ -222,8 +226,8 @@ curse.category.6954=Integrated Dynamics curse.category.6484=Create curse.category.6821=Bug Fixes curse.category.6145=Skyblock -curse.category.5190=QoL -curse.category.5191=Utility & QoL +curse.category.5190=QOL +curse.category.5191=Utility & QOL curse.category.5192=FancyMenu curse.category.423=Map and Information curse.category.426=Addons @@ -316,280 +320,330 @@ curse.sort.popularity=Popularity curse.sort.total_downloads=Total Downloads download=Download -download.hint=Install games and modpacks or download mods, resource packs and worlds +download.hint=Install games and modpacks or download mods, resource packs and worlds. download.code.404=File not found on the remote server: %s download.content=Addons -download.curseforge.customization=Shaders, and game customization -download.curseforge.unavailable=HMCL nightly build does not support access to CurseForge, please use release version or beta version to download. -download.existing=The file cannot be saved because it already exists. You can use 'Save As' to save the file elsewhere. -download.external_link=Open Download Website -download.failed=Failed to download %1$s, response code: %2$d +download.curseforge.unavailable=HMCL Nightly channel build does not support access to CurseForge, please use Release or Beta channel builds to download. +download.existing=The file cannot be saved due to it already exists. You can click "Save As" to save the file elsewhere. +download.external_link=Visit Download Website +download.failed=Failed to download "%1$s", response code: %2$d. download.failed.empty=No versions are available, please click here to go back. -download.failed.no_code=Failed to download %s +download.failed.no_code=Failed to download "%s". download.failed.refresh=Unable to fetch version list. Please click here to retry. -download.game=Game +download.game=New Game download.provider.bmclapi=BMCLAPI (bangbang93, https://bmclapi2.bangbang93.com/) -download.provider.mojang=Mojang (OptiFine is provided by BMCLAPI) +download.provider.mojang=Official (OptiFine is provided by BMCLAPI) download.provider.official=From Official Sources download.provider.balanced=From Fastest Available download.provider.mirror=From Mirror download.java=Downloading Java +download.java.override=This Java version already exists, do you want to uninstall and reinstall it? download.javafx=Downloading dependencies for the launcher... download.javafx.notes=We are currently downloading dependencies for HMCL from the Internet.\n\ -\n\ -You can click on 'Change Download Source' to select the download source or\nclick on 'Cancel' to stop and exit.\n\ -Note: If your download speed is too slow, you can try switching to another mirror. -download.javafx.component=Downloading module %s + \n\ + You can click "Change Download Source" to choose download source or\nclick "Cancel" to stop and exit.\n\ + Note: If your download speed is too slow, you can try switching to another mirror. +download.javafx.component=Downloading module "%s" download.javafx.prepare=Preparing to download -exception.access_denied=HMCL is unable to access the file %s, it may be locked by another process.\n\ -\n\ -For Windows users, you can open up 'Resource Monitor' to check if another process is currently using it. If so, you can try again after closing that process.\n\ -If not, please check if your account has enough permissions to access it. -exception.artifact_malformed=Cannot verify the integrity of the downloaded files . -exception.ssl_handshake=Unable to establish SSL connection due to missing SSL certificates in current Java installation. You can try starting HMCL in another Java, then try again. -extension.bat=Windows Batch File(.bat) +exception.access_denied=HMCL is unable to access the file "%s", it may be locked by another process.\n\ + \n\ + For Windows users, you can open "Resource Monitor" to check if another process is currently using it. If so, you can try again after closing that process.\n\ + If not, please check if your account has enough permissions to access it. +exception.artifact_malformed=Cannot verify the integrity of the downloaded files. +exception.ssl_handshake=Unable to establish SSL connection due to missing SSL certificates in current Java installation. You can try opening HMCL in another Java, then try again. +extension.bat=Windows Batch File (.bat) extension.mod=Mod File extension.png=Image File -extension.ps1=Windows PowerShell Script(.ps1) -extension.sh=Shell Script(.sh) -fatal.fractureiser=Hello Minecraft! Launcher has detected that your computer has been infected with the Fraureiser virus, which poses a serious security issue.\n\ -Please use antivirus software to perform a full scan immediately, and then change the passwords of all accounts you have logged in on this computer. +extension.ps1=Windows PowerShell Script (.ps1) +extension.sh=Shell Script (.sh) fatal.javafx.incompatible=Missing JavaFX environment.\n\ -HMCL cannot automatically install JavaFX under Java versions below 11.\n\ -Please update your Java to version 11 or higher. + Hello Minecraft! Launcher cannot automatically install JavaFX on Java <11.\n\ + Please update your Java to version 11 or later. fatal.javafx.incomplete=The JavaFX environment is incomplete.\n\ -Please try replacing your Java or reinstalling OpenJFX. -fatal.javafx.missing=Missing JavaFX environment. Please launch Hello Minecraft! Launcher with a Java which includes OpenJFX. -fatal.config_change_owner_root=You are using the root account to start Hello Minecraft! Launcher, this may cause you to fail to start Hello Minecraft! Launcher with other account in the future.\n\ -Do you still want to continue? -fatal.config_in_temp_dir=You are start Hello Minecraft! Launcher in a temporary directory, your settings and game data may be lost.\n\ -It is recommended to move HMCL to another location and restart it.\n\ -Do you still want to continue? + Please try replacing your Java or reinstalling OpenJFX. +fatal.javafx.missing=Missing JavaFX environment. Please open Hello Minecraft! Launcher with a Java which includes OpenJFX. +fatal.config_change_owner_root=You are using the root account to open Hello Minecraft! Launcher, this may cause you to fail to open HMCL with other account in the future.\n\ + Do you still want to continue? +fatal.config_in_temp_dir=You are opening Hello Minecraft! Launcher in a temporary directory, your settings and game data may be lost.\n\ + It is recommended to move HMCL to another location and reopen it.\n\ + Do you still want to continue? fatal.config_loading_failure=Cannot load configuration files.\n\ -Please make sure that "Hello Minecraft! Launcher" has read and write access to "%s" and the files in it.\n\ -For macOS, try putting HMCL somewhere with permissions other than "Desktop", "Downloads" and "Documents" and try again. -fatal.config_loading_failure.unix=Hello Minecraft! Launcher could not load the profile because the profile was created by user %1$s.\n\ -Please start HMCL with root user (not recommended), or execute the following command in the terminal to change the ownership of the configuration file to the current user:\n%2$s -fatal.mac_app_translocation=Due to the security mechanism of macOS, Hello Minecraft! Launcher is quarantined by the system to the temporary folder.\n\ -Please move Hello Minecraft! Launcher to a different folder before attempting to start, otherwise your settings and game data may be lost after restarting.\n\ -Do you still want to continue? -fatal.migration_requires_manual_reboot=Hello Minecraft! Launcher has been upgraded. Please reopen the launcher. -fatal.apply_update_failure=We are sorry, but Hello Minecraft! Launcher is unable to update autoamtically.\n\ -\n\ -You can update manually by downloading a newer version of the launcher from %s.\n\ -If the problem persists, please consider reporting this to us. + Please make sure that Hello Minecraft! Launcher has read and write access to "%s" and the files in it.\n\ + For macOS, try putting HMCL somewhere with permissions other than "Desktop", "Downloads" and "Documents" and try again. +fatal.config_loading_failure.unix=Hello Minecraft! Launcher could not load the profile due to it was created by user %1$s.\n\ + Please open HMCL as root user (not recommended), or execute the following command in the terminal to change the ownership of the configuration file to the current user:\n%2$s +fatal.mac_app_translocation=Due to macOS security mechanisms, Hello Minecraft! Launcher is isolated to a temporary directory by the OS.\n\ + Please move HMCL to a different directory before attempting to open, otherwise your settings and game data may be lost after restarting.\n\ + Do you still want to continue? +fatal.migration_requires_manual_reboot=Hello Minecraft! Launcher has been upgraded. Please restart the launcher. +fatal.apply_update_failure=We are sorry, but Hello Minecraft! Launcher is unable to update automatically.\n\ + \n\ + You can update manually by downloading a newer launcher version from %s.\n\ + If the problem persists, please consider reporting this to us. fatal.apply_update_need_win7=Hello Minecraft! Launcher cannot automatically update on Windows XP/Vista.\n\ -\n\ -You can update manually by downloading a newer version of the launcher from %s. -fatal.samba=If you launched HMCL from a Samba network drive, some features might not be working. Please try updating your Java or copy and run the launcher in a local folder. -fatal.illegal_char=Your user path contains an illegal character '=', you will not be able to use authlib-injector or change the skin of your offline account. -fatal.unsupported_platform=Minecraft is not yet fully supported for your platform, so you may experience missing functionality,\nor even be unable to launch the game.\n\ - \n\ - If you can't start Minecraft 1.17 and above, you can try switchiong the Renderer to Software in instance settings to use CPU rendering for better compatibility. -# ' -fatal.unsupported_platform.loongarch=Hello Minecraft! Launcher has provided support for Loongson Platform.\n\ -If you encounter problems when playing game, you can click the help button in the upper right corner for help. -fatal.unsupported_platform.osx_arm64=Hello Minecraft! Launcher has provided support for Apple Silicon platform, using native ARM java to launch games to get a smoother game experience.\nIf you encounter problems in the game, starting the game with Java based on x86-64 architecture may have better compatibility. -fatal.unsupported_platform.windows_arm64=Hello Minecraft! Launcher has provided native support for the Windows on ARM platform. If you encounter problems when playing game, please try starting the game with Java based on x86 architecture.\n\nIf you are using the Qualcomm platform, you may need to install the OpenGL Compatibility Pack before playing games.\nClick the link to go to the Microsoft Store and install the compatibility pack. + \n\ + You can update manually by downloading a newer launcher version from %s. +fatal.samba=If you opened Hello Minecraft! Launcher from a Samba network drive, some features might not be working. Please try updating your Java or copy and open the launcher in a local 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 yet fully supported for your platform, so you may experience missing functionality, or even be unable to launch the game.\n\ + \n\ + If you cannot launch Minecraft 1.17 and later, you can try switching the "Renderer" to "Software" in "Global/Instance-specific Settings → Advanced Settings" to use CPU rendering for better compatibility. +fatal.unsupported_platform.loongarch=Hello Minecraft! Launcher has provided support for Loongson platform.\n\ + If you encounter problems when playing game, you can visit https://docs.hmcl.net/groups.html for help. +fatal.unsupported_platform.osx_arm64=Hello Minecraft! Launcher has provided support for Apple silicon platform, using native ARM java to launch games to get a smoother game experience.\n\ + If you encounter problems when playing game, launching the game with Java based on x86-64 architecture may have better compatibility. +fatal.unsupported_platform.windows_arm64=Hello Minecraft! Launcher has provided native support for the Windows on Arm platform. If you encounter problems when playing game, please try launching the game with Java based on x86 architecture.\n\ + \n\ + If you are using the Qualcomm platform, you may need to install the OpenGL Compatibility Pack before playing games.\n\ + Click the link to navigate to Microsoft Store and install the compatibility pack. feedback=Feedback -feedback.channel=Feedback channel +feedback.channel=Feedback Channel feedback.discord=Discord -feedback.discord.statement=Join our Discord community. -feedback.github=GitHub Issue -feedback.github.statement=Creating an issue on GitHub. -feedback.qq_group=HMCL User Group -feedback.qq_group.statement=Join HMCL user QQ group. +feedback.discord.statement=Welcome to join our Discord server. +feedback.github=GitHub Issues +feedback.github.statement=Submit an issue on GitHub. +feedback.qq_group=HMCL User QQ Group +feedback.qq_group.statement=Welcome to join our user QQ group. file=File folder.config=Configs -folder.game=Game Directory +folder.game=Working Directory folder.logs=Logs folder.mod=Mods folder.resourcepacks=Resource Packs -folder.shaderpacks=Shader packs +folder.shaderpacks=Shader Packs folder.saves=Saves folder.screenshots=Screenshots -game=Game -game.crash.feedback=Please do not share screenshots of this interface with others! If you ask for help from others, please click to export the game crash information in the lower left corner and send the exported file to others for analysis. +game=Games +game.crash.feedback=Please do not share screenshots of this interface with others! If you ask for help from others, please click to export the game crash information in the lower left corner and send the exported file to others for analysis. game.crash.info=Crash Info game.crash.reason=Crash Cause game.crash.reason.analyzing=Analyzing... -game.crash.reason.multiple=Multiple reasons detected:\n\n -game.crash.reason.block=The game crashed due to a block.\n\ -\n\ -You can try removing this block using MCEdit or delete that mod that added it.\n\ -\n\ -Block Type: %1$s\n\ -Location: %2$s -game.crash.reason.bootstrap_failed=The game crashed due to mod %1$s.\n\ -\n\ -You can try deleting or updating it. -game.crash.reason.config=The game crashed because a mod %1$s cannot parse its config file %2$s. -game.crash.reason.debug_crash=The game crashed because you manually triggered it.\n\ -\n\ -So, you probably know why. -game.crash.reason.duplicated_mod=The game cannot launch due to duplicate mods: %1$s.\n\ -\n\ -%2$s\n\ -\n\ -Each mod can only be installed once, please delete the duplicate mod and try again. -game.crash.reason.entity=The game crashed due to an entity.\n\ -\n\ -You can try removing this entity using MCEdit or delete that mod that added it.\n\ -\n\ -Block Type: %1$s\n\ -Location: %2$s -game.crash.reason.modmixin_failure=The current game cannot continue to run because some Mod injection failed.\nThis generally means that the Mod has a bug or is incompatible with the current environment.\nYou can check the log to find the error mod. -game.crash.reason.file_or_content_verification_failed=The current game has a problem because some files or content verification failed.\nPlease try deleting the instance (including Mod) and download it again, or try using a proxy when downloading again. -game.crash.reason.mod_repeat_installation=Because the current game has installed duplicate Mods, each Mod can only appear once. Please delete the duplicate Mods and then restart the game. -game.crash.reason.forge_error=Forge may have provided error information.\nYou can view the log and make corresponding processing according to the log information in the error report.\nIf you do not see the error message, you can view the error report to understand how the error occurred.\n%1$s -game.crash.reason.mod_resolution0=The current game cannot continue to run because of some Mod problems.\nYou can check the log to find the error mod(s). -game.crash.reason.mixin_apply_mod_failed=The current game cannot continue to run because Mixin failed to apply the %1$s mod.\nYou can try deleting or updating the mod to resolve the issue. -game.crash.reason.mod_profile_causes_game_crash=The current game cannot continue to run because of a problem with the Mod configuration file.\nYou can check the log to find the error mod(s) and its configuration file. -game.crash.reason.fabric_reports_an_error_and_gives_a_solution=Forge may have provided error information.\nYou can view the log and make corresponding processing according to the log information in the error report.\nIf you do not see the error message, you can view the error report to understand how the error occurred. -game.crash.reason.java_version_is_too_high=The current game crashed because the Java version is too high to continue running.\nPlease use a lower version of Java in the Java Path tab of global game settings or per-instance game settings, and then start the game.\nIf not, you can download it from java.com (Java8) or BellSoft Liberica Full JRE (Java17) and other distributions to download and install one (restart the launcher after installation). -game.crash.reason.need_jdk11=The current game cannot continue to run due to an inappropriate version of the Java virtual machine. \nYou need to download and install Java 11, and set Java to a version starting with 11 in the global (per-instance) game settings. -game.crash.reason.mod_name=The current game cannot continue to run because of Mod file name problems. \nMod file names should use only English letters (Aa~Zz), numbers (0~9), hyphens (-), underscores (_), and dots (.) in half width. \nPlease go to the mods folder and add all non-compliant Mod file names with one of the above compliant characters. -game.crash.reason.incomplete_forge_installation=The current game cannot continue due to an incomplete installation of Forge / NeoForge. \nPlease reinstall Forge / NeoForge in Instance Settings - Modloaders / OptiFine. -game.crash.reason.fabric_version_0_12=Fabric 0.12 or above are incompatible with currently installed mods. You need to downgrade it to 0.11.7. -game.crash.reason.fabric_warnings=The Fabric modloader warned:\n\ -%1$s -game.crash.reason.file_already_exists=The game crashed because file %1$s already exists.\n\ -\n\ -You can try backing up and delete that file, then relaunch the game. -game.crash.reason.file_changed=The game crashed because it did not pass the integrity checks.\n\ -\n\ -If you modified the Minecraft jar, you will need to rollback the changes, or redownload the game. -game.crash.reason.gl_operation_failure=The game crashed due to some mods, shaders, and resource packs.\n\ -\n\ -Please disable the mods/shaders/resource packs you are using and then try again. -game.crash.reason.graphics_driver=The game crashed due to an issue with your graphics driver.\n\ -\n\ -Please try again after updating your graphics driver to the latest version.\n\ -\n\ -If your computer has a discrete graphics card, you need to check whether the game uses integrated/core graphics. If so, please start the launcher using your discrete graphics card. If the problem persists, you probably should consider getting a new graphics card or a new computer.\n\ -\n\ -If you are using your integrated graphics card, please notice that Minecraft 1.16.5 or older requires Java 1.8.0_51 or older for Intel(R) Core(TM) 3000 processor series or earlier.\n\ -\n\ -Turning on the "Use OpenGL software renderer" option in the instance settings can also solve this problem, but when this option is turned on, the frame rate will be significantly reduced in the case of insufficient CPU performance. So it is only recommended to turn it on for debugging purposes or in case of emergency. -game.crash.reason.macos_failed_to_find_service_port_for_display=The current game cannot continue due to a failure to initialize the OpenGL window on the Apple silicon platform.\nFor this issue, HMCL does not have direct solutions at the moment. Please try opening any browser and going fullscreen, then return to HMCL, launch the game, and quickly return to the browser page before the game window pops up, wait for the game window to appear, and then switch back to the game window. -game.crash.reason.illegal_access_error=The game crashed because of some mod(s).\n\ -\n\ -If you know: %1$s, you can update or delete the mod(s) and then try again. -game.crash.reason.install_mixinbootstrap=The current game cannot continue to run due to missing MixinBootstrap.\nYou can try installing MixinBootstrap to solve the problem. If it crashes after installation, try adding an English "!" in front of the module's filename. in front of the file name of the module to try to solve the problem. -game.crash.reason.optifine_is_not_compatible_with_forge=The current game crashes because OptiFine is incompatible with the current version of Forge.\nPlease go toOn the official website of OptiFine, check whether the Forge version is compatible with OptiFine, and reinstall the game in strict accordance with the corresponding version or change the version in the instance settings - Modloaders / OptiFine.\nAfter testing, too high or too low a Forge version may cause a crash. -game.crash.reason.mod_files_are_decompressed=The current game cannot continue to run because the Mod file has been decompressed.\nPlease put the entire Mod file directly into the Mod folder!\nIf unzipping will cause errors in the game, please delete the unzipped Mod in the Mod folder, and then start the game. -game.crash.reason.shaders_mod=The current game cannot continue to run because both OptiFine and Shaders Mod are installed. \nBecause OptiFine has built-in support for shaders, just remove Shaders Mod. -game.crash.reason.rtss_forest_sodium=The current game crashed because RivaTuner Statistics Server (RTSS) is incompatible with Sodium.\nClick here for more details. -game.crash.reason.too_many_mods_lead_to_exceeding_the_id_limit=The current game cannot continue to run because you have installed too many Mods, which exceeds the ID limit of the game.\nPlease try installingJEID, or delete some large Mods. -game.crash.reason.night_config_fixes=The current game cannot continue to run due to some problems with Night Config. \nYou can try to install the Night Config Fixes mod, which may help you this problem. \nFor more information, visit the mod's GitHub repository. -game.crash.reason.optifine_causes_the_world_to_fail_to_load=The current game may not continue to run because of OptiFine.\nThis problem only occurs in a specific version of OptiFine. You can try changing the version of OptiFine in instance settings - Modloader / OptiFine. -game.crash.reason.jdk_9=The game cannot run because the Java version is too new for this instance.\n\ -\n\ -You need to download and install Java 8 and select it in the instance settings. -game.crash.reason.jvm_32bit=The game crashed because the current memory allocation exceeds the limit of the 32-bit Java VM.\n\ -\n\ -If your OS is 64-bit, please install and use a 64-bit version of Java. Otherwise, you may need to reinstall a 64-bit OS or get a moderner computer.\n\ -\n\ -Or, you can disable the "Automatically allocate" option and set the maximum memory allocation size to 1024MB or below. -game.crash.reason.loading_crashed_forge=The game crashed due to mod %1$s (%2$s).\n\ -\n\ -You can try deleting or updating it. -game.crash.reason.loading_crashed_fabric=The game crashed due to mod %1$s.\n\ -\n\ -You can try deleting or updating it. -game.crash.reason.mac_jdk_8u261=The game crashed because your current Forge or OptiFine version is not compatible with your Java installation.\n\ -\n\ -Please try updating Forge and OptiFine, or try using Java 8u251 or earlier versions. -game.crash.reason.forge_repeat_installation=The current game cannot continue to run due to a duplicate installation of Forge. This is a known issue\nIt is recommended to upload the log feedback to GitHub so that we can find more clues and fix this question. \nCurrently you can go to the automatic installation to uninstall Forge and reinstall it. -game.crash.reason.optifine_repeat_installation=The current game cannot continue to run due to repeated installation of Optifine. \nPlease delete Optifine under the Mod folder or go to Game Management-Automatic Installation to uninstall Optifine that is automatically installed. +game.crash.reason.multiple=Several reasons detected:\n\n +game.crash.reason.block=The game crashed due to a block in world.\n\ + \n\ + You can try removing this block using MCEdit or delete that mod that added it.\n\ + \n\ + Block Type: %1$s\n\ + Location: %2$s +game.crash.reason.bootstrap_failed=The game crashed due to mod "%1$s".\n\ + \n\ + You can try deleting or updating it. +game.crash.reason.config=The game crashed due to a mod "%1$s" cannot parse its config file "%2$s". +game.crash.reason.debug_crash=The game crashed due to you manually triggered it. So, you probably know why. +game.crash.reason.duplicated_mod=The game cannot continue to run due to duplicate mods "%1$s".\n\ + \n\ + %2$s\n\ + \n\ + Each mod can only be installed once, please delete the duplicate mod and try again. +game.crash.reason.entity=The game crashed due to an entity in world.\n\ + \n\ + You can try removing this entity using MCEdit or delete that mod that added it.\n\ + \n\ + Entity Type: %1$s\n\ + Location: %2$s +game.crash.reason.modmixin_failure=The game crashed due to some mod injection failed.\n\ + \n\ + This generally means that the mod has a bug or is incompatible with the current environment.\n\ + \n\ + You can check the log to find the error mod. +game.crash.reason.mod_repeat_installation=The game crashed due to duplicate mods.\n\ + \n\ + Each mod can only be installed once, please delete the duplicate mod and then relaunch the game. +game.crash.reason.forge_error=Forge/NeoForge may have provided error information.\n\ + \n\ + You can view the log and make corresponding processing according to the log information in the error report.\n\ + \n\ + If you do not see the error message, you can view the error report to understand how the error occurred.\n\ + %1$s +game.crash.reason.mod_resolution0=The game crashed due to some mod problems. You can check the log to find the error mod(s). +game.crash.reason.mixin_apply_mod_failed=The game crashed due to mixin failed to apply the "%1$s" mod.\n\ + \n\ + You can try deleting or updating the mod to resolve the problem. +game.crash.reason.java_version_is_too_high=The game crashed due to the Java version is too high to continue running.\n\ + \n\ + Please use an older Java version in "Global/Instance-specific Settings → Java", and then launch the game.\n\ + \n\ + If not, you can download it from java.com (Java 8) or BellSoft Liberica Full JRE (Java 17) and other distributions to download and install one (restart the launcher after installation). +game.crash.reason.need_jdk11=The game crashed due to an inappropriate Java VM version.\n\ + \n\ + You need to download and install Java 11, and set it in "Global/Instance-specific Settings → Java". +game.crash.reason.mod_name=The game crashed due to mod file name problems.\n\ + \n\ + Mod file names should use only English letters (Aa~Zz), numbers (0~9), hyphens (-), underscores (_), and dots (.) in half-width.\n\ + \n\ + Please navigate to the mod directory and add all non-compliant mod file names with one of the above compliant characters. +game.crash.reason.incomplete_forge_installation=The game cannot continue to run due to an incomplete installation of Forge/NeoForge.\n\ + \n\ + Please reinstall Forge/NeoForge in "Edit Instance → Loaders". +game.crash.reason.fabric_version_0_12=Fabric Loader 0.12 or later are incompatible with currently installed mods. You need to downgrade it to 0.11.7. +game.crash.reason.fabric_warnings=The Fabric warned:\n\ + \n\ + %1$s +game.crash.reason.file_already_exists=The game crashed due to file "%1$s" already exists.\n\ + \n\ + You can try backing up and delete that file, then relaunch the game. +game.crash.reason.file_changed=The game crashed due to file "%1$s" already exists.\n\ + \n\ + You can try backing up and delete that file, then relaunch the game. +game.crash.reason.gl_operation_failure=The game crashed due to some mods, shaders, or resource packs.\n\ + \n\ + Please disable the mods, shaders or resource packs you are using and then try again. +game.crash.reason.graphics_driver=The game crashed due to an problem with your graphics driver.\n\ + \n\ + Please try again after updating your graphics driver to the latest version.\n\ + \n\ + If your PC has a discrete graphics card, you need to check whether the game uses integrated/core graphics. If so, please open the launcher using your discrete graphics card. If the problem persists, you probably should consider using a new graphics card or a new PC.\n\ + \n\ + If you are using your integrated graphics card, please notice that Minecraft 1.16.5 or older requires Java 1.8.0_51 or older for Intel(R) Core(TM) 3000 processor series or earlier. +game.crash.reason.macos_failed_to_find_service_port_for_display=The current game cannot continue due to a failure to initialize the OpenGL window on the Apple silicon platform.\n\ + \n\ + For this problem, HMCL does not have direct solutions at the moment. Please try opening any browser and going fullscreen, then return to HMCL, launch the game, and quickly return to the browser page before the game window pops up, wait for the game window to appear, and then switch back to the game window. +game.crash.reason.illegal_access_error=The game crashed due to some mod(s).\n\ + \n\ + If you know this: "%1$s", you can update or delete the mod(s) and then try again. +game.crash.reason.install_mixinbootstrap=The game crashed due to missing MixinBootstrap.\n\ + \n\ + You can try installing MixinBootstrap to resolve the problem. If it crashes after installation, try adding an exclamation mark (!) in front of the file name of this mod. in front of the file name of the mod to try to resolve the problem. +game.crash.reason.optifine_is_not_compatible_with_forge=The game crashes due to OptiFine being incompatible with the currently installed Forge.\n\ + \n\ + Please navigate tothe official website of OptiFine, check whether the Forge version is compatible with OptiFine, and reinstall the instance in strict accordance with the corresponding version, or change the OptiFine version in "Edit Instance → Loaders".\n\ + \n\ + After testing, we believe that too high or too low OptiFine versions may cause crashes. +game.crash.reason.mod_files_are_decompressed=The game crashed due to the mod file has been extracted.\n\ + \n\ + Please put the entire mod file directly into the mod directory!\n\ + \n\ + If extract will cause errors in the game, please delete the extracted mod in the mod directory, and then launch the game. +game.crash.reason.shaders_mod=The game crashed due to both OptiFine and Shaders mod are installed.\n\ + \n\ + Just remove Shaders mod due to OptiFine has built-in support for shaders. +game.crash.reason.rtss_forest_sodium=The game crashed due to RivaTuner Statistics Server (RTSS) is incompatible with Sodium.\n\ + \n\ + Click here for more details. +game.crash.reason.too_many_mods_lead_to_exceeding_the_id_limit=The game crashed due to you have installed too many mods, which exceeds the ID limit of the game.\n\ + \n\ + Please try installingJEID, or delete some large mods. +game.crash.reason.night_config_fixes=The game crashed due to some problems with Night Config.\n\ + \n\ + You can try to install the Night Config Fixes mod, which may help you this problem.\n\ + \n\ + For more information, visit the GitHub repository of this mod. +game.crash.reason.optifine_causes_the_world_to_fail_to_load=The game may not continue to run due to OptiFine.\n\ + \n\ + This problem only occurs in a specific OptiFine version. You can try changing OptiFine version in "Edit Instance → Loaders". +game.crash.reason.jdk_9=The game crashed due to the Java version is too new for this instance.\n\ + \n\ + You need to download and install Java 8 and choose it in "Global/Instance-specific Settings → Java". +game.crash.reason.jvm_32bit=The game crashed due to the current memory allocation exceeds the limit of the 32-bit Java VM.\n\ + \n\ + If your OS is 64-bit, please install and use a 64-bit Java version. Otherwise, you may need to reinstall a 64-bit OS or get a moderner PC.\n\ + \n\ + Or, you can disable the "Automatically Allocate" option in "Global/Instance-specific Settings → Memory", and set the maximum memory allocation size to 1024 MB or below. +game.crash.reason.loading_crashed_forge=The game crashed due to mod "%1$s" (%2$s).\n\ + \n\ + You can try deleting or updating it. +game.crash.reason.loading_crashed_fabric=The game crashed due to mod "%1$s".\n\ + \n\ + You can try deleting or updating it. +game.crash.reason.mac_jdk_8u261=The game crashed due to your current Forge or OptiFine version is not compatible with your Java installation.\n\ + \n\ + Please try updating Forge and OptiFine, or try using Java 8u251 or earlier versions. +game.crash.reason.forge_repeat_installation=The game crashed due to a duplicate installation of Forge. This is a known problem\n\ + \n\ + It is recommended to upload the log feedback to GitHub so that we can find more clues and resolve this problem.\n\ + \n\ + Currently you can uninstall Forge and reinstall it in "Edit Instance → Loaders". +game.crash.reason.optifine_repeat_installation=The game crashed due to repeated installation of OptiFine.\n\ + \n\ + Please delete OptiFine in the mod directory or uninstall it in "Edit Instance → Loaders". game.crash.reason.memory_exceeded=The game crashed due to too much memory allocated for a small page file.\n\ -\n\ -You can try turning off the automatically allocate memory option in settings, and adjust the value till the game launches.\n\ -You can also try increasing the page file size in system settings. -game.crash.reason.mod=The game crashed due to the mod %1$s.\n\ -\n\ -You may update or delete the mod and then try again. + \n\ + You can try disable the "Automatically Allocate" option in "Global/Instance-specific Settings → Memory", and adjust the value till the game launches.\n\ + \n\ + You can also try increasing the page file size in system settings. +game.crash.reason.mod=The game crashed due to the mod "%1$s".\n\ + \n\ + You may update or delete the mod and then try again. game.crash.reason.mod_resolution=The game crashed due to mod resolution failure.\n\ -\n\ -Fabric provided the following details:\n\ -%1$s + \n\ + Fabric provided the following details:\n\ + \n\ + %1$s game.crash.reason.forgemod_resolution=The game crashed due to mod resolution failure.\n\ -\n\ -Forge provided the following details:\n\ -%1$s -game.crash.reason.forge_found_duplicate_mods=The game cannot continue due to a duplicate mods issue. Forge provides the following information: \n%1$s -game.crash.reason.mod_resolution_collection=The game crashed because the mod version is not compatible.\n\ -\n\ -%1$s requires %2$s.\n\ -\n\ -You need to upgrade or downgrade %3$s before continuing. -game.crash.reason.mod_resolution_conflict=The game crashed because of conflicting mods.\n\ -\n\ -%1$s is incompatible with %2$s. -game.crash.reason.mod_resolution_missing=The game crashed because some dependency mods are not installed.\n\ -\n\ -%1$s requires mod: %2$s.\n\ -\n\ -This means that you have to download and install %2$s first to continue playing. -game.crash.reason.mod_resolution_missing_minecraft=The game crashed because a mod is incompatible with the current Minecraft version.\n\ -\n\ -%1$s requires Minecraft version %2$s.\n\ -\n\ -If you want to play with this version of the mod installed, you should change the instance version.\n\ -Otherwise, you should install a version that is compatible with this Minecraft version. + \n\ + Forge/NeoForge provided the following details:\n\ + %1$s +game.crash.reason.forge_found_duplicate_mods=The game crashed due to a duplicate mods problem. Forge/NeoForge provides the following information:\n\ + %1$s +game.crash.reason.mod_resolution_collection=The game crashed due to the mod version is not compatible.\n\ + \n\ + "%1$s" requires "%2$s".\n\ + \n\ + You need to upgrade or downgrade %3$s before continuing. +game.crash.reason.mod_resolution_conflict=The game crashed due to conflicting mods.\n\ + \n\ + "%1$s" is incompatible with "%2$s". +game.crash.reason.mod_resolution_missing=The game crashed due to some dependency mods are not installed.\n\ + \n\ + "%1$s" requires mod "%2$s".\n\ + \n\ + This means that you have to download and install "%2$s" first to continue playing. +game.crash.reason.mod_resolution_missing_minecraft=The game crashed due to a mod is incompatible with the current Minecraft version.\n\ + \n\ + "%1$s" requires Minecraft version %2$s.\n\ + \n\ + If you want to play with this mod version installed, you should change the game version of your instance.\n\ + \n\ + Otherwise, you should install a version that is compatible with this Minecraft version. game.crash.reason.mod_resolution_mod_version=%1$s (Version: %2$s) game.crash.reason.mod_resolution_mod_version.any=%1$s (Any Version) -game.crash.reason.modlauncher_8=The game crashed because your current Forge version is not compatible with your Java installation, please try updating Forge. -game.crash.reason.no_class_def_found_error=The game cannot run because of incomplete code.\n\ -\n\ -Your game instance is missing %1$s, this might be due to a mod missing, an incompatible mod installed, or some files might be corrupted.\n\ -\n\ -You may need to reinstall the game and all mods or ask someone for help. -game.crash.reason.no_such_method_error=The game cannot run because of incomplete code.\n\ -\n\ -Your game instance might be missing a mod, installed an incompatible mod, or some files might be corrupted.\n\ -\n\ -You may need to reinstall the game and all mods or ask someone for help. -game.crash.reason.opengl_not_supported=The game crashed because OpenGL is not supported by your graphics driver.\n\ -\n\ -If you're streaming the game over the Internet or using a remote desktop environment, please play the game on your local one.\n\ -Or, you can try updating your driver to the latest version and then try again.\n\ -\n\ -If your computer has a discrete graphics card, please make sure the game is actually using it for rendering. If the problem persists, please consider getting a new graphics card or a new computer. -# ' -game.crash.reason.openj9=The game is unable to run on an OpenJ9 VM. Please switch to a Hotspot Java VM in the game settings and relaunch the game. If do not have one, you can download one online. -game.crash.reason.out_of_memory=The game crashed because it ran out of memory.\n\ -\n\ -Maybe because there is not enough memory available, or too many mods installed. You can try fixing it by increasing the allocated memory under the game settings.\n\ -\n\ -If you still encounter these problems, you may need a better computer. -game.crash.reason.resolution_too_high=The game crashed because you are using a resource pack whose texture resolution was too high.\n\ -\n\ -You should switch to a resource pack with lower resolution, or consider buying a better graphics card with more VRAM. -game.crash.reason.processing_of_javaagent_failed=The current game crashed because processing of -javaagent failed.\nIf you add relevant parameters to the Java virtual machine parameters, please check whether they are legal and correct.\nIf you do not add relevant parameters or confirm that they are legal and correct, Please try:\nOpen the control panel -- Clock and region classification (this option is only available if the option is category display, and it will be skipped if not) -- Region -- the upper management tab -- the lower change system regional setting button -- turn off the "Use Unicode UTF-8 to provide global language support" option in the pop-up window, restart the device, and then try to start the game.\nYou can be accessed in Discard or QQ ask for help. -game.crash.reason.stacktrace=The crash reason is unknown. You can view its details by clicking the "Logs" button.\n\ -\n\ -There are some keywords that might contain some Mod Names. You can search them online to figure out the issue yourself.\n\ -\n\ -%s -game.crash.reason.too_old_java=The game crashed because you are using a historical Java VM version.\n\ -\n\ -You need to switch to a newer version (%1$s) of Java in the game settings and then relaunch the game. You can download Java from here. +game.crash.reason.modlauncher_8=The game crashed due to your current Forge version is not compatible with your Java installation, please try updating Forge. +game.crash.reason.no_class_def_found_error=The game cannot continue to run due to incomplete code.\n\ + \n\ + Your game instance is missing %1$s, this might be due to a mod missing, an incompatible mod installed, or some files might be corrupted.\n\ + \n\ + You may need to reinstall the game and all mods or ask someone for help. +game.crash.reason.no_such_method_error=The game cannot continue to run due to incomplete code.\n\ + \n\ + Your game instance might be missing a mod, installed an incompatible mod, or some files might be corrupted.\n\ + \n\ + You may need to reinstall the game and all mods or ask someone for help. +game.crash.reason.opengl_not_supported=The game crashed due to OpenGL is not supported by your graphics driver.\n\ + \n\ + If you are streaming the game over the Internet or using a remote desktop environment, please play the game on your local one.\n\ + \n\ + Or, you can try updating your driver to the latest version and then try again.\n\ + \n\ + If your PC has a discrete graphics card, please make sure the game is actually using it for rendering. If the problem persists, please consider getting a new graphics card or a new PC. +game.crash.reason.openj9=The game is unable to run on an OpenJ9 VM. Please switch to a Java that uses the Hotspot VM in "Global/Instance-specific Settings → Java" and relaunch the game. If do not have one, you can download one online. +game.crash.reason.out_of_memory=The game crashed due to it ran out of memory.\n\ + \n\ + Maybe because there is not enough memory available, or too many mods installed. You can try resolving it by increasing the allocated memory in "Global/Instance-specific Settings → Memory".\n\ + \n\ + If you still encounter these problems, you may need a better PC. +game.crash.reason.resolution_too_high=The game crashed due to the resource pack resolution being too high.\n\ + \n\ + You should switch to a resource pack with lower resolution, or consider buying a better graphics card with more VRAM. +game.crash.reason.stacktrace=The crash reason is unknown. You can view its details by clicking "Logs".\n\ + \n\ + There are some keywords that might contain some mod names. You can search them online to figure out the problem yourself.\n\ + \n\ + %s +game.crash.reason.too_old_java=The game crashed due to you are using an outdated Java VM version.\n\ + \n\ + You need to switch to a newer version (%1$s) of Java in "Global/Instance-specific Settings → Java" and then relaunch the game. You can download Java from here. game.crash.reason.unknown=We are not able to figure out why the game crashed, please refer to the game logs. game.crash.reason.unsatisfied_link_error=Unable to launch Minecraft due to missing libraries: %1$s.\n\ -\n\ -If you have modified native library settings, please make sure these libraries do exist. Or, please try launching again after reverting it back to default.\n\ -If you did not, please check if you have missing dependency mods.\n\ -Otherwise, if you believe this is caused by HMCL, please feedback to us. -game.crash.reason.failed_to_load_a_library=Failed to load a library.\n\ -\n\ -If you have modified native library settings, please make sure these libraries do exist. Or, please try launching again after reverting it back to default.\n\ -If you did not, please check if you have missing dependency mods.\n\ -Otherwise, if you believe this is caused by HMCL, please feed back to us. + \n\ + If you have edited native library path, please make sure these libraries do exist. Or, please try launching again after reverting it to default.\n\ + \n\ + If you did not, please check if you have missing dependency mods.\n\ + \n\ + Otherwise, if you believe this is caused by HMCL, please feedback to us. game.crash.title=Game Crashed game.directory=Game Path game.version=Game Version @@ -610,83 +664,109 @@ install.failed=Installation Failed install.failed.downloading=We are unable to download some required files. install.failed.downloading.detail=Unable to download file: %s install.failed.downloading.timeout=Download timeout when fetching: %s -install.failed.install_online=Unable to identify the provided file. If you are installing a mod, go to the "Manage Mods" page. -install.failed.malformed=The downloaded files are corrupted. You can try fixing this issue by switching to another download source. -install.failed.optifine_conflict=Cannot install both Fabric, OptiFine, and Forge on Minecraft 1.13 or above. -install.failed.optifine_forge_1.17=For Minecraft version 1.17.1 or lower, Forge only supports OptiFine H1 Pre2 or newer. You can install them under the snapshot versions tab. -install.failed.version_mismatch=This library requires the game version %s, but the installed one is %s. -install.installer.change_version=Version %s is not compatible with the current game version. Click here to replace it with another version or delete it. +install.failed.install_online=Unable to identify the provided file. If you are installing a mod, navigate to the "Mods" page. +install.failed.malformed=The downloaded files are corrupted. You can try resolving this problem by switching to another download source in "Settings → Download → Download Source". +install.failed.optifine_conflict=Cannot install both OptiFine and Fabric on Minecraft 1.13 or later. +install.failed.optifine_forge_1.17=For Minecraft 1.17.1, Forge only compatible with OptiFine H1 pre2 or later. You can install them by checking "Snapshots" when choosing an OptiFine version in HMCL. +install.failed.version_mismatch=This loader requires the game version %s, but the installed one is %s. +install.installer.change_version=%s Incompatible install.installer.choose=Choose Your %s Version install.installer.depend=Requires %s install.installer.fabric=Fabric install.installer.fabric-api=Fabric API -install.installer.fabric-api.warning=Warning: Fabric API is a mod, and will be installed into the mods folder of the game instance. Please do not change the working directory of the game, or the Fabric API will not work. If you do want to change these settings, you should reinstall it. +install.installer.fabric-api.warning=Warning: Fabric API is a mod, and will be installed into the mod directory of the game instance. Please do not change the working directory of the game, or the Fabric API will not work. If you do want to change these settings, you should reinstall it. install.installer.forge=Forge install.installer.neoforge=NeoForge install.installer.game=Minecraft install.installer.incompatible=Incompatible with %s install.installer.install=Install %s -install.installer.install_offline=Install/Update from the Local File -install.installer.install_offline.extension=Forge/OptiFine installer -install.installer.install_offline.tooltip=We support using the local Forge/OptiFine installer. +install.installer.install_offline=Install/Update From Local File +install.installer.install_offline.extension=(Neo)Forge/OptiFine installer +install.installer.install_offline.tooltip=We support using the local (Neo)Forge/OptiFine installer. install.installer.install_online=Online Install -install.installer.install_online.tooltip=We currently support Fabric, Forge, OptiFine, and LiteLoader. +install.installer.install_online.tooltip=We currently support Forge, NeoForge, OptiFine, Fabric, Quilt and LiteLoader. install.installer.liteloader=LiteLoader -install.installer.not_installed=Not Selected +install.installer.not_installed=Do not install install.installer.optifine=OptiFine install.installer.quilt=Quilt install.installer.quilt-api=QSL/QFAPI install.installer.version=%s -install.installer.external_version=%s Installed by external process, which cannot be configured -install.modpack=Install a Modpack -install.new_game=Add a New Instance -install.new_game.already_exists=This instance already exists. Please use another name. +install.installer.external_version=%s (Installed by external process, which cannot be configured) +install.modpack=Install Modpack +install.new_game=Install Instance +install.new_game.already_exists=This instance name already exists. Please use another name. install.new_game.current_game_version=Current Instance Version -install.new_game.malformed=Invalid Name -install.select=Select an operation +install.new_game.malformed=Invalid name. +install.select=Choose operation install.success=Installed successfully. -lang=English (US) +java.add=Add Java +java.add.failed=This Java is invalid or incompatible with the current platform. +java.disable=Disable Java +java.disable.confirm=Are you sure you want to disable this Java? +java.disabled.management=Disabled Java +java.disabled.management.remove=Remove this Java from the list +java.disabled.management.restore=Re-enable this Java +java.download=Download Java +java.download.load_list.failed=Failed to load version list +java.download.more=More Java distributions +java.download.prompt=Please choose the Java version you want to download: +java.download.distribution=Distribution +java.download.version=Version +java.download.packageType=Package Type +java.management=Java Management +java.info.architecture=Architecture +java.info.vendor=Vendor +java.info.version=Version +java.info.disco.distribution=Distribution +java.install=Install Java +java.install.archive=Source Path +java.install.failed.exists=This name is already owned +java.install.failed.invalid=This archive is not a valid Java installation package, so it cannot be installed. +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.reveal=Reveal the Java directory +java.uninstall=Uninstall Java +java.uninstall.confirm=Are you sure you want to uninstall this Java? This action cannot be undone! + +lang=English lang.default=Use System Locales launch.advice=%s Do you still want to continue to launch? launch.advice.multi=The following problems were detected:\n\n%s\n\nThese problems may cause unable to launch the game or affect the game experience.\nDo you still want to continue to launch? -launch.advice.java.auto=The current Java Virtual Machine version does not compatible with the instance.\n\ -\n\ -Click on 'Yes' to automatically choose the most compatible Java VM version. Or, you can go to the instance settings to select one yourself. +launch.advice.java.auto=The current Java VM version does not compatible with the instance.\n\nClick "Yes" to automatically choose the most compatible Java VM version. Or, you can navigate to "Global/Instance-specific Settings → Java" to choose one yourself. launch.advice.java.modded_java_7=Minecraft 1.7.2 and older versions require Java 7 or earlier. -launch.advice.corrected=We have fixed the Java VM issue. If you still want to use your choice of Java version, you can disable the compatibility checks under launcher game settings. -launch.advice.uncorrected=If you still want to use your choice of Java version, you can disable the compatibility checks under launcher game settings. -launch.advice.different_platform=The 64-bit version of Java is recommended for your device, but you have installed a 32-bit one. +launch.advice.corrected=We have resolved the Java VM problem. If you still want to use your choice of Java version, you can disable the "Do not check Java VM compatibility" in "Global/Instance-specific Settings → Advanced Settings". +launch.advice.uncorrected=If you still want to use your choice of Java version, you can disable the "Do not check Java VM compatibility" in "Global/Instance-specific Settings → Advanced Settings". +launch.advice.different_platform=The 64-bit Java version is recommended for your device, but you have installed a 32-bit one. launch.advice.forge2760_liteloader=Forge version 2760 is not compatible with LiteLoader, please consider upgrading Forge to version 2773 or later. launch.advice.forge28_2_2_optifine=Forge version 28.2.2 or later is not compatible with OptiFine. Please consider downgrading Forge to version 28.2.1 or earlier. -launch.advice.forge37_0_60=Forge versions earlier than 37.0.60 are not compatible with Java 17. Please update Forge to 37.0.60 or higher, or launch the game with Java 16. -launch.advice.java8_1_13=Minecraft 1.13 and later can only be run on Java 8 or later. Please use Java 8 or newer versions. -launch.advice.java8_51_1_13=Minecraft 1.13 may crash on Java 8 versions earlier than 1.8.0_51. Please install the latest version of Java 8. -launch.advice.java9=You cannot launch Minecraft 1.12 or earlier with Java 9 or newer, please use Java 8 instead. -launch.advice.modded_java=Some Mods may not be compatible with higher versions of Java. It is recommended to use Java %s to start Minecraft %s. -launch.advice.modlauncher8=The Forge version you are using is not compatible with the current Java version. Please try updating Forge, or launch the game with Java 8u312/11.0.13/17.0.1 or earlier. -launch.advice.newer_java=You are using the old Java to start the game. It is recommended to update to Java 8, otherwise some mods may cause the game to crash. -launch.advice.not_enough_space=You have allocated a memory size larger than the actual %d MB of memory installed on your computer. You may experience degraded performance, or even be unable to launch the game. -launch.advice.require_newer_java_version=Minecraft %1$s requires Java %2$s or later, but we could not find one. Do you want to download one now? +launch.advice.forge37_0_60=Forge versions earlier than 37.0.60 are not compatible with Java 17. Please update Forge to 37.0.60 or later, or launch the game with Java 16. +launch.advice.java8_1_13=Minecraft 1.13 and later can only be run on Java 8 or later. Please use Java 8 or later versions. +launch.advice.java8_51_1_13=Minecraft 1.13 may crash on Java 8 versions earlier than 1.8.0_51. Please install the latest Java 8 version. +launch.advice.java9=You cannot launch Minecraft 1.12 or earlier with Java 9 or later, please use Java 8 instead. +launch.advice.modded_java=Some mods may not be compatible with newer versions of Java. 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 the old Java 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 PC. You may experience degraded performance, or even be unable to launch the game. +launch.advice.require_newer_java_version=Current game version requires Java %s, but we could not find one. Do you want to download one now? launch.advice.too_large_memory_for_32bit=You have allocated a memory size larger than the memory limitation of the 32-bit Java installation. You may be unable to launch the game. -launch.advice.vanilla_linux_java_8=Minecraft 1.12.2 or below only supports Java 8 for the Linux x86-64 platform, because later versions cannot load 32-bit native libraries like liblwjgl.so\n\ -\n\ -Please download it from java.com, or install OpenJDK 8. -launch.advice.vanilla_x86.translation=Minecraft is not fully supported for your platform, so you may experience missing functionality, or even be unable to launch the game.\nYou can play through the Rosetta translation environment for a full gaming experience. +launch.advice.vanilla_linux_java_8=Minecraft 1.12.2 or before only supports Java 8 for the Linux x86-64 platform, due to the later versions cannot load 32-bit native libraries like liblwjgl.so\n\nPlease download it from java.com, or install OpenJDK 8. +launch.advice.vanilla_x86.translation=Minecraft is not fully supported for your platform, so you may experience missing functionality, or even be unable to launch the game.\nYou can download Java for the x86-64 architecture here for a full gaming experience. +launch.advice.unknown=The game cannot be launched due to the following reasons: launch.failed=Failed to launch -launch.failed.cannot_create_jvm=We are unable to create a Java virtual machine. It may be caused by incorrect Java VM arguments. You can try fixing it by removing all arguments you added under instance settings. -launch.failed.creating_process=We are unable to create a new process, please check your Java path. +launch.failed.cannot_create_jvm=We are unable to create a Java VM. It may be caused by incorrect Java VM arguments. You can try resolving it by removing all arguments you added in "Global/Instance-specific Settings → Advanced Settings → Java VM Options". +launch.failed.creating_process=We are unable to create a new process, please check your Java path.\n launch.failed.command_too_long=The command length exceeds the maximum length of a bat script. Please try exporting it as a PowerShell script. -launch.failed.decompressing_natives=Unable to unzip native libraries. -launch.failed.download_library=Unable to download libraries %s. +launch.failed.decompressing_natives=Unable to extract native libraries.\n +launch.failed.download_library=Unable to download libraries "%s". launch.failed.executable_permission=Unable to make the launch script executable. launch.failed.execution_policy=Set Execution Policy launch.failed.execution_policy.failed_to_set=Unable to set execution policy -launch.failed.execution_policy.hint=The current execution policy prevents the execution of PowerShell scripts.\n\ -\n\ -Click on 'OK' to allow the current user to execute PowerShell scripts, or click on 'Cancel' to keep it as it is. +launch.failed.execution_policy.hint=The current execution policy prevents the execution of PowerShell scripts.\n\nClick "OK" to allow the current user to execute PowerShell scripts, or click "Cancel" to keep it as it is. launch.failed.exited_abnormally=Game crashed. Please refer to the crash log for more details. -launch.failed.no_accepted_java=Unable to find a compatible Java version, do you want to start the game with the default Java?\nClick on 'Yes' to start the game with the default Java.\nOr, you can go to the instance settings to select one yourself. +launch.failed.java_version_too_low=The Java version you specified is too low, please reset the Java version. +launch.failed.no_accepted_java=Unable to find a compatible Java version, do you want to launch the game with the default Java?\nClick "Yes" to launch the game with the default Java.\nOr, you can navigate to "Global/Instance-specific Settings → Java" to choose one yourself. launch.failed.sigkill=Game was forcibly terminated by the user or system. launch.state.dependencies=Resolving dependencies launch.state.done=Launched @@ -694,7 +774,7 @@ launch.state.java=Checking Java version launch.state.logging_in=Logging in launch.state.modpack=Downloading required files launch.state.waiting_launching=Waiting for the game to launch -launch.wrong_javadir=Invalid Java path, falling back to the default one. +launch.invalid_java=Invalid Java path, please reset the Java path. launcher=Launcher launcher.agreement=ToS and EULA @@ -702,20 +782,20 @@ launcher.agreement.accept=Accept launcher.agreement.decline=Decline launcher.agreement.hint=You must agree to the EULA to use this software. launcher.background=Background Image -launcher.background.choose=Choose a Background Image +launcher.background.choose=Choose background image launcher.background.classic=Classic -launcher.background.default=Default (or background.png/jpg/gif, or images under bg folder) +launcher.background.default=Default (Or "background.png/.jpg/.gif" and the images in the "bg" directory) launcher.background.network=From URL launcher.background.translucent=Translucent launcher.cache_directory=Cache Directory launcher.cache_directory.clean=Clear Cache -launcher.cache_directory.choose=Choose the cache directory -launcher.cache_directory.default=Default (%AppData%/.minecraft or ~/.minecraft) +launcher.cache_directory.choose=Choose cache directory +launcher.cache_directory.default=Default ("%APPDATA%/.minecraft" or "~/.minecraft") launcher.cache_directory.disabled=Disabled launcher.cache_directory.invalid=Unable to create cache directory, falling back to default. launcher.contact=Contact Us -launcher.crash=Hello Minecraft! Launcher has encountered a fatal error! Please copy the following log and ask for help on our Discord community, GitHub or Minecraft Forum. -launcher.crash.java_internal_error=Hello Minecraft! Launcher has encountered a fatal error because your Java is broken. Please uninstall your Java, and download a suitable Java here. +launcher.crash=Hello Minecraft! Launcher has encountered a fatal error! Please copy the following log and ask for help on our Discord, QQ group, GitHub or other Minecraft Forum. +launcher.crash.java_internal_error=Hello Minecraft! Launcher has encountered a fatal error due to your Java is broken. Please uninstall your Java, and download a suitable Java here. launcher.crash.hmcl_out_dated=Hello Minecraft! Launcher has encountered a fatal error! Your launcher is outdated. Please update your launcher! launcher.update_java=Please update your Java version. @@ -725,13 +805,11 @@ login.enter_password=Please enter your password. logwindow.show_lines=Show Row Number logwindow.terminate_game=Kill Game Process logwindow.title=Log -logwindow.help=You can go to the HMCL community and find others for help +logwindow.help=You can navigate to the HMCL community and find others for help. logwindow.autoscroll=Auto-scroll logwindow.export_game_crash_logs=Export Crash Logs -logwindow.export_dump.dependency_ok.button=Export Game Stack Dump -logwindow.export_dump.dependency_ok.doing_button=Exporting Game Stack Dump (May take up to 15 seconds) -logwindow.export_dump.no_dependency.button=Export Game Stack Dump (Not compatible) -logwindow.export_dump.no_dependency.tooltip=Your Java does not contain the dependencies to create the stack dump. Please turn to HMCL QQ group or HMCL Discord for help. +logwindow.export_dump=Export Game Stack Dump +logwindow.export_dump.no_dependency=Your Java does not contain the dependencies to create the stack dump. Please join our Discord or QQ group for help. main_page=Home @@ -748,30 +826,30 @@ message.success=Operation completed successfully message.unknown=Unknown message.warning=Warning -modpack=Modpack -modpack.choose=Select a modpack -modpack.choose.local=Import from local file -modpack.choose.local.detail=You can drag the modpack file here -modpack.choose.remote=Download from URL -modpack.choose.remote.detail=A direct download link to the remote modpack file is required -modpack.choose.repository=Download a modpack from Curseforge or Modrinth -modpack.choose.repository.detail=Remember to go back to this page and drop the modpack file here after the modpack is downloaded +modpack=Modpacks +modpack.choose=Choose Modpack +modpack.choose.local=Import From Local File +modpack.choose.local.detail=You can drag the modpack file here. +modpack.choose.remote=Download From URL +modpack.choose.remote.detail=A direct download link to the remote modpack file is required. +modpack.choose.repository=Download Modpack From CurseForge or Modrinth +modpack.choose.repository.detail=Remember to go back to this page and drop the modpack file here after the modpack is downloaded. modpack.choose.remote.tooltip=Please enter your modpack URL modpack.completion=Downloading dependencies modpack.desc=Describe your modpack, including an introduction and probably some changelog. Markdown and images from URL are currently supported. modpack.description=Modpack Description modpack.download=Download Modpacks modpack.enter_name=Enter a name for this modpack. -modpack.export=Export the Modpack +modpack.export=Export as Modpack modpack.export.as=Export Modpack As... modpack.file_api=Modpack URL Prefix modpack.files.blueprints=BuildCraft Blueprints modpack.files.config=Mod Configs modpack.files.dumps=NEI Debug Output File modpack.files.hmclversion_cfg=Launcher Configuration File -modpack.files.liteconfig=Mod Configuration File +modpack.files.liteconfig=LiteLoader Related Files modpack.files.mods=Mods -modpack.files.mods.voxelmods=VoxelMods options +modpack.files.mods.voxelmods=VoxelMods Options modpack.files.options_txt=Minecraft Options File modpack.files.optionsshaders_txt=Shaders Settings File modpack.files.resourcepacks=Resource/Texture Packs @@ -780,11 +858,11 @@ modpack.files.scripts=MineTweaker Configuration File modpack.files.servers_dat=Server List File modpack.install=Install Modpack %s modpack.installing=Installing Modpack -modpack.introduction=CurseForge, Modrinth, MultiMC, and MCBBS modpacks are currently supported. +modpack.introduction=Curse, Modrinth, MultiMC, and MCBBS modpacks are currently supported. modpack.invalid=Invalid modpack, you can try downloading it again. modpack.mismatched_type=Modpack type mismatched, the current instance is a(an) %s type, but the provided one is %s type. modpack.name=Modpack Name -modpack.not_a_valid_name=Invalid Modpack Name +modpack.not_a_valid_name=Invalid modpack name. modpack.origin=Source modpack.origin.url=Official Website modpack.origin.mcbbs=MCBBS @@ -792,37 +870,34 @@ modpack.origin.mcbbs.prompt=Post ID modpack.scan=Parsing Modpack Index modpack.task.install=Import Modpack modpack.task.install.error=Unable to identify this modpack. We currently only support Curse, Modrinth, MultiMC, and MCBBS modpacks. -modpack.task.install.will=The location of the modpack you are going to install: modpack.type.curse=Curse -modpack.type.curse.tolerable_error=Unable to download dependencies, you can try continuing to download by launching this game instance. -modpack.type.curse.error=Unable to download dependencies, please try again or use a proxy connection. -modpack.type.curse.not_found=Some dependencies are no longer available, please try installing a newer version of the modpack. -modpack.type.manual.warning=The modpack is manually packaged by the publisher, which may already contain a launcher. It is recommended to try unzipping the modpack and running the game with its own launcher. HMCL can still import it, with no guarantee of its usability, still continue? -modpack.type.mcbbs=MCBBS Type +modpack.type.curse.error=Unable to download dependencies, please try again or use a proxy server. +modpack.type.curse.not_found=Some dependencies are no longer available, please try installing a newer modpack version. +modpack.type.manual.warning=The modpack is manually packaged by the publisher, which may already contain a launcher. It is recommended to try extracting the modpack and running the game with its own launcher. HMCL can still import it, with no guarantee of its usability, still continue? +modpack.type.mcbbs=MCBBS modpack.type.mcbbs.export=Can be imported by Hello Minecraft! Launcher modpack.type.modrinth=Modrinth modpack.type.multimc=MultiMC modpack.type.multimc.export=Can be imported by Hello Minecraft! Launcher and MultiMC -modpack.type.server=Auto-Update Modpack from Server +modpack.type.server=Auto-Update Modpack From Server modpack.type.server.export=Allows server owner to update the game instance remotely -modpack.type.server.malformed=Invalid modpack manifest, please refer to the modpack maker to fix this issue. +modpack.type.server.malformed=Invalid modpack manifest, please refer to the modpack maker to resolving this problem. modpack.unsupported=Unsupported modpack format modpack.update=Updating modpack modpack.wizard=Modpack Export Guide modpack.wizard.step.1=Basic Settings modpack.wizard.step.1.title=Some basic informations for the modpack. -modpack.wizard.step.2=Select Files -modpack.wizard.step.2.title=Select files you wanted to add to the modpack. +modpack.wizard.step.2=Choose Files +modpack.wizard.step.2.title=Choose files you wanted to add to the modpack. modpack.wizard.step.3=Modpack Type -modpack.wizard.step.3.title=Choose the modpack type you wanted to export as. +modpack.wizard.step.3.title=Choose modpack type you wanted to export as. modpack.wizard.step.initialization.exported_version=Game version to export -modpack.wizard.step.initialization.force_update=Force updating the modpack to the latest version (you'll need a file-hosting server) -# ' +modpack.wizard.step.initialization.force_update=Force updating the modpack to the latest version (you will need a file-hosting server) modpack.wizard.step.initialization.include_launcher=Include the launcher modpack.wizard.step.initialization.save=Export to... -modpack.wizard.step.initialization.warning=Before making a modpack, please make sure the game launches normally, and Minecraft is a release version instead of a snapshot version. The launcher will save your download settings.\n\ -\n\ -Keep in mind that you are not allowed to add mods and resource packs that are explicitly said not to be distributed or put in a modpack. +modpack.wizard.step.initialization.warning=Before making a modpack, please make sure the game launches normally, and Minecraft is a release version instead of a snapshot. The launcher will save your download settings.\n\ + \n\ + Keep in mind that you are not allowed to add mods and resource packs that are explicitly said not to be distributed or put in a modpack. modpack.wizard.step.initialization.server=Click here for more tutorials for making a server modpack that can be automatically updated. modrinth.category.adventure=Adventure @@ -895,12 +970,15 @@ modrinth.category.256x=256x modrinth.category.512x+=512x+ mods=Mods -mods.add=Add Mods +mods.add=Add Mod mods.add.failed=Failed to add mod %s. mods.add.success=%s was added successfully. mods.broken_dependency.title=Broken dependency -mods.broken_dependency.desc=This dependency existed before. However, it doesn't exist now. Try using another download source. +mods.broken_dependency.desc=This dependency existed before, but it does not exist now. Try using another download source. mods.category=Category +mods.channel.alpha=Alpha +mods.channel.beta=Beta +mods.channel.release=Release mods.check_updates=Check for Updates mods.check_updates.current_version=Current Version mods.check_updates.empty=All mods are up-to-date @@ -909,31 +987,33 @@ mods.check_updates.file=File mods.check_updates.source=Source mods.check_updates.target_version=Target Version mods.check_updates.update=Update -mods.choose_mod=Choose a mod +mods.choose_mod=Choose mod mods.curseforge=CurseForge -mods.dependency.embedded=built-in pre-mod (already packaged in the mod file by the author, no need to download separately) -mods.dependency.optional=optional pre-mod (if the game is missing, but mod functionality may be missing) -mods.dependency.required=required pre-mod (must be downloaded separately, missing may cause the game to fail to launch) -mods.dependency.tool=precursor library (must be downloaded separately, missing may cause the game to fail to launch) -mods.dependency.include=built-in pre-mod (already packaged in the mod file by the author, no need to download separately) -mods.dependency.incompatible=incompatible mod (installing both the mod and the mod being downloaded will cause the game to fail to launch) -mods.dependency.broken=Broken pre-mod (This premod used to exist on the mod repository, but is now deleted.) Try a different download source. +mods.dependency.embedded=Built-in Pre-mod (Already packaged in the mod file by the author, no need to download separately) +mods.dependency.optional=Optional Pre-mod (If missing, the game will run normally, but the mod features may be missing) +mods.dependency.required=Required Pre-mod (Must be downloaded separately, missing may cause the game to fail to launch) +mods.dependency.tool=Required Library (Must be downloaded separately, missing may cause the game to fail to launch) +mods.dependency.include=Built-in Pre-mod (Already packaged in the mod file by the author, no need to download separately) +mods.dependency.incompatible=Incompatible Mod (Installing these mods at the same time will cause the game to fail to launch) +mods.dependency.broken=Broken Pre-mod (This mod existed before, but it does not exist now, try using another download source.) mods.disable=Disable mods.download=Mod Download mods.download.title=Mod Download - %1s mods.download.recommend=Recommended Mod Version - Minecraft %1s mods.enable=Enable -mods.manage=Manage Mods +mods.manage=Mods mods.mcbbs=MCBBS -mods.mcmod=MCMOD -mods.mcmod.page=MCMOD Page -mods.mcmod.search=Search in MCMOD +mods.mcmod=MCMod +mods.mcmod.page=MCMod Page +mods.mcmod.search=Search in MCMod mods.modrinth=Modrinth mods.name=Name -mods.not_modded=You must install a mod loader (Fabric, Forge, Quilt or LiteLoader) first to manage your mods! -mods.restore=Rollback +mods.not_modded=You must install a mod loader (Forge, NeoForge, Fabric, Quilt or LiteLoader) first to manage your mods! +mods.restore=Restore mods.url=Official Page -mods.update_modpack_mod.warning=Updating mods in a modpack can lead to irreparable results, possibly corrupting the modpack so that it cannot start. Are you sure you want to update? +mods.update_modpack_mod.warning=Updating mods in a modpack can lead to irreparable results, possibly corrupting the modpack so that it cannot launch. Are you sure you want to update? +mods.install=Install +mods.save_as=Save As nbt.entries=%s entries nbt.open.failed=Fail to open file @@ -941,28 +1021,32 @@ nbt.save.failed=Fail to save file nbt.title=View File - %s datapack=Datapacks -datapack.add=Install datapack -datapack.choose_datapack=Select a datapack to import +datapack.add=Install Datapack +datapack.choose_datapack=Choose datapack to import datapack.extension=Datapack -datapack.title=World %s - Datapacks +datapack.title=World [%s] - Datapacks + +web.failed=Page loading failed +web.open_in_browser=Do you want to open this address in a browser:\n%s +web.view_in_browser=View in browser world=Worlds -world.add=Add a World (.zip) +world.add=Add World world.datapack=Manage Datapacks world.datapack.1_13=Only Minecraft 1.13 or later supports datapacks. -world.description=%s. Last played on %s. Game Version: %s. -world.download=Download a World +world.description=%s | Last played on %s | Game Version: %s +world.download=Download World world.export=Export the World -world.export.title=Select a directory for this exported world +world.export.title=Choose directory for this exported world world.export.location=Save As -world.export.wizard=Export World %s +world.export.wizard=Export World "%s" world.extension=World Archive world.game_version=Game Version world.import.already_exists=This world already exists. -world.import.choose=Select the save archive you want to import +world.import.choose=Choose world archive you want to import world.import.failed=Failed to import this world: %s -world.import.invalid=Unable to parse the save. -world.info.title=World %s - Information +world.import.invalid=Unable to import the save. +world.info.title=World [%s] - Information world.info.basic=Basic Information world.info.allow_cheats=Allow Cheats world.info.dimension.the_nether=The Nether @@ -972,6 +1056,7 @@ world.info.difficulty.peaceful=Peaceful world.info.difficulty.easy=Easy world.info.difficulty.normal=Normal world.info.difficulty.hard=Hard +world.info.failed=Failed to read the world info world.info.game_version=Game Version world.info.last_played=Last Played world.info.generate_features=Generate Structures @@ -990,41 +1075,41 @@ world.info.player.xp_level=Experience Level world.info.random_seed=Seed world.info.time=Game Time world.info.time.format=%s days -world.manage=Worlds / Datapacks +world.manage=Worlds world.name=World Name world.name.enter=Enter the world name world.reveal=Reveal in Explorer world.show_all=Show All -world.time=EEE, MMM d, yyyy HH\:mm\:ss +world.time=h\:mm\:ss a, EEE, MMM d yyyy profile=Game Directories profile.already_exists=This name already exists, please use a different name. -profile.default=Current Directory -profile.home=Vanilla Launcher Directory -profile.instance_directory=Instance Directory -profile.instance_directory.choose=Select an instance directory +profile.default=Current +profile.home=Minecraft Launcher +profile.instance_directory=Game Directory +profile.instance_directory.choose=Choose game directory profile.manage=Instance Directory List profile.name=Name profile.new=New Directory profile.title=Game Directories profile.selected=Selected -profile.use_relative_path=Use relative path for game path if possible +profile.use_relative_path=Use relative path for game directory if possible repositories.custom=Custom Maven Repository (%s) repositories.maven_central=Universal (Maven Central) repositories.tencentcloud_mirror=Mainland China Mirror (Tencent Cloud Maven Repository) repositories.chooser=HMCL requires JavaFX to work.\n\ -\n\ -Please click on 'OK' to download JavaFX from the specified repository, or click on 'Cancel' to exit.\n\ -\n\ -Repositories: -repositories.chooser.title=Select a download source to download JavaFX from + \n\ + Please click "OK" to download JavaFX from the specified repository, or click "Cancel" to exit.\n\ + \n\ + Repositories: +repositories.chooser.title=Choose download source for JavaFX resourcepack=Resource Packs search=Search -search.hint.chinese=Search queries support both Chinese and English -search.hint.english=Only English is supported +search.hint.chinese=Supports Chinese and English +search.hint.english=Supports English only search.enter=Enter text here search.sort=Sort By search.first_page=First @@ -1034,56 +1119,56 @@ search.last_page=Last search.page_n=%d / %s selector.choose=Choose -selector.choose_file=Select a file +selector.choose_file=Choose file selector.custom=Custom settings=Settings settings.advanced=Advanced Settings -settings.advanced.modify=Modify Advanced Settings +settings.advanced.modify=Edit Advanced Settings settings.advanced.title=Advanced Settings - %s settings.advanced.custom_commands=Custom Commands settings.advanced.custom_commands.hint=The following environment variables are provided:\n\ - - $INST_NAME: version name\n\ - - $INST_ID: version id\n\ - - $INST_DIR: absolute path of the version\n\ - - $INST_MC_DIR: absolute path of minecraft\n\ - - $INST_JAVA: java binary used for launch\n\ - - $INST_FORGE: set if Forge installed\n\ - - $INST_NEOFORGE: set if NeoForge installed\n\ - - $INST_LITELOADER: set if LiteLoader installed\n\ - - $INST_OPTIFINE: set if OptiFine installed\n\ - - $INST_FABRIC: set if Fabric installed\n\ - - $INST_QUILT: set if Quilt installed + \ · $INST_NAME: instance name.\n\ + \ · $INST_ID: instance name.\n\ + \ · $INST_DIR: absolute path of the instance working directory.\n\ + \ · $INST_MC_DIR: absolute path of the game directory.\n\ + \ · $INST_JAVA: java binary used for launch.\n\ + \ · $INST_FORGE: set if Forge installed.\n\ + \ · $INST_NEOFORGE: set if NeoForge installed.\n\ + \ · $INST_LITELOADER: set if LiteLoader installed.\n\ + \ · $INST_OPTIFINE: set if OptiFine installed.\n\ + \ · $INST_FABRIC: set if Fabric installed.\n\ + \ · $INST_QUILT: set if Quilt installed. settings.advanced.dont_check_game_completeness=Do not check game integrity -settings.advanced.dont_check_jvm_validity=Do not check JVM compatibility +settings.advanced.dont_check_jvm_validity=Do not check Java VM compatibility settings.advanced.dont_patch_natives=Do not attempt to automatically replace native libraries settings.advanced.environment_variables=Environment Variables -settings.advanced.game_dir.default=Default (.minecraft/) -settings.advanced.game_dir.independent=Isolated (.minecraft/versions//, except for assets and libraries) +settings.advanced.game_dir.default=Default (".minecraft/") +settings.advanced.game_dir.independent=Isolated (".minecraft/versions//", except for assets and libraries) settings.advanced.java_permanent_generation_space=PermGen Space settings.advanced.java_permanent_generation_space.prompt=in MB -settings.advanced.jvm=Java Virtual Machine options -settings.advanced.jvm_args=Java VM arguments -settings.advanced.jvm_args.prompt=- If the parameter entered in "Java Virtual Machine Parameters" is the same as the\ndefault parameter,it will not be added\n\ -- Enter any GC parameters in "Java Virtual Machine Parameters",\nthe G1 parameter of the\ndefault parameter will be disabled\n\ -- Click "Don't add default JVM parameters" below to start the game without adding\ndefault parameters -settings.advanced.launcher_visibility.close=Close the launcher after the game launches. -settings.advanced.launcher_visibility.hide=Hide the launcher after the game launches. -settings.advanced.launcher_visibility.hide_and_reopen=Hide the launcher and reopen it when the game closes. -settings.advanced.launcher_visibility.keep=Keep the launcher visible. +settings.advanced.jvm=Java VM Options +settings.advanced.jvm_args=Java VM Arguments +settings.advanced.jvm_args.prompt=\ · If the arguments entered in "Java VM arguments" is the same as the default arguments, it will not be added.\n\ + \ · Enter any GC arguments in "Java VM arguments", the G1 argument of the default arguments will be disabled.\n\ + \ · Enable "Do not add default Java VM arguments" to launch the game without adding default arguments. +settings.advanced.launcher_visibility.close=Close the launcher after the game launches +settings.advanced.launcher_visibility.hide=Hide the launcher after the game launches +settings.advanced.launcher_visibility.hide_and_reopen=Hide the launcher and show it when the game closes +settings.advanced.launcher_visibility.keep=Keep the launcher visible settings.advanced.launcher_visible=Launcher Visibility settings.advanced.minecraft_arguments=Launch Arguments settings.advanced.minecraft_arguments.prompt=Default settings.advanced.natives_directory=Native Library Path -settings.advanced.natives_directory.choose=Select where your desired native library is located +settings.advanced.natives_directory.choose=Choose the location of the desired native library settings.advanced.natives_directory.custom=Custom settings.advanced.natives_directory.default=Default -settings.advanced.natives_directory.hint=This option is intended only for users of Apple M1 or other not officially supported platforms. Please do not modify this option unless you know what you are doing.\n\ -\n\ -Before proceeding, please make sure all libraries (e.g. lwjgl.dll, libopenal.so) are provided in your desired directory.\n\ -Note: It is recommended to use a fully English character path for the specified local library file, otherwise it may lead to game launch failure. -settings.advanced.no_jvm_args=Do not add default JVM arguments +settings.advanced.natives_directory.hint=This option is intended only for users of Apple M1 or other not officially supported platforms. Please do not edit this option unless you know what you are doing.\n\ + \n\ + Before proceeding, please make sure all libraries (e.g. lwjgl.dll, libopenal.so) are provided in your desired directory.\n\ + Note: It is recommended to use a fully English letters path for the specified local library file, otherwise it may lead to game launch failure. +settings.advanced.no_jvm_args=Do not add default Java VM arguments settings.advanced.precall_command=Pre-launch Command settings.advanced.precall_command.prompt=Commands to execute before the game launches settings.advanced.process_priority=Process Priority @@ -1097,73 +1182,74 @@ settings.advanced.post_exit_command.prompt=Commands to execute after the game ex settings.advanced.renderer=Renderer settings.advanced.renderer.default=OpenGL (Default) settings.advanced.renderer.d3d12=DirectX 12 (Poor performance and compatibility) -settings.advanced.renderer.llvmpipe=Software (Poor efficiency and best compatibility) +settings.advanced.renderer.llvmpipe=Software (Poor performance and best compatibility) settings.advanced.renderer.zink=Vulkan (Best performance and poor compatibility) settings.advanced.server_ip=Server Address -settings.advanced.server_ip.prompt=Join automatically after launching the game. -settings.advanced.use_native_glfw=[Linux Only] Use system GLFW -settings.advanced.use_native_openal=[Linux Only] Use system OpenAL -settings.advanced.workaround=Workarounds -settings.advanced.workaround.warning=Workaround options are intended only for expert users. Tweaking with these options may crash the game. Unless you know what you are doing, please do not modify these options. +settings.advanced.server_ip.prompt=Automatically join after launching the game +settings.advanced.use_native_glfw=[Linux/FreeBSD Only] Use System GLFW +settings.advanced.use_native_openal=[Linux/FreeBSD Only] Use System OpenAL +settings.advanced.workaround=Workaround +settings.advanced.workaround.warning=Workaround options are intended only for advanced users. Tweaking with these options may crash the game. Unless you know what you are doing, please do not edit these options. settings.advanced.wrapper_launcher=Wrapper Command -settings.advanced.wrapper_launcher.prompt=Allows launching using an extra wrapper program like 'optirun' on Linux. +settings.advanced.wrapper_launcher.prompt=Allows launching using an extra wrapper program like "optirun" on Linux settings.custom=Custom -settings.game=Game Settings +settings.game=Settings settings.game.current=Game settings.game.dimension=Resolution settings.game.exploration=Explore settings.game.fullscreen=Fullscreen -settings.game.java_directory=Java Path -settings.game.java_directory.auto=Automatically Select +settings.game.java_directory=Java +settings.game.java_directory.auto=Automatically Choose settings.game.java_directory.auto.not_found=No suitable Java version was installed. settings.game.java_directory.bit=%s bit -settings.game.java_directory.choose=Select Java path. -settings.game.java_directory.invalid=Incorrect Java path. +settings.game.java_directory.choose=Choose Java +settings.game.java_directory.invalid=Incorrect Java path +settings.game.java_directory.version=Specify Java Version settings.game.java_directory.template=%s (%s) settings.game.management=Manage settings.game.working_directory=Working Directory -settings.game.working_directory.choose=Select working directory -settings.game.working_directory.hint=Turn on the 'Isolated' option under 'Working Directory' to allow the current instance to store its settings, saves, and mods in a separate directory.\n\ -\n\ -It is recommended to turn this option on to avoid mod conflicts, but you'll need to move your saves manually. -# ' +settings.game.working_directory.choose=Choose working directory +settings.game.working_directory.hint=Enable the "Isolated" option in "Working Directory" to allow the current instance to store its settings, saves, and mods in a separate directory.\n\ + \n\ + It is recommended to enable this option to avoid mod conflicts, but you will need to move your saves manually. settings.icon=Icon settings.launcher=Launcher Settings settings.launcher.appearance=Appearance -settings.launcher.common_path.tooltip=This app will put all game assets and dependencies here. If there are existing libraries under the game directory, the launcher will prefer to use them first. +settings.launcher.common_path.tooltip=HMCL will put all game assets and dependencies here. If there are existing libraries in the game directory, then HMCL will prefer to use them first. settings.launcher.debug=Debug settings.launcher.download=Download settings.launcher.download.threads=Threads settings.launcher.download.threads.auto=Automatically Determine settings.launcher.download.threads.hint=Too many threads may cause your system to freeze, and your download speed may be affected by your ISP and download servers. It is not always the case that more threads increase your download speed. settings.launcher.download_source=Download Source -settings.launcher.download_source.auto=Auto Choose Download Mirror -settings.launcher.enable_game_list=Show version list in home page +settings.launcher.download_source.auto=Automatically Choose Download Sources +settings.launcher.enable_game_list=Show instance list in homepage settings.launcher.font=Font settings.launcher.general=General -settings.launcher.language=Language (applies after restart) -settings.launcher.launcher_log.export=Export launcher logs -settings.launcher.launcher_log.export.failed=Unable to export logs -settings.launcher.launcher_log.export.success=Logs have been exported to %s +settings.launcher.language=Language (Applies After Restart) +settings.launcher.launcher_log.export=Export Launcher Logs +settings.launcher.launcher_log.reveal=Reveal Logs in Explorer +settings.launcher.launcher_log.export.failed=Unable to export logs. +settings.launcher.launcher_log.export.success=Logs have been exported to "%s". settings.launcher.log=Logging settings.launcher.log.font=Font settings.launcher.proxy=Proxy -settings.launcher.proxy.authentication=Requires authentication -settings.launcher.proxy.disable=Use system proxy +settings.launcher.proxy.authentication=Requires Authentication +settings.launcher.proxy.disable=Use System Proxy settings.launcher.proxy.host=Host settings.launcher.proxy.http=HTTP -settings.launcher.proxy.none=No proxy +settings.launcher.proxy.none=No Proxy settings.launcher.proxy.password=Password settings.launcher.proxy.port=Port -settings.launcher.proxy.socks=SOCKS +settings.launcher.proxy.socks=Socks settings.launcher.proxy.username=Username settings.launcher.theme=Theme -settings.launcher.title_transparent=Transparent titlebar -settings.launcher.turn_off_animations=Turn off animation (applies after restart) +settings.launcher.title_transparent=Transparent Titlebar +settings.launcher.turn_off_animations=Disable Animation (Applies After Restart) settings.launcher.version_list_source=Version List settings.memory=Memory @@ -1171,24 +1257,24 @@ settings.memory.allocate.auto=%1$.1f GB Minimum / %2$.1f GB Allocated settings.memory.allocate.auto.exceeded=%1$.1f GB Minimum / %2$.1f GB Allocated (%3$.1f GB Available) settings.memory.allocate.manual=%1$.1f GB Allocated settings.memory.allocate.manual.exceeded=%1$.1f GB Allocated (%3$.1f GB Available) -settings.memory.auto_allocate=Automatically allocate +settings.memory.auto_allocate=Automatically Allocate settings.memory.lower_bound=Minimum Memory settings.memory.used_per_total=%1$.1f GB Used / %2$.1f GB Total settings.physical_memory=Physical Memory Size settings.show_log=Show Logs -settings.skin=Skins for offline accounts are now supported. You can go to the account settings to change your skin and cape, but other players cannot see it in multiplayer. -settings.tabs.installers=Modloaders / OptiFine -settings.take_effect_after_restart=Applies after restart -settings.type=Instance Settings Type -settings.type.global=Global Instance Settings (shared among instances) -settings.type.global.manage=Global Game Settings -settings.type.global.edit=Edit Global Instance Settings -settings.type.special.enable=Enable per-instance settings +settings.skin=Skins for offline accounts are now supported. You can navigate to your account list to change your skin and cape, but other players cannot see it in multiplayer. +settings.tabs.installers=Loaders +settings.take_effect_after_restart=Applies After Restart +settings.type=Settings Type of Instance +settings.type.global=Global Settings (Shared Among Instances without the "Instance-specific Settings" enabled) +settings.type.global.manage=Global Settings +settings.type.global.edit=Edit Global Settings +settings.type.special.enable=Enable Instance-specific Settings settings.type.special.edit=Edit Current Instance Settings -settings.type.special.edit.hint=Current instance [%s] has enabled per-instance settings, all options on this page will NOT affect that instance. Click here to modify its own options. +settings.type.special.edit.hint=Current instance "%s" has enabled the "Instance-specific Settings", all options on this page will NOT affect that instance. Click here to edit its own settings. -sponsor=Donators -sponsor.bmclapi=Downloads are provided by BMCLAPI. Click here for more information. +sponsor=Donors +sponsor.bmclapi=Downloads for Chinese Mainland are provided by BMCLAPI. Click here for more information. sponsor.hmcl=Hello Minecraft! Launcher is a FOSS Minecraft launcher which allows users to manage multiple Minecraft instances easily. Click here for more information. system.architecture=Architecture @@ -1200,17 +1286,15 @@ update=Update update.accept=Update update.changelog=Changelog update.channel.dev=Beta -update.channel.dev.hint=You are currently using a beta build of the launcher, which may include some extra features, but is also sometimes more unstable than the release versions.\n\ -\n\ -If you encounter any bug or issue, you can go to the feedback page to report it, or tell us in our Discord or QQ community.\n\ -\n\ -Click here to hide this tooltip for the current version. -update.channel.dev.title=Beta Version Notice +update.channel.dev.hint=You are currently using a Beta channel build of the launcher, which may include some extra features, but is also sometimes more unstable than the Release channel.\n\ + \n\ + If you encounter any bugs or problems, please navigate to Settings → Feedback page to submit your feedback. +update.channel.dev.title=Beta Channel Notice update.channel.nightly=Nightly -update.channel.nightly.hint=You are currently using a nightly build of the launcher, which may include some extra features, but is also always more unstable than the other versions.\n\ -\n\ -If you encounter any bug or issue, you can go to the feedback page to report it, or tell us in our Discord or QQ community. -update.channel.nightly.title=Nightly Version Notice +update.channel.nightly.hint=You are currently using a Nightly channel build of the launcher, which may include some extra features, but is also always more unstable than the other channels.\n\ + \n\ + If you encounter any bugs or problems, please navigate to Settings → Feedback page to submit your feedback. +update.channel.nightly.title=Nightly Channel Notice update.channel.stable=Release update.checking=Checking for Updates update.failed=Unable to update @@ -1218,16 +1302,17 @@ update.found=Update Available! update.newest_version=Latest version: %s update.bubble.title=Update Available: %s update.bubble.subtitle=Click here to update -update.note=Warning: Beta versions and nightly versions may have more features or fixes, but they also come with more potential issues. -update.latest=This is the latest version. -update.no_browser=Cannot open in the system browser. But, we copied the link to your clipboard and you can open it manually. +update.note=Beta & Nightly channels may have more features or fixes, but is also come with more potential problems. +update.latest=This is the latest version +update.no_browser=Cannot open in system browser. But we copied the link to your clipboard and you can open it manually. update.tooltip=Update version=Games -version.cannot_read=Unable to parse the game version, automatic installation cannot continue. +version.name=Instance Name +version.cannot_read=Unable to parse the game instance, installation cannot continue. version.empty=No Instances -version.empty.add=Add an Instance -version.empty.launch=No instances, you can install one on the 'Download' tab. +version.empty.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 click here to download one. version.game.old=Historical version.game.release=Release @@ -1236,29 +1321,29 @@ version.game.snapshot=Snapshot version.game.snapshots=Snapshots version.launch=Launch Game version.launch.test=Test Launch -version.switch=Switch version +version.switch=Switch Instance version.launch_script=Export Launch Script version.launch_script.failed=Unable to export launch script. version.launch_script.save=Export Launch Script version.launch_script.success=Exported launch script as %s. version.manage=All Instances version.manage.clean=Delete Log Files -version.manage.clean.tooltip=Remove logs and crash reports. +version.manage.clean.tooltip=Delete the files in "logs" and "crash-reports" directories. version.manage.duplicate=Duplicate Instance -version.manage.duplicate.duplicate_save=Duplicate Save -version.manage.duplicate.prompt=Enter instance name -version.manage.duplicate.confirm=The duplicated instance will have a copy of all the files of this instance, with an isolated game directory and settings. -version.manage.manage=Manage Instances -version.manage.manage.title=Manage Instance - %1s +version.manage.duplicate.duplicate_save=Duplicate Saves +version.manage.duplicate.prompt=Enter New Instance Name +version.manage.duplicate.confirm=The duplicated instance will have a copy of all files in the instance directory (".minecraft/versions/"), with an isolated working directory and settings. +version.manage.manage=Edit Instance +version.manage.manage.title=Edit Instance - %1s version.manage.redownload_assets_index=Update Game Assets version.manage.remove=Delete Instance -version.manage.remove.confirm=Are you sure you want to permanently remove the version %s? This action cannot be undone! -version.manage.remove.confirm.trash=Are you sure you want to remove the version %s? You can still find its files in your recycle bin by the name of %s. -version.manage.remove.confirm.independent=Since this instance is stored in an isolated directory, deleting it will also delete its saves and other data. Do you still want to delete instance %s? +version.manage.remove.confirm=Are you sure you want to permanently remove the instance "%s"? This operation cannot be undone!!! +version.manage.remove.confirm.trash=Are you sure you want to remove the instance "%s"? You can still find its files in your recycle bin by the name of "%s". +version.manage.remove.confirm.independent=Since this instance is stored in an isolated directory, deleting it will also delete its saves and other data. Do you still want to delete the instance "%s"? version.manage.remove_assets=Delete All Assets version.manage.remove_libraries=Delete All Libraries version.manage.rename=Rename Instance -version.manage.rename.message=Please enter the new name for this instance +version.manage.rename.message=Enter New Instance Name version.manage.rename.fail=Unable to rename the instance, some files might be in use or the name contains an invalid character. version.settings=Settings version.update=Update Modpack diff --git a/HMCL/src/main/resources/assets/lang/I18N_es.properties b/HMCL/src/main/resources/assets/lang/I18N_es.properties index 036af612c4..e250a6bd31 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_es.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_es.properties @@ -1,6 +1,6 @@ # # Hello Minecraft! Launcher -# Copyright (C) 2023 huangyuhui and contributors +# Copyright (C) 2024 huangyuhui and contributors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -23,25 +23,29 @@ about=Acerca de about.copyright=Copyright about.copyright.statement=Copyright © 2024 huangyuhui. about.author=Autor -about.author.statement=@huanghongxun en bilibili +about.author.statement=bilibili @huanghongxun about.claim=EULA -about.claim.statement=Haz clic en este enlace para ver el texto completo. +about.claim.statement=Haga clic en este enlace para ver el texto completo. about.dependency=Bibliotecas de terceros about.legal=Reconocimiento legal about.thanks_to=Gracias a -about.thanks_to.bangbang93.statement=Por proporcionar la API de descarga de BMCLAPI, por favor considere hacer una donación. +about.thanks_to.bangbang93.statement=Por proporcionar el espejo de descarga de BMCLAPI, por favor considere hacer una donación! +about.thanks_to.burningtnt.statement=Aportar mucho apoyo técnico a HMCL. about.thanks_to.contributors=Todos los colaboradores en GitHub -about.thanks_to.contributors.statement=Sin la impresionante comunidad de código abierto, Hello Minecraft! Launcher no habría llegado tan lejos. +about.thanks_to.contributors.statement=Sin la impresionante comunidad de código abierto, HMCL no habría llegado tan lejos. about.thanks_to.gamerteam.statement=Por proporcionar la imagen de fondo por defecto. -about.thanks_to.glavo.statement=Aportar mucho apoyo técnico a HMCL. +about.thanks_to.glavo.statement=Responsable del mantenimiento de HMCL. about.thanks_to.zekerzhayard.statement=Aportar mucho apoyo técnico a HMCL. -about.thanks_to.zkitefly.statement=Aportar mucho apoyo técnico a HMCL. -about.thanks_to.mcmod=mcmod.cn -about.thanks_to.mcmod.statement=Por facilitar las traducciones al chino y el wiki de varios mods. +about.thanks_to.zkitefly.statement=Responsable del mantenimiento de la documentación de HMCL. +about.thanks_to.mcbbs=MCBBS (Foro Chino Minecraft) +about.thanks_to.mcbbs.statement=Por proporcionar el espejo de descarga de mcbbs.net para los usuarios de China continental. (Ya no disponible) +about.thanks_to.mcmod=MCMod (mcmod.cn) +about.thanks_to.mcmod.statement=Por proporcionar las traducciones al chino simplificado y la wiki de varios mods. about.thanks_to.red_lnn.statement=Por proporcionar la imagen de fondo por defecto. +about.thanks_to.shulkersakura.statement=Por proporcionar el logotipo de HMCL. about.thanks_to.users=Miembros del grupo de usuarios de HMCL about.thanks_to.users.statement=Gracias por las donaciones, los informes de errores, etc. -about.thanks_to.yushijinhun.statement=Por proporcionar soporte relacionado con authlib-injector. +about.thanks_to.yushijinhun.statement=Por proporcionar el soporte relacionado con authlib-injector. about.open_source=Código abierto about.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL) @@ -138,8 +142,7 @@ account.username=Nombre de usuario archive.author=Autor(es) archive.date=Fecha de publicación -archive.game_version=Versión del juego -archive.name=Nombre de archivo +archive.file.name=Nombre de archivo archive.version=Versión assets.download=Descargando assets @@ -149,25 +152,30 @@ assets.index.malformed=Los archivos de índice de los activos descargados estaba button.cancel=Cancelar button.change_source=Cambiar fuente de descarga button.clear=Limpiar +button.copy_and_exit=Copiar y salir button.delete=Borrar button.edit=Editar button.install=Instalar button.export=Exportar button.no=No -botón.ok=Aceptar +button.ok=Aceptar button.refresh=Refrescar button.remove=Eliminar -button.remove.confirm=¿Está seguro de querer eliminarlo definitivamente? Esta acción no se puede deshacer. +button.remove.confirm=¿Estás seguro de que deseas eliminarlo de forma permanente? ¡Esta acción no se puede deshacer! +button.retry=Reintentar button.save=Guardar button.save_as=Guardar como button.select_all=Seleccionar todo +button.view=Vista button.yes=Sí +chat=Chat de grupo + color.recent=Recomendado color.custom=Color personalizado -crash.NoClassDefFound=Por favor, verifique la integridad de este software, o intente actualizar su versión de Java. -crash.user_fault=El launcher se ha bloqueado debido a un entorno Java o de sistema corrupto. Por favor, asegúrese de que su Java o sistema operativo está instalado correctamente. +crash.NoClassDefFound=Por favor, verifique la integridad de este software, o intente actualizar su Java. +crash.user_fault=El lanzador se ha bloqueado debido a que Java o el entorno del sistema están dañados. Por favor, asegúrese de que su Java o sistema operativo está instalado correctamente. curse.category.0=Todos @@ -188,6 +196,7 @@ curse.category.4482=Extra grande curse.category.4472=Tecnología curse.category.4473=Magia curse.category.5128=Vanilla+ +curse.category.7418=Horror # https://addons-ecs.forgesvc.net/api/v2/category/section/6 curse.category.5299=Educación @@ -295,7 +304,6 @@ download=Descargar download.hint=Instalar juegos y modpacks o descargar mods, paquetes de recursos y mapas download.code.404=Archivo no encontrado en el servidor remoto: %s download.content=Complementos -download.curseforge.customization=Luz y sombras, y personalización del juego download.existing=El archivo no se puede guardar porque ya existe. Puedes usar '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 @@ -346,8 +354,13 @@ fatal.illegal_char=Su ruta de usuario contiene un carácter ilegal '\=', por lo Por ejemplo, no podrá utilizar authlib-injector o cambiar el skin de su cuenta offline. feedback=Comentarios +feedback.channel=Canal de comentarios feedback.discord=Discord -feedback.discord.statement=¡Únete a nuestra comunidad de Discord! +feedback.discord.statement=¡Únase a nuestro servidor Discord! +feedback.github=GitHub Issues +feedback.github.statement=Envíe una propuesta en GitHub. +feedback.qq_group=Grupo QQ de usuarios de HMCL +feedback.qq_group.statement=¡Únase a nuestro grupo QQ de usuarios! file=Archivo @@ -471,22 +484,12 @@ game.crash.reason.mod_resolution_mod_version.any=%1$s (Cualquier versión) game.crash.reason.forge_repeat_installation=El juego actual no puede continuar ejecutándose debido a una instalación duplicada de Forge. Este es un problema conocido\nSe recomienda cargar los comentarios del registro en GitHub para que podamos encontrar más pistas y arreglar esta pregunta. \nActualmente puede ir a la instalación automática para desinstalar Forge y volver a instalarlo. game.crash.reason.optifine_repeat_installation=El juego actual no puede seguir ejecutándose debido a la instalación repetida de OptiFine. \nElimine OptiFine de la carpeta Mod o vaya a Gestión de juegos-Instalación automática para desinstalar OptiFine que se instala automáticamente. game.crash.reason.modmixin_failure=El juego actual no puede continuar ejecutándose debido a algunas fallas en la inyección de mods. \nEsto generalmente significa que el mod tiene un error o no es compatible con el entorno actual. \nPuede consultar el registro para ver si hay un mod incorrecto. -game.crash.reason.file_or_content_verification_failed=El juego actual tiene un problema porque fallaron algunos archivos o la verificación de contenido. \nIntente eliminar esta versión (incluidas las modificaciones) y vuelva a descargarla, o intente usar un proxy cuando vuelva a descargar, etc. game.crash.reason.mod_repeat_installation=El juego actual tiene múltiples Mods idénticos instalados repetidamente, y cada Mod solo puede aparecer una vez. Elimina los Mods repetidos y luego inicia el juego. game.crash.reason.forge_error=Forge puede haber proporcionado un mensaje de error.\nPuede ver el registro y realizar las acciones correspondientes de acuerdo con la información de registro en el informe de errores.\nSi no ve un mensaje de error, puede consultar el informe de errores para saber cómo ocurrió el error.\n%1$s game.crash.reason.mod_solution0=El juego actual no puede seguir ejecutándose debido a algunos problemas de modificación.\nPuede consultar el registro para ver si hay un mod incorrecto. -game.crash.reason.mod_profile_causes_game_crash=El juego actual no puede seguir ejecutándose debido a un problema con el perfil de mod.\nPuede consultar el registro del mod defectuoso y su archivo de configuración. -game.crash.reason.fabric_reports_an_error_and_gives_a_solution=Forge puede haber dado un mensaje de error.\nPuede ver el registro y realizar las acciones correspondientes de acuerdo con la información de registro en el informe de errores.\nSi no ve un mensaje de error, puede consultar el informe de errores para saber cómo ocurrió el error. game.crash.reason.java_version_is_too_high=El juego actual se bloqueó porque la versión de Java es demasiado alta y no puede seguir ejecutándose.\nCambie a una versión anterior de Java en la pestaña Java Path de Configuración global del juego o Configuración específica del juego antes de iniciar el juego.\nSi no es así, puede descargarlo desde java.com (Java8) o BellSoft Liberica Full JRE (Java17) y otras plataformas para descargar e instalar una (reinicie el iniciador después de la instalación). game.crash.reason.mod_name=El juego actual no puede continuar ejecutándose debido a un problema con el nombre del archivo mod. El nombre del archivo de\nMod solo debe usar mayúsculas y minúsculas (Aa ~ Zz), números (0 ~ 9), líneas horizontales (-), subrayado (_) y puntos (.) en toda la mitad del inglés.\n Por favor, vaya a la carpeta mod para agregar un carácter de Cumplimiento anterior a todos los nombres de archivo mod que no cumplan. game.crash.reason.incomplete_forge_installation=O jogo atual não pode continuar devido a uma instalação incompleta do Forge / NeoForge.\nDesinstale e reinstale o Forge / NeoForge em Configurações de versão - Instalação automática. -game.crash.reason.forge_error=Forge puede haber proporcionado un mensaje de error. \nPuede ver el registro y realizar las acciones correspondientes de acuerdo con la información de registro en el informe de errores. \nSi no ve un mensaje de error, puede consultar el informe de errores para saber cómo ocurrió el error.\n%1$s -game.crash.reason.mod_solution0=El juego actual no puede seguir ejecutándose debido a algunos problemas de modificación. \nPuede consultar el registro para ver si hay un mod incorrecto. -game.crash.reason.mod_profile_causes_game_crash=El juego actual no puede seguir ejecutándose debido a un problema con el perfil de mod. \nPuede consultar el registro del mod defectuoso y su archivo de configuración. -game.crash.reason.fabric_reports_an_error_and_gives_a_solution=Forge puede haber dado un mensaje de error. \nPuede ver el registro y realizar las acciones correspondientes de acuerdo con la información de registro en el informe de errores. \nSi no ve un mensaje de error, puede consultar el informe de errores para saber cómo ocurrió el error. -game.crash.reason.java_version_is_too_high=El juego actual se bloqueó porque la versión de Java es demasiado alta y no puede seguir ejecutándose. \nCambie a una versión anterior de Java en la pestaña Java Path de Configuración global del juego o Configuración específica del juego antes de iniciar el juego. \nSi no es así, puede descargarlo desde java.com (Java8) o BellSoft Liberica Full JRE (Java17) y otras plataformas para descargar e instalar una (reinicie el iniciador después de la instalación). -game.crash.reason.mod_name=El juego actual no puede continuar ejecutándose debido a un problema con el nombre del archivo mod. El nombre del archivo de \nMod solo debe usar mayúsculas y minúsculas (Aa ~ Zz), números (0 ~ 9), líneas horizontales (-), subrayado (_) y puntos (.) en toda la mitad del inglés. \n Por favor, vaya a la carpeta mod para agregar un carácter de Cumplimiento anterior a todos los nombres de archivo mod que no cumplan. -game.crash.reason.incomplete_forge_installation=O jogo atual não pode continuar devido a uma instalação incompleta do Forge. \nDesinstale e reinstale o Forge em Configurações de versão - Instalação automática. game.crash.reason.modlauncher_8=El juego se ha bloqueado porque tu versión actual de Forge no es compatible con tu instalación de Java. Por favor, intente actualizar Forge, o intente utilizar Java 8u312/11.0.13/17.0.1 o versiones anteriores.\n\ \n\ 8u312 y anteriores\:\n\ @@ -525,12 +528,12 @@ game.crash.reason.shaders_mod=El juego actual no puede seguir ejecutándose porq game.crash.reason.rtss_forest_sodium=El juego actual se bloquea debido a una incompatibilidad entre RivaTuner Statistics Server (RTSS) y Sodium, lo que provoca el fallo del juego. Haz clic aquí para obtener más detalles. game.crash.reason.stacktrace=El motivo del fallo es desconocido. Puede ver los detalles haciendo clic en el botón "Registros".\n\ \n\ -Hay algunas palabras clave que pueden contener algunos Mod IDs. Puedes buscarlas en Internet para averiguar el problema por ti mismo..\n\ +Hay algunas palabras clave que pueden contener algunos Mod IDs. Puedes buscarlas en Internet para averiguar el problema por ti mismo.\n\ \n\ %s game.crash.reason.too_old_java=El juego se ha bloqueado porque estás utilizando una versión histórica de Java VM..\n\ \n\ -Tienes que cambiar a una versión más reciente (%1$s) de Java en la configuración del juego y luego volver a ejecutar el juego. Puedes descargar Java desde here. +Tienes que cambiar a una versión más reciente (%1$s) de Java en la configuración del juego y luego volver a ejecutar el juego. Puedes descargar Java desde here. game.crash.reason.unknown=No somos capaces de averiguar por qué el juego se estrelló, por favor refiérase a los registros del juego.\n\ \n\ Cuando pidas ayuda a otra persona, por favor, comparte con ella el registro completo del juego y el archivo de informe de fallos relacionado.\n\ @@ -541,11 +544,6 @@ game.crash.reason.unsatisfied_link_error=No se puede iniciar Minecraft porque fa Si ha modificado la configuración de la biblioteca nativa, por favor, asegúrese de que estas bibliotecas existen. O, por favor, intente iniciar de nuevo después de revertir a los valores predeterminados.\n\ Si no lo ha hecho, por favor, compruebe si le faltan mods de dependencia.\n\ De lo contrario, si usted cree que esto es causado por HMCL, por favor, comentarios a nosotros. -game.crash.reason.failed_to_load_a_library=Failed to load a library.\n\ -\n\ -Si ha modificado la configuración de la biblioteca nativa, por favor, asegúrese de que estas bibliotecas existen. O, por favor, intente iniciar de nuevo después de revertir a los valores predeterminados.\n\ -Si no lo ha hecho, por favor, compruebe si le faltan mods de dependencia.\n\ -De lo contrario, si usted cree que esto es causado por HMCL, por favor, comentarios a nosotros. game.crash.title=Juego interrumpido game.directory=Ruta del juego game.version=Versión del juego @@ -600,6 +598,36 @@ install.new_game.malformed=Nombre no válido install.select=Seleccione una operación install.success=Instalado con éxito. +java.add=Añadir Java +java.add.failed=Este Java no es válido o es incompatible con la plataforma actual. +java.disable=Deshabilitar este Java +java.disable.confirm=¿Está seguro de que desea desactivar este Java? +java.disabled.management=Java desactivado +java.disabled.management.remove=Eliminar este Java de la lista +java.disabled.management.restore=Activar este Java +java.download=Descargar Java +java.download.load_list.failed=Error al cargar la lista de versiones +java.download.more=Más distribuciones de Java +java.download.prompt=Elija la versión de Java que desea descargar: +java.download.distribution=Distribución +java.download.version=Versión +java.download.packageType=Tipo de paquete +java.management=Gestión Java +java.info.architecture=Arquitectura +java.info.vendor=Proveedor +java.info.version=Versión +java.info.disco.distribution=Distribución +java.install=Instalar Java +java.install.archive=Fuente Path +java.install.failed.exists=Este nombre ya tiene dueño +java.install.failed.invalid=Este archivo no es un paquete de instalación de Java válido, por lo que no se puede instalar. +java.install.failed.unsupported_platform=Este Java no es compatible con la plataforma actual, por lo que no puede instalarse. +java.install.name=Nombre +java.install.warning.invalid_character=Carácter ilegal en el nombre +java.reveal=Revelar el directorio de Java +java.uninstall=Desinstalar Java +java.uninstall.confirm=¿Está seguro de que desea desinstalar este Java? ¡Esta acción no se puede deshacer! + lang=Español lang.default=Usar idioma del sistema @@ -619,13 +647,10 @@ launch.advice.java9=No puedes ejecutar Minecraft 1.12 o anterior con Java 9 o m launch.advice.modlauncher8=La versión de Forge que estás utilizando no es compatible con la versión actual de Java. Por favor, intenta actualizar Forge, o ejecutar el juego con Java 8u312/11.0.13/17.0.1 o anterior. launch.advice.newer_java=Se recomienda Java 8 para una experiencia de juego más fluida. Y para Minecraft 1.12 o superior, y la mayoría de los mods, es obligatorio. launch.advice.not_enough_space=Has asignado un tamaño de memoria mayor que los %d MB reales de memoria instalados en tu máquina. Es posible que el rendimiento del juego se vea afectado, o incluso que no puedas iniciar el juego. -launch.advice.require_newer_java_version=Minecraft %1$s requiere Java %2$s o posterior, pero no hemos podido encontrar una versión. ¿Desea descargar una ahora? launch.advice.too_large_memory_for_32bit=Has asignado un tamaño de memoria mayor que la limitación de memoria de la instalación de Java de 32 bits. Es posible que no puedas iniciar el juego. launch.advice.vanilla_linux_java_8=Minecraft 1.12.2 o inferior sólo admite Java 8 para la plataforma Linux x86-64, porque las versiones posteriores no pueden cargar las bibliotecas nativas de 32 bits como liblwjgl.so\n\ \n\ Por favor, descárguelo de java.com, o instale OpenJDK 8. -launch.advice.vanilla_x86=Minecraft actualmente no proporciona soporte oficial para arquitecturas distintas de x86 o x86-64.\n\ -Puedes intentar descargar la librería nativa de tu plataforma y especificar su ruta, luego vuelve a intentarlo. launch.advice.vanilla_x86.translation=Minecraft no proporciona actualmente soporte oficial para arquitecturas distintas de x86 y x86-64.\n\ Por favor, instale Java para x86-64 para jugar a Minecraft a través del entorno de traducción Rosetta, o descargue sus bibliotecas nativas correspondientes y especifique su ruta. launch.failed=No se puede ejecutar @@ -648,7 +673,6 @@ launch.state.java=Comprobando la versión de Java launch.state.logging_in=Iniciando sesión launch.state.modpack=Descargando dependencias launch.state.waiting_launching=Esperando la ejecución del juego -launch.wrong_javadir=Ruta de Java no válida, volviendo a la predeterminada. launcher=Launcher launcher.agreement=Términos de servicio y EULA @@ -681,10 +705,8 @@ logwindow.title=Registro logwindow.help=Puede ir a la comunidad HMCL y encontrar a otros para ayudar logwindow.autoscroll=Desplazamiento automático logwindow.export_game_crash_logs=Exportar registros de errores -logwindow.export_dump.dependency_ok.button=Exportar volcado de pila de juegos -logwindow.export_dump.dependency_ok.doing_button=Exportación de volcado de pila de juego (puede tardar hasta 15 segundos) -logwindow.export_dump.no_dependency.button=Exportar volcado de pila de juegos (no compatible) -logwindow.export_dump.no_dependency.tooltip=Su Java no contiene las dependencias para crear el volcado de pila. Dirígete a HMCL QQ o HMCL Discord para obtener ayuda. +logwindow.export_dump=Exportar volcado de pila de juegos +logwindow.export_dump.no_dependency=Su Java no contiene las dependencias para crear el volcado de pila. Dirígete a HMCL QQ o HMCL Discord para obtener ayuda. main_page=Inicio @@ -743,10 +765,8 @@ modpack.origin.mcbbs=MCBBS modpack.origin.mcbbs.prompt=ID de publicación modpack.scan=Análisis del índice de modpacks modpack.task.install=Instalar Modpack -modpack.task.install.error=No se ha podido identificar este modpack. Actualmente sólo soportamos los modpacks Curse, Modrinth, MultiMC y MCBBS. -modpack.task.install.will=Vas a instalar el modpack: +modpack.task.install.error=No se ha podido identificar este modpack. Actualmente sólo soportamos los modpacks Curse, Modrinth, MultiMC y MCBBS. modpack.type.curse=Curse -modpack.type.curse.tolerable_error=No se han podido descargar las dependencias, puedes intentar continuar la descarga ejecutando esta instancia del juego. modpack.type.curse.error=No se han podido descargar las dependencias de CurseForge, inténtalo de nuevo o utiliza una conexión proxy. modpack.type.curse.not_found=Algunas dependencias ya no están disponibles, por favor intenta instalar una versión más reciente del modpack. modpack.type.manual.warning=Este archivo contiene una copia completa de una instancia de Minecraft. La mayoría de las veces, sólo tienes que descomprimirlo y ejecutar el juego usando su launcher incorporado. Pero, HMCL todavía puede importarlo, sin garantía de su utilidad, ¿aún continúa? @@ -950,8 +970,8 @@ settings.advanced.post_exit_command=Comando post-salida settings.advanced.post_exit_command.prompt=Comandos a ejecutar tras la salida del juego settings.advanced.server_ip=Dirección del servidor settings.advanced.server_ip.prompt=Entrar automáticamente después de ejecutar el juego -settings.advanced.use_native_glfw=[Sólo Linux] Utilizar GLFW nativo -settings.advanced.use_native_openal=[Sólo Linux] Utilizar OpenAL nativo +settings.advanced.use_native_glfw=[Sólo Linux/FreeBSD] Utilizar GLFW nativo +settings.advanced.use_native_openal=[Sólo Linux/FreeBSD] Utilizar OpenAL nativo settings.advanced.workaround=Métodos alternativos settings.advanced.workaround.warning=Éstas opciones están pensadas sólo para usuarios expertos. Jugar con estas opciones puede romper el juego. A menos que sepas lo que estás haciendo, por favor no modifiques estas opciones. settings.advanced.wrapper_launcher=Wrapper Launcher @@ -1035,8 +1055,8 @@ settings.type.special.edit=Editar ajustes de la instancia actual settings.type.special.edit.hint=La instancia actual [%s] tiene activada la configuración por instancia, todas las opciones de esta página NO afectarán a esa instancia. Haga clic aquí para modificar sus propias opciones. sponsor=Donantes -sponsor.bmclapi=Las descargas son proporcionadas por BMCLAPI. Haz clic aquí para obtener más información. -sponsor.hmcl=¡Hello Minecraft! Launcher es un launcher FOSS de Minecraft que permite a los usuarios gestionar múltiples instancias de Minecraft fácilmente. Usted puede donar a nosotros en Afdian para apoyar nuestro alojamiento de archivos y el desarrollo del proyecto. Haga clic aquí para obtener más información. +sponsor.bmclapi=Las descargas de China continental son proporcionadas por BMCLAPI. Haga clic aquí para obtener más información. +sponsor.hmcl=Hello Minecraft! Launcher es un launcher FOSS de Minecraft que permite a los usuarios gestionar múltiples instancias de Minecraft fácilmente. Haga clic aquí para obtener más información. system.architecture=Arquitectura system.operating_system=Sistema operativo @@ -1046,15 +1066,13 @@ update.accept=Actualizar update.changelog=Registro de cambios update.channel.dev=Beta update.channel.dev.hint=Actualmente está utilizando una versión beta del launcher, que puede incluir algunas características adicionales, pero también es más inestable que las versiones de lanzamiento.\n\ -\n\ -Si encuentra algún error o problema, puede ir a la página de comentarios para informar de ello, o bien para decírnoslo en nuestro Discord o en la comunidad QQ.\n\ -\n\ -Haga clic aquí para ocultar el recordatorio para la versión de prueba actual. + \n\ + Si encuentra algún error o problema, puede ir a la página de comentarios para informar de ello. update.channel.dev.title=Aviso de versión beta update.channel.nightly=Nocturna update.channel.nightly.hint=Estás utilizando una versión nocturna del launcher, que puede incluir algunas características adicionales, pero también es siempre más inestable que las otras versiones.\n\ -\n\ -Si encuentra algún error o problema, puede ir a la página de comentarios para informar de ello, o bien para decírnoslo en nuestro Discord o en la comunidad QQ community. + \n\ + Si encuentra algún error o problema, puede ir a la página de comentarios para informar de ello. update.channel.nightly.title=Aviso de versión nocturna update.channel.stable=Estable update.checking=Buscando actualizaciones diff --git a/HMCL/src/main/resources/assets/lang/I18N_ja.properties b/HMCL/src/main/resources/assets/lang/I18N_ja.properties index eed35a3f51..2194eb81d2 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ja.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ja.properties @@ -1,6 +1,6 @@ # # Hello Minecraft! Launcher -# Copyright (C) 2023 huangyuhui and contributors +# Copyright (C) 2024 huangyuhui and contributors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -17,31 +17,36 @@ # # Contributors: zhixuan2333 -about=About + +about=について about.copyright=著作権 -about.copyright.statement=Copyright © 2024 huangyuhui。 -about.author=Author -about.author.statement=bilibili : @huanghongxun +about.copyright.statement=Copyright © 2024 huangyuhui +about.author=著者 +about.author.statement=bilibili @huanghongxun about.claim=EULA -about.claim.statement=テキスト全体のリンクをクリックします。 -about.dependency=Dependencies +about.claim.statement=全文はこのリンクをクリック +about.dependency=依存関係 about.legal=法的承認 -about.thanks_to=Thanks to -about.thanks_to.bangbang93.statement=BMCLAPIダウンロードソースプロバイダー。 -about.thanks_to.contributors=問題、プルリクエストなどを介してこのプロジェクトに参加したすべての貢献者。 -about.thanks_to.contributors.statement=Hello Minecraftをサポートしてくれたオープンソースコミュニティに感謝します!ランチャー -about.thanks_to.gamerteam.statement=デフォルトの背景画像プロバイダー。 -about.thanks_to.glavo.statement=HMCLに多くのテクニカルサポートを提供 +about.thanks_to=感謝します… +about.thanks_to.bangbang93.statement=BMCLAPIダウンロードミラープロバイダー。寄付をご検討ください! +about.thanks_to.burningtnt.statement=HMCLに多くのテクニカルサポートを提供 +about.thanks_to.contributors=GitHubのすべての貢献者 +about.thanks_to.contributors.statement=HMCLをサポートしてくれたオープンソースコミュニティに感謝する +about.thanks_to.gamerteam.statement=デフォルトの背景画像プロバイダー +about.thanks_to.glavo.statement=HMCLのメンテナ about.thanks_to.zekerzhayard.statement=HMCLに多くのテクニカルサポートを提供 -about.thanks_to.zkitefly.statement=HMCLに多くのテクニカルサポートを提供。 -about.thanks_to.mcmod=mcmod.cn -about.thanks_to.mcmod.statement=Mod中国語の名前翻訳プロバイダー。 -about.thanks_to.red_lnn.statement=デフォルトの背景画像プロバイダー。 +about.thanks_to.zkitefly.statement=HMCLドキュメントメンテナ +about.thanks_to.mcbbs=MCBBS (Minecraft中国語フォーラム) +about.thanks_to.mcbbs.statement=中国本土ユーザー向けのmcbbs.netダウンロードミラープロバイダー(もう利用できません) +about.thanks_to.mcmod=MCMod (mcmod.cn) +about.thanks_to.mcmod.statement=様々なMODの簡体字中国語名前翻訳とウィキを提供する +about.thanks_to.red_lnn.statement=デフォルトの背景画像プロバイダー +about.thanks_to.shulkersakura.statement=HMCLロゴプロバイダー about.thanks_to.users=HMCLユーザーグループのメンバー -about.thanks_to.users.statement=寄付、バグレポートなどに感謝します。 -about.thanks_to.yushijinhun.statement=authlib-インジェクターのサポート +about.thanks_to.users.statement=寄付、バグレポートなどに感謝します +about.thanks_to.yushijinhun.statement=authlib-injector関連のサポートを提供する about.open_source=オープンソース -about.open_source.statement=GPL v3(https://github.com/HMCL-dev/HMCL/) +about.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL) account=アカウント account.cape=マント @@ -125,17 +130,17 @@ account.username=ユーザー名 archive.author=作成者 archive.date=公開日 -archive.game_version=ゲームバージョン -archive.name=名前 +archive.file.name=名前 archive.version=バージョン -Assets.download=アセットのダウンロード -Assets.download_all=アセットの整合性チェック -Assets.index.malformed=アセットインデックスの形式が正しくありません。バージョン設定の[ゲームアセットファイルの更新]で再試行できます。 +assets.download=アセットのダウンロード +assets.download_all=アセットの整合性チェック +assets.index.malformed=アセットインデックスの形式が正しくありません。バージョン設定の[ゲームアセットファイルの更新]で再試行できます。 button.cancel=キャンセル button.change_source=ダウンロードソースの変更 button.clear=クリア +button.copy_and_exit=コピーして終了 button.delete=削除 button.edit=編集 button.install=インストール @@ -144,19 +149,23 @@ button.no=いいえ button.ok=OK button.refresh=更新 button.remove=削除 -button.remove.confirm=削除してもよろしいですか?この操作をロールバックすることはできません! +button.remove.confirm=本当に完全に削除しますか?この操作は元に戻せません! +button.retry=リトライ button.save=保存 button.save_as=名前を付けて保存 button.select_all=すべて選択 +button.view=読む button.yes=はい +chat=グループチャット + color.recent=推奨 color.custom=カスタムカラー -crack.NoClassDefFound=「HelloMinecraft!Launcher」ソフトウェアが破損していないことを確認してください。 -crack.user_fault=OSまたはJava環境が正しくインストールされていない可能性があり、クラッシュする可能性があります。Javaランタイム環境またはコンピュータを確認してください。 +crash.NoClassDefFound=このソフトウェアの整合性を確認するか、Javaの更新をお試しください。 +crash.user_fault=JavaまたはOSの環境が壊れているため、ランチャーがクラッシュしました。JavaまたはOSが正しくインストールされているかご確認ください。 -curse.category.0=すべて +curse.category.0=全て # https://addons-ecs.forgesvc.net/api/v2/category/section/4471 curse.category.4474=Sci-Fi @@ -174,6 +183,7 @@ curse.category.4479=ハードコア curse.category.4482=特大 curse.category.4472=Tech curse.category.4473=魔法 +curse.category.7418=ホラー # https://addons-ecs.forgesvc.net/api/v2/category/section/6 curse.category.5129=Vanilla+ @@ -277,7 +287,6 @@ download=ダウンロード download.hint=ゲームや modpack をインストールするか、mod、リソース パック、マップをダウンロードします download.code.404=リモートサーバーにファイルが見つかりません:%s download.content=ゲームコンテンツ -download.curseforge.customization=光と影、およびゲームのカスタマイズ download.existing=ファイルは既に存在するため、保存できません。「名前を付けて保存」を選択して、ファイルを別の場所に保存できます。 download.external_link=ダウンロードサイトを開く download.failed=%1$s のダウンロードに失敗しました、応答コード:%2$d @@ -311,15 +320,18 @@ fatal.javafx.missing=JavaFXがありません。\nJava11以降を使用してい fatal.config_loading_failure=構成にアクセスできません。\nHelloMinecraftを確認してください。Launcherには、「%s」とその中のファイルへの読み取りおよび書き込みアクセス権があります。 fatal.migration_requires_manual_reboot=更新が完了しました。Hello Minecraftを再開してください!ランチャー。 fatal.apply_update_failure=ごめんなさい、Hello Minecraft! Launcher 何か問題が発生したため、ランチャーは更新を完了できませんでした。\nただし、Hello Minecraftをダウンロードすることで、手動で更新を終了できます。%s からのランチャー。\nこの問題を報告することを検討してください。 -fatal.config_loading_failure=構成にアクセスできません。\nHelloMinecraftを確認してください。 Launcherには、「%s」とその中のファイルへの読み取りおよび書き込みアクセス権があります。\nmacOS の場合は、「デスクトップ」、「ダウンロード」、「ドキュメント」以外の権限を持つ場所に HMCL を配置して、再試行してください。 -fatal.migration_requires_manual_reboot=更新が完了しました。 Hello Minecraftを再開してください!ランチャー。 -fatal.apply_update_failure=ごめんなさい、Hello Minecraft! Launcher 何か問題が発生したため、ランチャーは更新を完了できませんでした。\nただし、Hello Minecraftをダウンロードすることで、手動で更新を終了できます。 %s からのランチャー。\nこの問題を報告することを検討してください。 fatal.samba=If you are trying to run HMCL in a shared folder by Samba, HMCL may not working, please try updating your Java or running HMCL in a local folder. fatal.illegal_char=ユーザーフォルダーのパスに不正な文字'='が含まれています, ログインアカウントやオフラインログインではスキンの変更ができなくなり。 + feedback=フィードバック +feedback.channel=フィードバックチャンネル feedback.discord=Discord -feedback.discord.statement=チャットに参加しよう! +feedback.discord.statement=Discordサーバーに参加してください! +feedback.github=GitHub Issues +feedback.github.statement=GitHubで問題を送信します。 +feedback.qq_group=HMCLユーザーQQグループ +feedback.qq_group.statement=ユーザーQQグループに参加してください! file=ファイル @@ -347,12 +359,9 @@ game.crash.reason.entity=エンティティが原因でゲームを実行でき game.crash.reason.fabric_version_0_12=Fabric 0.12以降は、現在インストールされているmodと互換性がありません。ファブリックを0.11.7にダウングレードする必要があります。 game.crash.reason.fabric_warnings=Fabricはいくつかの警告を出します:\n%1$s game.crash.reason.modmixin_failure=mod インジェクションの失敗により、現在のゲームを続行できません。\nこれは通常、MOD にバグがあるか、現在の環境と互換性がないことを意味します。\n間違った mod のログを確認できます。 -game.crash.reason.file_or_content_verification_failed=一部のファイルまたはコンテンツの検証に失敗したため、現在のゲームに問題があります。\nこのバージョン (mod を含む) を削除して再ダウンロードするか、再ダウンロード時にプロキシを使用するなどしてください。 game.crash.reason.mod_repeat_installation=現在のゲームには複数の同一の Mod が繰り返しインストールされており、各 Mod は 1 回しか表示できません。ゲームを開始する前に繰り返しの Mod を削除してください。 game.crash.reason.forge_error=Forge がエラー メッセージを表示した可能性があります。 \nログを表示し、エラー レポートのログ情報に従って対応するアクションを実行できます。 \nエラー メッセージが表示されない場合は、エラー レポートをチェックして、エラーがどのように発生したかを知ることができます。\n%1$s game.crash.reason.mod_resolution0=mod の問題により、現在のゲームを続行できません。 \n間違った mod のログを確認できます。 -game.crash.reason.mod_profile_causes_game_crash=mod プロファイルに問題があるため、現在のゲームを続行できません。 \n障害のある mod とその構成ファイルのログを確認できます。 -game.crash.reason.fabric_reports_an_error_and_gives_a_solution=Forge がエラー メッセージを表示した可能性があります。 \nログを表示し、エラー レポートのログ情報に従って対応するアクションを実行できます。 \nエラー メッセージが表示されない場合は、エラー レポートをチェックして、エラーがどのように発生したかを知ることができます。 game.crash.reason.java_version_is_too_high=Java のバージョンが高すぎて実行を継続できないため、現在のゲームがクラッシュしました。 \nゲームを起動する前に、グローバル ゲーム設定またはゲーム固有の設定の Java パス タブで Java の以前のバージョンに切り替えてください。 \nそうでない場合は、java.com (Java8) または BellSoft Liberica Full JRE (Java17) およびその他のプラットフォームをダウンロードしてインストールします (インストール後にランチャーを再起動します)。 game.crash.reason.mod_name=現在のゲームはModファイル名の問題で続行できません。\nModファイル名は、英文の全半角の大文字と小文字(Aa ~ Zz)、数字(0 ~ 9)、横線(-)、アンダースコア(_)、点(.)のみを使用してください。\n上記のコンプライアンス文字をModフォルダに追加してください。 game.crash.reason.incomplete_forge_installation=Forge / NeoForge のインストールが不完全なため、現在のゲームを続行できません。\nバージョン設定 - 自動インストールで Forge / NeoForge をアンインストールしてから再インストールしてください。 @@ -397,7 +406,6 @@ game.crash.reason.stacktrace=不明。[ログ]ボタンをクリックすると game.crash.reason.too_old_java=Java仮想マシンのバージョンが低すぎるため、ゲームを実行できません。\nゲーム設定でJava仮想マシンの新しいバージョン(%1$s)に切り替えて、ゲームを再起動する必要があります。そうでない場合は、オンラインでダウンロードできます。 game.crash.reason.unknown=不明。「ログ」ボタンをクリックすると詳細を確認できます。 game.crash.reason.unsatisfied_link_error=必要なネイティブライブラリがないため、ゲームを実行できません。\n不足しているネイティブライブラリ:%1$s。\nゲーム設定でネイティブライブラリパスオプションを変更した場合は、元に戻すことをお勧めします。標準モードに切り替えます。\nすでに標準モードになっている場合は、不足しているネイティブライブラリがmodまたはゲームに属しているかどうかを確認してください。HMCLが原因であることが確実な場合は、フィードバックをお送りください。\nネイティブライブラリパスを本当にカスタマイズする必要がある場合は、ゲームに必要なすべてのネイティブライブラリをディレクトリに配置する必要があります。 -game.crash.reason.failed_to_load_a_library=ネイティブ ライブラリのロードに失敗したため、ゲームを実行できません。\nゲーム設定でネイティブライブラリパスオプションを変更した場合は、元に戻すことをお勧めします。標準モードに切り替えます。\nすでに標準モードになっている場合は、不足しているネイティブライブラリがmodまたはゲームに属しているかどうかを確認してください。HMCLが原因であることが確実な場合は、フィードバックをお送りください。\nネイティブライブラリパスを本当にカスタマイズする必要がある場合は、ゲームに必要なすべてのネイティブライブラリをディレクトリに配置する必要があります。 game.crash.title=ゲームがクラッシュしました game.directory=ゲームディレクトリ game.version=ゲームバージョン @@ -452,6 +460,36 @@ install.new_game.malformed=無効な名前 install.select=操作を選択します install.success=正常にインストールされました +java.add=Javaの追加 +java.add.failed=このJavaは無効であるか、現在のプラットフォームと互換性がない。 +java.disable=無効化 +java.disable.confirm=本当にこのJavaを無効にしますか? +java.disabled.management=無効なJava +java.disabled.management.remove=このJavaをリストから削除する +java.disabled.management.restore=有効化 +java.download=Javaをダウンロード +java.download.load_list.failed=バージョンリストの読み込みに失敗しました +java.download.more=その他のJavaディストリビューション +java.download.prompt=ダウンロードしたいJavaのバージョンを選択してください: +java.download.distribution=配布の種類 +java.download.version=バージョン +java.download.packageType=パッケージの種類 +java.management=Javaの管理 +java.info.architecture=アーキテクチャ +java.info.vendor=発行元 +java.info.version=バージョン +java.info.disco.distribution=配布の種類 +java.install=Javaのインストール +java.install.archive=場所 +java.install.failed.exists=この名前はすでに使用されている +java.install.failed.invalid=このアーカイブは有効なJavaインストール・パッケージではないため、インストールできません。 +java.install.failed.unsupported_platform=このJavaは現在のプラットフォームと互換性がないため、インストールできません。 +java.install.name=名前 +java.install.warning.invalid_character=名前に不正な文字が含まれています +java.reveal=Javaディレクトリを表示する +java.uninstall=このJavaをアンインストールする +java.uninstall.confirm=本当にこのJavaをアンインストールしますか?この操作は元に戻せません! + lang=日本語 lang.default=システム言語を使用する @@ -467,7 +505,6 @@ launch.advice.java8_51_1_13=Minecraft 1.13は、1.8.0_51より前のJava8でク launch.advice.java9=Java9以降のバージョンのJavaでMinecraft1.12以前を起動することはできません。 ゲームを高速化するには、launch.advice.newer_java=Java8をお勧めします。多くのMinecraft1.12以降、およびほとんどのModには、Java8が必要です。 launch.advice.not_enough_space=割り当てたメモリが多すぎます。物理メモリサイズが%dMBであるため、ゲームがクラッシュする可能性があります。 -launch.advice.require_newer_java_version=Minecraft %1$s にはJava %2$s 以降が必要です。今すぐダウンロードしますか? launch.advice.too_large_memory_for_32bit=32ビットJavaランタイム環境が原因で、割り当てたメモリが多すぎるため、ゲームがクラッシュする可能性があります。32ビットシステムの最大メモリ容量は1024MBです。 launch.advice.vanilla_linux_java_8=Linux x86-64の場合、Minecraft1.12.2以下はJava8でのみ実行できます。\nJava9以降のバージョンでは、liblwjgl.soなどの32ビットネイティブライブラリをロードできません。 launch.advice.vanilla_x86.translation=Minecraftは現在、x86およびx86-64以外のアーキテクチャの公式サポートを提供していません。\nJava for x86-64を使用して、トランスレータを介してminecraftを実行するか、プラットフォームの対応するネイティブライブラリをダウンロードして指定してくださいその配置パス。 @@ -489,7 +526,6 @@ launch.state.java=Javaバージョンの検出 launch.state.logging_in=ログイン launch.state.modpack=modpackを読み込んでいます launch.state.waiting_launching=ゲームの起動 -launch.wrong_javadir=無効なJavaディレクトリ、デフォルトのJavaパスが適用されます。 launcher=ランチャー launcher.agreement=EULA @@ -579,10 +615,7 @@ modpack.origin.mcbbs.prompt=スレッドID modpack.scan=このmodpackをスキャンしています modpack.task.install=Modpackのインポート modpack.task.install.error=このmodpackファイルは認識できません。CurseおよびMultiMCmodpackのみがサポートされています。 -modpack.task.install.will=modpackをインストールします: modpack.type.curse=Curse -modpack.type.curse.completion=Cursemodpackに関連するファイルをインストールします -modpack.type.curse.tolerable_error=このmodpackのすべてのファイルのダウンロードを完了できません。対応するゲームバージョンを開始するときに、ダウンロードを再試行できます。ネットワークの問題により、数回再試行する場合があります。 modpack.type.curse.error=このmodpackをインストールできません。再試行してください。 modpack.type.curse.not_found=必要なリソースの一部が欠落しているため、ダウンロードできませんでした。最新バージョンまたは他のmodpackを検討してください。 modpack.type.manual.warning=このmodpackをインポートする代わりに、おそらくこのmodpackファイルを直接解凍する必要があります。そして、バンドルされたランチャーを使用してゲームを起動します。このmodpackは、ランチャーによってエクスポートされるのではなく、.minecraftディレクトリを圧縮することによって手動で作成されます。HMCLはこのmodpackのインポートを試みることができます、続行しますか? @@ -768,8 +801,8 @@ settings.advanced.post_exit_command=終了後のコマンド settings.advanced.post_exit_command.prompt=ゲーム終了後に実行されます settings.advanced.server_ip=サーバーアドレス settings.advanced.server_ip.prompt=ゲームの起動時にサーバーに参加する -settings.advanced.use_native_glfw=システムGLFWを使用する -settings.advanced.use_native_openal=システムOpenALを使用する +settings.advanced.use_native_glfw=[Linux/FreeBSDのみ]システムGLFWを使用する +settings.advanced.use_native_openal=[Linux/FreeBSDのみ]システムOpenALを使用する settings.advanced.workaround=デバッグ用オプション settings.advanced.workaround.warning=デバッグオプションはプロフェッショナルのみ使用可能です。デバッグオプションにより、ゲームが起動しない場合があります。これらのオプションは、ご自分が何をしているのかが分からない限り、変更しないでください。 settings.advanced.wrapper_launcher=パッキングオーダー @@ -850,8 +883,8 @@ settings.type.special.edit=現在のゲーム設定を構成します settings.type.special.edit.hint=現在のゲームバージョン %s で特殊な設定が有効になっているため、現在のページのオプションはそのゲームバージョンに適用されません。リンクをクリックして、現在のゲーム設定を構成します。 sponsor=寄付 -sponsor.bmclapi=ダウンロードサービスはBMCLAPIによって提供されます。詳細については、ここをクリックしてください。 -sponsor.hmcl=こんにちはマインクラフト! Launcherは、無料のオープンソースのMinecraftランチャーであり、ユーザーは複数の個別のMinecraftインストールを簡単に管理できます。私たちはAfdianを使用して、ホスティングとプロジェクト開発の費用を引き続き支払います。詳細については、ここをクリックしてください。 +sponsor.bmclapi=中国本土向けのダウンロードサービスはBMCLAPIによって提供されています。詳細については、ここをクリックしてください。 +sponsor.hmcl=Hello Minecraft! Launcherは、無料のオープンソースのMinecraftランチャーであり、ユーザーは複数の個別のMinecraftインストールを簡単に管理できます。詳細については、ここをクリックしてください。 system.architecture=Arch system.operating_system=OS @@ -860,15 +893,14 @@ update=更新 update.accept=更新 update.changelog=変更 update.channel.dev=ベータ -update.channel.dev.hint=ベータ版を使用しています。ベータ版には、リリースバージョンと比較していくつかの追加機能が含まれている可能性があり、テストにのみ使用されます。\n\ - ベータ版は不安定なはずです!\n\ - 問題が発生した場合は、フィードバックページにアクセスして報告するか、チャット DiscordまたはQQで問題を報告します。\n\n\ - 現在のテスト版のヒントを非表示にするには、ここをクリックしてください。 +update.channel.dev.hint=ベータ版を使用しています。ベータ版には、リリースバージョンと比較していくつかの追加機能が含まれている可能性があり、テストにのみ使用されます。ベータ版は不安定なはずです!\n\ + \n\ + 問題が発生した場合は、フィードバックページにアクセスして報告してください。 update.channel.dev.title=ベータ版のヒント update.channel.nightly=アルファ -update.channel.nightly.hint=アルファ版を使用しています。これには、ベータ版およびリリース版と比較していくつかの追加機能が含まれている可能性があり、テストにのみ使用されます。\n\ - アルファ版は不安定です!\n\ - 問題が発生した場合は、フィードバックページにアクセスして報告するか、チャット Discordで問題を報告します。 +update.channel.nightly.hint=アルファ版を使用しています。これには、ベータ版およびリリース版と比較していくつかの追加機能が含まれている可能性があり、テストにのみ使用されます。アルファ版は不安定です!\n\ + \n\ + 問題が発生した場合は、フィードバックページにアクセスして報告してください。 update.channel.nightly.title=アルファ版のヒント update.channel.stable=リリース update.checking=更新の確認 diff --git a/HMCL/src/main/resources/assets/lang/I18N_ru.properties b/HMCL/src/main/resources/assets/lang/I18N_ru.properties index 9b7ca88be6..2938888937 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ru.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ru.properties @@ -1,44 +1,47 @@ # # Hello Minecraft! Launcher -# Авторские права (C) 2023 huangyuhui и участников +# Copyright (C) 2024 huangyuhui and contributors # -# Эта программа является бесплатным программным обеспечением: -# вы можете распространять и/или изменять её на условиях GNU -# General Public License, опубликованной Free Software Foundation, -# либо 3й версией лицензии, либо (по вашему выбору) любой версией выше. +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. # -# Эта программа распространяется в надежде, что она окажется полезной, -# но БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ; даже без подразумеваемых гарантий -# ТОВАРНОЙ ПРИГОДНОСТИ или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННЫХ ЦЕЛЕЙ. -# Подробности смотрите в Стандартной общественной лицензии GNU. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # -# Вместе с этой программой вы должны были получить копию GNU -# General Public License. Если нет, смотрите . +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . # -# Соавторы: vanja-san -# +# Contributors: vanja-san about=О лаунчере about.copyright=Авторские права about.copyright.statement=Авторские права © 2024 huangyuhui. about.author=Автор -about.author.statement=bilibili: @huanghongxun +about.author.statement=bilibili @huanghongxun about.claim=EULA about.claim.statement=Кликните на ссылку, чтобы увидеть весь текст. about.dependency=Зависимости about.legal=Юридическое подтверждение about.thanks_to=Отдельная благодарность -about.thanks_to.bangbang93.statement=За предоставление API скачивания BMCLAPI. Рассмотрите возможность пожертвования. +about.thanks_to.bangbang93.statement=За предоставление зеркала загрузки BMCLAPI, пожалуйста, подумайте о пожертвовании! +about.thanks_to.burningtnt.statement=Оказал большую техническую поддержку HMCL. about.thanks_to.contributors=Всем участникам на GitHub -about.thanks_to.contributors.statement=Без потрясающего сообщества с открытым исходным кодом, Hello Minecraft! Launcher не смог бы добраться так далеко. +about.thanks_to.contributors.statement=Без потрясающего сообщества с открытым исходным кодом, HMCL не смог бы добраться так далеко. about.thanks_to.gamerteam.statement=За предоставление фонового изображения. -about.thanks_to.glavo.statement=Оказал большую техническую поддержку HMCL. +about.thanks_to.glavo.statement=Отвечает за поддержание HMCL. about.thanks_to.zekerzhayard.statement=Оказал большую техническую поддержку HMCL. -about.thanks_to.zkitefly.statement=Оказал большую техническую поддержку HMCL. -about.thanks_to.mcmod=mcmod.cn -about.thanks_to.mcmod.statement=За предоставление перевода на китайский и вики для различных модов. +about.thanks_to.zkitefly.statement=Отвечает за поддержание документации HMCL. +about.thanks_to.mcbbs=MCBBS (Китайский форум Minecraft) +about.thanks_to.mcbbs.statement=За предоставление зеркала загрузки mcbbs.net для пользователей материкового Китая. (Больше недоступно) +about.thanks_to.mcmod=MCMod (mcmod.cn) +about.thanks_to.mcmod.statement=За предоставление упрощенного китайского перевода и вики для различных модов. about.thanks_to.red_lnn.statement=За предоставление фонового изображения. +about.thanks_to.shulkersakura.statement=За предоставление логотипа для HMCL. about.thanks_to.users=Членам группы пользователей HMCL about.thanks_to.users.statement=Спасибо за пожертвования, отчёты об ошибках и так далее. about.thanks_to.yushijinhun.statement=За предоставление поддержки, связанной с authlib-injector. @@ -133,8 +136,7 @@ account.username=Имя пользователя archive.author=Автор(ы) archive.date=Дата публикации -archive.game_version=Версия игры -archive.name=Имя файла +archive.file.name=Имя файла archive.version=Версия assets.download=Скачивание Assets @@ -158,8 +160,11 @@ button.retry=Повторить снова button.save=Сохранить button.save_as=Сохранить как button.select_all=Выбрать все +button.view=Просмотреть button.yes=Да +chat=Групповой чат + color.recent=Рекомендуемые color.custom=Пользовательский цвет @@ -185,6 +190,7 @@ curse.category.4482=Extra Large curse.category.4472=Tech curse.category.4473=Magic curse.category.5128=Vanilla+ +curse.category.7418=Horror # https://addons-ecs.forgesvc.net/api/v2/category/section/6 curse.category.5299=Education @@ -293,7 +299,6 @@ download=Скачать download.hint=Установите игры и пакеты модов или загрузите моды, пакеты ресурсов и карты download.code.404=Файл не найден на удалённом сервере: %s download.content=Игровой контент -download.curseforge.customization=Свет и тень, а также настройка игры download.curseforge.unavailable=Лаунчер версии Nightly не поддерживает доступ к CurseForge, используйте Release или Beta для скачивания. download.existing=Файл существует и по этому не может быть сохранён. Можно использовать «Сохранить как», чтобы сохранить файл в другом месте. download.external_link=Открыть сайт @@ -337,8 +342,13 @@ fatal.samba=Если вы пытаетесь запустить лаунчер fatal.illegal_char=Недопустимый символ '=' в пути к папке пользователя. Лаунчер может работать, но некоторые функции не будут работать.\nВы не сможете использовать аккаунт authlib-injector или изменить офлайн скин. feedback=Обратная связь +feedback.channel=Канал обратной связи feedback.discord=Discord -feedback.discord.statement=Присоединиться! +feedback.discord.statement=Добро пожаловать присоединиться к нашему Discord. +feedback.github=Проблемы GitHub +feedback.github.statement=Отправьте проблему на GitHub. +feedback.qq_group=Группа QQ пользователя HMCL +feedback.qq_group.statement=Добро пожаловать присоединиться к нашей группе QQ! file=Файл @@ -366,13 +376,10 @@ game.crash.reason.fabric_version_0_12=Fabric 0.12 (и выше) несовмес game.crash.reason.mixin_apply_mod_failed=Текущая игра не может продолжать работать из-за невозможности применения мода %1$s с помощью Mixin.\nВы можете попробовать удалить или обновить этот мод, чтобы решить проблему. game.crash.reason.fabric_warnings=Предупреждения от Fabric:\n%1$s game.crash.reason.modmixin_failure=Текущая игра не может продолжать работать из-за некоторых сбоев внедрения модов.\nОбычно это означает, что мод содержит ошибку или несовместим с текущей средой.\nВы можете проверить журнал на наличие неправильного мода. -game.crash.reason.file_or_content_verification_failed=В текущей игре возникла проблема, поскольку не удалось проверить некоторые файлы или содержимое.\nПожалуйста, попробуйте удалить эту версию (включая моды) и загрузить заново, или попробуйте использовать прокси при повторной загрузке и т. д. game.crash.reason.mod_repeat_installation=В текущей игре неоднократно установлено несколько одинаковых модов, и каждый мод может появиться только один раз. Пожалуйста, удалите повторяющиеся моды перед запуском игры. game.crash.reason.need_jdk11=Текущая игра не может продолжать работать из-за неподходящей версии виртуальной машины Java.\nВам необходимо загрузить и установить Java 11, а также установить версию Java, начинающуюся с 11, в глобальных (конкретных) настройках игры. game.crash.reason.forge_error=Возможно, Forge предоставил сообщение об ошибке.\nВы можете просмотреть журнал и выполнить соответствующие действия в соответствии с информацией журнала в отчете об ошибках.\nЕсли вы не видите сообщения об ошибке, вы можете просмотреть отчет об ошибке, чтобы узнать, как она возникла.\n%1$s game.crash.reason.mod_resolution0=Текущая игра не может продолжать работать из-за некоторых проблем с модом.\nВы можете проверить журнал на наличие неправильного мода. -game.crash.reason.mod_profile_causes_game_crash=Текущая игра не может продолжать работать из-за проблемы с профилем мода.\nВы можете проверить журнал на наличие неисправного мода и его конфигурационного файла. -game.crash.reason.fabric_reports_an_error_and_gives_a_solution=Forge мог выдать сообщение об ошибке.\nВы можете просмотреть журнал и выполнить соответствующие действия в соответствии с информацией журнала в отчете об ошибках.\nЕсли вы не видите сообщения об ошибке, вы можете просмотреть отчет об ошибке, чтобы узнать, как она возникла. game.crash.reason.java_version_is_too_high=Текущая игра аварийно завершилась из-за того, что версия Java слишком высока и не может продолжать работать.\nПеред запуском игры переключитесь на более раннюю версию Java на вкладке "Путь к Java" в глобальных настройках игры или в настройках игры.\nЕсли нет, вы можете загрузить его с java.com (Java8) или BellSoft Liberica Full JRE (Java17) и другие платформы, чтобы загрузить и установить ее (перезапустите программу запуска после установки). game.crash.reason.mod_name=Текущая игра не может продолжать работать из - за проблем с именем файла Mod. Имя файла\nMod должно быть использовано только в английском языке в полном углу с большими буквами (Aa ~ Zz), цифрами (0 ~ 9), горизонтальными линиями (-), подчеркнутыми (_) и точками (.) .\nПожалуйста, добавьте символ соответствия в папку Mod для всех несовместимых имен файлов Mod. game.crash.reason.incomplete_forge_installation=Текущая игра не может быть продолжена из-за незавершенной установки Forge / NeoForge.\nУдалите и переустановите Forge / NeoForge в настройках версии - Автоматическая установка. @@ -417,7 +424,6 @@ game.crash.reason.stacktrace=Неизвестно. Вы можете просм game.crash.reason.too_old_java=Невозможно запустить игру из-за того, что версия виртуальной машины Java слишком низкая.\nYou need to switch to a newer version (%1$s) of Java virtual machine in the game settings and restart the game. If not, you can download it online. game.crash.reason.unknown=Неизвестно. Вы можете просмотреть подробности, кликнув на кнопку «Журналы». game.crash.reason.unsatisfied_link_error=Невозможно запустить игру из-за того, что отсутствуют необходимые встроенные библиотеки.\nОтсутствующая нативная библиотека: %1$s.\nЕсли в настройках игры вы изменили параметр пути к нативной библиотеке, лучше переключиться обратно в стандартный режим.\nЕсли это уже стандартный режим, проверьте, принадлежит ли отсутствующая нативная библиотека моду или игре. Если вы уверены, что это произошло по вине HMCL, вы можете написать нам через обратную связь.\nЕсли вам действительно нужно настроить путь к нативным библиотекам, вы должны поместить в каталог все нативные библиотеки, которые требуются игре. -game.crash.reason.failed_to_load_a_library=Не удалось запустить игру, так как не удалось загрузить встроенную библиотеку.\nЕсли в настройках игры вы изменили параметр пути к нативной библиотеке, лучше переключиться обратно в стандартный режим.\nЕсли это уже стандартный режим, проверьте, принадлежит ли отсутствующая нативная библиотека моду или игре. Если вы уверены, что это произошло по вине HMCL, вы можете написать нам через обратную связь.\nЕсли вам действительно нужно настроить путь к нативным библиотекам, вы должны поместить в каталог все нативные библиотеки, которые требуются игре. game.crash.title=Игра вылетела game.directory=Путь игры game.version=Версия игры @@ -472,6 +478,36 @@ install.new_game.malformed=Недопустимое имя install.select=Выберите операцию install.success=Успешно установлено. +java.add=Добавить Java +java.add.failed=Этот Java недопустим или несовместим с текущей платформой. +java.disable=Отключить Java +java.disable.confirm=Вы уверены, что хотите отключить эту Java? +java.disabled.management=Отключенная Java +java.disabled.management.remove=Удалить эту Java из списка +java.disabled.management.restore=Включить эту Java +java.download=Скачать Java +java.download.load_list.failed=Не удалось загрузить список версий +java.download.more=Больше дистрибутивов Java +java.download.prompt=Выберите версию Java, которую вы хотите загрузить: +java.download.distribution=Дистрибуция +java.download.version=Версия +java.download.packageType=Тип упаковки +java.management=Управление Java +java.info.architecture=Архитектура +java.info.vendor=Поставщик +java.info.version=Версия +java.info.disco.distribution=Дистрибуция +java.install=Установите Java +java.install.archive=Расположение +java.install.failed.exists=Это имя уже используется +java.install.failed.invalid=Этот архив не является допустимым установочным пакетом Java, поэтому его установка невозможна. +java.install.failed.unsupported_platform=Эта Java несовместима с текущей платформой, поэтому ее невозможно установить. +java.install.name=Название +java.install.warning.invalid_character=Неправильный символ в названии +java.reveal=Откройте каталог Java +java.uninstall=Удалить Java +java.uninstall.confirm=Вы уверены, что хотите удалить эту Java? Это действие нельзя отменить! + lang=Русский lang.default=Использовать язык системы @@ -488,7 +524,6 @@ launch.advice.java8_51_1_13=Minecraft 1.13 может аварийно заве launch.advice.java9=Вы не сможете запустить Minecraft 1.12 и ниже с Java 9 и более новыми версиями Java. launch.advice.newer_java=Рекомендуется использовать Java 8, чтобы игра работала быстрее. Для многих версий Minecraft 1.12 и большинства модов требуется Java 8. launch.advice.not_enough_space=Вы выделили слишком много памяти, поскольку размер физической памяти составляет %d МБ, ваша игра может рухнуть. -launch.advice.require_newer_java_version=Minecraft %1$s требует Java %2$s или более новую версию, скачать её сейчас? launch.advice.too_large_memory_for_32bit=Вы выделили слишком много памяти, из-за 32-разрядной Java Runtime Environment ваша игра может рухнуть. Максимальный объем памяти для 32-разрядных систем составляет 1024 МБ. launch.advice.vanilla_linux_java_8=На Linux x86-64, Minecraft 1.12.2 и ниже может работать только на Java 8.\nВерсии Java 9 и выше не могут загружать 32-битные нативные библиотеки, такие как liblwjgl.so. launch.advice.modlauncher8=Используемая вами версия Forge несовместима с текущей версией Java. Попробуйте обновить Forge или начните с Java 8u312/11.0.13/17.0.1 или более ранней версии. @@ -513,7 +548,6 @@ launch.state.java=Проверка версии Java launch.state.logging_in=Вход в систему launch.state.modpack=Скачивание зависимостей launch.state.waiting_launching=Ожидание запуска игры -launch.wrong_javadir=Неверный путь Java, возврат к пути по умолчанию. launcher=Лаунчер launcher.agreement=Пользовательское соглашение @@ -604,10 +638,7 @@ modpack.origin.mcbbs.prompt=ID записи modpack.scan=Разбор индекса модпака modpack.task.install=Установить модпак modpack.task.install.error=Не удаётся идентифицировать этот модпак. Поддерживаются только модпаки Curse, Modrinth, MultiMC и MCBBS. -modpack.task.install.will=Вы собираетесь установить модпак\: modpack.type.curse=Curse -modpack.type.curse.completion=Установите файлы, связанные с модпаком Curse -modpack.type.curse.tolerable_error=Мы не можем завершить скачивание всех файлов этого модпака. Вы можете повторить скачивание при запуске соответствующей версии игры. Вы можете повторить попытку несколько раз из-за проблем с сетью. modpack.type.curse.error=Невозможно установить этот модпак. Повторите ещё раз. modpack.type.curse.not_found=Некоторые из необходимых ресурсов отсутствуют и поэтому не могут быть загружены. Скачайте последнюю версию или другие модпаки. modpack.type.manual.warning=Возможно, вам нужно напрямую распаковать файл этого модпака, а не импортировать его. И запустите игру с помощью прилагаемого лаунчера. Этот модпак создаётся вручную путем сжатия директории .minecraft, а не экспортируется лаунчером. HMCL может попытаться импортировать этот модпак, продолжать? @@ -835,8 +866,8 @@ settings.advanced.post_exit_command=Команда после выхода settings.advanced.post_exit_command.prompt=Команды, которые необходимо выполнить после выхода из игры settings.advanced.server_ip=Адрес сервера settings.advanced.server_ip.prompt=Присоединяться к серверу при запуске игры -settings.advanced.use_native_glfw=Использовать системный GLFW -settings.advanced.use_native_openal=Использовать системный OpenAL +settings.advanced.use_native_glfw=[Только для Linux/FreeBSD] Использовать системный GLFW +settings.advanced.use_native_openal=[Только для Linux/FreeBSD] Использовать системный OpenAL settings.advanced.workaround=Обходные пути settings.advanced.workaround.warning=Варианты обхода предназначены только для опытных пользователей. Изменение этих параметров может привести к вылету игры. Если не знаете, что делаете, то не изменяйте эти параметры. settings.advanced.wrapper_launcher=Команда-оболочка @@ -921,8 +952,8 @@ settings.type.special.edit=Изменить настройки текущего settings.type.special.edit.hint=В текущем экземпляре [%s] включены раздельные настройки для экземпляров, параметры на этой странице НЕ повлияют на этот экземпляр. Нажмите здесь, чтобы изменить раздельные параметры. sponsor=Спонсоры -sponsor.bmclapi=Скачивание предоставляется BMCLAPI. Нажмите здесь для получения подробностей. -sponsor.hmcl=Hello Minecraft! Launcher — лаунчер Minecraft с открытым исходным кодом, который позволяет пользователям легко управлять несколькими экземплярами Minecraft. Пожертвуйте нам на Afdian для поддержки нашего файлового хостинга и развития проекта. Для получения подробностей нажмите здесь. +sponsor.bmclapi=Загрузки для материкового Китая предоставляются BMCLAPI. Нажмите здесь для получения подробностей. +sponsor.hmcl=Hello Minecraft! Launcher — лаунчер Minecraft с открытым исходным кодом, который позволяет пользователям легко управлять несколькими экземплярами Minecraft. Для получения подробностей нажмите здесь. system.architecture=Архитектура system.operating_system=Операционная система @@ -931,15 +962,14 @@ update=Обновление update.accept=Обновить update.changelog=Изменения update.channel.dev=Бета -update.channel.dev.hint=Вы используете бета-версию, которая может включать некоторые дополнительные функции по сравнению с релизной версией, используемой только для тестирования.\n\ - Бета-версия может быть нестабильной!\n\ - Если вы встретили какие-то проблемы, вы можете зайти на страницу обратной связи, чтобы сообщить о них, или присоединиться к чату Discord или QQ, чтобы сообщить о проблемах.\n\n\ - Нажмите здесь, чтобы скрыть подсказку для текущей тестовой версии. +update.channel.dev.hint=Вы используете бета-версию, которая может включать некоторые дополнительные функции по сравнению с релизной версией, используемой только для тестирования. Бета-версия может быть нестабильной!\n\ + \n\ + Если вы встретили какие-то проблемы, вы можете зайти на страницу обратной связи. update.channel.dev.title=Подсказки для бета-версии update.channel.nightly=Альфа -update.channel.nightly.hint=Вы используете альфа-версию, которая может включать некоторые дополнительные функциональные возможности по сравнению с бета-версией и версией релиза, используемой только для тестирования.\n\ - Альфа-версия может быть нестабильной!\n\ - Если вы встретили какие-то проблемы, вы можете зайти на страницу обратной связи, чтобы сообщить о них, или присоединиться к чату Discord или QQ, чтобы сообщить о проблемах. +update.channel.nightly.hint=Вы используете альфа-версию, которая может включать некоторые дополнительные функциональные возможности по сравнению с бета-версией и версией релиза, используемой только для тестирования. Альфа-версия может быть нестабильной!\n\ + \n\ + Если вы встретили какие-то проблемы, вы можете зайти на страницу обратной связи. update.channel.nightly.title=Подсказки для альфа-версии update.channel.stable=Релиз update.checking=Проверка наличия обновлений diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 0063a9cb93..11938d23f9 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -1,6 +1,6 @@ # # Hello Minecraft! Launcher -# Copyright (C) 2023 huangyuhui and contributors +# Copyright (C) 2024 huangyuhui and contributors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -17,163 +17,156 @@ # # Contributors: pan93412 + about=關於 about.copyright=著作權 -about.copyright.statement=著作權所有 © 2024 huangyuhui。 +about.copyright.statement=著作權所有 © 2024 huangyuhui about.author=作者 -about.author.statement=bilibili: @huanghongxun -about.claim=用戶協議 +about.author.statement=bilibili @huanghongxun +about.claim=使用者協議 about.claim.statement=點擊連結以查看全文 about.dependency=相依元件 -about.legal=法律聲明 +about.legal=法律宣告 about.thanks_to=鳴謝 -about.thanks_to.bangbang93.statement=提供 BMCLAPI 下載地點。請贊助支持 BMCLAPI! -about.thanks_to.burningtnt.statement=為 HMCL 貢獻許多技術支持 -about.thanks_to.contributors=所有通過 Issues、Pull Requests 等管道參與本項目的貢獻者 -about.thanks_to.contributors.statement=沒有開源社區的支持,Hello Minecraft! Launcher 無法走到今天 +about.thanks_to.bangbang93.statement=提供 BMCLAPI 下載來源。請贊助支援 BMCLAPI! +about.thanks_to.burningtnt.statement=為 HMCL 貢獻許多技術支援 +about.thanks_to.contributors=所有透過 Issue、Pull Request 等管道參與本項目的貢獻者 +about.thanks_to.contributors.statement=沒有開源社群的支援,HMCL 無法走到今天 about.thanks_to.gamerteam.statement=提供預設背景圖 about.thanks_to.glavo.statement=負責 HMCL 的日常維護 -about.thanks_to.zekerzhayard.statement=為 HMCL 貢獻許多技術支持 -about.thanks_to.zkitefly.statement=負責維護 HMCL 的文檔 -about.thanks_to.mcbbs=MCBBS 我的世界中文論壇 +about.thanks_to.zekerzhayard.statement=為 HMCL 貢獻許多技術支援 +about.thanks_to.zkitefly.statement=負責維護 HMCL 的文件 +about.thanks_to.mcbbs=MCBBS (我的世界中文論壇) about.thanks_to.mcbbs.statement=提供 MCBBS 下載源 (現已停止服務) -about.thanks_to.mcmod=MC 百科 -about.thanks_to.mcmod.statement=提供 Mod 中文名映射表與 Mod 百科 +about.thanks_to.mcmod=MC 百科 (mcmod.cn) +about.thanks_to.mcmod.statement=提供模組簡體中文名映射表與模組百科 about.thanks_to.red_lnn.statement=提供預設背景圖 -about.thanks_to.shulkersakura.statement=提供 HMCL 的圖標 -about.thanks_to.users=HMCL 用戶群成員 -about.thanks_to.users.statement=感謝用戶群成員贊助充電、積極催更、迴響問題、出謀劃策 -about.thanks_to.yushijinhun.statement=authlib-injector 相关支援 +about.thanks_to.shulkersakura.statement=提供 HMCL 的標誌 +about.thanks_to.users=HMCL 使用者群組成員 +about.thanks_to.users.statement=感謝使用者群組成員贊助充電、積極催更、回報問題、出謀劃策 +about.thanks_to.yushijinhun.statement=提供 authlib-injector 相關支援 about.open_source=開放原始碼 about.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL/) account=帳戶 account.cape=披風 account.character=角色 -account.choose=請選擇角色 +account.choose=請選取角色 account.create=建立帳戶 -account.create.microsoft=添加微軟帳戶 -account.create.offline=添加離線模式帳戶 -account.create.authlibInjector=添加外置登入帳戶 (authlib-injector) +account.create.microsoft=加入 Microsoft 帳戶 +account.create.offline=加入離線模式帳戶 +account.create.authlibInjector=加入 authlib-injector 帳戶 account.email=電子信箱 -account.failed=帳戶刷新失敗 +account.failed=帳戶重新整理失敗 account.failed.character_deleted=已刪除此角色 -account.failed.connect_authentication_server=無法連線至認證伺服器,可能是網路問題,請檢查設備能否正常上網或使用代理服務 -account.failed.connect_injector_server=無法連線至認證伺服器,可能是網路故障,請檢查設備能否正常上網、檢查網址是否輸入錯誤或使用代理服務 -account.failed.injector_download_failure=無法下載 authlib-injector,請檢查網路或嘗試切換下載來源 -account.failed.invalid_credentials=您的使用者名稱或密碼錯誤,或者登入次數過多被暫時禁止登入,請稍後再試 +account.failed.connect_authentication_server=無法連線至認證伺服器,可能是網路問題,請檢查裝置能否正常上網或使用代理服務。 +account.failed.connect_injector_server=無法連線至認證伺服器,可能是網路故障,請檢查裝置能否正常上網、檢查網址是否輸入錯誤或使用代理服務。 +account.failed.injector_download_failure=無法下載 authlib-injector,請檢查網路或嘗試切換下載來源。 +account.failed.invalid_credentials=你的使用者名稱或密碼錯誤,或者登入次數過多被暫時禁止登入,請稍後再試。 account.failed.invalid_password=密碼無效 -account.failed.invalid_token=請嘗試登出並重新輸入密碼登入 -account.failed.migration=你的帳號需要被遷移至微軟帳號。如果你已經遷移,你需要使用微軟登錄方式登錄遷移後的微軟帳號 -account.failed.no_character=該帳戶沒有角色 -account.failed.server_disconnected=無法訪問登錄伺服器,賬戶信息刷新失敗。\n\ - 您可以選擇“再次刷新賬戶”重新嘗試。\n\ - 您也可以選擇“跳過賬戶刷新”繼續啟動遊戲,但可能會使賬戶信息不是最新的。\n\ - 若最近沒有刷新賬戶信息,則可能導致賬戶信息過期失效,\n\ - 若為 微軟賬戶 啟動遊戲,賬戶信息過期失效可能將無法進入需賬戶驗證的伺服器。\n\ - 若嘗試多次無法成功刷新,可嘗試重新添加該賬戶嘗試解決該問題。 -account.failed.server_response_malformed=無法解析認證伺服器回應,可能是伺服器故障 -account.failed.ssl=連接服務器時發生了 SSL 錯誤,可能網站證書已過期或你使用的 Java 版本過低,請嘗試更新 Java -account.failed.wrong_account=登錄了錯誤的帳號 -account.hmcl.hint=你需要點擊“登入”按鈕,並在打開的網頁中完成登入 +account.failed.invalid_token=請嘗試登出並重新輸入密碼登入。 +account.failed.migration=你的帳戶需要遷移至 Microsoft 帳戶。如果你已經遷移,你需要使用遷移後的 Microsoft 帳戶登入。 +account.failed.no_character=該帳戶沒有角色。 +account.failed.server_disconnected=無法訪問登入伺服器,帳戶資訊重新整理失敗。\n\ + 你可以選取「再次重新整理帳戶」重新嘗試。\n\ + 你也可以選取「跳過帳戶重新整理」繼續啟動遊戲,但可能會導致帳戶資訊未同步更新。\n\ + 若最近沒有重新整理帳戶資訊,則可能導致帳戶資訊過期失效。\n\ + 若使用 Microsoft/authlib-injector 帳戶啟動遊戲,帳戶資訊過期失效可能將無法進入需線上驗證的伺服器。\n\ + 若嘗試多次無法重新整理,可嘗試重新加入該帳戶,或許可以解決該問題。 +account.failed.server_response_malformed=無法解析認證伺服器回應,可能是伺服器故障。 +account.failed.ssl=連線伺服器時發生了 SSL 錯誤,可能網站證書已過期或你使用的 Java 版本過低,請嘗試更新 Java。 +account.failed.wrong_account=登入了錯誤的帳戶 +account.hmcl.hint=你需要點擊「登入」按鈕,並在打開的網頁中完成登入 account.injector.add=新增認證伺服器 account.injector.empty=無 (按一下右側 + 加入) -account.injector.http=警告: 此伺服器使用不安全的 HTTP 協定,您的密碼在登入時會被明文傳輸。 +account.injector.http=警告: 此伺服器使用不安全的 HTTP 協定,你的密碼在登入時會被明文傳輸。 account.injector.link.homepage=首頁 account.injector.link.register=註冊 account.injector.server=認證伺服器 account.injector.server_url=伺服器位址 account.injector.server_name=伺服器名稱 account.login=登入 -account.login.hint=我們不會保存你的密碼 -account.login.skip=跳過刷新帳戶 -account.login.retry=再次刷新帳戶 -account.login.refresh=重新登錄 -account.login.refresh.microsoft.hint=因為賬戶授權失效,你需要重新添加微軟賬戶 +account.login.hint=我們不會儲存你的密碼 +account.login.skip=跳過重新整理帳戶 +account.login.retry=再次重新整理帳戶 +account.login.refresh=重新登入 +account.login.refresh.microsoft.hint=由於帳戶授權失效,你需要重新加入 Microsoft 帳戶 account.logout=登出 account.register=註冊 -account.manage=帳戶列表 -account.copy_uuid=複製該賬戶的 UUID。 +account.manage=帳戶清單 +account.copy_uuid=複製該帳戶的 UUID account.methods=登入方式 account.methods.authlib_injector=authlib-injector 登入 -account.methods.microsoft=微軟帳戶 -account.methods.microsoft.birth=如何修改帳戶出生日期 -account.methods.microsoft.deauthorize=解除帳戶授權 -account.methods.microsoft.close_page=已完成微軟帳號授權,接下來啟動器還需要完成剩餘登入步驟。你已經可以關閉本頁面了。 -account.methods.microsoft.error.add_family=由於你未滿 18 歲,你的帳戶必須被加入到家庭中才能登錄遊戲。你也可以點擊上方連結更改你的帳戶的出生日期,使年齡滿 18 歲以上以繼續登錄。 -account.methods.microsoft.error.add_family_probably=請檢查你的帳戶設置,如果你未滿 18 歲,你的帳戶必須被加入到家庭中才能登錄遊戲。你也可以點擊上方連結更改你的帳戶的出生日期,使年齡滿 18 歲以上以繼續登錄。 -account.methods.microsoft.error.country_unavailable=你所在的國家或地區不受 XBox Live 的支援。 -account.methods.microsoft.error.missing_xbox_account=你的微軟帳號尚未關聯 XBox 帳號,你必須先創建 XBox 帳號,才能登入遊戲。 -account.methods.microsoft.error.no_character=該帳戶未包含 Minecraft Java 版購買記錄\n可能未創建遊戲檔案,請點擊上方鏈接創建 +account.methods.microsoft=Microsoft 帳戶 +account.methods.microsoft.birth=如何變更帳戶出生日期 +account.methods.microsoft.deauthorize=移除應用存取權 +account.methods.microsoft.close_page=已完成 Microsoft 帳戶授權,接下來啟動器還需要完成其餘登入步驟。你現在可以關閉本頁面了。 +account.methods.microsoft.error.add_family=由於你未滿 18 歲,你的帳戶必須被加入到家庭中才能登入遊戲。你也可以點擊上方「編輯帳戶配置檔」更改你的帳戶出生日期,使年齡滿 18 歲以上以繼續登入。 +account.methods.microsoft.error.add_family_probably=請檢查你的帳戶設定,如果你未滿 18 歲,你的帳戶必須被加入到家庭中才能登入遊戲。你也可以點擊上方「編輯帳戶配置檔」更改你的帳戶出生日期,使年齡滿 18 歲以上以繼續登入。 +account.methods.microsoft.error.country_unavailable=你所在的國家或地區不受 Xbox Live 的支援。 +account.methods.microsoft.error.missing_xbox_account=你的 Microsoft 帳戶尚未關聯 Xbox 帳戶,你必須先建立 Xbox 帳戶,才能登入遊戲。 +account.methods.microsoft.error.no_character=該帳戶未包含 Minecraft: Java 版購買記錄。\n若已購買,則可能未建立遊戲檔案,請點擊上方連結建立。 account.methods.microsoft.error.unknown=登入失敗,錯誤碼:%d -account.methods.microsoft.logging_in=登入中... -account.methods.microsoft.makegameidsettings=創建檔案/編輯檔案名稱 -account.methods.microsoft.hint=你需要按照以下步驟添加賬戶:\n\ - 1.點擊“登入”按鈕\n\ - 2.在網頁瀏覽器顯示的網站中輸入 HMCL 顯示的代碼(自動拷貝,直接粘貼即可),並點擊“下一步”\n\ - 3.按照網站的提示登入\n\ - 4.當網站顯示“ 是否允許此應用訪問你的信息?”的標識時,請點擊“是”\n\ - 5.在網站提示“大功告成”後,只需等待帳戶完成添加即可\n\ - 若網站提示“出现错误”的標識,請嘗試重新按照以上步驟登入\n\ - -若賬戶添加失敗,請嘗試重新按照以上步驟登入\n\ - -若登入微軟賬號的 Token 洩露,可點擊下方“解除帳戶授權”解除登入授權,然後等待 Token 過期\n\ +account.methods.microsoft.error.wrong_verify_method=請在 Microsoft 帳戶登入頁面使用密碼登入,不要使用驗證碼登入。 +account.methods.microsoft.logging_in=登入中…… +account.methods.microsoft.makegameidsettings=建立檔案 / 編輯檔案名稱 +account.methods.microsoft.hint=請點擊「登入」按鈕,稍後複製此處顯示的代碼,在打開的登入網頁中完成登入過程。\n\ + \n\ + 如果登入 Microsoft 帳戶的令牌洩露,可點擊下方「移除應用存取權」,然後等待令牌過期。\n\ + \n\ 如遇到問題,你可以點擊右上角幫助按鈕進行求助。 -account.methods.microsoft.manual=你需要按照以下步驟添加账户:\n\ - 1.點擊“登入”按鈕\n\ - 2.在網頁瀏覽器顯示的網站中輸入該代碼:%1$s(已自動拷貝,請點此處再次拷貝),並點擊“下一步”\n\ - 3.按照網站的提示登入\n\ - 4.當網站顯示“ 是否允許此應用訪問你的信息?”的標識時,請點擊“是”\n\ - 5.在網站提示“大功告成”後,只需等待帳戶完成添加即可\n\ - -若網站提示“出现错误”的標識或賬戶添加失敗時,請嘗試重新按照以上步驟登入\n\ - -若網站未能打開,請手動在網頁瀏覽器中打開:%2$s\n\ - -若登入微軟賬號的 Token 洩露,可點擊下方“解除帳戶授權”解除登入授權,然後等待 Token 過期\n\ +account.methods.microsoft.manual=你的代碼為 %1$s,請點擊此處複製。\n\ + \n\ + 點擊「登入」按鈕後,你應該在打開的登入網頁中完成登入過程。如果網頁沒有打開,你可以自行在瀏覽器中訪問 %2$s\n\ + \n\ + 如果登入 Microsoft 帳戶的令牌洩露,可點擊下方「移除應用存取權」,然後等待令牌過期。\n\ + \n\ 如遇到問題,你可以點擊右上角幫助按鈕進行求助。 -account.methods.microsoft.profile=帳戶設置頁 +account.methods.microsoft.profile=編輯帳戶配置檔 account.methods.microsoft.purchase=購買 Minecraft account.methods.forgot_password=忘記密碼 -account.methods.microsoft.snapshot=你正在使用非官方構建的 HMCL,請下載官方構建進行微軟登入。 +account.methods.microsoft.snapshot=你正在使用第三方提供的 HMCL,請下載官方版本進行登入。 account.methods.microsoft.snapshot.website=官方網站 account.methods.offline=離線模式 -account.methods.offline.name.special_characters=建議使用英文字符、數字以及下劃線命名 -account.methods.offline.name.invalid=正常情況下,遊戲用戶名只能包括英文字符、數字以及下劃線,且長度不能超過 16 個字符。\n\ - 一些合法的用戶名:HuangYu,huang_Yu,Huang_Yu_123;\n\ - 一些不合法的用戶名:黃魚,Huang Yu,Huang-Yu_%%%,Huang_Yu_hello_world_hello_world。\n\ - 如果你相信服務器端有相應的 Mod 或插件來解除此限制,你可以忽略本警告。 +account.methods.offline.name.special_characters=建議使用英文字母、數字以及底線命名 +account.methods.offline.name.invalid=遊戲使用者名稱通常僅允許使用英文字母、數字及底線,且長度不能超過 16 個字元。\n\ + \ · 一些有效的使用者名稱:HuangYu、huang_Yu、Huang_Yu_123;\n\ + \ · 一些無效的使用者名稱:黃魚,Huang Yu、Huang-Yu_%%%、Huang_Yu_hello_world_hello_world。\n\ + 如果你相信伺服器端有相應的模組或插件來解除此限制,你可以忽略本警告。 account.methods.offline.uuid=UUID -account.methods.offline.uuid.hint=UUID 是 Minecraft 對玩家角色的唯一標識符,每個啟動器生成 UUID 的方式可能不同。通過修改 UUID 選項至原啟動器所生成的 UUID,你可以保證在切換啟動器後,遊戲還能將你的遊戲角色識別為給定 UUID 所對應的角色,從而保留原來角色的背包物品。UUID 選項為高級選項,除非你知道你在做什麼,否則你不需要調整該選項。 +account.methods.offline.uuid.hint=UUID 是 Minecraft 玩家的唯一標識符,每個啟動器生成 UUID 的方式可能不同。\n透過修改 UUID 選項至原啟動器所生成的 UUID,你可以保證在切換啟動器後,遊戲還能將你的遊戲角色識別為給定 UUID 所對應的角色,從而保留原來角色的背包物品。\nUUID 選項為進階選項,除非你知道你在做什麼,否則你不需要調整該選項。 account.methods.offline.uuid.malformed=格式錯誤 account.missing=沒有遊戲帳戶 account.missing.add=按一下此處加入帳戶 -account.move_to_global=轉換為全域帳戶 -account.move_to_portable=轉換為便攜帳戶 +account.move_to_global=轉換為全域帳戶\n該帳戶的資訊會儲存至系統目前使用者目錄的配置檔案中 +account.move_to_portable=轉換為可攜式帳戶\n該帳戶的資訊會儲存至與 HMCL 同目錄的配置檔案中 account.not_logged_in=未登入 account.password=密碼 -account.portable=便攜帳戶 -account.skin=皮膚 -account.skin.file=皮膚圖片檔案 +account.portable=可攜式帳戶 +account.skin=外觀 +account.skin.file=外觀圖片檔案 account.skin.model=模型 -account.skin.model.default=經典 -account.skin.model.slim=苗條 +account.skin.model.default=寬型 +account.skin.model.slim=纖細 account.skin.type.csl_api=Blessing Skin 伺服器 account.skin.type.csl_api.location=伺服器位址 account.skin.type.csl_api.location.hint=CustomSkinAPI 位址 account.skin.type.little_skin=LittleSkin 皮膚站 -account.skin.type.little_skin.hint=你需要在皮膚站中創建並使用和該離線帳戶角色同名角色,此時離線帳戶皮膚將為皮膚站上角色所設定的皮膚。 -account.skin.type.local_file=本地皮膚圖片檔案 -account.skin.upload=上傳皮膚 -account.skin.upload.failed=皮膚上傳失敗 -account.skin.invalid_skin=無法識別的皮膚檔案 +account.skin.type.little_skin.hint=你需要在皮膚站中加入並使用和該離線帳戶同名角色,此時離線帳戶外觀將為皮膚站上對應角色所設定的外觀。 +account.skin.type.local_file=本機外觀圖片檔案 +account.skin.upload=上傳/編輯外觀 +account.skin.upload.failed=外觀上傳失敗 +account.skin.invalid_skin=無法識別的外觀檔案 account.username=使用者名稱 archive.author=作者 archive.date=發布日期 -archive.game_version=遊戲版本 -archive.name=名稱 +archive.file.name=檔案名稱 archive.version=版本 assets.download=下載資源 assets.download_all=驗證資源檔案完整性 -assets.index.malformed=資源檔案的索引檔案損壞,您可以在遊戲 [設定] 頁面右上角的設定按鈕中選擇 [更新遊戲資源檔案],以修復該問題 +assets.index.malformed=資源檔案的索引檔案損壞,你可以在相應實例的「實例管理」頁面中,點擊頁面左下角的「設定 → 更新遊戲資源檔案」以修復該問題。 button.cancel=取消 button.change_source=切換下載源 @@ -187,11 +180,12 @@ button.no=否 button.ok=確定 button.refresh=重新整理 button.remove=刪除 -button.remove.confirm=您確認要刪除嗎?該操作無法撤銷! +button.remove.confirm=你確認要刪除嗎?該操作無法復原! button.retry=重試 button.save=儲存 -button.save_as=另存為 +button.save_as=另存新檔 button.select_all=全選 +button.view=查看 button.yes=是 chat=官方群組 @@ -199,15 +193,15 @@ chat=官方群組 color.recent=建議 color.custom=自訂顏色 -crash.NoClassDefFound=請確認 Hello Minecraft! Launcher 本體是否完整,或更新您的 Java。 -crash.user_fault=您的系統或 Java 環境可能安裝不當導致本軟體當機,請檢查您的 Java 環境或您的電腦! 可以嘗試重新安裝 Java。 +crash.NoClassDefFound=請確認 Hello Minecraft! Launcher 本體是否完整,或更新你的 Java。 +crash.user_fault=你的系統或 Java 環境可能安裝不當導致本軟體當機,請檢查你的 Java 環境或你的電腦!可以嘗試重新安裝 Java。 curse.category.0=全部 # https://addons-ecs.forgesvc.net/api/v2/category/section/4471 curse.category.4474=科幻 curse.category.4481=輕量模組包 -curse.category.4483=戰鬥 PVP +curse.category.4483=戰鬥 PvP curse.category.4477=小遊戲 curse.category.4478=任務 curse.category.4484=多人 @@ -221,20 +215,21 @@ curse.category.4482=大型模組包 curse.category.4472=科技 curse.category.4473=魔法 curse.category.5128=原版增強 +curse.category.7418=恐怖 # https://addons-ecs.forgesvc.net/api/v2/category/section/6 curse.category.5299=教育 curse.category.5232=額外行星 curse.category.5129=原版增強 -curse.category.5189=實用與QOL +curse.category.5189=實用與 QOL curse.category.6814=性能 -curse.category.6954=動態聯合/集成動力 (Integrated Dynamics) +curse.category.6954=動態聯合/整合動力 (Integrated Dynamics) curse.category.6484=機械動力 (Create) curse.category.6821=錯誤修復 curse.category.6145=空島 -curse.category.5190=QoL -curse.category.5191=實用與QoL -curse.category.5192=夢幻菜單 +curse.category.5190=QOL +curse.category.5191=實用與 QOL +curse.category.5192=夢幻選單 curse.category.423=訊息展示 curse.category.426=模組擴展 curse.category.434=裝備武器 @@ -263,7 +258,7 @@ curse.category.433=林業 (Forestry) curse.category.425=其他 curse.category.4545=應用能源 2 (Applied Energistics 2) curse.category.416=農業 -curse.category.421=支持庫 +curse.category.421=支援庫 curse.category.4780=Fabric curse.category.424=裝飾 curse.category.406=世界生成 @@ -291,7 +286,7 @@ curse.category.393=16x curse.category.403=傳統 curse.category.394=32x curse.category.404=動態效果 -curse.category.4465=模組支持 +curse.category.4465=模組支援 curse.category.402=中世紀風格 curse.category.401=現代風格 @@ -315,369 +310,389 @@ curse.category.4549=指引書 curse.category.4547=配置 curse.category.4550=任務 curse.category.4555=世界生成 -curse.category.4552=腳本 +curse.category.4552=指令碼 curse.sort.author=作者 -curse.sort.date_created=創建日期 +curse.sort.date_created=建立日期 curse.sort.last_updated=最近更新 curse.sort.name=名稱 curse.sort.popularity=熱度 curse.sort.total_downloads=下載量 download=下載 -download.hint=安裝遊戲和整合包或下載模組、資源包和地圖 -download.code.404=遠端伺服器沒有需要下載的檔案: %s +download.hint=安裝遊戲和模組包或下載模組、資源包和地圖 +download.code.404=遠端伺服器沒有需要下載的檔案:%s download.content=遊戲內容 -download.curseforge.customization=光影與遊戲定制 -download.curseforge.unavailable=HMCL 預覽版暫不支持訪問 CurseForge,請使用穩定版或測試版進行下載。 -download.existing=檔案已存在,無法保存。你可以選擇另存為將檔案保存至其他地方。 +download.curseforge.unavailable=HMCL 預覽版暫不支援訪問 CurseForge,請使用穩定版或開發版進行下載。 +download.existing=檔案已存在,無法儲存。你可以將檔案儲存至其他地方。 download.external_link=打開下載網站 -download.failed=下載失敗: %1$s,錯誤碼:%2$d -download.failed.empty=沒有能安裝的版本,按一下此處返回。 -download.failed.no_code=下載失敗: %s -download.failed.refresh=載入版本列表失敗,按一下此處重試。 +download.failed=下載失敗:%1$s\n錯誤碼:%2$d +download.failed.empty=沒有可供安裝的版本,按一下此處返回。 +download.failed.no_code=下載失敗:%s +download.failed.refresh=載入版本清單失敗,按一下此處重試。 download.game=新遊戲 -download.provider.bmclapi=BMCLAPI (bangbang93,https://bmclapi2.bangbang93.com/) -download.provider.mojang=官方伺服器 (OptiFine 自動安裝的下載來源是 BMCLAPI) -download.provider.official=儘量使用官方源(最新,但可能加載慢) -download.provider.balanced=選擇加載速度快的下載源(平衡,但可能不是最新) -download.provider.mirror=儘量使用鏡像源(加載快,但可能不是最新) +download.provider.bmclapi=BMCLAPI (bangbang93, https://bmclapi2.bangbang93.com/) +download.provider.mojang=官方伺服器 (OptiFine 由 BMCLAPI 提供) +download.provider.official=盡量使用官方源 (最新,但可能載入慢) +download.provider.balanced=選取載入速度快的下載源 (平衡,但可能不是最新) +download.provider.mirror=盡量使用鏡像源 (載入快,但可能不是最新) download.java=下載 Java -download.javafx=正在下載必要的運行時組件 -download.javafx.notes=正在通過網絡下載 HMCL 必要的運行時組件。\n點擊“切換下載源”按鈕查看詳情以及選擇下載源,點擊“取消”按鈕停止並退出。\n注意:如果下載速度過慢,請嘗試切换下載源。 -download.javafx.component=正在下載模塊 %s +download.java.override=此 Java 版本已經存在,是否移除並重新安裝? +download.javafx=正在下載必要的執行時元件 +download.javafx.notes=正在透過網路下載 HMCL 必要的執行時元件。\n點擊「切換下載源」按鈕查看詳情以及選取下載源,點擊「取消」按鈕停止並退出。\n注意:如果下載速度過慢,請嘗試切換下載源。 +download.javafx.component=正在下載元件「%s」 download.javafx.prepare=準備開始下載 -exception.access_denied=因為無法存取檔案 %s,HMCL 沒有對該文件的訪問權限,或者該文件被其他程序打開。\n\ - 請你檢查當前操作系統帳戶是否能訪存取檔案,比如非管理員用戶可能不能訪問其他帳戶的個人資料夾內的文件。\n\ - 對於 Windows 用戶,你還可以嘗試通過資源監視器查看是否有程序占用了該文件,如果是,你可以關閉占用此文件相關程序,或者重啟電腦再試。 -exception.artifact_malformed=下載的文件正確,無法通過校驗。 -exception.ssl_handshake=無法建立 SSL 連接,因為當前 Java 虛擬機缺少相關的 SSL 證書。你可以嘗試使用其他的 Java 虛擬機或關閉網絡代理啟動 HMCL 再試。 +exception.access_denied=無法存取檔案「%s」,因為 HMCL 沒有對該檔案的訪問權限,或者該檔案已被其他程式打開。\n\ + 請你檢查目前作業系統帳戶是否能訪存取檔案,比如非管理員使用者可能不能訪問其他帳戶的個人目錄內的檔案。\n\ + 對於 Windows 使用者,你還可以嘗試透過資源監視器查看是否有程式占用了該檔案,如果是,你可以關閉占用此檔案的程式,或者重啟電腦再試。 +exception.artifact_malformed=下載的檔案正確,但無法透過校驗。 +exception.ssl_handshake=無法建立 SSL 連線,因為目前 Java 虛擬機缺少相關的 SSL 證書。你可以嘗試使用其他的 Java 虛擬機或關閉網路代理開啟 HMCL 再試。 -extension.bat=Windows 指令碼 +extension.bat=Windows 批次檔 extension.mod=模組檔案 extension.png=圖片檔案 extension.ps1=PowerShell 指令碼 extension.sh=Bash 指令碼 -fatal.fractureiser=Hello Minecraft! Launcher 檢測到你的電腦被 Fractureiser 病毒感染,存在嚴重安全問題。\n請立即使用殺毒軟體進行全盤查殺,隨後修改你在此電腦上登入過的所有帳號的密碼。 -fatal.javafx.incompatible=缺少 JavaFX 運行環境。\nHMCL 無法在低於 Java 11 的 Java 環境上自行補全 JavaFX 運行環境,請更新到 Java 11 或更高版本。 -fatal.javafx.incomplete=JavaFX 運行環境不完整,請嘗試更換你的 Java 或者重新安裝 OpenJFX。 -fatal.javafx.missing=缺少 JavaFX 運行環境,請使用包含 OpenJFX 的 Java 運行環境啟動 Hello Minecraft! Launcher。\n你可以訪問 https://docs.hmcl.net/help.html 頁面尋求幫助。 -fatal.config_change_owner_root=你正在使用 root 帳戶啟動 Hello Minecraft! Launcher,這可能導致你未來無法使用其他帳戶正常啟動 Hello Minecraft! Launcher。\n是否繼續啟動? -fatal.config_in_temp_dir=你正在臨時資料夾中啟動 Hello Minecraft! Launcher,你的設定和遊戲數據可能會遺失,建議將 HMCL 移動至其他位置再啟動。\n是否繼續啟動? -fatal.config_loading_failure=Hello Minecraft! Launcher 無法載入設定檔案。\n請確保 Hello Minecraft! Launcher 對 "%s" 目錄及該目錄下的檔案擁有讀寫權限。 -fatal.config_loading_failure.unix=Hello Minecraft! Launcher 無法載入設定檔案,因為設定檔案是由用戶 %1$s 創建的。\n請使用 root 帳戶啟動 HMCL (不推薦),或在終端中執行以下命令將設定檔案的所有權變更為當前用戶:\n%2$s -fatal.mac_app_translocation=由於 macOS 的安全機制,Hello Minecraft! Launcher 被系統隔離至臨時資料夾中。\n請將Hello Minecraft! Launcher 移動到其他資料夾後再嘗試啟動,否則你的設定和遊戲數據可能會在重啟後遺失。\n是否繼續啟動? -fatal.migration_requires_manual_reboot=Hello Minecraft! Launcher 即將升級完成,請重新開啟 Hello Minecraft! Launcher。 -fatal.apply_update_failure=我們很抱歉 Hello Minecraft! Launcher 無法自動完成升級程式,因為出現了一些問題。\n但你依然可以從 %s 處手動下載 Hello Minecraft! Launcher 來完成升級。\n你可以訪問 https://docs.hmcl.net/help.html 網頁進行迴響。 -fatal.apply_update_need_win7=Hello Minecraft! Launcher 無法在 Windows XP/Vista 上進行自動更新,請從 %s 處手動下載 Hello Minecraft! Launcher 來完成升級。 -fatal.samba=如果您正在通過 Samba 共亯的資料夾中運行 Hello Minecraft! Launcher,啟動器可能無法正常工作,請嘗試更新您的 Java 或在本地資料夾內運行 HMCL。 -fatal.illegal_char=由於您的用戶資料夾路徑中存在非法字元‘=’,您將無法使用外置登入帳戶以及離線登入更換皮膚功能。 -fatal.unsupported_platform=Minecraft 尚未你您的平臺提供完善支持,所以可能影響遊戲體驗或無法啟動遊戲。\n若無法啟動 Minecraft 1.17 及以上版本,可以嘗試在版本設定中打開“使用 OpenGL 軟渲染器”選項,使用 CPU 渲染以獲得更好的相容性。 +fatal.javafx.incompatible=缺少 JavaFX 執行環境。\nHMCL 無法在低於 Java 11 的 Java 環境上自行補全 JavaFX 執行環境,請更新到 Java 11 或更高版本。 +fatal.javafx.incomplete=JavaFX 執行環境不完整,請嘗試更換你的 Java 或者重新安裝 OpenJFX。 +fatal.javafx.missing=缺少 JavaFX 執行環境,請使用包含 OpenJFX 的 Java 執行環境開啟 Hello Minecraft! Launcher。 +fatal.config_change_owner_root=你正在使用 root 帳戶開啟 Hello Minecraft! Launcher,這可能導致你未來無法使用其他帳戶正常開啟 HMCL。\n是否繼續開啟? +fatal.config_in_temp_dir=你正在暫存目錄中開啟 Hello Minecraft! Launcher,你的設定和遊戲資料可能會遺失,建議將 HMCL 移動至其他位置再開啟。\n是否繼續開啟? +fatal.config_loading_failure=Hello Minecraft! Launcher 無法載入設定檔案。\n請確保 HMCL 對「%s」目錄及該目錄下的檔案擁有讀寫權限。 +fatal.config_loading_failure.unix=Hello Minecraft! Launcher 無法載入設定檔案,因為設定檔案是由使用者「%1$s」建立的。\n請使用 root 帳戶開啟 HMCL (不推薦),或在終端中執行以下指令將設定檔案的所有權變更為目前使用者:\n%2$s +fatal.mac_app_translocation=由於 macOS 的安全機制,Hello Minecraft! Launcher 被系統隔離至暫存目錄中。\n請將 HMCL 移動到其他目錄後再嘗試開啟,否則你的設定和遊戲資料可能會在重啟後遺失。\n是否繼續開啟? +fatal.migration_requires_manual_reboot=Hello Minecraft! Launcher 即將升級完成,請重新開啟 HMCL。 +fatal.apply_update_failure=我們很抱歉 Hello Minecraft! Launcher 無法自動完成升級程式,因為出現了一些問題。\n但你依然可以從 %s 處手動下載 HMCL 來完成升級。 +fatal.apply_update_need_win7=Hello Minecraft! Launcher 無法在 Windows XP/Vista 上進行自動更新,請從 %s 處手動下載 HMCL 來完成升級。 +fatal.samba=如果您正在透過 Samba 共亯的目錄中開啟 Hello Minecraft! Launcher,啟動器可能無法正常工作,請嘗試更新您的 Java 或在本機目錄內開啟 HMCL。 +fatal.illegal_char=由於您的使用者目錄路徑中存在無效字元『=』,您將無法使用外部登入帳戶以及離線登入更換外觀功能。 +fatal.unsupported_platform=Minecraft 尚未你您的平臺提供完善支援,所以可能影響遊戲體驗或無法啟動遊戲。\n若無法啟動 Minecraft 1.17 及更高版本,可以嘗試在「(全域/實例特定) 遊戲設定 → 進階設定 → 除錯選項」中將「繪製器」切換為「軟繪製器」,以獲得更好的相容性。 fatal.unsupported_platform.loongarch=Hello Minecraft! Launcher 已為龍芯提供支援。\n如果遇到問題,你可以點擊右上角幫助按鈕進行求助。 fatal.unsupported_platform.osx_arm64=Hello Minecraft! Launcher 已為 Apple Silicon 平臺提供支援,使用 ARM 原生 Java 啟動遊戲以獲得更流暢的遊戲體驗。\n如果你在遊戲中遭遇問題,使用 x86-64 架構的 Java 啟動遊戲可能有更好的相容性。 -fatal.unsupported_platform.windows_arm64=Hello Minecraft! Launcher 已為 Windows on Arm 平臺提供原生支持。如果你在遊戲中遭遇問題,請嘗試使用 x86 架構的 Java 啟動遊戲。\n\n如果你正在使用高通平臺,你可能需要安裝 OpenGL 相容包後才能進行遊戲。點擊連結前往 Microsoft Store 安裝相容包。 +fatal.unsupported_platform.windows_arm64=Hello Minecraft! Launcher 已為 Windows on Arm 平臺提供原生支援。如果你在遊戲中遭遇問題,請嘗試使用 x86 架構的 Java 啟動遊戲。\n\n如果你正在使用高通平臺,你可能需要安裝 OpenGL 相容包後才能進行遊戲。點擊連結前往 Microsoft Store 安裝相容包。 -feedback=反饋 -feedback.channel=反饋渠道 -feedback.discord=Discord -feedback.discord.statement=歡迎加入 Discord 討論區,加入後請遵守討論區規定。 -feedback.github=GitHub Issue -feedback.github.statement=打開一個 GitHub Issue。 -feedback.qq_group=HMCL 用戶群 -feedback.qq_group.statement=歡迎加入 HMCL 用戶群,加入後請遵守群規。 +feedback=回報 +feedback.channel=回報管道 +feedback.discord=Discord 伺服器 +feedback.discord.statement=歡迎加入 Discord 伺服器,加入後請遵守討論區規則 +feedback.github=GitHub Issues +feedback.github.statement=提交一個 GitHub Issue +feedback.qq_group=HMCL 使用者 QQ 群組 +feedback.qq_group.statement=歡迎加入 HMCL 使用者 QQ 群組,加入後請遵守群組規則 file=檔案 -folder.config=設定資料夾 -folder.game=遊戲資料夾 -folder.logs=日誌文件夾 -folder.mod=MOD 模組資料夾 -folder.resourcepacks=資源包資料夾 -folder.shaderpacks=著色器包文件夾 -folder.saves=遊戲存檔資料夾 -folder.screenshots=截圖資料夾 +folder.config=模組設定目錄 +folder.game=實例執行目錄 +folder.logs=日誌目錄 +folder.mod=模組目錄 +folder.resourcepacks=資源包目錄 +folder.shaderpacks=著色器包目錄 +folder.saves=遊戲存檔目錄 +folder.screenshots=截圖目錄 game=遊戲 -game.crash.feedback=請不要將本界面截圖給他人! 如果你要求助他人,請你點擊左下角 導出遊戲崩潰信息 後將導出的文件發送給他人以供分析。\n你可以點擊下方的 幫助 前往社區尋求幫助。 +game.crash.feedback=請不要將本介面截圖給他人!如果你要求助他人,請你點擊左下角「匯出遊戲崩潰資訊」後將匯出的檔案發送給他人以供分析。\n你可以點擊下方的「幫助」前往社群尋求幫助。 game.crash.info=遊戲訊息 game.crash.reason=崩潰原因 -game.crash.reason.analyzing=分析中... +game.crash.reason.analyzing=分析中…… game.crash.reason.multiple=檢測到多個原因:\n\n -game.crash.reason.block=當前遊戲因為某個方塊不能正常工作,無法繼續運行。\n你可以嘗試通過 MCEdit 工具編輯存檔刪除該方塊,或者直接刪除相應的 Mod。\n方塊類型:%1$s\n方塊坐標:%2$s -game.crash.reason.bootstrap_failed=當前遊戲因為模組 %1$s 錯誤,無法繼續運行。\n你可以嘗試刪除或更新該模組以解決問題。 -game.crash.reason.mixin_apply_mod_failed=當前遊戲因為 Mixin 無法應用 %1$s 模組,無法繼續運行。\n你可以嘗試刪除或更新該 Mod 以解決問題。 -game.crash.reason.config=當前遊戲因為無法解析模組配置文件,無法繼續運行\n模組 %1$s 的配置文件 %2$s 無法被解析。 -game.crash.reason.debug_crash=當前遊戲因為手動觸發崩潰,無法繼續運行。\n事實上遊戲並沒有問題,問題都是你造成的。 -game.crash.reason.duplicated_mod=當前遊戲因為 Mod 重複安裝,無法繼續運行。\n%s\n每種 Mod 只能安裝一個,請你刪除多餘的 Mod 再試。 -game.crash.reason.entity=當前遊戲因為某個實體不能正常工作,無法繼續運行。\n你可以嘗試通過 MCEdit 工具編輯存檔刪除該實體,或者直接刪除相應的 Mod。\n實體類型:%1$s\n實體坐標:%2$s -game.crash.reason.fabric_version_0_12=Fabric 0.12 及以上版本與當前已經安裝的 Mod 可能不相容,你需要將 Fabric 降級至 0.11.7。 +game.crash.reason.block=目前遊戲由於某個方塊不能正常工作,無法繼續執行。\n你可以嘗試透過 MCEdit 工具編輯存檔刪除該方塊,或者直接刪除相應的模組。\n方塊類型:%1$s\n方塊坐標:%2$s +game.crash.reason.bootstrap_failed=目前遊戲由於模組「%1$s」出現問題,無法繼續執行。\n你可以嘗試刪除或更新該模組以解決問題。 +game.crash.reason.mixin_apply_mod_failed=目前遊戲由於 Mixin 無法應用於「%1$s」模組,無法繼續執行。\n你可以嘗試刪除或更新該模組以解決問題。 +game.crash.reason.config=目前遊戲由於無法解析模組配置檔案,無法繼續執行\n模組「%1$s」的配置檔案「%2$s」無法被解析。 +game.crash.reason.debug_crash=目前遊戲由於手動觸發崩潰,無法繼續執行。\n事實上遊戲並沒有問題,問題都是你造成的! +game.crash.reason.duplicated_mod=目前遊戲由於模組重複安裝,無法繼續執行。\n%s\n每種模組只能安裝一個,請你刪除多餘的模組再試。 +game.crash.reason.entity=目前遊戲由於某個實體不能正常工作,無法繼續執行。\n你可以嘗試透過 MCEdit 工具編輯存檔刪除該實體,或者直接刪除相應的模組。\n實體類型:%1$s\n實體坐標:%2$s +game.crash.reason.fabric_version_0_12=Fabric Loader 0.12 及更高版本與目前已經安裝的模組可能不相容,你需要將 Fabric Loader 降級至 0.11.7。 game.crash.reason.fabric_warnings=Fabric 提供了一些警告訊息:\n%1$s -game.crash.reason.modmixin_failure=當前遊戲因為某些 Mod 注入失敗,無法繼續運行。\n這一般代表著該 Mod 存在 Bug,或與當前環境不兼容。\n你可以查看日誌尋找出錯模組。 -game.crash.reason.file_or_content_verification_failed=當前遊戲因為部分文件或內容校驗失敗,導致遊戲出現了問題。\n請嘗試刪除該版本(包括 Mod)並重新下載,或嘗試在重新下載時使用代理等。 -game.crash.reason.mod_repeat_installation=當前遊戲因為重複安裝了多個相同的 Mod,每個 Mod 只能出現一次,請刪除重複的 Mod,然後再啟動遊戲。 -game.crash.reason.forge_error=Forge 可能已經提供了錯誤信息。\n你可以查看日誌,並根據錯誤報告中的日誌信息進行對應處。\n如果沒有看到報錯信息,可以查看錯誤報告了解錯誤具體是如何發生的。\n%1$s -game.crash.reason.mod_resolution0=當前遊戲因為一些 Mod 出現問題,無法繼續運行。\n你可以查看日誌尋找出錯模組。 -game.crash.reason.need_jdk11=當前遊戲因為 Java 虛擬機版本不合適,無法繼續運行。\n你需要下載安裝 Java 11,並在全局(特定)遊戲設置中將 Java 設置為 11 開頭的版本。 -game.crash.reason.mod_profile_causes_game_crash=當前遊戲因為 Mod 配置文件出現問題,無法繼續運行。\n你可以查看日誌尋找出錯模組及其配置文件。 -game.crash.reason.fabric_reports_an_error_and_gives_a_solution=Forge 可能已經提供了錯誤信息。\n你可以查看日誌,並根據錯誤報告中的日誌信息進行對應處。\n如果沒有看到報錯信息,可以查看錯誤報告了解錯誤具體是如何發生的。 -game.crash.reason.java_version_is_too_high=當前遊戲因為使用的 Java 版本過高而崩潰了,無法繼續運行。\n請在 全局遊戲設置 或 遊戲特定設置 的 Java 路徑選項卡中改用較低版本的 Java,然後再啟動遊戲。\n如果沒有,可以從 java.com(Java8)BellSoft Liberica Full JRE(Java17) 等平台下載、安裝一個(安裝完後需重啟啟動器)。 -game.crash.reason.mod_name=當前遊戲因為 Mod 檔案名稱問題,無法繼續運行。\nMod 檔案名稱應只使用英文全半型的大小寫字母(Aa~Zz)、數位(0~9)、橫線(-)、底線(_)和點(.)。\n請到Mod資料夾中將所有不合規的Mod檔案名稱添加一個上述的合規的字元。 -game.crash.reason.incomplete_forge_installation=當前遊戲因為 Forge / NeoForge 安裝不完整,無法繼續運行。\n請在 版本設置 - 自動安裝 中卸載 Forge / NeoForge 並重新安裝。 -game.crash.reason.file_already_exists=當前遊戲因為文件 %1$s 已經存在,無法繼續運行。\n如果你認為這個文件可以刪除,你可以在備份這個文件後嘗試刪除它,並重新啟動遊戲。 -game.crash.reason.file_changed=當前遊戲因為檔案校驗失敗,無法繼續運行。\n如果你手動修改了 Minecraft.jar 檔案,你需要回退修改,或者重新下載遊戲。 -game.crash.reason.gl_operation_failure=當前遊戲因為你使用的某些 Mod、光影包、材質包,無法繼續運行。\n請先嘗試禁用你所使用的Mod/光影包/材質包再試。 -game.crash.reason.graphics_driver=當前遊戲因為你的顯示卡驅動存在問題崩潰了,請嘗試升級你的顯示卡驅動到最新版本後再嘗試啟動遊戲。\n\ - 如果你的電腦存在獨立顯示卡,你需要檢查遊戲是否使用集成/核芯顯示卡啟動。如果是,請嘗試使用獨立顯示卡啟動 HMCL 與遊戲。如果仍有問題,你可能需要考慮換一個新顯示卡或新電腦。\n\ - 如果你確實需要使用核芯顯示卡,請檢查你的電腦的 CPU 是否是 Intel(R) Core(TM) 3000 系列或更舊的處理器,如果是,對於 Minecraft 1.16.5 及更舊版本,請你將遊戲所使用的 Java 版本降級至 1.8.0_51 及以下版本,否則你需要更換獨立顯示卡或新電腦。\n\ - 在版本設定中打開“使用 OpenGL 軟渲染器”選項也可以解决此問題,但打開此選項後在 CPU 效能不足的情况下幀數會顯著降低,僅推薦以調試為目的或應急時開啟。 -game.crash.reason.macos_failed_to_find_service_port_for_display=當前遊戲因為 apple silicon 平台下初始化 opengl 窗口失敗,無法繼續運行。\n對於該問題,HMCL 暫無直接性的解決方案。請您嘗試任意打開一個瀏覽器並全屏,然後再回到 HMCL 啟動遊戲,在彈出遊戲窗口前迅速切回瀏覽器頁面,等待遊戲窗口出現後再切回遊戲窗口。 -game.crash.reason.illegal_access_error=當前遊戲因為某些 Mod 的問題,無法繼續運行。\n如果你認識:%1$s,你可以更新或刪除對應 Mod 再試。 -game.crash.reason.install_mixinbootstrap=當前遊戲因為缺失 MixinBootstrap,無法繼續運行。\n你可以嘗試安裝 MixinBootstrap 解決該問題。若安裝後崩潰,嘗試在該模組的文件名前加入英文“!”嘗試解決。 -game.crash.reason.jdk_9=當前遊戲因為 Java 版本過高,無法繼續運行。\n你需要下載安裝 Java 8,並在遊戲設置中將 Java 設置為 1.8 的版本。 -game.crash.reason.jvm_32bit=當前遊戲因為記憶體分配過大,超過了 32 位 Java 記憶體限制,無法繼續運行。\n如果你的電腦是 64 位系統,請下載安裝並更換 64 位 Java。如果你的電腦室 32 位系統,你或許可以重新安裝 64 位系統,或換一台新電腦。\n或者,你可以關閉遊戲記憶體的自動分配,並且把記憶體限制調節為 1024 MB 或以下。 -game.crash.reason.loading_crashed_forge=當前遊戲因為模組 %1$s (%2$s) 錯誤,無法繼續運行。\n你可以嘗試刪除或更新該模組以解決問題。 -game.crash.reason.loading_crashed_fabric=當前遊戲因為模組 %1$s 錯誤,無法繼續運行。\n你可以嘗試刪除或更新該模組以解決問題。 -game.crash.reason.mac_jdk_8u261=當前遊戲因為你所使用的 Forge 或 OptiFine 與 Java 衝突崩潰。\n請嘗試更新 Forge 和 OptiFine,或使用 Java 8u251 及更早版本啟動。 -game.crash.reason.memory_exceeded=當前遊戲因為分配的記憶體過大,無法繼續運行。\n該問題是由於系統頁面文件太小導致的。\n你需要在遊戲設置中關閉遊戲記憶體的自動分配,並將遊戲記憶體調低至遊戲能正常啟動為止。\n你還可以嘗試調大系統的頁面大小。 -game.crash.reason.mod=當前遊戲因為 %1$s 的問題,無法繼續運行。\n你可以更新或刪除已經安裝的 %1$s 再試。 -game.crash.reason.mod_resolution=當前遊戲因為 Mod 依賴問題,無法繼續運行。Fabric 提供了如下訊息:\n%1$s -game.crash.reason.forgemod_resolution=當前遊戲因為 Mod 依賴問題,無法繼續運行。Forge 提供了如下訊息:\n%1$s -game.crash.reason.forge_found_duplicate_mods=遊戲崩潰原因模組重複的問題,無法繼續運行。Forge 提供了以下信息:\n%1$s -game.crash.reason.mod_resolution_collection=當前遊戲因為前置 Mod 版本不匹配,無法繼續運行。\n%1$s 需要前置 Mod:%2$s 才能繼續運行。\n這表示你需要更新或降級前置。你可以到下載頁的模組下載,或到網路上下載 %3$s。 -game.crash.reason.mod_resolution_conflict=當前遊戲因為 Mod 衝突,無法繼續運行。\n%1$s 與 %2$s 不能相容。 -game.crash.reason.mod_resolution_missing=當前遊戲因為缺少 Mod 前置,無法繼續運行。\n%1$s 需要前置 Mod:%2$s 才能繼續運行。\n這表示你少安裝了 Mod,或該 Mod 版本不夠。你可以到下載頁的模組下載,或到網路上下載 %2$s。 -game.crash.reason.mod_resolution_missing_minecraft=當前遊戲因為 Mod 和 Minecraft 遊戲版本不匹配,無法繼續運行。\n%1$s 需要 Minecraft %2$s 才能運行。\n如果你要繼續使用你已經安裝的 Mod,你可以選擇安裝對應的 Minecraft 版本;如果你要繼續使用當前 Minecraft 版本,你需要安裝對應版本的 Mod。 +game.crash.reason.modmixin_failure=目前遊戲由於某些模組注入失敗,無法繼續執行。\n這一般代表著該模組存在問題,或與目前環境不相容。\n你可以查看日誌尋找出錯模組。 +game.crash.reason.mod_repeat_installation=目前遊戲由於重複安裝了多個相同的模組,無法繼續執行。\n每個模組只能出現一次,請刪除重複的模組,然後再啟動遊戲。 +game.crash.reason.forge_error=Forge/NeoForge 可能已經提供了錯誤資訊。\n你可以查看日誌,並根據錯誤報告中的日誌資訊進行對應處。\n如果沒有看到報錯資訊,可以查看錯誤報告了解錯誤具體是如何發生的。\n%1$s +game.crash.reason.mod_resolution0=目前遊戲由於一些模組出現問題,無法繼續執行。\n你可以查看日誌尋找出錯模組。 +game.crash.reason.need_jdk11=目前遊戲由於 Java 虛擬機版本不合適,無法繼續執行。\n你需要下載安裝 Java 11,並在「(全域/實例特定) 遊戲設定 → 遊戲 Java」中將 Java 設定為 11 開頭的版本。 +game.crash.reason.java_version_is_too_high=目前遊戲由於使用的 Java 版本過高而崩潰了,無法繼續執行。\n請在「(全域/實例特定) 遊戲設定 → 遊戲 Java」中改用較低版本的 Java,然後再啟動遊戲。\n如果沒有,可以從 java.com (Java 8)BellSoft Liberica Full JRE (Java 17) 等平台下載、安裝一個 (安裝完後需重啟啟動器)。 +game.crash.reason.mod_name=目前遊戲由於模組檔案名稱問題,無法繼續執行。\n模組檔案名稱應只使用半型的大小寫字母 (Aa~Zz)、數位 (0~9)、橫線 (-)、底線 (_)和點 (.)。\n請到模組目錄中將所有不合規的模組檔案名稱修正為上述合規字元。 +game.crash.reason.incomplete_forge_installation=目前遊戲由於 Forge 安裝不完整,無法繼續執行。\n請在「實例管理 - 自動安裝」中移除 Forge 並重新安裝。 +game.crash.reason.file_already_exists=目前遊戲由於檔案「%1$s」已經存在,無法繼續執行。\n如果你認為這個檔案可以刪除,你可以在備份這個檔案後嘗試刪除它,並重新啟動遊戲。 +game.crash.reason.file_changed=目前遊戲由於檔案校驗失敗,無法繼續執行。\n如果你手動修改了 Minecraft.jar 檔案,你需要回退修改,或者重新下載遊戲。 +game.crash.reason.gl_operation_failure=目前遊戲由於你使用的某些模組/光影包/資源包出現問題,無法繼續執行。\n請先嘗試禁用你所使用的模組/光影包/資源包再試。 +game.crash.reason.graphics_driver=目前遊戲由於你的顯示卡驅動存在問題崩潰了,請嘗試升級你的顯示卡驅動到最新版本後再嘗試啟動遊戲。\n\ + 如果你的電腦存在獨立顯示卡,你需要檢查遊戲是否使用整合/核芯顯示卡啟動。如果是,請嘗試使用獨立顯示卡開啟 HMCL 與遊戲。如果仍有問題,你可能需要考慮換一個新顯示卡或新電腦。\n\ + 如果你確實需要使用核芯顯示卡,請檢查你電腦的 CPU 是否為 Intel(R) Core(TM) 3000 系列或更舊的處理器,如果是,對於 Minecraft 1.16.5 及更低版本,請你將遊戲所使用的 Java 版本降級至 1.8.0_51 及更低版本,否則你需要更換獨立顯示卡或新電腦。 +game.crash.reason.macos_failed_to_find_service_port_for_display=目前遊戲由於 Apple Silicon 平台下初始化 OpenGL 視窗失敗,無法繼續執行。\n對於該問題,HMCL 暫無直接性的解決方案。請你嘗試任意打開一個瀏覽器並切換為全螢幕,然後再回到 HMCL 啟動遊戲,在彈出遊戲視窗前迅速切回瀏覽器頁面,等待遊戲視窗出現後再切回遊戲視窗。 +game.crash.reason.illegal_access_error=目前遊戲由於某些模組的問題,無法繼續執行。\n如果你認識「%1$s」,你可以更新或刪除對應模組再試。 +game.crash.reason.install_mixinbootstrap=目前遊戲由於缺失 MixinBootstrap,無法繼續執行。\n你可以嘗試安裝 MixinBootstrap 解決該問題。若安裝後崩潰,嘗試在該模組的檔案名前加入半形驚嘆號 (!) 嘗試解決。 +game.crash.reason.jdk_9=目前遊戲由於 Java 版本過高,無法繼續執行。\n你需要下載安裝 Java 8,並在「(全域/實例特定) 遊戲設定 → 遊戲 Java」中將 Java 設定為 1.8 的版本。 +game.crash.reason.jvm_32bit=目前遊戲由於記憶體分配過大,超過了 32 位 Java 記憶體限制,無法繼續執行。\n如果你的電腦是 64 位系統,請下載安裝並更換 64 位 Java。\n如果你的電腦是 32 位系統,你或許可以重新安裝 64 位系統,或換一台新電腦。\n或者,你可以關閉「(全域/實例特定) 遊戲設定 → 遊戲記憶體」中的「自動分配」,並且把記憶體限制調節為 1024 MB 或以下。 +game.crash.reason.loading_crashed_forge=目前遊戲由於模組「%1$s (%2$s)」錯誤,無法繼續執行。\n你可以嘗試刪除或更新該模組以解決問題。 +game.crash.reason.loading_crashed_fabric=目前遊戲由於模組「%1$s」錯誤,無法繼續執行。\n你可以嘗試刪除或更新該模組以解決問題。 +game.crash.reason.mac_jdk_8u261=目前遊戲由於你所使用的 Forge 或 OptiFine 與 Java 衝突而崩潰。\n請嘗試更新 Forge 和 OptiFine,或使用 Java 8u251 及更早版本啟動。 +game.crash.reason.memory_exceeded=目前遊戲由於分配的記憶體過大,無法繼續執行。\n該問題是由於系統頁面檔案太小導致的。\n你需要在「(全域/實例特定) 遊戲設定 → 遊戲記憶體」中關閉「自動分配」,並將遊戲記憶體調低至遊戲能正常啟動為止。\n你還可以嘗試調大系統的頁面大小。 +game.crash.reason.mod=目前遊戲由於 %1$s 的問題,無法繼續執行。\n你可以更新或刪除已經安裝的「%1$s」再試。 +game.crash.reason.mod_resolution=目前遊戲由於相依模組問題,無法繼續執行。Fabric 提供了如下訊息:\n%1$s +game.crash.reason.forgemod_resolution=目前遊戲由於模組相依元件問題,無法繼續執行。Forge/NeoForge 提供了以下資訊:\n%1$s +game.crash.reason.forge_found_duplicate_mods=目前遊戲由於模組重複的問題,無法繼續執行。Forge/NeoForge 提供了以下資訊:\n%1$s +game.crash.reason.mod_resolution_collection=目前遊戲由於相依模組版本不匹配,無法繼續執行。\n「%1$s」需要相依模組「%2$s」才能繼續執行。\n這表示你需要更新或降級相依模組。你可以到「下載 → 模組」頁面下載,或到網路上下載「%3$s」。 +game.crash.reason.mod_resolution_conflict=目前遊戲由於模組衝突,無法繼續執行。\n「%1$s」與「%2$s」不能相容。 +game.crash.reason.mod_resolution_missing=目前遊戲由於缺少相依模組,無法繼續執行。\n「%1$s」需要相依模組「%2$s」才能繼續執行。\n這表示你少安裝了模組,或該模組版本不夠。你可以到「下載 → 模組」頁面下載,或到網路上下載「%2$s」。 +game.crash.reason.mod_resolution_missing_minecraft=目前遊戲由於模組和 Minecraft 遊戲版本不匹配,無法繼續執行。\n「%1$s」需要 Minecraft %2$s 才能執行。\n如果你要繼續使用你已經安裝的模組,你可以選取安裝對應的 Minecraft 版本;如果你要繼續使用目前的 Minecraft 版本,你需要安裝對應版本的模組。 game.crash.reason.mod_resolution_mod_version=%1$s (版本號 %2$s) game.crash.reason.mod_resolution_mod_version.any=%1$s (任意版本) -game.crash.reason.forge_repeat_installation=當前遊戲因為 Forge 重複安裝,無法繼續運行。此為已知問題\n建議將日誌上傳反饋至 GitHub ,以便我們找到更多線索並修復此問題。\n目前你可以到 自動安裝 裡頭卸載 Forge 並重新安裝。 -game.crash.reason.optifine_repeat_installation=當前遊戲因為重複安裝 OptiFine,無法繼續運行。 \n請刪除 Mod 文件夾下的 OptiFine 或前往 遊戲管理-自動安裝 卸載自動安裝的 OptiFine。 -game.crash.reason.optifine_is_not_compatible_with_forge=當前遊戲因為OptiFine與當前版本的Forge不相容,導致了遊戲崩潰。\n請前往 OptiFine 官網查看 OptiFine 所相容的 Forge 版本,並嚴格按照對應版本重新安裝遊戲或在版本設定-自動安裝中更換版本。\n經測試,Forge版本過高或過低都可能導致崩潰。 -game.crash.reason.night_config_fixes=當前遊戲因為 Night Config 庫的一些問題,無法繼續運行。\n你可以嘗試安裝 Night Config Fixes 模組,這或許能幫助你解決這個問題。\n了解更多,可訪問該模組的 GitHub 倉庫。 -game.crash.reason.mod_files_are_decompressed=當前遊戲因為 Mod 檔案被解壓了,無法繼續運行。\n請直接把整個 Mod 檔案放進 Mod 資料夾中即可。\n若解壓就會導致遊戲出錯,請删除Mod資料夾中已被解壓的Mod,然後再啟動遊戲。 -game.crash.reason.too_many_mods_lead_to_exceeding_the_id_limit=當前遊戲因為您所安裝的 Mod 過多,超出了遊戲的ID限制,無法繼續運行。\n請嘗試安裝JEID等修復Mod,或删除部分大型Mod。 -game.crash.reason.optifine_causes_the_world_to_fail_to_load=當前遊戲因為 Mod 檔案被解壓了,無法繼續運行。\n請直接把整個Mod檔案放進Mod資料夾中即可!\n若解壓就會導致遊戲出錯,請删除Mod資料夾中已被解壓的Mod,然後再啟動遊戲。 -game.crash.reason.modlauncher_8=當前遊戲因為您所使用的 Forge 版本與當前使用的 Java 衝突崩潰,請嘗試更新 Forge。 -game.crash.reason.cannot_find_launch_target_fmlclient=當前遊戲因為 Forge 安裝不完整,無法繼續運行。 \n你可嘗試前往 遊戲管理 - 自動安裝 中選擇 Forge 並重新安裝。 -game.crash.reason.shaders_mod=當前遊戲因為同時安裝了 OptiFine 和 Shaders Mod,無法繼續運行。 \n因為 OptiFine 已集成 Shaders Mod 的功能,只需刪除 Shaders Mod 即可。 -game.crash.reason.rtss_forest_sodium=當前遊戲因為 RivaTuner Statistics Server (RTSS) 與 Sodium 不相容,導致遊戲崩潰。\n點擊 此處 查看詳情。 -game.crash.reason.no_class_def_found_error=當前遊戲因為代碼不完整,無法繼續運行。\n你的遊戲可能缺失了某個 Mod,或者某些 Mod 檔案不完整,或者 Mod 與遊戲的版本不匹配。\n你可能需要重新安裝遊戲和 Mod,或請求他人幫助。\n缺失:%1$s -game.crash.reason.no_such_method_error=當前遊戲因為代碼不完整,無法繼續運行。\n你的遊戲可能缺失了某個 Mod,或者某些 Mod 檔案不完整,或者 Mod 與遊戲的版本不匹配。\n你可能需要重新安裝遊戲和 Mod,或請求他人幫助。 -game.crash.reason.opengl_not_supported=當前遊戲因為你的顯示卡驅動存在問題,無法繼續運行。\n原因是 OpenGL 不受支援,你現在是否在遠程桌面或者串流模式下?如果是,請直接使用原電腦啟動遊戲。\n或者嘗試升級你的顯示卡驅動到最新版本後再嘗試啟動遊戲。如果你的電腦存在獨立顯示卡,你需要檢查遊戲是否使用集成/核心顯示卡啟動,如果是,請嘗試使用獨立顯示卡啟動 HMCL 與遊戲。如果仍有問題,你可能需要考慮換一個新顯示卡或新電腦。 -game.crash.reason.openj9=當前遊戲無法運行在 OpenJ9 虛擬機上,請你在遊戲設置中更換 Hotspot Java 虛擬機,並重新啟動遊戲。如果沒有下載安裝,你可以在網路上自行下載。 -game.crash.reason.out_of_memory=當前遊戲因為記憶體不足,無法繼續運行。\n這可能是記憶體分配太小,或者 Mod 數量過多導致的。\n你可以在遊戲設置中調大遊戲記憶體分配值以允許遊戲在更大的記憶體下運行。\n如果仍然出現該錯誤,你可能需要換一台更好的電腦。 -game.crash.reason.processing_of_javaagent_failed=當前遊戲因為加載 -javaagent 參數失敗,無法繼續運行。\n如果你在 Java 虛擬機參數 中添加了相關參數,請檢查是否正確。\n如果你沒有添加相關參數或參數確認正確, 請嘗試:\n打開 控制面板 -- 時鐘和區域 分類(選項為類別顯示才有此選項,沒有就跳過)-- 區域 -- 上方的 管理 選項卡 -- 下方的 更改系統區域設置 按鈕 -- 在彈出的視窗中將 “使用Unicode UTF-8提供全球語言支持” 選項關閉,重啓設備後再嘗試啟動遊戲。\n可在 DiscordQQ 群尋求幫助 -game.crash.reason.resolution_too_high=當前遊戲因為材質包解析度過高,無法繼續運行\n你可以更換一個解析度更低的材質,或者更換一個視訊記憶體更大的顯示卡。 -game.crash.reason.stacktrace=原因未知,請點擊日誌按鈕查看詳細訊息。\n下面是一些關鍵字,其中可能包含 Mod 名稱,你可以透過搜索的方式查找有關訊息。\n%s -game.crash.reason.too_old_java=當前遊戲因為 Java 虛擬機版本過低,無法繼續運行。\n你需要在遊戲設置中更換 %1$s 或更新版本的 Java 虛擬機,並重新啟動遊戲。如果沒有下載安裝,你可以點擊 此處 下載微軟 JDK。 +game.crash.reason.forge_repeat_installation=目前遊戲由於 Forge 重複安裝,無法繼續執行。此為已知問題\n建議將日誌上傳並回報至 GitHub,以便我們找到更多線索並修復此問題。\n目前你可以在「實例管理 → 自動安裝」中移除 Forge 並重新安裝。 +game.crash.reason.optifine_repeat_installation=目前遊戲由於重複安裝 OptiFine,無法繼續執行。\n請刪除模組目錄下的 OptiFine 或前往「實例管理 → 自動安裝」移除安裝的 OptiFine。 +game.crash.reason.optifine_is_not_compatible_with_forge=目前遊戲由於 OptiFine 與目前版本的 Forge 不相容,導致了遊戲崩潰。\n請前往 OptiFine 官網查看 OptiFine 所相容的 Forge 版本,並嚴格按照對應版本重新安裝遊戲,或在「實例管理 → 自動安裝」中更換版本。\n經測試,Forge 版本過高或過低都可能導致崩潰。 +game.crash.reason.night_config_fixes=目前遊戲由於 Night Config 庫的一些問題,無法繼續執行。\n你可以嘗試安裝 Night Config Fixes 模組,這或許能幫助你解決這個問題。\n了解更多,可訪問該模組的 GitHub 倉庫。 +game.crash.reason.mod_files_are_decompressed=目前遊戲由於模組檔案被解壓了,無法繼續執行。\n請直接把整個模組檔案放進模組目錄中即可。\n若解壓就會導致遊戲出錯,請刪除模組目錄中已被解壓的模組,然後再啟動遊戲。 +game.crash.reason.too_many_mods_lead_to_exceeding_the_id_limit=目前遊戲由於你所安裝的模組過多,超出了遊戲的 ID 限制,無法繼續執行。\n請嘗試安裝JEID等修正模組,或刪除部分大型模組。 +game.crash.reason.optifine_causes_the_world_to_fail_to_load=目前遊戲可能由於 OptiFine 而無法繼續執行。\n該問題只在特定 OptiFine 版本中出現,你可以嘗試在「實例管理 → 自動安裝」中更換 OptiFine 的版本。 +game.crash.reason.modlauncher_8=目前遊戲由於你所使用的 Forge 版本與目前使用的 Java 衝突崩潰,請嘗試更新 Forge。 +game.crash.reason.shaders_mod=目前遊戲由於同時安裝了 OptiFine 和 Shaders 模組,無法繼續執行。 \n由於 OptiFine 已整合 Shaders 模組的功能,只需刪除 Shaders 模組即可。 +game.crash.reason.rtss_forest_sodium=目前遊戲由於 RivaTuner Statistics Server (RTSS) 與 Sodium 不相容,導致遊戲崩潰。\n點擊 此處 查看詳情。 +game.crash.reason.no_class_def_found_error=目前遊戲由於程式碼不完整,無法繼續執行。\n你的遊戲可能缺失了某個模組,或者某些模組檔案不完整,或者模組與遊戲的版本不匹配。\n你可能需要重新安裝遊戲和模組,或請求他人幫助。\n缺失:%1$s +game.crash.reason.no_such_method_error=目前遊戲由於程式碼不完整,無法繼續執行。\n你的遊戲可能缺失了某個模組,或者某些模組檔案不完整,或者模組與遊戲的版本不匹配。\n你可能需要重新安裝遊戲和模組,或請求他人幫助。 +game.crash.reason.opengl_not_supported=目前遊戲由於你的顯示卡驅動存在問題,無法繼續執行。\n原因是 OpenGL 不受支援,你現在是否在遠端桌面或者串流模式下?如果是,請直接使用原電腦啟動遊戲。\n或者嘗試升級你的顯示卡驅動到最新版本後再嘗試啟動遊戲。如果你的電腦存在獨立顯示卡,你需要檢查遊戲是否使用整合/核心顯示卡啟動,如果是,請嘗試使用獨立顯示卡開啟 HMCL 與遊戲。如果仍有問題,你可能需要考慮換一個新顯示卡或新電腦。 +game.crash.reason.openj9=目前遊戲無法執行在 OpenJ9 虛擬機上,請你在「(全域/實例特定) 遊戲設定 → 遊戲 Java」中更換 Hotspot Java 虛擬機,並重新啟動遊戲。如果沒有下載安裝,你可以在網路上自行下載。 +game.crash.reason.out_of_memory=目前遊戲由於記憶體不足,無法繼續執行。\n這可能是記憶體分配太小,或者模組數量過多導致的。\n你可以在「(全域/實例特定) 遊戲設定 → 遊戲記憶體」中調大遊戲記憶體分配值以允許遊戲在更大的記憶體下執行。\n如果仍然出現該錯誤,你可能需要換一台更好的電腦。 +game.crash.reason.resolution_too_high=目前遊戲由於資源包解析度過高,無法繼續執行。\n你可以更換一個解析度更低的資源包,或者更換一個視訊記憶體更大的顯示卡。 +game.crash.reason.stacktrace=原因未知,請點擊日誌按鈕查看詳細訊息。\n下面是一些關鍵字,其中可能包含模組名稱,你可以透過搜尋的方式尋找有關訊息。\n%s +game.crash.reason.too_old_java=目前遊戲由於 Java 虛擬機版本過低,無法繼續執行。\n你需要在「(全域/實例特定) 遊戲設定 → 遊戲 Java」中更換 %1$s 或更高版本的 Java 虛擬機,並重新啟動遊戲。如果沒有下載安裝,你可以點擊 此處 下載 Microsoft JDK。 game.crash.reason.unknown=原因未知,請點擊日誌按鈕查看詳細訊息。 -game.crash.reason.unsatisfied_link_error=當前遊戲因為缺少本地庫,無法繼續運行。\n這些本地庫缺失:%1$s。\n如果你在全局(特定)遊戲設置中修改了本地庫路徑選項,請你修改回預設模式。\n如果已經在預設模式下,請檢查本地庫缺失是否是 Mod 引起的,或由 HMCL 引起的。如果你確定是 HMCL 引起的,建議你向我們反饋。\n或將游戲路徑中的所有非英文字符的名稱修改為英文字符(例如中文,空格等)\n如果你確實需要自定義本地庫路徑,你需要保證其中包含缺失的本地庫! -game.crash.reason.failed_to_load_a_library=當前遊戲因為加載本地庫失敗,無法繼續運行。\n如果你在全局(特定)遊戲設置中修改了本地庫路徑選項,請你修改回預設模式。\n如果已經在預設模式下,請檢查本地庫缺失是否是 Mod 引起的,或由 HMCL 引起的。如果你確定是 HMCL 引起的,建議你向我們反饋。\n或將游戲路徑中的所有非英文字符的名稱修改為英文字符(例如中文,空格等)\n如果你確實需要自定義本地庫路徑,你需要保證其中包含缺失的本地庫! +game.crash.reason.unsatisfied_link_error=目前遊戲由於缺少本機庫,無法繼續執行。\n這些本機庫缺失:%1$s。\n如果你在「(全域/實例特定) 遊戲設定 → 進階設定」中修改了本機庫路徑選項,請你修改回預設模式。\n如果你正在使用預設模式,請檢查遊戲目錄路徑是否只包含英文字母、數字和底線,\n如果是,那麼請檢查是否為模組或 HMCL 導致了本機庫缺失的問題。如果你確定是 HMCL 引起的,建議你向我們回報。\n對於 Windows 使用者,你還可以嘗試在「控制台 → 時鐘和區域 → 地區 → 系統管理 → 變更系統區域設定」中,關閉「Beta:使用 Unicode UTF-8 提供全球語言支援」選項;\n或將遊戲目錄路徑中的所有非英文字母的名稱 (例如中文、空格等) 修改為英文字母。\n如果你確實需要自訂本機庫路徑,你需要保證其中包含缺失的本機庫! game.crash.title=遊戲意外退出 -game.directory=遊戲路徑 +game.directory=遊戲目錄路徑 game.version=遊戲版本 help=說明 -help.doc=Hello Minecraft! Launcher 說明檔案 -help.detail=可查閱資料包、模組包製作指南等內容。 +help.doc=Hello Minecraft! Launcher 說明文件 +help.detail=可查閱資料包、模組包製作教學等內容 input.email=[使用者名稱] 必須是電子信箱格式 input.number=必須是數字 input.not_empty=必填 input.url=必須是有效連結 -install=新增遊戲 +install=新增實例 install.change_version=變更版本 -install.change_version.confirm=你確定要 %s 從 %s 更新到 %s 嗎? +install.change_version.confirm=你確定要將 %s 從 %s 更新到 %s 嗎? install.failed=安裝失敗 install.failed.downloading=安裝失敗,部分檔案未能完成下載 install.failed.downloading.detail=未能下載檔案: %s install.failed.downloading.timeout=下載逾時: %s -install.failed.install_online=無法識別要安裝的軟體。如果你要安裝 Mod,你需要在模組管理頁面安裝模組。 -install.failed.malformed=剛才下載的檔案格式損壞。您可以切換到其他下載來源以解決此問題。 -install.failed.optifine_conflict=暫不支援 OptiFine 與 Forge 同時安裝在 Minecraft 1.13 上 -install.failed.optifine_forge_1.17=Minecraft 1.17.1 下,僅 OptiFine H1 Pre2 及以上版本能相容 Forge。你可以從 OptiFine 測試版中選擇最新版本。 -install.failed.version_mismatch=該軟體需要的遊戲版本為 %s,但實際的遊戲版本為 %s。 -install.installer.change_version=%s,該版本與當前遊戲不相容,您需要點擊此處更換版本或刪除 -install.installer.choose=選擇 %s 版本 +install.failed.install_online=無法識別要安裝的載入器。如果你要安裝模組,你需要在模組管理頁面安裝模組。 +install.failed.malformed=剛才下載的檔案已損壞。你可以在「設定 → 下載 → 下載來源」中切換其他下載來源以解決此問題。 +install.failed.optifine_conflict=暫不支援 OptiFine 與 Fabric 同時安裝在 Minecraft 1.13 上。 +install.failed.optifine_forge_1.17=對於 Minecraft 1.17.1 版本,僅 OptiFine H1 pre2 及更高版本與 Forge 相容。你可以從 OptiFine 預覽版 (Preview versions) 中選取最新版本。 +install.failed.version_mismatch=該載入器需要的遊戲版本為 %s,但實際的遊戲版本為 %s。 +install.installer.change_version=%s 與目前遊戲不相容,請更換版本 +install.installer.choose=選取 %s 版本 install.installer.depend=需要先安裝 %s install.installer.fabric=Fabric install.installer.fabric-api=Fabric API -install.installer.fabric-api.warning=警告:Fabric API 是 Mod,將會被安裝到新遊戲的 Mod 資料夾,請你在安裝遊戲後不要修改當前遊戲的版本隔離/遊戲運行路徑設置,如果你在之後修改了相關設置,Fabric API 需要被重新安裝。 +install.installer.fabric-api.warning=警告:Fabric API 是模組,將會被安裝到新遊戲的模組目錄,請你在安裝遊戲後不要修改目前遊戲的「執行路徑」設定,如果你在之後修改了相關設定,則需要重新安裝 Fabric API。 install.installer.forge=Forge install.installer.neoforge=NeoForge install.installer.game=Minecraft install.installer.incompatible=與 %s 不相容 install.installer.install=安裝 %s install.installer.install_offline=從本機檔案安裝或升級 -install.installer.install_offline.extension=Forge/OptiFine 安裝器 -install.installer.install_offline.tooltip=支援匯入已經下載好的 Forge/OptiFine 安裝器 +install.installer.install_offline.extension=(Neo)Forge/OptiFine 安裝器 +install.installer.install_offline.tooltip=支援匯入已經下載好的 (Neo)Forge/OptiFine 安裝器 install.installer.install_online=線上安裝 -install.installer.install_online.tooltip=支援安裝 Fabric、Forge、OptiFine、LiteLoader +install.installer.install_online.tooltip=支援安裝 Forge、NeoForge、Fabric、Quilt、LiteLoader 和 OptiFine install.installer.liteloader=LiteLoader install.installer.not_installed=不安裝 install.installer.optifine=OptiFine install.installer.quilt=Quilt install.installer.quilt-api=QSL/QFAPI install.installer.version=%s -install.installer.external_version=%s 由外部安裝的版本,無法解除安裝或更換 +install.installer.external_version=%s [由外部安裝的版本,無法解除安裝或更換] install.modpack=安裝模組包 -install.new_game=安裝新遊戲版本 -install.new_game.already_exists=此版本已經存在,請重新命名 +install.new_game=安裝新實例 +install.new_game.already_exists=此實例已經存在,請重新命名 install.new_game.current_game_version=目前遊戲版本 install.new_game.malformed=名稱無效 -install.select=請選擇安裝方式 +install.select=請選取安裝方式 install.success=安裝成功 -lang=正體中文 +java.add=添加 Java +java.add.failed=Java 無效或與目前平臺不相容 +java.disable=禁用此 Java +java.disable.confirm=你確定要禁用此 Java 嗎? +java.disabled.management=管理已禁用的 Java +java.disabled.management.remove=從清單中移除此 Java +java.disabled.management.restore=重新啟用此 Java +java.download=下載 Java +java.download.load_list.failed=載入版本清單失敗 +java.download.more=更多發行版 +java.download.prompt=請選擇你要下載的 Java 版本: +java.download.distribution=發行版 +java.download.version=版本 +java.download.packageType=包類型 +java.management=Java 管理 +java.info.architecture=架構 +java.info.vendor=供應商 +java.info.version=版本 +java.info.disco.distribution=發行版本 +java.install=安裝 Java +java.install.archive=源路徑 +java.install.failed.exists=該名稱已被使用 +java.install.failed.invalid=該檔案不是合法的 Java 安裝包,無法繼續安裝。 +java.install.failed.unsupported_platform=此 Java 與目前平臺不相容,無法安裝。 +java.install.name=名稱 +java.install.warning.invalid_character=名稱中包含無效字元 +java.reveal=瀏覽 Java 目錄 +java.uninstall=移除此 Java +java.uninstall.confirm=你確定要移除此 Java 嗎?此操作無法復原! + +lang=繁體中文 lang.default=使用系統語言 -launch.advice=%s是否繼續啟動? +launch.advice=%s 是否繼續啟動? launch.advice.multi=檢測到以下問題:\n\n%s\n\n這些問題可能導致遊戲無法正常啟動或影響遊戲體驗,是否繼續啟動? -launch.advice.java.auto=當前選擇的 Java 虛擬機版本不滿足遊戲要求,是否自動選擇合適的 Java 虛擬機版本?或者你可以到遊戲設置中選擇一個合適的 Java 虛擬機版本。 -launch.advice.java.modded_java_7=Minecraft 1.7.2 及以下版本需要 Java 7 及以下版本。 -launch.advice.corrected=我們已經修正了問題。如果您確實希望使用您自訂的 Java 虛擬機,您可以在遊戲設定中關閉 Java 虛擬機相容性檢查。 -launch.advice.uncorrected=如果您確實希望使用您自訂的 Java 虛擬機,您可以在遊戲設定中關閉 Java 虛擬機相容性檢查。 +launch.advice.java.auto=目前選取的 Java 虛擬機版本不滿足遊戲要求,是否自動選取合適的 Java 虛擬機版本?\n或者你可以到「(全域/實例特定) 遊戲設定 → 遊戲 Java」中選取一個合適的 Java 虛擬機版本。 +launch.advice.java.modded_java_7=Minecraft 1.7.2 及更低版本需要 Java 7 及更低版本。 +launch.advice.corrected=我們已經修正了問題。如果你確實希望使用你自訂的 Java 虛擬機,你可以在「(全域/實例特定) 遊戲設定 → 進階設定」中往下滑,開啟「不檢查 JVM 與遊戲的相容性」。 +launch.advice.uncorrected=如果你確實希望使用你自訂的 Java 虛擬機,你可以在「(全域/實例特定) 遊戲設定 → 進階設定」中往下滑,開啟「不檢查 JVM 與遊戲的相容性」。 launch.advice.different_platform=你正在使用 32 位元 Java 啟動遊戲,建議更換至 64 位元 Java。 -launch.advice.forge2760_liteloader=Forge 2760 與 LiteLoader 不相容,請更新 Forge 到 2773 或更新的版本。 -launch.advice.forge28_2_2_optifine=Forge 28.2.2 或更高版本與 OptiFine 不相容,請将 Forge 降級至 28.2.1 或更低版本。 -launch.advice.forge37_0_60=Forge 低於 37.0.60 的版本不相容 Java 17。請更新 Forge 到 37.0.60 或更高版本,或者使用 Java 16 啟動遊戲。 -launch.advice.java8_1_13=Minecraft 1.13 只支援 Java 8 或更高版本,請使用 Java 8 或最新版本。 -launch.advice.java8_51_1_13=低於 1.8.0_51 的 Java 版本可能會導致 Minecraft 1.13 崩潰。建議您到 https://java.com 安裝最新版的 Java 8。 -launch.advice.java9=低於 (包含) 1.13 的有安裝 Mod 的 Minecraft 版本不支援 Java 9 或更高版本,請使用 Java 8。 -launch.advice.modded_java=部分 Mod 可能與高版本 Java 不相容,建議使用 Java %s 啟動 Minecraft %s。 -launch.advice.modlauncher8=您所使用的 Forge 版本與當前使用的 Java 不相容。請嘗試更新 Forge,或使用 Java 8u312/11.0.13/17.0.1 及更早版本啟動。是否繼續啟動? -launch.advice.newer_java=偵測到你正在使用舊版本 Java 啟動遊戲,這可能導致部分 Mod 引發遊戲崩潰,建議更新至 Java 8 後再次啟動。 -launch.advice.not_enough_space=您設定的記憶體大小過大,由於超過了系統記憶體大小 %dMB,所以可能影響遊戲體驗或無法啟動遊戲。是否繼續啟動? -launch.advice.require_newer_java_version=Minecraft %1$s 僅能運行在 Java %2$s 或更高版本上,但 HMCL 未能找到該 Java 版本,你可以點擊“是”,HMCL 會自動下載他,是否下載? -launch.advice.too_large_memory_for_32bit=您設定的記憶體大小過大,由於可能超過了 32 位元 Java 的記憶體分配限制,所以可能無法啟動遊戲,請將記憶體調至低於 1024MB 的值。 -launch.advice.vanilla_linux_java_8=對於 Linux x86-64 平台,Minecraft 1.12.2 及以下版本與 Java 9+ 不相容,請使用 Java 8 啟動遊戲。 -launch.advice.vanilla_x86.translation=Minecraft 尚未為您的平臺提供完善支持,所以可能影響遊戲體驗或無法啟動遊戲。\n你可以在 這裡 下載 X86-64 架構的 Java 以獲得更完整的體驗。\n是否繼續啟動? +launch.advice.forge2760_liteloader=Forge 14.23.5.2760 與 LiteLoader 不相容,請更新 Forge 至 14.23.5.2773 或更高版本。 +launch.advice.forge28_2_2_optifine=Forge 28.2.2 及更高版本與 OptiFine 不相容,請降級 Forge 至 28.2.1 或更低版本。 +launch.advice.forge37_0_60=Forge 37.0.59 及更低版本與 Java 17 不相容。請更新 Forge 到 37.0.60 或更高版本,或者使用 Java 16 啟動遊戲。 +launch.advice.java8_1_13=Minecraft 1.13 及更高版本僅支援 Java 8 或更高版本,請使用 Java 8 或最新版本。 +launch.advice.java8_51_1_13=低於 1.8.0_51 的 Java 版本可能會導致 Minecraft 1.13 崩潰。建議你到 https://java.com 安裝最新版的 Java 8。 +launch.advice.java9=低於 (包含) 1.13 的有安裝模組的 Minecraft 版本不支援 Java 9 或更高版本,請使用 Java 8。 +launch.advice.modded_java=部分模組可能與高版本 Java 不相容,建議使用 Java %s 啟動 Minecraft %s。 +launch.advice.modlauncher8=你所使用的 Forge 版本與目前使用的 Java 不相容。請更新 Forge。 +launch.advice.newer_java=偵測到你正在使用舊版本 Java 啟動遊戲,這可能導致部分模組引發遊戲崩潰,建議更新至 Java 8 後再次啟動。 +launch.advice.not_enough_space=你設定的記憶體大小過大,由於超過了系統記憶體大小 %d MB,所以可能影響遊戲體驗或無法啟動遊戲。 +launch.advice.require_newer_java_version=目前遊戲版本需要 Java %s,但 HMCL 未能找到該 Java 版本,你可以點擊「是」,HMCL 會自動下載他,是否下載? +launch.advice.too_large_memory_for_32bit=你設定的記憶體大小過大,由於可能超過了 32 位元 Java 的記憶體分配限制,所以可能無法啟動遊戲,請將記憶體調至低於 1024 MB 的值。 +launch.advice.vanilla_linux_java_8=對於 Linux x86-64 平台,Minecraft 1.12.2 及更低版本與 Java 9+ 不相容,請使用 Java 8 啟動遊戲。 +launch.advice.vanilla_x86.translation=Minecraft 尚未為你的平臺提供完善支援,所以可能影響遊戲體驗或無法啟動遊戲。\n你可以在 這裡 下載 x86-64 架構的 Java 以獲得更完整的體驗。\n是否繼續啟動? +launch.advice.unknown=由於以下原因,無法繼續啟動遊戲: launch.failed=啟動失敗 -launch.failed.cannot_create_jvm=偵測到無法建立 Java 虛擬機,可能是 Java 參數有問題。可以在設定中開啟無參數模式啟動。 +launch.failed.cannot_create_jvm=無法建立 Java 虛擬機,可能是 Java 參數有問題。可以在「(全域/實例特定) 遊戲設定 → 進階設定 → Java 虛擬機設定」中移除所有 Java 虛擬機參數後,嘗試再次啟動遊戲。 launch.failed.creating_process=啟動失敗,在建立新處理程式時發生錯誤。可能是 Java 路徑錯誤。 -launch.failed.command_too_long=命令長度超過限制,無法創建 bat 腳本,請匯出為 PowerShell 腳本。 -launch.failed.decompressing_natives=無法解壓縮遊戲資源庫。 -launch.failed.download_library=無法下載遊戲相依元件 %s。 +launch.failed.command_too_long=指令長度超過限制,無法建立批次檔指令碼,請匯出為 PowerShell 指令碼。 +launch.failed.decompressing_natives=無法解壓縮遊戲本機庫。 +launch.failed.download_library=無法下載遊戲相依元件「%s」。 launch.failed.executable_permission=無法為啟動檔案新增執行權限。 launch.failed.execution_policy=設定執行策略 launch.failed.execution_policy.failed_to_set=設定執行策略失敗 -launch.failed.execution_policy.hint=當前執行策略封锁您執行 PowerShell 腳本。\n點擊“確定”允許當前用戶執行本地 PowerShell 腳本,或點擊“取消”保持現狀。 -launch.failed.exited_abnormally=遊戲非正常退出,請查看記錄檔案,或聯絡他人尋求幫助。 -launch.failed.no_accepted_java=找不到適合當前遊戲使用的 Java,是否使用默認 Java 啟動遊戲?點擊“是”使用默認 Java 繼續啟動遊戲,\n或者請到遊戲設定中選擇一個合適的Java虛擬機器版本。 -launch.failed.sigkill=遊戲被用戶或系統強制終止。 +launch.failed.execution_policy.hint=目前執行策略封鎖你執行 PowerShell 指令碼。\n點擊「確定」允許目前使用者執行本機 PowerShell 指令碼,或點擊「取消」保持現狀。 +launch.failed.exited_abnormally=遊戲非正常退出,請查看日誌檔案,或聯絡他人尋求幫助。 +launch.failed.java_version_too_low=你所指定的 Java 版本過低,請重新設定 Java 版本。 +launch.failed.no_accepted_java=找不到適合目前遊戲使用的 Java,是否使用預設 Java 啟動遊戲?點擊「是」使用預設 Java 繼續啟動遊戲,\n或者請到「(全域/實例特定) 遊戲設定 → 遊戲 Java」中選取一個合適的 Java 虛擬機版本。 +launch.failed.sigkill=遊戲被使用者或系統強制終止。 launch.state.dependencies=處理遊戲相依元件 launch.state.done=啟動完成 launch.state.java=檢測 Java 版本 launch.state.logging_in=登入 launch.state.modpack=下載必要檔案 launch.state.waiting_launching=等待遊戲啟動 -launch.wrong_javadir=Java 路徑錯誤,將自動重設為預設 Java 路徑。 +launch.invalid_java=目前設定的 Java 路徑無效,請重新設定 Java 路徑。 launcher=啟動器 -launcher.agreement=用戶協議與免責聲明 +launcher.agreement=使用者協議與免責宣告 launcher.agreement.accept=同意 launcher.agreement.decline=拒絕 -launcher.agreement.hint=同意本軟體的用戶協議與免責聲明以使用本軟體。 -launcher.background=背景位址 -launcher.background.choose=選擇背景路徑 +launcher.agreement.hint=同意本軟體的使用者協議與免責宣告以使用本軟體。 +launcher.background=背景圖片 +launcher.background.choose=選取背景圖片 launcher.background.classic=經典 -launcher.background.default=預設(自動尋找啟動器同目錄下的 background.png/jpg/gif 及 bg 資料夾內的圖片) +launcher.background.default=預設 (自動尋找啟動器同目錄下的「background.png/.jpg/.gif」及「bg」目錄內的圖片) launcher.background.network=網路 launcher.background.translucent=半透明 launcher.cache_directory=檔案下載快取目錄 launcher.cache_directory.clean=清理 -launcher.cache_directory.choose=選擇檔案下載快取目錄 -launcher.cache_directory.default=預設 +launcher.cache_directory.choose=選取檔案下載快取目錄 +launcher.cache_directory.default=預設 ("%AppData%/.minecraft" 或 "~/.minecraft") launcher.cache_directory.disabled=停用 launcher.cache_directory.invalid=無法建立自訂的快取目錄,還原至預設設定 launcher.contact=聯絡我們 -launcher.crash=Hello Minecraft! Launcher 遇到了無法處理的錯誤,請複製下列內容並透過 MCBBS、貼吧、GitHub 或 Minecraft Forum 回報 bug。 -launcher.crash.java_internal_error=HHello Minecraft! Launcher 由於當前 Java 損壞而無法繼續運行,請卸載當前 Java,點擊 此處 安裝合適的 Java 版本。 -launcher.crash.hmcl_out_dated=Hello Minecraft! Launcher 遇到了無法處理的錯誤,已偵測到您的啟動器不是最新版本,請更新後重試! -launcher.update_java=請更新您的 Java +launcher.crash=Hello Minecraft! Launcher 遇到了無法處理的錯誤,請複製下列內容並透過 GitHub、Discord 或 HMCL QQ 群回報問題。 +launcher.crash.java_internal_error=Hello Minecraft! Launcher 由於目前 Java 損壞而無法繼續執行,請移除目前 Java,點擊 此處 安裝合適的 Java 版本。 +launcher.crash.hmcl_out_dated=Hello Minecraft! Launcher 遇到了無法處理的錯誤,已偵測到你的啟動器不是最新版本,請更新後重試! +launcher.update_java=請更新你的 Java -login.empty_username=你還未設定使用者名稱! -login.enter_password=請輸入您的密碼 +login.empty_username=你還未設定使用者名稱! +login.enter_password=請輸入你的密碼 logwindow.show_lines=顯示行數 logwindow.terminate_game=結束遊戲處理程式 -logwindow.title=記錄 -logwindow.help=你可以前往 HMCL 社區,尋找他人幫助 +logwindow.title=日誌 +logwindow.help=你可以前往 HMCL 社群,向他人尋求幫助 logwindow.autoscroll=自動滾動 -logwindow.export_game_crash_logs=導出遊戲崩潰訊息 -logwindow.export_dump.dependency_ok.button=導出遊戲運行棧 -logwindow.export_dump.dependency_ok.doing_button=正在導出遊戲運行棧(可能需要 15 秒) -logwindow.export_dump.no_dependency.button=導出遊戲運行棧(不兼容) -logwindow.export_dump.no_dependency.tooltip=你的 Java 不包含用於創建遊戲運行棧的依賴。請前往 HMCL QQ 群或 Discord 频道尋求幫助。 +logwindow.export_game_crash_logs=匯出遊戲崩潰訊息 +logwindow.export_dump=匯出遊戲執行堆疊 +logwindow.export_dump.no_dependency=你的 Java 不包含用於建立遊戲執行堆疊的相依元件。請前往 Discord 或 HMCL QQ 群尋求幫助。 main_page=首頁 message.cancelled=操作被取消 message.confirm=提示 -message.copied=已複製到剪貼板 +message.copied=已複製到剪貼簿 message.default=預設 message.doing=請耐心等待 -message.downloading=正在下載… +message.downloading=正在下載…… message.error=錯誤 message.failed=操作失敗 -message.info=資訊 +message.info=提示 message.success=完成 message.unknown=未知 message.warning=警告 modpack=模組包 -modpack.choose=選擇要安裝的遊戲模組包檔案 +modpack.choose=選取要安裝的遊戲模組包檔案 modpack.choose.local=匯入本機模組包檔案 modpack.choose.local.detail=你可以直接將模組包檔案拖入本頁面以安裝 modpack.choose.remote=從網路下載模組包 modpack.choose.remote.detail=需要提供模組包的下載連結 -modpack.choose.repository= 從 Curseforge / Modrinth 下載整合包 -modpack.choose.repository.detail=下載后記得回到這個界面,把整合包拖進來哦 +modpack.choose.repository=從 CurseForge/Modrinth 下載模組包 +modpack.choose.repository.detail=下載後記得回到這個介面,把模組包拖進來哦 modpack.choose.remote.tooltip=要下載的模組包的連結 modpack.completion=下載模組包相關檔案 -modpack.desc=描述你要製作的模組包,比如模組包注意事項和更新記錄,支援 Markdown(圖片請上傳至網路)。 +modpack.desc=描述你要製作的模組包,比如模組包注意事項和更新紀錄,支援 Markdown (圖片請上傳至網路)。 modpack.description=模組包描述 modpack.download=下載模組包 modpack.enter_name=給遊戲取個你喜歡的名稱 modpack.export=匯出模組包 -modpack.export.as=請選擇模組包類型。若你無法決定,請選擇 HMCL 類型。 +modpack.export.as=請選取模組包類型。若你無法決定,請選取 MCBBS 類型。 modpack.file_api=模組包下載連結前綴 modpack.files.blueprints=BuildCraft 藍圖 -modpack.files.config=Mod 模組設定檔案 +modpack.files.config=模組設定檔案 modpack.files.dumps=NEI 調校輸出 modpack.files.hmclversion_cfg=啟動器設定檔案 -modpack.files.liteconfig=Mod 模組設定檔案 -modpack.files.mods=Mod 模組 +modpack.files.liteconfig=LiteLoader 相關檔案 +modpack.files.mods=模組 modpack.files.mods.voxelmods=VoxelMods 設定,如小地圖 modpack.files.options_txt=遊戲設定 modpack.files.optionsshaders_txt=光影設定 -modpack.files.resourcepacks=資源包 (材質包) +modpack.files.resourcepacks=資源包 (紋理包) modpack.files.saves=遊戲存檔 modpack.files.scripts=MineTweaker 設定 -modpack.files.servers_dat=多人遊戲伺服器列表 +modpack.files.servers_dat=多人遊戲伺服器清單 modpack.install=安裝 %s 模組包 modpack.installing=正在安裝模組包 modpack.introduction=支援 Curse、Modrinth、MultiMC、MCBBS 模組包。 modpack.invalid=無效的模組包升級檔案,可能是下載時出現問題。 -modpack.mismatched_type=模組包類型不符,目前遊戲是 %s 模組包,但是提供的模組包更新檔案是 %s 模組包。 +modpack.mismatched_type=模組包類型不符,目前遊戲是「%s」模組包,但是提供的模組包更新檔案是「%s」模組包。 modpack.name=模組包名稱 modpack.not_a_valid_name=模組包名稱無效 modpack.origin=來源 modpack.origin.url=官方網站 modpack.origin.mcbbs=MCBBS -modpack.origin.mcbbs.prompt=貼子 id +modpack.origin.mcbbs.prompt=帖子 ID modpack.scan=解析模組包 modpack.task.install=匯入模組包 -modpack.task.install.error=無法識別該模組包,目前僅支援匯入 Curse、Modrinth、MultiMC、MCBBS 模組包。 -modpack.task.install.will=將會安裝模組包: +modpack.task.install.error=無法識別該模組包,目前僅支援匯入 Curse、Modrinth、MultiMC 和 MCBBS 模組包。 modpack.type.curse=Curse -modpack.type.curse.tolerable_error=但未能完成該模組包檔案的下載,您可以在啟動該遊戲版本時繼續該模組包檔案的下載。由於網路問題,您可能需要重試多次…… -modpack.type.curse.error=無法完成該模組包所需的依賴下載,請多次重試或設定代理…… +modpack.type.curse.error=無法完成該模組包所需的相依元件下載,請多次重試或設定代理…… modpack.type.curse.not_found=部分必需檔案已經從網路中被刪除並且再也無法下載,請嘗試該模組包的最新版本或者安裝其他模組包。 -modpack.type.manual.warning=該模組包由發佈者手動打包,其中可能已經包含啟動器,建議嘗試解壓後使用其自帶的啟動器運行遊戲。\nHMCL 可以嘗試導入該模組包,但不保證可用性,是否繼續? -modpack.type.mcbbs=我的世界中文論壇模組包標準 +modpack.type.manual.warning=該模組包由發佈者手動打包,其中可能已經包含啟動器,建議嘗試解壓後使用其內建的啟動器執行遊戲。\nHMCL 可以嘗試匯入該模組包,但不保證可用性,是否繼續? +modpack.type.mcbbs=MCBBS modpack.type.mcbbs.export=可以被 Hello Minecraft! Launcher 匯入 modpack.type.modrinth=Modrinth modpack.type.multimc=MultiMC @@ -685,20 +700,20 @@ modpack.type.multimc.export=可以被 Hello Minecraft! Launcher 和 MultiMC 匯 modpack.type.server=伺服器自動更新模組包 modpack.type.server.export=允許伺服器管理員遠端更新遊戲用戶端 modpack.type.server.malformed=伺服器模組包配置格式錯誤,請聯絡伺服器管理員解決此問題 -modpack.unsupported=Hello Minecraft! Launcher 不支持該綜合包格式 +modpack.unsupported=Hello Minecraft! Launcher 不支援該模組包格式 modpack.update=正在升級模組包 modpack.wizard=匯出模組包引導 modpack.wizard.step.1=基本設定 modpack.wizard.step.1.title=設定模組包的主要訊息 -modpack.wizard.step.2=檔案選擇 -modpack.wizard.step.2.title=選中你想加到模組包中的檔案或資料夾 +modpack.wizard.step.2=選取檔案 +modpack.wizard.step.2.title=選中你想加到模組包中的檔案或目錄 modpack.wizard.step.3=模組包類型 -modpack.wizard.step.3.title=選擇模組包匯出類型 +modpack.wizard.step.3.title=選取模組包匯出類型 modpack.wizard.step.initialization.exported_version=要匯出的遊戲版本 -modpack.wizard.step.initialization.force_update=強制升級模組包至最新版本(需要自建伺服器) +modpack.wizard.step.initialization.force_update=強制升級模組包至最新版本 (需要自建伺服器) modpack.wizard.step.initialization.include_launcher=包含啟動器 -modpack.wizard.step.initialization.save=選擇要匯出到的遊戲模組包位置 -modpack.wizard.step.initialization.warning=在製作模組包前,請您確認您選擇的版本可以正常啟動,\n並保證您的 Minecraft 是正式版而非快照版,\n而且不應將不允許非官方途徑傳播的 Mod 模組、材質包等納入模組包。\n模組包會儲存您目前的下載來源設定 +modpack.wizard.step.initialization.save=選取要匯出到的遊戲模組包位置 +modpack.wizard.step.initialization.warning=在製作模組包前,請你確認你選取的實例可以正常啟動,\n並保證你的 Minecraft 是正式版而非快照,\n而且不應將不允許非官方途徑傳播的模組、資源 (紋理) 包等納入模組包。\n模組包會儲存你目前的下載來源設定。 modpack.wizard.step.initialization.server=點選此處查看有關伺服器自動更新模組包的製作教學 modrinth.category.adventure=冒險 @@ -707,7 +722,7 @@ modrinth.category.blocks=方塊 modrinth.category.bukkit=Bukkit modrinth.category.bungeecord=BungeeCord modrinth.category.challenging=高難度 -modrinth.category.core-shaders=覈心著色器 +modrinth.category.core-shaders=核心著色器 modrinth.category.combat=戰鬥 modrinth.category.cursed=Cursed modrinth.category.decoration=裝飾 @@ -723,7 +738,7 @@ modrinth.category.game-mechanics=遊戲機制 modrinth.category.gui=GUI modrinth.category.items=物品 modrinth.category.kitchen-sink=大雜燴 -modrinth.category.library=支持庫 +modrinth.category.library=支援庫 modrinth.category.lightweight=輕量 modrinth.category.liteloader=LiteLoader modrinth.category.locale=在地化 @@ -735,7 +750,7 @@ modrinth.category.misc=其他 modrinth.category.mobs=生物 modrinth.category.modded=Modded modrinth.category.models=模型 -modrinth.category.modloader=Modloader +modrinth.category.modloader=ModLoader modrinth.category.multiplayer=多人 modrinth.category.neoforge=NeoForge modrinth.category.optimization=最佳化 @@ -753,39 +768,42 @@ modrinth.category.storage=儲存 modrinth.category.technology=科技 modrinth.category.themed=主題 modrinth.category.transportation=運輸 -modrinth.category.tweaks=優化 +modrinth.category.tweaks=最佳化 modrinth.category.utility=實用 modrinth.category.vanilla-like=類原生 modrinth.category.velocity=Velocity modrinth.category.waterfall=Waterfall modrinth.category.worldgen=世界生成 -modrinth.category.datapack=數據包 +modrinth.category.datapack=資料包 modrinth.category.folia=Folia mods=模組 mods.add=新增模組 -mods.add.failed=新增模組 %s 失敗。 -mods.add.success=成功新增模組 %s。 -mods.broken_dependency.title=損壞的前置模組 -mods.broken_dependency.desc=該前置模組曾經在該模組倉庫上存在過,但現在被刪除了,換個下載源試試吧。 +mods.add.failed=新增模組「%s」失敗。 +mods.add.success=成功新增模組「%s」。 +mods.broken_dependency.title=損壞的相依模組 +mods.broken_dependency.desc=該相依模組曾經存在於模組倉庫中,但現在已被刪除,請嘗試其他下載源。 mods.category=類別 +mods.channel.alpha=Alpha +mods.channel.beta=Beta +mods.channel.release=Release mods.check_updates=檢查模組更新 -mods.check_updates.current_version=當前版本 +mods.check_updates.current_version=目前版本 mods.check_updates.empty=沒有需要更新的模組 mods.check_updates.failed=部分檔案下載失敗 mods.check_updates.file=檔案 mods.check_updates.source=來源 mods.check_updates.target_version=目標版本 mods.check_updates.update=更新 -mods.choose_mod=選擇模組 +mods.choose_mod=選取模組 mods.curseforge=CurseForge -mods.dependency.embedded=內置前端模組(作者已經打包在模組檔中,無需額外下載) -mods.dependency.optional=可選的前模組(如果缺少遊戲,遊戲可以運行,但模組功能可能缺失) -mods.dependency.required=必需的預模式(必須單獨下載,缺少可能會導致遊戲無法啟動) -mods.dependency.tool=前端庫(必須單獨下載,缺少可能會導致遊戲無法啟動) -mods.dependency.include=內置前綴模組(作者已經打包在模組檔中,無需額外下載) -mods.dependency.incompatible=模組不相容(同時安裝模組和模組都下載會導致遊戲無法啟動) -mods.dependency.broken=損壞的前綴(這個前綴曾經存在於 mod 倉庫中,但現在已被刪除)。嘗試其他下載源。 +mods.dependency.embedded=內建相依模組 (作者已經打包在模組檔中,無需單獨下載) +mods.dependency.optional=可選相依模組 (如果不安裝,遊戲可以執行,但模組功能可能缺失) +mods.dependency.required=必需相依模組 (必須單獨下載,缺少可能會導致遊戲無法啟動) +mods.dependency.tool=相依庫 (必須單獨下載,缺少可能會導致遊戲無法啟動) +mods.dependency.include=內建相依模組 (作者已經打包在模組檔中,無需單獨下載) +mods.dependency.incompatible=不相容模組 (同時安裝此模組和正在下載的模組會導致遊戲無法啟動) +mods.dependency.broken=損壞的相依模組 (該模組曾經存在於模組倉庫中,但現在已被刪除,請嘗試其他下載源) mods.disable=停用 mods.download=模組下載 mods.download.title=模組下載 - %1s @@ -795,50 +813,57 @@ mods.manage=模組管理 mods.mcbbs=MCBBS mods.mcmod=MC 百科 mods.mcmod.page=MC 百科頁面 -mods.mcmod.search=MC 百科蒐索 +mods.mcmod.search=MC 百科檢索 mods.modrinth=Modrinth mods.name=名稱 -mods.not_modded=你需要先在自動安裝頁面安裝 Fabric、Forge、Quilt 或 LiteLoader 才能進行模組管理。 +mods.not_modded=你需要先在「自動安裝」頁面安裝 Forge、NeoForge、Fabric、Quilt 或 LiteLoader 才能管理模組。 mods.restore=回退 mods.url=官方頁面 -mods.update_modpack_mod.warning=更新模組包中的 Mod 可能導致綜合包損壞,使模組包無法正常啟動。該操作不可逆,確定要更新嗎? +mods.update_modpack_mod.warning=更新模組包中的模組可能導致模組包損壞,使模組包無法正常啟動。該操作不可逆,確定要更新嗎? +mods.install=安裝到目前實例 +mods.save_as=下載到本機目錄 nbt.entries=%s 個條目 nbt.open.failed=打開檔案失敗 -nbt.save.failed=保存檔案失敗 +nbt.save.failed=儲存檔案失敗 nbt.title=查看檔案 - %s datapack=資料包 datapack.add=加入資料包 -datapack.choose_datapack=選擇要匯入的資料包壓縮檔 +datapack.choose_datapack=選取要匯入的資料包壓縮檔 datapack.extension=資料包 -datapack.title=世界 %s - 資料包 +datapack.title=世界 [%s] - 資料包 + +web.failed=加載頁面失敗 +web.open_in_browser=是否要在瀏覽器中打開此連結:\n%s +web.view_in_browser=在瀏覽器中查看 world=世界 world.add=加入世界 world.datapack=管理資料包 world.datapack.1_13=僅 Minecraft 1.13 及之後的版本支援資料包 -world.description=%s. 上一次遊戲時間: %s. 遊戲版本: %s +world.description=%s | 上一次遊戲時間: %s | 遊戲版本: %s world.download=存檔下載 world.export=匯出此世界 -world.export.title=選擇該世界的儲存位置 +world.export.title=選取該世界的儲存位置 world.export.location=儲存到 -world.export.wizard=匯出世界 %s +world.export.wizard=匯出世界「%s」 world.extension=存檔壓縮檔 world.import.already_exists=此世界已經存在 -world.import.choose=選擇要匯入的存檔壓縮檔 +world.import.choose=選取要匯入的存檔壓縮檔 world.import.failed=無法匯入此世界: %s -world.import.invalid=無法識別的存檔壓縮包 -world.info.title=世界 %s - 世界資訊 +world.import.invalid=無法識別的存檔壓縮檔 +world.info.title=世界 [%s] - 世界資訊 world.info.basic=基本資訊 -world.info.allow_cheats=允許作弊 -world.info.dimension.the_nether=下界 +world.info.allow_cheats=允許指令 +world.info.dimension.the_nether=地獄 world.info.dimension.the_end=末地 -world.info.difficulty=難度 +world.info.difficulty=難易度 world.info.difficulty.peaceful=和平 world.info.difficulty.easy=簡單 world.info.difficulty.normal=普通 world.info.difficulty.hard=困難 +world.info.failed=讀取世界資訊失敗 world.info.game_version=遊戲版本 world.info.last_played=上一次遊戲時間 world.info.generate_features=生成建築 @@ -847,42 +872,42 @@ world.info.player.food_level=饑餓值 world.info.player.game_type=遊戲模式 world.info.player.game_type.adventure=冒險 world.info.player.game_type.creative=創造 -world.info.player.game_type.spectator=旁觀 +world.info.player.game_type.spectator=旁觀者 world.info.player.game_type.survival=生存 world.info.player.health=生命值 world.info.player.last_death_location=上次死亡位置 world.info.player.location=位置 world.info.player.spawn=床/重生錨位置 world.info.player.xp_level=經驗等級 -world.info.random_seed=種子 +world.info.random_seed=種子碼 world.info.time=遊戲內時間 world.info.time.format=%s 天 world.game_version=遊戲版本 world.manage=世界/資料包 world.name=世界名稱 world.name.enter=輸入世界名稱 -world.reveal=開啟資料夾 +world.reveal=開啟目錄 world.show_all=全部顯示 -world.time=yyyy年MM月dd日 HH:mm:ss +world.time=yyyy 年 M 月 d 日, HH:mm:ss profile=遊戲目錄 profile.already_exists=該名稱已存在 profile.default=目前目錄 profile.home=官方啟動器目錄 -profile.instance_directory=遊戲路徑 -profile.instance_directory.choose=選擇遊戲路徑 -profile.manage=遊戲目錄列表 +profile.instance_directory=遊戲目錄路徑 +profile.instance_directory.choose=選取遊戲目錄路徑 +profile.manage=遊戲目錄清單 profile.name=名稱 -profile.new=建立設定 +profile.new=建立新目錄 profile.title=遊戲目錄 profile.selected=已選取 -profile.use_relative_path=如可行,則在遊戲目錄使用相對路徑 +profile.use_relative_path=如可行,則對於遊戲目錄使用相對路徑 -repositories.custom=自定義 Maven 倉庫(%s) -repositories.maven_central=全球(Maven Central) -repositories.tencentcloud_mirror=中國大陸(騰訊雲 Maven 倉庫) -repositories.chooser=缺少 JavaFX 運行環境,HMCL 需要 JavaFX 才能正常運行。\n點擊“確認”從指定下載源下載 JavaFX 運行時組件並啟動 HMCL,點擊“取消”退出程式。\n選擇下載源: -repositories.chooser.title=選擇下載源下載JavaFX +repositories.custom=自訂 Maven 倉庫 (%s) +repositories.maven_central=全球 (Maven Central) +repositories.tencentcloud_mirror=中國大陸 (騰訊雲 Maven 倉庫) +repositories.chooser=缺少 JavaFX 執行環境,HMCL 需要 JavaFX 才能正常執行。\n點擊「確認」從指定下載源下載 JavaFX 執行時元件並開啟 HMCL,點擊「取消」退出程式。\n選取下載源: +repositories.chooser.title=選取 JavaFX 下載源 resourcepack=資源包 @@ -897,41 +922,41 @@ search.next_page=下一頁 search.last_page=最後一頁 search.page_n=%d / %s -selector.choose=選擇 -selector.choose_file=選擇檔案 +selector.choose=選取 +selector.choose_file=選取檔案 selector.custom=自訂 -settings=遊戲設定 +settings=設定 settings.advanced=進階設定 -settings.advanced.modify=修改進階設定 +settings.advanced.modify=編輯進階設定 settings.advanced.title=進階設定 - %s -settings.advanced.custom_commands=自訂命令 -settings.advanced.custom_commands.hint=自訂命令被調用時將包含如下的環境變數:\n\ - \ - $INST_NAME: 版本名稱\n\ - \ - $INST_ID: 版本名稱\n\ - \ - $INST_DIR: 版本資料夾\n\ - \ - $INST_MC_DIR: 遊戲運行路徑\n\ - \ - $INST_JAVA: 遊戲運行使用的 Java 路徑\n\ - \ - $INST_FORGE: 若安裝了 Forge,將會存在本環境變數\n\ - \ - $INST_NEOFORGE: 若安裝了 NeoForge,將會存在本環境變數\n\ - \ - $INST_LITELOADER: 若安裝了 LiteLoader,將會存在本環境變數\n\ - \ - $INST_OPTIFINE: 若安裝了 OptiFine,將會存在本環境變數\n\ - \ - $INST_FABRIC: 若安裝了 Fabric,將會存在本環境變數\n\ - \ - $INST_QUILT: 若安裝了 Quilt,將會存在本環境變數 +settings.advanced.custom_commands=自訂指令 +settings.advanced.custom_commands.hint=自訂指令被呼叫時將包含如下的環境變數:\n\ + \ · $INST_NAME: 實例名稱;\n\ + \ · $INST_ID: 實例名稱;\n\ + \ · $INST_DIR: 目前實例執行路徑;\n\ + \ · $INST_MC_DIR: 目前遊戲目錄路徑;\n\ + \ · $INST_JAVA: 遊戲執行使用的 Java 路徑;\n\ + \ · $INST_FORGE: 若安裝了 Forge,將會存在本環境變數;\n\ + \ · $INST_NEOFORGE: 若安裝了 NeoForge,將會存在本環境變數;\n\ + \ · $INST_LITELOADER: 若安裝了 LiteLoader,將會存在本環境變數;\n\ + \ · $INST_OPTIFINE: 若安裝了 OptiFine,將會存在本環境變數;\n\ + \ · $INST_FABRIC: 若安裝了 Fabric,將會存在本環境變數;\n\ + \ · $INST_QUILT: 若安裝了 Quilt,將會存在本環境變數。 settings.advanced.dont_check_game_completeness=不檢查遊戲完整性 settings.advanced.dont_check_jvm_validity=不檢查 JVM 與遊戲的相容性 -settings.advanced.dont_patch_natives=不嘗試自動替換本機庫 +settings.advanced.dont_patch_natives=不嘗試自動取代本機庫 settings.advanced.environment_variables=環境變數 -settings.advanced.game_dir.default=預設(.minecraft/) -settings.advanced.game_dir.independent=各版本獨立(.minecraft/versions/<版本名>/,除 assets、libraries) +settings.advanced.game_dir.default=預設 (".minecraft/") +settings.advanced.game_dir.independent=各實例獨立 (".minecraft/versions/<實例名>/",除 assets、libraries 外) settings.advanced.java_permanent_generation_space=記憶體永久儲存區域 -settings.advanced.java_permanent_generation_space.prompt=格式: MB +settings.advanced.java_permanent_generation_space.prompt=單位 MB settings.advanced.jvm=Java 虛擬機設定 settings.advanced.jvm_args=Java 虛擬機參數 -settings.advanced.jvm_args.prompt=- 若在“Java 虛擬機參數”中輸入的參數與默認參數相同,則不會添加\n\ -- 在“Java 虛擬機參數”輸入任何 GC 參數,默認參數的 G1 參數會禁用\n\ -- 點擊下方“不添加默認的 JVM 參數”可在啟動遊戲時不添加默認參數 +settings.advanced.jvm_args.prompt=\ · 若在「Java 虛擬機參數」中輸入的參數與預設參數相同,則不會加入;\n\ + \ · 在「Java 虛擬機參數」輸入任何 GC 參數,預設參數的 G1 參數會禁用;\n\ + \ · 點擊下方「不加入預設的 JVM 參數」可在啟動遊戲時不加入預設參數。 settings.advanced.launcher_visibility.close=遊戲啟動後結束啟動器 settings.advanced.launcher_visibility.hide=遊戲啟動後隱藏啟動器 settings.advanced.launcher_visibility.hide_and_reopen=隱藏啟動器並在遊戲結束後重新開啟 @@ -939,35 +964,35 @@ settings.advanced.launcher_visibility.keep=不隱藏啟動器 settings.advanced.launcher_visible=啟動器可見性 settings.advanced.minecraft_arguments=Minecraft 額外參數 settings.advanced.minecraft_arguments.prompt=預設 -settings.advanced.natives_directory=本地庫路徑(LWJGL) -settings.advanced.natives_directory.choose=選擇本地庫路徑 -settings.advanced.natives_directory.custom=自訂(由你提供遊戲需要的本地庫) -settings.advanced.natives_directory.default=預設(由啟動器提供遊戲本地庫) -settings.advanced.natives_directory.hint=本選項提供給 Apple M1 等未受遊戲官方支持的平台來自訂遊戲本地庫,如果你不知道本選項的含義,請你不要修改本選項,否則會導致遊戲無法啟動。\n如果你要修改本選項,你需要保證自訂目錄下有遊戲所需的本地庫文件,如 lwjgl.dll(liblwjgl.so), openal.dll(libopenal.so) 等文件。啟動器不會幫你補全缺少的本地庫文件。\n注意:建議指定的本地庫文件路徑使用全英文字符,否則可能導致遊戲啟動失敗。 -settings.advanced.no_jvm_args=不使用預設的 JVM 參數 +settings.advanced.natives_directory=本機庫路徑 (LWJGL) +settings.advanced.natives_directory.choose=選取本機庫路徑 +settings.advanced.natives_directory.custom=自訂 (由你提供遊戲需要的本機庫) +settings.advanced.natives_directory.default=預設 (由啟動器提供遊戲本機庫) +settings.advanced.natives_directory.hint=本選項提供給 Apple M1 等未受遊戲官方支援的平台來自訂遊戲本機庫,如果你不知道本選項的含義,請你不要修改本選項,否則會導致遊戲無法啟動。\n如果你要修改本選項,你需要保證自訂目錄下有遊戲所需的本機庫檔案,如 lwjgl.dll (liblwjgl.so), openal.dll (libopenal.so) 等檔案。啟動器不會幫你補全缺少的本機庫檔案。\n注意:建議指定的本機庫檔案路徑使用全英文字元,否則可能導致遊戲啟動失敗。 +settings.advanced.no_jvm_args=不加入預設的 JVM 參數 settings.advanced.precall_command=遊戲啟動前執行指令 settings.advanced.precall_command.prompt=將在遊戲啟動前呼叫使用 -settings.advanced.process_priority=進程優先度 -settings.advanced.process_priority.low=低(節省遊戲占用資源,可能會造成遊戲卡頓) -settings.advanced.process_priority.below_normal=較低(節省遊戲占用資源,可能會造成遊戲卡頓) -settings.advanced.process_priority.normal=中(平衡) -settings.advanced.process_priority.above_normal=較高(優先保證遊戲運行,但可能會導致其他程式卡頓) -settings.advanced.process_priority.high=高(優先保證遊戲運行,但可能會導致其他程式卡頓) -settings.advanced.post_exit_command=遊戲結束後執行命令 +settings.advanced.process_priority=處理程序優先度 +settings.advanced.process_priority.low=低 (節省遊戲占用資源,可能會造成遊戲卡頓) +settings.advanced.process_priority.below_normal=較低 (節省遊戲占用資源,可能會造成遊戲卡頓) +settings.advanced.process_priority.normal=中 (平衡) +settings.advanced.process_priority.above_normal=較高 (優先保證遊戲執行,但可能會導致其他程式卡頓) +settings.advanced.process_priority.high=高 (優先保證遊戲執行,但可能會導致其他程式卡頓) +settings.advanced.post_exit_command=遊戲結束後執行指令 settings.advanced.post_exit_command.prompt=將在遊戲結束後呼叫使用 -settings.advanced.renderer=渲染器 -settings.advanced.renderer.default=OpenGL(默認) -settings.advanced.renderer.d3d12=DirectX 12(效能與相容性較差,用於調試) -settings.advanced.renderer.llvmpipe=軟渲染器(效能較差,相容性最好) -settings.advanced.renderer.zink=Vulkan(效能最好,相容性較差) +settings.advanced.renderer=繪製器 +settings.advanced.renderer.default=OpenGL (預設) +settings.advanced.renderer.d3d12=DirectX 12 (效能與相容性較差,用於除錯) +settings.advanced.renderer.llvmpipe=軟繪製器 (效能較差,相容性最好) +settings.advanced.renderer.zink=Vulkan (效能最好,相容性較差) settings.advanced.server_ip=伺服器位址 settings.advanced.server_ip.prompt=預設,啟動遊戲後直接進入對應伺服器 -settings.advanced.use_native_glfw=使用系統 GLFW -settings.advanced.use_native_openal=使用系統 OpenAL +settings.advanced.use_native_glfw=[僅限 Linux/FreeBSD] 使用系統 GLFW +settings.advanced.use_native_openal=[僅限 Linux/FreeBSD] 使用系統 OpenAL settings.advanced.workaround=除錯選項 settings.advanced.workaround.warning=除錯選項僅提供給專業玩家使用。修改除錯選項可能會導致遊戲無法啟動。除非你知道你在做什麼,否則請不要修改這些選項。 settings.advanced.wrapper_launcher=前置指令 -settings.advanced.wrapper_launcher.prompt=如填寫 optirun 後,啟動命令將從 "java ..." 變為 "optirun java ..." +settings.advanced.wrapper_launcher.prompt=如填寫「optirun」後,啟動指令將從「java ...」變為「optirun java ...」 settings.custom=自訂 @@ -976,43 +1001,45 @@ settings.game.current=遊戲 settings.game.dimension=遊戲介面解析度大小 settings.game.exploration=瀏覽 settings.game.fullscreen=全螢幕 -settings.game.java_directory=Java 路徑 -settings.game.java_directory.auto=自動選擇合適的 Java +settings.game.java_directory=遊戲 Java +settings.game.java_directory.auto=自動選取合適的 Java settings.game.java_directory.auto.not_found=沒有合適的 Java settings.game.java_directory.bit=%s 位 -settings.game.java_directory.choose=選擇 Java 路徑 +settings.game.java_directory.choose=選取 Java settings.game.java_directory.invalid=Java 路徑不正確 -settings.game.java_directory.template=%s(%s) +settings.game.java_directory.version=指定 Java 版本 +settings.game.java_directory.template=%s (%s) settings.game.management=管理 -settings.game.working_directory=執行路徑(版本隔離,修改後請自行移動相關遊戲檔案,如存檔模組設定等) -settings.game.working_directory.choose=選擇執行路徑 -settings.game.working_directory.hint=在“運行路徑(版本隔離)”選項中開啟“各版本獨立”使當前版本獨立存放設定、存檔、模組等數據,使用模組時建議開啟此選項以避免不同版本模組衝突。修改此選項後請自行移動存檔等檔案。 +settings.game.working_directory=執行路徑 (建議使用模組時選取「各實例獨立」,修改後請自行移動相關遊戲檔案,如存檔、模組設定等) +settings.game.working_directory.choose=選取執行目錄 +settings.game.working_directory.hint=在「執行路徑」選項中選取「各實例獨立」使目前實例獨立存放設定、存檔、模組等資料,使用模組時建議開啟此選項以避免不同版本模組衝突。修改此選項後需自行移動存檔等檔案。 settings.icon=遊戲圖示 settings.launcher=啟動器設定 settings.launcher.appearance=外觀 -settings.launcher.common_path.tooltip=啟動器將所有遊戲資源及相依元件庫檔案放於此集中管理,如果遊戲資料夾內有現成的將不會使用公共庫檔案 +settings.launcher.common_path.tooltip=啟動器將所有遊戲資源及相依元件庫檔案放於此集中管理,如果遊戲目錄內有現成的將不會使用公共庫檔案。 settings.launcher.debug=除錯 settings.launcher.download=下載 -settings.launcher.download.threads=並發數 -settings.launcher.download.threads.auto=自動選擇並發數 -settings.launcher.download.threads.hint=並發數過大可能導致系統卡頓。你的下載速度會受到寬頻運營商、伺服器等方面的影響,調大下載並發數不一定能大幅提升總下載速度。 +settings.launcher.download.threads=執行緒數 +settings.launcher.download.threads.auto=自動選取執行緒數 +settings.launcher.download.threads.hint=執行緒數過高可能導致系統卡頓。你的下載速度會受到網際網路運營商、下載來源伺服器等方面的影響,調高下載執行緒數不一定能大幅提升總下載速度。 settings.launcher.download_source=下載來源 -settings.launcher.download_source.auto=自動選擇下載來源 -settings.launcher.enable_game_list=在首頁內顯示遊戲列表 +settings.launcher.download_source.auto=自動選取下載來源 +settings.launcher.enable_game_list=在首頁內顯示遊戲清單 settings.launcher.font=字體 -settings.launcher.general=通用 +settings.launcher.general=一般 settings.launcher.language=語言 (重啟後生效) settings.launcher.launcher_log.export=匯出啟動器日誌 -settings.launcher.launcher_log.export.failed=無法匯出日誌 -settings.launcher.launcher_log.export.success=日誌已儲存到 %s -settings.launcher.log=記錄 -settings.launcher.log.font=記錄字體 +settings.launcher.launcher_log.reveal=打開日誌目錄 +settings.launcher.launcher_log.export.failed=無法匯出日誌。 +settings.launcher.launcher_log.export.success=日誌已儲存到「%s」。 +settings.launcher.log=日誌 +settings.launcher.log.font=日誌字體 settings.launcher.proxy=代理 settings.launcher.proxy.authentication=身份驗證 settings.launcher.proxy.disable=使用系統代理 -settings.launcher.proxy.host=主機 +settings.launcher.proxy.host=IP 位址 settings.launcher.proxy.http=HTTP settings.launcher.proxy.none=不使用代理 settings.launcher.proxy.password=密碼 @@ -1022,7 +1049,7 @@ settings.launcher.proxy.username=帳戶 settings.launcher.theme=主題 settings.launcher.title_transparent=標題欄透明 settings.launcher.turn_off_animations=關閉動畫 (重啟後生效) -settings.launcher.version_list_source=版本列表來源 +settings.launcher.version_list_source=版本清單來源 settings.memory=遊戲記憶體 settings.memory.allocate.auto=最低分配 %1$.1f GB / 實際分配 %2$.1f GB @@ -1033,90 +1060,90 @@ settings.memory.auto_allocate=自動分配 settings.memory.lower_bound=最低分配 settings.memory.used_per_total=已使用 %1$.1f GB / 總記憶體 %2$.1f GB settings.physical_memory=實體記憶體大小 -settings.show_log=查看記錄 -settings.skin=現已支持離線帳戶更換皮膚,你可以到帳戶頁面更改離線帳戶的皮膚和披風(多人遊戲下其他玩家無法看到你的皮膚) +settings.show_log=查看日誌 +settings.skin=現已支援離線帳戶更換外觀,你可以到帳戶頁面更改離線帳戶的外觀和披風 (多人遊戲下其他玩家無法看到你的外觀)。 settings.tabs.installers=自動安裝 settings.take_effect_after_restart=重啟後生效 -settings.type=版本設定類型 -settings.type.global=全域版本設定(使用該設定的版本共用一套設定) +settings.type=實例遊戲設定類型 +settings.type.global=全域遊戲設定 (未啟用「實例特定遊戲設定」的實例共用一套設定) settings.type.global.manage=全域遊戲設定 settings.type.global.edit=編輯全域遊戲設定 -settings.type.special.enable=啟用遊戲特別設定(不影響其他遊戲版本) -settings.type.special.edit=編輯遊戲特定設置 -settings.type.special.edit.hint=當前遊戲版本 %s 啟動了遊戲特定設置,本頁面選項不對當前遊戲生效。點擊連結以修改當前遊戲設置。 +settings.type.special.enable=啟用實例特定遊戲設定 (不影響其他實例) +settings.type.special.edit=編輯實例特定遊戲設定 +settings.type.special.edit.hint=目前實例「%s」啟用了「實例特定遊戲設定」,本頁面選項不對目前實例生效。點擊連結以修改目前實例設定。 sponsor=贊助 -sponsor.bmclapi=大中華區下載源由 BMCLAPI 提供高速下載服務 -sponsor.hmcl=Hello Minecraft! Launcher 是一個免費、開源的 Minecraft 啟動器,允許玩家方便快捷地安裝、管理、執行遊戲。點選此處查閱更多詳細訊息。 +sponsor.bmclapi=中國大陸下載源由 BMCLAPI 提供高速下載服務。點選此處查閱詳細訊息。 +sponsor.hmcl=Hello Minecraft! Launcher 是一個免費、自由、開源的 Minecraft 啟動器,允許玩家方便快捷地安裝、管理、執行遊戲。點選此處查閱詳細訊息。 system.architecture=架構 -system.operating_system=操作系統 +system.operating_system=作業系統 -unofficial.hint=你正在使用非官方構建的 HMCL,我們無法保證其安全性,請注意甄別。 +unofficial.hint=你正在使用第三方提供的 HMCL,我們無法保證其安全性,請注意甄別。 update=啟動器更新 update.accept=更新 update.changelog=更新日誌 -update.channel.dev=測試版 -update.channel.dev.hint=你正在使用測試版。測試版包含一些未在正式版中包含的測試性功能,僅用於體驗新功能。\n\ - 測試版功能未受充分驗證,使用起來可能不穩定!\n\ - 如果你遇到了使用問題,可以在設置的 回饋頁面 中進行回饋,或加入回饋頁面中提供的 DiscordQQ 群以回饋問題。\n\n\ - 點擊此處為當前版本隱藏該提示。 -update.channel.dev.title=測試版提示 +update.channel.dev=開發版 +update.channel.dev.hint=你正在使用 HMCL 開發版。開發版包含一些未在穩定版中包含的測試性功能,僅用於體驗新功能。開發版功能未受充分驗證,使用起來可能不穩定!\n\ + \n\ + 如果你遇到了使用問題,可以透過設定中 回報頁面 提供的管道進行回報。 +update.channel.dev.title=開發版提示 update.channel.nightly=預覽版 -update.channel.nightly.hint=你正在使用預覽版。預覽版可能會每天更新一次,包含一些未在正式版和測試版中包含的測試性功能,僅用於體驗新功能\n\ - 測試版功能未受充分驗證,使用起來可能不穩定!\n\ - 如果你遇到了使用問題,可以在設置的 回饋頁面 中進行回饋,或加入 DiscordQQ 群以回饋問題。 +update.channel.nightly.hint=你正在使用 HMCL 預覽版。預覽版更新較為頻繁,包含一些未在穩定版和開發版中包含的測試性功能,僅用於體驗新功能。預覽版功能未受充分驗證,使用起來可能不穩定!\n\ + \n\ + 如果你遇到了使用問題,可以透過設定中 回報頁面 提供的管道進行回報。 update.channel.nightly.title=預覽版提示 -update.channel.stable=建議版本 +update.channel.stable=穩定版 update.checking=正在檢查更新 update.failed=更新失敗 update.found=發現到更新 -update.newest_version=最新版本為: %s -update.bubble.title=發現更新: %s +update.newest_version=最新版本為:%s +update.bubble.title=發現更新:%s update.bubble.subtitle=點選此處進行升級 -update.note=測試版與開發版包含更多的功能以及錯誤修復,但也可能會包含其他的問題。 +update.note=開發版與預覽版包含更多的功能以及錯誤修復,但也可能會包含其他的問題。 update.latest=目前版本為最新版本 -update.no_browser=無法開啟瀏覽器,網址已經複製到剪貼簿了,您可以手動複製網址開啟頁面 +update.no_browser=無法開啟瀏覽器,網址已經複製到剪貼簿了,你可以手動複製網址開啟頁面。 update.tooltip=更新 version=遊戲 +version.name=遊戲實例名稱 version.cannot_read=讀取遊戲版本失敗,無法進行自動安裝 version.empty=沒有遊戲版本 version.empty.add=進入下載頁安裝遊戲 version.empty.launch=沒有可啟動的遊戲,你可以點擊左側邊欄內的下載按鈕安裝遊戲 version.empty.hint=沒有已安裝的遊戲,你可以切換其他遊戲目錄,或者點擊此處進入遊戲下載頁面 -version.game.old=老舊版本 -version.game.release=穩定版本 -version.game.releases=穩定版本 -version.game.snapshot=測試版本 -version.game.snapshots=測試版本 +version.game.old=遠古版 +version.game.release=正式版 +version.game.releases=正式版 +version.game.snapshot=快照 +version.game.snapshots=快照 version.launch=啟動遊戲 version.launch.test=測試遊戲 -version.switch=切換版本 +version.switch=切換實例 version.launch_script=生成啟動指令碼 version.launch_script.failed=生成啟動指令碼失敗 version.launch_script.save=儲存啟動指令碼 version.launch_script.success=啟動指令碼已生成完畢: %s -version.manage=遊戲列表 +version.manage=實例清單 version.manage.clean=清理遊戲目錄 -version.manage.clean.tooltip=清理 logs, crash-reports +version.manage.clean.tooltip=清理「logs」與「crash-reports」目錄 version.manage.duplicate=複製遊戲實例 version.manage.duplicate.duplicate_save=複製存檔 version.manage.duplicate.prompt=請輸入新遊戲實例名稱 -version.manage.duplicate.confirm=將鎖定複製產生的新遊戲實例:強制版本隔離、遊戲設置獨立。 -version.manage.manage=遊戲管理 -version.manage.manage.title=遊戲管理 - %1s +version.manage.duplicate.confirm=新的遊戲將複製該實例目錄 (".minecraft/versions/<實例名>") 下的檔案,並帶有獨立的執行目錄和設定。 +version.manage.manage=實例管理 +version.manage.manage.title=實例管理 - %1s version.manage.redownload_assets_index=更新遊戲資源檔案 version.manage.remove=刪除該版本 -version.manage.remove.confirm=真的要刪除版本 %s 嗎? 你將無法找回被刪除的檔案! -version.manage.remove.confirm.trash=真的要刪除版本 %s 嗎? 你可以在系統的資源回收桶 (或垃圾桶) 中還原資料夾 %s 來找回該版本。 -version.manage.remove.confirm.independent=由於該遊戲使用了版本隔離,所以刪除該版本將導致該遊戲的存檔等資料一同被刪除,真的要刪除版本 %s 嗎? +version.manage.remove.confirm=真的要刪除實例「%s」嗎? 你將無法找回被刪除的檔案!!! +version.manage.remove.confirm.trash=真的要刪除實例「%s」嗎? 你可以在系統的資源回收筒 (或垃圾桶) 中還原目錄「%s」來找回該實例。 +version.manage.remove.confirm.independent=由於該實例啟用了「(全域/實例特定) 遊戲設定 → 執行路徑 → 各實例獨立」設定,所以刪除該實例將導致該遊戲的存檔等資料一同被刪除,真的要刪除實例「%s」嗎? version.manage.remove_assets=刪除所有遊戲資源檔案 -version.manage.remove_libraries=刪除所有函式庫檔案 +version.manage.remove_libraries=刪除所有支援庫檔案 version.manage.rename=重新命名該版本 version.manage.rename.message=請輸入新名稱 -version.manage.rename.fail=重新命名版本失敗,可能檔案被佔用或者名稱有特殊字元 +version.manage.rename.fail=重新命名版本失敗,可能檔案被佔用或者名稱有特殊字元。 version.settings=遊戲設定 version.update=更新模組包 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index ee0fca1ee2..4de5ee6e5d 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -1,6 +1,6 @@ # # Hello Minecraft! Launcher -# Copyright (C) 2023 huangyuhui and contributors +# Copyright (C) 2024 huangyuhui and contributors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -17,11 +17,12 @@ # # Contributors: huangyuhui + about=关于 about.copyright=版权 -about.copyright.statement=版权所有 © 2024 huangyuhui. +about.copyright.statement=版权所有 © 2024 huangyuhui about.author=作者 -about.author.statement=bilibili ID: @huanghongxun +about.author.statement=bilibili @huanghongxun about.claim=用户协议 about.claim.statement=点击链接以查看全文 about.dependency=依赖 @@ -29,21 +30,21 @@ about.legal=法律声明 about.thanks_to=鸣谢 about.thanks_to.bangbang93.statement=提供 BMCLAPI 下载源。请赞助支持 BMCLAPI! about.thanks_to.burningtnt.statement=为 HMCL 贡献许多技术支持 -about.thanks_to.contributors=所有通过 Issues、Pull Requests 等方式参与本项目的贡献者 -about.thanks_to.contributors.statement=没有开源社区的支持,Hello Minecraft! Launcher 就无法走到今天 +about.thanks_to.contributors=所有通过 Issue、Pull Request 等方式参与本项目的贡献者 +about.thanks_to.contributors.statement=没有开源社区的支持,HMCL 就无法走到今天 about.thanks_to.gamerteam.statement=提供默认背景图 about.thanks_to.glavo.statement=负责 HMCL 的日常维护 about.thanks_to.zekerzhayard.statement=为 HMCL 贡献许多技术支持 about.thanks_to.zkitefly.statement=负责维护 HMCL 的文档 -about.thanks_to.mcbbs=MCBBS 我的世界中文论坛 +about.thanks_to.mcbbs=MCBBS (我的世界中文论坛) about.thanks_to.mcbbs.statement=提供 MCBBS 下载源 (现已停止服务) -about.thanks_to.mcmod=MC 百科 -about.thanks_to.mcmod.statement=提供模组中文名映射表与 Mod 百科 +about.thanks_to.mcmod=MC 百科 (mcmod.cn) +about.thanks_to.mcmod.statement=提供模组简体中文名映射表与模组百科 about.thanks_to.red_lnn.statement=提供默认背景图 -about.thanks_to.shulkersakura.statement=提供 HMCL 的图标 +about.thanks_to.shulkersakura.statement=提供 HMCL 的徽标 about.thanks_to.users=HMCL 用户群成员 about.thanks_to.users.statement=感谢用户群成员赞助充电、积极催更、反馈问题、出谋划策 -about.thanks_to.yushijinhun.statement=authlib-injector 相关支持 +about.thanks_to.yushijinhun.statement=提供 authlib-injector 相关支持 about.open_source=开源 about.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL) @@ -58,28 +59,28 @@ account.create.authlibInjector=添加外置登录账户 (authlib-injector) account.email=邮箱 account.failed=账户刷新失败 account.failed.character_deleted=此角色已被删除 -account.failed.connect_authentication_server=无法连接认证服务器,可能是网络问题,请检查设备能否正常上网或使用代理服务\n你可以点击右上角帮助按钮进行求助。 -account.failed.connect_injector_server=无法连接认证服务器,可能是网络问题,请检查设备是否能正常上网,检查 URL 是否输入错误,或使用代理服务\n你可以点击右上角帮助按钮进行求助。 -account.failed.injector_download_failure=无法下载 authlib-injector,可能是网络问题,请检查设备是否能正常上网、尝试切换下载源或使用代理服务\n你可以点击右上角帮助按钮进行求助。 -account.failed.invalid_credentials=你的用户名或密码错误,或登录次数过多被暂时禁止登录,请稍后再试 +account.failed.connect_authentication_server=无法连接认证服务器,可能是网络问题,请检查设备能否正常上网或使用代理服务。\n你可以点击右上角帮助按钮进行求助。 +account.failed.connect_injector_server=无法连接认证服务器,可能是网络问题,请检查设备是否能正常上网、URL 是否输入错误,或使用代理服务。\n你可以点击右上角帮助按钮进行求助。 +account.failed.injector_download_failure=无法下载 authlib-injector,可能是网络问题,请检查设备是否能正常上网、尝试切换下载源或使用代理服务。\n你可以点击右上角帮助按钮进行求助。 +account.failed.invalid_credentials=你的用户名或密码错误,或登录次数过多被暂时禁止登录,请稍后再试。 account.failed.invalid_password=无效的密码 account.failed.invalid_token=请尝试登出并重新输入密码登录 -account.failed.migration=你的账户需要被迁移至微软账户。如果你已经迁移,你需要使用微软登录方式登录迁移后的微软账户 +account.failed.migration=你的账户需要迁移至微软账户。如果你已经迁移,你需要使用迁移后的微软账户登录。 account.failed.no_character=该账户没有角色 account.failed.server_disconnected=无法访问登录服务器,账户信息刷新失败。\n\ 你可以选择“再次刷新账户”重新尝试。\n\ - 你也可以选择“跳过账户刷新”继续启动游戏,但可能会使账户信息不是最新的。\n\ - 若最近没有刷新账户信息,则可能导致账户信息过期失效,\n\ - 若为 微软账户 启动游戏,账户信息过期失效可能将无法进入需账户验证的服务器。\n\ - 若尝试多次无法成功刷新,可尝试重新添加该账户尝试解决该问题。\n\ + 你也可以选择“跳过账户刷新”继续启动游戏,但可能会导致账户信息未同步更新。\n\ + 若最近没有刷新账户信息,则可能导致账户信息过期失效。\n\ + 若使用微软账户或外置登录账户启动游戏,账户信息过期失效可能将无法进入需在线验证的服务器。\n\ + 若尝试多次无法成功刷新,可尝试重新添加该账户,或许会解决该问题。\n\ 你可以点击右上角帮助按钮进行求助。 -account.failed.server_response_malformed=无法解析认证服务器响应,可能是服务器故障 +account.failed.server_response_malformed=无法解析认证服务器响应,可能是服务器故障。 account.failed.ssl=连接服务器时发生了 SSL 错误,可能网站证书已过期或你使用的 Java 版本过低,请尝试更新 Java,或关闭网络代理后再试。\n你可以点击右上角帮助按钮进行求助。 account.failed.wrong_account=登录了错误的账户 -account.hmcl.hint=你需要点击“登录”按钮,并在打开的网页中完成登录 +account.hmcl.hint=你需要点击“登录”按钮,并在弹出的网页中完成登录。 account.injector.add=添加认证服务器 -account.injector.empty=无(点击右侧加号添加) -account.injector.http=警告:此服务器使用不安全的 HTTP 协议,您的密码在登录时会被明文传输。 +account.injector.empty=无 (点击右侧加号添加) +account.injector.http=警告:此服务器使用不安全的 HTTP 协议,你的密码在登录时会被明文传输。 account.injector.link.homepage=主页 account.injector.link.register=注册 account.injector.server=认证服务器 @@ -90,91 +91,91 @@ account.login.hint=我们不会保存你的密码 account.login.skip=跳过账户刷新 account.login.retry=再次刷新账户 account.login.refresh=重新登录 -account.login.refresh.microsoft.hint=因为账户授权失效,你需要重新添加微软账户 +account.login.refresh.microsoft.hint=由于账户授权失效,你需要重新添加微软账户。 account.logout=登出 account.register=注册 account.manage=账户列表 -account.copy_uuid=复制该账户的 UUID。 +account.copy_uuid=复制该账户的 UUID account.methods=登录方式 account.methods.authlib_injector=外置登录 (authlib-injector) account.methods.microsoft=微软账户 -account.methods.microsoft.birth=如何修改账户出生日期 -account.methods.microsoft.close_page=已完成微软账户授权,接下来启动器还需要完成剩余登录步骤。你已经可以关闭本页面了。 +account.methods.microsoft.birth=如何更改账户出生日期 +account.methods.microsoft.close_page=已完成微软账户授权,接下来启动器还需要完成其余登录步骤。你现在可以关闭本页面了。 account.methods.microsoft.deauthorize=解除账户授权 -account.methods.microsoft.error.add_family=由于你未满 18 岁,你的账户必须被加入到家庭中才能登录游戏。你也可以点击上方【账户设置页】更改你的账户的出生日期,使年龄满 18 岁以上以继续登录。\n你可以点击右上角帮助按钮进行求助。 -account.methods.microsoft.error.add_family_probably=请检查你的账户设置,如果年龄未满 18 岁,你的账户必须被加入到家庭中才能登录游戏。你也可以点击上方链接更改你的账户的出生日期,使年龄满 18 岁以上以继续登录。\n你可以点击右上角帮助按钮进行求助。 -account.methods.microsoft.error.country_unavailable=你所在的国家或地区不受 XBox Live 的支持。 -account.methods.microsoft.error.missing_xbox_account=你的微软账户尚未关联 XBox 账户,你必须先创建 XBox 账户,才能登录游戏。\n你可以点击右上角帮助按钮进行求助。 -account.methods.microsoft.error.no_character=该账户未包含 Minecraft Java 版购买记录\n若已购买,则可能未创建游戏档案,请点击上方链接创建。\n若确定该账户完成了上述步骤,请先在 Minecraft 官网(minecraft.net)登录一次账户,然后再在启动器登录。\n你可以点击右上角帮助按钮进行求助。 -account.methods.microsoft.error.unknown=登录失败,错误码:%d +account.methods.microsoft.error.add_family=请点击上方“编辑账户个人信息”更改你的账户出生日期,使年龄满 18 岁以上,或将账户加入到家庭中。\n你可以点击右上角帮助按钮进行求助。 +account.methods.microsoft.error.add_family_probably=请点击上方“编辑账户个人信息”更改你的账户出生日期,使年龄满 18 岁以上,或将账户加入到家庭中。\n你可以点击右上角帮助按钮进行求助。 +account.methods.microsoft.error.country_unavailable=你所在的国家或地区不受 Xbox Live 的支持。 +account.methods.microsoft.error.missing_xbox_account=请点击上方“创建档案”关联 Xbox 账户。\n你可以点击右上角帮助按钮进行求助。 +account.methods.microsoft.error.no_character=请确认你已经购买了 Minecraft: Java 版。\n若已购买,则可能未创建游戏档案,请点击上方“创建档案”以创建游戏档案。\n你可以点击右上角帮助按钮进行求助。 +account.methods.microsoft.error.unknown=未知问题。错误码:%d。\n你可以点击右上角帮助按钮进行求助。 +account.methods.microsoft.error.wrong_verify_method=请在微软账户登录页面使用密码登录,不要使用验证码登录。\n你可以点击右上角帮助按钮进行求助。 account.methods.microsoft.logging_in=登录中…… -account.methods.microsoft.makegameidsettings=创建档案/编辑档案名称 +account.methods.microsoft.makegameidsettings=创建档案 / 编辑档案名称 account.methods.microsoft.hint=你需要按照以下步骤添加账户:\n\ - 1.点击“登录”按钮\n\ - 2.在网页浏览器显示的网站中输入 HMCL 显示的代码(自动拷贝,直接粘贴即可),并点击“下一步”\n\ - 3.按照网站的提示登录\n\ - 4.当网站提示“是否允许此应用访问你的信息?”的标识时,请点击“是”\n\ - 5.在网站提示“大功告成”后,只需等待账户完成添加即可\n\ - -若网站提示“出现错误”的标识或账户添加失败时,请重新按照以上步骤重新添加\n\ - -若设备网络环境不佳,可能登录网站加载很慢甚至无法加载,此时请使用网络代理并重试\n\ + \ 1. 点击“登录”按钮;\n\ + \ 2. 在弹出的网页中输入 HMCL 显示的代码,并点击“允许访问”;\n\ + \ 3. 按照网站的提示登录;\n\ + \ 4. 当网站提示“是否允许此应用访问你的信息?”时,请点击“接受”;\n\ + \ 5. 在网站提示“大功告成”后,等待账户完成添加即可。\n\ + 若网站提示“出现错误”或账户添加失败时,请按照以上步骤重新添加。\n\ + 若设备网络环境不佳,可能会导致网页加载缓慢甚至无法加载,请使用网络代理并重试。\n\ 如遇到问题,你可以点击右上角帮助按钮进行求助。 account.methods.microsoft.manual=你需要按照以下步骤添加:\n\ - 1.点击“登录”按钮\n\ - 2.在网页浏览器显示的网站中输入:%1$s(已自动拷贝,点此再次拷贝),并点击“下一步”\n\ - 3.按照网站的提示登录\n\ - 4.当网站提示“是否允许此应用访问你的信息?”的标识时,请点击“是”\n\ - 5.在网站提示“大功告成”后,只需等待账户完成添加即可\n\ - -若网站提示“出现错误”的标识或账户添加失败时,请重新按照以上步骤重新添加\n\ - -若网站未能显示,请手动在网页浏览器中打开:%2$s\n\ - -若设备网络环境不佳,可能登录网站加载很慢甚至无法加载,此时请使用网络代理并重试\n\ + \ 1. 点击“登录”按钮;\n\ + \ 2. 在弹出的网页中输入 %1$s (已自动复制),并点击“允许访问”;\n\ + \ 3. 按照网站的提示登录;\n\ + \ 4. 当网站提示“是否允许此应用访问你的信息?”时,请点击“接受”;\n\ + \ 5. 在网站提示“大功告成”后,等待账户完成添加即可。\n\ + 若网站未能显示,请手动在浏览器中打开:%2$s\n\ + 若网站提示“出现错误”或账户添加失败时,请按照以上步骤重新添加。\n\ + 若设备网络环境不佳,可能会导致网页加载缓慢甚至无法加载,请使用网络代理并重试。\n\ 如遇到问题,你可以点击右上角帮助按钮进行求助。 -account.methods.microsoft.profile=账户设置页 +account.methods.microsoft.profile=编辑账户个人信息 account.methods.microsoft.purchase=购买 Minecraft account.methods.forgot_password=忘记密码 -account.methods.microsoft.snapshot=你正在使用非官方构建的 HMCL,请下载官方构建进行微软登录。 +account.methods.microsoft.snapshot=你正在使用第三方提供的 HMCL,请下载官方版本来登录微软账户。 account.methods.microsoft.snapshot.website=官方网站 account.methods.offline=离线模式 account.methods.offline.name.special_characters=建议使用英文字符、数字以及下划线命名 -account.methods.offline.name.invalid=正常情况下,游戏用户名只能包括英文字符、数字以及下划线,且长度不能超过 16 个字符。\n\ - 一些合法的用户名:HuangYu,huang_Yu,Huang_Yu_123;\n\ - 一些不合法的用户名:黄鱼,Huang Yu,Huang-Yu_%%%,Huang_Yu_hello_world_hello_world。\n\ - 如果你相信服务器端有相应的模组或插件来解除此限制,你可以忽略本警告。\n\ +account.methods.offline.name.invalid=游戏用户名通常仅允许使用英文字母、数字及下划线,且长度不能超过 16 个字符。\n\ + \ · 一些合法用户名:HuangYu、huang_Yu、Huang_Yu_123;\n\ + \ · 一些非法用户名:黄鱼、Huang Yu、Huang-Yu_%%%、Huang_Yu_hello_world_hello_world。\n\ + 如果你相信服务端有相应模组或插件解除此限制,可以忽略本警告。\n\ 如遇到问题,你可以点击右上角帮助按钮进行求助。 account.methods.offline.uuid=UUID -account.methods.offline.uuid.hint=UUID 是 Minecraft 对玩家角色的唯一标识符,每个启动器生成 UUID 的方式可能不同。通过修改 UUID 选项至原启动器所生成的 UUID,你可以保证在切换启动器后,游戏还能将你的游戏角色识别为给定 UUID 所对应的角色,从而保留原来角色的背包物品。UUID 选项为高级选项,除非你知道你在做什么,否则你不需要调整该选项。 +account.methods.offline.uuid.hint=UUID 是 Minecraft 玩家的唯一标识符,每个启动器生成 UUID 的方式可能不同。通过将 UUID 修改为原启动器所生成的 UUID,你可以保证在切换启动器后,游戏还能将你的游戏角色识别为给定 UUID 所对应的角色,从而保留原角色的背包物品。UUID 选项为高级选项,除非你知道你在做什么,否则你不需要调整该选项。 account.methods.offline.uuid.malformed=格式错误 account.missing=没有游戏账户 account.missing.add=点击此处添加账户 -account.move_to_global=转换为全局账户\n该账户的信息会保存至用户目录的配置文件中 -account.move_to_portable=转换为便携账户\n该账户的信息会保存至 HMCL 同目录的配置文件中 +account.move_to_global=转换为全局账户\n该账户的信息会保存至系统当前用户文件夹的配置文件中 +account.move_to_portable=转换为便携账户\n该账户的信息会保存至与 HMCL 同文件夹的配置文件中 account.not_logged_in=未登录 account.password=密码 account.portable=便携账户 account.skin=皮肤 account.skin.file=皮肤图片文件 account.skin.model=模型 -account.skin.model.default=经典 -account.skin.model.slim=苗条 +account.skin.model.default=宽型 +account.skin.model.slim=纤细 account.skin.type.csl_api=Blessing Skin 服务器 account.skin.type.csl_api.location=服务器地址 account.skin.type.csl_api.location.hint=CustomSkinAPI 地址 account.skin.type.little_skin=LittleSkin 皮肤站 -account.skin.type.little_skin.hint=你需要在皮肤站中创建并使用和该离线账户角色同名角色,此时离线账户皮肤将为皮肤站上角色所设定的皮肤。\n你可以点击右上角帮助按钮进行求助。 +account.skin.type.little_skin.hint=你需要在皮肤站中创建并使用和该离线账户同名的角色,此时离线账户皮肤将显示为皮肤站上对应角色所设置的皮肤。\n你可以点击右上角帮助按钮进行求助。 account.skin.type.local_file=本地皮肤图片文件 -account.skin.upload=上传皮肤 +account.skin.upload=上传/编辑皮肤 account.skin.upload.failed=皮肤上传失败 account.skin.invalid_skin=无法识别的皮肤文件 account.username=用户名 archive.author=作者 archive.date=发布日期 -archive.game_version=游戏版本 -archive.name=名称 +archive.file.name=文件名 archive.version=版本 assets.download=下载资源 assets.download_all=检查资源文件完整性 -assets.index.malformed=资源文件的索引文件损坏,您可以在相应的游戏管理,点击左下角的管理按钮选择【更新游戏资源文件】以修复该问题\n你可以点击右上角帮助按钮进行求助。 +assets.index.malformed=资源文件的索引文件损坏,你可以在相应版本的“版本管理”页面中,点击左下角“管理 → 更新游戏资源文件”以修复该问题。\n你可以点击右上角帮助按钮进行求助。 button.cancel=取消 button.change_source=切换下载源 @@ -188,11 +189,12 @@ button.no=否 button.ok=确定 button.refresh=刷新 button.remove=删除 -button.remove.confirm=您确定要删除吗?此操作无法撤销! +button.remove.confirm=你确定要删除吗?此操作无法撤销! button.retry=重试 button.save=保存 button.save_as=另存为 button.select_all=全选 +button.view=查看 button.yes=是 chat=官方群组 @@ -200,15 +202,15 @@ chat=官方群组 color.recent=推荐 color.custom=自定义颜色 -crash.NoClassDefFound=请确认 Hello Minecraft! Launcher 本体是否完整,\n或更新您的 Java。\n你可以访问\n https://docs.hmcl.net/help.html\n页面寻求帮助。 -crash.user_fault=您的系统或 Java 环境可能安装不当导致本软件崩溃,\n请检查您的 Java 环境或您的电脑。\n你可以访问\n https://docs.hmcl.net/help.html\n页面寻求帮助。 +crash.NoClassDefFound=请确认 Hello Minecraft! Launcher 本体是否完整,或更新你的 Java。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。 +crash.user_fault=你的系统或 Java 环境可能安装不当导致本软件崩溃,请检查你的 Java 环境或你的电脑。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。 curse.category.0=全部 # https://addons-ecs.forgesvc.net/api/v2/category/section/4471 curse.category.4474=科幻 curse.category.4481=轻量整合包 -curse.category.4483=战斗 PVP +curse.category.4483=战斗 PvP curse.category.4477=小游戏 curse.category.4478=任务 curse.category.4484=多人 @@ -222,14 +224,15 @@ curse.category.4482=大型整合包 curse.category.4472=科技 curse.category.4473=魔法 curse.category.5128=原版增强 +curse.category.7418=恐怖 # https://addons-ecs.forgesvc.net/api/v2/category/section/6 curse.category.5299=教育 curse.category.5232=额外行星 curse.category.5129=原版增强 -curse.category.5189=实用与QOL -curse.category.5190=QoL -curse.category.5191=实用与QoL +curse.category.5189=实用与 QOL +curse.category.5190=QOL +curse.category.5191=实用与 QOL curse.category.5192=梦幻菜单 curse.category.6145=空岛 curse.category.6814=性能 @@ -327,34 +330,34 @@ curse.sort.total_downloads=下载量 download=下载 download.hint=安装游戏和整合包或下载模组、资源包和地图 -download.code.404=远程服务器不包含需要下载的文件: %s +download.code.404=远程服务器不包含需要下载的文件: %s\n你可以点击右上角帮助按钮进行求助。 download.content=游戏内容 -download.curseforge.customization=光影与游戏定制 -download.curseforge.unavailable=HMCL 预览版暂不支持访问 CurseForge,请使用稳定版或测试版进行下载。 -download.existing=文件已存在,无法保存。你可以在模组选择栏中的右侧按钮另存为将文件保存至其他地方。 +download.curseforge.unavailable=HMCL 预览版暂不支持访问 CurseForge,请使用稳定版或开发版进行下载。 +download.existing=文件已存在,无法保存。你可以将文件保存至其他地方。 download.external_link=打开下载网站 -download.failed=下载失败: %1$s,错误码:%2$d -download.failed.empty=[没有可供安装的版本,点击此处返回] +download.failed=下载失败: %1$s,\n错误码:%2$d\n你可以点击右上角帮助按钮进行求助。 +download.failed.empty=[没有可供安装的版本,点击此处返回]\n(你可以点击右上角帮助按钮进行求助) download.failed.no_code=下载失败: %s -download.failed.refresh=[加载版本列表失败,点击此处重试] +download.failed.refresh=[加载版本列表失败,点击此处重试]\n(你可以点击右上角帮助按钮进行求助) download.game=新游戏 -download.provider.bmclapi=BMCLAPI(bangbang93,https://bmclapi2.bangbang93.com) -download.provider.mojang=官方(OptiFine 自动安装使用 BMCLAPI 下载源) -download.provider.official=尽量使用官方源(最新,但可能加载慢) -download.provider.balanced=选择加载速度快的下载源(平衡,但可能不是最新) -download.provider.mirror=尽量使用镜像源(加载快,但可能不是最新) +download.provider.bmclapi=BMCLAPI (bangbang93, https://bmclapi2.bangbang93.com) +download.provider.mojang=官方 (OptiFine 自动安装使用 BMCLAPI 下载源) +download.provider.official=尽量使用官方源 (最新,但可能加载慢) +download.provider.balanced=选择加载速度快的下载源 (平衡,但可能不是最新) +download.provider.mirror=尽量使用镜像源 (加载快,但可能不是最新) download.java=下载 Java +download.java.override=此 Java 版本已经存在,是否卸载并重新安装? download.javafx=正在下载必要的运行时组件…… -download.javafx.notes=正在通过网络下载 HMCL 必要的运行时组件。\n点击“切换下载源”按钮查看详情以及选择下载源,点击“取消”按钮停止并退出。\n注意:若下载速度过慢,请尝试切换下载源 -download.javafx.component=正在下载模块 %s +download.javafx.notes=正在通过网络下载 HMCL 必要的运行时组件。\n点击“切换下载源”按钮查看详情以及选择下载源,点击“取消”按钮停止并退出。\n注意:若下载速度过慢,请尝试切换下载源。 +download.javafx.component=正在下载模块“%s” download.javafx.prepare=准备开始下载 -exception.access_denied=因为无法访问文件 %s,HMCL 没有对该文件的访问权限,或者该文件被其他程序打开。\n\ - 请你检查当前操作系统账户是否能访问该文件,比如非管理员用户可能不能访问其他账户的个人文件夹内的文件。\n\ - 对于 Windows 用户,你还可以尝试通过资源监视器查看是否有程序占用了该文件,如果是,请关闭占用此文件相关程序,或者重启电脑再试。\n\ +exception.access_denied=无法访问文件“%s”,因为 HMCL 没有对该文件的访问权限,或者该文件已被其他程序打开。\n\ + 请你检查当前操作系统账户是否能访问该文件,比如非管理员用户可能无法访问其他账户的个人文件夹内的文件。\n\ + 对于 Windows 用户,你还可以尝试通过资源监视器查看是否有程序占用了该文件,如果是,请关闭占用该文件的程序,或者重启电脑再试。\n\ 如遇到问题,你可以点击右上角帮助按钮进行求助。 exception.artifact_malformed=下载的文件无法通过校验。\n你可以点击右上角帮助按钮进行求助。 -exception.ssl_handshake=无法建立 SSL 连接,因为当前 Java 虚拟机缺少相关的 SSL 证书。你可以尝试使用其他的 Java 虚拟机启动 HMCL 再试。\n你可以点击右上角帮助按钮进行求助。 +exception.ssl_handshake=无法建立 SSL 连接,因为当前 Java 虚拟机缺少相关的 SSL 证书。你可以尝试使用其他 Java 虚拟机启动 HMCL 再试。\n你可以点击右上角帮助按钮进行求助。 extension.bat=Windows 脚本 extension.mod=模组文件 @@ -362,33 +365,32 @@ extension.png=图片文件 extension.ps1=PowerShell 脚本 extension.sh=Bash 脚本 -fatal.fractureiser=Hello Minecraft! Launcher 检测到你的电脑被 Fractureiser 病毒感染,存在严重安全问题。\n请立即使用杀毒软件进行全盘查杀,随后修改你在此电脑上登陆过的所有账户的密码。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。 fatal.javafx.incompatible=缺少 JavaFX 运行环境。\nHello Minecraft! Launcher 无法在低于 Java 11 的 Java 环境上自行补全 JavaFX 运行环境,请更新到 Java 11 或更高版本。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。 fatal.javafx.incomplete=JavaFX 运行环境不完整,请尝试更换你的 Java 或者重新安装 OpenJFX。 fatal.javafx.missing=缺少 JavaFX 运行环境,请使用包含 OpenJFX 的 Java 运行环境启动 Hello Minecraft! Launcher。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。 -fatal.config_change_owner_root=你正在使用 root 账户启动 Hello Minecraft! Launcher, 这可能导致你未来无法正常使用其他账户正常启动 Hello Minecraft! Launcher。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。\n是否继续启动? +fatal.config_change_owner_root=你正在使用 root 账户启动 Hello Minecraft! Launcher, 这可能导致你未来无法正常使用其他账户正常启动 HMCL。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。\n是否继续启动? fatal.config_in_temp_dir=你正在临时文件夹中启动 Hello Minecraft! Launcher, 你的设置和游戏数据可能会丢失,建议将 HMCL 移动至其他位置再启动。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。\n是否继续启动? -fatal.config_loading_failure=Hello Minecraft! Launcher 无法加载配置文件\n请确保 Hello Minecraft! Launcher 对 "%s" 目录及该目录下的文件拥有读写权限。\n对于 macOS,尝试将 HMCL 放在除「桌面」「下载」「文稿」之外的有权限的地方再试。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。 -fatal.config_loading_failure.unix=Hello Minecraft! Launcher 无法加载配置文件,因为配置文件是由用户 %1$s 创建的。\n请使用 root 账户启动 HMCL (不推荐),或在终端中执行以下命令将配置文件的所有权变更为当前用户:\n%2$s\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。 -fatal.mac_app_translocation=由于 macOS 的安全机制,Hello Minecraft! Launcher 被系统隔离至临时文件夹中。\n请将 Hello Minecraft! Launcher 移动到其他文件夹后再尝试启动,否则你的设置和游戏数据可能会在重启后丢失。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。\n是否继续启动? -fatal.migration_requires_manual_reboot=Hello Minecraft! Launcher 即将完成升级,请重新打开 Hello Minecraft! Launcher。\n如遇到问题,你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。 -fatal.apply_update_failure=我们很抱歉 Hello Minecraft! Launcher 无法自动完成升级,因为出现了一些问题。\n但你依可以从 %s 处手动下载 Hello Minecraft! Launcher 来完成升级\n你可以访问 https://docs.hmcl.net/help.html 网页进行反馈。 -fatal.apply_update_need_win7=Hello Minecraft! Launcher 无法在 Windows XP/Vista 上进行自动更新,请从 %s 处手动下载 Hello Minecraft! Launcher 来完成升级。 -fatal.samba=如果你正在通过 Samba 共享的文件夹中运行 Hello Minecraft! Launcher,启动器可能无法正常工作。请尝试更新你的 Java 或在本地文件夹内运行 Hello Minecraft! Launcher。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。 -fatal.illegal_char=由于你的用户文件夹路径中存在非法字符‘=’,你将无法使用外置登录账户以及离线登录更换皮肤功能。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。 -fatal.unsupported_platform=Minecraft 尚未为您的平台提供完善支持,所以可能影响游戏体验或无法启动游戏。\n若无法启动 Minecraft 1.17 及以上版本,可以尝试在版本设置中打开“使用 OpenGL 软渲染器”选项,使用 CPU 渲染以获得更好的兼容性。\n如遇到问题,你可以点击右上角帮助按钮进行求助。 +fatal.config_loading_failure=Hello Minecraft! Launcher 无法加载配置文件。\n请确保 HMCL 对“%s”文件夹及该文件夹下的文件拥有读写权限。\n对于 macOS,尝试将 HMCL 放在除“桌面”“下载”“文稿”之外的有权限的地方再试。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。 +fatal.config_loading_failure.unix=Hello Minecraft! Launcher 无法加载配置文件,因为配置文件是由用户“%1$s”创建的。\n请使用 root 账户启动 HMCL (不推荐),或在终端中执行以下命令将配置文件的所有权变更为当前用户:\n%2$s\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。 +fatal.mac_app_translocation=由于 macOS 的安全机制,Hello Minecraft! Launcher 被系统隔离至临时文件夹中。\n请将 HMCL 移动到其他文件夹后再尝试启动,否则你的设置和游戏数据可能会在重启后丢失。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。\n是否继续启动? +fatal.migration_requires_manual_reboot=Hello Minecraft! Launcher 即将完成升级,请重新打开 HMCL。\n如遇到问题,你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。 +fatal.apply_update_failure=我们很抱歉 Hello Minecraft! Launcher 无法自动完成升级,因为出现了一些问题。\n但你依可以从 %s 手动下载 HMCL 来完成升级。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。 +fatal.apply_update_need_win7=Hello Minecraft! Launcher 无法在 Windows XP/Vista 上进行自动更新,请从 %s 手动下载 HMCL 来完成升级。 +fatal.samba=如果你正在通过 Samba 共享的文件夹中运行 Hello Minecraft! Launcher,启动器可能无法正常工作。请尝试更新你的 Java 或在本地文件夹内运行 HMCL。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。 +fatal.illegal_char=由于你的用户文件夹路径中存在非法字符“=”,你将无法使用外置登录账户以及离线登录更换皮肤功能。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。 +fatal.unsupported_platform=Minecraft 尚未对你的平台提供完善支持,所以可能影响游戏体验或无法启动游戏。\n若无法启动 Minecraft 1.17 及更高版本,可以尝试在“(全局/版本特定) 游戏设置 → 高级设置 → 调试选项”中将“渲染器”切换为“软渲染器”,以获得更好的兼容性。\n如遇到问题,你可以点击右上角帮助按钮进行求助。 fatal.unsupported_platform.loongarch=Hello Minecraft! Launcher 已为龙芯提供支持。\n如果遇到问题,你可以点击右上角帮助按钮进行求助。 -fatal.unsupported_platform.osx_arm64=Hello Minecraft! Launcher 已为 Apple Silicon 平台提供支持,使用 ARM 原生 Java 启动游戏以获得更流畅的游戏体验。\n如果你在游戏中遭遇问题,使用 x86-64 架构的 Java 启动游戏可能有更好的兼容性。\n如遇到问题,你可以点击右上角帮助按钮进行求助。 -fatal.unsupported_platform.windows_arm64=Hello Minecraft! Launcher 已为 Windows on Arm 平台提供原生支持。如果你在游戏中遭遇问题,请尝试使用 x86 架构的 Java 启动游戏。\n\n如果你正在使用高通平台,你可能需要安装 OpenGL 兼容包后才能进行游戏。点击链接前往 Microsoft Store 安装兼容包。\n如遇到问题,你可以点击右上角帮助按钮进行求助。 +fatal.unsupported_platform.osx_arm64=Hello Minecraft! Launcher 已为 Apple Silicon 平台提供支持,使用 ARM 原生 Java 启动游戏以获得更流畅的游戏体验。\n如果你在游戏中遇到问题,使用 x86-64 架构的 Java 启动游戏可能有更好的兼容性。\n如遇到问题,你可以点击右上角帮助按钮进行求助。 +fatal.unsupported_platform.windows_arm64=Hello Minecraft! Launcher 已为 Windows on Arm 平台提供原生支持。如果你在游戏中遇到问题,请尝试使用 x86 架构的 Java 启动游戏。\n如果你正在使用 高通 平台,你可能需要安装 OpenGL 兼容包 后才能进行游戏。点击链接前往 Microsoft Store 安装兼容包。\n如遇到问题,你可以点击右上角帮助按钮进行求助。 feedback=反馈 feedback.channel=反馈渠道 feedback.discord=Discord -feedback.discord.statement=欢迎加入 Discord 讨论区,加入后请遵守讨论区规定。 +feedback.discord.statement=欢迎加入 Discord 服务器,加入后请遵守讨论区规定 feedback.github=GitHub Issue -feedback.github.statement=打开一个 GitHub Issue。 +feedback.github.statement=提交一个 GitHub Issue feedback.qq_group=HMCL 用户群 -feedback.qq_group.statement=欢迎加入 HMCL 用户群,加入后请遵守群规。 +feedback.qq_group.statement=欢迎加入 HMCL 用户群,加入后请遵守群规 file=文件 @@ -402,84 +404,81 @@ folder.saves=存档文件夹 folder.screenshots=截图文件夹 game=游戏 -game.crash.feedback=请不要将本界面截图给他人!如果你要求助他人,请你点击左下角 导出游戏崩溃信息 后将导出的文件发送给他人以供分析。\n你可以点击下方的 帮助 前往交流群寻求帮助。 +game.crash.feedback=请不要将本界面截图给他人!如果你要向他人求助,请你点击左下角“导出游戏崩溃信息”后将导出的文件发送给他人以供分析。\n你可以点击下方的“帮助”前往交流群寻求帮助。 game.crash.info=游戏信息 game.crash.reason=崩溃原因 game.crash.reason.analyzing=分析中…… -game.crash.reason.block=当前游戏因为某个方块不能正常工作,无法继续运行。\n你可以尝试通过 MCEdit 工具编辑存档删除该方块,或者直接删除相应的模组。\n方块类型:%1$s\n方块坐标:%2$s -game.crash.reason.bootstrap_failed=当前游戏因为模组 %1$s 错误,无法继续运行。\n你可以尝试删除或更新该模组以解决问题。 -game.crash.reason.mixin_apply_mod_failed=当前游戏因为 Mixin 无法应用 %1$s 模组,无法继续运行。\n你可以尝试删除或更新该 Mod 以解决问题。 -game.crash.reason.config=当前游戏因为无法解析模组配置文件,无法继续运行\n模组 %1$s 的配置文件 %2$s 无法被解析。 +game.crash.reason.block=当前游戏由于某个方块不能正常工作,无法继续运行。\n你可以尝试通过 MCEdit 工具编辑存档删除该方块,或者直接删除相应的模组。\n方块类型:%1$s\n方块坐标:%2$s +game.crash.reason.bootstrap_failed=当前游戏由于模组“%1$s”出现问题,无法继续运行。\n你可以尝试删除或更新该模组以解决问题。 +game.crash.reason.mixin_apply_mod_failed=当前游戏由于 Mixin 无法应用于“%1$s”模组,无法继续运行。\n你可以尝试删除或更新该模组以解决问题。 +game.crash.reason.config=当前游戏由于无法解析模组配置文件,无法继续运行。\n无法解析模组“%1$s”的配置文件“%2$s”。 game.crash.reason.multiple=检测到多个原因:\n\n -game.crash.reason.debug_crash=当前游戏因为手动触发崩溃,无法继续运行。\n事实上游戏并没有问题,问题都是你造成的! -game.crash.reason.duplicated_mod=当前游戏因为模组 %1$s 重复安装,无法继续运行。\n%2$s\n每种模组只能安装一个,请你删除多余的模组再试。 -game.crash.reason.entity=当前游戏因为某个实体不能正常工作,无法继续运行。\n你可以尝试通过 MCEdit 工具编辑存档删除该实体,或者直接删除相应的模组。\n实体类型:%1$s\n实体坐标:%2$s -game.crash.reason.fabric_version_0_12=Fabric 0.12 及以上版本与当前已经安装的模组可能不兼容,你需要将 Fabric 降级至 0.11.7。 +game.crash.reason.debug_crash=当前游戏由于手动触发崩溃,无法继续运行。\n事实上游戏并没有问题,问题都是你造成的! +game.crash.reason.duplicated_mod=当前游戏由于模组“%1$s”重复安装,无法继续运行。\n%2$s\n每种模组只能安装一个,请你删除多余的模组再试。 +game.crash.reason.entity=当前游戏由于某个实体不能正常工作,无法继续运行。\n你可以尝试通过 MCEdit 工具编辑存档删除该实体,或者直接删除相应的模组。\n实体类型:%1$s\n实体坐标:%2$s +game.crash.reason.fabric_version_0_12=Fabric Loader 0.12 及更高版本与当前已经安装的模组可能不兼容,你需要将 Fabric Loader 降级至 0.11.7。 game.crash.reason.fabric_warnings=Fabric 提供了一些警告信息:\n%1$s -game.crash.reason.file_already_exists=当前游戏因为文件 %1$s 已经存在,无法继续运行。\n如果你认为这个文件可以删除,你可以在备份这个文件后尝试删除它,并重新启动游戏。 -game.crash.reason.file_changed=当前游戏因为文件校验失败,无法继续运行。\n如果你手动修改了 Minecraft.jar 文件,你需要回退修改,或者重新下载游戏。 -game.crash.reason.gl_operation_failure=当前游戏因为你使用的某些模组、光影包、材质包,无法继续运行。\n请先尝试禁用你所使用的模组/光影包/材质包再试。 -game.crash.reason.graphics_driver=当前游戏因为显卡驱动问题而崩溃,请尝试以下操作:\n\ - - 如果你的电脑存在独立显卡,请尝试使用 独立显卡 而非 Intel 核显启动 HMCL 与游戏 详情 ;\n\ - - 尝试升级你的 显卡驱动 到最新版本,或回退到出厂版本;\n\ - - 如果你确实需要使用核芯显卡,请检查你的电脑的 CPU 是否是 Intel(R) Core(TM) 3000 系列或更旧的处理器,如果是,对于 Minecraft 1.16.5 及更旧版本,请你将游戏所使用的 Java 版本降级至 1.8.0_51 及以下版本 Java 1.8.0 历史版本 ,否则请跳过;\n\ - - 在全局(特定)游戏设置,高级设置中打开“使用 OpenGL 软渲染器”(开启此选项后帧数会显著降低,仅推荐在以调试为目的或应急时开启)。\n\ +game.crash.reason.file_already_exists=当前游戏由于文件“%1$s”已经存在,无法继续运行。\n如果你认为这个文件可以删除,你可以在备份这个文件后尝试删除它,并重新启动游戏。 +game.crash.reason.file_changed=当前游戏由于文件校验失败,无法继续运行。\n如果你手动修改了 Minecraft.jar 文件,你需要回退修改,或者重新下载游戏。 +game.crash.reason.gl_operation_failure=当前游戏由于你使用的某些模组/光影包/资源包有问题,导致无法继续运行。\n请先尝试禁用你所使用的模组/光影包/资源包再试。 +game.crash.reason.graphics_driver=当前游戏由于显卡驱动问题而崩溃,请尝试以下操作:\n\ + \ · 如果你的电脑存在独立显卡,请尝试使用独立显卡而非 Intel 核显启动 HMCL 与游戏;详情\n\ + \ · 尝试升级你的显卡驱动到最新版本,或回退到出厂版本;\n\ + \ · 如果你确实需要使用核芯显卡,请检查你电脑的 CPU 是否为 Intel(R) Core(TM) 3000 系列或更旧的处理器。如果是,对于 Minecraft 1.16.5 及更旧版本,请你将游戏所使用的 Java 降级至 1.8.0_51 及更低版本,否则请跳过;Java 1.8.0 历史版本\n\ 如果仍有问题,你可能需要考虑换一张新显卡或一台新电脑。 -game.crash.reason.macos_failed_to_find_service_port_for_display=当前游戏因为 Apple silicon 平台下初始化 OpenGL 窗口失败,无法继续运行。\n对于该问题,HMCL 暂无直接性的解决方案。请您尝试任意打开一个浏览器并全屏,然后再回到 HMCL 启动游戏,在弹出游戏窗口前迅速切回浏览器页面,等待游戏窗口出现后再切回游戏窗口。 -game.crash.reason.illegal_access_error=当前游戏因为某些模组的问题,无法继续运行。\n如果你认识:%1$s,你可以更新或删除对应模组再试。 -game.crash.reason.install_mixinbootstrap=当前游戏因为缺失 MixinBootstrap,无法继续运行。\n你可以尝试安装 MixinBootstrap 解决该问题。若安装后崩溃,尝试在该模组的文件名前加入英文“!”尝试解决。 -game.crash.reason.need_jdk11=当前游戏因为 Java 虚拟机版本不合适,无法继续运行。\n你需要下载安装 Java 11,并在全局(特定)游戏设置中将 Java 设置为 11 开头的版本。 -game.crash.reason.jdk_9=当前游戏因为 Java 版本过高,无法继续运行。\n你需要下载安装 Java 8,并在全局(特定)游戏设置中将 Java 设置为 1.8 的版本。 -game.crash.reason.jvm_32bit=当前游戏因为内存分配过大,超过了 32 位 Java 内存限制,无法继续运行。\n如果你的电脑是 64 位系统,请下载安装并更换 64 位 Java。下载 Java\n如果你的电脑是 32 位系统,你或许可以重新安装 64 位系统,或换一台新电脑。\n或者,你可以关闭游戏内存的自动分配,并且把内存限制调节为 1024 MB 或以下。 -game.crash.reason.loading_crashed_forge=当前游戏因为模组 %1$s (%2$s) 错误,无法继续运行。\n你可以尝试删除或更新该模组以解决问题。 -game.crash.reason.loading_crashed_fabric=当前游戏因为模组 %1$s 错误,无法继续运行。\n你可以尝试删除或更新该模组以解决问题。 -game.crash.reason.memory_exceeded=当前游戏因为分配的内存过大,无法继续运行。\n该问题是由于系统页面文件太小导致的。\n你需要在全局(特定)游戏设置中关闭游戏内存的自动分配,并将游戏内存调低至游戏能正常启动为止。\n你还可以尝试将 虚拟内存 设置调整至「自动管理所有驱动器分页文件大小」,详情。 -game.crash.reason.mac_jdk_8u261=当前游戏因为你所使用的 Forge 或 OptiFine 与 Java 冲突崩溃。\n请尝试更新 Forge 和 OptiFine,或使用 Java 8u251 及更早版本启动。 -game.crash.reason.mod=当前游戏因为 %1$s 的问题,无法继续运行。\n你可以更新或删除已经安装的 %1$s 再试。 -game.crash.reason.mod_resolution=当前游戏因为 Mod 依赖问题,无法继续运行。Fabric 提供了如下信息:\n%1$s -game.crash.reason.mod_resolution_collection=当前游戏因为前置 Mod 版本不匹配,无法继续运行。\n%1$s 需要前置 Mod:%2$s 才能继续运行。\n这表示你需要更新或降级前置。你可以到下载页的模组下载,或到网上下载 %3$s。 -game.crash.reason.mod_resolution_conflict=当前游戏因为 Mod 冲突,无法继续运行。\n%1$s 与 %2$s 不能兼容。 -game.crash.reason.mod_resolution_missing=当前游戏因为缺少 Mod 前置,无法继续运行。\n%1$s 需要前置 Mod:%2$s 才能继续运行。\n这表示你少安装了 Mod,或该 Mod 版本不够。你可以到下载页的模组下载,或到网上下载 %3$s。 -game.crash.reason.mod_resolution_missing_minecraft=当前游戏因为 Mod 和 Minecraft 游戏版本不匹配,无法继续运行。\n%1$s 需要 Minecraft %2$s 才能运行。\n如果你要继续使用你已经安装的 Mod,你可以选择安装对应的 Minecraft 版本;如果你要继续使用当前 Minecraft 版本,你需要安装对应版本的 Mod。 +game.crash.reason.macos_failed_to_find_service_port_for_display=当前游戏由于 Apple Silicon 平台下初始化 OpenGL 窗口失败,无法继续运行。\n对于该问题,HMCL 暂无直接性的解决方案。请你尝试任意打开一个浏览器并全屏,然后再回到 HMCL 启动游戏,在弹出游戏窗口前迅速切回浏览器页面,等待游戏窗口出现后再切回游戏窗口。 +game.crash.reason.illegal_access_error=当前游戏由于某些模组的问题,无法继续运行。\n如果你认识“%1$s”,你可以更新或删除对应模组再试。 +game.crash.reason.install_mixinbootstrap=当前游戏由于缺失 MixinBootstrap,无法继续运行。\n你可以尝试安装 MixinBootstrap 解决该问题。若安装后崩溃,尝试在该模组的文件名前加入半角感叹号 (!) 尝试解决。 +game.crash.reason.need_jdk11=当前游戏由于 Java 虚拟机版本不合适,无法继续运行。\n你需要下载安装 Java 11,并在“(全局/版本特定) 游戏设置 → 游戏 Java”中将 Java 设置为 11 开头的版本。 +game.crash.reason.jdk_9=当前游戏由于 Java 版本过高,无法继续运行。\n你需要下载安装 Java 8,并在“(全局/版本特定) 游戏设置 → 游戏 Java”中将 Java 设置为 1.8 的版本。 +game.crash.reason.jvm_32bit=当前游戏由于内存分配过大,超过了 32 位 Java 内存限制,无法继续运行。\n如果你的电脑是 64 位系统,请下载安装并更换 64 位 Java。下载 Java\n如果你的电脑是 32 位系统,你或许可以重新安装 64 位系统,或换一台新电脑。\n或者,你可以在“(全局/版本特定) 游戏设置 → 游戏内存”中关闭“自动分配内存”,并且把内存限制调节为 1024 MB 或以下。 +game.crash.reason.loading_crashed_forge=当前游戏由于模组“%1$s (%2$s)”错误,无法继续运行。\n你可以尝试删除或更新该模组以解决问题。 +game.crash.reason.loading_crashed_fabric=当前游戏由于模组“%1$s”错误,无法继续运行。\n你可以尝试删除或更新该模组以解决问题。 +game.crash.reason.memory_exceeded=当前游戏由于分配的内存过大,无法继续运行。\n该问题是由于系统页面文件太小导致的。\n你需要在全局(特定)游戏设置中关闭游戏内存的自动分配,并将游戏内存调低至游戏能正常启动为止。\n你还可以尝试将 虚拟内存 设置调整至「自动管理所有驱动器分页文件大小」,详情。 +game.crash.reason.mac_jdk_8u261=当前游戏由于你所使用的 Forge/OptiFine 与 Java 冲突而崩溃。\n请尝试更新 Forge/OptiFine,或使用 Java 8u251 及更早版本启动。 +game.crash.reason.mod=当前游戏由于“%1$s”的问题,无法继续运行。\n你可以更新或删除已经安装的“%1$s”再试。 +game.crash.reason.mod_resolution=当前游戏由于模组依赖问题,无法继续运行。Fabric 提供了如下信息:\n%1$s +game.crash.reason.mod_resolution_collection=当前游戏由于前置模组版本不匹配,无法继续运行。\n“%1$s”需要前置模组“%2$s”才能继续运行。\n这表示你需要更新或降级前置。你可以前往“下载 → 模组”页面下载,或到网上下载“%3$s”。 +game.crash.reason.mod_resolution_conflict=当前游戏由于模组冲突,无法继续运行。\n“%1$s”与“%2$s”不兼容。 +game.crash.reason.mod_resolution_missing=当前游戏由于缺少前置模组,无法继续运行。\n“%1$s”需要前置模组“%2$s”才能继续运行。\n这表示你少安装了模组,或该模组版本不够。你可以前往“下载 → 模组”页面下载,或到网上下载“%3$s”。 +game.crash.reason.mod_resolution_missing_minecraft=当前游戏由于模组和 Minecraft 游戏版本不匹配,无法继续运行。\n“%1$s”需要 Minecraft %2$s 才能运行。\n如果你要继续使用你已经安装的模组,你可以选择安装对应的 Minecraft 版本;如果你要继续使用当前 Minecraft 版本,你需要安装对应版本的模组。 game.crash.reason.mod_resolution_mod_version=%1$s (版本号 %2$s) game.crash.reason.mod_resolution_mod_version.any=%1$s (任意版本) -game.crash.reason.forge_repeat_installation=当前游戏因为 Forge 重复安装,无法继续运行。此为已知问题\n建议将日志上传反馈至 GitHub ,以便我们找到更多线索并修复此问题。\n目前你可以到 自动安装 里头卸载 Forge 并重新安装。 -game.crash.reason.optifine_repeat_installation=当前游戏因为 Optifine 重复安装,无法继续运行。\n请删除 Mod 文件夹下的 Optifine 或前往 游戏管理-自动安装 卸载自动安装的 Optifine。 -game.crash.reason.forgemod_resolution=当前游戏因为模组依赖问题,无法继续运行。Forge 提供了如下信息:\n%1$s -game.crash.reason.forge_found_duplicate_mods=当前游戏因为模组重复问题,无法继续运行。Forge 提供了如下信息:\n%1$s -game.crash.reason.modmixin_failure=当前游戏因为某些 Mod 注入失败,无法继续运行。\n这一般代表着该 Mod 存在 Bug,或与当前环境不兼容。\n你可以查看日志寻找出错模组。 -game.crash.reason.night_config_fixes=当前游戏因为 Night Config 库的一些问题,无法继续运行。\n你可以尝试安装 Night Config Fixes 模组,这或许能帮助你解决这个问题。\n了解更多,可访问该模组的 GitHub 仓库。 -game.crash.reason.forge_error=Forge 可能已经提供了错误信息。\n你可以查看日志,并根据错误报告中的日志信息进行对应处。\n如果没有看到报错信息,可以查看错误报告了解错误具体是如何发生的。\n%1$s -game.crash.reason.mod_resolution0=当前游戏因为一些模组出现问题,无法继续运行。\n你可以查看日志寻找出错模组。 -game.crash.reason.fabric_reports_an_error_and_gives_a_solution=Fabric 可能已经提供了错误信息。\n你可以查看日志,并根据错误报告中的日志信息进行对应处。\n如果没有看到报错信息,可以查看错误报告了解错误具体是如何发生的。 -game.crash.reason.java_version_is_too_high=当前游戏因为 Java 虚拟机版本过高,无法继续运行。\n请在全局(特定)游戏设置的 Java 路径选项卡中改用较低版本的 Java,然后再启动游戏。\n如果没有,可以从 java.com(Java8)BellSoft Liberica Full JRE(Java17) 等平台下载、安装一个(安装完后需重启启动器)。 -game.crash.reason.mod_name=当前游戏因为模组文件名称问题,无法继续运行。\n模组文件名称应只使用英文全半角的大小写字母(Aa~Zz)、数字(0~9)、横线(-)、下划线(_)和点(.)。\n请到模组文件夹中将所有不合规的模组文件名称添加一个上述的合规的字符。 -game.crash.reason.incomplete_forge_installation=当前游戏因为 Forge / NeoForge 安装不完整,无法继续运行。\n请在 版本设置 - 自动安装 中卸载 Forge 并重新安装。 -game.crash.reason.optifine_is_not_compatible_with_forge=当前游戏因为 OptiFine 与当前版本的 Forge 不兼容,导致了游戏崩溃。\n点击 此处 查看 OptiFine 所兼容的 Forge 版本,并严格按照对应版本重新安装游戏或在 版本设置 - 自动安装 中更换版本。\n经测试,Forge 版本过高或过低都可能导致崩溃。 -game.crash.reason.mod_files_are_decompressed=当前游戏因为模组文件被解压了,无法继续运行。\n请直接把整个模组文件放进模组文件夹中即可。\n解压模组会导致游戏出错,请删除模组文件夹中已被解压的模组,然后再启动游戏。 -game.crash.reason.shaders_mod=当前游戏因为同时安装了 OptiFine 和 Shaders 模组,无法继续运行。\n因为 OptiFine 已集成 Shaders 模组的功能,只需删除 Shaders 模组即可。 -game.crash.reason.rtss_forest_sodium=当前游戏因为 RivaTuner Statistics Server (RTSS) 与 Sodium 不兼容,导致了游戏崩溃。\n点击 此处 查看详情。 -game.crash.reason.too_many_mods_lead_to_exceeding_the_id_limit=当前游戏因为您所安装的模组过多,超出了游戏的 ID 限制,无法继续运行。\n请尝试安装 JEID 等修复模组,或删除部分大型模组。 -game.crash.reason.optifine_causes_the_world_to_fail_to_load=当前游戏可能因为 OptiFine ,无法继续运行。\n该问题只在特定 OptiFine 版本中出现,你可以尝试在 版本设置 - 自动安装 中更换 OptiFine 的版本。 -game.crash.reason.modlauncher_8=当前游戏因为您所使用的 Forge 版本与当前使用的 Java 冲突崩溃,请尝试更新 Forge 到 36.2.26 或更高版本或换用版本低于 1.8.0.320 的 Java,Liberica JDK 8u312+7。 -game.crash.reason.no_class_def_found_error=当前游戏因为代码不完整,无法继续运行。\n你的游戏可能缺失了某个模组,或者某些模组文件不完整,或者模组与游戏的版本不匹配。\n你可能需要重新安装游戏和模组,或请求他人帮助。\n缺失:\n%1$s -game.crash.reason.no_such_method_error=当前游戏因为代码不完整,无法继续运行。\n你的游戏可能缺失了某个模组,或者某些模组文件不完整,或者模组与游戏的版本不匹配。\n你可能需要重新安装游戏和模组,或请求他人帮助。 -game.crash.reason.opengl_not_supported=当前游戏因为你的显卡驱动存在问题,无法继续运行。\n原因是 OpenGL 不受支持,你现在是否在远程桌面或者串流模式下?如果是,请直接使用原电脑启动游戏。\n或者尝试升级你的显卡驱动到最新版本后再尝试启动游戏。如果你的电脑存在独立显卡,你需要检查游戏是否使用集成/核心显卡启动,如果是,请尝试使用独立显卡启动 HMCL 与游戏。如果仍有问题,你可能需要考虑换一个新显卡或新电脑。 -game.crash.reason.openj9=当前游戏无法运行在 OpenJ9 虚拟机上,请你在全局(特定)游戏设置中更换 Hotspot Java 虚拟机,并重新启动游戏。如果没有下载安装,你可以在网上自行下载。 -game.crash.reason.out_of_memory=当前游戏因为内存不足,无法继续运行。\n这可能是内存分配太小,或者模组数量过多导致的。\n你可以在全局(特定)游戏设置中调大游戏内存分配值以允许游戏在更大的内存下运行。\n如果仍然出现该错误,你可能需要换一台更好的电脑。 -game.crash.reason.resolution_too_high=当前游戏因为材质包分辨率过高,无法继续运行\n你可以更换一个分辨率更低的材质,或者更换一个显存更大的显卡。 +game.crash.reason.forge_repeat_installation=当前游戏由于 Forge 重复安装,无法继续运行。此为已知问题\n建议将日志上传并反馈至 GitHub,以便我们找到更多线索并修复此问题。\n目前你可以在“版本管理 → 自动安装”中卸载 Forge 并重新安装。 +game.crash.reason.optifine_repeat_installation=当前游戏由于 OptiFine 重复安装,无法继续运行。\n请删除模组文件夹下的 OptiFine 或前往“版本管理 → 自动安装”卸载安装的 OptiFine。 +game.crash.reason.forgemod_resolution=当前游戏由于模组依赖问题,无法继续运行。Forge/NeoForge 提供了如下信息:\n%1$s +game.crash.reason.forge_found_duplicate_mods=当前游戏由于模组重复安装,无法继续运行。Forge/NeoForge 提供了如下信息:\n%1$s +game.crash.reason.modmixin_failure=当前游戏由于某些模组注入失败,无法继续运行。\n这一般代表着该模组存在问题,或与当前环境不兼容。\n你可以查看日志寻找出错模组。 +game.crash.reason.night_config_fixes=当前游戏由于 Night Config 库的一些问题,无法继续运行。\n你可以尝试安装 Night Config Fixes 模组,这或许能帮助你解决这个问题。\n更多详情,可访问该模组的 GitHub 仓库。 +game.crash.reason.forge_error=Forge/NeoForge 可能已经提供了错误信息。\n你可以查看日志,并根据错误报告中的日志信息进行对应处理。\n如果没有看到报错信息,可以查看错误报告了解错误具体是如何发生的。\n%1$s +game.crash.reason.mod_resolution0=当前游戏由于一些模组出现问题,无法继续运行。\n你可以查看日志寻找出错模组。 +game.crash.reason.java_version_is_too_high=当前游戏由于 Java 虚拟机版本过高,无法继续运行。\n请在“(全局/版本特定) 游戏设置 → 游戏 Java”中改用较低版本的 Java,然后再启动游戏。\n如果没有,可以从 java.com (Java 8)BellSoft Liberica Full JRE (Java 17) 等平台下载、安装一个 (安装完后需重启启动器)。 +game.crash.reason.mod_name=当前游戏由于模组文件名称问题,无法继续运行。\n模组文件名称应只使用半角的大小写字母 (Aa~Zz)、数字 (0~9)、横线 (-)、下划线 (_)和点 (.)。\n请到模组文件夹中将所有不合规的模组文件名修改为上述合规字符。 +game.crash.reason.incomplete_forge_installation=当前游戏由于 Forge/NeoForge 安装不完整,无法继续运行。\n请在“版本管理 → 自动安装”中卸载 Forge 并重新安装。 +game.crash.reason.optifine_is_not_compatible_with_forge=当前游戏由于 OptiFine 与当前版本的 Forge 不兼容,导致游戏崩溃。\n点击 此处 查看 OptiFine 所兼容的 Forge 版本,并严格按照对应版本重新安装游戏或在“版本管理 → 自动安装”中更换版本。\n经测试,Forge 版本过高或过低都可能导致崩溃。 +game.crash.reason.mod_files_are_decompressed=当前游戏由于模组文件被解压了,无法继续运行。\n请直接把整个模组文件放进模组文件夹中即可。\n解压模组会导致游戏出错,请删除模组文件夹中已被解压的模组,然后再启动游戏。 +game.crash.reason.shaders_mod=当前游戏由于同时安装了 OptiFine 和 Shaders 模组,无法继续运行。\n由于 OptiFine 已集成 Shaders 模组的功能,所以只需删除 Shaders 模组即可。 +game.crash.reason.rtss_forest_sodium=当前游戏由于 RivaTuner Statistics Server (RTSS) 与 Sodium 不兼容,导致游戏崩溃。\n点击 此处 查看详情。 +game.crash.reason.too_many_mods_lead_to_exceeding_the_id_limit=当前游戏由于你所安装的模组过多,超出了游戏的 ID 限制,无法继续运行。\n请尝试安装 JEID 等修复模组,或删除部分大型模组。 +game.crash.reason.optifine_causes_the_world_to_fail_to_load=当前游戏可能由于 OptiFine 而无法继续运行。\n该问题只在特定 OptiFine 版本中出现,你可以尝试在“版本管理 → 自动安装”中更换 OptiFine 的版本。 +game.crash.reason.modlauncher_8=当前游戏由于你所使用的 Forge 版本与当前使用的 Java 冲突而崩溃,请尝试更新 Forge 到 36.2.26 或更高版本或换用版本低于 1.8.0.320 的 Java。Liberica JDK 8u312+7 +game.crash.reason.no_class_def_found_error=当前游戏由于代码不完整,无法继续运行。\n你的游戏可能缺失了某个模组,或者某些模组文件不完整,或者模组与游戏的版本不匹配。\n你可能需要重新安装游戏和模组,或寻求他人帮助。\n缺失:\n%1$s +game.crash.reason.no_such_method_error=当前游戏由于代码不完整,无法继续运行。\n你的游戏可能缺失了某个模组,或者某些模组文件不完整,或者模组与游戏的版本不匹配。\n你可能需要重新安装游戏和模组,或寻求他人帮助。 +game.crash.reason.opengl_not_supported=当前游戏由于你的显卡驱动存在问题,无法继续运行。\n原因是 OpenGL 不受支持,你现在是否在远程桌面或者串流模式下?如果是,请直接使用原电脑启动游戏。\n或者尝试升级你的显卡驱动到最新版本后再尝试启动游戏。如果你的电脑存在独立显卡,你需要检查游戏是否使用集成/核心显卡启动,如果是,请尝试使用独立显卡启动 HMCL 与游戏。如果仍有问题,你可能需要考虑换一个新显卡或新电脑。 +game.crash.reason.openj9=当前游戏无法在 OpenJ9 虚拟机上运行,请你在“(全局/版本特定) 游戏设置 → 游戏 Java”中更换 Hotspot Java 虚拟机,并重新启动游戏。如果没有下载安装,你可以在网上自行下载。 +game.crash.reason.out_of_memory=当前游戏由于内存不足,无法继续运行。\n这可能是内存分配太小,或者模组数量过多导致的。\n你可以在全局(特定)游戏设置中调大游戏内存分配值以允许游戏在更大的内存下运行。\n如果仍然出现该错误,你可能需要换一台更好的电脑。 +game.crash.reason.resolution_too_high=当前游戏由于材质包分辨率过高,无法继续运行\n你可以更换一个分辨率更低的材质,或者更换一个显存更大的显卡。 game.crash.reason.stacktrace=原因未知,请点击日志按钮查看详细信息。\n下面是一些关键词,其中可能包含模组名称,你可以通过搜索的方式查找有关信息。\n%s -game.crash.reason.too_old_java=当前游戏因为 Java 虚拟机版本过低,无法继续运行。\n你需要在全局(特定)游戏设置中更换 Java %1$s 或更新版本的 Java 虚拟机,并重新启动游戏。如果没有下载安装,你可以点击 此处 下载 Liberica JDK。 +game.crash.reason.too_old_java=当前游戏由于 Java 虚拟机版本过低,无法继续运行。\n你需要在“(全局/版本特定) 游戏设置 → 游戏 Java”中更换 Java %1$s 或更新版本的 Java 虚拟机,并重新启动游戏。如果没有下载安装,你可以点击 此处 下载 Microsoft JDK。 game.crash.reason.unknown=原因未知,请点击日志按钮查看详细信息。 -game.crash.reason.unsatisfied_link_error=当前游戏因为缺少本地库,无法继续运行。\n这些本地库缺失:%1$s。\n如果你在全局(特定)游戏设置中修改了本地库路径选项,请你修改回预设模式。\n如果你正在使用预设模式,请检查游戏路径是否只包含英文大小写字母,数字,下划线,\n如果是,那么请检查是否是由于模组或者 HMCL 导致了本地库缺失的问题。如果你确定是 HMCL 引起的,建议你向我们反馈。\n你可以尝试在控制在 控制面板 时钟和区域 区域 管理 更改系统区域设置 中将 当前系统区域设置选项卡修改为:中文(简体,中国),并且把 使用Unicode UTF-8提供全球语言支持 关闭;\n或将游戏路径中的所有 非英文字符的名称 修改为 英文字符(例如中文,空格等)\n如果你确实需要自定义本地库路径,你需要保证其中包含缺失的本地库! -game.crash.reason.failed_to_load_a_library=当前游戏因为加载本地库失败,无法继续运行。\n如果你在全局(特定)游戏设置中修改了本地库路径选项,请你修改回预设模式。\n如果已经在预设模式下,请检查本地库缺失是否是 Mod 引起的,或由 HMCL 引起的。如果你确定是 HMCL 引起的,建议你向我们反馈。\n你可以尝试在控制在 控制面板 时钟和区域 区域 管理 更改系统区域设置 中将 当前系统区域设置选项卡修改为:中文(简体,中国),并且把 使用Unicode UTF-8提供全球语言支持 关闭;\n或将游戏路径中的所有 非英文字符的名称 修改为 英文字符(例如中文,空格等)\n如果你确实需要自定义本地库路径,你需要保证其中包含缺失的本地库! +game.crash.reason.unsatisfied_link_error=当前游戏由于缺少本地库,无法继续运行。\n这些本地库缺失:%1$s。\n如果你在“(全局/版本特定) 游戏设置 → 高级设置”中修改了本地库路径选项,请你修改回默认模式。\n如果你正在使用默认模式,请检查游戏文件夹路径是否只包含英文字母、数字和下划线,\n如果是,那么请检查是否为模组或 HMCL 导致了本地库缺失的问题。如果你确定是 HMCL 引起的,建议你向我们反馈。\n对于 Windows 用户,你还可以尝试在“控制面板 → 时钟和区域 → 区域 → 管理 → 更改系统区域设置”中将“当前系统区域设置”修改为“中文(简体,中国大陆)”,并关闭“Beta 版:使用 Unicode UTF-8 提供全球语言支持”选项;\n或将游戏文件夹路径中的所有非英文字符的名称 (例如中文、空格等) 修改为英文字符。\n如果你确实需要自定义本地库路径,你需要保证其中包含缺失的本地库! game.crash.title=游戏意外退出 -game.directory=游戏路径 +game.directory=游戏文件夹路径 game.version=游戏版本 help=帮助 help.doc=Hello Minecraft! Launcher 帮助文档 -help.detail=可查阅数据包、整合包制作指南等内容。 +help.detail=可查阅数据包、整合包制作指南等内容 input.email=用户名必须是邮箱 input.number=必须是数字 @@ -494,77 +493,109 @@ install.failed.downloading=安装失败,部分文件未能完成下载 install.failed.downloading.detail=未能下载文件:%s install.failed.downloading.timeout=下载超时:%s install.failed.install_online=无法识别要安装的组件。如果你要安装模组,你需要在模组管理页面安装模组。 -install.failed.malformed=下载的文件格式损坏。您可以在设置-下载中切换到其他下载源来尝试解决此问题。 -install.failed.optifine_conflict=暂不支持 OptiFine, Fabric 或 OptiFine , Forge 同时安装在 Minecraft 1.13 及以上版本 -install.failed.optifine_forge_1.17=Minecraft 1.17.1 下,仅 OptiFine H1 Pre2 及以上版本能兼容 Forge。你可以从 OptiFine 测试版中选择最新版本。 +install.failed.malformed=下载的文件已损坏。你可以在“设置 → 下载 → 下载源”中切换其他下载源来尝试解决此问题。 +install.failed.optifine_conflict=暂不支持在 Minecraft 1.13 及更高版本同时安装 OptiFine 与 Fabric。 +install.failed.optifine_forge_1.17=对于 Minecraft 1.17.1 版本,仅 OptiFine H1 pre2 及更高版本与 Forge 兼容。你可以在 OptiFine 预览版 (Preview versions) 中选择最新版本。 install.failed.version_mismatch=该组件需要的游戏版本为 %s,但实际的游戏版本为 %s。 -install.installer.change_version=%s,该版本与当前游戏不兼容,您需要点击此处更换版本或删除 +install.installer.change_version=%s 与当前游戏不兼容,请更换版本 install.installer.choose=选择 %s 版本 install.installer.depend=需要先安装 %s install.installer.fabric=Fabric install.installer.fabric-api=Fabric API -install.installer.fabric-api.warning=警告:Fabric API 是一个模组,将会被安装到新游戏的模组文件夹,请你在安装游戏后不要修改当前游戏的版本隔离/游戏运行路径设置,如果你在之后修改了相关设置,Fabric API 需要被重新安装。 +install.installer.fabric-api.warning=警告:Fabric API 是一个模组,将会被安装到新游戏的模组文件夹,请你在安装游戏后不要修改当前游戏的“运行路径”设置,如果你在之后修改了相关设置,则需要重新安装 Fabric API。 install.installer.forge=Forge install.installer.neoforge=NeoForge install.installer.game=Minecraft install.installer.incompatible=与 %s 不兼容 install.installer.install=安装 %s install.installer.install_offline=从本地文件安装/升级 -install.installer.install_offline.extension=Forge/OptiFine 安装器 -install.installer.install_offline.tooltip=支持导入已经下载好的 Forge/OptiFine 安装器 +install.installer.install_offline.extension=(Neo)Forge/OptiFine 安装器 +install.installer.install_offline.tooltip=支持导入已经下载好的 (Neo)Forge/OptiFine 安装器 install.installer.install_online=在线安装 -install.installer.install_online.tooltip=支持安装 Fabric、Forge、OptiFine、LiteLoader +install.installer.install_online.tooltip=支持安装 Forge、NeoForge、Fabric、Quilt、LiteLoader 和 OptiFine install.installer.liteloader=LiteLoader install.installer.not_installed=不安装 install.installer.optifine=OptiFine install.installer.quilt=Quilt install.installer.quilt-api=QSL/QFAPI install.installer.version=%s -install.installer.external_version=%s 由外部安装的版本,无法卸载或更换 +install.installer.external_version=%s (由外部安装的版本,无法卸载或更换) install.modpack=安装整合包 -install.new_game=安装新游戏版本 +install.new_game=安装新游戏 install.new_game.already_exists=此版本已经存在,请换一个名字 install.new_game.current_game_version=当前游戏版本 install.new_game.malformed=名字不合法 install.select=请选择安装方式 install.success=安装成功 +java.add=添加 Java +java.add.failed=Java 无效或与当前平台不兼容。 +java.disable=禁用此 Java +java.disable.confirm=你确定要禁用此 Java 吗? +java.disabled.management=管理已禁用的 Java +java.disabled.management.remove=从列表中移除此 Java +java.disabled.management.restore=重新启用此 Java +java.download=下载 Java +java.download.load_list.failed=加载版本列表失败 +java.download.more=更多发行版 +java.download.prompt=请选择你要下载的 Java 版本: +java.download.distribution=发行版 +java.download.version=版本 +java.download.packageType=包类型 +java.management=Java 管理 +java.info.architecture=架构 +java.info.vendor=供应商 +java.info.version=版本 +java.info.disco.distribution=发行版 +java.install=安装 Java +java.install.archive=源路径 +java.install.failed.exists=该名称已被使用 +java.install.failed.invalid=该压缩包不是合法的 Java 安装包,无法继续安装。 +java.install.failed.unsupported_platform=此 Java 与当前平台不兼容,无法安装。 +java.install.name=名称 +java.install.warning.invalid_character=名称中包含非法字符 +java.reveal=浏览 Java 文件夹 +java.uninstall=卸载此 Java +java.uninstall.confirm=你确定要卸载此 Java 吗?此操作无法撤销! + lang=简体中文 lang.default=跟随系统语言 -launch.advice=%s是否继续启动? +launch.advice==%s 是否继续启动? launch.advice.multi=检测到以下问题:\n\n%s\n\n这些问题可能导致游戏无法正常启动或影响游戏体验,是否继续启动?\n你可以点击右上角帮助按钮进行求助。 -launch.advice.java.auto=当前选择的 Java 虚拟机版本不满足游戏要求\n点击“是”即可由 HMCL 来自动选取合适的 Java 虚拟机版本\n或者你可以到全局(特定)游戏设置中选择一个合适的 Java 虚拟机版本。 -launch.advice.java.modded_java_7=Minecraft 1.7.2 及以下版本需要 Java 7 及以下版本。 -launch.advice.corrected=我们已经修复了 Java 虚拟机版本问题。如果您确实希望使用您自定义的 Java 虚拟机,您可以在全局(特定)游戏设置往下滑,关闭 Java 虚拟机兼容性检查。 -launch.advice.uncorrected=如果您确实希望使用您自定义的 Java 虚拟机,您可以在全局(特定)游戏设置往下滑,关闭 Java 虚拟机兼容性检查 +launch.advice.java.auto=当前选择的 Java 虚拟机版本不满足游戏要求。\n点击“是”即可由 HMCL 来自动选取合适的 Java 虚拟机版本。\n或者你可以在“(全局/版本特定) 游戏设置 → 游戏 Java”中选择一个合适的 Java 虚拟机版本。 +launch.advice.java.modded_java_7=Minecraft 1.7.2 及更低版本需要 Java 7 及更低版本。 +launch.advice.corrected=我们已经修复了 Java 虚拟机版本问题。如果你确实希望使用你自定义的 Java 虚拟机,你可以在“(全局/版本特定) 游戏设置 → 高级设置”中往下滑,启用“不检查 JVM 与游戏的兼容性”。 +launch.advice.uncorrected=如果你确实希望使用你自定义的 Java 虚拟机,你可以在“(全局/版本特定) 游戏设置 → 高级设置”中往下滑,启用“不检查 JVM 与游戏的兼容性”。 launch.advice.different_platform=你正在使用 32 位 Java 启动游戏,建议更换至 64 位 Java。 -launch.advice.forge2760_liteloader=Forge 2760 与 LiteLoader 不兼容,请更新 Forge 至 2773 或更新的版本。 -launch.advice.forge28_2_2_optifine=Forge 28.2.2 或更高版本与 OptiFine 不兼容。请将 Forge 降级至 28.2.1 或更低版本。 -launch.advice.forge37_0_60=Forge 低于 37.0.60 的版本不兼容 Java 17。请更新 Forge 到 37.0.60 或更高版本,或者使用 Java 16 启动游戏。 -launch.advice.java8_1_13=Minecraft 1.13 及以上版本只能运行在 Java 8 或更高版本上,请使用 Java 8 或最新版本。 +launch.advice.forge2760_liteloader=Forge 14.23.5.2760 与 LiteLoader 不兼容,请更新 Forge 至 14.23.5.2773 或更高版本。 +launch.advice.forge28_2_2_optifine=Forge 28.2.2 及更高版本与 OptiFine 不兼容。请降级 Forge 至 28.2.1 或更低版本。 +launch.advice.forge37_0_60=Forge 37.0.59 及更低版本与 Java 17 不兼容。请更新 Forge 至 37.0.60 或更高版本,或使用 Java 16 启动游戏。 +launch.advice.java8_1_13=Minecraft 1.13 及更高版本只能在 Java 8 或更高版本上运行,请使用 Java 8 或最新版本。 launch.advice.java8_51_1_13=低于 1.8.0_51 的 Java 版本可能会导致 Minecraft 1.13 崩溃,建议更新 Java 至 1.8.0_51 或更高版本后再次启动。 -launch.advice.java9=低于 1.13 的有安装模组的 Minecraft 版本不支持 Java 9 或更高版本,请使用 Java 8。 +launch.advice.java9=低于 1.13 的有安装模组的 Minecraft 版本与 Java 9 或更高版本不兼容,请使用 Java 8。 launch.advice.modded_java=部分模组可能与高版本 Java 不兼容,建议使用 Java %s 启动 Minecraft %s。 -launch.advice.modlauncher8=您所使用的 Forge 版本与当前使用的 Java 不兼容。请尝试更新 Forge,或使用 Java 8u312/11.0.13/17.0.1 及更早版本启动。 +launch.advice.modlauncher8=你所使用的 Forge 版本与当前使用的 Java 不兼容,请更新 Forge。 launch.advice.newer_java=检测到你正在使用旧版本 Java 启动游戏,这可能导致部分模组引发游戏崩溃,建议更新至 Java 8 后再次启动。 -launch.advice.not_enough_space=你设置的内存大小过大,超过了系统内存容量 %dMB,可能导致游戏无法启动。 -launch.advice.require_newer_java_version=Minecraft %1$s 仅能运行在 Java %2$s 或更高版本上,但 HMCL 未能找到该 Java 版本,你可以点击“是”,HMCL会自动下载他,是否下载?\n如遇到问题,你可以点击右上角帮助按钮进行求助。 -launch.advice.too_large_memory_for_32bit=您设置的内存大小过大,由于可能超过了 32 位 Java 的内存分配限制,所以可能无法启动游戏,请将内存调至 1024MB 或更小。\n如遇到问题,你可以点击右上角帮助按钮进行求助。 -launch.advice.vanilla_linux_java_8=对于 Linux x86-64 平台,Minecraft 1.12.2 及以下版本与 Java 9+ 不兼容,请使用 Java 8 启动游戏。\n如遇到问题,你可以点击右上角帮助按钮进行求助。 -launch.advice.vanilla_x86.translation=Minecraft 尚未为您的平台提供完善支持,所以可能影响游戏体验或无法启动游戏。\n你可以在 这里 下载 X86-64 架构的 Java 以获得更完整的体验。 +launch.advice.not_enough_space=你设置的内存大小过大,超过了系统内存容量 %d MB,可能导致游戏无法启动。 +launch.advice.require_newer_java_version=当前游戏版本需要 Java %s,但 HMCL 未能找到该 Java 版本,你可以点击“是”,HMCL 会自动下载他,是否下载?\n如遇到问题,你可以点击右上角帮助按钮进行求助。 +launch.advice.too_large_memory_for_32bit=你设置的内存大小过大,由于可能超过了 32 位 Java 的内存分配限制,所以可能无法启动游戏,请将内存调至 1024 MB 或更小。\n如遇到问题,你可以点击右上角帮助按钮进行求助。 +launch.advice.vanilla_linux_java_8=对于 Linux x86-64 平台,Minecraft 1.12.2 及更低版本与 Java 9+ 不兼容,请使用 Java 8 启动游戏。\n如遇到问题,你可以点击右上角帮助按钮进行求助。 +launch.advice.vanilla_x86.translation=Minecraft 尚未为你的平台提供完善支持,所以可能影响游戏体验或无法启动游戏。\n你可以在 这里 下载 x86-64 架构的 Java 以获得更完整的体验。 +launch.advice.unknown=由于以下原因,无法继续启动游戏: launch.failed=启动失败 -launch.failed.cannot_create_jvm=截获到无法创建 Java 虚拟机,可能是 Java 参数有问题,可以在设置中开启无参数模式启动。 +launch.failed.cannot_create_jvm=无法创建 Java 虚拟机,可能是 Java 参数有问题,你可以在“(全局/版本特定) 游戏设置 → 高级设置 → Java 虚拟机设置”中移除所有 Java 虚拟机参数后,尝试再次启动游戏。 launch.failed.creating_process=启动失败,在创建新进程时发生错误,可能是 Java 路径错误。 -launch.failed.command_too_long=命令长度超过限制,无法创建 bat 脚本,请导出为 PowerShell 脚本。 +launch.failed.command_too_long=命令长度超过限制,无法创建批处理脚本,请导出为 PowerShell 脚本。 launch.failed.decompressing_natives=未能解压游戏本地库。 -launch.failed.download_library=未能下载游戏依赖 %s +launch.failed.download_library=未能下载游戏依赖“%s” launch.failed.executable_permission=未能为启动文件添加执行权限。 launch.failed.execution_policy=设置执行策略 launch.failed.execution_policy.failed_to_set=设置执行策略失败 -launch.failed.execution_policy.hint=当前执行策略阻止您执行 PowerShell 脚本。\n点击“确定”允许当前用户执行本地 PowerShell 脚本,或点击“取消”保持现状。 +launch.failed.execution_policy.hint=当前执行策略阻止你执行 PowerShell 脚本。\n点击“确定”允许当前用户执行本地 PowerShell 脚本,或点击“取消”保持现状。 launch.failed.exited_abnormally=游戏非正常退出,请查看日志文件,或联系他人寻求帮助。 -launch.failed.no_accepted_java=找不到适合当前游戏使用的 Java,是否使用默认 Java 启动游戏?点击“是”使用默认 Java 继续启动游戏,\n或者请到全局(特定)游戏设置中选择一个合适的 Java 虚拟机版本。\n你可以点击右上角帮助按钮进行求助。 +launch.failed.java_version_too_low=你所指定的 Java 版本过低,请重新设置 Java 版本。 +launch.failed.no_accepted_java=找不到适合当前游戏使用的 Java,是否使用默认 Java 启动游戏?点击“是”使用默认 Java 继续启动游戏,\n或者在“(全局/版本特定) 游戏设置 → 游戏 Java”中选择一个合适的 Java 虚拟机版本。\n你可以点击右上角帮助按钮进行求助。 launch.failed.sigkill=游戏被用户或系统强制终止。 launch.state.dependencies=处理游戏依赖 launch.state.done=启动完成 @@ -572,33 +603,33 @@ launch.state.java=检测 Java 版本 launch.state.logging_in=登录 launch.state.modpack=下载必要文件 launch.state.waiting_launching=等待游戏启动 -launch.wrong_javadir=错误的 Java 路径,将自动重置为默认 Java 路径。 +launch.invalid_java=当前设置的 Java 路径无效,请重新设置 Java 路径。 launcher=启动器 launcher.agreement=用户协议与免责声明 launcher.agreement.accept=同意 launcher.agreement.decline=拒绝 launcher.agreement.hint=同意本软件的用户协议与免责声明以使用本软件。 -launcher.background=背景地址 -launcher.background.choose=选择背景路径 +launcher.background=背景图片 +launcher.background.choose=选择背景图片 launcher.background.classic=经典 -launcher.background.default=默认(自动检索启动器同目录下的 background.png/jpg/gif 及 bg 文件夹内的图片) +launcher.background.default=默认 (自动检索启动器同文件夹下的“background.png/.jpg/.gif”及“bg”文件夹内的图片) launcher.background.network=网络 launcher.background.translucent=半透明 -launcher.cache_directory=文件下载缓存目录 +launcher.cache_directory=文件下载缓存文件夹 launcher.cache_directory.clean=清理缓存 -launcher.cache_directory.choose=选择文件下载缓存目录 -launcher.cache_directory.default=默认(%AppData%/.minecraft 或者 ~/.minecraft) -launcher.cache_directory.disabled=禁用(总是使用游戏路径) -launcher.cache_directory.invalid=无法创建自定义的缓存目录,恢复默认设置 +launcher.cache_directory.choose=选择文件下载缓存文件夹 +launcher.cache_directory.default=默认 ("%AppData%/.minecraft" 或 "~/.minecraft") +launcher.cache_directory.disabled=禁用 (总是使用游戏文件夹路径) +launcher.cache_directory.invalid=无法创建自定义的缓存文件夹,恢复默认设置 launcher.contact=联系我们 -launcher.crash=Hello Minecraft! Launcher 遇到了无法处理的错误,请复制下列内容并点击右下角的按钮反馈 bug。 +launcher.crash=Hello Minecraft! Launcher 遇到了无法处理的错误,请复制下列内容并点击右下角的按钮反馈问题。 launcher.crash.java_internal_error=Hello Minecraft! Launcher 由于当前 Java 损坏而无法继续运行,请卸载当前 Java,点击 此处 安装合适的 Java 版本。 -launcher.crash.hmcl_out_dated=Hello Minecraft! Launcher 遇到了无法处理的错误,已检测到您的启动器不是最新版本,请更新后再试。 -launcher.update_java=请更新您的 Java \n你可以访问\n https://docs.hmcl.net/help.html \n页面寻求帮助。 +launcher.crash.hmcl_out_dated=Hello Minecraft! Launcher 遇到了无法处理的错误,已检测到你的启动器不是最新版本,请更新后再试。 +launcher.update_java=请更新你的 Java。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。 login.empty_username=你还未设置用户名! -login.enter_password=请输入您的密码 +login.enter_password=请输入你的密码 logwindow.show_lines=显示行数 logwindow.terminate_game=结束游戏进程 @@ -606,23 +637,21 @@ logwindow.title=日志 logwindow.help=你可以前往 HMCL 社区,寻找他人帮助 logwindow.autoscroll=自动滚动 logwindow.export_game_crash_logs=导出游戏崩溃信息 -logwindow.export_dump.dependency_ok.button=导出游戏运行栈 -logwindow.export_dump.dependency_ok.doing_button=正在导出游戏运行栈(可能需要 15 秒) -logwindow.export_dump.no_dependency.button=导出游戏运行栈(不兼容) -logwindow.export_dump.no_dependency.tooltip=你的 Java 不包含用于创建游戏运行栈的依赖。请前往 HMCL QQ 群或 Discord 频道寻求帮助。 +logwindow.export_dump=导出游戏运行栈 +logwindow.export_dump.no_dependency=你的 Java 不包含用于创建游戏运行栈的依赖。请前往 HMCL QQ 群或 Discord 寻求帮助。 main_page=主页 message.cancelled=操作被取消 message.confirm=提示 -message.copied=已拷贝至剪贴板 +message.copied=已复制到剪贴板 message.default=默认 message.doing=请耐心等待 message.downloading=正在下载 message.error=错误 message.failed=操作失败 message.info=提示 -message.success=已完成 +message.success=完成 message.unknown=未知 message.warning=警告 @@ -632,27 +661,27 @@ modpack.choose.local=导入本地整合包文件 modpack.choose.local.detail=你可以直接将整合包文件拖入本页面以安装 modpack.choose.remote=从互联网下载整合包 modpack.choose.remote.detail=需要提供整合包的下载链接 -modpack.choose.repository=从 Curseforge / Modrinth 下载整合包 +modpack.choose.repository=从 CurseForge/Modrinth 下载整合包 modpack.choose.repository.detail=下载后记得回到这个界面,把整合包拖进来哦 modpack.choose.remote.tooltip=要下载的整合包的链接 modpack.completion=下载整合包相关文件 -modpack.desc=描述你要制作的整合包,比如整合包注意事项和更新记录,支持 HTML(图片请用网络图) +modpack.desc=描述你要制作的整合包,比如整合包注意事项和更新记录,支持 Markdown (图片请用网络链接) modpack.description=整合包描述 modpack.download=下载整合包 modpack.enter_name=给游戏起个你喜欢的名字 modpack.export=导出整合包 -modpack.export.as=请选择整合包类型 (若无法决定,请选择我的世界中文论坛整合包标准) +modpack.export.as=请选择整合包类型 (若无法决定,请选择“我的世界中文论坛整合包标准”) modpack.file_api=整合包下载链接前缀 modpack.files.blueprints=BuildCraft 蓝图 modpack.files.config=模组配置文件 modpack.files.dumps=NEI 调试输出 modpack.files.hmclversion_cfg=启动器配置文件 -modpack.files.liteconfig=模组配置文件 +modpack.files.liteconfig=LiteLoader 相关文件 modpack.files.mods=模组 modpack.files.mods.voxelmods=VoxelMods 配置,如小地图 -modpack.files.options_txt=游戏设定 -modpack.files.optionsshaders_txt=光影设定 -modpack.files.resourcepacks=资源包(材质包) +modpack.files.options_txt=游戏设置 +modpack.files.optionsshaders_txt=光影设置 +modpack.files.resourcepacks=资源包 (纹理包) modpack.files.saves=游戏存档 modpack.files.scripts=MineTweaker 配置 modpack.files.servers_dat=多人游戏服务器列表 @@ -660,20 +689,18 @@ modpack.install=安装 %s 整合包 modpack.installing=正在安装整合包 modpack.introduction=支持 Curse、Modrinth、MultiMC、MCBBS 整合包。 modpack.invalid=无效的整合包升级文件,可能是下载时出现问题。 -modpack.mismatched_type=整合包类型不匹配,当前游戏是 %s 整合包,但是提供的整合包更新文件是 %s 整合包。\n如遇到问题,你可以点击右上角帮助按钮进行求助。 +modpack.mismatched_type=整合包类型不匹配,当前游戏为“%s”整合包,但是提供的整合包更新文件为“%s”整合包。\n如遇到问题,你可以点击右上角帮助按钮进行求助。 modpack.name=整合包名称 modpack.not_a_valid_name=不是一个有效的整合包名称 modpack.origin=来源 modpack.origin.url=官方网站 modpack.origin.mcbbs=MCBBS -modpack.origin.mcbbs.prompt=贴子 id +modpack.origin.mcbbs.prompt=帖子 ID modpack.scan=解析整合包 modpack.task.install=导入整合包 modpack.task.install.error=无法识别该整合包,目前仅支持导入 Curse、Modrinth、MultiMC、MCBBS 整合包。\n你可以点击右上角帮助按钮进行求助。 -modpack.task.install.will=导入的整合包文件位置: modpack.type.curse=Curse -modpack.type.curse.tolerable_error=但未能完成该整合包所需的依赖下载,您可以在启动该游戏版本时继续该整合包文件的下载。由于网络问题,您可能需要重试多次。\n你可以点击右上角帮助按钮进行求助。 -modpack.type.curse.error=未能完成该整合包所需的依赖下载,请多次重试或设置代理。\n你可以点击右上角帮助按钮进行求助。 +modpack.type.curse.error=未能完成该整合包所需的依赖下载,请再次尝试或设置代理。\n你可以点击右上角帮助按钮进行求助。 modpack.type.curse.not_found=部分必需文件已经在网络中被删除并且再也无法下载,请尝试该整合包的最新版本或者安装其他整合包。\n如遇到问题,你可以点击右上角帮助按钮进行求助。 modpack.type.manual.warning=该整合包由发布者手动打包,其中可能已经包含启动器,建议尝试解压后使用其自带的启动器运行游戏。\n如遇到问题,你可以点击右上角帮助按钮进行求助。\nHMCL 可以尝试导入该整合包,但不保证可用性,是否继续? modpack.type.mcbbs=我的世界中文论坛整合包标准 @@ -683,21 +710,21 @@ modpack.type.multimc=MultiMC modpack.type.multimc.export=可以被 Hello Minecraft! Launcher 和 MultiMC 导入 modpack.type.server=服务器自动更新整合包 modpack.type.server.export=允许服务器管理员远程更新游戏客户端 -modpack.type.server.malformed=服务器整合包配置格式错误,请联系服务器管理员解决此问题\n如遇到问题,你可以点击右上角帮助按钮进行求助。 +modpack.type.server.malformed=服务器整合包配置格式错误,请联系服务器管理员解决此问题。\n如遇到问题,你可以点击右上角帮助按钮进行求助。 modpack.unsupported=Hello Minecraft! Launcher 不支持该整合包格式 modpack.update=正在升级整合包 modpack.wizard=导出整合包向导 modpack.wizard.step.1=基本设置 modpack.wizard.step.1.title=设置整合包的主要信息 modpack.wizard.step.2=文件选择 -modpack.wizard.step.2.title=选中你想加到整合包中的文件或文件夹 +modpack.wizard.step.2.title=选中你想添加到整合包中的文件或文件夹 modpack.wizard.step.3=整合包类型 modpack.wizard.step.3.title=选择整合包导出类型 modpack.wizard.step.initialization.exported_version=要导出的游戏版本 -modpack.wizard.step.initialization.force_update=强制升级整合包至最新版本(需要自建服务器) +modpack.wizard.step.initialization.force_update=强制升级整合包至最新版本 (需要自建服务器) modpack.wizard.step.initialization.include_launcher=包含启动器 modpack.wizard.step.initialization.save=选择要导出到的游戏整合包位置 -modpack.wizard.step.initialization.warning=在制作整合包前,请您确认您选择的版本可以正常启动,\n并保证您的 Minecraft 是正式版而非快照版,\n而且不应当将不允许非官方途径传播的模组、材质包等纳入整合包。\n整合包会保存您目前的下载源设置\n如遇到问题,你可以点击右上角帮助按钮进行求助。 +modpack.wizard.step.initialization.warning=在制作整合包前,请你确认你选择的版本可以正常启动,\n并保证你的 Minecraft 是正式版而非快照,\n而且不应当将不允许非官方途径传播的模组、资源 (纹理) 包等纳入整合包。\n整合包会保存你目前的下载源设置。\n如遇到问题,你可以点击右上角帮助按钮进行求助。 modpack.wizard.step.initialization.server=点击此处查看有关服务器自动更新整合包的制作教程 modrinth.category.adventure=冒险 @@ -763,11 +790,14 @@ modrinth.category.folia=Folia mods=模组 mods.add=添加模组 -mods.add.failed=添加模组 %s 失败。\n如遇到问题,你可以点击右上角帮助按钮进行求助。 +mods.add.failed=添加模组“%s”失败。\n如遇到问题,你可以点击右上角帮助按钮进行求助。 mods.add.success=成功添加模组 %s。 mods.broken_dependency.title=损坏的前置模组 mods.broken_dependency.desc=该前置模组曾经在该模组仓库上存在过,但现在被删除了。换个下载源试试吧。 mods.category=类别 +mods.channel.alpha=快照版本 +mods.channel.beta=测试版本 +mods.channel.release=稳定版本 mods.check_updates=检查模组更新 mods.check_updates.current_version=当前版本 mods.check_updates.empty=没有需要更新的模组 @@ -778,13 +808,13 @@ mods.check_updates.target_version=目标版本 mods.check_updates.update=更新 mods.choose_mod=选择模组 mods.curseforge=CurseForge -mods.dependency.embedded=内置的前置模组(已经由作者打包在模组文件中,无需另外下载) -mods.dependency.optional=可选的前置模组(若缺失游戏能够正常运行,但模组功能可能缺失) -mods.dependency.required=必须的前置模组(必须另外下载,缺失可能会导致游戏无法启动) -mods.dependency.tool=前置库(必须另外下载,缺失可能会导致游戏无法启动) -mods.dependency.include=内置的前置模组(已经由作者打包在模组文件中,无需另外下载) -mods.dependency.incompatible=不兼容的模组(同时安装该模组和正在下载的模组会导致游戏无法启动) -mods.dependency.broken=损坏的前置模组(该前置模组曾经在该模组仓库上存在过,但现在被删除了。换个下载源试试吧。) +mods.dependency.embedded=内置的前置模组 (已经由作者打包在模组文件中,无需另外下载) +mods.dependency.optional=可选的前置模组 (若缺失游戏能够正常运行,但模组功能可能缺失) +mods.dependency.required=必须的前置模组 (必须另外下载,缺失可能会导致游戏无法启动) +mods.dependency.tool=前置库 (必须另外下载,缺失可能会导致游戏无法启动) +mods.dependency.include=内置的前置模组 (已经由作者打包在模组文件中,无需另外下载) +mods.dependency.incompatible=不兼容的模组 (同时安装该模组和正在下载的模组会导致游戏无法启动) +mods.dependency.broken=损坏的前置模组 (该前置模组曾经在该模组仓库上存在过,但现在被删除了,换个下载源试试吧) mods.disable=禁用 mods.download=模组下载 mods.download.title=模组下载 - %1s @@ -797,10 +827,12 @@ mods.mcmod.page=MC 百科页面 mods.mcmod.search=MC 百科搜索 mods.modrinth=Modrinth mods.name=名称 -mods.not_modded=你需要先在自动安装页面安装 Fabric、Forge 或 LiteLoader 才能进行模组管理。 +mods.not_modded=你需要先在“自动安装”页面安装 Forge、NeoForge、Fabric、Quilt 或 LiteLoader 才能管理模组。 mods.restore=回退 mods.url=官方页面 mods.update_modpack_mod.warning=更新整合包中的模组可能导致整合包损坏,使整合包无法正常启动。该操作不可逆,确定要更新吗? +mods.install=安装到当前版本 +mods.save_as=下载到本地文件夹 nbt.entries=%s 个条目 nbt.open.failed=打开文件失败 @@ -811,25 +843,29 @@ datapack=数据包 datapack.add=添加数据包 datapack.choose_datapack=选择要导入的数据包压缩包 datapack.extension=数据包 -datapack.title=世界 %s - 数据包 +datapack.title=世界 [%s] - 数据包 + +web.failed=加载页面失败 +web.open_in_browser=是否要在浏览器中打开此链接:\n%s +web.view_in_browser=在浏览器中查看 world=世界 world.add=添加世界 world.datapack=管理数据包 world.datapack.1_13=仅 Minecraft 1.13 及之后的版本支持数据包 -world.description=%s。上一次游戏时间: %s。游戏版本: %s。 +world.description=%s | 上一次游戏时间: %s | 游戏版本: %s world.download=存档下载 world.export=导出此世界 world.export.title=选择该世界的存储位置 world.export.location=保存到 -world.export.wizard=导出世界 %s +world.export.wizard=导出世界“%s” world.extension=世界压缩包 world.game_version=游戏版本 world.import.already_exists=此世界已经存在 world.import.choose=选择要导入的存档压缩包 world.import.failed=无法导入此世界:%s world.import.invalid=无法识别该存档压缩包 -world.info.title=世界 %s - 世界信息 +world.info.title=世界 [%s] - 世界信息 world.info.basic=基本信息 world.info.allow_cheats=允许作弊 world.info.dimension.the_nether=下界 @@ -839,6 +875,7 @@ world.info.difficulty.peaceful=和平 world.info.difficulty.easy=简单 world.info.difficulty.normal=普通 world.info.difficulty.hard=困难 +world.info.failed=读取世界信息失败 world.info.game_version=游戏版本 world.info.last_played=上一次游戏时间 world.info.generate_features=生成建筑 @@ -847,7 +884,7 @@ world.info.player.food_level=饥饿值 world.info.player.game_type=游戏模式 world.info.player.game_type.adventure=冒险 world.info.player.game_type.creative=创造 -world.info.player.game_type.spectator=旁观 +world.info.player.game_type.spectator=旁观者 world.info.player.game_type.survival=生存 world.info.player.health=生命值 world.info.player.last_death_location=上次死亡位置 @@ -862,26 +899,26 @@ world.name=世界名称 world.name.enter=输入世界名称 world.reveal=打开文件夹 world.show_all=显示全部 -world.time=yyyy 年 MM 月 dd 日 HH:mm:ss +world.time=yyyy 年 MM 月 dd 日, HH:mm:ss -profile=游戏目录 +profile=游戏文件夹 profile.already_exists=该名称已存在 -profile.default=当前目录 -profile.home=官方启动器目录 -profile.instance_directory=游戏路径 -profile.instance_directory.choose=选择游戏路径 -profile.manage=游戏目录列表 +profile.default=当前文件夹 +profile.home=官方启动器文件夹 +profile.instance_directory=游戏文件夹路径 +profile.instance_directory.choose=选择游戏文件夹路径 +profile.manage=游戏文件夹列表 profile.name=名称 -profile.new=添加游戏目录 -profile.title=游戏目录 +profile.new=添加游戏文件夹 +profile.title=游戏文件夹 profile.selected=已选中 -profile.use_relative_path=若可能,游戏目录使用相对路径 +profile.use_relative_path=若可能,游戏文件夹使用相对路径 -repositories.custom=自定义 Maven 仓库(%s) -repositories.maven_central=全球(Maven Central) -repositories.tencentcloud_mirror=中国大陆(腾讯云镜像源 Maven 仓库) +repositories.custom=自定义 Maven 仓库 (%s) +repositories.maven_central=全球 (Maven Central) +repositories.tencentcloud_mirror=中国大陆 (腾讯云镜像源 Maven 仓库) repositories.chooser=缺少 JavaFX 运行环境,HMCL 需要 JavaFX 才能正常运行。\n点击“确认”从指定下载源下载 JavaFX 运行时组件并启动 HMCL,点击“取消”退出程序。\n选择下载源: -repositories.chooser.title=选择下载源下载 JavaFX +repositories.chooser.title=选择 JavaFX 下载源 resourcepack=资源包 @@ -903,34 +940,34 @@ selector.custom=自定义 settings=设置 settings.advanced=高级设置 -settings.advanced.modify=修改高级设置 +settings.advanced.modify=编辑高级设置 settings.advanced.title=高级设置 - %s settings.advanced.custom_commands=自定义命令 settings.advanced.custom_commands.hint=自定义命令被调用时将包含如下的环境变量:\n\ - \ - $INST_NAME: 版本名称\n\ - \ - $INST_ID: 版本名称\n\ - \ - $INST_DIR: 版本文件夹\n\ - \ - $INST_MC_DIR: 游戏运行路径\n\ - \ - $INST_JAVA: 游戏运行使用的 Java 路径\n\ - \ - $INST_FORGE: 若安装了 Forge,将会存在本环境变量\n\ - \ - $INST_NEOFORGE: 若安装了 NeoForge,将会存在本环境变量\n\ - \ - $INST_LITELOADER: 若安装了 LiteLoader,将会存在本环境变量\n\ - \ - $INST_OPTIFINE: 若安装了 OptiFine,将会存在本环境变量\n\ - \ - $INST_FABRIC: 若安装了 Fabric,将会存在本环境变量\n\ - \ - $INST_QUILT: 若安装了 Quilt,将会存在本环境变量 + \ · $INST_NAME: 版本名称;\n\ + \ · $INST_ID: 版本名称;\n\ + \ · $INST_DIR: 当前版本运行路径;\n\ + \ · $INST_MC_DIR: 当前游戏文件夹路径;\n\ + \ · $INST_JAVA: 游戏运行使用的 Java 路径;\n\ + \ · $INST_FORGE: 若安装了 Forge,将会存在本环境变量;\n\ + \ · $INST_NEOFORGE: 若安装了 NeoForge,将会存在本环境变量;\n\ + \ · $INST_LITELOADER: 若安装了 LiteLoader,将会存在本环境变量;\n\ + \ · $INST_OPTIFINE: 若安装了 OptiFine,将会存在本环境变量;\n\ + \ · $INST_FABRIC: 若安装了 Fabric,将会存在本环境变量;\n\ + \ · $INST_QUILT: 若安装了 Quilt,将会存在本环境变量。 settings.advanced.dont_check_game_completeness=不检查游戏完整性 settings.advanced.dont_check_jvm_validity=不检查 JVM 与游戏的兼容性 settings.advanced.dont_patch_natives=不尝试自动替换本地库 settings.advanced.environment_variables=环境变量 -settings.advanced.game_dir.default=默认(.minecraft/) -settings.advanced.game_dir.independent=各版本独立(存放在 .minecraft/versions/<版本名>/,除 assets、libraries 外) +settings.advanced.game_dir.default=默认 (".minecraft/") +settings.advanced.game_dir.independent=各版本独立 (存放在 ".minecraft/versions/<版本名>/",除 assets、libraries 外) settings.advanced.java_permanent_generation_space=内存永久保存区域 settings.advanced.java_permanent_generation_space.prompt=单位 MB settings.advanced.jvm=Java 虚拟机设置 settings.advanced.jvm_args=Java 虚拟机参数 -settings.advanced.jvm_args.prompt=- 若在“Java 虚拟机参数”输入的参数与默认参数相同,则不会添加\n\ -- 在“Java 虚拟机参数”输入任何 GC 参数,默认参数的 G1 参数会禁用\n\ -- 点击下方“不添加默认的 JVM 参数”可在启动游戏时不添加默认参数 +settings.advanced.jvm_args.prompt=\ · 若在“Java 虚拟机参数”输入的参数与默认参数相同,则不会添加;\n\ + \ · 在“Java 虚拟机参数”输入任何 GC 参数,默认参数的 G1 参数会禁用;\n\ + \ · 点击下方“不添加默认的 JVM 参数”可在启动游戏时不添加默认参数。 settings.advanced.launcher_visibility.close=游戏启动后结束启动器 settings.advanced.launcher_visibility.hide=游戏启动后隐藏启动器 settings.advanced.launcher_visibility.hide_and_reopen=隐藏启动器并在游戏结束后重新打开 @@ -938,35 +975,35 @@ settings.advanced.launcher_visibility.keep=保持启动器可见 settings.advanced.launcher_visible=启动器可见性 settings.advanced.minecraft_arguments=游戏参数 settings.advanced.minecraft_arguments.prompt=默认 -settings.advanced.natives_directory=本地库路径(LWJGL) +settings.advanced.natives_directory=本地库路径 (LWJGL) settings.advanced.natives_directory.choose=选择本地库路径 -settings.advanced.natives_directory.custom=自定义(由你提供游戏需要的本地库) -settings.advanced.natives_directory.default=预设(由启动器提供游戏本地库) -settings.advanced.natives_directory.hint=本选项提供给 Apple M1 等未受游戏官方支持的平台来自定义游戏本地库,如果你不知道本选项的含义,请你不要修改本选项,否则会导致游戏无法启动!\n如果你要修改本选项,你需要保证自定义目录下有游戏所需的本地库文件,如 lwjgl.dll(liblwjgl.so), openal.dll(libopenal.so) 等文件。启动器不会帮你补全缺少的本地库文件!\n注意:指定的本地库文件路径建议只包含英文大小写字母,数字,下划线,否则可能会导致启动游戏失败。 +settings.advanced.natives_directory.custom=自定义 (由你提供游戏需要的本地库) +settings.advanced.natives_directory.default=默认 (由启动器提供游戏本地库) +settings.advanced.natives_directory.hint=本选项提供给 Apple M1 等未受游戏官方支持的平台来自定义游戏本地库,如果你不知道本选项的含义,请不要修改本选项,否则会导致游戏无法启动!\n如果你要修改本选项,你需要保证自定义文件夹下有游戏所需的本地库文件,如 lwjgl.dll (liblwjgl.so)、openal.dll (libopenal.so) 等文件。启动器不会帮你补全缺少的本地库文件!\n注意:指定的本地库文件路径建议只包含英文大小写字母、数字和下划线,否则可能会导致启动游戏失败。 settings.advanced.no_jvm_args=不添加默认的 JVM 参数 settings.advanced.precall_command=游戏启动前执行命令 settings.advanced.precall_command.prompt=将在游戏启动前调用 settings.advanced.process_priority=进程优先级 -settings.advanced.process_priority.low=低(节省游戏占用资源,可能会造成游戏卡顿) -settings.advanced.process_priority.below_normal=较低(节省游戏占用资源,可能会造成游戏卡顿) -settings.advanced.process_priority.normal=中(平衡) -settings.advanced.process_priority.above_normal=较高(优先保证游戏运行,但可能会导致其他程序卡顿) -settings.advanced.process_priority.high=高(优先保证游戏运行,但可能会导致其他程序卡顿) +settings.advanced.process_priority.low=低 (节省游戏占用资源,可能会造成游戏卡顿) +settings.advanced.process_priority.below_normal=较低 (节省游戏占用资源,可能会造成游戏卡顿) +settings.advanced.process_priority.normal=中 (平衡) +settings.advanced.process_priority.above_normal=较高 (优先保证游戏运行,但可能会导致其他程序卡顿) +settings.advanced.process_priority.high=高 (优先保证游戏运行,但可能会导致其他程序卡顿) settings.advanced.post_exit_command=游戏结束后执行命令 settings.advanced.post_exit_command.prompt=将在游戏结束后调用 settings.advanced.renderer=渲染器 -settings.advanced.renderer.default=OpenGL(默认) -settings.advanced.renderer.d3d12=DirectX 12(性能与兼容性较差,用于调试) -settings.advanced.renderer.llvmpipe=软渲染器(性能较差,兼容性最好) -settings.advanced.renderer.zink=Vulkan(性能最好,兼容性较差) +settings.advanced.renderer.default=OpenGL (默认) +settings.advanced.renderer.d3d12=DirectX 12 (性能与兼容性较差,用于调试) +settings.advanced.renderer.llvmpipe=软渲染器 (性能较差,兼容性最好) +settings.advanced.renderer.zink=Vulkan (性能最好,兼容性较差) settings.advanced.server_ip=服务器地址 settings.advanced.server_ip.prompt=默认,启动游戏后可以直接进入对应服务器 -settings.advanced.use_native_glfw=使用系统 GLFW -settings.advanced.use_native_openal=使用系统 OpenAL +settings.advanced.use_native_glfw=[仅 Linux/FreeBSD] 使用系统 GLFW +settings.advanced.use_native_openal=[仅 Linux/FreeBSD] 使用系统 OpenAL settings.advanced.workaround=调试选项 settings.advanced.workaround.warning=调试选项仅提供给专业玩家使用。调试选项可能会导致游戏无法启动。除非你知道你在做什么,否则请不要修改这些选项! settings.advanced.wrapper_launcher=包装命令 -settings.advanced.wrapper_launcher.prompt=如填写 optirun 后,启动命令将从 "java ..." 变为 "optirun java ..." +settings.advanced.wrapper_launcher.prompt=如填写“optirun”后,启动命令将从“java ...”变为“optirun java ...” settings.custom=自定义 @@ -975,28 +1012,29 @@ settings.game.current=游戏 settings.game.dimension=游戏窗口分辨率 settings.game.exploration=浏览 settings.game.fullscreen=全屏 -settings.game.java_directory=Java 路径 +settings.game.java_directory=游戏 Java settings.game.java_directory.auto=自动选择合适的 Java settings.game.java_directory.auto.not_found=没有合适的 Java settings.game.java_directory.bit=%s 位 -settings.game.java_directory.choose=选择 Java 路径 +settings.game.java_directory.choose=选择 Java settings.game.java_directory.invalid=Java 路径不正确 -settings.game.java_directory.template=%s(%s) +settings.game.java_directory.version=指定 Java 版本 +settings.game.java_directory.template=%s (%s) settings.game.management=管理 -settings.game.working_directory=版本隔离(建议使用模组时开启“各版本隔离”,改后需移动存档模组等相关游戏文件) -settings.game.working_directory.choose=选择运行路径 -settings.game.working_directory.hint=在“运行路径(版本隔离)”选项中开启“各版本独立”使当前版本独立存放设置、存档、模组等数据,使用模组时建议开启此选项以避免不同版本模组冲突。修改此选项后请自行移动存档等文件。 +settings.game.working_directory=版本隔离 (建议使用模组时选择“各版本独立”,改后需移动存档、模组等相关游戏文件) +settings.game.working_directory.choose=选择运行文件夹 +settings.game.working_directory.hint=在“版本隔离”中选择“各版本独立”使当前版本独立存放设置、存档、模组等数据,使用模组时建议启用此选项以避免不同版本模组冲突。修改此选项后需自行移动存档等文件。 settings.icon=游戏图标 settings.launcher=启动器设置 settings.launcher.appearance=外观 -settings.launcher.common_path.tooltip=启动器将所有游戏资源及依赖库文件放于此集中管理,如果游戏文件夹内有现成的将不会使用公共库文件 +settings.launcher.common_path.tooltip=启动器将所有游戏资源及依赖库文件存放于此集中管理,如果游戏文件夹内有现成的将不会使用公共库文件。 settings.launcher.debug=调试 settings.launcher.download=下载 -settings.launcher.download.threads=并发数 -settings.launcher.download.threads.auto=自动选择并发数 -settings.launcher.download.threads.hint=并发数过大可能导致系统卡顿。你的下载速度会受到宽带运营商、服务器等方面的影响,调大下载并发数不一定能大幅提升总下载速度。 +settings.launcher.download.threads=线程数 +settings.launcher.download.threads.auto=自动选择线程数 +settings.launcher.download.threads.hint=线程数过高可能导致系统卡顿。你的下载速度会受到互联网运营商、下载源服务器等方面的影响,调高下载线程数不一定能大幅提升总下载速度。 settings.launcher.download_source=下载源 settings.launcher.download_source.auto=自动选择下载源 settings.launcher.enable_game_list=在主页内显示版本列表 @@ -1004,8 +1042,9 @@ settings.launcher.font=字体 settings.launcher.general=通用 settings.launcher.language=语言 (重启后生效) settings.launcher.launcher_log.export=导出启动器日志 +settings.launcher.launcher_log.reveal=打开日志文件夹 settings.launcher.launcher_log.export.failed=无法导出日志 -settings.launcher.launcher_log.export.success=日志已保存到 %s +settings.launcher.launcher_log.export.success=日志已保存到“%s” settings.launcher.log=日志 settings.launcher.log.font=日志字体 settings.launcher.proxy=代理 @@ -1033,20 +1072,20 @@ settings.memory.lower_bound=最低内存分配 settings.memory.used_per_total=设备中已使用 %1$.1f GB / 设备总内存 %2$.1f GB settings.physical_memory=物理内存大小 settings.show_log=查看日志 -settings.skin=现已支持离线账户更换皮肤,你可以到账户页面更改离线账户的皮肤和披风(多人游戏下其他玩家无法看到你的皮肤) +settings.skin=现已支持离线账户更换皮肤,你可以到账户页面更改离线账户的皮肤和披风 (多人游戏下其他玩家无法看到你的皮肤)。 settings.tabs.installers=自动安装 settings.take_effect_after_restart=重启后生效 -settings.type=版本设置类型 -settings.type.global=全局版本设置(未启用游戏特定设置的共用此设定) +settings.type=版本游戏设置类型 +settings.type.global=全局游戏设置 (未启用“版本特定游戏设置”的版本共用此设置) settings.type.global.manage=全局游戏设置 -settings.type.global.edit=编辑全局版本设置 -settings.type.special.enable=启用游戏特定设置(不影响其他游戏版本) -settings.type.special.edit=编辑游戏特定设置 -settings.type.special.edit.hint=当前游戏版本 %s 启用了游戏特定设置,因此本页面选项不对该游戏生效。点击链接前往该游戏版本的游戏特定设置页 +settings.type.global.edit=编辑全局游戏设置 +settings.type.special.enable=启用版本特定游戏设置 (不影响其他游戏版本) +settings.type.special.edit=编辑版本特定游戏设置 +settings.type.special.edit.hint=当前游戏版本“%s”启用了“版本特定游戏设置”,因此本页面选项不对该版本生效。点击链接前往该版本的“游戏设置”页。 sponsor=赞助 -sponsor.bmclapi=国内下载源由 BMCLAPI 提供高速下载服务。BMCLAPI 为公益服务,赞助 BMCLAPI 可以帮助作者更好的提供稳定高速的下载服务,[点击此处查阅详细信息] -sponsor.hmcl=Hello Minecraft! Launcher 是一个免费、自由、开放源代码的 Minecraft 启动器。[点击此处查阅更多详细信息] +sponsor.bmclapi=国内下载源由 BMCLAPI 提供高速下载服务。BMCLAPI 为公益服务,赞助 BMCLAPI 可以帮助作者更好地提供稳定高速的下载服务。[点击此处查阅详细信息] +sponsor.hmcl=Hello Minecraft! Launcher 是一个免费、自由、开放源代码的 Minecraft 启动器。[点击此处查阅详细信息] system.architecture=架构 system.operating_system=操作系统 @@ -1056,40 +1095,40 @@ unofficial.hint=你正在使用非官方构建的 HMCL,我们无法保证其 update=启动器更新 update.accept=更新 update.changelog=更新日志 -update.channel.dev=测试版 -update.channel.dev.hint=你正在使用测试版。测试版包含一些未在正式版中包含的测试性功能,仅用于体验新功能。\n\ - 测试版功能未受充分验证,使用起来可能不稳定!下载稳定版\n\ - 如果你遇到了使用问题,可以在设置的 反馈页面 中进行反馈,或加入反馈页面中提供的 DiscordQQ 群以反馈问题。欢迎关注 B 站账号 huanghongxun 以关注 HMCL 的开发进展。\n\n\ - 点击此处为当前版本隐藏该提示。 -update.channel.dev.title=测试版提示 +update.channel.dev=开发版 +update.channel.dev.hint=你正在使用 HMCL 开发版。开发版包含一些未在稳定版中包含的测试性功能,仅用于体验新功能。开发版功能未受充分验证,使用起来可能不稳定!下载稳定版\n\ + \n\ + 如果你使用时遇到了问题,可以通过设置中反馈页面提供的渠道进行反馈。欢迎关注 B 站账号 @huanghongxun 以关注 HMCL 的开发进展。 +update.channel.dev.title=开发版提示 update.channel.nightly=预览版 -update.channel.nightly.hint=你正在使用预览版。预览版可能会每天更新一次,包含一些未在正式版和测试版中包含的测试性功能,仅用于体验新功能。\n\ - 测试版功能未受充分验证,使用起来可能不稳定!下载稳定版\n\ - 如果你遇到了使用问题,可以在设置的 反馈页面 中进行反馈,或加入反馈页面中提供的 DiscordQQ 群以反馈问题。欢迎关注 B 站账号 huanghongxun 以关注 HMCL 的开发进展。 +update.channel.nightly.hint=你正在使用 HMCL 预览版。预览版更新较为频繁,包含一些未在稳定版和开发版中包含的测试性功能,仅用于体验新功能。预览版功能未受充分验证,使用起来可能不稳定!下载稳定版\n\ + \n\ + 如果你使用时遇到了问题,可以通过设置中反馈页面提供的渠道进行反馈。欢迎关注 B 站账号 @huanghongxun 以关注 HMCL 的开发进展。 update.channel.nightly.title=预览版提示 -update.channel.stable=推荐版本 +update.channel.stable=稳定版 update.checking=正在检查更新 update.failed=更新失败 update.found=发现更新 update.newest_version=最新版本为:%s update.bubble.title=发现更新:%s update.bubble.subtitle=点击此处进行升级 -update.note=测试版与开发版包含更多的功能以及错误修复,但也可能会包含其他的问题。 +update.note=开发版与预览版包含更多的功能以及错误修复,但也可能会包含其他的问题。 update.latest=当前版本为最新版本 -update.no_browser=无法打开浏览器,网址已经复制到剪贴板了,您可以手动粘贴网址打开页面 +update.no_browser=无法打开浏览器,网址已经复制到剪贴板,你可以手动粘贴网址打开页面。 update.tooltip=更新 version=游戏 +version.name=游戏版本名称 version.cannot_read=读取游戏版本失败,无法进行自动安装 version.empty=没有游戏版本 version.empty.add=进入下载页安装游戏 version.empty.launch=没有可启动的游戏,你可以点击左侧边栏内的下载按钮安装游戏 -version.empty.hint=没有已安装的游戏,你可以切换其他游戏目录,或者点击此处进入游戏下载页面 +version.empty.hint=没有已安装的游戏,你可以切换其他游戏文件夹,或者点击此处进入游戏下载页面 version.game.old=远古版 version.game.release=正式版 version.game.releases=正式版 -version.game.snapshot=测试版 -version.game.snapshots=测试版 +version.game.snapshot=快照 +version.game.snapshots=快照 version.launch=启动游戏 version.launch.test=测试游戏 version.switch=切换版本 @@ -1098,24 +1137,24 @@ version.launch_script.failed=生成启动脚本失败 version.launch_script.save=保存启动脚本 version.launch_script.success=启动脚本已生成完毕:%s version.manage=版本列表 -version.manage.clean=清理游戏目录 -version.manage.clean.tooltip=清理 logs, crash-reports -version.manage.duplicate=复制游戏实例 +version.manage.clean=清理游戏文件夹 +version.manage.clean.tooltip=清理“logs”和“crash-reports”文件夹 +version.manage.duplicate=复制游戏版本 version.manage.duplicate.duplicate_save=复制存档 -version.manage.duplicate.prompt=请输入新游戏实例名称 -version.manage.duplicate.confirm=将锁定复制产生的新游戏实例:开启版本隔离、游戏设置并复制该版本产生的文件 -version.manage.manage=游戏管理 -version.manage.manage.title=游戏管理 - %1s +version.manage.duplicate.prompt=请输入新游戏名称 +version.manage.duplicate.confirm=新的游戏版本将复制该版本文件夹 (".minecraft/versions/<版本名>") 下的文件,并带有独立的运行文件夹和设置。 +version.manage.manage=版本管理 +version.manage.manage.title=版本管理 - %1s version.manage.redownload_assets_index=更新游戏资源文件 version.manage.remove=删除该版本 -version.manage.remove.confirm=真的要删除版本 %s 吗?你将无法找回被删除的文件!!! -version.manage.remove.confirm.trash=真的要删除版本 %s 吗?你可以在系统的回收站中恢复文件夹 %s 来找回该版本。 -version.manage.remove.confirm.independent=由于该游戏使用了版本隔离,所以删除该版本将导致该游戏的存档等数据一同被删除!真的要删除版本 %s 吗? +version.manage.remove.confirm=真的要删除版本“%s”吗?你将无法找回被删除的文件!!! +version.manage.remove.confirm.trash=真的要删除版本“%s”吗?你可以在系统的回收站中还原“%s”文件夹来找回该版本。 +version.manage.remove.confirm.independent=由于该游戏启用了“(全局/版本特定) 游戏设置 → 版本隔离 → 各版本独立”选项,所以删除该版本将导致该游戏的存档等数据一同被删除!真的要删除版本“%s”吗? version.manage.remove_assets=删除所有游戏资源文件 version.manage.remove_libraries=删除所有库文件 version.manage.rename=重命名该版本 version.manage.rename.message=请输入要修改的名称 -version.manage.rename.fail=重命名版本失败,可能文件被占用或者名字有特殊字符 +version.manage.rename.fail=重命名版本失败,可能文件被占用或者名字有特殊字符。 version.settings=游戏设置 version.update=更新整合包 diff --git a/HMCL/src/main/resources/assets/natives.json b/HMCL/src/main/resources/assets/natives.json index ede49575d4..7f4f6eaaff 100644 --- a/HMCL/src/main/resources/assets/natives.json +++ b/HMCL/src/main/resources/assets/natives.json @@ -154,6 +154,160 @@ } } }, + "org.lwjgl:lwjgl:3.2.1": { + "name": "org.lwjgl:lwjgl:3.3.2", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2.jar", + "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2.jar", + "sha1": "4421d94af68e35dcaa31737a6fc59136a1e61b94", + "size": 786196 + } + } + }, + "org.lwjgl:lwjgl:3.2.1:natives": { + "name": "org.lwjgl:lwjgl:3.3.2:natives-linux-arm64", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2-natives-linux-arm64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2-natives-linux-arm64.jar", + "sha1": "8bd89332c90a90e6bc4aa997a25c05b7db02c90a", + "size": 90795 + } + } + }, + "org.lwjgl:lwjgl-jemalloc:3.2.1": { + "name": "org.lwjgl:lwjgl-jemalloc:3.3.2", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2.jar", + "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2.jar", + "sha1": "877e17e39ebcd58a9c956dc3b5b777813de0873a", + "size": 43233 + } + } + }, + "org.lwjgl:lwjgl-jemalloc:3.2.1:natives": { + "name": "org.lwjgl:lwjgl-jemalloc:3.3.2:natives-linux-arm64", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2-natives-linux-arm64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.2/lwjgl-jemalloc-3.3.2-natives-linux-arm64.jar", + "sha1": "5249f18a9ae20ea86c5816bc3107a888ce7a17d2", + "size": 206402 + } + } + }, + "org.lwjgl:lwjgl-openal:3.2.1": { + "name": "org.lwjgl:lwjgl-openal:3.3.2", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2.jar", + "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2.jar", + "sha1": "ae5357ed6d934546d3533993ea84c0cfb75eed95", + "size": 108230 + } + } + }, + "org.lwjgl:lwjgl-openal:3.2.1:natives": { + "name": "org.lwjgl:lwjgl-openal:3.3.2:natives-linux-arm64", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2-natives-linux-arm64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.2/lwjgl-openal-3.3.2-natives-linux-arm64.jar", + "sha1": "22408980cc579709feaf9acb807992d3ebcf693f", + "size": 590865 + } + } + }, + "org.lwjgl:lwjgl-opengl:3.2.1": { + "name": "org.lwjgl:lwjgl-opengl:3.3.2", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2.jar", + "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2.jar", + "sha1": "ee8e95be0b438602038bc1f02dc5e3d011b1b216", + "size": 928871 + } + } + }, + "org.lwjgl:lwjgl-opengl:3.2.1:natives": { + "name": "org.lwjgl:lwjgl-opengl:3.3.2:natives-linux-arm64", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2-natives-linux-arm64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2-natives-linux-arm64.jar", + "sha1": "bb9eb56da6d1d549d6a767218e675e36bc568eb9", + "size": 58627 + } + } + }, + "org.lwjgl:lwjgl-glfw:3.2.1": { + "name": "org.lwjgl:lwjgl-glfw:3.3.2", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2.jar", + "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2.jar", + "sha1": "757920418805fb90bfebb3d46b1d9e7669fca2eb", + "size": 135828 + } + } + }, + "org.lwjgl:lwjgl-glfw:3.2.1:natives": { + "name": "org.lwjgl:lwjgl-glfw:3.3.2:natives-linux-arm64", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2-natives-linux-arm64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2-natives-linux-arm64.jar", + "sha1": "bc49e64bae0f7ff103a312ee8074a34c4eb034c7", + "size": 120168 + } + } + }, + "org.lwjgl:lwjgl-stb:3.2.1": { + "name": "org.lwjgl:lwjgl-stb:3.3.2", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2.jar", + "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2.jar", + "sha1": "a2550795014d622b686e9caac50b14baa87d2c70", + "size": 118874 + } + } + }, + "org.lwjgl:lwjgl-stb:3.2.1:natives": { + "name": "org.lwjgl:lwjgl-stb:3.3.2:natives-linux-arm64", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2-natives-linux-arm64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2-natives-linux-arm64.jar", + "sha1": "11a380c37b0f03cb46db235e064528f84d736ff7", + "size": 207419 + } + } + }, + "org.lwjgl:lwjgl-tinyfd:3.2.1": { + "name": "org.lwjgl:lwjgl-tinyfd:3.3.2", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2.jar", + "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2.jar", + "sha1": "9f65c248dd77934105274fcf8351abb75b34327c", + "size": 13404 + } + } + }, + "org.lwjgl:lwjgl-tinyfd:3.2.1:natives": { + "name": "org.lwjgl:lwjgl-tinyfd:3.3.2:natives-linux-arm64", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2-natives-linux-arm64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.2/lwjgl-tinyfd-3.3.2-natives-linux-arm64.jar", + "sha1": "93f8c5bc1984963cd79109891fb5a9d1e580373e", + "size": 43381 + } + } + }, "org.lwjgl:lwjgl:3.2.2": { "name": "org.lwjgl:lwjgl:3.3.2", "downloads": { @@ -851,7 +1005,7 @@ } } }, - "org.lwjgl:lwjgl:3.2.2": { + "org.lwjgl:lwjgl:3.2.1": { "name": "org.lwjgl:lwjgl:3.2.3", "downloads": { "artifact": { @@ -862,7 +1016,7 @@ } } }, - "org.lwjgl:lwjgl:3.2.2:natives": { + "org.lwjgl:lwjgl:3.2.1:natives": { "name": "org.lwjgl:lwjgl:3.2.3:natives-linux-arm32", "downloads": { "artifact": { @@ -873,7 +1027,7 @@ } } }, - "org.lwjgl:lwjgl-jemalloc:3.2.2": { + "org.lwjgl:lwjgl-jemalloc:3.2.1": { "name": "org.lwjgl:lwjgl-jemalloc:3.2.3", "downloads": { "artifact": { @@ -884,7 +1038,7 @@ } } }, - "org.lwjgl:lwjgl-jemalloc:3.2.2:natives": { + "org.lwjgl:lwjgl-jemalloc:3.2.1:natives": { "name": "org.lwjgl:lwjgl-jemalloc:3.2.3:natives-linux-arm32", "downloads": { "artifact": { @@ -895,7 +1049,7 @@ } } }, - "org.lwjgl:lwjgl-openal:3.2.2": { + "org.lwjgl:lwjgl-openal:3.2.1": { "name": "org.lwjgl:lwjgl-openal:3.2.3", "downloads": { "artifact": { @@ -906,7 +1060,7 @@ } } }, - "org.lwjgl:lwjgl-openal:3.2.2:natives": { + "org.lwjgl:lwjgl-openal:3.2.1:natives": { "name": "org.lwjgl:lwjgl-openal:3.2.3:natives-linux-arm32", "downloads": { "artifact": { @@ -917,7 +1071,7 @@ } } }, - "org.lwjgl:lwjgl-opengl:3.2.2": { + "org.lwjgl:lwjgl-opengl:3.2.1": { "name": "org.lwjgl:lwjgl-opengl:3.2.3", "downloads": { "artifact": { @@ -928,7 +1082,7 @@ } } }, - "org.lwjgl:lwjgl-opengl:3.2.2:natives": { + "org.lwjgl:lwjgl-opengl:3.2.1:natives": { "name": "org.lwjgl:lwjgl-opengl:3.2.3:natives-linux-arm32", "downloads": { "artifact": { @@ -939,7 +1093,7 @@ } } }, - "org.lwjgl:lwjgl-glfw:3.2.2": { + "org.lwjgl:lwjgl-glfw:3.2.1": { "name": "org.lwjgl:lwjgl-glfw:3.2.3", "downloads": { "artifact": { @@ -950,7 +1104,7 @@ } } }, - "org.lwjgl:lwjgl-glfw:3.2.2:natives": { + "org.lwjgl:lwjgl-glfw:3.2.1:natives": { "name": "org.lwjgl:lwjgl-glfw:3.2.3:natives-linux-arm32", "downloads": { "artifact": { @@ -961,7 +1115,7 @@ } } }, - "org.lwjgl:lwjgl-stb:3.2.2": { + "org.lwjgl:lwjgl-stb:3.2.1": { "name": "org.lwjgl:lwjgl-stb:3.2.3", "downloads": { "artifact": { @@ -972,7 +1126,7 @@ } } }, - "org.lwjgl:lwjgl-stb:3.2.2:natives": { + "org.lwjgl:lwjgl-stb:3.2.1:natives": { "name": "org.lwjgl:lwjgl-stb:3.2.3:natives-linux-arm32", "downloads": { "artifact": { @@ -983,7 +1137,7 @@ } } }, - "org.lwjgl:lwjgl-tinyfd:3.2.2": { + "org.lwjgl:lwjgl-tinyfd:3.2.1": { "name": "org.lwjgl:lwjgl-tinyfd:3.2.3", "downloads": { "artifact": { @@ -994,7 +1148,7 @@ } } }, - "org.lwjgl:lwjgl-tinyfd:3.2.2:natives": { + "org.lwjgl:lwjgl-tinyfd:3.2.1:natives": { "name": "org.lwjgl:lwjgl-tinyfd:3.2.3:natives-linux-arm32", "downloads": { "artifact": { @@ -1005,74 +1159,228 @@ } } }, - "org.lwjgl:lwjgl:3.3.1:natives-linux": { - "name": "org.lwjgl:lwjgl:3.3.1:natives-linux-arm32", + "org.lwjgl:lwjgl:3.2.2": { + "name": "org.lwjgl:lwjgl:3.2.3", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1-natives-linux-arm32.jar", - "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1-natives-linux-arm32.jar", - "sha1": "41a3c1dd15d6b964eb8196dde69720a3e3e5e969", - "size": 82374 + "path": "org/lwjgl/lwjgl/3.2.3/lwjgl-3.2.3.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.2.3/lwjgl-3.2.3.jar", + "sha1": "17a59ba0fe8d474ec9dbe0d5db40d2cfe59c4c08", + "size": 552997 } } }, - "org.lwjgl:lwjgl-jemalloc:3.3.1:natives-linux": { - "name": "org.lwjgl:lwjgl-jemalloc:3.3.1:natives-linux-arm32", + "org.lwjgl:lwjgl:3.2.2:natives": { + "name": "org.lwjgl:lwjgl:3.2.3:natives-linux-arm32", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1-natives-linux-arm32.jar", - "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1-natives-linux-arm32.jar", - "sha1": "a96a6d6cb3876d7813fcee53c3c24f246aeba3b3", - "size": 136157 + "path": "org/lwjgl/lwjgl/3.2.3/lwjgl-3.2.3-natives-linux-arm32.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.2.3/lwjgl-3.2.3-natives-linux-arm32.jar", + "sha1": "3180d363040744dfe0c6a0dd5d018cedae476e9a", + "size": 53035 } } }, - "org.lwjgl:lwjgl-openal:3.3.1:natives-linux": { - "name": "org.lwjgl:lwjgl-openal:3.3.1:natives-linux-arm32", + "org.lwjgl:lwjgl-jemalloc:3.2.2": { + "name": "org.lwjgl:lwjgl-jemalloc:3.2.3", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1-natives-linux-arm32.jar", - "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1-natives-linux-arm32.jar", - "sha1": "ffbe35d7fa5ec9b7eca136a7c71f24d4025a510b", - "size": 400129 + "path": "org/lwjgl/lwjgl-jemalloc/3.2.3/lwjgl-jemalloc-3.2.3.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.2.3/lwjgl-jemalloc-3.2.3.jar", + "sha1": "b6fd0932171ba3f2eaa4547beddca3a3e645342d", + "size": 34130 } } }, - "org.lwjgl:lwjgl-opengl:3.3.1:natives-linux": { - "name": "org.lwjgl:lwjgl-opengl:3.3.1:natives-linux-arm32", + "org.lwjgl:lwjgl-jemalloc:3.2.2:natives": { + "name": "org.lwjgl:lwjgl-jemalloc:3.2.3:natives-linux-arm32", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1-natives-linux-arm32.jar", - "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1-natives-linux-arm32.jar", - "sha1": "e3550fa91097fd56e361b4370fa822220fef3595", - "size": 58474 + "path": "org/lwjgl/lwjgl-jemalloc/3.2.3/lwjgl-jemalloc-3.2.3-natives-linux-arm32.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.2.3/lwjgl-jemalloc-3.2.3-natives-linux-arm32.jar", + "sha1": "d7e5cecbf045b7b7863343273ffea94e0e2f6994", + "size": 137847 } } }, - "org.lwjgl:lwjgl-glfw:3.3.1:natives-linux": { - "name": "org.lwjgl:lwjgl-glfw:3.3.1:natives-linux-arm32", + "org.lwjgl:lwjgl-openal:3.2.2": { + "name": "org.lwjgl:lwjgl-openal:3.2.3", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1-natives-linux-arm32.jar", - "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1-natives-linux-arm32.jar", - "sha1": "816d935933f2dd743074c4e717cc25b55720f294", - "size": 104027 + "path": "org/lwjgl/lwjgl-openal/3.2.3/lwjgl-openal-3.2.3.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.2.3/lwjgl-openal-3.2.3.jar", + "sha1": "106742e805803ab9eab8e343f0fb31a3d263903c", + "size": 79432 } } }, - "org.lwjgl:lwjgl-stb:3.3.1:natives-linux": { - "name": "org.lwjgl:lwjgl-stb:3.3.1:natives-linux-arm32", + "org.lwjgl:lwjgl-openal:3.2.2:natives": { + "name": "org.lwjgl:lwjgl-openal:3.2.3:natives-linux-arm32", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1-natives-linux-arm32.jar", - "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1-natives-linux-arm32.jar", - "sha1": "b08226bab162c06ae69337d8a1b0ee0a3fdf0b90", - "size": 153889 + "path": "org/lwjgl/lwjgl-openal/3.2.3/lwjgl-openal-3.2.3-natives-linux-arm32.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.2.3/lwjgl-openal-3.2.3-natives-linux-arm32.jar", + "sha1": "5c30ef08c829252e542f9fbc04772d51013326c5", + "size": 552314 } } }, - "org.lwjgl:lwjgl-tinyfd:3.3.1:natives-linux": { - "name": "org.lwjgl:lwjgl-tinyfd:3.3.1:natives-linux-arm32", + "org.lwjgl:lwjgl-opengl:3.2.2": { + "name": "org.lwjgl:lwjgl-opengl:3.2.3", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-opengl/3.2.3/lwjgl-opengl-3.2.3.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.2.3/lwjgl-opengl-3.2.3.jar", + "sha1": "bdd534a323d0c8f54969b95e424b6ac8984f7d6e", + "size": 936589 + } + } + }, + "org.lwjgl:lwjgl-opengl:3.2.2:natives": { + "name": "org.lwjgl:lwjgl-opengl:3.2.3:natives-linux-arm32", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-opengl/3.2.3/lwjgl-opengl-3.2.3-natives-linux-arm32.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.2.3/lwjgl-opengl-3.2.3-natives-linux-arm32.jar", + "sha1": "4925362a5f2412cb6467e6d6c6de26b9e1ccfc71", + "size": 58594 + } + } + }, + "org.lwjgl:lwjgl-glfw:3.2.2": { + "name": "org.lwjgl:lwjgl-glfw:3.2.3", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-glfw/3.2.3/lwjgl-glfw-3.2.3.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.2.3/lwjgl-glfw-3.2.3.jar", + "sha1": "5e520d5c290c8b012545a8d34fa5db5ab051ea53", + "size": 107999 + } + } + }, + "org.lwjgl:lwjgl-glfw:3.2.2:natives": { + "name": "org.lwjgl:lwjgl-glfw:3.2.3:natives-linux-arm32", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-glfw/3.2.3/lwjgl-glfw-3.2.3-natives-linux-arm32.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.2.3/lwjgl-glfw-3.2.3-natives-linux-arm32.jar", + "sha1": "aab1a5a1e21eca87f4acd5ba055f6bfd5d90951c", + "size": 138698 + } + } + }, + "org.lwjgl:lwjgl-stb:3.2.2": { + "name": "org.lwjgl:lwjgl-stb:3.2.3", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-stb/3.2.3/lwjgl-stb-3.2.3.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.2.3/lwjgl-stb-3.2.3.jar", + "sha1": "40eccaa4fa86fc815f2e17946a392fb5fdcc286a", + "size": 104049 + } + } + }, + "org.lwjgl:lwjgl-stb:3.2.2:natives": { + "name": "org.lwjgl:lwjgl-stb:3.2.3:natives-linux-arm32", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-stb/3.2.3/lwjgl-stb-3.2.3-natives-linux-arm32.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.2.3/lwjgl-stb-3.2.3-natives-linux-arm32.jar", + "sha1": "f28dc1e73025cf699a2cdd4f6db7964ed357ce50", + "size": 146890 + } + } + }, + "org.lwjgl:lwjgl-tinyfd:3.2.2": { + "name": "org.lwjgl:lwjgl-tinyfd:3.2.3", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-tinyfd/3.2.3/lwjgl-tinyfd-3.2.3.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.2.3/lwjgl-tinyfd-3.2.3.jar", + "sha1": "d5edf89c7b6ca1ea20865a6ba0a09bfc5efb29c1", + "size": 6392 + } + } + }, + "org.lwjgl:lwjgl-tinyfd:3.2.2:natives": { + "name": "org.lwjgl:lwjgl-tinyfd:3.2.3:natives-linux-arm32", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-tinyfd/3.2.3/lwjgl-tinyfd-3.2.3-natives-linux-arm32.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.2.3/lwjgl-tinyfd-3.2.3-natives-linux-arm32.jar", + "sha1": "b3ad16cb0e4c1307bf3d1ecb29559e18a4f8633c", + "size": 38752 + } + } + }, + "org.lwjgl:lwjgl:3.3.1:natives-linux": { + "name": "org.lwjgl:lwjgl:3.3.1:natives-linux-arm32", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1-natives-linux-arm32.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1-natives-linux-arm32.jar", + "sha1": "41a3c1dd15d6b964eb8196dde69720a3e3e5e969", + "size": 82374 + } + } + }, + "org.lwjgl:lwjgl-jemalloc:3.3.1:natives-linux": { + "name": "org.lwjgl:lwjgl-jemalloc:3.3.1:natives-linux-arm32", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1-natives-linux-arm32.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1-natives-linux-arm32.jar", + "sha1": "a96a6d6cb3876d7813fcee53c3c24f246aeba3b3", + "size": 136157 + } + } + }, + "org.lwjgl:lwjgl-openal:3.3.1:natives-linux": { + "name": "org.lwjgl:lwjgl-openal:3.3.1:natives-linux-arm32", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1-natives-linux-arm32.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1-natives-linux-arm32.jar", + "sha1": "ffbe35d7fa5ec9b7eca136a7c71f24d4025a510b", + "size": 400129 + } + } + }, + "org.lwjgl:lwjgl-opengl:3.3.1:natives-linux": { + "name": "org.lwjgl:lwjgl-opengl:3.3.1:natives-linux-arm32", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1-natives-linux-arm32.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1-natives-linux-arm32.jar", + "sha1": "e3550fa91097fd56e361b4370fa822220fef3595", + "size": 58474 + } + } + }, + "org.lwjgl:lwjgl-glfw:3.3.1:natives-linux": { + "name": "org.lwjgl:lwjgl-glfw:3.3.1:natives-linux-arm32", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1-natives-linux-arm32.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1-natives-linux-arm32.jar", + "sha1": "816d935933f2dd743074c4e717cc25b55720f294", + "size": 104027 + } + } + }, + "org.lwjgl:lwjgl-stb:3.3.1:natives-linux": { + "name": "org.lwjgl:lwjgl-stb:3.3.1:natives-linux-arm32", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1-natives-linux-arm32.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1-natives-linux-arm32.jar", + "sha1": "b08226bab162c06ae69337d8a1b0ee0a3fdf0b90", + "size": 153889 + } + } + }, + "org.lwjgl:lwjgl-tinyfd:3.3.1:natives-linux": { + "name": "org.lwjgl:lwjgl-tinyfd:3.3.1:natives-linux-arm32", "downloads": { "artifact": { "path": "org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1-natives-linux-arm32.jar", @@ -1319,6 +1627,104 @@ } }, "org.lwjgl:lwjgl-tinyfd:3.1.6:natives": null, + "org.lwjgl:lwjgl:3.2.1": { + "name": "org.lwjgl:lwjgl:3.3.1", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar", + "sha1": "ae58664f88e18a9bb2c77b063833ca7aaec484cb", + "size": 724243 + } + } + }, + "org.lwjgl:lwjgl:3.2.1:natives": { + "name": "org.glavo.hmcl:lwjgl3-natives:3.3.1-rc2", + "downloads": { + "classifiers": { + "linux-mips64el": { + "path": "org/glavo/hmcl/lwjgl3-natives/3.3.1-rc2/lwjgl3-natives-3.3.1-rc2-linux-mips64el.jar", + "url": "https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl3-natives/3.3.1-rc2-linux-mips64el/lwjgl3-natives-3.3.1-rc2-linux-mips64el.jar", + "sha1": "babec61846d8feb7a60cce1c9909281b1a3e0640", + "size": 2464146 + } + } + }, + "extract": { + "exclude": [ + "META-INF/" + ] + }, + "natives": { + "linux": "linux-mips64el" + } + }, + "org.lwjgl:lwjgl-jemalloc:3.2.1": { + "name": "org.lwjgl:lwjgl-jemalloc:3.3.1", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar", + "sha1": "a817bcf213db49f710603677457567c37d53e103", + "size": 36601 + } + } + }, + "org.lwjgl:lwjgl-openal:3.2.1": { + "name": "org.lwjgl:lwjgl-openal:3.3.1", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar", + "sha1": "2623a6b8ae1dfcd880738656a9f0243d2e6840bd", + "size": 88237 + } + } + }, + "org.lwjgl:lwjgl-opengl:3.2.1": { + "name": "org.lwjgl:lwjgl-opengl:3.3.1", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar", + "sha1": "831a5533a21a5f4f81bbc51bb13e9899319b5411", + "size": 921563 + } + } + }, + "org.lwjgl:lwjgl-glfw:3.2.1": { + "name": "org.lwjgl:lwjgl-glfw:3.3.1", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar", + "sha1": "cbac1b8d30cb4795149c1ef540f912671a8616d0", + "size": 128801 + } + } + }, + "org.lwjgl:lwjgl-stb:3.2.1": { + "name": "org.lwjgl:lwjgl-stb:3.3.1", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar", + "sha1": "b119297cf8ed01f247abe8685857f8e7fcf5980f", + "size": 112380 + } + } + }, + "org.lwjgl:lwjgl-tinyfd:3.2.1": { + "name": "org.lwjgl:lwjgl-tinyfd:3.3.1", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar", + "sha1": "0ff1914111ef2e3e0110ef2dabc8d8cdaad82347", + "size": 6767 + } + } + }, "org.lwjgl:lwjgl:3.2.2": { "name": "org.lwjgl:lwjgl:3.3.1", "downloads": { @@ -1624,7 +2030,111 @@ } }, "org.lwjgl:lwjgl-tinyfd:3.1.6:natives": null, - "org.lwjgl:lwjgl:3.2.2": { + "org.lwjgl:lwjgl:3.2.1": { + "name": "org.lwjgl:lwjgl:3.3.1", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar", + "sha1": "ae58664f88e18a9bb2c77b063833ca7aaec484cb", + "size": 724243 + } + } + }, + "org.lwjgl:lwjgl:3.2.1:natives": { + "name": "org.glavo.hmcl:lwjgl3-natives:3.3.1-rc1", + "downloads": { + "classifiers": { + "linux-loongarch64": { + "path": "org/glavo/hmcl/lwjgl3-natives/3.3.1-rc1/lwjgl3-natives-3.3.1-rc1-linux-loongarch64.jar", + "url": "https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl3-natives/3.3.1-rc1-linux-loongarch64/lwjgl3-natives-3.3.1-rc1-linux-loongarch64.jar", + "sha1": "2375ec8e8094a765ef61f3c4f2f832b1b8dfed4b", + "size": 2651163 + } + } + }, + "extract": { + "exclude": [ + "META-INF/" + ] + }, + "natives": { + "linux": "linux-loongarch64" + } + }, + "org.lwjgl:lwjgl-jemalloc:3.2.1": { + "name": "org.lwjgl:lwjgl-jemalloc:3.3.1", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar", + "sha1": "a817bcf213db49f710603677457567c37d53e103", + "size": 36601 + } + } + }, + "org.lwjgl:lwjgl-jemalloc:3.2.1:natives": null, + "org.lwjgl:lwjgl-openal:3.2.1": { + "name": "org.lwjgl:lwjgl-openal:3.3.1", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar", + "sha1": "2623a6b8ae1dfcd880738656a9f0243d2e6840bd", + "size": 88237 + } + } + }, + "org.lwjgl:lwjgl-openal:3.2.1:natives": null, + "org.lwjgl:lwjgl-opengl:3.2.1": { + "name": "org.lwjgl:lwjgl-opengl:3.3.1", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar", + "sha1": "831a5533a21a5f4f81bbc51bb13e9899319b5411", + "size": 921563 + } + } + }, + "org.lwjgl:lwjgl-opengl:3.2.1:natives": null, + "org.lwjgl:lwjgl-glfw:3.2.1": { + "name": "org.lwjgl:lwjgl-glfw:3.3.1", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar", + "sha1": "cbac1b8d30cb4795149c1ef540f912671a8616d0", + "size": 128801 + } + } + }, + "org.lwjgl:lwjgl-glfw:3.2.1:natives": null, + "org.lwjgl:lwjgl-stb:3.2.1": { + "name": "org.lwjgl:lwjgl-stb:3.3.1", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar", + "sha1": "b119297cf8ed01f247abe8685857f8e7fcf5980f", + "size": 112380 + } + } + }, + "org.lwjgl:lwjgl-stb:3.2.1:natives": null, + "org.lwjgl:lwjgl-tinyfd:3.2.1": { + "name": "org.lwjgl:lwjgl-tinyfd:3.3.1", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar", + "sha1": "0ff1914111ef2e3e0110ef2dabc8d8cdaad82347", + "size": 6767 + } + } + }, + "org.lwjgl:lwjgl-tinyfd:3.2.1:natives": null, + "org.lwjgl:lwjgl:3.2.2": { "name": "org.lwjgl:lwjgl:3.3.1", "downloads": { "artifact": { @@ -1755,6 +2265,226 @@ "org.lwjgl:lwjgl-glfw:3.3.1:natives-linux": null, "org.lwjgl:lwjgl-stb:3.3.1:natives-linux": null, "org.lwjgl:lwjgl-tinyfd:3.3.1:natives-linux": null, + "org.lwjgl:lwjgl:3.3.2": { + "name": "org.lwjgl:lwjgl:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar", + "sha1": "b86c3e4832426e8a6b466013b7cb34b40e9ce956", + "size": 800127 + } + } + }, + "org.lwjgl:lwjgl:3.3.2:natives-linux": { + "name": "org.glavo.hmcl:lwjgl3-natives:3.3.4-rc2", + "downloads": { + "classifiers": { + "linux-loongarch64": { + "path": "org/glavo/hmcl/lwjgl3-natives/3.3.4-rc2/lwjgl3-natives-3.3.4-rc2-linux-loongarch64.jar", + "url": "https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl3-natives/3.3.4-rc2-linux-loongarch64/lwjgl3-natives-3.3.4-rc2-linux-loongarch64.jar", + "sha1": "34a7f913c6750f2bede863f59c074cc4d540fb64", + "size": 12234234 + } + } + }, + "extract": { + "exclude": [ + "META-INF/" + ] + }, + "natives": { + "linux": "linux-loongarch64" + } + }, + "org.lwjgl:lwjgl-jemalloc:3.3.2": { + "name": "org.lwjgl:lwjgl-jemalloc:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar", + "sha1": "e3f5dcb8e13f3a5ed3f740fd30a114cee2a80bc4", + "size": 46430 + } + } + }, + "org.lwjgl:lwjgl-jemalloc:3.3.2:natives-linux": null, + "org.lwjgl:lwjgl-openal:3.3.2": { + "name": "org.lwjgl:lwjgl-openal:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar", + "sha1": "9b74d3ea380c83353d42af43ad9659e04dabe84a", + "size": 113103 + } + } + }, + "org.lwjgl:lwjgl-openal:3.3.2:natives-linux": null, + "org.lwjgl:lwjgl-opengl:3.3.2": { + "name": "org.lwjgl:lwjgl-opengl:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar", + "sha1": "2852ac7d9f6fc71349f1ce28e2708ff1977f18af", + "size": 931960 + } + } + }, + "org.lwjgl:lwjgl-opengl:3.3.2:natives-linux": null, + "org.lwjgl:lwjgl-glfw:3.3.2": { + "name": "org.lwjgl:lwjgl-glfw:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar", + "sha1": "7e46ecdec85db8738053cfde1414352cd62dab74", + "size": 147044 + } + } + }, + "org.lwjgl:lwjgl-glfw:3.3.2:natives-linux": null, + "org.lwjgl:lwjgl-stb:3.3.2": { + "name": "org.lwjgl:lwjgl-stb:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar", + "sha1": "5821735d5ef23f6da8542887344e57eb181b7cac", + "size": 143112 + } + } + }, + "org.lwjgl:lwjgl-stb:3.3.2:natives-linux": null, + "org.lwjgl:lwjgl-tinyfd:3.3.2": { + "name": "org.lwjgl:lwjgl-tinyfd:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar", + "sha1": "2d73789ffd8962b38d9d599cc38b2383ce818c7a", + "size": 15928 + } + } + }, + "org.lwjgl:lwjgl-tinyfd:3.3.2:natives-linux": null, + "org.lwjgl:lwjgl:3.3.3": { + "name": "org.lwjgl:lwjgl:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar", + "sha1": "b86c3e4832426e8a6b466013b7cb34b40e9ce956", + "size": 800127 + } + } + }, + "org.lwjgl:lwjgl:3.3.3:natives-linux": { + "name": "org.glavo.hmcl:lwjgl3-natives:3.3.4-rc2", + "downloads": { + "classifiers": { + "linux-loongarch64": { + "path": "org/glavo/hmcl/lwjgl3-natives/3.3.4-rc2/lwjgl3-natives-3.3.4-rc2-linux-loongarch64.jar", + "url": "https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl3-natives/3.3.4-rc2-linux-loongarch64/lwjgl3-natives-3.3.4-rc2-linux-loongarch64.jar", + "sha1": "34a7f913c6750f2bede863f59c074cc4d540fb64", + "size": 12234234 + } + } + }, + "extract": { + "exclude": [ + "META-INF/" + ] + }, + "natives": { + "linux": "linux-loongarch64" + } + }, + "org.lwjgl:lwjgl-jemalloc:3.3.3": { + "name": "org.lwjgl:lwjgl-jemalloc:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar", + "sha1": "e3f5dcb8e13f3a5ed3f740fd30a114cee2a80bc4", + "size": 46430 + } + } + }, + "org.lwjgl:lwjgl-jemalloc:3.3.3:natives-linux": null, + "org.lwjgl:lwjgl-openal:3.3.3": { + "name": "org.lwjgl:lwjgl-openal:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar", + "sha1": "9b74d3ea380c83353d42af43ad9659e04dabe84a", + "size": 113103 + } + } + }, + "org.lwjgl:lwjgl-openal:3.3.3:natives-linux": null, + "org.lwjgl:lwjgl-opengl:3.3.3": { + "name": "org.lwjgl:lwjgl-opengl:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar", + "sha1": "2852ac7d9f6fc71349f1ce28e2708ff1977f18af", + "size": 931960 + } + } + }, + "org.lwjgl:lwjgl-opengl:3.3.3:natives-linux": null, + "org.lwjgl:lwjgl-glfw:3.3.3": { + "name": "org.lwjgl:lwjgl-glfw:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar", + "sha1": "7e46ecdec85db8738053cfde1414352cd62dab74", + "size": 147044 + } + } + }, + "org.lwjgl:lwjgl-glfw:3.3.3:natives-linux": null, + "org.lwjgl:lwjgl-stb:3.3.3": { + "name": "org.lwjgl:lwjgl-stb:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar", + "sha1": "5821735d5ef23f6da8542887344e57eb181b7cac", + "size": 143112 + } + } + }, + "org.lwjgl:lwjgl-stb:3.3.3:natives-linux": null, + "org.lwjgl:lwjgl-tinyfd:3.3.3": { + "name": "org.lwjgl:lwjgl-tinyfd:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar", + "sha1": "2d73789ffd8962b38d9d599cc38b2383ce818c7a", + "size": 15928 + } + } + }, + "org.lwjgl:lwjgl-tinyfd:3.3.3:natives-linux": null, + "org.lwjgl:lwjgl-freetype:3.3.3": { + "name": "org.lwjgl:lwjgl-freetype:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-freetype/3.3.4/lwjgl-freetype-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-freetype/3.3.4/lwjgl-freetype-3.3.4.jar", + "sha1": "23f7bf165068ef2ca80ae1b79fd905af20498600", + "size": 453489 + } + } + }, + "org.lwjgl:lwjgl-freetype:3.3.3:natives-linux": null, "net.java.dev.jna:jna:5.8.0": { "name": "net.java.dev.jna:jna:5.13.0", "downloads": { @@ -1995,7 +2725,7 @@ } }, "org.lwjgl:lwjgl-tinyfd:3.1.6:natives": null, - "org.lwjgl:lwjgl:3.2.2": { + "org.lwjgl:lwjgl:3.2.1": { "name": "org.lwjgl:lwjgl:3.3.1", "downloads": { "artifact": { @@ -2006,7 +2736,7 @@ } } }, - "org.lwjgl:lwjgl:3.2.2:natives": { + "org.lwjgl:lwjgl:3.2.1:natives": { "name": "org.glavo.hmcl:lwjgl3-natives:3.3.1-rc1", "downloads": { "classifiers": { @@ -2027,7 +2757,7 @@ "linux": "linux-loongarch64_ow" } }, - "org.lwjgl:lwjgl-jemalloc:3.2.2": { + "org.lwjgl:lwjgl-jemalloc:3.2.1": { "name": "org.lwjgl:lwjgl-jemalloc:3.3.1", "downloads": { "artifact": { @@ -2038,8 +2768,8 @@ } } }, - "org.lwjgl:lwjgl-jemalloc:3.2.2:natives": null, - "org.lwjgl:lwjgl-openal:3.2.2": { + "org.lwjgl:lwjgl-jemalloc:3.2.1:natives": null, + "org.lwjgl:lwjgl-openal:3.2.1": { "name": "org.lwjgl:lwjgl-openal:3.3.1", "downloads": { "artifact": { @@ -2050,8 +2780,8 @@ } } }, - "org.lwjgl:lwjgl-openal:3.2.2:natives": null, - "org.lwjgl:lwjgl-opengl:3.2.2": { + "org.lwjgl:lwjgl-openal:3.2.1:natives": null, + "org.lwjgl:lwjgl-opengl:3.2.1": { "name": "org.lwjgl:lwjgl-opengl:3.3.1", "downloads": { "artifact": { @@ -2062,8 +2792,8 @@ } } }, - "org.lwjgl:lwjgl-opengl:3.2.2:natives": null, - "org.lwjgl:lwjgl-glfw:3.2.2": { + "org.lwjgl:lwjgl-opengl:3.2.1:natives": null, + "org.lwjgl:lwjgl-glfw:3.2.1": { "name": "org.lwjgl:lwjgl-glfw:3.3.1", "downloads": { "artifact": { @@ -2074,8 +2804,8 @@ } } }, - "org.lwjgl:lwjgl-glfw:3.2.2:natives": null, - "org.lwjgl:lwjgl-stb:3.2.2": { + "org.lwjgl:lwjgl-glfw:3.2.1:natives": null, + "org.lwjgl:lwjgl-stb:3.2.1": { "name": "org.lwjgl:lwjgl-stb:3.3.1", "downloads": { "artifact": { @@ -2086,8 +2816,8 @@ } } }, - "org.lwjgl:lwjgl-stb:3.2.2:natives": null, - "org.lwjgl:lwjgl-tinyfd:3.2.2": { + "org.lwjgl:lwjgl-stb:3.2.1:natives": null, + "org.lwjgl:lwjgl-tinyfd:3.2.1": { "name": "org.lwjgl:lwjgl-tinyfd:3.3.1", "downloads": { "artifact": { @@ -2098,8 +2828,19 @@ } } }, - "org.lwjgl:lwjgl-tinyfd:3.2.2:natives": null, - "org.lwjgl:lwjgl:3.3.1:natives-linux": { + "org.lwjgl:lwjgl-tinyfd:3.2.1:natives": null, + "org.lwjgl:lwjgl:3.2.2": { + "name": "org.lwjgl:lwjgl:3.3.1", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar", + "sha1": "ae58664f88e18a9bb2c77b063833ca7aaec484cb", + "size": 724243 + } + } + }, + "org.lwjgl:lwjgl:3.2.2:natives": { "name": "org.glavo.hmcl:lwjgl3-natives:3.3.1-rc1", "downloads": { "classifiers": { @@ -2120,15 +2861,108 @@ "linux": "linux-loongarch64_ow" } }, - "org.lwjgl:lwjgl-jemalloc:3.3.1:natives-linux": null, - "org.lwjgl:lwjgl-openal:3.3.1:natives-linux": null, - "org.lwjgl:lwjgl-opengl:3.3.1:natives-linux": null, - "org.lwjgl:lwjgl-glfw:3.3.1:natives-linux": null, - "org.lwjgl:lwjgl-stb:3.3.1:natives-linux": null, - "org.lwjgl:lwjgl-tinyfd:3.3.1:natives-linux": null, - "net.java.dev.jna:jna:5.8.0": { - "name": "org.glavo.hmcl:jna:5.13.0-rc1-linux-loongarch64_ow", - "downloads": { + "org.lwjgl:lwjgl-jemalloc:3.2.2": { + "name": "org.lwjgl:lwjgl-jemalloc:3.3.1", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar", + "sha1": "a817bcf213db49f710603677457567c37d53e103", + "size": 36601 + } + } + }, + "org.lwjgl:lwjgl-jemalloc:3.2.2:natives": null, + "org.lwjgl:lwjgl-openal:3.2.2": { + "name": "org.lwjgl:lwjgl-openal:3.3.1", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar", + "sha1": "2623a6b8ae1dfcd880738656a9f0243d2e6840bd", + "size": 88237 + } + } + }, + "org.lwjgl:lwjgl-openal:3.2.2:natives": null, + "org.lwjgl:lwjgl-opengl:3.2.2": { + "name": "org.lwjgl:lwjgl-opengl:3.3.1", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar", + "sha1": "831a5533a21a5f4f81bbc51bb13e9899319b5411", + "size": 921563 + } + } + }, + "org.lwjgl:lwjgl-opengl:3.2.2:natives": null, + "org.lwjgl:lwjgl-glfw:3.2.2": { + "name": "org.lwjgl:lwjgl-glfw:3.3.1", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar", + "sha1": "cbac1b8d30cb4795149c1ef540f912671a8616d0", + "size": 128801 + } + } + }, + "org.lwjgl:lwjgl-glfw:3.2.2:natives": null, + "org.lwjgl:lwjgl-stb:3.2.2": { + "name": "org.lwjgl:lwjgl-stb:3.3.1", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar", + "sha1": "b119297cf8ed01f247abe8685857f8e7fcf5980f", + "size": 112380 + } + } + }, + "org.lwjgl:lwjgl-stb:3.2.2:natives": null, + "org.lwjgl:lwjgl-tinyfd:3.2.2": { + "name": "org.lwjgl:lwjgl-tinyfd:3.3.1", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar", + "sha1": "0ff1914111ef2e3e0110ef2dabc8d8cdaad82347", + "size": 6767 + } + } + }, + "org.lwjgl:lwjgl-tinyfd:3.2.2:natives": null, + "org.lwjgl:lwjgl:3.3.1:natives-linux": { + "name": "org.glavo.hmcl:lwjgl3-natives:3.3.1-rc1", + "downloads": { + "classifiers": { + "linux-loongarch64_ow": { + "path": "org/glavo/hmcl/lwjgl3-natives/3.3.1-rc1/lwjgl3-natives-3.3.1-rc1-linux-loongarch64_ow.jar", + "url": "https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl3-natives/3.3.1-rc1-linux-loongarch64_ow/lwjgl3-natives-3.3.1-rc1-linux-loongarch64_ow.jar", + "sha1": "4c7d6978dae411e5041f478d78cc329c4c75fc73", + "size": 2311861 + } + } + }, + "extract": { + "exclude": [ + "META-INF/" + ] + }, + "natives": { + "linux": "linux-loongarch64_ow" + } + }, + "org.lwjgl:lwjgl-jemalloc:3.3.1:natives-linux": null, + "org.lwjgl:lwjgl-openal:3.3.1:natives-linux": null, + "org.lwjgl:lwjgl-opengl:3.3.1:natives-linux": null, + "org.lwjgl:lwjgl-glfw:3.3.1:natives-linux": null, + "org.lwjgl:lwjgl-stb:3.3.1:natives-linux": null, + "org.lwjgl:lwjgl-tinyfd:3.3.1:natives-linux": null, + "net.java.dev.jna:jna:5.8.0": { + "name": "org.glavo.hmcl:jna:5.13.0-rc1-linux-loongarch64_ow", + "downloads": { "artifact": { "path": "org/glavo/hmcl/jna/5.13.0-rc1-linux-loongarch64_ow/jna-5.13.0-rc1-linux-loongarch64_ow.jar", "url": "https://repo1.maven.org/maven2/org/glavo/hmcl/jna/5.13.0-rc1-linux-loongarch64_ow/jna-5.13.0-rc1-linux-loongarch64_ow.jar", @@ -2200,391 +3034,948 @@ }, "linux-riscv64": { "org.lwjgl:lwjgl:3.1.6": { - "name": "org.lwjgl:lwjgl:3.3.1", + "name": "org.lwjgl:lwjgl:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar", - "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar", - "sha1": "ae58664f88e18a9bb2c77b063833ca7aaec484cb", - "size": 724243 + "path": "org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar", + "sha1": "b86c3e4832426e8a6b466013b7cb34b40e9ce956", + "size": 800127 } } }, "org.lwjgl:lwjgl:3.1.6:natives": { - "name": "org.glavo.hmcl:lwjgl3-natives:3.3.1-rc1", + "name": "org.lwjgl:lwjgl:3.3.4:natives-linux-riscv64", "downloads": { - "classifiers": { - "linux-riscv64": { - "path": "org/glavo/hmcl/lwjgl3-natives/3.3.1-rc1/lwjgl3-natives-3.3.1-rc1-linux-riscv64.jar", - "url": "https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl3-natives/3.3.1-rc1-linux-riscv64/lwjgl3-natives-3.3.1-rc1-linux-riscv64.jar", - "sha1": "8544853aa77b10692548b2a728cd890afd740ba5", - "size": 11905844 - } + "artifact": { + "path": "org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-linux-riscv64.jar", + "sha1": "3ce36a5c5e6feb61bad2ea1426352482e04c6db1", + "size": 80164 + } + } + }, + "org.lwjgl:lwjgl-jemalloc:3.1.6": { + "name": "org.lwjgl:lwjgl-jemalloc:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar", + "sha1": "e3f5dcb8e13f3a5ed3f740fd30a114cee2a80bc4", + "size": 46430 + } + } + }, + "org.lwjgl:lwjgl-jemalloc:3.1.6:natives": { + "name": "org.lwjgl:lwjgl-jemalloc:3.3.4:natives-linux-riscv64", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-linux-riscv64.jar", + "sha1": "6c5d38aad1dbd1c4945bcbe86640e616ceb9588f", + "size": 197209 + } + } + }, + "org.lwjgl:lwjgl-openal:3.1.6": { + "name": "org.lwjgl:lwjgl-openal:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar", + "sha1": "9b74d3ea380c83353d42af43ad9659e04dabe84a", + "size": 113103 + } + } + }, + "org.lwjgl:lwjgl-openal:3.1.6:natives": { + "name": "org.lwjgl:lwjgl-openal:3.3.4:natives-linux-riscv64", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-linux-riscv64.jar", + "sha1": "74c5ddf47ad6bee41348bbb0735008694b5315af", + "size": 516661 + } + } + }, + "org.lwjgl:lwjgl-opengl:3.1.6": { + "name": "org.lwjgl:lwjgl-opengl:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar", + "sha1": "2852ac7d9f6fc71349f1ce28e2708ff1977f18af", + "size": 931960 + } + } + }, + "org.lwjgl:lwjgl-opengl:3.1.6:natives": { + "name": "org.lwjgl:lwjgl-opengl:3.3.4:natives-linux-riscv64", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-linux-riscv64.jar", + "sha1": "6ea07b47dd91af8f3ee4a24ddb3cbf086d157a4e", + "size": 57864 + } + } + }, + "org.lwjgl:lwjgl-glfw:3.1.6": { + "name": "org.lwjgl:lwjgl-glfw:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar", + "sha1": "7e46ecdec85db8738053cfde1414352cd62dab74", + "size": 147044 + } + } + }, + "org.lwjgl:lwjgl-glfw:3.1.6:natives": { + "name": "org.lwjgl:lwjgl-glfw:3.3.4:natives-linux-riscv64", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-linux-riscv64.jar", + "sha1": "9582481036b6fb762c3f791e2ed26128152a1cec", + "size": 117084 + } + } + }, + "org.lwjgl:lwjgl-stb:3.1.6": { + "name": "org.lwjgl:lwjgl-stb:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar", + "sha1": "5821735d5ef23f6da8542887344e57eb181b7cac", + "size": 143112 + } + } + }, + "org.lwjgl:lwjgl-stb:3.1.6:natives": { + "name": "org.lwjgl:lwjgl-stb:3.3.4:natives-linux-riscv64", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-linux-riscv64.jar", + "sha1": "502804ee61eb7adb30bbb5e4459899f9a0cb23cf", + "size": 215885 + } + } + }, + "org.lwjgl:lwjgl-tinyfd:3.1.6": { + "name": "org.lwjgl:lwjgl-tinyfd:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar", + "sha1": "2d73789ffd8962b38d9d599cc38b2383ce818c7a", + "size": 15928 + } + } + }, + "org.lwjgl:lwjgl-tinyfd:3.1.6:natives": { + "name": "org.lwjgl:lwjgl-tinyfd:3.3.4:natives-linux-riscv64", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-linux-riscv64.jar", + "sha1": "8bec171d3fa4ec3cfc20b4ddd675cb08935bd5f8", + "size": 46550 + } + } + }, + "org.lwjgl:lwjgl:3.2.1": { + "name": "org.lwjgl:lwjgl:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar", + "sha1": "b86c3e4832426e8a6b466013b7cb34b40e9ce956", + "size": 800127 + } + } + }, + "org.lwjgl:lwjgl:3.2.1:natives": { + "name": "org.lwjgl:lwjgl:3.3.4:natives-linux-riscv64", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-linux-riscv64.jar", + "sha1": "3ce36a5c5e6feb61bad2ea1426352482e04c6db1", + "size": 80164 + } + } + }, + "org.lwjgl:lwjgl-jemalloc:3.2.1": { + "name": "org.lwjgl:lwjgl-jemalloc:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar", + "sha1": "e3f5dcb8e13f3a5ed3f740fd30a114cee2a80bc4", + "size": 46430 + } + } + }, + "org.lwjgl:lwjgl-jemalloc:3.2.1:natives": { + "name": "org.lwjgl:lwjgl-jemalloc:3.3.4:natives-linux-riscv64", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-linux-riscv64.jar", + "sha1": "6c5d38aad1dbd1c4945bcbe86640e616ceb9588f", + "size": 197209 + } + } + }, + "org.lwjgl:lwjgl-openal:3.2.1": { + "name": "org.lwjgl:lwjgl-openal:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar", + "sha1": "9b74d3ea380c83353d42af43ad9659e04dabe84a", + "size": 113103 + } + } + }, + "org.lwjgl:lwjgl-openal:3.2.1:natives": { + "name": "org.lwjgl:lwjgl-openal:3.3.4:natives-linux-riscv64", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-linux-riscv64.jar", + "sha1": "74c5ddf47ad6bee41348bbb0735008694b5315af", + "size": 516661 + } + } + }, + "org.lwjgl:lwjgl-opengl:3.2.1": { + "name": "org.lwjgl:lwjgl-opengl:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar", + "sha1": "2852ac7d9f6fc71349f1ce28e2708ff1977f18af", + "size": 931960 + } + } + }, + "org.lwjgl:lwjgl-opengl:3.2.1:natives": { + "name": "org.lwjgl:lwjgl-opengl:3.3.4:natives-linux-riscv64", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-linux-riscv64.jar", + "sha1": "6ea07b47dd91af8f3ee4a24ddb3cbf086d157a4e", + "size": 57864 + } + } + }, + "org.lwjgl:lwjgl-glfw:3.2.1": { + "name": "org.lwjgl:lwjgl-glfw:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar", + "sha1": "7e46ecdec85db8738053cfde1414352cd62dab74", + "size": 147044 + } + } + }, + "org.lwjgl:lwjgl-glfw:3.2.1:natives": { + "name": "org.lwjgl:lwjgl-glfw:3.3.4:natives-linux-riscv64", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-linux-riscv64.jar", + "sha1": "9582481036b6fb762c3f791e2ed26128152a1cec", + "size": 117084 + } + } + }, + "org.lwjgl:lwjgl-stb:3.2.1": { + "name": "org.lwjgl:lwjgl-stb:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar", + "sha1": "5821735d5ef23f6da8542887344e57eb181b7cac", + "size": 143112 + } + } + }, + "org.lwjgl:lwjgl-stb:3.2.1:natives": { + "name": "org.lwjgl:lwjgl-stb:3.3.4:natives-linux-riscv64", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-linux-riscv64.jar", + "sha1": "502804ee61eb7adb30bbb5e4459899f9a0cb23cf", + "size": 215885 + } + } + }, + "org.lwjgl:lwjgl-tinyfd:3.2.1": { + "name": "org.lwjgl:lwjgl-tinyfd:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar", + "sha1": "2d73789ffd8962b38d9d599cc38b2383ce818c7a", + "size": 15928 + } + } + }, + "org.lwjgl:lwjgl-tinyfd:3.2.1:natives": { + "name": "org.lwjgl:lwjgl-tinyfd:3.3.4:natives-linux-riscv64", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-linux-riscv64.jar", + "sha1": "8bec171d3fa4ec3cfc20b4ddd675cb08935bd5f8", + "size": 46550 + } + } + }, + "org.lwjgl:lwjgl:3.2.2": { + "name": "org.lwjgl:lwjgl:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar", + "sha1": "b86c3e4832426e8a6b466013b7cb34b40e9ce956", + "size": 800127 + } + } + }, + "org.lwjgl:lwjgl:3.2.2:natives": { + "name": "org.lwjgl:lwjgl:3.3.4:natives-linux-riscv64", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-linux-riscv64.jar", + "sha1": "3ce36a5c5e6feb61bad2ea1426352482e04c6db1", + "size": 80164 + } + } + }, + "org.lwjgl:lwjgl-jemalloc:3.2.2": { + "name": "org.lwjgl:lwjgl-jemalloc:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar", + "sha1": "e3f5dcb8e13f3a5ed3f740fd30a114cee2a80bc4", + "size": 46430 + } + } + }, + "org.lwjgl:lwjgl-jemalloc:3.2.2:natives": { + "name": "org.lwjgl:lwjgl-jemalloc:3.3.4:natives-linux-riscv64", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-linux-riscv64.jar", + "sha1": "6c5d38aad1dbd1c4945bcbe86640e616ceb9588f", + "size": 197209 + } + } + }, + "org.lwjgl:lwjgl-openal:3.2.2": { + "name": "org.lwjgl:lwjgl-openal:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar", + "sha1": "9b74d3ea380c83353d42af43ad9659e04dabe84a", + "size": 113103 + } + } + }, + "org.lwjgl:lwjgl-openal:3.2.2:natives": { + "name": "org.lwjgl:lwjgl-openal:3.3.4:natives-linux-riscv64", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-linux-riscv64.jar", + "sha1": "74c5ddf47ad6bee41348bbb0735008694b5315af", + "size": 516661 + } + } + }, + "org.lwjgl:lwjgl-opengl:3.2.2": { + "name": "org.lwjgl:lwjgl-opengl:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar", + "sha1": "2852ac7d9f6fc71349f1ce28e2708ff1977f18af", + "size": 931960 + } + } + }, + "org.lwjgl:lwjgl-opengl:3.2.2:natives": { + "name": "org.lwjgl:lwjgl-opengl:3.3.4:natives-linux-riscv64", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-linux-riscv64.jar", + "sha1": "6ea07b47dd91af8f3ee4a24ddb3cbf086d157a4e", + "size": 57864 + } + } + }, + "org.lwjgl:lwjgl-glfw:3.2.2": { + "name": "org.lwjgl:lwjgl-glfw:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar", + "sha1": "7e46ecdec85db8738053cfde1414352cd62dab74", + "size": 147044 + } + } + }, + "org.lwjgl:lwjgl-glfw:3.2.2:natives": { + "name": "org.lwjgl:lwjgl-glfw:3.3.4:natives-linux-riscv64", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-linux-riscv64.jar", + "sha1": "9582481036b6fb762c3f791e2ed26128152a1cec", + "size": 117084 + } + } + }, + "org.lwjgl:lwjgl-stb:3.2.2": { + "name": "org.lwjgl:lwjgl-stb:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar", + "sha1": "5821735d5ef23f6da8542887344e57eb181b7cac", + "size": 143112 + } + } + }, + "org.lwjgl:lwjgl-stb:3.2.2:natives": { + "name": "org.lwjgl:lwjgl-stb:3.3.4:natives-linux-riscv64", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-linux-riscv64.jar", + "sha1": "502804ee61eb7adb30bbb5e4459899f9a0cb23cf", + "size": 215885 + } + } + }, + "org.lwjgl:lwjgl-tinyfd:3.2.2": { + "name": "org.lwjgl:lwjgl-tinyfd:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar", + "sha1": "2d73789ffd8962b38d9d599cc38b2383ce818c7a", + "size": 15928 + } + } + }, + "org.lwjgl:lwjgl-tinyfd:3.2.2:natives": { + "name": "org.lwjgl:lwjgl-tinyfd:3.3.4:natives-linux-riscv64", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-linux-riscv64.jar", + "sha1": "8bec171d3fa4ec3cfc20b4ddd675cb08935bd5f8", + "size": 46550 + } + } + }, + "org.lwjgl:lwjgl:3.3.1": { + "name": "org.lwjgl:lwjgl:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar", + "sha1": "b86c3e4832426e8a6b466013b7cb34b40e9ce956", + "size": 800127 + } + } + }, + "org.lwjgl:lwjgl:3.3.1:natives-linux": { + "name": "org.lwjgl:lwjgl:3.3.4:natives-linux-riscv64", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-linux-riscv64.jar", + "sha1": "3ce36a5c5e6feb61bad2ea1426352482e04c6db1", + "size": 80164 + } + } + }, + "org.lwjgl:lwjgl-jemalloc:3.3.1": { + "name": "org.lwjgl:lwjgl-jemalloc:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar", + "sha1": "e3f5dcb8e13f3a5ed3f740fd30a114cee2a80bc4", + "size": 46430 + } + } + }, + "org.lwjgl:lwjgl-jemalloc:3.3.1:natives-linux": { + "name": "org.lwjgl:lwjgl-jemalloc:3.3.4:natives-linux-riscv64", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-linux-riscv64.jar", + "sha1": "6c5d38aad1dbd1c4945bcbe86640e616ceb9588f", + "size": 197209 + } + } + }, + "org.lwjgl:lwjgl-openal:3.3.1": { + "name": "org.lwjgl:lwjgl-openal:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar", + "sha1": "9b74d3ea380c83353d42af43ad9659e04dabe84a", + "size": 113103 + } + } + }, + "org.lwjgl:lwjgl-openal:3.3.1:natives-linux": { + "name": "org.lwjgl:lwjgl-openal:3.3.4:natives-linux-riscv64", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-linux-riscv64.jar", + "sha1": "74c5ddf47ad6bee41348bbb0735008694b5315af", + "size": 516661 + } + } + }, + "org.lwjgl:lwjgl-opengl:3.3.1": { + "name": "org.lwjgl:lwjgl-opengl:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar", + "sha1": "2852ac7d9f6fc71349f1ce28e2708ff1977f18af", + "size": 931960 + } + } + }, + "org.lwjgl:lwjgl-opengl:3.3.1:natives-linux": { + "name": "org.lwjgl:lwjgl-opengl:3.3.4:natives-linux-riscv64", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-linux-riscv64.jar", + "sha1": "6ea07b47dd91af8f3ee4a24ddb3cbf086d157a4e", + "size": 57864 + } + } + }, + "org.lwjgl:lwjgl-glfw:3.3.1": { + "name": "org.lwjgl:lwjgl-glfw:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar", + "sha1": "7e46ecdec85db8738053cfde1414352cd62dab74", + "size": 147044 + } + } + }, + "org.lwjgl:lwjgl-glfw:3.3.1:natives-linux": { + "name": "org.lwjgl:lwjgl-glfw:3.3.4:natives-linux-riscv64", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-linux-riscv64.jar", + "sha1": "9582481036b6fb762c3f791e2ed26128152a1cec", + "size": 117084 + } + } + }, + "org.lwjgl:lwjgl-stb:3.3.1": { + "name": "org.lwjgl:lwjgl-stb:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar", + "sha1": "5821735d5ef23f6da8542887344e57eb181b7cac", + "size": 143112 + } + } + }, + "org.lwjgl:lwjgl-stb:3.3.1:natives-linux": { + "name": "org.lwjgl:lwjgl-stb:3.3.4:natives-linux-riscv64", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-linux-riscv64.jar", + "sha1": "502804ee61eb7adb30bbb5e4459899f9a0cb23cf", + "size": 215885 + } + } + }, + "org.lwjgl:lwjgl-tinyfd:3.3.1": { + "name": "org.lwjgl:lwjgl-tinyfd:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar", + "sha1": "2d73789ffd8962b38d9d599cc38b2383ce818c7a", + "size": 15928 + } + } + }, + "org.lwjgl:lwjgl-tinyfd:3.3.1:natives-linux": { + "name": "org.lwjgl:lwjgl-tinyfd:3.3.4:natives-linux-riscv64", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-linux-riscv64.jar", + "sha1": "8bec171d3fa4ec3cfc20b4ddd675cb08935bd5f8", + "size": 46550 + } + } + }, + "org.lwjgl:lwjgl:3.3.2": { + "name": "org.lwjgl:lwjgl:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar", + "sha1": "b86c3e4832426e8a6b466013b7cb34b40e9ce956", + "size": 800127 } - }, - "extract": { - "exclude": [ - "META-INF/" - ] - }, - "natives": { - "linux": "linux-riscv64" } }, - "org.lwjgl:lwjgl-jemalloc:3.1.6": { - "name": "org.lwjgl:lwjgl-jemalloc:3.3.1", + "org.lwjgl:lwjgl:3.3.2:natives-linux": { + "name": "org.lwjgl:lwjgl:3.3.4:natives-linux-riscv64", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar", - "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar", - "sha1": "a817bcf213db49f710603677457567c37d53e103", - "size": 36601 + "path": "org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-linux-riscv64.jar", + "sha1": "3ce36a5c5e6feb61bad2ea1426352482e04c6db1", + "size": 80164 } } }, - "org.lwjgl:lwjgl-jemalloc:3.1.6:natives": null, - "org.lwjgl:lwjgl-openal:3.1.6": { - "name": "org.lwjgl:lwjgl-openal:3.3.1", + "org.lwjgl:lwjgl-jemalloc:3.3.2": { + "name": "org.lwjgl:lwjgl-jemalloc:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar", - "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar", - "sha1": "2623a6b8ae1dfcd880738656a9f0243d2e6840bd", - "size": 88237 + "path": "org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar", + "sha1": "e3f5dcb8e13f3a5ed3f740fd30a114cee2a80bc4", + "size": 46430 } } }, - "org.lwjgl:lwjgl-openal:3.1.6:natives": null, - "org.lwjgl:lwjgl-opengl:3.1.6": { - "name": "org.lwjgl:lwjgl-opengl:3.3.1", + "org.lwjgl:lwjgl-jemalloc:3.3.2:natives-linux": { + "name": "org.lwjgl:lwjgl-jemalloc:3.3.4:natives-linux-riscv64", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar", - "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar", - "sha1": "831a5533a21a5f4f81bbc51bb13e9899319b5411", - "size": 921563 + "path": "org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-linux-riscv64.jar", + "sha1": "6c5d38aad1dbd1c4945bcbe86640e616ceb9588f", + "size": 197209 } } }, - "org.lwjgl:lwjgl-opengl:3.1.6:natives": null, - "org.lwjgl:lwjgl-glfw:3.1.6": { - "name": "org.lwjgl:lwjgl-glfw:3.3.1", + "org.lwjgl:lwjgl-openal:3.3.2": { + "name": "org.lwjgl:lwjgl-openal:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar", - "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar", - "sha1": "cbac1b8d30cb4795149c1ef540f912671a8616d0", - "size": 128801 + "path": "org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar", + "sha1": "9b74d3ea380c83353d42af43ad9659e04dabe84a", + "size": 113103 } } }, - "org.lwjgl:lwjgl-glfw:3.1.6:natives": null, - "org.lwjgl:lwjgl-stb:3.1.6": { - "name": "org.lwjgl:lwjgl-stb:3.3.1", + "org.lwjgl:lwjgl-openal:3.3.2:natives-linux": { + "name": "org.lwjgl:lwjgl-openal:3.3.4:natives-linux-riscv64", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar", - "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar", - "sha1": "b119297cf8ed01f247abe8685857f8e7fcf5980f", - "size": 112380 + "path": "org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-linux-riscv64.jar", + "sha1": "74c5ddf47ad6bee41348bbb0735008694b5315af", + "size": 516661 } } }, - "org.lwjgl:lwjgl-stb:3.1.6:natives": null, - "org.lwjgl:lwjgl-tinyfd:3.1.6": { - "name": "org.lwjgl:lwjgl-tinyfd:3.3.1", + "org.lwjgl:lwjgl-opengl:3.3.2": { + "name": "org.lwjgl:lwjgl-opengl:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar", - "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar", - "sha1": "0ff1914111ef2e3e0110ef2dabc8d8cdaad82347", - "size": 6767 + "path": "org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar", + "sha1": "2852ac7d9f6fc71349f1ce28e2708ff1977f18af", + "size": 931960 } } }, - "org.lwjgl:lwjgl-tinyfd:3.1.6:natives": null, - "org.lwjgl:lwjgl:3.2.2": { - "name": "org.lwjgl:lwjgl:3.3.1", + "org.lwjgl:lwjgl-opengl:3.3.2:natives-linux": { + "name": "org.lwjgl:lwjgl-opengl:3.3.4:natives-linux-riscv64", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar", - "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1.jar", - "sha1": "ae58664f88e18a9bb2c77b063833ca7aaec484cb", - "size": 724243 + "path": "org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-linux-riscv64.jar", + "sha1": "6ea07b47dd91af8f3ee4a24ddb3cbf086d157a4e", + "size": 57864 } } }, - "org.lwjgl:lwjgl:3.2.2:natives": { - "name": "org.glavo.hmcl:lwjgl3-natives:3.3.1-rc1", + "org.lwjgl:lwjgl-glfw:3.3.2": { + "name": "org.lwjgl:lwjgl-glfw:3.3.4", "downloads": { - "classifiers": { - "linux-riscv64": { - "path": "org/glavo/hmcl/lwjgl3-natives/3.3.1-rc1/lwjgl3-natives-3.3.1-rc1-linux-riscv64.jar", - "url": "https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl3-natives/3.3.1-rc1-linux-riscv64/lwjgl3-natives-3.3.1-rc1-linux-riscv64.jar", - "sha1": "8544853aa77b10692548b2a728cd890afd740ba5", - "size": 11905844 - } + "artifact": { + "path": "org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar", + "sha1": "7e46ecdec85db8738053cfde1414352cd62dab74", + "size": 147044 } - }, - "extract": { - "exclude": [ - "META-INF/" - ] - }, - "natives": { - "linux": "linux-riscv64" } }, - "org.lwjgl:lwjgl-jemalloc:3.2.2": { - "name": "org.lwjgl:lwjgl-jemalloc:3.3.1", + "org.lwjgl:lwjgl-glfw:3.3.2:natives-linux": { + "name": "org.lwjgl:lwjgl-glfw:3.3.4:natives-linux-riscv64", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar", - "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.1/lwjgl-jemalloc-3.3.1.jar", - "sha1": "a817bcf213db49f710603677457567c37d53e103", - "size": 36601 + "path": "org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-linux-riscv64.jar", + "sha1": "9582481036b6fb762c3f791e2ed26128152a1cec", + "size": 117084 } } }, - "org.lwjgl:lwjgl-jemalloc:3.2.2:natives": null, - "org.lwjgl:lwjgl-openal:3.2.2": { - "name": "org.lwjgl:lwjgl-openal:3.3.1", + "org.lwjgl:lwjgl-stb:3.3.2": { + "name": "org.lwjgl:lwjgl-stb:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar", - "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.1/lwjgl-openal-3.3.1.jar", - "sha1": "2623a6b8ae1dfcd880738656a9f0243d2e6840bd", - "size": 88237 + "path": "org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar", + "sha1": "5821735d5ef23f6da8542887344e57eb181b7cac", + "size": 143112 } } }, - "org.lwjgl:lwjgl-openal:3.2.2:natives": null, - "org.lwjgl:lwjgl-opengl:3.2.2": { - "name": "org.lwjgl:lwjgl-opengl:3.3.1", + "org.lwjgl:lwjgl-stb:3.3.2:natives-linux": { + "name": "org.lwjgl:lwjgl-stb:3.3.4:natives-linux-riscv64", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar", - "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.1/lwjgl-opengl-3.3.1.jar", - "sha1": "831a5533a21a5f4f81bbc51bb13e9899319b5411", - "size": 921563 + "path": "org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-linux-riscv64.jar", + "sha1": "502804ee61eb7adb30bbb5e4459899f9a0cb23cf", + "size": 215885 } } }, - "org.lwjgl:lwjgl-opengl:3.2.2:natives": null, - "org.lwjgl:lwjgl-glfw:3.2.2": { - "name": "org.lwjgl:lwjgl-glfw:3.3.1", + "org.lwjgl:lwjgl-tinyfd:3.3.2": { + "name": "org.lwjgl:lwjgl-tinyfd:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar", - "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.1/lwjgl-glfw-3.3.1.jar", - "sha1": "cbac1b8d30cb4795149c1ef540f912671a8616d0", - "size": 128801 + "path": "org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar", + "sha1": "2d73789ffd8962b38d9d599cc38b2383ce818c7a", + "size": 15928 } } }, - "org.lwjgl:lwjgl-glfw:3.2.2:natives": null, - "org.lwjgl:lwjgl-stb:3.2.2": { - "name": "org.lwjgl:lwjgl-stb:3.3.1", + "org.lwjgl:lwjgl-tinyfd:3.3.2:natives-linux": { + "name": "org.lwjgl:lwjgl-tinyfd:3.3.4:natives-linux-riscv64", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar", - "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.1/lwjgl-stb-3.3.1.jar", - "sha1": "b119297cf8ed01f247abe8685857f8e7fcf5980f", - "size": 112380 + "path": "org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-linux-riscv64.jar", + "sha1": "8bec171d3fa4ec3cfc20b4ddd675cb08935bd5f8", + "size": 46550 } } }, - "org.lwjgl:lwjgl-stb:3.2.2:natives": null, - "org.lwjgl:lwjgl-tinyfd:3.2.2": { - "name": "org.lwjgl:lwjgl-tinyfd:3.3.1", + "org.lwjgl:lwjgl:3.3.3": { + "name": "org.lwjgl:lwjgl:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar", - "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.1/lwjgl-tinyfd-3.3.1.jar", - "sha1": "0ff1914111ef2e3e0110ef2dabc8d8cdaad82347", - "size": 6767 + "path": "org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar", + "sha1": "b86c3e4832426e8a6b466013b7cb34b40e9ce956", + "size": 800127 } } }, - "org.lwjgl:lwjgl-tinyfd:3.2.2:natives": null, - "org.lwjgl:lwjgl:3.3.1:natives-linux": { - "name": "org.glavo.hmcl:lwjgl3-natives:3.3.1-rc1", + "org.lwjgl:lwjgl:3.3.3:natives-linux": { + "name": "org.lwjgl:lwjgl:3.3.4:natives-linux-riscv64", "downloads": { - "classifiers": { - "linux-riscv64": { - "path": "org/glavo/hmcl/lwjgl3-natives/3.3.1-rc1/lwjgl3-natives-3.3.1-rc1-linux-riscv64.jar", - "url": "https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl3-natives/3.3.1-rc1-linux-riscv64/lwjgl3-natives-3.3.1-rc1-linux-riscv64.jar", - "sha1": "8544853aa77b10692548b2a728cd890afd740ba5", - "size": 11905844 - } + "artifact": { + "path": "org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-linux-riscv64.jar", + "sha1": "3ce36a5c5e6feb61bad2ea1426352482e04c6db1", + "size": 80164 } - }, - "extract": { - "exclude": [ - "META-INF/" - ] - }, - "natives": { - "linux": "linux-riscv64" } }, - "org.lwjgl:lwjgl-jemalloc:3.3.1:natives-linux": null, - "org.lwjgl:lwjgl-openal:3.3.1:natives-linux": null, - "org.lwjgl:lwjgl-opengl:3.3.1:natives-linux": null, - "org.lwjgl:lwjgl-glfw:3.3.1:natives-linux": null, - "org.lwjgl:lwjgl-stb:3.3.1:natives-linux": null, - "org.lwjgl:lwjgl-tinyfd:3.3.1:natives-linux": null, - "org.lwjgl:lwjgl:3.3.2": { - "name": "org.lwjgl:lwjgl:3.3.4-SNAPSHOT", + "org.lwjgl:lwjgl-jemalloc:3.3.3": { + "name": "org.lwjgl:lwjgl-jemalloc:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl/3.3.4-SNAPSHOT/lwjgl-3.3.4-20231218.151521-4.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl/3.3.4-SNAPSHOT/lwjgl-3.3.4-20231218.151521-4.jar", - "sha1": "7202012cf0cadb9ffad4874494920fd8bbd93413", - "size": 792204 + "path": "org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar", + "sha1": "e3f5dcb8e13f3a5ed3f740fd30a114cee2a80bc4", + "size": 46430 } } }, - "org.lwjgl:lwjgl:3.3.2:natives-linux": { - "name": "org.lwjgl:lwjgl:3.3.4-SNAPSHOT:natives-linux-riscv64", + "org.lwjgl:lwjgl-jemalloc:3.3.3:natives-linux": { + "name": "org.lwjgl:lwjgl-jemalloc:3.3.4:natives-linux-riscv64", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl/3.3.4-SNAPSHOT/lwjgl-3.3.4-20231218.151521-4-natives-linux-riscv64.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl/3.3.4-SNAPSHOT/lwjgl-3.3.4-20231218.151521-4-natives-linux-riscv64.jar", - "sha1": "642848dc964fc5332cda40225771a5143740b49c", - "size": 79691 + "path": "org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-linux-riscv64.jar", + "sha1": "6c5d38aad1dbd1c4945bcbe86640e616ceb9588f", + "size": 197209 } } }, - "org.lwjgl:lwjgl-jemalloc:3.3.2": { - "name": "org.lwjgl:lwjgl-jemalloc:3.3.4-SNAPSHOT", + "org.lwjgl:lwjgl-openal:3.3.3": { + "name": "org.lwjgl:lwjgl-openal:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-jemalloc/3.3.4-SNAPSHOT/lwjgl-jemalloc-3.3.4-20231218.151521-4.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-jemalloc/3.3.4-SNAPSHOT/lwjgl-jemalloc-3.3.4-20231218.151521-4.jar", - "sha1": "41256f2c098806304fd224613d3d01b02725470e", - "size": 46421 + "path": "org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar", + "sha1": "9b74d3ea380c83353d42af43ad9659e04dabe84a", + "size": 113103 } } }, - "org.lwjgl:lwjgl-jemalloc:3.3.2:natives-linux": { - "name": "org.lwjgl:lwjgl-jemalloc:3.3.4-SNAPSHOT:natives-linux-riscv64", + "org.lwjgl:lwjgl-openal:3.3.3:natives-linux": { + "name": "org.lwjgl:lwjgl-openal:3.3.4:natives-linux-riscv64", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-jemalloc/3.3.4-SNAPSHOT/lwjgl-jemalloc-3.3.4-20231218.151521-4-natives-linux-riscv64.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-jemalloc/3.3.4-SNAPSHOT/lwjgl-jemalloc-3.3.4-20231218.151521-4-natives-linux-riscv64.jar", - "sha1": "01129d98674994eb8204f62c26b5717d4d989536", - "size": 200633 + "path": "org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-linux-riscv64.jar", + "sha1": "74c5ddf47ad6bee41348bbb0735008694b5315af", + "size": 516661 } } }, - "org.lwjgl:lwjgl-openal:3.3.2": { - "name": "org.lwjgl:lwjgl-openal:3.3.4-SNAPSHOT", + "org.lwjgl:lwjgl-opengl:3.3.3": { + "name": "org.lwjgl:lwjgl-opengl:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-openal/3.3.4-SNAPSHOT/lwjgl-openal-3.3.4-20231218.151521-4.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-openal/3.3.4-SNAPSHOT/lwjgl-openal-3.3.4-20231218.151521-4.jar", - "sha1": "89d8868c2d688b55e3e923345e4a146c6d034229", - "size": 113094 + "path": "org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar", + "sha1": "2852ac7d9f6fc71349f1ce28e2708ff1977f18af", + "size": 931960 } } }, - "org.lwjgl:lwjgl-openal:3.3.2:natives-linux": { - "name": "org.lwjgl:lwjgl-openal:3.3.4-SNAPSHOT:natives-linux-riscv64", + "org.lwjgl:lwjgl-opengl:3.3.3:natives-linux": { + "name": "org.lwjgl:lwjgl-opengl:3.3.4:natives-linux-riscv64", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-openal/3.3.4-SNAPSHOT/lwjgl-openal-3.3.4-20231218.151521-4-natives-linux-riscv64.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-openal/3.3.4-SNAPSHOT/lwjgl-openal-3.3.4-20231218.151521-4-natives-linux-riscv64.jar", - "sha1": "bce3174f137d0ffcbbe88ddae305de11c5131137", - "size": 520217 + "path": "org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-linux-riscv64.jar", + "sha1": "6ea07b47dd91af8f3ee4a24ddb3cbf086d157a4e", + "size": 57864 } } }, - "org.lwjgl:lwjgl-opengl:3.3.2": { - "name": "org.lwjgl:lwjgl-opengl:3.3.4-SNAPSHOT", + "org.lwjgl:lwjgl-glfw:3.3.3": { + "name": "org.lwjgl:lwjgl-glfw:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-opengl/3.3.4-SNAPSHOT/lwjgl-opengl-3.3.4-20231218.151521-4.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-opengl/3.3.4-SNAPSHOT/lwjgl-opengl-3.3.4-20231218.151521-4.jar", - "sha1": "81d0a7fd96bf5eb6257fddf6b77e338d8918bf32", - "size": 931744 + "path": "org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar", + "sha1": "7e46ecdec85db8738053cfde1414352cd62dab74", + "size": 147044 } } }, - "org.lwjgl:lwjgl-opengl:3.3.2:natives-linux": { - "name": "org.lwjgl:lwjgl-opengl:3.3.4-SNAPSHOT:natives-linux-riscv64", + "org.lwjgl:lwjgl-glfw:3.3.3:natives-linux": { + "name": "org.lwjgl:lwjgl-glfw:3.3.4:natives-linux-riscv64", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-opengl/3.3.4-SNAPSHOT/lwjgl-opengl-3.3.4-20231218.151521-4-natives-linux-riscv64.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-opengl/3.3.4-SNAPSHOT/lwjgl-opengl-3.3.4-20231218.151521-4-natives-linux-riscv64.jar", - "sha1": "13dccf954b781883b0661bd55dfc423f8eece864", - "size": 57244 + "path": "org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-linux-riscv64.jar", + "sha1": "9582481036b6fb762c3f791e2ed26128152a1cec", + "size": 117084 } } }, - "org.lwjgl:lwjgl-glfw:3.3.2": { - "name": "org.lwjgl:lwjgl-glfw:3.3.4-SNAPSHOT", + "org.lwjgl:lwjgl-stb:3.3.3": { + "name": "org.lwjgl:lwjgl-stb:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-glfw/3.3.4-SNAPSHOT/lwjgl-glfw-3.3.4-20231218.151521-4.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-glfw/3.3.4-SNAPSHOT/lwjgl-glfw-3.3.4-20231218.151521-4.jar", - "sha1": "e6dba9ab8532cb6aac273adf26496ce689999943", - "size": 146829 + "path": "org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar", + "sha1": "5821735d5ef23f6da8542887344e57eb181b7cac", + "size": 143112 } } }, - "org.lwjgl:lwjgl-glfw:3.3.2:natives-linux": { - "name": "org.lwjgl:lwjgl-glfw:3.3.4-SNAPSHOT:natives-linux-riscv64", + "org.lwjgl:lwjgl-stb:3.3.3:natives-linux": { + "name": "org.lwjgl:lwjgl-stb:3.3.4:natives-linux-riscv64", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-glfw/3.3.4-SNAPSHOT/lwjgl-glfw-3.3.4-20231218.151521-4-natives-linux-riscv64.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-glfw/3.3.4-SNAPSHOT/lwjgl-glfw-3.3.4-20231218.151521-4-natives-linux-riscv64.jar", - "sha1": "865417f4aa171aa8e7a448d88877a82844896c92", - "size": 113875 + "path": "org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-linux-riscv64.jar", + "sha1": "502804ee61eb7adb30bbb5e4459899f9a0cb23cf", + "size": 215885 } } }, - "org.lwjgl:lwjgl-stb:3.3.2": { - "name": "org.lwjgl:lwjgl-stb:3.3.4-SNAPSHOT", + "org.lwjgl:lwjgl-tinyfd:3.3.3": { + "name": "org.lwjgl:lwjgl-tinyfd:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-stb/3.3.4-SNAPSHOT/lwjgl-stb-3.3.4-20231218.151521-3.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-stb/3.3.4-SNAPSHOT/lwjgl-stb-3.3.4-20231218.151521-3.jar", - "sha1": "033fe42d1b37e35afd8b6e2653abc77deadb0730", - "size": 143099 + "path": "org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar", + "sha1": "2d73789ffd8962b38d9d599cc38b2383ce818c7a", + "size": 15928 } } }, - "org.lwjgl:lwjgl-stb:3.3.2:natives-linux": { - "name": "org.lwjgl:lwjgl-stb:3.3.4-SNAPSHOT:natives-linux-riscv64", + "org.lwjgl:lwjgl-tinyfd:3.3.3:natives-linux": { + "name": "org.lwjgl:lwjgl-tinyfd:3.3.4:natives-linux-riscv64", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-stb/3.3.4-SNAPSHOT/lwjgl-stb-3.3.4-20231218.151521-3-natives-linux-riscv64.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-stb/3.3.4-SNAPSHOT/lwjgl-stb-3.3.4-20231218.151521-3-natives-linux-riscv64.jar", - "sha1": "f5b96c0fd649cb1e18816f63bed49e0dfe1d1f1a", - "size": 217005 + "path": "org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-linux-riscv64.jar", + "sha1": "8bec171d3fa4ec3cfc20b4ddd675cb08935bd5f8", + "size": 46550 } } }, - "org.lwjgl:lwjgl-tinyfd:3.3.2": { - "name": "org.lwjgl:lwjgl-tinyfd:3.3.4-SNAPSHOT", + "org.lwjgl:lwjgl-freetype:3.3.3": { + "name": "org.lwjgl:lwjgl-freetype:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-tinyfd/3.3.4-SNAPSHOT/lwjgl-tinyfd-3.3.4-20231218.151521-3.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-tinyfd/3.3.4-SNAPSHOT/lwjgl-tinyfd-3.3.4-20231218.151521-3.jar", - "sha1": "8b7c94a57f56a5b38b23c02c1cada77dccba9930", - "size": 15917 + "path": "org/lwjgl/lwjgl-freetype/3.3.4/lwjgl-freetype-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-freetype/3.3.4/lwjgl-freetype-3.3.4.jar", + "sha1": "23f7bf165068ef2ca80ae1b79fd905af20498600", + "size": 453489 } } }, - "org.lwjgl:lwjgl-tinyfd:3.3.2:natives-linux": { - "name": "org.lwjgl:lwjgl-tinyfd:3.3.4-SNAPSHOT:natives-linux-riscv64", + "org.lwjgl:lwjgl-freetype:3.3.3:natives-linux": { + "name": "org.lwjgl:lwjgl-freetype:3.3.4:natives-linux-riscv64", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-tinyfd/3.3.4-SNAPSHOT/lwjgl-tinyfd-3.3.4-20231218.151521-3-natives-linux-riscv64.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-tinyfd/3.3.4-SNAPSHOT/lwjgl-tinyfd-3.3.4-20231218.151521-3-natives-linux-riscv64.jar", - "sha1": "f042c17188c5db910b195d298c284bbfbe550c53", - "size": 46885 + "path": "org/lwjgl/lwjgl-freetype/3.3.4/lwjgl-freetype-3.3.4-natives-linux-riscv64.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-freetype/3.3.4/lwjgl-freetype-3.3.4-natives-linux-riscv64.jar", + "sha1": "2cb82939dc283686f9febf5349b66e773f515c59", + "size": 1194584 } } }, @@ -3411,618 +4802,772 @@ }, "freebsd-x86_64": { "org.lwjgl:lwjgl:3.1.6": { - "name": "org.lwjgl:lwjgl:3.3.4-SNAPSHOT", + "name": "org.lwjgl:lwjgl:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl/3.3.4-SNAPSHOT/lwjgl-3.3.4-20231218.151521-4.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl/3.3.4-SNAPSHOT/lwjgl-3.3.4-20231218.151521-4.jar", - "sha1": "7202012cf0cadb9ffad4874494920fd8bbd93413", - "size": 792204 + "path": "org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar", + "sha1": "b86c3e4832426e8a6b466013b7cb34b40e9ce956", + "size": 800127 } } }, "org.lwjgl:lwjgl:3.1.6:natives": { - "name": "org.lwjgl:lwjgl:3.3.4-SNAPSHOT:natives-freebsd", + "name": "org.lwjgl:lwjgl:3.3.4:natives-freebsd", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl/3.3.4-SNAPSHOT/lwjgl-3.3.4-20231218.151521-4-natives-freebsd.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl/3.3.4-SNAPSHOT/lwjgl-3.3.4-20231218.151521-4-natives-freebsd.jar", - "sha1": "2d38355b453edfe2daee1a567bcdb82c0485edcf", - "size": 95872 + "path": "org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-freebsd.jar", + "sha1": "610d14530e637564d97d74af7cb98a737e70b77b", + "size": 96209 } } }, "org.lwjgl:lwjgl-jemalloc:3.1.6": { - "name": "org.lwjgl:lwjgl-jemalloc:3.3.4-SNAPSHOT", + "name": "org.lwjgl:lwjgl-jemalloc:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-jemalloc/3.3.4-SNAPSHOT/lwjgl-jemalloc-3.3.4-20231218.151521-4.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-jemalloc/3.3.4-SNAPSHOT/lwjgl-jemalloc-3.3.4-20231218.151521-4.jar", - "sha1": "41256f2c098806304fd224613d3d01b02725470e", - "size": 46421 + "path": "org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar", + "sha1": "e3f5dcb8e13f3a5ed3f740fd30a114cee2a80bc4", + "size": 46430 } } }, "org.lwjgl:lwjgl-jemalloc:3.1.6:natives": { - "name": "org.lwjgl:lwjgl-jemalloc:3.3.4-SNAPSHOT:natives-freebsd", + "name": "org.lwjgl:lwjgl-jemalloc:3.3.4:natives-freebsd", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-jemalloc/3.3.4-SNAPSHOT/lwjgl-jemalloc-3.3.4-20231218.151521-4-natives-freebsd.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-jemalloc/3.3.4-SNAPSHOT/lwjgl-jemalloc-3.3.4-20231218.151521-4-natives-freebsd.jar", - "sha1": "bdba1662b621228679c7aed87945d1f28c590d5b", - "size": 155867 + "path": "org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-freebsd.jar", + "sha1": "5ee27f3bad4715067cef0630682da4bb5a1b88ac", + "size": 157297 } } }, "org.lwjgl:lwjgl-openal:3.1.6": { - "name": "org.lwjgl:lwjgl-openal:3.3.4-SNAPSHOT", + "name": "org.lwjgl:lwjgl-openal:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-openal/3.3.4-SNAPSHOT/lwjgl-openal-3.3.4-20231218.151521-4.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-openal/3.3.4-SNAPSHOT/lwjgl-openal-3.3.4-20231218.151521-4.jar", - "sha1": "89d8868c2d688b55e3e923345e4a146c6d034229", - "size": 113094 + "path": "org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar", + "sha1": "9b74d3ea380c83353d42af43ad9659e04dabe84a", + "size": 113103 } } }, "org.lwjgl:lwjgl-openal:3.1.6:natives": { - "name": "org.lwjgl:lwjgl-openal:3.3.4-SNAPSHOT:natives-freebsd", + "name": "org.lwjgl:lwjgl-openal:3.3.4:natives-freebsd", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-openal/3.3.4-SNAPSHOT/lwjgl-openal-3.3.4-20231218.151521-4-natives-freebsd.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-openal/3.3.4-SNAPSHOT/lwjgl-openal-3.3.4-20231218.151521-4-natives-freebsd.jar", - "sha1": "0fc6495e6752727b629cf03a105c9d56087d4edd", - "size": 597512 + "path": "org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-freebsd.jar", + "sha1": "3863f8268f5515c27f1364257f8a018f0c6afa79", + "size": 597486 } } }, "org.lwjgl:lwjgl-opengl:3.1.6": { - "name": "org.lwjgl:lwjgl-opengl:3.3.4-SNAPSHOT", + "name": "org.lwjgl:lwjgl-opengl:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-opengl/3.3.4-SNAPSHOT/lwjgl-opengl-3.3.4-20231218.151521-4.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-opengl/3.3.4-SNAPSHOT/lwjgl-opengl-3.3.4-20231218.151521-4.jar", - "sha1": "81d0a7fd96bf5eb6257fddf6b77e338d8918bf32", - "size": 931744 + "path": "org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar", + "sha1": "2852ac7d9f6fc71349f1ce28e2708ff1977f18af", + "size": 931960 } } }, "org.lwjgl:lwjgl-opengl:3.1.6:natives": { - "name": "org.lwjgl:lwjgl-opengl:3.3.4-SNAPSHOT:natives-freebsd", + "name": "org.lwjgl:lwjgl-opengl:3.3.4:natives-freebsd", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-opengl/3.3.4-SNAPSHOT/lwjgl-opengl-3.3.4-20231218.151521-4-natives-freebsd.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-opengl/3.3.4-SNAPSHOT/lwjgl-opengl-3.3.4-20231218.151521-4-natives-freebsd.jar", - "sha1": "d3e8ec997cef8bc66819c7e0ad7a54182f7aff2a", - "size": 81034 + "path": "org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-freebsd.jar", + "sha1": "579071d2a3714f5662522f7d3edf58e941580587", + "size": 81028 } } }, "org.lwjgl:lwjgl-glfw:3.1.6": { - "name": "org.lwjgl:lwjgl-glfw:3.3.4-SNAPSHOT", + "name": "org.lwjgl:lwjgl-glfw:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-glfw/3.3.4-SNAPSHOT/lwjgl-glfw-3.3.4-20231218.151521-4.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-glfw/3.3.4-SNAPSHOT/lwjgl-glfw-3.3.4-20231218.151521-4.jar", - "sha1": "e6dba9ab8532cb6aac273adf26496ce689999943", - "size": 146829 + "path": "org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar", + "sha1": "7e46ecdec85db8738053cfde1414352cd62dab74", + "size": 147044 } } }, "org.lwjgl:lwjgl-glfw:3.1.6:natives": { - "name": "org.lwjgl:lwjgl-glfw:3.3.4-SNAPSHOT:natives-freebsd", + "name": "org.lwjgl:lwjgl-glfw:3.3.4:natives-freebsd", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-glfw/3.3.4-SNAPSHOT/lwjgl-glfw-3.3.4-20231218.151521-4-natives-freebsd.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-glfw/3.3.4-SNAPSHOT/lwjgl-glfw-3.3.4-20231218.151521-4-natives-freebsd.jar", - "sha1": "4881a965c7679b4984c0d8a5890f07ea85c653c4", - "size": 101847 + "path": "org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-freebsd.jar", + "sha1": "f67b9b6c29451d8fea66db17aaba2f65e908c7e9", + "size": 104415 } } }, "org.lwjgl:lwjgl-stb:3.1.6": { - "name": "org.lwjgl:lwjgl-stb:3.3.4-SNAPSHOT", + "name": "org.lwjgl:lwjgl-stb:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-stb/3.3.4-SNAPSHOT/lwjgl-stb-3.3.4-20231218.151521-3.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-stb/3.3.4-SNAPSHOT/lwjgl-stb-3.3.4-20231218.151521-3.jar", - "sha1": "033fe42d1b37e35afd8b6e2653abc77deadb0730", - "size": 143099 + "path": "org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar", + "sha1": "5821735d5ef23f6da8542887344e57eb181b7cac", + "size": 143112 } } }, "org.lwjgl:lwjgl-stb:3.1.6:natives": { - "name": "org.lwjgl:lwjgl-stb:3.3.4-SNAPSHOT:natives-freebsd", + "name": "org.lwjgl:lwjgl-stb:3.3.4:natives-freebsd", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-stb/3.3.4-SNAPSHOT/lwjgl-stb-3.3.4-20231218.151521-3-natives-freebsd.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-stb/3.3.4-SNAPSHOT/lwjgl-stb-3.3.4-20231218.151521-3-natives-freebsd.jar", - "sha1": "0aad82a857ebb9a3a212dc2c386761d57e11a8f2", - "size": 225735 + "path": "org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-freebsd.jar", + "sha1": "f5551338a1e2035ff747053f0e985dc93db1235c", + "size": 226093 } } }, "org.lwjgl:lwjgl-tinyfd:3.1.6": { - "name": "org.lwjgl:lwjgl-tinyfd:3.3.4-SNAPSHOT", + "name": "org.lwjgl:lwjgl-tinyfd:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-tinyfd/3.3.4-SNAPSHOT/lwjgl-tinyfd-3.3.4-20231218.151521-3.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-tinyfd/3.3.4-SNAPSHOT/lwjgl-tinyfd-3.3.4-20231218.151521-3.jar", - "sha1": "8b7c94a57f56a5b38b23c02c1cada77dccba9930", - "size": 15917 + "path": "org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar", + "sha1": "2d73789ffd8962b38d9d599cc38b2383ce818c7a", + "size": 15928 } } }, "org.lwjgl:lwjgl-tinyfd:3.1.6:natives": { - "name": "org.lwjgl:lwjgl-tinyfd:3.3.4-SNAPSHOT:natives-freebsd", + "name": "org.lwjgl:lwjgl-tinyfd:3.3.4:natives-freebsd", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-freebsd.jar", + "sha1": "acd5e1b9b9b99ce4d21867058ee468ee45a859e5", + "size": 40104 + } + } + }, + "org.lwjgl:lwjgl:3.2.1": { + "name": "org.lwjgl:lwjgl:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar", + "sha1": "b86c3e4832426e8a6b466013b7cb34b40e9ce956", + "size": 800127 + } + } + }, + "org.lwjgl:lwjgl:3.2.1:natives": { + "name": "org.lwjgl:lwjgl:3.3.4:natives-freebsd", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-freebsd.jar", + "sha1": "610d14530e637564d97d74af7cb98a737e70b77b", + "size": 96209 + } + } + }, + "org.lwjgl:lwjgl-jemalloc:3.2.1": { + "name": "org.lwjgl:lwjgl-jemalloc:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar", + "sha1": "e3f5dcb8e13f3a5ed3f740fd30a114cee2a80bc4", + "size": 46430 + } + } + }, + "org.lwjgl:lwjgl-jemalloc:3.2.1:natives": { + "name": "org.lwjgl:lwjgl-jemalloc:3.3.4:natives-freebsd", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-freebsd.jar", + "sha1": "5ee27f3bad4715067cef0630682da4bb5a1b88ac", + "size": 157297 + } + } + }, + "org.lwjgl:lwjgl-openal:3.2.1": { + "name": "org.lwjgl:lwjgl-openal:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar", + "sha1": "9b74d3ea380c83353d42af43ad9659e04dabe84a", + "size": 113103 + } + } + }, + "org.lwjgl:lwjgl-openal:3.2.1:natives": { + "name": "org.lwjgl:lwjgl-openal:3.3.4:natives-freebsd", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-freebsd.jar", + "sha1": "3863f8268f5515c27f1364257f8a018f0c6afa79", + "size": 597486 + } + } + }, + "org.lwjgl:lwjgl-opengl:3.2.1": { + "name": "org.lwjgl:lwjgl-opengl:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar", + "sha1": "2852ac7d9f6fc71349f1ce28e2708ff1977f18af", + "size": 931960 + } + } + }, + "org.lwjgl:lwjgl-opengl:3.2.1:natives": { + "name": "org.lwjgl:lwjgl-opengl:3.3.4:natives-freebsd", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-freebsd.jar", + "sha1": "579071d2a3714f5662522f7d3edf58e941580587", + "size": 81028 + } + } + }, + "org.lwjgl:lwjgl-glfw:3.2.1": { + "name": "org.lwjgl:lwjgl-glfw:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar", + "sha1": "7e46ecdec85db8738053cfde1414352cd62dab74", + "size": 147044 + } + } + }, + "org.lwjgl:lwjgl-glfw:3.2.1:natives": { + "name": "org.lwjgl:lwjgl-glfw:3.3.4:natives-freebsd", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-freebsd.jar", + "sha1": "f67b9b6c29451d8fea66db17aaba2f65e908c7e9", + "size": 104415 + } + } + }, + "org.lwjgl:lwjgl-stb:3.2.1": { + "name": "org.lwjgl:lwjgl-stb:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-tinyfd/3.3.4-SNAPSHOT/lwjgl-tinyfd-3.3.4-20231218.151521-3-natives-freebsd.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-tinyfd/3.3.4-SNAPSHOT/lwjgl-tinyfd-3.3.4-20231218.151521-3-natives-freebsd.jar", - "sha1": "124d0e48ae1584f09e5701588ae8ad139be26003", - "size": 39077 + "path": "org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar", + "sha1": "5821735d5ef23f6da8542887344e57eb181b7cac", + "size": 143112 + } + } + }, + "org.lwjgl:lwjgl-stb:3.2.1:natives": { + "name": "org.lwjgl:lwjgl-stb:3.3.4:natives-freebsd", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-freebsd.jar", + "sha1": "f5551338a1e2035ff747053f0e985dc93db1235c", + "size": 226093 + } + } + }, + "org.lwjgl:lwjgl-tinyfd:3.2.1": { + "name": "org.lwjgl:lwjgl-tinyfd:3.3.4", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar", + "sha1": "2d73789ffd8962b38d9d599cc38b2383ce818c7a", + "size": 15928 + } + } + }, + "org.lwjgl:lwjgl-tinyfd:3.2.1:natives": { + "name": "org.lwjgl:lwjgl-tinyfd:3.3.4:natives-freebsd", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-freebsd.jar", + "sha1": "acd5e1b9b9b99ce4d21867058ee468ee45a859e5", + "size": 40104 } } }, "org.lwjgl:lwjgl:3.2.2": { - "name": "org.lwjgl:lwjgl:3.3.4-SNAPSHOT", + "name": "org.lwjgl:lwjgl:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl/3.3.4-SNAPSHOT/lwjgl-3.3.4-20231218.151521-4.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl/3.3.4-SNAPSHOT/lwjgl-3.3.4-20231218.151521-4.jar", - "sha1": "7202012cf0cadb9ffad4874494920fd8bbd93413", - "size": 792204 + "path": "org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar", + "sha1": "b86c3e4832426e8a6b466013b7cb34b40e9ce956", + "size": 800127 } } }, "org.lwjgl:lwjgl:3.2.2:natives": { - "name": "org.lwjgl:lwjgl:3.3.4-SNAPSHOT:natives-freebsd", + "name": "org.lwjgl:lwjgl:3.3.4:natives-freebsd", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl/3.3.4-SNAPSHOT/lwjgl-3.3.4-20231218.151521-4-natives-freebsd.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl/3.3.4-SNAPSHOT/lwjgl-3.3.4-20231218.151521-4-natives-freebsd.jar", - "sha1": "2d38355b453edfe2daee1a567bcdb82c0485edcf", - "size": 95872 + "path": "org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-freebsd.jar", + "sha1": "610d14530e637564d97d74af7cb98a737e70b77b", + "size": 96209 } } }, "org.lwjgl:lwjgl-jemalloc:3.2.2": { - "name": "org.lwjgl:lwjgl-jemalloc:3.3.4-SNAPSHOT", + "name": "org.lwjgl:lwjgl-jemalloc:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-jemalloc/3.3.4-SNAPSHOT/lwjgl-jemalloc-3.3.4-20231218.151521-4.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-jemalloc/3.3.4-SNAPSHOT/lwjgl-jemalloc-3.3.4-20231218.151521-4.jar", - "sha1": "41256f2c098806304fd224613d3d01b02725470e", - "size": 46421 + "path": "org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar", + "sha1": "e3f5dcb8e13f3a5ed3f740fd30a114cee2a80bc4", + "size": 46430 } } }, "org.lwjgl:lwjgl-jemalloc:3.2.2:natives": { - "name": "org.lwjgl:lwjgl-jemalloc:3.3.4-SNAPSHOT:natives-freebsd", + "name": "org.lwjgl:lwjgl-jemalloc:3.3.4:natives-freebsd", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-jemalloc/3.3.4-SNAPSHOT/lwjgl-jemalloc-3.3.4-20231218.151521-4-natives-freebsd.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-jemalloc/3.3.4-SNAPSHOT/lwjgl-jemalloc-3.3.4-20231218.151521-4-natives-freebsd.jar", - "sha1": "bdba1662b621228679c7aed87945d1f28c590d5b", - "size": 155867 + "path": "org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-freebsd.jar", + "sha1": "5ee27f3bad4715067cef0630682da4bb5a1b88ac", + "size": 157297 } } }, "org.lwjgl:lwjgl-openal:3.2.2": { - "name": "org.lwjgl:lwjgl-openal:3.3.4-SNAPSHOT", + "name": "org.lwjgl:lwjgl-openal:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-openal/3.3.4-SNAPSHOT/lwjgl-openal-3.3.4-20231218.151521-4.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-openal/3.3.4-SNAPSHOT/lwjgl-openal-3.3.4-20231218.151521-4.jar", - "sha1": "89d8868c2d688b55e3e923345e4a146c6d034229", - "size": 113094 + "path": "org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar", + "sha1": "9b74d3ea380c83353d42af43ad9659e04dabe84a", + "size": 113103 } } }, "org.lwjgl:lwjgl-openal:3.2.2:natives": { - "name": "org.lwjgl:lwjgl-openal:3.3.4-SNAPSHOT:natives-freebsd", + "name": "org.lwjgl:lwjgl-openal:3.3.4:natives-freebsd", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-openal/3.3.4-SNAPSHOT/lwjgl-openal-3.3.4-20231218.151521-4-natives-freebsd.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-openal/3.3.4-SNAPSHOT/lwjgl-openal-3.3.4-20231218.151521-4-natives-freebsd.jar", - "sha1": "0fc6495e6752727b629cf03a105c9d56087d4edd", - "size": 597512 + "path": "org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-freebsd.jar", + "sha1": "3863f8268f5515c27f1364257f8a018f0c6afa79", + "size": 597486 } } }, "org.lwjgl:lwjgl-opengl:3.2.2": { - "name": "org.lwjgl:lwjgl-opengl:3.3.4-SNAPSHOT", + "name": "org.lwjgl:lwjgl-opengl:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-opengl/3.3.4-SNAPSHOT/lwjgl-opengl-3.3.4-20231218.151521-4.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-opengl/3.3.4-SNAPSHOT/lwjgl-opengl-3.3.4-20231218.151521-4.jar", - "sha1": "81d0a7fd96bf5eb6257fddf6b77e338d8918bf32", - "size": 931744 + "path": "org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar", + "sha1": "2852ac7d9f6fc71349f1ce28e2708ff1977f18af", + "size": 931960 } } }, "org.lwjgl:lwjgl-opengl:3.2.2:natives": { - "name": "org.lwjgl:lwjgl-opengl:3.3.4-SNAPSHOT:natives-freebsd", + "name": "org.lwjgl:lwjgl-opengl:3.3.4:natives-freebsd", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-opengl/3.3.4-SNAPSHOT/lwjgl-opengl-3.3.4-20231218.151521-4-natives-freebsd.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-opengl/3.3.4-SNAPSHOT/lwjgl-opengl-3.3.4-20231218.151521-4-natives-freebsd.jar", - "sha1": "d3e8ec997cef8bc66819c7e0ad7a54182f7aff2a", - "size": 81034 + "path": "org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-freebsd.jar", + "sha1": "579071d2a3714f5662522f7d3edf58e941580587", + "size": 81028 } } }, "org.lwjgl:lwjgl-glfw:3.2.2": { - "name": "org.lwjgl:lwjgl-glfw:3.3.4-SNAPSHOT", + "name": "org.lwjgl:lwjgl-glfw:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-glfw/3.3.4-SNAPSHOT/lwjgl-glfw-3.3.4-20231218.151521-4.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-glfw/3.3.4-SNAPSHOT/lwjgl-glfw-3.3.4-20231218.151521-4.jar", - "sha1": "e6dba9ab8532cb6aac273adf26496ce689999943", - "size": 146829 + "path": "org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar", + "sha1": "7e46ecdec85db8738053cfde1414352cd62dab74", + "size": 147044 } } }, "org.lwjgl:lwjgl-glfw:3.2.2:natives": { - "name": "org.lwjgl:lwjgl-glfw:3.3.4-SNAPSHOT:natives-freebsd", + "name": "org.lwjgl:lwjgl-glfw:3.3.4:natives-freebsd", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-glfw/3.3.4-SNAPSHOT/lwjgl-glfw-3.3.4-20231218.151521-4-natives-freebsd.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-glfw/3.3.4-SNAPSHOT/lwjgl-glfw-3.3.4-20231218.151521-4-natives-freebsd.jar", - "sha1": "4881a965c7679b4984c0d8a5890f07ea85c653c4", - "size": 101847 + "path": "org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-freebsd.jar", + "sha1": "f67b9b6c29451d8fea66db17aaba2f65e908c7e9", + "size": 104415 } } }, "org.lwjgl:lwjgl-stb:3.2.2": { - "name": "org.lwjgl:lwjgl-stb:3.3.4-SNAPSHOT", + "name": "org.lwjgl:lwjgl-stb:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-stb/3.3.4-SNAPSHOT/lwjgl-stb-3.3.4-20231218.151521-3.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-stb/3.3.4-SNAPSHOT/lwjgl-stb-3.3.4-20231218.151521-3.jar", - "sha1": "033fe42d1b37e35afd8b6e2653abc77deadb0730", - "size": 143099 + "path": "org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar", + "sha1": "5821735d5ef23f6da8542887344e57eb181b7cac", + "size": 143112 } } }, "org.lwjgl:lwjgl-stb:3.2.2:natives": { - "name": "org.lwjgl:lwjgl-stb:3.3.4-SNAPSHOT:natives-freebsd", + "name": "org.lwjgl:lwjgl-stb:3.3.4:natives-freebsd", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-stb/3.3.4-SNAPSHOT/lwjgl-stb-3.3.4-20231218.151521-3-natives-freebsd.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-stb/3.3.4-SNAPSHOT/lwjgl-stb-3.3.4-20231218.151521-3-natives-freebsd.jar", - "sha1": "0aad82a857ebb9a3a212dc2c386761d57e11a8f2", - "size": 225735 + "path": "org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-freebsd.jar", + "sha1": "f5551338a1e2035ff747053f0e985dc93db1235c", + "size": 226093 } } }, "org.lwjgl:lwjgl-tinyfd:3.2.2": { - "name": "org.lwjgl:lwjgl-tinyfd:3.3.4-SNAPSHOT", + "name": "org.lwjgl:lwjgl-tinyfd:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-tinyfd/3.3.4-SNAPSHOT/lwjgl-tinyfd-3.3.4-20231218.151521-3.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-tinyfd/3.3.4-SNAPSHOT/lwjgl-tinyfd-3.3.4-20231218.151521-3.jar", - "sha1": "8b7c94a57f56a5b38b23c02c1cada77dccba9930", - "size": 15917 + "path": "org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar", + "sha1": "2d73789ffd8962b38d9d599cc38b2383ce818c7a", + "size": 15928 } } }, "org.lwjgl:lwjgl-tinyfd:3.2.2:natives": { - "name": "org.lwjgl:lwjgl-tinyfd:3.3.4-SNAPSHOT:natives-freebsd", + "name": "org.lwjgl:lwjgl-tinyfd:3.3.4:natives-freebsd", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-tinyfd/3.3.4-SNAPSHOT/lwjgl-tinyfd-3.3.4-20231218.151521-3-natives-freebsd.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-tinyfd/3.3.4-SNAPSHOT/lwjgl-tinyfd-3.3.4-20231218.151521-3-natives-freebsd.jar", - "sha1": "124d0e48ae1584f09e5701588ae8ad139be26003", - "size": 39077 + "path": "org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-freebsd.jar", + "sha1": "acd5e1b9b9b99ce4d21867058ee468ee45a859e5", + "size": 40104 } } }, "org.lwjgl:lwjgl:3.3.1": { - "name": "org.lwjgl:lwjgl:3.3.4-SNAPSHOT", + "name": "org.lwjgl:lwjgl:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl/3.3.4-SNAPSHOT/lwjgl-3.3.4-20231218.151521-4.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl/3.3.4-SNAPSHOT/lwjgl-3.3.4-20231218.151521-4.jar", - "sha1": "7202012cf0cadb9ffad4874494920fd8bbd93413", - "size": 792204 + "path": "org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar", + "sha1": "b86c3e4832426e8a6b466013b7cb34b40e9ce956", + "size": 800127 } } }, - "org.lwjgl:lwjgl:3.3.1:natives-linux": { - "name": "org.lwjgl:lwjgl:3.3.4-SNAPSHOT:natives-freebsd", + "org.lwjgl:lwjgl:3.3.1:natives": { + "name": "org.lwjgl:lwjgl:3.3.4:natives-freebsd", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl/3.3.4-SNAPSHOT/lwjgl-3.3.4-20231218.151521-4-natives-freebsd.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl/3.3.4-SNAPSHOT/lwjgl-3.3.4-20231218.151521-4-natives-freebsd.jar", - "sha1": "2d38355b453edfe2daee1a567bcdb82c0485edcf", - "size": 95872 + "path": "org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-freebsd.jar", + "sha1": "610d14530e637564d97d74af7cb98a737e70b77b", + "size": 96209 } } }, "org.lwjgl:lwjgl-jemalloc:3.3.1": { - "name": "org.lwjgl:lwjgl-jemalloc:3.3.4-SNAPSHOT", + "name": "org.lwjgl:lwjgl-jemalloc:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-jemalloc/3.3.4-SNAPSHOT/lwjgl-jemalloc-3.3.4-20231218.151521-4.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-jemalloc/3.3.4-SNAPSHOT/lwjgl-jemalloc-3.3.4-20231218.151521-4.jar", - "sha1": "41256f2c098806304fd224613d3d01b02725470e", - "size": 46421 + "path": "org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar", + "sha1": "e3f5dcb8e13f3a5ed3f740fd30a114cee2a80bc4", + "size": 46430 } } }, - "org.lwjgl:lwjgl-jemalloc:3.3.1:natives-linux": { - "name": "org.lwjgl:lwjgl-jemalloc:3.3.4-SNAPSHOT:natives-freebsd", + "org.lwjgl:lwjgl-jemalloc:3.3.1:natives": { + "name": "org.lwjgl:lwjgl-jemalloc:3.3.4:natives-freebsd", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-jemalloc/3.3.4-SNAPSHOT/lwjgl-jemalloc-3.3.4-20231218.151521-4-natives-freebsd.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-jemalloc/3.3.4-SNAPSHOT/lwjgl-jemalloc-3.3.4-20231218.151521-4-natives-freebsd.jar", - "sha1": "bdba1662b621228679c7aed87945d1f28c590d5b", - "size": 155867 + "path": "org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-freebsd.jar", + "sha1": "5ee27f3bad4715067cef0630682da4bb5a1b88ac", + "size": 157297 } } }, "org.lwjgl:lwjgl-openal:3.3.1": { - "name": "org.lwjgl:lwjgl-openal:3.3.4-SNAPSHOT", + "name": "org.lwjgl:lwjgl-openal:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-openal/3.3.4-SNAPSHOT/lwjgl-openal-3.3.4-20231218.151521-4.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-openal/3.3.4-SNAPSHOT/lwjgl-openal-3.3.4-20231218.151521-4.jar", - "sha1": "89d8868c2d688b55e3e923345e4a146c6d034229", - "size": 113094 + "path": "org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar", + "sha1": "9b74d3ea380c83353d42af43ad9659e04dabe84a", + "size": 113103 } } }, - "org.lwjgl:lwjgl-openal:3.3.1:natives-linux": { - "name": "org.lwjgl:lwjgl-openal:3.3.4-SNAPSHOT:natives-freebsd", + "org.lwjgl:lwjgl-openal:3.3.1:natives": { + "name": "org.lwjgl:lwjgl-openal:3.3.4:natives-freebsd", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-openal/3.3.4-SNAPSHOT/lwjgl-openal-3.3.4-20231218.151521-4-natives-freebsd.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-openal/3.3.4-SNAPSHOT/lwjgl-openal-3.3.4-20231218.151521-4-natives-freebsd.jar", - "sha1": "0fc6495e6752727b629cf03a105c9d56087d4edd", - "size": 597512 + "path": "org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-freebsd.jar", + "sha1": "3863f8268f5515c27f1364257f8a018f0c6afa79", + "size": 597486 } } }, "org.lwjgl:lwjgl-opengl:3.3.1": { - "name": "org.lwjgl:lwjgl-opengl:3.3.4-SNAPSHOT", + "name": "org.lwjgl:lwjgl-opengl:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-opengl/3.3.4-SNAPSHOT/lwjgl-opengl-3.3.4-20231218.151521-4.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-opengl/3.3.4-SNAPSHOT/lwjgl-opengl-3.3.4-20231218.151521-4.jar", - "sha1": "81d0a7fd96bf5eb6257fddf6b77e338d8918bf32", - "size": 931744 + "path": "org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar", + "sha1": "2852ac7d9f6fc71349f1ce28e2708ff1977f18af", + "size": 931960 } } }, - "org.lwjgl:lwjgl-opengl:3.3.1:natives-linux": { - "name": "org.lwjgl:lwjgl-opengl:3.3.4-SNAPSHOT:natives-freebsd", + "org.lwjgl:lwjgl-opengl:3.3.1:natives": { + "name": "org.lwjgl:lwjgl-opengl:3.3.4:natives-freebsd", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-opengl/3.3.4-SNAPSHOT/lwjgl-opengl-3.3.4-20231218.151521-4-natives-freebsd.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-opengl/3.3.4-SNAPSHOT/lwjgl-opengl-3.3.4-20231218.151521-4-natives-freebsd.jar", - "sha1": "d3e8ec997cef8bc66819c7e0ad7a54182f7aff2a", - "size": 81034 + "path": "org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-freebsd.jar", + "sha1": "579071d2a3714f5662522f7d3edf58e941580587", + "size": 81028 } } }, "org.lwjgl:lwjgl-glfw:3.3.1": { - "name": "org.lwjgl:lwjgl-glfw:3.3.4-SNAPSHOT", + "name": "org.lwjgl:lwjgl-glfw:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-glfw/3.3.4-SNAPSHOT/lwjgl-glfw-3.3.4-20231218.151521-4.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-glfw/3.3.4-SNAPSHOT/lwjgl-glfw-3.3.4-20231218.151521-4.jar", - "sha1": "e6dba9ab8532cb6aac273adf26496ce689999943", - "size": 146829 + "path": "org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar", + "sha1": "7e46ecdec85db8738053cfde1414352cd62dab74", + "size": 147044 } } }, - "org.lwjgl:lwjgl-glfw:3.3.1:natives-linux": { - "name": "org.lwjgl:lwjgl-glfw:3.3.4-SNAPSHOT:natives-freebsd", + "org.lwjgl:lwjgl-glfw:3.3.1:natives": { + "name": "org.lwjgl:lwjgl-glfw:3.3.4:natives-freebsd", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-glfw/3.3.4-SNAPSHOT/lwjgl-glfw-3.3.4-20231218.151521-4-natives-freebsd.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-glfw/3.3.4-SNAPSHOT/lwjgl-glfw-3.3.4-20231218.151521-4-natives-freebsd.jar", - "sha1": "4881a965c7679b4984c0d8a5890f07ea85c653c4", - "size": 101847 + "path": "org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-freebsd.jar", + "sha1": "f67b9b6c29451d8fea66db17aaba2f65e908c7e9", + "size": 104415 } } }, "org.lwjgl:lwjgl-stb:3.3.1": { - "name": "org.lwjgl:lwjgl-stb:3.3.4-SNAPSHOT", + "name": "org.lwjgl:lwjgl-stb:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-stb/3.3.4-SNAPSHOT/lwjgl-stb-3.3.4-20231218.151521-3.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-stb/3.3.4-SNAPSHOT/lwjgl-stb-3.3.4-20231218.151521-3.jar", - "sha1": "033fe42d1b37e35afd8b6e2653abc77deadb0730", - "size": 143099 + "path": "org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar", + "sha1": "5821735d5ef23f6da8542887344e57eb181b7cac", + "size": 143112 } } }, - "org.lwjgl:lwjgl-stb:3.3.1:natives-linux": { - "name": "org.lwjgl:lwjgl-stb:3.3.4-SNAPSHOT:natives-freebsd", + "org.lwjgl:lwjgl-stb:3.3.1:natives": { + "name": "org.lwjgl:lwjgl-stb:3.3.4:natives-freebsd", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-stb/3.3.4-SNAPSHOT/lwjgl-stb-3.3.4-20231218.151521-3-natives-freebsd.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-stb/3.3.4-SNAPSHOT/lwjgl-stb-3.3.4-20231218.151521-3-natives-freebsd.jar", - "sha1": "0aad82a857ebb9a3a212dc2c386761d57e11a8f2", - "size": 225735 + "path": "org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-freebsd.jar", + "sha1": "f5551338a1e2035ff747053f0e985dc93db1235c", + "size": 226093 } } }, "org.lwjgl:lwjgl-tinyfd:3.3.1": { - "name": "org.lwjgl:lwjgl-tinyfd:3.3.4-SNAPSHOT", + "name": "org.lwjgl:lwjgl-tinyfd:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-tinyfd/3.3.4-SNAPSHOT/lwjgl-tinyfd-3.3.4-20231218.151521-3.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-tinyfd/3.3.4-SNAPSHOT/lwjgl-tinyfd-3.3.4-20231218.151521-3.jar", - "sha1": "8b7c94a57f56a5b38b23c02c1cada77dccba9930", - "size": 15917 + "path": "org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar", + "sha1": "2d73789ffd8962b38d9d599cc38b2383ce818c7a", + "size": 15928 } } }, - "org.lwjgl:lwjgl-tinyfd:3.3.1:natives-linux": { - "name": "org.lwjgl:lwjgl-tinyfd:3.3.4-SNAPSHOT:natives-freebsd", + "org.lwjgl:lwjgl-tinyfd:3.3.1:natives": { + "name": "org.lwjgl:lwjgl-tinyfd:3.3.4:natives-freebsd", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-tinyfd/3.3.4-SNAPSHOT/lwjgl-tinyfd-3.3.4-20231218.151521-3-natives-freebsd.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-tinyfd/3.3.4-SNAPSHOT/lwjgl-tinyfd-3.3.4-20231218.151521-3-natives-freebsd.jar", - "sha1": "124d0e48ae1584f09e5701588ae8ad139be26003", - "size": 39077 + "path": "org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-freebsd.jar", + "sha1": "acd5e1b9b9b99ce4d21867058ee468ee45a859e5", + "size": 40104 } } }, "org.lwjgl:lwjgl:3.3.2": { - "name": "org.lwjgl:lwjgl:3.3.4-SNAPSHOT", + "name": "org.lwjgl:lwjgl:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl/3.3.4-SNAPSHOT/lwjgl-3.3.4-20231218.151521-4.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl/3.3.4-SNAPSHOT/lwjgl-3.3.4-20231218.151521-4.jar", - "sha1": "7202012cf0cadb9ffad4874494920fd8bbd93413", - "size": 792204 + "path": "org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar", + "sha1": "b86c3e4832426e8a6b466013b7cb34b40e9ce956", + "size": 800127 } } }, - "org.lwjgl:lwjgl:3.3.2:natives-linux": { - "name": "org.lwjgl:lwjgl:3.3.4-SNAPSHOT:natives-freebsd", + "org.lwjgl:lwjgl:3.3.2:natives": { + "name": "org.lwjgl:lwjgl:3.3.4:natives-freebsd", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl/3.3.4-SNAPSHOT/lwjgl-3.3.4-20231218.151521-4-natives-freebsd.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl/3.3.4-SNAPSHOT/lwjgl-3.3.4-20231218.151521-4-natives-freebsd.jar", - "sha1": "2d38355b453edfe2daee1a567bcdb82c0485edcf", - "size": 95872 + "path": "org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-freebsd.jar", + "sha1": "610d14530e637564d97d74af7cb98a737e70b77b", + "size": 96209 } } }, "org.lwjgl:lwjgl-jemalloc:3.3.2": { - "name": "org.lwjgl:lwjgl-jemalloc:3.3.4-SNAPSHOT", + "name": "org.lwjgl:lwjgl-jemalloc:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-jemalloc/3.3.4-SNAPSHOT/lwjgl-jemalloc-3.3.4-20231218.151521-4.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-jemalloc/3.3.4-SNAPSHOT/lwjgl-jemalloc-3.3.4-20231218.151521-4.jar", - "sha1": "41256f2c098806304fd224613d3d01b02725470e", - "size": 46421 + "path": "org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar", + "sha1": "e3f5dcb8e13f3a5ed3f740fd30a114cee2a80bc4", + "size": 46430 } } }, - "org.lwjgl:lwjgl-jemalloc:3.3.2:natives-linux": { - "name": "org.lwjgl:lwjgl-jemalloc:3.3.4-SNAPSHOT:natives-freebsd", + "org.lwjgl:lwjgl-jemalloc:3.3.2:natives": { + "name": "org.lwjgl:lwjgl-jemalloc:3.3.4:natives-freebsd", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-jemalloc/3.3.4-SNAPSHOT/lwjgl-jemalloc-3.3.4-20231218.151521-4-natives-freebsd.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-jemalloc/3.3.4-SNAPSHOT/lwjgl-jemalloc-3.3.4-20231218.151521-4-natives-freebsd.jar", - "sha1": "bdba1662b621228679c7aed87945d1f28c590d5b", - "size": 155867 + "path": "org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-freebsd.jar", + "sha1": "5ee27f3bad4715067cef0630682da4bb5a1b88ac", + "size": 157297 } } }, "org.lwjgl:lwjgl-openal:3.3.2": { - "name": "org.lwjgl:lwjgl-openal:3.3.4-SNAPSHOT", + "name": "org.lwjgl:lwjgl-openal:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-openal/3.3.4-SNAPSHOT/lwjgl-openal-3.3.4-20231218.151521-4.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-openal/3.3.4-SNAPSHOT/lwjgl-openal-3.3.4-20231218.151521-4.jar", - "sha1": "89d8868c2d688b55e3e923345e4a146c6d034229", - "size": 113094 + "path": "org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar", + "sha1": "9b74d3ea380c83353d42af43ad9659e04dabe84a", + "size": 113103 } } }, - "org.lwjgl:lwjgl-openal:3.3.2:natives-linux": { - "name": "org.lwjgl:lwjgl-openal:3.3.4-SNAPSHOT:natives-freebsd", + "org.lwjgl:lwjgl-openal:3.3.2:natives": { + "name": "org.lwjgl:lwjgl-openal:3.3.4:natives-freebsd", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-openal/3.3.4-SNAPSHOT/lwjgl-openal-3.3.4-20231218.151521-4-natives-freebsd.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-openal/3.3.4-SNAPSHOT/lwjgl-openal-3.3.4-20231218.151521-4-natives-freebsd.jar", - "sha1": "0fc6495e6752727b629cf03a105c9d56087d4edd", - "size": 597512 + "path": "org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-freebsd.jar", + "sha1": "3863f8268f5515c27f1364257f8a018f0c6afa79", + "size": 597486 } } }, "org.lwjgl:lwjgl-opengl:3.3.2": { - "name": "org.lwjgl:lwjgl-opengl:3.3.4-SNAPSHOT", + "name": "org.lwjgl:lwjgl-opengl:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-opengl/3.3.4-SNAPSHOT/lwjgl-opengl-3.3.4-20231218.151521-4.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-opengl/3.3.4-SNAPSHOT/lwjgl-opengl-3.3.4-20231218.151521-4.jar", - "sha1": "81d0a7fd96bf5eb6257fddf6b77e338d8918bf32", - "size": 931744 + "path": "org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar", + "sha1": "2852ac7d9f6fc71349f1ce28e2708ff1977f18af", + "size": 931960 } } }, - "org.lwjgl:lwjgl-opengl:3.3.2:natives-linux": { - "name": "org.lwjgl:lwjgl-opengl:3.3.4-SNAPSHOT:natives-freebsd", + "org.lwjgl:lwjgl-opengl:3.3.2:natives": { + "name": "org.lwjgl:lwjgl-opengl:3.3.4:natives-freebsd", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-opengl/3.3.4-SNAPSHOT/lwjgl-opengl-3.3.4-20231218.151521-4-natives-freebsd.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-opengl/3.3.4-SNAPSHOT/lwjgl-opengl-3.3.4-20231218.151521-4-natives-freebsd.jar", - "sha1": "d3e8ec997cef8bc66819c7e0ad7a54182f7aff2a", - "size": 81034 + "path": "org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-freebsd.jar", + "sha1": "579071d2a3714f5662522f7d3edf58e941580587", + "size": 81028 } } }, "org.lwjgl:lwjgl-glfw:3.3.2": { - "name": "org.lwjgl:lwjgl-glfw:3.3.4-SNAPSHOT", + "name": "org.lwjgl:lwjgl-glfw:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-glfw/3.3.4-SNAPSHOT/lwjgl-glfw-3.3.4-20231218.151521-4.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-glfw/3.3.4-SNAPSHOT/lwjgl-glfw-3.3.4-20231218.151521-4.jar", - "sha1": "e6dba9ab8532cb6aac273adf26496ce689999943", - "size": 146829 + "path": "org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar", + "sha1": "7e46ecdec85db8738053cfde1414352cd62dab74", + "size": 147044 } } }, - "org.lwjgl:lwjgl-glfw:3.3.2:natives-linux": { - "name": "org.lwjgl:lwjgl-glfw:3.3.4-SNAPSHOT:natives-freebsd", + "org.lwjgl:lwjgl-glfw:3.3.2:natives": { + "name": "org.lwjgl:lwjgl-glfw:3.3.4:natives-freebsd", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-glfw/3.3.4-SNAPSHOT/lwjgl-glfw-3.3.4-20231218.151521-4-natives-freebsd.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-glfw/3.3.4-SNAPSHOT/lwjgl-glfw-3.3.4-20231218.151521-4-natives-freebsd.jar", - "sha1": "4881a965c7679b4984c0d8a5890f07ea85c653c4", - "size": 101847 + "path": "org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-freebsd.jar", + "sha1": "f67b9b6c29451d8fea66db17aaba2f65e908c7e9", + "size": 104415 } } }, "org.lwjgl:lwjgl-stb:3.3.2": { - "name": "org.lwjgl:lwjgl-stb:3.3.4-SNAPSHOT", + "name": "org.lwjgl:lwjgl-stb:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-stb/3.3.4-SNAPSHOT/lwjgl-stb-3.3.4-20231218.151521-3.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-stb/3.3.4-SNAPSHOT/lwjgl-stb-3.3.4-20231218.151521-3.jar", - "sha1": "033fe42d1b37e35afd8b6e2653abc77deadb0730", - "size": 143099 + "path": "org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar", + "sha1": "5821735d5ef23f6da8542887344e57eb181b7cac", + "size": 143112 } } }, - "org.lwjgl:lwjgl-stb:3.3.2:natives-linux": { - "name": "org.lwjgl:lwjgl-stb:3.3.4-SNAPSHOT:natives-freebsd", + "org.lwjgl:lwjgl-stb:3.3.2:natives": { + "name": "org.lwjgl:lwjgl-stb:3.3.4:natives-freebsd", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-stb/3.3.4-SNAPSHOT/lwjgl-stb-3.3.4-20231218.151521-3-natives-freebsd.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-stb/3.3.4-SNAPSHOT/lwjgl-stb-3.3.4-20231218.151521-3-natives-freebsd.jar", - "sha1": "0aad82a857ebb9a3a212dc2c386761d57e11a8f2", - "size": 225735 + "path": "org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-freebsd.jar", + "sha1": "f5551338a1e2035ff747053f0e985dc93db1235c", + "size": 226093 } } }, "org.lwjgl:lwjgl-tinyfd:3.3.2": { - "name": "org.lwjgl:lwjgl-tinyfd:3.3.4-SNAPSHOT", + "name": "org.lwjgl:lwjgl-tinyfd:3.3.4", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-tinyfd/3.3.4-SNAPSHOT/lwjgl-tinyfd-3.3.4-20231218.151521-3.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-tinyfd/3.3.4-SNAPSHOT/lwjgl-tinyfd-3.3.4-20231218.151521-3.jar", - "sha1": "8b7c94a57f56a5b38b23c02c1cada77dccba9930", - "size": 15917 + "path": "org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar", + "sha1": "2d73789ffd8962b38d9d599cc38b2383ce818c7a", + "size": 15928 } } }, - "org.lwjgl:lwjgl-tinyfd:3.3.2:natives-linux": { - "name": "org.lwjgl:lwjgl-tinyfd:3.3.4-SNAPSHOT:natives-freebsd", + "org.lwjgl:lwjgl-tinyfd:3.3.2:natives": { + "name": "org.lwjgl:lwjgl-tinyfd:3.3.4:natives-freebsd", "downloads": { "artifact": { - "path": "org/lwjgl/lwjgl-tinyfd/3.3.4-SNAPSHOT/lwjgl-tinyfd-3.3.4-20231218.151521-3-natives-freebsd.jar", - "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-tinyfd/3.3.4-SNAPSHOT/lwjgl-tinyfd-3.3.4-20231218.151521-3-natives-freebsd.jar", - "sha1": "124d0e48ae1584f09e5701588ae8ad139be26003", - "size": 39077 + "path": "org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-freebsd.jar", + "url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-freebsd.jar", + "sha1": "acd5e1b9b9b99ce4d21867058ee468ee45a859e5", + "size": 40104 } } }, diff --git a/HMCL/src/main/resources/assets/openjfx-dependencies.json b/HMCL/src/main/resources/assets/openjfx-dependencies.json new file mode 100644 index 0000000000..cebc7ca4d6 --- /dev/null +++ b/HMCL/src/main/resources/assets/openjfx-dependencies.json @@ -0,0 +1,314 @@ +{ + "windows-x86": [ + { + "module": "javafx.base", + "groupId": "org.openjfx", + "artifactId": "javafx-base", + "version": "19.0.2.1", + "classifier": "win-x86", + "sha1": "6c9ebafc7f9c4544d72fa5e306f4111e56b5db58" + }, + { + "module": "javafx.graphics", + "groupId": "org.openjfx", + "artifactId": "javafx-graphics", + "version": "19.0.2.1", + "classifier": "win-x86", + "sha1": "a14a1fbe3a0dca81d99c53fd7be8e7c784a68afe" + }, + { + "module": "javafx.controls", + "groupId": "org.openjfx", + "artifactId": "javafx-controls", + "version": "19.0.2.1", + "classifier": "win-x86", + "sha1": "7df1501701f9e9fbadab8ce55ef1dde128e2e88a" + } + ], + "windows-x86_64": [ + { + "module": "javafx.base", + "groupId": "org.openjfx", + "artifactId": "javafx-base", + "version": "17.0.13", + "classifier": "win", + "sha1": "ae86377359860449040372d3693bccf046148513" + }, + { + "module": "javafx.graphics", + "groupId": "org.openjfx", + "artifactId": "javafx-graphics", + "version": "17.0.13", + "classifier": "win", + "sha1": "bc0055a059b201d6c0be2293ccf72b6d2874fd1c" + }, + { + "module": "javafx.controls", + "groupId": "org.openjfx", + "artifactId": "javafx-controls", + "version": "17.0.13", + "classifier": "win", + "sha1": "bcd765dca36fa9e5fb4c997491a2d908947603f5" + } + ], + "windows-arm64": [ + { + "module": "javafx.base", + "groupId": "org.glavo.hmcl.openjfx", + "artifactId": "javafx-base", + "version": "18.0.2+1-arm64", + "classifier": "win", + "sha1": "4518a696b9d509dc09a7fe283452fce84a1686a8" + }, + { + "module": "javafx.graphics", + "groupId": "org.glavo.hmcl.openjfx", + "artifactId": "javafx-graphics", + "version": "18.0.2+1-arm64", + "classifier": "win", + "sha1": "e19ba9aefc4bba8ff86dcdf416620b93b7bdea39" + }, + { + "module": "javafx.controls", + "groupId": "org.glavo.hmcl.openjfx", + "artifactId": "javafx-controls", + "version": "18.0.2+1-arm64", + "classifier": "win", + "sha1": "0bf7380823bb8c420dd41837d2c71087b8953ec1" + } + ], + "osx-x86_64": [ + { + "module": "javafx.base", + "groupId": "org.openjfx", + "artifactId": "javafx-base", + "version": "17.0.13", + "classifier": "mac", + "sha1": "e1744403a6691aaa6b65a7e9b4ff9aad98831bb1" + }, + { + "module": "javafx.graphics", + "groupId": "org.openjfx", + "artifactId": "javafx-graphics", + "version": "17.0.13", + "classifier": "mac", + "sha1": "dbe733470094abee56560067d0fea3ad24991d2b" + }, + { + "module": "javafx.controls", + "groupId": "org.openjfx", + "artifactId": "javafx-controls", + "version": "17.0.13", + "classifier": "mac", + "sha1": "0d43a776d6c9e2622e3581ed04501e2cadcd6e65" + } + ], + "osx-arm64": [ + { + "module": "javafx.base", + "groupId": "org.openjfx", + "artifactId": "javafx-base", + "version": "17.0.13", + "classifier": "mac-aarch64", + "sha1": "d4dc7d2864c07b56a27e39fa5691856ddccf0ba4" + }, + { + "module": "javafx.graphics", + "groupId": "org.openjfx", + "artifactId": "javafx-graphics", + "version": "17.0.13", + "classifier": "mac-aarch64", + "sha1": "d6a49f19e90db56c134757d1bbab4935de075d45" + }, + { + "module": "javafx.controls", + "groupId": "org.openjfx", + "artifactId": "javafx-controls", + "version": "17.0.13", + "classifier": "mac-aarch64", + "sha1": "ad564497edc49f7a6135c3b52d360962e19a2539" + } + ], + "linux-x86_64": [ + { + "module": "javafx.base", + "groupId": "org.openjfx", + "artifactId": "javafx-base", + "version": "17.0.13", + "classifier": "linux", + "sha1": "875f02b498b00c9692f6ed0af3da373b1d07bf03" + }, + { + "module": "javafx.graphics", + "groupId": "org.openjfx", + "artifactId": "javafx-graphics", + "version": "17.0.13", + "classifier": "linux", + "sha1": "103f3bba5a177ba8912a92ef2ee0855de7ba050c" + }, + { + "module": "javafx.controls", + "groupId": "org.openjfx", + "artifactId": "javafx-controls", + "version": "17.0.13", + "classifier": "linux", + "sha1": "940e9e05a974a3e484d5ec241d88a0c77ad013f2" + } + ], + "linux-arm32": [ + { + "module": "javafx.base", + "groupId": "org.openjfx", + "artifactId": "javafx-base", + "version": "19.0.2.1", + "classifier": "linux-arm32-monocle", + "sha1": "452f455d6948788c1e5350a41259eb8101d3f82a" + }, + { + "module": "javafx.graphics", + "groupId": "org.openjfx", + "artifactId": "javafx-graphics", + "version": "19.0.2.1", + "classifier": "linux-arm32-monocle", + "sha1": "b45b33252e88263fe80a462a45828b4562c3c709" + }, + { + "module": "javafx.controls", + "groupId": "org.openjfx", + "artifactId": "javafx-controls", + "version": "19.0.2.1", + "classifier": "linux-arm32-monocle", + "sha1": "e606c619fc493ecd18d281b0ded3aa38ba15a9e5" + } + ], + "linux-arm64": [ + { + "module": "javafx.base", + "groupId": "org.openjfx", + "artifactId": "javafx-base", + "version": "19.0.2.1", + "classifier": "linux-aarch64", + "sha1": "1490bfe619e148b3d58746d43f549d697640c935" + }, + { + "module": "javafx.graphics", + "groupId": "org.openjfx", + "artifactId": "javafx-graphics", + "version": "19.0.2.1", + "classifier": "linux-aarch64", + "sha1": "cad8004a87f57d9813c52985894ef15e8011baee" + }, + { + "module": "javafx.controls", + "groupId": "org.openjfx", + "artifactId": "javafx-controls", + "version": "19.0.2.1", + "classifier": "linux-aarch64", + "sha1": "ccc33fc1fcbf46130346f4330d6d70b71bdec7d0" + } + ], + "linux-loongarch64": [ + { + "module": "javafx.base", + "groupId": "org.glavo.hmcl.openjfx", + "artifactId": "javafx-base", + "version": "17.0.8-loongarch64", + "classifier": "linux", + "sha1": "9d692dfc79eb334a7b4bae922aa57cb3a437345e" + }, + { + "module": "javafx.graphics", + "groupId": "org.glavo.hmcl.openjfx", + "artifactId": "javafx-graphics", + "version": "17.0.8-loongarch64", + "classifier": "linux", + "sha1": "7f241dc6adc8beddb776f453a3c63a5848d7b90b" + }, + { + "module": "javafx.controls", + "groupId": "org.glavo.hmcl.openjfx", + "artifactId": "javafx-controls", + "version": "17.0.8-loongarch64", + "classifier": "linux", + "sha1": "1c8b0141bec93ed21d7c0e9a2f69a1c7e3c734d2" + } + ], + "linux-loongarch64_ow": [ + { + "module": "javafx.base", + "groupId": "org.glavo.hmcl.openjfx", + "artifactId": "javafx-base", + "version": "19-ea+10-loongson64", + "classifier": "linux", + "sha1": "f90663ba93aef9f818236ce19ecfa33dca0ffe10" + }, + { + "module": "javafx.graphics", + "groupId": "org.glavo.hmcl.openjfx", + "artifactId": "javafx-graphics", + "version": "19-ea+10-loongson64", + "classifier": "linux", + "sha1": "6ff76304d08bba093abfe7f4d50ce6a49279c87f" + }, + { + "module": "javafx.controls", + "groupId": "org.glavo.hmcl.openjfx", + "artifactId": "javafx-controls", + "version": "19-ea+10-loongson64", + "classifier": "linux", + "sha1": "8a16096d42de70f2548f18840cdcd49a07fc1654" + } + ], + "linux-riscv64": [ + { + "module": "javafx.base", + "groupId": "org.glavo.hmcl.openjfx", + "artifactId": "javafx-base", + "version": "19.0.2.1-riscv64", + "classifier": "linux", + "sha1": "e33c7e0bac2931a8f7a752bb74e4e4e535eec4ad" + }, + { + "module": "javafx.graphics", + "groupId": "org.glavo.hmcl.openjfx", + "artifactId": "javafx-graphics", + "version": "19.0.2.1-riscv64", + "classifier": "linux", + "sha1": "ac4e5edd55d84da80f8fc2d81a4c9994296a6c09" + }, + { + "module": "javafx.controls", + "groupId": "org.glavo.hmcl.openjfx", + "artifactId": "javafx-controls", + "version": "19.0.2.1-riscv64", + "classifier": "linux", + "sha1": "efe6a87ea24972d45f1931528f87e64a53bd2232" + } + ], + "freebsd-x86_64": [ + { + "module": "javafx.base", + "groupId": "org.glavo.hmcl.openjfx", + "artifactId": "javafx-base", + "version": "14.0.2.1-freebsd", + "classifier": "freebsd", + "sha1": "7bac900f0ab0d4d6dcf178252cf37ee1e0470d40" + }, + { + "module": "javafx.graphics", + "groupId": "org.glavo.hmcl.openjfx", + "artifactId": "javafx-graphics", + "version": "14.0.2.1-freebsd", + "classifier": "freebsd", + "sha1": "7773ce02d1dc29160801e4e077ed2a26e93bed13" + }, + { + "module": "javafx.controls", + "groupId": "org.glavo.hmcl.openjfx", + "artifactId": "javafx-controls", + "version": "14.0.2.1-freebsd", + "classifier": "freebsd", + "sha1": "34a3dd3ccbc898bacfdcb3256cfb87ddc50621d3" + } + ] +} \ No newline at end of file diff --git a/HMCL/src/test/java/org/jackhuang/hmcl/ui/GameCrashWindowTest.java b/HMCL/src/test/java/org/jackhuang/hmcl/ui/GameCrashWindowTest.java index 016e790a63..36c691e405 100644 --- a/HMCL/src/test/java/org/jackhuang/hmcl/ui/GameCrashWindowTest.java +++ b/HMCL/src/test/java/org/jackhuang/hmcl/ui/GameCrashWindowTest.java @@ -20,10 +20,11 @@ import org.jackhuang.hmcl.JavaFXLauncher; import org.jackhuang.hmcl.game.ClassicVersion; import org.jackhuang.hmcl.game.LaunchOptions; +import org.jackhuang.hmcl.java.JavaInfo; +import org.jackhuang.hmcl.game.Log; import org.jackhuang.hmcl.launch.ProcessListener; -import org.jackhuang.hmcl.util.Log4jLevel; import org.jackhuang.hmcl.util.io.FileUtils; -import org.jackhuang.hmcl.util.platform.JavaVersion; +import org.jackhuang.hmcl.java.JavaRuntime; import org.jackhuang.hmcl.util.platform.ManagedProcess; import org.jackhuang.hmcl.util.platform.Platform; import org.junit.jupiter.api.Disabled; @@ -35,8 +36,6 @@ import java.util.concurrent.CountDownLatch; import java.util.stream.Collectors; -import static org.jackhuang.hmcl.util.Pair.pair; - public class GameCrashWindowTest { @Test @@ -53,11 +52,11 @@ public void test() throws Exception { GameCrashWindow window = new GameCrashWindow(process, ProcessListener.ExitType.APPLICATION_ERROR, null, new ClassicVersion(), new LaunchOptions.Builder() - .setJava(new JavaVersion(Paths.get("."), "16", Platform.SYSTEM_PLATFORM)) + .setJava(new JavaRuntime(Paths.get("."), new JavaInfo(Platform.SYSTEM_PLATFORM, "16", null), false, false)) .setGameDir(new File(".")) .create(), Arrays.stream(logs.split("\\n")) - .map(log -> pair(log, Log4jLevel.guessLevel(log))) + .map(Log::new) .collect(Collectors.toList())); window.showAndWait(); diff --git a/HMCLCore/build.gradle.kts b/HMCLCore/build.gradle.kts index c94473b502..c25db1c3ee 100644 --- a/HMCLCore/build.gradle.kts +++ b/HMCLCore/build.gradle.kts @@ -4,13 +4,20 @@ plugins { dependencies { api("org.glavo:simple-png-javafx:0.3.0") - api("com.google.code.gson:gson:2.10.1") + api("com.google.code.gson:gson:2.11.0") api("com.moandjiezana.toml:toml4j:0.7.2") - api("org.tukaani:xz:1.9") + api("org.tukaani:xz:1.10") api("org.hildan.fxgson:fx-gson:5.0.0") api("org.jenkins-ci:constant-pool-scanner:1.2") api("com.github.steveice10:opennbt:1.5") api("org.nanohttpd:nanohttpd:2.3.1") api("org.apache.commons:commons-compress:1.25.0") + api("org.jsoup:jsoup:1.18.1") compileOnlyApi("org.jetbrains:annotations:24.1.0") + + if (JavaVersion.current().isJava8) { + org.gradle.internal.jvm.Jvm.current().toolsJar?.let { + compileOnly(files(it)) + } + } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Account.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Account.java index 8ac98a2a98..3d40c983fe 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Account.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Account.java @@ -30,6 +30,7 @@ import org.jackhuang.hmcl.util.ToStringBuilder; import org.jackhuang.hmcl.util.javafx.ObservableHelper; +import java.nio.file.Path; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -69,6 +70,14 @@ public abstract class Account implements Observable { */ public abstract AuthInfo playOffline() throws AuthenticationException; + public boolean canUploadSkin() { + return false; + } + + public void uploadSkin(boolean isSlim, Path file) throws AuthenticationException, UnsupportedOperationException { + throw new UnsupportedOperationException("Unsupported Operation"); + } + public abstract Map toStorage(); public void clearCache() { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftAccount.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftAccount.java index 774178a725..ef9401630d 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftAccount.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftAccount.java @@ -24,6 +24,7 @@ import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService; import org.jackhuang.hmcl.util.javafx.BindingMapping; +import java.nio.file.Path; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -32,7 +33,7 @@ import static java.util.Objects.requireNonNull; import static org.jackhuang.hmcl.util.logging.Logger.LOG; -public class MicrosoftAccount extends OAuthAccount { +public final class MicrosoftAccount extends OAuthAccount { protected final MicrosoftService service; protected UUID characterUUID; @@ -125,6 +126,16 @@ public AuthInfo playOffline() { return session.toAuthInfo(); } + @Override + public boolean canUploadSkin() { + return true; + } + + @Override + public void uploadSkin(boolean isSlim, Path file) throws AuthenticationException, UnsupportedOperationException { + service.uploadSkin(session.getAccessToken(), isSlim, file); + } + @Override public Map toStorage() { return session.toStorage(); @@ -150,6 +161,7 @@ public ObjectBinding>> getTextures() { @Override public void clearCache() { authenticated = false; + service.getProfileRepository().invalidate(characterUUID); } @Override diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftService.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftService.java index 79e4a32d10..335e074d00 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftService.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftService.java @@ -21,21 +21,22 @@ import com.google.gson.GsonBuilder; import com.google.gson.JsonParseException; import com.google.gson.annotations.SerializedName; -import org.jackhuang.hmcl.auth.*; +import org.jackhuang.hmcl.auth.AuthenticationException; import org.jackhuang.hmcl.auth.OAuth; -import org.jackhuang.hmcl.auth.yggdrasil.CompleteGameProfile; -import org.jackhuang.hmcl.auth.yggdrasil.RemoteAuthenticationException; -import org.jackhuang.hmcl.auth.yggdrasil.Texture; -import org.jackhuang.hmcl.auth.yggdrasil.TextureType; +import org.jackhuang.hmcl.auth.ServerDisconnectException; +import org.jackhuang.hmcl.auth.ServerResponseMalformedException; +import org.jackhuang.hmcl.auth.yggdrasil.*; +import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.gson.*; -import org.jackhuang.hmcl.util.io.HttpRequest; -import org.jackhuang.hmcl.util.io.NetworkUtils; -import org.jackhuang.hmcl.util.io.ResponseCodeException; +import org.jackhuang.hmcl.util.io.*; import org.jackhuang.hmcl.util.javafx.ObservableOptionalCache; import java.io.IOException; +import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.*; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -44,8 +45,8 @@ import static java.util.Objects.requireNonNull; import static org.jackhuang.hmcl.util.Lang.mapOf; import static org.jackhuang.hmcl.util.Lang.threadPool; -import static org.jackhuang.hmcl.util.logging.Logger.LOG; import static org.jackhuang.hmcl.util.Pair.pair; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; public class MicrosoftService { private static final String SCOPE = "XboxLive.signin offline_access"; @@ -110,30 +111,40 @@ private String getUhs(XBoxLiveAuthenticationResponse response, String existingUh } private MicrosoftSession authenticateViaLiveAccessToken(String liveAccessToken, String liveRefreshToken) throws IOException, JsonParseException, AuthenticationException { - // Authenticate with XBox Live - XBoxLiveAuthenticationResponse xboxResponse = HttpRequest - .POST("https://user.auth.xboxlive.com/user/authenticate") - .json(mapOf( - pair("Properties", - mapOf(pair("AuthMethod", "RPS"), pair("SiteName", "user.auth.xboxlive.com"), - pair("RpsTicket", "d=" + liveAccessToken))), - pair("RelyingParty", "http://auth.xboxlive.com"), pair("TokenType", "JWT"))) - .retry(5) - .accept("application/json").getJson(XBoxLiveAuthenticationResponse.class); - - String uhs = getUhs(xboxResponse, null); - - // Authenticate Minecraft with XSTS - XBoxLiveAuthenticationResponse minecraftXstsResponse = HttpRequest - .POST("https://xsts.auth.xboxlive.com/xsts/authorize") - .json(mapOf( - pair("Properties", - mapOf(pair("SandboxId", "RETAIL"), - pair("UserTokens", Collections.singletonList(xboxResponse.token)))), - pair("RelyingParty", "rp://api.minecraftservices.com/"), pair("TokenType", "JWT"))) - .ignoreHttpErrorCode(401) - .retry(5) - .getJson(XBoxLiveAuthenticationResponse.class); + String uhs; + XBoxLiveAuthenticationResponse xboxResponse, minecraftXstsResponse; + try { + // Authenticate with XBox Live + xboxResponse = HttpRequest + .POST("https://user.auth.xboxlive.com/user/authenticate") + .json(mapOf( + pair("Properties", + mapOf(pair("AuthMethod", "RPS"), pair("SiteName", "user.auth.xboxlive.com"), + pair("RpsTicket", "d=" + liveAccessToken))), + pair("RelyingParty", "http://auth.xboxlive.com"), pair("TokenType", "JWT"))) + .retry(5) + .accept("application/json") + .getJson(XBoxLiveAuthenticationResponse.class); + + uhs = getUhs(xboxResponse, null); + + minecraftXstsResponse = HttpRequest + .POST("https://xsts.auth.xboxlive.com/xsts/authorize") + .json(mapOf( + pair("Properties", + mapOf(pair("SandboxId", "RETAIL"), + pair("UserTokens", Collections.singletonList(xboxResponse.token)))), + pair("RelyingParty", "rp://api.minecraftservices.com/"), pair("TokenType", "JWT"))) + .ignoreHttpErrorCode(401) + .retry(5) + .getJson(XBoxLiveAuthenticationResponse.class); + } catch (ResponseCodeException e) { + if (e.getResponseCode() == 400) { + throw new XBox400Exception(); + } + + throw e; + } getUhs(minecraftXstsResponse, uhs); @@ -249,6 +260,33 @@ public Optional getCompleteGameProfile(UUID uuid) throws Au return Optional.ofNullable(GSON.fromJson(request(NetworkUtils.toURL("https://sessionserver.mojang.com/session/minecraft/profile/" + UUIDTypeAdapter.fromUUID(uuid)), null), CompleteGameProfile.class)); } + public void uploadSkin(String accessToken, boolean isSlim, Path file) throws AuthenticationException, UnsupportedOperationException { + try { + HttpURLConnection con = NetworkUtils.createHttpConnection(NetworkUtils.toURL("https://api.minecraftservices.com/minecraft/profile/skins")); + con.setRequestMethod("POST"); + con.setRequestProperty("Authorization", "Bearer " + accessToken); + con.setDoOutput(true); + try (HttpMultipartRequest request = new HttpMultipartRequest(con)) { + request.param("variant", isSlim ? "slim" : "classic"); + try (InputStream fis = Files.newInputStream(file)) { + request.file("file", FileUtils.getName(file), "image/" + FileUtils.getExtension(file), fis); + } + } + + String response = NetworkUtils.readData(con); + if (StringUtils.isBlank(response)) { + if (con.getResponseCode() / 100 != 2) + throw new ResponseCodeException(con.getURL(), con.getResponseCode()); + } else { + MinecraftErrorResponse profileResponse = GSON.fromJson(response, MinecraftErrorResponse.class); + if (StringUtils.isNotBlank(profileResponse.errorMessage) || con.getResponseCode() / 100 != 2) + throw new AuthenticationException("Failed to upload skin, response code: " + con.getResponseCode() + ", response: " + response); + } + } catch (IOException | JsonParseException e) { + throw new AuthenticationException(e); + } + } + private static String request(URL url, Object payload) throws AuthenticationException { try { if (payload == null) @@ -260,14 +298,6 @@ private static String request(URL url, Object payload) throws AuthenticationExce } } - private static T fromJson(String text, Class typeOfT) throws ServerResponseMalformedException { - try { - return GSON.fromJson(text, typeOfT); - } catch (JsonParseException e) { - throw new ServerResponseMalformedException(text, e); - } - } - public static class XboxAuthorizationException extends AuthenticationException { private final long errorCode; private final String redirect; @@ -290,6 +320,9 @@ public String getRedirect() { public static final long ADD_FAMILY = 2148916238L; } + public static class XBox400Exception extends AuthenticationException { + } + public static class NoMinecraftJavaEditionProfileException extends AuthenticationException { } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftSession.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftSession.java index 22fd566b1a..3e4c491c11 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftSession.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftSession.java @@ -18,8 +18,8 @@ package org.jackhuang.hmcl.auth.microsoft; import org.jackhuang.hmcl.auth.AuthInfo; -import org.jackhuang.hmcl.util.logging.Logger; import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter; +import org.jackhuang.hmcl.util.logging.Logger; import java.util.Map; import java.util.UUID; @@ -87,7 +87,7 @@ public static MicrosoftSession fromStorage(Map storage) { .orElseThrow(() -> new IllegalArgumentException("accessToken is missing")); String refreshToken = tryCast(storage.get("refreshToken"), String.class) .orElseThrow(() -> new IllegalArgumentException("refreshToken is missing")); - Long notAfter = tryCast(storage.get("notAfter"), Long.class).orElse(0L); + Long notAfter = tryCast(storage.get("notAfter"), Number.class).map(Number::longValue).orElse(0L); String userId = tryCast(storage.get("userid"), String.class) .orElseThrow(() -> new IllegalArgumentException("userid is missing")); return new MicrosoftSession(tokenType, accessToken, notAfter, refreshToken, new User(userId), new GameProfile(uuid, name)); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/YggdrasilServer.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/YggdrasilServer.java index f3deed3d4f..7e722fa6bb 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/YggdrasilServer.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/YggdrasilServer.java @@ -17,7 +17,6 @@ */ package org.jackhuang.hmcl.auth.offline; -import com.google.gson.reflect.TypeToken; import org.glavo.png.javafx.PNGJavaFXUtils; import org.jackhuang.hmcl.auth.yggdrasil.GameProfile; import org.jackhuang.hmcl.auth.yggdrasil.TextureModel; @@ -38,6 +37,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.jackhuang.hmcl.util.Lang.mapOf; import static org.jackhuang.hmcl.util.Pair.pair; +import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf; public class YggdrasilServer extends HttpServer { @@ -81,8 +81,7 @@ private Response status(Request request) { } private Response profiles(Request request) throws IOException { - List names = JsonUtils.fromNonNullJsonFully(request.getSession().getInputStream(), new TypeToken>() { - }.getType()); + List names = JsonUtils.fromNonNullJsonFully(request.getSession().getInputStream(), listTypeOf(String.class)); return ok(names.stream().distinct() .map(this::findCharacterByName) .flatMap(Lang::toStream) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.java index db0f06fdeb..f253eb40b3 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.java @@ -203,8 +203,14 @@ public ObjectBinding>> getTextures() { } - public void uploadSkin(String model, Path file) throws AuthenticationException, UnsupportedOperationException { - service.uploadSkin(characterUUID, session.getAccessToken(), model, file); + @Override + public boolean canUploadSkin() { + return true; + } + + @Override + public void uploadSkin(boolean isSlim, Path file) throws AuthenticationException, UnsupportedOperationException { + service.uploadSkin(characterUUID, session.getAccessToken(), isSlim, file); } private static String randomClientToken() { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilService.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilService.java index 3e3b71e5ee..ef3b695626 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilService.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilService.java @@ -148,14 +148,14 @@ public void invalidate(String accessToken, String clientToken) throws Authentica requireEmpty(request(provider.getInvalidationURL(), createRequestWithCredentials(accessToken, clientToken))); } - public void uploadSkin(UUID uuid, String accessToken, String model, Path file) throws AuthenticationException, UnsupportedOperationException { + public void uploadSkin(UUID uuid, String accessToken, boolean isSlim, Path file) throws AuthenticationException, UnsupportedOperationException { try { HttpURLConnection con = NetworkUtils.createHttpConnection(provider.getSkinUploadURL(uuid)); con.setRequestMethod("PUT"); con.setRequestProperty("Authorization", "Bearer " + accessToken); con.setDoOutput(true); try (HttpMultipartRequest request = new HttpMultipartRequest(con)) { - request.param("model", model); + request.param("model", isSlim ? "slim" : ""); try (InputStream fis = Files.newInputStream(file)) { request.file("file", FileUtils.getName(file), "image/" + FileUtils.getExtension(file), fis); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java index 71f963cfb9..d7ac46571e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java @@ -270,6 +270,13 @@ private String scanVersion(Version version) { private final Pattern group, artifact; private final ModLoaderType modLoaderType; + private static final Map PATCH_ID_MAP = new HashMap<>(); + static { + for (LibraryType type : values()) { + PATCH_ID_MAP.put(type.getPatchId(), type); + } + } + LibraryType(boolean modLoader, String patchId, Pattern group, Pattern artifact, ModLoaderType modLoaderType) { this.modLoader = modLoader; this.patchId = patchId; @@ -291,10 +298,7 @@ public ModLoaderType getModLoaderType() { } public static LibraryType fromPatchId(String patchId) { - for (LibraryType type : values()) - if (type.getPatchId().equals(patchId)) - return type; - return null; + return PATCH_ID_MAP.get(patchId); } protected boolean matchLibrary(Library library, List libraries) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MojangDownloadProvider.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MojangDownloadProvider.java index a0a677f250..86fc3e032f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MojangDownloadProvider.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MojangDownloadProvider.java @@ -19,7 +19,7 @@ import org.jackhuang.hmcl.download.fabric.FabricAPIVersionList; import org.jackhuang.hmcl.download.fabric.FabricVersionList; -import org.jackhuang.hmcl.download.forge.ForgeBMCLVersionList; +import org.jackhuang.hmcl.download.forge.ForgeVersionList; import org.jackhuang.hmcl.download.game.GameVersionList; import org.jackhuang.hmcl.download.liteloader.LiteLoaderVersionList; import org.jackhuang.hmcl.download.neoforge.NeoForgeOfficialVersionList; @@ -35,7 +35,7 @@ public class MojangDownloadProvider implements DownloadProvider { private final GameVersionList game; private final FabricVersionList fabric; private final FabricAPIVersionList fabricApi; - private final ForgeBMCLVersionList forge; + private final ForgeVersionList forge; private final NeoForgeOfficialVersionList neoforge; private final LiteLoaderVersionList liteLoader; private final OptiFineBMCLVersionList optifine; @@ -49,7 +49,7 @@ public MojangDownloadProvider() { this.game = new GameVersionList(this); this.fabric = new FabricVersionList(this); this.fabricApi = new FabricAPIVersionList(this); - this.forge = new ForgeBMCLVersionList(apiRoot); + this.forge = new ForgeVersionList(this); this.neoforge = new NeoForgeOfficialVersionList(this); this.liteLoader = new LiteLoaderVersionList(this); this.optifine = new OptiFineBMCLVersionList(apiRoot); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricVersionList.java index 9837bc6791..8440f70b79 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricVersionList.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricVersionList.java @@ -17,7 +17,6 @@ */ package org.jackhuang.hmcl.download.fabric; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.download.DownloadProvider; import org.jackhuang.hmcl.download.VersionList; import org.jackhuang.hmcl.util.gson.JsonUtils; @@ -25,13 +24,13 @@ import org.jetbrains.annotations.Nullable; import java.io.IOException; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import static org.jackhuang.hmcl.util.Lang.wrap; +import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf; public final class FabricVersionList extends VersionList { private final DownloadProvider downloadProvider; @@ -69,8 +68,8 @@ public CompletableFuture refreshAsync() { private List getGameVersions(String metaUrl) throws IOException { String json = NetworkUtils.doGet(downloadProvider.injectURLWithCandidates(metaUrl)); - return JsonUtils.GSON.>fromJson(json, new TypeToken>() { - }.getType()).stream().map(GameVersion::getVersion).collect(Collectors.toList()); + return JsonUtils.GSON.fromJson(json, listTypeOf(GameVersion.class)) + .stream().map(GameVersion::getVersion).collect(Collectors.toList()); } private static String getLaunchMetaUrl(String gameVersion, String loaderVersion) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java index d63d7d156e..c0d0fdc8b7 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java @@ -18,9 +18,9 @@ package org.jackhuang.hmcl.download.forge; import com.google.gson.JsonParseException; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.download.VersionList; import org.jackhuang.hmcl.util.Immutable; +import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.gson.Validation; import org.jackhuang.hmcl.util.io.HttpRequest; @@ -30,13 +30,17 @@ import java.time.Instant; import java.time.format.DateTimeParseException; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import static org.jackhuang.hmcl.util.Lang.mapOf; import static org.jackhuang.hmcl.util.Lang.wrap; -import static org.jackhuang.hmcl.util.logging.Logger.LOG; import static org.jackhuang.hmcl.util.Pair.pair; +import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; public final class ForgeBMCLVersionList extends VersionList { private final String apiRoot; @@ -63,14 +67,29 @@ public CompletableFuture refreshAsync() { throw new UnsupportedOperationException("ForgeBMCLVersionList does not support loading the entire Forge remote version list."); } + private static String toLookupVersion(String gameVersion) { + return "1.7.10-pre4".equals(gameVersion) ? "1.7.10_pre4" : gameVersion; + } + + private static String fromLookupVersion(String lookupVersion) { + return "1.7.10_pre4".equals(lookupVersion) ? "1.7.10-pre4" : lookupVersion; + } + + private static String toLookupBranch(String gameVersion, String branch) { + if ("1.7.10-pre4".equals(gameVersion)) { + return "prerelease"; + } + return Lang.requireNonNullElse(branch, ""); + } + @Override public CompletableFuture refreshAsync(String gameVersion) { + String lookupVersion = toLookupVersion(gameVersion); + return CompletableFuture.completedFuture(null) - .thenApplyAsync(wrap(unused -> HttpRequest.GET(apiRoot + "/forge/minecraft/" + gameVersion).>getJson(new TypeToken>() { - }.getType()))) + .thenApplyAsync(wrap(unused -> HttpRequest.GET(apiRoot + "/forge/minecraft/" + lookupVersion).getJson(listTypeOf(ForgeVersion.class)))) .thenAcceptAsync(forgeVersions -> { lock.writeLock().lock(); - try { versions.clear(gameVersion); if (forgeVersions == null) return; @@ -80,16 +99,17 @@ public CompletableFuture refreshAsync(String gameVersion) { List urls = new ArrayList<>(); for (ForgeVersion.File file : version.getFiles()) if ("installer".equals(file.getCategory()) && "jar".equals(file.getFormat())) { - String classifier = gameVersion + "-" + version.getVersion() - + (StringUtils.isNotBlank(version.getBranch()) ? "-" + version.getBranch() : ""); + String branch = toLookupBranch(gameVersion, version.getBranch()); + + String classifier = lookupVersion + "-" + version.getVersion() + (branch.isEmpty() ? "" : '-' + branch); String fileName1 = "forge-" + classifier + "-" + file.getCategory() + "." + file.getFormat(); - String fileName2 = "forge-" + classifier + "-" + gameVersion + "-" + file.getCategory() + "." + file.getFormat(); + String fileName2 = "forge-" + classifier + "-" + lookupVersion + "-" + file.getCategory() + "." + file.getFormat(); urls.add("https://files.minecraftforge.net/maven/net/minecraftforge/forge/" + classifier + "/" + fileName1); - urls.add("https://files.minecraftforge.net/maven/net/minecraftforge/forge/" + classifier + "-" + gameVersion + "/" + fileName2); + urls.add("https://files.minecraftforge.net/maven/net/minecraftforge/forge/" + classifier + "-" + lookupVersion + "/" + fileName2); urls.add(NetworkUtils.withQuery("https://bmclapi2.bangbang93.com/forge/download", mapOf( pair("mcversion", version.getGameVersion()), pair("version", version.getVersion()), - pair("branch", version.getBranch()), + pair("branch", branch), pair("category", file.getCategory()), pair("format", file.getFormat()) ))); @@ -108,7 +128,7 @@ public CompletableFuture refreshAsync(String gameVersion) { } versions.put(gameVersion, new ForgeRemoteVersion( - version.getGameVersion(), version.getVersion(), releaseDate, urls)); + fromLookupVersion(version.getGameVersion()), version.getVersion(), releaseDate, urls)); } } finally { lock.writeLock().unlock(); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java index 4c63f7e9c2..b2b626e0fa 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java @@ -40,7 +40,7 @@ import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.platform.CommandBuilder; -import org.jackhuang.hmcl.util.platform.JavaVersion; +import org.jackhuang.hmcl.java.JavaRuntime; import org.jackhuang.hmcl.util.platform.OperatingSystem; import org.jackhuang.hmcl.util.platform.SystemUtils; import org.jetbrains.annotations.NotNull; @@ -129,7 +129,7 @@ public void execute() throws Exception { throw new Exception("Game processor jar does not have main class " + jar); List command = new ArrayList<>(); - command.add(JavaVersion.fromCurrentEnvironment().getBinary().toString()); + command.add(JavaRuntime.getDefault().getBinary().toString()); command.add("-cp"); List classpath = new ArrayList<>(processor.getClasspath().size() + 1); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersion.java index 429f61d371..a7bb771935 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersion.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersion.java @@ -33,7 +33,7 @@ public final class ForgeVersion implements Validation { private final String jobver; private final String version; private final int build; - private final double modified; + private final long modified; private final String[][] files; /** @@ -44,7 +44,7 @@ public ForgeVersion() { this(null, null, null, null, 0, 0, null); } - public ForgeVersion(String branch, String mcversion, String jobver, String version, int build, double modified, String[][] files) { + public ForgeVersion(String branch, String mcversion, String jobver, String version, int build, long modified, String[][] files) { this.branch = branch; this.mcversion = mcversion; this.jobver = jobver; @@ -74,7 +74,7 @@ public int getBuild() { return build; } - public double getModified() { + public long getModified() { return modified; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionList.java index fa9a558133..acb97fef3c 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionList.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionList.java @@ -23,6 +23,7 @@ import org.jackhuang.hmcl.util.io.HttpRequest; import org.jackhuang.hmcl.util.versioning.VersionNumber; +import java.time.Instant; import java.util.Collections; import java.util.Map; import java.util.concurrent.CompletableFuture; @@ -43,9 +44,17 @@ public boolean hasType() { return false; } + private static String toLookupVersion(String gameVersion) { + return "1.7.10-pre4".equals(gameVersion) ? "1.7.10_pre4" : gameVersion; + } + + private static String fromLookupVersion(String lookupVersion) { + return "1.7.10_pre4".equals(lookupVersion) ? "1.7.10-pre4" : lookupVersion; + } + @Override public CompletableFuture refreshAsync() { - return HttpRequest.GET(downloadProvider.injectURL(FORGE_LIST)).getJsonAsync(ForgeVersionRoot.class) + return HttpRequest.GET(FORGE_LIST).getJsonAsync(ForgeVersionRoot.class) .thenAcceptAsync(root -> { lock.writeLock().lock(); @@ -55,7 +64,7 @@ public CompletableFuture refreshAsync() { versions.clear(); for (Map.Entry entry : root.getGameVersions().entrySet()) { - String gameVersion = VersionNumber.normalize(entry.getKey()); + String gameVersion = fromLookupVersion(VersionNumber.normalize(entry.getKey())); for (int v : entry.getValue()) { ForgeVersion version = root.getNumber().get(v); if (version == null) @@ -71,8 +80,12 @@ public CompletableFuture refreshAsync() { if (jar == null) continue; + versions.put(gameVersion, new ForgeRemoteVersion( - version.getGameVersion(), version.getVersion(), null, Collections.singletonList(jar) + toLookupVersion(version.getGameVersion()), + version.getVersion(), + version.getModified() > 0 ? Instant.ofEpochSecond(version.getModified()) : null, + Collections.singletonList(jar) )); } } @@ -82,5 +95,5 @@ public CompletableFuture refreshAsync() { }); } - public static final String FORGE_LIST = "https://files.minecraftforge.net/maven/net/minecraftforge/forge/json"; + public static final String FORGE_LIST = "https://hmcl-dev.github.io/metadata/forge/"; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameLibrariesTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameLibrariesTask.java index 793a6b1223..f177e13482 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameLibrariesTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameLibrariesTask.java @@ -134,7 +134,7 @@ public void execute() throws IOException { } } } - if (shouldDownloadLibrary(gameRepository, version, library, integrityCheck) && library.hasDownloadURL()) { + if (shouldDownloadLibrary(gameRepository, version, library, integrityCheck) && (library.hasDownloadURL() || !"optifine".equals(library.getGroupId()))) { dependencies.add(new LibraryDownloadTask(dependencyManager, file, library)); } else { dependencyManager.getCacheRepository().tryCacheLibrary(library, file.toPath()); diff --git a/HMCL/src/main/resources/assets/css/custom.css b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaDistribution.java similarity index 55% rename from HMCL/src/main/resources/assets/css/custom.css rename to HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaDistribution.java index 72c29e9aca..96d02c95cc 100644 --- a/HMCL/src/main/resources/assets/css/custom.css +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaDistribution.java @@ -1,6 +1,6 @@ -/** +/* * Hello Minecraft! Launcher - * Copyright (C) 2020 huangyuhui and contributors + * Copyright (C) 2024 huangyuhui and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,16 +15,15 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -.root { - -fx-base-color: %base-color%; - -fx-base-darker-color: derive(-fx-base-color, -10%); - -fx-base-check-color: derive(-fx-base-color, 30%); - -fx-rippler-color: rgba(%base-red%, %base-green%, %base-blue%, 0.3); - -fx-base-rippler-color: derive(%base-rippler-color%, 100%); - -fx-base-disabled-text-fill: %disabled-font-color%; - -fx-base-text-fill: %font-color%; +package org.jackhuang.hmcl.download.java; + +import java.util.Set; - -theme-thumb: rgba(%base-red%, %base-green%, %base-blue%, 0.7); +/** + * @author Glavo + */ +public interface JavaDistribution { + String getDisplayName(); - %font% -} \ No newline at end of file + Set getSupportedPackageTypes(); +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaPackageType.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaPackageType.java new file mode 100644 index 0000000000..8fcf4d3739 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaPackageType.java @@ -0,0 +1,51 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.download.java; + +/** + * @author Glavo + */ +public enum JavaPackageType { + JRE(false, false), + JDK(true, false), + JREFX(false, true), + JDKFX(true, true); + + private final boolean jdk; + private final boolean javafx; + + JavaPackageType(boolean jdk, boolean javafx) { + this.jdk = jdk; + this.javafx = javafx; + } + + public static JavaPackageType of(boolean jdk, boolean javafx) { + if (jdk) + return javafx ? JDKFX : JDK; + else + return javafx ? JREFX : JRE; + } + + public boolean isJDK() { + return jdk; + } + + public boolean isJavaFXBundled() { + return javafx; + } +} diff --git a/HMCLCore/src/test/java/org/jackhuang/hmcl/game/JavaVersionConstraintTest.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaRemoteVersion.java similarity index 50% rename from HMCLCore/src/test/java/org/jackhuang/hmcl/game/JavaVersionConstraintTest.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaRemoteVersion.java index ddc1ddfe23..fec207b592 100644 --- a/HMCLCore/src/test/java/org/jackhuang/hmcl/game/JavaVersionConstraintTest.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaRemoteVersion.java @@ -1,6 +1,6 @@ /* * Hello Minecraft! Launcher - * Copyright (C) 2021 huangyuhui and contributors + * Copyright (C) 2024 huangyuhui and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,23 +15,15 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.jackhuang.hmcl.game; +package org.jackhuang.hmcl.download.java; -import org.jackhuang.hmcl.util.versioning.GameVersionNumber; -import org.jackhuang.hmcl.util.versioning.VersionNumber; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -public class JavaVersionConstraintTest { +/** + * @author Glavo + */ +public interface JavaRemoteVersion { + int getJdkVersion(); - @Test - public void vanillaJava16() { - JavaVersionConstraint.VersionRanges range = JavaVersionConstraint.findSuitableJavaVersionRange( - GameVersionNumber.asGameVersion("1.17"), - null - ); + String getJavaVersion(); - assertEquals(VersionNumber.atLeast("16"), range.getMandatory()); - } + String getDistributionVersion(); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaRepository.java deleted file mode 100644 index 10f08fdceb..0000000000 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaRepository.java +++ /dev/null @@ -1,168 +0,0 @@ -package org.jackhuang.hmcl.download.java; - -import org.jackhuang.hmcl.download.DownloadProvider; -import org.jackhuang.hmcl.game.GameJavaVersion; -import org.jackhuang.hmcl.task.Task; -import org.jackhuang.hmcl.util.CacheRepository; -import org.jackhuang.hmcl.util.io.FileUtils; -import org.jackhuang.hmcl.util.platform.Architecture; -import org.jackhuang.hmcl.util.platform.JavaVersion; -import org.jackhuang.hmcl.util.platform.OperatingSystem; -import org.jackhuang.hmcl.util.platform.Platform; - -import java.io.BufferedReader; -import java.io.IOException; -import java.nio.file.*; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.function.Consumer; -import java.util.stream.Stream; - -import static org.jackhuang.hmcl.util.logging.Logger.LOG; - -public final class JavaRepository { - private JavaRepository() { - } - - public static Task downloadJava(GameJavaVersion javaVersion, DownloadProvider downloadProvider) { - return new JavaDownloadTask(javaVersion, getJavaStoragePath(), downloadProvider) - .thenSupplyAsync(() -> { - String platform = getSystemJavaPlatform().orElseThrow(JavaDownloadTask.UnsupportedPlatformException::new); - return addJava(getJavaHome(javaVersion, platform)); - }); - } - - public static JavaVersion addJava(Path javaHome) throws InterruptedException, IOException { - if (Files.isDirectory(javaHome)) { - Path executable = JavaVersion.getExecutable(javaHome); - if (Files.isRegularFile(executable)) { - JavaVersion javaVersion = JavaVersion.fromExecutable(executable); - JavaVersion.getJavas().add(javaVersion); - return javaVersion; - } - } - - throw new IOException("Incorrect java home " + javaHome); - } - - public static Stream> findMinecraftRuntimeDirs() { - switch (OperatingSystem.CURRENT_OS) { - case WINDOWS: - return Stream.of( - FileUtils.tryGetPath(System.getenv("localappdata"), - "Packages\\Microsoft.4297127D64EC6_8wekyb3d8bbwe\\LocalCache\\Local\\runtime"), - FileUtils.tryGetPath( - Optional.ofNullable(System.getenv("ProgramFiles(x86)")).orElse("C:\\Program Files (x86)"), - "Minecraft Launcher\\runtime")); - case LINUX: - case FREEBSD: - return Stream.of(FileUtils.tryGetPath(System.getProperty("user.home", ".minecraft/runtime"))); - case OSX: - return Stream.of(FileUtils.tryGetPath(System.getProperty("user.home"), "Library/Application Support/minecraft/runtime")); - default: - return Stream.empty(); - } - } - - public static Stream findJavaHomeInMinecraftRuntimeDir(Path runtimeDir) { - if (!Files.isDirectory(runtimeDir)) - return Stream.empty(); - // Examples: - // $HOME/Library/Application Support/minecraft/runtime/java-runtime-beta/mac-os/java-runtime-beta/jre.bundle/Contents/Home - // $HOME/.minecraft/runtime/java-runtime-beta/linux/java-runtime-beta - List javaHomes = new ArrayList<>(); - Consumer action = platform -> { - try (DirectoryStream dir = Files.newDirectoryStream(runtimeDir)) { - // component can be jre-legacy, java-runtime-alpha, java-runtime-beta, java-runtime-gamma or any other being added in the future. - for (Path component : dir) { - findJavaHomeInComponentDir(platform, component).ifPresent(javaHomes::add); - } - } catch (IOException e) { - LOG.warning("Failed to list java-runtime directory " + runtimeDir, e); - } - }; - getSystemJavaPlatform().ifPresent(action); - - // Workaround, which will be removed in the future - if (Platform.SYSTEM_PLATFORM == Platform.OSX_ARM64) - action.accept("mac-os-arm64"); - - return javaHomes.stream(); - } - - private static Optional findJavaHomeInComponentDir(String platform, Path component) { - Path sha1File = component.resolve(platform).resolve(component.getFileName() + ".sha1"); - if (!Files.isRegularFile(sha1File)) - return Optional.empty(); - Path dir = component.resolve(platform).resolve(component.getFileName()); - - try (BufferedReader reader = Files.newBufferedReader(sha1File)) { - String line; - while ((line = reader.readLine()) != null) { - if (line.isEmpty()) continue; - - int idx = line.indexOf(" /#//"); - if (idx <= 0) - throw new IOException("Illegal line: " + line); - - Path file = dir.resolve(line.substring(0, idx)); - - // Should we check the sha1 of files? This will take a lot of time. - if (Files.notExists(file)) - throw new NoSuchFileException(file.toAbsolutePath().toString()); - } - - if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX) { - Path macPath = dir.resolve("jre.bundle/Contents/Home"); - if (Files.exists(macPath)) - return Optional.of(macPath); - else - LOG.warning("The Java is not in 'jre.bundle/Contents/Home'"); - } - - return Optional.of(dir); - } catch (IOException e) { - LOG.warning("Failed to verify Java in " + component, e); - return Optional.empty(); - } - } - - public static Optional getSystemJavaPlatform() { - if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX) { - if (Architecture.SYSTEM_ARCH == Architecture.X86) { - return Optional.of("linux-i386"); - } else if (Architecture.SYSTEM_ARCH == Architecture.X86_64) { - return Optional.of("linux"); - } - } else if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX) { - if (Architecture.SYSTEM_ARCH == Architecture.X86_64 || Architecture.SYSTEM_ARCH == Architecture.ARM64) { - return Optional.of("mac-os"); - } - } else if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) { - if (Architecture.SYSTEM_ARCH == Architecture.X86) { - return Optional.of("windows-x86"); - } else if (Architecture.SYSTEM_ARCH == Architecture.X86_64) { - return Optional.of("windows-x64"); - } else if (Architecture.SYSTEM_ARCH == Architecture.ARM64) { - if (OperatingSystem.SYSTEM_BUILD_NUMBER >= 21277) { - return Optional.of("windows-x64"); - } else { - return Optional.of("windows-x86"); - } - } - } - return Optional.empty(); - } - - public static Path getJavaStoragePath() { - return CacheRepository.getInstance().getCacheDirectory().resolve("java"); - } - - public static Path getJavaHome(GameJavaVersion javaVersion, String platform) { - Path javaHome = getJavaStoragePath().resolve(javaVersion.getComponent()).resolve(platform).resolve(javaVersion.getComponent()); - if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX) - javaHome = javaHome.resolve("jre.bundle/Contents/Home"); - return javaHome; - } -} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/disco/DiscoFetchJavaListTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/disco/DiscoFetchJavaListTask.java new file mode 100644 index 0000000000..df178fe576 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/disco/DiscoFetchJavaListTask.java @@ -0,0 +1,100 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.download.java.disco; + +import org.jackhuang.hmcl.download.DownloadProvider; +import org.jackhuang.hmcl.download.java.JavaPackageType; +import org.jackhuang.hmcl.task.GetTask; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.gson.JsonUtils; +import org.jackhuang.hmcl.util.io.NetworkUtils; +import 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.versioning.VersionNumber; + +import java.util.*; + +/** + * @author Glavo + */ +public final class DiscoFetchJavaListTask extends Task>> { + + public static final String API_ROOT = System.getProperty("hmcl.discoapi.override", "https://api.foojay.io/disco/v3.0"); + + private static String getOperatingSystemName(OperatingSystem os) { + return os == OperatingSystem.OSX ? "macos" : os.getCheckedName(); + } + + private static String getArchitectureName(Architecture arch) { + return arch.getCheckedName(); + } + + private final DiscoJavaDistribution distribution; + private final String archiveType; + private final Task fetchPackagesTask; + + public DiscoFetchJavaListTask(DownloadProvider downloadProvider, DiscoJavaDistribution distribution, Platform platform) { + this.distribution = distribution; + this.archiveType = platform.getOperatingSystem() == OperatingSystem.WINDOWS ? "zip" : "tar.gz"; + + HashMap params = new HashMap<>(); + params.put("distribution", distribution.getApiParameter()); + params.put("operating_system", getOperatingSystemName(platform.getOperatingSystem())); + params.put("architecture", getArchitectureName(platform.getArchitecture())); + params.put("archive_type", archiveType); + params.put("directly_downloadable", "true"); + if (platform.getOperatingSystem() == OperatingSystem.LINUX) + params.put("lib_c_type", "glibc"); + + this.fetchPackagesTask = new GetTask(downloadProvider.injectURLWithCandidates(NetworkUtils.withQuery(API_ROOT + "/packages", params))); + } + + @Override + public Collection> getDependents() { + return Collections.singleton(fetchPackagesTask); + } + + @Override + public void execute() throws Exception { + String json = fetchPackagesTask.getResult(); + List list = JsonUtils.fromNonNullJson(json, DiscoResult.typeOf(DiscoJavaRemoteVersion.class)).getResult(); + EnumMap> result = new EnumMap<>(JavaPackageType.class); + + for (DiscoJavaRemoteVersion version : list) { + if (!distribution.getApiParameter().equals(version.getDistribution()) + || !version.isDirectlyDownloadable() + || !archiveType.equals(version.getArchiveType())) + continue; + + if (!distribution.testVersion(version)) + continue; + + JavaPackageType packageType = JavaPackageType.of("jdk".equals(version.getPackageType()), version.isJavaFXBundled()); + TreeMap map = result.computeIfAbsent(packageType, ignored -> new TreeMap<>()); + + int jdkVersion = version.getJdkVersion(); + DiscoJavaRemoteVersion oldVersion = map.get(jdkVersion); + if (oldVersion == null || VersionNumber.compare(version.getDistributionVersion(), oldVersion.getDistributionVersion()) > 0) + map.put(jdkVersion, version); + } + + setResult(result); + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/disco/DiscoJavaDistribution.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/disco/DiscoJavaDistribution.java new file mode 100644 index 0000000000..5a9c696beb --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/disco/DiscoJavaDistribution.java @@ -0,0 +1,135 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.download.java.disco; + +import org.jackhuang.hmcl.download.java.JavaDistribution; +import org.jackhuang.hmcl.download.java.JavaPackageType; +import org.jackhuang.hmcl.util.Pair; +import org.jackhuang.hmcl.util.platform.Architecture; +import org.jackhuang.hmcl.util.platform.OperatingSystem; +import org.jackhuang.hmcl.util.platform.Platform; + +import java.util.*; + +import static org.jackhuang.hmcl.download.java.JavaPackageType.*; +import static org.jackhuang.hmcl.util.Pair.pair; +import static org.jackhuang.hmcl.util.platform.Architecture.*; +import static org.jackhuang.hmcl.util.platform.OperatingSystem.*; + +/** + * @author Glavo + * @see discoapi + */ +public enum DiscoJavaDistribution implements JavaDistribution { + TEMURIN("Eclipse Temurin", "temurin", "Adoptium", + EnumSet.of(JDK, JRE), + pair(WINDOWS, EnumSet.of(X86_64, X86, ARM64)), + pair(LINUX, EnumSet.of(X86_64, X86, ARM64, ARM32, RISCV64, PPC64, PPC64LE, S390X, SPARCV9)), + pair(OSX, EnumSet.of(X86_64, ARM64))), + LIBERICA("BellSoft Liberica", "liberica", "BellSoft", + EnumSet.of(JDK, JRE, JDKFX, JREFX), + pair(WINDOWS, EnumSet.of(X86_64, X86, ARM64)), + pair(LINUX, EnumSet.of(X86_64, X86, ARM64, ARM32, RISCV64, PPC64LE)), + pair(OSX, EnumSet.of(X86_64, ARM64))) { + @Override + public boolean testVersion(DiscoJavaRemoteVersion version) { + if (!super.testVersion(version)) + return false; + + String fileName = version.getFileName(); + return !fileName.endsWith("-lite.tar.gz") && !fileName.endsWith("-lite.zip"); + } + }, + ZULU("Azul Zulu", "zulu", "Azul", + EnumSet.of(JDK, JRE, JDKFX, JREFX), + pair(WINDOWS, EnumSet.of(X86_64, X86, ARM64)), + pair(LINUX, EnumSet.of(X86_64, X86, ARM64, ARM32, RISCV64, PPC64LE)), + pair(OSX, EnumSet.of(X86_64, ARM64))), + GRAALVM("Oracle GraalVM", "graalvm", "Oracle", + EnumSet.of(JDK), + pair(WINDOWS, EnumSet.of(X86_64)), + pair(LINUX, EnumSet.of(X86_64, ARM64)), + pair(OSX, EnumSet.of(X86_64, ARM64))), + SEMERU("IBM Semeru (OpenJ9)", "semeru", "IBM", + EnumSet.of(JDK, JRE), + pair(WINDOWS, EnumSet.of(X86_64)), + pair(LINUX, EnumSet.of(X86_64, ARM64, PPC64LE, S390X)), + pair(OSX, EnumSet.of(X86_64, ARM64)) + ), + CORRETTO("Amazon Corretto", "corretto", "Amazon", + EnumSet.of(JDK), + pair(WINDOWS, EnumSet.of(X86_64)), + pair(LINUX, EnumSet.of(X86_64, ARM64)), + pair(OSX, EnumSet.of(X86_64, ARM64)) + ); + + public static DiscoJavaDistribution of(String name) { + for (DiscoJavaDistribution distribution : values()) { + if (distribution.apiParameter.equalsIgnoreCase(name) || distribution.name().equalsIgnoreCase(name)) { + return distribution; + } + } + + return null; + } + + private final String displayName; + private final String apiParameter; + private final String vendor; + private final Set supportedPackageTypes; + private final Map> supportedPlatforms = new EnumMap<>(OperatingSystem.class); + + @SafeVarargs + DiscoJavaDistribution(String displayName, String apiParameter, String vendor, Set supportedPackageTypes, Pair>... supportedPlatforms) { + this.displayName = displayName; + this.apiParameter = apiParameter; + this.vendor = vendor; + this.supportedPackageTypes = supportedPackageTypes; + + for (Pair> platform : supportedPlatforms) { + this.supportedPlatforms.put(platform.getKey(), platform.getValue()); + } + } + + @Override + public String getDisplayName() { + return displayName; + } + + public String getApiParameter() { + return apiParameter; + } + + public String getVendor() { + return vendor; + } + + @Override + public Set getSupportedPackageTypes() { + return supportedPackageTypes; + } + + public boolean isSupport(Platform platform) { + EnumSet architectures = supportedPlatforms.get(platform.getOperatingSystem()); + return architectures != null && architectures.contains(platform.getArchitecture()); + } + + public boolean testVersion(DiscoJavaRemoteVersion version) { + return this.getApiParameter().equals(version.getDistribution()); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/disco/DiscoJavaRemoteVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/disco/DiscoJavaRemoteVersion.java new file mode 100644 index 0000000000..ca65643db7 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/disco/DiscoJavaRemoteVersion.java @@ -0,0 +1,263 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.download.java.disco; + +import com.google.gson.annotations.SerializedName; +import org.jackhuang.hmcl.download.java.JavaRemoteVersion; +import org.jackhuang.hmcl.util.gson.JsonUtils; + +/** + * @author Glavo + */ +public final class DiscoJavaRemoteVersion implements JavaRemoteVersion { + @SerializedName("id") + private final String id; + + @SerializedName("archive_type") + private final String archiveType; + + @SerializedName("distribution") + private final String distribution; + + @SerializedName("major_version") + private final int majorVersion; + + @SerializedName("java_version") + private final String javaVersion; + + @SerializedName("distribution_version") + private final String distributionVersion; + + @SerializedName("jdk_version") + private final int jdkVersion; + + @SerializedName("latest_build_available") + private final boolean latestBuildAvailable; + + @SerializedName("release_status") + private final String releaseStatus; + + @SerializedName("term_of_support") + private final String termOfSupport; + + @SerializedName("operating_system") + private final String operatingSystem; + + @SerializedName("lib_c_type") + private final String libCType; + + @SerializedName("architecture") + private final String architecture; + + @SerializedName("fpu") + private final String fpu; + + @SerializedName("package_type") + private final String packageType; + + @SerializedName("javafx_bundled") + private final boolean javafxBundled; + + @SerializedName("directly_downloadable") + private final boolean directlyDownloadable; + + @SerializedName("filename") + private final String fileName; + + @SerializedName("links") + private final Links links; + + @SerializedName("free_use_in_production") + private final boolean freeUseInProduction; + + @SerializedName("tck_tested") + private final String tckTested; + + @SerializedName("tck_cert_uri") + private final String tckCertUri; + + @SerializedName("aqavit_certified") + private final String aqavitCertified; + + @SerializedName("aqavit_cert_uri") + private final String aqavitCertUri; + + @SerializedName("size") + private final long size; + + public DiscoJavaRemoteVersion(String id, String archiveType, String distribution, int majorVersion, String javaVersion, String distributionVersion, int jdkVersion, boolean latestBuildAvailable, String releaseStatus, String termOfSupport, String operatingSystem, String libCType, String architecture, String fpu, String packageType, boolean javafxBundled, boolean directlyDownloadable, String fileName, Links links, boolean freeUseInProduction, String tckTested, String tckCertUri, String aqavitCertified, String aqavitCertUri, long size) { + this.id = id; + this.archiveType = archiveType; + this.distribution = distribution; + this.majorVersion = majorVersion; + this.javaVersion = javaVersion; + this.distributionVersion = distributionVersion; + this.jdkVersion = jdkVersion; + this.latestBuildAvailable = latestBuildAvailable; + this.releaseStatus = releaseStatus; + this.termOfSupport = termOfSupport; + this.operatingSystem = operatingSystem; + this.libCType = libCType; + this.architecture = architecture; + this.fpu = fpu; + this.packageType = packageType; + this.javafxBundled = javafxBundled; + this.directlyDownloadable = directlyDownloadable; + this.fileName = fileName; + this.links = links; + this.freeUseInProduction = freeUseInProduction; + this.tckTested = tckTested; + this.tckCertUri = tckCertUri; + this.aqavitCertified = aqavitCertified; + this.aqavitCertUri = aqavitCertUri; + this.size = size; + } + + public String getId() { + return id; + } + + public String getArchiveType() { + return archiveType; + } + + public String getDistribution() { + return distribution; + } + + public int getMajorVersion() { + return majorVersion; + } + + @Override + public String getJavaVersion() { + return javaVersion; + } + + @Override + public String getDistributionVersion() { + return distributionVersion; + } + + @Override + public int getJdkVersion() { + return jdkVersion; + } + + public boolean isLatestBuildAvailable() { + return latestBuildAvailable; + } + + public String getReleaseStatus() { + return releaseStatus; + } + + public String getTermOfSupport() { + return termOfSupport; + } + + public boolean isLTS() { + return "lts".equals(termOfSupport); + } + + public String getOperatingSystem() { + return operatingSystem; + } + + public String getLibCType() { + return libCType; + } + + public String getArchitecture() { + return architecture; + } + + public String getFpu() { + return fpu; + } + + public String getPackageType() { + return packageType; + } + + public boolean isJavaFXBundled() { + return javafxBundled; + } + + public boolean isDirectlyDownloadable() { + return directlyDownloadable; + } + + public String getFileName() { + return fileName; + } + + public Links getLinks() { + return links; + } + + public boolean isFreeUseInProduction() { + return freeUseInProduction; + } + + public String getTckTested() { + return tckTested; + } + + public String getTckCertUri() { + return tckCertUri; + } + + public String getAqavitCertified() { + return aqavitCertified; + } + + public String getAqavitCertUri() { + return aqavitCertUri; + } + + public long getSize() { + return size; + } + + @Override + public String toString() { + return "DiscoJavaRemoteVersion " + JsonUtils.GSON.toJson(this); + } + + public static final class Links { + @SerializedName("pkg_info_uri") + private final String pkgInfoUri; + + @SerializedName("pkg_download_redirect") + private final String pkgDownloadRedirect; + + public Links(String pkgInfoUri, String pkgDownloadRedirect) { + this.pkgInfoUri = pkgInfoUri; + this.pkgDownloadRedirect = pkgDownloadRedirect; + } + + public String getPkgInfoUri() { + return pkgInfoUri; + } + + public String getPkgDownloadRedirect() { + return pkgDownloadRedirect; + } + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/disco/DiscoRemoteFileInfo.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/disco/DiscoRemoteFileInfo.java new file mode 100644 index 0000000000..2b31610f01 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/disco/DiscoRemoteFileInfo.java @@ -0,0 +1,68 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.download.java.disco; + +import com.google.gson.annotations.SerializedName; + +/** + * @author Glavo + */ +public final class DiscoRemoteFileInfo { + @SerializedName("filename") + private final String fileName; + + @SerializedName("direct_download_uri") + private final String directDownloadUri; + + @SerializedName("checksum_type") + private final String checksumType; + + @SerializedName("checksum") + private final String checksum; + + @SerializedName("checksum_uri") + private final String checksumUri; + + public DiscoRemoteFileInfo(String fileName, String directDownloadUri, String checksumType, String checksum, String checksumUri) { + this.fileName = fileName; + this.directDownloadUri = directDownloadUri; + this.checksumType = checksumType; + this.checksum = checksum; + this.checksumUri = checksumUri; + } + + public String getFileName() { + return fileName; + } + + public String getDirectDownloadUri() { + return directDownloadUri; + } + + public String getChecksumType() { + return checksumType; + } + + public String getChecksum() { + return checksum; + } + + public String getChecksumUri() { + return checksumUri; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/disco/DiscoResult.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/disco/DiscoResult.java new file mode 100644 index 0000000000..2d40f276ec --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/disco/DiscoResult.java @@ -0,0 +1,49 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.download.java.disco; + +import com.google.gson.reflect.TypeToken; + +import java.util.List; + +/** + * @author Glavo + */ +public final class DiscoResult { + + @SuppressWarnings("unchecked") + public static TypeToken> typeOf(Class argType) { + return (TypeToken>) TypeToken.getParameterized(DiscoResult.class, argType); + } + + private final List result; + private final String message; + + private DiscoResult(List result, String message) { + this.result = result; + this.message = message; + } + + public List getResult() { + return result; + } + + public String getMessage() { + return message; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/mojang/MojangJavaDistribution.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/mojang/MojangJavaDistribution.java new file mode 100644 index 0000000000..3f257264fd --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/mojang/MojangJavaDistribution.java @@ -0,0 +1,46 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.download.java.mojang; + +import org.jackhuang.hmcl.download.java.JavaDistribution; +import org.jackhuang.hmcl.download.java.JavaPackageType; +import org.jackhuang.hmcl.download.java.JavaRemoteVersion; + +import java.util.Collections; +import java.util.Set; + +/** + * @author Glavo + */ +public final class MojangJavaDistribution implements JavaDistribution { + + public static final MojangJavaDistribution DISTRIBUTION = new MojangJavaDistribution(); + + private MojangJavaDistribution() { + } + + @Override + public String getDisplayName() { + return "Mojang"; + } + + @Override + public Set getSupportedPackageTypes() { + return Collections.singleton(JavaPackageType.JRE); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaDownloadTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/mojang/MojangJavaDownloadTask.java similarity index 61% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaDownloadTask.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/mojang/MojangJavaDownloadTask.java index 9742ac1065..a37ad844d8 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaDownloadTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/mojang/MojangJavaDownloadTask.java @@ -15,20 +15,19 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.jackhuang.hmcl.download.java; +package org.jackhuang.hmcl.download.java.mojang; import org.jackhuang.hmcl.download.ArtifactMalformedException; import org.jackhuang.hmcl.download.DownloadProvider; import org.jackhuang.hmcl.game.DownloadInfo; import org.jackhuang.hmcl.game.GameJavaVersion; +import org.jackhuang.hmcl.java.*; import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.task.GetTask; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.io.ChecksumMismatchException; -import org.jackhuang.hmcl.util.io.FileUtils; -import org.jackhuang.hmcl.util.platform.OperatingSystem; -import org.jackhuang.hmcl.util.versioning.VersionNumber; +import org.jackhuang.hmcl.util.platform.UnsupportedPlatformException; import org.tukaani.xz.LZMAInputStream; import java.io.File; @@ -40,50 +39,39 @@ import java.nio.file.StandardCopyOption; import java.nio.file.attribute.BasicFileAttributes; import java.util.*; -import java.util.stream.Collectors; import static org.jackhuang.hmcl.util.logging.Logger.LOG; -public class JavaDownloadTask extends Task { - private final GameJavaVersion javaVersion; - private final Path rootDir; - private String platform; - private final Task javaDownloadsTask; - private JavaDownloads.JavaDownload download; - private final List> dependencies = new ArrayList<>(); +public final class MojangJavaDownloadTask extends Task { + private final DownloadProvider downloadProvider; + private final Path target; + private final Task javaDownloadsTask; + private final List> dependencies = new ArrayList<>(); + + private volatile MojangJavaDownloads.JavaDownload download; - public JavaDownloadTask(GameJavaVersion javaVersion, Path rootDir, DownloadProvider downloadProvider) { - this.javaVersion = javaVersion; - this.rootDir = rootDir; + public MojangJavaDownloadTask(DownloadProvider downloadProvider, Path target, GameJavaVersion javaVersion, String platform) { + this.target = target; this.downloadProvider = downloadProvider; this.javaDownloadsTask = new GetTask(downloadProvider.injectURLWithCandidates( "https://piston-meta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json")) .thenComposeAsync(javaDownloadsJson -> { - JavaDownloads allDownloads = JsonUtils.fromNonNullJson(javaDownloadsJson, JavaDownloads.class); - if (!allDownloads.getDownloads().containsKey(platform)) throw new UnsupportedPlatformException(); - Map> osDownloads = allDownloads.getDownloads().get(platform); - if (!osDownloads.containsKey(javaVersion.getComponent())) throw new UnsupportedPlatformException(); - List candidates = osDownloads.get(javaVersion.getComponent()); - for (JavaDownloads.JavaDownload download : candidates) { - if (VersionNumber.compare(download.getVersion().getName(), Integer.toString(javaVersion.getMajorVersion())) >= 0) { + MojangJavaDownloads allDownloads = JsonUtils.fromNonNullJson(javaDownloadsJson, MojangJavaDownloads.class); + + Map> osDownloads = allDownloads.getDownloads().get(platform); + if (osDownloads == null || !osDownloads.containsKey(javaVersion.getComponent())) + throw new UnsupportedPlatformException("Unsupported platform: " + platform); + List candidates = osDownloads.get(javaVersion.getComponent()); + for (MojangJavaDownloads.JavaDownload download : candidates) { + if (JavaInfo.parseVersion(download.getVersion().getName()) >= javaVersion.getMajorVersion()) { this.download = download; return new GetTask(downloadProvider.injectURLWithCandidates(download.getManifest().getUrl())); } } - throw new UnsupportedPlatformException(); + throw new UnsupportedPlatformException("Candidates: " + JsonUtils.GSON.toJson(candidates)); }) - .thenApplyAsync(javaDownloadJson -> JsonUtils.fromNonNullJson(javaDownloadJson, RemoteFiles.class)); - } - - @Override - public boolean doPreExecute() { - return true; - } - - @Override - public void preExecute() throws Exception { - this.platform = JavaRepository.getSystemJavaPlatform().orElseThrow(UnsupportedPlatformException::new); + .thenApplyAsync(javaDownloadJson -> JsonUtils.fromNonNullJson(javaDownloadJson, MojangJavaRemoteFiles.class)); } @Override @@ -93,11 +81,10 @@ public Collection> getDependents() { @Override public void execute() throws Exception { - Path jvmDir = rootDir.resolve(javaVersion.getComponent()).resolve(platform).resolve(javaVersion.getComponent()); - for (Map.Entry entry : javaDownloadsTask.getResult().getFiles().entrySet()) { - Path dest = jvmDir.resolve(entry.getKey()); - if (entry.getValue() instanceof RemoteFiles.RemoteFile) { - RemoteFiles.RemoteFile file = ((RemoteFiles.RemoteFile) entry.getValue()); + for (Map.Entry entry : javaDownloadsTask.getResult().getFiles().entrySet()) { + Path dest = target.resolve(entry.getKey()); + if (entry.getValue() instanceof MojangJavaRemoteFiles.RemoteFile) { + MojangJavaRemoteFiles.RemoteFile file = ((MojangJavaRemoteFiles.RemoteFile) entry.getValue()); // Use local file if it already exists try { @@ -115,11 +102,11 @@ public void execute() throws Exception { if (file.getDownloads().containsKey("lzma")) { DownloadInfo download = file.getDownloads().get("lzma"); - File tempFile = jvmDir.resolve(entry.getKey() + ".lzma").toFile(); + File tempFile = target.resolve(entry.getKey() + ".lzma").toFile(); FileDownloadTask task = new FileDownloadTask(downloadProvider.injectURLWithCandidates(download.getUrl()), tempFile, new FileDownloadTask.IntegrityCheck("SHA-1", download.getSha1())); task.setName(entry.getKey()); dependencies.add(task.thenRunAsync(() -> { - Path decompressed = jvmDir.resolve(entry.getKey() + ".tmp"); + Path decompressed = target.resolve(entry.getKey() + ".tmp"); try (LZMAInputStream input = new LZMAInputStream(new FileInputStream(tempFile))) { Files.copy(input, decompressed, StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { @@ -144,10 +131,10 @@ public void execute() throws Exception { } else { continue; } - } else if (entry.getValue() instanceof RemoteFiles.RemoteDirectory) { + } else if (entry.getValue() instanceof MojangJavaRemoteFiles.RemoteDirectory) { Files.createDirectories(dest); - } else if (entry.getValue() instanceof RemoteFiles.RemoteLink) { - RemoteFiles.RemoteLink link = ((RemoteFiles.RemoteLink) entry.getValue()); + } else if (entry.getValue() instanceof MojangJavaRemoteFiles.RemoteLink) { + MojangJavaRemoteFiles.RemoteLink link = ((MojangJavaRemoteFiles.RemoteLink) entry.getValue()); Files.deleteIfExists(dest); Files.createSymbolicLink(dest, Paths.get(link.getTarget())); } @@ -166,16 +153,16 @@ public boolean doPostExecute() { @Override public void postExecute() throws Exception { - FileUtils.writeText(rootDir.resolve(javaVersion.getComponent()).resolve(platform).resolve(".version").toFile(), download.getVersion().getName()); - FileUtils.writeText(rootDir.resolve(javaVersion.getComponent()).resolve(platform).resolve(javaVersion.getComponent() + ".sha1").toFile(), - javaDownloadsTask.getResult().getFiles().entrySet().stream() - .filter(entry -> entry.getValue() instanceof RemoteFiles.RemoteFile) - .map(entry -> { - RemoteFiles.RemoteFile file = (RemoteFiles.RemoteFile) entry.getValue(); - return entry.getKey() + " /#// " + file.getDownloads().get("raw").getSha1() + " " + file.getDownloads().get("raw").getSize(); - }) - .collect(Collectors.joining(OperatingSystem.LINE_SEPARATOR))); + setResult(new Result(download, javaDownloadsTask.getResult())); } - public static class UnsupportedPlatformException extends Exception {} + public static final class Result { + public final MojangJavaDownloads.JavaDownload download; + public final MojangJavaRemoteFiles remoteFiles; + + public Result(MojangJavaDownloads.JavaDownload download, MojangJavaRemoteFiles remoteFiles) { + this.download = download; + this.remoteFiles = remoteFiles; + } + } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaDownloads.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/mojang/MojangJavaDownloads.java similarity index 77% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaDownloads.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/mojang/MojangJavaDownloads.java index 1c214ea8eb..cd1cf741cd 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaDownloads.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/mojang/MojangJavaDownloads.java @@ -15,11 +15,10 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.jackhuang.hmcl.download.java; +package org.jackhuang.hmcl.download.java.mojang; import com.google.gson.*; import com.google.gson.annotations.JsonAdapter; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.game.DownloadInfo; import org.jackhuang.hmcl.util.Immutable; @@ -27,12 +26,15 @@ import java.util.List; import java.util.Map; -@JsonAdapter(JavaDownloads.Adapter.class) -public class JavaDownloads { +import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf; +import static org.jackhuang.hmcl.util.gson.JsonUtils.mapTypeOf; + +@JsonAdapter(MojangJavaDownloads.Adapter.class) +public class MojangJavaDownloads { private final Map>> downloads; - public JavaDownloads(Map>> downloads) { + public MojangJavaDownloads(Map>> downloads) { this.downloads = downloads; } @@ -40,17 +42,16 @@ public Map>> getDownloads() { return downloads; } - public static class Adapter implements JsonSerializer, JsonDeserializer { + public static class Adapter implements JsonSerializer, JsonDeserializer { @Override - public JsonElement serialize(JavaDownloads src, Type typeOfSrc, JsonSerializationContext context) { + public JsonElement serialize(MojangJavaDownloads src, Type typeOfSrc, JsonSerializationContext context) { return context.serialize(src.downloads); } @Override - public JavaDownloads deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { - return new JavaDownloads(context.deserialize(json, new TypeToken>>>() { - }.getType())); + public MojangJavaDownloads deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + return new MojangJavaDownloads(context.deserialize(json, mapTypeOf(String.class, mapTypeOf(String.class, listTypeOf(JavaDownload.class))).getType())); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/RemoteFiles.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/mojang/MojangJavaRemoteFiles.java similarity index 94% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/RemoteFiles.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/mojang/MojangJavaRemoteFiles.java index d880863ca6..bc6052d169 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/RemoteFiles.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/mojang/MojangJavaRemoteFiles.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.jackhuang.hmcl.download.java; +package org.jackhuang.hmcl.download.java.mojang; import org.jackhuang.hmcl.game.DownloadInfo; import org.jackhuang.hmcl.util.gson.JsonSubtype; @@ -24,10 +24,10 @@ import java.util.Collections; import java.util.Map; -public class RemoteFiles { +public final class MojangJavaRemoteFiles { private final Map files; - public RemoteFiles(Map files) { + public MojangJavaRemoteFiles(Map files) { this.files = files; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/mojang/MojangJavaRemoteVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/mojang/MojangJavaRemoteVersion.java new file mode 100644 index 0000000000..ed8fece9a7 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/mojang/MojangJavaRemoteVersion.java @@ -0,0 +1,56 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.download.java.mojang; + +import org.jackhuang.hmcl.download.java.JavaRemoteVersion; +import org.jackhuang.hmcl.game.GameJavaVersion; + +/** + * @author Glavo + */ +public final class MojangJavaRemoteVersion implements JavaRemoteVersion { + private final GameJavaVersion gameJavaVersion; + + public MojangJavaRemoteVersion(GameJavaVersion gameJavaVersion) { + this.gameJavaVersion = gameJavaVersion; + } + + public GameJavaVersion getGameJavaVersion() { + return gameJavaVersion; + } + + @Override + public int getJdkVersion() { + return gameJavaVersion.getMajorVersion(); + } + + @Override + public String getJavaVersion() { + return String.valueOf(getJdkVersion()); + } + + @Override + public String getDistributionVersion() { + return String.valueOf(getJdkVersion()); + } + + @Override + public String toString() { + return "MojangJavaRemoteVersion[gameJavaVersion=" + gameJavaVersion + "]"; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeBMCLVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeBMCLVersionList.java index d1d8c83407..a00f2eaebc 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeBMCLVersionList.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeBMCLVersionList.java @@ -19,18 +19,17 @@ import com.google.gson.JsonParseException; import com.google.gson.annotations.SerializedName; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.download.VersionList; import org.jackhuang.hmcl.util.Immutable; import org.jackhuang.hmcl.util.gson.Validation; import org.jackhuang.hmcl.util.io.HttpRequest; import java.util.Collections; -import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; import static org.jackhuang.hmcl.util.Lang.wrap; +import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf; public final class NeoForgeBMCLVersionList extends VersionList { private final String apiRoot; @@ -68,8 +67,7 @@ public Optional getVersion(String gameVersion, String rem @Override public CompletableFuture refreshAsync(String gameVersion) { return CompletableFuture.completedFuture((Void) null) - .thenApplyAsync(wrap(unused -> HttpRequest.GET(apiRoot + "/neoforge/list/" + gameVersion).>getJson(new TypeToken>() { - }.getType()))) + .thenApplyAsync(wrap(unused -> HttpRequest.GET(apiRoot + "/neoforge/list/" + gameVersion).getJson(listTypeOf(NeoForgeVersion.class)))) .thenAcceptAsync(neoForgeVersions -> { lock.writeLock().lock(); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeOfficialVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeOfficialVersionList.java index fc4292bce6..2496b14574 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeOfficialVersionList.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeOfficialVersionList.java @@ -56,7 +56,9 @@ public CompletableFuture refreshAsync() { } for (String version : results[1].versions) { - String mcVersion = "1." + version.substring(0, version.indexOf('.', version.indexOf('.') + 1)); + int si1 = version.indexOf('.'), si2 = version.indexOf('.', version.indexOf('.') + 1); + String mcVersion = "1." + version.substring(0, Integer.parseInt(version.substring(si1 + 1, si2)) == 0 ? si1 : si2); + versions.put(mcVersion, new NeoForgeRemoteVersion( mcVersion, NeoForgeRemoteVersion.normalize(version), Collections.singletonList( diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeOldInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeOldInstallTask.java index 838232fe8e..3569a6e11f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeOldInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeOldInstallTask.java @@ -36,7 +36,7 @@ import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.platform.CommandBuilder; -import org.jackhuang.hmcl.util.platform.JavaVersion; +import org.jackhuang.hmcl.java.JavaRuntime; import org.jackhuang.hmcl.util.platform.OperatingSystem; import org.jackhuang.hmcl.util.platform.SystemUtils; import org.jetbrains.annotations.NotNull; @@ -125,7 +125,7 @@ public void execute() throws Exception { throw new Exception("Game processor jar does not have main class " + jar); List command = new ArrayList<>(); - command.add(JavaVersion.fromCurrentEnvironment().getBinary().toString()); + command.add(JavaRuntime.getDefault().getBinary().toString()); command.add("-cp"); List classpath = new ArrayList<>(processor.getClasspath().size() + 1); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineBMCLVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineBMCLVersionList.java index 47aeb340ee..5e9c368235 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineBMCLVersionList.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineBMCLVersionList.java @@ -17,7 +17,7 @@ */ package org.jackhuang.hmcl.download.optifine; -import com.google.gson.reflect.TypeToken; +import com.google.gson.annotations.SerializedName; import org.jackhuang.hmcl.download.VersionList; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.io.HttpRequest; @@ -25,12 +25,12 @@ import java.util.Collections; import java.util.HashSet; -import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; +import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf; + /** - * * @author huangyuhui */ public final class OptiFineBMCLVersionList extends VersionList { @@ -48,34 +48,122 @@ public boolean hasType() { return true; } + private String fromLookupVersion(String version) { + switch (version) { + case "1.8.0": + return "1.8"; + case "1.9.0": + return "1.9"; + default: + return version; + } + } + + private String toLookupVersion(String version) { + switch (version) { + case "1.8": + return "1.8.0"; + case "1.9": + return "1.9.0"; + default: + return version; + } + } + @Override public CompletableFuture refreshAsync() { - return HttpRequest.GET(apiRoot + "/optifine/versionlist").>getJsonAsync(new TypeToken>() { - }.getType()) - .thenAcceptAsync(root -> { - lock.writeLock().lock(); - - try { - versions.clear(); - Set duplicates = new HashSet<>(); - for (OptiFineVersion element : root) { - String version = element.getType() + "_" + element.getPatch(); - String mirror = "https://bmclapi2.bangbang93.com/optifine/" + element.getGameVersion() + "/" + element.getType() + "/" + element.getPatch(); - if (!duplicates.add(mirror)) - continue; - - boolean isPre = element.getPatch() != null && (element.getPatch().startsWith("pre") || element.getPatch().startsWith("alpha")); - - if (StringUtils.isBlank(element.getGameVersion())) - continue; - - String gameVersion = VersionNumber.normalize(element.getGameVersion()); - versions.put(gameVersion, new OptiFineRemoteVersion(gameVersion, version, Collections.singletonList(mirror), isPre)); - } - } finally { - lock.writeLock().unlock(); - } - }); + return HttpRequest.GET(apiRoot + "/optifine/versionlist").getJsonAsync(listTypeOf(OptiFineVersion.class)).thenAcceptAsync(root -> { + lock.writeLock().lock(); + + try { + versions.clear(); + Set duplicates = new HashSet<>(); + for (OptiFineVersion element : root) { + String version = element.getType() + "_" + element.getPatch(); + String mirror = "https://bmclapi2.bangbang93.com/optifine/" + toLookupVersion(element.getGameVersion()) + "/" + element.getType() + "/" + element.getPatch(); + if (!duplicates.add(mirror)) + continue; + + boolean isPre = element.getPatch() != null && (element.getPatch().startsWith("pre") || element.getPatch().startsWith("alpha")); + + if (StringUtils.isBlank(element.getGameVersion())) + continue; + + String gameVersion = VersionNumber.normalize(fromLookupVersion(element.getGameVersion())); + versions.put(gameVersion, new OptiFineRemoteVersion(gameVersion, version, Collections.singletonList(mirror), isPre)); + } + } finally { + lock.writeLock().unlock(); + } + }); } + /** + * @author huangyuhui + */ + private static final class OptiFineVersion { + + @SerializedName("dl") + private final String downloadLink; + + @SerializedName("ver") + private final String version; + + @SerializedName("date") + private final String date; + + @SerializedName("type") + private final String type; + + @SerializedName("patch") + private final String patch; + + @SerializedName("mirror") + private final String mirror; + + @SerializedName("mcversion") + private final String gameVersion; + + public OptiFineVersion() { + this(null, null, null, null, null, null, null); + } + + public OptiFineVersion(String downloadLink, String version, String date, String type, String patch, String mirror, String gameVersion) { + this.downloadLink = downloadLink; + this.version = version; + this.date = date; + this.type = type; + this.patch = patch; + this.mirror = mirror; + this.gameVersion = gameVersion; + } + + public String getDownloadLink() { + return downloadLink; + } + + public String getVersion() { + return version; + } + + public String getDate() { + return date; + } + + public String getType() { + return type; + } + + public String getPatch() { + return patch; + } + + public String getMirror() { + return mirror; + } + + public String getGameVersion() { + return gameVersion; + } + } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineInstallTask.java index ffc73e3f17..1320d1a172 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineInstallTask.java @@ -27,7 +27,7 @@ import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.platform.CommandBuilder; -import org.jackhuang.hmcl.util.platform.JavaVersion; +import org.jackhuang.hmcl.java.JavaRuntime; import org.jackhuang.hmcl.util.platform.SystemUtils; import org.jackhuang.hmcl.util.versioning.VersionNumber; import org.jenkinsci.constant_pool_scanner.ConstantPool; @@ -144,7 +144,7 @@ public void execute() throws Exception { Path optiFineLibraryPath = gameRepository.getLibraryFile(version, optiFineLibrary).toPath(); if (Files.exists(fs.getPath("optifine/Patcher.class"))) { String[] command = { - JavaVersion.fromCurrentEnvironment().getBinary().toString(), + JavaRuntime.getDefault().getBinary().toString(), "-cp", dest.toString(), "optifine.Patcher", diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineVersion.java deleted file mode 100644 index a18fe1b4fb..0000000000 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineVersion.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Hello Minecraft! Launcher - * Copyright (C) 2020 huangyuhui and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.jackhuang.hmcl.download.optifine; - -import com.google.gson.annotations.SerializedName; - -/** - * - * @author huangyuhui - */ -public final class OptiFineVersion { - - @SerializedName("dl") - private final String downloadLink; - - @SerializedName("ver") - private final String version; - - @SerializedName("date") - private final String date; - - @SerializedName("type") - private final String type; - - @SerializedName("patch") - private final String patch; - - @SerializedName("mirror") - private final String mirror; - - @SerializedName("mcversion") - private final String gameVersion; - - public OptiFineVersion() { - this(null, null, null, null, null, null, null); - } - - public OptiFineVersion(String downloadLink, String version, String date, String type, String patch, String mirror, String gameVersion) { - this.downloadLink = downloadLink; - this.version = version; - this.date = date; - this.type = type; - this.patch = patch; - this.mirror = mirror; - this.gameVersion = gameVersion; - } - - public String getDownloadLink() { - return downloadLink; - } - - public String getVersion() { - return version; - } - - public String getDate() { - return date; - } - - public String getType() { - return type; - } - - public String getPatch() { - return patch; - } - - public String getMirror() { - return mirror; - } - - public String getGameVersion() { - return gameVersion; - } -} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/quilt/QuiltVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/quilt/QuiltVersionList.java index bc7bf88041..f7d6a2e6b2 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/quilt/QuiltVersionList.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/quilt/QuiltVersionList.java @@ -17,7 +17,6 @@ */ package org.jackhuang.hmcl.download.quilt; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.download.DownloadProvider; import org.jackhuang.hmcl.download.VersionList; import org.jackhuang.hmcl.util.gson.JsonUtils; @@ -25,13 +24,13 @@ import org.jetbrains.annotations.Nullable; import java.io.IOException; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import static org.jackhuang.hmcl.util.Lang.wrap; +import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf; public final class QuiltVersionList extends VersionList { private final DownloadProvider downloadProvider; @@ -69,8 +68,8 @@ public CompletableFuture refreshAsync() { private List getGameVersions(String metaUrl) throws IOException { String json = NetworkUtils.doGet(downloadProvider.injectURLWithCandidates(metaUrl)); - return JsonUtils.GSON.>fromJson(json, new TypeToken>() { - }.getType()).stream().map(GameVersion::getVersion).collect(Collectors.toList()); + return JsonUtils.GSON.fromJson(json, listTypeOf(GameVersion.class)) + .stream().map(GameVersion::getVersion).collect(Collectors.toList()); } private static String getLaunchMetaUrl(String gameVersion, String loaderVersion) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/CrashReportAnalyzer.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/CrashReportAnalyzer.java index 42e96367ed..22cce5a2aa 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/CrashReportAnalyzer.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/CrashReportAnalyzer.java @@ -113,7 +113,7 @@ public enum Rule { //https://github.com/HMCL-dev/HMCL/pull/2038 MODMIXIN_FAILURE(Pattern.compile("(MixinApplyError|Mixin prepare failed |Mixin apply failed |mixin\\.injection\\.throwables\\.|\\.mixins\\.json\\] FAILED during \\))")),//ModMixin失败 MIXIN_APPLY_MOD_FAILED(Pattern.compile("Mixin apply for mod (?.*) failed"), "id"),//Mixin应用失败 - FORGE_ERROR(Pattern.compile("An exception was thrown, the game will display an error screen and halt\\.(?(.*)[\\n\\r]*((.*)[\\n\\r]*)+)at "), "reason"),//Forge报错,Forge可能已经提供了错误信息 + FORGE_ERROR(Pattern.compile("An exception was thrown, the game will display an error screen and halt\\.\\R*(?.*\\R*(\\s*at .*\\R)+)"), "reason"),//Forge报错,Forge可能已经提供了错误信息 MOD_RESOLUTION0(Pattern.compile("(\tMod File:|-- MOD |\tFailure message:)")), FORGE_REPEAT_INSTALLATION(Pattern.compile("MultipleArgumentsForOptionException: Found multiple arguments for option (.*?), but you asked for only one")),//https://github.com/HMCL-dev/HMCL/issues/1880 OPTIFINE_REPEAT_INSTALLATION(Pattern.compile("ResolutionException: Module optifine reads another module named optifine")),//Optifine 重复安装(及Mod文件夹有,自动安装也有) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java index 115b79c332..21c63c60ef 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.game; import com.google.gson.JsonParseException; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.download.MaintainTask; import org.jackhuang.hmcl.download.game.VersionJsonSaveTask; import org.jackhuang.hmcl.event.*; @@ -499,18 +498,16 @@ public File getModpackConfiguration(String version) { * read modpack configuration for a version. * * @param version version installed as modpack - * @param manifest type of ModpackConfiguration * @return modpack configuration object, or null if this version is not a modpack. * @throws VersionNotFoundException if version does not exist. * @throws IOException if an i/o error occurs. */ @Nullable - public ModpackConfiguration readModpackConfiguration(String version) throws IOException, VersionNotFoundException { + public ModpackConfiguration readModpackConfiguration(String version) throws IOException, VersionNotFoundException { if (!hasVersion(version)) throw new VersionNotFoundException(version); File file = getModpackConfiguration(version); if (!file.exists()) return null; - return JsonUtils.GSON.fromJson(FileUtils.readText(file), new TypeToken>() { - }.getType()); + return JsonUtils.GSON.fromJson(FileUtils.readText(file), ModpackConfiguration.class); } public boolean isModpack(String version) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameJavaVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameJavaVersion.java index c77ec06f01..5f5afda69c 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameJavaVersion.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameJavaVersion.java @@ -17,7 +17,91 @@ */ package org.jackhuang.hmcl.game; -public class GameJavaVersion { +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.versioning.GameVersionNumber; + +import java.util.*; + +public final class GameJavaVersion { + public static final GameJavaVersion JAVA_21 = new GameJavaVersion("java-runtime-delta", 21); + public static final GameJavaVersion JAVA_17 = new GameJavaVersion("java-runtime-beta", 17); + public static final GameJavaVersion JAVA_16 = new GameJavaVersion("java-runtime-alpha", 16); + public static final GameJavaVersion JAVA_8 = new GameJavaVersion("jre-legacy", 8); + + public static final GameJavaVersion LATEST = JAVA_21; + + public static GameJavaVersion getMinimumJavaVersion(GameVersionNumber gameVersion) { + if (gameVersion.compareTo("1.21") >= 0) + return JAVA_21; + if (gameVersion.compareTo("1.18") >= 0) + return JAVA_17; + if (gameVersion.compareTo("1.17") >= 0) + return JAVA_16; + if (gameVersion.compareTo("1.13") >= 0) + return JAVA_8; + return null; + } + + public static GameJavaVersion get(int major) { + switch (major) { + case 8: + return JAVA_8; + case 16: + return JAVA_16; + case 17: + return JAVA_17; + case 21: + return JAVA_21; + default: + return null; + } + } + + public static boolean isSupportedPlatform(Platform platform) { + OperatingSystem os = platform.getOperatingSystem(); + Architecture arch = platform.getArchitecture(); + switch (arch) { + case X86: + return os == OperatingSystem.WINDOWS || os == OperatingSystem.LINUX; + case X86_64: + return os == OperatingSystem.WINDOWS || os == OperatingSystem.LINUX || os == OperatingSystem.OSX; + case ARM64: + return os == OperatingSystem.WINDOWS || os == OperatingSystem.OSX; + default: + return false; + } + } + + public static List getSupportedVersions(Platform platform) { + OperatingSystem operatingSystem = platform.getOperatingSystem(); + Architecture architecture = platform.getArchitecture(); + if (architecture == Architecture.X86) { + switch (operatingSystem) { + case WINDOWS: + return Arrays.asList(JAVA_8, JAVA_16, JAVA_17); + case LINUX: + return Collections.singletonList(JAVA_8); + } + } else if (architecture == Architecture.X86_64) { + switch (operatingSystem) { + case WINDOWS: + case LINUX: + case OSX: + return Arrays.asList(JAVA_8, JAVA_16, JAVA_17, JAVA_21); + } + } else if (architecture == Architecture.ARM64) { + switch (operatingSystem) { + case WINDOWS: + case OSX: + return Arrays.asList(JAVA_17, JAVA_21); + } + } + + return Collections.emptyList(); + } + private final String component; private final int majorVersion; @@ -38,8 +122,16 @@ public int getMajorVersion() { return majorVersion; } - public static final GameJavaVersion JAVA_21 = new GameJavaVersion("java-runtime-delta", 21); - public static final GameJavaVersion JAVA_17 = new GameJavaVersion("java-runtime-beta", 17); - public static final GameJavaVersion JAVA_16 = new GameJavaVersion("java-runtime-alpha", 16); - public static final GameJavaVersion JAVA_8 = new GameJavaVersion("jre-legacy", 8); + @Override + public int hashCode() { + return getMajorVersion(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof GameJavaVersion)) return false; + GameJavaVersion that = (GameJavaVersion) o; + return majorVersion == that.majorVersion; + } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameVersion.java index 063c90240d..04188c8857 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameVersion.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameVersion.java @@ -35,7 +35,6 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipFile; -import static org.jackhuang.hmcl.util.Lang.tryCast; import static org.jackhuang.hmcl.util.logging.Logger.LOG; /** @@ -48,8 +47,11 @@ private GameVersion() { private static Optional getVersionFromJson(InputStream versionJson) { try { Map version = JsonUtils.fromNonNullJsonFully(versionJson, Map.class); - return tryCast(version.get("id"), String.class); - } catch (IOException | JsonParseException e) { + String id = (String) version.get("id"); + if (id != null && id.contains(" / ")) + id = id.substring(0, id.indexOf(" / ")); + return Optional.ofNullable(id); + } catch (IOException | JsonParseException | ClassCastException e) { LOG.warning("Failed to parse version.json", e); return Optional.empty(); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/JavaVersionConstraint.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/JavaVersionConstraint.java index 9dbbd233c7..dc7abb453c 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/JavaVersionConstraint.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/JavaVersionConstraint.java @@ -20,7 +20,7 @@ import org.jackhuang.hmcl.download.LibraryAnalyzer; import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.platform.Architecture; -import org.jackhuang.hmcl.util.platform.JavaVersion; +import org.jackhuang.hmcl.java.JavaRuntime; import org.jackhuang.hmcl.util.platform.OperatingSystem; import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import org.jackhuang.hmcl.util.versioning.VersionNumber; @@ -33,48 +33,47 @@ import static org.jackhuang.hmcl.download.LibraryAnalyzer.LAUNCH_WRAPPER_MAIN; public enum JavaVersionConstraint { - // Minecraft>=1.13 requires Java 8 - VANILLA_JAVA_8(JavaVersionConstraint.RULE_MANDATORY, GameVersionNumber.atLeast("1.13"), VersionNumber.atLeast("1.8")), - // Minecraft 1.17 requires Java 16 - VANILLA_JAVA_16(JavaVersionConstraint.RULE_MANDATORY, GameVersionNumber.atLeast("1.17"), VersionNumber.atLeast("16")), - // Minecraft>=1.18 requires Java 17 - VANILLA_JAVA_17(JavaVersionConstraint.RULE_MANDATORY, GameVersionNumber.atLeast("1.18"), VersionNumber.atLeast("17")), - // Minecraft>=1.20.5 requires Java 21 - VANILLA_JAVA_21(JavaVersionConstraint.RULE_MANDATORY, GameVersionNumber.atLeast("1.20.5"), VersionNumber.atLeast("21")), + VANILLA(true, VersionRange.all(), VersionRange.all()) { + @Override + public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaRuntime java) { + GameJavaVersion minimumJavaVersion = GameJavaVersion.getMinimumJavaVersion(gameVersionNumber); + return minimumJavaVersion == null || java.getParsedVersion() >= minimumJavaVersion.getMajorVersion(); + } + }, // Minecraft<=1.7.2+Forge requires Java<=7, But LegacyModFixer may fix that problem. So only suggest user using Java 7. - MODDED_JAVA_7(JavaVersionConstraint.RULE_SUGGESTED, GameVersionNumber.atMost("1.7.2"), VersionNumber.atMost("1.7.999")) { + MODDED_JAVA_7(false, GameVersionNumber.atMost("1.7.2"), VersionNumber.atMost("1.7.999")) { @Override protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version, - @Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) { + @Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) { return version != null && analyzer != null && analyzer.has(LibraryAnalyzer.LibraryType.FORGE); } }, - MODDED_JAVA_8(JavaVersionConstraint.RULE_SUGGESTED, GameVersionNumber.between("1.7.10", "1.16.999"), VersionNumber.between("1.8", "1.8.999")) { + MODDED_JAVA_8(false, GameVersionNumber.between("1.7.10", "1.16.999"), VersionNumber.between("1.8", "1.8.999")) { @Override protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version, - @Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) { + @Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) { return analyzer != null && analyzer.has(LibraryAnalyzer.LibraryType.FORGE); } }, - MODDED_JAVA_16(JavaVersionConstraint.RULE_SUGGESTED, GameVersionNumber.between("1.17", "1.17.999"), VersionNumber.between("16", "16.999")) { + MODDED_JAVA_16(false, GameVersionNumber.between("1.17", "1.17.999"), VersionNumber.between("16", "16.999")) { @Override protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version, - @Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) { + @Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) { return analyzer != null && analyzer.has(LibraryAnalyzer.LibraryType.FORGE); } }, - MODDED_JAVA_17(JavaVersionConstraint.RULE_SUGGESTED, GameVersionNumber.atLeast("1.18"), VersionNumber.between("17", "17.999")) { + MODDED_JAVA_17(false, GameVersionNumber.atLeast("1.18"), VersionNumber.between("17", "17.999")) { @Override protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version, - @Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) { + @Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) { return analyzer != null && analyzer.has(LibraryAnalyzer.LibraryType.FORGE); } }, // LaunchWrapper<=1.12 will crash because LaunchWrapper assumes the system class loader is an instance of URLClassLoader (Java 8) - LAUNCH_WRAPPER(JavaVersionConstraint.RULE_MANDATORY, GameVersionNumber.atMost("1.12.999"), VersionNumber.atMost("1.8.999")) { + LAUNCH_WRAPPER(true, GameVersionNumber.atMost("1.12.999"), VersionNumber.atMost("1.8.999")) { @Override protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version, - @Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) { + @Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) { if (version == null) return false; return LAUNCH_WRAPPER_MAIN.equals(version.getMainClass()) && version.getLibraries().stream() @@ -83,12 +82,12 @@ protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nul } }, // Minecraft>=1.13 may crash when generating world on Java [1.8,1.8.0_51) - VANILLA_JAVA_8_51(JavaVersionConstraint.RULE_SUGGESTED, GameVersionNumber.atLeast("1.13"), VersionNumber.atLeast("1.8.0_51")), + VANILLA_JAVA_8_51(false, GameVersionNumber.atLeast("1.13"), VersionNumber.atLeast("1.8.0_51")), // Minecraft with suggested java version recorded in game json is restrictedly constrained. - GAME_JSON(JavaVersionConstraint.RULE_MANDATORY, VersionRange.all(), VersionRange.all()) { + GAME_JSON(true, VersionRange.all(), VersionRange.all()) { @Override protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version, - @Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) { + @Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) { if (version == null) return false; // We only checks for 1.7.10 and above, since 1.7.2 with Forge can only run on Java 7, but it is recorded Java 8 in game json, which is not correct. return gameVersionNumber.compareTo("1.7.10") >= 0 && version.getJavaVersion() != null; @@ -107,26 +106,26 @@ public VersionRange getJavaVersionRange(Version version) { }, // On Linux, JDK 9+ cannot launch Minecraft<=1.12.2, since JDK 9+ does not accept loading native library built in different arch. // For example, JDK 9+ 64-bit cannot load 32-bit lwjgl native library. - VANILLA_LINUX_JAVA_8(JavaVersionConstraint.RULE_MANDATORY, GameVersionNumber.atMost("1.12.999"), VersionNumber.atMost("1.8.999")) { + VANILLA_LINUX_JAVA_8(true, GameVersionNumber.atMost("1.12.999"), VersionNumber.atMost("1.8.999")) { @Override protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version, - @Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) { + @Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) { return OperatingSystem.CURRENT_OS == OperatingSystem.LINUX && Architecture.SYSTEM_ARCH == Architecture.X86_64 - && (javaVersion == null || javaVersion.getArchitecture() == Architecture.X86_64); + && (java == null || java.getArchitecture() == Architecture.X86_64); } @Override - public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaVersion javaVersion) { - return javaVersion.getArchitecture() != Architecture.X86_64 || super.checkJava(gameVersionNumber, version, javaVersion); + public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaRuntime java) { + return java.getArchitecture() != Architecture.X86_64 || super.checkJava(gameVersionNumber, version, java); } }, // Minecraft currently does not provide official support for architectures other than x86 and x86-64. - VANILLA_X86(JavaVersionConstraint.RULE_SUGGESTED, VersionRange.all(), VersionRange.all()) { + VANILLA_X86(false, VersionRange.all(), VersionRange.all()) { @Override protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version, - @Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) { - if (javaVersion == null || javaVersion.getArchitecture() != Architecture.ARM64) + @Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) { + if (java == null || java.getArchitecture() != Architecture.ARM64) return false; if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS || OperatingSystem.CURRENT_OS == OperatingSystem.OSX) @@ -136,16 +135,16 @@ protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nul } @Override - public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaVersion javaVersion) { - return javaVersion.getArchitecture().isX86(); + public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaRuntime java) { + return java.getArchitecture().isX86(); } }, // Minecraft 1.16+Forge with crash because JDK-8273826 - MODLAUNCHER_8(JavaVersionConstraint.RULE_SUGGESTED, GameVersionNumber.between("1.16.3", "1.17.1"), VersionRange.all()) { + MODLAUNCHER_8(false, GameVersionNumber.between("1.16.3", "1.17.1"), VersionRange.all()) { @Override protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version, - @Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) { - if (version == null || javaVersion == null || analyzer == null) return false; + @Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) { + if (version == null || java == null || analyzer == null) return false; VersionNumber forgePatchVersion = analyzer.getVersion(LibraryAnalyzer.LibraryType.FORGE) .map(VersionNumber::asVersion) .orElse(null); @@ -167,36 +166,36 @@ protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nul } @Override - public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaVersion javaVersion) { - int parsedJavaVersion = javaVersion.getParsedVersion(); + public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaRuntime java) { + int parsedJavaVersion = java.getParsedVersion(); if (parsedJavaVersion > 17) { return false; } else if (parsedJavaVersion == 8) { - return javaVersion.getVersionNumber().compareTo(VersionNumber.asVersion("1.8.0_321")) < 0; + return java.getVersionNumber().compareTo(VersionNumber.asVersion("1.8.0_321")) < 0; } else if (parsedJavaVersion == 11) { - return javaVersion.getVersionNumber().compareTo(VersionNumber.asVersion("11.0.14")) < 0; + return java.getVersionNumber().compareTo(VersionNumber.asVersion("11.0.14")) < 0; } else if (parsedJavaVersion == 15) { - return javaVersion.getVersionNumber().compareTo(VersionNumber.asVersion("15.0.6")) < 0; + return java.getVersionNumber().compareTo(VersionNumber.asVersion("15.0.6")) < 0; } else if (parsedJavaVersion == 17) { - return javaVersion.getVersionNumber().compareTo(VersionNumber.asVersion("17.0.2")) < 0; + return java.getVersionNumber().compareTo(VersionNumber.asVersion("17.0.2")) < 0; } else { return true; } } }; - private final int type; + private final boolean isMandatory; private final VersionRange gameVersionRange; private final VersionRange javaVersionRange; - JavaVersionConstraint(int type, VersionRange gameVersionRange, VersionRange javaVersionRange) { - this.type = type; + JavaVersionConstraint(boolean isMandatory, VersionRange gameVersionRange, VersionRange javaVersionRange) { + this.isMandatory = isMandatory; this.gameVersionRange = gameVersionRange; this.javaVersionRange = javaVersionRange; } - public int getType() { - return type; + public boolean isMandatory() { + return isMandatory; } public VersionRange getGameVersionRange() { @@ -208,112 +207,20 @@ public VersionRange getJavaVersionRange(Version version) { } public final boolean appliesToVersion(@Nullable GameVersionNumber gameVersionNumber, @Nullable Version version, - @Nullable JavaVersion javaVersion, LibraryAnalyzer analyzer) { + @Nullable JavaRuntime java, LibraryAnalyzer analyzer) { return gameVersionRange.contains(gameVersionNumber) - && appliesToVersionImpl(gameVersionNumber, version, javaVersion, analyzer); + && appliesToVersionImpl(gameVersionNumber, version, java, analyzer); } protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version, - @Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) { + @Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) { return true; } @SuppressWarnings("BooleanMethodIsAlwaysInverted") - public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaVersion javaVersion) { - return getJavaVersionRange(version).contains(javaVersion.getVersionNumber()); + public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaRuntime java) { + return getJavaVersionRange(version).contains(java.getVersionNumber()); } public static final List ALL = Lang.immutableListOf(values()); - - public static VersionRanges findSuitableJavaVersionRange(GameVersionNumber gameVersion, Version version) { - VersionRange mandatoryJavaRange = VersionRange.all(); - VersionRange suggestedJavaRange = VersionRange.all(); - LibraryAnalyzer analyzer = version != null ? LibraryAnalyzer.analyze(version, gameVersion != null ? gameVersion.toString() : null) : null; - for (JavaVersionConstraint java : ALL) { - if (java.appliesToVersion(gameVersion, version, null, analyzer)) { - VersionRange javaVersionRange = java.getJavaVersionRange(version); - if (java.type == RULE_MANDATORY) { - mandatoryJavaRange = mandatoryJavaRange.intersectionWith(javaVersionRange); - suggestedJavaRange = suggestedJavaRange.intersectionWith(javaVersionRange); - } else if (java.type == RULE_SUGGESTED) { - suggestedJavaRange = suggestedJavaRange.intersectionWith(javaVersionRange); - } - } - } - return new VersionRanges(mandatoryJavaRange, suggestedJavaRange); - } - - @Nullable - public static JavaVersion findSuitableJavaVersion(GameVersionNumber gameVersion, Version version) throws InterruptedException { - VersionRanges range = findSuitableJavaVersionRange(gameVersion, version); - - boolean forceX86 = Architecture.SYSTEM_ARCH == Architecture.ARM64 - && (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS || OperatingSystem.CURRENT_OS == OperatingSystem.OSX) - && gameVersion.compareTo("1.6") < 0; - - JavaVersion mandatory = null; - JavaVersion suggested = null; - for (JavaVersion javaVersion : JavaVersion.getJavas()) { - // Do not automatically select 32-bit Java - if (Architecture.SYSTEM_ARCH == Architecture.X86_64 && javaVersion.getArchitecture() == Architecture.X86) - continue; - - // select the latest x86 java that this version accepts. - if (forceX86 && !javaVersion.getArchitecture().isX86()) - continue; - - VersionNumber javaVersionNumber = javaVersion.getVersionNumber(); - if (range.getMandatory().contains(javaVersionNumber)) { - if (mandatory == null) mandatory = javaVersion; - else if (compareJavaVersion(javaVersion, mandatory) > 0) { - mandatory = javaVersion; - } - } - if (range.getSuggested().contains(javaVersionNumber)) { - if (suggested == null) suggested = javaVersion; - else if (compareJavaVersion(javaVersion, suggested) > 0) { - suggested = javaVersion; - } - } - } - - if (suggested != null) return suggested; - else return mandatory; - } - - private static int compareJavaVersion(JavaVersion javaVersion1, JavaVersion javaVersion2) { - Architecture arch1 = javaVersion1.getArchitecture(); - Architecture arch2 = javaVersion2.getArchitecture(); - - if (arch1 != arch2) { - if (arch1 == Architecture.X86_64) { - return 1; - } - if (arch2 == Architecture.X86_64) { - return -1; - } - } - return javaVersion1.getVersionNumber().compareTo(javaVersion2.getVersionNumber()); - } - - public static final int RULE_MANDATORY = 1; - public static final int RULE_SUGGESTED = 2; - - public static final class VersionRanges { - private final VersionRange mandatory; - private final VersionRange suggested; - - public VersionRanges(VersionRange mandatory, VersionRange suggested) { - this.mandatory = mandatory; - this.suggested = suggested; - } - - public VersionRange getMandatory() { - return mandatory; - } - - public VersionRange getSuggested() { - return suggested; - } - } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/LaunchOptions.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/LaunchOptions.java index 40ecbc8692..f524533b22 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/LaunchOptions.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/LaunchOptions.java @@ -17,7 +17,7 @@ */ package org.jackhuang.hmcl.game; -import org.jackhuang.hmcl.util.platform.JavaVersion; +import org.jackhuang.hmcl.java.JavaRuntime; import org.jetbrains.annotations.NotNull; import java.io.File; @@ -32,7 +32,7 @@ public class LaunchOptions implements Serializable { private File gameDir; - private JavaVersion java; + private JavaRuntime java; private String versionName; private String versionType; private String profileName; @@ -73,7 +73,7 @@ public File getGameDir() { /** * The Java Environment that Minecraft runs on. */ - public JavaVersion getJava() { + public JavaRuntime getJava() { return java; } @@ -312,7 +312,7 @@ public Builder setGameDir(File gameDir) { return this; } - public Builder setJava(JavaVersion java) { + public Builder setJava(JavaRuntime java) { options.java = java; return this; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/RuledArgument.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/RuledArgument.java index d812258c02..b55937f15b 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/RuledArgument.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/RuledArgument.java @@ -19,13 +19,14 @@ import com.google.gson.*; import com.google.gson.annotations.JsonAdapter; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.util.Immutable; import java.lang.reflect.Type; import java.util.*; import java.util.stream.Collectors; +import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf; + /** * * @author huangyuhui @@ -86,8 +87,7 @@ public JsonElement serialize(RuledArgument src, Type typeOfSrc, JsonSerializatio public RuledArgument deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { JsonObject obj = json.getAsJsonObject(); - List rules = context.deserialize(obj.get("rules"), new TypeToken>() { - }.getType()); + List rules = context.deserialize(obj.get("rules"), listTypeOf(CompatibilityRule.class).getType()); JsonElement valuesElement; if (obj.has("values")) { @@ -102,8 +102,7 @@ public RuledArgument deserialize(JsonElement json, Type typeOfT, JsonDeserializa if (valuesElement.isJsonPrimitive()) { values = Collections.singletonList(valuesElement.getAsString()); } else { - values = context.deserialize(valuesElement, new TypeToken>() { - }.getType()); + values = context.deserialize(valuesElement, listTypeOf(String.class).getType()); } return new RuledArgument(rules, values); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java index b50bdd83c8..16bda75f0f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java @@ -296,8 +296,8 @@ protected Version resolve(VersionProvider provider, Set resolvedSoFar) t } if (patches == null) { - // This is a version from external launcher. - thisVersion = thisVersion.merge(this, true); + // This is a version from external launcher. NO need to resolve the patches. + return thisVersion; } else if (!patches.isEmpty()) { // Assume patches themselves do not have patches recursively. List sortedPatches = patches.stream() diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/java/JavaInfo.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/java/JavaInfo.java new file mode 100644 index 0000000000..d23f5a46a9 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/java/JavaInfo.java @@ -0,0 +1,283 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.java; + +import org.apache.commons.compress.archivers.ArchiveEntry; +import org.jackhuang.hmcl.util.KeyValuePairProperties; +import org.jackhuang.hmcl.util.gson.JsonUtils; +import org.jackhuang.hmcl.util.platform.Architecture; +import org.jackhuang.hmcl.util.platform.OperatingSystem; +import org.jackhuang.hmcl.util.platform.Platform; +import org.jackhuang.hmcl.util.tree.ArchiveFileTree; +import org.jackhuang.hmcl.util.versioning.VersionNumber; +import org.jetbrains.annotations.Nullable; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * @author Glavo + */ +public final class JavaInfo { + public static int parseVersion(String version) { + try { + int idx = version.indexOf('.'); + if (idx < 0) { + idx = version.indexOf('u'); + return idx > 0 ? Integer.parseInt(version.substring(0, idx)) : Integer.parseInt(version); + } else { + int major = Integer.parseInt(version.substring(0, idx)); + if (major != 1) { + return major; + } else { + int idx2 = version.indexOf('.', idx + 1); + if (idx2 < 0) { + return -1; + } + return Integer.parseInt(version.substring(idx + 1, idx2)); + } + } + } catch (NumberFormatException e) { + return -1; + } + } + + public static JavaInfo fromReleaseFile(BufferedReader reader) throws IOException { + KeyValuePairProperties properties = KeyValuePairProperties.load(reader); + String osName = properties.get("OS_NAME"); + String osArch = properties.get("OS_ARCH"); + String vendor = properties.get("IMPLEMENTOR"); + + OperatingSystem os = "".equals(osName) && "OpenJDK BSD Porting Team".equals(vendor) + ? OperatingSystem.FREEBSD + : OperatingSystem.parseOSName(osName); + + Architecture arch = Architecture.parseArchName(osArch); + String javaVersion = properties.get("JAVA_VERSION"); + + if (os == OperatingSystem.UNKNOWN) + throw new IOException("Unknown operating system: " + osName); + + if (arch == Architecture.UNKNOWN) + throw new IOException("Unknown architecture: " + osArch); + + if (javaVersion == null) + throw new IOException("Missing Java version"); + + return new JavaInfo(Platform.getPlatform(os, arch), javaVersion, vendor); + } + + public static JavaInfo fromReleaseFile(Path releaseFile) throws IOException { + try (BufferedReader reader = Files.newBufferedReader(releaseFile)) { + return fromReleaseFile(reader); + } + } + + public static JavaInfo fromArchive(ArchiveFileTree tree) throws IOException { + if (tree.getRoot().getSubDirs().size() != 1 || !tree.getRoot().getFiles().isEmpty()) + throw new IOException(); + + ArchiveFileTree.Dir jdkRoot = tree.getRoot().getSubDirs().values().iterator().next(); + E releaseEntry = jdkRoot.getFiles().get("release"); + if (releaseEntry == null) + throw new IOException("Missing release file"); + + JavaInfo info; + try (BufferedReader reader = new BufferedReader(new InputStreamReader(tree.getInputStream(releaseEntry), StandardCharsets.UTF_8))) { + info = JavaInfo.fromReleaseFile(reader); + } + + ArchiveFileTree.Dir binDir = jdkRoot.getSubDirs().get("bin"); + if (binDir == null || binDir.getFiles().get(info.getPlatform().getOperatingSystem().getJavaExecutable()) == null) + throw new IOException("Missing java executable file"); + + return info; + } + + public static String normalizeVendor(String vendor) { + if (vendor == null) + return null; + + switch (vendor) { + case "N/A": + return null; + case "Oracle Corporation": + return "Oracle"; + case "Azul Systems, Inc.": + return "Azul"; + case "IBM Corporation": + case "International Business Machines Corporation": + case "Eclipse OpenJ9": + return "IBM"; + case "Eclipse Adoptium": + return "Adoptium"; + case "Amazon.com Inc.": + return "Amazon"; + default: + return vendor; + } + } + + private static final String OS_ARCH = "os.arch = "; + private static final String JAVA_VERSION = "java.version = "; + private static final String JAVA_VENDOR = "java.vendor = "; + private static final String VERSION_PREFIX = "version \""; + + public static JavaInfo fromExecutable(Path executable) throws IOException { + return fromExecutable(executable, true); + } + + public static JavaInfo fromExecutable(Path executable, boolean tryFindReleaseFile) throws IOException { + assert executable.isAbsolute(); + Path parent = executable.getParent(); + if (tryFindReleaseFile && parent != null && parent.getFileName() != null && parent.getFileName().toString().equals("bin")) { + Path javaHome = parent.getParent(); + if (javaHome != null && javaHome.getFileName() != null) { + Path releaseFile = javaHome.resolve("release"); + String javaHomeName = javaHome.getFileName().toString(); + if ((javaHomeName.contains("jre") || javaHomeName.contains("jdk") || javaHomeName.contains("openj9")) && Files.isRegularFile(releaseFile)) { + try { + return fromReleaseFile(releaseFile); + } catch (IOException ignored) { + } + } + } + } + + String osArch = null; + String version = null; + String vendor = null; + Platform platform = null; + + String executablePath = executable.toString(); + + Process process = new ProcessBuilder(executablePath, "-XshowSettings:properties", "-version").start(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream(), OperatingSystem.NATIVE_CHARSET))) { + for (String line; (line = reader.readLine()) != null; ) { + + int idx = line.indexOf(OS_ARCH); + if (idx >= 0) { + osArch = line.substring(idx + OS_ARCH.length()).trim(); + if (version != null && vendor != null) + break; + else + continue; + } + + idx = line.indexOf(JAVA_VERSION); + if (idx >= 0) { + version = line.substring(idx + JAVA_VERSION.length()).trim(); + if (osArch != null && vendor != null) + break; + else + continue; + } + + idx = line.indexOf(JAVA_VENDOR); + if (idx >= 0) { + vendor = line.substring(idx + JAVA_VENDOR.length()).trim(); + if (osArch != null && version != null) + break; + else + //noinspection UnnecessaryContinue + continue; + } + } + } + + if (osArch != null) + platform = Platform.getPlatform(OperatingSystem.CURRENT_OS, Architecture.parseArchName(osArch)); + + // Java 6 + if (version == null) { + boolean is64Bit = false; + process = new ProcessBuilder(executablePath, "-version").start(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream(), OperatingSystem.NATIVE_CHARSET))) { + for (String line; (line = reader.readLine()) != null; ) { + if (version == null) { + int idx = line.indexOf(VERSION_PREFIX); + if (idx >= 0) { + int begin = idx + VERSION_PREFIX.length(); + int end = line.indexOf('"', begin); + if (end >= 0) { + version = line.substring(begin, end); + } + } + } + + if (line.contains("64-Bit")) + is64Bit = true; + } + } + + if (platform == null) + platform = Platform.getPlatform(OperatingSystem.CURRENT_OS, is64Bit ? Architecture.X86_64 : Architecture.X86); + + if (version == null) + throw new IOException("Cannot determine version"); + } + + return new JavaInfo(platform, version, vendor); + } + + public static final JavaInfo CURRENT_ENVIRONMENT = new JavaInfo(Platform.CURRENT_PLATFORM, System.getProperty("java.version"), System.getProperty("java.vendor")); + + private final Platform platform; + private final String version; + private final @Nullable String vendor; + + private final transient int parsedVersion; + private final transient VersionNumber versionNumber; + + public JavaInfo(Platform platform, String version, @Nullable String vendor) { + this.platform = platform; + this.version = version; + this.parsedVersion = parseVersion(version); + this.versionNumber = VersionNumber.asVersion(version); + this.vendor = vendor; + } + + public Platform getPlatform() { + return platform; + } + + public String getVersion() { + return version; + } + + public VersionNumber getVersionNumber() { + return versionNumber; + } + + public int getParsedVersion() { + return parsedVersion; + } + + public @Nullable String getVendor() { + return vendor; + } + + @Override + public String toString() { + return JsonUtils.GSON.toJson(this); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/java/JavaRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/java/JavaRepository.java new file mode 100644 index 0000000000..2fda58be71 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/java/JavaRepository.java @@ -0,0 +1,44 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.java; + +import org.jackhuang.hmcl.download.DownloadProvider; +import org.jackhuang.hmcl.game.GameJavaVersion; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.platform.Platform; + +import java.nio.file.Path; +import java.util.Collection; + +/** + * @author Glavo + */ +public interface JavaRepository { + + Path getJavaDir(Platform platform, String name); + + Path getManifestFile(Platform platform, String name); + + Collection getAllJava(Platform platform); + + Task getDownloadJavaTask(DownloadProvider downloadProvider, Platform platform, GameJavaVersion gameJavaVersion); + + Task getUninstallJavaTask(Platform platform, String name); + + Task getUninstallJavaTask(JavaRuntime java); +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/java/JavaRuntime.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/java/JavaRuntime.java new file mode 100644 index 0000000000..dffdab18ea --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/java/JavaRuntime.java @@ -0,0 +1,156 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.java; + +import org.jackhuang.hmcl.util.platform.Architecture; +import org.jackhuang.hmcl.util.platform.Bits; +import org.jackhuang.hmcl.util.platform.OperatingSystem; +import org.jackhuang.hmcl.util.platform.Platform; +import org.jackhuang.hmcl.util.versioning.VersionNumber; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * @author Glavo + */ +public final class JavaRuntime implements Comparable { + + public static JavaRuntime of(Path binary, JavaInfo info, boolean isManaged) { + String javacName = info.getPlatform().getOperatingSystem() == OperatingSystem.WINDOWS ? "javac.exe" : "javac"; + return new JavaRuntime(binary, info, isManaged, Files.isRegularFile(binary.resolveSibling(javacName))); + } + + private final Path binary; + private final JavaInfo info; + private final boolean isManaged; + private final boolean isJDK; + + public JavaRuntime(Path binary, JavaInfo info, boolean isManaged, boolean isJDK) { + this.binary = binary; + this.info = info; + this.isManaged = isManaged; + this.isJDK = isJDK; + } + + public boolean isManaged() { + return isManaged; + } + + public Path getBinary() { + return binary; + } + + public String getVersion() { + return info.getVersion(); + } + + public Platform getPlatform() { + return info.getPlatform(); + } + + public Architecture getArchitecture() { + return getPlatform().getArchitecture(); + } + + public Bits getBits() { + return getPlatform().getBits(); + } + + public VersionNumber getVersionNumber() { + return info.getVersionNumber(); + } + + /** + * The major version of Java installation. + */ + public int getParsedVersion() { + return info.getParsedVersion(); + } + + public String getVendor() { + return info.getVendor(); + } + + public boolean isJDK() { + return isJDK; + } + + @Override + public int compareTo(@NotNull JavaRuntime that) { + if (this.isManaged != that.isManaged) { + return this.isManaged ? -1 : 1; + } + + int c = Integer.compare(this.getParsedVersion(), that.getParsedVersion()); + if (c != 0) + return c; + + c = this.getVersionNumber().compareTo(that.getVersionNumber()); + if (c != 0) + return c; + + c = this.getArchitecture().compareTo(that.getArchitecture()); + if (c != 0) + return c; + + return this.getBinary().compareTo(that.getBinary()); + } + + @Override + public int hashCode() { + return binary.hashCode(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof JavaRuntime)) return false; + JavaRuntime that = (JavaRuntime) o; + return this.getBinary().equals(that.getBinary()); + } + + public static final JavaRuntime CURRENT_JAVA; + public static final int CURRENT_VERSION; + + public static JavaRuntime getDefault() { + return CURRENT_JAVA; + } + + static { + String javaHome = System.getProperty("java.home"); + Path executable = null; + if (javaHome != null) { + executable = Paths.get(javaHome, "bin", OperatingSystem.CURRENT_OS.getJavaExecutable()); + try { + executable = executable.toRealPath(); + } catch (IOException ignored) { + } + + if (!Files.isRegularFile(executable)) { + executable = null; + } + } + + CURRENT_JAVA = executable != null ? JavaRuntime.of(executable, JavaInfo.CURRENT_ENVIRONMENT, false) : null; + CURRENT_VERSION = JavaInfo.CURRENT_ENVIRONMENT.getParsedVersion(); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java index 2d27b56233..6bb0494739 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java @@ -133,7 +133,7 @@ private Command generateCommandLine(File nativeFolder) throws IOException { res.addDefault("-Xms", options.getMinMemory() + "m"); if (options.getMetaspace() != null && options.getMetaspace() > 0) - if (options.getJava().getParsedVersion() < JavaVersion.JAVA_8) + if (options.getJava().getParsedVersion() < 8) res.addDefault("-XX:PermSize=", options.getMetaspace() + "m"); else res.addDefault("-XX:MetaspaceSize=", options.getMetaspace() + "m"); @@ -186,7 +186,7 @@ private Command generateCommandLine(File nativeFolder) throws IOException { res.addDefault("-Duser.home=", options.getGameDir().getParent()); // Using G1GC with its settings by default - if (options.getJava().getParsedVersion() >= JavaVersion.JAVA_8 + if (options.getJava().getParsedVersion() >= 8 && res.noneMatch(arg -> "-XX:-UseG1GC".equals(arg) || (arg.startsWith("-XX:+Use") && arg.endsWith("GC")))) { res.addUnstableDefault("UnlockExperimentalVMOptions", true); res.addUnstableDefault("UseG1GC", true); @@ -206,7 +206,7 @@ private Command generateCommandLine(File nativeFolder) throws IOException { res.addDefault("-Xss", "1m"); } - if (options.getJava().getParsedVersion() == JavaVersion.JAVA_16) + if (options.getJava().getParsedVersion() == 16) res.addDefault("--illegal-access=", "permit"); res.addDefault("-Dfml.ignoreInvalidMinecraftCertificates=", "true"); @@ -308,7 +308,7 @@ public Map getFeatures() { } private final Map> forbiddens = mapOf( - pair("-Xincgc", () -> options.getJava().getParsedVersion() >= JavaVersion.JAVA_9) + pair("-Xincgc", () -> options.getJava().getParsedVersion() >= 9) ); protected Map> getForbiddens() { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModpackConfiguration.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModpackConfiguration.java index e936db80d7..743f930454 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModpackConfiguration.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModpackConfiguration.java @@ -18,6 +18,7 @@ package org.jackhuang.hmcl.mod; import com.google.gson.JsonParseException; +import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.util.Immutable; import org.jackhuang.hmcl.util.gson.Validation; import org.jetbrains.annotations.Nullable; @@ -29,6 +30,11 @@ @Immutable public final class ModpackConfiguration implements Validation { + @SuppressWarnings("unchecked") + public static TypeToken> typeOf(Class clazz) { + return (TypeToken>) TypeToken.getParameterized(ModpackConfiguration.class, clazz); + } + private final T manifest; private final String type; private final String name; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java index 2b6c031326..3913ef7f0e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java @@ -38,6 +38,7 @@ import static org.jackhuang.hmcl.util.Lang.mapOf; import static org.jackhuang.hmcl.util.Pair.pair; +import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf; public final class CurseForgeRemoteModRepository implements RemoteModRepository { @@ -94,6 +95,10 @@ private String toSortOrder(SortOrder sortOrder) { return "asc"; } + private int calculateTotalPages(Response> response, int pageSize) { + return (int) Math.ceil((double) Math.min(response.pagination.totalCount, 10000) / pageSize); + } + @Override public SearchResult search(String gameVersion, @Nullable RemoteModRepository.Category category, int pageOffset, int pageSize, String searchFilter, SortType sortType, SortOrder sortOrder) throws IOException { int categoryId = 0; @@ -109,10 +114,9 @@ public SearchResult search(String gameVersion, @Nullable RemoteModRepository.Cat pair("index", Integer.toString(pageOffset * pageSize)), pair("pageSize", Integer.toString(pageSize))) .header("X-API-KEY", apiKey) - .getJson(new TypeToken>>() { - }.getType()); + .getJson(Response.typeOf(listTypeOf(CurseAddon.class))); if (searchFilter.isEmpty()) { - return new SearchResult(response.getData().stream().map(CurseAddon::toMod), (int)Math.ceil((double)response.pagination.totalCount / pageSize)); + return new SearchResult(response.getData().stream().map(CurseAddon::toMod), calculateTotalPages(response, pageSize)); } // https://github.com/HMCL-dev/HMCL/issues/1549 @@ -135,7 +139,7 @@ public SearchResult search(String gameVersion, @Nullable RemoteModRepository.Cat } return pair(remoteMod, diff); - }).sorted(Comparator.comparingInt(Pair::getValue)).map(Pair::getKey), response.getData().stream().map(CurseAddon::toMod), response.pagination.totalCount); + }).sorted(Comparator.comparingInt(Pair::getValue)).map(Pair::getKey), response.getData().stream().map(CurseAddon::toMod), calculateTotalPages(response, pageSize)); } @Override @@ -156,11 +160,10 @@ public Optional getRemoteVersionByLocalFile(LocalModFile loca long hash = Integer.toUnsignedLong(MurmurHash2.hash32(baos.toByteArray(), baos.size(), 1)); - Response response = HttpRequest.POST(PREFIX + "/v1/fingerprints") + Response response = HttpRequest.POST(PREFIX + "/v1/fingerprints/432") .json(mapOf(pair("fingerprints", Collections.singletonList(hash)))) .header("X-API-KEY", apiKey) - .getJson(new TypeToken>() { - }.getType()); + .getJson(Response.typeOf(FingerprintMatchesResult.class)); if (response.getData().getExactMatches() == null || response.getData().getExactMatches().isEmpty()) { return Optional.empty(); @@ -173,8 +176,7 @@ public Optional getRemoteVersionByLocalFile(LocalModFile loca public RemoteMod getModById(String id) throws IOException { Response response = HttpRequest.GET(PREFIX + "/v1/mods/" + id) .header("X-API-KEY", apiKey) - .getJson(new TypeToken>() { - }.getType()); + .getJson(Response.typeOf(CurseAddon.class)); return response.data.toMod(); } @@ -182,8 +184,7 @@ public RemoteMod getModById(String id) throws IOException { public RemoteMod.File getModFile(String modId, String fileId) throws IOException { Response response = HttpRequest.GET(String.format("%s/v1/mods/%s/files/%s", PREFIX, modId, fileId)) .header("X-API-KEY", apiKey) - .getJson(new TypeToken>() { - }.getType()); + .getJson(Response.typeOf(CurseAddon.LatestFile.class)); return response.getData().toVersion().getFile(); } @@ -192,16 +193,14 @@ public Stream getRemoteVersionsById(String id) throws IOExcep Response> response = HttpRequest.GET(PREFIX + "/v1/mods/" + id + "/files", pair("pageSize", "10000")) .header("X-API-KEY", apiKey) - .getJson(new TypeToken>>() { - }.getType()); + .getJson(Response.typeOf(listTypeOf(CurseAddon.LatestFile.class))); return response.getData().stream().map(CurseAddon.LatestFile::toVersion); } public List getCategoriesImpl() throws IOException { Response> categories = HttpRequest.GET(PREFIX + "/v1/categories", pair("gameId", "432")) .header("X-API-KEY", apiKey) - .getJson(new TypeToken>>() { - }.getType()); + .getJson(Response.typeOf(listTypeOf(CurseAddon.Category.class))); return reorganizeCategories(categories.getData(), section); } @@ -280,6 +279,17 @@ public int getTotalCount() { } public static class Response { + + @SuppressWarnings("unchecked") + public static TypeToken> typeOf(Class responseType) { + return (TypeToken>) TypeToken.getParameterized(Response.class, responseType); + } + + @SuppressWarnings("unchecked") + public static TypeToken> typeOf(TypeToken responseType) { + return (TypeToken>) TypeToken.getParameterized(Response.class, responseType.getType()); + } + private final T data; private final Pagination pagination; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseInstallTask.java index 6796cf3308..b40618c689 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseInstallTask.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.mod.curse; import com.google.gson.JsonParseException; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.GameBuilder; import org.jackhuang.hmcl.game.DefaultGameRepository; @@ -99,8 +98,7 @@ public CurseInstallTask(DefaultDependencyManager dependencyManager, File zipFile ModpackConfiguration config = null; try { if (json.exists()) { - config = JsonUtils.GSON.fromJson(FileUtils.readText(json), new TypeToken>() { - }.getType()); + config = JsonUtils.GSON.fromJson(FileUtils.readText(json), ModpackConfiguration.typeOf(CurseManifest.class)); if (!CurseModpackProvider.INSTANCE.getName().equals(config.getType())) throw new IllegalArgumentException("Version " + name + " is not a Curse modpack. Cannot update this version."); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackCompletionTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackCompletionTask.java index 93be7eb009..1df44c1ea7 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackCompletionTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackCompletionTask.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.mod.mcbbs; import com.google.gson.JsonParseException; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.game.DefaultGameRepository; import org.jackhuang.hmcl.mod.ModManager; @@ -88,8 +87,7 @@ public CompletableFuture getFuture(TaskCompletableFuture executor) { if (configuration == null) { // Load configuration from disk try { - configuration = JsonUtils.fromNonNullJson(FileUtils.readText(configurationFile), new TypeToken>() { - }.getType()); + configuration = JsonUtils.fromNonNullJson(FileUtils.readText(configurationFile), ModpackConfiguration.typeOf(McbbsModpackManifest.class)); } catch (IOException | JsonParseException e) { throw new IOException("Malformed modpack configuration"); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackLocalInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackLocalInstallTask.java index 3cee33c603..7bd81ae526 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackLocalInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackLocalInstallTask.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.mod.mcbbs; import com.google.gson.JsonParseException; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.GameBuilder; import org.jackhuang.hmcl.game.DefaultGameRepository; @@ -80,8 +79,7 @@ public McbbsModpackLocalInstallTask(DefaultDependencyManager dependencyManager, ModpackConfiguration config = null; try { if (json.exists()) { - config = JsonUtils.GSON.fromJson(FileUtils.readText(json), new TypeToken>() { - }.getType()); + config = JsonUtils.GSON.fromJson(FileUtils.readText(json), ModpackConfiguration.typeOf(McbbsModpackManifest.class)); if (!McbbsModpackProvider.INSTANCE.getName().equals(config.getType())) throw new IllegalArgumentException("Version " + name + " is not a Mcbbs modpack. Cannot update this version."); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackProvider.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackProvider.java index 2deaf72333..6a7ab07281 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackProvider.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackProvider.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.mod.mcbbs; import com.google.gson.JsonParseException; -import com.google.gson.reflect.TypeToken; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipFile; import org.jackhuang.hmcl.download.DefaultDependencyManager; @@ -56,8 +55,7 @@ public Task createUpdateTask(DefaultDependencyManager dependencyManager, Stri @Override public void injectLaunchOptions(String modpackConfigurationJson, LaunchOptions.Builder builder) { - ModpackConfiguration config = JsonUtils.GSON.fromJson(modpackConfigurationJson, new TypeToken>() { - }.getType()); + ModpackConfiguration config = JsonUtils.GSON.fromJson(modpackConfigurationJson, ModpackConfiguration.typeOf(McbbsModpackManifest.class)); if (!getName().equals(config.getType())) { throw new IllegalArgumentException("Incorrect manifest type, actual=" + config.getType() + ", expected=" + getName()); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackRemoteInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackRemoteInstallTask.java index 3e970980e6..4550273d8e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackRemoteInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackRemoteInstallTask.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.mod.mcbbs; import com.google.gson.JsonParseException; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.GameBuilder; import org.jackhuang.hmcl.game.DefaultGameRepository; @@ -66,8 +65,7 @@ public McbbsModpackRemoteInstallTask(DefaultDependencyManager dependencyManager, ModpackConfiguration config = null; try { if (json.exists()) { - config = JsonUtils.GSON.fromJson(FileUtils.readText(json), new TypeToken>() { - }.getType()); + config = JsonUtils.GSON.fromJson(FileUtils.readText(json), ModpackConfiguration.typeOf(McbbsModpackManifest.class)); if (!MODPACK_TYPE.equals(config.getType())) throw new IllegalArgumentException("Version " + name + " is not a Mcbbs modpack. Cannot update this version."); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeOldModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeOldModMetadata.java index 067c5b8c25..78b673ece9 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeOldModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeOldModMetadata.java @@ -19,7 +19,6 @@ import com.google.gson.JsonParseException; import com.google.gson.annotations.SerializedName; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.mod.LocalModFile; import org.jackhuang.hmcl.mod.ModLoaderType; import org.jackhuang.hmcl.mod.ModManager; @@ -34,6 +33,8 @@ import java.nio.file.Path; import java.util.List; +import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf; + /** * * @author huangyuhui @@ -125,9 +126,7 @@ public static LocalModFile fromFile(ModManager modManager, Path modFile, FileSys Path mcmod = fs.getPath("mcmod.info"); if (Files.notExists(mcmod)) throw new IOException("File " + modFile + " is not a Forge mod."); - List modList = JsonUtils.GSON.fromJson(FileUtils.readText(mcmod), - new TypeToken>() { - }.getType()); + List modList = JsonUtils.GSON.fromJson(FileUtils.readText(mcmod), listTypeOf(ForgeOldModMetadata.class)); if (modList == null || modList.isEmpty()) throw new IOException("Mod " + modFile + " `mcmod.info` is malformed.."); ForgeOldModMetadata metadata = modList.get(0); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthInstallTask.java index 64ccc7c2f6..b796413d4b 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthInstallTask.java @@ -18,12 +18,10 @@ package org.jackhuang.hmcl.mod.modrinth; import com.google.gson.JsonParseException; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.GameBuilder; import org.jackhuang.hmcl.game.DefaultGameRepository; import org.jackhuang.hmcl.mod.*; -import org.jackhuang.hmcl.mod.curse.CurseManifest; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.io.FileUtils; @@ -95,8 +93,7 @@ public ModrinthInstallTask(DefaultDependencyManager dependencyManager, File zipF ModpackConfiguration config = null; try { if (json.exists()) { - config = JsonUtils.GSON.fromJson(FileUtils.readText(json), new TypeToken>() { - }.getType()); + config = JsonUtils.GSON.fromJson(FileUtils.readText(json), ModpackConfiguration.typeOf(ModrinthManifest.class)); if (!ModrinthModpackProvider.INSTANCE.getName().equals(config.getType())) throw new IllegalArgumentException("Version " + name + " is not a Modrinth modpack. Cannot update this version."); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java index 4cc9e06329..c257a38816 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java @@ -39,6 +39,7 @@ import static org.jackhuang.hmcl.util.Lang.mapOf; import static org.jackhuang.hmcl.util.Pair.pair; +import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf; public final class ModrinthRemoteModRepository implements RemoteModRepository { public static final ModrinthRemoteModRepository MODS = new ModrinthRemoteModRepository("mod"); @@ -93,9 +94,8 @@ public SearchResult search(String gameVersion, @Nullable RemoteModRepository.Cat pair("index", convertSortType(sort)) ); Response response = HttpRequest.GET(NetworkUtils.withQuery(PREFIX + "/v2/search", query)) - .getJson(new TypeToken>() { - }.getType()); - return new SearchResult(response.getHits().stream().map(ProjectSearchResult::toMod), (int)Math.ceil((double)response.totalHits / pageSize)); + .getJson(Response.typeOf(ProjectSearchResult.class)); + return new SearchResult(response.getHits().stream().map(ProjectSearchResult::toMod), (int) Math.ceil((double) response.totalHits / pageSize)); } @Override @@ -132,13 +132,12 @@ public RemoteMod.File getModFile(String modId, String fileId) throws IOException public Stream getRemoteVersionsById(String id) throws IOException { id = StringUtils.removePrefix(id, "local-"); List versions = HttpRequest.GET(PREFIX + "/v2/project/" + id + "/version") - .getJson(new TypeToken>() { - }.getType()); + .getJson(listTypeOf(ProjectVersion.class)); return versions.stream().map(ProjectVersion::toVersion).flatMap(Lang::toStream); } public List getCategoriesImpl() throws IOException { - List categories = HttpRequest.GET(PREFIX + "/v2/tag/category").getJson(new TypeToken>() {}.getType()); + List categories = HttpRequest.GET(PREFIX + "/v2/tag/category").getJson(listTypeOf(Category.class)); return categories.stream().filter(category -> category.getProjectType().equals(projectType)).collect(Collectors.toList()); } @@ -696,6 +695,12 @@ public RemoteMod toMod() { } public static class Response { + + @SuppressWarnings("unchecked") + public static TypeToken> typeOf(Class responseType) { + return (TypeToken>) TypeToken.getParameterized(Response.class, responseType); + } + private final int offset; private final int limit; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java index 017f722445..973da7726e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.mod.multimc; import com.google.gson.JsonParseException; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.GameBuilder; import org.jackhuang.hmcl.game.Arguments; @@ -128,8 +127,7 @@ public void preExecute() throws Exception { ModpackConfiguration config = null; try { if (json.exists()) { - config = JsonUtils.GSON.fromJson(FileUtils.readText(json), new TypeToken>() { - }.getType()); + config = JsonUtils.GSON.fromJson(FileUtils.readText(json), ModpackConfiguration.typeOf(MultiMCInstanceConfiguration.class)); if (!MultiMCModpackProvider.INSTANCE.getName().equals(config.getType())) throw new IllegalArgumentException("Version " + name + " is not a MultiMC modpack. Cannot update this version."); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackCompletionTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackCompletionTask.java index 9f7ddfb95c..3130f24e0e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackCompletionTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackCompletionTask.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.mod.server; import com.google.gson.JsonParseException; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.GameBuilder; import org.jackhuang.hmcl.game.DefaultGameRepository; @@ -66,8 +65,7 @@ public ServerModpackCompletionTask(DefaultDependencyManager dependencyManager, S try { File manifestFile = repository.getModpackConfiguration(version); if (manifestFile.exists()) { - this.manifest = JsonUtils.GSON.fromJson(FileUtils.readText(manifestFile), new TypeToken>() { - }.getType()); + this.manifest = JsonUtils.GSON.fromJson(FileUtils.readText(manifestFile), ModpackConfiguration.typeOf(ServerModpackManifest.class)); } } catch (Exception e) { LOG.warning("Unable to read Server modpack manifest.json", e); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackLocalInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackLocalInstallTask.java index b23753f304..fec92572d3 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackLocalInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackLocalInstallTask.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.mod.server; import com.google.gson.JsonParseException; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.GameBuilder; import org.jackhuang.hmcl.game.DefaultGameRepository; @@ -72,8 +71,7 @@ public ServerModpackLocalInstallTask(DefaultDependencyManager dependencyManager, ModpackConfiguration config = null; try { if (json.exists()) { - config = JsonUtils.GSON.fromJson(FileUtils.readText(json), new TypeToken>() { - }.getType()); + config = JsonUtils.GSON.fromJson(FileUtils.readText(json), ModpackConfiguration.typeOf(ServerModpackManifest.class)); if (!ServerModpackProvider.INSTANCE.getName().equals(config.getType())) throw new IllegalArgumentException("Version " + name + " is not a Server modpack. Cannot update this version."); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackRemoteInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackRemoteInstallTask.java index eddafb81ae..e6bb9b5565 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackRemoteInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackRemoteInstallTask.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.mod.server; import com.google.gson.JsonParseException; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.GameBuilder; import org.jackhuang.hmcl.game.DefaultGameRepository; @@ -63,11 +62,10 @@ public ServerModpackRemoteInstallTask(DefaultDependencyManager dependencyManager repository.removeVersionFromDisk(name); }); - ModpackConfiguration config = null; + ModpackConfiguration config; try { if (json.exists()) { - config = JsonUtils.GSON.fromJson(FileUtils.readText(json), new TypeToken>() { - }.getType()); + config = JsonUtils.GSON.fromJson(FileUtils.readText(json), ModpackConfiguration.typeOf(ServerModpackManifest.class)); if (!MODPACK_TYPE.equals(config.getType())) throw new IllegalArgumentException("Version " + name + " is not a Server modpack. Cannot update this version."); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/CacheRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/CacheRepository.java index 89082d4cc5..223ff4d631 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/CacheRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/CacheRepository.java @@ -19,9 +19,7 @@ import com.google.gson.JsonParseException; import com.google.gson.annotations.SerializedName; -import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.util.function.ExceptionalSupplier; -import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.io.IOUtils; @@ -46,6 +44,7 @@ import java.util.stream.Stream; import static java.nio.charset.StandardCharsets.UTF_8; +import static org.jackhuang.hmcl.util.gson.JsonUtils.*; import static org.jackhuang.hmcl.util.logging.Logger.LOG; public class CacheRepository { @@ -68,7 +67,7 @@ public void changeDirectory(Path commonDir) { } if (Files.isRegularFile(indexFile)) { - ETagIndex raw = JsonUtils.GSON.fromJson(FileUtils.readText(indexFile), ETagIndex.class); + ETagIndex raw = GSON.fromJson(FileUtils.readText(indexFile), ETagIndex.class); if (raw == null) index = new HashMap<>(); else @@ -289,10 +288,10 @@ public void saveETagIndex() throws IOException { try (FileChannel channel = FileChannel.open(indexFile, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE)) { FileLock lock = channel.lock(); try { - ETagIndex indexOnDisk = JsonUtils.fromMaybeMalformedJson(new String(IOUtils.readFullyWithoutClosing(Channels.newInputStream(channel)), UTF_8), ETagIndex.class); + ETagIndex indexOnDisk = fromMaybeMalformedJson(new String(IOUtils.readFullyWithoutClosing(Channels.newInputStream(channel)), UTF_8), ETagIndex.class); Map newIndex = joinETagIndexes(indexOnDisk == null ? null : indexOnDisk.eTag, index.values()); channel.truncate(0); - ByteBuffer writeTo = ByteBuffer.wrap(JsonUtils.GSON.toJson(new ETagIndex(newIndex.values())).getBytes(UTF_8)); + ByteBuffer writeTo = ByteBuffer.wrap(GSON.toJson(new ETagIndex(newIndex.values())).getBytes(UTF_8)); while (writeTo.hasRemaining()) { if (channel.write(writeTo) == 0) { throw new IOException("No value is written"); @@ -412,8 +411,7 @@ private void changeDirectory(Path cacheDirectory) { try { indexFile = cacheDirectory.resolve(name + ".json"); if (Files.isRegularFile(indexFile)) { - joinEntries(JsonUtils.fromNonNullJson(FileUtils.readText(indexFile), new TypeToken>() { - }.getType())); + joinEntries(fromNonNullJson(FileUtils.readText(indexFile), mapTypeOf(String.class, Object.class))); } } catch (IOException | JsonParseException e) { LOG.warning("Unable to read storage {" + name + "} file"); @@ -426,12 +424,11 @@ public void saveToFile() { try (FileChannel channel = FileChannel.open(indexFile, StandardOpenOption.READ, StandardOpenOption.WRITE)) { FileLock lock = channel.lock(); try { - Map indexOnDisk = JsonUtils.fromMaybeMalformedJson(new String(IOUtils.readFullyWithoutClosing(Channels.newInputStream(channel)), UTF_8), new TypeToken>() { - }.getType()); + Map indexOnDisk = fromMaybeMalformedJson(new String(IOUtils.readFullyWithoutClosing(Channels.newInputStream(channel)), UTF_8), mapTypeOf(String.class, Object.class)); if (indexOnDisk == null) indexOnDisk = new HashMap<>(); indexOnDisk.putAll(storage); channel.truncate(0); - channel.write(ByteBuffer.wrap(JsonUtils.GSON.toJson(storage).getBytes(UTF_8))); + channel.write(ByteBuffer.wrap(GSON.toJson(storage).getBytes(UTF_8))); this.storage = indexOnDisk; } finally { lock.release(); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/FXThread.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/FXThread.java new file mode 100644 index 0000000000..53974cb0a2 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/FXThread.java @@ -0,0 +1,32 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.util; + +import java.lang.annotation.*; + +/** + * Mark a method that can only be called on the JavaFX Application Thread, + * or mark a field that can only be read/modified on the JavaFX Application Thread. + * + * @author Glavo + */ +@Documented +@Target({ElementType.METHOD, ElementType.FIELD}) +@Retention(RetentionPolicy.SOURCE) +public @interface FXThread { +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/KeyValuePairProperties.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/KeyValuePairProperties.java new file mode 100644 index 0000000000..042527227d --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/KeyValuePairProperties.java @@ -0,0 +1,94 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.util; + +import java.io.BufferedReader; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.LinkedHashMap; + +/** + * @author Glavo + */ +public final class KeyValuePairProperties extends LinkedHashMap { + public static KeyValuePairProperties load(Path file) throws IOException { + try (BufferedReader reader = Files.newBufferedReader(file)) { + return load(reader); + } + } + + public static KeyValuePairProperties load(BufferedReader reader) throws IOException { + KeyValuePairProperties result = new KeyValuePairProperties(); + + String line; + while ((line = reader.readLine()) != null) { + if (line.startsWith("#")) + continue; + + int idx = line.indexOf('='); + if (idx <= 0) + continue; + + String name = line.substring(0, idx); + String value; + + if (line.length() > idx + 2 && line.charAt(idx + 1) == '"' && line.charAt(line.length() - 1) == '"') { + if (line.indexOf('\\', idx + 1) < 0) { + value = line.substring(idx + 2, line.length() - 1); + } else { + StringBuilder builder = new StringBuilder(); + for (int i = idx + 2, end = line.length() - 1; i < end; i++) { + char ch = line.charAt(i); + if (ch == '\\' && i < end - 1) { + char nextChar = line.charAt(++i); + switch (nextChar) { + case 'n': + builder.append('\n'); + break; + case 'r': + builder.append('\r'); + break; + case 't': + builder.append('\t'); + break; + case 'f': + builder.append('\f'); + break; + case 'b': + builder.append('\b'); + break; + default: + builder.append(nextChar); + break; + } + } else { + builder.append(ch); + } + } + value = builder.toString(); + } + } else { + value = line.substring(idx + 1); + } + + result.put(name, value); + } + return result; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java index 06a1f8ab73..ccd6bd1733 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java @@ -346,9 +346,15 @@ public static String parseColorEscapes(String original) { } public static String parseEscapeSequence(String str) { - StringBuilder builder = new StringBuilder(); + int idx = str.indexOf('\033'); + if (idx < 0) + return str; + + StringBuilder builder = new StringBuilder(str.length()); boolean inEscape = false; - for (int i = 0; i < str.length(); i++) { + + builder.append(str, 0, idx); + for (int i = idx; i < str.length(); i++) { char ch = str.charAt(i); if (ch == '\033') { inEscape = true; @@ -371,15 +377,15 @@ public static String repeats(char ch, int repeat) { return result.toString(); } - public static int MAX_SHORT_STRING_LENGTH = 77; + public static String truncate(String str, int limit) { + assert limit > 5; - public static Optional truncate(String str) { - if (str.length() <= MAX_SHORT_STRING_LENGTH) { - return Optional.empty(); + if (str.length() <= limit) { + return str; } - final int halfLength = (MAX_SHORT_STRING_LENGTH - 5) / 2; - return Optional.of(str.substring(0, halfLength) + " ... " + str.substring(str.length() - halfLength)); + final int halfLength = (limit - 5) / 2; + return str.substring(0, halfLength) + " ... " + str.substring(str.length() - halfLength); } public static boolean isASCII(String cs) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/JsonUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/JsonUtils.java index cbd5f88e25..bfd07080ac 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/JsonUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/JsonUtils.java @@ -21,19 +21,22 @@ import com.google.gson.GsonBuilder; import com.google.gson.JsonParseException; import com.google.gson.JsonSyntaxException; +import com.google.gson.reflect.TypeToken; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; import java.time.Instant; +import java.util.List; +import java.util.Map; import java.util.UUID; /** * @author yushijinhun */ +@SuppressWarnings("unchecked") public final class JsonUtils { public static final Gson GSON = defaultGsonBuilder().create(); @@ -47,13 +50,29 @@ public final class JsonUtils { private JsonUtils() { } + public static TypeToken> listTypeOf(Class elementType) { + return (TypeToken>) TypeToken.getParameterized(List.class, elementType); + } + + public static TypeToken> listTypeOf(TypeToken elementType) { + return (TypeToken>) TypeToken.getParameterized(List.class, elementType.getType()); + } + + public static TypeToken> mapTypeOf(Class keyType, Class valueType) { + return (TypeToken>) TypeToken.getParameterized(Map.class, keyType, valueType); + } + + public static TypeToken> mapTypeOf(Class keyType, TypeToken valueType) { + return (TypeToken>) TypeToken.getParameterized(Map.class, keyType, valueType.getType()); + } + public static T fromJsonFully(InputStream json, Class classOfT) throws IOException, JsonParseException { try (InputStreamReader reader = new InputStreamReader(json, StandardCharsets.UTF_8)) { return GSON.fromJson(reader, classOfT); } } - public static T fromJsonFully(InputStream json, Type type) throws IOException, JsonParseException { + public static T fromJsonFully(InputStream json, TypeToken type) throws IOException, JsonParseException { try (InputStreamReader reader = new InputStreamReader(json, StandardCharsets.UTF_8)) { return GSON.fromJson(reader, type); } @@ -66,7 +85,7 @@ public static T fromNonNullJson(String json, Class classOfT) throws JsonP return parsed; } - public static T fromNonNullJson(String json, Type type) throws JsonParseException { + public static T fromNonNullJson(String json, TypeToken type) throws JsonParseException { T parsed = GSON.fromJson(json, type); if (parsed == null) throw new JsonParseException("Json object cannot be null."); @@ -82,7 +101,7 @@ public static T fromNonNullJsonFully(InputStream json, Class classOfT) th } } - public static T fromNonNullJsonFully(InputStream json, Type type) throws IOException, JsonParseException { + public static T fromNonNullJsonFully(InputStream json, TypeToken type) throws IOException, JsonParseException { try (InputStreamReader reader = new InputStreamReader(json, StandardCharsets.UTF_8)) { T parsed = GSON.fromJson(reader, type); if (parsed == null) @@ -99,7 +118,7 @@ public static T fromMaybeMalformedJson(String json, Class classOfT) throw } } - public static T fromMaybeMalformedJson(String json, Type type) throws JsonParseException { + public static T fromMaybeMalformedJson(String json, TypeToken type) throws JsonParseException { try { return GSON.fromJson(json, type); } catch (JsonSyntaxException e) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/HttpRequest.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/HttpRequest.java index c8dbc8792c..4fb0fead5b 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/HttpRequest.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/HttpRequest.java @@ -18,6 +18,7 @@ package org.jackhuang.hmcl.util.io; import com.google.gson.JsonParseException; +import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.util.Pair; import org.jackhuang.hmcl.util.function.ExceptionalBiConsumer; @@ -26,7 +27,6 @@ import java.io.IOException; import java.io.OutputStream; -import java.lang.reflect.Type; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; @@ -101,7 +101,7 @@ public T getJson(Class typeOfT) throws IOException, JsonParseException { return JsonUtils.fromNonNullJson(getString(), typeOfT); } - public T getJson(Type type) throws IOException, JsonParseException { + public T getJson(TypeToken type) throws IOException, JsonParseException { return JsonUtils.fromNonNullJson(getString(), type); } @@ -109,7 +109,7 @@ public CompletableFuture getJsonAsync(Class typeOfT) { return getStringAsync().thenApplyAsync(jsonString -> JsonUtils.fromNonNullJson(jsonString, typeOfT)); } - public CompletableFuture getJsonAsync(Type type) { + public CompletableFuture getJsonAsync(TypeToken type) { return getStringAsync().thenApplyAsync(jsonString -> JsonUtils.fromNonNullJson(jsonString, type)); } @@ -187,13 +187,18 @@ public String getString() throws IOException { os.write(bytes); } + URL url = new URL(this.url); + if (responseCodeTester != null) { - responseCodeTester.accept(new URL(url), con.getResponseCode()); + responseCodeTester.accept(url, con.getResponseCode()); } else { if (con.getResponseCode() / 100 != 2) { if (!ignoreHttpCode && !toleratedHttpCodes.contains(con.getResponseCode())) { - String data = NetworkUtils.readData(con); - throw new ResponseCodeException(new URL(url), con.getResponseCode(), data); + try { + throw new ResponseCodeException(url, con.getResponseCode(), NetworkUtils.readData(con)); + } catch (IOException e) { + throw new ResponseCodeException(url, con.getResponseCode(), e); + } } } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/IOUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/IOUtils.java index fc9e265949..82830279b1 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/IOUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/IOUtils.java @@ -90,4 +90,21 @@ public static void copyTo(InputStream src, OutputStream dest, byte[] buf) throws public static InputStream wrapFromGZip(InputStream inputStream) throws IOException { return new GZIPInputStream(inputStream); } + + public static void closeQuietly(AutoCloseable closeable) { + try { + if (closeable != null) + closeable.close(); + } catch (Throwable ignored) { + } + } + + public static void closeQuietly(AutoCloseable closeable, Throwable exception) { + try { + if (closeable != null) + closeable.close(); + } catch (Throwable e) { + exception.addSuppressed(e); + } + } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/ResponseCodeException.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/ResponseCodeException.java index 19d4d9a3fd..b386497646 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/ResponseCodeException.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/ResponseCodeException.java @@ -33,6 +33,13 @@ public ResponseCodeException(URL url, int responseCode) { this.data = null; } + public ResponseCodeException(URL url, int responseCode, Throwable cause) { + super("Unable to request url " + url + ", response code: " + responseCode, cause); + this.url = url; + this.responseCode = responseCode; + this.data = null; + } + public ResponseCodeException(URL url, int responseCode, String data) { super("Unable to request url " + url + ", response code: " + responseCode + ", data: " + data); this.url = url; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/Architecture.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/Architecture.java index 9f84eca094..bbfd0caf3f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/Architecture.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/Architecture.java @@ -176,6 +176,9 @@ public static Architecture parseArchName(String value) { return LOONGARCH64_OW; return LOONGARCH64; } + case "loongarch64_ow": { + return LOONGARCH64_OW; + } default: if (value.startsWith("armv7")) { return ARM32; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/JavaVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/JavaVersion.java deleted file mode 100644 index 633942e3cd..0000000000 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/JavaVersion.java +++ /dev/null @@ -1,448 +0,0 @@ -/* - * Hello Minecraft! Launcher - * Copyright (C) 2020 huangyuhui and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.jackhuang.hmcl.util.platform; - -import org.jackhuang.hmcl.download.java.JavaRepository; -import org.jackhuang.hmcl.task.Schedulers; -import org.jackhuang.hmcl.util.Lang; -import org.jackhuang.hmcl.util.StringUtils; -import org.jackhuang.hmcl.util.io.FileUtils; -import org.jackhuang.hmcl.util.versioning.VersionNumber; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.file.*; -import java.util.*; -import java.util.concurrent.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Stream; - -import static java.util.stream.Collectors.toList; -import static org.jackhuang.hmcl.util.logging.Logger.LOG; - -/** - * Represents a Java installation. - * - * @author huangyuhui - */ -public final class JavaVersion { - - private final Path binary; - private final String longVersion; - private final Platform platform; - private final int version; - private final VersionNumber versionNumber; - - public JavaVersion(Path binary, String longVersion, Platform platform) { - this.binary = binary; - this.longVersion = longVersion; - this.platform = platform; - - if (longVersion != null) { - version = parseVersion(longVersion); - versionNumber = VersionNumber.asVersion(longVersion); - } else { - version = UNKNOWN; - versionNumber = null; - } - } - - public String toString() { - return "JavaVersion {" + binary + ", " + longVersion + "(" + version + ")" + ", " + platform + "}"; - } - - public Path getBinary() { - return binary; - } - - public String getVersion() { - return longVersion; - } - - public Platform getPlatform() { - return platform; - } - - public Architecture getArchitecture() { - return platform.getArchitecture(); - } - - public Bits getBits() { - return platform.getBits(); - } - - public VersionNumber getVersionNumber() { - return versionNumber; - } - - /** - * The major version of Java installation. - * - * @see org.jackhuang.hmcl.util.platform.JavaVersion#JAVA_9 - * @see org.jackhuang.hmcl.util.platform.JavaVersion#JAVA_8 - * @see org.jackhuang.hmcl.util.platform.JavaVersion#JAVA_7 - * @see org.jackhuang.hmcl.util.platform.JavaVersion#UNKNOWN - */ - public int getParsedVersion() { - return version; - } - - private static final Pattern REGEX = Pattern.compile("version \"(?(.*?))\""); - private static final Pattern VERSION = Pattern.compile("^(?[0-9]+)"); - - private static final Pattern OS_ARCH = Pattern.compile("os\\.arch = (?.*)"); - private static final Pattern JAVA_VERSION = Pattern.compile("java\\.version = (?.*)"); - - private static final String JAVA_EXECUTABLE = OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS ? "java.exe" : "java"; - - public static final int UNKNOWN = -1; - public static final int JAVA_6 = 6; - public static final int JAVA_7 = 7; - public static final int JAVA_8 = 8; - public static final int JAVA_9 = 9; - public static final int JAVA_16 = 16; - public static final int JAVA_17 = 17; - - private static int parseVersion(String version) { - Matcher matcher = VERSION.matcher(version); - if (matcher.find()) { - int head = Lang.parseInt(matcher.group(), -1); - if (head > 1) return head; - } - if (version.contains("1.8")) - return JAVA_8; - else if (version.contains("1.7")) - return JAVA_7; - else if (version.contains("1.6")) - return JAVA_6; - else - return UNKNOWN; - } - - private static final Map fromExecutableCache = new ConcurrentHashMap<>(); - - public static JavaVersion fromExecutable(Path executable) throws IOException { - executable = executable.toRealPath(); - JavaVersion cachedJavaVersion = fromExecutableCache.get(executable); - if (cachedJavaVersion != null) - return cachedJavaVersion; - - String osArch = null; - String version = null; - - Platform platform = null; - - Process process = new ProcessBuilder(executable.toString(), "-XshowSettings:properties", "-version").start(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream(), OperatingSystem.NATIVE_CHARSET))) { - for (String line; (line = reader.readLine()) != null; ) { - Matcher m; - - m = OS_ARCH.matcher(line); - if (m.find()) { - osArch = m.group("arch"); - if (version != null) { - break; - } else { - continue; - } - } - - m = JAVA_VERSION.matcher(line); - if (m.find()) { - version = m.group("version"); - if (osArch != null) { - break; - } else { - //noinspection UnnecessaryContinue - continue; - } - } - } - } - - if (osArch != null) { - platform = Platform.getPlatform(OperatingSystem.CURRENT_OS, Architecture.parseArchName(osArch)); - } - - if (version == null) { - boolean is64Bit = false; - process = new ProcessBuilder(executable.toString(), "-version").start(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream(), OperatingSystem.NATIVE_CHARSET))) { - for (String line; (line = reader.readLine()) != null; ) { - Matcher m = REGEX.matcher(line); - if (m.find()) - version = m.group("version"); - if (line.contains("64-Bit")) - is64Bit = true; - } - } - - if (platform == null) { - platform = Platform.getPlatform(OperatingSystem.CURRENT_OS, is64Bit ? Architecture.X86_64 : Architecture.X86); - } - } - - JavaVersion javaVersion = new JavaVersion(executable, version, platform); - if (javaVersion.getParsedVersion() == UNKNOWN) - throw new IOException("Unrecognized Java version " + version + " at " + executable); - fromExecutableCache.put(executable, javaVersion); - return javaVersion; - } - - public static Path getExecutable(Path javaHome) { - return javaHome.resolve("bin").resolve(JAVA_EXECUTABLE); - } - - public static JavaVersion fromCurrentEnvironment() { - return CURRENT_JAVA; - } - - public static final JavaVersion CURRENT_JAVA; - - static { - Path currentExecutable = getExecutable(Paths.get(System.getProperty("java.home")).toAbsolutePath()); - try { - currentExecutable = currentExecutable.toRealPath(); - } catch (IOException e) { - LOG.warning("Failed to resolve current Java path: " + currentExecutable, e); - } - CURRENT_JAVA = new JavaVersion( - currentExecutable, - System.getProperty("java.version"), - Platform.CURRENT_PLATFORM - ); - } - - private static Collection JAVAS; - private static final CountDownLatch LATCH = new CountDownLatch(1); - - public static Collection getJavas() throws InterruptedException { - if (JAVAS != null) - return JAVAS; - LATCH.await(); - return JAVAS; - } - - public static synchronized void initialize() { - if (JAVAS != null) - throw new IllegalStateException("JavaVersions have already been initialized."); - - List javaVersions; - - try (Stream stream = searchPotentialJavaExecutables()) { - javaVersions = lookupJavas(stream); - } catch (IOException e) { - LOG.warning("Failed to search Java homes", e); - javaVersions = new ArrayList<>(); - } - - // insert current java to the list - if (!javaVersions.contains(CURRENT_JAVA)) { - javaVersions.add(CURRENT_JAVA); - } - - JAVAS = Collections.newSetFromMap(new ConcurrentHashMap<>()); - JAVAS.addAll(javaVersions); - - LOG.trace("Finished Java installation lookup, found " + JAVAS.size()); - - LATCH.countDown(); - } - - private static List lookupJavas(Stream javaExecutables) { - return javaExecutables - .filter(Files::isExecutable) - .flatMap(executable -> { // resolve symbolic links - try { - return Stream.of(executable.toRealPath()); - } catch (IOException e) { - LOG.warning("Failed to lookup Java executable at " + executable, e); - return Stream.empty(); - } - }) - .distinct() // remove duplicated javas - .flatMap(executable -> { - if (executable.equals(CURRENT_JAVA.getBinary())) { - return Stream.of(CURRENT_JAVA); - } - try { - LOG.trace("Looking for Java:" + executable); - Future future = Schedulers.io().submit(() -> fromExecutable(executable)); - JavaVersion javaVersion = future.get(5, TimeUnit.SECONDS); - LOG.trace("Found Java (" + javaVersion.getVersion() + ") " + javaVersion.getBinary().toString()); - return Stream.of(javaVersion); - } catch (ExecutionException | InterruptedException | TimeoutException e) { - LOG.warning("Failed to determine Java at " + executable, e); - return Stream.empty(); - } - }) - .collect(toList()); - } - - private static Stream searchPotentialJavaExecutables() throws IOException { - // Add order: - // 1. System-defined locations - // 2. Minecraft-installed locations - // 3. PATH - List> javaExecutables = new ArrayList<>(); - - switch (OperatingSystem.CURRENT_OS) { - case WINDOWS: - javaExecutables.add(queryJavaHomesInRegistryKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Runtime Environment\\").stream().map(JavaVersion::getExecutable)); - javaExecutables.add(queryJavaHomesInRegistryKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\").stream().map(JavaVersion::getExecutable)); - javaExecutables.add(queryJavaHomesInRegistryKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\JRE\\").stream().map(JavaVersion::getExecutable)); - javaExecutables.add(queryJavaHomesInRegistryKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\JDK\\").stream().map(JavaVersion::getExecutable)); - - for (Optional programFiles : Arrays.asList( - FileUtils.tryGetPath(Optional.ofNullable(System.getenv("ProgramFiles")).orElse("C:\\Program Files")), - FileUtils.tryGetPath(Optional.ofNullable(System.getenv("ProgramFiles(x86)")).orElse("C:\\Program Files (x86)")), - FileUtils.tryGetPath(Optional.ofNullable(System.getenv("ProgramFiles(ARM)")).orElse("C:\\Program Files (ARM)")) - )) { - if (!programFiles.isPresent()) - continue; - - for (String vendor : new String[]{"Java", "BellSoft", "AdoptOpenJDK", "Zulu", "Microsoft", "Eclipse Foundation", "Semeru"}) { - javaExecutables.add(listDirectory(programFiles.get().resolve(vendor)).map(JavaVersion::getExecutable)); - } - } - break; - - case LINUX: - case FREEBSD: - javaExecutables.add(listDirectory(Paths.get("/usr/java")).map(JavaVersion::getExecutable)); // Oracle RPMs - javaExecutables.add(listDirectory(Paths.get("/usr/lib/jvm")).map(JavaVersion::getExecutable)); // General locations - javaExecutables.add(listDirectory(Paths.get("/usr/lib32/jvm")).map(JavaVersion::getExecutable)); // General locations - break; - - case OSX: - javaExecutables.add(listDirectory(Paths.get("/Library/Java/JavaVirtualMachines")) - .flatMap(dir -> Stream.of(dir.resolve("Contents/Home"), dir.resolve("Contents/Home/jre"))) - .map(JavaVersion::getExecutable)); - javaExecutables.add(listDirectory(Paths.get(System.getProperty("user.home"), "Library/Java/JavaVirtualMachines")) - .flatMap(dir -> Stream.of(dir.resolve("Contents/Home"), dir.resolve("Contents/Home/jre"))) - .map(JavaVersion::getExecutable)); - javaExecutables.add(listDirectory(Paths.get("/System/Library/Java/JavaVirtualMachines")) - .map(dir -> dir.resolve("Contents/Home")) - .map(JavaVersion::getExecutable)); - javaExecutables.add(Stream.of(Paths.get("/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java"))); - javaExecutables.add(Stream.of(Paths.get("/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/MacOS/itms/java/bin/java"))); - // Homebrew - javaExecutables.add(Stream.of(Paths.get("/opt/homebrew/opt/java/bin/java"))); - javaExecutables.add(listDirectory(Paths.get("/opt/homebrew/Cellar/openjdk")) - .map(JavaVersion::getExecutable)); - break; - - default: - break; - } - - // Search Minecraft bundled runtimes. - javaExecutables.add(Stream.concat(Stream.of(Optional.of(JavaRepository.getJavaStoragePath())), JavaRepository.findMinecraftRuntimeDirs()) - .flatMap(Lang::toStream) - .flatMap(JavaRepository::findJavaHomeInMinecraftRuntimeDir) - .map(JavaVersion::getExecutable)); - - // Search in PATH. - if (System.getenv("PATH") != null) { - javaExecutables.add(Arrays.stream(System.getenv("PATH").split(OperatingSystem.PATH_SEPARATOR)) - .flatMap(path -> Lang.toStream(FileUtils.tryGetPath(path, JAVA_EXECUTABLE)))); - } - - // Search in HMCL_JRES, convenient environment variable for users to add JRE in global - // May be removed when we implement global Java configuration. - if (System.getenv("HMCL_JRES") != null) { - javaExecutables.add(Arrays.stream(System.getenv("HMCL_JRES").split(OperatingSystem.PATH_SEPARATOR)) - .flatMap(path -> Lang.toStream(FileUtils.tryGetPath(path, "bin", JAVA_EXECUTABLE)))); - } - return javaExecutables.parallelStream().flatMap(stream -> stream); - } - - private static Stream listDirectory(Path directory) throws IOException { - if (Files.isDirectory(directory)) { - try (final DirectoryStream subDirs = Files.newDirectoryStream(directory)) { - final ArrayList paths = new ArrayList<>(); - for (Path subDir : subDirs) { - paths.add(subDir); - } - return paths.stream(); - } - } else { - return Stream.empty(); - } - } - - // ==== Windows Registry Support ==== - private static List queryJavaHomesInRegistryKey(String location) throws IOException { - List homes = new ArrayList<>(); - for (String java : querySubFolders(location)) { - if (!querySubFolders(java).contains(java + "\\MSI")) - continue; - String home = queryRegisterValue(java, "JavaHome"); - if (home != null) { - try { - homes.add(Paths.get(home)); - } catch (InvalidPathException e) { - LOG.warning("Invalid Java path in system registry: " + home); - } - } - } - return homes; - } - - private static List querySubFolders(String location) throws IOException { - List res = new ArrayList<>(); - - 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); - } - } - } - return res; - } - - private static String queryRegisterValue(String location, String name) throws IOException { - boolean last = false; - 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; - } - } - } - } - return null; - } -} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/ManagedProcess.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/ManagedProcess.java index 85bba1eb7a..07ca5a5165 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/ManagedProcess.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/ManagedProcess.java @@ -17,6 +17,7 @@ */ package org.jackhuang.hmcl.util.platform; +import org.jackhuang.hmcl.java.JavaRuntime; import org.jackhuang.hmcl.launch.StreamPump; import org.jackhuang.hmcl.util.Lang; @@ -90,7 +91,7 @@ public Process getProcess() { * @return PID */ public long getPID() throws UnsupportedOperationException { - if (JavaVersion.CURRENT_JAVA.getParsedVersion() >= 9) { + if (JavaRuntime.CURRENT_VERSION >= 9) { // Method Process.pid() is provided (Java 9 or later). Invoke it to get the pid. try { return (long) MethodHandles.publicLookup() diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/OperatingSystem.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/OperatingSystem.java index 145a934fd1..70a32512ee 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/OperatingSystem.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/OperatingSystem.java @@ -17,6 +17,8 @@ */ package org.jackhuang.hmcl.util.platform; +import org.jackhuang.hmcl.util.KeyValuePairProperties; + import java.io.BufferedReader; import java.io.File; import java.io.IOException; @@ -28,8 +30,9 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; +import java.util.Collections; import java.util.Locale; -import java.util.Optional; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -74,6 +77,10 @@ public boolean isLinuxOrBSD() { return this == LINUX || this == FREEBSD; } + public String getJavaExecutable() { + return this == WINDOWS ? "java.exe" : "java"; + } + /** * The current operating system. */ @@ -114,6 +121,9 @@ public boolean isLinuxOrBSD() { */ public static final String SYSTEM_VERSION; + public static final String OS_RELEASE_NAME; + public static final String OS_RELEASE_PRETTY_NAME; + public static final Pattern INVALID_RESOURCE_CHARACTERS; private static final String[] INVALID_RESOURCE_BASENAMES; private static final String[] INVALID_RESOURCE_FULLNAMES; @@ -140,7 +150,7 @@ public boolean isLinuxOrBSD() { } } } catch (UnsupportedCharsetException e) { - e.printStackTrace(); + e.printStackTrace(System.err); } NATIVE_CHARSET = nativeCharset; @@ -183,9 +193,24 @@ public boolean isLinuxOrBSD() { SYSTEM_BUILD_NUMBER = -1; } - TOTAL_MEMORY = getPhysicalMemoryStatus() - .map(physicalMemoryStatus -> (int) (physicalMemoryStatus.getTotal() / 1024 / 1024)) - .orElse(1024); + Map osRelease = Collections.emptyMap(); + if (CURRENT_OS == LINUX || CURRENT_OS == FREEBSD) { + Path osReleaseFile = Paths.get("/etc/os-release"); + if (Files.exists(osReleaseFile)) { + try { + osRelease = KeyValuePairProperties.load(osReleaseFile); + } catch (IOException e) { + e.printStackTrace(System.err); + } + } + } + OS_RELEASE_NAME = osRelease.get("NAME"); + OS_RELEASE_PRETTY_NAME = osRelease.get("PRETTY_NAME"); + + PhysicalMemoryStatus physicalMemoryStatus = getPhysicalMemoryStatus(); + TOTAL_MEMORY = physicalMemoryStatus != PhysicalMemoryStatus.INVALID + ? (int) (physicalMemoryStatus.getTotal() / 1024 / 1024) + : 1024; SUGGESTED_MEMORY = TOTAL_MEMORY >= 32768 ? 8192 : (int) (Math.round(1.0 * TOTAL_MEMORY / 4.0 / 128.0) * 128); @@ -215,10 +240,10 @@ public static OperatingSystem parseOSName(String name) { name = name.trim().toLowerCase(Locale.ROOT); - if (name.contains("win")) - return WINDOWS; - else if (name.contains("mac")) + if (name.contains("mac") || name.contains("darwin") || name.contains("osx")) return OSX; + else if (name.contains("win")) + return WINDOWS; else if (name.contains("solaris") || name.contains("linux") || name.contains("unix") || name.contains("sunos")) return LINUX; else if (name.equals("freebsd")) @@ -252,7 +277,7 @@ public static boolean isWindows7OrLater() { } @SuppressWarnings("deprecation") - public static Optional getPhysicalMemoryStatus() { + public static PhysicalMemoryStatus getPhysicalMemoryStatus() { if (CURRENT_OS == LINUX) { try { long free = 0, available = 0, total = 0; @@ -273,10 +298,10 @@ public static Optional getPhysicalMemoryStatus() { } } if (total > 0) { - return Optional.of(new PhysicalMemoryStatus(total, available > 0 ? available : free)); + return new PhysicalMemoryStatus(total, available > 0 ? available : free); } } catch (IOException e) { - e.printStackTrace(); + e.printStackTrace(System.err); } } @@ -286,11 +311,11 @@ public static Optional getPhysicalMemoryStatus() { com.sun.management.OperatingSystemMXBean sunBean = (com.sun.management.OperatingSystemMXBean) java.lang.management.ManagementFactory.getOperatingSystemMXBean(); - return Optional.of(new PhysicalMemoryStatus(sunBean.getTotalPhysicalMemorySize(), sunBean.getFreePhysicalMemorySize())); + return new PhysicalMemoryStatus(sunBean.getTotalPhysicalMemorySize(), sunBean.getFreePhysicalMemorySize()); } } catch (NoClassDefFoundError ignored) { } - return Optional.empty(); + return PhysicalMemoryStatus.INVALID; } @SuppressWarnings("removal") diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/Platform.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/Platform.java index 56bc7b0eda..7ad533ceec 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/Platform.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/Platform.java @@ -5,6 +5,7 @@ public final class Platform { public static final Platform UNKNOWN = new Platform(OperatingSystem.UNKNOWN, Architecture.UNKNOWN); + public static final Platform WINDOWS_X86 = new Platform(OperatingSystem.WINDOWS, Architecture.X86); public static final Platform WINDOWS_X86_64 = new Platform(OperatingSystem.WINDOWS, Architecture.X86_64); public static final Platform WINDOWS_ARM64 = new Platform(OperatingSystem.WINDOWS, Architecture.ARM64); @@ -78,6 +79,10 @@ public int hashCode() { return Objects.hash(os, arch); } + public boolean equals(OperatingSystem os, Architecture arch) { + return this.os == os && this.arch == arch; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/SystemUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/SystemUtils.java index 92e74708f2..d5e956ced1 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/SystemUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/SystemUtils.java @@ -17,6 +17,8 @@ */ package org.jackhuang.hmcl.util.platform; +import org.jackhuang.hmcl.java.JavaRuntime; + import java.io.IOException; import java.util.Arrays; import java.util.List; @@ -42,8 +44,7 @@ public static int callExternalProcess(ProcessBuilder processBuilder) throws IOEx } public static boolean supportJVMAttachment() { - return JavaVersion.CURRENT_JAVA.getParsedVersion() >= 9 - && Thread.currentThread().getContextClassLoader().getResource("com/sun/tools/attach/VirtualMachine.class") != null; + return JavaRuntime.CURRENT_VERSION >= 9 && Thread.currentThread().getContextClassLoader().getResource("com/sun/tools/attach/VirtualMachine.class") != null; } private static void onLogLine(String log) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LoadingState.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/UnsupportedPlatformException.java similarity index 55% rename from HMCL/src/main/java/org/jackhuang/hmcl/game/LoadingState.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/UnsupportedPlatformException.java index 6b434a9b59..4e9552d586 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LoadingState.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/UnsupportedPlatformException.java @@ -1,6 +1,6 @@ /* * Hello Minecraft! Launcher - * Copyright (C) 2020 huangyuhui and contributors + * Copyright (C) 2024 huangyuhui and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,24 +15,16 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.jackhuang.hmcl.game; +package org.jackhuang.hmcl.util.platform; -import static org.jackhuang.hmcl.util.i18n.I18n.i18n; - -public enum LoadingState { - DEPENDENCIES("launch.state.dependencies"), - MODS("launch.state.modpack"), - LOGGING_IN("launch.state.logging_in"), - LAUNCHING("launch.state.waiting_launching"), - DONE("launch.state.done"); - - private final String key; - - LoadingState(String key) { - this.key = key; +/** + * @author Glavo + */ +public final class UnsupportedPlatformException extends Exception { + public UnsupportedPlatformException() { } - public String getLocalizedMessage() { - return i18n(key); + public UnsupportedPlatformException(String message) { + super(message); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/tree/ArchiveFileTree.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/tree/ArchiveFileTree.java new file mode 100644 index 0000000000..ff744e79fd --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/tree/ArchiveFileTree.java @@ -0,0 +1,129 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.util.tree; + +import org.apache.commons.compress.archivers.ArchiveEntry; +import org.apache.commons.compress.archivers.zip.ZipFile; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.util.*; + +/** + * @author Glavo + */ +public abstract class ArchiveFileTree implements Closeable { + + public static ArchiveFileTree open(Path file) throws IOException { + Path namePath = file.getFileName(); + if (namePath == null) { + throw new IOException(file + " is not a valid archive file"); + } + + String name = namePath.toString(); + if (name.endsWith(".jar") || name.endsWith(".zip")) { + return new ZipFileTree(new ZipFile(file)); + } else if (name.endsWith(".tar") || name.endsWith(".tar.gz") || name.endsWith(".tgz")) { + return TarFileTree.open(file); + } else { + throw new IOException(file + " is not a valid archive file"); + } + } + + protected final F file; + protected final Dir root = new Dir<>(); + + public ArchiveFileTree(F file) { + this.file = file; + } + + public F getFile() { + return file; + } + + public Dir getRoot() { + return root; + } + + public void addEntry(E entry) throws IOException { + String[] path = entry.getName().split("/"); + + Dir dir = root; + + for (int i = 0, end = entry.isDirectory() ? path.length : path.length - 1; i < end; i++) { + String item = path[i]; + if (item.equals(".")) + continue; + if (item.equals("..") || item.isEmpty()) + throw new IOException("Invalid entry: " + entry.getName()); + + if (dir.files.containsKey(item)) { + throw new IOException("A file and a directory have the same name: " + entry.getName()); + } + + dir = dir.subDirs.computeIfAbsent(item, name -> new Dir<>()); + } + + if (entry.isDirectory()) { + if (dir.entry != null) { + throw new IOException("Duplicate entry: " + entry.getName()); + } + dir.entry = entry; + } else { + String fileName = path[path.length - 1]; + + if (dir.subDirs.containsKey(fileName)) { + throw new IOException("A file and a directory have the same name: " + entry.getName()); + } + + if (dir.files.containsKey(fileName)) { + throw new IOException("Duplicate entry: " + entry.getName()); + } + + dir.files.put(fileName, entry); + } + } + + public abstract InputStream getInputStream(E entry) throws IOException; + + public abstract boolean isLink(E entry); + + public abstract String getLink(E entry) throws IOException; + + public abstract boolean isExecutable(E entry); + + @Override + public abstract void close() throws IOException; + + public static final class Dir { + E entry; + + final Map> subDirs = new HashMap<>(); + final Map files = new HashMap<>(); + + public Map> getSubDirs() { + return subDirs; + } + + public Map getFiles() { + return files; + } + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/tree/TarFileTree.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/tree/TarFileTree.java new file mode 100644 index 0000000000..bffa40eefa --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/tree/TarFileTree.java @@ -0,0 +1,133 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.jackhuang.hmcl.util.tree; + +import org.apache.commons.compress.archivers.tar.TarArchiveEntry; +import org.apache.commons.compress.archivers.tar.TarFile; +import org.jackhuang.hmcl.util.io.IOUtils; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.zip.GZIPInputStream; + +/** + * @author Glavo + */ +public final class TarFileTree extends ArchiveFileTree { + + public static TarFileTree open(Path file) throws IOException { + String fileName = file.getFileName().toString(); + + if (fileName.endsWith(".tar.gz") || fileName.endsWith(".tgz")) { + Path tempFile = Files.createTempFile("hmcl-", ".tar"); + TarFile tarFile; + try (GZIPInputStream input = new GZIPInputStream(Files.newInputStream(file)); + OutputStream output = Files.newOutputStream(tempFile, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE) + ) { + IOUtils.copyTo(input, output); + tarFile = new TarFile(tempFile.toFile()); + } catch (Throwable e) { + try { + Files.deleteIfExists(tempFile); + } catch (Throwable e2) { + e.addSuppressed(e2); + } + throw e; + } + + return new TarFileTree(tarFile, tempFile); + } else { + return new TarFileTree(new TarFile(file), null); + } + } + + private final Path tempFile; + private final Thread shutdownHook; + + public TarFileTree(TarFile file, Path tempFile) throws IOException { + super(file); + this.tempFile = tempFile; + try { + for (TarArchiveEntry entry : file.getEntries()) { + addEntry(entry); + } + } catch (Throwable e) { + try { + file.close(); + } catch (Throwable e2) { + e.addSuppressed(e2); + } + + if (tempFile != null) { + try { + Files.deleteIfExists(tempFile); + } catch (Throwable e2) { + e.addSuppressed(e2); + } + } + + throw e; + } + + if (tempFile != null) { + this.shutdownHook = new Thread(() -> { + try { + Files.deleteIfExists(tempFile); + } catch (Throwable ignored) { + } + }); + Runtime.getRuntime().addShutdownHook(shutdownHook); + } else + this.shutdownHook = null; + } + + @Override + public InputStream getInputStream(TarArchiveEntry entry) throws IOException { + return file.getInputStream(entry); + } + + @Override + public boolean isLink(TarArchiveEntry entry) { + return entry.isSymbolicLink(); + } + + @Override + public String getLink(TarArchiveEntry entry) throws IOException { + return entry.getLinkName(); + } + + @Override + public boolean isExecutable(TarArchiveEntry entry) { + return entry.isFile() && (entry.getMode() & 0b1000000) != 0; + } + + @Override + public void close() throws IOException { + try { + file.close(); + } finally { + if (tempFile != null) { + Runtime.getRuntime().removeShutdownHook(shutdownHook); + Files.deleteIfExists(tempFile); + } + } + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/tree/ZipFileTree.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/tree/ZipFileTree.java new file mode 100644 index 0000000000..70e2d3a826 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/tree/ZipFileTree.java @@ -0,0 +1,72 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.util.tree; + +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; +import org.apache.commons.compress.archivers.zip.ZipFile; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; + +/** + * @author Glavo + */ +public final class ZipFileTree extends ArchiveFileTree { + public ZipFileTree(ZipFile file) throws IOException { + super(file); + try { + Enumeration entries = file.getEntries(); + while (entries.hasMoreElements()) { + addEntry(entries.nextElement()); + } + } catch (Throwable e) { + try { + file.close(); + } catch (Throwable e2) { + e.addSuppressed(e2); + } + throw e; + } + } + + @Override + public void close() throws IOException { + file.close(); + } + + @Override + public InputStream getInputStream(ZipArchiveEntry entry) throws IOException { + return getFile().getInputStream(entry); + } + + @Override + public boolean isLink(ZipArchiveEntry entry) { + return entry.isUnixSymlink(); + } + + @Override + public String getLink(ZipArchiveEntry entry) throws IOException { + return getFile().getUnixSymlink(entry); + } + + @Override + public boolean isExecutable(ZipArchiveEntry entry) { + return !entry.isDirectory() && !entry.isUnixSymlink() && (entry.getUnixMode() & 0b1000000) != 0; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/GameVersionNumber.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/GameVersionNumber.java index 65564db578..fff98ea6aa 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/GameVersionNumber.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/GameVersionNumber.java @@ -1,3 +1,20 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package org.jackhuang.hmcl.util.versioning; import org.jetbrains.annotations.NotNull; @@ -10,6 +27,9 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +/** + * @author Glavo + */ public abstract class GameVersionNumber implements Comparable { public static String[] getDefaultGameVersions() { @@ -22,26 +42,21 @@ public static GameVersionNumber asGameVersion(String version) { char ch = version.charAt(0); switch (ch) { case 'r': - return Old.parsePreClassic(version); case 'a': case 'b': case 'c': - return Old.parseAlphaBetaClassic(version); case 'i': - return Old.parseInfdev(version); + return Old.parse(version); } - if (version.equals("0.0")) { + if (version.equals("0.0")) return Release.ZERO; - } - if (version.startsWith("1.")) { + if (version.startsWith("1.")) return Release.parse(version); - } - if (version.length() == 6 && version.charAt(2) == 'w') { + if (version.length() == 6 && version.charAt(2) == 'w') return Snapshot.parse(version); - } } } catch (IllegalArgumentException ignore) { } @@ -97,9 +112,8 @@ public int compareTo(@NotNull String other) { @Override public int compareTo(@NotNull GameVersionNumber other) { - if (this.getType() != other.getType()) { + if (this.getType() != other.getType()) return Integer.compare(this.getType().ordinal(), other.getType().ordinal()); - } return compareToImpl(other); } @@ -110,27 +124,25 @@ public String toString() { } static final class Old extends GameVersionNumber { - - private static final Pattern PATTERN = Pattern.compile("[abc](?[0-9]+)\\.(?[0-9]+)(\\.(?[0-9]+))?([^0-9]*(?[0-9]+).*)?"); - - static Old parsePreClassic(String value) { - int version; - try { - version = Integer.parseInt(value.substring("rd-".length())); - } catch (NumberFormatException e) { - throw new IllegalArgumentException(e); - } - return new Old(value, Type.PRE_CLASSIC, version, 0, 0, 0); - } - - static Old parseAlphaBetaClassic(String value) { - Matcher matcher = PATTERN.matcher(value); - if (!matcher.matches()) { - throw new IllegalArgumentException(value); - } - + static Old parse(String value) { Type type; + int prefixLength = 1; switch (value.charAt(0)) { + case 'r': + if (!value.startsWith("rd-")) { + throw new IllegalArgumentException(value); + } + + type = Type.PRE_CLASSIC; + prefixLength = "rd-".length(); + break; + case 'i': + if (!value.startsWith("inf-")) { + throw new IllegalArgumentException(value); + } + type = Type.INFDEV; + prefixLength = "inf-".length(); + break; case 'a': type = Type.ALPHA; break; @@ -141,60 +153,22 @@ static Old parseAlphaBetaClassic(String value) { type = Type.CLASSIC; break; default: - throw new AssertionError(value); - } - - int major = Integer.parseInt(matcher.group("major")); - int minor = Integer.parseInt(matcher.group("minor")); - - String patchString = matcher.group("patch"); - int patch = patchString != null ? Integer.parseInt(patchString) : 0; - - String additionalString = matcher.group("additional"); - int additional = additionalString != null ? Integer.parseInt(additionalString) : 0; - - return new Old(value, type, major, minor, patch, additional); - } - - static Old parseInfdev(String value) { - String version = value.substring("inf-".length()); - int major; - int patch; - - try { - major = Integer.parseInt(version); - patch = 0; - } catch (NumberFormatException e) { - int idx = version.indexOf('-'); - if (idx >= 0) { - try { - major = Integer.parseInt(version.substring(0, idx)); - patch = Integer.parseInt(version.substring(idx + 1)); - } catch (NumberFormatException ignore) { - throw new IllegalArgumentException(value); - } - } else { throw new IllegalArgumentException(value); - } } - return new Old(value, Type.INFDEV, major, 0, patch, 0); - } + if (value.length() < prefixLength + 1 || !Character.isDigit(value.charAt(prefixLength))) + throw new IllegalArgumentException(value); + return new Old(value, type, VersionNumber.asVersion(value.substring(prefixLength))); + } final Type type; - final int major; - final int minor; - final int patch; - final int additional; + final VersionNumber versionNumber; - private Old(String value, Type type, int major, int minor, int patch, int additional) { + private Old(String value, Type type, VersionNumber versionNumber) { super(value); this.type = type; - this.major = major; - this.minor = minor; - this.patch = patch; - this.additional = additional; + this.versionNumber = versionNumber; } @Override @@ -204,42 +178,26 @@ Type getType() { @Override int compareToImpl(@NotNull GameVersionNumber other) { - Old that = (Old) other; - int c = Integer.compare(this.major, that.major); - if (c != 0) { - return c; - } - - c = Integer.compare(this.minor, that.minor); - if (c != 0) { - return c; - } - - c = Integer.compare(this.patch, that.patch); - if (c != 0) { - return c; - } - - return Integer.compare(this.additional, that.additional); + return this.versionNumber.compareTo(((Old) other).versionNumber); } @Override public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (!(o instanceof Old)) return false; Old other = (Old) o; - return major == other.major && minor == other.minor && patch == other.patch && additional == other.additional && type == other.type; + return type == other.type && this.versionNumber.compareTo(other.versionNumber) == 0; } @Override public int hashCode() { - return Objects.hash(type, major, minor, patch, additional); + return Objects.hash(type, versionNumber.hashCode()); } } static final class Release extends GameVersionNumber { - private static final Pattern PATTERN = Pattern.compile("1\\.(?[0-9]+)(\\.(?[0-9]+))?((?(-[a-zA-Z]+| Pre-Release ))(?[0-9]+))?"); + private static final Pattern PATTERN = Pattern.compile("1\\.(?[0-9]+)(\\.(?[0-9]+))?((?(-[a-zA-Z]+| Pre-Release ))(?.+))?"); static final int TYPE_GA = Integer.MAX_VALUE; @@ -248,7 +206,7 @@ static final class Release extends GameVersionNumber { static final int TYPE_PRE = 2; static final int TYPE_RC = 3; - static final Release ZERO = new Release("0.0", 0, 0, 0, TYPE_GA, 0); + static final Release ZERO = new Release("0.0", 0, 0, 0, TYPE_GA, VersionNumber.ZERO); static Release parse(String value) { Matcher matcher = PATTERN.matcher(value); @@ -276,7 +234,7 @@ static Release parse(String value) { } String eaVersionString = matcher.group("eaVersion"); - int eaVersion = eaVersionString == null ? 0 : Integer.parseInt(eaVersionString); + VersionNumber eaVersion = eaVersionString != null ? VersionNumber.asVersion(eaVersionString) : VersionNumber.ZERO; return new Release(value, 1, minor, patch, eaType, eaVersion); } @@ -286,9 +244,9 @@ static Release parse(String value) { private final int patch; private final int eaType; - private final int eaVersion; + private final VersionNumber eaVersion; - Release(String value, int major, int minor, int patch, int eaType, int eaVersion) { + Release(String value, int major, int minor, int patch, int eaType, VersionNumber eaVersion) { super(value); this.major = major; this.minor = minor; @@ -323,36 +281,31 @@ int compareToRelease(Release other) { return c; } - return Integer.compare(this.eaVersion, other.eaVersion); + return this.eaVersion.compareTo(other.eaVersion); } int compareToSnapshot(Snapshot other) { int idx = Arrays.binarySearch(Versions.SNAPSHOT_INTS, other.intValue); - if (idx >= 0) { + if (idx >= 0) return this.compareToRelease(Versions.SNAPSHOT_PREV[idx]) <= 0 ? -1 : 1; - } idx = -(idx + 1); - if (idx == Versions.SNAPSHOT_INTS.length) { + if (idx == Versions.SNAPSHOT_INTS.length) return -1; - } return this.compareToRelease(Versions.SNAPSHOT_PREV[idx]) <= 0 ? -1 : 1; } @Override int compareToImpl(@NotNull GameVersionNumber other) { - if (other instanceof Release) { + if (other instanceof Release) return compareToRelease((Release) other); - } - if (other instanceof Snapshot) { + if (other instanceof Snapshot) return compareToSnapshot((Snapshot) other); - } - if (other instanceof Special) { + if (other instanceof Special) return -((Special) other).compareToReleaseOrSnapshot(this); - } throw new AssertionError(other.getClass()); } @@ -367,15 +320,14 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Release other = (Release) o; - return major == other.major && minor == other.minor && patch == other.patch && eaType == other.eaType && eaVersion == other.eaVersion; + return major == other.major && minor == other.minor && patch == other.patch && eaType == other.eaType && eaVersion.equals(other.eaVersion); } } static final class Snapshot extends GameVersionNumber { static Snapshot parse(String value) { - if (value.length() != 6 || value.charAt(2) != 'w') { + if (value.length() != 6 || value.charAt(2) != 'w') throw new IllegalArgumentException(value); - } int year; int week; @@ -387,9 +339,8 @@ static Snapshot parse(String value) { } char suffix = value.charAt(5); - if ((suffix < 'a' || suffix > 'z') && suffix != '~') { + if ((suffix < 'a' || suffix > 'z') && suffix != '~') throw new IllegalArgumentException(value); - } return new Snapshot(value, year, week, suffix); } @@ -412,17 +363,14 @@ Type getType() { @Override int compareToImpl(@NotNull GameVersionNumber other) { - if (other instanceof Release) { + if (other instanceof Release) return -((Release) other).compareToSnapshot(this); - } - if (other instanceof Snapshot) { + if (other instanceof Snapshot) return Integer.compare(this.intValue, ((Snapshot) other).intValue); - } - if (other instanceof Special) { + if (other instanceof Special) return -((Special) other).compareToReleaseOrSnapshot(this); - } throw new AssertionError(other.getClass()); } @@ -460,9 +408,8 @@ boolean isUnknown() { } VersionNumber asVersionNumber() { - if (versionNumber != null) { + if (versionNumber != null) return versionNumber; - } return versionNumber = VersionNumber.asVersion(value); } @@ -473,9 +420,7 @@ GameVersionNumber getPrevNormalVersion() { v = ((Special) v).prev; } - if (v == null) { - throw new AssertionError("version: " + value); - } + if (v == null) throw new AssertionError("version: " + value); return v; } @@ -493,28 +438,23 @@ int compareToReleaseOrSnapshot(GameVersionNumber other) { } int compareToSpecial(Special other) { - if (this.isUnknown()) { + if (this.isUnknown()) return other.isUnknown() ? this.asVersionNumber().compareTo(other.asVersionNumber()) : 1; - } - if (other.isUnknown()) { + if (other.isUnknown()) return -1; - } - if (this.value.equals(other.value)) { + if (this.value.equals(other.value)) return 0; - } int c = this.getPrevNormalVersion().compareTo(other.getPrevNormalVersion()); - if (c != 0) { + if (c != 0) return c; - } GameVersionNumber v = prev; while (v instanceof Special) { - if (v == other) { + if (v == other) return 1; - } v = ((Special) v).prev; } @@ -524,17 +464,14 @@ int compareToSpecial(Special other) { @Override int compareToImpl(@NotNull GameVersionNumber o) { - if (o instanceof Release) { + if (o instanceof Release) return compareToReleaseOrSnapshot(o); - } - if (o instanceof Snapshot) { + if (o instanceof Snapshot) return compareToReleaseOrSnapshot(o); - } - if (o instanceof Special) { + if (o instanceof Special) return compareToSpecial((Special) o); - } throw new AssertionError(o.getClass()); } @@ -566,7 +503,6 @@ static final class Versions { List snapshots = new ArrayList<>(1024); List snapshotPrev = new ArrayList<>(1024); - // Convert it to dynamic resource after the website is repaired? try (BufferedReader reader = new BufferedReader(new InputStreamReader(GameVersionNumber.class.getResourceAsStream("/assets/game/versions.txt"), StandardCharsets.US_ASCII))) { Release currentRelease = null; GameVersionNumber prev = null; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/VersionNumber.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/VersionNumber.java index 0344125500..0753d02505 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/VersionNumber.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/VersionNumber.java @@ -33,6 +33,8 @@ */ public final class VersionNumber implements Comparable { + public static final VersionNumber ZERO = asVersion("0"); + public static VersionNumber asVersion(String version) { Objects.requireNonNull(version); return new VersionNumber(version); diff --git a/HMCLCore/src/main/resources/assets/game/versions.txt b/HMCLCore/src/main/resources/assets/game/versions.txt index 287242f5c9..2d8fdada50 100644 --- a/HMCLCore/src/main/resources/assets/game/versions.txt +++ b/HMCLCore/src/main/resources/assets/game/versions.txt @@ -756,4 +756,34 @@ 1.20.5 1.20.6-rc1 1.20.6 -24w18a \ No newline at end of file +24w18a +24w19a +24w19b +24w20a +24w21a +24w21b +1.21-pre1 +1.21-pre2 +1.21-pre3 +1.21-pre4 +1.21-rc1 +1.21 +1.21.1-rc1 +1.21.1 +24w33a +24w34a +24w35a +24w36a +24w37a +24w38a +24w39a +24w40a +1.21.2-pre1 +1.21.2-pre2 +1.21.2-pre3 +1.21.2-pre4 +1.21.2-pre5 +1.21.2-rc1 +1.21.2-rc2 +1.21.2 +1.21.3 \ No newline at end of file diff --git a/HMCLCore/src/test/java/org/jackhuang/hmcl/game/CrashReportAnalyzerTest.java b/HMCLCore/src/test/java/org/jackhuang/hmcl/game/CrashReportAnalyzerTest.java index 289573c969..a47f263050 100644 --- a/HMCLCore/src/test/java/org/jackhuang/hmcl/game/CrashReportAnalyzerTest.java +++ b/HMCLCore/src/test/java/org/jackhuang/hmcl/game/CrashReportAnalyzerTest.java @@ -140,7 +140,8 @@ public void forgeEroor() throws IOException { "\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_131]\n" + "\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_131]\n" + "\tat java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_131]\n" + - "\tat oolloo.jlw.Wrapper.invokeMain(Wrapper.java:58) [JavaWrapper.jar:?]\n").replaceAll("\\s+", ""), + "\tat oolloo.jlw.Wrapper.invokeMain(Wrapper.java:58) [JavaWrapper.jar:?]\n" + + "\tat oolloo.jlw.Wrapper.main(Wrapper.java:51) [JavaWrapper.jar:?]").replaceAll("\\s+", ""), result.getMatcher().group("reason").replaceAll("\\s+", "")); } diff --git a/HMCLCore/src/test/java/org/jackhuang/hmcl/util/KeyValuePairPropertiesTest.java b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/KeyValuePairPropertiesTest.java new file mode 100644 index 0000000000..5a04f7cb9c --- /dev/null +++ b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/KeyValuePairPropertiesTest.java @@ -0,0 +1,31 @@ +package org.jackhuang.hmcl.util; + +import org.junit.jupiter.api.Test; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringReader; + +import static org.jackhuang.hmcl.util.Pair.pair; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Glavo + */ +public final class KeyValuePairPropertiesTest { + @Test + public void test() throws IOException { + String content = "#test: key0=value0\n \n" + + "key1=value1\n" + + "key2=\"value2\"\n" + + "key3=\"\\\" \\n\"\n"; + + KeyValuePairProperties properties = KeyValuePairProperties.load(new BufferedReader(new StringReader(content))); + + assertEquals(Lang.mapOf( + pair("key1", "value1"), + pair("key2", "value2"), + pair("key3", "\" \n") + ), properties); + } +} diff --git a/HMCLCore/src/test/java/org/jackhuang/hmcl/util/TaskTest.java b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/TaskTest.java index eb3482a4c9..91ebcb635f 100644 --- a/HMCLCore/src/test/java/org/jackhuang/hmcl/util/TaskTest.java +++ b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/TaskTest.java @@ -20,7 +20,6 @@ import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.TaskExecutor; -import org.jackhuang.hmcl.util.platform.JavaVersion; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIf; @@ -79,10 +78,11 @@ public void testWithCompose() { @EnabledIf("org.jackhuang.hmcl.JavaFXLauncher#isStarted") public void testThenAccept() { AtomicBoolean flag = new AtomicBoolean(); - boolean result = Task.supplyAsync(JavaVersion::fromCurrentEnvironment) - .thenAcceptAsync(Schedulers.io(), javaVersion -> { + Object obj = new Object(); + boolean result = Task.supplyAsync(() -> obj) + .thenAcceptAsync(Schedulers.io(), o -> { flag.set(true); - assertEquals(javaVersion, JavaVersion.fromCurrentEnvironment()); + assertSame(obj, o); }) .test(); diff --git a/HMCLCore/src/test/java/org/jackhuang/hmcl/util/gson/JsonUtilsTest.java b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/gson/JsonUtilsTest.java new file mode 100644 index 0000000000..a3f0aaeed6 --- /dev/null +++ b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/gson/JsonUtilsTest.java @@ -0,0 +1,42 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.util.gson; + +import com.google.gson.reflect.TypeToken; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf; +import static org.jackhuang.hmcl.util.gson.JsonUtils.mapTypeOf; +import static org.junit.jupiter.api.Assertions.*; + +/** + * @author Glavo + */ +public class JsonUtilsTest { + + @Test + public void testGetTypeToken() { + assertEquals(new TypeToken>(){}, listTypeOf(Object.class)); + assertEquals(new TypeToken>(){}, listTypeOf(String.class)); + assertEquals(new TypeToken>>(){}, listTypeOf(mapTypeOf(String.class, Integer.class))); + assertEquals(new TypeToken>>>(){}, listTypeOf(mapTypeOf(String.class, listTypeOf(Integer.class)))); + } +} diff --git a/HMCLCore/src/test/java/org/jackhuang/hmcl/util/platform/JavaRuntimeTest.java b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/platform/JavaRuntimeTest.java new file mode 100644 index 0000000000..00065986c2 --- /dev/null +++ b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/platform/JavaRuntimeTest.java @@ -0,0 +1,18 @@ +package org.jackhuang.hmcl.util.platform; + +import org.junit.jupiter.api.Test; + +import static org.jackhuang.hmcl.java.JavaInfo.parseVersion; +import static org.junit.jupiter.api.Assertions.*; + +/** + * @author Glavo + */ +public final class JavaRuntimeTest { + @Test + public void testParseVersion() { + assertEquals(8, parseVersion("1.8.0_302")); + assertEquals(11, parseVersion("11")); + assertEquals(11, parseVersion("11.0.12")); + } +} diff --git a/HMCLCore/src/test/java/org/jackhuang/hmcl/util/versioning/GameVersionNumberTest.java b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/versioning/GameVersionNumberTest.java index e6e4881b04..8fb4650d2f 100644 --- a/HMCLCore/src/test/java/org/jackhuang/hmcl/util/versioning/GameVersionNumberTest.java +++ b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/versioning/GameVersionNumberTest.java @@ -1,3 +1,20 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package org.jackhuang.hmcl.util.versioning; import org.junit.jupiter.api.Test; @@ -5,6 +22,7 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; +import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; import java.util.*; @@ -13,18 +31,25 @@ /** * @author Glavo */ -public class GameVersionNumberTest { +public final class GameVersionNumberTest { - @Test - public void testSortVersions() throws IOException { + private static List readVersions() { List versions = new ArrayList<>(); try (BufferedReader reader = new BufferedReader(new InputStreamReader(GameVersionNumber.class.getResourceAsStream("/assets/game/versions.txt"), StandardCharsets.UTF_8))) { for (String line; (line = reader.readLine()) != null && !line.isEmpty(); ) { versions.add(line); } + } catch (IOException e) { + throw new UncheckedIOException(e); } + return versions; + } + + @Test + public void testSortVersions() { + List versions = readVersions(); List copied = new ArrayList<>(versions); Collections.shuffle(copied, new Random(0)); copied.sort(Comparator.comparing(GameVersionNumber::asGameVersion)); @@ -42,52 +67,57 @@ private static void assertGameVersionEquals(String version) { private static void assertGameVersionEquals(String version1, String version2) { assertEquals(0, GameVersionNumber.asGameVersion(version1).compareTo(version2), errorMessage(version1, version2)); + assertEquals(GameVersionNumber.asGameVersion(version1), GameVersionNumber.asGameVersion(version2), errorMessage(version1, version2)); } - private static void assertLessThan(String version1, String version2) { - assertTrue(GameVersionNumber.asGameVersion(version1).compareTo(version2) < 0, errorMessage(version1, version2)); + private static String toString(GameVersionNumber gameVersionNumber) { + return gameVersionNumber.getClass().getSimpleName(); } private static void assertOrder(String... versions) { for (int i = 0; i < versions.length - 1; i++) { GameVersionNumber version1 = GameVersionNumber.asGameVersion(versions[i]); - //noinspection EqualsWithItself - assertEquals(0, version1.compareTo(version1), "version=" + versions[i]); + assertGameVersionEquals(versions[i]); for (int j = i + 1; j < versions.length; j++) { GameVersionNumber version2 = GameVersionNumber.asGameVersion(versions[j]); - assertEquals(-1, version1.compareTo(version2), String.format("version1=%s, version2=%s", versions[i], versions[j])); - assertEquals(1, version2.compareTo(version1), String.format("version1=%s, version2=%s", versions[i], versions[j])); + assertEquals(-1, version1.compareTo(version2), String.format("version1=%s (%s), version2=%s (%s)", versions[i], toString(version1), versions[j], toString(version2))); + assertEquals(1, version2.compareTo(version1), String.format("version1=%s (%s), version2=%s (%s)", versions[i], toString(version1), versions[j], toString(version2))); } } assertGameVersionEquals(versions[versions.length - 1]); } + private void assertOldVersion(String oldVersion, GameVersionNumber.Type type, String versionNumber) { + GameVersionNumber version = GameVersionNumber.asGameVersion(oldVersion); + assertInstanceOf(GameVersionNumber.Old.class, version); + GameVersionNumber.Old old = (GameVersionNumber.Old) version; + assertSame(type, old.type); + assertEquals(VersionNumber.asVersion(versionNumber), old.versionNumber); + } + @Test public void testParseOld() { - { - GameVersionNumber version = GameVersionNumber.asGameVersion("b1.0"); - assertInstanceOf(GameVersionNumber.Old.class, version); - GameVersionNumber.Old old = (GameVersionNumber.Old) version; - assertEquals(GameVersionNumber.Type.BETA, old.type); - assertEquals(1, old.major); - assertEquals(0, old.minor); - assertEquals(0, old.patch); - assertEquals(0, old.additional); - } + assertOldVersion("rd-132211", GameVersionNumber.Type.PRE_CLASSIC, "132211"); + assertOldVersion("inf-20100618", GameVersionNumber.Type.INFDEV, "20100618"); + assertOldVersion("inf-20100330-1", GameVersionNumber.Type.INFDEV, "20100330-1"); + assertOldVersion("a1.0.6", GameVersionNumber.Type.ALPHA, "1.0.6"); + assertOldVersion("a1.0.8_01", GameVersionNumber.Type.ALPHA, "1.0.8_01"); + assertOldVersion("a1.0.13_01-1", GameVersionNumber.Type.ALPHA, "1.0.13_01-1"); + assertOldVersion("b1.0", GameVersionNumber.Type.BETA, "1.0"); + assertOldVersion("b1.0_01", GameVersionNumber.Type.BETA, "1.0_01"); + assertOldVersion("b1.8-pre1-2", GameVersionNumber.Type.BETA, "1.8-pre1-2"); + assertOldVersion("b1.9-pre1", GameVersionNumber.Type.BETA, "1.9-pre1"); + } - { - GameVersionNumber version = GameVersionNumber.asGameVersion("b1.0_01"); - assertInstanceOf(GameVersionNumber.Old.class, version); - GameVersionNumber.Old old = (GameVersionNumber.Old) version; - assertEquals(GameVersionNumber.Type.BETA, old.type); - assertEquals(1, old.major); - assertEquals(0, old.minor); - assertEquals(0, old.patch); - assertEquals(1, old.additional); + @Test + public void testParseNew() { + List versions = readVersions(); + for (String version : versions) { + assertFalse(GameVersionNumber.asGameVersion(version) instanceof GameVersionNumber.Old, "version=" + version); } } @@ -138,15 +168,24 @@ public void testCompareMix() { "inf-20100330-2", "inf-20100618", "a1.0.4", + "a1.0.8_01", + "a1.0.10", + "a1.0.13_01-1", "a1.0.17_02", "a1.0.17_04", "a1.1.0", + "a1.1.1", "b1.0", "b1.0_01", "b1.1_02", "b1.2", + "b1.8-pre1-2", "b1.8.1", "0.0", + "1.0.0-rc1", + "1.0.0-rc2-1", + "1.0.0-rc2-2", + "1.0.0-rc2-3", "1.0", "11w47a", "1.1", diff --git a/HMCLauncher/.gitignore b/HMCLauncher/.gitignore index df5a7f68f9..3b5cac370e 100644 --- a/HMCLauncher/.gitignore +++ b/HMCLauncher/.gitignore @@ -1,4 +1,5 @@ Debug Release .vs -*.APS \ No newline at end of file +*.APS +cmake-build-*/ diff --git a/HMCLauncher/CMakeLists.txt b/HMCLauncher/CMakeLists.txt new file mode 100644 index 0000000000..c7afc7f3ba --- /dev/null +++ b/HMCLauncher/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.25) +project(HMCLauncher) +if(MSVC) + add_compile_options(/utf-8 /D_UNICODE /W4) + add_link_options(/ENTRY:wWinMainCRTStartup) +else() + add_compile_options(-municode -Wall -Wextra -Wpedantic) + add_link_options(-municode) +endif() +OPTION(ENABLE_MINGW_STATIC_LINK_LIBSTDCXX "Link the C++ standard library statically to the executable file(mingw only)." ON) +if(ENABLE_MINGW_STATIC_LINK_LIBSTDCXX AND MINGW) + add_link_options(-static) +endif() +set(CMAKE_WIN32_EXECUTABLE ON) +add_executable(HMCLauncher WIN32 HMCL/HMCL.rc HMCL/java.cpp HMCL/main.cpp HMCL/os.cpp HMCL/stdafx.cpp HMCL/Version.cpp) +target_link_libraries(HMCLauncher Version) +install(TARGETS HMCLauncher) \ No newline at end of file diff --git a/HMCLauncher/HMCL/main.cpp b/HMCLauncher/HMCL/main.cpp index caee316071..fd1f9e1cba 100644 --- a/HMCLauncher/HMCL/main.cpp +++ b/HMCLauncher/HMCL/main.cpp @@ -10,8 +10,8 @@ Version J8(TEXT("8")); extern "C" { #ifdef _MSC_VER -_declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; -_declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 0x00000001; +__declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; +__declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 0x00000001; #else __attribute__ ((dllexport)) DWORD NvOptimusEnablement = 0x00000001; __attribute__ ((dllexport)) DWORD AmdPowerXpressRequestHighPerformance = 0x00000001; diff --git a/PLATFORM.md b/PLATFORM.md index 3205c5ee37..b60d0a9002 100644 --- a/PLATFORM.md +++ b/PLATFORM.md @@ -1,18 +1,19 @@ # Platform Support Status -English | [简体中文](PLATFORM_cn.md) - -| | Windows | Linux | Mac OS | FreeBSD | -|----------------------------|:--------------------------------------------------|:---------------------------|:------------------------------------------------------------------------|:---------------------------| -| x86-64 | ✅️ | ✅️ | ✅️ | 👌 (Minecraft 1.13~1.20.4) | -| x86 | ✅️ | ✅️ | / | / | -| ARM64 | 👌 (Minecraft 1.8~1.18.2)
✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.20.6) | 👌 (Minecraft 1.6~1.18.2)
✅ (Minecraft 1.19+)
✅ (use Rosetta 2) | ❔ | -| ARM32 | /️ | 👌 (Minecraft 1.8~1.20.1) | / | / | -| MIPS64el | / | 👌 (Minecraft 1.8~1.20.1) | / | / | -| RISC-V 64 | / | 👌 (Minecraft 1.13~1.20.4) | / | / | -| LoongArch64 | / | 👌 (Minecraft 1.6~1.20.1) | / | / | -| PowerPC-64 (Little-Endian) | / | ❔ | / | / | -| S390x | / | ❔ | / | / | +**English** | [简体中文](PLATFORM_cn.md) | [繁體中文](PLATFORM_tw.md) + +| | Windows | Linux | macOS | FreeBSD | +| -------------------------- | :----------------------------------------------- | :------------------------ | :--------------------------------------------------------------------- | :------------------------ | +| x86-64 | ✅️ | ✅️ | ✅️ | 👌 (Minecraft 1.13~1.21.1) | +| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / | +| ARM64 | 👌 (Minecraft 1.8~1.18.2)
✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21) | 👌 (Minecraft 1.6~1.18.2)
✅ (Minecraft 1.19+)
✅ (use Rosetta 2) | ❔ | +| ARM32 | /️ | 👌 (Minecraft 1.8~1.20.1) | / | / | +| MIPS64el | / | 👌 (Minecraft 1.8~1.20.1) | / | / | +| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.1) | / | / | +| LoongArch64 | / | 👌 (Minecraft 1.6~1.21.1) | / | / | +| LoongArch64 (Old World) | / | 👌 (Minecraft 1.6~1.20.1) | / | / | +| PowerPC-64 (Little-Endian) | / | ❔ | / | / | +| S390x | / | ❔ | / | / | Legend: @@ -22,18 +23,16 @@ Legend: * 👌: Supported platforms. - Support is provided by HMCL, tested to work, but may have more issues than a fully supported platform. - Support for versions below Minecraft 1.6 is not guaranteed. + Support is provided by HMCL, tested to work, but may have more issues than a fully supported platform. + Support for versions below Minecraft 1.6 is not guaranteed. If you encounter a problem that does not exist on fully supported platforms, you can report it to HMCL. * ❔: Low level supported platforms. - HMCL can run on this platform and has some basic support. - However, launching the game directly is not yet available. - If you want to start the game, - you'll need to get the native libraries needed by Minecraft in other way and specify the native path in the instance settings. + HMCL can run on this platform and has some basic support. However, launching the game directly is not yet available. + If you want to start the game, you will need to get the native libraries needed by Minecraft in other way and specify the native path in the instance settings. -* /: Not applicable. +* `/`: Not applicable. - We have no plans to support these platforms at this time, mainly because we don't have the equipment to test them. - If you can help us adapt, please file a support request via issue. + We have no plans to support these platforms at this time, mainly because we do not have the equipment to test them. + If you can help us adapt, please file a support request via GitHub Issue. \ No newline at end of file diff --git a/PLATFORM_cn.md b/PLATFORM_cn.md index c0298173b6..660f5a319e 100644 --- a/PLATFORM_cn.md +++ b/PLATFORM_cn.md @@ -1,39 +1,38 @@ # 平台支持状态 -[English](PLATFORM.md) | 简体中文 +[English](PLATFORM.md) | **简体中文** | [繁體中文](PLATFORM_tw.md) -| | Windows | Linux | Mac OS | FreeBSD | -|------------------|:---------------------------------------------------|:---------------------------|:-----------------------------------------------------------------------|:--------------------------| -| x86-64 | ✅️ | ✅️ | ✅️ | 👌(Minecraft 1.13~1.20.4) | -| x86 | ✅️ | ✅️ | / | / | -| ARM64 | 👌 (Minecraft 1.8~1.18.2)
✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.20.6) | 👌 (Minecraft 1.6~1.18.2)
✅ (Minecraft 1.19+)
✅ (使用 Rosetta 2) | ❔ | -| ARM32 | /️ | 👌 (Minecraft 1.6~1.20.1) | / | / | -| MIPS64el | / | 👌 (Minecraft 1.6~1.20.1) | / | / | -| RISC-V 64 | / | 👌 (Minecraft 1.13~1.20.4) | / | / | -| LoongArch64 | / | 👌 (Minecraft 1.6~1.20.1) | / | / | -| PowerPC-64 (小端序) | / | ❔ | / | / | -| S390x | / | ❔ | / | / | +| | Windows | Linux | macOS | FreeBSD | +| -------------------------- | :----------------------------------------------- | :------------------------ | :---------------------------------------------------------------------- | :----------------------- | +| x86-64 | ✅️ | ✅️ | ✅️ | 👌(Minecraft 1.13~1.21.1) | +| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / | +| ARM64 | 👌 (Minecraft 1.8~1.18.2)
✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21) | 👌 (Minecraft 1.6~1.18.2)
✅ (Minecraft 1.19+)
✅ (使用 Rosetta 2) | ❔ | +| ARM32 | /️ | 👌 (Minecraft 1.8~1.20.1) | / | / | +| MIPS64el | / | 👌 (Minecraft 1.8~1.20.1) | / | / | +| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.1) | / | / | +| LoongArch64 | / | 👌 (Minecraft 1.6~1.21.1) | / | / | +| LoongArch64 (旧世界) | / | 👌 (Minecraft 1.6~1.20.1) | / | / | +| PowerPC-64 (Little-Endian) | / | ❔ | / | / | +| S390x | / | ❔ | / | / | -图例: +图例: -* ✅: 官方支持的平台. +* ✅: 官方支持的平台 - 受 Mojang 官方支持. 在游戏中遇到的问题应该直接向 Mojang 反馈. + 受 Mojang 官方支持。在游戏中遇到的问题应该直接向 Mojang 反馈。 -* 👌: 支持的平台. +* 👌: 支持的平台 - 由 HMCL 提供支持, 经过测试可以正常运行, 但可能比得到全面支持的平台有更多问题. - 不保证支持 Minecraft 1.6 以下的版本. - 如果你遇到在得到全面支持的平台上不存在的问题, 可以向 HMCL 反馈. + 由 HMCL 提供支持,经过测试可以正常运行,但可能比得到全面支持的平台有更多问题。 + 不保证支持 Minecraft 1.6 以下的版本。 + 如果你遇到在得到全面支持的平台上不存在的问题,可以向 HMCL 反馈。 -* ❔: 低级别支持的平台. +* ❔: 低级别支持的平台 - HMCL 可以在这个平台上运行, 并且有一些基本的支持. - 但是, 还不能正常地启动游戏. - 如果你想正常启动游戏, - 则需要通过其他方式获得游戏所需的本地库(LWJGL), 并在版本设置中指定本地库路径. + HMCL 可以在这个平台上运行,并且有一些基本的支持。但是,还不能正常地启动游戏。 + 如果你想正常启动游戏,则需要通过其他方式获得游戏所需的本地库 (LWJGL),并在(全局)游戏设置中指定本地库路径。 -* /: 不支持的平台. +* `/`: 不支持的平台 - 我们目前还没有打算支持这些平台, 主要是因为我们没有测试这些平台的设备. - 如果你能帮助我们进行测试, 请通过提交 Issue 提出支持请求. + 我们目前还没有打算支持这些平台,主要是因为我们没有测试这些平台的设备。 + 如果你能帮助我们进行测试,请通过提交 Issue 提出支持请求。 \ No newline at end of file diff --git a/PLATFORM_tw.md b/PLATFORM_tw.md new file mode 100644 index 0000000000..58f20451d5 --- /dev/null +++ b/PLATFORM_tw.md @@ -0,0 +1,38 @@ +# 平臺支援狀態 + +[English](PLATFORM.md) | [简体中文](PLATFORM_cn.md) | **繁體中文** + +| | Windows | Linux | macOS | FreeBSD | +| -------------------------- | :----------------------------------------------- | :------------------------ | :---------------------------------------------------------------------- | :----------------------- | +| x86-64 | ✅️ | ✅️ | ✅️ | 👌(Minecraft 1.13~1.21.1) | +| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / | +| ARM64 | 👌 (Minecraft 1.8~1.18.2)
✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21) | 👌 (Minecraft 1.6~1.18.2)
✅ (Minecraft 1.19+)
✅ (使用 Rosetta 2) | ❔ | +| ARM32 | /️ | 👌 (Minecraft 1.8~1.20.1) | / | / | +| MIPS64el | / | 👌 (Minecraft 1.8~1.20.1) | / | / | +| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.1) | / | / | +| LoongArch64 | / | 👌 (Minecraft 1.6~1.21.1) | / | / | +| LoongArch64 (舊世界) | / | 👌 (Minecraft 1.6~1.20.1) | / | / | +| PowerPC-64 (Little-Endian) | / | ❔ | / | / | +| S390x | / | ❔ | / | / | + +圖例: + +* ✅: 官方支援的平臺 + + 受 Mojang 官方支援。在遊戲中遇到的問題應該直接向 Mojang 回報。 + +* 👌: 支援的平臺 + + 由 HMCL 提供支援,經過測試可以正常執行,但可能比得到全面支援的平臺有更多問題。 + 不保證支援 Minecraft 1.6 以下的版本。 + 如果你遇到在得到全面支援的平臺上不存在的問題,可以向 HMCL 回報。 + +* ❔: 低級別支援的平臺 + + HMCL 可以在這個平臺上執行,並且有一些基本的支援。但是,還不能正常地啟動遊戲。 + 如果你想正常啟動遊戲,則需要透過其他方式獲得遊戲所需的本機庫 (LWJGL),並在(全域)遊戲設定中指定本機庫路徑。 + +* `/`: 不支援的平臺 + + 我們目前還沒有打算支援這些平臺,主要是因為我們沒有測試這些平臺的裝置。 + 如果你能幫助我們進行測試,請透過提交 Issue 提出支援請求。 \ No newline at end of file diff --git a/README.md b/README.md index d46d9a0d27..b0118ea253 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,18 @@ # ⛏ Hello Minecraft! Launcher 💎 [![Build Status](https://ci.huangyuhui.net/job/HMCL/badge/icon?.svg)](https://ci.huangyuhui.net/job/HMCL) -![Downloads](https://img.shields.io/github/downloads/huanghongxun/HMCL/total) -![Stars](https://img.shields.io/github/stars/huanghongxun/HMCL) +![Downloads](https://img.shields.io/github/downloads/HMCL-dev/HMCL/total?style=flat) +![Stars](https://img.shields.io/github/stars/HMCL-dev/HMCL?style=flat) [![Discord](https://img.shields.io/discord/995291757799538688.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/jVvC7HfM6U) -[![QQ Group](https://img.shields.io/badge/QQ-HMCL-brightgreen)](https://docs.hmcl.net/groups.html) +[![QQ Group](https://img.shields.io/badge/QQ-HMCL-bright?label=&logo=tencentqq&logoColor=ffffff&color=1EBAFC&labelColor=1DB0EF&logoSize=auto)](https://docs.hmcl.net/groups.html) -English | [中文](README_cn.md) +**English** | [简体中文](README_cn.md) | [繁體中文](README_tw.md) ## Introduction -HMCL is a cross-platform Minecraft launcher which supports Mod Management, Game Customizing, Auto Installing (Forge, NeoForge, Fabric, Quilt, LiteLoader and OptiFine), Modpack Creating, UI Customization, and more. +HMCL is an open-source, cross-platform Minecraft launcher that supports Mod Management, Game Customizing, ModLoader Installing (Forge, NeoForge, Fabric, Quilt, LiteLoader and OptiFine), Modpack Creating, UI Customization, and more. -HMCL has amazing cross-platform capabilities. -It can not only run on different operating systems such as Windows, Linux, and macOS, -but also supports multiple CPU architectures such as x86, arm, mips, and loongarch. -You can easily play Minecraft on different platforms through HMCL. +HMCL has amazing cross-platform capabilities. Not only does it run on different operating systems like Windows, Linux, and macOS, but it also supports various CPU architectures such as x86, ARM, MIPS, and LoongArch. You can easily enjoy Minecraft across different platforms through HMCL. For systems and CPU architectures supported by HMCL, see [this table](PLATFORM.md). @@ -23,13 +20,13 @@ For systems and CPU architectures supported by HMCL, see [this table](PLATFORM.m Download the latest version from [the official website](https://hmcl.huangyuhui.net/download). -You can also download the latest version of HMCL in [GitHub Releases](https://github.com/HMCL-dev/HMCL/releases). +You can also find the latest version of HMCL in [GitHub Releases](https://github.com/HMCL-dev/HMCL/releases). -Although not necessary, it is recommended to download the ones from the official website. +Although not necessary, it is recommended only to download releases from the official websites listed above. ## License -The software is distributed under [GPLv3](https://www.gnu.org/licenses/gpl-3.0.html) with additional terms. +The software is distributed under [GPLv3](https://www.gnu.org/licenses/gpl-3.0.html) license with the following additional terms: ### Additional terms under GPLv3 Section 7 @@ -41,9 +38,9 @@ The software is distributed under [GPLv3](https://www.gnu.org/licenses/gpl-3.0.h ## Contribution -If you want to submit a pull request, there are some requirements: +If you want to submit a pull request, here are some requirements: -* IDE: Intellij IDEA +* IDE: IntelliJ IDEA * Compiler: Java 1.8 or Java 11+ * Do NOT modify `gradle` files @@ -55,20 +52,20 @@ Simply execute the following command in project root directory: ./gradlew clean build ``` -Make sure you have Java installed with JavaFX 8 at least. Liberica Full JDK 8 or later is recommended. +Make sure you have at least JavaFX 8 installed. Liberica Full JDK 8 or later is recommended. ## JVM Options (for debugging) -| Parameter | Description | -|----------------------------------------------|------------------------------------------------------------------------------------------------| -| `-Dhmcl.home=` | Override HMCL directory. | -| `-Dhmcl.self_integrity_check.disable=true` | Bypass the self integrity check when checking for update. | -| `-Dhmcl.bmclapi.override=` | Override API Root of BMCLAPI download provider, defaults to `https://bmclapi2.bangbang93.com`. | -| `-Dhmcl.font.override=` | Override font family. | -| `-Dhmcl.version.override=` | Override the version number. | -| `-Dhmcl.update_source.override=` | Override the update source for HMCL itself. | -| `-Dhmcl.authlibinjector.location=` | Use specified authlib-injector (instead of downloading one). | -| `-Dhmcl.openjfx.repo=` | Add custom Maven repository for download OpenJFX. | -| `-Dhmcl.native.encoding=` | Override the native encoding. | -| `-Dhmcl.microsoft.auth.id=` | Override Microsoft OAuth App ID. | -| `-Dhmcl.microsoft.auth.secret=` | Override Microsoft OAuth App secret. | +| Parameter | Description | +| -------------------------------------------- | --------------------------------------------------------------------------------------------- | +| `-Dhmcl.home=` | Override HMCL directory | +| `-Dhmcl.self_integrity_check.disable=true` | Bypass the self integrity check when checking for update | +| `-Dhmcl.bmclapi.override=` | Override API Root of BMCLAPI download provider, defaults to `https://bmclapi2.bangbang93.com` | +| `-Dhmcl.font.override=` | Override font family | +| `-Dhmcl.version.override=` | Override the version number | +| `-Dhmcl.update_source.override=` | Override the update source for HMCL itself | +| `-Dhmcl.authlibinjector.location=` | Use specified authlib-injector (instead of downloading one) | +| `-Dhmcl.openjfx.repo=` | Add custom Maven repository for download OpenJFX | +| `-Dhmcl.native.encoding=` | Override the native encoding | +| `-Dhmcl.microsoft.auth.id=` | Override Microsoft OAuth App ID | +| `-Dhmcl.microsoft.auth.secret=` | Override Microsoft OAuth App secret | diff --git a/README_cn.md b/README_cn.md index bd9e1a9718..2d8d7d0d40 100644 --- a/README_cn.md +++ b/README_cn.md @@ -1,72 +1,71 @@ # ⛏ Hello Minecraft! Launcher 💎 [![Build Status](https://ci.huangyuhui.net/job/HMCL/badge/icon?.svg)](https://ci.huangyuhui.net/job/HMCL) -![Downloads](https://img.shields.io/github/downloads/huanghongxun/HMCL/total) -![Stars](https://img.shields.io/github/stars/huanghongxun/HMCL) +![Downloads](https://img.shields.io/github/downloads/HMCL-dev/HMCL/total?style=flat) +![Stars](https://img.shields.io/github/stars/HMCL-dev/HMCL?style=flat) [![Discord](https://img.shields.io/discord/995291757799538688.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/jVvC7HfM6U) -[![QQ Group](https://img.shields.io/badge/QQ-HMCL-brightgreen)](https://docs.hmcl.net/groups.html) +[![QQ Group](https://img.shields.io/badge/QQ-HMCL-bright?label=&logo=tencentqq&logoColor=ffffff&color=1EBAFC&labelColor=1DB0EF&logoSize=auto)](https://docs.hmcl.net/groups.html) -[English](README.md) | 中文 +[English](README.md) | **简体中文** | [繁體中文](README_tw.md) ## 简介 -HMCL 是一款跨平台 Minecraft 启动器, 支持 Mod 管理, 游戏自定义, 游戏自动安装 (Forge, NeoForge, Fabric, Quilt, LiteLoader 与 OptiFine), 整合包创建, 界面自定义等功能. +HMCL 是一款开源、跨平台的 Minecraft 启动器,支持模组管理、游戏自定义、游戏自动安装 (Forge、NeoForge、Fabric、Quilt、LiteLoader 和 OptiFine)、整合包创建、界面自定义等功能。 -HMCL 有着强大的跨平台能力. 它不仅支持 Windows、Linux、macOS 等常见的操作系统,同时也支持 x86、ARM、MIPS 和 LoongArch 等不同的 CPU 架构. -您可以使用 HMCL 在不同平台上轻松的游玩 Minecraft. +HMCL 有着强大的跨平台能力。它不仅支持 Windows、Linux、macOS 等常见的操作系统,同时也支持 x86、ARM、MIPS 和 LoongArch 等不同的 CPU 架构。你可以使用 HMCL 在不同平台上轻松地游玩 Minecraft。 -如果您想要了解 HMCL 对不同平台的支持程度,请参见[此表格](PLATFORM_cn.md). +如果你想要了解 HMCL 对不同平台的支持程度,请参见 [此表格](PLATFORM_cn.md)。 ## 下载 -请从 [HMCL 官网](https://hmcl.huangyuhui.net/download) 下载最新版本的 HMCL. +请从 [HMCL 官网](https://hmcl.huangyuhui.net/download) 下载最新版本的 HMCL。 -你也可以在 [GitHub Releases](https://github.com/HMCL-dev/HMCL/releases) 中下载最新版本的 HMCL. +你也可以在 [GitHub Releases](https://github.com/HMCL-dev/HMCL/releases) 中下载最新版本的 HMCL。 -虽然并不强制, 但仍建议通过 HMCL 官网下载启动器. +虽然并不强制,但仍建议通过 HMCL 官网下载启动器。 ## 开源协议 -该程序在 [GPLv3](https://www.gnu.org/licenses/gpl-3.0.html) 开源协议下发布, 同时附有附加条款. +该程序在 [GPLv3](https://www.gnu.org/licenses/gpl-3.0.html) 开源协议下发布,同时附有以下附加条款。 ### 附加条款 (依据 GPLv3 开源协议第七条) -1. 当您分发该程序的修改版本时, 您必须以一种合理的方式修改该程序的名称或版本号, 以示其与原始版本不同. (依据 [GPLv3, 7(c)](https://github.com/HMCL-dev/HMCL/blob/11820e31a85d8989e41d97476712b07e7094b190/LICENSE#L372-L374)) +1. 当你分发该程序的修改版本时,你必须以一种合理的方式修改该程序的名称或版本号,以示其与原始版本不同。(依据 [GPLv3, 7(c)](https://github.com/HMCL-dev/HMCL/blob/11820e31a85d8989e41d97476712b07e7094b190/LICENSE#L372-L374)) - 该程序的名称及版本号可在[此处](https://github.com/HMCL-dev/HMCL/blob/javafx/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java#L33-L35)修改. + 该程序的名称及版本号可在 [此处](https://github.com/HMCL-dev/HMCL/blob/javafx/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java#L33-L35) 修改。 -2. 您不得移除该程序所显示的版权声明. (依据 [GPLv3, 7(b)](https://github.com/HMCL-dev/HMCL/blob/11820e31a85d8989e41d97476712b07e7094b190/LICENSE#L368-L370)) +2. 你不得移除该程序所显示的版权声明。(依据 [GPLv3, 7(b)](https://github.com/HMCL-dev/HMCL/blob/11820e31a85d8989e41d97476712b07e7094b190/LICENSE#L368-L370)) ## 贡献 -如果您想提交一个 Pull Request, 必须遵守如下要求: +如果你想提交一个 Pull Request,必须遵守如下要求: -* IDE: Intellij IDEA -* 编译器: Java 1.8 或 Java 11+ +* IDE:IntelliJ IDEA +* 编译器:Java 1.8 或 Java 11+ * **不要**修改 `gradle` 相关文件 ### 编译 -于项目根目录执行以下命令: +于项目根文件夹执行以下命令: ```bash ./gradlew clean build ``` -请确保您至少安装了含有 JavaFX 8 的 Java. 建议使用 Liberica Full JDK 8 或更高版本. +请确保你至少安装了含有 JavaFX 8 的 Java。建议使用 Liberica Full JDK 8 或更高版本。 ## JVM 选项 (用于调试) -| 参数 | 简介 | -|----------------------------------------------|----------------------------------------------------------------| -| `-Dhmcl.home=` | 覆盖 HMCL 数据文件夹. | -| `-Dhmcl.self_integrity_check.disable=true` | 检查更新时绕过本体完整性检查. | -| `-Dhmcl.bmclapi.override=` | 覆盖 BMCLAPI 的 API Root, 默认值为 `https://bmclapi2.bangbang93.com`. | -| `-Dhmcl.font.override=` | 覆盖字族. | -| `-Dhmcl.version.override=` | 覆盖版本号. | -| `-Dhmcl.update_source.override=` | 覆盖 HMCL 更新源. | -| `-Dhmcl.authlibinjector.location=` | 使用指定的 authlib-injector (而非下载一个). | -| `-Dhmcl.openjfx.repo=` | 添加用于下载 OpenJFX 的自定义 Maven 仓库 | -| `-Dhmcl.native.encoding=` | 覆盖原生编码. | -| `-Dhmcl.microsoft.auth.id=` | 覆盖 Microsoft OAuth App ID. | -| `-Dhmcl.microsoft.auth.secret=` | 覆盖 Microsoft OAuth App 密钥. | +| 参数 | 简介 | +| -------------------------------------------- | -------------------------------------------------------------------- | +| `-Dhmcl.home=` | 覆盖 HMCL 数据文件夹 | +| `-Dhmcl.self_integrity_check.disable=true` | 检查更新时绕过本体完整性检查 | +| `-Dhmcl.bmclapi.override=` | 覆盖 BMCLAPI 的 API Root,默认值为 `https://bmclapi2.bangbang93.com` | +| `-Dhmcl.font.override=` | 覆盖字族 | +| `-Dhmcl.version.override=` | 覆盖版本号 | +| `-Dhmcl.update_source.override=` | 覆盖 HMCL 更新源 | +| `-Dhmcl.authlibinjector.location=` | 使用指定的 authlib-injector (而非下载一个) | +| `-Dhmcl.openjfx.repo=` | 添加用于下载 OpenJFX 的自定义 Maven 仓库 | +| `-Dhmcl.native.encoding=` | 覆盖原生编码 | +| `-Dhmcl.microsoft.auth.id=` | 覆盖 Microsoft OAuth App ID | +| `-Dhmcl.microsoft.auth.secret=` | 覆盖 Microsoft OAuth App 密钥 | \ No newline at end of file diff --git a/README_tw.md b/README_tw.md new file mode 100644 index 0000000000..4fcebadbfb --- /dev/null +++ b/README_tw.md @@ -0,0 +1,71 @@ +# ⛏ Hello Minecraft! Launcher 💎 + +[![Build Status](https://ci.huangyuhui.net/job/HMCL/badge/icon?.svg)](https://ci.huangyuhui.net/job/HMCL) +![Downloads](https://img.shields.io/github/downloads/HMCL-dev/HMCL/total?style=flat) +![Stars](https://img.shields.io/github/stars/HMCL-dev/HMCL?style=flat) +[![Discord](https://img.shields.io/discord/995291757799538688.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/jVvC7HfM6U) +[![QQ Group](https://img.shields.io/badge/QQ-HMCL-bright?label=&logo=tencentqq&logoColor=ffffff&color=1EBAFC&labelColor=1DB0EF&logoSize=auto)](https://docs.hmcl.net/groups.html) + +[English](README.md) | [简体中文](README_cn.md) | **繁體中文** + +## 簡介 + +HMCL 是一款開源、跨平臺的 Minecraft 啟動器,支援模組管理、遊戲自訂、遊戲自動安裝 (Forge、NeoForge、Fabric、Quilt、LiteLoader 和 OptiFine)、模組包建立、介面自訂等功能。 + +HMCL 有著強大的跨平臺能力。它不僅支援 Windows、Linux、macOS 等常見的作業系統,同時也支援 x86、ARM、MIPS 和 LoongArch 等不同的 CPU 架構。你可以使用 HMCL 在不同平臺上輕鬆地遊玩 Minecraft。 + +如果你想要了解 HMCL 對不同平臺的支援程度,請參見 [此表格](PLATFORM_tw.md)。 + +## 下載 + +請從 [HMCL 官網](https://hmcl.huangyuhui.net/download) 下載最新版本的 HMCL。 + +你也可以在 [GitHub Releases](https://github.com/HMCL-dev/HMCL/releases) 中下載最新版本的 HMCL。 + +雖然並不強制,但仍建議透過 HMCL 官網下載啟動器。 + +## 開源協議 + +該程式在 [GPLv3](https://www.gnu.org/licenses/gpl-3.0.html) 開源協議下發布,同時附有以下附加條款。 + +### 附加條款 (依據 GPLv3 開源協議第七條) + +1. 當你分發該程式的修改版本時,你必須以一種合理的方式修改該程式的名稱或版本號,以示其與原始版本不同。(依據 [GPLv3, 7(c)](https://github.com/HMCL-dev/HMCL/blob/11820e31a85d8989e41d97476712b07e7094b190/LICENSE#L372-L374)) + + 該程式的名稱及版本號可在 [此處](https://github.com/HMCL-dev/HMCL/blob/javafx/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java#L33-L35) 修改。 + +2. 你不得移除該程式所顯示的版權宣告。(依據 [GPLv3, 7(b)](https://github.com/HMCL-dev/HMCL/blob/11820e31a85d8989e41d97476712b07e7094b190/LICENSE#L368-L370)) + +## 貢獻 + +如果你想提交一個 Pull Request,必須遵守如下要求: + +* IDE:IntelliJ IDEA +* 編譯器:Java 1.8 或 Java 11+ +* **不要**修改 `gradle` 相關文件 + +### 編譯 + +於項目根目錄執行以下指令: + +```bash +./gradlew clean build +``` + +請確保你至少安裝了含有 JavaFX 8 的 Java。建議使用 Liberica Full JDK 8 或更高版本。 + +## JVM 選項 (用於除錯) + +| 參數 | 簡介 | +| -------------------------------------------- | -------------------------------------------------------------------- | +| `-Dhmcl.home=` | 覆蓋 HMCL 使用者目錄 | +| `-Dhmcl.self_integrity_check.disable=true` | 檢查更新時繞過本體完整性檢查 | +| `-Dhmcl.bmclapi.override=` | 覆蓋 BMCLAPI 的 API Root,預設值為 `https://bmclapi2.bangbang93.com` | +| `-Dhmcl.font.override=` | 覆蓋字族 | +| `-Dhmcl.version.override=` | 覆蓋版本號 | +| `-Dhmcl.update_source.override=` | 覆蓋 HMCL 更新來源 | +| `-Dhmcl.authlibinjector.location=` | 使用指定的 authlib-injector (而非下載一個) | +| `-Dhmcl.openjfx.repo=` | 添加用於下載 OpenJFX 的自訂 Maven 倉庫 | +| `-Dhmcl.native.encoding=` | 覆蓋原生編碼 | +| `-Dhmcl.microsoft.auth.id=` | 覆蓋 Microsoft OAuth App ID | +| `-Dhmcl.microsoft.auth.secret=` | 覆蓋 Microsoft OAuth App 金鑰 | \ No newline at end of file diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index daa5be2bf0..900c4340d8 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -54,7 +54,7 @@ - + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index d64cd49177..a4b76b9530 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a80b22ce5c..94913af205 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip -networkTimeout=10000 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +networkTimeout=120000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 1aa94a4269..f5feea6d6b 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,8 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 6689b85bee..9b42019c79 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/javafx.gradle.kts b/javafx.gradle.kts index 734639eeb3..aaa266e241 100644 --- a/javafx.gradle.kts +++ b/javafx.gradle.kts @@ -4,21 +4,19 @@ buildscript { } dependencies { - classpath("com.google.code.gson:gson:2.10.1") + classpath("com.google.code.gson:gson:2.11.0") } } -val jfxVersion = "19.0.2.1" +val jfxVersion = "17.0.13" +val oldJfxVersion = "19.0.2.1" data class Platform( val name: String, val classifier: String, val groupId: String = "org.openjfx", - val version: String = jfxVersion, - val unsupportedModules: List = listOf() + val version: String = jfxVersion ) { - val modules: List = jfxModules.filter { it !in unsupportedModules } - fun fileUrl( module: String, classifier: String, ext: String, repo: String = "https://repo1.maven.org/maven2" @@ -28,22 +26,22 @@ data class Platform( ).toURL() } -val jfxModules = listOf("base", "graphics", "controls", "media", "web") +val jfxModules = listOf("base", "graphics", "controls") val jfxMirrorRepos = listOf("https://mirrors.cloud.tencent.com/nexus/repository/maven-public") -val jfxDependenciesFile = project("HMCL").layout.buildDirectory.file("openjfx-dependencies.json").get().asFile +val jfxDependenciesFile = project.file("HMCL/src/main/resources/assets/openjfx-dependencies.json") val jfxPlatforms = listOf( - Platform("windows-x86", "win-x86"), + Platform("windows-x86", "win-x86", version = oldJfxVersion), Platform("windows-x86_64", "win"), - Platform("windows-arm64", "win", groupId = "org.glavo.hmcl.openjfx", version = "18.0.2+1-arm64", unsupportedModules = listOf("media", "web")), + Platform("windows-arm64", "win", groupId = "org.glavo.hmcl.openjfx", version = "18.0.2+1-arm64"), Platform("osx-x86_64", "mac"), Platform("osx-arm64", "mac-aarch64"), Platform("linux-x86_64", "linux"), - Platform("linux-arm32", "linux-arm32-monocle", unsupportedModules = listOf("media", "web")), - Platform("linux-arm64", "linux-aarch64"), + Platform("linux-arm32", "linux-arm32-monocle", version = oldJfxVersion), + Platform("linux-arm64", "linux-aarch64", version = oldJfxVersion), Platform("linux-loongarch64", "linux", groupId = "org.glavo.hmcl.openjfx", version = "17.0.8-loongarch64"), - Platform("linux-loongarch64_ow", "linux", groupId = "org.glavo.hmcl.openjfx", version = "19-ea+10-loongson64", unsupportedModules = listOf("media", "web")), - Platform("linux-riscv64", "linux", groupId = "org.glavo.hmcl.openjfx", version = "19.0.2.1-riscv64", unsupportedModules = listOf("media", "web")), - Platform("freebsd-x86_64", "freebsd", groupId = "org.glavo.hmcl.openjfx", version = "14.0.2.1-freebsd", unsupportedModules = listOf("media", "web")), + Platform("linux-loongarch64_ow", "linux", groupId = "org.glavo.hmcl.openjfx", version = "19-ea+10-loongson64"), + Platform("linux-riscv64", "linux", groupId = "org.glavo.hmcl.openjfx", version = "19.0.2.1-riscv64"), + Platform("freebsd-x86_64", "freebsd", groupId = "org.glavo.hmcl.openjfx", version = "14.0.2.1-freebsd"), ) val jfxInClasspath = @@ -89,11 +87,9 @@ if (!jfxInClasspath && JavaVersion.current() >= JavaVersion.VERSION_11) { } rootProject.tasks.create("generateOpenJFXDependencies") { - outputs.file(jfxDependenciesFile) - doLast { val jfxDependencies = jfxPlatforms.associate { platform -> - platform.name to platform.modules.map { module -> + platform.name to jfxModules.map { module -> mapOf( "module" to "javafx.$module", "groupId" to platform.groupId, @@ -117,7 +113,7 @@ rootProject.tasks.create("preTouchOpenJFXDependencies") { doLast { for (repo in jfxMirrorRepos) { for (platform in jfxPlatforms) { - for (module in platform.modules) { + for (module in jfxModules) { val url = platform.fileUrl(module, platform.classifier, "jar", repo = repo) logger.quiet("Getting $url") try {