From 4149876e04b62dbe3f329b8b0ce0f3cfcef830de Mon Sep 17 00:00:00 2001 From: Burning_TNT <88144530+burningtnt@users.noreply.github.com> Date: Mon, 8 Jan 2024 20:35:03 +0800 Subject: [PATCH] Support Neoforge (#2548) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Support #2376 * Add necessary @Nullable annotations * Display different types of dependencies in different sections. * Fix checkstyle * Add I18N for different types of dependencies. * Enhance UI * Code cleanup * Enhance UI * Manually sort the result from curseforge when searching mods by name. * Render the search results from remote mod repositories in several pages. * Fix merge * Fix * Add a button which navigates to the modpack download page in the modpack installl page * Fix I18N * Render the mod loaders supported by the version in mod info page. * Fix #2104 * Enhance TwoLineListItem * Render the mod loader supported by this mod file on the ModListPage * Fix chinese searching and curseforge searching * Update I18N * Fix * Fix * Select the specific game version when clicking the 'download' button on ModListPage * Support HMCL to update mod_data and mod_pack data from https://github.com/huanghongxun/HMCL/raw/javafx/data-json/dynamic-remote-resources.json * Enhance :HMCL:build.gradle.kts * Revert parse_mcmod_data.py * Abstract 'new Image' to FXUtils.newBuiltinImage and FXUtils.newRemoteImage FXUtils.newBuiltinImage is used to load image which is supposed to be correct definitely and is a file within the jar. Or, it will throw ResourceNotFoundError. FXUtils.newRemoteImage is used to load image from the internet. It will cache the data of images for the further usage. The cached data will be deleted when HMCL is closed or hidden. * Add javadoc for FXUtils.newBuiltinImage and FXUtils.newRemoteImage. * Fix checkstyle * Fix * Fix * Fix * Add license for RemoteResourceManager * Remove TODO * Enhance Chinese searching * Support to decode metadata for local quilt mod. * Enhance ModManager * Fix checkstyle * Refactor * Fix * Fix * Refactor DownloadPage * Fix * Revert "Refactor DownloadPage" This reverts commit 953558da77af5a0fe3153e77cdcb9b6affa30ffa. * Refactor DownloadPage * Refactor * Fix * Fix checkstyle * Set org.jackhuang.hmcl.ui.construct.TwoLineListItem.TagChangeListener as a private static inner class. * Fix * Fix * Fix * Enhance SimpleMultimap * Revert TwoLineListItem * Fix * Code cleanup * Code cleanup * Fix * Code cleanup * Add license for IModMetadataReader * Add prefix 'Minecraft' at the supported minecrft version list in DownloadPage * Fix #2498 * Update README_cn.md * Opti ModMananger * Log a warning message when 'hmcl.update_source.override' is used. * Fix chinese searching * Enhance chinese searching. * Enhance memory usage * Add basic support for NeoForge. * Enable HMCl to show the version list of NeoForged. * Fix checkstyle * Enable HMCL to download and install NeoForged from BMCL. Fix some bugs. * Close the mod version dialog window after clicking the downloading / save as button if the dependency list is empty. * Fix * Enable HMCL to install NeoForge from modpacks. * Fix the dirty implememtation * Fix UI * Cache builtin images. * Enhance FXUtils (Make tooltip installer faster). * Fix * Fix * Fix #2560 * Fix typo * Fix remote image cache. * Fix javadoc * Fix checkstyle * Optimize FXUtils::shutdown * Support NeoForged 1.20.2 * Fix merge * I have no idea on why the sha1 was matched. * Revert "Enhance FXUtils (Make tooltip installer faster)." This reverts commit 0a49eb2c1204e4be7dc0df3084faa59fdf9b0394. * Support multi download source in order balance the traffic of hmcl.huangyuhui.net and the download speed in China Mainland. * Modify dynamic remote resource urls. * Optimize codes with StringUtils.DynamicCommonSubsequence. * Prevent unofficial HMCL to access HMCL Resource Update URL. * Zip the dynamic-remote-resources json by Gradle automatically. * Remove unnecessary getters. * Fix * Fix LibraryAnalyzer: Cannot analyze the version number of NeoForge correctly. * Support to choose the NeoForge icon if a game is installed with NeoForge. * Fix checkstyle * Fix: The version number on DownloadPage of NeoForge is incorrect, which has leading string 'neoforge-'. * Fix. * Rename references. Fix: Invalid spaces in VersionIconType. --------- Co-authored-by: Burning_TNT --- .../hmcl/game/HMCLGameRepository.java | 2 + .../jackhuang/hmcl/game/LauncherHelper.java | 4 +- .../hmcl/setting/VersionIconType.java | 1 + .../org/jackhuang/hmcl/ui/InstallerItem.java | 28 +- .../hmcl/ui/construct/TaskListPane.java | 4 + .../hmcl/ui/download/InstallersPage.java | 17 +- .../hmcl/ui/download/VersionsPage.java | 3 + .../hmcl/ui/versions/DownloadPage.java | 3 + .../hmcl/ui/versions/InstallerListPage.java | 4 +- .../hmcl/ui/versions/ModListPageSkin.java | 43 +- .../hmcl/ui/versions/VersionIconDialog.java | 1 + .../main/resources/assets/img/neoforge.png | Bin 0 -> 1202 bytes .../resources/assets/img/neoforged@2x.png | Bin 0 -> 1412 bytes .../resources/assets/lang/I18N.properties | 2 + .../resources/assets/lang/I18N_zh.properties | 2 + .../assets/lang/I18N_zh_CN.properties | 2 + .../download/BMCLAPIDownloadProvider.java | 6 + .../download/DefaultDependencyManager.java | 6 + .../hmcl/download/LibraryAnalyzer.java | 71 ++- .../jackhuang/hmcl/download/MaintainTask.java | 2 +- .../hmcl/download/MojangDownloadProvider.java | 7 +- .../download/forge/ForgeNewInstallTask.java | 2 +- .../neoforge/NeoForgeBMCLVersionList.java | 136 ++++++ .../neoforge/NeoForgeInstallTask.java | 120 +++++ .../neoforge/NeoForgeOldInstallTask.java | 424 ++++++++++++++++++ .../neoforge/NeoForgeRemoteVersion.java | 20 + .../hmcl/game/JavaVersionConstraint.java | 1 - .../org/jackhuang/hmcl/mod/ModLoaderType.java | 23 +- .../jackhuang/hmcl/mod/curse/CurseAddon.java | 1 + .../hmcl/mod/curse/CurseInstallTask.java | 8 +- .../modrinth/ModrinthRemoteModRepository.java | 2 + 31 files changed, 881 insertions(+), 64 deletions(-) create mode 100644 HMCL/src/main/resources/assets/img/neoforge.png create mode 100644 HMCL/src/main/resources/assets/img/neoforged@2x.png create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeBMCLVersionList.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeInstallTask.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeOldInstallTask.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeRemoteVersion.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java index 2609468b9b..2faa26e382 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java @@ -279,6 +279,8 @@ else if (LibraryAnalyzer.isModded(this, version)) { return newBuiltinImage("/assets/img/fabric.png"); else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.FORGE)) return newBuiltinImage("/assets/img/forge.png"); + else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.NEO_FORGE)) + return newBuiltinImage("/assets/img/neoforge.png"); else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.QUILT)) return newBuiltinImage("/assets/img/quilt.png"); else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.OPTIFINE)) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java index c547ef37ca..13989d727a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java @@ -510,9 +510,7 @@ private static Task checkGameState(Profile profile, VersionSetting break; case MODDED_JAVA_16: // Minecraft<=1.17.1+Forge[37.0.0,37.0.60) not compatible with Java 17 - String forgePatchVersion = analyzer.getVersion(LibraryAnalyzer.LibraryType.FORGE) - .map(LibraryAnalyzer.LibraryType.FORGE::patchVersion) - .orElse(null); + String forgePatchVersion = analyzer.getVersion(LibraryAnalyzer.LibraryType.FORGE).orElse(null); if (forgePatchVersion != null && VersionNumber.VERSION_COMPARATOR.compare(forgePatchVersion, "37.0.60") < 0) suggestions.add(i18n("launch.advice.forge37_0_60")); else diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionIconType.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionIconType.java index d641472f1a..1a9c418691 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionIconType.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionIconType.java @@ -27,6 +27,7 @@ public enum VersionIconType { CRAFT_TABLE("/assets/img/craft_table.png"), FABRIC("/assets/img/fabric.png"), FORGE("/assets/img/forge.png"), + NEO_FORGE("/assets/img/neoforge.png"), FURNACE("/assets/img/furnace.png"), QUILT("/assets/img/quilt.png"); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java index d5c8a11f9f..7bc6a2b82e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java @@ -92,6 +92,9 @@ public InstallerItem(String id) { case "quilt-api": imageUrl = "/assets/img/quilt.png"; break; + case "neoforge": + imageUrl = "/assets/img/neoforge.png"; + break; default: imageUrl = null; break; @@ -122,6 +125,7 @@ public static class InstallerItemGroup { public final InstallerItem fabric = new InstallerItem(FABRIC); public final InstallerItem fabricApi = new InstallerItem(FABRIC_API); public final InstallerItem forge = new InstallerItem(FORGE); + public final InstallerItem neoForge = new InstallerItem(NEO_FORGE); public final InstallerItem liteLoader = new InstallerItem(LITELOADER); public final InstallerItem optiFine = new InstallerItem(OPTIFINE); public final InstallerItem quilt = new InstallerItem(QUILT); @@ -131,30 +135,41 @@ public InstallerItemGroup() { forge.incompatibleLibraryName.bind(Bindings.createStringBinding(() -> { if (fabric.libraryVersion.get() != null) return FABRIC.getPatchId(); if (quilt.libraryVersion.get() != null) return QUILT.getPatchId(); + if (neoForge.libraryVersion.get() != null) return NEO_FORGE.getPatchId(); + return null; + }, fabric.libraryVersion, quilt.libraryVersion, neoForge.libraryVersion)); + + neoForge.incompatibleLibraryName.bind(Bindings.createStringBinding(() -> { + if (fabric.libraryVersion.get() != null) return FABRIC.getPatchId(); + if (quilt.libraryVersion.get() != null) return QUILT.getPatchId(); + if (forge.libraryVersion.get() != null) return FORGE.getPatchId(); return null; - }, fabric.libraryVersion, quilt.libraryVersion)); + }, fabric.libraryVersion, quilt.libraryVersion, forge.libraryVersion)); liteLoader.incompatibleLibraryName.bind(Bindings.createStringBinding(() -> { if (fabric.libraryVersion.get() != null) return FABRIC.getPatchId(); if (quilt.libraryVersion.get() != null) return QUILT.getPatchId(); + if (neoForge.libraryVersion.get() != null) return NEO_FORGE.getPatchId(); return null; - }, fabric.libraryVersion, quilt.libraryVersion)); + }, fabric.libraryVersion, quilt.libraryVersion, neoForge.libraryVersion)); optiFine.incompatibleLibraryName.bind(Bindings.createStringBinding(() -> { if (fabric.libraryVersion.get() != null) return FABRIC.getPatchId(); if (quilt.libraryVersion.get() != null) return QUILT.getPatchId(); + if (neoForge.libraryVersion.get() != null) return NEO_FORGE.getPatchId(); return null; - }, fabric.libraryVersion, quilt.libraryVersion)); + }, fabric.libraryVersion, quilt.libraryVersion, neoForge.libraryVersion)); for (InstallerItem fabric : new InstallerItem[]{fabric, fabricApi}) { fabric.incompatibleLibraryName.bind(Bindings.createStringBinding(() -> { if (forge.libraryVersion.get() != null) return FORGE.getPatchId(); + if (neoForge.libraryVersion.get() != null) return NEO_FORGE.getPatchId(); if (liteLoader.libraryVersion.get() != null) return LITELOADER.getPatchId(); if (optiFine.libraryVersion.get() != null) return OPTIFINE.getPatchId(); if (quilt.libraryVersion.get() != null) return QUILT.getPatchId(); if (quiltApi.libraryVersion.get() != null) return QUILT_API.getPatchId(); return null; - }, forge.libraryVersion, liteLoader.libraryVersion, optiFine.libraryVersion, quilt.libraryVersion, quiltApi.libraryVersion)); + }, forge.libraryVersion, neoForge.libraryVersion, liteLoader.libraryVersion, optiFine.libraryVersion, quilt.libraryVersion, quiltApi.libraryVersion)); } fabricApi.dependencyName.bind(Bindings.createStringBinding(() -> { @@ -167,10 +182,11 @@ public InstallerItemGroup() { if (fabric.libraryVersion.get() != null) return FABRIC.getPatchId(); if (fabricApi.libraryVersion.get() != null) return FABRIC_API.getPatchId(); if (forge.libraryVersion.get() != null) return FORGE.getPatchId(); + if (neoForge.libraryVersion.get() != null) return NEO_FORGE.getPatchId(); if (liteLoader.libraryVersion.get() != null) return LITELOADER.getPatchId(); if (optiFine.libraryVersion.get() != null) return OPTIFINE.getPatchId(); return null; - }, fabric.libraryVersion, fabricApi.libraryVersion, forge.libraryVersion, liteLoader.libraryVersion, optiFine.libraryVersion)); + }, fabric.libraryVersion, fabricApi.libraryVersion, forge.libraryVersion, neoForge.libraryVersion, liteLoader.libraryVersion, optiFine.libraryVersion)); } quiltApi.dependencyName.bind(Bindings.createStringBinding(() -> { @@ -180,7 +196,7 @@ public InstallerItemGroup() { } public InstallerItem[] getLibraries() { - return new InstallerItem[]{game, forge, liteLoader, optiFine, fabric, fabricApi, quilt, quiltApi}; + return new InstallerItem[]{game, forge, neoForge, liteLoader, optiFine, fabric, fabricApi, quilt, quiltApi}; } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java index 1d12e0f17c..e9fd304379 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java @@ -36,6 +36,7 @@ import org.jackhuang.hmcl.download.game.GameInstallTask; import org.jackhuang.hmcl.download.java.JavaDownloadTask; import org.jackhuang.hmcl.download.liteloader.LiteLoaderInstallTask; +import org.jackhuang.hmcl.download.neoforge.NeoForgeInstallTask; import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask; import org.jackhuang.hmcl.download.quilt.QuiltAPIInstallTask; import org.jackhuang.hmcl.download.quilt.QuiltInstallTask; @@ -123,6 +124,8 @@ public void onRunning(Task task) { task.setName(i18n("install.installer.install", i18n("install.installer.game"))); } else if (task instanceof ForgeNewInstallTask || task instanceof ForgeOldInstallTask) { task.setName(i18n("install.installer.install", i18n("install.installer.forge"))); + } else if (task instanceof NeoForgeInstallTask) { + task.setName(i18n("install.installer.install", i18n("install.installer.neoforge"))); } else if (task instanceof LiteLoaderInstallTask) { task.setName(i18n("install.installer.install", i18n("install.installer.liteloader"))); } else if (task instanceof OptiFineInstallTask) { @@ -248,6 +251,7 @@ public StageNode(String stage) { case "hmcl.install.assets": message = i18n("assets.download"); break; case "hmcl.install.game": message = i18n("install.installer.install", i18n("install.installer.game") + " " + stageValue); break; case "hmcl.install.forge": message = i18n("install.installer.install", i18n("install.installer.forge") + " " + stageValue); break; + case "hmcl.install.neoforge": message = i18n("install.installer.install", i18n("install.installer.neoforge") + " " + stageValue); break; case "hmcl.install.liteloader": message = i18n("install.installer.install", i18n("install.installer.liteloader") + " " + stageValue); break; case "hmcl.install.optifine": message = i18n("install.installer.install", i18n("install.installer.optifine") + " " + stageValue); break; case "hmcl.install.fabric": message = i18n("install.installer.install", i18n("install.installer.fabric") + " " + stageValue); break; 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 19235aa737..a36a2c09d0 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 @@ -23,14 +23,12 @@ import javafx.beans.property.SimpleBooleanProperty; import javafx.geometry.Insets; import javafx.geometry.Pos; -import javafx.scene.control.Control; -import javafx.scene.control.Label; -import javafx.scene.control.Skin; -import javafx.scene.control.SkinBase; +import javafx.scene.control.*; import javafx.scene.layout.BorderPane; import javafx.scene.layout.FlowPane; import javafx.scene.layout.HBox; import org.jackhuang.hmcl.download.DownloadProvider; +import org.jackhuang.hmcl.download.LibraryAnalyzer; import org.jackhuang.hmcl.download.RemoteVersion; import org.jackhuang.hmcl.game.HMCLGameRepository; import org.jackhuang.hmcl.ui.Controllers; @@ -72,9 +70,9 @@ public InstallersPage(WizardController controller, HMCLGameRepository repository for (InstallerItem library : group.getLibraries()) { String libraryId = library.getLibraryId(); - if (libraryId.equals("game")) continue; + if (libraryId.equals(LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId())) continue; library.action.set(e -> { - if ("fabric-api".equals(libraryId)) { + if (LibraryAnalyzer.LibraryType.FABRIC_API.getPatchId().equals(libraryId)) { Controllers.dialog(i18n("install.installer.fabric-api.warning"), i18n("message.warning"), MessageDialogPane.MessageType.WARNING); } @@ -155,10 +153,13 @@ protected InstallersPageSkin(InstallersPage control) { { FlowPane libraryPane = new FlowPane(control.group.getLibraries()); - BorderPane.setMargin(libraryPane, new Insets(16, 0, 16, 0)); libraryPane.setVgap(16); libraryPane.setHgap(16); - root.setCenter(libraryPane); + ScrollPane scrollPane = new ScrollPane(libraryPane); + scrollPane.setFitToWidth(true); + scrollPane.setFitToHeight(true); + BorderPane.setMargin(scrollPane, new Insets(16, 0, 16, 0)); + root.setCenter(scrollPane); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java index 9bee8d246b..e5b8311f6b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java @@ -36,6 +36,7 @@ import org.jackhuang.hmcl.download.forge.ForgeRemoteVersion; import org.jackhuang.hmcl.download.game.GameRemoteVersion; import org.jackhuang.hmcl.download.liteloader.LiteLoaderRemoteVersion; +import org.jackhuang.hmcl.download.neoforge.NeoForgeRemoteVersion; import org.jackhuang.hmcl.download.optifine.OptiFineRemoteVersion; import org.jackhuang.hmcl.download.quilt.QuiltAPIRemoteVersion; import org.jackhuang.hmcl.download.quilt.QuiltRemoteVersion; @@ -335,6 +336,8 @@ else if (remoteVersion instanceof OptiFineRemoteVersion) iconType = VersionIconType.COMMAND; else if (remoteVersion instanceof ForgeRemoteVersion) iconType = VersionIconType.FORGE; + else if (remoteVersion instanceof NeoForgeRemoteVersion) + iconType = VersionIconType.NEO_FORGE; else if (remoteVersion instanceof FabricRemoteVersion || remoteVersion instanceof FabricAPIRemoteVersion) iconType = VersionIconType.FABRIC; else if (remoteVersion instanceof QuiltRemoteVersion || remoteVersion instanceof QuiltAPIRemoteVersion) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java index d968920b9e..2a8f8a74f0 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java @@ -399,6 +399,9 @@ private static final class ModItem extends StackPane { case FORGE: content.getTags().add(i18n("install.installer.forge")); break; + case NEO_FORGED: + content.getTags().add(i18n("install.installer.neoforge")); + break; case FABRIC: content.getTags().add(i18n("install.installer.fabric")); break; 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 f3e66e86f6..b5ebc9865f 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 @@ -86,7 +86,7 @@ public void loadVersion(Profile profile, String versionId) { InstallerItem.InstallerItemGroup group = new InstallerItem.InstallerItemGroup(); - // Conventional libraries: game, fabric, forge, liteloader, optifine + // Conventional libraries: game, fabric, forge, neoforge, liteloader, optifine for (InstallerItem installerItem : group.getLibraries()) { String libraryId = installerItem.getLibraryId(); String libraryVersion = analyzer.getVersion(libraryId).orElse(null); @@ -96,7 +96,7 @@ public void loadVersion(Profile profile, String versionId) { installerItem.action.set(e -> { Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, libraryId, libraryVersion)); }); - boolean removable = !"game".equals(libraryId) && libraryVersion != null; + boolean removable = !LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId().equals(libraryId) && libraryVersion != null; installerItem.removable.set(removable); if (removable) { Runnable action = removeAction.apply(libraryId); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java index d31b5c9003..99f0d63ac2 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java @@ -335,15 +335,28 @@ class ModInfoDialog extends JFXDialogLayout { RemoteMod remoteMod = remoteModRepository.getModById(versionOptional.get().getModid()); FXUtils.runInFX(() -> { for (ModLoaderType modLoaderType : versionOptional.get().getLoaders()) { + String loaderName; switch (modLoaderType) { - case FABRIC: case FORGE: + loaderName = i18n("install.installer.forge"); + break; + case NEO_FORGED: + loaderName = i18n("install.installer.neoforge"); + break; + case FABRIC: + loaderName = i18n("install.installer.fabric"); + break; case LITE_LOADER: - case QUILT: { - if (!title.getTags().contains(modLoaderType.getLoaderName())) { - title.getTags().add(modLoaderType.getLoaderName()); - } - } + loaderName = i18n("install.installer.liteloader"); + break; + case QUILT: + loaderName = i18n("install.installer.quilt"); + break; + default: + continue; + } + if (!title.getTags().contains(loaderName)) { + title.getTags().add(loaderName); } } @@ -457,7 +470,23 @@ protected void updateControl(ModInfoObject dataItem, boolean empty) { if (empty) return; content.setTitle(dataItem.getTitle()); content.getTags().clear(); - content.getTags().add(dataItem.getModInfo().getModLoaderType().getLoaderName()); + switch (dataItem.getModInfo().getModLoaderType()) { + case FORGE: + content.getTags().add(i18n("install.installer.forge")); + break; + case NEO_FORGED: + content.getTags().add(i18n("install.installer.neoforge")); + break; + case FABRIC: + content.getTags().add(i18n("install.installer.fabric")); + break; + case LITE_LOADER: + content.getTags().add(i18n("install.installer.liteloader")); + break; + case QUILT: + content.getTags().add(i18n("install.installer.quilt")); + break; + } if (dataItem.getMod() != null) { if (I18n.getCurrentLocale().getLocale() == Locale.CHINA) { content.getTags().add(dataItem.getMod().getDisplayName()); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java index 01f26789c0..cde2d27f25 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java @@ -65,6 +65,7 @@ public VersionIconDialog(Profile profile, String versionId, Runnable onFinish) { createIcon(VersionIconType.CRAFT_TABLE), createIcon(VersionIconType.FABRIC), createIcon(VersionIconType.FORGE), + createIcon(VersionIconType.NEO_FORGE), createIcon(VersionIconType.FURNACE), createIcon(VersionIconType.QUILT) ); diff --git a/HMCL/src/main/resources/assets/img/neoforge.png b/HMCL/src/main/resources/assets/img/neoforge.png new file mode 100644 index 0000000000000000000000000000000000000000..95c3b5cf69190900f7ad27bd0711121f0d79e2d5 GIT binary patch literal 1202 zcmV;j1Wo&iP)Px#1am@3R0s$N2z&@+hyVZtTuDShR9J=0mrHC^RT#&A=gysWrc6775-<^IVSzy< zZ9=iFjj>(G0?Mmu6G9gqr8Rbg>0pVsY+eMc{PA#P8$xVDG6Kv_AP@++jH04t04F|p zUj>=BK}ng{``y4md9Lql_YrS&GLoN>5UQ20B$Ia<8yn-$mC_mI%xBI0^;;<6(0;@7|D@O(#J&pRN3RcPFzrR;@8@ z%r*iQx`t59b^kv6FG!X^X4*>~24yAF^bt+qPNrrMu!Q?;rdArZRbOCBkZU{2{xTnK z_YuF{2f(`3<_L>DDlU(`G_L6+v?Kt*i|+!k23TY6NX8}{dHXD^8Sr>?6lt!J$DMv) z(0sL}ZWGrKcJ0^*_*_6H!Bb*a`Me0^sV$3T8c)-%3UFayph^|O%eNm_GWyE{H=dKN zc&75=r4OYpWq3n<{g40%_^SgzgU6lDvR~CT06;8qpV(vwfKYcUU>9LH7@yt(U;A+Y z+@(I;Ws3p$y{jGoEvlf!902`|J=Yo+h6q!;4(}0);?vF_snTu@9|TyHN$5Fonvc4B z0qEOk`VyK>LemR^zO=m!fVbM)=xp}`5S(zD!R2fA=o>qSq+x{UsW%C1sIMOa2((pC zJDwhbjSdgJTvyj`y{Z78ZChU@(s)&;QR#A;8i+;io4;Cw=#+I2a08G`36d#0(;}y& z$Z1O4%8zN0ERMA`<0+-`^Udp}bP(Qo`s$DhAk)f0DQHOQ21~pd0vX}Ov@o%#HiJM$ zNLllc&JwWlV_Gu>d@gQy1ArStQ9k`SFfH~Rn=$E&oeF`f?E#%J1+vn zL!lt&2J;i}`~7iX1mO6Vn!?}O`IWqSoW&uDJe(U7QiKY%nb(*9_?DVKfQnhtMq1BYUO89HM=rvD0H77Iz<0;D Q@&Et;07*qoM6N<$g5CQrf&c&j literal 0 HcmV?d00001 diff --git a/HMCL/src/main/resources/assets/img/neoforged@2x.png b/HMCL/src/main/resources/assets/img/neoforged@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..7d46d392e44d7b7007041bb213ddc4540925b33f GIT binary patch literal 1412 zcmV-~1$+95P)Px#1am@3R0s$N2z&@+hyVZuE=fc|RCt{2T3c)sRT%zfW_P>W?sna5tu0kc14R=h z7BvwRo89o@CAAdff#O4fG#ZUD1_`Dw$V&r>Au&dyA<}(NE~3~%6g4TkK`LmJG|>kO zkuGR2nl81w)7i_+p78;@nKS3?>`bj5&CK_-^X-4m|IfE`zCD-WUxg*X2L@LE~ zz0%k6__}pTqnI-nE-e4#_5SfneW?J6Lm!`xny-=${_l41e}^Lg>jGHAWwk*(9&bP% zPnDMY@Zf&0x^y*}0uSIF9vNxIxI!TxjYg-f^kZ3o^Iv{;^q0#&MYP;qcQz4mW7j9F zc5Mhsjxv8 z228x?!3feFaQcGPoo+ePrsZl|+aTDAQIJjlL3EtEbdunL4WuLGL~_EEr*Pi;HxZn# z_7?1V?#d{nuZ&`x><)}(=cFMm#{(pyvF{Q98WThS5uSs++CaCQAPK;A0 z$irNk05s5UmIm(wqyV9&79H~*povQuBTAXJJSSkk7`o|;2>@pZI0D#SfRYFB012d< zGsx~hrzqhjSR<2~4ICRJxGqXEn&FuREyu21FJspQnFmqA>yzDqQ6!NjxbV3oz+bMI z&<4hLZ)(n`B}{;jOrE90j#C79f}l(r9u2e{qyQX)PAYvGV24nWhjRv$fjp8PMXK}=5E4AFnWV32inSymP20$ha_Uz zoXLsnkM(XI&~gIL4Q_e&qagr|0J*8_YdGL~w1H|^0e=`ThDTY*X7H(RRuU<)N@SSCoBPPCEDL z(ag_3tX)j9AJ2cWm}UR~Fp;Xfk`z{~z9_UmxmC-lE|&<0$?dcS7z)tc(J=!6*qhcz zUh2t}31huB4SVwjTnH0IUmO4a+k@*CSiD79)j<@4KDYw&5#K;r7Lqr2_}Q z3fo_Ov{INLdvfqb%wpI75Z-_4p>Y6MzC_(6pE>o7{wR1!VEZB92*A1k*7y^FE3H!r SKl*(D0000 getVersionListById(String id) { return fabricApi; case "forge": return forge; + case "neoforge": + return neoforge; case "liteloader": return liteLoader; case "optifine": @@ -102,6 +107,7 @@ public String injectURL(String baseURL) { .replace("https://libraries.minecraft.net", apiRoot + "/libraries") .replaceFirst("https?://files\\.minecraftforge\\.net/maven", apiRoot + "/maven") .replace("https://maven.minecraftforge.net", apiRoot + "/maven") + .replace("https://maven.neoforged.net/releases/net/neoforged/forge", apiRoot + "/maven/net/neoforged/forge") .replace("http://dl.liteloader.com/versions/versions.json", apiRoot + "/maven/com/mumfrey/liteloader/versions.json") .replace("http://dl.liteloader.com/versions", apiRoot + "/maven") .replace("https://meta.fabricmc.net", apiRoot + "/fabric-meta") 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 a492c66c26..559f6b70fd 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java @@ -21,6 +21,7 @@ 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.neoforge.NeoForgeInstallTask; import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask; import org.jackhuang.hmcl.game.Artifact; import org.jackhuang.hmcl.game.DefaultGameRepository; @@ -179,6 +180,11 @@ public Task installLibraryAsync(Version oldVersion, Path installer) { return Task .composeAsync(() -> { + try { + return NeoForgeInstallTask.install(this, oldVersion, installer); + } catch (IOException ignore) { + } + try { return ForgeInstallTask.install(this, oldVersion, installer); } catch (IOException ignore) { 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 e1d78f0f2e..1d664484a1 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java @@ -17,9 +17,7 @@ */ package org.jackhuang.hmcl.download; -import org.jackhuang.hmcl.game.Library; -import org.jackhuang.hmcl.game.Version; -import org.jackhuang.hmcl.game.VersionProvider; +import org.jackhuang.hmcl.game.*; import org.jackhuang.hmcl.mod.ModLoaderType; import org.jackhuang.hmcl.util.Pair; import org.jetbrains.annotations.NotNull; @@ -113,7 +111,8 @@ private Version removingMatchedLibrary(Version version, String libraryId) { /** * Remove library by library id - * @param libraryId patch id or "forge"/"optifine"/"liteloader"/"fabric"/"quilt" + * + * @param libraryId patch id or "forge"/"optifine"/"liteloader"/"fabric"/"quilt"/"neoforge" * @return this */ public LibraryAnalyzer removeLibrary(String libraryId) { @@ -139,7 +138,7 @@ public static LibraryAnalyzer analyze(Version version) { for (Library library : version.resolve(null).getLibraries()) { for (LibraryType type : LibraryType.values()) { if (type.matchLibrary(library)) { - libraries.put(type.getPatchId(), pair(library, type.patchVersion(library.getVersion()))); + libraries.put(type.getPatchId(), pair(library, type.patchVersion(version, library.getVersion()))); break; } } @@ -179,14 +178,56 @@ public enum LibraryType { private final Pattern FORGE_VERSION_MATCHER = Pattern.compile("^([0-9.]+)-(?[0-9.]+)(-([0-9.]+))?$"); @Override - public String patchVersion(String libraryVersion) { + public String patchVersion(Version gameVersion, String libraryVersion) { Matcher matcher = FORGE_VERSION_MATCHER.matcher(libraryVersion); if (matcher.find()) { return matcher.group("forge"); } - return super.patchVersion(libraryVersion); + return super.patchVersion(gameVersion, libraryVersion); } }, + NEO_FORGE(true, "neoforge", Pattern.compile("net\\.neoforged\\.fancymodloader"), Pattern.compile("(core|loader)"), ModLoaderType.NEO_FORGED) { + @Override + public String patchVersion(Version gameVersion, String libraryVersion) { + String res = scanVersion(gameVersion); + if (res != null) { + return res; + } + + for (Version patch : gameVersion.getPatches()) { + res = scanVersion(patch); + if (res != null) { + return res; + } + } + + return super.patchVersion(gameVersion, libraryVersion); + } + + private String scanVersion(Version version) { + Optional optArgument = version.getArguments(); + if (!optArgument.isPresent()) { + return null; + } + List gameArguments = optArgument.get().getGame(); + if (gameArguments == null) { + return null; + } + + for (int i = 0; i < gameArguments.size() - 1; i++) { + Argument argument = gameArguments.get(i); + if (argument instanceof StringArgument && "--fml.neoForgeVersion".equals(((StringArgument) argument).getArgument())) { + Argument next = gameArguments.get(i + 1); + if (next instanceof StringArgument) { + return ((StringArgument) next).getArgument(); + } + return null; // Normally, there should not be two --fml.neoForgeVersion argument. + } + } + return null; + } + + }, LITELOADER(true, "liteloader", Pattern.compile("com\\.mumfrey"), Pattern.compile("liteloader"), ModLoaderType.LITE_LOADER), OPTIFINE(false, "optifine", Pattern.compile("(net\\.)?optifine"), Pattern.compile("^(?!.*launchwrapper).*$"), null), QUILT(true, "quilt", Pattern.compile("org\\.quiltmc"), Pattern.compile("quilt-loader"), ModLoaderType.QUILT), @@ -229,7 +270,7 @@ public boolean matchLibrary(Library library) { return group.matcher(library.getGroupId()).matches() && artifact.matcher(library.getArtifactId()).matches(); } - public String patchVersion(String libraryVersion) { + public String patchVersion(Version gameVersion, String libraryVersion) { return libraryVersion; } } @@ -259,14 +300,14 @@ public String getLibraryVersion() { public static final String MOD_LAUNCHER_MAIN = "cpw.mods.modlauncher.Launcher"; public static final String BOOTSTRAP_LAUNCHER_MAIN = "cpw.mods.bootstraplauncher.BootstrapLauncher"; - public static final String[] FORGE_TWEAKERS = new String[] { - "net.minecraftforge.legacy._1_5_2.LibraryFixerTweaker", // 1.5.2 - "cpw.mods.fml.common.launcher.FMLTweaker", // 1.6.1 ~ 1.7.10 - "net.minecraftforge.fml.common.launcher.FMLTweaker" // 1.8 ~ 1.12.2 + public static final String[] FORGE_TWEAKERS = new String[]{ + "net.minecraftforge.legacy._1_5_2.LibraryFixerTweaker", // 1.5.2 + "cpw.mods.fml.common.launcher.FMLTweaker", // 1.6.1 ~ 1.7.10 + "net.minecraftforge.fml.common.launcher.FMLTweaker" // 1.8 ~ 1.12.2 }; - public static final String[] OPTIFINE_TWEAKERS = new String[] { - "optifine.OptiFineTweaker", - "optifine.OptiFineForgeTweaker" + public static final String[] OPTIFINE_TWEAKERS = new String[]{ + "optifine.OptiFineTweaker", + "optifine.OptiFineForgeTweaker" }; public static final String LITELOADER_TWEAKER = "com.mumfrey.liteloader.launch.LiteLoaderTweaker"; } 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 e8fe8a1e99..ced364ea55 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MaintainTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MaintainTask.java @@ -208,7 +208,7 @@ private static Version maintainGameWithCpwBoostrapLauncher(GameRepository reposi LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version); VersionLibraryBuilder builder = new VersionLibraryBuilder(version); - if (!libraryAnalyzer.has(FORGE)) return version; + if (!libraryAnalyzer.has(FORGE) && !libraryAnalyzer.has(NEO_FORGE)) return version; Optional bslVersion = libraryAnalyzer.getVersion(BOOTSTRAP_LAUNCHER); 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 34736b4c8d..33d11327e8 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MojangDownloadProvider.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MojangDownloadProvider.java @@ -22,19 +22,21 @@ import org.jackhuang.hmcl.download.forge.ForgeBMCLVersionList; import org.jackhuang.hmcl.download.game.GameVersionList; import org.jackhuang.hmcl.download.liteloader.LiteLoaderVersionList; +import org.jackhuang.hmcl.download.neoforge.NeoForgeBMCLVersionList; import org.jackhuang.hmcl.download.optifine.OptiFineBMCLVersionList; import org.jackhuang.hmcl.download.quilt.QuiltAPIVersionList; import org.jackhuang.hmcl.download.quilt.QuiltVersionList; /** - * @see http://wiki.vg * @author huangyuhui + * @see http://wiki.vg */ public class MojangDownloadProvider implements DownloadProvider { private final GameVersionList game; private final FabricVersionList fabric; private final FabricAPIVersionList fabricApi; private final ForgeBMCLVersionList forge; + private final NeoForgeBMCLVersionList neoforge; private final LiteLoaderVersionList liteLoader; private final OptiFineBMCLVersionList optifine; private final QuiltVersionList quilt; @@ -47,6 +49,7 @@ public MojangDownloadProvider() { this.fabric = new FabricVersionList(this); this.fabricApi = new FabricAPIVersionList(this); this.forge = new ForgeBMCLVersionList(apiRoot); + this.neoforge = new NeoForgeBMCLVersionList(apiRoot); this.liteLoader = new LiteLoaderVersionList(this); this.optifine = new OptiFineBMCLVersionList(apiRoot); this.quilt = new QuiltVersionList(this); @@ -74,6 +77,8 @@ public VersionList getVersionListById(String id) { return fabricApi; case "forge": return forge; + case "neoforge": + return neoforge; case "liteloader": return liteLoader; case "optifine": 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 7fc1584922..8c96ba321e 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 @@ -192,7 +192,7 @@ public void execute() throws Exception { private Path tempDir; private AtomicInteger processorDoneCount = new AtomicInteger(0); - ForgeNewInstallTask(DefaultDependencyManager dependencyManager, Version version, String selfVersion, Path installer) { + public ForgeNewInstallTask(DefaultDependencyManager dependencyManager, Version version, String selfVersion, Path installer) { this.dependencyManager = dependencyManager; this.gameRepository = dependencyManager.getGameRepository(); this.version = version; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeBMCLVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeBMCLVersionList.java new file mode 100644 index 0000000000..cf4cee35b8 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeBMCLVersionList.java @@ -0,0 +1,136 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2021 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.neoforge; + +import com.google.gson.JsonParseException; +import com.google.gson.annotations.SerializedName; +import com.google.gson.reflect.TypeToken; +import org.jackhuang.hmcl.download.VersionList; +import org.jackhuang.hmcl.util.Immutable; +import org.jackhuang.hmcl.util.Lang; +import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.gson.Validation; +import org.jackhuang.hmcl.util.io.HttpRequest; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +import static org.jackhuang.hmcl.util.Lang.wrap; + +public final class NeoForgeBMCLVersionList extends VersionList { + private final String apiRoot; + + /** + * @param apiRoot API Root of BMCLAPI implementations + */ + public NeoForgeBMCLVersionList(String apiRoot) { + this.apiRoot = apiRoot; + } + + @Override + public boolean hasType() { + return false; + } + + @Override + public CompletableFuture loadAsync() { + throw new UnsupportedOperationException("NeoForgeBMCLVersionList does not support loading the entire NeoForge remote version list."); + } + + @Override + public CompletableFuture refreshAsync() { + throw new UnsupportedOperationException("NeoForgeBMCLVersionList does not support loading the entire NeoForge remote version list."); + } + + @Override + public CompletableFuture refreshAsync(String gameVersion) { + return CompletableFuture.completedFuture((Void) null) + .thenApplyAsync(wrap(unused -> HttpRequest.GET(apiRoot + "/neoforge/list/" + gameVersion).>getJson(new TypeToken>() { + }.getType()))) + .thenAcceptAsync(neoForgeVersions -> { + lock.writeLock().lock(); + + try { + versions.clear(gameVersion); + for (NeoForgeVersion neoForgeVersion : neoForgeVersions) { + String nf = StringUtils.removePrefix( + neoForgeVersion.version, + "1.20.1".equals(gameVersion) ? "1.20.1-forge-" : "neoforge-" // Som of the version numbers for 1.20.1 are like forge. + ); + versions.put(gameVersion, new NeoForgeRemoteVersion( + neoForgeVersion.mcVersion, + nf, + Lang.immutableListOf( + apiRoot + "/neoforge/version/" + neoForgeVersion.version + "/download/installer.jar" + ) + )); + } + } finally { + lock.writeLock().unlock(); + } + }); + } + + @Override + public Optional getVersion(String gameVersion, String remoteVersion) { + remoteVersion = StringUtils.substringAfter(remoteVersion, "-", remoteVersion); + return super.getVersion(gameVersion, remoteVersion); + } + + @Immutable + private static final class NeoForgeVersion implements Validation { + private final String rawVersion; + + private final String version; + + @SerializedName("mcversion") + private final String mcVersion; + + public NeoForgeVersion(String rawVersion, String version, String mcVersion) { + this.rawVersion = rawVersion; + this.version = version; + this.mcVersion = mcVersion; + } + + public String getRawVersion() { + return this.rawVersion; + } + + public String getVersion() { + return this.version; + } + + public String getMcVersion() { + return this.mcVersion; + } + + @Override + public void validate() throws JsonParseException { + if (this.rawVersion == null) { + throw new JsonParseException("NeoForgeVersion rawVersion cannot be null."); + } + if (this.version == null) { + throw new JsonParseException("NeoForgeVersion version cannot be null."); + } + if (this.mcVersion == null) { + throw new JsonParseException("NeoForgeVersion mcversion cannot be null."); + } + } + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeInstallTask.java new file mode 100644 index 0000000000..58305192b5 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeInstallTask.java @@ -0,0 +1,120 @@ +package org.jackhuang.hmcl.download.neoforge; + +import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.download.LibraryAnalyzer; +import org.jackhuang.hmcl.download.VersionMismatchException; +import org.jackhuang.hmcl.download.forge.*; +import org.jackhuang.hmcl.game.Version; +import org.jackhuang.hmcl.task.FileDownloadTask; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.gson.JsonUtils; +import org.jackhuang.hmcl.util.io.CompressingUtils; +import org.jackhuang.hmcl.util.io.FileUtils; + +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; + +import static org.jackhuang.hmcl.util.StringUtils.removePrefix; +import static org.jackhuang.hmcl.util.StringUtils.removeSuffix; + +public final class NeoForgeInstallTask extends Task { + private final DefaultDependencyManager dependencyManager; + + private final Version version; + + private final NeoForgeRemoteVersion remoteVersion; + + private Path installer = null; + + private FileDownloadTask dependent; + + private Task dependency; + + public NeoForgeInstallTask(DefaultDependencyManager dependencyManager, Version version, NeoForgeRemoteVersion remoteVersion) { + this.dependencyManager = dependencyManager; + this.version = version; + this.remoteVersion = remoteVersion; + } + + @Override + public boolean doPreExecute() { + return true; + } + + @Override + public void preExecute() throws Exception { + installer = Files.createTempFile("neoforge-installer", ".jar"); + + dependent = new FileDownloadTask( + dependencyManager.getDownloadProvider().injectURLsWithCandidates(remoteVersion.getUrls()), + installer.toFile(), null + ); + dependent.setCacheRepository(dependencyManager.getCacheRepository()); + dependent.setCaching(true); + dependent.addIntegrityCheckHandler(FileDownloadTask.ZIP_INTEGRITY_CHECK_HANDLER); + } + + @Override + public boolean doPostExecute() { + return true; + } + + @Override + public void postExecute() throws Exception { + Files.deleteIfExists(installer); + this.setResult(dependency.getResult()); + } + + @Override + public Collection> getDependents() { + return Collections.singleton(dependent); + } + + @Override + public Collection> getDependencies() { + return Collections.singleton(dependency); + } + + @Override + public void execute() throws Exception { + dependency = install(dependencyManager, version, installer); + } + + public static Task install(DefaultDependencyManager dependencyManager, Version version, Path installer) throws IOException, VersionMismatchException { + Optional gameVersion = dependencyManager.getGameRepository().getGameVersion(version); + if (!gameVersion.isPresent()) throw new IOException(); + try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) { + String installProfileText = FileUtils.readText(fs.getPath("install_profile.json")); + Map installProfile = JsonUtils.fromNonNullJson(installProfileText, Map.class); + if (LibraryAnalyzer.LibraryType.FORGE.getPatchId().equals(installProfile.get("profile")) && Files.exists(fs.getPath("META-INF/NEOFORGE.RSA"))) { + ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class); + if (!gameVersion.get().equals(profile.getMinecraft())) + throw new VersionMismatchException(profile.getMinecraft(), gameVersion.get()); + return new ForgeNewInstallTask(dependencyManager, version, modifyNeoForgeOldVersion(gameVersion.get(), profile.getVersion()), installer).thenApplyAsync(neoForgeVersion -> { + if (!neoForgeVersion.getId().equals(LibraryAnalyzer.LibraryType.FORGE.getPatchId()) || neoForgeVersion.getVersion() == null) { + throw new IOException("Invalid neoforge version."); + } + return neoForgeVersion.setId(LibraryAnalyzer.LibraryType.NEO_FORGE.getPatchId()).setVersion(neoForgeVersion.getVersion().replace(LibraryAnalyzer.LibraryType.FORGE.getPatchId(), LibraryAnalyzer.LibraryType.NEO_FORGE.getPatchId())); + }); + } else if (LibraryAnalyzer.LibraryType.NEO_FORGE.getPatchId().equals(installProfile.get("profile"))) { + ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class); + if (!gameVersion.get().equals(profile.getMinecraft())) + throw new VersionMismatchException(profile.getMinecraft(), gameVersion.get()); + return new NeoForgeOldInstallTask(dependencyManager, version, modifyNeoForgeNewVersion(profile.getVersion()), installer); + } else { + throw new IOException(); + } + } + } + + private static String modifyNeoForgeOldVersion(String gameVersion, String version) { + return removeSuffix(removePrefix(removeSuffix(removePrefix(version.replace(gameVersion, "").trim(), "-"), "-"), "_"), "_"); + } + + private static String modifyNeoForgeNewVersion(String version) { + return removePrefix(version.replace("neoforge", ""), "-"); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeOldInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeOldInstallTask.java new file mode 100644 index 0000000000..3de56d9dfa --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeOldInstallTask.java @@ -0,0 +1,424 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2021 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.neoforge; + +import org.jackhuang.hmcl.download.ArtifactMalformedException; +import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.download.LibraryAnalyzer; +import org.jackhuang.hmcl.download.forge.ForgeNewInstallProfile; +import org.jackhuang.hmcl.download.forge.ForgeNewInstallProfile.Processor; +import org.jackhuang.hmcl.download.game.GameLibrariesTask; +import org.jackhuang.hmcl.download.game.VersionJsonDownloadTask; +import org.jackhuang.hmcl.game.*; +import org.jackhuang.hmcl.task.FileDownloadTask; +import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.DigestUtils; +import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.function.ExceptionalFunction; +import org.jackhuang.hmcl.util.gson.JsonUtils; +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.util.platform.OperatingSystem; +import org.jackhuang.hmcl.util.platform.SystemUtils; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.jar.Attributes; +import java.util.jar.JarFile; +import java.util.zip.ZipException; + +import static org.jackhuang.hmcl.util.Logging.LOG; +import static org.jackhuang.hmcl.util.gson.JsonUtils.fromNonNullJson; + +public class NeoForgeOldInstallTask extends Task { + + private class ProcessorTask extends Task { + + private final Processor processor; + private final Map vars; + + public ProcessorTask(@NotNull Processor processor, @NotNull Map vars) { + this.processor = processor; + this.vars = vars; + setSignificance(TaskSignificance.MODERATE); + } + + @Override + public void execute() throws Exception { + Map outputs = new HashMap<>(); + boolean miss = false; + + for (Map.Entry entry : processor.getOutputs().entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + + key = parseLiteral(key, vars); + value = parseLiteral(value, vars); + + if (key == null || value == null) { + throw new ArtifactMalformedException("Invalid forge installation configuration"); + } + + outputs.put(key, value); + + Path artifact = Paths.get(key); + if (Files.exists(artifact)) { + String code; + try (InputStream stream = Files.newInputStream(artifact)) { + code = (DigestUtils.digestToString("SHA-1", stream)); + } + + if (!Objects.equals(code, value)) { + Files.delete(artifact); + LOG.info("Found existing file is not valid: " + artifact); + + miss = true; + } + } else { + miss = true; + } + } + + if (!processor.getOutputs().isEmpty() && !miss) { + return; + } + + Path jar = gameRepository.getArtifactFile(version, processor.getJar()); + if (!Files.isRegularFile(jar)) + throw new FileNotFoundException("Game processor file not found, should be downloaded in preprocess"); + + String mainClass; + try (JarFile jarFile = new JarFile(jar.toFile())) { + mainClass = jarFile.getManifest().getMainAttributes().getValue(Attributes.Name.MAIN_CLASS); + } + + if (StringUtils.isBlank(mainClass)) + throw new Exception("Game processor jar does not have main class " + jar); + + List command = new ArrayList<>(); + command.add(JavaVersion.fromCurrentEnvironment().getBinary().toString()); + command.add("-cp"); + + List classpath = new ArrayList<>(processor.getClasspath().size() + 1); + for (Artifact artifact : processor.getClasspath()) { + Path file = gameRepository.getArtifactFile(version, artifact); + if (!Files.isRegularFile(file)) + throw new Exception("Game processor dependency missing"); + classpath.add(file.toString()); + } + classpath.add(jar.toString()); + command.add(String.join(OperatingSystem.PATH_SEPARATOR, classpath)); + + command.add(mainClass); + + List args = new ArrayList<>(processor.getArgs().size()); + for (String arg : processor.getArgs()) { + String parsed = parseLiteral(arg, vars); + if (parsed == null) + throw new ArtifactMalformedException("Invalid forge installation configuration"); + args.add(parsed); + } + + command.addAll(args); + + LOG.info("Executing external processor " + processor.getJar().toString() + ", command line: " + new CommandBuilder().addAll(command).toString()); + int exitCode = SystemUtils.callExternalProcess(command); + if (exitCode != 0) + throw new IOException("Game processor exited abnormally with code " + exitCode); + + for (Map.Entry entry : outputs.entrySet()) { + Path artifact = Paths.get(entry.getKey()); + if (!Files.isRegularFile(artifact)) + throw new FileNotFoundException("File missing: " + artifact); + + String code; + try (InputStream stream = Files.newInputStream(artifact)) { + code = DigestUtils.digestToString("SHA-1", stream); + } + + if (!Objects.equals(code, entry.getValue())) { + Files.delete(artifact); + throw new ChecksumMismatchException("SHA-1", entry.getValue(), code); + } + } + } + } + + private final DefaultDependencyManager dependencyManager; + private final DefaultGameRepository gameRepository; + private final Version version; + private final Path installer; + private final List> dependents = new ArrayList<>(1); + private final List> dependencies = new ArrayList<>(1); + + private ForgeNewInstallProfile profile; + private List processors; + private Version neoForgeVersion; + private final String selfVersion; + + private Path tempDir; + private AtomicInteger processorDoneCount = new AtomicInteger(0); + + NeoForgeOldInstallTask(DefaultDependencyManager dependencyManager, Version version, String selfVersion, Path installer) { + this.dependencyManager = dependencyManager; + this.gameRepository = dependencyManager.getGameRepository(); + this.version = version; + this.installer = installer; + this.selfVersion = selfVersion; + + setSignificance(TaskSignificance.MAJOR); + } + + private static String replaceTokens(Map tokens, String value) { + StringBuilder buf = new StringBuilder(); + for (int x = 0; x < value.length(); x++) { + char c = value.charAt(x); + if (c == '\\') { + if (x == value.length() - 1) + throw new IllegalArgumentException("Illegal pattern (Bad escape): " + value); + buf.append(value.charAt(++x)); + } else if (c == '{' || c == '\'') { + StringBuilder key = new StringBuilder(); + for (int y = x + 1; y <= value.length(); y++) { + if (y == value.length()) + throw new IllegalArgumentException("Illegal pattern (Unclosed " + c + "): " + value); + char d = value.charAt(y); + if (d == '\\') { + if (y == value.length() - 1) + throw new IllegalArgumentException("Illegal pattern (Bad escape): " + value); + key.append(value.charAt(++y)); + } else { + if (c == '{' && d == '}') { + x = y; + break; + } + if (c == '\'' && d == '\'') { + x = y; + break; + } + key.append(d); + } + } + if (c == '\'') { + buf.append(key); + } else { + if (!tokens.containsKey(key.toString())) + throw new IllegalArgumentException("Illegal pattern: " + value + " Missing Key: " + key); + buf.append(tokens.get(key.toString())); + } + } else { + buf.append(c); + } + } + return buf.toString(); + } + + private String parseLiteral(String literal, Map var, ExceptionalFunction plainConverter) throws E { + if (StringUtils.isSurrounded(literal, "{", "}")) + return var.get(StringUtils.removeSurrounding(literal, "{", "}")); + else if (StringUtils.isSurrounded(literal, "'", "'")) + return StringUtils.removeSurrounding(literal, "'"); + else if (StringUtils.isSurrounded(literal, "[", "]")) + return gameRepository.getArtifactFile(version, Artifact.fromDescriptor(StringUtils.removeSurrounding(literal, "[", "]"))).toString(); + else + return plainConverter.apply(replaceTokens(var, literal)); + } + + private String parseLiteral(String literal, Map var) { + return parseLiteral(literal, var, ExceptionalFunction.identity()); + } + + @Override + public Collection> getDependents() { + return dependents; + } + + @Override + public Collection> getDependencies() { + return dependencies; + } + + @Override + public boolean doPreExecute() { + return true; + } + + @Override + public void preExecute() throws Exception { + try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) { + profile = JsonUtils.fromNonNullJson(FileUtils.readText(fs.getPath("install_profile.json")), ForgeNewInstallProfile.class); + processors = profile.getProcessors(); + neoForgeVersion = JsonUtils.fromNonNullJson(FileUtils.readText(fs.getPath(profile.getJson())), Version.class); + + for (Library library : profile.getLibraries()) { + Path file = fs.getPath("maven").resolve(library.getPath()); + if (Files.exists(file)) { + Path dest = gameRepository.getLibraryFile(version, library).toPath(); + FileUtils.copyFile(file, dest); + } + } + + if (profile.getPath().isPresent()) { + Path mainJar = profile.getPath().get().getPath(fs.getPath("maven")); + if (Files.exists(mainJar)) { + Path dest = gameRepository.getArtifactFile(version, profile.getPath().get()); + FileUtils.copyFile(mainJar, dest); + } + } + } catch (ZipException ex) { + throw new ArtifactMalformedException("Malformed forge installer file", ex); + } + + dependents.add(new GameLibrariesTask(dependencyManager, version, true, profile.getLibraries())); + } + + private Map parseOptions(List args, Map vars) { + Map options = new LinkedHashMap<>(); + String optionName = null; + for (String arg : args) { + if (arg.startsWith("--")) { + if (optionName != null) { + options.put(optionName, ""); + } + optionName = arg.substring(2); + } else { + if (optionName == null) { + // ignore + } else { + options.put(optionName, parseLiteral(arg, vars)); + optionName = null; + } + } + } + if (optionName != null) { + options.put(optionName, ""); + } + return options; + } + + private Task patchDownloadMojangMappingsTask(Processor processor, Map vars) { + Map options = parseOptions(processor.getArgs(), vars); + if (!"DOWNLOAD_MOJMAPS".equals(options.get("task")) || !"client".equals(options.get("side"))) + return null; + String version = options.get("version"); + String output = options.get("output"); + if (version == null || output == null) + return null; + + LOG.info("Patching DOWNLOAD_MOJMAPS task"); + return new VersionJsonDownloadTask(version, dependencyManager) + .thenComposeAsync(json -> { + DownloadInfo mappings = fromNonNullJson(json, Version.class) + .getDownloads().get(DownloadType.CLIENT_MAPPINGS); + if (mappings == null) { + throw new Exception("client_mappings download info not found"); + } + + List mappingsUrl = dependencyManager.getDownloadProvider() + .injectURLWithCandidates(mappings.getUrl()); + FileDownloadTask mappingsTask = new FileDownloadTask( + mappingsUrl, + new File(output), + IntegrityCheck.of("SHA-1", mappings.getSha1())); + mappingsTask.setCaching(true); + mappingsTask.setCacheRepository(dependencyManager.getCacheRepository()); + return mappingsTask; + }); + } + + private Task createProcessorTask(Processor processor, Map vars) { + Task task = patchDownloadMojangMappingsTask(processor, vars); + if (task == null) { + task = new ProcessorTask(processor, vars); + } + task.onDone().register( + () -> updateProgress(processorDoneCount.incrementAndGet(), processors.size())); + return task; + } + + @Override + public void execute() throws Exception { + tempDir = Files.createTempDirectory("neoforge_installer"); + + Map vars = new HashMap<>(); + + try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) { + for (Map.Entry entry : profile.getData().entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + + vars.put(key, parseLiteral(value, + Collections.emptyMap(), + str -> { + Path dest = Files.createTempFile(tempDir, null, null); + FileUtils.copyFile(fs.getPath(str), dest); + return dest.toString(); + })); + } + } catch (ZipException ex) { + throw new ArtifactMalformedException("Malformed neoforge installer file", ex); + } + + vars.put("SIDE", "client"); + vars.put("MINECRAFT_JAR", gameRepository.getVersionJar(version).getAbsolutePath()); + vars.put("MINECRAFT_VERSION", gameRepository.getVersionJar(version).getAbsolutePath()); + vars.put("ROOT", gameRepository.getBaseDirectory().getAbsolutePath()); + vars.put("INSTALLER", installer.toAbsolutePath().toString()); + vars.put("LIBRARY_DIR", gameRepository.getLibrariesDirectory(version).getAbsolutePath()); + + updateProgress(0, processors.size()); + + Task processorsTask = Task.runSequentially( + processors.stream() + .map(processor -> createProcessorTask(processor, vars)) + .toArray(Task[]::new)); + + dependencies.add( + processorsTask.thenComposeAsync( + dependencyManager.checkLibraryCompletionAsync(neoForgeVersion, true))); + + setResult(neoForgeVersion + .setPriority(30000) + .setId(LibraryAnalyzer.LibraryType.NEO_FORGE.getPatchId()) + .setVersion(selfVersion)); + } + + @Override + public boolean doPostExecute() { + return true; + } + + @Override + public void postExecute() throws Exception { + FileUtils.deleteDirectory(tempDir.toFile()); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeRemoteVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeRemoteVersion.java new file mode 100644 index 0000000000..3a08acedce --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeRemoteVersion.java @@ -0,0 +1,20 @@ +package org.jackhuang.hmcl.download.neoforge; + +import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.download.LibraryAnalyzer; +import org.jackhuang.hmcl.download.RemoteVersion; +import org.jackhuang.hmcl.game.Version; +import org.jackhuang.hmcl.task.Task; + +import java.util.List; + +public class NeoForgeRemoteVersion extends RemoteVersion { + public NeoForgeRemoteVersion(String gameVersion, String selfVersion, List urls) { + super(LibraryAnalyzer.LibraryType.NEO_FORGE.getPatchId(), gameVersion, selfVersion, null, urls); + } + + @Override + public Task getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) { + return new NeoForgeInstallTask(dependencyManager, baseVersion, this); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/JavaVersionConstraint.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/JavaVersionConstraint.java index 53ec612428..c8f5a37993 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/JavaVersionConstraint.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/JavaVersionConstraint.java @@ -145,7 +145,6 @@ protected boolean appliesToVersionImpl(VersionNumber gameVersionNumber, @Nullabl @Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) { if (version == null || javaVersion == null || analyzer == null) return false; VersionNumber forgePatchVersion = analyzer.getVersion(LibraryAnalyzer.LibraryType.FORGE) - .map(LibraryAnalyzer.LibraryType.FORGE::patchVersion) .map(VersionNumber::asVersion) .orElse(null); if (forgePatchVersion == null) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModLoaderType.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModLoaderType.java index f4fcf4d39a..97ec524b9f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModLoaderType.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModLoaderType.java @@ -18,20 +18,11 @@ package org.jackhuang.hmcl.mod; public enum ModLoaderType { - UNKNOWN("Unknown"), - FORGE("Forge"), - FABRIC("Fabric"), - QUILT("Quilt"), - LITE_LOADER("LiteLoader"), - PACK("Pack"); - - private final String loaderName; - - ModLoaderType(String loaderName) { - this.loaderName = loaderName; - } - - public final String getLoaderName() { - return loaderName; - } + UNKNOWN, + FORGE, + NEO_FORGED, + FABRIC, + QUILT, + LITE_LOADER, + PACK; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseAddon.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseAddon.java index 1fd22d38de..fa474943a9 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseAddon.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseAddon.java @@ -588,6 +588,7 @@ public RemoteMod.Version toVersion() { if ("fabric".equalsIgnoreCase(version)) return Stream.of(ModLoaderType.FABRIC); else if ("forge".equalsIgnoreCase(version)) return Stream.of(ModLoaderType.FORGE); else if ("quilt".equalsIgnoreCase(version)) return Stream.of(ModLoaderType.QUILT); + else if ("neoforge".equalsIgnoreCase(version)) return Stream.of(ModLoaderType.NEO_FORGED); else return Stream.empty(); }).collect(Collectors.toList()) ); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseInstallTask.java index a195a11206..6796cf3308 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseInstallTask.java @@ -57,9 +57,9 @@ public final class CurseInstallTask extends Task { * Constructor. * * @param dependencyManager the dependency manager. - * @param zipFile the CurseForge modpack file. - * @param manifest The manifest content of given CurseForge modpack. - * @param name the new version name + * @param zipFile the CurseForge modpack file. + * @param manifest The manifest content of given CurseForge modpack. + * @param name the new version name * @see CurseManifest#readCurseForgeModpackManifest */ public CurseInstallTask(DefaultDependencyManager dependencyManager, File zipFile, Modpack modpack, CurseManifest manifest, String name) { @@ -81,6 +81,8 @@ public CurseInstallTask(DefaultDependencyManager dependencyManager, File zipFile builder.version("forge", modLoader.getId().substring("forge-".length())); } else if (modLoader.getId().startsWith("fabric-")) { builder.version("fabric", modLoader.getId().substring("fabric-".length())); + } else if (modLoader.getId().startsWith("neoforge-")) { + builder.version("neoforge", modLoader.getId().substring("neoforge-".length())); } } dependents.add(builder.buildAsync()); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java index b66b4729a2..e0d9cc9a56 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java @@ -513,7 +513,9 @@ public Optional toVersion() { loaders.stream().flatMap(loader -> { if ("fabric".equalsIgnoreCase(loader)) return Stream.of(ModLoaderType.FABRIC); else if ("forge".equalsIgnoreCase(loader)) return Stream.of(ModLoaderType.FORGE); + else if ("neoforge".equalsIgnoreCase(loader)) return Stream.of(ModLoaderType.NEO_FORGED); else if ("quilt".equalsIgnoreCase(loader)) return Stream.of(ModLoaderType.QUILT); + else if ("liteloader".equalsIgnoreCase(loader)) return Stream.of(ModLoaderType.LITE_LOADER); else return Stream.empty(); }).collect(Collectors.toList()) ));