Compare commits
39
Commits
v3.5.8.251
...
v3.5.9.257
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4f5e013cb | ||
|
|
d4579852bb | ||
|
|
edd95ad626 | ||
|
|
dec307b531 | ||
|
|
00b7085370 | ||
|
|
17c1998119 | ||
|
|
1c05cd981f | ||
|
|
e9c3795ffc | ||
|
|
c3917082ee | ||
|
|
6265580d75 | ||
|
|
65fc3d0c4f | ||
|
|
1c864ba072 | ||
|
|
ef9159666c | ||
|
|
541f2e2619 | ||
|
|
c7f248a092 | ||
|
|
7937f8ddf5 | ||
|
|
74926704bd | ||
|
|
9196bda537 | ||
|
|
4c1e607b8e | ||
|
|
26224ae366 | ||
|
|
d3e9511aca | ||
|
|
b624cebc08 | ||
|
|
7e4d437a1d | ||
|
|
37d6857b82 | ||
|
|
f8d7c92d51 | ||
|
|
ea9999a1f9 | ||
|
|
d969fab284 | ||
|
|
97fe52c2a7 | ||
|
|
8a816f7f35 | ||
|
|
6409841cca | ||
|
|
6348166dac | ||
|
|
48417b9b7b | ||
|
|
738000ff39 | ||
|
|
9b5445b686 | ||
|
|
abf58b2c20 | ||
|
|
fd97f6c321 | ||
|
|
abd37a093a | ||
|
|
0f0cf55509 | ||
|
|
95a1496389 |
No files matched your search
+57
-5
@@ -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<String> {
|
||||
if (options == null)
|
||||
return listOf()
|
||||
|
||||
val builder = StringBuilder()
|
||||
val result = mutableListOf<String>()
|
||||
|
||||
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<JavaExec>("run") {
|
||||
dependsOn(tasks.jar)
|
||||
|
||||
@@ -215,4 +260,11 @@ tasks.create<JavaExec>("run") {
|
||||
|
||||
classpath = files(jarPath)
|
||||
workingDir = rootProject.rootDir
|
||||
|
||||
val vmOptions = parseToolOptions(System.getenv("HMCL_JAVA_OPTS"))
|
||||
jvmArgs(vmOptions)
|
||||
|
||||
doFirst {
|
||||
logger.quiet("HMCL_JAVA_OPTS: $vmOptions")
|
||||
}
|
||||
}
|
||||
@@ -222,7 +222,9 @@ public final class Launcher extends Application {
|
||||
|
||||
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"));
|
||||
|
||||
@@ -20,10 +20,9 @@ package org.jackhuang.hmcl;
|
||||
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 final class Main {
|
||||
|
||||
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 final class Main {
|
||||
|
||||
checkJavaFX();
|
||||
verifyJavaFX();
|
||||
detectFractureiser();
|
||||
|
||||
Launcher.main(args);
|
||||
}
|
||||
@@ -94,13 +92,6 @@ public final class Main {
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.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 = "";
|
||||
}
|
||||
@@ -5,14 +5,8 @@ import org.jackhuang.hmcl.util.StringUtils;
|
||||
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 class CrashReport {
|
||||
return true;
|
||||
}
|
||||
|
||||
public Map<String, Object> 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 class CrashReport {
|
||||
" 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;
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.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 class HMCLGameRepository extends DefaultGameRepository {
|
||||
vs.setUsesGlobal(true);
|
||||
}
|
||||
|
||||
public LaunchOptions getLaunchOptions(String version, JavaVersion javaVersion, File gameDir, List<String> javaAgents, boolean makeLaunchScript) {
|
||||
public LaunchOptions getLaunchOptions(String version, JavaRuntime javaVersion, File gameDir, List<String> javaAgents, boolean makeLaunchScript) {
|
||||
VersionSetting vs = getVersionSetting(version);
|
||||
|
||||
LaunchOptions.Builder builder = new LaunchOptions.Builder()
|
||||
@@ -396,7 +396,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
.setOverrideJavaArguments(StringUtils.tokenize(vs.getJavaArgs()))
|
||||
.setMaxMemory(vs.isNoJVMArgs() && vs.isAutoMemory() ? null : (int)(getAllocatedMemory(
|
||||
vs.getMaxMemory() * 1024L * 1024L,
|
||||
OperatingSystem.getPhysicalMemoryStatus().orElse(OperatingSystem.PhysicalMemoryStatus.INVALID).getAvailable(),
|
||||
OperatingSystem.getPhysicalMemoryStatus().getAvailable(),
|
||||
vs.isAutoMemory()
|
||||
) / 1024 / 1024))
|
||||
.setMinMemory(vs.getMinMemory())
|
||||
|
||||
@@ -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 final class HMCLModpackInstallTask extends Task<Void> {
|
||||
ModpackConfiguration<Modpack> config = null;
|
||||
try {
|
||||
if (json.exists()) {
|
||||
config = JsonUtils.GSON.fromJson(FileUtils.readText(json), new TypeToken<ModpackConfiguration<Modpack>>() {
|
||||
}.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.");
|
||||
|
||||
@@ -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.DownloadProvider;
|
||||
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.SocketTimeoutException;
|
||||
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 @@ public final class LauncherHelper {
|
||||
CountDownLatch launchingLatch = new CountDownLatch(1);
|
||||
List<String> javaAgents = new ArrayList<>(0);
|
||||
|
||||
AtomicReference<JavaVersion> javaVersionRef = new AtomicReference<>();
|
||||
AtomicReference<JavaRuntime> 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 @@ public final class LauncherHelper {
|
||||
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 @@ public final class LauncherHelper {
|
||||
})
|
||||
);
|
||||
}).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 @@ public final class LauncherHelper {
|
||||
it.fireEvent(new DialogCloseEvent());
|
||||
}));
|
||||
} else {
|
||||
Platform.runLater(() -> {
|
||||
runLater(() -> {
|
||||
launchingStepsPane.fireEvent(new DialogCloseEvent());
|
||||
Controllers.dialog(i18n("version.launch_script.success", scriptFile.getAbsolutePath()));
|
||||
});
|
||||
@@ -234,7 +227,7 @@ public final class LauncherHelper {
|
||||
|
||||
@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()) {
|
||||
@@ -331,178 +324,181 @@ public final class LauncherHelper {
|
||||
executor.start();
|
||||
}
|
||||
|
||||
private static Task<JavaVersion> checkGameState(Profile profile, VersionSetting setting, Version version) {
|
||||
private static Task<JavaRuntime> 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<JavaRuntime> getJavaTask = Task.supplyAsync(() -> {
|
||||
try {
|
||||
return setting.getJava(gameVersion, version);
|
||||
} catch (InterruptedException e) {
|
||||
throw new CancellationException();
|
||||
}
|
||||
});
|
||||
Task<JavaRuntime> task;
|
||||
if (setting.isNotCheckJVM()) {
|
||||
return Task.composeAsync(() -> setting.getJavaVersion(gameVersion, version))
|
||||
.thenApplyAsync(javaVersion -> Optional.ofNullable(javaVersion).orElseGet(JavaVersion::fromCurrentEnvironment))
|
||||
.withStage("launch.state.java");
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
return Task.composeAsync(() -> {
|
||||
return setting.getJavaVersion(gameVersion, version);
|
||||
}).thenComposeAsync(Schedulers.javafx(), javaVersion -> {
|
||||
// Reset invalid java version
|
||||
if (javaVersion == null) {
|
||||
CompletableFuture<JavaVersion> future = new CompletableFuture<>();
|
||||
Runnable continueAction = () -> future.complete(JavaVersion.fromCurrentEnvironment());
|
||||
// Reset invalid java version
|
||||
CompletableFuture<JavaRuntime> future = new CompletableFuture<>();
|
||||
Task<JavaRuntime> result = Task.fromCompletableFuture(future);
|
||||
Runnable breakAction = () -> future.completeExceptionally(new CancellationException("No accepted java"));
|
||||
List<GameJavaVersion> supportedVersions = GameJavaVersion.getSupportedVersions(SYSTEM_PLATFORM);
|
||||
|
||||
if (setting.isJavaAutoSelected()) {
|
||||
GameJavaVersion targetJavaVersion = null;
|
||||
GameJavaVersion targetJavaVersion = null;
|
||||
if (setting.getJavaVersionType() == JavaVersionType.VERSION) {
|
||||
try {
|
||||
int targetJavaVersionMajor = Integer.parseInt(setting.getJavaVersion());
|
||||
GameJavaVersion minimumJavaVersion = GameJavaVersion.getMinimumJavaVersion(gameVersion);
|
||||
|
||||
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 (minimumJavaVersion != null && targetJavaVersionMajor < minimumJavaVersion.getMajorVersion()) {
|
||||
Controllers.dialog(
|
||||
i18n("launch.failed.java_version_too_low"),
|
||||
i18n("message.error"),
|
||||
MessageType.ERROR,
|
||||
breakAction
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
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;
|
||||
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.confirm(i18n("launch.failed.no_accepted_java"), i18n("message.warning"), MessageType.WARNING,
|
||||
() -> future.complete(JavaRuntime.getDefault()),
|
||||
breakAction);
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
} else {
|
||||
task = getJavaTask.thenComposeAsync(java -> {
|
||||
Set<JavaVersionConstraint> violatedMandatoryConstraints = EnumSet.noneOf(JavaVersionConstraint.class);
|
||||
Set<JavaVersionConstraint> 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);
|
||||
}
|
||||
}
|
||||
|
||||
if (java8Version != null && range.getMandatory().contains(VersionNumber.asVersion(java8Version)))
|
||||
targetJavaVersion = GameJavaVersion.JAVA_8;
|
||||
else
|
||||
targetJavaVersion = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (targetJavaVersion == null) {
|
||||
Controllers.confirm(i18n("launch.failed.no_accepted_java"), i18n("message.warning"), MessageType.WARNING, continueAction, () -> {
|
||||
future.completeExceptionally(new CancellationException("No accepted java"));
|
||||
CompletableFuture<JavaRuntime> future = new CompletableFuture<>();
|
||||
Task<JavaRuntime> 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 {
|
||||
downloadJava(gameVersion.toString(), targetJavaVersion, profile)
|
||||
.thenAcceptAsync(downloadedJavaVersion -> {
|
||||
future.complete(downloadedJavaVersion);
|
||||
})
|
||||
.exceptionally(throwable -> {
|
||||
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 (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);
|
||||
Controllers.confirm(i18n("launch.failed.no_accepted_java"), i18n("message.warning"), MessageType.WARNING, continueAction, () -> {
|
||||
future.completeExceptionally(new CancellationException("No accepted java"));
|
||||
});
|
||||
return null;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
Controllers.dialog(i18n("launch.wrong_javadir"), i18n("message.warning"), MessageType.WARNING, continueAction);
|
||||
breakAction.run();
|
||||
}
|
||||
}, Schedulers.javafx()));
|
||||
return result;
|
||||
}
|
||||
|
||||
setting.setJava(null);
|
||||
setting.setDefaultJavaPath(null);
|
||||
setting.setJavaVersion(JavaVersion.fromCurrentEnvironment());
|
||||
}
|
||||
if (violatedMandatoryConstraints.contains(JavaVersionConstraint.VANILLA_LINUX_JAVA_8)) {
|
||||
if (setting.getNativesDirType() == NativesDirectoryType.VERSION_FOLDER) {
|
||||
FXUtils.runInFX(() -> Controllers.dialog(i18n("launch.advice.vanilla_linux_java_8"), i18n("message.error"), MessageType.ERROR, breakAction));
|
||||
return result;
|
||||
} else {
|
||||
violatedMandatoryConstraints.remove(JavaVersionConstraint.VANILLA_LINUX_JAVA_8);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
JavaVersionConstraint violatedMandatoryConstraint = null;
|
||||
List<JavaVersionConstraint> 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);
|
||||
if (!violatedMandatoryConstraints.isEmpty()) {
|
||||
FXUtils.runInFX(() -> Controllers.dialog(
|
||||
i18n("launch.advice.unknown") + "\n" + violatedMandatoryConstraints,
|
||||
i18n("message.error"),
|
||||
MessageType.ERROR,
|
||||
breakAction
|
||||
));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
CompletableFuture<JavaVersion> future = new CompletableFuture<>();
|
||||
Runnable breakAction = () -> future.completeExceptionally(new CancellationException("Launch operation was cancelled by user"));
|
||||
List<String> suggestions = new ArrayList<>();
|
||||
|
||||
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 (setting.getNativesDirType() == NativesDirectoryType.VERSION_FOLDER) {
|
||||
Controllers.dialog(i18n("launch.advice.vanilla_linux_java_8"), i18n("message.error"), MessageType.ERROR, breakAction);
|
||||
return Task.fromCompletableFuture(future);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
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 (Architecture.SYSTEM_ARCH == Architecture.X86_64 && java.getPlatform().getArchitecture() == Architecture.X86) {
|
||||
suggestions.add(i18n("launch.advice.different_platform"));
|
||||
}
|
||||
}
|
||||
|
||||
List<String> suggestions = new ArrayList<>(0);
|
||||
// 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 (Architecture.SYSTEM_ARCH == Architecture.X86_64 && javaVersion.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"));
|
||||
}
|
||||
|
||||
if (violatedSuggestedConstraints != null) {
|
||||
for (JavaVersionConstraint violatedSuggestedConstraint : violatedSuggestedConstraints) {
|
||||
switch (violatedSuggestedConstraint) {
|
||||
case MODDED_JAVA_7:
|
||||
@@ -510,7 +506,7 @@ public final class LauncherHelper {
|
||||
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 @@ public final class LauncherHelper {
|
||||
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));
|
||||
}
|
||||
|
||||
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"));
|
||||
}
|
||||
|
||||
// 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"));
|
||||
}
|
||||
|
||||
if (suggestions.isEmpty()) {
|
||||
if (!future.isDone()) {
|
||||
future.complete(javaVersion);
|
||||
// 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));
|
||||
}
|
||||
} else {
|
||||
String message;
|
||||
if (suggestions.size() == 1) {
|
||||
message = i18n("launch.advice", suggestions.get(0));
|
||||
|
||||
VersionNumber forgeVersion = analyzer.getVersion(LibraryAnalyzer.LibraryType.FORGE)
|
||||
.map(VersionNumber::asVersion)
|
||||
.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"));
|
||||
}
|
||||
|
||||
// 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"));
|
||||
}
|
||||
|
||||
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<JavaVersion> downloadJava(String gameVersion, GameJavaVersion javaVersion, Profile profile) {
|
||||
CompletableFuture<JavaVersion> 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<JavaRuntime> downloadJava(GameJavaVersion javaVersion, Profile profile) {
|
||||
CompletableFuture<JavaRuntime> 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<JavaVersion> downloadJavaImpl(GameJavaVersion javaVersion, DownloadProvider downloadProvider) {
|
||||
CompletableFuture<JavaVersion> 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<AuthInfo> logIn(Account account) {
|
||||
return Task.composeAsync(() -> {
|
||||
try {
|
||||
@@ -685,7 +655,7 @@ public final class LauncherHelper {
|
||||
private void checkExit() {
|
||||
switch (launcherVisibility) {
|
||||
case HIDE_AND_REOPEN:
|
||||
Platform.runLater(() -> {
|
||||
runLater(() -> {
|
||||
Optional.ofNullable(Controllers.getStage())
|
||||
.ifPresent(Stage::show);
|
||||
});
|
||||
@@ -696,9 +666,9 @@ public final class LauncherHelper {
|
||||
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 @@ public final class LauncherHelper {
|
||||
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<String> logs;
|
||||
private final ArrayDeque</*Log4jLevel*/Object> levels;
|
||||
private final CountDownLatch logWindowLatch = new CountDownLatch(1);
|
||||
private final CircularArrayList<Log> logs;
|
||||
private final CountDownLatch launchingLatch;
|
||||
private final String forbiddenAccessToken;
|
||||
private Thread submitLogThread;
|
||||
private LinkedBlockingQueue<Log> logBuffer;
|
||||
|
||||
public HMCLProcessListener(HMCLGameRepository repository, Version version, AuthInfo authInfo, LaunchOptions launchOptions, CountDownLatch launchingLatch, boolean detectWindow) {
|
||||
this.repository = repository;
|
||||
@@ -733,10 +703,7 @@ public final class LauncherHelper {
|
||||
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 final class LauncherHelper {
|
||||
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<Log> 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 @@ public final class LauncherHelper {
|
||||
// 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 @@ public final class LauncherHelper {
|
||||
|
||||
@Override
|
||||
public void onLog(String log, boolean isErrorStream) {
|
||||
String filteredLog = forbiddenAccessToken == null ? log : log.replace(forbiddenAccessToken, "<access token>");
|
||||
|
||||
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, "<access token>");
|
||||
|
||||
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 final class LauncherHelper {
|
||||
@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 final class LauncherHelper {
|
||||
return;
|
||||
|
||||
// Game crashed before opening the game window.
|
||||
if (!lwjgl) finishLaunch();
|
||||
if (!lwjgl) {
|
||||
synchronized (this) {
|
||||
if (!lwjgl)
|
||||
finishLaunch();
|
||||
}
|
||||
}
|
||||
|
||||
if (exitType != ExitType.NORMAL) {
|
||||
ArrayList<Pair<String, Log4jLevel>> 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();
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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 final class ModpackHelper {
|
||||
throw new FileNotFoundException(file.getPath());
|
||||
else
|
||||
try {
|
||||
return JsonUtils.GSON.fromJson(FileUtils.readText(file), new TypeToken<ModpackConfiguration<?>>() {
|
||||
}.getType());
|
||||
return JsonUtils.GSON.fromJson(FileUtils.readText(file), ModpackConfiguration.class);
|
||||
} catch (JsonParseException e) {
|
||||
throw new IOException("Malformed modpack configuration");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.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<JavaRuntime> getAllJava(Platform platform) {
|
||||
Path root = getPlatformRoot(platform);
|
||||
if (!Files.isDirectory(root))
|
||||
return Collections.emptyList();
|
||||
|
||||
ArrayList<JavaRuntime> list = new ArrayList<>();
|
||||
try (DirectoryStream<Path> stream = Files.newDirectoryStream(root)) {
|
||||
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<JavaRuntime> 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<String, Object> update = new LinkedHashMap<>();
|
||||
update.put("provider", "mojang");
|
||||
update.put("component", gameJavaVersion.getComponent());
|
||||
|
||||
Map<String, JavaLocalFiles.Local> 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<JavaRuntime> getInstallJavaTask(Platform platform, String name, Map<String, Object> 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<Void> getUninstallJavaTask(Platform platform, String name) {
|
||||
return Task.runAsync(() -> {
|
||||
Files.deleteIfExists(getManifestFile(platform, name));
|
||||
FileUtils.deleteDirectory(getJavaDir(platform, name).toFile());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<Void> 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());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.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<JavaManifest> {
|
||||
|
||||
private final Path targetDir;
|
||||
private final Map<String, Object> update;
|
||||
private final Path archiveFile;
|
||||
|
||||
private final Map<String, JavaLocalFiles.Local> files = new LinkedHashMap<>();
|
||||
private final ArrayList<String> 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<String, Object> 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 <F, E extends ArchiveEntry> void copyDirContent(ArchiveFileTree<F, E> tree, Path targetDir) throws IOException {
|
||||
copyDirContent(tree, tree.getRoot().getSubDirs().values().iterator().next(), targetDir);
|
||||
}
|
||||
|
||||
private <F, E extends ArchiveEntry> void copyDirContent(ArchiveFileTree<F, E> tree, ArchiveFileTree.Dir<E> dir, Path targetDir) throws IOException {
|
||||
Files.createDirectories(targetDir);
|
||||
|
||||
for (Map.Entry<String, E> 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<String, ArchiveFileTree.Dir<E>> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.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<Local>, JsonDeserializer<Local> {
|
||||
|
||||
@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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,698 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.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<Path, JavaRuntime> allJava;
|
||||
private static final CountDownLatch LATCH = new CountDownLatch(1);
|
||||
|
||||
private static final ObjectProperty<Collection<JavaRuntime>> allJavaProperty = new SimpleObjectProperty<>();
|
||||
|
||||
private static Map<Path, JavaRuntime> getAllJavaMap() throws InterruptedException {
|
||||
Map<Path, JavaRuntime> map = allJava;
|
||||
if (map == null) {
|
||||
LATCH.await();
|
||||
map = allJava;
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private static void updateAllJavaProperty(Map<Path, JavaRuntime> 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<JavaRuntime> getAllJava() throws InterruptedException {
|
||||
return getAllJavaMap().values();
|
||||
}
|
||||
|
||||
public static ObjectProperty<Collection<JavaRuntime>> 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<JavaRuntime> 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<JavaRuntime> getDownloadJavaTask(DownloadProvider downloadProvider, Platform platform, GameJavaVersion gameJavaVersion) {
|
||||
return REPOSITORY.getDownloadJavaTask(downloadProvider, platform, gameJavaVersion)
|
||||
.thenApplyAsync(Schedulers.javafx(), java -> {
|
||||
addJava(java);
|
||||
return java;
|
||||
});
|
||||
}
|
||||
|
||||
public static Task<JavaRuntime> getInstallJavaTask(Platform platform, String name, Map<String, Object> update, Path archiveFile) {
|
||||
return REPOSITORY.getInstallJavaTask(platform, name, update, archiveFile)
|
||||
.thenApplyAsync(Schedulers.javafx(), java -> {
|
||||
addJava(java);
|
||||
return java;
|
||||
});
|
||||
}
|
||||
|
||||
public static Task<Void> 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<Path, JavaRuntime> oldMap = getAllJavaMap();
|
||||
if (!oldMap.containsKey(java.getBinary())) {
|
||||
HashMap<Path, JavaRuntime> 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<Path, JavaRuntime> oldMap = getAllJavaMap();
|
||||
if (oldMap.containsKey(realPath)) {
|
||||
HashMap<Path, JavaRuntime> 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<JavaRuntime> 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<Path, JavaRuntime> allJava = searchPotentialJavaExecutables();
|
||||
JavaManager.allJava = allJava;
|
||||
LATCH.countDown();
|
||||
FXUtils.runInFX(() -> updateAllJavaProperty(allJava));
|
||||
}
|
||||
|
||||
// search java
|
||||
|
||||
private static Map<Path, JavaRuntime> searchPotentialJavaExecutables() {
|
||||
Map<Path, JavaRuntime> 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<Path> 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<Path, JavaRuntime> 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<Path, JavaRuntime> 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<Path, JavaRuntime> 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<Path, JavaRuntime> javaRuntimes, Platform platform) {
|
||||
for (JavaRuntime java : REPOSITORY.getAllJava(platform)) {
|
||||
javaRuntimes.put(java.getBinary(), java);
|
||||
}
|
||||
}
|
||||
|
||||
private static void searchAllOfficialJava(Map<Path, JavaRuntime> 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<Path, JavaRuntime> javaRuntimes, Path directory, String platform, boolean verify) {
|
||||
try (DirectoryStream<Path> 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<Path, JavaRuntime> javaRuntimes, Path directory) {
|
||||
if (!Files.isDirectory(directory)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try (DirectoryStream<Path> 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<Path, JavaRuntime> 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<Path, JavaRuntime> 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<String> querySubFolders(String location) {
|
||||
List<String> res = new ArrayList<>();
|
||||
|
||||
try {
|
||||
Process process = Runtime.getRuntime().exec(new String[]{"cmd", "/c", "reg", "query", location});
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), OperatingSystem.NATIVE_CHARSET))) {
|
||||
for (String line; (line = reader.readLine()) != null; ) {
|
||||
if (line.startsWith(location) && !line.equals(location)) {
|
||||
res.add(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to query sub folders of " + location, e);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private static String queryRegisterValue(String location, String name) {
|
||||
boolean last = false;
|
||||
|
||||
try {
|
||||
Process process = Runtime.getRuntime().exec(new String[]{"cmd", "/c", "reg", "query", location, "/v", name});
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), OperatingSystem.NATIVE_CHARSET))) {
|
||||
for (String line; (line = reader.readLine()) != null; ) {
|
||||
if (StringUtils.isNotBlank(line)) {
|
||||
if (last && line.trim().startsWith(name)) {
|
||||
int begins = line.indexOf(name);
|
||||
if (begins > 0) {
|
||||
String s2 = line.substring(begins + name.length());
|
||||
begins = s2.indexOf("REG_SZ");
|
||||
if (begins > 0) {
|
||||
return s2.substring(begins + "REG_SZ".length()).trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (location.equals(line.trim())) {
|
||||
last = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to query register value of " + location, e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.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<String, Object> update;
|
||||
|
||||
@Nullable
|
||||
private final Map<String, JavaLocalFiles.Local> files;
|
||||
|
||||
public JavaManifest(JavaInfo info, @Nullable Map<String, Object> update, @Nullable Map<String, JavaLocalFiles.Local> files) {
|
||||
this.info = info;
|
||||
this.update = update;
|
||||
this.files = files;
|
||||
}
|
||||
|
||||
public JavaInfo getInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
public Map<String, Object> getUpdate() {
|
||||
return update;
|
||||
}
|
||||
|
||||
public Map<String, JavaLocalFiles.Local> getFiles() {
|
||||
return files;
|
||||
}
|
||||
|
||||
public static final class Serializer implements JsonSerializer<JavaManifest>, JsonDeserializer<JavaManifest> {
|
||||
|
||||
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<String, Object> update = jsonObject.has("update") ? context.deserialize(jsonObject.get("update"), Map.class) : null;
|
||||
Map<String, JavaLocalFiles.Local> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
@@ -55,6 +54,8 @@ import static org.jackhuang.hmcl.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.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;
|
||||
import static org.jackhuang.hmcl.util.gson.JsonUtils.mapTypeOf;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
@@ -171,14 +172,11 @@ public final class Accounts {
|
||||
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<Map<Object, Object>>)
|
||||
Config.CONFIG_GSON.fromJson(reader, new TypeToken<List<Map<Object, Object>>>() {
|
||||
}.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);
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ public final class Config implements Cloneable, Observable {
|
||||
private StringProperty launcherFontFamily = new SimpleStringProperty();
|
||||
|
||||
@SerializedName("logLines")
|
||||
private IntegerProperty logLines = new SimpleIntegerProperty(1000);
|
||||
private ObjectProperty<Integer> logLines = new SimpleObjectProperty<>();
|
||||
|
||||
@SerializedName("titleTransparent")
|
||||
private BooleanProperty titleTransparent = new SimpleBooleanProperty(false);
|
||||
@@ -573,15 +573,15 @@ public final class Config implements Cloneable, Observable {
|
||||
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<Integer> logLinesProperty() {
|
||||
return logLines;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ import javafx.beans.InvalidationListener;
|
||||
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 final class GlobalConfig implements Cloneable, Observable {
|
||||
|
||||
private final IntegerProperty logRetention = new SimpleIntegerProperty();
|
||||
|
||||
private final ObservableSet<String> userJava = FXCollections.observableSet(new LinkedHashSet<>());
|
||||
|
||||
private final ObservableSet<String> disabledJava = FXCollections.observableSet(new LinkedHashSet<>());
|
||||
|
||||
private final Map<String, Object> unknownFields = new HashMap<>();
|
||||
|
||||
private final transient ObservableHelper helper = new ObservableHelper(this);
|
||||
@@ -114,11 +120,21 @@ public final class GlobalConfig implements Cloneable, Observable {
|
||||
this.logRetention.set(logRetention);
|
||||
}
|
||||
|
||||
public ObservableSet<String> getUserJava() {
|
||||
return userJava;
|
||||
}
|
||||
|
||||
public ObservableSet<String> getDisabledJava() {
|
||||
return disabledJava;
|
||||
}
|
||||
|
||||
public static final class Serializer implements JsonSerializer<GlobalConfig>, JsonDeserializer<GlobalConfig> {
|
||||
private static final Set<String> knownFields = new HashSet<>(Arrays.asList(
|
||||
"agreementVersion",
|
||||
"platformPromptVersion",
|
||||
"logRetention"
|
||||
"logRetention",
|
||||
"userJava",
|
||||
"disabledJava"
|
||||
));
|
||||
|
||||
@Override
|
||||
@@ -131,6 +147,12 @@ public final class GlobalConfig implements Cloneable, Observable {
|
||||
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<String, Object> entry : src.unknownFields.entrySet()) {
|
||||
jsonObject.add(entry.getKey(), context.serialize(entry.getValue()));
|
||||
}
|
||||
@@ -149,6 +171,20 @@ public final class GlobalConfig implements Cloneable, Observable {
|
||||
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<String, JsonElement> entry : obj.entrySet()) {
|
||||
if (!knownFields.contains(entry.getKey())) {
|
||||
config.unknownFields.put(entry.getKey(), context.deserialize(entry.getValue(), Object.class));
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public enum JavaVersionType {
|
||||
DEFAULT, AUTO, VERSION, DETECTED, CUSTOM
|
||||
}
|
||||
@@ -22,29 +22,23 @@ import com.google.gson.annotations.JsonAdapter;
|
||||
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 final class VersionSetting implements Cloneable {
|
||||
|
||||
// java
|
||||
|
||||
private final StringProperty javaProperty = new SimpleStringProperty(this, "java", "");
|
||||
private final ObjectProperty<JavaVersionType> javaVersionTypeProperty = new SimpleObjectProperty<>(this, "javaVersionType", JavaVersionType.AUTO);
|
||||
|
||||
public StringProperty javaProperty() {
|
||||
return javaProperty;
|
||||
public ObjectProperty<JavaVersionType> 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 String getJavaVersion() {
|
||||
return javaVersionProperty.get();
|
||||
}
|
||||
|
||||
public void setJavaVersion(String java) {
|
||||
javaVersionProperty.set(java);
|
||||
}
|
||||
|
||||
public void setUsesCustomJavaDir() {
|
||||
setJava("Custom");
|
||||
setJavaVersionType(JavaVersionType.CUSTOM);
|
||||
setJavaVersion("");
|
||||
setDefaultJavaPath(null);
|
||||
}
|
||||
|
||||
public boolean isJavaAutoSelected() {
|
||||
return "Auto".equals(getJava());
|
||||
}
|
||||
|
||||
public void setJavaAutoSelected() {
|
||||
setJava("Auto");
|
||||
setJavaVersionType(JavaVersionType.AUTO);
|
||||
setJavaVersion("");
|
||||
setDefaultJavaPath(null);
|
||||
}
|
||||
|
||||
@@ -644,58 +643,74 @@ public final class VersionSetting implements Cloneable {
|
||||
launcherVisibilityProperty.set(launcherVisibility);
|
||||
}
|
||||
|
||||
public Task<JavaVersion> getJavaVersion(GameVersionNumber gameVersion, Version version) {
|
||||
return getJavaVersion(gameVersion, version, true);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
public Task<JavaVersion> getJavaVersion(GameVersionNumber gameVersion, Version version, boolean checkJava) {
|
||||
return Task.runAsync(Schedulers.javafx(), () -> {
|
||||
if (StringUtils.isBlank(getJava())) {
|
||||
setJava(StringUtils.isBlank(getJavaDir()) ? "Auto" : "Custom");
|
||||
}
|
||||
}).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,
|
||||
}
|
||||
} else if (StringUtils.isNotBlank(getJava())) {
|
||||
List<JavaVersion> 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));
|
||||
}
|
||||
} else throw new Error();
|
||||
} catch (InterruptedException e) {
|
||||
throw new CancellationException();
|
||||
}
|
||||
});
|
||||
}
|
||||
int majorVersion = -1;
|
||||
try {
|
||||
majorVersion = Integer.parseInt(javaVersion);
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
|
||||
public void setJavaVersion(JavaVersion java) {
|
||||
setJava(java.getVersion());
|
||||
setDefaultJavaPath(java.getBinary().toString());
|
||||
if (majorVersion < 0) {
|
||||
LOG.warning("Invalid Java version: " + javaVersion);
|
||||
return null;
|
||||
}
|
||||
|
||||
final int finalMajorVersion = majorVersion;
|
||||
Collection<JavaRuntime> allJava = JavaManager.getAllJava().stream()
|
||||
.filter(it -> it.getParsedVersion() == finalMajorVersion)
|
||||
.collect(Collectors.toList());
|
||||
return JavaManager.findSuitableJava(allJava, gameVersion, version);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
} catch (IOException | InvalidPathException ignored) {
|
||||
}
|
||||
|
||||
for (JavaRuntime java : JavaManager.getAllJava()) {
|
||||
if (java.getVersion().equals(javaVersion)) {
|
||||
return java;
|
||||
}
|
||||
}
|
||||
|
||||
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 final class VersionSetting implements Cloneable {
|
||||
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 final class VersionSetting implements Cloneable {
|
||||
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 final class VersionSetting implements Cloneable {
|
||||
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 final class VersionSetting implements Cloneable {
|
||||
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 final class VersionSetting implements Cloneable {
|
||||
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 @@ public final class VersionSetting implements Cloneable {
|
||||
else
|
||||
return Lang.parseInt(primitive.getAsString(), defaultValue);
|
||||
}
|
||||
|
||||
private <E extends Enum<E>> E parseJsonPrimitive(JsonPrimitive primitive, Class<E> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ import javafx.stage.StageStyle;
|
||||
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.ui.versions.VersionPage;
|
||||
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 final class Controllers {
|
||||
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);
|
||||
|
||||
@@ -31,6 +31,7 @@ import javafx.beans.value.WritableValue;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
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 +41,7 @@ import javafx.scene.layout.ColumnConstraints;
|
||||
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 +52,12 @@ import javafx.util.StringConverter;
|
||||
import org.glavo.png.PNGType;
|
||||
import org.glavo.png.PNGWriter;
|
||||
import org.glavo.png.javafx.PNGJavaFXUtils;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.JFXHyperlink;
|
||||
import org.jackhuang.hmcl.util.Holder;
|
||||
import org.jackhuang.hmcl.util.ResourceNotFoundError;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.ExtendedProperties;
|
||||
import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
|
||||
@@ -67,12 +69,9 @@ import org.w3c.dom.NodeList;
|
||||
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;
|
||||
@@ -81,6 +80,7 @@ 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;
|
||||
@@ -408,6 +408,22 @@ public final class FXUtils {
|
||||
});
|
||||
}
|
||||
|
||||
private static String which(String command) {
|
||||
String path = System.getenv("PATH");
|
||||
if (path == null)
|
||||
return null;
|
||||
|
||||
for (String item : path.split(OperatingSystem.PATH_SEPARATOR)) {
|
||||
try {
|
||||
Path program = Paths.get(item, command);
|
||||
if (Files.isExecutable(program))
|
||||
return program.toRealPath().toString();
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void showFileInExplorer(Path file) {
|
||||
String path = file.toAbsolutePath().toString();
|
||||
|
||||
@@ -416,6 +432,16 @@ public final class FXUtils {
|
||||
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 +498,13 @@ public final class FXUtils {
|
||||
}
|
||||
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 +523,6 @@ public final class FXUtils {
|
||||
});
|
||||
}
|
||||
|
||||
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 <T> void bind(JFXTextField textField, Property<T> property, StringConverter<T> converter) {
|
||||
textField.setText(converter == null ? (String) property.getValue() : converter.toString(property.getValue()));
|
||||
TextFieldBindingListener<T> listener = new TextFieldBindingListener<>(textField, property, converter);
|
||||
@@ -815,6 +796,17 @@ public final class FXUtils {
|
||||
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<List<File>> callback) {
|
||||
applyDragListener(node, filter, callback, null);
|
||||
}
|
||||
@@ -977,4 +969,11 @@ public final class FXUtils {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -20,8 +20,6 @@ package org.jackhuang.hmcl.ui;
|
||||
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.setting.Theme;
|
||||
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;
|
||||
@@ -77,8 +76,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 +86,9 @@ public class GameCrashWindow extends Stage {
|
||||
private final LaunchOptions launchOptions;
|
||||
private final View view;
|
||||
|
||||
private final Collection<Pair<String, Log4jLevel>> logs;
|
||||
private final List<Log> logs;
|
||||
|
||||
public GameCrashWindow(ManagedProcess managedProcess, ProcessListener.ExitType exitType, DefaultGameRepository repository, Version version, LaunchOptions launchOptions, Collection<Pair<String, Log4jLevel>> logs) {
|
||||
public GameCrashWindow(ManagedProcess managedProcess, ProcessListener.ExitType exitType, DefaultGameRepository repository, Version version, LaunchOptions launchOptions, List<Log> logs) {
|
||||
this.managedProcess = managedProcess;
|
||||
this.exitType = exitType;
|
||||
this.repository = repository;
|
||||
@@ -124,7 +121,7 @@ public class GameCrashWindow extends Stage {
|
||||
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 +261,18 @@ public class GameCrashWindow extends Stage {
|
||||
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<String, Log4jLevel> 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) -> {
|
||||
@@ -358,12 +353,12 @@ public class GameCrashWindow extends Stage {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
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<javafx.scene.Node> children = new ArrayList<>();
|
||||
private final List<Node> 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<URI> onClickHyperlink;
|
||||
|
||||
public HTMLRenderer(Consumer<URI> 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) {
|
||||
text.setOnMouseClicked(event -> 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) {
|
||||
imageView.setOnMouseClicked(event -> 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;
|
||||
}
|
||||
}
|
||||
@@ -25,8 +25,8 @@ import javafx.application.Platform;
|
||||
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;
|
||||
@@ -38,13 +38,14 @@ 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 +55,11 @@ import java.nio.file.Paths;
|
||||
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 +67,120 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
*/
|
||||
public final class LogWindow extends Stage {
|
||||
|
||||
private final ArrayDeque<Log> logs = new ArrayDeque<>();
|
||||
private final Map<Log4jLevel, SimpleIntegerProperty> levelCountMap = new EnumMap<Log4jLevel, SimpleIntegerProperty>(Log4jLevel.class) {
|
||||
{
|
||||
for (Log4jLevel level : Log4jLevel.values()) put(level, new SimpleIntegerProperty());
|
||||
private static final Log4jLevel[] LEVELS = {Log4jLevel.FATAL, Log4jLevel.ERROR, Log4jLevel.WARN, Log4jLevel.INFO, Log4jLevel.DEBUG};
|
||||
|
||||
private final CircularArrayList<Log> logs;
|
||||
private final Map<Log4jLevel, SimpleIntegerProperty> levelCountMap = new EnumMap<>(Log4jLevel.class);
|
||||
private final Map<Log4jLevel, SimpleBooleanProperty> levelShownMap = new EnumMap<>(Log4jLevel.class);
|
||||
|
||||
{
|
||||
for (Log4jLevel level : Log4jLevel.values()) {
|
||||
levelCountMap.put(level, new SimpleIntegerProperty());
|
||||
levelShownMap.put(level, new SimpleBooleanProperty(true));
|
||||
}
|
||||
};
|
||||
private final Map<Log4jLevel, SimpleBooleanProperty> levelShownMap = new EnumMap<Log4jLevel, SimpleBooleanProperty>(Log4jLevel.class) {
|
||||
{
|
||||
for (Log4jLevel level : Log4jLevel.values()) {
|
||||
SimpleBooleanProperty property = new SimpleBooleanProperty(true);
|
||||
put(level, property);
|
||||
}
|
||||
}
|
||||
};
|
||||
private final LogWindowImpl impl = new LogWindowImpl();
|
||||
private final ChangeListener<Number> logLinesListener = FXUtils.onWeakChange(config().logLinesProperty(), logLines -> checkLogCount());
|
||||
|
||||
private Consumer<String> exportGameCrashInfoCallback;
|
||||
|
||||
private boolean stopCheckLogCount = false;
|
||||
}
|
||||
|
||||
private final LogWindowImpl impl;
|
||||
private final ManagedProcess gameProcess;
|
||||
|
||||
public LogWindow(ManagedProcess gameProcess) {
|
||||
this(gameProcess, new CircularArrayList<>());
|
||||
}
|
||||
|
||||
public LogWindow(ManagedProcess gameProcess, CircularArrayList<Log> 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<String> 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;
|
||||
}
|
||||
public void logLines(List<Log> logs) {
|
||||
for (Log log : logs) {
|
||||
Log4jLevel level = log.getLevel();
|
||||
this.logs.add(log);
|
||||
if (levelShownMap.get(level).get())
|
||||
impl.listView.getItems().add(log);
|
||||
|
||||
SimpleIntegerProperty property = levelCountMap.get(log.getLevel());
|
||||
property.set(property.get() + 1);
|
||||
}
|
||||
show();
|
||||
}
|
||||
|
||||
public void showNormal() {
|
||||
this.impl.showCrashReport.set(false);
|
||||
show();
|
||||
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<Log> 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++;
|
||||
}
|
||||
|
||||
items.remove(0, count);
|
||||
}
|
||||
|
||||
private static class Log {
|
||||
private final String log;
|
||||
private final Log4jLevel level;
|
||||
private boolean selected = false;
|
||||
|
||||
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<Log> listView = new JFXListView<>();
|
||||
private final BooleanProperty autoScroll = new SimpleBooleanProperty();
|
||||
private final List<StringProperty> buttonText = IntStream.range(0, 5).mapToObj(x -> new SimpleStringProperty()).collect(Collectors.toList());
|
||||
private final List<BooleanProperty> showLevel = IntStream.range(0, 5).mapToObj(x -> new SimpleBooleanProperty(true)).collect(Collectors.toList());
|
||||
private final JFXComboBox<String> 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<Integer> 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 +197,7 @@ public final class LogWindow extends Stage {
|
||||
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 +213,31 @@ public final class LogWindow extends Stage {
|
||||
});
|
||||
}
|
||||
|
||||
private void onExportDump(JFXButton button) {
|
||||
private void onExportDump(SpinnerPane pane) {
|
||||
assert SystemUtils.supportJVMAttachment();
|
||||
|
||||
pane.setLoading(true);
|
||||
|
||||
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")));
|
||||
Path dumpFile = Paths.get("minecraft-exported-jstack-dump-" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH-mm-ss")) + ".log").toAbsolutePath();
|
||||
|
||||
Path dumpFile = Paths.get("minecraft-exported-jstack-dump-" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH-mm-ss")) + ".log").toAbsolutePath();
|
||||
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
private void onExportGameCrashInfo() {
|
||||
if (exportGameCrashInfoCallback == null) return;
|
||||
exportGameCrashInfoCallback.accept(logs.stream().map(x -> x.log).collect(Collectors.joining(OperatingSystem.LINE_SEPARATOR)));
|
||||
Platform.runLater(() -> {
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR, i18n("logwindow.export_dump"));
|
||||
alert.setTitle(i18n("message.error"));
|
||||
alert.showAndWait();
|
||||
});
|
||||
}
|
||||
|
||||
Platform.runLater(() -> pane.setLoading(false));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -263,7 +246,7 @@ public final class LogWindow extends Stage {
|
||||
}
|
||||
}
|
||||
|
||||
private static class LogWindowSkin extends SkinBase<LogWindowImpl> {
|
||||
private static final class LogWindowSkin extends SkinBase<LogWindowImpl> {
|
||||
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 +258,7 @@ public final class LogWindow extends Stage {
|
||||
|
||||
private final Set<ListCell<Log>> 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 +283,16 @@ public final class LogWindow extends Stage {
|
||||
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -347,11 +323,11 @@ public final class LogWindow extends Stage {
|
||||
|
||||
setOnMouseClicked(event -> {
|
||||
if (!event.isControlDown()) {
|
||||
for (ListCell<Log> logListCell: selected) {
|
||||
for (ListCell<Log> logListCell : selected) {
|
||||
if (logListCell != this) {
|
||||
logListCell.pseudoClassStateChanged(SELECTED, false);
|
||||
if (logListCell.getItem() != null) {
|
||||
logListCell.getItem().selected = false;
|
||||
logListCell.getItem().setSelected(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -362,7 +338,7 @@ public final class LogWindow extends Stage {
|
||||
selected.add(this);
|
||||
pseudoClassStateChanged(SELECTED, true);
|
||||
if (getItem() != null) {
|
||||
getItem().selected = true;
|
||||
getItem().setSelected(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -377,18 +353,18 @@ public final class LogWindow extends Stage {
|
||||
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 +374,9 @@ public final class LogWindow extends Stage {
|
||||
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 +392,6 @@ public final class LogWindow extends Stage {
|
||||
{
|
||||
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 +402,24 @@ public final class LogWindow extends Stage {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -19,58 +19,73 @@ package org.jackhuang.hmcl.ui;
|
||||
|
||||
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#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);
|
||||
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");
|
||||
|
||||
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();
|
||||
|
||||
{
|
||||
JFXButton updateButton = new JFXButton(i18n("update.accept"));
|
||||
updateButton.getStyleClass().add("dialog-accept");
|
||||
updateButton.setOnMouseClicked(e -> updateRunnable.run());
|
||||
JFXHyperlink openInBrowser = new JFXHyperlink(i18n("web.view_in_browser"));
|
||||
openInBrowser.setExternalLink(url);
|
||||
|
||||
JFXButton cancelButton = new JFXButton(i18n("button.cancel"));
|
||||
cancelButton.getStyleClass().add("dialog-cancel");
|
||||
cancelButton.setOnMouseClicked(e -> fireEvent(new DialogCloseEvent()));
|
||||
JFXButton updateButton = new JFXButton(i18n("update.accept"));
|
||||
updateButton.getStyleClass().add("dialog-accept");
|
||||
updateButton.setOnAction(e -> updateRunnable.run());
|
||||
|
||||
setActions(updateButton, cancelButton);
|
||||
onEscPressed(this, cancelButton::fire);
|
||||
}
|
||||
JFXButton cancelButton = new JFXButton(i18n("button.cancel"));
|
||||
cancelButton.getStyleClass().add("dialog-cancel");
|
||||
cancelButton.setOnAction(e -> fireEvent(new DialogCloseEvent()));
|
||||
|
||||
setActions(openInBrowser, updateButton, cancelButton);
|
||||
onEscPressed(this, cancelButton::fire);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
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<DecoratorPage.State> 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<DecoratorPage.State> stateProperty() {
|
||||
return stateProperty;
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -177,7 +177,7 @@ public class AccountListItemSkin extends SkinBase<AccountListItem> {
|
||||
right.getChildren().add(spinnerCopyUUID);
|
||||
|
||||
JFXButton btnRemove = new JFXButton();
|
||||
btnRemove.setOnMouseClicked(e -> skinnable.remove());
|
||||
btnRemove.setOnMouseClicked(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));
|
||||
|
||||
@@ -123,7 +123,9 @@ public class AccountListPage extends DecoratorAnimatedPage implements DecoratorP
|
||||
|
||||
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");
|
||||
|
||||
@@ -166,6 +166,16 @@ public final class MessageDialogPane extends HBox {
|
||||
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");
|
||||
|
||||
@@ -57,7 +57,7 @@ public final class MultiFileItem<T> extends VBox {
|
||||
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<Toggle> selecting = group.getToggles().stream()
|
||||
@@ -183,6 +183,10 @@ public final class MultiFileItem<T> extends VBox {
|
||||
super(title, data);
|
||||
}
|
||||
|
||||
public JFXTextField getCustomField() {
|
||||
return customField;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return customField.getText();
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ import org.jackhuang.hmcl.download.forge.ForgeNewInstallTask;
|
||||
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.optifine.OptiFineInstallTask;
|
||||
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 final class TaskListPane extends StackPane {
|
||||
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(() -> {
|
||||
|
||||
@@ -52,6 +52,8 @@ public class InstallersPage extends Control implements WizardPage {
|
||||
protected JFXTextField txtName = new JFXTextField();
|
||||
protected BooleanProperty installable = new SimpleBooleanProperty();
|
||||
|
||||
private boolean isNameModifiedByUser = false;
|
||||
|
||||
public InstallersPage(WizardController controller, HMCLGameRepository repository, String gameVersion, DownloadProvider downloadProvider) {
|
||||
this.controller = controller;
|
||||
this.group = new InstallerItem.InstallerItemGroup(gameVersion, getInstallerItemStyle());
|
||||
@@ -61,7 +63,8 @@ public class InstallersPage extends Control implements WizardPage {
|
||||
new Validator(i18n("install.new_game.already_exists"), str -> !repository.versionIdConflicts(str)),
|
||||
new Validator(i18n("install.new_game.malformed"), HMCLGameRepository::isValidVersionId));
|
||||
installable.bind(createBooleanBinding(txtName::validate, txtName.textProperty()));
|
||||
txtName.setText(gameVersion);
|
||||
|
||||
txtName.textProperty().addListener((obs, oldText, newText) -> isNameModifiedByUser = true);
|
||||
|
||||
for (InstallerItem library : group.getLibraries()) {
|
||||
String libraryId = library.getLibraryId();
|
||||
@@ -103,6 +106,9 @@ public class InstallersPage extends Control implements WizardPage {
|
||||
library.versionProperty().set(null);
|
||||
}
|
||||
}
|
||||
if (!isNameModifiedByUser) {
|
||||
setTxtNameWithLoaders();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -124,6 +130,49 @@ public class InstallersPage extends Control implements WizardPage {
|
||||
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<InstallersPage> {
|
||||
|
||||
/**
|
||||
|
||||
@@ -31,6 +31,7 @@ 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.WebPage;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.RequiredValidator;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
@@ -107,7 +108,6 @@ public final class LocalModpackPage extends ModpackPage {
|
||||
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,8 +130,6 @@ public final class LocalModpackPage extends ModpackPage {
|
||||
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());
|
||||
@@ -153,9 +151,8 @@ public final class LocalModpackPage extends ModpackPage {
|
||||
}
|
||||
|
||||
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";
|
||||
|
||||
@@ -22,7 +22,6 @@ 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;
|
||||
|
||||
@@ -37,15 +36,6 @@ public abstract class ModpackPage extends SpinnerPane implements WizardPage {
|
||||
|
||||
ComponentList componentList = new ComponentList();
|
||||
{
|
||||
BorderPane locationPane = new BorderPane();
|
||||
{
|
||||
locationPane.setLeft(new Label(i18n("modpack.task.install.will")));
|
||||
|
||||
lblModpackLocation = new Label();
|
||||
BorderPane.setAlignment(lblModpackLocation, Pos.CENTER_RIGHT);
|
||||
locationPane.setCenter(lblModpackLocation);
|
||||
}
|
||||
|
||||
BorderPane archiveNamePane = new BorderPane();
|
||||
{
|
||||
Label label = new Label(i18n("archive.file.name"));
|
||||
@@ -99,7 +89,7 @@ public abstract class ModpackPage extends SpinnerPane implements WizardPage {
|
||||
}
|
||||
|
||||
componentList.getContent().setAll(
|
||||
locationPane, archiveNamePane, modpackNamePane, versionPane, authorPane, descriptionPane);
|
||||
archiveNamePane, modpackNamePane, versionPane, authorPane, descriptionPane);
|
||||
}
|
||||
|
||||
borderPane.getChildren().setAll(componentList);
|
||||
|
||||
@@ -22,7 +22,7 @@ import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
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;
|
||||
@@ -43,7 +43,6 @@ public final class RemoteModpackPage extends ModpackPage {
|
||||
|
||||
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));
|
||||
@@ -84,7 +83,7 @@ public final class RemoteModpackPage extends ModpackPage {
|
||||
}
|
||||
|
||||
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";
|
||||
|
||||
@@ -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 class AboutPage extends StackPane {
|
||||
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/");
|
||||
ComponentList thanks = loadIconedTwoLineList("/assets/about/thanks.json");
|
||||
|
||||
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 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 class AboutPage extends StackPane {
|
||||
thanks,
|
||||
|
||||
ComponentList.createComponentListTitle(i18n("about.dependency")),
|
||||
dep,
|
||||
deps,
|
||||
|
||||
ComponentList.createComponentListTitle(i18n("about.legal")),
|
||||
legal
|
||||
@@ -216,4 +106,49 @@ public class AboutPage extends StackPane {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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 org.jackhuang.hmcl.util.io.HttpRequest;
|
||||
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 class HelpPage extends SpinnerPane {
|
||||
|
||||
private void loadHelp() {
|
||||
showSpinner();
|
||||
Task.<List<HelpCategory>>supplyAsync(() -> HttpRequest.GET("https://docs.hmcl.net/index.json").getJson(new TypeToken<List<HelpCategory>>() {
|
||||
}.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();
|
||||
|
||||
@@ -0,0 +1,423 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.main;
|
||||
|
||||
import com.jfoenix.controls.*;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.control.Label;
|
||||
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.Pair;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.platform.Platform;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CancellationException;
|
||||
|
||||
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<GameJavaVersion> supportedVersions = GameJavaVersion.getSupportedVersions(platform);
|
||||
|
||||
EnumSet<DiscoJavaDistribution> 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<GameJavaVersion> supportedGameJavaVersions;
|
||||
private final EnumSet<DiscoJavaDistribution> distributions;
|
||||
|
||||
private JavaDownloadDialog(DownloadProvider downloadProvider, Platform platform, List<GameJavaVersion> supportedGameJavaVersions, EnumSet<DiscoJavaDistribution> 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);
|
||||
}
|
||||
|
||||
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<Void> 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 boolean isLTS(int major) {
|
||||
if (major <= 8) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (major < 21) {
|
||||
return major == 11 || major == 17;
|
||||
}
|
||||
|
||||
return major % 4 == 1;
|
||||
}
|
||||
|
||||
private final JFXComboBox<DiscoJavaDistribution> distributionBox;
|
||||
private final JFXComboBox<DiscoJavaRemoteVersion> remoteVersionBox;
|
||||
private final JFXComboBox<JavaPackageType> 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 ObjectProperty<DiscoJavaVersionList> currentDiscoJavaVersionList = new SimpleObjectProperty<>();
|
||||
|
||||
private final Map<Pair<DiscoJavaDistribution, JavaPackageType>, DiscoJavaVersionList> javaVersionLists = new HashMap<>();
|
||||
|
||||
private boolean changingDistribution = false;
|
||||
|
||||
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<>();
|
||||
|
||||
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)));
|
||||
ChangeListener<DiscoJavaVersionList.Status> updateStatusListener = (observable, oldValue, newValue) -> updateStatus(newValue);
|
||||
this.currentDiscoJavaVersionList.addListener((observable, oldValue, newValue) -> {
|
||||
if (oldValue != null) {
|
||||
oldValue.status.removeListener(updateStatusListener);
|
||||
}
|
||||
|
||||
if (newValue != null) {
|
||||
newValue.status.addListener(updateStatusListener);
|
||||
updateStatus(newValue.status.get());
|
||||
} else {
|
||||
updateStatus(null);
|
||||
}
|
||||
});
|
||||
|
||||
packageTypeBox.getSelectionModel().selectedItemProperty().addListener(ignored -> updateVersions());
|
||||
FXUtils.onChangeAndOperate(distributionBox.getSelectionModel().selectedItemProperty(), distribution -> {
|
||||
if (distribution != null) {
|
||||
changingDistribution = true;
|
||||
packageTypeBox.setItems(FXCollections.observableList(new ArrayList<>(distribution.getSupportedPackageTypes())));
|
||||
packageTypeBox.getSelectionModel().select(0);
|
||||
changingDistribution = false;
|
||||
updateVersions();
|
||||
packageTypeBox.setDisable(false);
|
||||
remoteVersionBox.setDisable(false);
|
||||
} else {
|
||||
packageTypeBox.setItems(null);
|
||||
updateVersions();
|
||||
remoteVersionBox.setItems(null);
|
||||
packageTypeBox.setDisable(true);
|
||||
remoteVersionBox.setDisable(true);
|
||||
}
|
||||
});
|
||||
|
||||
setHeading(new Label(i18n("java.download")));
|
||||
setBody(body);
|
||||
setActions(warningLabel, downloadButtonPane, cancelButton);
|
||||
}
|
||||
|
||||
private void updateStatus(DiscoJavaVersionList.Status status) {
|
||||
if (status == DiscoJavaVersionList.Status.LOADING) {
|
||||
downloadButtonPane.getChildren().setAll(new JFXSpinner());
|
||||
remoteVersionBox.setDisable(true);
|
||||
warningLabel.setText(null);
|
||||
} else {
|
||||
downloadButtonPane.getChildren().setAll(downloadButton);
|
||||
if (status == DiscoJavaVersionList.Status.SUCCESS || status == null) {
|
||||
remoteVersionBox.setDisable(false);
|
||||
warningLabel.setText(null);
|
||||
} else if (status == DiscoJavaVersionList.Status.FAILED) {
|
||||
remoteVersionBox.setDisable(true);
|
||||
warningLabel.setText(i18n("java.download.load_list.failed"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onDownload() {
|
||||
fireEvent(new DialogCloseEvent());
|
||||
|
||||
DiscoJavaDistribution distribution = distributionBox.getSelectionModel().getSelectedItem();
|
||||
DiscoJavaRemoteVersion version = remoteVersionBox.getSelectionModel().getSelectedItem();
|
||||
|
||||
if (version == null)
|
||||
return;
|
||||
|
||||
Controllers.taskDialog(new GetTask(downloadProvider.injectURLWithCandidates(version.getLinks().getPkgInfoUri()))
|
||||
.setExecutor(Schedulers.io())
|
||||
.thenComposeAsync(json -> {
|
||||
DiscoResult<DiscoRemoteFileInfo> 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<FileDownloadTask.IntegrityCheck> 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 ->
|
||||
new FileDownloadTask.IntegrityCheck(fileInfo.getChecksumType(), checksum.trim()));
|
||||
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<String, Object> 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;
|
||||
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 void updateVersions() {
|
||||
if (changingDistribution) return;
|
||||
|
||||
DiscoJavaDistribution distribution = distributionBox.getSelectionModel().getSelectedItem();
|
||||
if (distribution == null) {
|
||||
this.currentDiscoJavaVersionList.set(null);
|
||||
return;
|
||||
}
|
||||
|
||||
JavaPackageType packageType = packageTypeBox.getSelectionModel().getSelectedItem();
|
||||
|
||||
DiscoJavaVersionList list = javaVersionLists.computeIfAbsent(Pair.pair(distribution, packageType), pair -> {
|
||||
DiscoJavaVersionList res = new DiscoJavaVersionList();
|
||||
new DiscoFetchJavaListTask(downloadProvider, distribution, platform, packageType).setExecutor(Schedulers.io()).thenApplyAsync(versions -> {
|
||||
if (versions.isEmpty()) return Collections.<DiscoJavaRemoteVersion>emptyList();
|
||||
|
||||
int lastLTS = -1;
|
||||
for (int v : versions.keySet()) {
|
||||
if (isLTS(v)) {
|
||||
lastLTS = v;
|
||||
}
|
||||
}
|
||||
|
||||
ArrayList<DiscoJavaRemoteVersion> remoteVersions = new ArrayList<>();
|
||||
for (Map.Entry<Integer, DiscoJavaRemoteVersion> entry : versions.entrySet()) {
|
||||
int v = entry.getKey();
|
||||
if (v >= lastLTS || isLTS(v) || v == 16) {
|
||||
remoteVersions.add(entry.getValue());
|
||||
}
|
||||
}
|
||||
Collections.reverse(remoteVersions);
|
||||
return remoteVersions;
|
||||
}).whenComplete(Schedulers.javafx(), ((result, exception) -> {
|
||||
if (exception == null) {
|
||||
res.status.set(DiscoJavaVersionList.Status.SUCCESS);
|
||||
res.versions.setAll(result);
|
||||
selectLTS(res);
|
||||
} else {
|
||||
LOG.warning("Failed to load java list", exception);
|
||||
res.status.set(DiscoJavaVersionList.Status.FAILED);
|
||||
}
|
||||
})).start();
|
||||
return res;
|
||||
});
|
||||
this.currentDiscoJavaVersionList.set(list);
|
||||
this.remoteVersionBox.setItems(list.versions);
|
||||
selectLTS(list);
|
||||
}
|
||||
|
||||
private void selectLTS(DiscoJavaVersionList list) {
|
||||
if (remoteVersionBox.getItems() == list.versions) {
|
||||
for (int i = 0; i < list.versions.size(); i++) {
|
||||
JavaRemoteVersion item = list.versions.get(i);
|
||||
if (item.getJdkVersion() == GameJavaVersion.LATEST.getMajorVersion()) {
|
||||
remoteVersionBox.getSelectionModel().select(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class DiscoJavaVersionList {
|
||||
enum Status {
|
||||
LOADING, SUCCESS, FAILED
|
||||
}
|
||||
|
||||
final ObservableList<DiscoJavaRemoteVersion> versions = FXCollections.observableArrayList();
|
||||
final ObjectProperty<Status> status = new SimpleObjectProperty<>(Status.LOADING);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.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<String, Object> update;
|
||||
private final StringProperty nameProperty = new SimpleStringProperty();
|
||||
|
||||
public JavaInstallPage(Runnable onFinish, JavaInfo info, JavaRemoteVersion remoteVersion, Map<String, Object> 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<JavaRuntime> 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<JavaInstallPage> {
|
||||
|
||||
private final ComponentList componentList = new ComponentList();
|
||||
|
||||
private final JFXTextField nameField;
|
||||
|
||||
private final Set<String> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,322 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.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 org.jackhuang.hmcl.util.tree.TarFileTree;
|
||||
|
||||
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<JavaManagementPage.JavaItem> {
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
private final ChangeListener<Collection<JavaRuntime>> 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 = TarFileTree.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<JavaRuntime> javaRuntimes) {
|
||||
if (javaRuntimes != null) {
|
||||
List<JavaItem> 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<JavaItem> {
|
||||
|
||||
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<JavaManagementPage> {
|
||||
|
||||
JavaPageSkin(JavaManagementPage skinnable) {
|
||||
super(skinnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Node> initializeToolbar(JavaManagementPage skinnable) {
|
||||
ArrayList<Node> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.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<JavaRestorePage.DisabledJavaItem> implements DecoratorPage {
|
||||
|
||||
private final ObjectProperty<State> state = new SimpleObjectProperty<>(State.fromTitle(i18n("java.disabled.management")));
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
private final InvalidationListener listener;
|
||||
|
||||
public JavaRestorePage(ObservableSet<String> disabledJava) {
|
||||
this.listener = o -> {
|
||||
ArrayList<DisabledJavaItem> 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<State> stateProperty() {
|
||||
return state;
|
||||
}
|
||||
|
||||
static final class DisabledJavaItem extends Control {
|
||||
final ObservableSet<String> disabledJava;
|
||||
final String path;
|
||||
final Path realPath;
|
||||
|
||||
DisabledJavaItem(ObservableSet<String> 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<DisabledJavaItem> {
|
||||
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<JavaRestorePage> {
|
||||
JavaRestorePageSkin(JavaRestorePage skinnable) {
|
||||
super(skinnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Node> initializeToolbar(JavaRestorePage skinnable) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
||||
import org.jackhuang.hmcl.ui.construct.PageAware;
|
||||
import org.jackhuang.hmcl.ui.construct.TabControl;
|
||||
import org.jackhuang.hmcl.ui.construct.TabHeader;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
@@ -40,6 +41,7 @@ public class LauncherSettingsPage extends DecoratorAnimatedPage implements Decor
|
||||
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("settings")));
|
||||
private final TabHeader tab;
|
||||
private final TabHeader.Tab<VersionSettingsPage> gameTab = new TabHeader.Tab<>("versionSettingsPage");
|
||||
private final TabControl.Tab<JavaManagementPage> javaManagementTab = new TabControl.Tab<>("javaManagementPage");
|
||||
private final TabHeader.Tab<SettingsPage> settingsTab = new TabHeader.Tab<>("settingsPage");
|
||||
private final TabHeader.Tab<PersonalizationPage> personalizationTab = new TabHeader.Tab<>("personalizationPage");
|
||||
private final TabHeader.Tab<DownloadSettingsPage> downloadTab = new TabHeader.Tab<>("downloadSettingsPage");
|
||||
@@ -50,13 +52,14 @@ 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();
|
||||
@@ -74,6 +77,12 @@ public class LauncherSettingsPage extends DecoratorAnimatedPage implements Decor
|
||||
runInFX(() -> FXUtils.installFastTooltip(settingsItem, i18n("settings.type.global.manage")));
|
||||
settingsItem.setOnAction(e -> tab.select(gameTab));
|
||||
})
|
||||
.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"))
|
||||
.addNavigationDrawerItem(settingsItem -> {
|
||||
settingsItem.setTitle(i18n("settings.launcher.general"));
|
||||
|
||||
@@ -237,10 +237,13 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import javafx.scene.layout.*;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextAlignment;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.setting.EnumCommonDirectory;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
@@ -191,10 +192,19 @@ public abstract class SettingsView extends StackPane {
|
||||
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
|
||||
debugPane.setLeft(left);
|
||||
|
||||
JFXButton openLogFolderButton = new JFXButton(i18n("settings.launcher.launcher_log.reveal"));
|
||||
openLogFolderButton.setOnMouseClicked(e -> openLogFolder());
|
||||
openLogFolderButton.getStyleClass().add("jfx-button-border");
|
||||
|
||||
JFXButton logButton = new JFXButton(i18n("settings.launcher.launcher_log.export"));
|
||||
logButton.setOnMouseClicked(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 abstract class SettingsView extends StackPane {
|
||||
}
|
||||
}
|
||||
|
||||
public void openLogFolder() {
|
||||
FXUtils.openFolder(Metadata.HMCL_DIRECTORY.resolve("logs").toFile());
|
||||
}
|
||||
|
||||
protected abstract void onUpdate();
|
||||
|
||||
protected abstract void onExportLogs();
|
||||
|
||||
@@ -381,8 +381,6 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
|
||||
{
|
||||
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());
|
||||
@@ -390,11 +388,16 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
|
||||
switch (dataItem.getVersionType()) {
|
||||
case Alpha:
|
||||
content.getTags().add(i18n("version.game.snapshot"));
|
||||
graphicPane.getChildren().setAll(SVG.ALPHA_CIRCLE_OUTLINE.createIcon(Theme.blackFill(), 24, 24));
|
||||
break;
|
||||
case Beta:
|
||||
content.getTags().add(i18n("version.game.snapshot"));
|
||||
graphicPane.getChildren().setAll(SVG.BETA_CIRCLE_OUTLINE.createIcon(Theme.blackFill(), 24, 24));
|
||||
break;
|
||||
case Release:
|
||||
content.getTags().add(i18n("version.game.release"));
|
||||
graphicPane.getChildren().setAll(SVG.RELEASE_CIRCLE_OUTLINE.createIcon(Theme.blackFill(), 24, 24));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -457,7 +460,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
|
||||
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) {
|
||||
@@ -466,7 +469,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
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) {
|
||||
|
||||
@@ -79,6 +79,12 @@ public class InstallerListPage extends ListPageBase<InstallerItem> implements Ve
|
||||
// Conventional libraries: game, fabric, forge, neoforge, liteloader, optifine
|
||||
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);
|
||||
|
||||
if (libraryVersion != null) {
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
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,6 +34,7 @@ import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.mod.LocalModFile;
|
||||
import org.jackhuang.hmcl.mod.ModLoaderType;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
@@ -59,15 +61,11 @@ 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.Arrays;
|
||||
import java.util.List;
|
||||
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;
|
||||
@@ -117,7 +115,12 @@ class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
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,
|
||||
() -> {
|
||||
@@ -297,25 +300,65 @@ class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
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<String> 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());
|
||||
|
||||
@@ -56,8 +56,8 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>();
|
||||
private final TabHeader tab;
|
||||
private final TabHeader.Tab<VersionSettingsPage> versionSettingsTab = new TabHeader.Tab<>("versionSettingsTab");
|
||||
private final TabHeader.Tab<ModListPage> modListTab = new TabHeader.Tab<>("modListTab");
|
||||
private final TabHeader.Tab<InstallerListPage> installerListTab = new TabHeader.Tab<>("installerListTab");
|
||||
private final TabHeader.Tab<ModListPage> modListTab = new TabHeader.Tab<>("modListTab");
|
||||
private final TabHeader.Tab<WorldListPage> worldListTab = new TabHeader.Tab<>("worldList");
|
||||
private final TransitionPane transitionPane = new TransitionPane();
|
||||
private final BooleanProperty currentVersionUpgradable = new SimpleBooleanProperty();
|
||||
@@ -68,11 +68,11 @@ 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);
|
||||
|
||||
@@ -128,10 +128,10 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
|
||||
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));
|
||||
@@ -247,15 +247,6 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
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"));
|
||||
@@ -265,6 +256,15 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
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));
|
||||
runInFX(() -> FXUtils.installFastTooltip(modListItem, i18n("mods.manage")));
|
||||
modListItem.setOnAction(e -> control.tab.select(control.modListTab));
|
||||
|
||||
AdvancedListItem worldListItem = new AdvancedListItem();
|
||||
worldListItem.getStyleClass().add("navigation-drawer-item");
|
||||
worldListItem.setTitle(i18n("world.manage"));
|
||||
@@ -276,8 +276,8 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
|
||||
AdvancedListBox sideBar = new AdvancedListBox()
|
||||
.add(versionSettingsItem)
|
||||
.add(modListItem)
|
||||
.add(installerListItem)
|
||||
.add(modListItem)
|
||||
.add(worldListItem);
|
||||
VBox.setVgrow(sideBar, Priority.ALWAYS);
|
||||
|
||||
|
||||
@@ -22,20 +22,22 @@ import javafx.application.Platform;
|
||||
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.Pair;
|
||||
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<Pair<JavaVersionType, JavaVersion>> javaItem;
|
||||
private final MultiFileItem.Option<Pair<JavaVersionType, JavaVersion>> javaAutoDeterminedOption;
|
||||
private final MultiFileItem.FileOption<Pair<JavaVersionType, JavaVersion>> javaCustomOption;
|
||||
private final MultiFileItem<Pair<JavaVersionType, JavaRuntime>> javaItem;
|
||||
private final MultiFileItem.Option<Pair<JavaVersionType, JavaRuntime>> javaAutoDeterminedOption;
|
||||
private final MultiFileItem.StringOption<Pair<JavaVersionType, JavaRuntime>> javaVersionOption;
|
||||
private final MultiFileItem.FileOption<Pair<JavaVersionType, JavaRuntime>> javaCustomOption;
|
||||
|
||||
private final ComponentSublist gameDirSublist;
|
||||
private final MultiFileItem<GameDirectoryType> gameDirItem;
|
||||
private final MultiFileItem.FileOption<GameDirectoryType> gameDirCustomOption;
|
||||
@@ -93,9 +91,11 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
private final OptionToggleButton showLogsPane;
|
||||
private final ImagePickerItem iconPickerItem;
|
||||
|
||||
private final ChangeListener<Collection<JavaRuntime>> 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 final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
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<Pair<JavaVersionType, JavaVersion>>(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<Pair<JavaVersionType, JavaRuntime>>(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<MultiFileItem.Option<Pair<JavaVersionType, JavaRuntime>>> 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 final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
}
|
||||
|
||||
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<MultiFileItem.Option<Pair<JavaVersionType, JavaVersion>>> 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 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void loadVersion(Profile profile, String versionId) {
|
||||
this.profile = profile;
|
||||
this.versionId = versionId;
|
||||
@@ -517,8 +520,10 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
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 final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
|
||||
// unbind data fields
|
||||
javaItem.setToggleSelectedListener(null);
|
||||
javaVersionOption.valueProperty().unbind();
|
||||
|
||||
// bind new data fields
|
||||
FXUtils.bindInt(txtWidth, versionSetting.widthProperty());
|
||||
@@ -551,18 +557,42 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
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<JavaVersionType, JavaVersion>) newValue.getUserData()).getValue());
|
||||
@SuppressWarnings("unchecked")
|
||||
JavaRuntime java = ((Pair<JavaVersionType, JavaRuntime>) 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 final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
}
|
||||
|
||||
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<JavaVersionType, JavaRuntime> userData = (Pair<JavaVersionType, JavaRuntime>) 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<JavaVersionType, JavaRuntime> 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 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
public ReadOnlyObjectProperty<State> stateProperty() {
|
||||
return state.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
private enum JavaVersionType {
|
||||
DETECTED,
|
||||
CUSTOM,
|
||||
AUTO,
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,8 @@ public interface WizardPage {
|
||||
default void onNavigate(Map<String, Object> settings) {
|
||||
}
|
||||
|
||||
void cleanup(Map<String, Object> settings);
|
||||
default void cleanup(Map<String, Object> settings) {
|
||||
}
|
||||
|
||||
String getTitle();
|
||||
}
|
||||
@@ -33,7 +33,7 @@ import org.jackhuang.hmcl.ui.SwingUtils;
|
||||
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 @@ public final class UpdateHandler {
|
||||
|
||||
private static void startJava(Path jar, String... appArgs) throws IOException {
|
||||
List<String> commandline = new ArrayList<>();
|
||||
commandline.add(JavaVersion.fromCurrentEnvironment().getBinary().toString());
|
||||
commandline.add(JavaRuntime.getDefault().getBinary().toString());
|
||||
for (Map.Entry<Object, Object> entry : System.getProperties().entrySet()) {
|
||||
Object key = entry.getKey();
|
||||
if (key instanceof String && ((String) key).startsWith("hmcl.")) {
|
||||
|
||||
@@ -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 <a href="https://github.com/fractureiser-investigation/fractureiser">fractureiser-investigation/fractureiser</a>
|
||||
* @see <a href="https://prismlauncher.org/news/cf-compromised-alert/#automated-script">[MALWARE WARNING] "fractureiser" malware in many popular Minecraft mods and modpacks</a>
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,12 +17,11 @@
|
||||
*/
|
||||
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;
|
||||
@@ -34,6 +33,7 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.util.gson.JsonUtils.mapTypeOf;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
/**
|
||||
@@ -51,9 +51,7 @@ public final class NativePatcher {
|
||||
return natives.computeIfAbsent(platform, p -> {
|
||||
//noinspection ConstantConditions
|
||||
try (Reader reader = new InputStreamReader(NativePatcher.class.getResourceAsStream("/assets/natives.json"), StandardCharsets.UTF_8)) {
|
||||
Map<String, Map<String, Library>> natives = JsonUtils.GSON.fromJson(reader, new TypeToken<Map<String, Map<String, Library>>>() {
|
||||
}.getType());
|
||||
|
||||
Map<String, Map<String, Library>> 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);
|
||||
@@ -62,7 +60,7 @@ public final class NativePatcher {
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -154,7 +152,7 @@ public final class NativePatcher {
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -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 java.util.concurrent.atomic.AtomicBoolean;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
import static org.jackhuang.hmcl.Metadata.HMCL_DIRECTORY;
|
||||
import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;
|
||||
import static org.jackhuang.hmcl.util.gson.JsonUtils.mapTypeOf;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
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 @@ public final class SelfDependencyPatcher {
|
||||
private static final Path DEPENDENCIES_DIR_PATH = HMCL_DIRECTORY.resolve("dependencies").resolve(Platform.getPlatform().toString()).resolve("openjfx");
|
||||
|
||||
static List<DependencyDescriptor> readDependencies() {
|
||||
ArrayList<DependencyDescriptor> dependencies;
|
||||
//noinspection ConstantConditions
|
||||
try (Reader reader = new InputStreamReader(SelfDependencyPatcher.class.getResourceAsStream(DEPENDENCIES_LIST_FILE), UTF_8)) {
|
||||
Map<String, ArrayList<DependencyDescriptor>> allDependencies =
|
||||
new Gson().fromJson(reader, new TypeToken<Map<String, ArrayList<DependencyDescriptor>>>(){}.getType());
|
||||
dependencies = allDependencies.get(Platform.getPlatform().toString());
|
||||
Map<String, List<DependencyDescriptor>> 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 final class SelfDependencyPatcher {
|
||||
// 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();
|
||||
}
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@ package org.jackhuang.hmcl.util.i18n;
|
||||
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;
|
||||
@@ -116,7 +116,7 @@ public final class Locales {
|
||||
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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
@@ -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;
|
||||
}
|
||||
@@ -183,6 +183,7 @@ 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
|
||||
@@ -211,6 +212,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
|
||||
@@ -333,6 +335,7 @@ 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\
|
||||
@@ -351,8 +354,6 @@ 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.
|
||||
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.
|
||||
@@ -645,6 +646,36 @@ install.new_game.malformed=Invalid Name
|
||||
install.select=Select an operation
|
||||
install.success=Installed successfully.
|
||||
|
||||
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 (US)
|
||||
lang.default=Use System Locales
|
||||
launch.advice=%s Do you still want to continue to launch?
|
||||
@@ -663,15 +694,16 @@ launch.advice.java8_1_13=Minecraft 1.13 and later can only be run on Java 8 or l
|
||||
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.modlauncher8=The Forge version you are using is not compatible with the current Java version, please try updating Forge.
|
||||
launch.advice.newer_java=You are using the old Java to start the game. It is recommended to update to Java 8, otherwise some mods may cause the game to crash.
|
||||
launch.advice.not_enough_space=You have allocated a memory size larger than the actual %d MB of memory installed on your computer. You may experience degraded performance, or even be unable to launch the game.
|
||||
launch.advice.require_newer_java_version=Minecraft %1$s requires Java %2$s or later, but we could not find one. Do you want to download one now?
|
||||
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.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.
|
||||
@@ -685,6 +717,7 @@ launch.failed.execution_policy.hint=The current execution policy prevents the ex
|
||||
\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.exited_abnormally=Game crashed. Please refer to the crash log for more details.
|
||||
launch.failed.java_version_too_low=The Java version you specified is too low, please reset the Java version.
|
||||
launch.failed.no_accepted_java=Unable to find a compatible Java version, do you want to start the game with the default Java?\nClick on 'Yes' to start the game with the default Java.\nOr, you can go to the instance settings to select one yourself.
|
||||
launch.failed.sigkill=Game was forcibly terminated by the user or system.
|
||||
launch.state.dependencies=Resolving dependencies
|
||||
@@ -693,7 +726,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
|
||||
@@ -727,10 +760,8 @@ logwindow.title=Log
|
||||
logwindow.help=You can go 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 turn to HMCL QQ group or HMCL Discord for help.
|
||||
|
||||
main_page=Home
|
||||
|
||||
@@ -791,7 +822,6 @@ 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.
|
||||
@@ -933,6 +963,8 @@ mods.not_modded=You must install a mod loader (Fabric, Forge, Quilt or LiteLoade
|
||||
mods.restore=Rollback
|
||||
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.install=Install
|
||||
mods.save_as=Save As
|
||||
|
||||
nbt.entries=%s entries
|
||||
nbt.open.failed=Fail to open file
|
||||
@@ -945,6 +977,10 @@ datapack.choose_datapack=Select a datapack to import
|
||||
datapack.extension=Datapack
|
||||
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.datapack=Manage Datapacks
|
||||
@@ -1120,6 +1156,7 @@ settings.game.java_directory.auto.not_found=No suitable Java version was install
|
||||
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.version=Specify Java Version
|
||||
settings.game.java_directory.template=%s (%s)
|
||||
settings.game.management=Manage
|
||||
settings.game.working_directory=Working Directory
|
||||
@@ -1146,6 +1183,7 @@ 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.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
|
||||
|
||||
@@ -187,6 +187,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
|
||||
@@ -610,7 +611,6 @@ 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\
|
||||
@@ -639,7 +639,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
|
||||
@@ -672,10 +671,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
|
||||
@@ -734,8 +731,7 @@ 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.
|
||||
|
||||
@@ -173,6 +173,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+
|
||||
@@ -462,7 +463,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を実行するか、プラットフォームの対応するネイティブライブラリをダウンロードして指定してくださいその配置パス。
|
||||
@@ -484,7 +484,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
|
||||
@@ -574,7 +573,6 @@ 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のすべてのファイルのダウンロードを完了できません。対応するゲームバージョンを開始するときに、ダウンロードを再試行できます。ネットワークの問題により、数回再試行する場合があります。
|
||||
|
||||
@@ -184,6 +184,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
|
||||
@@ -486,7 +487,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 или более ранней версии.
|
||||
@@ -511,7 +511,6 @@ launch.state.java=Проверка версии Java
|
||||
launch.state.logging_in=Вход в систему
|
||||
launch.state.modpack=Скачивание зависимостей
|
||||
launch.state.waiting_launching=Ожидание запуска игры
|
||||
launch.wrong_javadir=Неверный путь Java, возврат к пути по умолчанию.
|
||||
|
||||
launcher=Лаунчер
|
||||
launcher.agreement=Пользовательское соглашение
|
||||
@@ -602,7 +601,6 @@ 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=Мы не можем завершить скачивание всех файлов этого модпака. Вы можете повторить скачивание при запуске соответствующей версии игры. Вы можете повторить попытку несколько раз из-за проблем с сетью.
|
||||
|
||||
@@ -192,6 +192,7 @@ button.retry=重試
|
||||
button.save=儲存
|
||||
button.save_as=另存為
|
||||
button.select_all=全選
|
||||
button.view=查看
|
||||
button.yes=是
|
||||
|
||||
chat=官方群組
|
||||
@@ -221,6 +222,7 @@ 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=教育
|
||||
@@ -342,6 +344,7 @@ 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
|
||||
@@ -359,7 +362,6 @@ 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 頁面尋求幫助。
|
||||
@@ -423,7 +425,7 @@ game.crash.reason.mod_profile_causes_game_crash=當前遊戲因為 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如果沒有,可以從 <a href="https://www.java.com/download/">java.com(Java8)</a> 或 <a href="https://bell-sw.com/pages/downloads/#downloads">BellSoft Liberica Full JRE(Java17)</a> 等平台下載、安裝一個(安裝完後需重啟啟動器)。
|
||||
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.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/光影包/材質包再試。
|
||||
@@ -450,14 +452,14 @@ game.crash.reason.mod_resolution_missing=當前遊戲因為缺少 Mod 前置,
|
||||
game.crash.reason.mod_resolution_missing_minecraft=當前遊戲因為 Mod 和 Minecraft 遊戲版本不匹配,無法繼續運行。\n%1$s 需要 Minecraft %2$s 才能運行。\n如果你要繼續使用你已經安裝的 Mod,你可以選擇安裝對應的 Minecraft 版本;如果你要繼續使用當前 Minecraft 版本,你需要安裝對應版本的 Mod。
|
||||
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 重複安裝,無法繼續運行。<a href="https://github.com/HMCL-dev/HMCL/issues/1880">此為已知問題</a>\n建議將日誌上傳反饋至 GitHub ,以便我們找到更多線索並修復此問題。\n目前你可以到 自動安裝 裡頭卸載 Forge 並重新安裝。
|
||||
game.crash.reason.optifine_repeat_installation=當前遊戲因為重複安裝 OptiFine,無法繼續運行。 \n請刪除 Mod 文件夾下的 OptiFine 或前往 遊戲管理-自動安裝 卸載自動安裝的 OptiFine。
|
||||
game.crash.reason.forge_repeat_installation=當前遊戲因為 Forge 重複安裝,無法繼續運行。<a href="https://github.com/HMCL-dev/HMCL/issues/1880">此為已知問題</a>\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請前往 <a href="https://optifine.net/downloads">OptiFine 官網</a>查看 OptiFine 所相容的 Forge 版本,並嚴格按照對應版本重新安裝遊戲或在版本設定-自動安裝中更換版本。\n經測試,Forge版本過高或過低都可能導致崩潰。
|
||||
game.crash.reason.night_config_fixes=當前遊戲因為 Night Config 庫的一些問題,無法繼續運行。\n你可以嘗試安裝 <a href="https://www.curseforge.com/minecraft/mc-mods/night-config-fixes">Night Config Fixes</a> 模組,這或許能幫助你解決這個問題。\n了解更多,可訪問該模組的 <a href="https://www.github.com/Fuzss/nightconfigfixes">GitHub 倉庫</a>。
|
||||
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請嘗試安裝<a href="https://www.curseforge.com/minecraft/mc-mods/jeid">JEID</a>等修復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.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點擊 <a href="https://github.com/CaffeineMC/sodium-fabric/wiki/Known-Issues#rtss-incompatible">此處</a> 查看詳情。
|
||||
@@ -529,6 +531,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.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=使用系統語言
|
||||
|
||||
@@ -536,23 +568,24 @@ 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.corrected=我們已經修正了問題。如果確實希望使用你自訂的 Java 虛擬機,你可以在遊戲設定中關閉 Java 虛擬機相容性檢查。
|
||||
launch.advice.uncorrected=如果你確實希望使用你自訂的 Java 虛擬機,你可以在遊戲設定中關閉 Java 虛擬機相容性檢查。
|
||||
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.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.modlauncher8=你所使用的 Forge 版本與當前使用的 Java 不相容,請更新 Forge。
|
||||
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.not_enough_space=你設定的記憶體大小過大,由於超過了系統記憶體大小 %dMB,所以可能影響遊戲體驗或無法啟動遊戲。
|
||||
launch.advice.require_newer_java_version=當前遊戲版本需要 Java %s,但 HMCL 未能找到該 Java 版本,你可以點擊“是”,HMCL 會自動下載他,是否下載?
|
||||
launch.advice.too_large_memory_for_32bit=你設定的記憶體大小過大,由於可能超過了 32 位元 Java 的記憶體分配限制,所以可能無法啟動遊戲,請將記憶體調至低於 1024MB 的值。
|
||||
launch.advice.vanilla_linux_java_8=對於 Linux x86-64 平台,Minecraft 1.12.2 及以下版本與 Java 9+ 不相容,請使用 Java 8 啟動遊戲。
|
||||
launch.advice.vanilla_x86.translation=Minecraft 尚未為您的平臺提供完善支持,所以可能影響遊戲體驗或無法啟動遊戲。\n你可以在 <a href="https://docs.microsoft.com/java/openjdk/download"> 這裡 </a>下載<b> X86-64 </b> 架構的 Java 以獲得更完整的體驗。\n是否繼續啟動?
|
||||
launch.advice.vanilla_x86.translation=Minecraft 尚未為你的平臺提供完善支持,所以可能影響遊戲體驗或無法啟動遊戲。\n你可以在 <a href="https://docs.microsoft.com/java/openjdk/download"> 這裡 </a>下載<b> X86-64 </b> 架構的 Java 以獲得更完整的體驗。\n是否繼續啟動?
|
||||
launch.advice.unknown=由於以下原因,無法繼續啟動遊戲:
|
||||
launch.failed=啟動失敗
|
||||
launch.failed.cannot_create_jvm=偵測到無法建立 Java 虛擬機,可能是 Java 參數有問題。可以在設定中開啟無參數模式啟動。
|
||||
launch.failed.creating_process=啟動失敗,在建立新處理程式時發生錯誤。可能是 Java 路徑錯誤。
|
||||
@@ -562,8 +595,9 @@ launch.failed.download_library=無法下載遊戲相依元件 %s。
|
||||
launch.failed.executable_permission=無法為啟動檔案新增執行權限。
|
||||
launch.failed.execution_policy=設定執行策略
|
||||
launch.failed.execution_policy.failed_to_set=設定執行策略失敗
|
||||
launch.failed.execution_policy.hint=當前執行策略封锁您執行 PowerShell 腳本。\n點擊“確定”允許當前用戶執行本地 PowerShell 腳本,或點擊“取消”保持現狀。
|
||||
launch.failed.execution_policy.hint=當前執行策略封锁你執行 PowerShell 腳本。\n點擊“確定”允許當前用戶執行本地 PowerShell 腳本,或點擊“取消”保持現狀。
|
||||
launch.failed.exited_abnormally=遊戲非正常退出,請查看記錄檔案,或聯絡他人尋求幫助。
|
||||
launch.failed.java_version_too_low=你所指定的 Java 版本過低,請重新設定 Java 版本。
|
||||
launch.failed.no_accepted_java=找不到適合當前遊戲使用的 Java,是否使用默認 Java 啟動遊戲?點擊“是”使用默認 Java 繼續啟動遊戲,\n或者請到遊戲設定中選擇一個合適的Java虛擬機器版本。
|
||||
launch.failed.sigkill=遊戲被用戶或系統強制終止。
|
||||
launch.state.dependencies=處理遊戲相依元件
|
||||
@@ -572,7 +606,7 @@ 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=用戶協議與免責聲明
|
||||
@@ -593,9 +627,9 @@ 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,點擊 <a href="https://bell-sw.com/pages/downloads/#downloads">此處</a> 安裝合適的 Java 版本。
|
||||
launcher.crash.java_internal_error=HHello Minecraft! Launcher 由於當前 Java 損壞而無法繼續運行,請移除當前 Java,點擊 <a href="https://bell-sw.com/pages/downloads/#downloads">此處</a> 安裝合適的 Java 版本。
|
||||
launcher.crash.hmcl_out_dated=Hello Minecraft! Launcher 遇到了無法處理的錯誤,已偵測到您的啟動器不是最新版本,請更新後重試!
|
||||
launcher.update_java=請更新您的 Java
|
||||
launcher.update_java=請更新你的 Java
|
||||
|
||||
login.empty_username=你還未設定使用者名稱!
|
||||
login.enter_password=請輸入您的密碼
|
||||
@@ -606,10 +640,8 @@ 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=首頁
|
||||
|
||||
@@ -670,7 +702,6 @@ 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.tolerable_error=但未能完成該模組包檔案的下載,您可以在啟動該遊戲版本時繼續該模組包檔案的下載。由於網路問題,您可能需要重試多次……
|
||||
modpack.type.curse.error=無法完成該模組包所需的依賴下載,請多次重試或設定代理……
|
||||
@@ -801,6 +832,8 @@ mods.not_modded=你需要先在自動安裝頁面安裝 Fabric、Forge、Quilt
|
||||
mods.restore=回退
|
||||
mods.url=官方頁面
|
||||
mods.update_modpack_mod.warning=更新模組包中的 Mod 可能導致綜合包損壞,使模組包無法正常啟動。該操作不可逆,確定要更新嗎?
|
||||
mods.install=安裝到當前實例
|
||||
mods.save_as=下載到本地目錄
|
||||
|
||||
nbt.entries=%s 個條目
|
||||
nbt.open.failed=打開檔案失敗
|
||||
@@ -813,6 +846,10 @@ datapack.choose_datapack=選擇要匯入的資料包壓縮檔
|
||||
datapack.extension=資料包
|
||||
datapack.title=世界 %s - 資料包
|
||||
|
||||
web.failed=加載頁面失敗
|
||||
web.open_in_browser=是否要在瀏覽器中打開此連結:\n%s
|
||||
web.view_in_browser=在瀏覽器中查看
|
||||
|
||||
world=世界
|
||||
world.add=加入世界
|
||||
world.datapack=管理資料包
|
||||
@@ -981,6 +1018,7 @@ settings.game.java_directory.auto.not_found=沒有合適的 Java
|
||||
settings.game.java_directory.bit=%s 位
|
||||
settings.game.java_directory.choose=選擇 Java 路徑
|
||||
settings.game.java_directory.invalid=Java 路徑不正確
|
||||
settings.game.java_directory.version=指定 Java 版本
|
||||
settings.game.java_directory.template=%s(%s)
|
||||
settings.game.management=管理
|
||||
settings.game.working_directory=執行路徑(版本隔離,修改後請自行移動相關遊戲檔案,如存檔模組設定等)
|
||||
@@ -1004,6 +1042,7 @@ 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.log=記錄
|
||||
|
||||
@@ -193,6 +193,7 @@ button.retry=重试
|
||||
button.save=保存
|
||||
button.save_as=另存为
|
||||
button.select_all=全选
|
||||
button.view=查看
|
||||
button.yes=是
|
||||
|
||||
chat=官方群组
|
||||
@@ -222,6 +223,7 @@ 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=教育
|
||||
@@ -343,6 +345,7 @@ 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
|
||||
@@ -361,7 +364,6 @@ 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 页面寻求帮助。
|
||||
@@ -528,6 +530,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.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=跟随系统语言
|
||||
|
||||
@@ -545,13 +577,14 @@ launch.advice.java8_1_13=Minecraft 1.13 及以上版本只能运行在 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.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.require_newer_java_version=当前游戏版本需要 Java %s,但 HMCL 未能找到该 Java 版本,你可以点击“是”,HMCL 会自动下载他,是否下载?\n如遇到问题,你可以点击右上角帮助按钮进行求助。
|
||||
launch.advice.too_large_memory_for_32bit=您设置的内存大小过大,由于可能超过了 32 位 Java 的内存分配限制,所以可能无法启动游戏,请将内存调至 1024MB 或更小。\n如遇到问题,你可以点击右上角帮助按钮进行求助。
|
||||
launch.advice.vanilla_linux_java_8=对于 Linux x86-64 平台,Minecraft 1.12.2 及以下版本与 Java 9+ 不兼容,请使用 Java 8 启动游戏。\n如遇到问题,你可以点击右上角帮助按钮进行求助。
|
||||
launch.advice.vanilla_x86.translation=Minecraft 尚未为您的平台提供完善支持,所以可能影响游戏体验或无法启动游戏。\n你可以在 <a href="https://docs.microsoft.com/java/openjdk/download"> 这里 </a> 下载 <b> X86-64 </b> 架构的 Java 以获得更完整的体验。
|
||||
launch.advice.unknown=由于以下原因,无法继续启动游戏:
|
||||
launch.failed=启动失败
|
||||
launch.failed.cannot_create_jvm=截获到无法创建 Java 虚拟机,可能是 Java 参数有问题,可以在设置中开启无参数模式启动。
|
||||
launch.failed.creating_process=启动失败,在创建新进程时发生错误,可能是 Java 路径错误。
|
||||
@@ -563,6 +596,7 @@ 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.java_version_too_low=你所指定的 Java 版本过低,请重新设置 Java 版本。
|
||||
launch.failed.no_accepted_java=找不到适合当前游戏使用的 Java,是否使用默认 Java 启动游戏?点击“是”使用默认 Java 继续启动游戏,\n或者请到全局(特定)游戏设置中选择一个合适的 Java 虚拟机版本。\n你可以点击右上角帮助按钮进行求助。
|
||||
launch.failed.sigkill=游戏被用户或系统强制终止。
|
||||
launch.state.dependencies=处理游戏依赖
|
||||
@@ -571,7 +605,7 @@ 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=用户协议与免责声明
|
||||
@@ -605,10 +639,8 @@ 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=主页
|
||||
|
||||
@@ -669,7 +701,6 @@ 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你可以点击右上角帮助按钮进行求助。
|
||||
@@ -800,6 +831,8 @@ mods.not_modded=你需要先在自动安装页面安装 Fabric、Forge 或 LiteL
|
||||
mods.restore=回退
|
||||
mods.url=官方页面
|
||||
mods.update_modpack_mod.warning=更新整合包中的模组可能导致整合包损坏,使整合包无法正常启动。该操作不可逆,确定要更新吗?
|
||||
mods.install=安装到当前版本
|
||||
mods.save_as=下载到本地目录
|
||||
|
||||
nbt.entries=%s 个条目
|
||||
nbt.open.failed=打开文件失败
|
||||
@@ -812,6 +845,10 @@ datapack.choose_datapack=选择要导入的数据包压缩包
|
||||
datapack.extension=数据包
|
||||
datapack.title=世界 %s - 数据包
|
||||
|
||||
web.failed=加载页面失败
|
||||
web.open_in_browser=是否要在浏览器中打开此链接:\n%s
|
||||
web.view_in_browser=在浏览器中查看
|
||||
|
||||
world=世界
|
||||
world.add=添加世界
|
||||
world.datapack=管理数据包
|
||||
@@ -980,6 +1017,7 @@ settings.game.java_directory.auto.not_found=没有合适的 Java
|
||||
settings.game.java_directory.bit=%s 位
|
||||
settings.game.java_directory.choose=选择 Java 路径
|
||||
settings.game.java_directory.invalid=Java 路径不正确
|
||||
settings.game.java_directory.version=指定 Java 版本
|
||||
settings.game.java_directory.template=%s(%s)
|
||||
settings.game.management=管理
|
||||
settings.game.working_directory=版本隔离(建议使用模组时开启“各版本隔离”,改后需移动存档模组等相关游戏文件)
|
||||
@@ -1003,6 +1041,7 @@ 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.log=日志
|
||||
|
||||
@@ -2265,6 +2265,226 @@
|
||||
"org.lwjgl:lwjgl-glfw:3.3.1:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-stb:3.3.1:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-tinyfd:3.3.1:natives-linux": null,
|
||||
"org.lwjgl:lwjgl:3.3.2": {
|
||||
"name": "org.lwjgl:lwjgl:3.3.4",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar",
|
||||
"sha1": "b86c3e4832426e8a6b466013b7cb34b40e9ce956",
|
||||
"size": 800127
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl:3.3.2:natives-linux": {
|
||||
"name": "org.glavo.hmcl:lwjgl3-natives:3.3.4-rc2",
|
||||
"downloads": {
|
||||
"classifiers": {
|
||||
"linux-loongarch64": {
|
||||
"path": "org/glavo/hmcl/lwjgl3-natives/3.3.4-rc2/lwjgl3-natives-3.3.4-rc2-linux-loongarch64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl3-natives/3.3.4-rc2-linux-loongarch64/lwjgl3-natives-3.3.4-rc2-linux-loongarch64.jar",
|
||||
"sha1": "34a7f913c6750f2bede863f59c074cc4d540fb64",
|
||||
"size": 12234234
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"exclude": [
|
||||
"META-INF/"
|
||||
]
|
||||
},
|
||||
"natives": {
|
||||
"linux": "linux-loongarch64"
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-jemalloc:3.3.2": {
|
||||
"name": "org.lwjgl:lwjgl-jemalloc:3.3.4",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar",
|
||||
"sha1": "e3f5dcb8e13f3a5ed3f740fd30a114cee2a80bc4",
|
||||
"size": 46430
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-jemalloc:3.3.2:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-openal:3.3.2": {
|
||||
"name": "org.lwjgl:lwjgl-openal:3.3.4",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar",
|
||||
"sha1": "9b74d3ea380c83353d42af43ad9659e04dabe84a",
|
||||
"size": 113103
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-openal:3.3.2:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-opengl:3.3.2": {
|
||||
"name": "org.lwjgl:lwjgl-opengl:3.3.4",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar",
|
||||
"sha1": "2852ac7d9f6fc71349f1ce28e2708ff1977f18af",
|
||||
"size": 931960
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-opengl:3.3.2:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-glfw:3.3.2": {
|
||||
"name": "org.lwjgl:lwjgl-glfw:3.3.4",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar",
|
||||
"sha1": "7e46ecdec85db8738053cfde1414352cd62dab74",
|
||||
"size": 147044
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-glfw:3.3.2:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-stb:3.3.2": {
|
||||
"name": "org.lwjgl:lwjgl-stb:3.3.4",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar",
|
||||
"sha1": "5821735d5ef23f6da8542887344e57eb181b7cac",
|
||||
"size": 143112
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-stb:3.3.2:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-tinyfd:3.3.2": {
|
||||
"name": "org.lwjgl:lwjgl-tinyfd:3.3.4",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar",
|
||||
"sha1": "2d73789ffd8962b38d9d599cc38b2383ce818c7a",
|
||||
"size": 15928
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-tinyfd:3.3.2:natives-linux": null,
|
||||
"org.lwjgl:lwjgl:3.3.3": {
|
||||
"name": "org.lwjgl:lwjgl:3.3.4",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar",
|
||||
"sha1": "b86c3e4832426e8a6b466013b7cb34b40e9ce956",
|
||||
"size": 800127
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl:3.3.3:natives-linux": {
|
||||
"name": "org.glavo.hmcl:lwjgl3-natives:3.3.4-rc2",
|
||||
"downloads": {
|
||||
"classifiers": {
|
||||
"linux-loongarch64": {
|
||||
"path": "org/glavo/hmcl/lwjgl3-natives/3.3.4-rc2/lwjgl3-natives-3.3.4-rc2-linux-loongarch64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl3-natives/3.3.4-rc2-linux-loongarch64/lwjgl3-natives-3.3.4-rc2-linux-loongarch64.jar",
|
||||
"sha1": "34a7f913c6750f2bede863f59c074cc4d540fb64",
|
||||
"size": 12234234
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"exclude": [
|
||||
"META-INF/"
|
||||
]
|
||||
},
|
||||
"natives": {
|
||||
"linux": "linux-loongarch64"
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-jemalloc:3.3.3": {
|
||||
"name": "org.lwjgl:lwjgl-jemalloc:3.3.4",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar",
|
||||
"sha1": "e3f5dcb8e13f3a5ed3f740fd30a114cee2a80bc4",
|
||||
"size": 46430
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-jemalloc:3.3.3:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-openal:3.3.3": {
|
||||
"name": "org.lwjgl:lwjgl-openal:3.3.4",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar",
|
||||
"sha1": "9b74d3ea380c83353d42af43ad9659e04dabe84a",
|
||||
"size": 113103
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-openal:3.3.3:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-opengl:3.3.3": {
|
||||
"name": "org.lwjgl:lwjgl-opengl:3.3.4",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar",
|
||||
"sha1": "2852ac7d9f6fc71349f1ce28e2708ff1977f18af",
|
||||
"size": 931960
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-opengl:3.3.3:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-glfw:3.3.3": {
|
||||
"name": "org.lwjgl:lwjgl-glfw:3.3.4",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar",
|
||||
"sha1": "7e46ecdec85db8738053cfde1414352cd62dab74",
|
||||
"size": 147044
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-glfw:3.3.3:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-stb:3.3.3": {
|
||||
"name": "org.lwjgl:lwjgl-stb:3.3.4",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar",
|
||||
"sha1": "5821735d5ef23f6da8542887344e57eb181b7cac",
|
||||
"size": 143112
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-stb:3.3.3:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-tinyfd:3.3.3": {
|
||||
"name": "org.lwjgl:lwjgl-tinyfd:3.3.4",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar",
|
||||
"sha1": "2d73789ffd8962b38d9d599cc38b2383ce818c7a",
|
||||
"size": 15928
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-tinyfd:3.3.3:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-freetype:3.3.3": {
|
||||
"name": "org.lwjgl:lwjgl-freetype:3.3.4",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-freetype/3.3.4/lwjgl-freetype-3.3.4.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-freetype/3.3.4/lwjgl-freetype-3.3.4.jar",
|
||||
"sha1": "23f7bf165068ef2ca80ae1b79fd905af20498600",
|
||||
"size": 453489
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-freetype:3.3.3:natives-linux": null,
|
||||
"net.java.dev.jna:jna:5.8.0": {
|
||||
"name": "net.java.dev.jna:jna:5.13.0",
|
||||
"downloads": {
|
||||
|
||||
@@ -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": "19.0.2.1",
|
||||
"classifier": "win",
|
||||
"sha1": "f33d0776b2b027c2ea9267c184f9048ef94c4deb"
|
||||
},
|
||||
{
|
||||
"module": "javafx.graphics",
|
||||
"groupId": "org.openjfx",
|
||||
"artifactId": "javafx-graphics",
|
||||
"version": "19.0.2.1",
|
||||
"classifier": "win",
|
||||
"sha1": "951ca77bd50ef4fd44f4b7fb1e38301088fde4e2"
|
||||
},
|
||||
{
|
||||
"module": "javafx.controls",
|
||||
"groupId": "org.openjfx",
|
||||
"artifactId": "javafx-controls",
|
||||
"version": "19.0.2.1",
|
||||
"classifier": "win",
|
||||
"sha1": "78ac7de48d0aac48e7d5282efc81bab7f337b587"
|
||||
}
|
||||
],
|
||||
"windows-arm64": [
|
||||
{
|
||||
"module": "javafx.base",
|
||||
"groupId": "org.glavo.hmcl.openjfx",
|
||||
"artifactId": "javafx-base",
|
||||
"version": "18.0.2+1-arm64",
|
||||
"classifier": "win",
|
||||
"sha1": "4518a696b9d509dc09a7fe283452fce84a1686a8"
|
||||
},
|
||||
{
|
||||
"module": "javafx.graphics",
|
||||
"groupId": "org.glavo.hmcl.openjfx",
|
||||
"artifactId": "javafx-graphics",
|
||||
"version": "18.0.2+1-arm64",
|
||||
"classifier": "win",
|
||||
"sha1": "e19ba9aefc4bba8ff86dcdf416620b93b7bdea39"
|
||||
},
|
||||
{
|
||||
"module": "javafx.controls",
|
||||
"groupId": "org.glavo.hmcl.openjfx",
|
||||
"artifactId": "javafx-controls",
|
||||
"version": "18.0.2+1-arm64",
|
||||
"classifier": "win",
|
||||
"sha1": "0bf7380823bb8c420dd41837d2c71087b8953ec1"
|
||||
}
|
||||
],
|
||||
"osx-x86_64": [
|
||||
{
|
||||
"module": "javafx.base",
|
||||
"groupId": "org.openjfx",
|
||||
"artifactId": "javafx-base",
|
||||
"version": "19.0.2.1",
|
||||
"classifier": "mac",
|
||||
"sha1": "73e422d8426aaa23e8c712b9f4d9bebf70d3bfb9"
|
||||
},
|
||||
{
|
||||
"module": "javafx.graphics",
|
||||
"groupId": "org.openjfx",
|
||||
"artifactId": "javafx-graphics",
|
||||
"version": "19.0.2.1",
|
||||
"classifier": "mac",
|
||||
"sha1": "6ab6f3e23421fcfa04e692d9d26a8f55a830c114"
|
||||
},
|
||||
{
|
||||
"module": "javafx.controls",
|
||||
"groupId": "org.openjfx",
|
||||
"artifactId": "javafx-controls",
|
||||
"version": "19.0.2.1",
|
||||
"classifier": "mac",
|
||||
"sha1": "b7786b1b63e741c0e234829825fae5fef9d96c31"
|
||||
}
|
||||
],
|
||||
"osx-arm64": [
|
||||
{
|
||||
"module": "javafx.base",
|
||||
"groupId": "org.openjfx",
|
||||
"artifactId": "javafx-base",
|
||||
"version": "19.0.2.1",
|
||||
"classifier": "mac-aarch64",
|
||||
"sha1": "1d0d887c492330ed527b0614d115a4f32d2d05a4"
|
||||
},
|
||||
{
|
||||
"module": "javafx.graphics",
|
||||
"groupId": "org.openjfx",
|
||||
"artifactId": "javafx-graphics",
|
||||
"version": "19.0.2.1",
|
||||
"classifier": "mac-aarch64",
|
||||
"sha1": "64db28799e61e0f8f51e471c732599b2a6961803"
|
||||
},
|
||||
{
|
||||
"module": "javafx.controls",
|
||||
"groupId": "org.openjfx",
|
||||
"artifactId": "javafx-controls",
|
||||
"version": "19.0.2.1",
|
||||
"classifier": "mac-aarch64",
|
||||
"sha1": "3a14bd5f3ebe45d344c1f2bade0fe074e6ea2a83"
|
||||
}
|
||||
],
|
||||
"linux-x86_64": [
|
||||
{
|
||||
"module": "javafx.base",
|
||||
"groupId": "org.openjfx",
|
||||
"artifactId": "javafx-base",
|
||||
"version": "19.0.2.1",
|
||||
"classifier": "linux",
|
||||
"sha1": "9e4ca5ce2b87c479d2cc445ff411a48aeae76936"
|
||||
},
|
||||
{
|
||||
"module": "javafx.graphics",
|
||||
"groupId": "org.openjfx",
|
||||
"artifactId": "javafx-graphics",
|
||||
"version": "19.0.2.1",
|
||||
"classifier": "linux",
|
||||
"sha1": "27c64e4e1569ddf198f28eda84cf8d014ab80693"
|
||||
},
|
||||
{
|
||||
"module": "javafx.controls",
|
||||
"groupId": "org.openjfx",
|
||||
"artifactId": "javafx-controls",
|
||||
"version": "19.0.2.1",
|
||||
"classifier": "linux",
|
||||
"sha1": "b5b2a8ead40bd43476740aa51697bb1cac23de5c"
|
||||
}
|
||||
],
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -20,10 +20,11 @@ package org.jackhuang.hmcl.ui;
|
||||
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.Arrays;
|
||||
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 class GameCrashWindowTest {
|
||||
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();
|
||||
|
||||
@@ -12,5 +12,6 @@ dependencies {
|
||||
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")
|
||||
}
|
||||
@@ -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 java.util.stream.Stream;
|
||||
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 @@ public class YggdrasilServer extends HttpServer {
|
||||
}
|
||||
|
||||
private Response profiles(Request request) throws IOException {
|
||||
List<String> names = JsonUtils.fromNonNullJsonFully(request.getSession().getInputStream(), new TypeToken<List<String>>() {
|
||||
}.getType());
|
||||
List<String> names = JsonUtils.fromNonNullJsonFully(request.getSession().getInputStream(), listTypeOf(String.class));
|
||||
return ok(names.stream().distinct()
|
||||
.map(this::findCharacterByName)
|
||||
.flatMap(Lang::toStream)
|
||||
|
||||
@@ -270,6 +270,13 @@ public final class LibraryAnalyzer implements Iterable<LibraryAnalyzer.LibraryMa
|
||||
private final Pattern group, artifact;
|
||||
private final ModLoaderType modLoaderType;
|
||||
|
||||
private static final Map<String, LibraryType> 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 final class LibraryAnalyzer implements Iterable<LibraryAnalyzer.LibraryMa
|
||||
}
|
||||
|
||||
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<Library> libraries) {
|
||||
|
||||
@@ -19,7 +19,7 @@ package org.jackhuang.hmcl.download;
|
||||
|
||||
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 class MojangDownloadProvider implements DownloadProvider {
|
||||
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);
|
||||
|
||||
@@ -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.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
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<FabricRemoteVersion> {
|
||||
private final DownloadProvider downloadProvider;
|
||||
@@ -69,8 +68,8 @@ public final class FabricVersionList extends VersionList<FabricRemoteVersion> {
|
||||
|
||||
private List<String> getGameVersions(String metaUrl) throws IOException {
|
||||
String json = NetworkUtils.doGet(downloadProvider.injectURLWithCandidates(metaUrl));
|
||||
return JsonUtils.GSON.<ArrayList<GameVersion>>fromJson(json, new TypeToken<ArrayList<GameVersion>>() {
|
||||
}.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) {
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package org.jackhuang.hmcl.download.forge;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.jackhuang.hmcl.download.VersionList;
|
||||
import org.jackhuang.hmcl.util.Immutable;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
@@ -40,6 +39,7 @@ import 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.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<ForgeRemoteVersion> {
|
||||
@@ -87,11 +87,9 @@ public final class ForgeBMCLVersionList extends VersionList<ForgeRemoteVersion>
|
||||
String lookupVersion = toLookupVersion(gameVersion);
|
||||
|
||||
return CompletableFuture.completedFuture(null)
|
||||
.thenApplyAsync(wrap(unused -> HttpRequest.GET(apiRoot + "/forge/minecraft/" + lookupVersion).<List<ForgeVersion>>getJson(new TypeToken<List<ForgeVersion>>() {
|
||||
}.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;
|
||||
|
||||
@@ -40,7 +40,7 @@ import org.jackhuang.hmcl.util.io.ChecksumMismatchException;
|
||||
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 class ForgeNewInstallTask extends Task<Version> {
|
||||
throw new Exception("Game processor jar does not have main class " + jar);
|
||||
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add(JavaVersion.fromCurrentEnvironment().getBinary().toString());
|
||||
command.add(JavaRuntime.getDefault().getBinary().toString());
|
||||
command.add("-cp");
|
||||
|
||||
List<String> classpath = new ArrayList<>(processor.getClasspath().size() + 1);
|
||||
|
||||
@@ -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 final class ForgeVersion implements Validation {
|
||||
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 final class ForgeVersion implements Validation {
|
||||
return build;
|
||||
}
|
||||
|
||||
public double getModified() {
|
||||
public long getModified() {
|
||||
return modified;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.jackhuang.hmcl.util.StringUtils;
|
||||
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 final class ForgeVersionList extends VersionList<ForgeRemoteVersion> {
|
||||
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 final class ForgeVersionList extends VersionList<ForgeRemoteVersion> {
|
||||
versions.clear();
|
||||
|
||||
for (Map.Entry<String, int[]> 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 final class ForgeVersionList extends VersionList<ForgeRemoteVersion> {
|
||||
|
||||
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 final class ForgeVersionList extends VersionList<ForgeRemoteVersion> {
|
||||
});
|
||||
}
|
||||
|
||||
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/";
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.download.java;
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.platform.Platform;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public interface JavaDistribution<V extends JavaRemoteVersion> {
|
||||
String getDisplayName();
|
||||
|
||||
Set<JavaPackageType> getSupportedPackageTypes();
|
||||
|
||||
Task<TreeMap<Integer, V>> getFetchJavaVersionsTask(DownloadProvider provider, Platform platform, JavaPackageType packageType);
|
||||
}
|
||||
+21
-15
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
* Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -15,24 +15,30 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
package org.jackhuang.hmcl.download.java;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public enum JavaPackageType {
|
||||
JDK(true, false),
|
||||
JRE(false, false),
|
||||
JDKFX(true, true),
|
||||
JREFX(false, true);
|
||||
|
||||
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 boolean jdk;
|
||||
private final boolean javafx;
|
||||
|
||||
private final String key;
|
||||
|
||||
LoadingState(String key) {
|
||||
this.key = key;
|
||||
JavaPackageType(boolean jdk, boolean javafx) {
|
||||
this.jdk = jdk;
|
||||
this.javafx = javafx;
|
||||
}
|
||||
|
||||
public String getLocalizedMessage() {
|
||||
return i18n(key);
|
||||
public boolean isJDK() {
|
||||
return jdk;
|
||||
}
|
||||
|
||||
public boolean isJavaFXBundled() {
|
||||
return javafx;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.download.java;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public interface JavaRemoteVersion {
|
||||
int getJdkVersion();
|
||||
|
||||
String getJavaVersion();
|
||||
|
||||
String getDistributionVersion();
|
||||
}
|
||||
@@ -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<JavaVersion> 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<Optional<Path>> 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<Path> 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<Path> javaHomes = new ArrayList<>();
|
||||
Consumer<String> action = platform -> {
|
||||
try (DirectoryStream<Path> 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<Path> 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<String> 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;
|
||||
}
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.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<TreeMap<Integer, DiscoJavaRemoteVersion>> {
|
||||
|
||||
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 Task<String> fetchPackagesTask;
|
||||
|
||||
public DiscoFetchJavaListTask(DownloadProvider downloadProvider, DiscoJavaDistribution distribution, Platform platform, JavaPackageType packageType) {
|
||||
this.distribution = distribution;
|
||||
|
||||
HashMap<String, String> params = new HashMap<>();
|
||||
params.put("distribution", distribution.getApiParameter());
|
||||
params.put("package", packageType.isJDK() ? "jdk" : "jre");
|
||||
params.put("javafx_bundled", Boolean.toString(packageType.isJavaFXBundled()));
|
||||
params.put("operating_system", getOperatingSystemName(platform.getOperatingSystem()));
|
||||
params.put("architecture", getArchitectureName(platform.getArchitecture()));
|
||||
params.put("archive_type", platform.getOperatingSystem() == OperatingSystem.WINDOWS ? "zip" : "tar.gz");
|
||||
params.put("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<Task<?>> getDependents() {
|
||||
return Collections.singleton(fetchPackagesTask);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
String json = fetchPackagesTask.getResult();
|
||||
List<DiscoJavaRemoteVersion> result = JsonUtils.fromNonNullJson(json, DiscoResult.typeOf(DiscoJavaRemoteVersion.class)).getResult();
|
||||
|
||||
TreeMap<Integer, DiscoJavaRemoteVersion> map = new TreeMap<>();
|
||||
|
||||
for (DiscoJavaRemoteVersion version : result) {
|
||||
if (!distribution.getApiParameter().equals(version.getDistribution()))
|
||||
continue;
|
||||
|
||||
int jdkVersion = version.getJdkVersion();
|
||||
DiscoJavaRemoteVersion oldVersion = map.get(jdkVersion);
|
||||
if (oldVersion == null || VersionNumber.compare(version.getDistributionVersion(), oldVersion.getDistributionVersion()) > 0) {
|
||||
map.put(jdkVersion, version);
|
||||
}
|
||||
}
|
||||
|
||||
setResult(map);
|
||||
}
|
||||
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.download.java.disco;
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.java.JavaDistribution;
|
||||
import org.jackhuang.hmcl.download.java.JavaPackageType;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
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
|
||||
*/
|
||||
public enum DiscoJavaDistribution implements JavaDistribution<DiscoJavaRemoteVersion> {
|
||||
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("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))),
|
||||
ZULU("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("GraalVM", "graalvm", "Oracle",
|
||||
EnumSet.of(JDK),
|
||||
pair(WINDOWS, EnumSet.of(X86_64, X86)),
|
||||
pair(LINUX, EnumSet.of(X86_64, X86, ARM64, ARM32, RISCV64, PPC64LE)),
|
||||
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<JavaPackageType> supportedPackageTypes;
|
||||
private final Map<OperatingSystem, EnumSet<Architecture>> supportedPlatforms = new EnumMap<>(OperatingSystem.class);
|
||||
|
||||
@SafeVarargs
|
||||
DiscoJavaDistribution(String displayName, String apiParameter, String vendor, Set<JavaPackageType> supportedPackageTypes, Pair<OperatingSystem, EnumSet<Architecture>>... supportedPlatforms) {
|
||||
this.displayName = displayName;
|
||||
this.apiParameter = apiParameter;
|
||||
this.vendor = vendor;
|
||||
this.supportedPackageTypes = supportedPackageTypes;
|
||||
|
||||
for (Pair<OperatingSystem, EnumSet<Architecture>> 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<JavaPackageType> getSupportedPackageTypes() {
|
||||
return supportedPackageTypes;
|
||||
}
|
||||
|
||||
public boolean isSupport(Platform platform) {
|
||||
EnumSet<Architecture> architectures = supportedPlatforms.get(platform.getOperatingSystem());
|
||||
return architectures != null && architectures.contains(platform.getArchitecture());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<TreeMap<Integer, DiscoJavaRemoteVersion>> getFetchJavaVersionsTask(DownloadProvider provider, Platform platform, JavaPackageType packageType) {
|
||||
return new DiscoFetchJavaListTask(provider, this, platform, packageType);
|
||||
}
|
||||
}
|
||||
+259
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.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 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.download.java.disco;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class DiscoResult<T> {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> TypeToken<DiscoResult<T>> typeOf(Class<T> argType) {
|
||||
return (TypeToken<DiscoResult<T>>) TypeToken.getParameterized(DiscoResult.class, argType);
|
||||
}
|
||||
|
||||
private final List<T> result;
|
||||
private final String message;
|
||||
|
||||
private DiscoResult(List<T> result, String message) {
|
||||
this.result = result;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public List<T> getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.download.java.mojang;
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.java.JavaDistribution;
|
||||
import org.jackhuang.hmcl.download.java.JavaPackageType;
|
||||
import org.jackhuang.hmcl.download.java.JavaRemoteVersion;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.platform.Platform;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class MojangJavaDistribution implements JavaDistribution<JavaRemoteVersion> {
|
||||
|
||||
public static final MojangJavaDistribution DISTRIBUTION = new MojangJavaDistribution();
|
||||
|
||||
private MojangJavaDistribution() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return "Mojang";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<JavaPackageType> getSupportedPackageTypes() {
|
||||
return Collections.singleton(JavaPackageType.JRE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<TreeMap<Integer, JavaRemoteVersion>> getFetchJavaVersionsTask(DownloadProvider provider, Platform platform, JavaPackageType packageType) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+42
-55
@@ -15,20 +15,19 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.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.Paths;
|
||||
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<Void> {
|
||||
private final GameJavaVersion javaVersion;
|
||||
private final Path rootDir;
|
||||
private String platform;
|
||||
private final Task<RemoteFiles> javaDownloadsTask;
|
||||
private JavaDownloads.JavaDownload download;
|
||||
private final List<Task<?>> dependencies = new ArrayList<>();
|
||||
private final DownloadProvider downloadProvider;
|
||||
public final class MojangJavaDownloadTask extends Task<MojangJavaDownloadTask.Result> {
|
||||
|
||||
public JavaDownloadTask(GameJavaVersion javaVersion, Path rootDir, DownloadProvider downloadProvider) {
|
||||
this.javaVersion = javaVersion;
|
||||
this.rootDir = rootDir;
|
||||
private final DownloadProvider downloadProvider;
|
||||
private final Path target;
|
||||
private final Task<MojangJavaRemoteFiles> javaDownloadsTask;
|
||||
private final List<Task<?>> dependencies = new ArrayList<>();
|
||||
|
||||
private volatile MojangJavaDownloads.JavaDownload download;
|
||||
|
||||
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<String, List<JavaDownloads.JavaDownload>> osDownloads = allDownloads.getDownloads().get(platform);
|
||||
if (!osDownloads.containsKey(javaVersion.getComponent())) throw new UnsupportedPlatformException();
|
||||
List<JavaDownloads.JavaDownload> 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<String, List<MojangJavaDownloads.JavaDownload>> osDownloads = allDownloads.getDownloads().get(platform);
|
||||
if (osDownloads == null || !osDownloads.containsKey(javaVersion.getComponent()))
|
||||
throw new UnsupportedPlatformException("Unsupported platform: " + platform);
|
||||
List<MojangJavaDownloads.JavaDownload> 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 class JavaDownloadTask extends Task<Void> {
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
Path jvmDir = rootDir.resolve(javaVersion.getComponent()).resolve(platform).resolve(javaVersion.getComponent());
|
||||
for (Map.Entry<String, RemoteFiles.Remote> 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<String, MojangJavaRemoteFiles.Remote> 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 class JavaDownloadTask extends Task<Void> {
|
||||
|
||||
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 class JavaDownloadTask extends Task<Void> {
|
||||
} 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 class JavaDownloadTask extends Task<Void> {
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
-10
@@ -15,11 +15,10 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.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.lang.reflect.Type;
|
||||
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<String, Map<String, List<JavaDownload>>> downloads;
|
||||
|
||||
public JavaDownloads(Map<String, Map<String, List<JavaDownload>>> downloads) {
|
||||
public MojangJavaDownloads(Map<String, Map<String, List<JavaDownload>>> downloads) {
|
||||
this.downloads = downloads;
|
||||
}
|
||||
|
||||
@@ -40,17 +42,16 @@ public class JavaDownloads {
|
||||
return downloads;
|
||||
}
|
||||
|
||||
public static class Adapter implements JsonSerializer<JavaDownloads>, JsonDeserializer<JavaDownloads> {
|
||||
public static class Adapter implements JsonSerializer<MojangJavaDownloads>, JsonDeserializer<MojangJavaDownloads> {
|
||||
|
||||
@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<Map<String, Map<String, List<JavaDownload>>>>() {
|
||||
}.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()));
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -15,7 +15,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.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 org.jackhuang.hmcl.util.gson.JsonType;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
public class RemoteFiles {
|
||||
public final class MojangJavaRemoteFiles {
|
||||
private final Map<String, Remote> files;
|
||||
|
||||
public RemoteFiles(Map<String, Remote> files) {
|
||||
public MojangJavaRemoteFiles(Map<String, Remote> files) {
|
||||
this.files = files;
|
||||
}
|
||||
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.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 + "]";
|
||||
}
|
||||
}
|
||||
+2
-4
@@ -19,18 +19,17 @@ package org.jackhuang.hmcl.download.neoforge;
|
||||
|
||||
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<NeoForgeRemoteVersion> {
|
||||
private final String apiRoot;
|
||||
@@ -68,8 +67,7 @@ public final class NeoForgeBMCLVersionList extends VersionList<NeoForgeRemoteVer
|
||||
@Override
|
||||
public CompletableFuture<?> refreshAsync(String gameVersion) {
|
||||
return CompletableFuture.completedFuture((Void) null)
|
||||
.thenApplyAsync(wrap(unused -> HttpRequest.GET(apiRoot + "/neoforge/list/" + gameVersion).<List<NeoForgeVersion>>getJson(new TypeToken<List<NeoForgeVersion>>() {
|
||||
}.getType())))
|
||||
.thenApplyAsync(wrap(unused -> HttpRequest.GET(apiRoot + "/neoforge/list/" + gameVersion).getJson(listTypeOf(NeoForgeVersion.class))))
|
||||
.thenAcceptAsync(neoForgeVersions -> {
|
||||
lock.writeLock().lock();
|
||||
|
||||
|
||||
+2
-2
@@ -36,7 +36,7 @@ import org.jackhuang.hmcl.util.io.ChecksumMismatchException;
|
||||
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 class NeoForgeOldInstallTask extends Task<Version> {
|
||||
throw new Exception("Game processor jar does not have main class " + jar);
|
||||
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add(JavaVersion.fromCurrentEnvironment().getBinary().toString());
|
||||
command.add(JavaRuntime.getDefault().getBinary().toString());
|
||||
command.add("-cp");
|
||||
|
||||
List<String> classpath = new ArrayList<>(processor.getClasspath().size() + 1);
|
||||
|
||||
+3
-4
@@ -18,7 +18,6 @@
|
||||
package org.jackhuang.hmcl.download.optifine;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.jackhuang.hmcl.download.VersionList;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
||||
@@ -26,10 +25,11 @@ import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
*/
|
||||
@@ -72,8 +72,7 @@ public final class OptiFineBMCLVersionList extends VersionList<OptiFineRemoteVer
|
||||
|
||||
@Override
|
||||
public CompletableFuture<?> refreshAsync() {
|
||||
return HttpRequest.GET(apiRoot + "/optifine/versionlist").<List<OptiFineVersion>>getJsonAsync(new TypeToken<List<OptiFineVersion>>() {
|
||||
}.getType()).thenAcceptAsync(root -> {
|
||||
return HttpRequest.GET(apiRoot + "/optifine/versionlist").getJsonAsync(listTypeOf(OptiFineVersion.class)).thenAcceptAsync(root -> {
|
||||
lock.writeLock().lock();
|
||||
|
||||
try {
|
||||
|
||||
+2
-2
@@ -27,7 +27,7 @@ import org.jackhuang.hmcl.task.Task;
|
||||
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 final class OptiFineInstallTask extends Task<Version> {
|
||||
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",
|
||||
|
||||
@@ -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.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
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<QuiltRemoteVersion> {
|
||||
private final DownloadProvider downloadProvider;
|
||||
@@ -69,8 +68,8 @@ public final class QuiltVersionList extends VersionList<QuiltRemoteVersion> {
|
||||
|
||||
private List<String> getGameVersions(String metaUrl) throws IOException {
|
||||
String json = NetworkUtils.doGet(downloadProvider.injectURLWithCandidates(metaUrl));
|
||||
return JsonUtils.GSON.<ArrayList<GameVersion>>fromJson(json, new TypeToken<ArrayList<GameVersion>>() {
|
||||
}.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) {
|
||||
|
||||
@@ -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 class DefaultGameRepository implements GameRepository {
|
||||
* read modpack configuration for a version.
|
||||
*
|
||||
* @param version version installed as modpack
|
||||
* @param <M> 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 <M> ModpackConfiguration<M> 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<ModpackConfiguration<M>>() {
|
||||
}.getType());
|
||||
return JsonUtils.GSON.fromJson(FileUtils.readText(file), ModpackConfiguration.class);
|
||||
}
|
||||
|
||||
public boolean isModpack(String version) {
|
||||
|
||||
@@ -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<GameJavaVersion> 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 class GameJavaVersion {
|
||||
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;
|
||||
}
|
||||
}
|
||||
Loaded 100 of 148 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user