重构 Java 管理 (#2988)
* update * update * Update task name * update * update * update * update * update * update * update * update * update * update * Update logo
This commit is contained in:
@@ -23,7 +23,7 @@ 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 +61,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);
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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.*;
|
||||
@@ -59,10 +56,13 @@ import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static javafx.application.Platform.runLater;
|
||||
import static javafx.application.Platform.setImplicitExit;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.Lang.resolveException;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.platform.Platform.*;
|
||||
|
||||
public final class LauncherHelper {
|
||||
|
||||
@@ -127,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(
|
||||
@@ -150,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);
|
||||
@@ -174,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);
|
||||
@@ -209,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()));
|
||||
});
|
||||
@@ -229,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()) {
|
||||
@@ -326,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:
|
||||
@@ -505,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));
|
||||
@@ -529,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 {
|
||||
@@ -680,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);
|
||||
});
|
||||
@@ -691,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();
|
||||
});
|
||||
@@ -746,7 +721,7 @@ public final class LauncherHelper {
|
||||
|
||||
if (showLogs) {
|
||||
CountDownLatch logWindowLatch = new CountDownLatch(1);
|
||||
Platform.runLater(() -> {
|
||||
runLater(() -> {
|
||||
logWindow = new LogWindow(process, logs);
|
||||
logWindow.show();
|
||||
logWindowLatch.countDown();
|
||||
@@ -760,9 +735,9 @@ public final class LauncherHelper {
|
||||
private void submitLogs() {
|
||||
if (currentLogs.size() == 1) {
|
||||
Log log = currentLogs.get(0);
|
||||
Platform.runLater(() -> logWindow.logLine(log));
|
||||
runLater(() -> logWindow.logLine(log));
|
||||
} else {
|
||||
Platform.runLater(() -> {
|
||||
runLater(() -> {
|
||||
logWindow.logLines(currentLogs);
|
||||
semaphore.release();
|
||||
});
|
||||
@@ -803,7 +778,7 @@ public final class LauncherHelper {
|
||||
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) {
|
||||
@@ -816,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) {
|
||||
@@ -898,7 +873,7 @@ public final class LauncherHelper {
|
||||
|
||||
if (exitType != ExitType.NORMAL) {
|
||||
repository.markVersionLaunchedAbnormally(version.getId());
|
||||
Platform.runLater(() -> new GameCrashWindow(process, exitType, repository, version, launchOptions, logs).show());
|
||||
runLater(() -> new GameCrashWindow(process, exitType, repository, version, launchOptions, logs).show());
|
||||
}
|
||||
|
||||
checkExit();
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
116
HMCL/src/main/java/org/jackhuang/hmcl/java/JavaInstallTask.java
Normal file
116
HMCL/src/main/java/org/jackhuang/hmcl/java/JavaInstallTask.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
128
HMCL/src/main/java/org/jackhuang/hmcl/java/JavaLocalFiles.java
Normal file
128
HMCL/src/main/java/org/jackhuang/hmcl/java/JavaLocalFiles.java
Normal file
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
697
HMCL/src/main/java/org/jackhuang/hmcl/java/JavaManager.java
Normal file
697
HMCL/src/main/java/org/jackhuang/hmcl/java/JavaManager.java
Normal file
@@ -0,0 +1,697 @@
|
||||
/*
|
||||
* 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")).resolve(".minecraft/runtime"), false);
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX) {
|
||||
searchAllOfficialJava(javaRuntimes, Paths.get(System.getProperty("user.home")).resolve("Library/Application Support/minecraft/runtime"), false);
|
||||
}
|
||||
searchAllOfficialJava(javaRuntimes, 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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
112
HMCL/src/main/java/org/jackhuang/hmcl/java/JavaManifest.java
Normal file
112
HMCL/src/main/java/org/jackhuang/hmcl/java/JavaManifest.java
Normal file
@@ -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 com.google.gson.reflect.TypeToken;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.Platform;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* @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 = new TypeToken<Map<String, JavaLocalFiles.Local>>() {
|
||||
}.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -57,6 +58,7 @@ 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;
|
||||
@@ -837,6 +839,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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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(() -> {
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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();
|
||||
@@ -430,30 +458,6 @@ 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"));
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.")) {
|
||||
|
||||
@@ -22,7 +22,7 @@ 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;
|
||||
@@ -62,7 +62,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 +154,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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ import org.jackhuang.hmcl.ui.SwingUtils;
|
||||
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.*;
|
||||
@@ -69,7 +70,6 @@ import static java.util.stream.Collectors.toSet;
|
||||
import static org.jackhuang.hmcl.Metadata.HMCL_DIRECTORY;
|
||||
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 {
|
||||
@@ -188,7 +188,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
|
||||
|
||||
@@ -334,6 +334,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\
|
||||
@@ -646,6 +647,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?
|
||||
@@ -664,15 +695,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.
|
||||
@@ -686,6 +718,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
|
||||
@@ -694,7 +727,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
|
||||
@@ -1119,6 +1152,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
|
||||
|
||||
@@ -611,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\
|
||||
@@ -640,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
|
||||
|
||||
@@ -463,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を実行するか、プラットフォームの対応するネイティブライブラリをダウンロードして指定してくださいその配置パス。
|
||||
@@ -485,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
|
||||
|
||||
@@ -487,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 или более ранней версии.
|
||||
@@ -512,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=Пользовательское соглашение
|
||||
|
||||
@@ -343,6 +343,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
|
||||
@@ -424,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/光影包/材質包再試。
|
||||
@@ -451,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> 查看詳情。
|
||||
@@ -530,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=使用系統語言
|
||||
|
||||
@@ -537,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 路徑錯誤。
|
||||
@@ -563,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=處理遊戲相依元件
|
||||
@@ -573,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=用戶協議與免責聲明
|
||||
@@ -594,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=請輸入您的密碼
|
||||
@@ -980,6 +1013,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=執行路徑(版本隔離,修改後請自行移動相關遊戲檔案,如存檔模組設定等)
|
||||
|
||||
@@ -344,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
|
||||
@@ -529,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=跟随系统语言
|
||||
|
||||
@@ -546,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 路径错误。
|
||||
@@ -564,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=处理游戏依赖
|
||||
@@ -572,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=用户协议与免责声明
|
||||
@@ -979,6 +1012,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=版本隔离(建议使用模组时开启“各版本隔离”,改后需移动存档模组等相关游戏文件)
|
||||
|
||||
@@ -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.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;
|
||||
@@ -51,7 +52,7 @@ 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"))
|
||||
|
||||
Reference in New Issue
Block a user