getModLoadingIssues() {
+ return modLoadingIssues;
}
}
diff --git a/loader/src/main/java/net/neoforged/fml/loading/MavenCoordinate.java b/loader/src/main/java/net/neoforged/fml/loading/MavenCoordinate.java
new file mode 100644
index 000000000..09d532519
--- /dev/null
+++ b/loader/src/main/java/net/neoforged/fml/loading/MavenCoordinate.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) NeoForged and contributors
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+package net.neoforged.fml.loading;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Objects;
+
+/**
+ * Models the Maven coordinates for an artifact.
+ */
+public record MavenCoordinate(String groupId, String artifactId, String extension, String classifier, String version) {
+ public MavenCoordinate {
+ Objects.requireNonNull(groupId);
+ Objects.requireNonNull(artifactId);
+ Objects.requireNonNull(version);
+ if (extension == null) {
+ extension = "";
+ }
+ if (classifier == null) {
+ classifier = "";
+ }
+ }
+
+ /**
+ * Valid forms:
+ *
+ * - {@code groupId:artifactId:version}
+ * - {@code groupId:artifactId:version:classifier}
+ * - {@code groupId:artifactId:version:classifier@extension}
+ * - {@code groupId:artifactId:version@extension}
+ *
+ */
+ public static MavenCoordinate parse(String coordinate) {
+ var coordinateAndExt = coordinate.split("@");
+ String extension = "";
+ if (coordinateAndExt.length > 2) {
+ throw new IllegalArgumentException("Malformed Maven coordinate: " + coordinate);
+ } else if (coordinateAndExt.length == 2) {
+ extension = coordinateAndExt[1];
+ coordinate = coordinateAndExt[0];
+ }
+
+ var parts = coordinate.split(":");
+ if (parts.length != 3 && parts.length != 4) {
+ throw new IllegalArgumentException("Malformed Maven coordinate: " + coordinate);
+ }
+
+ var groupId = parts[0];
+ var artifactId = parts[1];
+ var version = parts[2];
+ var classifier = parts.length == 4 ? parts[3] : "";
+ return new MavenCoordinate(groupId, artifactId, extension, classifier, version);
+ }
+
+ /**
+ * Constructs a path relative to the root of a Maven repository pointing to the artifact expressed through
+ * these coordinates.
+ */
+ public Path toRelativeRepositoryPath() {
+ final String fileName = artifactId + "-" + version +
+ (!classifier.isEmpty() ? "-" + classifier : "") +
+ (!extension.isEmpty() ? "." + extension : ".jar");
+
+ String[] groups = groupId.split("\\.");
+ Path result = Paths.get(groups[0]);
+ for (int i = 1; i < groups.length; i++) {
+ result = result.resolve(groups[i]);
+ }
+
+ return result.resolve(artifactId).resolve(version).resolve(fileName);
+ }
+
+ @Override
+ public String toString() {
+ var result = new StringBuilder();
+ result.append(groupId).append(":").append(artifactId).append(":").append(version);
+ if (!classifier.isEmpty()) {
+ result.append(":").append(classifier);
+ }
+ if (!extension.isEmpty()) {
+ result.append("@").append(extension);
+ }
+ return result.toString();
+ }
+}
diff --git a/loader/src/main/java/net/neoforged/fml/loading/MavenCoordinateResolver.java b/loader/src/main/java/net/neoforged/fml/loading/MavenCoordinateResolver.java
index f5ab00059..6707bb1be 100644
--- a/loader/src/main/java/net/neoforged/fml/loading/MavenCoordinateResolver.java
+++ b/loader/src/main/java/net/neoforged/fml/loading/MavenCoordinateResolver.java
@@ -6,38 +6,20 @@
package net.neoforged.fml.loading;
import java.nio.file.Path;
-import java.nio.file.Paths;
/**
* Convert a maven coordinate into a Path.
- *
+ *
* This is gradle standard not maven standard coordinate formatting
* {@code :[:]:[@extension]}, must not be {@code null}.
*/
public class MavenCoordinateResolver {
- public static Path get(final String coordinate) {
- final String[] parts = coordinate.split(":");
- final String groupId = parts[0];
- final String artifactId = parts[1];
- final String classifier = parts.length > 3 ? parts[2] : "";
- final String[] versext = parts[parts.length - 1].split("@");
- final String version = versext[0];
- final String extension = versext.length > 1 ? versext[1] : "";
- return get(groupId, artifactId, extension, classifier, version);
+ public static Path get(String coordinate) {
+ return MavenCoordinate.parse(coordinate).toRelativeRepositoryPath();
}
- public static Path get(final String groupId, final String artifactId, final String extension, final String classifier, final String version) {
- final String fileName = artifactId + "-" + version +
- (!classifier.isEmpty() ? "-" + classifier : "") +
- (!extension.isEmpty() ? "." + extension : ".jar");
-
- String[] groups = groupId.split("\\.");
- Path result = Paths.get(groups[0]);
- for (int i = 1; i < groups.length; i++) {
- result = result.resolve(groups[i]);
- }
-
- return result.resolve(artifactId).resolve(version).resolve(fileName);
+ public static Path get(String groupId, String artifactId, String extension, String classifier, String version) {
+ return new MavenCoordinate(groupId, artifactId, extension, classifier, version).toRelativeRepositoryPath();
}
}
diff --git a/loader/src/main/java/net/neoforged/fml/loading/ModDirTransformerDiscoverer.java b/loader/src/main/java/net/neoforged/fml/loading/ModDirTransformerDiscoverer.java
index 43cdf7c78..44be5bd95 100644
--- a/loader/src/main/java/net/neoforged/fml/loading/ModDirTransformerDiscoverer.java
+++ b/loader/src/main/java/net/neoforged/fml/loading/ModDirTransformerDiscoverer.java
@@ -56,7 +56,7 @@ public void earlyInitialization(final String launchTarget, final String[] argume
@Override
public List candidates(final Path gameDirectory) {
- ModDirTransformerDiscoverer.scan(gameDirectory);
+ scan(gameDirectory);
return List.copyOf(found);
}
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..162089711 100644
--- a/loader/src/main/java/net/neoforged/fml/loading/ModSorter.java
+++ b/loader/src/main/java/net/neoforged/fml/loading/ModSorter.java
@@ -29,7 +29,8 @@
import java.util.jar.Manifest;
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import net.neoforged.fml.loading.moddiscovery.MinecraftLocator;
+import net.neoforged.fml.ModLoadingException;
+import net.neoforged.fml.ModLoadingIssue;
import net.neoforged.fml.loading.moddiscovery.ModFile;
import net.neoforged.fml.loading.moddiscovery.ModFileInfo;
import net.neoforged.fml.loading.moddiscovery.ModInfo;
@@ -52,13 +53,13 @@ private ModSorter(final List modFiles) {
this.uniqueModListBuilder = new UniqueModListBuilder(modFiles);
}
- public static LoadingModList sort(List mods, final List errors) {
+ public static LoadingModList sort(List mods, final List issues) {
final ModSorter ms = new ModSorter(mods);
try {
ms.buildUniqueList();
- } catch (EarlyLoadingException e) {
+ } catch (ModLoadingException e) {
// We cannot build any list with duped mods. We have to abort immediately and report it
- return LoadingModList.of(ms.systemMods, ms.systemMods.stream().map(mf -> (ModInfo) mf.getModInfos().get(0)).collect(toList()), e);
+ return LoadingModList.of(ms.systemMods, ms.systemMods.stream().map(mf -> (ModInfo) mf.getModInfos().get(0)).collect(toList()), e.getIssues());
}
// try and validate dependencies
@@ -68,28 +69,40 @@ 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()), concat(issues, resolutionResult.buildErrorMessages()));
} else {
// Otherwise, lets try and sort the modlist and proceed
- EarlyLoadingException earlyLoadingException = null;
+ ModLoadingException modLoadingException = null;
try {
ms.sort();
- } catch (EarlyLoadingException e) {
- earlyLoadingException = e;
+ } catch (ModLoadingException e) {
+ modLoadingException = e;
+ }
+ if (modLoadingException == null) {
+ list = LoadingModList.of(ms.modFiles, ms.sortedList, issues);
+ } else {
+ list = LoadingModList.of(ms.modFiles, ms.sortedList, concat(issues, modLoadingException.getIssues()));
}
- list = LoadingModList.of(ms.modFiles, ms.sortedList, earlyLoadingException);
}
// If we have conflicts those are considered warnings
if (!resolutionResult.discouraged.isEmpty()) {
- list.getWarnings().add(new EarlyLoadingException(
+ list.getModLoadingIssues().add(ModLoadingIssue.warning(
"found mod conflicts",
- null,
resolutionResult.buildWarningMessages()));
}
return list;
}
+ @SafeVarargs
+ private static List concat(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
@@ -119,9 +132,9 @@ private void sort() {
.mapMulti(Iterable::forEach)
.mapMulti((mf, c) -> mf.getMods().forEach(c))
.map(IModInfo::getModId)
- .map(list -> new EarlyLoadingException.ExceptionData("fml.modloading.cycle", list))
+ .map(list -> ModLoadingIssue.error("fml.modloading.cycle", list).withCause(e))
.toList();
- throw new EarlyLoadingException("Sorting error", e, dataList);
+ throw new ModLoadingException(dataList);
}
this.sortedList = sorted.stream()
.map(ModFileInfo::getMods)
@@ -166,9 +179,8 @@ private void detectSystemMods(final Map> modFilesByFirstId
final Set systemMods = new HashSet<>();
// The minecraft mod is always a system mod
systemMods.add("minecraft");
- // Find mod file from MinecraftLocator to define the system mods
+ // Find system mod files and scan them for system mods
modFiles.stream()
- .filter(modFile -> modFile.getProvider().getClass() == MinecraftLocator.class)
.map(ModFile::getSecureJar)
.map(SecureJar::moduleDataProvider)
.map(SecureJar.ModuleDataProvider::getManifest)
@@ -184,9 +196,7 @@ private void detectSystemMods(final Map> modFilesByFirstId
var container = modFilesByFirstId.get(systemMod);
if (container != null && !container.isEmpty()) {
LOGGER.debug("Found system mod: {}", systemMod);
- this.systemMods.add((ModFile) container.get(0));
- } else {
- throw new IllegalStateException("Failed to find system mod: " + systemMod);
+ this.systemMods.add(container.getFirst());
}
}
}
@@ -196,26 +206,26 @@ public record DependencyResolutionResult(
Collection discouraged,
Collection versionResolution,
Map modVersions) {
- public List buildWarningMessages() {
+ public List buildWarningMessages() {
return Stream.concat(discouraged.stream()
- .map(mv -> new EarlyLoadingException.ExceptionData("fml.modloading.discouragedmod",
- mv.getOwner(), mv.getModId(), mv.getOwner().getModId(), mv.getVersionRange(),
- modVersions.get(mv.getModId()), mv.getReason().orElse("fml.modloading.discouragedmod.noreason"))),
+ .map(mv -> ModLoadingIssue.warning("fml.modloading.discouragedmod",
+ mv.getModId(), mv.getOwner().getModId(), mv.getVersionRange(),
+ modVersions.get(mv.getModId()), mv.getReason().orElse("fml.modloading.discouragedmod.noreason")).withAffectedMod(mv.getOwner())),
- Stream.of(new EarlyLoadingException.ExceptionData("fml.modloading.discouragedmod.proceed")))
+ Stream.of(ModLoadingIssue.warning("fml.modloading.discouragedmod.proceed")))
.toList();
}
- public List buildErrorMessages() {
+ public List buildErrorMessages() {
return Stream.concat(
versionResolution.stream()
- .map(mv -> new EarlyLoadingException.ExceptionData(mv.getType() == IModInfo.DependencyType.REQUIRED ? "fml.modloading.missingdependency" : "fml.modloading.missingdependency.optional",
- mv.getOwner(), mv.getModId(), mv.getOwner().getModId(), mv.getVersionRange(),
- modVersions.getOrDefault(mv.getModId(), new DefaultArtifactVersion("null")), mv.getReason())),
+ .map(mv -> ModLoadingIssue.error(mv.getType() == IModInfo.DependencyType.REQUIRED ? "fml.modloading.missingdependency" : "fml.modloading.missingdependency.optional",
+ mv.getModId(), mv.getOwner().getModId(), mv.getVersionRange(),
+ modVersions.getOrDefault(mv.getModId(), new DefaultArtifactVersion("null")), mv.getReason()).withAffectedMod(mv.getOwner())),
incompatibilities.stream()
- .map(mv -> new EarlyLoadingException.ExceptionData("fml.modloading.incompatiblemod",
- mv.getOwner(), mv.getModId(), mv.getOwner().getModId(), mv.getVersionRange(),
- modVersions.get(mv.getModId()), mv.getReason().orElse("fml.modloading.incompatiblemod.noreason"))))
+ .map(mv -> ModLoadingIssue.error("fml.modloading.incompatiblemod",
+ mv.getModId(), mv.getOwner().getModId(), mv.getVersionRange(),
+ modVersions.get(mv.getModId()), mv.getReason().orElse("fml.modloading.incompatiblemod.noreason")).withAffectedMod(mv.getOwner())))
.toList();
}
}
diff --git a/loader/src/main/java/net/neoforged/fml/loading/TransformerDiscovererConstants.java b/loader/src/main/java/net/neoforged/fml/loading/TransformerDiscovererConstants.java
index af1ec5e8c..09a9cc22c 100644
--- a/loader/src/main/java/net/neoforged/fml/loading/TransformerDiscovererConstants.java
+++ b/loader/src/main/java/net/neoforged/fml/loading/TransformerDiscovererConstants.java
@@ -5,12 +5,13 @@
package net.neoforged.fml.loading;
-import cpw.mods.jarhandling.JarContentsBuilder;
+import cpw.mods.jarhandling.JarContents;
import cpw.mods.jarhandling.JarMetadata;
import cpw.mods.jarhandling.SecureJar;
import cpw.mods.modlauncher.api.IModuleLayerManager.Layer;
import cpw.mods.modlauncher.serviceapi.ITransformerDiscoveryService;
import java.nio.file.Path;
+import java.util.Collection;
import java.util.Set;
/**
@@ -32,8 +33,16 @@ private TransformerDiscovererConstants() {}
"net.neoforged.fml.loading.ImmediateWindowProvider", // FIXME: remove this when removing the legacy ImmediateWindowProvider
"net.neoforged.neoforgespi.earlywindow.ImmediateWindowProvider");
- public static boolean shouldLoadInServiceLayer(Path... path) {
- JarMetadata metadata = JarMetadata.from(new JarContentsBuilder().paths(path).build());
+ public static boolean shouldLoadInServiceLayer(Collection paths) {
+ return shouldLoadInServiceLayer(JarContents.of(paths));
+ }
+
+ public static boolean shouldLoadInServiceLayer(Path path) {
+ return shouldLoadInServiceLayer(JarContents.of(path));
+ }
+
+ public static boolean shouldLoadInServiceLayer(JarContents jarContents) {
+ JarMetadata metadata = JarMetadata.from(jarContents);
return metadata.providers().stream()
.map(SecureJar.Provider::serviceName)
.anyMatch(SERVICES::contains);
diff --git a/loader/src/main/java/net/neoforged/fml/loading/UniqueModListBuilder.java b/loader/src/main/java/net/neoforged/fml/loading/UniqueModListBuilder.java
index b25357e06..a156b51b9 100644
--- a/loader/src/main/java/net/neoforged/fml/loading/UniqueModListBuilder.java
+++ b/loader/src/main/java/net/neoforged/fml/loading/UniqueModListBuilder.java
@@ -17,6 +17,8 @@
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
+import net.neoforged.fml.ModLoadingException;
+import net.neoforged.fml.ModLoadingIssue;
import net.neoforged.fml.loading.moddiscovery.ModFile;
import net.neoforged.neoforgespi.language.IModInfo;
import org.apache.maven.artifact.versioning.ArtifactVersion;
@@ -79,10 +81,8 @@ public UniqueModListData buildUniqueList() {
.toList();
if (!dupedModErrors.isEmpty()) {
- LOGGER.error(LOADING, "Found duplicate mods:\n{}", dupedModErrors.stream().collect(joining("\n")));
- throw new EarlyLoadingException("Duplicate mods found", null, dupedModErrors.stream()
- .map(s -> new EarlyLoadingException.ExceptionData(s))
- .toList());
+ LOGGER.error(LOADING, "Found duplicate mods:\n{}", String.join("\n", dupedModErrors));
+ throw new ModLoadingException(dupedModErrors.stream().map(ModLoadingIssue::error).toList());
}
final List dupedLibErrors = versionedLibIds.values().stream()
@@ -94,10 +94,8 @@ public UniqueModListData buildUniqueList() {
.toList();
if (!dupedLibErrors.isEmpty()) {
- LOGGER.error(LOADING, "Found duplicate plugins or libraries:\n{}", dupedLibErrors.stream().collect(joining("\n")));
- throw new EarlyLoadingException("Duplicate plugins or libraries found", null, dupedLibErrors.stream()
- .map(s -> new EarlyLoadingException.ExceptionData(s))
- .toList());
+ LOGGER.error(LOADING, "Found duplicate plugins or libraries:\n{}", String.join("\n", dupedLibErrors));
+ throw new ModLoadingException(dupedLibErrors.stream().map(ModLoadingIssue::error).toList());
}
// Collect unique mod files by module name. This will be used for deduping purposes
diff --git a/loader/src/main/java/net/neoforged/fml/loading/VersionInfo.java b/loader/src/main/java/net/neoforged/fml/loading/VersionInfo.java
index 98a917810..8255b5a1e 100644
--- a/loader/src/main/java/net/neoforged/fml/loading/VersionInfo.java
+++ b/loader/src/main/java/net/neoforged/fml/loading/VersionInfo.java
@@ -5,13 +5,7 @@
package net.neoforged.fml.loading;
-import java.util.Map;
-
public record VersionInfo(String neoForgeVersion, String fmlVersion, String mcVersion, String neoFormVersion) {
- VersionInfo(Map arguments) {
- this((String) arguments.get("neoForgeVersion"), (String) arguments.get("fmlVersion"), (String) arguments.get("mcVersion"), (String) arguments.get("neoFormVersion"));
- }
-
public String mcAndFmlVersion() {
return mcVersion + "-" + fmlVersion;
}
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
deleted file mode 100644
index 8ac424901..000000000
--- a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/AbstractJarFileModLocator.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (c) Forge Development LLC and contributors
- * SPDX-License-Identifier: LGPL-2.1-only
- */
-
-package net.neoforged.fml.loading.moddiscovery;
-
-import java.io.File;
-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();
-
- 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
deleted file mode 100644
index aaadda4c1..000000000
--- a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/BuiltinGameLibraryLocator.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (c) Forge Development LLC and contributors
- * SPDX-License-Identifier: LGPL-2.1-only
- */
-
-package net.neoforged.fml.loading.moddiscovery;
-
-import java.nio.file.Path;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-public class BuiltinGameLibraryLocator extends AbstractJarFileModLocator {
- private final List legacyClasspath = AbstractJarFileModLocator.getLegacyClasspath();
-
- @Override
- public String name() {
- return "builtin game layer libraries";
- }
-
- @Override
- public void initArguments(Map arguments) {}
-
- @Override
- public Stream scanCandidates() {
- String gameLibrariesStr = System.getProperty("fml.gameLayerLibraries");
- if (gameLibrariesStr == null || gameLibrariesStr.isBlank())
- return Stream.of();
-
- Set targets = Arrays.stream(gameLibrariesStr.split(",")).map(Path::of).collect(Collectors.toSet());
- var paths = Stream.builder();
-
- for (Path path : this.legacyClasspath) {
- if (targets.contains(path.getFileName()))
- paths.add(path);
- }
-
- return paths.build();
- }
-}
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
deleted file mode 100644
index 2f9ac8f62..000000000
--- a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ClasspathLocator.java
+++ /dev/null
@@ -1,90 +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.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Stream;
-import net.neoforged.fml.loading.ClasspathLocatorUtils;
-import net.neoforged.fml.loading.LogMarkers;
-import org.slf4j.Logger;
-
-public class ClasspathLocator extends AbstractJarFileModLocator {
- private static final Logger LOGGER = LogUtils.getLogger();
- private final List legacyClasspath = AbstractJarFileModLocator.getLegacyClasspath();
- private boolean enabled = false;
-
- @Override
- public String name() {
- return "userdev classpath";
- }
-
- @Override
- public Stream scanCandidates() {
- if (!enabled)
- return Stream.of();
-
- try {
- var claimed = new ArrayList<>(legacyClasspath);
- var paths = Stream.builder();
-
- findPaths(claimed, MODS_TOML).forEach(paths::add);
- findPaths(claimed, MANIFEST).forEach(paths::add);
-
- return paths.build();
- } catch (IOException e) {
- LOGGER.error(LogMarkers.SCAN, "Error trying to find resources", e);
- throw new RuntimeException(e);
- }
- }
-
- private List findPaths(List claimed, String resource) throws IOException {
- var ret = new ArrayList();
- final Enumeration resources = ClassLoader.getSystemClassLoader().getResources(resource);
- while (resources.hasMoreElements()) {
- URL url = resources.nextElement();
- Path path = ClasspathLocatorUtils.findJarPathFor(resource, resource, url);
- if (claimed.stream().anyMatch(path::equals) || !Files.exists(path) || Files.isDirectory(path))
- continue;
- ret.add(path);
- }
- return ret;
- }
-
- @Override
- public void initArguments(Map arguments) {
- var launchTarget = (String) arguments.get("launchTarget");
- enabled = launchTarget != null && launchTarget.contains("dev");
- }
-
- private Path findJarPathFor(final String resourceName, final String jarName, final URL resource) {
- try {
- Path path;
- final URI uri = resource.toURI();
- if (uri.getScheme().equals("jar") && uri.getRawSchemeSpecificPart().contains("!/")) {
- int lastExcl = uri.getRawSchemeSpecificPart().lastIndexOf("!/");
- path = Paths.get(new URI(uri.getRawSchemeSpecificPart().substring(0, lastExcl)));
- } else {
- path = Paths.get(new URI("file://" + uri.getRawSchemeSpecificPart().substring(0, uri.getRawSchemeSpecificPart().length() - resourceName.length())));
- }
- //LOGGER.debug(CORE, "Found JAR {} at path {}", jarName, path.toString());
- return path;
- } catch (NullPointerException | URISyntaxException e) {
- LOGGER.error(LogMarkers.SCAN, "Failed to find JAR for class {} - {}", resourceName, jarName);
- throw new RuntimeException("Unable to locate " + resourceName + " - " + jarName, 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
deleted file mode 100644
index f748c58f9..000000000
--- a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/ExplodedDirectoryLocator.java
+++ /dev/null
@@ -1,83 +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.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;
-
-public class ExplodedDirectoryLocator implements IModLocator {
- private static final Logger LOGGER = LogUtils.getLogger();
-
- 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 -> {
- 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 mods.values().stream().map(mf -> new IModLocator.ModFileOrException(mf, null)).toList();
- }
-
- @Override
- 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}";
- }
-
- @SuppressWarnings("unchecked")
- @Override
- public void initArguments(final Map arguments) {
- final var explodedTargets = ((Map>) arguments).get("explodedTargets");
- if (explodedTargets != null && !explodedTargets.isEmpty()) {
- explodedMods.addAll(explodedTargets);
- }
- }
-
- @Override
- public boolean isValid(final IModFile modFile) {
- return true;
- }
-}
diff --git a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/IncompatibleModReason.java b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/IncompatibleModReason.java
new file mode 100644
index 000000000..27a863edb
--- /dev/null
+++ b/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/IncompatibleModReason.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) Forge Development LLC and contributors
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+package net.neoforged.fml.loading.moddiscovery;
+
+import cpw.mods.jarhandling.JarContents;
+import java.util.Arrays;
+import java.util.Optional;
+import java.util.function.Predicate;
+import net.neoforged.fml.loading.StringUtils;
+
+/**
+ * When we find a jar file that no {@link net.neoforged.neoforgespi.locating.IModFileReader} can handle,
+ * we try to detect if the mod potentially came from another modding system and warn the user about it
+ * not being compatible.
+ */
+public enum IncompatibleModReason {
+ OLDFORGE(filePresent("mcmod.info")),
+ MINECRAFT_FORGE(filePresent("META-INF/mods.toml")),
+ FABRIC(filePresent("fabric.mod.json")),
+ QUILT(filePresent("quilt.mod.json")),
+ LITELOADER(filePresent("litemod.json")),
+ OPTIFINE(filePresent("optifine/Installer.class")),
+ BUKKIT(filePresent("plugin.yml"));
+
+ private final Predicate ident;
+
+ IncompatibleModReason(Predicate identifier) {
+ this.ident = identifier;
+ }
+
+ public String getReason() {
+ return "fml.modloading.brokenfile." + StringUtils.toLowerCase(name());
+ }
+
+ public static Optional detect(JarContents jar) {
+ return Arrays.stream(values())
+ .filter(i -> i.ident.test(jar))
+ .findAny();
+ }
+
+ private static Predicate filePresent(String filename) {
+ return jarContents -> jarContents.findFile(filename).isPresent();
+ }
+}
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
deleted file mode 100644
index 9f8aa80f8..000000000
--- a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/InvalidModIdentifier.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (c) Forge Development LLC and contributors
- * SPDX-License-Identifier: LGPL-2.1-only
- */
-
-package net.neoforged.fml.loading.moddiscovery;
-
-import cpw.mods.modlauncher.api.LambdaExceptionUtils;
-import java.nio.file.Path;
-import java.util.Arrays;
-import java.util.Optional;
-import java.util.function.BiPredicate;
-import java.util.zip.ZipFile;
-import net.neoforged.fml.loading.StringUtils;
-
-public enum InvalidModIdentifier {
- OLDFORGE(filePresent("mcmod.info")),
- FABRIC(filePresent("fabric.mod.json")),
- LITELOADER(filePresent("litemod.json")),
- OPTIFINE(filePresent("optifine/Installer.class")),
- BUKKIT(filePresent("plugin.yml")),
- INVALIDZIP((f, zf) -> !zf.isPresent());
-
- private BiPredicate> ident;
-
- InvalidModIdentifier(BiPredicate> identifier) {
- this.ident = identifier;
- }
-
- private 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;
- }
-
- 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();
- }
- }
-}
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
deleted file mode 100644
index f3d6c7486..000000000
--- a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/MavenDirectoryLocator.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 java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import net.neoforged.fml.loading.FMLPaths;
-import net.neoforged.fml.loading.MavenCoordinateResolver;
-
-public class MavenDirectoryLocator extends AbstractJarFileModLocator {
- private List modCoords;
-
- @Override
- public Stream scanCandidates() {
- return modCoords.stream();
- }
-
- @Override
- public String name() {
- return "maven libs";
- }
-
- public String toString() {
- return "{Maven Directory locator for mods " + this.modCoords + "}";
- }
-
- @SuppressWarnings("unchecked")
- @Override
- public void initArguments(final Map arguments) {
- final List mavenRoots = (List) arguments.get("mavenRoots");
- final List mavenRootPaths = mavenRoots.stream().map(n -> FMLPaths.GAMEDIR.get().resolve(n)).collect(Collectors.toList());
- final List mods = (List) arguments.get("mods");
- final List listedMods = ModListHandler.processModLists((List) arguments.get("modLists"), mavenRootPaths);
-
- List localModCoords = Stream.concat(mods.stream(), listedMods.stream()).map(MavenCoordinateResolver::get).collect(Collectors.toList());
- // find the modCoords path in each supplied maven path, and turn it into a mod file. (skips not found files)
-
- this.modCoords = localModCoords.stream().map(mc -> mavenRootPaths.stream().map(root -> root.resolve(mc)).filter(path -> Files.exists(path)).findFirst().orElseThrow(() -> new IllegalArgumentException("Failed to locate requested mod coordinate " + mc))).collect(Collectors.toList());
- }
-}
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
deleted file mode 100644
index d1ae069f8..000000000
--- a/loader/src/main/java/net/neoforged/fml/loading/moddiscovery/MinecraftLocator.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (c) Forge Development LLC and contributors
- * SPDX-License-Identifier: LGPL-2.1-only
- */
-
-package net.neoforged.fml.loading.moddiscovery;
-
-import com.electronwill.nightconfig.core.Config;
-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.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.ModFileFactory;
-import org.slf4j.Logger;
-
-public class MinecraftLocator extends AbstractModProvider implements IModLocator {
- private static final Logger LOGGER = LogUtils.getLogger();
-
- @Override
- public List scanMods() {
- final var launchHandler = FMLLoader.getLaunchHandler();
- var baseMC = launchHandler.getMinecraftPaths();
- var mcJarContents = new JarContentsBuilder()
- .paths(baseMC.minecraftPaths().toArray(Path[]::new))
- .pathFilter(baseMC.minecraftFilter())
- .build();
- var mcJarMetadata = new ModJarMetadata(mcJarContents);
- 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();
- }
-
- private IModFileInfo buildMinecraftTOML(final IModFile iModFile) {
- final ModFile modFile = (ModFile) iModFile;
- /*
- final Path mcmodtoml = modFile.findResource("META-INF", "minecraftmod.toml");
- if (Files.notExists(mcmodtoml)) {
- LOGGER.fatal(LOADING, "Mod file {} is missing minecraftmod.toml file", modFile.getFilePath());
- return null;
- }
-
- final FileConfig mcmodstomlfile = FileConfig.builder(mcmodtoml).build();
- mcmodstomlfile.load();
- mcmodstomlfile.close();
- */
-
- // We haven't changed this in years, and I can't be asked right now to special case this one file in the path.
- final var conf = Config.inMemory();
- conf.set("modLoader", "minecraft");
- conf.set("loaderVersion", "1");
- conf.set("license", "Mojang Studios, All Rights Reserved");
- final var mods = Config.inMemory();
- mods.set("modId", "minecraft");
- mods.set("version", FMLLoader.versionInfo().mcVersion());
- mods.set("displayName", "Minecraft");
- mods.set("logoFile", "mcplogo.png");
- mods.set("credits", "Mojang, deobfuscated by MCP");
- mods.set("authors", "MCP: Searge,ProfMobius,IngisKahn,Fesh0r,ZeuX,R4wk,LexManos,Bspkrs");
- mods.set("description", "Minecraft, decompiled and deobfuscated with MCP technology");
- conf.set("mods", List.of(mods));
- /*
- conf.putAll(mcmodstomlfile);
-
- final var extralangs = Stream.builder();
- final Path forgemodtoml = modFile.findResource("META-INF", "mods.toml");
- if (Files.notExists(forgemodtoml)) {
- LOGGER.info("No forge mods.toml file found, not loading forge mod");
- } else {
- final FileConfig forgemodstomlfile = FileConfig.builder(forgemodtoml).build();
- forgemodstomlfile.load();
- forgemodstomlfile.close();
- conf.putAll(forgemodstomlfile);
- conf.>get("mods").add(0, mcmodstomlfile.>get("mods").get(0)); // Add MC as a sub-mod
- extralangs.add(new IModFileInfo.LanguageSpec(mcmodstomlfile.get("modLoader"), MavenVersionAdapter.createFromVersionSpec(mcmodstomlfile.get("loaderVersion"))));
- }
- */
-
- final NightConfigWrapper configWrapper = new NightConfigWrapper(conf);
- //final ModFileInfo modFileInfo = new ModFileInfo(modFile, configWrapper, extralangs.build().toList());
- return new ModFileInfo(modFile, configWrapper, configWrapper::setFile, List.of());
- }
-
- @Override
- public String name() {
- return "minecraft";
- }
-
- @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);
- }
-
- @Override
- public void initArguments(final Map arguments) {
- // no op
- }
-}
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..96c86f8f4 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
@@ -5,101 +5,82 @@
package net.neoforged.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 cpw.mods.jarhandling.JarContents;
+import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
-import java.util.Objects;
-import java.util.ServiceLoader;
+import java.util.Optional;
import java.util.stream.Collectors;
-import net.neoforged.fml.loading.EarlyLoadingException;
+import java.util.zip.ZipException;
+import net.neoforged.fml.ModLoadingException;
+import net.neoforged.fml.ModLoadingIssue;
import net.neoforged.fml.loading.ImmediateWindowHandler;
import net.neoforged.fml.loading.LogMarkers;
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.fml.util.ServiceLoaderUtil;
+import net.neoforged.neoforgespi.ILaunchContext;
import net.neoforged.neoforgespi.locating.IDependencyLocator;
+import net.neoforged.neoforgespi.locating.IDiscoveryPipeline;
import net.neoforged.neoforgespi.locating.IModFile;
-import net.neoforged.neoforgespi.locating.IModLocator;
+import net.neoforged.neoforgespi.locating.IModFileCandidateLocator;
+import net.neoforged.neoforgespi.locating.IModFileReader;
+import net.neoforged.neoforgespi.locating.IncompatibleFileReporting;
+import net.neoforged.neoforgespi.locating.ModFileDiscoveryAttributes;
+import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
public class ModDiscoverer {
private static final Logger LOGGER = LogUtils.getLogger();
- 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 -> StartupNotificationManager.locatorConsumer().orElseGet(() -> s -> {}));
- final var moduleLayerManager = Launcher.INSTANCE.environment().findModuleLayerManager().orElseThrow();
- 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 : {}", 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(",")));
- }
+ private final List modFileLocators;
+ private final List dependencyLocators;
+ private final List modFileReaders;
+ private final ILaunchContext launchContext;
+
+ public ModDiscoverer(ILaunchContext launchContext) {
+ this(launchContext, List.of());
+ }
+
+ public ModDiscoverer(ILaunchContext launchContext,
+ Collection additionalModFileLocators) {
+ this.launchContext = launchContext;
+
+ modFileLocators = ServiceLoaderUtil.loadServices(launchContext, IModFileCandidateLocator.class, additionalModFileLocators);
+ modFileReaders = ServiceLoaderUtil.loadServices(launchContext, IModFileReader.class);
+ dependencyLocators = ServiceLoaderUtil.loadServices(launchContext, IDependencyLocator.class);
}
public ModValidator discoverMods() {
- LOGGER.debug(LogMarkers.SCAN, "Scanning for mods and other resources to load. We know {} ways to find mods", modLocatorList.size());
+ LOGGER.debug(LogMarkers.SCAN, "Scanning for mods and other resources to load. We know {} ways to find mods", modFileLocators.size());
List loadedFiles = new ArrayList<>();
- List discoveryErrorData = new ArrayList<>();
+ List discoveryIssues = 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.
- 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());
- 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());
+ // Loop all mod locators to get the root mods to load from.
+ for (var locator : modFileLocators) {
+ LOGGER.debug(LogMarkers.SCAN, "Trying locator {}", locator);
+
+ var defaultAttributes = ModFileDiscoveryAttributes.DEFAULT.withLocator(locator);
+ var pipeline = new DiscoveryPipeline(defaultAttributes, loadedFiles, discoveryIssues);
+ try {
+ locator.findCandidates(launchContext, pipeline);
+ } catch (ModLoadingException e) {
+ discoveryIssues.addAll(e.getIssues());
+ } catch (Exception e) {
+ discoveryIssues.add(ModLoadingIssue.error("fml.modloading.technical_error", locator.toString() + "failed").withCause(e));
}
+
+ LOGGER.debug(LogMarkers.SCAN, "Locator {} found {} mods, {} warnings, {} errors and skipped {} candidates", locator,
+ pipeline.successCount, pipeline.warningCount, pipeline.errorCount, pipeline.skipCount);
}
//First processing run of the mod list. Any duplicates will cause resolution failure and dependency loading will be skipped.
- Map> modFilesMap = Maps.newHashMap();
+ Map