diff --git a/build.gradle b/build.gradle index 008a4b2438..3b59215d5c 100644 --- a/build.gradle +++ b/build.gradle @@ -39,7 +39,7 @@ ext { MAPPING_VERSION = '1.19' MC_VERSION = '1.19' MCP_VERSION = '20220607.102129' - SPI_VERSION = '5.0.3' + SPI_VERSION = '6.0.0' ACCESSTRANSFORMERS_VERSION = '8.0.4' COREMODS_VERSION = '5.0.1' EVENTBUS_VERSION = '6.0.0' diff --git a/fmlloader/src/main/java/net/minecraftforge/fml/loading/LoadingModList.java b/fmlloader/src/main/java/net/minecraftforge/fml/loading/LoadingModList.java index 5ce37eea4f..6f48b42b46 100644 --- a/fmlloader/src/main/java/net/minecraftforge/fml/loading/LoadingModList.java +++ b/fmlloader/src/main/java/net/minecraftforge/fml/loading/LoadingModList.java @@ -10,11 +10,18 @@ import net.minecraftforge.fml.loading.moddiscovery.BackgroundScanHandler; import net.minecraftforge.fml.loading.moddiscovery.ModFile; import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo; import net.minecraftforge.fml.loading.moddiscovery.ModInfo; +import net.minecraftforge.forgespi.locating.IModFile; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; import java.util.stream.Collectors; /** @@ -28,7 +35,7 @@ public class LoadingModList private final List sortedList; private final Map fileById; private final List preLoadErrors; - private List brokenFiles; + private List brokenFiles; private LoadingModList(final List modFiles, final List sortedList) { @@ -154,11 +161,11 @@ public class LoadingModList return preLoadErrors; } - public void setBrokenFiles(final List brokenFiles) { + public void setBrokenFiles(final List brokenFiles) { this.brokenFiles = brokenFiles; } - public List getBrokenFiles() { + public List getBrokenFiles() { return this.brokenFiles; } } 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 index 8e9c652cd0..d715e833e6 100644 --- a/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/AbstractJarFileDependencyLocator.java +++ b/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/AbstractJarFileDependencyLocator.java @@ -8,6 +8,7 @@ package net.minecraftforge.fml.loading.moddiscovery; import com.mojang.logging.LogUtils; import net.minecraftforge.forgespi.locating.IDependencyLocator; import net.minecraftforge.forgespi.locating.IModFile; +import net.minecraftforge.forgespi.locating.ModFileLoadingException; import org.slf4j.Logger; import java.io.FileNotFoundException; @@ -37,11 +38,11 @@ public abstract class AbstractJarFileDependencyLocator extends AbstractJarFileMo protected Optional loadModFileFrom(final IModFile file, final Path path) { try { final Path pathInModFile = file.findResource(path.toString()); - return createMod(pathInModFile); + return Optional.of(createMod(pathInModFile).file()); } catch (Exception e) { LOGGER.error("Failed to load mod file {} from {}", path, file.getFileName()); - return Optional.empty(); + throw new ModFileLoadingException("Failed to load mod file "+file.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 index 43700f3199..628d562347 100644 --- a/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/AbstractJarFileModLocator.java +++ b/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/AbstractJarFileModLocator.java @@ -5,25 +5,18 @@ 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() + public List scanMods() { - return scanCandidates() - .map(this::createMod) - .filter(Optional::isPresent) - .map(Optional::get) - .collect(Collectors.toList()); + return scanCandidates().map(this::createMod).toList(); } public abstract Stream scanCandidates(); diff --git a/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/AbstractModProvider.java b/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/AbstractModProvider.java index c2212627e2..74c3246f40 100644 --- a/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/AbstractModProvider.java +++ b/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/AbstractModProvider.java @@ -13,10 +13,13 @@ 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 net.minecraftforge.forgespi.locating.ModFileLoadingException; import org.slf4j.Logger; import java.nio.file.Path; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; @@ -30,7 +33,7 @@ public abstract class AbstractModProvider implements IModProvider protected static final String MODS_TOML = "META-INF/mods.toml"; protected static final String MANIFEST = "META-INF/MANIFEST.MF"; - protected Optional createMod(Path... path) { + protected IModLocator.ModFileOrException createMod(Path... path) { var mjm = new ModJarMetadata(); var sj = SecureJar.from( Manifest::new, @@ -44,18 +47,22 @@ public abstract class AbstractModProvider implements IModProvider 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, type); - } else { - return Optional.empty(); + try { + 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, type); + } else { + return new IModLocator.ModFileOrException(null, new ModFileLoadingException("Invalid mod file found "+ Arrays.toString(path))); + } + } catch (InvalidModFileException e) { + return new IModLocator.ModFileOrException(null, e); } mjm.setModFile(mod); - return Optional.of(mod); + return new IModLocator.ModFileOrException(mod, null); } protected IModFileInfo manifestParser(final IModFile mod) { diff --git a/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/ExplodedDirectoryLocator.java b/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/ExplodedDirectoryLocator.java index faef0000b0..1d4ebc95b4 100644 --- a/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/ExplodedDirectoryLocator.java +++ b/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/ExplodedDirectoryLocator.java @@ -30,14 +30,14 @@ public class ExplodedDirectoryLocator implements IModLocator { private final Map mods = new HashMap<>(); @Override - public List scanMods() { + public List scanMods() { explodedMods.forEach(explodedMod -> ModJarMetadata.buildFile(this, jar->jar.findFile("/META-INF/mods.toml").isPresent(), (a,b) -> true, explodedMod.paths().toArray(Path[]::new)) .ifPresentOrElse(f->mods.put(explodedMod, f), () -> LOGGER.warn(LogMarkers.LOADING, "Failed to find exploded resource mods.toml in directory {}", explodedMod.paths().get(0).toString()))); - return List.copyOf(mods.values()); + return mods.values().stream().map(mf->new IModLocator.ModFileOrException(mf, null)).toList(); } @Override diff --git a/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/InvalidModFileException.java b/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/InvalidModFileException.java index 3c7e67d92c..1177e18972 100644 --- a/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/InvalidModFileException.java +++ b/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/InvalidModFileException.java @@ -6,16 +6,22 @@ package net.minecraftforge.fml.loading.moddiscovery; import net.minecraftforge.forgespi.language.IModFileInfo; +import net.minecraftforge.forgespi.locating.ModFileLoadingException; import java.util.Locale; +import java.util.Optional; -public class InvalidModFileException extends RuntimeException +public class InvalidModFileException extends ModFileLoadingException { private final IModFileInfo modFileInfo; public InvalidModFileException(String message, IModFileInfo modFileInfo) { - super(String.format(Locale.ENGLISH, "%s (%s)", message, ((ModFileInfo)modFileInfo).getFile().getFileName())); + super(String.format(Locale.ROOT, "%s (%s)", message, Optional.ofNullable(modFileInfo).map(mf->mf.getFile().getFileName()).orElse("MISSING FILE NAME"))); this.modFileInfo = modFileInfo; } + + public IModFileInfo getBrokenFile() { + return modFileInfo; + } } 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 0fe7a4386b..c4d00a602d 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 @@ -17,8 +17,11 @@ import net.minecraftforge.forgespi.locating.ModFileFactory; import org.slf4j.Logger; import java.io.IOException; -import java.nio.file.*; -import java.util.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -28,7 +31,7 @@ public class MinecraftLocator extends AbstractModProvider implements IModLocator private static final Logger LOGGER = LogUtils.getLogger(); @Override - public List scanMods() { + public List scanMods() { final var launchHandler = FMLLoader.getLaunchHandler(); var baseMC = launchHandler.getMinecraftPaths(); var mcjar = ModJarMetadata.buildFile(j->ModFileFactory.FACTORY.build(j, this, this::buildMinecraftTOML), j->true, baseMC.minecraftFilter(), baseMC.minecraftPaths().toArray(Path[]::new)).orElseThrow(); @@ -37,12 +40,11 @@ public class MinecraftLocator extends AbstractModProvider implements IModLocator .map(sj -> new ModFile(sj, this, ModFileParser::modsTomlParser)) .collect(Collectors.toList()); var othermods = baseMC.otherModPaths().stream() - .map(p -> createMod(p.toArray(Path[]::new)).orElse(null)) - .filter(Objects::nonNull) - .toList(); + .map(p -> createMod(p.toArray(Path[]::new))) + .filter(Objects::nonNull); artifacts.add(mcjar); - artifacts.addAll(othermods); - return artifacts; + + return Stream.concat(artifacts.stream().map(f -> new ModFileOrException(f, null)), othermods).toList(); } private IModFileInfo buildMinecraftTOML(final IModFile iModFile) { 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 71e3bf301d..4b9b8816d1 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 @@ -16,6 +16,7 @@ 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.language.IModFileInfo; import net.minecraftforge.forgespi.locating.IDependencyLocator; import net.minecraftforge.forgespi.locating.IModFile; import net.minecraftforge.forgespi.locating.IModLocator; @@ -24,6 +25,7 @@ import org.slf4j.Logger; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.ServiceLoader; import java.util.stream.Collectors; @@ -63,18 +65,33 @@ public class ModDiscoverer { List loadedFiles = new ArrayList<>(); List discoveryErrorData = new ArrayList<>(); boolean successfullyLoadedMods = true; + List brokenFiles = new ArrayList<>(); //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!"); + LOGGER.debug(LogMarkers.SCAN, "Trying locator {}", locator); + var candidates = locator.scanMods(); + LOGGER.debug(LogMarkers.SCAN, "Locator {} found {} candidates or errors", locator, candidates.size()); + var exceptions = candidates.stream().map(IModLocator.ModFileOrException::ex).filter(Objects::nonNull).toList(); + if (!exceptions.isEmpty()) { + LOGGER.debug(LogMarkers.SCAN, "Locator {} found {} invalid mod files", locator, exceptions.size()); + brokenFiles.addAll(exceptions.stream().map(e->e instanceof InvalidModFileException ime ? ime.getBrokenFile() : null).filter(Objects::nonNull).toList()); } + var locatedFiles = candidates.stream().map(IModLocator.ModFileOrException::file).filter(Objects::nonNull).toList(); + var badModFiles = locatedFiles.stream().filter(file -> !(file instanceof ModFile)).toList(); + if (!badModFiles.isEmpty()) { + LOGGER.error(LogMarkers.SCAN, "Locator {} returned {} files which is are not ModFile instances! They will be skipped!", locator, badModFiles.size()); + brokenFiles.addAll(badModFiles.stream().map(mf->mf.getModFileInfo()).toList()); + } + locatedFiles.removeAll(badModFiles); + LOGGER.debug(LogMarkers.SCAN, "Locator {} found {} valid mod files", locator, locatedFiles.size()); handleLocatedFiles(loadedFiles, locatedFiles); + } catch (InvalidModFileException imfe) { + // We don't generally expect this exception, since it should come from the candidates stream above and be handled in the Locator, but just in case. + LOGGER.error(LogMarkers.SCAN, "Locator {} found an invalid mod file {}", locator, imfe.getBrokenFile(), imfe); + brokenFiles.add(imfe.getBrokenFile()); } catch (EarlyLoadingException exception) { LOGGER.error(LogMarkers.SCAN, "Failed to load mods with locator {}", locator, exception); discoveryErrorData.addAll(exception.getAllData()); @@ -141,7 +158,7 @@ public class ModDiscoverer { //Validate the loading. With a deduplicated list, we can now successfully process the artifacts and load //transformer plugins. - var validator = new ModValidator(modFilesMap, discoveryErrorData); + var validator = new ModValidator(modFilesMap, brokenFiles, discoveryErrorData); validator.stage1Validation(); return validator; } diff --git a/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/ModInfo.java b/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/ModInfo.java index c6db1c3f1e..3542685ccf 100644 --- a/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/ModInfo.java +++ b/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/ModInfo.java @@ -31,6 +31,7 @@ public class ModInfo implements IModInfo, IConfigurable private static final DefaultArtifactVersion DEFAULT_VERSION = new DefaultArtifactVersion("1"); private static final Pattern VALID_MODID = Pattern.compile("^[a-z][a-z0-9_]{1,63}$"); private static final Pattern VALID_NAMESPACE = Pattern.compile("^[a-z][a-z0-9_.-]{1,63}$"); + private static final Pattern VALID_VERSION = Pattern.compile("^\\d+.*"); private final ModFileInfo owningFile; private final String modId; @@ -97,6 +98,11 @@ public class ModInfo implements IModInfo, IConfigurable this.modUrl = config.getConfigElement("modUrl") .map(StringUtils::toURL); + + // verify we have a valid mod version otherwise throw an exception + if (!VALID_VERSION.matcher(this.version.toString()).matches()) { + throw new InvalidModFileException("Illegal version number specified "+this.version, this.getOwningFile()); + } } @Override 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 b7417fb6fe..4db790f857 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 @@ -13,11 +13,16 @@ import net.minecraftforge.fml.loading.LoadingModList; import net.minecraftforge.fml.loading.LogMarkers; import net.minecraftforge.fml.loading.ModSorter; import net.minecraftforge.fml.loading.progress.StartupMessageManager; +import net.minecraftforge.forgespi.language.IModFileInfo; import net.minecraftforge.forgespi.locating.IModFile; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; -import java.util.*; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; public class ModValidator { private static final Logger LOGGER = LogUtils.getLogger(); @@ -25,16 +30,17 @@ public class ModValidator { private final List candidatePlugins; private final List candidateMods; private LoadingModList loadingModList; - private List brokenFiles; + private List brokenFiles; private final List discoveryErrorData; - public ModValidator(final Map> modFiles, final List discoveryErrorData) { + public ModValidator(final Map> modFiles, final List brokenFiles, 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; + this.brokenFiles = brokenFiles.stream().map(IModFileInfo::getFile).collect(Collectors.toList()); // mutable list } private static List lst(List files) { @@ -42,7 +48,7 @@ public class ModValidator { } public void stage1Validation() { - brokenFiles = validateFiles(candidateMods); + brokenFiles.addAll(validateFiles(candidateMods)); if (LOGGER.isDebugEnabled(LogMarkers.SCAN)) { LOGGER.debug(LogMarkers.SCAN, "Found {} mod files with {} mods", candidateMods.size(), candidateMods.stream().mapToInt(mf -> mf.getModInfos().size()).sum()); }