diff --git a/.gitignore b/.gitignore index b226c50ca..6299bca5a 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,6 @@ hs_err_pid* *.log .mine* NVIDIA -*.json # gradle build /build/ @@ -42,4 +41,4 @@ NVIDIA !/HMCL/src/main/resources/assets/HMCLauncher.exe # macos -.DS_Store \ No newline at end of file +.DS_Store diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/util/SelfDependencyPatcher.java b/HMCL/src/main/java/org/jackhuang/hmcl/util/SelfDependencyPatcher.java index 2f80a540d..2f9784e6b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/util/SelfDependencyPatcher.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/util/SelfDependencyPatcher.java @@ -41,59 +41,104 @@ */ package org.jackhuang.hmcl.util; -import org.jackhuang.hmcl.util.io.ChecksumMismatchException; -import org.jackhuang.hmcl.util.io.NetworkUtils; -import org.jackhuang.hmcl.util.platform.Architecture; -import org.jackhuang.hmcl.util.platform.OperatingSystem; - -import javax.swing.*; -import java.awt.*; -import java.io.IOException; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.util.List; -import java.util.*; -import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.ForkJoinTask; -import java.util.stream.Collectors; - import static java.lang.Class.forName; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.stream.Collectors.toSet; import static org.jackhuang.hmcl.Metadata.HMCL_DIRECTORY; import static org.jackhuang.hmcl.util.Logging.LOG; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.platform.JavaVersion.CURRENT_JAVA; -/** - * Utility for patching self when missing dependencies. - * Copy from https://github.com/Col-E/Recaf/blob/master/src/main/java/me/coley/recaf/util/self/SelfDependencyPatcher.java - * - * @author Matt - */ +import java.awt.Dialog; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JProgressBar; +import javax.swing.SwingUtilities; +import javax.swing.WindowConstants; + +import org.jackhuang.hmcl.util.io.ChecksumMismatchException; +import org.jackhuang.hmcl.util.io.IOUtils; +import org.jackhuang.hmcl.util.platform.Architecture; +import org.jackhuang.hmcl.util.platform.OperatingSystem; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +// From: https://github.com/Col-E/Recaf/blob/7378b397cee664ae81b7963b0355ef8ff013c3a7/src/main/java/me/coley/recaf/util/self/SelfDependencyPatcher.java public final class SelfDependencyPatcher { private SelfDependencyPatcher() { } - private static final Path DEPENDENCIES_DIR_PATH = HMCL_DIRECTORY.resolve("dependencies"); - private static final String DEFAULT_JFX_VERSION = "16"; - private static final Map JFX_DEPENDENCIES = new HashMap<>(); + static class DependencyDescriptor { - static { - addJfxDependency("base"); - addJfxDependency("controls"); - addJfxDependency("fxml"); - addJfxDependency("graphics"); - addJfxDependency("media"); - // Fix #874: Remove the dependency on javafx.swing - // addJfxDependency("swing"); - addJfxDependency("web"); + private static final String REPOSITORY_URL = "https://maven.aliyun.com/repository/central/"; + private static final Path DEPENDENCIES_DIR_PATH = HMCL_DIRECTORY.resolve("dependencies"); + + private static String currentArchClassifier() { + switch (OperatingSystem.CURRENT_OS) { + case LINUX: + return "linux"; + case OSX: + return "mac"; + default: + return "win"; + } + } + + public String module; + public String groupId; + public String artifactId; + public String version; + public Map sha1; + + public String filename() { + return artifactId + "-" + version + "-" + currentArchClassifier() + ".jar"; + } + + public String sha1() { + return sha1.get(currentArchClassifier()); + } + + public String url() { + return REPOSITORY_URL + groupId.replace('.', '/') + "/" + artifactId + "/" + version + "/" + filename(); + } + + public Path localPath() { + return DEPENDENCIES_DIR_PATH.resolve(filename()); + } } - private static void addJfxDependency(String name) { - JFX_DEPENDENCIES.put("javafx." + name, jfxUrl(name)); + private static final String DEPENDENCIES_LIST_FILE = "/assets/openjfx-dependencies.json"; + + private static List readDependencies() { + String content; + try (InputStream in = SelfDependencyPatcher.class.getResourceAsStream(DEPENDENCIES_LIST_FILE)) { + content = IOUtils.readFullyAsString(in, UTF_8); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + return new Gson().fromJson(content, TypeToken.getParameterized(List.class, DependencyDescriptor.class).getType()); } + private static final List JFX_DEPENDENCIES = readDependencies(); + /** * Patch in any missing dependencies, if any. */ @@ -125,17 +170,17 @@ public final class SelfDependencyPatcher { // Otherwise we're free to download in Java 11+ LOG.info("Missing JavaFX dependencies, attempting to patch in missing classes"); - // Check if dependencies need to be downloaded - if (!hasCachedDependencies()) { - LOG.info(" - No local cache, downloading dependencies..."); + + // Download missing dependencies + List missingDependencies = checkMissingDependencies(); + if (!missingDependencies.isEmpty()) { try { - fetchDependencies(); - } catch (Exception ex) { - throw new PatchException("Failed to download dependencies", ex); + fetchDependencies(missingDependencies); + } catch (IOException e) { + throw new PatchException("Failed to download dependencies", e); } - } else { - LOG.info(" - Local cache found!"); } + // Add the dependencies try { loadFromCache(); @@ -155,10 +200,16 @@ public final class SelfDependencyPatcher { */ private static void loadFromCache() throws IOException, ReflectiveOperationException { LOG.info(" - Loading dependencies..."); - List jarPaths = new ArrayList<>(); - List jfxDepFile = JFX_DEPENDENCIES.values().stream().map(SelfDependencyPatcher::getFileName).collect(Collectors.toList()); - Files.walk(DEPENDENCIES_DIR_PATH).filter(p -> jfxDepFile.contains(p.getFileName().toString())).forEach(jarPaths::add); - JavaFXPatcher.patch(JFX_DEPENDENCIES.keySet(), jarPaths.toArray(new Path[0])); + + Set modules = JFX_DEPENDENCIES.stream() + .map(it -> it.module) + .collect(toSet()); + + Path[] jars = JFX_DEPENDENCIES.stream() + .map(it -> it.localPath()) + .toArray(Path[]::new); + + JavaFXPatcher.patch(modules, jars); } /** @@ -166,106 +217,57 @@ public final class SelfDependencyPatcher { * * @throws IOException When the files cannot be fetched or saved. */ - private static void fetchDependencies() throws Exception { - // Get dir to store dependencies in - Path dependenciesDir = DEPENDENCIES_DIR_PATH; - if (!Files.isDirectory(dependenciesDir)) { - Files.createDirectories(dependenciesDir); - } + private static void fetchDependencies(List dependencies) throws IOException { ProgressFrame dialog = new ProgressFrame(i18n("download.javafx")); - - ForkJoinTask task = ForkJoinPool.commonPool().submit(() -> { - // Download each dependency - Collection dependencies = JFX_DEPENDENCIES.values(); - int i = 1; - for (String dependencyUrlPath : dependencies) { - URL depURL = new URL(dependencyUrlPath); - Path dependencyFilePath = DEPENDENCIES_DIR_PATH.resolve(getFileName(dependencyUrlPath)); - int finalI = i; - SwingUtilities.invokeLater(() -> { - dialog.setStatus(dependencyUrlPath); - dialog.setProgress(finalI, dependencies.size()); - }); - Files.copy(depURL.openStream(), dependencyFilePath, StandardCopyOption.REPLACE_EXISTING); - checksum(dependencyFilePath, dependencyUrlPath); - i++; - } - - dialog.dispose(); - return null; - }); - dialog.setVisible(true); - task.get(); + int progress = 0; + for (DependencyDescriptor dependency : dependencies) { + int currentProgress = ++progress; + SwingUtilities.invokeLater(() -> { + dialog.setStatus(dependency.url()); + dialog.setProgress(currentProgress, dependencies.size()); + }); + + LOG.info("Downloading " + dependency.url()); + Files.createDirectories(dependency.localPath().getParent()); + Files.copy(new URL(dependency.url()).openStream(), dependency.localPath(), StandardCopyOption.REPLACE_EXISTING); + verifyChecksum(dependency); + } + + dialog.dispose(); } - /** - * @return {@code true} when the dependencies directory has files in it. - */ - private static boolean hasCachedDependencies() { - if (!Files.isDirectory(DEPENDENCIES_DIR_PATH)) - return false; + private static List checkMissingDependencies() { + List missing = new ArrayList<>(); - for (String url : JFX_DEPENDENCIES.values()) { - Path dependencyFilePath = DEPENDENCIES_DIR_PATH.resolve(getFileName(url)); - if (!Files.exists(dependencyFilePath)) - return false; + for (DependencyDescriptor dependency : JFX_DEPENDENCIES) { + if (!Files.exists(dependency.localPath())) { + missing.add(dependency); + continue; + } try { - checksum(dependencyFilePath, url); + verifyChecksum(dependency); } catch (ChecksumMismatchException e) { - return false; - } catch (IOException ignored) { - // Ignore other situations + LOG.warning("Corrupted dependency " + dependency.filename() + ": " + e.getMessage()); + missing.add(dependency); + } catch (IOException e) { + throw new UncheckedIOException(e); } } - return true; + + return missing; } - private static void checksum(Path dependencyFilePath, String dependencyUrlPath) throws IOException { - String expectedHash = NetworkUtils.doGet(NetworkUtils.toURL(dependencyUrlPath + ".sha1")); - String actualHash = Hex.encodeHex(DigestUtils.digest("SHA-1", dependencyFilePath)); + private static void verifyChecksum(DependencyDescriptor dependency) throws IOException, ChecksumMismatchException { + String expectedHash = dependency.sha1(); + String actualHash = Hex.encodeHex(DigestUtils.digest("SHA-1", dependency.localPath())); if (!expectedHash.equalsIgnoreCase(actualHash)) { throw new ChecksumMismatchException("SHA-1", expectedHash, actualHash); } } - /** - * @param url Full url path. - * @return Name of file at url. - */ - private static String getFileName(String url) { - return url.substring(url.lastIndexOf('/') + 1); - } - - /** - * @param component Name of the component. - * @return Formed URL for the component. - */ - private static String jfxUrl(String component) { - return jfxUrl(component, DEFAULT_JFX_VERSION); - } - - private static String jfxUrl(String component, String version) { - // https://repo1.maven.org/maven2/org/openjfx/javafx-%s/%s/javafx-%s-%s-%s.jar - return String.format("https://maven.aliyun.com/repository/central/org/openjfx/javafx-%s/%s/javafx-%s-%s-%s.jar", - component, version, component, version, getMvnName()); -// return String.format("https://bmclapi.bangbang93.com/maven/org/openjfx/javafx-%s/%s/javafx-%s-%s-%s.jar", -// component, version, component, version, getMvnName()); - } - - private static String getMvnName() { - switch (OperatingSystem.CURRENT_OS) { - case LINUX: - return "linux"; - case OSX: - return "mac"; - default: - return "win"; - } - } - public static class PatchException extends Exception { PatchException(String message, Throwable cause) { super(message, cause); @@ -276,8 +278,6 @@ public final class SelfDependencyPatcher { } public static class ProgressFrame extends JDialog { - private int totalTasks = 0; - private int finishedTasks = 0; private final JProgressBar progressBar; private final JLabel progressText; @@ -295,10 +295,10 @@ public final class SelfDependencyPatcher { setLocationRelativeTo(null); GridBagLayout gridBagLayout = new GridBagLayout(); - gridBagLayout.columnWidths = new int[]{600, 0}; - gridBagLayout.rowHeights = new int[]{0, 0, 0, 200}; - gridBagLayout.columnWeights = new double[]{1.0, Double.MIN_VALUE}; - gridBagLayout.rowWeights = new double[]{0.0, 0.0, 0.0, 1.0}; + gridBagLayout.columnWidths = new int[] { 600, 0 }; + gridBagLayout.rowHeights = new int[] { 0, 0, 0, 200 }; + gridBagLayout.columnWeights = new double[] { 1.0, Double.MIN_VALUE }; + gridBagLayout.rowWeights = new double[] { 0.0, 0.0, 0.0, 1.0 }; panel.setLayout(gridBagLayout); progressText = new JLabel(""); diff --git a/HMCL/src/main/resources/assets/openjfx-dependencies.json b/HMCL/src/main/resources/assets/openjfx-dependencies.json new file mode 100644 index 000000000..1a2f0a072 --- /dev/null +++ b/HMCL/src/main/resources/assets/openjfx-dependencies.json @@ -0,0 +1,68 @@ +[ + { + "module": "javafx.base", + "groupId": "org.openjfx", + "artifactId": "javafx-base", + "version": "16", + "sha1": { + "linux": "7d6b85ec89e99ea40c2a63bb1f23d43e05cd2557", + "mac": "ea97d8a5ab95d070df86de1215f29f08378c98c8", + "win": "e0564fea3f27dd3a10fa62e5005612333fc244bc" + } + }, + { + "module": "javafx.controls", + "groupId": "org.openjfx", + "artifactId": "javafx-controls", + "version": "16", + "sha1": { + "linux": "116b127e512d23ddb84c62017c256e7f67f5b9eb", + "mac": "8df933b095bdd8203dc937e39e3a9f199654e47e", + "win": "65cdeae29c67d25932dcc66ca4f7d269923631ba" + } + }, + { + "module": "javafx.fxml", + "groupId": "org.openjfx", + "artifactId": "javafx-fxml", + "version": "16", + "sha1": { + "linux": "c7147c450773c3d4fd038ac9d1a6fdebbc3c11e0", + "mac": "7e4f07a331991485560673a398d6988cc805b67f", + "win": "369f646fc29d2223cc98cfc3c059f2409b3e8fcb" + } + }, + { + "module": "javafx.graphics", + "groupId": "org.openjfx", + "artifactId": "javafx-graphics", + "version": "16", + "sha1": { + "linux": "01bf2fd9f083daa2f492c4c58c4f7d5acb6f4b7d", + "mac": "a45de3d40e217f7ba22cecf54abc281d13a172f6", + "win": "0cd18f8477818b40cfdbcfec7e2fc15f632f0a2f" + } + }, + { + "module": "javafx.media", + "groupId": "org.openjfx", + "artifactId": "javafx-media", + "version": "16", + "sha1": { + "linux": "10edb5fd8eb64b3312b31225c396e1850b9d178b", + "mac": "9e0248083267cdc22b2b698f0b0b62b7399ad32d", + "win": "6d776658906fc25051734bc6b03f214cc72c808e" + } + }, + { + "module": "javafx.web", + "groupId": "org.openjfx", + "artifactId": "javafx-web", + "version": "16", + "sha1": { + "linux": "522f626e1798a7d589d9b187ca5c74c5c2f8a0ee", + "mac": "32b0e13b649ea828e7d185187f178680cc04e564", + "win": "d2f80089a4ae1629a5ec14637abf4f6d70b96dab" + } + } +] diff --git a/tools/generate-openjfx-dependencies.sh b/tools/generate-openjfx-dependencies.sh new file mode 100755 index 000000000..cd2c7df66 --- /dev/null +++ b/tools/generate-openjfx-dependencies.sh @@ -0,0 +1,30 @@ +#!/bin/bash +set -e + +modules=(base controls fxml graphics media web) +arches=(linux mac win) +version=16 + +echo '[' +for module in ${modules[@]}; do + if [[ ! "$module" == "${modules[0]}" ]]; then + echo ',' + fi + echo ' {' + echo ' "module": "javafx.'$module'",' + echo ' "groupId": "org.openjfx",' + echo ' "artifactId": "javafx-'$module'",' + echo ' "version": "'$version'",' + echo ' "sha1": {' + for arch in ${arches[@]}; do + if [[ ! "$arch" == "${arches[0]}" ]]; then + echo ',' + fi + echo -n ' "'$arch'": "'$(curl -Ss "https://repo1.maven.org/maven2/org/openjfx/javafx-$module/$version/javafx-$module-$version-$arch.jar.sha1")'"' + done + echo + echo ' }' + echo -n ' }' +done +echo +echo ']'