From eb6baa8ad1ccea1b61da85069c4cf896b7e7d904 Mon Sep 17 00:00:00 2001 From: huanghongxun Date: Fri, 16 Aug 2019 12:35:56 +0800 Subject: [PATCH] Support fabric auto-installation --- .../ui/download/AdditionalInstallersPage.java | 70 +++++----- .../ui/download/InstallerWizardProvider.java | 34 +---- .../hmcl/ui/download/InstallersPage.java | 50 +++---- .../VanillaInstallWizardProvider.java | 11 +- .../jackhuang/hmcl/ui/versions/GameItem.java | 12 +- .../hmcl/ui/versions/InstallerListPage.java | 21 +-- .../fxml/download/additional-installers.fxml | 12 ++ .../assets/fxml/download/installers.fxml | 12 ++ .../resources/assets/lang/I18N.properties | 1 + .../resources/assets/lang/I18N_es.properties | 1 + .../assets/lang/I18N_zh_CN.properties | 3 +- .../download/BMCLAPIDownloadProvider.java | 3 + .../download/DefaultDependencyManager.java | 35 ++--- .../hmcl/download/DefaultGameBuilder.java | 14 +- .../hmcl/download/DependencyManager.java | 8 +- .../hmcl/download/LibraryAnalyzer.java | 12 +- .../jackhuang/hmcl/download/MaintainTask.java | 1 + .../hmcl/download/MojangDownloadProvider.java | 3 + .../hmcl/download/RemoteVersion.java | 6 + .../download/fabric/FabricInstallTask.java | 132 ++++++++++++++++++ .../download/fabric/FabricRemoteVersion.java | 41 ++++++ .../download/fabric/FabricVersionList.java | 119 ++++++++++++++++ .../hmcl/download/forge/ForgeInstallTask.java | 5 +- .../download/forge/ForgeNewInstallTask.java | 2 +- .../download/forge/ForgeRemoteVersion.java | 8 ++ .../liteloader/LiteLoaderInstallTask.java | 5 +- .../liteloader/LiteLoaderRemoteVersion.java | 7 + .../optifine/OptiFineInstallTask.java | 7 +- .../optifine/OptiFineRemoteVersion.java | 8 ++ .../java/org/jackhuang/hmcl/game/Version.java | 10 +- .../java/org/jackhuang/hmcl/util/Lang.java | 7 + 31 files changed, 484 insertions(+), 176 deletions(-) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricInstallTask.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricRemoteVersion.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricVersionList.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/AdditionalInstallersPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/AdditionalInstallersPage.java index 4a773ebed..e084d66ab 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/AdditionalInstallersPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/AdditionalInstallersPage.java @@ -23,6 +23,7 @@ import javafx.scene.control.Label; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import org.jackhuang.hmcl.download.DownloadProvider; +import org.jackhuang.hmcl.download.LibraryAnalyzer; import org.jackhuang.hmcl.download.RemoteVersion; import org.jackhuang.hmcl.game.GameRepository; import org.jackhuang.hmcl.ui.FXUtils; @@ -33,6 +34,7 @@ import org.jackhuang.hmcl.util.Lang; import java.util.Map; import java.util.Optional; +import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.*; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; class AdditionalInstallersPage extends StackPane implements WizardPage { @@ -42,6 +44,8 @@ class AdditionalInstallersPage extends StackPane implements WizardPage { @FXML private VBox list; @FXML + private JFXButton btnFabric; + @FXML private JFXButton btnForge; @FXML private JFXButton btnLiteLoader; @@ -52,6 +56,8 @@ class AdditionalInstallersPage extends StackPane implements WizardPage { @FXML private Label lblVersionName; @FXML + private Label lblFabric; + @FXML private Label lblForge; @FXML private Label lblLiteLoader; @@ -69,26 +75,17 @@ class AdditionalInstallersPage extends StackPane implements WizardPage { lblGameVersion.setText(provider.getGameVersion()); lblVersionName.setText(provider.getVersion().getId()); - btnForge.setOnMouseClicked(e -> { - controller.getSettings().put(INSTALLER_TYPE, 0); - controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.forge")), provider.getGameVersion(), downloadProvider, "forge", () -> { - controller.onPrev(false); - })); - }); + JFXButton[] buttons = new JFXButton[]{btnFabric, btnForge, btnLiteLoader, btnOptiFine}; + String[] libraryIds = new String[]{"fabric", "forge", "liteloader", "optifine"}; - btnLiteLoader.setOnMouseClicked(e -> { - controller.getSettings().put(INSTALLER_TYPE, 1); - controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.liteloader")), provider.getGameVersion(), downloadProvider, "liteloader", () -> { - controller.onPrev(false); - })); - }); - - btnOptiFine.setOnMouseClicked(e -> { - controller.getSettings().put(INSTALLER_TYPE, 2); - controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.optifine")), provider.getGameVersion(), downloadProvider, "optifine", () -> { - controller.onPrev(false); - })); - }); + for (int i = 0; i < libraryIds.length; ++i) { + String libraryId = libraryIds[i]; + buttons[i].setOnMouseClicked(e -> { + controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer." + libraryId)), provider.getGameVersion(), downloadProvider, libraryId, () -> { + controller.onPrev(false); + })); + }); + } btnInstall.setOnMouseClicked(e -> onInstall()); } @@ -109,30 +106,29 @@ class AdditionalInstallersPage extends StackPane implements WizardPage { @Override public void onNavigate(Map settings) { lblGameVersion.setText(i18n("install.new_game.current_game_version") + ": " + provider.getGameVersion()); - btnForge.setDisable(provider.getForge() != null); - if (provider.getForge() != null || controller.getSettings().containsKey("forge")) - lblForge.setText(i18n("install.installer.version", i18n("install.installer.forge")) + ": " + Lang.nonNull(provider.getForge(), getVersion("forge"))); - else - lblForge.setText(i18n("install.installer.not_installed", i18n("install.installer.forge"))); - btnLiteLoader.setDisable(provider.getLiteLoader() != null); - if (provider.getLiteLoader() != null || controller.getSettings().containsKey("liteloader")) - lblLiteLoader.setText(i18n("install.installer.version", i18n("install.installer.liteloader")) + ": " + Lang.nonNull(provider.getLiteLoader(), getVersion("liteloader"))); - else - lblLiteLoader.setText(i18n("install.installer.not_installed", i18n("install.installer.liteloader"))); + LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(provider.getVersion().resolve(provider.getProfile().getRepository())); + String fabric = analyzer.getVersion(FABRIC).orElse(null); + String forge = analyzer.getVersion(FORGE).orElse(null); + String liteLoader = analyzer.getVersion(LITELOADER).orElse(null); + String optiFine = analyzer.getVersion(OPTIFINE).orElse(null); - btnOptiFine.setDisable(provider.getOptiFine() != null); - if (provider.getOptiFine() != null || controller.getSettings().containsKey("optifine")) - lblOptiFine.setText(i18n("install.installer.version", i18n("install.installer.optifine")) + ": " + Lang.nonNull(provider.getOptiFine(), getVersion("optifine"))); - else - lblOptiFine.setText(i18n("install.installer.not_installed", i18n("install.installer.optifine"))); + JFXButton[] buttons = new JFXButton[]{btnFabric, btnForge, btnLiteLoader, btnOptiFine}; + Label[] labels = new Label[]{lblFabric, lblForge, lblLiteLoader, lblOptiFine}; + String[] libraryIds = new String[]{"fabric", "forge", "liteloader", "optifine"}; + String[] versions = new String[]{fabric, forge, liteLoader, optiFine}; + for (int i = 0; i < libraryIds.length; ++i) { + String libraryId = libraryIds[i]; + buttons[i].setDisable(versions[i] != null); + if (versions[i] != null || controller.getSettings().containsKey(libraryId)) + labels[i].setText(i18n("install.installer.version", i18n("install.installer." + libraryId)) + ": " + Lang.nonNull(versions[i], getVersion(libraryId))); + else + labels[i].setText(i18n("install.installer.not_installed", i18n("install.installer." + libraryId))); + } } @Override public void cleanup(Map settings) { - settings.remove(INSTALLER_TYPE); } - - public static final String INSTALLER_TYPE = "INSTALLER_TYPE"; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallerWizardProvider.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallerWizardProvider.java index f0bc7b7cb..0c9208654 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallerWizardProvider.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallerWizardProvider.java @@ -19,7 +19,6 @@ package org.jackhuang.hmcl.ui.download; import javafx.scene.Node; import org.jackhuang.hmcl.download.DownloadProvider; -import org.jackhuang.hmcl.download.LibraryAnalyzer; import org.jackhuang.hmcl.download.RemoteVersion; import org.jackhuang.hmcl.download.VersionMismatchException; import org.jackhuang.hmcl.download.game.LibraryDownloadException; @@ -39,26 +38,17 @@ import org.jackhuang.hmcl.util.io.ResponseCodeException; import java.net.SocketTimeoutException; import java.util.Map; -import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.*; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public final class InstallerWizardProvider implements WizardProvider { private final Profile profile; private final String gameVersion; private final Version version; - private final String forge; - private final String liteLoader; - private final String optiFine; public InstallerWizardProvider(Profile profile, String gameVersion, Version version) { this.profile = profile; this.gameVersion = gameVersion; this.version = version; - - LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version.resolve(profile.getRepository())); - forge = analyzer.getVersion(FORGE).orElse(null); - liteLoader = analyzer.getVersion(LITELOADER).orElse(null); - optiFine = analyzer.getVersion(OPTIFINE).orElse(null); } public Profile getProfile() { @@ -73,18 +63,6 @@ public final class InstallerWizardProvider implements WizardProvider { return version; } - public String getForge() { - return forge; - } - - public String getLiteLoader() { - return liteLoader; - } - - public String getOptiFine() { - return optiFine; - } - @Override public void start(Map settings) { } @@ -96,14 +74,10 @@ public final class InstallerWizardProvider implements WizardProvider { Task ret = Task.supplyAsync(() -> version); - if (settings.containsKey("forge")) - ret = ret.thenComposeAsync(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("forge"))); - - if (settings.containsKey("liteloader")) - ret = ret.thenComposeAsync(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("liteloader"))); - - if (settings.containsKey("optifine")) - ret = ret.thenComposeAsync(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("optifine"))); + for (Object value : settings.values()) { + if (value instanceof RemoteVersion) + ret = ret.thenComposeAsync(profile.getDependency().installLibraryAsync((RemoteVersion) value)); + } return ret.thenComposeAsync(profile.getRepository().refreshVersionsAsync()); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java index 388524a07..64ce1f6ed 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java @@ -42,6 +42,9 @@ public class InstallersPage extends StackPane implements WizardPage { @FXML private VBox list; + @FXML + private JFXButton btnFabric; + @FXML private JFXButton btnForge; @@ -54,6 +57,9 @@ public class InstallersPage extends StackPane implements WizardPage { @FXML private Label lblGameVersion; + @FXML + private Label lblFabric; + @FXML private Label lblForge; @@ -81,20 +87,14 @@ public class InstallersPage extends StackPane implements WizardPage { txtName.textProperty().addListener(e -> btnInstall.setDisable(!txtName.validate())); txtName.setText(gameVersion); - btnForge.setOnMouseClicked(e -> { - controller.getSettings().put(INSTALLER_TYPE, 0); - controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.forge")), gameVersion, downloadProvider, "forge", () -> controller.onPrev(false))); - }); + JFXButton[] buttons = new JFXButton[]{btnFabric, btnForge, btnLiteLoader, btnOptiFine}; + String[] libraryIds = new String[]{"fabric", "forge", "liteloader", "optifine"}; - btnLiteLoader.setOnMouseClicked(e -> { - controller.getSettings().put(INSTALLER_TYPE, 1); - controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.liteloader")), gameVersion, downloadProvider, "liteloader", () -> controller.onPrev(false))); - }); - - btnOptiFine.setOnMouseClicked(e -> { - controller.getSettings().put(INSTALLER_TYPE, 2); - controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.optifine")), gameVersion, downloadProvider, "optifine", () -> controller.onPrev(false))); - }); + for (int i = 0; i < libraryIds.length; ++i) { + String libraryId = libraryIds[i]; + buttons[i].setOnMouseClicked(e -> + controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer." + libraryId)), gameVersion, downloadProvider, libraryId, () -> controller.onPrev(false)))); + } } @Override @@ -109,25 +109,21 @@ public class InstallersPage extends StackPane implements WizardPage { @Override public void onNavigate(Map settings) { lblGameVersion.setText(i18n("install.new_game.current_game_version") + ": " + getVersion("game")); - if (controller.getSettings().containsKey("forge")) - lblForge.setText(i18n("install.installer.version", i18n("install.installer.forge")) + ": " + getVersion("forge")); - else - lblForge.setText(i18n("install.installer.not_installed", i18n("install.installer.forge"))); - if (controller.getSettings().containsKey("liteloader")) - lblLiteLoader.setText(i18n("install.installer.version", i18n("install.installer.liteloader")) + ": " + getVersion("liteloader")); - else - lblLiteLoader.setText(i18n("install.installer.not_installed", i18n("install.installer.liteloader"))); + Label[] labels = new Label[]{lblFabric, lblForge, lblLiteLoader, lblOptiFine}; + String[] libraryIds = new String[]{"fabric", "forge", "liteloader", "optifine"}; - if (controller.getSettings().containsKey("optifine")) - lblOptiFine.setText(i18n("install.installer.version", i18n("install.installer.optifine")) + ": " + getVersion("optifine")); - else - lblOptiFine.setText(i18n("install.installer.not_installed", i18n("install.installer.optifine"))); + for (int i = 0; i < libraryIds.length; ++i) { + String libraryId = libraryIds[i]; + if (controller.getSettings().containsKey(libraryId)) + labels[i].setText(i18n("install.installer.version", i18n("install.installer." + libraryId)) + ": " + getVersion(libraryId)); + else + labels[i].setText(i18n("install.installer.not_installed", i18n("install.installer." + libraryId))); + } } @Override public void cleanup(Map settings) { - settings.remove(INSTALLER_TYPE); } @FXML @@ -135,6 +131,4 @@ public class InstallersPage extends StackPane implements WizardPage { controller.getSettings().put("name", txtName.getText()); controller.onFinish(); } - - public static final String INSTALLER_TYPE = "INSTALLER_TYPE"; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VanillaInstallWizardProvider.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VanillaInstallWizardProvider.java index f9f012219..2fe58401f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VanillaInstallWizardProvider.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VanillaInstallWizardProvider.java @@ -50,14 +50,9 @@ public final class VanillaInstallWizardProvider implements WizardProvider { builder.name(name); builder.gameVersion(((RemoteVersion) settings.get("game")).getGameVersion()); - if (settings.containsKey("forge")) - builder.version((RemoteVersion) settings.get("forge")); - - if (settings.containsKey("liteloader")) - builder.version((RemoteVersion) settings.get("liteloader")); - - if (settings.containsKey("optifine")) - builder.version((RemoteVersion) settings.get("optifine")); + for (Map.Entry entry : settings.entrySet()) + if (!"game".equals(entry.getKey()) && entry.getValue() instanceof RemoteVersion) + builder.version((RemoteVersion) entry.getValue()); return builder.buildAsync().whenComplete(any -> profile.getRepository().refreshVersions()) .thenRunAsync(Schedulers.javafx(), () -> profile.setSelectedVersion(name)); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameItem.java index f3d4b0f35..ebafffe23 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameItem.java @@ -33,7 +33,6 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.*; import static org.jackhuang.hmcl.util.Lang.handleUncaught; import static org.jackhuang.hmcl.util.Lang.threadPool; import static org.jackhuang.hmcl.util.StringUtils.removePrefix; @@ -58,10 +57,13 @@ public class GameItem extends Control { CompletableFuture.supplyAsync(() -> GameVersion.minecraftVersion(profile.getRepository().getVersionJar(id)).orElse(i18n("message.unknown")), POOL_VERSION_RESOLVE) .thenAcceptAsync(game -> { StringBuilder libraries = new StringBuilder(game); - LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(profile.getRepository().getVersion(id)); - analyzer.getVersion(FORGE).ifPresent(library -> libraries.append(", ").append(i18n("install.installer.forge")).append(": ").append(modifyVersion(game, library.replaceAll("(?i)forge", "")))); - analyzer.getVersion(LITELOADER).ifPresent(library -> libraries.append(", ").append(i18n("install.installer.liteloader")).append(": ").append(modifyVersion(game, library.replaceAll("(?i)liteloader", "")))); - analyzer.getVersion(OPTIFINE).ifPresent(library -> libraries.append(", ").append(i18n("install.installer.optifine")).append(": ").append(modifyVersion(game, library.replaceAll("(?i)optifine", "")))); + LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(profile.getRepository().getResolvedVersion(id)); + for (LibraryAnalyzer.LibraryType type : LibraryAnalyzer.LibraryType.values()) + analyzer.getVersion(type).ifPresent(library -> + libraries + .append(", ").append(i18n("install.installer." + type.name().toLowerCase())) + .append(": ").append(modifyVersion(game, library.replaceAll("(?i)" + type.name().toLowerCase(), "")))); + subtitle.set(libraries.toString()); }, Platform::runLater) .exceptionally(handleUncaught); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java index 7731b4fb3..b85560375 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java @@ -44,7 +44,6 @@ import java.util.List; import java.util.function.Consumer; import java.util.function.Function; -import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.*; import static org.jackhuang.hmcl.ui.FXUtils.runInFX; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; @@ -85,19 +84,13 @@ public class InstallerListPage extends ListPageBase { }; itemsProperty().clear(); - analyzer.getVersion(FORGE).ifPresent(libraryVersion -> itemsProperty().add( - new InstallerItem("Forge", libraryVersion, () -> { - Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, "forge", libraryVersion)); - }, removeAction.apply("forge")))); - analyzer.getVersion(LITELOADER).ifPresent(libraryVersion -> itemsProperty().add( - new InstallerItem("LiteLoader", libraryVersion, () -> { - Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, "liteloader", libraryVersion)); - }, removeAction.apply("liteloader")))); - analyzer.getVersion(OPTIFINE).ifPresent(libraryVersion -> itemsProperty().add( - new InstallerItem("OptiFine", libraryVersion, () -> { - Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, "optifine", libraryVersion)); - }, removeAction.apply("optifine")))); - analyzer.getVersion(FABRIC).ifPresent(libraryVersion -> itemsProperty().add(new InstallerItem("Fabric", libraryVersion, null, null))); + for (LibraryAnalyzer.LibraryType type : LibraryAnalyzer.LibraryType.values()) { + String libraryId = type.getPatchId(); + analyzer.getVersion(type).ifPresent(libraryVersion -> itemsProperty().add( + new InstallerItem(i18n("install.installer." + libraryId), libraryVersion, () -> { + Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, libraryId, libraryVersion)); + }, removeAction.apply(libraryId)))); + } }).start(); } diff --git a/HMCL/src/main/resources/assets/fxml/download/additional-installers.fxml b/HMCL/src/main/resources/assets/fxml/download/additional-installers.fxml index 115fca5f7..4b17cdc41 100644 --- a/HMCL/src/main/resources/assets/fxml/download/additional-installers.fxml +++ b/HMCL/src/main/resources/assets/fxml/download/additional-installers.fxml @@ -15,6 +15,18 @@
+ + + + + + + + + + + diff --git a/HMCL/src/main/resources/assets/fxml/download/installers.fxml b/HMCL/src/main/resources/assets/fxml/download/installers.fxml index a299dce6f..bb6cecccb 100644 --- a/HMCL/src/main/resources/assets/fxml/download/installers.fxml +++ b/HMCL/src/main/resources/assets/fxml/download/installers.fxml @@ -15,6 +15,18 @@
+ + + + + + + + + + + diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 2b64d760f..4302ab0eb 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -135,6 +135,7 @@ install.failed.install_online=Unable to recognize what you provided installer fi install.failed.optifine_conflict=OptiFine and Forge are both installed simultaneously on Minecraft 1.13 install.failed.version_mismatch=The library requires game version %s, but actual version is %s. install.installer.choose=Choose a %s version +install.installer.fabric=Fabric install.installer.forge=Forge install.installer.game=Game install.installer.install=Install %s diff --git a/HMCL/src/main/resources/assets/lang/I18N_es.properties b/HMCL/src/main/resources/assets/lang/I18N_es.properties index 0e8e55496..6a23fa02b 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_es.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_es.properties @@ -135,6 +135,7 @@ install.failed.install_online=No se pudo reconocer el archivo instalador proveí install.failed.optifine_conflict=OptiFine y Forge ambos están instalados simultáneamente en Minecraft 1.13 install.failed.version_mismatch=El library requiere versión del juego %s, pero versión actual es %s. install.installer.choose=Escoja una versión de %s +install.installer.fabric=Fabric install.installer.forge=Forge install.installer.game=Juego install.installer.install=Instalar %s diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 55aadb12d..c97665ce6 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -131,9 +131,10 @@ install.failed.downloading=安装失败,部分文件未能完成下载 install.failed.downloading.detail=未能下载文件:%s install.failed.downloading.timeout=下载超时:%s install.failed.install_online=无法识别要安装的软件 -install.failed.optifine_conflict=暂不支持 OptiFine 与 Forge 同时安装在 Minecraft 1.13 上 +install.failed.optifine_conflict=暂不支持 OptiFine, Fabric 与 Forge 同时安装在 Minecraft 1.13 及以上版本 install.failed.version_mismatch=该软件需要的游戏版本为 %s,但实际的游戏版本为 %s。 install.installer.choose=选择 %s 版本 +install.installer.fabric=Fabric install.installer.forge=Forge install.installer.game=游戏 install.installer.install=安装 %s diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.java index 123487c55..73b39e237 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.java @@ -17,6 +17,7 @@ */ package org.jackhuang.hmcl.download; +import org.jackhuang.hmcl.download.fabric.FabricVersionList; import org.jackhuang.hmcl.download.forge.ForgeBMCLVersionList; import org.jackhuang.hmcl.download.game.GameVersionList; import org.jackhuang.hmcl.download.liteloader.LiteLoaderBMCLVersionList; @@ -43,6 +44,8 @@ public class BMCLAPIDownloadProvider implements DownloadProvider { switch (id) { case "game": return GameVersionList.INSTANCE; + case "fabric": + return FabricVersionList.INSTANCE; case "forge": return ForgeBMCLVersionList.INSTANCE; case "liteloader": diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java index a7a4b462c..100a0d7dd 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java @@ -18,14 +18,10 @@ package org.jackhuang.hmcl.download; import org.jackhuang.hmcl.download.forge.ForgeInstallTask; -import org.jackhuang.hmcl.download.forge.ForgeRemoteVersion; import org.jackhuang.hmcl.download.game.GameAssetDownloadTask; import org.jackhuang.hmcl.download.game.GameDownloadTask; import org.jackhuang.hmcl.download.game.GameLibrariesTask; -import org.jackhuang.hmcl.download.liteloader.LiteLoaderInstallTask; -import org.jackhuang.hmcl.download.liteloader.LiteLoaderRemoteVersion; import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask; -import org.jackhuang.hmcl.download.optifine.OptiFineRemoteVersion; import org.jackhuang.hmcl.game.DefaultGameRepository; import org.jackhuang.hmcl.game.Library; import org.jackhuang.hmcl.game.Version; @@ -93,30 +89,21 @@ public class DefaultDependencyManager extends AbstractDependencyManager { } @Override - public Task installLibraryAsync(String gameVersion, Version version, String libraryId, String libraryVersion) { - if (version.isResolved()) throw new IllegalArgumentException("Version should not be resolved"); + public Task installLibraryAsync(String gameVersion, Version baseVersion, String libraryId, String libraryVersion) { + if (baseVersion.isResolved()) throw new IllegalArgumentException("Version should not be resolved"); VersionList versionList = getVersionList(libraryId); return versionList.loadAsync(gameVersion, getDownloadProvider()) - .thenComposeAsync(() -> installLibraryAsync(version, versionList.getVersion(gameVersion, libraryVersion) + .thenComposeAsync(() -> installLibraryAsync(baseVersion, versionList.getVersion(gameVersion, libraryVersion) .orElseThrow(() -> new IllegalStateException("Remote library " + libraryId + " has no version " + libraryVersion)))); } @Override - public Task installLibraryAsync(Version oldVersion, RemoteVersion libraryVersion) { - if (oldVersion.isResolved()) throw new IllegalArgumentException("Version should not be resolved"); + public Task installLibraryAsync(Version baseVersion, RemoteVersion libraryVersion) { + if (baseVersion.isResolved()) throw new IllegalArgumentException("Version should not be resolved"); - Task task; - if (libraryVersion instanceof ForgeRemoteVersion) - task = new ForgeInstallTask(this, oldVersion, (ForgeRemoteVersion) libraryVersion); - else if (libraryVersion instanceof LiteLoaderRemoteVersion) - task = new LiteLoaderInstallTask(this, oldVersion, (LiteLoaderRemoteVersion) libraryVersion); - else if (libraryVersion instanceof OptiFineRemoteVersion) - task = new OptiFineInstallTask(this, oldVersion, (OptiFineRemoteVersion) libraryVersion); - else - throw new IllegalArgumentException("Remote library " + libraryVersion + " is unrecognized."); - return task - .thenApplyAsync(oldVersion::addPatch) + return libraryVersion.getInstallTask(this, baseVersion) + .thenApplyAsync(baseVersion::addPatch) .thenComposeAsync(repository::save); } @@ -163,19 +150,19 @@ public class DefaultDependencyManager extends AbstractDependencyManager { switch (libraryId) { case "forge": analyzer.ifPresent(LibraryAnalyzer.LibraryType.FORGE, (library, libraryVersion) -> newList.remove(library)); - version = version.removePatchById("net.minecraftforge"); + version = version.removePatchById(LibraryAnalyzer.LibraryType.FORGE.getPatchId()); break; case "liteloader": analyzer.ifPresent(LibraryAnalyzer.LibraryType.LITELOADER, (library, libraryVersion) -> newList.remove(library)); - version = version.removePatchById("com.mumfrey.liteloader"); + version = version.removePatchById(LibraryAnalyzer.LibraryType.LITELOADER.getPatchId()); break; case "optifine": analyzer.ifPresent(LibraryAnalyzer.LibraryType.OPTIFINE, (library, libraryVersion) -> newList.remove(library)); - version = version.removePatchById("net.optifine"); + version = version.removePatchById(LibraryAnalyzer.LibraryType.OPTIFINE.getPatchId()); break; case "fabric": analyzer.ifPresent(LibraryAnalyzer.LibraryType.FABRIC, (library, libraryVersion) -> newList.remove(library)); - version = version.removePatchById("net.fabricmc"); + version = version.removePatchById(LibraryAnalyzer.LibraryType.FABRIC.getPatchId()); break; } return new MaintainTask(version.setLibraries(newList)); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultGameBuilder.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultGameBuilder.java index 6ff7c1a98..f9ff5bd5d 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultGameBuilder.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultGameBuilder.java @@ -26,6 +26,8 @@ import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.util.function.ExceptionalFunction; import org.jackhuang.hmcl.util.gson.JsonUtils; +import java.util.Map; + /** * * @author huangyuhui @@ -60,12 +62,8 @@ public class DefaultGameBuilder extends GameBuilder { Task libraryTask = vanillaTask.thenSupplyAsync(() -> version); - if (toolVersions.containsKey("forge")) - libraryTask = libraryTask.thenComposeAsync(libraryTaskHelper(gameVersion, "forge")); - if (toolVersions.containsKey("liteloader")) - libraryTask = libraryTask.thenComposeAsync(libraryTaskHelper(gameVersion, "liteloader")); - if (toolVersions.containsKey("optifine")) - libraryTask = libraryTask.thenComposeAsync(libraryTaskHelper(gameVersion, "optifine")); + for (Map.Entry entry : toolVersions.entrySet()) + libraryTask = libraryTask.thenComposeAsync(libraryTaskHelper(gameVersion, entry.getKey(), entry.getValue())); for (RemoteVersion remoteVersion : remoteVersions) libraryTask = libraryTask.thenComposeAsync(dependencyManager.installLibraryAsync(remoteVersion)); @@ -77,8 +75,8 @@ public class DefaultGameBuilder extends GameBuilder { }); } - private ExceptionalFunction, ?> libraryTaskHelper(String gameVersion, String libraryId) { - return version -> dependencyManager.installLibraryAsync(gameVersion, version, libraryId, toolVersions.get(libraryId)); + private ExceptionalFunction, ?> libraryTaskHelper(String gameVersion, String libraryId, String libraryVersion) { + return version -> dependencyManager.installLibraryAsync(gameVersion, version, libraryId, libraryVersion); } protected Task downloadGameAsync(String gameVersion, Version version) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DependencyManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DependencyManager.java index c627bf265..d90d27ca4 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DependencyManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DependencyManager.java @@ -66,22 +66,22 @@ public interface DependencyManager { * **Note**: Installing a library may change the version.json. * * @param gameVersion the Minecraft version that the library relies on. - * @param version the version.json. + * @param baseVersion the version.json. * @param libraryId the type of being installed library. i.e. "forge", "liteloader", "optifine" * @param libraryVersion the version of being installed library. * @return the task to install the specific library. */ - Task installLibraryAsync(String gameVersion, Version version, String libraryId, String libraryVersion); + Task installLibraryAsync(String gameVersion, Version baseVersion, String libraryId, String libraryVersion); /** * Install a library to a version. * **Note**: Installing a library may change the version.json. * - * @param version the version.json.\ + * @param baseVersion the version.json. * @param libraryVersion the remote version of being installed library. * @return the task to install the specific library. */ - Task installLibraryAsync(Version version, RemoteVersion libraryVersion); + Task installLibraryAsync(Version baseVersion, RemoteVersion libraryVersion); /** * Get registered version list. diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java index a610eb51f..b6927d9a9 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java @@ -84,10 +84,10 @@ public final class LibraryAnalyzer { } public enum LibraryType { - FORGE(true, "net.minecraftforge", Pattern.compile("net\\.minecraftforge"), Pattern.compile("forge")), - LITELOADER(true, "com.mumfrey.liteloader", Pattern.compile("com\\.mumfrey"), Pattern.compile("liteloader")), - OPTIFINE(false, "net.optifine", Pattern.compile("(net\\.)?optifine"), Pattern.compile(".*")), - FABRIC(true, "net.fabricmc", Pattern.compile("net\\.fabricmc"), Pattern.compile("fabric-loader")); + FABRIC(true, "fabric", Pattern.compile("net\\.fabricmc"), Pattern.compile("fabric-loader")), + FORGE(true, "forge", Pattern.compile("net\\.minecraftforge"), Pattern.compile("forge")), + LITELOADER(true, "liteloader", Pattern.compile("com\\.mumfrey"), Pattern.compile("liteloader")), + OPTIFINE(false, "optifine", Pattern.compile("(net\\.)?optifine"), Pattern.compile("^(?!.*launchwrapper).*$")); private final boolean modLoader; private final String patchId; @@ -103,5 +103,9 @@ public final class LibraryAnalyzer { public boolean isModLoader() { return modLoader; } + + public String getPatchId() { + return patchId; + } } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MaintainTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MaintainTask.java index c785fa418..94f3b5b1f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MaintainTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MaintainTask.java @@ -40,6 +40,7 @@ public class MaintainTask extends Task { } else { // Vanilla Minecraft does not need maintain // Forge 1.13 support not implemented, not compatible with OptiFine currently. + // Fabric does not need maintain, nothing compatible with fabric now. return version; } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MojangDownloadProvider.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MojangDownloadProvider.java index d32e17dff..5ddf32de8 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MojangDownloadProvider.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MojangDownloadProvider.java @@ -17,6 +17,7 @@ */ package org.jackhuang.hmcl.download; +import org.jackhuang.hmcl.download.fabric.FabricVersionList; import org.jackhuang.hmcl.download.forge.ForgeBMCLVersionList; import org.jackhuang.hmcl.download.game.GameVersionList; import org.jackhuang.hmcl.download.liteloader.LiteLoaderVersionList; @@ -43,6 +44,8 @@ public class MojangDownloadProvider implements DownloadProvider { switch (id) { case "game": return GameVersionList.INSTANCE; + case "fabric": + return FabricVersionList.INSTANCE; case "forge": return ForgeBMCLVersionList.INSTANCE; case "liteloader": diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/RemoteVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/RemoteVersion.java index 08495d30c..7dde950ab 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/RemoteVersion.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/RemoteVersion.java @@ -17,6 +17,8 @@ */ package org.jackhuang.hmcl.download; +import org.jackhuang.hmcl.game.Version; +import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.util.ToStringBuilder; import org.jackhuang.hmcl.util.versioning.VersionNumber; @@ -75,6 +77,10 @@ public class RemoteVersion implements Comparable { return type; } + public Task getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) { + throw new UnsupportedOperationException(toString() + " cannot be installed yet"); + } + @Override public boolean equals(Object obj) { return obj instanceof RemoteVersion && Objects.equals(selfVersion, ((RemoteVersion) obj).selfVersion); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricInstallTask.java new file mode 100644 index 000000000..16c3a047f --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricInstallTask.java @@ -0,0 +1,132 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2019 huangyuhui 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 . + */ +package org.jackhuang.hmcl.download.fabric; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.game.Arguments; +import org.jackhuang.hmcl.game.Library; +import org.jackhuang.hmcl.game.Version; +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 java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; + +/** + * Note: Fabric should be installed first. + * + * @author huangyuhui + */ +public final class FabricInstallTask extends Task { + + private final DefaultDependencyManager dependencyManager; + private final Version version; + private final FabricRemoteVersion remote; + private final GetTask launchMetaTask; + private final List> dependencies = new LinkedList<>(); + + public FabricInstallTask(DefaultDependencyManager dependencyManager, Version version, FabricRemoteVersion remoteVersion) { + this.dependencyManager = dependencyManager; + this.version = version; + this.remote = remoteVersion; + + launchMetaTask = new GetTask(NetworkUtils.toURL(getLaunchMetaUrl(remote.getSelfVersion()))) + .setCacheRepository(dependencyManager.getCacheRepository()); + } + + @Override + public boolean doPreExecute() { + return true; + } + + @Override + public void preExecute() { + if (!Objects.equals("net.minecraft.client.main.Main", version.getMainClass())) + throw new UnsupportedFabricInstallationException(); + } + + @Override + public Collection> getDependents() { + return Collections.singleton(launchMetaTask); + } + + @Override + public Collection> getDependencies() { + return dependencies; + } + + @Override + public boolean isRelyingOnDependencies() { + return false; + } + + @Override + public void execute() throws IOException { + setResult(getPatch(JsonUtils.GSON.fromJson(launchMetaTask.getResult(), JsonObject.class), remote.getGameVersion(), remote.getSelfVersion())); + + dependencies.add(dependencyManager.checkLibraryCompletionAsync(getResult())); + } + + private static String getLaunchMetaUrl(String loaderVersion) { + return String.format("%s/%s/%s/%s/%3$s-%4$s.json", "https://maven.fabricmc.net/", "net/fabricmc", "fabric-loader", loaderVersion); + } + + private Version getPatch(JsonObject jsonObject, String gameVersion, String loaderVersion) { + Arguments arguments = new Arguments(); + + String mainClass; + if (!jsonObject.get("mainClass").isJsonObject()) { + mainClass = jsonObject.get("mainClass").getAsString(); + } else { + mainClass = jsonObject.get("mainClass").getAsJsonObject().get("client").getAsString(); + } + + if (jsonObject.has("launchwrapper")) { + String clientTweaker = jsonObject.get("launchwrapper").getAsJsonObject().get("tweakers").getAsJsonObject().get("client").getAsJsonArray().get(0).getAsString(); + arguments = arguments.addGameArguments("--tweakClass", clientTweaker); + } + + JsonObject librariesObject = jsonObject.getAsJsonObject("libraries"); + List libraries = new ArrayList<>(); + + // "common, server" is hard coded in fabric installer. + // Don't know the purpose of ignoring client libraries. + for (String side : new String[]{"common", "server"}) { + for (JsonElement element : librariesObject.getAsJsonArray(side)) { + libraries.add(JsonUtils.GSON.fromJson(element, Library.class)); + } + } + + libraries.add(new Library("net.fabricmc", "intermediary", gameVersion, null, "https://maven.fabricmc.net/", null)); + libraries.add(new Library("net.fabricmc", "fabric-loader", loaderVersion, null, "https://maven.fabricmc.net/", null)); + + return new Version("net.fabricmc", loaderVersion, 30000, arguments, mainClass, libraries); + } + + public static class UnsupportedFabricInstallationException extends UnsupportedOperationException { + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricRemoteVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricRemoteVersion.java new file mode 100644 index 000000000..16cff16c9 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricRemoteVersion.java @@ -0,0 +1,41 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2019 huangyuhui 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 . + */ +package org.jackhuang.hmcl.download.fabric; + +import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.download.RemoteVersion; +import org.jackhuang.hmcl.game.Version; +import org.jackhuang.hmcl.task.Task; + +public class FabricRemoteVersion extends RemoteVersion { + /** + * Constructor. + * + * @param gameVersion the Minecraft version that this remote version suits. + * @param selfVersion the version string of the remote version. + * @param url the installer or universal jar URL. + */ + FabricRemoteVersion(String gameVersion, String selfVersion, String url) { + super(gameVersion, selfVersion, url); + } + + @Override + public Task getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) { + return new FabricInstallTask(dependencyManager, baseVersion, this); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricVersionList.java new file mode 100644 index 000000000..cd0d0f8b5 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricVersionList.java @@ -0,0 +1,119 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2019 huangyuhui 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 . + */ +package org.jackhuang.hmcl.download.fabric; + +import com.google.gson.reflect.TypeToken; +import org.jackhuang.hmcl.download.DownloadProvider; +import org.jackhuang.hmcl.download.VersionList; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.gson.JsonUtils; +import org.jackhuang.hmcl.util.io.NetworkUtils; + +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +public final class FabricVersionList extends VersionList { + + public static final FabricVersionList INSTANCE = new FabricVersionList(); + + private FabricVersionList() { + } + + @Override + public boolean hasType() { + return false; + } + + @Override + public Task refreshAsync(DownloadProvider downloadProvider) { + return new Task() { + @Override + public void execute() throws IOException, XMLStreamException { + List gameVersions = getGameVersions(META_URL); + List loaderVersions = getVersions(FABRIC_MAVEN_URL, FABRIC_PACKAGE_NAME, FABRIC_JAR_NAME); + + lock.writeLock().lock(); + + try { + for (String gameVersion : gameVersions) + for (String loaderVersion : loaderVersions) + versions.put(gameVersion, new FabricRemoteVersion(gameVersion, loaderVersion, "")); + } finally { + lock.writeLock().unlock(); + } + } + }; + } + + private static final String META_URL = "https://meta.fabricmc.net/v2/versions/game"; + private static final String FABRIC_MAVEN_URL = "https://maven.fabricmc.net/"; + private static final String FABRIC_PACKAGE_NAME = "net/fabricmc"; + private static final String FABRIC_JAR_NAME = "fabric-loader"; + + private List getVersions(String mavenServerURL, String packageName, String jarName) throws IOException, XMLStreamException { + List versions = new ArrayList<>(); + URL url = new URL(mavenServerURL + packageName + "/" + jarName + "/maven-metadata.xml"); + XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(url.openStream()); + + while(reader.hasNext()) { + if (reader.next() == 1 && reader.getLocalName().equals("version")) { + String text = reader.getElementText(); + versions.add(text); + } + } + + reader.close(); + Collections.reverse(versions); + return versions; + } + + private List getGameVersions(String metaUrl) throws IOException { + String json = NetworkUtils.doGet(NetworkUtils.toURL(metaUrl)); + return JsonUtils.GSON.>fromJson(json, new TypeToken>() { + }.getType()).stream().map(GameVersion::getVersion).collect(Collectors.toList()); + } + + private static class GameVersion { + private final String version; + private final boolean stable; + + public GameVersion() { + this("", false); + } + + public GameVersion(String version, boolean stable) { + this.version = version; + this.stable = stable; + } + + public String getVersion() { + return version; + } + + public boolean isStable() { + return stable; + } + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java index 5932240ad..61fd9de36 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java @@ -18,6 +18,7 @@ package org.jackhuang.hmcl.download.forge; import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.download.LibraryAnalyzer; import org.jackhuang.hmcl.download.VersionMismatchException; import org.jackhuang.hmcl.game.GameVersion; import org.jackhuang.hmcl.game.Version; @@ -80,8 +81,8 @@ public final class ForgeInstallTask extends Task { public void postExecute() throws Exception { Files.deleteIfExists(installer); setResult(dependency.getResult() - .setPriority(10000) - .setId("net.minecraftforge") + .setPriority(30000) + .setId(LibraryAnalyzer.LibraryType.FORGE.getPatchId()) .setVersion(remote.getSelfVersion())); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java index d453d2ca1..7896af8d7 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java @@ -134,7 +134,7 @@ public class ForgeNewInstallTask extends Task { @Override public void execute() throws Exception { - if ("net.minecraft.launchwrapper.Launch".equals(version.getMainClass())) + if ("net.minecraft.launchwrapper.Launch".equals(version.resolve(dependencyManager.getGameRepository()).getMainClass())) throw new OptiFineInstallTask.UnsupportedOptiFineInstallationException(); Path temp = Files.createTempDirectory("forge_installer"); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeRemoteVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeRemoteVersion.java index 28ef7b06a..a695e92cb 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeRemoteVersion.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeRemoteVersion.java @@ -17,7 +17,10 @@ */ package org.jackhuang.hmcl.download.forge; +import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.RemoteVersion; +import org.jackhuang.hmcl.game.Version; +import org.jackhuang.hmcl.task.Task; public class ForgeRemoteVersion extends RemoteVersion { /** @@ -30,4 +33,9 @@ public class ForgeRemoteVersion extends RemoteVersion { public ForgeRemoteVersion(String gameVersion, String selfVersion, String url) { super(gameVersion, selfVersion, url); } + + @Override + public Task getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) { + return new ForgeInstallTask(dependencyManager, baseVersion, this); + } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderInstallTask.java index 9fe763b16..f6a655938 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderInstallTask.java @@ -18,6 +18,7 @@ package org.jackhuang.hmcl.download.liteloader; import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.download.LibraryAnalyzer; import org.jackhuang.hmcl.game.Arguments; import org.jackhuang.hmcl.game.LibrariesDownloadInfo; import org.jackhuang.hmcl.game.Library; @@ -68,9 +69,9 @@ public final class LiteLoaderInstallTask extends Task { new LibrariesDownloadInfo(new LibraryDownloadInfo(null, remote.getUrl())) ); - setResult(new Version("com.mumfrey.liteloader", + setResult(new Version(LibraryAnalyzer.LibraryType.LITELOADER.getPatchId(), remote.getSelfVersion(), - 20000, + 60000, new Arguments().addGameArguments("--tweakClass", "com.mumfrey.liteloader.launch.LiteLoaderTweaker"), "net.minecraft.launchwrapper.Launch", Lang.merge(remote.getLibraries(), Collections.singleton(library))) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderRemoteVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderRemoteVersion.java index 32ae25525..353b97258 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderRemoteVersion.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/liteloader/LiteLoaderRemoteVersion.java @@ -17,8 +17,11 @@ */ package org.jackhuang.hmcl.download.liteloader; +import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.RemoteVersion; import org.jackhuang.hmcl.game.Library; +import org.jackhuang.hmcl.game.Version; +import org.jackhuang.hmcl.task.Task; import java.util.Collection; @@ -47,4 +50,8 @@ public class LiteLoaderRemoteVersion extends RemoteVersion { return tweakClass; } + @Override + public Task getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) { + return new LiteLoaderInstallTask(dependencyManager, baseVersion, this); + } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineInstallTask.java index fc318aa73..b389fd2a6 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineInstallTask.java @@ -18,6 +18,7 @@ package org.jackhuang.hmcl.download.optifine; import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.download.LibraryAnalyzer; import org.jackhuang.hmcl.download.VersionMismatchException; import org.jackhuang.hmcl.game.Arguments; import org.jackhuang.hmcl.game.GameVersion; @@ -97,7 +98,7 @@ public final class OptiFineInstallTask extends Task { public void preExecute() throws Exception { if (!Arrays.asList("net.minecraft.client.main.Main", "net.minecraft.launchwrapper.Launch") - .contains(version.getMainClass())) + .contains(version.resolve(dependencyManager.getGameRepository()).getMainClass())) throw new UnsupportedOptiFineInstallationException(); @@ -156,9 +157,9 @@ public final class OptiFineInstallTask extends Task { } setResult(new Version( - "net.optifine", + LibraryAnalyzer.LibraryType.OPTIFINE.getPatchId(), remote.getSelfVersion(), - 30000, + 90000, new Arguments().addGameArguments("--tweakClass", "optifine.OptiFineTweaker"), "net.minecraft.launchwrapper.Launch", libraries diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineRemoteVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineRemoteVersion.java index 3e635967a..c7752dd35 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineRemoteVersion.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineRemoteVersion.java @@ -17,7 +17,10 @@ */ package org.jackhuang.hmcl.download.optifine; +import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.RemoteVersion; +import org.jackhuang.hmcl.game.Version; +import org.jackhuang.hmcl.task.Task; import java.util.function.Supplier; @@ -34,4 +37,9 @@ public class OptiFineRemoteVersion extends RemoteVersion { public String getUrl() { return url.get(); } + + @Override + public Task getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) { + return new OptiFineInstallTask(dependencyManager, baseVersion, this); + } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java index d975745a8..e0915b95c 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java @@ -65,17 +65,17 @@ public class Version implements Comparable, Validation { private final ReleaseType type; private final Date time; private final Date releaseTime; - private final int minimumLauncherVersion; + private final Integer minimumLauncherVersion; private final Boolean hidden; private final List patches; private transient final boolean resolved; public Version(String id, String version, int priority, Arguments arguments, String mainClass, List libraries) { - this(false, id, version, priority, null, arguments, mainClass, null, null, null, null, libraries, null, null, null, null, null, null, 0, null, null); + this(false, id, version, priority, null, arguments, mainClass, null, null, null, null, libraries, null, null, null, null, null, null, null, null, null); } - public Version(boolean resolved, String id, String version, Integer priority, String minecraftArguments, Arguments arguments, String mainClass, String inheritsFrom, String jar, AssetIndexInfo assetIndex, String assets, List libraries, List compatibilityRules, Map downloads, Map logging, ReleaseType type, Date time, Date releaseTime, int minimumLauncherVersion, Boolean hidden, List patches) { + public Version(boolean resolved, String id, String version, Integer priority, String minecraftArguments, Arguments arguments, String mainClass, String inheritsFrom, String jar, AssetIndexInfo assetIndex, String assets, List libraries, List compatibilityRules, Map downloads, Map logging, ReleaseType type, Date time, Date releaseTime, Integer minimumLauncherVersion, Boolean hidden, List patches) { this.resolved = resolved; this.id = id; this.version = version; @@ -150,7 +150,7 @@ public class Version implements Comparable, Validation { } public int getMinimumLauncherVersion() { - return minimumLauncherVersion; + return minimumLauncherVersion == null ? 0 : minimumLauncherVersion; } public boolean isHidden() { @@ -222,7 +222,7 @@ public class Version implements Comparable, Validation { type, time, releaseTime, - Math.max(minimumLauncherVersion, parent.minimumLauncherVersion), + Lang.merge(minimumLauncherVersion, parent.minimumLauncherVersion, Math::max), hidden, Lang.merge(parent.patches, patches)); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.java index a8a206555..9b449f1b9 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.java @@ -24,6 +24,7 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BinaryOperator; import java.util.function.Consumer; import java.util.function.Function; @@ -95,6 +96,12 @@ public final class Lang { return index < 0 || index >= a.size() ? defaultValue : a.get(index); } + public static T merge(T a, T b, BinaryOperator operator) { + if (a == null) return b; + if (b == null) return a; + return operator.apply(a, b); + } + /** * Join two collections into one list. *