diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ModListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ModListPage.java index cc11d1d7125..b58c62f7fa8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ModListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ModListPage.java @@ -57,7 +57,7 @@ public final class ModListPage extends ListPageBase supportedLoaders = EnumSet.noneOf(ModLoaderType.class); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ModListPageSkin.java index 154705397d1..788b7b0a440 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ModListPageSkin.java @@ -40,6 +40,7 @@ import javafx.util.Duration; import org.jackhuang.hmcl.addon.RemoteAddon; import org.jackhuang.hmcl.addon.RemoteAddonRepository; +import org.jackhuang.hmcl.addon.meta.CoreMod; import org.jackhuang.hmcl.addon.mod.LocalModFile; import org.jackhuang.hmcl.addon.mod.ModLoaderType; import org.jackhuang.hmcl.addon.repository.CurseForgeRemoteAddonRepository; @@ -63,6 +64,7 @@ import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.io.NetworkUtils; +import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import org.jetbrains.annotations.NotNullByDefault; import org.jetbrains.annotations.Nullable; @@ -628,18 +630,44 @@ protected void updateControl(ModInfoObject dataItem, boolean empty) { content.setSubtitle(joiner.toString()); - if (modLoaderType == ModLoaderType.UNKNOWN) { - content.addTagWarning(i18n("mods.unknown")); - } else if (!ModListPageSkin.this.getSkinnable().supportedLoaders.contains(modLoaderType)) { - warning.add(i18n("mods.warning.loader_mismatch")); - switch (dataItem.getModInfo().getModLoaderType()) { - case FORGE -> content.addTagWarning(i18n("install.installer.forge")); - case LEGACY_FABRIC -> content.addTagWarning(i18n("install.installer.legacyfabric")); - case CLEANROOM -> content.addTagWarning(i18n("install.installer.cleanroom")); - case NEO_FORGE -> content.addTagWarning(i18n("install.installer.neoforge")); - case FABRIC -> content.addTagWarning(i18n("install.installer.fabric")); - case LITE_LOADER -> content.addTagWarning(i18n("install.installer.liteloader")); - case QUILT -> content.addTagWarning(i18n("install.installer.quilt")); + { + Set modFileLoaders = EnumSet.noneOf(ModLoaderType.class); + GameVersionNumber gameVersionNumber = GameVersionNumber.asGameVersion(Optional.ofNullable(getSkinnable().gameVersion)); + // Uses 1.7 snapshot as there's no snapshots for 1.6 after its first release + boolean checkCoreModsDir = !gameVersionNumber.isAtLeast("1.6.1", "13w36a"); + + if (modLoaderType != ModLoaderType.UNKNOWN) modFileLoaders.add(modLoaderType); + modFileLoaders.addAll(CoreMod.getSupportedLoaders(modInfo.getCoreMods(), gameVersionNumber)); + + if (modFileLoaders.stream().anyMatch(loader -> getSkinnable().supportedLoaders.contains(loader))) { + if (modInfo.isCoreMod()) { + if (checkCoreModsDir) { + content.addTagWarning("CoreMod"); + warning.add(i18n("mods.coremods.check_dir")); + } else { + content.addTag("CoreMod"); + } + } + } else { + if (modLoaderType == ModLoaderType.UNKNOWN) { + content.addTagWarning(i18n("mods.unknown")); + } else { + warning.add(i18n("mods.warning.loader_mismatch")); + switch (modLoaderType) { + case FORGE -> content.addTagWarning(i18n("install.installer.forge")); + case LEGACY_FABRIC -> content.addTagWarning(i18n("install.installer.legacyfabric")); + case CLEANROOM -> content.addTagWarning(i18n("install.installer.cleanroom")); + case NEO_FORGE -> content.addTagWarning(i18n("install.installer.neoforge")); + case FABRIC -> content.addTagWarning(i18n("install.installer.fabric")); + case LITE_LOADER -> content.addTagWarning(i18n("install.installer.liteloader")); + case QUILT -> content.addTagWarning(i18n("install.installer.quilt")); + } + } + if (modInfo.isCoreMod()) { + content.addTagWarning("CoreMod"); + if (checkCoreModsDir) + warning.add(i18n("mods.coremods.check_dir")); + } } } diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index ca60b1b0a01..5edb03d63d3 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -1202,6 +1202,7 @@ mods.add=Add mods.add.failed=Failed to add mod %s. mods.add.success=%s was successfully added. mods.add.title=Choose mod file you want to add +mods.coremods.check_dir=Should put into 'coremods' directory mods.disable=Disable mods.download=Download Mod mods.download.title=Download Mod - %1s diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 5eeafafcb3b..c8edf84f65f 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -1009,6 +1009,7 @@ mods.add=新增模組 mods.add.failed=新增模組「%s」失敗。 mods.add.success=成功新增模組「%s」。 mods.add.title=選取要新增的模組檔案 +mods.coremods.check_dir=應放入 "coremods" 資料夾 mods.disable=停用 mods.download=下載模組 mods.download.title=模組下載 - %1s 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 6fe08134e54..a8db7f178f9 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -1009,6 +1009,7 @@ mods.add=添加模组 mods.add.failed=添加模组“%s”失败。\n如遇到问题,你可以点击右上角帮助按钮进行求助。 mods.add.success=成功添加模组 %s。 mods.add.title=选择要添加的模组文件 +mods.coremods.check_dir=应放入 "coremods" 文件夹 mods.disable=禁用 mods.download=下载模组 mods.download.title=模组下载 - %1s diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/CoreMod.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/CoreMod.java new file mode 100644 index 00000000000..de82868305f --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/CoreMod.java @@ -0,0 +1,142 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2026 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.addon.meta; + +import com.google.gson.JsonIOException; +import com.google.gson.JsonSyntaxException; +import com.google.gson.reflect.TypeToken; +import kala.compress.archivers.zip.ZipArchiveEntry; +import org.jackhuang.hmcl.addon.mod.ModLoaderType; +import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.gson.JsonUtils; +import org.jackhuang.hmcl.util.io.FileUtils; +import org.jackhuang.hmcl.util.tree.ZipFileTree; +import org.jackhuang.hmcl.util.versioning.GameVersionNumber; +import org.jackhuang.hmcl.util.versioning.VersionRange; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Unmodifiable; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.jar.Attributes; +import java.util.jar.Manifest; + +import static org.jackhuang.hmcl.util.logging.Logger.LOG; + +/// @see CoreModTutor by xfl03 and contributors +public record CoreMod(ModLoaderType modLoaderType, VersionRange mcVersionRange) { + + private CoreMod(ModLoaderType modLoaderType, String min) { + this(modLoaderType, GameVersionNumber.atLeast(min)); + } + + private CoreMod(ModLoaderType modLoaderType, String min, String max) { + this(modLoaderType, GameVersionNumber.between(min, max)); + } + + @NotNull + @Unmodifiable + public static List fromFile(Path modFile, ZipFileTree tree) { + if (!"jar".equalsIgnoreCase(FileUtils.getExtension(modFile))) return List.of(); + List coreMods = new ArrayList<>(); + { + // Below 1.13 + ZipArchiveEntry mf = tree.getEntry("META-INF/MANIFEST.MF"); + if (mf != null) { + Attributes attr = null; + try (var in = tree.getInputStream(mf)) { + attr = new Manifest(in).getMainAttributes(); + } catch (IOException e) { + LOG.warning("Failed to load jar manifest for jar: " + modFile, e); + } + if (attr != null) { + String fmlCorePlg = attr.getValue("FMLCorePlugin"); + String tweakClass = attr.getValue("TweakClass"); + if (StringUtils.isNotBlank(fmlCorePlg)) + coreMods.add(new CoreMod(ModLoaderType.FORGE, "1.3.2", "1.12.2")); + if (StringUtils.isNotBlank(tweakClass)) + coreMods.addAll(List.of( + new CoreMod(ModLoaderType.FORGE, "1.6.1", "1.12.2"), + new CoreMod(ModLoaderType.LITE_LOADER, "1.6.1", "1.12.2") + )); + } + } + } + { + // coremods.json in Forge 1.13-1.21.10 & NeoForge 1.21.4- + // TODO Find a sample + ZipArchiveEntry coreModsJson = tree.getEntry("META-INF/coremods.json"); + if (coreModsJson != null) { + try (var in = new InputStreamReader(tree.getInputStream(coreModsJson))) { + var map = JsonUtils.fromJson(in, new TypeToken>() { + }); + if (map != null && !map.isEmpty()) + coreMods.addAll(List.of( + // Removed in https://github.com/MinecraftForge/MinecraftForge/pull/10746 + new CoreMod(ModLoaderType.FORGE, "1.13", "1.21.10"), + // Removed in https://github.com/neoforged/NeoForge/pull/2072 + new CoreMod(ModLoaderType.NEO_FORGE, "1.20.1", "1.21.4") + )); + } catch (IOException | JsonIOException e) { + LOG.warning("Failed to read coremods.json for jar: " + modFile, e); + } catch (JsonSyntaxException ignored) { + } + } + } + { + // ITransformationService + if (tree.getEntry("META-INF/services/cpw.mods.modlauncher.api.ITransformationService") != null) + coreMods.addAll(List.of( + new CoreMod(ModLoaderType.FORGE, "1.13.2"), + new CoreMod(ModLoaderType.NEO_FORGE, "1.20.1", "1.21.8") // Replaced by ClassProcessorProvider + )); + } + { + // ICoreMod for NeoForge + // https://github.com/neoforged/FancyModLoader/pull/79 + // https://neoforged.net/news/2024-retrospection/#the-changes + // TODO Find a sample + if (tree.getEntry("META-INF/services/net.neoforged.neoforgespi.coremod.ICoreMod") != null) + coreMods.add(new CoreMod(ModLoaderType.NEO_FORGE, "1.20.5", "1.21.8")); // Replaced by ClassProcessorProvider + } + { + // ClassProcessorProvider for NeoForge, replaces ITransformationService & ICoreMod + // https://github.com/neoforged/NeoForge/pull/2655 + // https://github.com/neoforged/FancyModLoader/pull/358 + if (tree.getEntry("META-INF/services/net.neoforged.neoforgespi.transformation.ClassProcessorProvider") != null) + coreMods.add(new CoreMod(ModLoaderType.NEO_FORGE, "1.21.9")); + } + return List.copyOf(coreMods); + } + + @NotNull + public static EnumSet getSupportedLoaders(List coreMods, GameVersionNumber gameVersion) { + EnumSet supportedLoaders = EnumSet.noneOf(ModLoaderType.class); + if (gameVersion == GameVersionNumber.unknown()) return supportedLoaders; + for (var coreMod : coreMods) + if (coreMod.mcVersionRange().contains(gameVersion)) + supportedLoaders.add(coreMod.modLoaderType()); + return supportedLoaders; + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/FabricModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/FabricModMetadata.java index 4601ed1ca5d..a19512cb8e0 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/FabricModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/FabricModMetadata.java @@ -60,14 +60,14 @@ public FabricModMetadata(String id, String name, String version, String icon, St this.contact = contact; } - public static LocalModFile fromFile(ModManager modManager, Path modFile, ZipFileTree tree) throws IOException, JsonParseException { + public static LocalModFile fromFile(ModManager modManager, Path modFile, ZipFileTree tree, List coreMods) throws IOException, JsonParseException { ZipArchiveEntry mcmod = tree.getEntry("fabric.mod.json"); if (mcmod == null) throw new IOException("File " + modFile + " is not a Fabric mod."); FabricModMetadata metadata = JsonUtils.fromNonNullJsonFully(tree.getInputStream(mcmod), FabricModMetadata.class); String authors = metadata.authors == null ? "" : metadata.authors.stream().map(author -> author.name).collect(Collectors.joining(", ")); return new LocalModFile(modManager, modManager.getLocalMod(metadata.id, ModLoaderType.FABRIC), modFile, metadata.name, new LocalAddonFile.Description(metadata.description), - authors, metadata.version, "", metadata.contact != null ? metadata.contact.getOrDefault("homepage", "") : "", metadata.icon); + authors, metadata.version, "", metadata.contact != null ? metadata.contact.getOrDefault("homepage", "") : "", metadata.icon, coreMods); } @JsonAdapter(FabricModAuthorSerializer.class) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/ForgeNewModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/ForgeNewModMetadata.java index 6b7ff0085b4..58cfd0bb03a 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/ForgeNewModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/ForgeNewModMetadata.java @@ -36,10 +36,7 @@ import org.jackhuang.hmcl.util.gson.Validation; import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.tree.ZipFileTree; -import org.tomlj.Toml; -import org.tomlj.TomlArray; -import org.tomlj.TomlParseResult; -import org.tomlj.TomlTable; +import org.tomlj.*; import java.io.IOException; import java.io.InputStream; @@ -177,33 +174,33 @@ public String deserialize(JsonElement authors, Type type, JsonDeserializationCon } } - public static LocalModFile fromForgeFile(ModManager modManager, Path modFile, ZipFileTree tree) throws IOException { - return fromFile(modManager, modFile, tree, ModLoaderType.FORGE); + public static LocalModFile fromForgeFile(ModManager modManager, Path modFile, ZipFileTree tree, List coreMods) throws IOException { + return fromFile(modManager, modFile, tree, ModLoaderType.FORGE, coreMods); } - public static LocalModFile fromNeoForgeFile(ModManager modManager, Path modFile, ZipFileTree tree) throws IOException { - return fromFile(modManager, modFile, tree, ModLoaderType.NEO_FORGE); + public static LocalModFile fromNeoForgeFile(ModManager modManager, Path modFile, ZipFileTree tree, List coreMods) throws IOException { + return fromFile(modManager, modFile, tree, ModLoaderType.NEO_FORGE, coreMods); } - private static LocalModFile fromFile(ModManager modManager, Path modFile, ZipFileTree tree, ModLoaderType modLoaderType) throws IOException { + private static LocalModFile fromFile(ModManager modManager, Path modFile, ZipFileTree tree, ModLoaderType modLoaderType, List coreMods) throws IOException { if (modLoaderType != ModLoaderType.FORGE && modLoaderType != ModLoaderType.NEO_FORGE) { throw new IOException("Invalid mod loader: " + modLoaderType); } if (modLoaderType == ModLoaderType.NEO_FORGE) { try { - return fromFile0("META-INF/neoforge.mods.toml", modLoaderType, modManager, modFile, tree); + return fromFile0("META-INF/neoforge.mods.toml", modLoaderType, modManager, modFile, tree, coreMods); } catch (Exception ignored) { } } try { - return fromFile0("META-INF/mods.toml", modLoaderType, modManager, modFile, tree); + return fromFile0("META-INF/mods.toml", modLoaderType, modManager, modFile, tree, coreMods); } catch (Exception ignored) { } try { - return fromEmbeddedMod(modManager, modFile, tree, modLoaderType); + return fromEmbeddedMod(modManager, modFile, tree, modLoaderType, coreMods); } catch (Exception ignored) { } @@ -215,7 +212,8 @@ private static LocalModFile fromFile0( ModLoaderType modLoaderType, ModManager modManager, Path modFile, - ZipFileTree tree) throws IOException, JsonParseException { + ZipFileTree tree, + List coreMods) throws IOException, JsonParseException { ZipArchiveEntry modToml = tree.getEntry(tomlPath); if (modToml == null) throw new IOException("File " + modFile + " is not a Forge 1.13+ or NeoForge mod."); @@ -247,10 +245,10 @@ private static LocalModFile fromFile0( return new LocalModFile(modManager, modManager.getLocalMod(mod.getModId(), type), modFile, mod.getDisplayName(), new LocalAddonFile.Description(mod.getDescription()), mod.getAuthors(), jarVersion == null ? mod.getVersion() : mod.getVersion().replace("${file.jarVersion}", jarVersion), "", mod.getDisplayURL(), - logoPath); + logoPath, coreMods); } - private static LocalModFile fromEmbeddedMod(ModManager modManager, Path modFile, ZipFileTree tree, ModLoaderType modLoaderType) throws IOException { + private static LocalModFile fromEmbeddedMod(ModManager modManager, Path modFile, ZipFileTree tree, ModLoaderType modLoaderType, List coreMods) throws IOException { ZipArchiveEntry manifestFile = tree.getEntry("META-INF/MANIFEST.MF"); if (manifestFile == null) throw new IOException("Missing MANIFEST.MF in file " + modFile); @@ -300,7 +298,7 @@ private static LocalModFile fromEmbeddedMod(ModManager modManager, Path modFile, for (ZipArchiveEntry embeddedModFile : embeddedModFiles) { tree.extractTo(embeddedModFile, tempFile); try (ZipFileTree embeddedTree = CompressingUtils.openZipTree(tempFile)) { - return fromFile(modManager, modFile, embeddedTree, modLoaderType); + return fromFile(modManager, modFile, embeddedTree, modLoaderType, coreMods); } catch (Exception ignored) { } } @@ -318,7 +316,7 @@ private static ModLoaderType analyzeLoader(TomlParseResult toml, String modID, M if (tomlArray != null) { dependencies = tomlArray.toList().stream().map( o -> ((TomlTable) o).toMap()).toList(); } - } catch (ClassCastException ignored) { // https://github.com/HMCL-dev/HMCL/issues/5068 + } catch (ClassCastException | TomlInvalidTypeException ignored) { // https://github.com/HMCL-dev/HMCL/issues/5068 } if (dependencies == null) { @@ -327,7 +325,7 @@ private static ModLoaderType analyzeLoader(TomlParseResult toml, String modID, M if (tomlArray != null) { dependencies = tomlArray.toList().stream().map( o -> ((TomlTable) o).toMap()).toList(); } - } catch (ClassCastException e) { + } catch (ClassCastException | TomlInvalidTypeException e) { try { TomlTable table = toml.getTable("dependencies"); if (table == null) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/ForgeOldModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/ForgeOldModMetadata.java index 81f358c94e6..4d1cf418872 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/ForgeOldModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/ForgeOldModMetadata.java @@ -124,7 +124,7 @@ public String[] getAuthors() { return authors; } - public static LocalModFile fromFile(ModManager modManager, Path modFile, ZipFileTree tree) throws IOException, JsonParseException { + public static LocalModFile fromFile(ModManager modManager, Path modFile, ZipFileTree tree, List coreMods) throws IOException, JsonParseException { ZipArchiveEntry mcmod = tree.getEntry("mcmod.info"); if (mcmod == null) throw new IOException("File " + modFile + " is not a Forge mod."); @@ -160,6 +160,6 @@ else if (firstToken == JsonToken.BEGIN_OBJECT) { return new LocalModFile(modManager, modManager.getLocalMod(metadata.getModId(), ModLoaderType.FORGE), modFile, metadata.getName(), new LocalAddonFile.Description(metadata.getDescription()), authors, metadata.getVersion(), metadata.getGameVersion(), StringUtils.isBlank(metadata.getUrl()) ? metadata.getUpdateUrl() : metadata.url, - metadata.getLogoFile()); + metadata.getLogoFile(), coreMods); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/LiteModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/LiteModMetadata.java index aea8c4fab2e..c60b2c32f2a 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/LiteModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/LiteModMetadata.java @@ -29,6 +29,7 @@ import java.io.IOException; import java.nio.file.Path; +import java.util.List; /** * @@ -110,7 +111,7 @@ public String getUpdateURI() { return updateURI; } - public static LocalModFile fromFile(ModManager modManager, Path modFile, ZipFileTree tree) throws IOException, JsonParseException { + public static LocalModFile fromFile(ModManager modManager, Path modFile, ZipFileTree tree, List coreMods) throws IOException, JsonParseException { ZipArchiveEntry entry = tree.getEntry("litemod.json"); if (entry == null) throw new IOException("File " + modFile + " is not a LiteLoader mod."); @@ -118,7 +119,7 @@ public static LocalModFile fromFile(ModManager modManager, Path modFile, ZipFile if (metadata == null) throw new IOException("Mod " + modFile + " `litemod.json` is malformed."); return new LocalModFile(modManager, modManager.getLocalMod(metadata.getName(), ModLoaderType.LITE_LOADER), modFile, metadata.getName(), new LocalAddonFile.Description(metadata.getDescription()), metadata.getAuthor(), - metadata.getVersion(), metadata.getGameVersion(), metadata.getUpdateURI(), ""); + metadata.getVersion(), metadata.getGameVersion(), metadata.getUpdateURI(), "", coreMods); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/QuiltModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/QuiltModMetadata.java index 02e31ed7269..beec8e88081 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/QuiltModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/QuiltModMetadata.java @@ -30,6 +30,7 @@ import java.io.IOException; import java.nio.file.Path; +import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @@ -71,7 +72,7 @@ public QuiltModMetadata(int schemaVersion, QuiltLoader quiltLoader) { this.quilt_loader = quiltLoader; } - public static LocalModFile fromFile(ModManager modManager, Path modFile, ZipFileTree tree) throws IOException, JsonParseException { + public static LocalModFile fromFile(ModManager modManager, Path modFile, ZipFileTree tree, List coreMods) throws IOException, JsonParseException { ZipArchiveEntry path = tree.getEntry("quilt.mod.json"); if (path == null) { throw new IOException("File " + modFile + " is not a Quilt mod."); @@ -92,7 +93,7 @@ public static LocalModFile fromFile(ModManager modManager, Path modFile, ZipFile root.quilt_loader.version, "", Optional.ofNullable(root.quilt_loader.metadata.contact.get("homepage")).map(jsonElement -> jsonElement.getAsJsonPrimitive().getAsString()).orElse(""), - root.quilt_loader.metadata.icon + root.quilt_loader.metadata.icon, coreMods ); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/LocalModFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/LocalModFile.java index 32984fa3baa..e91c172cc46 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/LocalModFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/LocalModFile.java @@ -23,8 +23,11 @@ import org.jackhuang.hmcl.addon.LocalAddonManager; import org.jackhuang.hmcl.addon.RemoteAddon; import org.jackhuang.hmcl.addon.RemoteAddonRepository; +import org.jackhuang.hmcl.addon.meta.CoreMod; import org.jackhuang.hmcl.download.DownloadProvider; import org.jackhuang.hmcl.util.io.FileUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Unmodifiable; import java.io.IOException; import java.nio.file.Files; @@ -50,13 +53,15 @@ public final class LocalModFile extends LocalAddonFile implements Comparable coreMods; + private final BooleanProperty activeProperty; - public LocalModFile(ModManager modManager, LocalMod mod, Path file, String name, Description description) { - this(modManager, mod, file, name, description, "", "", "", "", ""); + public LocalModFile(ModManager modManager, LocalMod mod, Path file, String name, Description description, List coreMods) { + this(modManager, mod, file, name, description, "", "", "", "", "", coreMods); } - public LocalModFile(ModManager modManager, LocalMod mod, Path file, String name, Description description, String authors, String version, String gameVersion, String url, String logoPath) { + public LocalModFile(ModManager modManager, LocalMod mod, Path file, String name, Description description, String authors, String version, String gameVersion, String url, String logoPath, List coreMods) { super(); this.modManager = modManager; this.mod = mod; @@ -68,6 +73,7 @@ public LocalModFile(ModManager modManager, LocalMod mod, Path file, String name, this.gameVersion = gameVersion; this.url = url; this.logoPath = logoPath; + this.coreMods = List.copyOf(coreMods); activeProperty = new SimpleBooleanProperty(this, "active", !modManager.isDisabled(file)) { @Override @@ -145,6 +151,16 @@ public String getLogoPath() { return logoPath; } + public boolean isCoreMod() { + return !coreMods.isEmpty(); + } + + @NotNull + @Unmodifiable + public List getCoreMods() { + return coreMods; + } + public BooleanProperty activeProperty() { return activeProperty; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/ModManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/ModManager.java index ab60fac8b29..b21877828b4 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/ModManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/ModManager.java @@ -44,7 +44,7 @@ public final class ModManager extends LocalAddonManager { @FunctionalInterface private interface ModMetadataReader { - LocalModFile fromFile(ModManager modManager, Path modFile, ZipFileTree tree) throws IOException, JsonParseException; + LocalModFile fromFile(ModManager modManager, Path modFile, ZipFileTree tree, List coreMods) throws IOException, JsonParseException; } private static final Map>> READERS; @@ -126,12 +126,15 @@ private void addModInfo(Path file) { } LocalModFile modInfo = null; + List coreMods = List.of(); List exceptions = new ArrayList<>(); try (ZipFileTree tree = CompressingUtils.openZipTree(file)) { + coreMods = CoreMod.fromFile(file, tree); + for (ModMetadataReader reader : supportedReaders) { try { - modInfo = reader.fromFile(this, file, tree); + modInfo = reader.fromFile(this, file, tree, coreMods); break; } catch (Exception e) { exceptions.add(e); @@ -141,7 +144,7 @@ private void addModInfo(Path file) { if (modInfo == null) { for (ModMetadataReader reader : unsupportedReaders) { try { - modInfo = reader.fromFile(this, file, tree); + modInfo = reader.fromFile(this, file, tree, coreMods); break; } catch (Exception ignored) { } @@ -164,7 +167,8 @@ private void addModInfo(Path file) { getLocalMod(fileNameWithoutExtension, ModLoaderType.UNKNOWN), file, fileNameWithoutExtension, - new LocalAddonFile.Description("litemod".equals(extension) ? "LiteLoader Mod" : "") + new LocalAddonFile.Description("litemod".equals(extension) ? "LiteLoader Mod" : ""), + coreMods ); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/GameVersionNumber.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/GameVersionNumber.java index 461732f0b06..ed3cc5b73f0 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/GameVersionNumber.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/GameVersionNumber.java @@ -40,6 +40,7 @@ public static String[] getDefaultGameVersions() { return Versions.DEFAULT_GAME_VERSIONS; } + @NotNull public static GameVersionNumber asGameVersion(String version) { GameVersionNumber versionNumber = Versions.SPECIALS.get(version); if (versionNumber != null) @@ -71,8 +72,9 @@ public static GameVersionNumber asGameVersion(String version) { return new Special(version, version); } + @NotNull public static GameVersionNumber asGameVersion(Optional version) { - return version.isPresent() ? asGameVersion(version.get()) : unknown(); + return version.map(GameVersionNumber::asGameVersion).orElseGet(GameVersionNumber::unknown); } public static GameVersionNumber unknown() {