feat: auto select java. Closes #1068.
This commit is contained in:
@@ -293,10 +293,9 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
vs.setUsesGlobal(true);
|
||||
}
|
||||
|
||||
public LaunchOptions getLaunchOptions(String version, File gameDir, boolean checkJava) throws InterruptedException {
|
||||
public LaunchOptions getLaunchOptions(String version, JavaVersion javaVersion, File gameDir) {
|
||||
VersionSetting vs = getVersionSetting(version);
|
||||
|
||||
JavaVersion javaVersion = Optional.ofNullable(vs.getJavaVersion(checkJava)).orElse(JavaVersion.fromCurrentEnvironment());
|
||||
LaunchOptions.Builder builder = new LaunchOptions.Builder()
|
||||
.setGameDir(gameDir)
|
||||
.setJava(javaVersion)
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import org.jackhuang.hmcl.util.Range;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LAUNCH_WRAPPER_MAIN;
|
||||
|
||||
public enum HMCLJavaVersion {
|
||||
|
||||
// Minecraft>=1.17 requires Java 16
|
||||
VANILLA_JAVA_16(HMCLJavaVersion.RULE_MANDATORY, versionRange("1.17", HMCLJavaVersion.MAX), versionRange("16", HMCLJavaVersion.MAX)),
|
||||
// Minecraft>=1.13 requires Java 8
|
||||
VANILLA_JAVA_8(HMCLJavaVersion.RULE_MANDATORY, versionRange("1.13", HMCLJavaVersion.MAX), versionRange("8", HMCLJavaVersion.MAX)),
|
||||
// Minecraft>=1.7.10+Forge accepts Java 8
|
||||
SUGGEST_JAVA_8(HMCLJavaVersion.RULE_SUGGESTED, versionRange("1.7.10", HMCLJavaVersion.MAX), versionRange("8", HMCLJavaVersion.MAX)),
|
||||
// LaunchWrapper<=1.12 will crash because of assuming the system class loader is an instance of URLClassLoader (Java 8)
|
||||
LAUNCH_WRAPPER(HMCLJavaVersion.RULE_MANDATORY, versionRange("0", "1.12"), versionRange("0", "8")) {
|
||||
@Override
|
||||
public boolean test(Version version) {
|
||||
return LAUNCH_WRAPPER_MAIN.equals(version.getMainClass()) &&
|
||||
version.getLibraries().stream()
|
||||
.filter(library -> "launchwrapper".equals(library.getArtifactId()))
|
||||
.anyMatch(library -> VersionNumber.asVersion(library.getVersion()).compareTo(VersionNumber.asVersion("1.13")) < 0);
|
||||
}
|
||||
},
|
||||
// Minecraft>=1.13 may crash when generating world on Java [1.8,1.8.0_51)
|
||||
VANILLA_JAVA_8_51(HMCLJavaVersion.RULE_SUGGESTED, versionRange("1.13", HMCLJavaVersion.MAX), versionRange("1.8.0_51", HMCLJavaVersion.MAX)),
|
||||
|
||||
;
|
||||
|
||||
private final int type;
|
||||
private final Range<VersionNumber> gameVersion;
|
||||
private final Range<VersionNumber> javaVersion;
|
||||
|
||||
HMCLJavaVersion(int type, Range<VersionNumber> gameVersion, Range<VersionNumber> javaVersion) {
|
||||
this.type = type;
|
||||
this.gameVersion = gameVersion;
|
||||
this.javaVersion = javaVersion;
|
||||
}
|
||||
|
||||
public boolean test(Version version) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static JavaVersion findSuitableJavaVersion(VersionNumber gameVersion, Version version) throws InterruptedException {
|
||||
Range<VersionNumber> mandatoryJavaRange = versionRange(MIN, MAX);
|
||||
Range<VersionNumber> suggestedJavaRange = versionRange(MIN, MAX);
|
||||
for (HMCLJavaVersion java : values()) {
|
||||
if (java.gameVersion.contains(gameVersion) && java.test(version)) {
|
||||
if (java.type == RULE_MANDATORY) {
|
||||
mandatoryJavaRange = mandatoryJavaRange.intersectionWith(java.javaVersion);
|
||||
suggestedJavaRange = suggestedJavaRange.intersectionWith(java.javaVersion);
|
||||
} else if (java.type == RULE_SUGGESTED) {
|
||||
suggestedJavaRange = suggestedJavaRange.intersectionWith(java.javaVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JavaVersion mandatory = null;
|
||||
JavaVersion suggested = null;
|
||||
for (JavaVersion javaVersion : JavaVersion.getJavas()) {
|
||||
// select the latest java version that this version accepts.
|
||||
if (mandatoryJavaRange.contains(javaVersion.getVersionNumber())) {
|
||||
if (mandatory == null) mandatory = javaVersion;
|
||||
else if (javaVersion.getVersionNumber().compareTo(mandatory.getVersionNumber()) > 0) {
|
||||
mandatory = javaVersion;
|
||||
}
|
||||
}
|
||||
if (suggestedJavaRange.contains(javaVersion.getVersionNumber())) {
|
||||
if (suggested == null) suggested = javaVersion;
|
||||
else if (javaVersion.getVersionNumber().compareTo(suggested.getVersionNumber()) > 0) {
|
||||
suggested = javaVersion;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (suggested != null) return suggested;
|
||||
else return mandatory;
|
||||
}
|
||||
|
||||
public static final int RULE_MANDATORY = 1;
|
||||
public static final int RULE_SUGGESTED = 2;
|
||||
|
||||
public static final String MIN = "0";
|
||||
public static final String MAX = "10000";
|
||||
|
||||
private static Range<VersionNumber> versionRange(String fromInclusive, String toExclusive) {
|
||||
return Range.between(VersionNumber.asVersion(fromInclusive), VersionNumber.asVersion(toExclusive));
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,6 @@ import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.*;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDownloadException;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.download.MaintainTask;
|
||||
import org.jackhuang.hmcl.download.game.GameAssetIndexDownloadTask;
|
||||
import org.jackhuang.hmcl.download.game.GameVerificationFixTask;
|
||||
@@ -64,9 +63,11 @@ import java.net.SocketTimeoutException;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
@@ -110,13 +111,10 @@ public final class LauncherHelper {
|
||||
Version version = repository.getResolvedVersion(selectedVersion);
|
||||
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
checkGameState(profile, setting, version, () -> {
|
||||
Controllers.dialog(launchingStepsPane);
|
||||
Schedulers.defaultScheduler().execute(this::launch0);
|
||||
});
|
||||
} catch (InterruptedException | RejectedExecutionException ignore) {
|
||||
}
|
||||
checkGameState(profile, setting, version, javaVersion -> {
|
||||
Controllers.dialog(launchingStepsPane);
|
||||
Schedulers.defaultScheduler().execute(() -> launch0(javaVersion));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -126,7 +124,7 @@ public final class LauncherHelper {
|
||||
launch();
|
||||
}
|
||||
|
||||
private void launch0() {
|
||||
private void launch0(JavaVersion javaVersion) {
|
||||
HMCLGameRepository repository = profile.getRepository();
|
||||
DefaultDependencyManager dependencyManager = profile.getDependency();
|
||||
Version version = MaintainTask.maintain(repository, repository.getResolvedVersion(selectedVersion));
|
||||
@@ -175,7 +173,7 @@ public final class LauncherHelper {
|
||||
}
|
||||
}).withStage("launch.state.logging_in"))
|
||||
.thenComposeAsync(authInfo -> Task.supplyAsync(() -> {
|
||||
LaunchOptions launchOptions = repository.getLaunchOptions(selectedVersion, profile.getGameDir(), !setting.isNotCheckJVM());
|
||||
LaunchOptions launchOptions = repository.getLaunchOptions(selectedVersion, javaVersion, profile.getGameDir());
|
||||
return new HMCLGameLauncher(
|
||||
repository,
|
||||
version,
|
||||
@@ -301,224 +299,204 @@ public final class LauncherHelper {
|
||||
executor.start();
|
||||
}
|
||||
|
||||
private static void checkGameState(Profile profile, VersionSetting setting, Version version, Runnable onAccept) throws InterruptedException {
|
||||
private static void checkGameState(Profile profile, VersionSetting setting, Version version, Consumer<JavaVersion> onAccept) {
|
||||
VersionNumber gameVersion = VersionNumber.asVersion(profile.getRepository().getGameVersion(version).orElse("Unknown"));
|
||||
|
||||
if (setting.isNotCheckJVM()) {
|
||||
onAccept.run();
|
||||
Task.composeAsync(() -> {
|
||||
return setting.getJavaVersion(gameVersion, version);
|
||||
}).thenAcceptAsync(javaVersion -> {
|
||||
onAccept.accept(Optional.ofNullable(javaVersion).orElseGet(JavaVersion::fromCurrentEnvironment));
|
||||
}).start();
|
||||
return;
|
||||
}
|
||||
|
||||
boolean javaChanged = false;
|
||||
boolean mayContinueAfterJavaChanged = false;
|
||||
boolean java8required = false;
|
||||
boolean newJavaRequired = false;
|
||||
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());
|
||||
|
||||
// Without onAccept called, the launching operation will be terminated.
|
||||
|
||||
VersionNumber gameVersion = VersionNumber.asVersion(profile.getRepository().getGameVersion(version).orElse("Unknown"));
|
||||
if (setting.getJavaVersion() == null) {
|
||||
Controllers.dialog(i18n("launch.wrong_javadir"), i18n("message.warning"), MessageType.WARNING, onAccept);
|
||||
setting.setJava(null);
|
||||
setting.setDefaultJavaPath(null);
|
||||
setting.setJavaVersion(JavaVersion.fromCurrentEnvironment());
|
||||
// continue java version checking
|
||||
}
|
||||
|
||||
// Check java version recorded in game json
|
||||
// We only checks for 1.7.10 and above, since 1.7.2 with Forge can only run on Java 7, but it is recorded Java 8 in game json, which is not correct.
|
||||
if (!javaChanged && version.getJavaVersion() != null && gameVersion.compareTo(VersionNumber.asVersion("1.7.10")) >= 0) {
|
||||
if (setting.getJavaVersion().getParsedVersion() < version.getJavaVersion().getMajorVersion()) {
|
||||
Optional<JavaVersion> acceptableJava = JavaVersion.getJavas().stream()
|
||||
.filter(javaVersion -> javaVersion.getParsedVersion() >= version.getJavaVersion().getMajorVersion())
|
||||
.max(Comparator.comparing(JavaVersion::getVersionNumber));
|
||||
if (acceptableJava.isPresent()) {
|
||||
setting.setJavaVersion(acceptableJava.get());
|
||||
mayContinueAfterJavaChanged = true;
|
||||
if (setting.isJavaAutoSelected()) {
|
||||
Controllers.dialog(i18n("launch.failed.no_accepted_java"), i18n("message.warning"), MessageType.WARNING, continueAction);
|
||||
} else {
|
||||
MessageDialogPane dialog = new MessageDialogPane(
|
||||
i18n("launch.advice.require_newer_java_version",
|
||||
gameVersion.toString(),
|
||||
version.getJavaVersion().getMajorVersion()),
|
||||
i18n("message.warning"),
|
||||
MessageType.QUESTION);
|
||||
Controllers.dialog(i18n("launch.wrong_javadir"), i18n("message.warning"), MessageType.WARNING, continueAction);
|
||||
|
||||
JFXButton linkButton = new JFXButton(i18n("download.external_link"));
|
||||
linkButton.setOnAction(e -> FXUtils.openLink("https://adoptopenjdk.net/"));
|
||||
linkButton.getStyleClass().add("dialog-accept");
|
||||
dialog.addButton(linkButton);
|
||||
setting.setJava(null);
|
||||
setting.setDefaultJavaPath(null);
|
||||
setting.setJavaVersion(JavaVersion.fromCurrentEnvironment());
|
||||
}
|
||||
|
||||
JFXButton yesButton = new JFXButton(i18n("button.ok"));
|
||||
yesButton.setOnAction(event -> {
|
||||
downloadJava(version.getJavaVersion(), profile)
|
||||
.thenAcceptAsync(x -> {
|
||||
try {
|
||||
Optional<JavaVersion> newAcceptableJava = JavaVersion.getJavas().stream()
|
||||
.filter(javaVersion -> javaVersion.getParsedVersion() >= version.getJavaVersion().getMajorVersion())
|
||||
.max(Comparator.comparing(JavaVersion::getVersionNumber));
|
||||
newAcceptableJava.ifPresent(setting::setJavaVersion);
|
||||
} catch (InterruptedException e) {
|
||||
LOG.log(Level.SEVERE, "Cannot list javas", e);
|
||||
}
|
||||
}, Platform::runLater).thenAccept(x -> onAccept.run());
|
||||
});
|
||||
yesButton.getStyleClass().add("dialog-accept");
|
||||
dialog.addButton(yesButton);
|
||||
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);
|
||||
|
||||
JFXButton noButton = new JFXButton(i18n("button.cancel"));
|
||||
noButton.getStyleClass().add("dialog-cancel");
|
||||
dialog.addButton(noButton);
|
||||
dialog.setCancelButton(noButton);
|
||||
JavaVersionConstraint violatedMandatoryConstraint = null;
|
||||
JavaVersionConstraint violatedSuggestedConstraint = null;
|
||||
for (JavaVersionConstraint constraint : JavaVersionConstraint.values()) {
|
||||
if (constraint.getGameVersion().contains(gameVersion) && constraint.appliesToVersion(gameVersion, version)) {
|
||||
if (!constraint.getJavaVersion(version).contains(javaVersion.getVersionNumber())) {
|
||||
if (constraint.getType() == JavaVersionConstraint.RULE_MANDATORY) {
|
||||
violatedMandatoryConstraint = constraint;
|
||||
} else if (constraint.getType() == JavaVersionConstraint.RULE_SUGGESTED) {
|
||||
violatedSuggestedConstraint = constraint;
|
||||
}
|
||||
}
|
||||
|
||||
Controllers.dialog(dialog);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Game later than 1.17 requires Java 16.
|
||||
if (!javaChanged && setting.getJavaVersion().getParsedVersion() < JavaVersion.JAVA_16 && gameVersion.compareTo(VersionNumber.asVersion("1.17")) >= 0) {
|
||||
Optional<JavaVersion> acceptableJava = JavaVersion.getJavas().stream()
|
||||
.filter(javaVersion -> javaVersion.getParsedVersion() >= JavaVersion.JAVA_16)
|
||||
.max(Comparator.comparing(JavaVersion::getVersionNumber));
|
||||
if (acceptableJava.isPresent()) {
|
||||
setting.setJavaVersion(acceptableJava.get());
|
||||
mayContinueAfterJavaChanged = true;
|
||||
} else {
|
||||
Controllers.confirm(i18n("launch.advice.require_newer_java_version", gameVersion.toString(), 16), i18n("message.warning"), () -> {
|
||||
FXUtils.openLink("https://adoptopenjdk.net/");
|
||||
}, null);
|
||||
}
|
||||
javaChanged = true;
|
||||
}
|
||||
boolean suggested = false;
|
||||
CompletableFuture<JavaVersion> future = new CompletableFuture<>();
|
||||
Runnable continueAction = () -> future.complete(javaVersion);
|
||||
Runnable breakAction = () -> {
|
||||
future.completeExceptionally(new CancellationException("Launch operation was cancelled by user"));
|
||||
};
|
||||
|
||||
// Game later than 1.7.2 accepts Java 8.
|
||||
if (!javaChanged && setting.getJavaVersion().getParsedVersion() < JavaVersion.JAVA_8 && gameVersion.compareTo(VersionNumber.asVersion("1.7.2")) > 0) {
|
||||
Optional<JavaVersion> java8 = JavaVersion.getJavas().stream()
|
||||
.filter(javaVersion -> javaVersion.getParsedVersion() >= JavaVersion.JAVA_8)
|
||||
.max(Comparator.comparing(JavaVersion::getVersionNumber));
|
||||
if (java8.isPresent()) {
|
||||
newJavaRequired = true;
|
||||
setting.setJavaVersion(java8.get());
|
||||
} else {
|
||||
if (gameVersion.compareTo(VersionNumber.asVersion("1.13")) >= 0) {
|
||||
// Minecraft 1.13 and later versions only support Java 8 or later.
|
||||
// Terminate launching operation.
|
||||
Controllers.dialog(i18n("launch.advice.java8_1_13"), i18n("message.error"), MessageType.ERROR, null);
|
||||
if (violatedMandatoryConstraint != null) {
|
||||
if (suggestedJavaVersion != null) {
|
||||
Controllers.confirm(i18n("launch.advice.java.auto"), i18n("message.warning"), () -> {
|
||||
setting.setJavaAutoSelected();
|
||||
future.complete(suggestedJavaVersion);
|
||||
}, breakAction);
|
||||
return Task.fromCompletableFuture(future);
|
||||
} else {
|
||||
// Most mods require Java 8 or later version.
|
||||
Controllers.dialog(i18n("launch.advice.newer_java"), i18n("message.warning"), MessageType.WARNING, onAccept);
|
||||
switch (violatedMandatoryConstraint) {
|
||||
case GAME_JSON:
|
||||
MessageDialogPane dialog = new MessageDialogPane(
|
||||
i18n("launch.advice.require_newer_java_version",
|
||||
gameVersion.toString(),
|
||||
version.getJavaVersion().getMajorVersion()),
|
||||
i18n("message.warning"),
|
||||
MessageType.QUESTION);
|
||||
|
||||
JFXButton linkButton = new JFXButton(i18n("download.external_link"));
|
||||
linkButton.setOnAction(e -> FXUtils.openLink("https://adoptopenjdk.net/"));
|
||||
linkButton.getStyleClass().add("dialog-accept");
|
||||
dialog.addButton(linkButton);
|
||||
|
||||
JFXButton yesButton = new JFXButton(i18n("button.ok"));
|
||||
yesButton.setOnAction(event -> {
|
||||
downloadJava(version.getJavaVersion(), profile)
|
||||
.thenAcceptAsync(x -> {
|
||||
try {
|
||||
Optional<JavaVersion> newAcceptableJava = JavaVersion.getJavas().stream()
|
||||
.filter(newJava -> newJava.getParsedVersion() >= version.getJavaVersion().getMajorVersion())
|
||||
.max(Comparator.comparing(JavaVersion::getVersionNumber));
|
||||
if (newAcceptableJava.isPresent()) {
|
||||
setting.setJavaVersion(newAcceptableJava.get());
|
||||
future.complete(newAcceptableJava.get());
|
||||
return;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
LOG.log(Level.SEVERE, "Cannot list javas", e);
|
||||
}
|
||||
future.complete(javaVersion);
|
||||
}, Platform::runLater)
|
||||
.exceptionally(Lang.handleUncaught);
|
||||
});
|
||||
yesButton.getStyleClass().add("dialog-accept");
|
||||
dialog.addButton(yesButton);
|
||||
|
||||
JFXButton noButton = new JFXButton(i18n("button.cancel"));
|
||||
noButton.getStyleClass().add("dialog-cancel");
|
||||
dialog.addButton(noButton);
|
||||
dialog.setCancelButton(noButton);
|
||||
|
||||
Controllers.dialog(dialog);
|
||||
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("https://adoptopenjdk.net/");
|
||||
}, breakAction);
|
||||
return null;
|
||||
case VANILLA_JAVA_8:
|
||||
Controllers.dialog(i18n("launch.advice.java8_1_13"), i18n("message.error"), MessageType.ERROR, breakAction);
|
||||
return null;
|
||||
case LAUNCH_WRAPPER:
|
||||
Controllers.dialog(i18n("launch.advice.java9") + "\n" + i18n("launch.advice.uncorrected"), i18n("message.error"), MessageType.ERROR, breakAction);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
javaChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
// LaunchWrapper 1.12 will crash because of assuming the system class loader is an instance of URLClassLoader.
|
||||
if (!javaChanged && setting.getJavaVersion().getParsedVersion() >= JavaVersion.JAVA_9
|
||||
&& gameVersion.compareTo(VersionNumber.asVersion("1.13")) < 0
|
||||
&& LibraryAnalyzer.LAUNCH_WRAPPER_MAIN.equals(version.getMainClass())
|
||||
&& version.getLibraries().stream()
|
||||
.filter(library -> "launchwrapper".equals(library.getArtifactId()))
|
||||
.anyMatch(library -> VersionNumber.asVersion(library.getVersion()).compareTo(VersionNumber.asVersion("1.13")) < 0)) {
|
||||
Optional<JavaVersion> java8 = JavaVersion.getJavas().stream().filter(javaVersion -> javaVersion.getParsedVersion() == JavaVersion.JAVA_8).findAny();
|
||||
if (java8.isPresent()) {
|
||||
java8required = true;
|
||||
setting.setJavaVersion(java8.get());
|
||||
Controllers.dialog(i18n("launch.advice.java9") + "\n" + i18n("launch.advice.corrected"), i18n("message.info"), MessageType.INFO, onAccept);
|
||||
javaChanged = true;
|
||||
} else {
|
||||
Controllers.dialog(i18n("launch.advice.java9") + "\n" + i18n("launch.advice.uncorrected"), i18n("message.error"), MessageType.ERROR, null);
|
||||
javaChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Minecraft 1.13 may crash when generating world on Java 8 earlier than 1.8.0_51
|
||||
VersionNumber JAVA_8 = VersionNumber.asVersion("1.8.0_51");
|
||||
if (!javaChanged && gameVersion.compareTo(VersionNumber.asVersion("1.13")) >= 0 && setting.getJavaVersion().getParsedVersion() == JavaVersion.JAVA_8 && setting.getJavaVersion().getVersionNumber().compareTo(JAVA_8) < 0) {
|
||||
Optional<JavaVersion> java8 = JavaVersion.getJavas().stream()
|
||||
.filter(javaVersion -> javaVersion.getVersionNumber().compareTo(JAVA_8) >= 0)
|
||||
.max(Comparator.comparing(JavaVersion::getVersionNumber));
|
||||
if (java8.isPresent()) {
|
||||
newJavaRequired = true;
|
||||
setting.setJavaVersion(java8.get());
|
||||
} else {
|
||||
Controllers.dialog(i18n("launch.advice.java8_51_1_13"), i18n("message.warning"), MessageType.WARNING, onAccept);
|
||||
javaChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!javaChanged && setting.getJavaVersion().getPlatform() == org.jackhuang.hmcl.util.platform.Platform.BIT_32 &&
|
||||
Architecture.CURRENT.getPlatform() == org.jackhuang.hmcl.util.platform.Platform.BIT_64) {
|
||||
final JavaVersion java32 = setting.getJavaVersion();
|
||||
|
||||
// First find if same java version but whose platform is 64-bit installed.
|
||||
Optional<JavaVersion> java64 = JavaVersion.getJavas().stream()
|
||||
.filter(javaVersion -> javaVersion.getPlatform() == org.jackhuang.hmcl.util.platform.Platform.getPlatform())
|
||||
.filter(javaVersion -> javaVersion.getParsedVersion() == java32.getParsedVersion())
|
||||
.max(Comparator.comparing(JavaVersion::getVersionNumber));
|
||||
|
||||
if (!java64.isPresent()) {
|
||||
final boolean java8requiredFinal = java8required, newJavaRequiredFinal = newJavaRequired;
|
||||
|
||||
// Then find if other java version which satisfies requirements installed.
|
||||
java64 = JavaVersion.getJavas().stream()
|
||||
.filter(javaVersion -> javaVersion.getPlatform() == org.jackhuang.hmcl.util.platform.Platform.getPlatform())
|
||||
.filter(javaVersion -> {
|
||||
if (java8requiredFinal) return javaVersion.getParsedVersion() == JavaVersion.JAVA_8;
|
||||
if (newJavaRequiredFinal) return javaVersion.getParsedVersion() >= JavaVersion.JAVA_8;
|
||||
return true;
|
||||
})
|
||||
.max(Comparator.comparing(JavaVersion::getVersionNumber));
|
||||
}
|
||||
|
||||
if (java64.isPresent()) {
|
||||
setting.setJavaVersion(java64.get());
|
||||
} else {
|
||||
Controllers.dialog(i18n("launch.advice.different_platform"), i18n("message.error"), MessageType.ERROR, onAccept);
|
||||
javaChanged = true;
|
||||
// 32-bit JVM cannot make use of too much memory.
|
||||
if (javaVersion.getPlatform() == org.jackhuang.hmcl.util.platform.Platform.BIT_32 &&
|
||||
setting.getMaxMemory() > 1.5 * 1024) {
|
||||
// 1.5 * 1024 is an inaccurate number.
|
||||
// Actual memory limit depends on operating system and memory.
|
||||
Controllers.confirm(i18n("launch.advice.too_large_memory_for_32bit"), i18n("message.error"), continueAction, null);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 32-bit JVM cannot make use of too much memory.
|
||||
if (!javaChanged && setting.getJavaVersion().getPlatform() == org.jackhuang.hmcl.util.platform.Platform.BIT_32 &&
|
||||
setting.getMaxMemory() > 1.5 * 1024) {
|
||||
// 1.5 * 1024 is an inaccurate number.
|
||||
// Actual memory limit depends on operating system and memory.
|
||||
Controllers.confirm(i18n("launch.advice.too_large_memory_for_32bit"), i18n("message.error"), onAccept, null);
|
||||
javaChanged = true;
|
||||
}
|
||||
|
||||
// Cannot allocate too much memory exceeding free space.
|
||||
if (!javaChanged && OperatingSystem.TOTAL_MEMORY > 0 && OperatingSystem.TOTAL_MEMORY < setting.getMaxMemory()) {
|
||||
Controllers.confirm(i18n("launch.advice.not_enough_space", OperatingSystem.TOTAL_MEMORY), i18n("message.error"), onAccept, null);
|
||||
javaChanged = true;
|
||||
}
|
||||
|
||||
// Forge 2760~2773 will crash game with LiteLoader.
|
||||
if (!javaChanged) {
|
||||
boolean hasForge2760 = version.getLibraries().stream().filter(it -> it.is("net.minecraftforge", "forge"))
|
||||
.anyMatch(it ->
|
||||
VersionNumber.VERSION_COMPARATOR.compare("1.12.2-14.23.5.2760", it.getVersion()) <= 0 &&
|
||||
VersionNumber.VERSION_COMPARATOR.compare(it.getVersion(), "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(VersionNumber.asVersion("1.12.2")) == 0) {
|
||||
Controllers.confirm(i18n("launch.advice.forge2760_liteloader"), i18n("message.error"), onAccept, null);
|
||||
javaChanged = true;
|
||||
if (violatedSuggestedConstraint != null) {
|
||||
suggested = true;
|
||||
switch (violatedSuggestedConstraint) {
|
||||
case MODDED_JAVA_7:
|
||||
Controllers.dialog(i18n("launch.advice.java.modded_java_7"), i18n("message.error"), MessageType.ERROR, continueAction);
|
||||
return null;
|
||||
case MODDED_JAVA_8:
|
||||
Controllers.dialog(i18n("launch.advice.newer_java"), i18n("message.warning"), MessageType.WARNING, continueAction);
|
||||
break;
|
||||
case VANILLA_JAVA_8_51:
|
||||
Controllers.dialog(i18n("launch.advice.java8_51_1_13"), i18n("message.warning"), MessageType.WARNING, continueAction);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OptiFine 1.14.4 is not compatible with Forge 28.2.2 and later versions.
|
||||
if (!javaChanged) {
|
||||
boolean hasForge28_2_2 = version.getLibraries().stream().filter(it -> it.is("net.minecraftforge", "forge"))
|
||||
.anyMatch(it ->
|
||||
VersionNumber.VERSION_COMPARATOR.compare("1.14.4-28.2.2", it.getVersion()) <= 0);
|
||||
boolean hasOptiFine = version.getLibraries().stream().anyMatch(it -> it.is("optifine", "OptiFine"));
|
||||
if (hasForge28_2_2 && hasOptiFine && gameVersion.compareTo(VersionNumber.asVersion("1.14.4")) == 0) {
|
||||
Controllers.confirm(i18n("launch.advice.forge28_2_2_optifine"), i18n("message.error"), onAccept, null);
|
||||
javaChanged = true;
|
||||
if (!suggested && javaVersion.getPlatform() != Architecture.CURRENT.getPlatform()) {
|
||||
Controllers.dialog(i18n("launch.advice.different_platform"), i18n("message.warning"), MessageType.ERROR, continueAction);
|
||||
suggested = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!javaChanged || mayContinueAfterJavaChanged)
|
||||
onAccept.run();
|
||||
// Cannot allocate too much memory exceeding free space.
|
||||
if (!suggested && OperatingSystem.TOTAL_MEMORY > 0 && OperatingSystem.TOTAL_MEMORY < setting.getMaxMemory()) {
|
||||
Controllers.confirm(i18n("launch.advice.not_enough_space", OperatingSystem.TOTAL_MEMORY), i18n("message.error"), continueAction, null);
|
||||
suggested = true;
|
||||
}
|
||||
|
||||
// Forge 2760~2773 will crash game with LiteLoader.
|
||||
if (!suggested) {
|
||||
boolean hasForge2760 = version.getLibraries().stream().filter(it -> it.is("net.minecraftforge", "forge"))
|
||||
.anyMatch(it ->
|
||||
VersionNumber.VERSION_COMPARATOR.compare("1.12.2-14.23.5.2760", it.getVersion()) <= 0 &&
|
||||
VersionNumber.VERSION_COMPARATOR.compare(it.getVersion(), "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(VersionNumber.asVersion("1.12.2")) == 0) {
|
||||
Controllers.confirm(i18n("launch.advice.forge2760_liteloader"), i18n("message.error"), continueAction, null);
|
||||
suggested = true;
|
||||
}
|
||||
}
|
||||
|
||||
// OptiFine 1.14.4 is not compatible with Forge 28.2.2 and later versions.
|
||||
if (!suggested) {
|
||||
boolean hasForge28_2_2 = version.getLibraries().stream().filter(it -> it.is("net.minecraftforge", "forge"))
|
||||
.anyMatch(it ->
|
||||
VersionNumber.VERSION_COMPARATOR.compare("1.14.4-28.2.2", it.getVersion()) <= 0);
|
||||
boolean hasOptiFine = version.getLibraries().stream().anyMatch(it -> it.is("optifine", "OptiFine"));
|
||||
if (hasForge28_2_2 && hasOptiFine && gameVersion.compareTo(VersionNumber.asVersion("1.14.4")) == 0) {
|
||||
Controllers.confirm(i18n("launch.advice.forge28_2_2_optifine"), i18n("message.error"), continueAction, null);
|
||||
suggested = true;
|
||||
}
|
||||
}
|
||||
|
||||
return Task.fromCompletableFuture(future);
|
||||
}).thenAcceptAsync(Schedulers.javafx(), javaVersion -> {
|
||||
if (javaVersion == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
onAccept.accept(javaVersion);
|
||||
}).start();
|
||||
}
|
||||
|
||||
private static CompletableFuture<Void> downloadJava(GameJavaVersion javaVersion, Profile profile) {
|
||||
|
||||
@@ -22,6 +22,7 @@ import com.google.gson.annotations.JsonAdapter;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.property.*;
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
@@ -36,7 +37,6 @@ import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -125,6 +125,10 @@ public final class VersionSetting implements Cloneable {
|
||||
return defaultJavaPathProperty.get();
|
||||
}
|
||||
|
||||
public StringProperty defaultJavaPathPropertyProperty() {
|
||||
return defaultJavaPathProperty;
|
||||
}
|
||||
|
||||
public void setDefaultJavaPath(String defaultJavaPath) {
|
||||
defaultJavaPathProperty.set(defaultJavaPath);
|
||||
}
|
||||
@@ -581,19 +585,19 @@ public final class VersionSetting implements Cloneable {
|
||||
launcherVisibilityProperty.set(launcherVisibility);
|
||||
}
|
||||
|
||||
public CompletableFuture<JavaVersion> getJavaVersion(String gameVersion, Version version) {
|
||||
public Task<JavaVersion> getJavaVersion(VersionNumber gameVersion, Version version) {
|
||||
return getJavaVersion(gameVersion, version, true);
|
||||
}
|
||||
|
||||
public CompletableFuture<JavaVersion> getJavaVersion(String gameVersion, Version version, boolean checkJava) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
public Task<JavaVersion> getJavaVersion(VersionNumber gameVersion, Version version, boolean checkJava) {
|
||||
return Task.supplyAsync(() -> {
|
||||
try {
|
||||
if (StringUtils.isBlank(getJava()))
|
||||
setJava(StringUtils.isBlank(getJavaDir()) ? "Default" : "Custom");
|
||||
if ("Default".equals(getJava())) {
|
||||
return JavaVersion.fromCurrentEnvironment();
|
||||
} else if (isJavaAutoSelected()) {
|
||||
return HMCLJavaVersion.findSuitableJavaVersion(VersionNumber.asVersion(gameVersion), version);
|
||||
return JavaVersionConstraint.findSuitableJavaVersion(gameVersion, version);
|
||||
} else if (isUsesCustomJavaDir()) {
|
||||
try {
|
||||
if (checkJava)
|
||||
|
||||
@@ -116,6 +116,7 @@ public class MultiFileItem<T> extends VBox {
|
||||
protected String subtitle;
|
||||
protected final T data;
|
||||
protected final BooleanProperty selected = new SimpleBooleanProperty();
|
||||
protected final JFXRadioButton left = new JFXRadioButton();
|
||||
|
||||
public Option(String title, T data) {
|
||||
this.title = title;
|
||||
@@ -140,15 +141,15 @@ public class MultiFileItem<T> extends VBox {
|
||||
}
|
||||
|
||||
public boolean isSelected() {
|
||||
return selected.get();
|
||||
return left.isSelected();
|
||||
}
|
||||
|
||||
public BooleanProperty selectedProperty() {
|
||||
return selected;
|
||||
return left.selectedProperty();
|
||||
}
|
||||
|
||||
public void setSelected(boolean selected) {
|
||||
this.selected.set(selected);
|
||||
left.setSelected(selected);
|
||||
}
|
||||
|
||||
protected Node createItem(ToggleGroup group) {
|
||||
@@ -156,11 +157,10 @@ public class MultiFileItem<T> extends VBox {
|
||||
pane.setPadding(new Insets(3));
|
||||
FXUtils.setLimitHeight(pane, 30);
|
||||
|
||||
JFXRadioButton left = new JFXRadioButton(title);
|
||||
left.setText(title);
|
||||
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
|
||||
left.setToggleGroup(group);
|
||||
left.setUserData(data);
|
||||
selected.bind(left.selectedProperty());
|
||||
pane.setLeft(left);
|
||||
|
||||
if (StringUtils.isNotBlank(subtitle)) {
|
||||
@@ -218,11 +218,10 @@ public class MultiFileItem<T> extends VBox {
|
||||
pane.setPadding(new Insets(3));
|
||||
FXUtils.setLimitHeight(pane, 30);
|
||||
|
||||
JFXRadioButton left = new JFXRadioButton(title);
|
||||
left.setText(title);
|
||||
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
|
||||
left.setToggleGroup(group);
|
||||
left.setUserData(data);
|
||||
selected.bind(left.selectedProperty());
|
||||
pane.setLeft(left);
|
||||
|
||||
BorderPane.setAlignment(customField, Pos.CENTER_RIGHT);
|
||||
@@ -277,11 +276,10 @@ public class MultiFileItem<T> extends VBox {
|
||||
pane.setPadding(new Insets(3));
|
||||
FXUtils.setLimitHeight(pane, 30);
|
||||
|
||||
JFXRadioButton left = new JFXRadioButton(title);
|
||||
left.setText(title);
|
||||
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
|
||||
left.setToggleGroup(group);
|
||||
left.setUserData(data);
|
||||
selected.bind(left.selectedProperty());
|
||||
pane.setLeft(left);
|
||||
|
||||
selector.disableProperty().bind(left.selectedProperty().not());
|
||||
|
||||
@@ -31,10 +31,7 @@ import javafx.scene.image.Image;
|
||||
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.NativesDirectoryType;
|
||||
import org.jackhuang.hmcl.game.ProcessPriority;
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.setting.LauncherVisibility;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
@@ -53,6 +50,7 @@ import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -674,6 +672,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
});
|
||||
|
||||
versionSetting.javaDirProperty().addListener(javaListener);
|
||||
versionSetting.defaultJavaPathPropertyProperty().addListener(javaListener);
|
||||
versionSetting.javaProperty().addListener(javaListener);
|
||||
|
||||
gameDirItem.selectedDataProperty().bindBidirectional(versionSetting.gameDirTypeProperty());
|
||||
@@ -686,7 +685,6 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
|
||||
lastVersionSetting = versionSetting;
|
||||
|
||||
initializeSelectedJava();
|
||||
initJavaSubtitle();
|
||||
|
||||
loadIcon();
|
||||
@@ -699,26 +697,44 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
}
|
||||
|
||||
if (lastVersionSetting.isUsesCustomJavaDir()) {
|
||||
javaItem.getGroup().getToggles().stream()
|
||||
.filter(java -> java.getUserData() == null)
|
||||
.findFirst().get()
|
||||
.setSelected(true);
|
||||
javaCustomOption.setSelected(true);
|
||||
} else if (lastVersionSetting.isJavaAutoSelected()) {
|
||||
javaAutoDeterminedOption.setSelected(true);
|
||||
} else {
|
||||
try {
|
||||
javaItem.setSelectedData(lastVersionSetting.getJavaVersion());
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
// javaLoading.set(true);
|
||||
lastVersionSetting.getJavaVersion(null, null)
|
||||
.thenAcceptAsync(Schedulers.javafx(), javaVersion -> {
|
||||
javaItem.setSelectedData(javaVersion);
|
||||
// javaLoading.set(false);
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
private void initJavaSubtitle() {
|
||||
FXUtils.checkFxUserThread();
|
||||
initializeSelectedJava();
|
||||
VersionSetting versionSetting = lastVersionSetting;
|
||||
if (versionSetting == null)
|
||||
return;
|
||||
Task.fromCompletableFuture(versionSetting.getJavaVersion())
|
||||
.thenAcceptAsync(Schedulers.javafx(), javaVersion -> javaSublist.setSubtitle(Optional.ofNullable(javaVersion)
|
||||
.map(JavaVersion::getBinary).map(Path::toString).orElse("Invalid Java Path")))
|
||||
Profile profile = this.profile;
|
||||
String versionId = this.versionId;
|
||||
boolean autoSelected = versionSetting.isJavaAutoSelected();
|
||||
|
||||
if (autoSelected && versionId == null) {
|
||||
javaSublist.setSubtitle(i18n("settings.game.java_directory.auto"));
|
||||
return;
|
||||
}
|
||||
|
||||
Task.composeAsync(Schedulers.javafx(), () -> {
|
||||
if (versionId == null) {
|
||||
return versionSetting.getJavaVersion(VersionNumber.asVersion("Unknown"), null);
|
||||
} else {
|
||||
return versionSetting.getJavaVersion(
|
||||
VersionNumber.asVersion(GameVersion.minecraftVersion(profile.getRepository().getVersionJar(versionId)).orElse("Unknown")),
|
||||
profile.getRepository().getVersion(versionId));
|
||||
}
|
||||
}).thenAcceptAsync(Schedulers.javafx(), javaVersion -> javaSublist.setSubtitle(Optional.ofNullable(javaVersion)
|
||||
.map(JavaVersion::getBinary).map(Path::toString).orElse("Invalid Java Path")))
|
||||
.start();
|
||||
}
|
||||
|
||||
|
||||
@@ -405,6 +405,8 @@ install.success=successfully installed
|
||||
lang=English
|
||||
lang.default=Use system language
|
||||
|
||||
launch.advice.java.auto=Currently selected Java VM does not fulfill the requirement of game. Do you allow us to selecting a suitable Java VM? Or you can select a proper Java VM in game settings.
|
||||
launch.advice.java.modded_java_7=Minecraft 1.7.2 or earlier requires Java 7 or earlier version.
|
||||
launch.advice.corrected=We have already fixed the JVM selection. If you want to keep your choice of Java version, you can disable the Java VM check in game settings.
|
||||
launch.advice.uncorrected=If you are sure that the game can be started normally, you can disable the Java VM check in game settings.
|
||||
launch.advice.different_platform=Your OS is 64-Bit but your Java is 32-Bit. The 64-Bit Java is recommended.
|
||||
|
||||
@@ -405,6 +405,8 @@ install.success=安裝成功
|
||||
lang=正體中文
|
||||
lang.default=使用系統語言
|
||||
|
||||
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.different_platform=您的系統為 64 位元,但是 Java 是 32 位元,建議您安裝 64 位 Java。
|
||||
|
||||
@@ -349,7 +349,7 @@ game.crash.reason.openj9=当前游戏无法运行在 OpenJ9 虚拟机上,请
|
||||
game.crash.reason.out_of_memory=当前游戏因为内存不足,无法继续运行。\n这可能是内存分配太小,或者 Mod 数量过多导致的。\n你可以在游戏设置中调大游戏内存分配值以允许游戏在更大的内存下运行。\n如果仍然出现该错误,你可能需要换一台更好的电脑。
|
||||
game.crash.reason.resolution_too_high=当前游戏因为材质包分辨率过高,无法继续运行\n你可以更换一个分辨率更低的材质,或者更换一个显存更大的显卡。
|
||||
game.crash.reason.stacktrace=原因未知,请点击日志按钮查看详细信息。\n下面是一些关键词,其中可能包含 Mod 名称,你可以通过搜索的方式查找有关信息。\n%s
|
||||
game.crash.reason.too_old_java=当前游戏因为 Java 虚拟机版本过低,无法继续运行。\n你需要在游戏设置中更换 %1$s 或更新版本的 Java 虚拟机,并重新启动游戏。如果没有下载安装,你可以在网上自行下载。
|
||||
game.crash.reason.too_old_java=当前游戏因为 Java 虚拟机版本过低,无法继续运行。\n你需要在游戏设置中更换 Java %1$s 或更新版本的 Java 虚拟机,并重新启动游戏。如果没有下载安装,你可以在 https://adoptopenjdk.net 上自行下载。
|
||||
game.crash.reason.unknown=原因未知,请点击日志按钮查看详细信息。
|
||||
game.crash.title=游戏意外退出
|
||||
game.directory=游戏路径
|
||||
@@ -405,7 +405,9 @@ install.success=安装成功
|
||||
lang=简体中文
|
||||
lang.default=跟随系统语言
|
||||
|
||||
launch.advice.corrected=我们已经修复了问题。如果您确实希望使用您自定义的 Java 虚拟机,您可以在游戏设置中关闭 Java 虚拟机兼容性检查。
|
||||
launch.advice.java.auto=当前选择的 Java 虚拟机版本不满足游戏要求。是否自动选择合适的 Java 虚拟机版本?或者你可以到游戏设置中选择一个合适的 Java 虚拟机版本。
|
||||
launch.advice.java.modded_java_7=Minecraft 1.7.2 及以下版本需要 Java 7 及以下版本。
|
||||
launch.advice.corrected=我们已经修复了 Java 虚拟机版本问题。如果您确实希望使用您自定义的 Java 虚拟机,您可以在游戏设置中关闭 Java 虚拟机兼容性检查。
|
||||
launch.advice.uncorrected=如果您确实希望使用您自定义的 Java 虚拟机,您可以在游戏设置中关闭 Java 虚拟机兼容性检查。
|
||||
launch.advice.different_platform=您的系统是 64 位,但是 Java 是 32 位的,建议您安装 64 位 Java。
|
||||
launch.advice.forge2760_liteloader=Forge 2760 与 LiteLoader 不兼容。请删除 LiteLoader 或者更换 Forge 至 2773 或更新的版本。是否继续启动?
|
||||
@@ -768,6 +770,7 @@ settings.game.java_directory=Java 路径
|
||||
settings.game.java_directory.auto=自动选择合适的 Java
|
||||
settings.game.java_directory.bit=,%s 位
|
||||
settings.game.java_directory.choose=选择 Java 路径
|
||||
settings.game.
|
||||
settings.game.management=管理
|
||||
settings.game.working_directory=版本隔离(修改后请自行移动相关游戏文件,如存档模组配置等)
|
||||
settings.game.working_directory.choose=选择运行路径
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.Range;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LAUNCH_WRAPPER_MAIN;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
public enum JavaVersionConstraint {
|
||||
|
||||
// Minecraft>=1.17 requires Java 16
|
||||
VANILLA_JAVA_16(JavaVersionConstraint.RULE_MANDATORY, versionRange("1.17", JavaVersionConstraint.MAX), versionRange("16", JavaVersionConstraint.MAX)),
|
||||
// Minecraft>=1.13 requires Java 8
|
||||
VANILLA_JAVA_8(JavaVersionConstraint.RULE_MANDATORY, versionRange("1.13", JavaVersionConstraint.MAX), versionRange("1.8", JavaVersionConstraint.MAX)),
|
||||
// Minecraft>=1.7.10+Forge accepts Java 8
|
||||
MODDED_JAVA_8(JavaVersionConstraint.RULE_SUGGESTED, versionRange("1.7.10", JavaVersionConstraint.MAX), versionRange("1.8", JavaVersionConstraint.MAX)),
|
||||
// Minecraft<=1.7.2+Forge requires Java<=7
|
||||
MODDED_JAVA_7(JavaVersionConstraint.RULE_SUGGESTED, versionRange(JavaVersionConstraint.MIN, "1.7.2"), versionRange(JavaVersionConstraint.MIN, "1.7.999")) {
|
||||
@Override
|
||||
public boolean appliesToVersion(@Nullable VersionNumber gameVersion, @Nullable Version version) {
|
||||
if (version == null) return false;
|
||||
return LAUNCH_WRAPPER_MAIN.equals(version.getMainClass());
|
||||
}
|
||||
},
|
||||
// LaunchWrapper<=1.12 will crash because of assuming the system class loader is an instance of URLClassLoader (Java 8)
|
||||
LAUNCH_WRAPPER(JavaVersionConstraint.RULE_MANDATORY, versionRange("0", "1.12"), versionRange("0", "1.8")) {
|
||||
@Override
|
||||
public boolean appliesToVersion(VersionNumber gameVersion, Version version) {
|
||||
if (version == null) return false;
|
||||
return LAUNCH_WRAPPER_MAIN.equals(version.getMainClass()) &&
|
||||
version.getLibraries().stream()
|
||||
.filter(library -> "launchwrapper".equals(library.getArtifactId()))
|
||||
.anyMatch(library -> VersionNumber.asVersion(library.getVersion()).compareTo(VersionNumber.asVersion("1.13")) < 0);
|
||||
}
|
||||
},
|
||||
// Minecraft>=1.13 may crash when generating world on Java [1.8,1.8.0_51)
|
||||
VANILLA_JAVA_8_51(JavaVersionConstraint.RULE_SUGGESTED, versionRange("1.13", JavaVersionConstraint.MAX), versionRange("1.8.0_51", JavaVersionConstraint.MAX)),
|
||||
// Minecraft with suggested java version recorded in game json is restrictedly constrained.
|
||||
GAME_JSON(JavaVersionConstraint.RULE_MANDATORY, versionRange(JavaVersionConstraint.MIN, JavaVersionConstraint.MAX), versionRange(JavaVersionConstraint.MIN, JavaVersionConstraint.MAX)) {
|
||||
@Override
|
||||
public boolean appliesToVersion(VersionNumber gameVersion, Version version) {
|
||||
if (gameVersion == null || version == null) return false;
|
||||
// We only checks for 1.7.10 and above, since 1.7.2 with Forge can only run on Java 7, but it is recorded Java 8 in game json, which is not correct.
|
||||
return gameVersion.compareTo(VersionNumber.asVersion("1.7.10")) >= 0 && version.getJavaVersion() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Range<VersionNumber> getJavaVersion(Version version) {
|
||||
String javaVersion;
|
||||
if (Objects.requireNonNull(version.getJavaVersion()).getMajorVersion() >= 9) {
|
||||
javaVersion = "" + version.getJavaVersion().getMajorVersion();
|
||||
} else {
|
||||
javaVersion = "1." + version.getJavaVersion().getMajorVersion();
|
||||
}
|
||||
return JavaVersionConstraint.versionRange(javaVersion, JavaVersionConstraint.MAX);
|
||||
}
|
||||
},
|
||||
;
|
||||
|
||||
private final int type;
|
||||
private final Range<VersionNumber> gameVersion;
|
||||
private final Range<VersionNumber> javaVersion;
|
||||
|
||||
JavaVersionConstraint(int type, Range<VersionNumber> gameVersion, Range<VersionNumber> javaVersion) {
|
||||
this.type = type;
|
||||
this.gameVersion = gameVersion;
|
||||
this.javaVersion = javaVersion;
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Range<VersionNumber> getGameVersion() {
|
||||
return gameVersion;
|
||||
}
|
||||
|
||||
public Range<VersionNumber> getJavaVersion(Version version) {
|
||||
return javaVersion;
|
||||
}
|
||||
|
||||
public boolean appliesToVersion(@Nullable VersionNumber gameVersion, @Nullable Version version) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static Pair<Range<VersionNumber>, Range<VersionNumber>> findSuitableJavaVersionRange(VersionNumber gameVersion, Version version) {
|
||||
Range<VersionNumber> mandatoryJavaRange = versionRange(MIN, MAX);
|
||||
Range<VersionNumber> suggestedJavaRange = versionRange(MIN, MAX);
|
||||
for (JavaVersionConstraint java : values()) {
|
||||
if (java.gameVersion.contains(gameVersion) && java.appliesToVersion(gameVersion, version)) {
|
||||
Range<VersionNumber> javaVersionRange = java.getJavaVersion(version);
|
||||
if (java.type == RULE_MANDATORY) {
|
||||
mandatoryJavaRange = mandatoryJavaRange.intersectionWith(javaVersionRange);
|
||||
suggestedJavaRange = suggestedJavaRange.intersectionWith(javaVersionRange);
|
||||
} else if (java.type == RULE_SUGGESTED) {
|
||||
suggestedJavaRange = suggestedJavaRange.intersectionWith(javaVersionRange);
|
||||
}
|
||||
}
|
||||
}
|
||||
return pair(mandatoryJavaRange, suggestedJavaRange);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static JavaVersion findSuitableJavaVersion(VersionNumber gameVersion, Version version) throws InterruptedException {
|
||||
Pair<Range<VersionNumber>, Range<VersionNumber>> range = findSuitableJavaVersionRange(gameVersion, version);
|
||||
Range<VersionNumber> mandatoryJavaRange = range.getKey();
|
||||
Range<VersionNumber> suggestedJavaRange = range.getValue();
|
||||
|
||||
JavaVersion mandatory = null;
|
||||
JavaVersion suggested = null;
|
||||
for (JavaVersion javaVersion : JavaVersion.getJavas()) {
|
||||
// select the latest java version that this version accepts.
|
||||
if (mandatoryJavaRange.contains(javaVersion.getVersionNumber())) {
|
||||
if (mandatory == null) mandatory = javaVersion;
|
||||
else if (javaVersion.getVersionNumber().compareTo(mandatory.getVersionNumber()) > 0) {
|
||||
mandatory = javaVersion;
|
||||
}
|
||||
}
|
||||
if (suggestedJavaRange.contains(javaVersion.getVersionNumber())) {
|
||||
if (suggested == null) suggested = javaVersion;
|
||||
else if (javaVersion.getVersionNumber().compareTo(suggested.getVersionNumber()) > 0) {
|
||||
suggested = javaVersion;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (suggested != null) return suggested;
|
||||
else return mandatory;
|
||||
}
|
||||
|
||||
public static final int RULE_MANDATORY = 1;
|
||||
public static final int RULE_SUGGESTED = 2;
|
||||
|
||||
public static final String MIN = "0";
|
||||
public static final String MAX = "10000";
|
||||
|
||||
private static Range<VersionNumber> versionRange(String fromInclusive, String toExclusive) {
|
||||
return Range.between(VersionNumber.asVersion(fromInclusive), VersionNumber.asVersion(toExclusive));
|
||||
}
|
||||
}
|
||||
@@ -550,7 +550,7 @@ public abstract class Task<T> {
|
||||
public final <U> Task<U> thenComposeAsync(Task<U> other) {
|
||||
return thenComposeAsync(() -> other);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a new Task that, when this task completes
|
||||
* normally, is executed.
|
||||
@@ -560,7 +560,20 @@ public abstract class Task<T> {
|
||||
* @return the Task
|
||||
*/
|
||||
public final <U> Task<U> thenComposeAsync(ExceptionalSupplier<Task<U>, ?> fn) {
|
||||
return new UniCompose<>(fn, true);
|
||||
return thenComposeAsync(Schedulers.defaultScheduler(), fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new Task that, when this task completes
|
||||
* normally, is executed.
|
||||
*
|
||||
* @param fn the function returning a new Task
|
||||
* @param executor the executor to use for asynchronous execution
|
||||
* @param <U> the type of the returned Task's result
|
||||
* @return the Task
|
||||
*/
|
||||
public final <U> Task<U> thenComposeAsync(Executor executor, ExceptionalSupplier<Task<U>, ?> fn) {
|
||||
return new UniCompose<>(fn, true).setExecutor(executor);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -573,7 +586,21 @@ public abstract class Task<T> {
|
||||
* @return the Task
|
||||
*/
|
||||
public <U, E extends Exception> Task<U> thenComposeAsync(ExceptionalFunction<T, Task<U>, E> fn) {
|
||||
return new UniCompose<>(fn, true);
|
||||
return thenComposeAsync(Schedulers.defaultScheduler(), fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new Task that, when this task completes
|
||||
* normally, is executed with result of this task as the argument
|
||||
* to the supplied function.
|
||||
*
|
||||
* @param fn the function returning a new Task
|
||||
* @param executor the executor to use for asynchronous execution
|
||||
* @param <U> the type of the returned Task's result
|
||||
* @return the Task
|
||||
*/
|
||||
public <U, E extends Exception> Task<U> thenComposeAsync(Executor executor, ExceptionalFunction<T, Task<U>, E> fn) {
|
||||
return new UniCompose<>(fn, true).setExecutor(executor);
|
||||
}
|
||||
|
||||
public final <U> Task<U> withComposeAsync(Task<U> other) {
|
||||
@@ -840,6 +867,10 @@ public abstract class Task<T> {
|
||||
}.setName(name);
|
||||
}
|
||||
|
||||
public static <T> Task<T> composeAsync(Executor executor, ExceptionalSupplier<Task<T>, ?> fn) {
|
||||
return composeAsync(fn).setExecutor(executor);
|
||||
}
|
||||
|
||||
public static <V> Task<V> supplyAsync(Callable<V> callable) {
|
||||
return supplyAsync(getCaller(), callable).setSignificance(TaskSignificance.MODERATE);
|
||||
}
|
||||
@@ -856,6 +887,10 @@ public abstract class Task<T> {
|
||||
return new SimpleTask<>(callable).setExecutor(executor).setName(name);
|
||||
}
|
||||
|
||||
public static <V> Task<V> completed(V value) {
|
||||
return fromCompletableFuture(CompletableFuture.completedFuture(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new Task that is completed when all of the given Tasks
|
||||
* complete. If any of the given Tasks complete exceptionally,
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.Range;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class JavaVersionConstraintTest {
|
||||
|
||||
@Test
|
||||
public void vanillaJava16() {
|
||||
Pair<Range<VersionNumber>, Range<VersionNumber>> range = JavaVersionConstraint.findSuitableJavaVersionRange(
|
||||
VersionNumber.asVersion("1.17"),
|
||||
null
|
||||
);
|
||||
|
||||
Assert.assertEquals(
|
||||
Range.between(VersionNumber.asVersion("16"), VersionNumber.asVersion(JavaVersionConstraint.MAX)),
|
||||
range.getKey());
|
||||
}
|
||||
}
|
||||
@@ -67,6 +67,10 @@ public class VersionNumberTest {
|
||||
v = VersionNumber.asVersion("1.8.0_11");
|
||||
Assert.assertTrue(u.compareTo(v) < 0);
|
||||
|
||||
u = VersionNumber.asVersion("1.7.0_22");
|
||||
v = VersionNumber.asVersion("1.7.99");
|
||||
Assert.assertTrue(u.compareTo(v) < 0);
|
||||
|
||||
u = VersionNumber.asVersion("1.12.2-14.23.5.2760");
|
||||
v = VersionNumber.asVersion("1.12.2-14.23.4.2739");
|
||||
Assert.assertTrue(u.compareTo(v) > 0);
|
||||
|
||||
Reference in New Issue
Block a user