diff --git a/build.gradle b/build.gradle index 09496c4a7c..008a4b2438 100644 --- a/build.gradle +++ b/build.gradle @@ -20,8 +20,8 @@ plugins { id 'net.minecraftforge.gradleutils' version '2.+' id 'eclipse' id 'de.undercouch.download' version '4.1.2' - id 'net.minecraftforge.gradle.patcher' version '[5.1.39,)' apply false - id 'net.minecraftforge.gradle.mcp' version '[5.1.39,)' apply false + id 'net.minecraftforge.gradle.patcher' version '[5.1.43,)' apply false + id 'net.minecraftforge.gradle.mcp' version '[5.1.43,)' apply false } Util.init() //Init all our extension methods! @@ -65,6 +65,7 @@ ext { APACHE_COMMONS_LANG3_VERSION = '3.12.0' JOPT_SIMPLE_VERSION = '5.0.4' COMMONS_IO_VERSION = '2.11.0' + JARJAR_VERSION = '0.2.24' GIT_INFO = gradleutils.gitInfo VERSION = gradleutils.getFilteredMCTagOffsetBranchVersion(true, '[0-9]', MC_VERSION) @@ -75,7 +76,7 @@ ext { INSTALLER_TOOLS = 'net.minecraftforge:installertools:1.3.0' JAR_SPLITTER = 'net.minecraftforge:jarsplitter:1.1.4' FART = 'net.minecraftforge:ForgeAutoRenamingTool:0.1.22:all' - MIN_TAG_FOR_CHANGELOG = "39.0" + MIN_TAG_FOR_CHANGELOG = "41.0" } println('Version: ' + VERSION + @@ -283,6 +284,7 @@ def sharedDeps = { installer "org.jline:jline-terminal:${JLINE_VERSION}" // Dep of TerminalConsoleAppender installer "org.spongepowered:mixin:${MIXIN_VERSION}" installer 'org.openjdk.nashorn:nashorn-core:15.3' + installer "net.minecraftforge:JarJar:${JARJAR_VERSION}" /* installer 'org.lwjgl:lwjgl:3.2.2' diff --git a/fmlcore/src/main/java/net/minecraftforge/fml/ModLoader.java b/fmlcore/src/main/java/net/minecraftforge/fml/ModLoader.java index 21be77c0a1..5dc6eca83e 100644 --- a/fmlcore/src/main/java/net/minecraftforge/fml/ModLoader.java +++ b/fmlcore/src/main/java/net/minecraftforge/fml/ModLoader.java @@ -12,13 +12,13 @@ import net.minecraftforge.fml.loading.FMLEnvironment; import net.minecraftforge.fml.loading.FMLLoader; import net.minecraftforge.fml.loading.LoadingModList; import net.minecraftforge.fml.loading.moddiscovery.InvalidModIdentifier; -import net.minecraftforge.fml.loading.moddiscovery.ModFile; import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo; import net.minecraftforge.fml.loading.moddiscovery.ModInfo; import net.minecraftforge.fml.loading.progress.StartupMessageManager; import net.minecraftforge.forgespi.language.IModInfo; import net.minecraftforge.forgespi.language.IModLanguageProvider; import net.minecraftforge.forgespi.locating.ForgeFeature; +import net.minecraftforge.forgespi.locating.IModFile; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -27,7 +27,6 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.Executor; import java.util.function.BiConsumer; -import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; @@ -96,7 +95,7 @@ public class ModLoader .map(file -> new ModLoadingWarning(null, ModLoadingStage.VALIDATE, InvalidModIdentifier.identifyJarProblem(file.getFilePath()).orElse("fml.modloading.brokenfile"), file.getFileName())) .collect(Collectors.toList()); FMLLoader.getLoadingModList().getModFiles().stream() - .filter(ModFileInfo::missingLicense) //Search for files with missing licenses + .filter(ModFileInfo::missingLicense) .filter(modFileInfo -> modFileInfo.getMods().stream().noneMatch(thisModInfo -> this.loadingExceptions.stream().map(ModLoadingException::getModInfo).anyMatch(otherInfo -> otherInfo == thisModInfo))) //Ignore files where any other mod already encountered an error .map(modFileInfo -> new ModLoadingException(null, ModLoadingStage.VALIDATE, "fml.modloading.missinglicense", null, modFileInfo.getFile())) .forEach(this.loadingExceptions::add); @@ -239,7 +238,7 @@ public class ModLoader } } - private List buildMods(final ModFile modFile) + private List buildMods(final IModFile modFile) { final Map modInfoMap = modFile.getModFileInfo().getMods().stream().collect(Collectors.toMap(IModInfo::getModId, Function.identity())); @@ -261,7 +260,7 @@ public class ModLoader return containers.stream().filter(mc -> mc.modLoadingStage != ModLoadingStage.ERROR).collect(Collectors.toList()); } - private ModContainer buildModContainerFromTOML(final ModFile modFile, final Map modInfoMap, final Map.Entry idToProviderEntry) { + private ModContainer buildModContainerFromTOML(final IModFile modFile, final Map modInfoMap, final Map.Entry idToProviderEntry) { try { final String modId = idToProviderEntry.getKey(); final IModLanguageProvider.IModLanguageLoader languageLoader = idToProviderEntry.getValue(); diff --git a/fmlloader/build.gradle b/fmlloader/build.gradle index 2b13f4e2f5..d7a21c0200 100644 --- a/fmlloader/build.gradle +++ b/fmlloader/build.gradle @@ -28,6 +28,7 @@ dependencies { api("cpw.mods:modlauncher:${MODLAUNCHER_VERSION}") api("net.minecraftforge:coremods:${COREMODS_VERSION}") api("com.mojang:logging:${MOJANG_LOGGING_VERSION}") + api "net.minecraftforge:JarJar:${JARJAR_VERSION}" implementation("net.sf.jopt-simple:jopt-simple:${JOPT_SIMPLE_VERSION}") implementation("cpw.mods:securejarhandler:${SECUREJARHANDLER_VERSION}") diff --git a/fmlloader/src/main/java/net/minecraftforge/fml/loading/EarlyLoadingException.java b/fmlloader/src/main/java/net/minecraftforge/fml/loading/EarlyLoadingException.java index e151f2b146..371fe9e44b 100644 --- a/fmlloader/src/main/java/net/minecraftforge/fml/loading/EarlyLoadingException.java +++ b/fmlloader/src/main/java/net/minecraftforge/fml/loading/EarlyLoadingException.java @@ -47,7 +47,7 @@ public class EarlyLoadingException extends RuntimeException { return errorMessages; } - EarlyLoadingException(final String message, final Throwable originalException, List errorMessages) { + public EarlyLoadingException(final String message, final Throwable originalException, List errorMessages) { super(message, originalException); this.errorMessages = errorMessages; } diff --git a/fmlloader/src/main/java/net/minecraftforge/fml/loading/ModSorter.java b/fmlloader/src/main/java/net/minecraftforge/fml/loading/ModSorter.java index 1e705ba693..4529342547 100644 --- a/fmlloader/src/main/java/net/minecraftforge/fml/loading/ModSorter.java +++ b/fmlloader/src/main/java/net/minecraftforge/fml/loading/ModSorter.java @@ -18,7 +18,6 @@ import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo; import net.minecraftforge.fml.loading.moddiscovery.ModInfo; import net.minecraftforge.fml.loading.toposort.CyclePresentException; import net.minecraftforge.fml.loading.toposort.TopologicalSort; -import net.minecraftforge.forgespi.locating.IModFile; import org.apache.maven.artifact.versioning.ArtifactVersion; import org.apache.maven.artifact.versioning.DefaultArtifactVersion; import org.slf4j.Logger; @@ -36,14 +35,15 @@ import static net.minecraftforge.fml.loading.LogMarkers.LOADING; public class ModSorter { private static final Logger LOGGER = LogUtils.getLogger(); + private final UniqueModListBuilder uniqueModListBuilder; private List modFiles; private List sortedList; - private Map modIdNameLookup; + private Map modIdNameLookup; private List systemMods; private ModSorter(final List modFiles) { - this.modFiles = modFiles; + this.uniqueModListBuilder = new UniqueModListBuilder(modFiles); } public static LoadingModList sort(List mods, final List errors) @@ -78,11 +78,12 @@ public class ModSorter // lambdas are identity based, so sorting them is impossible unless you hold reference to them final MutableGraph graph = GraphBuilder.directed().build(); AtomicInteger counter = new AtomicInteger(); - Map infos = modFiles.stream() + Map infos = modFiles.stream() .map(ModFile::getModFileInfo) .filter(ModFileInfo.class::isInstance) + .map(ModFileInfo.class::cast) .collect(toMap(Function.identity(), e -> counter.incrementAndGet())); - infos.keySet().forEach(i -> graph.addNode((ModFileInfo) i)); + infos.keySet().forEach(graph::addNode); modFiles.stream() .map(ModFile::getModInfos) .mapMulti(Iterable::forEach) @@ -124,10 +125,9 @@ public class ModSorter private void addDependency(MutableGraph topoGraph, IModInfo.ModVersion dep) { final ModFileInfo self = (ModFileInfo)dep.getOwner().getOwningFile(); - final ModInfo targetModInfo = modIdNameLookup.get(dep.getModId()); + final IModInfo targetModInfo = modIdNameLookup.get(dep.getModId()); // soft dep that doesn't exist. Just return. No edge required. - if (targetModInfo == null) return; - final ModFileInfo target = targetModInfo.getOwningFile(); + if (targetModInfo == null || !(targetModInfo.getOwningFile() instanceof final ModFileInfo target)) return; if (self == target) return; // in case a jar has two mods that have dependencies between switch (dep.getOrdering()) { @@ -138,10 +138,22 @@ public class ModSorter private void buildUniqueList() { - // Collect mod files by module name. This will be used for deduping purposes - final Map> modFilesByFirstId = modFiles.stream() - .collect(groupingBy(mf -> mf.getModFileInfo().moduleName())); + final UniqueModListBuilder.UniqueModListData uniqueModListData = uniqueModListBuilder.buildUniqueList(); + this.modFiles = uniqueModListData.modFiles(); + + detectSystemMods(uniqueModListData.modFilesByFirstId()); + + modIdNameLookup = uniqueModListData.modFilesByFirstId().entrySet().stream() + .filter(e -> !e.getValue().get(0).getModInfos().isEmpty()) + .collect(Collectors.toMap( + Map.Entry::getKey, + e -> e.getValue().get(0).getModInfos().get(0) + )); + } + + private void detectSystemMods(final Map> modFilesByFirstId) + { // Capture system mods (ex. MC, Forge) here, so we can keep them for later final Set systemMods = new HashSet<>(); // The minecraft mod is always a system mod @@ -168,47 +180,6 @@ public class ModSorter throw new IllegalStateException("Failed to find system mod: " + systemMod); } } - - // Select the newest by artifact version sorting of non-unique files thus identified - this.modFiles = modFilesByFirstId.entrySet().stream() - .map(this::selectNewestModInfo) - .map(Map.Entry::getValue) - .map(ModFile.class::cast) - .collect(toList()); - - // Transform to the full mod id list - final Map> modIds = modFiles.stream() - .map(ModFile::getModInfos) - .flatMap(Collection::stream) - .map(ModInfo.class::cast) - .collect(groupingBy(IModInfo::getModId)); - - // Its theoretically possible that some mod has somehow moved an id to a secondary place, thus causing a dupe. - // We can't handle this - final List dupedMods = modIds.values().stream() - .filter(modInfos -> modInfos.size() > 1) - .map(modInfos -> modInfos.get(0)) - .toList(); - - if (!dupedMods.isEmpty()) { - final List duplicateModErrors = dupedMods.stream() - .map(dm -> new EarlyLoadingException.ExceptionData("fml.modloading.dupedmod", - dm, Objects.toString(dm))).toList(); - throw new EarlyLoadingException("Duplicate mods found", null, duplicateModErrors); - } - - modIdNameLookup = modIds.entrySet().stream() - .collect(toMap(Map.Entry::getKey, e -> e.getValue().get(0))); - } - - private Map.Entry selectNewestModInfo(Map.Entry> fullList) { - List modInfoList = fullList.getValue(); - if (modInfoList.size() > 1) { - LOGGER.debug("Found {} mods for first modid {}, selecting most recent based on version data", modInfoList.size(), fullList.getKey()); - modInfoList.sort(Comparator.comparing(mf -> mf.getModInfos().get(0).getVersion()).reversed()); - LOGGER.debug("Selected file {} for modid {} with version {}", modInfoList.get(0).getFileName(), fullList.getKey(), modInfoList.get(0).getModInfos().get(0).getVersion()); - } - return Map.entry(fullList.getKey(), modInfoList.get(0)); } private List verifyDependencyVersions() diff --git a/fmlloader/src/main/java/net/minecraftforge/fml/loading/UniqueModListBuilder.java b/fmlloader/src/main/java/net/minecraftforge/fml/loading/UniqueModListBuilder.java new file mode 100644 index 0000000000..bf8431c247 --- /dev/null +++ b/fmlloader/src/main/java/net/minecraftforge/fml/loading/UniqueModListBuilder.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.fml.loading; + +import com.mojang.logging.LogUtils; +import net.minecraftforge.fml.loading.moddiscovery.ModFile; +import net.minecraftforge.forgespi.language.IModInfo; +import org.apache.maven.artifact.versioning.ArtifactVersion; +import org.slf4j.Logger; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static java.util.stream.Collectors.*; + +public class UniqueModListBuilder +{ + private final static Logger LOGGER = LogUtils.getLogger(); + + private final List modFiles; + + public UniqueModListBuilder(final List modFiles) {this.modFiles = modFiles;} + + public UniqueModListData buildUniqueList() + { + List uniqueModList; + List uniqueLibListWithVersion; + + // Collect mod files by module name. This will be used for deduping purposes + final Map> modFilesByFirstId = modFiles.stream() + .filter(mf -> mf.getModFileInfo() != null) + .collect(groupingBy(UniqueModListBuilder::getModId)); + + final Map> libFilesWithVersionByModuleName = modFiles.stream() + .filter(mf -> mf.getModFileInfo() == null) + .collect(groupingBy(UniqueModListBuilder::getModId)); + + // Select the newest by artifact version sorting of non-unique files thus identified + uniqueModList = modFilesByFirstId.entrySet().stream() + .map(this::selectNewestModInfo) + .toList(); + + // Select the newest by artifact version sorting of non-unique files thus identified + uniqueLibListWithVersion = libFilesWithVersionByModuleName.entrySet().stream() + .map(this::selectNewestModInfo) + .toList(); + + // Transform to the full mod id list + final Map> modIds = uniqueModList.stream() + .filter(mf -> mf.getModFileInfo() != null) //Filter out non-mod files, we don't care about those for now..... + .map(ModFile::getModInfos) + .flatMap(Collection::stream) + .collect(groupingBy(IModInfo::getModId)); + + // Transform to the full lib id list + final Map> versionedLibIds = uniqueLibListWithVersion.stream() + .map(UniqueModListBuilder::getModId) + .collect(Collectors.toMap( + Function.identity(), + libFilesWithVersionByModuleName::get + )); + + // Its theoretically possible that some mod has somehow moved an id to a secondary place, thus causing a dupe. + // We can't handle this + final List dupedMods = modIds.values().stream() + .filter(modInfos -> modInfos.size() > 1) + .map(modInfos -> modInfos.get(0)) + .toList(); + + if (!dupedMods.isEmpty()) { + final List duplicateModErrors = dupedMods.stream() + .map(dm -> new EarlyLoadingException.ExceptionData("fml.modloading.dupedmod", dm, Objects.toString(dm))) + .toList(); + throw new EarlyLoadingException("Duplicate mods found", null, duplicateModErrors); + } + + final List dupedLibs = versionedLibIds.values().stream() + .filter(modFiles -> modFiles.size() > 1) + .map(modFiles -> modFiles.get(0)) + .toList(); + + if (!dupedLibs.isEmpty()) { + final List duplicateLibErrors = dupedLibs.stream() + .map(dm -> new EarlyLoadingException.ExceptionData("fml.modloading.dupedlib.versioned", dm, Objects.toString(dm))) + .toList(); + throw new EarlyLoadingException("Duplicate plugins or libraries found", null, duplicateLibErrors); + } + + // Collect unique mod files by module name. This will be used for deduping purposes + final Map> uniqueModFilesByFirstId = uniqueModList.stream() + .collect(groupingBy(UniqueModListBuilder::getModId)); + + final List loadedList = new ArrayList<>(); + loadedList.addAll(uniqueModList); + loadedList.addAll(uniqueLibListWithVersion); + + return new UniqueModListData(loadedList, uniqueModFilesByFirstId); + } + + private ModFile selectNewestModInfo(Map.Entry> fullList) { + List modInfoList = fullList.getValue(); + if (modInfoList.size() > 1) { + LOGGER.debug("Found {} mods for first modid {}, selecting most recent based on version data", modInfoList.size(), fullList.getKey()); + modInfoList.sort(Comparator.comparing(this::getVersion).reversed()); + LOGGER.debug("Selected file {} for modid {} with version {}", modInfoList.get(0).getFileName(), fullList.getKey(), modInfoList.get(0).getModInfos().get(0).getVersion()); + } + return modInfoList.get(0); + } + + private ArtifactVersion getVersion(final ModFile mf) + { + if (mf.getModFileInfo() == null) { + return mf.getJarVersion(); + } + return mf.getModInfos().get(0).getVersion(); + } + + private static String getModId(ModFile modFile) { + if (modFile.getModFileInfo() == null || modFile.getModFileInfo().getMods().isEmpty()) { + return modFile.getSecureJar().name(); + } + + return modFile.getModFileInfo().moduleName(); + } + + public record UniqueModListData(List modFiles, Map> modFilesByFirstId) {} + +} diff --git a/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/AbstractJarFileDependencyLocator.java b/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/AbstractJarFileDependencyLocator.java new file mode 100644 index 0000000000..8e9c652cd0 --- /dev/null +++ b/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/AbstractJarFileDependencyLocator.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.fml.loading.moddiscovery; + +import com.mojang.logging.LogUtils; +import net.minecraftforge.forgespi.locating.IDependencyLocator; +import net.minecraftforge.forgespi.locating.IModFile; +import org.slf4j.Logger; + +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; + +public abstract class AbstractJarFileDependencyLocator extends AbstractJarFileModProvider implements IDependencyLocator +{ + private static final Logger LOGGER = LogUtils.getLogger(); + + protected Optional loadResourceFromModFile(final IModFile modFile, final Path path) { + try { + return Optional.of(Files.newInputStream(modFile.findResource(path.toString()))); + } + catch (final FileNotFoundException e) { + LOGGER.debug("Failed to load resource {} from {}, it does not contain dependency information.", path, modFile.getFileName()); + return Optional.empty(); + } + catch (final Exception e) { + LOGGER.error("Failed to load resource {} from mod {}, cause {}", path, modFile.getFileName(), e); + return Optional.empty(); + } + } + + protected Optional loadModFileFrom(final IModFile file, final Path path) { + try { + final Path pathInModFile = file.findResource(path.toString()); + return createMod(pathInModFile); + } + catch (Exception e) { + LOGGER.error("Failed to load mod file {} from {}", path, file.getFileName()); + return Optional.empty(); + } + } + + protected String identifyMod(final IModFile modFile) { + return modFile.getFileName(); + } +} diff --git a/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/AbstractJarFileModLocator.java b/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/AbstractJarFileModLocator.java new file mode 100644 index 0000000000..43700f3199 --- /dev/null +++ b/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/AbstractJarFileModLocator.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.fml.loading.moddiscovery; + +import net.minecraftforge.forgespi.locating.IModFile; +import net.minecraftforge.forgespi.locating.IModLocator; + +import java.nio.file.Path; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public abstract class AbstractJarFileModLocator extends AbstractJarFileModProvider implements IModLocator +{ + @Override + public List scanMods() + { + return scanCandidates() + .map(this::createMod) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + } + + public abstract Stream scanCandidates(); +} diff --git a/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/AbstractJarFileLocator.java b/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/AbstractJarFileModProvider.java similarity index 74% rename from fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/AbstractJarFileLocator.java rename to fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/AbstractJarFileModProvider.java index 14b0c88591..3a337fd5ff 100644 --- a/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/AbstractJarFileLocator.java +++ b/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/AbstractJarFileModProvider.java @@ -7,20 +7,19 @@ package net.minecraftforge.fml.loading.moddiscovery; import com.mojang.logging.LogUtils; import cpw.mods.jarhandling.SecureJar; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.List; -import java.util.Optional; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; import net.minecraftforge.fml.loading.LogMarkers; import net.minecraftforge.forgespi.locating.IModFile; import org.slf4j.Logger; -public abstract class AbstractJarFileLocator extends AbstractModLocator { +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Stream; + +public abstract class AbstractJarFileModProvider extends AbstractModProvider +{ private static final Logger LOGGER = LogUtils.getLogger(); @Override @@ -34,15 +33,4 @@ public abstract class AbstractJarFileLocator extends AbstractModLocator { } LOGGER.debug(LogMarkers.SCAN,"Scan finished: {}", file); } - - @Override - public List scanMods() { - return scanCandidates() - .map(this::createMod) - .filter(Optional::isPresent) - .map(Optional::get) - .collect(Collectors.toList()); - } - - public abstract Stream scanCandidates(); } diff --git a/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/AbstractModLocator.java b/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/AbstractModProvider.java similarity index 56% rename from fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/AbstractModLocator.java rename to fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/AbstractModProvider.java index 97b9a3ddc2..c2212627e2 100644 --- a/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/AbstractModLocator.java +++ b/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/AbstractModProvider.java @@ -13,7 +13,7 @@ import net.minecraftforge.forgespi.language.IConfigurable; import net.minecraftforge.forgespi.language.IModFileInfo; import net.minecraftforge.forgespi.language.IModInfo; import net.minecraftforge.forgespi.locating.IModFile; -import net.minecraftforge.forgespi.locating.IModLocator; +import net.minecraftforge.forgespi.locating.IModProvider; import org.slf4j.Logger; import java.nio.file.Path; @@ -24,8 +24,9 @@ import java.util.Optional; import java.util.function.Function; import java.util.jar.Manifest; -public abstract class AbstractModLocator implements IModLocator { - private static final Logger LOGGER = LogUtils.getLogger(); +public abstract class AbstractModProvider implements IModProvider +{ + private static final Logger LOGGER = LogUtils.getLogger(); protected static final String MODS_TOML = "META-INF/mods.toml"; protected static final String MANIFEST = "META-INF/MANIFEST.MF"; @@ -40,12 +41,15 @@ public abstract class AbstractModLocator implements IModLocator { IModFile mod; var type = sj.getManifest().getMainAttributes().getValue(ModFile.TYPE); + if (type == null) { + type = getDefaultJarModType(); + } if (sj.findFile(MODS_TOML).isPresent()) { LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", MODS_TOML, type, path); mod = new ModFile(sj, this, ModFileParser::modsTomlParser); } else if (type != null) { LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", MANIFEST, type, path); - mod = new ModFile(sj, this, this::manifestParser); + mod = new ModFile(sj, this, this::manifestParser, type); } else { return Optional.empty(); } @@ -68,29 +72,49 @@ public abstract class AbstractModLocator implements IModLocator { } }; - return new IModFileInfo() { - @Override public List getMods() { return Collections.emptyList(); } - @Override public List requiredLanguageLoaders() { return Collections.emptyList(); } - @Override public boolean showAsResourcePack() { return false; } - @Override public Map getFileProperties() { return Collections.emptyMap(); } - @Override public String getLicense() { return license; } - @Override public IModFile getFile() { return mod; } - @Override public IConfigurable getConfig() { return dummy; } - - // These Should never be called as it's only called from ModJarMetadata.version and we bypass that - @Override public String moduleName() { return mod.getSecureJar().name(); } - @Override public String versionString() { return null; } - @Override public List usesServices() { return null; } - - @Override - public String toString() { - return "IModFileInfo(" + mod.getFilePath() + ")"; - } - }; + return new DefaultModFileInfo(mod, license, dummy); } @Override public boolean isValid(final IModFile modFile) { return true; } + + protected String getDefaultJarModType() { + return null; + } + + private record DefaultModFileInfo(IModFile mod, String license, IConfigurable configurable) implements IModFileInfo, IConfigurable { + @Override + public Optional getConfigElement(final String... strings) + { + return Optional.empty(); + } + + @Override + public List getConfigList(final String... strings) + { + return null; + } + + @Override public List getMods() { return Collections.emptyList(); } + @Override public List requiredLanguageLoaders() { return Collections.emptyList(); } + @Override public boolean showAsResourcePack() { return false; } + @Override public Map getFileProperties() { return Collections.emptyMap(); } + @Override + public String getLicense() { return license; } + + @Override public IModFile getFile() { return mod; } + @Override public IConfigurable getConfig() { return configurable; } + + // These Should never be called as it's only called from ModJarMetadata.version and we bypass that + @Override public String moduleName() { return mod.getSecureJar().name(); } + @Override public String versionString() { return null; } + @Override public List usesServices() { return null; } + + @Override + public String toString() { + return "IModFileInfo(" + mod.getFilePath() + ")"; + } + } } diff --git a/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/ClasspathLocator.java b/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/ClasspathLocator.java index 0283bf3b01..bf5242cbbe 100644 --- a/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/ClasspathLocator.java +++ b/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/ClasspathLocator.java @@ -24,7 +24,8 @@ import java.util.List; import java.util.Map; import java.util.stream.Stream; -public class ClasspathLocator extends AbstractJarFileLocator { +public class ClasspathLocator extends AbstractJarFileModLocator +{ private static final Logger LOGGER = LogUtils.getLogger(); private final List legacyClasspath = Arrays.stream(System.getProperty("legacyClassPath", "").split(File.pathSeparator)).map(Path::of).toList(); private boolean enabled = false; diff --git a/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/JarInJarDependencyLocator.java b/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/JarInJarDependencyLocator.java new file mode 100644 index 0000000000..4c0c4e6569 --- /dev/null +++ b/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/JarInJarDependencyLocator.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.fml.loading.moddiscovery; + +import com.google.common.collect.Lists; +import com.mojang.logging.LogUtils; +import net.minecraftforge.fml.loading.EarlyLoadingException; +import net.minecraftforge.forgespi.language.IModInfo; +import net.minecraftforge.forgespi.locating.IModFile; +import net.minecraftforge.jarjar.selection.JarSelector; +import org.apache.maven.artifact.versioning.ArtifactVersion; +import org.apache.maven.artifact.versioning.VersionRange; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class JarInJarDependencyLocator extends AbstractJarFileDependencyLocator +{ + private static final Logger LOGGER = LogUtils.getLogger(); + + @Override + public String name() + { + return "JarInJar"; + } + + @Override + public List scanMods(final Iterable loadedMods) { + final List sources = Lists.newArrayList(); + loadedMods.forEach(sources::add); + + + final List dependenciesToLoad = JarSelector.detectAndSelect( + sources, + this::loadResourceFromModFile, + this::loadModFileFrom, + this::identifyMod, + this::exception + ); + + if (dependenciesToLoad.isEmpty()) { + LOGGER.info("No dependencies to load found. Skipping!"); + return Collections.emptyList(); + } + + LOGGER.info("Found {} dependencies adding them to mods collection", dependenciesToLoad.size()); + return dependenciesToLoad; + } + + @Override + public void initArguments(final Map arguments) + { + // NO-OP, for now + } + + @Override + protected String getDefaultJarModType() + { + return IModFile.Type.GAMELIBRARY.name(); + } + + protected EarlyLoadingException exception(Collection> failedDependencies) + { + + final List errors = failedDependencies.stream() + .filter(entry -> !entry.sources().isEmpty()) //Should never be the case, but just to be sure + .map(entry -> new EarlyLoadingException.ExceptionData( + getErrorTranslationKey(entry), + entry.identifier().group() + ":" + entry.identifier().artifact(), + entry.sources() + .stream() + .flatMap(this::getModWithVersionRangeStream) + .map(this::formatError) + .collect(Collectors.joining(", ")))).toList(); + + return new EarlyLoadingException(failedDependencies.size() + " Dependency restrictions were not met.", null, errors); + } + + @NotNull + private String getErrorTranslationKey(final JarSelector.ResolutionFailureInformation entry) + { + return entry.failureReason() == JarSelector.FailureReason.VERSION_RESOLUTION_FAILED + ? "fml.dependencyloading.conflictingdependencies" + : "fml.dependencyloading.mismatchedcontaineddependencies"; + } + + @NotNull + private Stream getModWithVersionRangeStream(final JarSelector.SourceWithRequestedVersionRange file) + { + return file.source().getModFileInfo().getMods().stream().map(modInfo -> new ModWithVersionRange(modInfo, file.requestedVersionRange(), file.includedVersion())); + } + + @NotNull + private String formatError(final ModWithVersionRange modWithVersionRange) + { + return "\u00a7e" + modWithVersionRange.modInfo().getModId() + "\u00a7r - \u00a74" + + modWithVersionRange.versionRange().toString() + "\u00a74 - \u00a72" + + modWithVersionRange.artifactVersion().toString() + "\u00a72"; + } + + private record ModWithVersionRange(IModInfo modInfo, VersionRange versionRange, ArtifactVersion artifactVersion) {} +} diff --git a/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/MavenDirectoryLocator.java b/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/MavenDirectoryLocator.java index 94ebc4d07d..e8afa04a53 100644 --- a/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/MavenDirectoryLocator.java +++ b/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/MavenDirectoryLocator.java @@ -15,7 +15,8 @@ import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; -public class MavenDirectoryLocator extends AbstractJarFileLocator { +public class MavenDirectoryLocator extends AbstractJarFileModLocator +{ private List modCoords; @Override diff --git a/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/MinecraftLocator.java b/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/MinecraftLocator.java index ea527c050b..0fe7a4386b 100644 --- a/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/MinecraftLocator.java +++ b/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/MinecraftLocator.java @@ -12,6 +12,7 @@ import net.minecraftforge.fml.loading.FMLLoader; import net.minecraftforge.fml.loading.LogMarkers; import net.minecraftforge.forgespi.language.IModFileInfo; import net.minecraftforge.forgespi.locating.IModFile; +import net.minecraftforge.forgespi.locating.IModLocator; import net.minecraftforge.forgespi.locating.ModFileFactory; import org.slf4j.Logger; @@ -22,7 +23,8 @@ import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; -public class MinecraftLocator extends AbstractModLocator { +public class MinecraftLocator extends AbstractModProvider implements IModLocator +{ private static final Logger LOGGER = LogUtils.getLogger(); @Override diff --git a/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/ModDiscoverer.java b/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/ModDiscoverer.java index db90d1881c..71e3bf301d 100644 --- a/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/ModDiscoverer.java +++ b/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/ModDiscoverer.java @@ -5,13 +5,18 @@ package net.minecraftforge.fml.loading.moddiscovery; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Maps; import com.mojang.logging.LogUtils; import cpw.mods.modlauncher.Launcher; import cpw.mods.modlauncher.api.IModuleLayerManager; import cpw.mods.modlauncher.util.ServiceLoaderUtils; +import net.minecraftforge.fml.loading.EarlyLoadingException; import net.minecraftforge.fml.loading.LogMarkers; +import net.minecraftforge.fml.loading.UniqueModListBuilder; import net.minecraftforge.fml.loading.progress.StartupMessageManager; import net.minecraftforge.forgespi.Environment; +import net.minecraftforge.forgespi.locating.IDependencyLocator; import net.minecraftforge.forgespi.locating.IModFile; import net.minecraftforge.forgespi.locating.IModLocator; import org.slf4j.Logger; @@ -24,47 +29,130 @@ import java.util.stream.Collectors; public class ModDiscoverer { private static final Logger LOGGER = LogUtils.getLogger(); - private final ServiceLoader locators; - private final List locatorList; + private final ServiceLoader modLocators; + private final ServiceLoader dependencyLocators; + private final List modLocatorList; + private final List dependencyLocatorList; public ModDiscoverer(Map arguments) { Launcher.INSTANCE.environment().computePropertyIfAbsent(Environment.Keys.MODDIRECTORYFACTORY.get(), v->ModsFolderLocator::new); Launcher.INSTANCE.environment().computePropertyIfAbsent(Environment.Keys.PROGRESSMESSAGE.get(), v-> StartupMessageManager.locatorConsumer().orElseGet(()-> s->{})); final var moduleLayerManager = Launcher.INSTANCE.environment().findModuleLayerManager().orElseThrow(); - locators = ServiceLoader.load(moduleLayerManager.getLayer(IModuleLayerManager.Layer.SERVICE).orElseThrow(), IModLocator.class); - locatorList = ServiceLoaderUtils.streamServiceLoader(()->locators, sce->LOGGER.error("Failed to load locator list", sce)).collect(Collectors.toList()); - locatorList.forEach(l->l.initArguments(arguments)); + modLocators = ServiceLoader.load(moduleLayerManager.getLayer(IModuleLayerManager.Layer.SERVICE).orElseThrow(), IModLocator.class); + dependencyLocators = ServiceLoader.load(moduleLayerManager.getLayer(IModuleLayerManager.Layer.SERVICE).orElseThrow(), IDependencyLocator.class); + modLocatorList = ServiceLoaderUtils.streamServiceLoader(()-> modLocators, sce->LOGGER.error("Failed to load mod locator list", sce)).collect(Collectors.toList()); + modLocatorList.forEach(l->l.initArguments(arguments)); + dependencyLocatorList = ServiceLoaderUtils.streamServiceLoader(()-> dependencyLocators, sce->LOGGER.error("Failed to load dependency locator list", sce)).collect(Collectors.toList()); + dependencyLocatorList.forEach(l->l.initArguments(arguments)); if (LOGGER.isDebugEnabled(LogMarkers.CORE)) { - LOGGER.debug(LogMarkers.CORE, "Found Mod Locators : {}", locatorList.stream() - .map(iModLocator -> "(" + iModLocator.name() + ":" + iModLocator.getClass().getPackage().getImplementationVersion() + ")").collect(Collectors.joining(","))); + LOGGER.debug(LogMarkers.CORE, "Found Mod Locators : {}", modLocatorList.stream() + .map(modLocator -> "(%s:%s)".formatted(modLocator.name(), + modLocator.getClass().getPackage().getImplementationVersion())).collect(Collectors.joining(","))); + } + if (LOGGER.isDebugEnabled(LogMarkers.CORE)) + { + LOGGER.debug(LogMarkers.CORE, "Found Dependency Locators : {}", dependencyLocatorList.stream() + .map(dependencyLocator -> "(%s:%s)".formatted(dependencyLocator.name(), + dependencyLocator.getClass().getPackage().getImplementationVersion())).collect(Collectors.joining(","))); } - } - - ModDiscoverer(List locatorList) { - this.locatorList = locatorList; - this.locators = null; } public ModValidator discoverMods() { - LOGGER.debug(LogMarkers.SCAN,"Scanning for mods and other resources to load. We know {} ways to find mods", locatorList.size()); - var loadedFiles = new ArrayList<>(); - for (IModLocator locator : locatorList) { - LOGGER.debug(LogMarkers.SCAN,"Trying locator {}", locator); - var modFiles = locator.scanMods(); - for (IModFile mf : modFiles) { - LOGGER.info(LogMarkers.SCAN, "Found mod file {} of type {} with provider {}", mf.getFileName(), mf.getType(), mf.getProvider()); - StartupMessageManager.modLoaderConsumer().ifPresent(c->c.accept("Found mod file "+mf.getFileName()+" of type "+mf.getType())); - } - loadedFiles.addAll(modFiles); - } - final var modFilesMap = loadedFiles.stream() - .map(ModFile.class::cast) - .collect(Collectors.groupingBy(IModFile::getType)); + LOGGER.debug(LogMarkers.SCAN,"Scanning for mods and other resources to load. We know {} ways to find mods", modLocatorList.size()); + List loadedFiles = new ArrayList<>(); + List discoveryErrorData = new ArrayList<>(); + boolean successfullyLoadedMods = true; - var validator = new ModValidator(modFilesMap); + //Loop all mod locators to get the prime mods to load from. + for (IModLocator locator : modLocatorList) { + try { + LOGGER.debug(LogMarkers.SCAN,"Trying locator {}", locator); + var locatedFiles = locator.scanMods(); + + if (locatedFiles.stream().anyMatch(file -> !(file instanceof ModFile))) { + LOGGER.error(LogMarkers.SCAN, "A mod locator returned a file which is not a ModFile instance!. They will be skipped!"); + } + + handleLocatedFiles(loadedFiles, locatedFiles); + } catch (EarlyLoadingException exception) { + LOGGER.error(LogMarkers.SCAN, "Failed to load mods with locator {}", locator, exception); + discoveryErrorData.addAll(exception.getAllData()); + } + } + + //First processing run of the mod list. Any duplicates will cause resolution failure and dependency loading will be skipped. + Map> modFilesMap = Maps.newHashMap(); + try { + final UniqueModListBuilder modsUniqueListBuilder = new UniqueModListBuilder(loadedFiles); + final UniqueModListBuilder.UniqueModListData uniqueModsData = modsUniqueListBuilder.buildUniqueList(); + + //Grab the temporary results. + //This allows loading to continue to a base state, in case dependency loading fails. + modFilesMap = uniqueModsData.modFiles().stream() + .collect(Collectors.groupingBy(IModFile::getType)); + loadedFiles = uniqueModsData.modFiles(); + } + catch (EarlyLoadingException exception) { + LOGGER.error(LogMarkers.SCAN, "Failed to build unique mod list after mod discovery.", exception); + discoveryErrorData.addAll(exception.getAllData()); + successfullyLoadedMods = false; + } + + //We can continue loading if prime mods loaded successfully. + if (successfullyLoadedMods) { + LOGGER.debug(LogMarkers.SCAN, "Successfully Loaded {} mods. Attempting to load dependencies...", loadedFiles.size()); + for (IDependencyLocator locator : dependencyLocatorList) { + try { + LOGGER.debug(LogMarkers.SCAN,"Trying locator {}", locator); + final List locatedMods = ImmutableList.copyOf(loadedFiles); + + var locatedFiles = locator.scanMods(locatedMods); + if (locatedFiles.stream().anyMatch(file -> !(file instanceof ModFile))) { + LOGGER.error(LogMarkers.SCAN, "A dependency locator returned a file which is not a ModFile instance!. They will be skipped!"); + } + + handleLocatedFiles(loadedFiles, locatedFiles); + } + catch (EarlyLoadingException exception) { + LOGGER.error(LogMarkers.SCAN, "Failed to load dependencies with locator {}", locator, exception); + discoveryErrorData.addAll(exception.getAllData()); + } + } + + //Second processing run of the mod list. Any duplicates will cause resolution failure and only the mods list will be loaded. + try { + final UniqueModListBuilder modsAndDependenciesUniqueListBuilder = new UniqueModListBuilder(loadedFiles); + final UniqueModListBuilder.UniqueModListData uniqueModsAndDependenciesData = modsAndDependenciesUniqueListBuilder.buildUniqueList(); + + //We now only need the mod files map, not the list. + modFilesMap = uniqueModsAndDependenciesData.modFiles().stream() + .collect(Collectors.groupingBy(IModFile::getType)); + } catch (EarlyLoadingException exception) { + LOGGER.error(LogMarkers.SCAN, "Failed to build unique mod list after dependency discovery.", exception); + discoveryErrorData.addAll(exception.getAllData()); + modFilesMap = loadedFiles.stream().collect(Collectors.groupingBy(IModFile::getType)); + } + } + else { + //Failure notify the listeners. + LOGGER.error(LogMarkers.SCAN, "Mod Discovery failed. Skipping dependency discovery."); + } + + //Validate the loading. With a deduplicated list, we can now successfully process the artifacts and load + //transformer plugins. + var validator = new ModValidator(modFilesMap, discoveryErrorData); validator.stage1Validation(); return validator; } + private void handleLocatedFiles(final List loadedFiles, final List locatedFiles) + { + var locatedModFiles = locatedFiles.stream().filter(ModFile.class::isInstance).map(ModFile.class::cast).toList(); + for (IModFile mf : locatedModFiles) { + LOGGER.info(LogMarkers.SCAN, "Found mod file {} of type {} with provider {}", mf.getFileName(), mf.getType(), mf.getProvider()); + StartupMessageManager.modLoaderConsumer().ifPresent(c->c.accept("Found mod file "+mf.getFileName()+" of type "+mf.getType())); + } + loadedFiles.addAll(locatedModFiles); + } } diff --git a/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/ModFile.java b/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/ModFile.java index d7fef5beff..888f046038 100644 --- a/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/ModFile.java +++ b/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/ModFile.java @@ -18,6 +18,8 @@ import net.minecraftforge.forgespi.language.ModFileScanData; import net.minecraftforge.forgespi.locating.IModFile; import net.minecraftforge.forgespi.locating.IModProvider; import net.minecraftforge.forgespi.locating.ModFileFactory; +import org.apache.maven.artifact.versioning.ArtifactVersion; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; import org.slf4j.Logger; import java.nio.file.Files; @@ -51,9 +53,9 @@ public class ModFile implements IModFile { private Throwable scanError; private final SecureJar jar; private final Type modFileType; - private final Manifest manifest; + private final Manifest manifest; private final IModProvider provider; - private IModFileInfo modFileInfo; + private IModFileInfo modFileInfo; private ModFileScanData fileModFileScanData; private CompletableFuture futureScanResult; private List coreMods; @@ -63,14 +65,18 @@ public class ModFile implements IModFile { private SecureJar.Status securityStatus; public ModFile(final SecureJar jar, final IModProvider provider, final ModFileFactory.ModFileInfoParser parser) { + this(jar, provider, parser, parseType(jar)); + } + + public ModFile(final SecureJar jar, final IModProvider provider, final ModFileFactory.ModFileInfoParser parser, String type) { this.provider = provider; this.jar = jar; this.parser = parser; manifest = this.jar.getManifest(); - final Optional value = Optional.ofNullable(manifest.getMainAttributes().getValue(TYPE)); - modFileType = Type.valueOf(value.orElse("MOD")); + modFileType = Type.valueOf(type); jarVersion = Optional.ofNullable(manifest.getMainAttributes().getValue(Attributes.Name.IMPLEMENTATION_VERSION)).orElse("0.0NONE"); + this.modFileInfo = ModFileParser.readModList(this, this.parser); } @Override @@ -189,7 +195,7 @@ public class ModFile implements IModFile { @Override public IModProvider getProvider() { - return this.provider; + return provider; } @Override @@ -201,4 +207,15 @@ public class ModFile implements IModFile { public void setSecurityStatus(final SecureJar.Status status) { this.securityStatus = status; } + + public ArtifactVersion getJarVersion() + { + return new DefaultArtifactVersion(this.jarVersion); + } + + private static String parseType(final SecureJar jar) { + final Manifest m = jar.getManifest(); + final Optional value = Optional.ofNullable(m.getMainAttributes().getValue(TYPE)); + return value.orElse("MOD"); + } } diff --git a/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/ModValidator.java b/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/ModValidator.java index a830ce0f85..b7417fb6fe 100644 --- a/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/ModValidator.java +++ b/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/ModValidator.java @@ -26,13 +26,15 @@ public class ModValidator { private final List candidateMods; private LoadingModList loadingModList; private List brokenFiles; + private final List discoveryErrorData; - public ModValidator(final Map> modFiles) { + public ModValidator(final Map> modFiles, final List discoveryErrorData) { this.modFiles = modFiles; this.candidateMods = lst(modFiles.get(IModFile.Type.MOD)); this.candidateMods.addAll(lst(modFiles.get(IModFile.Type.GAMELIBRARY))); this.candidatePlugins = lst(modFiles.get(IModFile.Type.LANGPROVIDER)); this.candidatePlugins.addAll(lst(modFiles.get(IModFile.Type.LIBRARY))); + this.discoveryErrorData = discoveryErrorData; } private static List lst(List files) { @@ -52,22 +54,22 @@ public class ModValidator { final List brokenFiles = new ArrayList<>(); for (Iterator iterator = mods.iterator(); iterator.hasNext(); ) { - ModFile mod = iterator.next(); - if (!mod.getProvider().isValid(mod) || !mod.identifyMods()) { - LOGGER.warn(LogMarkers.SCAN, "File {} has been ignored - it is invalid", mod.getFilePath()); + ModFile modFile = iterator.next(); + if (!modFile.getProvider().isValid(modFile) || !modFile.identifyMods()) { + LOGGER.warn(LogMarkers.SCAN, "File {} has been ignored - it is invalid", modFile.getFilePath()); iterator.remove(); - brokenFiles.add(mod); + brokenFiles.add(modFile); } } return brokenFiles; } public ITransformationService.Resource getPluginResources() { - return new ITransformationService.Resource(IModuleLayerManager.Layer.PLUGIN, this.candidatePlugins.stream().map(ModFile::getSecureJar).toList()); + return new ITransformationService.Resource(IModuleLayerManager.Layer.PLUGIN, this.candidatePlugins.stream().map(IModFile::getSecureJar).toList()); } public ITransformationService.Resource getModResources() { - return new ITransformationService.Resource(IModuleLayerManager.Layer.GAME, this.candidateMods.stream().map(ModFile::getSecureJar).toList()); + return new ITransformationService.Resource(IModuleLayerManager.Layer.GAME, this.candidateMods.stream().map(IModFile::getSecureJar).toList()); } private List validateLanguages() { @@ -86,7 +88,11 @@ public class ModValidator { public BackgroundScanHandler stage2Validation() { var errors = validateLanguages(); - loadingModList = ModSorter.sort(candidateMods, errors); + + var allErrors = new ArrayList<>(errors); + allErrors.addAll(this.discoveryErrorData); + + loadingModList = ModSorter.sort(candidateMods, allErrors); loadingModList.addCoreMods(); loadingModList.addAccessTransformers(); loadingModList.setBrokenFiles(brokenFiles); diff --git a/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/ModsFolderLocator.java b/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/ModsFolderLocator.java index 7d77055411..a5999a2115 100644 --- a/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/ModsFolderLocator.java +++ b/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/ModsFolderLocator.java @@ -23,7 +23,8 @@ import static cpw.mods.modlauncher.api.LamdbaExceptionUtils.uncheck; /** * Support loading mods located in JAR files in the mods folder */ -public class ModsFolderLocator extends AbstractJarFileLocator { +public class ModsFolderLocator extends AbstractJarFileModLocator +{ private static final String SUFFIX = ".jar"; private static final Logger LOGGER = LogUtils.getLogger(); private final Path modFolder; diff --git a/fmlloader/src/main/resources/META-INF/services/net.minecraftforge.forgespi.locating.IDependencyLocator b/fmlloader/src/main/resources/META-INF/services/net.minecraftforge.forgespi.locating.IDependencyLocator new file mode 100644 index 0000000000..feaf7ec746 --- /dev/null +++ b/fmlloader/src/main/resources/META-INF/services/net.minecraftforge.forgespi.locating.IDependencyLocator @@ -0,0 +1 @@ +net.minecraftforge.fml.loading.moddiscovery.JarInJarDependencyLocator \ No newline at end of file diff --git a/src/main/java/net/minecraftforge/client/gui/LoadingErrorScreen.java b/src/main/java/net/minecraftforge/client/gui/LoadingErrorScreen.java index a4d8e4b124..543dce0466 100644 --- a/src/main/java/net/minecraftforge/client/gui/LoadingErrorScreen.java +++ b/src/main/java/net/minecraftforge/client/gui/LoadingErrorScreen.java @@ -94,7 +94,11 @@ public class LoadingErrorScreen extends ErrorScreen { } public static class LoadingEntryList extends ObjectSelectionList { LoadingEntryList(final LoadingErrorScreen parent, final List errors, final List warnings) { - super(parent.minecraft, parent.width, parent.height, 35, parent.height - 50, 2 * parent.minecraft.font.lineHeight + 8); + super(Objects.requireNonNull(parent.minecraft), parent.width, parent.height, 35, parent.height - 50, + Math.max( + errors.stream().mapToInt(error -> parent.font.split(Component.literal(error.getMessage() != null ? error.getMessage() : ""), parent.width - 20).size()).max().orElse(0), + warnings.stream().mapToInt(warning -> parent.font.split(Component.literal(warning.formatToString() != null ? warning.formatToString() : ""), parent.width - 20).size()).max().orElse(0) + ) * parent.minecraft.font.lineHeight + 8); boolean both = !errors.isEmpty() && !warnings.isEmpty(); if (both) addEntry(new LoadingMessageEntry(parent.errorHeader, true)); @@ -140,13 +144,14 @@ public class LoadingErrorScreen extends ErrorScreen { @Override public void render(PoseStack poseStack, int entryIdx, int top, int left, final int entryWidth, final int entryHeight, final int mouseX, final int mouseY, final boolean p_194999_5_, final float partialTick) { Font font = Minecraft.getInstance().font; - final List strings = font.split(message, LoadingEntryList.this.width); + final List strings = font.split(message, LoadingEntryList.this.width - 20); int y = top + 2; - for (int i = 0; i < Math.min(strings.size(), 2); i++) { + for (FormattedCharSequence string : strings) + { if (center) - font.draw(poseStack, strings.get(i), left + (width) - font.width(strings.get(i)) / 2F, y, 0xFFFFFF); + font.draw(poseStack, string, left + (width) - font.width(string) / 2F, y, 0xFFFFFF); else - font.draw(poseStack, strings.get(i), left + 5, y, 0xFFFFFF); + font.draw(poseStack, string, left + 5, y, 0xFFFFFF); y += font.lineHeight; } } diff --git a/src/main/java/net/minecraftforge/client/gui/ModListScreen.java b/src/main/java/net/minecraftforge/client/gui/ModListScreen.java index d6651575d6..6169648056 100644 --- a/src/main/java/net/minecraftforge/client/gui/ModListScreen.java +++ b/src/main/java/net/minecraftforge/client/gui/ModListScreen.java @@ -20,6 +20,8 @@ import com.mojang.blaze3d.vertex.PoseStack; import net.minecraft.client.gui.narration.NarrationElementOutput; import net.minecraft.client.renderer.GameRenderer; import net.minecraftforge.client.gui.widget.ModListWidget; +import net.minecraftforge.fml.loading.moddiscovery.ModFile; +import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo; import net.minecraftforge.resource.PathResourcePack; import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.LogManager; @@ -450,7 +452,7 @@ public class ModListScreen extends Screen if (vercheck.status() == VersionChecker.Status.OUTDATED || vercheck.status() == VersionChecker.Status.BETA_OUTDATED) lines.add(ForgeI18n.parseMessage("fml.menu.mods.info.updateavailable", vercheck.url() == null ? "" : vercheck.url())); - lines.add(ForgeI18n.parseMessage("fml.menu.mods.info.license", selectedMod.getOwningFile().getLicense())); + lines.add(ForgeI18n.parseMessage("fml.menu.mods.info.license", ((ModFileInfo) selectedMod.getOwningFile()).getLicense())); lines.add(null); lines.add(selectedMod.getDescription()); diff --git a/src/main/resources/assets/forge/lang/en_us.json b/src/main/resources/assets/forge/lang/en_us.json index b9ce92ec8c..8437ad38c1 100644 --- a/src/main/resources/assets/forge/lang/en_us.json +++ b/src/main/resources/assets/forge/lang/en_us.json @@ -67,6 +67,8 @@ "fml.modloading.failedtoloadforge": "Failed to load Forge", "fml.modloading.missingdependency": "Mod \u00a7e{4}\u00a7r requires \u00a76{3}\u00a7r \u00a7o{5,vr}\u00a7r\n\u00a77Currently, \u00a76{3}\u00a7r\u00a77 is \u00a7o{6,i18n,fml.messages.artifactversion.ornotinstalled}", "fml.modloading.missingdependency.optional": "Mod \u00a7e{4}\u00a7r only supports \u00a73{3}\u00a7r \u00a7o{5,vr}\u00a7r\n\u00a77Currently, \u00a73{3}\u00a7r\u00a77 is \u00a7o{6}", + "fml.dependencyloading.conflictingdependencies": "Some mods have requested conflicting versions of: \u00a76{3}\u00a7r. Requested by: \u00a7e{4}\u00a7r.", + "fml.dependencyloading.mismatchedcontaineddependencies": "Some mods have agreed upon an acceptable version range for : \u00a76{3}\u00a7r, but no jar was provided which matched the range. Requested by: \u00a7e{4}\u00a7r.", "fml.modloading.cycle": "Detected a mod dependency cycle: {0}", "fml.modloading.failedtoprocesswork":"{0,modinfo,name} ({0,modinfo,id}) encountered an error processing deferred work\n\u00a77{2,exc,msg}", "fml.modloading.brokenfile": "File {2} is not a valid mod file", diff --git a/src/test/resources/META-INF/jarjar/ForgeJiJTestMod-1.2.jar b/src/test/resources/META-INF/jarjar/ForgeJiJTestMod-1.2.jar new file mode 100644 index 0000000000..df2812e60d Binary files /dev/null and b/src/test/resources/META-INF/jarjar/ForgeJiJTestMod-1.2.jar differ diff --git a/src/test/resources/META-INF/jarjar/metadata.json b/src/test/resources/META-INF/jarjar/metadata.json new file mode 100644 index 0000000000..4b0210ed9c --- /dev/null +++ b/src/test/resources/META-INF/jarjar/metadata.json @@ -0,0 +1,15 @@ +{ + "jars": [ + { + "identifier": { + "group": "net.minecraftforge.jij", + "artifact": "JijTestMod" + }, + "version": { + "range": "[1.0,)", + "artifactVersion": "1.2" + }, + "path": "META-INF/jarjar/ForgeJiJTestMod-1.2.jar" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/META-INF/mods.toml b/src/test/resources/META-INF/mods.toml index ec4fe73d30..76f8214680 100644 --- a/src/test/resources/META-INF/mods.toml +++ b/src/test/resources/META-INF/mods.toml @@ -13,4 +13,236 @@ license="LGPL v2.1" [[mods]] modId="fluid_type_test" + +# LEGACY TEST CASES +###### The mods below are from the old test framework and need to be yeeted later again. + + + + +[[mods]] +modId="containertypetest" +[[mods]] +modId="potion_event_test" +[[mods]] +modId="base_block_place_event_test" +[[mods]] +modId="farmland_trample_test" +[[mods]] +modId="neighbor_notify_event_test" +[[mods]] +modId="block_place_event_test" +[[mods]] +modId="slipperiness_test" +[[mods]] +modId="vanilla_game_event_test" +[[mods]] +modId="custom_slime_block_test" +[[mods]] +modId="client_chat_event_test" +[[mods]] +modId="command_event_test" +[[mods]] +modId="entity_selector_test" +[[mods]] +modId="gravity_attribute_test" +[[mods]] +modId="new_fluid_test" +[[mods]] +modId="milk_fluid_test" +[[mods]] +modId="data_gen_test" +[[mods]] +modId="piston_event_test" +[[mods]] +modId="flower_pot_test" +[[mods]] +modId="player_xp_event_test" +[[mods]] +modId="new_model_loader_test" +[[mods]] +modId="forgedebugmultilayermodel" +[[mods]] +modId="chunkwatchworldtest" +[[mods]] +modId="trsr_transformer_test" +[[mods]] +modId="nameplate_render_test" +[[mods]] +modId="global_loot_test" +[[dependencies.global_loot_test]] +modId="forge" +mandatory=true +versionRange="[1,)" +ordering="AFTER" +side="BOTH" +[[mods]] +modId="custom_plant_type_test" +[[mods]] +modId="chunk_data_event_save_null_world_test" +[[mods]] +modId="composite_model_test" +[[mods]] +modId="dyn_bucket_model_test" +[[mods]] +modId="music_disc_test" +[[mods]] +modId="stencil_enable_test" +[[mods]] +modId="deferred_registry_test" +[[mods]] +modId="create_entity_classification_test" +[[mods]] +modId="ender_mask_test" +[[mods]] +modId="custom_head_test" +[[mods]] +modId="raid_enum_test" +[[mods]] +modId="player_name_event_test" +[[mods]] +modId="tool_interact_test" +[[mods]] +modId="custom_elytra_test" +[[mods]] +modId="snow_boots_test" +[[mods]] +modId="finite_water_test" +[[mods]] +modId="scaffolding_test" +[[mods]] +modId="forge_chunk_manager_test" +[[mods]] +modId="living_conversion_event_test" +[[mods]] +modId="player_game_mode_event_test" +[[mods]] +modId="structure_spawn_list_event_test" +[[mods]] +modId="forge_codecs_test" +[[mods]] +modId="render_local_player_test" +[[mods]] +modId="forge_world_type_test" +[[mods]] +modId="custom_sound_type_test" +[[mods]] +modId="item_modifier_test" +[[mods]] +modId="custom_signs_test" +[[mods]] +modId="shader_fix_test" +[[mods]] +modId="worldgen_registry_desync_test" +[[mods]] +modId="add_entity_attribute_test" +[[mods]] +modId="enum_argument_test" +[[mods]] +modId="entity_teleport_event_test" +[[mods]] +modId="fake_player_test" +[[mods]] +modId="text_linear_filtering_test" +[[mods]] +modId="ranged_mobs_use_modded_weapons_test" +[[mods]] +modId="fishing_bobber_event_test" +[[mods]] +modId="custom_respawn_test" +[[mods]] +modId="custom_tas_test" +[[mods]] +modId="dimension_settings_test" +[[mods]] +modId="player_attack_knockback_test" +[[mods]] +modId="overlay_layers_test" +[[mods]] +modId="entity_renderer_events_test" +[[mods]] +modId="send_datapacks_to_client" +[[mods]] +modId="remove_tag_datagen_test" +[[mods]] +modId="tag_based_tool_types" +[[mods]] +modId="check_spawn_event_test" +[[mods]] +modId="redstone_sided_connectivity_test" +[[mods]] +modId="permissions_changed_event_test" +[[mods]] +modId="custom_shield_test" +[[mods]] +modId="lazy_capabilities_on_items" +[[mods]] +modId="gui_layer_test" +[[mods]] +modId="add_pack_finders_test" +[[mods]] +modId="be_onload_test" +[[mods]] +modId="forge_spawnegg_test" +[[mods]] +modId="custom_tooltip_test" +[[mods]] +modId="full_pots_accessor_demo" +[[mods]] +modId="permissiontest" +[[mods]] +modId="part_entity_test" +[[mods]] +modId="capabilities_test" +[[mods]] +modId="audio_stream_test" +[[mods]] +modId="custom_mob_bucket_test" +[[mods]] +modId="custom_armor_model_test" +[[mods]] +modId="duplicate_optional_tag_test" +[[mods]] +modId="custom_particle_type_test" +[[mods]] +modId="shield_block_event" +[[mods]] +modId="renderable_test" +[[mods]] +modId="ingredient_invalidation" +[[mods]] +modId="registry_codec_test" +[[mods]] +modId="registry_object_test" +[[mods]] +modId="hidden_tooltip_parts" +[[mods]] +modId="gametest_test" +[[mods]] +modId="many_mob_effects_test" +[[mods]] +modId="potion_size_event_test" +[[mods]] +modId="recipe_book_extension_test" +[[mods]] +modId="hide_neighbor_face_test" +[[mods]] +modId="living_get_projectile_event_test" +[[mods]] +modId="server_world_creation_test" +[[mods]] +modId="valid_railshape_test" +[[mods]] +modId="player_negotiation_event_test" +[[mods]] +modId="custom_rarity_test" +[[mods]] +modId="data_pack_registries_test" +[[mods]] +modId="mod_mismatch_test" +[[mods]] +modId="custom_transformtype_test" + + + # ADD ABOVE THIS LINE