重构 Java 管理 (#2988)
* update * update * Update task name * update * update * update * update * update * update * update * update * update * update * Update logo
This commit is contained in:
@@ -40,7 +40,7 @@ import org.jackhuang.hmcl.util.io.ChecksumMismatchException;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.platform.CommandBuilder;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.SystemUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -129,7 +129,7 @@ public class ForgeNewInstallTask extends Task<Version> {
|
||||
throw new Exception("Game processor jar does not have main class " + jar);
|
||||
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add(JavaVersion.fromCurrentEnvironment().getBinary().toString());
|
||||
command.add(JavaRuntime.getDefault().getBinary().toString());
|
||||
command.add("-cp");
|
||||
|
||||
List<String> classpath = new ArrayList<>(processor.getClasspath().size() + 1);
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.download.java;
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.platform.Platform;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public interface JavaDistribution<V extends JavaRemoteVersion> {
|
||||
String getDisplayName();
|
||||
|
||||
Set<JavaPackageType> getSupportedPackageTypes();
|
||||
|
||||
Task<TreeMap<Integer, V>> getFetchJavaVersionsTask(DownloadProvider provider, Platform platform, JavaPackageType packageType);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
* Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -15,23 +15,30 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
package org.jackhuang.hmcl.download.java;
|
||||
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
import org.junit.jupiter.api.Test;
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public enum JavaPackageType {
|
||||
JDK(true, false),
|
||||
JRE(false, false),
|
||||
JDKFX(true, true),
|
||||
JREFX(false, true);
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
private final boolean jdk;
|
||||
private final boolean javafx;
|
||||
|
||||
public class JavaVersionConstraintTest {
|
||||
JavaPackageType(boolean jdk, boolean javafx) {
|
||||
this.jdk = jdk;
|
||||
this.javafx = javafx;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void vanillaJava16() {
|
||||
JavaVersionConstraint.VersionRanges range = JavaVersionConstraint.findSuitableJavaVersionRange(
|
||||
GameVersionNumber.asGameVersion("1.17"),
|
||||
null
|
||||
);
|
||||
public boolean isJDK() {
|
||||
return jdk;
|
||||
}
|
||||
|
||||
assertEquals(VersionNumber.atLeast("16"), range.getMandatory());
|
||||
public boolean isJavaFXBundled() {
|
||||
return javafx;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.download.java;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public interface JavaRemoteVersion {
|
||||
int getJdkVersion();
|
||||
|
||||
String getJavaVersion();
|
||||
|
||||
String getDistributionVersion();
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
package org.jackhuang.hmcl.download.java;
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.game.GameJavaVersion;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.CacheRepository;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.Platform;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public final class JavaRepository {
|
||||
private JavaRepository() {
|
||||
}
|
||||
|
||||
public static Task<JavaVersion> downloadJava(GameJavaVersion javaVersion, DownloadProvider downloadProvider) {
|
||||
return new JavaDownloadTask(javaVersion, getJavaStoragePath(), downloadProvider)
|
||||
.thenSupplyAsync(() -> {
|
||||
String platform = getSystemJavaPlatform().orElseThrow(JavaDownloadTask.UnsupportedPlatformException::new);
|
||||
return addJava(getJavaHome(javaVersion, platform));
|
||||
});
|
||||
}
|
||||
|
||||
public static JavaVersion addJava(Path javaHome) throws InterruptedException, IOException {
|
||||
if (Files.isDirectory(javaHome)) {
|
||||
Path executable = JavaVersion.getExecutable(javaHome);
|
||||
if (Files.isRegularFile(executable)) {
|
||||
JavaVersion javaVersion = JavaVersion.fromExecutable(executable);
|
||||
JavaVersion.getJavas().add(javaVersion);
|
||||
return javaVersion;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IOException("Incorrect java home " + javaHome);
|
||||
}
|
||||
|
||||
public static Stream<Optional<Path>> findMinecraftRuntimeDirs() {
|
||||
switch (OperatingSystem.CURRENT_OS) {
|
||||
case WINDOWS:
|
||||
return Stream.of(
|
||||
FileUtils.tryGetPath(System.getenv("localappdata"),
|
||||
"Packages\\Microsoft.4297127D64EC6_8wekyb3d8bbwe\\LocalCache\\Local\\runtime"),
|
||||
FileUtils.tryGetPath(
|
||||
Optional.ofNullable(System.getenv("ProgramFiles(x86)")).orElse("C:\\Program Files (x86)"),
|
||||
"Minecraft Launcher\\runtime"));
|
||||
case LINUX:
|
||||
case FREEBSD:
|
||||
return Stream.of(FileUtils.tryGetPath(System.getProperty("user.home"), ".minecraft/runtime"));
|
||||
case OSX:
|
||||
return Stream.of(FileUtils.tryGetPath(System.getProperty("user.home"), "Library/Application Support/minecraft/runtime"));
|
||||
default:
|
||||
return Stream.empty();
|
||||
}
|
||||
}
|
||||
|
||||
public static Stream<Path> findJavaHomeInMinecraftRuntimeDir(Path runtimeDir) {
|
||||
if (!Files.isDirectory(runtimeDir))
|
||||
return Stream.empty();
|
||||
// Examples:
|
||||
// $HOME/Library/Application Support/minecraft/runtime/java-runtime-beta/mac-os/java-runtime-beta/jre.bundle/Contents/Home
|
||||
// $HOME/.minecraft/runtime/java-runtime-beta/linux/java-runtime-beta
|
||||
List<Path> javaHomes = new ArrayList<>();
|
||||
Consumer<String> action = platform -> {
|
||||
try (DirectoryStream<Path> dir = Files.newDirectoryStream(runtimeDir)) {
|
||||
// component can be jre-legacy, java-runtime-alpha, java-runtime-beta, java-runtime-gamma or any other being added in the future.
|
||||
for (Path component : dir) {
|
||||
findJavaHomeInComponentDir(platform, component).ifPresent(javaHomes::add);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to list java-runtime directory " + runtimeDir, e);
|
||||
}
|
||||
};
|
||||
getSystemJavaPlatform().ifPresent(action);
|
||||
|
||||
// Workaround, which will be removed in the future
|
||||
if (Platform.SYSTEM_PLATFORM == Platform.OSX_ARM64)
|
||||
action.accept("mac-os-arm64");
|
||||
|
||||
return javaHomes.stream();
|
||||
}
|
||||
|
||||
private static Optional<Path> findJavaHomeInComponentDir(String platform, Path component) {
|
||||
Path sha1File = component.resolve(platform).resolve(component.getFileName() + ".sha1");
|
||||
if (!Files.isRegularFile(sha1File))
|
||||
return Optional.empty();
|
||||
Path dir = component.resolve(platform).resolve(component.getFileName());
|
||||
|
||||
try (BufferedReader reader = Files.newBufferedReader(sha1File)) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (line.isEmpty()) continue;
|
||||
|
||||
int idx = line.indexOf(" /#//");
|
||||
if (idx <= 0)
|
||||
throw new IOException("Illegal line: " + line);
|
||||
|
||||
Path file = dir.resolve(line.substring(0, idx));
|
||||
|
||||
// Should we check the sha1 of files? This will take a lot of time.
|
||||
if (Files.notExists(file))
|
||||
throw new NoSuchFileException(file.toAbsolutePath().toString());
|
||||
}
|
||||
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX) {
|
||||
Path macPath = dir.resolve("jre.bundle/Contents/Home");
|
||||
if (Files.exists(macPath))
|
||||
return Optional.of(macPath);
|
||||
else
|
||||
LOG.warning("The Java is not in 'jre.bundle/Contents/Home'");
|
||||
}
|
||||
|
||||
return Optional.of(dir);
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to verify Java in " + component, e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
public static Optional<String> getSystemJavaPlatform() {
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX) {
|
||||
if (Architecture.SYSTEM_ARCH == Architecture.X86) {
|
||||
return Optional.of("linux-i386");
|
||||
} else if (Architecture.SYSTEM_ARCH == Architecture.X86_64) {
|
||||
return Optional.of("linux");
|
||||
}
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX) {
|
||||
if (Architecture.SYSTEM_ARCH == Architecture.X86_64 || Architecture.SYSTEM_ARCH == Architecture.ARM64) {
|
||||
return Optional.of("mac-os");
|
||||
}
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
if (Architecture.SYSTEM_ARCH == Architecture.X86) {
|
||||
return Optional.of("windows-x86");
|
||||
} else if (Architecture.SYSTEM_ARCH == Architecture.X86_64) {
|
||||
return Optional.of("windows-x64");
|
||||
} else if (Architecture.SYSTEM_ARCH == Architecture.ARM64) {
|
||||
if (OperatingSystem.SYSTEM_BUILD_NUMBER >= 21277) {
|
||||
return Optional.of("windows-x64");
|
||||
} else {
|
||||
return Optional.of("windows-x86");
|
||||
}
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public static Path getJavaStoragePath() {
|
||||
return CacheRepository.getInstance().getCacheDirectory().resolve("java");
|
||||
}
|
||||
|
||||
public static Path getJavaHome(GameJavaVersion javaVersion, String platform) {
|
||||
Path javaHome = getJavaStoragePath().resolve(javaVersion.getComponent()).resolve(platform).resolve(javaVersion.getComponent());
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX)
|
||||
javaHome = javaHome.resolve("jre.bundle/Contents/Home");
|
||||
return javaHome;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.download.java.disco;
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.java.JavaPackageType;
|
||||
import org.jackhuang.hmcl.task.GetTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.Platform;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class DiscoFetchJavaListTask extends Task<TreeMap<Integer, DiscoJavaRemoteVersion>> {
|
||||
|
||||
public static final String API_ROOT = System.getProperty("hmcl.discoapi.override", "https://api.foojay.io/disco/v3.0");
|
||||
|
||||
private static String getOperatingSystemName(OperatingSystem os) {
|
||||
return os == OperatingSystem.OSX ? "macos" : os.getCheckedName();
|
||||
}
|
||||
|
||||
private static String getArchitectureName(Architecture arch) {
|
||||
return arch.getCheckedName();
|
||||
}
|
||||
|
||||
private final DiscoJavaDistribution distribution;
|
||||
private final Task<String> fetchPackagesTask;
|
||||
|
||||
public DiscoFetchJavaListTask(DownloadProvider downloadProvider, DiscoJavaDistribution distribution, Platform platform, JavaPackageType packageType) {
|
||||
this.distribution = distribution;
|
||||
|
||||
HashMap<String, String> params = new HashMap<>();
|
||||
params.put("distribution", distribution.getApiParameter());
|
||||
params.put("package", packageType.isJDK() ? "jdk" : "jre");
|
||||
params.put("javafx_bundled", Boolean.toString(packageType.isJavaFXBundled()));
|
||||
params.put("operating_system", getOperatingSystemName(platform.getOperatingSystem()));
|
||||
params.put("architecture", getArchitectureName(platform.getArchitecture()));
|
||||
params.put("archive_type", platform.getOperatingSystem() == OperatingSystem.WINDOWS ? "zip" : "tar.gz");
|
||||
params.put("directly_downloadable", "true");
|
||||
if (platform.getOperatingSystem() == OperatingSystem.LINUX) {
|
||||
params.put("lib_c_type", "glibc");
|
||||
}
|
||||
|
||||
this.fetchPackagesTask = new GetTask(downloadProvider.injectURLWithCandidates(NetworkUtils.withQuery(API_ROOT + "/packages", params)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Task<?>> getDependents() {
|
||||
return Collections.singleton(fetchPackagesTask);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
String json = fetchPackagesTask.getResult();
|
||||
List<DiscoJavaRemoteVersion> result = JsonUtils.fromNonNullJson(json, DiscoResult.typeOf(DiscoJavaRemoteVersion.class)).getResult();
|
||||
|
||||
TreeMap<Integer, DiscoJavaRemoteVersion> map = new TreeMap<>();
|
||||
|
||||
for (DiscoJavaRemoteVersion version : result) {
|
||||
if (!distribution.getApiParameter().equals(version.getDistribution()))
|
||||
continue;
|
||||
|
||||
int jdkVersion = version.getJdkVersion();
|
||||
DiscoJavaRemoteVersion oldVersion = map.get(jdkVersion);
|
||||
if (oldVersion == null || VersionNumber.compare(version.getDistributionVersion(), oldVersion.getDistributionVersion()) > 0) {
|
||||
map.put(jdkVersion, version);
|
||||
}
|
||||
}
|
||||
|
||||
setResult(map);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.download.java.disco;
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.java.JavaDistribution;
|
||||
import org.jackhuang.hmcl.download.java.JavaPackageType;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.Platform;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static org.jackhuang.hmcl.download.java.JavaPackageType.*;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.platform.Architecture.*;
|
||||
import static org.jackhuang.hmcl.util.platform.OperatingSystem.*;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public enum DiscoJavaDistribution implements JavaDistribution<DiscoJavaRemoteVersion> {
|
||||
TEMURIN("Eclipse Temurin", "temurin", "Adoptium",
|
||||
EnumSet.of(JDK, JRE),
|
||||
pair(WINDOWS, EnumSet.of(X86_64, X86, ARM64)),
|
||||
pair(LINUX, EnumSet.of(X86_64, X86, ARM64, ARM32, RISCV64, PPC64, PPC64LE, S390X, SPARCV9)),
|
||||
pair(OSX, EnumSet.of(X86_64, ARM64))),
|
||||
LIBERICA("Liberica", "liberica", "BellSoft",
|
||||
EnumSet.of(JDK, JRE, JDKFX, JREFX),
|
||||
pair(WINDOWS, EnumSet.of(X86_64, X86, ARM64)),
|
||||
pair(LINUX, EnumSet.of(X86_64, X86, ARM64, ARM32, RISCV64, PPC64LE)),
|
||||
pair(OSX, EnumSet.of(X86_64, ARM64))),
|
||||
ZULU("Zulu", "zulu", "Azul",
|
||||
EnumSet.of(JDK, JRE, JDKFX, JREFX),
|
||||
pair(WINDOWS, EnumSet.of(X86_64, X86, ARM64)),
|
||||
pair(LINUX, EnumSet.of(X86_64, X86, ARM64, ARM32, RISCV64, PPC64LE)),
|
||||
pair(OSX, EnumSet.of(X86_64, ARM64))),
|
||||
GRAALVM("GraalVM", "graalvm", "Oracle",
|
||||
EnumSet.of(JDK),
|
||||
pair(WINDOWS, EnumSet.of(X86_64, X86)),
|
||||
pair(LINUX, EnumSet.of(X86_64, X86, ARM64, ARM32, RISCV64, PPC64LE)),
|
||||
pair(OSX, EnumSet.of(X86_64, ARM64)));
|
||||
|
||||
public static DiscoJavaDistribution of(String name) {
|
||||
for (DiscoJavaDistribution distribution : values()) {
|
||||
if (distribution.apiParameter.equalsIgnoreCase(name) || distribution.name().equalsIgnoreCase(name)) {
|
||||
return distribution;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private final String displayName;
|
||||
private final String apiParameter;
|
||||
private final String vendor;
|
||||
private final Set<JavaPackageType> supportedPackageTypes;
|
||||
private final Map<OperatingSystem, EnumSet<Architecture>> supportedPlatforms = new EnumMap<>(OperatingSystem.class);
|
||||
|
||||
@SafeVarargs
|
||||
DiscoJavaDistribution(String displayName, String apiParameter, String vendor, Set<JavaPackageType> supportedPackageTypes, Pair<OperatingSystem, EnumSet<Architecture>>... supportedPlatforms) {
|
||||
this.displayName = displayName;
|
||||
this.apiParameter = apiParameter;
|
||||
this.vendor = vendor;
|
||||
this.supportedPackageTypes = supportedPackageTypes;
|
||||
|
||||
for (Pair<OperatingSystem, EnumSet<Architecture>> platform : supportedPlatforms) {
|
||||
this.supportedPlatforms.put(platform.getKey(), platform.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public String getApiParameter() {
|
||||
return apiParameter;
|
||||
}
|
||||
|
||||
public String getVendor() {
|
||||
return vendor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<JavaPackageType> getSupportedPackageTypes() {
|
||||
return supportedPackageTypes;
|
||||
}
|
||||
|
||||
public boolean isSupport(Platform platform) {
|
||||
EnumSet<Architecture> architectures = supportedPlatforms.get(platform.getOperatingSystem());
|
||||
return architectures != null && architectures.contains(platform.getArchitecture());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<TreeMap<Integer, DiscoJavaRemoteVersion>> getFetchJavaVersionsTask(DownloadProvider provider, Platform platform, JavaPackageType packageType) {
|
||||
return new DiscoFetchJavaListTask(provider, this, platform, packageType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.download.java.disco;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import org.jackhuang.hmcl.download.java.JavaRemoteVersion;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class DiscoJavaRemoteVersion implements JavaRemoteVersion {
|
||||
@SerializedName("id")
|
||||
private final String id;
|
||||
|
||||
@SerializedName("archive_type")
|
||||
private final String archiveType;
|
||||
|
||||
@SerializedName("distribution")
|
||||
private final String distribution;
|
||||
|
||||
@SerializedName("major_version")
|
||||
private final int majorVersion;
|
||||
|
||||
@SerializedName("java_version")
|
||||
private final String javaVersion;
|
||||
|
||||
@SerializedName("distribution_version")
|
||||
private final String distributionVersion;
|
||||
|
||||
@SerializedName("jdk_version")
|
||||
private final int jdkVersion;
|
||||
|
||||
@SerializedName("latest_build_available")
|
||||
private final boolean latestBuildAvailable;
|
||||
|
||||
@SerializedName("release_status")
|
||||
private final String releaseStatus;
|
||||
|
||||
@SerializedName("term_of_support")
|
||||
private final String termOfSupport;
|
||||
|
||||
@SerializedName("operating_system")
|
||||
private final String operatingSystem;
|
||||
|
||||
@SerializedName("lib_c_type")
|
||||
private final String libCType;
|
||||
|
||||
@SerializedName("architecture")
|
||||
private final String architecture;
|
||||
|
||||
@SerializedName("fpu")
|
||||
private final String fpu;
|
||||
|
||||
@SerializedName("package_type")
|
||||
private final String packageType;
|
||||
|
||||
@SerializedName("javafx_bundled")
|
||||
private final boolean javafxBundled;
|
||||
|
||||
@SerializedName("directly_downloadable")
|
||||
private final boolean directlyDownloadable;
|
||||
|
||||
@SerializedName("filename")
|
||||
private final String fileName;
|
||||
|
||||
@SerializedName("links")
|
||||
private final Links links;
|
||||
|
||||
@SerializedName("free_use_in_production")
|
||||
private final boolean freeUseInProduction;
|
||||
|
||||
@SerializedName("tck_tested")
|
||||
private final String tckTested;
|
||||
|
||||
@SerializedName("tck_cert_uri")
|
||||
private final String tckCertUri;
|
||||
|
||||
@SerializedName("aqavit_certified")
|
||||
private final String aqavitCertified;
|
||||
|
||||
@SerializedName("aqavit_cert_uri")
|
||||
private final String aqavitCertUri;
|
||||
|
||||
@SerializedName("size")
|
||||
private final long size;
|
||||
|
||||
public DiscoJavaRemoteVersion(String id, String archiveType, String distribution, int majorVersion, String javaVersion, String distributionVersion, int jdkVersion, boolean latestBuildAvailable, String releaseStatus, String termOfSupport, String operatingSystem, String libCType, String architecture, String fpu, String packageType, boolean javafxBundled, boolean directlyDownloadable, String fileName, Links links, boolean freeUseInProduction, String tckTested, String tckCertUri, String aqavitCertified, String aqavitCertUri, long size) {
|
||||
this.id = id;
|
||||
this.archiveType = archiveType;
|
||||
this.distribution = distribution;
|
||||
this.majorVersion = majorVersion;
|
||||
this.javaVersion = javaVersion;
|
||||
this.distributionVersion = distributionVersion;
|
||||
this.jdkVersion = jdkVersion;
|
||||
this.latestBuildAvailable = latestBuildAvailable;
|
||||
this.releaseStatus = releaseStatus;
|
||||
this.termOfSupport = termOfSupport;
|
||||
this.operatingSystem = operatingSystem;
|
||||
this.libCType = libCType;
|
||||
this.architecture = architecture;
|
||||
this.fpu = fpu;
|
||||
this.packageType = packageType;
|
||||
this.javafxBundled = javafxBundled;
|
||||
this.directlyDownloadable = directlyDownloadable;
|
||||
this.fileName = fileName;
|
||||
this.links = links;
|
||||
this.freeUseInProduction = freeUseInProduction;
|
||||
this.tckTested = tckTested;
|
||||
this.tckCertUri = tckCertUri;
|
||||
this.aqavitCertified = aqavitCertified;
|
||||
this.aqavitCertUri = aqavitCertUri;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getArchiveType() {
|
||||
return archiveType;
|
||||
}
|
||||
|
||||
public String getDistribution() {
|
||||
return distribution;
|
||||
}
|
||||
|
||||
public int getMajorVersion() {
|
||||
return majorVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getJavaVersion() {
|
||||
return javaVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDistributionVersion() {
|
||||
return distributionVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getJdkVersion() {
|
||||
return jdkVersion;
|
||||
}
|
||||
|
||||
public boolean isLatestBuildAvailable() {
|
||||
return latestBuildAvailable;
|
||||
}
|
||||
|
||||
public String getReleaseStatus() {
|
||||
return releaseStatus;
|
||||
}
|
||||
|
||||
public String getTermOfSupport() {
|
||||
return termOfSupport;
|
||||
}
|
||||
|
||||
public String getOperatingSystem() {
|
||||
return operatingSystem;
|
||||
}
|
||||
|
||||
public String getLibCType() {
|
||||
return libCType;
|
||||
}
|
||||
|
||||
public String getArchitecture() {
|
||||
return architecture;
|
||||
}
|
||||
|
||||
public String getFpu() {
|
||||
return fpu;
|
||||
}
|
||||
|
||||
public String getPackageType() {
|
||||
return packageType;
|
||||
}
|
||||
|
||||
public boolean isJavafxBundled() {
|
||||
return javafxBundled;
|
||||
}
|
||||
|
||||
public boolean isDirectlyDownloadable() {
|
||||
return directlyDownloadable;
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public Links getLinks() {
|
||||
return links;
|
||||
}
|
||||
|
||||
public boolean isFreeUseInProduction() {
|
||||
return freeUseInProduction;
|
||||
}
|
||||
|
||||
public String getTckTested() {
|
||||
return tckTested;
|
||||
}
|
||||
|
||||
public String getTckCertUri() {
|
||||
return tckCertUri;
|
||||
}
|
||||
|
||||
public String getAqavitCertified() {
|
||||
return aqavitCertified;
|
||||
}
|
||||
|
||||
public String getAqavitCertUri() {
|
||||
return aqavitCertUri;
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DiscoJavaRemoteVersion " + JsonUtils.GSON.toJson(this);
|
||||
}
|
||||
|
||||
public static final class Links {
|
||||
@SerializedName("pkg_info_uri")
|
||||
private final String pkgInfoUri;
|
||||
|
||||
@SerializedName("pkg_download_redirect")
|
||||
private final String pkgDownloadRedirect;
|
||||
|
||||
public Links(String pkgInfoUri, String pkgDownloadRedirect) {
|
||||
this.pkgInfoUri = pkgInfoUri;
|
||||
this.pkgDownloadRedirect = pkgDownloadRedirect;
|
||||
}
|
||||
|
||||
public String getPkgInfoUri() {
|
||||
return pkgInfoUri;
|
||||
}
|
||||
|
||||
public String getPkgDownloadRedirect() {
|
||||
return pkgDownloadRedirect;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.download.java.disco;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class DiscoRemoteFileInfo {
|
||||
@SerializedName("filename")
|
||||
private final String fileName;
|
||||
|
||||
@SerializedName("direct_download_uri")
|
||||
private final String directDownloadUri;
|
||||
|
||||
@SerializedName("checksum_type")
|
||||
private final String checksumType;
|
||||
|
||||
@SerializedName("checksum")
|
||||
private final String checksum;
|
||||
|
||||
@SerializedName("checksum_uri")
|
||||
private final String checksumUri;
|
||||
|
||||
public DiscoRemoteFileInfo(String fileName, String directDownloadUri, String checksumType, String checksum, String checksumUri) {
|
||||
this.fileName = fileName;
|
||||
this.directDownloadUri = directDownloadUri;
|
||||
this.checksumType = checksumType;
|
||||
this.checksum = checksum;
|
||||
this.checksumUri = checksumUri;
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public String getDirectDownloadUri() {
|
||||
return directDownloadUri;
|
||||
}
|
||||
|
||||
public String getChecksumType() {
|
||||
return checksumType;
|
||||
}
|
||||
|
||||
public String getChecksum() {
|
||||
return checksum;
|
||||
}
|
||||
|
||||
public String getChecksumUri() {
|
||||
return checksumUri;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.download.java.disco;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class DiscoResult<T> {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> TypeToken<DiscoResult<T>> typeOf(Class<T> argType) {
|
||||
return (TypeToken<DiscoResult<T>>) TypeToken.getParameterized(DiscoResult.class, argType);
|
||||
}
|
||||
|
||||
private final List<T> result;
|
||||
private final String message;
|
||||
|
||||
private DiscoResult(List<T> result, String message) {
|
||||
this.result = result;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public List<T> getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.download.java.mojang;
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.java.JavaDistribution;
|
||||
import org.jackhuang.hmcl.download.java.JavaPackageType;
|
||||
import org.jackhuang.hmcl.download.java.JavaRemoteVersion;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.platform.Platform;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class MojangJavaDistribution implements JavaDistribution<JavaRemoteVersion> {
|
||||
|
||||
public static final MojangJavaDistribution DISTRIBUTION = new MojangJavaDistribution();
|
||||
|
||||
private MojangJavaDistribution() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return "Mojang";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<JavaPackageType> getSupportedPackageTypes() {
|
||||
return Collections.singleton(JavaPackageType.JRE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<TreeMap<Integer, JavaRemoteVersion>> getFetchJavaVersionsTask(DownloadProvider provider, Platform platform, JavaPackageType packageType) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -15,20 +15,19 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.download.java;
|
||||
package org.jackhuang.hmcl.download.java.mojang;
|
||||
|
||||
import org.jackhuang.hmcl.download.ArtifactMalformedException;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.game.DownloadInfo;
|
||||
import org.jackhuang.hmcl.game.GameJavaVersion;
|
||||
import org.jackhuang.hmcl.java.*;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.GetTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.ChecksumMismatchException;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
import org.jackhuang.hmcl.util.platform.UnsupportedPlatformException;
|
||||
import org.tukaani.xz.LZMAInputStream;
|
||||
|
||||
import java.io.File;
|
||||
@@ -40,50 +39,39 @@ import java.nio.file.Paths;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public class JavaDownloadTask extends Task<Void> {
|
||||
private final GameJavaVersion javaVersion;
|
||||
private final Path rootDir;
|
||||
private String platform;
|
||||
private final Task<RemoteFiles> javaDownloadsTask;
|
||||
private JavaDownloads.JavaDownload download;
|
||||
private final List<Task<?>> dependencies = new ArrayList<>();
|
||||
private final DownloadProvider downloadProvider;
|
||||
public final class MojangJavaDownloadTask extends Task<MojangJavaDownloadTask.Result> {
|
||||
|
||||
public JavaDownloadTask(GameJavaVersion javaVersion, Path rootDir, DownloadProvider downloadProvider) {
|
||||
this.javaVersion = javaVersion;
|
||||
this.rootDir = rootDir;
|
||||
private final DownloadProvider downloadProvider;
|
||||
private final Path target;
|
||||
private final Task<MojangJavaRemoteFiles> javaDownloadsTask;
|
||||
private final List<Task<?>> dependencies = new ArrayList<>();
|
||||
|
||||
private volatile MojangJavaDownloads.JavaDownload download;
|
||||
|
||||
public MojangJavaDownloadTask(DownloadProvider downloadProvider, Path target, GameJavaVersion javaVersion, String platform) {
|
||||
this.target = target;
|
||||
this.downloadProvider = downloadProvider;
|
||||
this.javaDownloadsTask = new GetTask(downloadProvider.injectURLWithCandidates(
|
||||
"https://piston-meta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"))
|
||||
.thenComposeAsync(javaDownloadsJson -> {
|
||||
JavaDownloads allDownloads = JsonUtils.fromNonNullJson(javaDownloadsJson, JavaDownloads.class);
|
||||
if (!allDownloads.getDownloads().containsKey(platform)) throw new UnsupportedPlatformException();
|
||||
Map<String, List<JavaDownloads.JavaDownload>> osDownloads = allDownloads.getDownloads().get(platform);
|
||||
if (!osDownloads.containsKey(javaVersion.getComponent())) throw new UnsupportedPlatformException();
|
||||
List<JavaDownloads.JavaDownload> candidates = osDownloads.get(javaVersion.getComponent());
|
||||
for (JavaDownloads.JavaDownload download : candidates) {
|
||||
if (VersionNumber.compare(download.getVersion().getName(), Integer.toString(javaVersion.getMajorVersion())) >= 0) {
|
||||
MojangJavaDownloads allDownloads = JsonUtils.fromNonNullJson(javaDownloadsJson, MojangJavaDownloads.class);
|
||||
|
||||
Map<String, List<MojangJavaDownloads.JavaDownload>> osDownloads = allDownloads.getDownloads().get(platform);
|
||||
if (osDownloads == null || !osDownloads.containsKey(javaVersion.getComponent()))
|
||||
throw new UnsupportedPlatformException("Unsupported platform: " + platform);
|
||||
List<MojangJavaDownloads.JavaDownload> candidates = osDownloads.get(javaVersion.getComponent());
|
||||
for (MojangJavaDownloads.JavaDownload download : candidates) {
|
||||
if (JavaInfo.parseVersion(download.getVersion().getName()) >= javaVersion.getMajorVersion()) {
|
||||
this.download = download;
|
||||
return new GetTask(downloadProvider.injectURLWithCandidates(download.getManifest().getUrl()));
|
||||
}
|
||||
}
|
||||
throw new UnsupportedPlatformException();
|
||||
throw new UnsupportedPlatformException("Candidates: " + JsonUtils.GSON.toJson(candidates));
|
||||
})
|
||||
.thenApplyAsync(javaDownloadJson -> JsonUtils.fromNonNullJson(javaDownloadJson, RemoteFiles.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doPreExecute() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preExecute() throws Exception {
|
||||
this.platform = JavaRepository.getSystemJavaPlatform().orElseThrow(UnsupportedPlatformException::new);
|
||||
.thenApplyAsync(javaDownloadJson -> JsonUtils.fromNonNullJson(javaDownloadJson, MojangJavaRemoteFiles.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -93,11 +81,10 @@ public class JavaDownloadTask extends Task<Void> {
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
Path jvmDir = rootDir.resolve(javaVersion.getComponent()).resolve(platform).resolve(javaVersion.getComponent());
|
||||
for (Map.Entry<String, RemoteFiles.Remote> entry : javaDownloadsTask.getResult().getFiles().entrySet()) {
|
||||
Path dest = jvmDir.resolve(entry.getKey());
|
||||
if (entry.getValue() instanceof RemoteFiles.RemoteFile) {
|
||||
RemoteFiles.RemoteFile file = ((RemoteFiles.RemoteFile) entry.getValue());
|
||||
for (Map.Entry<String, MojangJavaRemoteFiles.Remote> entry : javaDownloadsTask.getResult().getFiles().entrySet()) {
|
||||
Path dest = target.resolve(entry.getKey());
|
||||
if (entry.getValue() instanceof MojangJavaRemoteFiles.RemoteFile) {
|
||||
MojangJavaRemoteFiles.RemoteFile file = ((MojangJavaRemoteFiles.RemoteFile) entry.getValue());
|
||||
|
||||
// Use local file if it already exists
|
||||
try {
|
||||
@@ -115,11 +102,11 @@ public class JavaDownloadTask extends Task<Void> {
|
||||
|
||||
if (file.getDownloads().containsKey("lzma")) {
|
||||
DownloadInfo download = file.getDownloads().get("lzma");
|
||||
File tempFile = jvmDir.resolve(entry.getKey() + ".lzma").toFile();
|
||||
File tempFile = target.resolve(entry.getKey() + ".lzma").toFile();
|
||||
FileDownloadTask task = new FileDownloadTask(downloadProvider.injectURLWithCandidates(download.getUrl()), tempFile, new FileDownloadTask.IntegrityCheck("SHA-1", download.getSha1()));
|
||||
task.setName(entry.getKey());
|
||||
dependencies.add(task.thenRunAsync(() -> {
|
||||
Path decompressed = jvmDir.resolve(entry.getKey() + ".tmp");
|
||||
Path decompressed = target.resolve(entry.getKey() + ".tmp");
|
||||
try (LZMAInputStream input = new LZMAInputStream(new FileInputStream(tempFile))) {
|
||||
Files.copy(input, decompressed, StandardCopyOption.REPLACE_EXISTING);
|
||||
} catch (IOException e) {
|
||||
@@ -144,10 +131,10 @@ public class JavaDownloadTask extends Task<Void> {
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else if (entry.getValue() instanceof RemoteFiles.RemoteDirectory) {
|
||||
} else if (entry.getValue() instanceof MojangJavaRemoteFiles.RemoteDirectory) {
|
||||
Files.createDirectories(dest);
|
||||
} else if (entry.getValue() instanceof RemoteFiles.RemoteLink) {
|
||||
RemoteFiles.RemoteLink link = ((RemoteFiles.RemoteLink) entry.getValue());
|
||||
} else if (entry.getValue() instanceof MojangJavaRemoteFiles.RemoteLink) {
|
||||
MojangJavaRemoteFiles.RemoteLink link = ((MojangJavaRemoteFiles.RemoteLink) entry.getValue());
|
||||
Files.deleteIfExists(dest);
|
||||
Files.createSymbolicLink(dest, Paths.get(link.getTarget()));
|
||||
}
|
||||
@@ -166,16 +153,16 @@ public class JavaDownloadTask extends Task<Void> {
|
||||
|
||||
@Override
|
||||
public void postExecute() throws Exception {
|
||||
FileUtils.writeText(rootDir.resolve(javaVersion.getComponent()).resolve(platform).resolve(".version").toFile(), download.getVersion().getName());
|
||||
FileUtils.writeText(rootDir.resolve(javaVersion.getComponent()).resolve(platform).resolve(javaVersion.getComponent() + ".sha1").toFile(),
|
||||
javaDownloadsTask.getResult().getFiles().entrySet().stream()
|
||||
.filter(entry -> entry.getValue() instanceof RemoteFiles.RemoteFile)
|
||||
.map(entry -> {
|
||||
RemoteFiles.RemoteFile file = (RemoteFiles.RemoteFile) entry.getValue();
|
||||
return entry.getKey() + " /#// " + file.getDownloads().get("raw").getSha1() + " " + file.getDownloads().get("raw").getSize();
|
||||
})
|
||||
.collect(Collectors.joining(OperatingSystem.LINE_SEPARATOR)));
|
||||
setResult(new Result(download, javaDownloadsTask.getResult()));
|
||||
}
|
||||
|
||||
public static class UnsupportedPlatformException extends Exception {}
|
||||
public static final class Result {
|
||||
public final MojangJavaDownloads.JavaDownload download;
|
||||
public final MojangJavaRemoteFiles remoteFiles;
|
||||
|
||||
public Result(MojangJavaDownloads.JavaDownload download, MojangJavaRemoteFiles remoteFiles) {
|
||||
this.download = download;
|
||||
this.remoteFiles = remoteFiles;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.download.java;
|
||||
package org.jackhuang.hmcl.download.java.mojang;
|
||||
|
||||
import com.google.gson.*;
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
@@ -27,12 +27,12 @@ import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@JsonAdapter(JavaDownloads.Adapter.class)
|
||||
public class JavaDownloads {
|
||||
@JsonAdapter(MojangJavaDownloads.Adapter.class)
|
||||
public class MojangJavaDownloads {
|
||||
|
||||
private final Map<String, Map<String, List<JavaDownload>>> downloads;
|
||||
|
||||
public JavaDownloads(Map<String, Map<String, List<JavaDownload>>> downloads) {
|
||||
public MojangJavaDownloads(Map<String, Map<String, List<JavaDownload>>> downloads) {
|
||||
this.downloads = downloads;
|
||||
}
|
||||
|
||||
@@ -40,16 +40,16 @@ public class JavaDownloads {
|
||||
return downloads;
|
||||
}
|
||||
|
||||
public static class Adapter implements JsonSerializer<JavaDownloads>, JsonDeserializer<JavaDownloads> {
|
||||
public static class Adapter implements JsonSerializer<MojangJavaDownloads>, JsonDeserializer<MojangJavaDownloads> {
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(JavaDownloads src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
public JsonElement serialize(MojangJavaDownloads src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
return context.serialize(src.downloads);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaDownloads deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
return new JavaDownloads(context.deserialize(json, new TypeToken<Map<String, Map<String, List<JavaDownload>>>>() {
|
||||
public MojangJavaDownloads deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
return new MojangJavaDownloads(context.deserialize(json, new TypeToken<Map<String, Map<String, List<JavaDownload>>>>() {
|
||||
}.getType()));
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.download.java;
|
||||
package org.jackhuang.hmcl.download.java.mojang;
|
||||
|
||||
import org.jackhuang.hmcl.game.DownloadInfo;
|
||||
import org.jackhuang.hmcl.util.gson.JsonSubtype;
|
||||
@@ -24,10 +24,10 @@ import org.jackhuang.hmcl.util.gson.JsonType;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
public class RemoteFiles {
|
||||
public final class MojangJavaRemoteFiles {
|
||||
private final Map<String, Remote> files;
|
||||
|
||||
public RemoteFiles(Map<String, Remote> files) {
|
||||
public MojangJavaRemoteFiles(Map<String, Remote> files) {
|
||||
this.files = files;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.download.java.mojang;
|
||||
|
||||
import org.jackhuang.hmcl.download.java.JavaRemoteVersion;
|
||||
import org.jackhuang.hmcl.game.GameJavaVersion;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class MojangJavaRemoteVersion implements JavaRemoteVersion {
|
||||
private final GameJavaVersion gameJavaVersion;
|
||||
|
||||
public MojangJavaRemoteVersion(GameJavaVersion gameJavaVersion) {
|
||||
this.gameJavaVersion = gameJavaVersion;
|
||||
}
|
||||
|
||||
public GameJavaVersion getGameJavaVersion() {
|
||||
return gameJavaVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getJdkVersion() {
|
||||
return gameJavaVersion.getMajorVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getJavaVersion() {
|
||||
return String.valueOf(getJdkVersion());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDistributionVersion() {
|
||||
return String.valueOf(getJdkVersion());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MojangJavaRemoteVersion[gameJavaVersion=" + gameJavaVersion + "]";
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ import org.jackhuang.hmcl.util.io.ChecksumMismatchException;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.platform.CommandBuilder;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.SystemUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -125,7 +125,7 @@ public class NeoForgeOldInstallTask extends Task<Version> {
|
||||
throw new Exception("Game processor jar does not have main class " + jar);
|
||||
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add(JavaVersion.fromCurrentEnvironment().getBinary().toString());
|
||||
command.add(JavaRuntime.getDefault().getBinary().toString());
|
||||
command.add("-cp");
|
||||
|
||||
List<String> classpath = new ArrayList<>(processor.getClasspath().size() + 1);
|
||||
|
||||
@@ -27,7 +27,7 @@ import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.platform.CommandBuilder;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
import org.jackhuang.hmcl.util.platform.SystemUtils;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
import org.jenkinsci.constant_pool_scanner.ConstantPool;
|
||||
@@ -144,7 +144,7 @@ public final class OptiFineInstallTask extends Task<Version> {
|
||||
Path optiFineLibraryPath = gameRepository.getLibraryFile(version, optiFineLibrary).toPath();
|
||||
if (Files.exists(fs.getPath("optifine/Patcher.class"))) {
|
||||
String[] command = {
|
||||
JavaVersion.fromCurrentEnvironment().getBinary().toString(),
|
||||
JavaRuntime.getDefault().getBinary().toString(),
|
||||
"-cp",
|
||||
dest.toString(),
|
||||
"optifine.Patcher",
|
||||
|
||||
@@ -17,7 +17,91 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
public class GameJavaVersion {
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.Platform;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public final class GameJavaVersion {
|
||||
public static final GameJavaVersion JAVA_21 = new GameJavaVersion("java-runtime-delta", 21);
|
||||
public static final GameJavaVersion JAVA_17 = new GameJavaVersion("java-runtime-beta", 17);
|
||||
public static final GameJavaVersion JAVA_16 = new GameJavaVersion("java-runtime-alpha", 16);
|
||||
public static final GameJavaVersion JAVA_8 = new GameJavaVersion("jre-legacy", 8);
|
||||
|
||||
public static final GameJavaVersion LATEST = JAVA_21;
|
||||
|
||||
public static GameJavaVersion getMinimumJavaVersion(GameVersionNumber gameVersion) {
|
||||
if (gameVersion.compareTo("1.21") >= 0)
|
||||
return JAVA_21;
|
||||
if (gameVersion.compareTo("1.18") >= 0)
|
||||
return JAVA_17;
|
||||
if (gameVersion.compareTo("1.17") >= 0)
|
||||
return JAVA_16;
|
||||
if (gameVersion.compareTo("1.13") >= 0)
|
||||
return JAVA_8;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static GameJavaVersion get(int major) {
|
||||
switch (major) {
|
||||
case 8:
|
||||
return JAVA_8;
|
||||
case 16:
|
||||
return JAVA_16;
|
||||
case 17:
|
||||
return JAVA_17;
|
||||
case 21:
|
||||
return JAVA_21;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isSupportedPlatform(Platform platform) {
|
||||
OperatingSystem os = platform.getOperatingSystem();
|
||||
Architecture arch = platform.getArchitecture();
|
||||
switch (arch) {
|
||||
case X86:
|
||||
return os == OperatingSystem.WINDOWS || os == OperatingSystem.LINUX;
|
||||
case X86_64:
|
||||
return os == OperatingSystem.WINDOWS || os == OperatingSystem.LINUX || os == OperatingSystem.OSX;
|
||||
case ARM64:
|
||||
return os == OperatingSystem.WINDOWS || os == OperatingSystem.OSX;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<GameJavaVersion> getSupportedVersions(Platform platform) {
|
||||
OperatingSystem operatingSystem = platform.getOperatingSystem();
|
||||
Architecture architecture = platform.getArchitecture();
|
||||
if (architecture == Architecture.X86) {
|
||||
switch (operatingSystem) {
|
||||
case WINDOWS:
|
||||
return Arrays.asList(JAVA_8, JAVA_16, JAVA_17);
|
||||
case LINUX:
|
||||
return Collections.singletonList(JAVA_8);
|
||||
}
|
||||
} else if (architecture == Architecture.X86_64) {
|
||||
switch (operatingSystem) {
|
||||
case WINDOWS:
|
||||
case LINUX:
|
||||
case OSX:
|
||||
return Arrays.asList(JAVA_8, JAVA_16, JAVA_17, JAVA_21);
|
||||
}
|
||||
} else if (architecture == Architecture.ARM64) {
|
||||
switch (operatingSystem) {
|
||||
case WINDOWS:
|
||||
case OSX:
|
||||
return Arrays.asList(JAVA_17, JAVA_21);
|
||||
}
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private final String component;
|
||||
private final int majorVersion;
|
||||
|
||||
@@ -38,8 +122,16 @@ public class GameJavaVersion {
|
||||
return majorVersion;
|
||||
}
|
||||
|
||||
public static final GameJavaVersion JAVA_21 = new GameJavaVersion("java-runtime-delta", 21);
|
||||
public static final GameJavaVersion JAVA_17 = new GameJavaVersion("java-runtime-beta", 17);
|
||||
public static final GameJavaVersion JAVA_16 = new GameJavaVersion("java-runtime-alpha", 16);
|
||||
public static final GameJavaVersion JAVA_8 = new GameJavaVersion("jre-legacy", 8);
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getMajorVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof GameJavaVersion)) return false;
|
||||
GameJavaVersion that = (GameJavaVersion) o;
|
||||
return majorVersion == that.majorVersion;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ package org.jackhuang.hmcl.game;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
@@ -33,48 +33,47 @@ import java.util.Objects;
|
||||
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LAUNCH_WRAPPER_MAIN;
|
||||
|
||||
public enum JavaVersionConstraint {
|
||||
// Minecraft>=1.13 requires Java 8
|
||||
VANILLA_JAVA_8(JavaVersionConstraint.RULE_MANDATORY, GameVersionNumber.atLeast("1.13"), VersionNumber.atLeast("1.8")),
|
||||
// Minecraft 1.17 requires Java 16
|
||||
VANILLA_JAVA_16(JavaVersionConstraint.RULE_MANDATORY, GameVersionNumber.atLeast("1.17"), VersionNumber.atLeast("16")),
|
||||
// Minecraft>=1.18 requires Java 17
|
||||
VANILLA_JAVA_17(JavaVersionConstraint.RULE_MANDATORY, GameVersionNumber.atLeast("1.18"), VersionNumber.atLeast("17")),
|
||||
// Minecraft>=1.20.5 requires Java 21
|
||||
VANILLA_JAVA_21(JavaVersionConstraint.RULE_MANDATORY, GameVersionNumber.atLeast("1.20.5"), VersionNumber.atLeast("21")),
|
||||
VANILLA(true, VersionRange.all(), VersionRange.all()) {
|
||||
@Override
|
||||
public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaRuntime java) {
|
||||
GameJavaVersion minimumJavaVersion = GameJavaVersion.getMinimumJavaVersion(gameVersionNumber);
|
||||
return minimumJavaVersion == null || java.getParsedVersion() >= minimumJavaVersion.getMajorVersion();
|
||||
}
|
||||
},
|
||||
// Minecraft<=1.7.2+Forge requires Java<=7, But LegacyModFixer may fix that problem. So only suggest user using Java 7.
|
||||
MODDED_JAVA_7(JavaVersionConstraint.RULE_SUGGESTED, GameVersionNumber.atMost("1.7.2"), VersionNumber.atMost("1.7.999")) {
|
||||
MODDED_JAVA_7(false, GameVersionNumber.atMost("1.7.2"), VersionNumber.atMost("1.7.999")) {
|
||||
@Override
|
||||
protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,
|
||||
@Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) {
|
||||
@Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) {
|
||||
return version != null && analyzer != null && analyzer.has(LibraryAnalyzer.LibraryType.FORGE);
|
||||
}
|
||||
},
|
||||
MODDED_JAVA_8(JavaVersionConstraint.RULE_SUGGESTED, GameVersionNumber.between("1.7.10", "1.16.999"), VersionNumber.between("1.8", "1.8.999")) {
|
||||
MODDED_JAVA_8(false, GameVersionNumber.between("1.7.10", "1.16.999"), VersionNumber.between("1.8", "1.8.999")) {
|
||||
@Override
|
||||
protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,
|
||||
@Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) {
|
||||
@Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) {
|
||||
return analyzer != null && analyzer.has(LibraryAnalyzer.LibraryType.FORGE);
|
||||
}
|
||||
},
|
||||
MODDED_JAVA_16(JavaVersionConstraint.RULE_SUGGESTED, GameVersionNumber.between("1.17", "1.17.999"), VersionNumber.between("16", "16.999")) {
|
||||
MODDED_JAVA_16(false, GameVersionNumber.between("1.17", "1.17.999"), VersionNumber.between("16", "16.999")) {
|
||||
@Override
|
||||
protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,
|
||||
@Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) {
|
||||
@Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) {
|
||||
return analyzer != null && analyzer.has(LibraryAnalyzer.LibraryType.FORGE);
|
||||
}
|
||||
},
|
||||
MODDED_JAVA_17(JavaVersionConstraint.RULE_SUGGESTED, GameVersionNumber.atLeast("1.18"), VersionNumber.between("17", "17.999")) {
|
||||
MODDED_JAVA_17(false, GameVersionNumber.atLeast("1.18"), VersionNumber.between("17", "17.999")) {
|
||||
@Override
|
||||
protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,
|
||||
@Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) {
|
||||
@Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) {
|
||||
return analyzer != null && analyzer.has(LibraryAnalyzer.LibraryType.FORGE);
|
||||
}
|
||||
},
|
||||
// LaunchWrapper<=1.12 will crash because LaunchWrapper assumes the system class loader is an instance of URLClassLoader (Java 8)
|
||||
LAUNCH_WRAPPER(JavaVersionConstraint.RULE_MANDATORY, GameVersionNumber.atMost("1.12.999"), VersionNumber.atMost("1.8.999")) {
|
||||
LAUNCH_WRAPPER(true, GameVersionNumber.atMost("1.12.999"), VersionNumber.atMost("1.8.999")) {
|
||||
@Override
|
||||
protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,
|
||||
@Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) {
|
||||
@Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) {
|
||||
if (version == null) return false;
|
||||
return LAUNCH_WRAPPER_MAIN.equals(version.getMainClass()) &&
|
||||
version.getLibraries().stream()
|
||||
@@ -83,12 +82,12 @@ public enum JavaVersionConstraint {
|
||||
}
|
||||
},
|
||||
// Minecraft>=1.13 may crash when generating world on Java [1.8,1.8.0_51)
|
||||
VANILLA_JAVA_8_51(JavaVersionConstraint.RULE_SUGGESTED, GameVersionNumber.atLeast("1.13"), VersionNumber.atLeast("1.8.0_51")),
|
||||
VANILLA_JAVA_8_51(false, GameVersionNumber.atLeast("1.13"), VersionNumber.atLeast("1.8.0_51")),
|
||||
// Minecraft with suggested java version recorded in game json is restrictedly constrained.
|
||||
GAME_JSON(JavaVersionConstraint.RULE_MANDATORY, VersionRange.all(), VersionRange.all()) {
|
||||
GAME_JSON(true, VersionRange.all(), VersionRange.all()) {
|
||||
@Override
|
||||
protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,
|
||||
@Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) {
|
||||
@Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) {
|
||||
if (version == null) return false;
|
||||
// We only checks for 1.7.10 and above, since 1.7.2 with Forge can only run on Java 7, but it is recorded Java 8 in game json, which is not correct.
|
||||
return gameVersionNumber.compareTo("1.7.10") >= 0 && version.getJavaVersion() != null;
|
||||
@@ -107,26 +106,26 @@ public enum JavaVersionConstraint {
|
||||
},
|
||||
// On Linux, JDK 9+ cannot launch Minecraft<=1.12.2, since JDK 9+ does not accept loading native library built in different arch.
|
||||
// For example, JDK 9+ 64-bit cannot load 32-bit lwjgl native library.
|
||||
VANILLA_LINUX_JAVA_8(JavaVersionConstraint.RULE_MANDATORY, GameVersionNumber.atMost("1.12.999"), VersionNumber.atMost("1.8.999")) {
|
||||
VANILLA_LINUX_JAVA_8(true, GameVersionNumber.atMost("1.12.999"), VersionNumber.atMost("1.8.999")) {
|
||||
@Override
|
||||
protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,
|
||||
@Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) {
|
||||
@Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) {
|
||||
return OperatingSystem.CURRENT_OS == OperatingSystem.LINUX
|
||||
&& Architecture.SYSTEM_ARCH == Architecture.X86_64
|
||||
&& (javaVersion == null || javaVersion.getArchitecture() == Architecture.X86_64);
|
||||
&& (java == null || java.getArchitecture() == Architecture.X86_64);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaVersion javaVersion) {
|
||||
return javaVersion.getArchitecture() != Architecture.X86_64 || super.checkJava(gameVersionNumber, version, javaVersion);
|
||||
public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaRuntime java) {
|
||||
return java.getArchitecture() != Architecture.X86_64 || super.checkJava(gameVersionNumber, version, java);
|
||||
}
|
||||
},
|
||||
// Minecraft currently does not provide official support for architectures other than x86 and x86-64.
|
||||
VANILLA_X86(JavaVersionConstraint.RULE_SUGGESTED, VersionRange.all(), VersionRange.all()) {
|
||||
VANILLA_X86(false, VersionRange.all(), VersionRange.all()) {
|
||||
@Override
|
||||
protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,
|
||||
@Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) {
|
||||
if (javaVersion == null || javaVersion.getArchitecture() != Architecture.ARM64)
|
||||
@Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) {
|
||||
if (java == null || java.getArchitecture() != Architecture.ARM64)
|
||||
return false;
|
||||
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS || OperatingSystem.CURRENT_OS == OperatingSystem.OSX)
|
||||
@@ -136,16 +135,16 @@ public enum JavaVersionConstraint {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaVersion javaVersion) {
|
||||
return javaVersion.getArchitecture().isX86();
|
||||
public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaRuntime java) {
|
||||
return java.getArchitecture().isX86();
|
||||
}
|
||||
},
|
||||
// Minecraft 1.16+Forge with crash because JDK-8273826
|
||||
MODLAUNCHER_8(JavaVersionConstraint.RULE_SUGGESTED, GameVersionNumber.between("1.16.3", "1.17.1"), VersionRange.all()) {
|
||||
MODLAUNCHER_8(false, GameVersionNumber.between("1.16.3", "1.17.1"), VersionRange.all()) {
|
||||
@Override
|
||||
protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,
|
||||
@Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) {
|
||||
if (version == null || javaVersion == null || analyzer == null) return false;
|
||||
@Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) {
|
||||
if (version == null || java == null || analyzer == null) return false;
|
||||
VersionNumber forgePatchVersion = analyzer.getVersion(LibraryAnalyzer.LibraryType.FORGE)
|
||||
.map(VersionNumber::asVersion)
|
||||
.orElse(null);
|
||||
@@ -167,36 +166,36 @@ public enum JavaVersionConstraint {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaVersion javaVersion) {
|
||||
int parsedJavaVersion = javaVersion.getParsedVersion();
|
||||
public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaRuntime java) {
|
||||
int parsedJavaVersion = java.getParsedVersion();
|
||||
if (parsedJavaVersion > 17) {
|
||||
return false;
|
||||
} else if (parsedJavaVersion == 8) {
|
||||
return javaVersion.getVersionNumber().compareTo(VersionNumber.asVersion("1.8.0_321")) < 0;
|
||||
return java.getVersionNumber().compareTo(VersionNumber.asVersion("1.8.0_321")) < 0;
|
||||
} else if (parsedJavaVersion == 11) {
|
||||
return javaVersion.getVersionNumber().compareTo(VersionNumber.asVersion("11.0.14")) < 0;
|
||||
return java.getVersionNumber().compareTo(VersionNumber.asVersion("11.0.14")) < 0;
|
||||
} else if (parsedJavaVersion == 15) {
|
||||
return javaVersion.getVersionNumber().compareTo(VersionNumber.asVersion("15.0.6")) < 0;
|
||||
return java.getVersionNumber().compareTo(VersionNumber.asVersion("15.0.6")) < 0;
|
||||
} else if (parsedJavaVersion == 17) {
|
||||
return javaVersion.getVersionNumber().compareTo(VersionNumber.asVersion("17.0.2")) < 0;
|
||||
return java.getVersionNumber().compareTo(VersionNumber.asVersion("17.0.2")) < 0;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final int type;
|
||||
private final boolean isMandatory;
|
||||
private final VersionRange<GameVersionNumber> gameVersionRange;
|
||||
private final VersionRange<VersionNumber> javaVersionRange;
|
||||
|
||||
JavaVersionConstraint(int type, VersionRange<GameVersionNumber> gameVersionRange, VersionRange<VersionNumber> javaVersionRange) {
|
||||
this.type = type;
|
||||
JavaVersionConstraint(boolean isMandatory, VersionRange<GameVersionNumber> gameVersionRange, VersionRange<VersionNumber> javaVersionRange) {
|
||||
this.isMandatory = isMandatory;
|
||||
this.gameVersionRange = gameVersionRange;
|
||||
this.javaVersionRange = javaVersionRange;
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return type;
|
||||
public boolean isMandatory() {
|
||||
return isMandatory;
|
||||
}
|
||||
|
||||
public VersionRange<GameVersionNumber> getGameVersionRange() {
|
||||
@@ -208,112 +207,20 @@ public enum JavaVersionConstraint {
|
||||
}
|
||||
|
||||
public final boolean appliesToVersion(@Nullable GameVersionNumber gameVersionNumber, @Nullable Version version,
|
||||
@Nullable JavaVersion javaVersion, LibraryAnalyzer analyzer) {
|
||||
@Nullable JavaRuntime java, LibraryAnalyzer analyzer) {
|
||||
return gameVersionRange.contains(gameVersionNumber)
|
||||
&& appliesToVersionImpl(gameVersionNumber, version, javaVersion, analyzer);
|
||||
&& appliesToVersionImpl(gameVersionNumber, version, java, analyzer);
|
||||
}
|
||||
|
||||
protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,
|
||||
@Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) {
|
||||
@Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaVersion javaVersion) {
|
||||
return getJavaVersionRange(version).contains(javaVersion.getVersionNumber());
|
||||
public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaRuntime java) {
|
||||
return getJavaVersionRange(version).contains(java.getVersionNumber());
|
||||
}
|
||||
|
||||
public static final List<JavaVersionConstraint> ALL = Lang.immutableListOf(values());
|
||||
|
||||
public static VersionRanges findSuitableJavaVersionRange(GameVersionNumber gameVersion, Version version) {
|
||||
VersionRange<VersionNumber> mandatoryJavaRange = VersionRange.all();
|
||||
VersionRange<VersionNumber> suggestedJavaRange = VersionRange.all();
|
||||
LibraryAnalyzer analyzer = version != null ? LibraryAnalyzer.analyze(version, gameVersion != null ? gameVersion.toString() : null) : null;
|
||||
for (JavaVersionConstraint java : ALL) {
|
||||
if (java.appliesToVersion(gameVersion, version, null, analyzer)) {
|
||||
VersionRange<VersionNumber> javaVersionRange = java.getJavaVersionRange(version);
|
||||
if (java.type == RULE_MANDATORY) {
|
||||
mandatoryJavaRange = mandatoryJavaRange.intersectionWith(javaVersionRange);
|
||||
suggestedJavaRange = suggestedJavaRange.intersectionWith(javaVersionRange);
|
||||
} else if (java.type == RULE_SUGGESTED) {
|
||||
suggestedJavaRange = suggestedJavaRange.intersectionWith(javaVersionRange);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new VersionRanges(mandatoryJavaRange, suggestedJavaRange);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static JavaVersion findSuitableJavaVersion(GameVersionNumber gameVersion, Version version) throws InterruptedException {
|
||||
VersionRanges range = findSuitableJavaVersionRange(gameVersion, version);
|
||||
|
||||
boolean forceX86 = Architecture.SYSTEM_ARCH == Architecture.ARM64
|
||||
&& (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS || OperatingSystem.CURRENT_OS == OperatingSystem.OSX)
|
||||
&& gameVersion.compareTo("1.6") < 0;
|
||||
|
||||
JavaVersion mandatory = null;
|
||||
JavaVersion suggested = null;
|
||||
for (JavaVersion javaVersion : JavaVersion.getJavas()) {
|
||||
// Do not automatically select 32-bit Java
|
||||
if (Architecture.SYSTEM_ARCH == Architecture.X86_64 && javaVersion.getArchitecture() == Architecture.X86)
|
||||
continue;
|
||||
|
||||
// select the latest x86 java that this version accepts.
|
||||
if (forceX86 && !javaVersion.getArchitecture().isX86())
|
||||
continue;
|
||||
|
||||
VersionNumber javaVersionNumber = javaVersion.getVersionNumber();
|
||||
if (range.getMandatory().contains(javaVersionNumber)) {
|
||||
if (mandatory == null) mandatory = javaVersion;
|
||||
else if (compareJavaVersion(javaVersion, mandatory) > 0) {
|
||||
mandatory = javaVersion;
|
||||
}
|
||||
}
|
||||
if (range.getSuggested().contains(javaVersionNumber)) {
|
||||
if (suggested == null) suggested = javaVersion;
|
||||
else if (compareJavaVersion(javaVersion, suggested) > 0) {
|
||||
suggested = javaVersion;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (suggested != null) return suggested;
|
||||
else return mandatory;
|
||||
}
|
||||
|
||||
private static int compareJavaVersion(JavaVersion javaVersion1, JavaVersion javaVersion2) {
|
||||
Architecture arch1 = javaVersion1.getArchitecture();
|
||||
Architecture arch2 = javaVersion2.getArchitecture();
|
||||
|
||||
if (arch1 != arch2) {
|
||||
if (arch1 == Architecture.X86_64) {
|
||||
return 1;
|
||||
}
|
||||
if (arch2 == Architecture.X86_64) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return javaVersion1.getVersionNumber().compareTo(javaVersion2.getVersionNumber());
|
||||
}
|
||||
|
||||
public static final int RULE_MANDATORY = 1;
|
||||
public static final int RULE_SUGGESTED = 2;
|
||||
|
||||
public static final class VersionRanges {
|
||||
private final VersionRange<VersionNumber> mandatory;
|
||||
private final VersionRange<VersionNumber> suggested;
|
||||
|
||||
public VersionRanges(VersionRange<VersionNumber> mandatory, VersionRange<VersionNumber> suggested) {
|
||||
this.mandatory = mandatory;
|
||||
this.suggested = suggested;
|
||||
}
|
||||
|
||||
public VersionRange<VersionNumber> getMandatory() {
|
||||
return mandatory;
|
||||
}
|
||||
|
||||
public VersionRange<VersionNumber> getSuggested() {
|
||||
return suggested;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.File;
|
||||
@@ -32,7 +32,7 @@ import java.util.*;
|
||||
public class LaunchOptions implements Serializable {
|
||||
|
||||
private File gameDir;
|
||||
private JavaVersion java;
|
||||
private JavaRuntime java;
|
||||
private String versionName;
|
||||
private String versionType;
|
||||
private String profileName;
|
||||
@@ -73,7 +73,7 @@ public class LaunchOptions implements Serializable {
|
||||
/**
|
||||
* The Java Environment that Minecraft runs on.
|
||||
*/
|
||||
public JavaVersion getJava() {
|
||||
public JavaRuntime getJava() {
|
||||
return java;
|
||||
}
|
||||
|
||||
@@ -312,7 +312,7 @@ public class LaunchOptions implements Serializable {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setJava(JavaVersion java) {
|
||||
public Builder setJava(JavaRuntime java) {
|
||||
options.java = java;
|
||||
return this;
|
||||
}
|
||||
|
||||
280
HMCLCore/src/main/java/org/jackhuang/hmcl/java/JavaInfo.java
Normal file
280
HMCLCore/src/main/java/org/jackhuang/hmcl/java/JavaInfo.java
Normal file
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
* 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.util.KeyValuePairProperties;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.Platform;
|
||||
import org.jackhuang.hmcl.util.tree.ArchiveFileTree;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class JavaInfo {
|
||||
public static int parseVersion(String version) {
|
||||
try {
|
||||
int idx = version.indexOf('.');
|
||||
if (idx < 0) {
|
||||
idx = version.indexOf('u');
|
||||
return idx > 0 ? Integer.parseInt(version.substring(0, idx)) : Integer.parseInt(version);
|
||||
} else {
|
||||
int major = Integer.parseInt(version.substring(0, idx));
|
||||
if (major != 1) {
|
||||
return major;
|
||||
} else {
|
||||
int idx2 = version.indexOf('.', idx + 1);
|
||||
if (idx2 < 0) {
|
||||
return -1;
|
||||
}
|
||||
return Integer.parseInt(version.substring(idx + 1, idx2));
|
||||
}
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public static JavaInfo fromReleaseFile(BufferedReader reader) throws IOException {
|
||||
KeyValuePairProperties properties = KeyValuePairProperties.load(reader);
|
||||
String osName = properties.get("OS_NAME");
|
||||
String osArch = properties.get("OS_ARCH");
|
||||
String vendor = properties.get("IMPLEMENTOR");
|
||||
|
||||
OperatingSystem os = "".equals(osName) && "OpenJDK BSD Porting Team".equals(vendor)
|
||||
? OperatingSystem.FREEBSD
|
||||
: OperatingSystem.parseOSName(osName);
|
||||
|
||||
Architecture arch = Architecture.parseArchName(osArch);
|
||||
String javaVersion = properties.get("JAVA_VERSION");
|
||||
|
||||
if (os == OperatingSystem.UNKNOWN)
|
||||
throw new IOException("Unknown operating system: " + osName);
|
||||
|
||||
if (arch == Architecture.UNKNOWN)
|
||||
throw new IOException("Unknown architecture: " + osArch);
|
||||
|
||||
if (javaVersion == null)
|
||||
throw new IOException("Missing Java version");
|
||||
|
||||
return new JavaInfo(Platform.getPlatform(os, arch), javaVersion, vendor);
|
||||
}
|
||||
|
||||
public static JavaInfo fromReleaseFile(Path releaseFile) throws IOException {
|
||||
try (BufferedReader reader = Files.newBufferedReader(releaseFile)) {
|
||||
return fromReleaseFile(reader);
|
||||
}
|
||||
}
|
||||
|
||||
public static <F, E extends ArchiveEntry> JavaInfo fromArchive(ArchiveFileTree<F, E> tree) throws IOException {
|
||||
if (tree.getRoot().getSubDirs().size() != 1 || !tree.getRoot().getFiles().isEmpty())
|
||||
throw new IOException();
|
||||
|
||||
ArchiveFileTree.Dir<E> jdkRoot = tree.getRoot().getSubDirs().values().iterator().next();
|
||||
E releaseEntry = jdkRoot.getFiles().get("release");
|
||||
if (releaseEntry == null)
|
||||
throw new IOException("Missing release file");
|
||||
|
||||
JavaInfo info;
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(tree.getInputStream(releaseEntry), StandardCharsets.UTF_8))) {
|
||||
info = JavaInfo.fromReleaseFile(reader);
|
||||
}
|
||||
|
||||
ArchiveFileTree.Dir<E> binDir = jdkRoot.getSubDirs().get("bin");
|
||||
if (binDir == null || binDir.getFiles().get(info.getPlatform().getOperatingSystem().getJavaExecutable()) == null)
|
||||
throw new IOException("Missing java executable file");
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
public static String normalizeVendor(String vendor) {
|
||||
if (vendor == null)
|
||||
return null;
|
||||
|
||||
switch (vendor) {
|
||||
case "N/A":
|
||||
return null;
|
||||
case "Oracle Corporation":
|
||||
return "Oracle";
|
||||
case "Azul Systems, Inc.":
|
||||
return "Azul";
|
||||
case "IBM Corporation":
|
||||
case "International Business Machines Corporation":
|
||||
return "IBM";
|
||||
case "Eclipse Adoptium":
|
||||
return "Adoptium";
|
||||
default:
|
||||
return vendor;
|
||||
}
|
||||
}
|
||||
|
||||
private static final String OS_ARCH = "os.arch = ";
|
||||
private static final String JAVA_VERSION = "java.version = ";
|
||||
private static final String JAVA_VENDOR = "java.vendor = ";
|
||||
private static final String VERSION_PREFIX = "version \"";
|
||||
|
||||
public static JavaInfo fromExecutable(Path executable) throws IOException {
|
||||
return fromExecutable(executable, true);
|
||||
}
|
||||
|
||||
public static JavaInfo fromExecutable(Path executable, boolean tryFindReleaseFile) throws IOException {
|
||||
assert executable.isAbsolute();
|
||||
Path parent = executable.getParent();
|
||||
if (tryFindReleaseFile && parent != null && parent.getFileName() != null && parent.getFileName().toString().equals("bin")) {
|
||||
Path javaHome = parent.getParent();
|
||||
if (javaHome != null && javaHome.getFileName() != null) {
|
||||
Path releaseFile = javaHome.resolve("release");
|
||||
String javaHomeName = javaHome.getFileName().toString();
|
||||
if ((javaHomeName.contains("jre") || javaHomeName.contains("jdk") || javaHomeName.contains("openj9")) && Files.isRegularFile(releaseFile)) {
|
||||
try {
|
||||
return fromReleaseFile(releaseFile);
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String osArch = null;
|
||||
String version = null;
|
||||
String vendor = null;
|
||||
Platform platform = null;
|
||||
|
||||
String executablePath = executable.toString();
|
||||
|
||||
Process process = new ProcessBuilder(executablePath, "-XshowSettings:properties", "-version").start();
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream(), OperatingSystem.NATIVE_CHARSET))) {
|
||||
for (String line; (line = reader.readLine()) != null; ) {
|
||||
|
||||
int idx = line.indexOf(OS_ARCH);
|
||||
if (idx >= 0) {
|
||||
osArch = line.substring(idx + OS_ARCH.length()).trim();
|
||||
if (version != null && vendor != null)
|
||||
break;
|
||||
else
|
||||
continue;
|
||||
}
|
||||
|
||||
idx = line.indexOf(JAVA_VERSION);
|
||||
if (idx >= 0) {
|
||||
version = line.substring(idx + JAVA_VERSION.length()).trim();
|
||||
if (osArch != null && vendor != null)
|
||||
break;
|
||||
else
|
||||
continue;
|
||||
}
|
||||
|
||||
idx = line.indexOf(JAVA_VENDOR);
|
||||
if (idx >= 0) {
|
||||
vendor = line.substring(idx + JAVA_VENDOR.length()).trim();
|
||||
if (osArch != null && version != null)
|
||||
break;
|
||||
else
|
||||
//noinspection UnnecessaryContinue
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (osArch != null)
|
||||
platform = Platform.getPlatform(OperatingSystem.CURRENT_OS, Architecture.parseArchName(osArch));
|
||||
|
||||
// Java 6
|
||||
if (version == null) {
|
||||
boolean is64Bit = false;
|
||||
process = new ProcessBuilder(executablePath, "-version").start();
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream(), OperatingSystem.NATIVE_CHARSET))) {
|
||||
for (String line; (line = reader.readLine()) != null; ) {
|
||||
if (version == null) {
|
||||
int idx = line.indexOf(VERSION_PREFIX);
|
||||
if (idx >= 0) {
|
||||
int begin = idx + VERSION_PREFIX.length();
|
||||
int end = line.indexOf('"', begin);
|
||||
if (end >= 0) {
|
||||
version = line.substring(begin, end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (line.contains("64-Bit"))
|
||||
is64Bit = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (platform == null)
|
||||
platform = Platform.getPlatform(OperatingSystem.CURRENT_OS, is64Bit ? Architecture.X86_64 : Architecture.X86);
|
||||
|
||||
if (version == null)
|
||||
throw new IOException("Cannot determine version");
|
||||
}
|
||||
|
||||
return new JavaInfo(platform, version, vendor);
|
||||
}
|
||||
|
||||
public static final JavaInfo CURRENT_ENVIRONMENT = new JavaInfo(Platform.CURRENT_PLATFORM, System.getProperty("java.version"), System.getProperty("java.vendor"));
|
||||
|
||||
private final Platform platform;
|
||||
private final String version;
|
||||
private final @Nullable String vendor;
|
||||
|
||||
private final transient int parsedVersion;
|
||||
private final transient VersionNumber versionNumber;
|
||||
|
||||
public JavaInfo(Platform platform, String version, @Nullable String vendor) {
|
||||
this.platform = platform;
|
||||
this.version = version;
|
||||
this.parsedVersion = parseVersion(version);
|
||||
this.versionNumber = VersionNumber.asVersion(version);
|
||||
this.vendor = vendor;
|
||||
}
|
||||
|
||||
public Platform getPlatform() {
|
||||
return platform;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public VersionNumber getVersionNumber() {
|
||||
return versionNumber;
|
||||
}
|
||||
|
||||
public int getParsedVersion() {
|
||||
return parsedVersion;
|
||||
}
|
||||
|
||||
public @Nullable String getVendor() {
|
||||
return vendor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return JsonUtils.GSON.toJson(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.game.GameJavaVersion;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.platform.Platform;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public interface JavaRepository {
|
||||
|
||||
Path getJavaDir(Platform platform, String name);
|
||||
|
||||
Path getManifestFile(Platform platform, String name);
|
||||
|
||||
Collection<JavaRuntime> getAllJava(Platform platform);
|
||||
|
||||
Task<JavaRuntime> getDownloadJavaTask(DownloadProvider downloadProvider, Platform platform, GameJavaVersion gameJavaVersion);
|
||||
|
||||
Task<Void> getUninstallJavaTask(Platform platform, String name);
|
||||
|
||||
Task<Void> getUninstallJavaTask(JavaRuntime java);
|
||||
}
|
||||
156
HMCLCore/src/main/java/org/jackhuang/hmcl/java/JavaRuntime.java
Normal file
156
HMCLCore/src/main/java/org/jackhuang/hmcl/java/JavaRuntime.java
Normal file
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* 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.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.Bits;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.Platform;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class JavaRuntime implements Comparable<JavaRuntime> {
|
||||
|
||||
public static JavaRuntime of(Path binary, JavaInfo info, boolean isManaged) {
|
||||
String javacName = info.getPlatform().getOperatingSystem() == OperatingSystem.WINDOWS ? "javac.exe" : "javac";
|
||||
return new JavaRuntime(binary, info, isManaged, Files.isRegularFile(binary.resolveSibling(javacName)));
|
||||
}
|
||||
|
||||
private final Path binary;
|
||||
private final JavaInfo info;
|
||||
private final boolean isManaged;
|
||||
private final boolean isJDK;
|
||||
|
||||
public JavaRuntime(Path binary, JavaInfo info, boolean isManaged, boolean isJDK) {
|
||||
this.binary = binary;
|
||||
this.info = info;
|
||||
this.isManaged = isManaged;
|
||||
this.isJDK = isJDK;
|
||||
}
|
||||
|
||||
public boolean isManaged() {
|
||||
return isManaged;
|
||||
}
|
||||
|
||||
public Path getBinary() {
|
||||
return binary;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return info.getVersion();
|
||||
}
|
||||
|
||||
public Platform getPlatform() {
|
||||
return info.getPlatform();
|
||||
}
|
||||
|
||||
public Architecture getArchitecture() {
|
||||
return getPlatform().getArchitecture();
|
||||
}
|
||||
|
||||
public Bits getBits() {
|
||||
return getPlatform().getBits();
|
||||
}
|
||||
|
||||
public VersionNumber getVersionNumber() {
|
||||
return info.getVersionNumber();
|
||||
}
|
||||
|
||||
/**
|
||||
* The major version of Java installation.
|
||||
*/
|
||||
public int getParsedVersion() {
|
||||
return info.getParsedVersion();
|
||||
}
|
||||
|
||||
public String getVendor() {
|
||||
return info.getVendor();
|
||||
}
|
||||
|
||||
public boolean isJDK() {
|
||||
return isJDK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull JavaRuntime that) {
|
||||
if (this.isManaged != that.isManaged) {
|
||||
return this.isManaged ? -1 : 1;
|
||||
}
|
||||
|
||||
int c = Integer.compare(this.getParsedVersion(), that.getParsedVersion());
|
||||
if (c != 0)
|
||||
return c;
|
||||
|
||||
c = this.getVersionNumber().compareTo(that.getVersionNumber());
|
||||
if (c != 0)
|
||||
return c;
|
||||
|
||||
c = this.getArchitecture().compareTo(that.getArchitecture());
|
||||
if (c != 0)
|
||||
return c;
|
||||
|
||||
return this.getBinary().compareTo(that.getBinary());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return binary.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof JavaRuntime)) return false;
|
||||
JavaRuntime that = (JavaRuntime) o;
|
||||
return this.getBinary().equals(that.getBinary());
|
||||
}
|
||||
|
||||
public static final JavaRuntime CURRENT_JAVA;
|
||||
public static final int CURRENT_VERSION;
|
||||
|
||||
public static JavaRuntime getDefault() {
|
||||
return CURRENT_JAVA;
|
||||
}
|
||||
|
||||
static {
|
||||
String javaHome = System.getProperty("java.home");
|
||||
Path executable = null;
|
||||
if (javaHome != null) {
|
||||
executable = Paths.get(javaHome, "bin", OperatingSystem.CURRENT_OS.getJavaExecutable());
|
||||
try {
|
||||
executable = executable.toRealPath();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
|
||||
if (!Files.isRegularFile(executable)) {
|
||||
executable = null;
|
||||
}
|
||||
}
|
||||
|
||||
CURRENT_JAVA = executable != null ? JavaRuntime.of(executable, JavaInfo.CURRENT_ENVIRONMENT, false) : null;
|
||||
CURRENT_VERSION = JavaInfo.CURRENT_ENVIRONMENT.getParsedVersion();
|
||||
}
|
||||
}
|
||||
@@ -133,7 +133,7 @@ public class DefaultLauncher extends Launcher {
|
||||
res.addDefault("-Xms", options.getMinMemory() + "m");
|
||||
|
||||
if (options.getMetaspace() != null && options.getMetaspace() > 0)
|
||||
if (options.getJava().getParsedVersion() < JavaVersion.JAVA_8)
|
||||
if (options.getJava().getParsedVersion() < 8)
|
||||
res.addDefault("-XX:PermSize=", options.getMetaspace() + "m");
|
||||
else
|
||||
res.addDefault("-XX:MetaspaceSize=", options.getMetaspace() + "m");
|
||||
@@ -186,7 +186,7 @@ public class DefaultLauncher extends Launcher {
|
||||
res.addDefault("-Duser.home=", options.getGameDir().getParent());
|
||||
|
||||
// Using G1GC with its settings by default
|
||||
if (options.getJava().getParsedVersion() >= JavaVersion.JAVA_8
|
||||
if (options.getJava().getParsedVersion() >= 8
|
||||
&& res.noneMatch(arg -> "-XX:-UseG1GC".equals(arg) || (arg.startsWith("-XX:+Use") && arg.endsWith("GC")))) {
|
||||
res.addUnstableDefault("UnlockExperimentalVMOptions", true);
|
||||
res.addUnstableDefault("UseG1GC", true);
|
||||
@@ -206,7 +206,7 @@ public class DefaultLauncher extends Launcher {
|
||||
res.addDefault("-Xss", "1m");
|
||||
}
|
||||
|
||||
if (options.getJava().getParsedVersion() == JavaVersion.JAVA_16)
|
||||
if (options.getJava().getParsedVersion() == 16)
|
||||
res.addDefault("--illegal-access=", "permit");
|
||||
|
||||
res.addDefault("-Dfml.ignoreInvalidMinecraftCertificates=", "true");
|
||||
@@ -308,7 +308,7 @@ public class DefaultLauncher extends Launcher {
|
||||
}
|
||||
|
||||
private final Map<String, Supplier<Boolean>> forbiddens = mapOf(
|
||||
pair("-Xincgc", () -> options.getJava().getParsedVersion() >= JavaVersion.JAVA_9)
|
||||
pair("-Xincgc", () -> options.getJava().getParsedVersion() >= 9)
|
||||
);
|
||||
|
||||
protected Map<String, Supplier<Boolean>> getForbiddens() {
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class KeyValuePairProperties extends LinkedHashMap<String, String> {
|
||||
public static KeyValuePairProperties load(Path file) throws IOException {
|
||||
try (BufferedReader reader = Files.newBufferedReader(file)) {
|
||||
return load(reader);
|
||||
}
|
||||
}
|
||||
|
||||
public static KeyValuePairProperties load(BufferedReader reader) throws IOException {
|
||||
KeyValuePairProperties result = new KeyValuePairProperties();
|
||||
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (line.startsWith("#"))
|
||||
continue;
|
||||
|
||||
int idx = line.indexOf('=');
|
||||
if (idx <= 0)
|
||||
continue;
|
||||
|
||||
String name = line.substring(0, idx);
|
||||
String value;
|
||||
|
||||
if (line.length() > idx + 2 && line.charAt(idx + 1) == '"' && line.charAt(line.length() - 1) == '"') {
|
||||
if (line.indexOf('\\', idx + 1) < 0) {
|
||||
value = line.substring(idx + 2, line.length() - 1);
|
||||
} else {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (int i = idx + 2, end = line.length() - 1; i < end; i++) {
|
||||
char ch = line.charAt(i);
|
||||
if (ch == '\\' && i < end - 1) {
|
||||
char nextChar = line.charAt(++i);
|
||||
switch (nextChar) {
|
||||
case 'n':
|
||||
builder.append('\n');
|
||||
break;
|
||||
case 'r':
|
||||
builder.append('\r');
|
||||
break;
|
||||
case 't':
|
||||
builder.append('\t');
|
||||
break;
|
||||
case 'f':
|
||||
builder.append('\f');
|
||||
break;
|
||||
case 'b':
|
||||
builder.append('\b');
|
||||
break;
|
||||
default:
|
||||
builder.append(nextChar);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
builder.append(ch);
|
||||
}
|
||||
}
|
||||
value = builder.toString();
|
||||
}
|
||||
} else {
|
||||
value = line.substring(idx + 1);
|
||||
}
|
||||
|
||||
result.put(name, value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -377,15 +377,15 @@ public final class StringUtils {
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
public static int MAX_SHORT_STRING_LENGTH = 77;
|
||||
public static String truncate(String str, int limit) {
|
||||
assert limit > 5;
|
||||
|
||||
public static Optional<String> truncate(String str) {
|
||||
if (str.length() <= MAX_SHORT_STRING_LENGTH) {
|
||||
return Optional.empty();
|
||||
if (str.length() <= limit) {
|
||||
return str;
|
||||
}
|
||||
|
||||
final int halfLength = (MAX_SHORT_STRING_LENGTH - 5) / 2;
|
||||
return Optional.of(str.substring(0, halfLength) + " ... " + str.substring(str.length() - halfLength));
|
||||
final int halfLength = (limit - 5) / 2;
|
||||
return str.substring(0, halfLength) + " ... " + str.substring(str.length() - halfLength);
|
||||
}
|
||||
|
||||
public static boolean isASCII(String cs) {
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -73,6 +74,13 @@ public final class JsonUtils {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
public static <T> T fromNonNullJson(String json, TypeToken<T> type) throws JsonParseException {
|
||||
T parsed = GSON.fromJson(json, type);
|
||||
if (parsed == null)
|
||||
throw new JsonParseException("Json object cannot be null.");
|
||||
return parsed;
|
||||
}
|
||||
|
||||
public static <T> T fromNonNullJsonFully(InputStream json, Class<T> classOfT) throws IOException, JsonParseException {
|
||||
try (InputStreamReader reader = new InputStreamReader(json, StandardCharsets.UTF_8)) {
|
||||
T parsed = GSON.fromJson(reader, classOfT);
|
||||
|
||||
@@ -90,4 +90,21 @@ public final class IOUtils {
|
||||
public static InputStream wrapFromGZip(InputStream inputStream) throws IOException {
|
||||
return new GZIPInputStream(inputStream);
|
||||
}
|
||||
|
||||
public static void closeQuietly(AutoCloseable closeable) {
|
||||
try {
|
||||
if (closeable != null)
|
||||
closeable.close();
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
public static void closeQuietly(AutoCloseable closeable, Throwable exception) {
|
||||
try {
|
||||
if (closeable != null)
|
||||
closeable.close();
|
||||
} catch (Throwable e) {
|
||||
exception.addSuppressed(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,6 +176,9 @@ public enum Architecture {
|
||||
return LOONGARCH64_OW;
|
||||
return LOONGARCH64;
|
||||
}
|
||||
case "loongarch64_ow": {
|
||||
return LOONGARCH64_OW;
|
||||
}
|
||||
default:
|
||||
if (value.startsWith("armv7")) {
|
||||
return ARM32;
|
||||
|
||||
@@ -1,449 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util.platform;
|
||||
|
||||
import org.jackhuang.hmcl.download.java.JavaRepository;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
/**
|
||||
* Represents a Java installation.
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class JavaVersion {
|
||||
|
||||
private final Path binary;
|
||||
private final String longVersion;
|
||||
private final Platform platform;
|
||||
private final int version;
|
||||
private final VersionNumber versionNumber;
|
||||
|
||||
public JavaVersion(Path binary, String longVersion, Platform platform) {
|
||||
this.binary = binary;
|
||||
this.longVersion = longVersion;
|
||||
this.platform = platform;
|
||||
|
||||
if (longVersion != null) {
|
||||
version = parseVersion(longVersion);
|
||||
versionNumber = VersionNumber.asVersion(longVersion);
|
||||
} else {
|
||||
version = UNKNOWN;
|
||||
versionNumber = null;
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "JavaVersion {" + binary + ", " + longVersion + "(" + version + ")" + ", " + platform + "}";
|
||||
}
|
||||
|
||||
public Path getBinary() {
|
||||
return binary;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return longVersion;
|
||||
}
|
||||
|
||||
public Platform getPlatform() {
|
||||
return platform;
|
||||
}
|
||||
|
||||
public Architecture getArchitecture() {
|
||||
return platform.getArchitecture();
|
||||
}
|
||||
|
||||
public Bits getBits() {
|
||||
return platform.getBits();
|
||||
}
|
||||
|
||||
public VersionNumber getVersionNumber() {
|
||||
return versionNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* The major version of Java installation.
|
||||
*
|
||||
* @see org.jackhuang.hmcl.util.platform.JavaVersion#JAVA_9
|
||||
* @see org.jackhuang.hmcl.util.platform.JavaVersion#JAVA_8
|
||||
* @see org.jackhuang.hmcl.util.platform.JavaVersion#JAVA_7
|
||||
* @see org.jackhuang.hmcl.util.platform.JavaVersion#UNKNOWN
|
||||
*/
|
||||
public int getParsedVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
private static final Pattern REGEX = Pattern.compile("version \"(?<version>(.*?))\"");
|
||||
private static final Pattern VERSION = Pattern.compile("^(?<version>[0-9]+)");
|
||||
|
||||
private static final Pattern OS_ARCH = Pattern.compile("os\\.arch = (?<arch>.*)");
|
||||
private static final Pattern JAVA_VERSION = Pattern.compile("java\\.version = (?<version>.*)");
|
||||
|
||||
private static final String JAVA_EXECUTABLE = OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS ? "java.exe" : "java";
|
||||
|
||||
public static final int UNKNOWN = -1;
|
||||
public static final int JAVA_6 = 6;
|
||||
public static final int JAVA_7 = 7;
|
||||
public static final int JAVA_8 = 8;
|
||||
public static final int JAVA_9 = 9;
|
||||
public static final int JAVA_16 = 16;
|
||||
public static final int JAVA_17 = 17;
|
||||
|
||||
private static int parseVersion(String version) {
|
||||
Matcher matcher = VERSION.matcher(version);
|
||||
if (matcher.find()) {
|
||||
int head = Lang.parseInt(matcher.group(), -1);
|
||||
if (head > 1) return head;
|
||||
}
|
||||
if (version.contains("1.8"))
|
||||
return JAVA_8;
|
||||
else if (version.contains("1.7"))
|
||||
return JAVA_7;
|
||||
else if (version.contains("1.6"))
|
||||
return JAVA_6;
|
||||
else
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
private static final Map<Path, JavaVersion> fromExecutableCache = new ConcurrentHashMap<>();
|
||||
|
||||
public static JavaVersion fromExecutable(Path executable) throws IOException {
|
||||
executable = executable.toRealPath();
|
||||
JavaVersion cachedJavaVersion = fromExecutableCache.get(executable);
|
||||
if (cachedJavaVersion != null)
|
||||
return cachedJavaVersion;
|
||||
|
||||
String osArch = null;
|
||||
String version = null;
|
||||
|
||||
Platform platform = null;
|
||||
|
||||
Process process = new ProcessBuilder(executable.toString(), "-XshowSettings:properties", "-version").start();
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream(), OperatingSystem.NATIVE_CHARSET))) {
|
||||
for (String line; (line = reader.readLine()) != null; ) {
|
||||
Matcher m;
|
||||
|
||||
m = OS_ARCH.matcher(line);
|
||||
if (m.find()) {
|
||||
osArch = m.group("arch");
|
||||
if (version != null) {
|
||||
break;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
m = JAVA_VERSION.matcher(line);
|
||||
if (m.find()) {
|
||||
version = m.group("version");
|
||||
if (osArch != null) {
|
||||
break;
|
||||
} else {
|
||||
//noinspection UnnecessaryContinue
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (osArch != null) {
|
||||
platform = Platform.getPlatform(OperatingSystem.CURRENT_OS, Architecture.parseArchName(osArch));
|
||||
}
|
||||
|
||||
if (version == null) {
|
||||
boolean is64Bit = false;
|
||||
process = new ProcessBuilder(executable.toString(), "-version").start();
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream(), OperatingSystem.NATIVE_CHARSET))) {
|
||||
for (String line; (line = reader.readLine()) != null; ) {
|
||||
Matcher m = REGEX.matcher(line);
|
||||
if (m.find())
|
||||
version = m.group("version");
|
||||
if (line.contains("64-Bit"))
|
||||
is64Bit = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (platform == null) {
|
||||
platform = Platform.getPlatform(OperatingSystem.CURRENT_OS, is64Bit ? Architecture.X86_64 : Architecture.X86);
|
||||
}
|
||||
}
|
||||
|
||||
JavaVersion javaVersion = new JavaVersion(executable, version, platform);
|
||||
if (javaVersion.getParsedVersion() == UNKNOWN)
|
||||
throw new IOException("Unrecognized Java version " + version + " at " + executable);
|
||||
fromExecutableCache.put(executable, javaVersion);
|
||||
return javaVersion;
|
||||
}
|
||||
|
||||
public static Path getExecutable(Path javaHome) {
|
||||
return javaHome.resolve("bin").resolve(JAVA_EXECUTABLE);
|
||||
}
|
||||
|
||||
public static JavaVersion fromCurrentEnvironment() {
|
||||
return CURRENT_JAVA;
|
||||
}
|
||||
|
||||
public static final JavaVersion CURRENT_JAVA;
|
||||
|
||||
static {
|
||||
Path currentExecutable = getExecutable(Paths.get(System.getProperty("java.home")).toAbsolutePath());
|
||||
try {
|
||||
currentExecutable = currentExecutable.toRealPath();
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to resolve current Java path: " + currentExecutable, e);
|
||||
}
|
||||
CURRENT_JAVA = new JavaVersion(
|
||||
currentExecutable,
|
||||
System.getProperty("java.version"),
|
||||
Platform.CURRENT_PLATFORM
|
||||
);
|
||||
}
|
||||
|
||||
private static Collection<JavaVersion> JAVAS;
|
||||
private static final CountDownLatch LATCH = new CountDownLatch(1);
|
||||
|
||||
public static Collection<JavaVersion> getJavas() throws InterruptedException {
|
||||
if (JAVAS != null)
|
||||
return JAVAS;
|
||||
LATCH.await();
|
||||
return JAVAS;
|
||||
}
|
||||
|
||||
public static synchronized void initialize() {
|
||||
if (JAVAS != null)
|
||||
throw new IllegalStateException("JavaVersions have already been initialized.");
|
||||
|
||||
List<JavaVersion> javaVersions;
|
||||
|
||||
try (Stream<Path> stream = searchPotentialJavaExecutables()) {
|
||||
javaVersions = lookupJavas(stream);
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to search Java homes", e);
|
||||
javaVersions = new ArrayList<>();
|
||||
}
|
||||
|
||||
// insert current java to the list
|
||||
if (!javaVersions.contains(CURRENT_JAVA)) {
|
||||
javaVersions.add(CURRENT_JAVA);
|
||||
}
|
||||
|
||||
JAVAS = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
||||
JAVAS.addAll(javaVersions);
|
||||
|
||||
LOG.trace("Finished Java installation lookup, found " + JAVAS.size());
|
||||
|
||||
LATCH.countDown();
|
||||
}
|
||||
|
||||
private static List<JavaVersion> lookupJavas(Stream<Path> javaExecutables) {
|
||||
return javaExecutables
|
||||
.filter(Files::isExecutable)
|
||||
.flatMap(executable -> { // resolve symbolic links
|
||||
try {
|
||||
return Stream.of(executable.toRealPath());
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to lookup Java executable at " + executable, e);
|
||||
return Stream.empty();
|
||||
}
|
||||
})
|
||||
.distinct() // remove duplicated javas
|
||||
.flatMap(executable -> {
|
||||
if (executable.equals(CURRENT_JAVA.getBinary())) {
|
||||
return Stream.of(CURRENT_JAVA);
|
||||
}
|
||||
try {
|
||||
LOG.trace("Looking for Java:" + executable);
|
||||
Future<JavaVersion> future = Schedulers.io().submit(() -> fromExecutable(executable));
|
||||
JavaVersion javaVersion = future.get(5, TimeUnit.SECONDS);
|
||||
LOG.trace("Found Java (" + javaVersion.getVersion() + ") " + javaVersion.getBinary().toString());
|
||||
return Stream.of(javaVersion);
|
||||
} catch (ExecutionException | InterruptedException | TimeoutException e) {
|
||||
LOG.warning("Failed to determine Java at " + executable, e);
|
||||
return Stream.empty();
|
||||
}
|
||||
})
|
||||
.collect(toList());
|
||||
}
|
||||
|
||||
private static Stream<Path> searchPotentialJavaExecutables() throws IOException {
|
||||
// Add order:
|
||||
// 1. System-defined locations
|
||||
// 2. Minecraft-installed locations
|
||||
// 3. PATH
|
||||
List<Stream<Path>> javaExecutables = new ArrayList<>();
|
||||
|
||||
switch (OperatingSystem.CURRENT_OS) {
|
||||
case WINDOWS:
|
||||
javaExecutables.add(queryJavaHomesInRegistryKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Runtime Environment\\").stream().map(JavaVersion::getExecutable));
|
||||
javaExecutables.add(queryJavaHomesInRegistryKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\").stream().map(JavaVersion::getExecutable));
|
||||
javaExecutables.add(queryJavaHomesInRegistryKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\JRE\\").stream().map(JavaVersion::getExecutable));
|
||||
javaExecutables.add(queryJavaHomesInRegistryKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\JDK\\").stream().map(JavaVersion::getExecutable));
|
||||
|
||||
for (Optional<Path> programFiles : Arrays.asList(
|
||||
FileUtils.tryGetPath(Optional.ofNullable(System.getenv("ProgramFiles")).orElse("C:\\Program Files")),
|
||||
FileUtils.tryGetPath(Optional.ofNullable(System.getenv("ProgramFiles(x86)")).orElse("C:\\Program Files (x86)")),
|
||||
FileUtils.tryGetPath(Optional.ofNullable(System.getenv("ProgramFiles(ARM)")).orElse("C:\\Program Files (ARM)"))
|
||||
)) {
|
||||
if (!programFiles.isPresent())
|
||||
continue;
|
||||
|
||||
for (String vendor : new String[]{"Java", "BellSoft", "AdoptOpenJDK", "Zulu", "Microsoft", "Eclipse Foundation", "Semeru"}) {
|
||||
javaExecutables.add(listDirectory(programFiles.get().resolve(vendor)).map(JavaVersion::getExecutable));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case LINUX:
|
||||
case FREEBSD:
|
||||
javaExecutables.add(listDirectory(Paths.get("/usr/java")).map(JavaVersion::getExecutable)); // Oracle RPMs
|
||||
javaExecutables.add(listDirectory(Paths.get("/usr/lib/jvm")).map(JavaVersion::getExecutable)); // General locations
|
||||
javaExecutables.add(listDirectory(Paths.get("/usr/lib32/jvm")).map(JavaVersion::getExecutable)); // General locations
|
||||
javaExecutables.add(listDirectory(Paths.get(System.getProperty("user.home"), ".sdkman/candidates/java")).map(JavaVersion::getExecutable)); // SDKMAN!
|
||||
break;
|
||||
|
||||
case OSX:
|
||||
javaExecutables.add(listDirectory(Paths.get("/Library/Java/JavaVirtualMachines"))
|
||||
.flatMap(dir -> Stream.of(dir.resolve("Contents/Home"), dir.resolve("Contents/Home/jre")))
|
||||
.map(JavaVersion::getExecutable));
|
||||
javaExecutables.add(listDirectory(Paths.get(System.getProperty("user.home"), "Library/Java/JavaVirtualMachines"))
|
||||
.flatMap(dir -> Stream.of(dir.resolve("Contents/Home"), dir.resolve("Contents/Home/jre")))
|
||||
.map(JavaVersion::getExecutable));
|
||||
javaExecutables.add(listDirectory(Paths.get("/System/Library/Java/JavaVirtualMachines"))
|
||||
.map(dir -> dir.resolve("Contents/Home"))
|
||||
.map(JavaVersion::getExecutable));
|
||||
javaExecutables.add(Stream.of(Paths.get("/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java")));
|
||||
javaExecutables.add(Stream.of(Paths.get("/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/MacOS/itms/java/bin/java")));
|
||||
// Homebrew
|
||||
javaExecutables.add(Stream.of(Paths.get("/opt/homebrew/opt/java/bin/java")));
|
||||
javaExecutables.add(listDirectory(Paths.get("/opt/homebrew/Cellar/openjdk"))
|
||||
.map(JavaVersion::getExecutable));
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Search Minecraft bundled runtimes.
|
||||
javaExecutables.add(Stream.concat(Stream.of(Optional.of(JavaRepository.getJavaStoragePath())), JavaRepository.findMinecraftRuntimeDirs())
|
||||
.flatMap(Lang::toStream)
|
||||
.flatMap(JavaRepository::findJavaHomeInMinecraftRuntimeDir)
|
||||
.map(JavaVersion::getExecutable));
|
||||
|
||||
// Search in PATH.
|
||||
if (System.getenv("PATH") != null) {
|
||||
javaExecutables.add(Arrays.stream(System.getenv("PATH").split(OperatingSystem.PATH_SEPARATOR))
|
||||
.flatMap(path -> Lang.toStream(FileUtils.tryGetPath(path, JAVA_EXECUTABLE))));
|
||||
}
|
||||
|
||||
// Search in HMCL_JRES, convenient environment variable for users to add JRE in global
|
||||
// May be removed when we implement global Java configuration.
|
||||
if (System.getenv("HMCL_JRES") != null) {
|
||||
javaExecutables.add(Arrays.stream(System.getenv("HMCL_JRES").split(OperatingSystem.PATH_SEPARATOR))
|
||||
.flatMap(path -> Lang.toStream(FileUtils.tryGetPath(path, "bin", JAVA_EXECUTABLE))));
|
||||
}
|
||||
return javaExecutables.parallelStream().flatMap(stream -> stream);
|
||||
}
|
||||
|
||||
private static Stream<Path> listDirectory(Path directory) throws IOException {
|
||||
if (Files.isDirectory(directory)) {
|
||||
try (final DirectoryStream<Path> subDirs = Files.newDirectoryStream(directory)) {
|
||||
final ArrayList<Path> paths = new ArrayList<>();
|
||||
for (Path subDir : subDirs) {
|
||||
paths.add(subDir);
|
||||
}
|
||||
return paths.stream();
|
||||
}
|
||||
} else {
|
||||
return Stream.empty();
|
||||
}
|
||||
}
|
||||
|
||||
// ==== Windows Registry Support ====
|
||||
private static List<Path> queryJavaHomesInRegistryKey(String location) throws IOException {
|
||||
List<Path> homes = new ArrayList<>();
|
||||
for (String java : querySubFolders(location)) {
|
||||
if (!querySubFolders(java).contains(java + "\\MSI"))
|
||||
continue;
|
||||
String home = queryRegisterValue(java, "JavaHome");
|
||||
if (home != null) {
|
||||
try {
|
||||
homes.add(Paths.get(home));
|
||||
} catch (InvalidPathException e) {
|
||||
LOG.warning("Invalid Java path in system registry: " + home);
|
||||
}
|
||||
}
|
||||
}
|
||||
return homes;
|
||||
}
|
||||
|
||||
private static List<String> querySubFolders(String location) throws IOException {
|
||||
List<String> res = new ArrayList<>();
|
||||
|
||||
Process process = Runtime.getRuntime().exec(new String[] { "cmd", "/c", "reg", "query", location });
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), OperatingSystem.NATIVE_CHARSET))) {
|
||||
for (String line; (line = reader.readLine()) != null;) {
|
||||
if (line.startsWith(location) && !line.equals(location)) {
|
||||
res.add(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private static String queryRegisterValue(String location, String name) throws IOException {
|
||||
boolean last = false;
|
||||
Process process = Runtime.getRuntime().exec(new String[] { "cmd", "/c", "reg", "query", location, "/v", name });
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), OperatingSystem.NATIVE_CHARSET))) {
|
||||
for (String line; (line = reader.readLine()) != null;) {
|
||||
if (StringUtils.isNotBlank(line)) {
|
||||
if (last && line.trim().startsWith(name)) {
|
||||
int begins = line.indexOf(name);
|
||||
if (begins > 0) {
|
||||
String s2 = line.substring(begins + name.length());
|
||||
begins = s2.indexOf("REG_SZ");
|
||||
if (begins > 0) {
|
||||
return s2.substring(begins + "REG_SZ".length()).trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (location.equals(line.trim())) {
|
||||
last = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.util.platform;
|
||||
|
||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
import org.jackhuang.hmcl.launch.StreamPump;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
@@ -90,7 +91,7 @@ public final class ManagedProcess {
|
||||
* @return PID
|
||||
*/
|
||||
public long getPID() throws UnsupportedOperationException {
|
||||
if (JavaVersion.CURRENT_JAVA.getParsedVersion() >= 9) {
|
||||
if (JavaRuntime.CURRENT_VERSION >= 9) {
|
||||
// Method Process.pid() is provided (Java 9 or later). Invoke it to get the pid.
|
||||
try {
|
||||
return (long) MethodHandles.publicLookup()
|
||||
|
||||
@@ -74,6 +74,10 @@ public enum OperatingSystem {
|
||||
return this == LINUX || this == FREEBSD;
|
||||
}
|
||||
|
||||
public String getJavaExecutable() {
|
||||
return this == WINDOWS ? "java.exe" : "java";
|
||||
}
|
||||
|
||||
/**
|
||||
* The current operating system.
|
||||
*/
|
||||
@@ -215,10 +219,10 @@ public enum OperatingSystem {
|
||||
|
||||
name = name.trim().toLowerCase(Locale.ROOT);
|
||||
|
||||
if (name.contains("win"))
|
||||
return WINDOWS;
|
||||
else if (name.contains("mac"))
|
||||
if (name.contains("mac") || name.contains("darwin") || name.contains("osx"))
|
||||
return OSX;
|
||||
else if (name.contains("win"))
|
||||
return WINDOWS;
|
||||
else if (name.contains("solaris") || name.contains("linux") || name.contains("unix") || name.contains("sunos"))
|
||||
return LINUX;
|
||||
else if (name.equals("freebsd"))
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.util.Objects;
|
||||
public final class Platform {
|
||||
public static final Platform UNKNOWN = new Platform(OperatingSystem.UNKNOWN, Architecture.UNKNOWN);
|
||||
|
||||
public static final Platform WINDOWS_X86 = new Platform(OperatingSystem.WINDOWS, Architecture.X86);
|
||||
public static final Platform WINDOWS_X86_64 = new Platform(OperatingSystem.WINDOWS, Architecture.X86_64);
|
||||
public static final Platform WINDOWS_ARM64 = new Platform(OperatingSystem.WINDOWS, Architecture.ARM64);
|
||||
|
||||
@@ -78,6 +79,10 @@ public final class Platform {
|
||||
return Objects.hash(os, arch);
|
||||
}
|
||||
|
||||
public boolean equals(OperatingSystem os, Architecture arch) {
|
||||
return this.os == os && this.arch == arch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.util.platform;
|
||||
|
||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -42,8 +44,7 @@ public final class SystemUtils {
|
||||
}
|
||||
|
||||
public static boolean supportJVMAttachment() {
|
||||
return JavaVersion.CURRENT_JAVA.getParsedVersion() >= 9
|
||||
&& Thread.currentThread().getContextClassLoader().getResource("com/sun/tools/attach/VirtualMachine.class") != null;
|
||||
return JavaRuntime.CURRENT_VERSION >= 9 && Thread.currentThread().getContextClassLoader().getResource("com/sun/tools/attach/VirtualMachine.class") != null;
|
||||
}
|
||||
|
||||
private static void onLogLine(String log) {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.util.platform;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class UnsupportedPlatformException extends Exception {
|
||||
public UnsupportedPlatformException() {
|
||||
}
|
||||
|
||||
public UnsupportedPlatformException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* 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.util.tree;
|
||||
|
||||
import org.apache.commons.compress.archivers.ArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.zip.ZipFile;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public abstract class ArchiveFileTree<F, E extends ArchiveEntry> implements Closeable {
|
||||
|
||||
public static ArchiveFileTree<?, ?> open(Path file) throws IOException {
|
||||
Path namePath = file.getFileName();
|
||||
if (namePath == null) {
|
||||
throw new IOException(file + " is not a valid archive file");
|
||||
}
|
||||
|
||||
String name = namePath.toString();
|
||||
if (name.endsWith(".jar") || name.endsWith(".zip")) {
|
||||
return new ZipFileTree(new ZipFile(file));
|
||||
} else if (name.endsWith(".tar") || name.endsWith(".tar.gz") || name.endsWith(".tgz")) {
|
||||
return TarFileTree.open(file);
|
||||
} else {
|
||||
throw new IOException(file + " is not a valid archive file");
|
||||
}
|
||||
}
|
||||
|
||||
protected final F file;
|
||||
protected final Dir<E> root = new Dir<>();
|
||||
|
||||
public ArchiveFileTree(F file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
public F getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
public Dir<E> getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
public void addEntry(E entry) throws IOException {
|
||||
String[] path = entry.getName().split("/");
|
||||
|
||||
Dir<E> dir = root;
|
||||
|
||||
for (int i = 0, end = entry.isDirectory() ? path.length : path.length - 1; i < end; i++) {
|
||||
String item = path[i];
|
||||
if (item.equals("."))
|
||||
continue;
|
||||
if (item.equals("..") || item.isEmpty())
|
||||
throw new IOException("Invalid entry: " + entry.getName());
|
||||
|
||||
if (dir.files.containsKey(item)) {
|
||||
throw new IOException("A file and a directory have the same name: " + entry.getName());
|
||||
}
|
||||
|
||||
dir = dir.subDirs.computeIfAbsent(item, name -> new Dir<>());
|
||||
}
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
if (dir.entry != null) {
|
||||
throw new IOException("Duplicate entry: " + entry.getName());
|
||||
}
|
||||
dir.entry = entry;
|
||||
} else {
|
||||
String fileName = path[path.length - 1];
|
||||
|
||||
if (dir.subDirs.containsKey(fileName)) {
|
||||
throw new IOException("A file and a directory have the same name: " + entry.getName());
|
||||
}
|
||||
|
||||
if (dir.files.containsKey(fileName)) {
|
||||
throw new IOException("Duplicate entry: " + entry.getName());
|
||||
}
|
||||
|
||||
dir.files.put(fileName, entry);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract InputStream getInputStream(E entry) throws IOException;
|
||||
|
||||
public abstract boolean isLink(E entry);
|
||||
|
||||
public abstract String getLink(E entry) throws IOException;
|
||||
|
||||
public abstract boolean isExecutable(E entry);
|
||||
|
||||
@Override
|
||||
public abstract void close() throws IOException;
|
||||
|
||||
public static final class Dir<E extends ArchiveEntry> {
|
||||
E entry;
|
||||
|
||||
final Map<String, Dir<E>> subDirs = new HashMap<>();
|
||||
final Map<String, E> files = new HashMap<>();
|
||||
|
||||
public Map<String, Dir<E>> getSubDirs() {
|
||||
return subDirs;
|
||||
}
|
||||
|
||||
public Map<String, E> getFiles() {
|
||||
return files;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* 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.util.tree;
|
||||
|
||||
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.tar.TarFile;
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class TarFileTree extends ArchiveFileTree<TarFile, TarArchiveEntry> {
|
||||
|
||||
public static TarFileTree open(Path file) throws IOException {
|
||||
String fileName = file.getFileName().toString();
|
||||
|
||||
if (fileName.endsWith(".tar.gz") || fileName.endsWith(".tgz")) {
|
||||
Path tempFile = Files.createTempFile("hmcl-", ".tar");
|
||||
TarFile tarFile;
|
||||
try (GZIPInputStream input = new GZIPInputStream(Files.newInputStream(file));
|
||||
OutputStream output = Files.newOutputStream(tempFile, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE)
|
||||
) {
|
||||
IOUtils.copyTo(input, output);
|
||||
tarFile = new TarFile(tempFile.toFile());
|
||||
} catch (Throwable e) {
|
||||
try {
|
||||
Files.deleteIfExists(tempFile);
|
||||
} catch (Throwable e2) {
|
||||
e.addSuppressed(e2);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
return new TarFileTree(tarFile, tempFile);
|
||||
} else {
|
||||
return new TarFileTree(new TarFile(file), null);
|
||||
}
|
||||
}
|
||||
|
||||
private final Path tempFile;
|
||||
private final Thread shutdownHook;
|
||||
|
||||
public TarFileTree(TarFile file, Path tempFile) throws IOException {
|
||||
super(file);
|
||||
this.tempFile = tempFile;
|
||||
try {
|
||||
for (TarArchiveEntry entry : file.getEntries()) {
|
||||
addEntry(entry);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
try {
|
||||
file.close();
|
||||
} catch (Throwable e2) {
|
||||
e.addSuppressed(e2);
|
||||
}
|
||||
|
||||
if (tempFile != null) {
|
||||
try {
|
||||
Files.deleteIfExists(tempFile);
|
||||
} catch (Throwable e2) {
|
||||
e.addSuppressed(e2);
|
||||
}
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (tempFile != null) {
|
||||
this.shutdownHook = new Thread(() -> {
|
||||
try {
|
||||
Files.deleteIfExists(tempFile);
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
});
|
||||
Runtime.getRuntime().addShutdownHook(shutdownHook);
|
||||
} else
|
||||
this.shutdownHook = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream(TarArchiveEntry entry) throws IOException {
|
||||
return file.getInputStream(entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLink(TarArchiveEntry entry) {
|
||||
return entry.isSymbolicLink();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLink(TarArchiveEntry entry) throws IOException {
|
||||
return entry.getLinkName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExecutable(TarArchiveEntry entry) {
|
||||
return entry.isFile() && (entry.getMode() & 0b1000000) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
try {
|
||||
file.close();
|
||||
} finally {
|
||||
if (tempFile != null) {
|
||||
Runtime.getRuntime().removeShutdownHook(shutdownHook);
|
||||
Files.deleteIfExists(tempFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util.tree;
|
||||
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.zip.ZipFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Enumeration;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class ZipFileTree extends ArchiveFileTree<ZipFile, ZipArchiveEntry> {
|
||||
public ZipFileTree(ZipFile file) throws IOException {
|
||||
super(file);
|
||||
try {
|
||||
Enumeration<ZipArchiveEntry> entries = file.getEntries();
|
||||
while (entries.hasMoreElements()) {
|
||||
addEntry(entries.nextElement());
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
try {
|
||||
file.close();
|
||||
} catch (Throwable e2) {
|
||||
e.addSuppressed(e2);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
file.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream(ZipArchiveEntry entry) throws IOException {
|
||||
return getFile().getInputStream(entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLink(ZipArchiveEntry entry) {
|
||||
return entry.isUnixSymlink();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLink(ZipArchiveEntry entry) throws IOException {
|
||||
return getFile().getUnixSymlink(entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExecutable(ZipArchiveEntry entry) {
|
||||
return !entry.isDirectory() && !entry.isUnixSymlink() && (entry.getUnixMode() & 0b1000000) != 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class KeyValuePairPropertiesTest {
|
||||
@Test
|
||||
public void test() throws IOException {
|
||||
String content = "#test: key0=value0\n \n" +
|
||||
"key1=value1\n" +
|
||||
"key2=\"value2\"\n" +
|
||||
"key3=\"\\\" \\n\"\n";
|
||||
|
||||
KeyValuePairProperties properties = KeyValuePairProperties.load(new BufferedReader(new StringReader(content)));
|
||||
|
||||
assertEquals(Lang.mapOf(
|
||||
pair("key1", "value1"),
|
||||
pair("key2", "value2"),
|
||||
pair("key3", "\" \n")
|
||||
), properties);
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,6 @@ package org.jackhuang.hmcl.util;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIf;
|
||||
@@ -79,10 +78,11 @@ public class TaskTest {
|
||||
@EnabledIf("org.jackhuang.hmcl.JavaFXLauncher#isStarted")
|
||||
public void testThenAccept() {
|
||||
AtomicBoolean flag = new AtomicBoolean();
|
||||
boolean result = Task.supplyAsync(JavaVersion::fromCurrentEnvironment)
|
||||
.thenAcceptAsync(Schedulers.io(), javaVersion -> {
|
||||
Object obj = new Object();
|
||||
boolean result = Task.supplyAsync(() -> obj)
|
||||
.thenAcceptAsync(Schedulers.io(), o -> {
|
||||
flag.set(true);
|
||||
assertEquals(javaVersion, JavaVersion.fromCurrentEnvironment());
|
||||
assertSame(obj, o);
|
||||
})
|
||||
.test();
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.jackhuang.hmcl.util.platform;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.jackhuang.hmcl.java.JavaInfo.parseVersion;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class JavaRuntimeTest {
|
||||
@Test
|
||||
public void testParseVersion() {
|
||||
assertEquals(8, parseVersion("1.8.0_302"));
|
||||
assertEquals(11, parseVersion("11"));
|
||||
assertEquals(11, parseVersion("11.0.12"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user