diff --git a/loader/src/main/java/net/neoforged/fml/ModLoader.java b/loader/src/main/java/net/neoforged/fml/ModLoader.java index 80f6981ce..f9d938f94 100644 --- a/loader/src/main/java/net/neoforged/fml/ModLoader.java +++ b/loader/src/main/java/net/neoforged/fml/ModLoader.java @@ -34,8 +34,6 @@ import net.neoforged.fml.loading.FMLLoader; import net.neoforged.fml.loading.ImmediateWindowHandler; import net.neoforged.fml.loading.LoadingModList; -import net.neoforged.fml.loading.moddiscovery.AbstractModProvider; -import net.neoforged.fml.loading.moddiscovery.InvalidModIdentifier; import net.neoforged.fml.loading.moddiscovery.ModFileInfo; import net.neoforged.fml.loading.moddiscovery.ModInfo; import net.neoforged.fml.loading.progress.ProgressMeter; @@ -104,9 +102,7 @@ private ModLoader() { this.loadingExceptions = FMLLoader.getLoadingModList().getErrors().stream() .flatMap(ModLoadingException::fromEarlyException) .collect(Collectors.toList()); - this.loadingWarnings = FMLLoader.getLoadingModList().getBrokenFiles().stream() - .map(file -> new ModLoadingWarning(null, ModLoadingStage.VALIDATE, InvalidModIdentifier.identifyJarProblem(file.getFilePath()).orElse("fml.modloading.brokenfile"), file.getFileName())) - .collect(Collectors.toList()); + this.loadingWarnings = new ArrayList<>(); FMLLoader.getLoadingModList().getWarnings().stream() .flatMap(ModLoadingWarning::fromEarlyException) @@ -282,7 +278,7 @@ private List buildMods(final IModFile modFile) { var missingClasses = new ArrayList<>(modIds); missingClasses.removeAll(containerIds); - LOGGER.fatal(LOADING, "The following classes are missing, but are reported in the {}: {}", AbstractModProvider.MODS_TOML, missingClasses); + LOGGER.fatal(LOADING, "The following classes are missing, but are reported in the {}: {}", JarModsDotTomlModProvider.MODS_TOML, missingClasses); var missingMods = new ArrayList<>(containerIds); missingMods.removeAll(modIds); diff --git a/loader/src/main/java/net/neoforged/fml/loading/FMLServiceProvider.java b/loader/src/main/java/net/neoforged/fml/loading/FMLServiceProvider.java index d141b3df8..e577ee59b 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/FMLServiceProvider.java +++ b/loader/src/main/java/net/neoforged/fml/loading/FMLServiceProvider.java @@ -65,7 +65,17 @@ public void initialize(IEnvironment environment) { LOGGER.debug(CORE, "Loading configuration"); FMLConfig.load(); LOGGER.debug(CORE, "Preparing ModFile"); - environment.computePropertyIfAbsent(Environment.Keys.MODFILEFACTORY.get(), k -> ModFile::new); + environment.computePropertyIfAbsent(Environment.Keys.MODFILEFACTORY.get(), k -> new ModFileFactory() { + @Override + public IModFile build(SecureJar jar, IModProvider provider, ModFileInfoParser parser) throws InvalidModFileException { + return new ModFile(jar, provider, parser); + } + + @Override + public IModFile build(SecureJar jar, IModProvider provider, ModFileInfoParser parser, IModFile.Type type) throws InvalidModFileException { + return new ModFile(jar, provider, parser, type.name()); + } + }); arguments = new HashMap<>(); arguments.put("modLists", modListsArgumentList); arguments.put("mods", modsArgumentList); diff --git a/loader/src/main/java/net/neoforged/fml/loading/LoadingModList.java b/loader/src/main/java/net/neoforged/fml/loading/LoadingModList.java index 7da76d8e5..dc8188e6b 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/LoadingModList.java +++ b/loader/src/main/java/net/neoforged/fml/loading/LoadingModList.java @@ -22,7 +22,6 @@ import net.neoforged.fml.loading.moddiscovery.ModFile; import net.neoforged.fml.loading.moddiscovery.ModFileInfo; import net.neoforged.fml.loading.moddiscovery.ModInfo; -import net.neoforged.neoforgespi.locating.IModFile; /** * Master list of all mods in the loading context. This class cannot refer outside the @@ -35,7 +34,6 @@ public class LoadingModList { private final Map fileById; private final List preLoadErrors; private final List preLoadWarnings; - private List brokenFiles; private LoadingModList(final List modFiles, final List sortedList) { this.modFiles = modFiles.stream() @@ -166,12 +164,4 @@ public List getErrors() { public List getWarnings() { return preLoadWarnings; } - - public void setBrokenFiles(final List brokenFiles) { - this.brokenFiles = brokenFiles; - } - - public List getBrokenFiles() { - return this.brokenFiles; - } } diff --git a/loader/src/main/java/net/neoforged/fml/loading/ModSorter.java b/loader/src/main/java/net/neoforged/fml/loading/ModSorter.java index 0771fdb73..777ea3012 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/ModSorter.java +++ b/loader/src/main/java/net/neoforged/fml/loading/ModSorter.java @@ -68,7 +68,7 @@ public static LoadingModList sort(List mods, final List (ModInfo) mf.getModInfos().get(0)).collect(toList()), new EarlyLoadingException("failure to validate mod list", null, resolutionResult.buildErrorMessages())); + list = LoadingModList.of(ms.systemMods, ms.systemMods.stream().map(mf -> (ModInfo) mf.getModInfos().get(0)).collect(toList()), new EarlyLoadingException("failure to validate mod list", null, listOf(errors, resolutionResult.buildErrorMessages()))); } else { // Otherwise, lets try and sort the modlist and proceed EarlyLoadingException earlyLoadingException = null; @@ -77,7 +77,11 @@ public static LoadingModList sort(List mods, final List mods, final List List listOf(List... lists) { + var lst = new ArrayList(); + for (List list : lists) { + lst.addAll(list); + } + return lst; + } + @SuppressWarnings("UnstableApiUsage") private void sort() { // lambdas are identity based, so sorting them is impossible unless you hold reference to them diff --git a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/AbstractJarFileDependencyLocator.java b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/AbstractJarFileDependencyLocator.java deleted file mode 100644 index 0fe4ca55c..000000000 --- a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/AbstractJarFileDependencyLocator.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.fml.loading.moddiscovery; - -import com.mojang.logging.LogUtils; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.NoSuchFileException; -import java.nio.file.Path; -import java.util.Optional; -import net.neoforged.neoforgespi.locating.IDependencyLocator; -import net.neoforged.neoforgespi.locating.IModFile; -import net.neoforged.neoforgespi.locating.ModFileLoadingException; -import org.slf4j.Logger; - -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 NoSuchFileException e) { - LOGGER.trace("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 Optional.of(createMod(pathInModFile).file()); - } catch (Exception e) { - LOGGER.error("Failed to load mod file {} from {}", path, file.getFileName()); - throw new ModFileLoadingException("Failed to load mod file " + file.getFileName()); - } - } - - protected String identifyMod(final IModFile modFile) { - return modFile.getFileName(); - } -} diff --git a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/AbstractJarFileModLocator.java b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/AbstractJarFileModLocator.java index 8ac424901..8949c19e2 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/AbstractJarFileModLocator.java +++ b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/AbstractJarFileModLocator.java @@ -9,17 +9,9 @@ import java.nio.file.Path; import java.util.Arrays; import java.util.List; -import java.util.stream.Stream; import net.neoforged.neoforgespi.locating.IModLocator; -public abstract class AbstractJarFileModLocator extends AbstractJarFileModProvider implements IModLocator { - @Override - public List scanMods() { - return scanCandidates().map(this::createMod).toList(); - } - - public abstract Stream scanCandidates(); - +public abstract class AbstractJarFileModLocator implements IModLocator { protected static List getLegacyClasspath() { return Arrays.stream(System.getProperty("legacyClassPath", "").split(File.pathSeparator)).map(Path::of).toList(); } diff --git a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/AbstractJarFileModProvider.java b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/AbstractJarFileModProvider.java deleted file mode 100644 index b392bfc80..000000000 --- a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/AbstractJarFileModProvider.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.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.function.Consumer; -import java.util.function.Function; -import java.util.stream.Stream; -import net.neoforged.fml.loading.LogMarkers; -import net.neoforged.neoforgespi.locating.IModFile; -import org.slf4j.Logger; - -public abstract class AbstractJarFileModProvider extends AbstractModProvider { - private static final Logger LOGGER = LogUtils.getLogger(); - - @Override - public void scanFile(final IModFile file, final Consumer pathConsumer) { - LOGGER.debug(LogMarkers.SCAN, "Scan started: {}", file); - final Function status = p -> file.getSecureJar().verifyPath(p); - try (Stream files = Files.find(file.getSecureJar().getRootPath(), Integer.MAX_VALUE, (p, a) -> p.getNameCount() > 0 && p.getFileName().toString().endsWith(".class"))) { - file.setSecurityStatus(files.peek(pathConsumer).map(status).reduce((s1, s2) -> SecureJar.Status.values()[Math.min(s1.ordinal(), s2.ordinal())]).orElse(SecureJar.Status.INVALID)); - } catch (IOException e) { - e.printStackTrace(); - } - LOGGER.debug(LogMarkers.SCAN, "Scan finished: {}", file); - } -} diff --git a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/BuiltinGameLibraryLocator.java b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/BuiltinGameLibraryLocator.java index aaadda4c1..1ab864be2 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/BuiltinGameLibraryLocator.java +++ b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/BuiltinGameLibraryLocator.java @@ -5,6 +5,7 @@ package net.neoforged.fml.loading.moddiscovery; +import cpw.mods.jarhandling.SecureJar; import java.nio.file.Path; import java.util.Arrays; import java.util.List; @@ -25,7 +26,7 @@ public String name() { public void initArguments(Map arguments) {} @Override - public Stream scanCandidates() { + public Stream scanCandidates() { String gameLibrariesStr = System.getProperty("fml.gameLayerLibraries"); if (gameLibrariesStr == null || gameLibrariesStr.isBlank()) return Stream.of(); @@ -38,6 +39,6 @@ public Stream scanCandidates() { paths.add(path); } - return paths.build(); + return paths.build().map(SecureJar::from); } } diff --git a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ClasspathLocator.java b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ClasspathLocator.java index 2f9ac8f62..130d6af8d 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ClasspathLocator.java +++ b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ClasspathLocator.java @@ -6,6 +6,7 @@ package net.neoforged.fml.loading.moddiscovery; import com.mojang.logging.LogUtils; +import cpw.mods.jarhandling.SecureJar; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; @@ -33,7 +34,7 @@ public String name() { } @Override - public Stream scanCandidates() { + public Stream scanCandidates() { if (!enabled) return Stream.of(); @@ -41,10 +42,10 @@ public Stream scanCandidates() { var claimed = new ArrayList<>(legacyClasspath); var paths = Stream.builder(); - findPaths(claimed, MODS_TOML).forEach(paths::add); - findPaths(claimed, MANIFEST).forEach(paths::add); + findPaths(claimed, JarModsDotTomlModProvider.MODS_TOML).forEach(paths::add); + findPaths(claimed, JarModsDotTomlModProvider.MANIFEST).forEach(paths::add); - return paths.build(); + return paths.build().map(SecureJar::from); } catch (IOException e) { LOGGER.error(LogMarkers.SCAN, "Error trying to find resources", e); throw new RuntimeException(e); diff --git a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ExplodedDirectoryLocator.java b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ExplodedDirectoryLocator.java index f748c58f9..e28dc8cdf 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ExplodedDirectoryLocator.java +++ b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ExplodedDirectoryLocator.java @@ -8,17 +8,11 @@ import com.mojang.logging.LogUtils; import cpw.mods.jarhandling.JarContentsBuilder; import cpw.mods.jarhandling.SecureJar; -import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.function.Consumer; import java.util.stream.Stream; -import net.neoforged.fml.loading.LogMarkers; -import net.neoforged.neoforgespi.locating.IModFile; import net.neoforged.neoforgespi.locating.IModLocator; import org.slf4j.Logger; @@ -28,22 +22,13 @@ public class ExplodedDirectoryLocator implements IModLocator { public record ExplodedMod(String modid, List paths) {} private final List explodedMods = new ArrayList<>(); - private final Map mods = new HashMap<>(); @Override - public List scanMods() { - explodedMods.forEach(explodedMod -> { + public Stream scanCandidates() { + return explodedMods.stream().map(explodedMod -> { var jarContents = new JarContentsBuilder().paths(explodedMod.paths().toArray(Path[]::new)).build(); - if (jarContents.findFile(AbstractModProvider.MODS_TOML).isPresent()) { - var mjm = new ModJarMetadata(jarContents); - var mf = new ModFile(SecureJar.from(jarContents, mjm), this, ModFileParser::modsTomlParser); - mjm.setModFile(mf); - mods.put(explodedMod, mf); - } else { - LOGGER.warn(LogMarkers.LOADING, "Failed to find exploded resource {} in directory {}", AbstractModProvider.MODS_TOML, explodedMod.paths().get(0).toString()); - } + return SecureJar.from(jarContents); }); - return mods.values().stream().map(mf -> new IModLocator.ModFileOrException(mf, null)).toList(); } @Override @@ -51,17 +36,6 @@ public String name() { return "exploded directory"; } - @Override - public void scanFile(final IModFile file, final Consumer pathConsumer) { - LOGGER.debug(LogMarkers.SCAN, "Scanning exploded directory {}", file.getFilePath().toString()); - try (Stream files = Files.find(file.getSecureJar().getRootPath(), Integer.MAX_VALUE, (p, a) -> p.getNameCount() > 0 && p.getFileName().toString().endsWith(".class"))) { - files.forEach(pathConsumer); - } catch (IOException e) { - e.printStackTrace(); - } - LOGGER.debug(LogMarkers.SCAN, "Exploded directory scan complete {}", file.getFilePath().toString()); - } - @Override public String toString() { return "{ExplodedDir locator}"; @@ -75,9 +49,4 @@ public void initArguments(final Map arguments) { explodedMods.addAll(explodedTargets); } } - - @Override - public boolean isValid(final IModFile modFile) { - return true; - } } diff --git a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/InvalidModIdentifier.java b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/InvalidModIdentifier.java index 9f8aa80f8..86ed45084 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/InvalidModIdentifier.java +++ b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/InvalidModIdentifier.java @@ -5,48 +5,37 @@ package net.neoforged.fml.loading.moddiscovery; -import cpw.mods.modlauncher.api.LambdaExceptionUtils; -import java.nio.file.Path; +import cpw.mods.jarhandling.JarContents; import java.util.Arrays; import java.util.Optional; -import java.util.function.BiPredicate; -import java.util.zip.ZipFile; +import java.util.function.Predicate; import net.neoforged.fml.loading.StringUtils; public enum InvalidModIdentifier { OLDFORGE(filePresent("mcmod.info")), + MINECRAFT_FORGE(filePresent("mods.toml")), FABRIC(filePresent("fabric.mod.json")), LITELOADER(filePresent("litemod.json")), OPTIFINE(filePresent("optifine/Installer.class")), - BUKKIT(filePresent("plugin.yml")), - INVALIDZIP((f, zf) -> !zf.isPresent()); + BUKKIT(filePresent("plugin.yml")); - private BiPredicate> ident; + private final Predicate ident; - InvalidModIdentifier(BiPredicate> identifier) { + InvalidModIdentifier(Predicate identifier) { this.ident = identifier; } - private String getReason() { + public String getReason() { return "fml.modloading.brokenfile." + StringUtils.toLowerCase(name()); } - public static Optional identifyJarProblem(Path path) { - Optional zfo = tryOpenFile(path); - Optional result = Arrays.stream(values()).filter(i -> i.ident.test(path, zfo)).map(InvalidModIdentifier::getReason).findAny(); - zfo.ifPresent(LambdaExceptionUtils.rethrowConsumer(ZipFile::close)); - return result; + public static Optional identifyJarProblem(JarContents jar) { + return Arrays.stream(values()) + .filter(i -> i.ident.test(jar)) + .findAny(); } - private static BiPredicate> filePresent(String filename) { - return (f, zfo) -> zfo.map(zf -> zf.getEntry(filename) != null).orElse(false); - } - - private static Optional tryOpenFile(Path path) { - try { - return Optional.of(new ZipFile(path.toFile())); - } catch (Exception ignored) { - return Optional.empty(); - } + private static Predicate filePresent(String filename) { + return jarContents -> jarContents.findFile(filename).isPresent(); } } diff --git a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/JarInJarDependencyLocator.java b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/JarInJarDependencyLocator.java index 5894b4371..978cfeb53 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/JarInJarDependencyLocator.java +++ b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/JarInJarDependencyLocator.java @@ -8,36 +8,61 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.mojang.logging.LogUtils; +import cpw.mods.jarhandling.JarContents; +import cpw.mods.jarhandling.JarContentsBuilder; +import cpw.mods.jarhandling.SecureJar; +import java.io.InputStream; import java.net.URI; import java.nio.file.FileSystem; import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; 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.neoforged.fml.loading.EarlyLoadingException; import net.neoforged.jarjar.selection.JarSelector; import net.neoforged.neoforgespi.language.IModInfo; +import net.neoforged.neoforgespi.locating.IDependencyLocator; import net.neoforged.neoforgespi.locating.IModFile; +import net.neoforged.neoforgespi.locating.IModProvider; +import net.neoforged.neoforgespi.locating.ModFileFactory; import net.neoforged.neoforgespi.locating.ModFileLoadingException; import org.apache.maven.artifact.versioning.ArtifactVersion; import org.apache.maven.artifact.versioning.VersionRange; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; -public class JarInJarDependencyLocator extends AbstractJarFileDependencyLocator { +public class JarInJarDependencyLocator implements IDependencyLocator, IModProvider { private static final Logger LOGGER = LogUtils.getLogger(); @Override public String name() { - return "JarInJar"; + return "jarinjar"; } @Override - public List scanMods(final Iterable loadedMods) { + public void scanFile(IModFile modFile, Consumer pathConsumer) {} + + @Override + public boolean isValid(IModFile modFile) { + return true; + } + + @Override + public @Nullable ModFileOrException provide(JarContents jar) { + return null; + } + + @Override + public List scanMods(Iterable loadedMods, Function> provider) { final List sources = Lists.newArrayList(); loadedMods.forEach(sources::add); @@ -52,26 +77,21 @@ public List scanMods(final Iterable loadedMods) { return dependenciesToLoad; } - @Override - public void initArguments(final Map arguments) { - // NO-OP, for now - } - - @Override - protected String getDefaultJarModType() { - return IModFile.Type.LIBRARY.name(); - } - @SuppressWarnings("resource") @Override - protected Optional loadModFileFrom(final IModFile file, final Path path) { + protected Optional loadModFileFrom(final IModFile file, final Path path, Function> provider) { try { final Path pathInModFile = file.findResource(path.toString()); final URI filePathUri = new URI("jij:" + (pathInModFile.toAbsolutePath().toUri().getRawSchemeSpecificPart())).normalize(); final Map outerFsArgs = ImmutableMap.of("packagePath", pathInModFile); final FileSystem zipFS = FileSystems.newFileSystem(filePathUri, outerFsArgs); - final Path pathInFS = zipFS.getPath("/"); - return Optional.of(createMod(pathInFS).file()); + final var jar = new JarContentsBuilder().paths(zipFS.getPath("/")).build(); + final var fileOrEx = provider.apply(jar) + .orElseGet(() -> new ModFileOrException(ModFileFactory.FACTORY.build(SecureJar.from(jar), this, JarModsDotTomlModProvider::manifestParser, IModFile.Type.LIBRARY), null)); + if (fileOrEx.ex() != null) { + throw fileOrEx.ex(); + } + return Optional.of(fileOrEx.file()); } catch (Exception e) { LOGGER.error("Failed to load mod file {} from {}", path, file.getFileName()); final RuntimeException exception = new ModFileLoadingException("Failed to load mod file " + file.getFileName()); @@ -117,7 +137,6 @@ private String formatError(final ModWithVersionRange modWithVersionRange) { return "\u00a7e" + modWithVersionRange.modInfo().getModId() + "\u00a7r - \u00a74" + modWithVersionRange.versionRange().toString() + "\u00a74 - \u00a72" + modWithVersionRange.artifactVersion().toString() + "\u00a72"; } - @Override protected String identifyMod(final IModFile modFile) { if (modFile.getModFileInfo() == null || modFile.getModInfos().isEmpty()) { return modFile.getFileName(); @@ -127,4 +146,16 @@ protected String identifyMod(final IModFile modFile) { } private record ModWithVersionRange(IModInfo modInfo, VersionRange versionRange, ArtifactVersion artifactVersion) {} + + protected Optional loadResourceFromModFile(final IModFile modFile, final Path path) { + try { + return Optional.of(Files.newInputStream(modFile.findResource(path.toString()))); + } catch (final NoSuchFileException e) { + LOGGER.trace("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(); + } + } } diff --git a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/AbstractModProvider.java b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/JarModsDotTomlModProvider.java similarity index 66% rename from loader/src/main/java/net/neoforged/fml/loading/moddiscovery/AbstractModProvider.java rename to loader/src/main/java/net/neoforged/fml/loading/moddiscovery/JarModsDotTomlModProvider.java index 37da5891f..bd6c0c626 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/AbstractModProvider.java +++ b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/JarModsDotTomlModProvider.java @@ -1,15 +1,8 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - package net.neoforged.fml.loading.moddiscovery; import com.mojang.logging.LogUtils; -import cpw.mods.jarhandling.JarContentsBuilder; +import cpw.mods.jarhandling.JarContents; import cpw.mods.jarhandling.SecureJar; -import java.nio.file.Path; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; @@ -20,42 +13,42 @@ import net.neoforged.neoforgespi.language.IModFileInfo; import net.neoforged.neoforgespi.language.IModInfo; import net.neoforged.neoforgespi.locating.IModFile; -import net.neoforged.neoforgespi.locating.IModLocator; import net.neoforged.neoforgespi.locating.IModProvider; import net.neoforged.neoforgespi.locating.ModFileLoadingException; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; -public abstract class AbstractModProvider implements IModProvider { +public class JarModsDotTomlModProvider implements IModProvider { private static final Logger LOGGER = LogUtils.getLogger(); public static final String MODS_TOML = "META-INF/neoforge.mods.toml"; protected static final String MANIFEST = "META-INF/MANIFEST.MF"; - protected IModLocator.ModFileOrException createMod(Path... path) { - var jarContents = new JarContentsBuilder() - .paths(path) - .build(); + @Override + public String name() { + return "modsdottoml"; + } - IModFile mod; - var type = jarContents.getManifest().getMainAttributes().getValue(ModFile.TYPE); - if (type == null) { - type = getDefaultJarModType(); - } - if (jarContents.findFile(MODS_TOML).isPresent()) { - LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", MODS_TOML, type, path); - var mjm = new ModJarMetadata(jarContents); - mod = new ModFile(SecureJar.from(jarContents, mjm), this, ModFileParser::modsTomlParser); - mjm.setModFile(mod); - } else if (type != null) { - LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", MANIFEST, type, path); - mod = new ModFile(SecureJar.from(jarContents), this, this::manifestParser, type); - } else { - return new IModLocator.ModFileOrException(null, new ModFileLoadingException("Invalid mod file found " + Arrays.toString(path))); - } + protected IModProvider.ModFileOrException createMod(JarContents jar) { + var type = jar.getManifest().getMainAttributes().getValue(ModFile.TYPE); + try { + IModFile mod = null; + if (jar.findFile(MODS_TOML).isPresent()) { + LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", MODS_TOML, type, jar.getPrimaryPath()); + var mjm = new ModJarMetadata(jar); + mod = new ModFile(SecureJar.from(jar, mjm), this, ModFileParser::modsTomlParser); + mjm.setModFile(mod); + } else if (type != null) { + LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", MANIFEST, type, jar.getPrimaryPath()); + mod = new ModFile(SecureJar.from(jar), this, JarModsDotTomlModProvider::manifestParser, type); + } - return new IModLocator.ModFileOrException(mod, null); + return mod == null ? null : new ModFileOrException(mod, null); + } catch (ModFileLoadingException exception) { + return new ModFileOrException(null, exception); + } } - protected IModFileInfo manifestParser(final IModFile mod) { + public static IModFileInfo manifestParser(final IModFile mod) { Function> cfg = name -> Optional.ofNullable(mod.getSecureJar().moduleDataProvider().getManifest().getMainAttributes().getValue(name)); var license = cfg.apply("LICENSE").orElse(""); var dummy = new IConfigurable() { @@ -78,11 +71,13 @@ public boolean isValid(final IModFile modFile) { return true; } - protected String getDefaultJarModType() { - return null; + @Override + public @Nullable ModFileOrException provide(JarContents jar) { + return createMod(jar); } - private record DefaultModFileInfo(IModFile mod, String license, IConfigurable configurable) implements IModFileInfo, IConfigurable { + private record DefaultModFileInfo(IModFile mod, String license, + IConfigurable configurable) implements IModFileInfo, IConfigurable { @Override public Optional getConfigElement(final String... strings) { return Optional.empty(); diff --git a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/MavenDirectoryLocator.java b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/MavenDirectoryLocator.java index f3d6c7486..6533f5bd4 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/MavenDirectoryLocator.java +++ b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/MavenDirectoryLocator.java @@ -5,6 +5,7 @@ package net.neoforged.fml.loading.moddiscovery; +import cpw.mods.jarhandling.SecureJar; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; @@ -13,13 +14,14 @@ import java.util.stream.Stream; import net.neoforged.fml.loading.FMLPaths; import net.neoforged.fml.loading.MavenCoordinateResolver; +import net.neoforged.neoforgespi.locating.IModLocator; -public class MavenDirectoryLocator extends AbstractJarFileModLocator { +public class MavenDirectoryLocator implements IModLocator { private List modCoords; @Override - public Stream scanCandidates() { - return modCoords.stream(); + public Stream scanCandidates() { + return modCoords.stream().map(SecureJar::from); } @Override diff --git a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/MinecraftLocator.java b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/MinecraftLocator.java index d1ae069f8..478ace632 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/MinecraftLocator.java +++ b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/MinecraftLocator.java @@ -7,31 +7,46 @@ import com.electronwill.nightconfig.core.Config; import com.mojang.logging.LogUtils; +import cpw.mods.jarhandling.JarContents; import cpw.mods.jarhandling.JarContentsBuilder; 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.Map; -import java.util.Objects; -import java.util.function.Consumer; -import java.util.stream.Collectors; import java.util.stream.Stream; import net.neoforged.fml.loading.ClasspathTransformerDiscoverer; import net.neoforged.fml.loading.FMLLoader; -import net.neoforged.fml.loading.LogMarkers; import net.neoforged.neoforgespi.language.IModFileInfo; import net.neoforged.neoforgespi.locating.IModFile; import net.neoforged.neoforgespi.locating.IModLocator; +import net.neoforged.neoforgespi.locating.IModProvider; import net.neoforged.neoforgespi.locating.ModFileFactory; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; -public class MinecraftLocator extends AbstractModProvider implements IModLocator { +public class MinecraftLocator implements IModProvider, IModLocator { private static final Logger LOGGER = LogUtils.getLogger(); @Override - public List scanMods() { + public @Nullable ModFileOrException provide(JarContents jar) { + return null; + } + + @Override + public Stream scanCandidates() { + final var launchHandler = FMLLoader.getLaunchHandler(); + var baseMC = launchHandler.getMinecraftPaths(); + var otherModsExcluded = ClasspathTransformerDiscoverer.allExcluded(); + var othermods = baseMC.otherModPaths().stream() + .filter(p -> p.stream().noneMatch(otherModsExcluded::contains)) //We cannot load MOD_CLASSES from the classpath if they are loaded on the SERVICE layer. + .map(set -> new JarContentsBuilder().paths(set.toArray(Path[]::new)).build()); + var artifacts = baseMC.otherArtifacts().stream() + .map(pt -> new JarContentsBuilder().paths(pt).build()); + return Stream.concat(othermods, artifacts); + } + + @Override + public List provide() { final var launchHandler = FMLLoader.getLaunchHandler(); var baseMC = launchHandler.getMinecraftPaths(); var mcJarContents = new JarContentsBuilder() @@ -42,18 +57,8 @@ public List scanMods() { var mcSecureJar = SecureJar.from(mcJarContents, mcJarMetadata); var mcjar = ModFileFactory.FACTORY.build(mcSecureJar, this, this::buildMinecraftTOML); mcJarMetadata.setModFile(mcjar); - var artifacts = baseMC.otherArtifacts().stream() - .map(SecureJar::from) - .map(sj -> new ModFile(sj, this, ModFileParser::modsTomlParser)) - .collect(Collectors.toList()); - var otherModsExcluded = ClasspathTransformerDiscoverer.allExcluded(); - var othermods = baseMC.otherModPaths().stream() - .filter(p -> p.stream().noneMatch(otherModsExcluded::contains)) //We cannot load MOD_CLASSES from the classpath if they are loaded on the SERVICE layer. - .map(p -> createMod(p.toArray(Path[]::new))) - .filter(Objects::nonNull); - artifacts.add(mcjar); - return Stream.concat(artifacts.stream().map(f -> new ModFileOrException(f, null)), othermods).toList(); + return List.of(new ModFileOrException(mcjar, null)); } private IModFileInfo buildMinecraftTOML(final IModFile iModFile) { @@ -112,18 +117,12 @@ public String name() { } @Override - public void scanFile(final IModFile modFile, final Consumer pathConsumer) { - LOGGER.debug(LogMarkers.SCAN, "Scan started: {}", modFile); - try (Stream files = Files.find(modFile.getSecureJar().getRootPath(), Integer.MAX_VALUE, (p, a) -> p.getNameCount() > 0 && p.getFileName().toString().endsWith(".class"))) { - files.forEach(pathConsumer); - } catch (IOException e) { - e.printStackTrace(); - } - LOGGER.debug(LogMarkers.SCAN, "Scan finished: {}", modFile); + public void initArguments(final Map arguments) { + // no op } @Override - public void initArguments(final Map arguments) { - // no op + public boolean isValid(IModFile modFile) { + return true; } } diff --git a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ModDiscoverer.java b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ModDiscoverer.java index a0d5733a7..933dfedc5 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ModDiscoverer.java +++ b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ModDiscoverer.java @@ -8,6 +8,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Maps; import com.mojang.logging.LogUtils; +import cpw.mods.jarhandling.JarContents; import cpw.mods.modlauncher.Launcher; import cpw.mods.modlauncher.api.IModuleLayerManager; import cpw.mods.modlauncher.util.ServiceLoaderUtils; @@ -15,7 +16,9 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.ServiceLoader; +import java.util.function.Function; import java.util.stream.Collectors; import net.neoforged.fml.loading.EarlyLoadingException; import net.neoforged.fml.loading.ImmediateWindowHandler; @@ -23,29 +26,34 @@ import net.neoforged.fml.loading.UniqueModListBuilder; import net.neoforged.fml.loading.progress.StartupNotificationManager; import net.neoforged.neoforgespi.Environment; -import net.neoforged.neoforgespi.language.IModFileInfo; import net.neoforged.neoforgespi.locating.IDependencyLocator; import net.neoforged.neoforgespi.locating.IModFile; import net.neoforged.neoforgespi.locating.IModLocator; +import net.neoforged.neoforgespi.locating.IModProvider; +import net.neoforged.neoforgespi.locating.InvalidModFileException; import org.slf4j.Logger; public class ModDiscoverer { private static final Logger LOGGER = LogUtils.getLogger(); private final ServiceLoader modLocators; + private final ServiceLoader modProviders; private final ServiceLoader dependencyLocators; private final List modLocatorList; private final List dependencyLocatorList; + private final List modProviderList; + private final Function> provider; public ModDiscoverer(Map arguments) { Launcher.INSTANCE.environment().computePropertyIfAbsent(Environment.Keys.MODDIRECTORYFACTORY.get(), v -> ModsFolderLocator::new); Launcher.INSTANCE.environment().computePropertyIfAbsent(Environment.Keys.PROGRESSMESSAGE.get(), v -> StartupNotificationManager.locatorConsumer().orElseGet(() -> s -> {})); final var moduleLayerManager = Launcher.INSTANCE.environment().findModuleLayerManager().orElseThrow(); modLocators = ServiceLoader.load(moduleLayerManager.getLayer(IModuleLayerManager.Layer.SERVICE).orElseThrow(), IModLocator.class); + modProviders = ServiceLoader.load(moduleLayerManager.getLayer(IModuleLayerManager.Layer.SERVICE).orElseThrow(), IModProvider.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)); + modProviderList = ServiceLoaderUtils.streamServiceLoader(() -> modProviders, sce -> LOGGER.error("Failed to load mod provider list", sce)).collect(Collectors.toList()); 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 : {}", modLocatorList.stream() .map(modLocator -> "(%s:%s)".formatted(modLocator.name(), @@ -58,44 +66,58 @@ public ModDiscoverer(Map arguments) { dependencyLocator.getClass().getPackage().getImplementationVersion())) .collect(Collectors.joining(","))); } + provider = jar -> { + for (final var prov : modProviderList) { + final var provided = prov.provide(jar); + if (provided != null) { + return Optional.of(provided); + } + } + return Optional.empty(); + }; } public ModValidator discoverMods() { 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<>(); + List discoveryWarnings = new ArrayList<>(); boolean successfullyLoadedMods = true; - List brokenFiles = new ArrayList<>(); ImmediateWindowHandler.updateProgress("Discovering mod files"); //Loop all mod locators to get the prime mods to load from. + List candidates = new ArrayList<>(); for (IModLocator locator : modLocatorList) { - try { - 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).collect(Collectors.toList()); + LOGGER.debug(LogMarkers.SCAN, "Trying locator {}", locator); + var cands = locator.scanCandidates().toList(); + candidates.addAll(cands); + LOGGER.debug(LogMarkers.SCAN, "Locator {} found {} candidates", locator, cands.size()); + } - 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(IModFile::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()); + var unclaimed = new ArrayList(); + // todo - logging + try { + var provided = new ArrayList(); + for (JarContents candidate : candidates) { + final var prov = provider.apply(candidate); + prov.ifPresentOrElse(provided::add, () -> unclaimed.add(candidate)); + } + var exceptions = provided.stream().map(IModProvider.ModFileOrException::ex).filter(Objects::nonNull).toList(); + if (!exceptions.isEmpty()) { + exceptions.stream().map(e -> new EarlyLoadingException.ExceptionData(e.getMessage(), e instanceof InvalidModFileException ime ? ime.getBrokenFile() : null)).forEach(discoveryErrorData::add); + } + var locatedFiles = provided.stream().map(IModProvider.ModFileOrException::file).filter(Objects::nonNull).collect(Collectors.toList()); + + var badModFiles = locatedFiles.stream().filter(file -> !(file instanceof ModFile)).toList(); + if (!badModFiles.isEmpty()) { + exceptions.stream().map(e -> new EarlyLoadingException.ExceptionData(e.getMessage(), e instanceof InvalidModFileException ime ? ime.getBrokenFile() : null)).forEach(discoveryErrorData::add); } + locatedFiles.removeAll(badModFiles); + 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. + discoveryErrorData.add(new EarlyLoadingException.ExceptionData(imfe.getMessage(), imfe.getBrokenFile())); + } catch (EarlyLoadingException exception) { + discoveryErrorData.addAll(exception.getAllData()); } //First processing run of the mod list. Any duplicates will cause resolution failure and dependency loading will be skipped. @@ -123,7 +145,7 @@ public ModValidator discoverMods() { LOGGER.debug(LogMarkers.SCAN, "Trying locator {}", locator); final List locatedMods = ImmutableList.copyOf(loadedFiles); - var locatedFiles = locator.scanMods(locatedMods); + var locatedFiles = locator.scanMods(locatedMods, provider); 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!"); } @@ -153,9 +175,17 @@ public ModValidator discoverMods() { LOGGER.error(LogMarkers.SCAN, "Mod Discovery failed. Skipping dependency discovery."); } + unclaimed.forEach(jarContents -> { + var reason = InvalidModIdentifier.identifyJarProblem(jarContents); + if (reason.isPresent()) { + LOGGER.warn(LogMarkers.SCAN, "Found jar {} for loader {}. Skipping.", jarContents.getPrimaryPath(), reason.get()); + discoveryWarnings.add(new EarlyLoadingException.ExceptionData(reason.get().getReason(), jarContents.getPrimaryPath())); + } + }); + //Validate the loading. With a deduplicated list, we can now successfully process the artifacts and load //transformer plugins. - var validator = new ModValidator(modFilesMap, brokenFiles, discoveryErrorData); + var validator = new ModValidator(modFilesMap, discoveryWarnings, discoveryErrorData); validator.stage1Validation(); return validator; } diff --git a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ModFileInfo.java b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ModFileInfo.java index f2f062e0b..8aecb7297 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ModFileInfo.java +++ b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ModFileInfo.java @@ -35,6 +35,8 @@ import net.neoforged.neoforgespi.language.IModFileInfo; import net.neoforged.neoforgespi.language.IModInfo; import net.neoforged.neoforgespi.language.MavenVersionAdapter; +import net.neoforged.neoforgespi.locating.InvalidModFileException; +import org.jetbrains.annotations.ApiStatus; import org.slf4j.Logger; public class ModFileInfo implements IModFileInfo, IConfigurable { @@ -50,7 +52,8 @@ public class ModFileInfo implements IModFileInfo, IConfigurable { private final String license; private final List usesServices; - ModFileInfo(final ModFile modFile, final IConfigurable config, Consumer configFileConsumer) { + @ApiStatus.Internal + public ModFileInfo(final ModFile modFile, final IConfigurable config, Consumer configFileConsumer) { this.modFile = modFile; this.config = config; configFileConsumer.accept(this); diff --git a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ModFileParser.java b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ModFileParser.java index 8aeb46dbc..800edaeec 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ModFileParser.java +++ b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ModFileParser.java @@ -20,6 +20,7 @@ import net.neoforged.fml.loading.LogMarkers; import net.neoforged.neoforgespi.language.IModFileInfo; import net.neoforged.neoforgespi.locating.IModFile; +import net.neoforged.neoforgespi.locating.InvalidModFileException; import net.neoforged.neoforgespi.locating.ModFileFactory; import org.slf4j.Logger; @@ -33,9 +34,9 @@ public static IModFileInfo readModList(final ModFile modFile, final ModFileFacto public static IModFileInfo modsTomlParser(final IModFile imodFile) { ModFile modFile = (ModFile) imodFile; LOGGER.debug(LogMarkers.LOADING, "Considering mod file candidate {}", modFile.getFilePath()); - final Path modsjson = modFile.findResource(AbstractModProvider.MODS_TOML); + final Path modsjson = modFile.findResource(JarModsDotTomlModProvider.MODS_TOML); if (!Files.exists(modsjson)) { - LOGGER.warn(LogMarkers.LOADING, "Mod file {} is missing {} file", modFile.getFilePath(), AbstractModProvider.MODS_TOML); + LOGGER.warn(LogMarkers.LOADING, "Mod file {} is missing {} file", modFile.getFilePath(), JarModsDotTomlModProvider.MODS_TOML); return null; } diff --git a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ModInfo.java b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ModInfo.java index c23bce2ce..0ef510c31 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ModInfo.java +++ b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ModInfo.java @@ -20,6 +20,7 @@ import net.neoforged.neoforgespi.language.IModInfo; import net.neoforged.neoforgespi.language.MavenVersionAdapter; import net.neoforged.neoforgespi.locating.ForgeFeature; +import net.neoforged.neoforgespi.locating.InvalidModFileException; import org.apache.maven.artifact.versioning.ArtifactVersion; import org.apache.maven.artifact.versioning.DefaultArtifactVersion; import org.apache.maven.artifact.versioning.VersionRange; diff --git a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ModValidator.java b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ModValidator.java index d6eec29e9..afb42de85 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ModValidator.java +++ b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ModValidator.java @@ -12,14 +12,12 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import java.util.stream.Stream; import net.neoforged.fml.loading.EarlyLoadingException; import net.neoforged.fml.loading.ImmediateWindowHandler; import net.neoforged.fml.loading.LoadingModList; import net.neoforged.fml.loading.LogMarkers; import net.neoforged.fml.loading.ModSorter; -import net.neoforged.neoforgespi.language.IModFileInfo; import net.neoforged.neoforgespi.locating.IModFile; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; @@ -30,16 +28,16 @@ public class ModValidator { private final List candidatePlugins; private final List candidateMods; private LoadingModList loadingModList; - private List brokenFiles; private final List discoveryErrorData; + private final List warnings; - public ModValidator(final Map> modFiles, final List brokenFiles, final List discoveryErrorData) { + public ModValidator(Map> modFiles, List warnings, 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.LIBRARY)); this.discoveryErrorData = discoveryErrorData; - this.brokenFiles = brokenFiles.stream().map(IModFileInfo::getFile).collect(Collectors.toList()); // mutable list + this.warnings = new ArrayList<>(warnings); } private static List lst(@Nullable List files) { @@ -47,24 +45,21 @@ private static List lst(@Nullable List files) { } public void stage1Validation() { - brokenFiles.addAll(validateFiles(candidateMods)); + 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()); } ImmediateWindowHandler.updateProgress("Found " + candidateMods.size() + " mod candidates"); } - private List validateFiles(final List mods) { - final List brokenFiles = new ArrayList<>(); + private void validateFiles(final List mods) { for (Iterator iterator = mods.iterator(); iterator.hasNext();) { ModFile modFile = iterator.next(); - if (!modFile.getProvider().isValid(modFile) || !modFile.identifyMods()) { + if (!modFile.identifyMods()) { LOGGER.warn(LogMarkers.SCAN, "File {} has been ignored - it is invalid", modFile.getFilePath()); iterator.remove(); - brokenFiles.add(modFile); } } - return brokenFiles; } public ITransformationService.Resource getPluginResources() { @@ -104,7 +99,9 @@ public BackgroundScanHandler stage2Validation() { loadingModList.addCoreMods(); loadingModList.addAccessTransformers(); loadingModList.addMixinConfigs(); - loadingModList.setBrokenFiles(brokenFiles); + if (!this.warnings.isEmpty()) { + loadingModList.getWarnings().add(new EarlyLoadingException("discovery warnings", null, this.warnings)); + } BackgroundScanHandler backgroundScanHandler = new BackgroundScanHandler(); loadingModList.addForScanning(backgroundScanHandler); return backgroundScanHandler; diff --git a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ModsFolderLocator.java b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ModsFolderLocator.java index 1a6c73e28..c752a0649 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ModsFolderLocator.java +++ b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ModsFolderLocator.java @@ -6,6 +6,7 @@ package net.neoforged.fml.loading.moddiscovery; import com.mojang.logging.LogUtils; +import cpw.mods.jarhandling.SecureJar; import cpw.mods.modlauncher.api.LambdaExceptionUtils; import java.nio.file.Files; import java.nio.file.Path; @@ -41,13 +42,14 @@ public ModsFolderLocator() { } @Override - public Stream scanCandidates() { + public Stream scanCandidates() { LOGGER.debug(LogMarkers.SCAN, "Scanning mods dir {} for mods", this.modFolder); var excluded = ModDirTransformerDiscoverer.allExcluded(); return LambdaExceptionUtils.uncheck(() -> Files.list(this.modFolder)) .filter(p -> !excluded.contains(p) && StringUtils.toLowerCase(p.getFileName().toString()).endsWith(SUFFIX)) - .sorted(Comparator.comparing(path -> StringUtils.toLowerCase(path.getFileName().toString()))); + .sorted(Comparator.comparing(path -> StringUtils.toLowerCase(path.getFileName().toString()))) + .map(SecureJar::from); } @Override diff --git a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/NightConfigWrapper.java b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/NightConfigWrapper.java index 9936fa6d8..0f8b70697 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/NightConfigWrapper.java +++ b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/NightConfigWrapper.java @@ -15,6 +15,7 @@ import java.util.stream.Collectors; import net.neoforged.neoforgespi.language.IConfigurable; import net.neoforged.neoforgespi.language.IModFileInfo; +import net.neoforged.neoforgespi.locating.InvalidModFileException; public class NightConfigWrapper implements IConfigurable { private final UnmodifiableConfig config; diff --git a/loader/src/main/java/net/neoforged/fml/loading/targets/CommonDevLaunchHandler.java b/loader/src/main/java/net/neoforged/fml/loading/targets/CommonDevLaunchHandler.java index 2bca63ba4..2b02e9cd9 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/targets/CommonDevLaunchHandler.java +++ b/loader/src/main/java/net/neoforged/fml/loading/targets/CommonDevLaunchHandler.java @@ -17,7 +17,7 @@ import java.util.regex.Pattern; import java.util.stream.Stream; import net.neoforged.fml.loading.FileUtils; -import net.neoforged.fml.loading.moddiscovery.AbstractModProvider; +import net.neoforged.fml.loading.moddiscovery.JarModsDotTomlModProvider; public abstract class CommonDevLaunchHandler extends CommonLaunchHandler { @Override @@ -122,7 +122,7 @@ protected UnionPathFilter getMcFilter(Path extra, List minecraft, Stream.B } protected String[] getExcludedPrefixes() { - return new String[] { "net/neoforged/neoforge/", "META-INF/services/", "META-INF/coremods.json", AbstractModProvider.MODS_TOML }; + return new String[] { "net/neoforged/neoforge/", "META-INF/services/", "META-INF/coremods.json", JarModsDotTomlModProvider.MODS_TOML }; } private static String getRandomNumbers(int length) { diff --git a/loader/src/main/java/net/neoforged/neoforgespi/locating/IDependencyLocator.java b/loader/src/main/java/net/neoforged/neoforgespi/locating/IDependencyLocator.java index 81e675aed..ae5a7839c 100644 --- a/loader/src/main/java/net/neoforged/neoforgespi/locating/IDependencyLocator.java +++ b/loader/src/main/java/net/neoforged/neoforgespi/locating/IDependencyLocator.java @@ -5,13 +5,21 @@ package net.neoforged.neoforgespi.locating; +import cpw.mods.jarhandling.JarContents; import java.util.List; +import java.util.Optional; +import java.util.function.Function; /** * Loaded as a ServiceLoader. Takes mechanisms for locating candidate "mod-dependencies". * and transforms them into {@link IModFile} objects. */ -public interface IDependencyLocator extends IModProvider { +public interface IDependencyLocator { + /** + * {@return the name of this dependency locator} + */ + String name(); + /** * Invoked to find all mod dependencies that this dependency locator can find. * It is not guaranteed that all these are loaded into the runtime, @@ -19,5 +27,5 @@ public interface IDependencyLocator extends IModProvider { * * @return All found, or discovered, mod files which function as dependencies. */ - List scanMods(final Iterable loadedMods); + List scanMods(final Iterable loadedMods, final Function> provider); } diff --git a/loader/src/main/java/net/neoforged/neoforgespi/locating/IModLocator.java b/loader/src/main/java/net/neoforged/neoforgespi/locating/IModLocator.java index 7cc4708a1..aa09f6011 100644 --- a/loader/src/main/java/net/neoforged/neoforgespi/locating/IModLocator.java +++ b/loader/src/main/java/net/neoforged/neoforgespi/locating/IModLocator.java @@ -16,27 +16,28 @@ package net.neoforged.neoforgespi.locating; -import java.util.List; +import cpw.mods.jarhandling.JarContents; +import java.util.Map; +import java.util.stream.Stream; /** - * Loaded as a ServiceLoader. Takes mechanisms for locating candidate "mods" - * and transforms them into {@link IModFile} objects. + * Loaded as a ServiceLoader. Takes mechanisms for locating candidate "mod" JARs. */ -public interface IModLocator extends IModProvider { +public interface IModLocator { /** - * A simple record which contains either a valid modfile or a reason one failed to be constructed by {@link #scanMods()} - * - * @param file the file - * @param ex an exception that occurred during the attempt to load the mod + * {@return all mod paths that this mod locator can find} */ - record ModFileOrException(IModFile file, ModFileLoadingException ex) {} + Stream scanCandidates(); /** - * Invoked to find all mods that this mod locator can find. - * It is not guaranteed that all these are loaded into the runtime, - * as such the result of this method should be seen as a list of candidates to load. + * {@return the name of this locator} + */ + String name(); + + /** + * Invoked with the game startup arguments to allow for configuration of the provider. * - * @return All found, or discovered, mod files. + * @param arguments The arguments. */ - List scanMods(); + default void initArguments(Map arguments) {} } diff --git a/loader/src/main/java/net/neoforged/neoforgespi/locating/IModProvider.java b/loader/src/main/java/net/neoforged/neoforgespi/locating/IModProvider.java index 187a958fa..00cea67cd 100644 --- a/loader/src/main/java/net/neoforged/neoforgespi/locating/IModProvider.java +++ b/loader/src/main/java/net/neoforged/neoforgespi/locating/IModProvider.java @@ -5,9 +5,16 @@ package net.neoforged.neoforgespi.locating; +import cpw.mods.jarhandling.JarContents; +import cpw.mods.jarhandling.SecureJar; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; -import java.util.Map; +import java.util.List; import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Stream; +import org.jetbrains.annotations.Nullable; /** * Describes objects which can provide mods (or related jars) to the loading runtime. @@ -24,17 +31,17 @@ public interface IModProvider { /** * Invoked to scan a particular {@link IModFile} for metadata. * - * @param modFile The mod file to scan. + * @param file The mod file to scan. * @param pathConsumer A consumer which extracts metadata from the path given. */ - void scanFile(IModFile modFile, Consumer pathConsumer); - - /** - * Invoked with the game startup arguments to allow for configuration of the provider. - * - * @param arguments The arguments. - */ - void initArguments(Map arguments); + default void scanFile(IModFile file, Consumer pathConsumer) { + final Function status = p -> file.getSecureJar().verifyPath(p); + try (Stream files = Files.find(file.getSecureJar().getRootPath(), Integer.MAX_VALUE, (p, a) -> p.getNameCount() > 0 && p.getFileName().toString().endsWith(".class"))) { + file.setSecurityStatus(files.peek(pathConsumer).map(status).reduce((s1, s2) -> SecureJar.Status.values()[Math.min(s1.ordinal(), s2.ordinal())]).orElse(SecureJar.Status.INVALID)); + } catch (IOException e) { + e.printStackTrace(); + } + } /** * Indicates if the given mod file is valid. @@ -43,4 +50,28 @@ public interface IModProvider { * @return True to mark as valid, false otherwise. */ boolean isValid(IModFile modFile); + + /** + * A simple record which contains either a valid modfile or a reason one failed to be constructed by {@link #scanMods()} + * + * @param file the file + * @param ex an exception that occurred during the attempt to load the mod + */ + record ModFileOrException(IModFile file, ModFileLoadingException ex) {} + + /** + * Provides a mod from the given {@code jar}. + * + * @param jar the mod jar contents + * @return {@code null} if this provider can't provide a mod from that jar, or {@link ModFileOrException} otherwise + */ + @Nullable + ModFileOrException provide(JarContents jar); + + /** + * {@return a list of standalone mods that this provider can provide without being found by a locator} + */ + default List provide() { + return List.of(); + } } diff --git a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/InvalidModFileException.java b/loader/src/main/java/net/neoforged/neoforgespi/locating/InvalidModFileException.java similarity index 85% rename from loader/src/main/java/net/neoforged/fml/loading/moddiscovery/InvalidModFileException.java rename to loader/src/main/java/net/neoforged/neoforgespi/locating/InvalidModFileException.java index 32a591713..39a9f37a8 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/InvalidModFileException.java +++ b/loader/src/main/java/net/neoforged/neoforgespi/locating/InvalidModFileException.java @@ -3,12 +3,11 @@ * SPDX-License-Identifier: LGPL-2.1-only */ -package net.neoforged.fml.loading.moddiscovery; +package net.neoforged.neoforgespi.locating; import java.util.Locale; import java.util.Optional; import net.neoforged.neoforgespi.language.IModFileInfo; -import net.neoforged.neoforgespi.locating.ModFileLoadingException; public class InvalidModFileException extends ModFileLoadingException { private final IModFileInfo modFileInfo; diff --git a/loader/src/main/java/net/neoforged/neoforgespi/locating/ModFileFactory.java b/loader/src/main/java/net/neoforged/neoforgespi/locating/ModFileFactory.java index 7a6f1d480..a749d765e 100644 --- a/loader/src/main/java/net/neoforged/neoforgespi/locating/ModFileFactory.java +++ b/loader/src/main/java/net/neoforged/neoforgespi/locating/ModFileFactory.java @@ -24,13 +24,25 @@ public interface ModFileFactory { * @param jar The secure jar to load the mod file from. * @param provider The provider which is offering the mod file for loading- * @param parser The parser which is responsible for parsing the metadata of the file itself. + * @param type the type of the mod * @return The mod file. */ - IModFile build(final SecureJar jar, final IModProvider provider, ModFileInfoParser parser); + IModFile build(final SecureJar jar, final IModProvider provider, ModFileInfoParser parser, IModFile.Type type) throws InvalidModFileException; + + /** + * Builds a new mod file instance depending on the current runtime. + * + * @param jar The secure jar to load the mod file from. + * @param provider The provider which is offering the mod file for loading- + * @param parser The parser which is responsible for parsing the metadata of the file itself. + * @return The mod file. + */ + IModFile build(final SecureJar jar, final IModProvider provider, ModFileInfoParser parser) throws InvalidModFileException; /** * A parser specification for building a particular mod files metadata. */ + @FunctionalInterface interface ModFileInfoParser { /** * Invoked to get the freshly build mod files metadata. @@ -38,6 +50,6 @@ interface ModFileInfoParser { * @param file The file to parse the metadata for. * @return The mod file metadata info. */ - IModFileInfo build(IModFile file); + IModFileInfo build(IModFile file) throws InvalidModFileException; } }