From 2e81c85b1da702f3668105bf22f4b9ace26cbcdb Mon Sep 17 00:00:00 2001 From: ToobLac Date: Fri, 7 Aug 2026 18:40:30 +0800 Subject: [PATCH 1/7] coremod --- .../hmcl/ui/instances/ModListPage.java | 2 +- .../hmcl/ui/instances/ModListPageSkin.java | 37 ++++-- .../jackhuang/hmcl/addon/meta/CoreMod.java | 120 ++++++++++++++++++ .../hmcl/addon/meta/FabricModMetadata.java | 4 +- .../hmcl/addon/meta/ForgeNewModMetadata.java | 25 ++-- .../hmcl/addon/meta/ForgeOldModMetadata.java | 4 +- .../hmcl/addon/meta/LiteModMetadata.java | 5 +- .../hmcl/addon/meta/QuiltModMetadata.java | 5 +- .../hmcl/addon/mod/LocalModFile.java | 18 ++- .../jackhuang/hmcl/addon/mod/ModManager.java | 12 +- .../java/org/jackhuang/hmcl/util/Lang.java | 12 ++ .../util/versioning/GameVersionNumber.java | 4 +- 12 files changed, 207 insertions(+), 41 deletions(-) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/CoreMod.java 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..5f05cb1c2d9 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; @@ -628,18 +629,30 @@ 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); + if (modLoaderType != ModLoaderType.UNKNOWN) modFileLoaders.add(modLoaderType); + modFileLoaders.addAll(CoreMod.getSupportedLoaders(modInfo.getCoreMods(), getSkinnable().gameVersion)); + if (modFileLoaders.stream().anyMatch(loader -> getSkinnable().supportedLoaders.contains(loader))) { + if (!modInfo.getCoreMods().isEmpty()) + 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.getCoreMods().isEmpty()) + content.addTagWarning("CoreMod"); } } 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..cba4db99296 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/CoreMod.java @@ -0,0 +1,120 @@ +/* + * 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 kala.compress.archivers.zip.ZipArchiveEntry; +import org.jackhuang.hmcl.addon.mod.ModLoaderType; +import org.jackhuang.hmcl.util.Lang; +import org.jackhuang.hmcl.util.StringUtils; +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.Nullable; +import org.jetbrains.annotations.Unmodifiable; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.*; +import java.util.jar.Attributes; +import java.util.jar.Manifest; + +import static org.jackhuang.hmcl.util.logging.Logger.LOG; +import static org.jackhuang.hmcl.util.versioning.GameVersionNumber.asGameVersion; + +public record CoreMod(ModLoaderType modLoaderType, VersionRange mcVersionRange) { + + @NotNull + @Unmodifiable + public static List fromFile(Path modFile, ZipFileTree tree) { + if (!"jar".equalsIgnoreCase(FileUtils.getExtension(modFile))) return List.of(); + GameVersionNumber forgeMin = null, forgeMax = null, neoMin = null, neoMax = null; + { + // Below 1.13 + ZipArchiveEntry mf = tree.getEntry("META-INF/MANIFEST.MF"); + if (mf != null) { + Attributes attr = null; + try { + attr = new Manifest(tree.getInputStream(mf)).getMainAttributes(); + } catch (IOException e) { + LOG.warning("Failed to load jar manifest for jar: " + modFile, e); + } + if (attr != null) { + String fmlCorePlg = Objects.toString(attr.get("FMLCorePlugin"), null); + String tweakClass = Objects.toString(attr.get("TweakClass"), null); + if (StringUtils.isNotBlank(fmlCorePlg) || StringUtils.isNotBlank(tweakClass)) { + forgeMin = asGameVersion("1.6.1"); + forgeMax = asGameVersion("1.12.2"); + } + } + } + } + { + // coremods.json in Forge 1.13-1.20.1 & NeoForge 1.21.4- + // TODO Find a sample + if (tree.getEntry("META-INF/coremods.json") != null) { + forgeMin = Lang.minNullable(forgeMin, asGameVersion("1.13")); + forgeMax = asGameVersion("1.21.10"); + neoMin = asGameVersion("1.20.1"); + neoMax = asGameVersion("1.21.4"); + } + } + { + // ITransformationService + if (tree.getEntry("META-INF/services/cpw.mods.modlauncher.api.ITransformationService") != null) { + forgeMin = Lang.minNullable(forgeMin, asGameVersion("1.13")); + forgeMax = GameVersionNumber.unknown(); + neoMin = asGameVersion("1.20.1"); + neoMax = asGameVersion("1.21.8"); + } + } + { + // ICoreMod for NeoForge + // TODO Find a sample + if (tree.getEntry("META-INF/services/net.neoforged.neoforgespi.coremod.ICoreMod") != null) { + neoMin = Lang.minNullable(neoMin, asGameVersion("1.20.5")); + neoMax = GameVersionNumber.unknown(); + } + } + List infoList = new ArrayList<>(); + if (forgeMin != null && forgeMax != null) + infoList.add(new CoreMod( + ModLoaderType.FORGE, + forgeMax == GameVersionNumber.unknown() ? VersionRange.atLeast(forgeMin) : VersionRange.between(forgeMin, forgeMax) + )); + if (neoMin != null && neoMax != null) + infoList.add(new CoreMod( + ModLoaderType.NEO_FORGE, + neoMax == GameVersionNumber.unknown() ? VersionRange.atLeast(neoMin) : VersionRange.between(neoMin, neoMax) + )); + return List.copyOf(infoList); + } + + @NotNull + @Unmodifiable + public static Set getSupportedLoaders(List coreMods, @Nullable String gameVersion) { + Set supportedLoaders = EnumSet.noneOf(ModLoaderType.class); + var version = GameVersionNumber.asGameVersion(Optional.ofNullable(gameVersion)); + for (var coreMod : coreMods) + if (coreMod.mcVersionRange().contains(Objects.requireNonNullElse(GameVersionNumber.getReleaseOfSnapshot(version), version))) + supportedLoaders.add(coreMod.modLoaderType()); + return Set.copyOf(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..fb2e97c0053 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 @@ -177,33 +177,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 +215,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 +248,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 +301,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) { } } 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..d60541cde70 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,12 @@ public String getLogoPath() { return logoPath; } + @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/Lang.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.java index c00a7767855..47ede0b55af 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.java @@ -391,6 +391,18 @@ public static List copyWithSize(List list, int newSize, T defaultValue return result; } + public static > T minNullable(T a, T b) { + if (a == null) return b; + if (b == null) return a; + return a.compareTo(b) <= 0 ? a : b; + } + + public static > T maxNullable(T a, T b) { + if (a == null) return b; + if (b == null) return a; + return a.compareTo(b) >= 0 ? a : b; + } + public static Throwable resolveException(Throwable e) { if (e instanceof ExecutionException || e instanceof CompletionException) return resolveException(e.getCause()); 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() { From 9d9990799e093946b3146497cf0bda00c01391d3 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Fri, 7 Aug 2026 19:05:16 +0800 Subject: [PATCH 2/7] update --- .../jackhuang/hmcl/addon/meta/CoreMod.java | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) 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 index cba4db99296..1d3f5d43266 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/CoreMod.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/CoreMod.java @@ -66,13 +66,13 @@ public static List fromFile(Path modFile, ZipFileTree tree) { } } { - // coremods.json in Forge 1.13-1.20.1 & NeoForge 1.21.4- + // coremods.json in Forge 1.13-1.21.10 & NeoForge 1.21.4- // TODO Find a sample if (tree.getEntry("META-INF/coremods.json") != null) { forgeMin = Lang.minNullable(forgeMin, asGameVersion("1.13")); - forgeMax = asGameVersion("1.21.10"); + forgeMax = asGameVersion("1.21.10"); // Removed in https://github.com/MinecraftForge/MinecraftForge/pull/10746 neoMin = asGameVersion("1.20.1"); - neoMax = asGameVersion("1.21.4"); + neoMax = asGameVersion("1.21.4"); // Removed in https://github.com/neoforged/NeoForge/pull/2072 } } { @@ -81,14 +81,25 @@ public static List fromFile(Path modFile, ZipFileTree tree) { forgeMin = Lang.minNullable(forgeMin, asGameVersion("1.13")); forgeMax = GameVersionNumber.unknown(); neoMin = asGameVersion("1.20.1"); - neoMax = asGameVersion("1.21.8"); + neoMax = asGameVersion("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) { neoMin = Lang.minNullable(neoMin, asGameVersion("1.20.5")); + neoMax = asGameVersion("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) { + neoMin = Lang.minNullable(neoMin, asGameVersion("1.21.9")); neoMax = GameVersionNumber.unknown(); } } From 4cbe464aa67d1c4685a74d1dcfd6a455ea27a384 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Fri, 7 Aug 2026 20:07:31 +0800 Subject: [PATCH 3/7] update --- .../src/main/java/org/jackhuang/hmcl/addon/meta/CoreMod.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 index 1d3f5d43266..c34f09770d0 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/CoreMod.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/CoreMod.java @@ -56,8 +56,8 @@ public static List fromFile(Path modFile, ZipFileTree tree) { LOG.warning("Failed to load jar manifest for jar: " + modFile, e); } if (attr != null) { - String fmlCorePlg = Objects.toString(attr.get("FMLCorePlugin"), null); - String tweakClass = Objects.toString(attr.get("TweakClass"), null); + String fmlCorePlg = attr.getValue("FMLCorePlugin"); + String tweakClass = attr.getValue("TweakClass"); if (StringUtils.isNotBlank(fmlCorePlg) || StringUtils.isNotBlank(tweakClass)) { forgeMin = asGameVersion("1.6.1"); forgeMax = asGameVersion("1.12.2"); From c329792eeaf9ce6358d985841a3489d6b75241b9 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Fri, 7 Aug 2026 20:30:29 +0800 Subject: [PATCH 4/7] fix rei meta --- .../jackhuang/hmcl/addon/meta/ForgeNewModMetadata.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) 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 fb2e97c0053..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; @@ -319,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) { @@ -328,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) From 27db2f33a17ea133c521ea5dbf3ca36fad3c64d7 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Fri, 7 Aug 2026 21:17:38 +0800 Subject: [PATCH 5/7] update --- .../jackhuang/hmcl/addon/meta/CoreMod.java | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) 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 index c34f09770d0..4ca8a2d4bfc 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/CoreMod.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/CoreMod.java @@ -17,10 +17,14 @@ */ 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.Lang; 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; @@ -30,6 +34,7 @@ import org.jetbrains.annotations.Unmodifiable; import java.io.IOException; +import java.io.InputStreamReader; import java.nio.file.Path; import java.util.*; import java.util.jar.Attributes; @@ -50,8 +55,8 @@ public static List fromFile(Path modFile, ZipFileTree tree) { ZipArchiveEntry mf = tree.getEntry("META-INF/MANIFEST.MF"); if (mf != null) { Attributes attr = null; - try { - attr = new Manifest(tree.getInputStream(mf)).getMainAttributes(); + 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); } @@ -68,11 +73,21 @@ public static List fromFile(Path modFile, ZipFileTree tree) { { // coremods.json in Forge 1.13-1.21.10 & NeoForge 1.21.4- // TODO Find a sample - if (tree.getEntry("META-INF/coremods.json") != null) { - forgeMin = Lang.minNullable(forgeMin, asGameVersion("1.13")); - forgeMax = asGameVersion("1.21.10"); // Removed in https://github.com/MinecraftForge/MinecraftForge/pull/10746 - neoMin = asGameVersion("1.20.1"); - neoMax = asGameVersion("1.21.4"); // Removed in https://github.com/neoforged/NeoForge/pull/2072 + 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()) { + forgeMin = Lang.minNullable(forgeMin, asGameVersion("1.13")); + forgeMax = asGameVersion("1.21.10"); // Removed in https://github.com/MinecraftForge/MinecraftForge/pull/10746 + neoMin = asGameVersion("1.20.1"); + neoMax = asGameVersion("1.21.4"); // Removed in https://github.com/neoforged/NeoForge/pull/2072 + } + } catch (IOException | JsonIOException e) { + LOG.warning("Failed to read coremods.json for jar: " + modFile, e); + } catch (JsonSyntaxException ignored) { + } } } { From 08f04008b49f049859b98541741b31dbbca74b6f Mon Sep 17 00:00:00 2001 From: ToobLac Date: Sun, 9 Aug 2026 18:59:58 +0800 Subject: [PATCH 6/7] update --- .../jackhuang/hmcl/addon/meta/CoreMod.java | 61 +++++++------------ .../java/org/jackhuang/hmcl/util/Lang.java | 12 ---- 2 files changed, 23 insertions(+), 50 deletions(-) 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 index 4ca8a2d4bfc..9591f2324a9 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/CoreMod.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/CoreMod.java @@ -22,7 +22,6 @@ import com.google.gson.reflect.TypeToken; import kala.compress.archivers.zip.ZipArchiveEntry; import org.jackhuang.hmcl.addon.mod.ModLoaderType; -import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.io.FileUtils; @@ -41,7 +40,8 @@ import java.util.jar.Manifest; import static org.jackhuang.hmcl.util.logging.Logger.LOG; -import static org.jackhuang.hmcl.util.versioning.GameVersionNumber.asGameVersion; +import static org.jackhuang.hmcl.util.versioning.GameVersionNumber.atLeast; +import static org.jackhuang.hmcl.util.versioning.GameVersionNumber.between; public record CoreMod(ModLoaderType modLoaderType, VersionRange mcVersionRange) { @@ -49,7 +49,7 @@ public record CoreMod(ModLoaderType modLoaderType, VersionRange fromFile(Path modFile, ZipFileTree tree) { if (!"jar".equalsIgnoreCase(FileUtils.getExtension(modFile))) return List.of(); - GameVersionNumber forgeMin = null, forgeMax = null, neoMin = null, neoMax = null; + List coreMods = new ArrayList<>(); { // Below 1.13 ZipArchiveEntry mf = tree.getEntry("META-INF/MANIFEST.MF"); @@ -64,8 +64,7 @@ public static List fromFile(Path modFile, ZipFileTree tree) { String fmlCorePlg = attr.getValue("FMLCorePlugin"); String tweakClass = attr.getValue("TweakClass"); if (StringUtils.isNotBlank(fmlCorePlg) || StringUtils.isNotBlank(tweakClass)) { - forgeMin = asGameVersion("1.6.1"); - forgeMax = asGameVersion("1.12.2"); + coreMods.add(new CoreMod(ModLoaderType.FORGE, between("1.6.1", "1.12.2"))); } } } @@ -78,12 +77,13 @@ public static List fromFile(Path modFile, ZipFileTree tree) { try (var in = new InputStreamReader(tree.getInputStream(coreModsJson))) { var map = JsonUtils.fromJson(in, new TypeToken>() { }); - if (map != null && !map.isEmpty()) { - forgeMin = Lang.minNullable(forgeMin, asGameVersion("1.13")); - forgeMax = asGameVersion("1.21.10"); // Removed in https://github.com/MinecraftForge/MinecraftForge/pull/10746 - neoMin = asGameVersion("1.20.1"); - neoMax = asGameVersion("1.21.4"); // Removed in https://github.com/neoforged/NeoForge/pull/2072 - } + if (map != null && !map.isEmpty()) + coreMods.addAll(List.of( + // Removed in https://github.com/MinecraftForge/MinecraftForge/pull/10746 + new CoreMod(ModLoaderType.FORGE, between("1.13", "1.21.10")), + // Removed in https://github.com/neoforged/NeoForge/pull/2072 + new CoreMod(ModLoaderType.NEO_FORGE, between("1.20.1", "1.21.4")) + )); } catch (IOException | JsonIOException e) { LOG.warning("Failed to read coremods.json for jar: " + modFile, e); } catch (JsonSyntaxException ignored) { @@ -92,44 +92,28 @@ public static List fromFile(Path modFile, ZipFileTree tree) { } { // ITransformationService - if (tree.getEntry("META-INF/services/cpw.mods.modlauncher.api.ITransformationService") != null) { - forgeMin = Lang.minNullable(forgeMin, asGameVersion("1.13")); - forgeMax = GameVersionNumber.unknown(); - neoMin = asGameVersion("1.20.1"); - neoMax = asGameVersion("1.21.8"); // Replaced by ClassProcessorProvider - } + if (tree.getEntry("META-INF/services/cpw.mods.modlauncher.api.ITransformationService") != null) + coreMods.addAll(List.of( + new CoreMod(ModLoaderType.FORGE, atLeast("1.13.2")), + new CoreMod(ModLoaderType.NEO_FORGE, between("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) { - neoMin = Lang.minNullable(neoMin, asGameVersion("1.20.5")); - neoMax = asGameVersion("1.21.8"); // Replaced by ClassProcessorProvider - } + if (tree.getEntry("META-INF/services/net.neoforged.neoforgespi.coremod.ICoreMod") != null) + coreMods.add(new CoreMod(ModLoaderType.NEO_FORGE, between("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) { - neoMin = Lang.minNullable(neoMin, asGameVersion("1.21.9")); - neoMax = GameVersionNumber.unknown(); - } + if (tree.getEntry("META-INF/services/net.neoforged.neoforgespi.transformation.ClassProcessorProvider") != null) + coreMods.add(new CoreMod(ModLoaderType.NEO_FORGE, atLeast("1.21.9"))); } - List infoList = new ArrayList<>(); - if (forgeMin != null && forgeMax != null) - infoList.add(new CoreMod( - ModLoaderType.FORGE, - forgeMax == GameVersionNumber.unknown() ? VersionRange.atLeast(forgeMin) : VersionRange.between(forgeMin, forgeMax) - )); - if (neoMin != null && neoMax != null) - infoList.add(new CoreMod( - ModLoaderType.NEO_FORGE, - neoMax == GameVersionNumber.unknown() ? VersionRange.atLeast(neoMin) : VersionRange.between(neoMin, neoMax) - )); - return List.copyOf(infoList); + return List.copyOf(coreMods); } @NotNull @@ -137,8 +121,9 @@ public static List fromFile(Path modFile, ZipFileTree tree) { public static Set getSupportedLoaders(List coreMods, @Nullable String gameVersion) { Set supportedLoaders = EnumSet.noneOf(ModLoaderType.class); var version = GameVersionNumber.asGameVersion(Optional.ofNullable(gameVersion)); + if (version == GameVersionNumber.unknown()) return Set.of(); for (var coreMod : coreMods) - if (coreMod.mcVersionRange().contains(Objects.requireNonNullElse(GameVersionNumber.getReleaseOfSnapshot(version), version))) + if (coreMod.mcVersionRange().contains(version)) supportedLoaders.add(coreMod.modLoaderType()); return Set.copyOf(supportedLoaders); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.java index 47ede0b55af..c00a7767855 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.java @@ -391,18 +391,6 @@ public static List copyWithSize(List list, int newSize, T defaultValue return result; } - public static > T minNullable(T a, T b) { - if (a == null) return b; - if (b == null) return a; - return a.compareTo(b) <= 0 ? a : b; - } - - public static > T maxNullable(T a, T b) { - if (a == null) return b; - if (b == null) return a; - return a.compareTo(b) >= 0 ? a : b; - } - public static Throwable resolveException(Throwable e) { if (e instanceof ExecutionException || e instanceof CompletionException) return resolveException(e.getCause()); From 6e29b1aed72b5ebaa948f98aa11dca22b3580964 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Sun, 9 Aug 2026 21:57:10 +0800 Subject: [PATCH 7/7] update --- .../hmcl/ui/instances/ModListPageSkin.java | 23 +++++++-- .../resources/assets/lang/I18N.properties | 1 + .../resources/assets/lang/I18N_zh.properties | 1 + .../assets/lang/I18N_zh_CN.properties | 1 + .../jackhuang/hmcl/addon/meta/CoreMod.java | 51 +++++++++++-------- .../hmcl/addon/mod/LocalModFile.java | 4 ++ 6 files changed, 57 insertions(+), 24 deletions(-) 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 5f05cb1c2d9..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 @@ -64,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; @@ -631,11 +632,22 @@ protected void updateControl(ModInfoObject dataItem, boolean empty) { { 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(), getSkinnable().gameVersion)); + modFileLoaders.addAll(CoreMod.getSupportedLoaders(modInfo.getCoreMods(), gameVersionNumber)); + if (modFileLoaders.stream().anyMatch(loader -> getSkinnable().supportedLoaders.contains(loader))) { - if (!modInfo.getCoreMods().isEmpty()) - content.addTag("CoreMod"); + 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")); @@ -651,8 +663,11 @@ protected void updateControl(ModInfoObject dataItem, boolean empty) { case QUILT -> content.addTagWarning(i18n("install.installer.quilt")); } } - if (!modInfo.getCoreMods().isEmpty()) + 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 index 9591f2324a9..de82868305f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/CoreMod.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/CoreMod.java @@ -29,22 +29,31 @@ import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import org.jackhuang.hmcl.util.versioning.VersionRange; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Unmodifiable; import java.io.IOException; import java.io.InputStreamReader; import java.nio.file.Path; -import java.util.*; +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; -import static org.jackhuang.hmcl.util.versioning.GameVersionNumber.atLeast; -import static org.jackhuang.hmcl.util.versioning.GameVersionNumber.between; +/// @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) { @@ -63,9 +72,13 @@ public static List fromFile(Path modFile, ZipFileTree tree) { if (attr != null) { String fmlCorePlg = attr.getValue("FMLCorePlugin"); String tweakClass = attr.getValue("TweakClass"); - if (StringUtils.isNotBlank(fmlCorePlg) || StringUtils.isNotBlank(tweakClass)) { - coreMods.add(new CoreMod(ModLoaderType.FORGE, between("1.6.1", "1.12.2"))); - } + 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") + )); } } } @@ -80,9 +93,9 @@ public static List fromFile(Path modFile, ZipFileTree tree) { if (map != null && !map.isEmpty()) coreMods.addAll(List.of( // Removed in https://github.com/MinecraftForge/MinecraftForge/pull/10746 - new CoreMod(ModLoaderType.FORGE, between("1.13", "1.21.10")), + new CoreMod(ModLoaderType.FORGE, "1.13", "1.21.10"), // Removed in https://github.com/neoforged/NeoForge/pull/2072 - new CoreMod(ModLoaderType.NEO_FORGE, between("1.20.1", "1.21.4")) + 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); @@ -94,8 +107,8 @@ public static List fromFile(Path modFile, ZipFileTree tree) { // ITransformationService if (tree.getEntry("META-INF/services/cpw.mods.modlauncher.api.ITransformationService") != null) coreMods.addAll(List.of( - new CoreMod(ModLoaderType.FORGE, atLeast("1.13.2")), - new CoreMod(ModLoaderType.NEO_FORGE, between("1.20.1", "1.21.8")) // Replaced by ClassProcessorProvider + new CoreMod(ModLoaderType.FORGE, "1.13.2"), + new CoreMod(ModLoaderType.NEO_FORGE, "1.20.1", "1.21.8") // Replaced by ClassProcessorProvider )); } { @@ -104,28 +117,26 @@ public static List fromFile(Path modFile, ZipFileTree tree) { // 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, between("1.20.5", "1.21.8"))); // Replaced by ClassProcessorProvider + 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, atLeast("1.21.9"))); + coreMods.add(new CoreMod(ModLoaderType.NEO_FORGE, "1.21.9")); } return List.copyOf(coreMods); } @NotNull - @Unmodifiable - public static Set getSupportedLoaders(List coreMods, @Nullable String gameVersion) { - Set supportedLoaders = EnumSet.noneOf(ModLoaderType.class); - var version = GameVersionNumber.asGameVersion(Optional.ofNullable(gameVersion)); - if (version == GameVersionNumber.unknown()) return Set.of(); + 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(version)) + if (coreMod.mcVersionRange().contains(gameVersion)) supportedLoaders.add(coreMod.modLoaderType()); - return Set.copyOf(supportedLoaders); + return supportedLoaders; } } 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 d60541cde70..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 @@ -151,6 +151,10 @@ public String getLogoPath() { return logoPath; } + public boolean isCoreMod() { + return !coreMods.isEmpty(); + } + @NotNull @Unmodifiable public List getCoreMods() {