Modify how modfiles load slightly, for better decoupling.

Signed-off-by: cpw <cpw+github@weeksfamily.ca>
This commit is contained in:
cpw 2020-06-23 22:12:39 -04:00
parent b117722d84
commit 20f78ac724
No known key found for this signature in database
GPG Key ID: 8EB3DF749553B1B7
13 changed files with 261 additions and 114 deletions

View File

@ -439,7 +439,7 @@ project(':forge') {
installer 'cpw.mods:grossjava9hacks:1.3.+'
installer 'net.minecraftforge:accesstransformers:2.1.+:shadowed'
installer 'net.minecraftforge:eventbus:2.2.+:service'
installer 'net.minecraftforge:forgespi:2.1.+'
installer 'net.minecraftforge:forgespi:3.0.+'
installer 'net.minecraftforge:coremods:2.0.+'
installer 'net.minecraftforge:unsafe:0.2.+'
installer 'com.electronwill.night-config:core:3.6.2'

View File

@ -79,6 +79,8 @@ public class FMLServiceProvider implements ITransformationService
FMLPaths.setup(environment);
LOGGER.debug(CORE, "Loading configuration");
FMLConfig.load();
LOGGER.debug(CORE, "Preparing ModFile");
environment.computePropertyIfAbsent(Environment.Keys.MODFILEFACTORY.get(), k->ModFile.buildFactory());
arguments = new HashMap<>();
arguments.put("modLists", modListsArgumentList);
arguments.put("mods", modsArgumentList);

View File

@ -54,7 +54,7 @@ public class ExplodedDirectoryLocator implements IModLocator {
Path modtoml = resources.resolve(modstoml);
if (Files.exists(modtoml)) {
LOGGER.debug(LOADING, "Found exploded directory mod manifest at {}", modtoml.toString());
ModFile mf = new ModFile(pathPathPair.getLeft(), this);
ModFile mf = ModFile.newFMLInstance(pathPathPair.getLeft(), this);
mods.put(mf, pathPathPair);
} else {
LOGGER.warn(LOADING, "Failed to find exploded resource mods.toml in directory {}", resources.toString());

View File

@ -36,7 +36,7 @@ public class MavenDirectoryLocator extends AbstractJarFileLocator {
@Override
public List<IModFile> scanMods() {
return modCoords.stream()
.map(mc -> new ModFile(mc, this))
.map(mc -> ModFile.newFMLInstance(mc, this))
.peek(f->modJars.compute(f, (mf, fs)->createFileSystem(mf)))
.collect(Collectors.toList());
}

View File

@ -158,7 +158,7 @@ public class ModDiscoverer {
@Override
public List<IModFile> scanMods() {
return Collections.singletonList(new ModFile(mcJar, this));
return Collections.singletonList(ModFile.newFMLInstance(mcJar, this));
}
@Override

View File

@ -21,13 +21,15 @@ package net.minecraftforge.fml.loading.moddiscovery;
import com.google.common.collect.ImmutableMap;
import net.minecraftforge.fml.loading.progress.StartupMessageManager;
import net.minecraftforge.forgespi.Environment;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.forgespi.language.IModFileInfo;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.forgespi.language.ModFileScanData;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.forgespi.language.IModLanguageProvider;
import net.minecraftforge.forgespi.language.ModFileScanData;
import net.minecraftforge.forgespi.locating.IModFile;
import net.minecraftforge.forgespi.locating.IModLocator;
import net.minecraftforge.forgespi.locating.ModFileFactory;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -57,31 +59,10 @@ public class ModFile implements IModFile {
}
private final String jarVersion;
private final ModFileFactory.ModFileInfoParser parser;
private Map<String, Object> fileProperties;
private IModLanguageProvider loader;
private Throwable scanError;
public void setFileProperties(Map<String, Object> fileProperties)
{
this.fileProperties = fileProperties;
}
@Override
public IModLanguageProvider getLoader()
{
return loader;
}
@Override
public Path findResource(String className)
{
return locator.findPath(this, className);
}
public void identifyLanguage() {
this.loader = FMLLoader.getLanguageLoadingProvider().findLanguage(this, this.modFileInfo.getModLoader(), this.modFileInfo.getModLoaderVersion());
}
private final Path filePath;
private final Type modFileType;
private final Manifest manifest;
@ -94,9 +75,10 @@ public class ModFile implements IModFile {
public static final Attributes.Name TYPE = new Attributes.Name("FMLModType");
public ModFile(final Path file, final IModLocator locator) {
public ModFile(final Path file, final IModLocator locator, final ModFileFactory.ModFileInfoParser parser) {
this.locator = locator;
this.filePath = file;
this.parser = parser;
manifest = locator.findManifest(file).orElse(DEFAULTMANIFEST);
if (manifest != DEFAULTMANIFEST) LOGGER.debug(SCAN,"Mod file {} has a manifest", file);
else LOGGER.debug(SCAN,"Mod file {} is missing a manifest", file);
@ -129,7 +111,7 @@ public class ModFile implements IModFile {
}
public boolean identifyMods() {
this.modFileInfo = ModFileParser.readModList(this);
this.modFileInfo = ModFileParser.readModList(this, this.parser);
if (this.modFileInfo == null) return false;
LOGGER.debug(LOADING,"Loading mod file {} with language {}", this.getFilePath(), this.modFileInfo.getModLoader());
this.coreMods = ModFileParser.getCoreMods(this);
@ -182,6 +164,27 @@ public class ModFile implements IModFile {
StartupMessageManager.modLoaderConsumer().ifPresent(c->c.accept("Completed deep scan of "+this.getFileName()));
}
public void setFileProperties(Map<String, Object> fileProperties)
{
this.fileProperties = fileProperties;
}
@Override
public IModLanguageProvider getLoader()
{
return loader;
}
@Override
public Path findResource(String className)
{
return locator.findPath(this, className);
}
public void identifyLanguage() {
this.loader = FMLLoader.getLanguageLoadingProvider().findLanguage(this, this.modFileInfo.getModLoader(), this.modFileInfo.getModLoaderVersion());
}
@Override
public String toString() {
return "Mod File: " + Objects.toString(this.filePath);
@ -201,4 +204,12 @@ public class ModFile implements IModFile {
public IModFileInfo getModFileInfo() {
return modFileInfo;
}
public static ModFileFactory buildFactory() {
return ModFile::new;
}
public static ModFile newFMLInstance(final Path path, final IModLocator locator) {
return (ModFile) ModFileFactory.FACTORY.build(path, locator, ModFileParser::modsTomlParser);
}
}

View File

@ -20,25 +20,30 @@
package net.minecraftforge.fml.loading.moddiscovery;
import com.electronwill.nightconfig.core.UnmodifiableConfig;
import net.minecraftforge.forgespi.language.MavenVersionAdapter;
import net.minecraftforge.fml.loading.StringUtils;
import net.minecraftforge.forgespi.language.IConfigurable;
import net.minecraftforge.forgespi.language.IModFileInfo;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.fml.loading.StringUtils;
import net.minecraftforge.forgespi.language.MavenVersionAdapter;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.maven.artifact.versioning.VersionRange;
import java.net.URL;
import java.util.*;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.jar.Manifest;
import java.util.stream.Collectors;
import static net.minecraftforge.fml.loading.LogMarkers.LOADING;
public class ModFileInfo implements IModFileInfo
public class ModFileInfo implements IModFileInfo, IConfigurable
{
private static final Logger LOGGER = LogManager.getLogger();
private final UnmodifiableConfig config;
private final IConfigurable config;
private final ModFile modFile;
private final URL issueURL;
private final String modLoader;
@ -47,32 +52,31 @@ public class ModFileInfo implements IModFileInfo
private final List<IModInfo> mods;
private final Map<String,Object> properties;
ModFileInfo(final ModFile modFile, final UnmodifiableConfig config)
ModFileInfo(final ModFile modFile, final IConfigurable config)
{
this.modFile = modFile;
this.config = config;
this.modLoader = config.<String>getOptional("modLoader").
this.modLoader = config.<String>getConfigElement("modLoader").
orElseThrow(()->new InvalidModFileException("Missing ModLoader in file", this));
this.modLoaderVersion = config.<String>getOptional("loaderVersion").
this.modLoaderVersion = config.<String>getConfigElement("loaderVersion").
map(MavenVersionAdapter::createFromVersionSpec).
orElseThrow(()->new InvalidModFileException("Missing ModLoader version in file", this));
this.showAsResourcePack = config.<Boolean>getOrElse("showAsResourcePack", false);
this.properties = config.<UnmodifiableConfig>getOptional("properties").
this.showAsResourcePack = config.<Boolean>getConfigElement("showAsResourcePack").orElse(false);
this.properties = config.<UnmodifiableConfig>getConfigElement("properties").
map(UnmodifiableConfig::valueMap).orElse(Collections.emptyMap());
this.modFile.setFileProperties(this.properties);
if (config.contains("mods") && !(config.get("mods") instanceof Collection)) {
throw new InvalidModFileException("Mods list is not a list.", this);
}
final ArrayList<UnmodifiableConfig> modConfigs = config.getOrElse("mods", ArrayList::new);
this.issueURL = config.<String>getConfigElement("issueTrackerURL").map(StringUtils::toURL).orElse(null);
final List<? extends IConfigurable> modConfigs = config.getConfigList("mods");
if (modConfigs.isEmpty()) {
throw new InvalidModFileException("Missing mods list", this);
}
this.mods = modConfigs.stream().map(mi-> new ModInfo(this, mi)).collect(Collectors.toList());
this.issueURL = config.<String>getOptional("issueTrackerURL").map(StringUtils::toURL).orElse(null);
this.mods = modConfigs.stream()
.map(mi-> new ModInfo(this, mi))
.collect(Collectors.toList());
LOGGER.debug(LOADING, "Found valid mod file {} with {} mods - versions {}",
this.modFile::getFileName,
() -> mods.stream().map(IModInfo::getModId).collect(Collectors.joining(",", "{", "}")),
() -> mods.stream().map(IModInfo::getVersion).map(Objects::toString).collect(Collectors.joining(",", "{", "}")));
() -> this.mods.stream().map(IModInfo::getModId).collect(Collectors.joining(",", "{", "}")),
() -> this.mods.stream().map(IModInfo::getVersion).map(Objects::toString).collect(Collectors.joining(",", "{", "}")));
}
@Override
@ -86,12 +90,6 @@ public class ModFileInfo implements IModFileInfo
return this.modFile;
}
@Override
public UnmodifiableConfig getConfig()
{
return this.config;
}
@Override
public String getModLoader()
{
@ -117,4 +115,14 @@ public class ModFileInfo implements IModFileInfo
public boolean showAsResourcePack() {
return this.showAsResourcePack;
}
@Override
public <T> Optional<T> getConfigElement(final String... key) {
return this.config.getConfigElement(key);
}
@Override
public List<? extends IConfigurable> getConfigList(final String... key) {
return this.config.getConfigList(key);
}
}

View File

@ -23,6 +23,8 @@ import com.electronwill.nightconfig.core.file.FileConfig;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import net.minecraftforge.forgespi.language.IModFileInfo;
import net.minecraftforge.forgespi.locating.IModFile;
import net.minecraftforge.forgespi.locating.ModFileFactory;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -38,25 +40,28 @@ import java.util.stream.Collectors;
import static net.minecraftforge.fml.loading.LogMarkers.LOADING;
public class ModFileParser {
private static final Logger LOGGER = LogManager.getLogger();
public static IModFileInfo readModList(final ModFile modFile) {
public static IModFileInfo readModList(final ModFile modFile, final ModFileFactory.ModFileInfoParser parser) {
return parser.build(modFile);
}
public static IModFileInfo modsTomlParser(final IModFile imodFile) {
ModFile modFile = (ModFile) imodFile;
LOGGER.debug(LOADING,"Considering mod file candidate {}", modFile.getFilePath());
final Path modsjson = modFile.getLocator().findPath(modFile, "META-INF", "mods.toml");
if (!Files.exists(modsjson)) {
LOGGER.warn(LOADING, "Mod file {} is missing mods.toml file", modFile);
return null;
}
return loadModFile(modFile, modsjson);
}
public static IModFileInfo loadModFile(final ModFile file, final Path modsjson)
{
final FileConfig fileConfig = FileConfig.builder(modsjson).build();
fileConfig.load();
fileConfig.close();
return new ModFileInfo(file, fileConfig);
final NightConfigWrapper configWrapper = new NightConfigWrapper(fileConfig);
final ModFileInfo modFileInfo = new ModFileInfo(modFile, configWrapper);
configWrapper.setFile(modFileInfo);
return modFileInfo;
}
protected static List<CoreModFile> getCoreMods(final ModFile modFile) {

View File

@ -19,13 +19,18 @@
package net.minecraftforge.fml.loading.moddiscovery;
import com.electronwill.nightconfig.core.UnmodifiableConfig;
import net.minecraftforge.fml.loading.StringUtils;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.fml.loading.StringSubstitutor;
import net.minecraftforge.fml.loading.StringUtils;
import net.minecraftforge.forgespi.language.IConfigurable;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.forgespi.language.MavenVersionAdapter;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.artifact.versioning.VersionRange;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@ -33,12 +38,7 @@ import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
public class ModInfo implements IModInfo
public class ModInfo implements IModInfo, IConfigurable
{
private static final Logger LOGGER = LogManager.getLogger();
private static final DefaultArtifactVersion DEFAULT_VERSION = new DefaultArtifactVersion("1");
@ -53,50 +53,51 @@ public class ModInfo implements IModInfo
private final Optional<String> logoFile;
private final boolean logoBlur;
private final URL updateJSONURL;
private final List<IModInfo.ModVersion> dependencies;
private final List<? extends IModInfo.ModVersion> dependencies;
private final Map<String,Object> properties;
private final UnmodifiableConfig modConfig;
private final IConfigurable config;
public ModInfo(final ModFileInfo owningFile, final UnmodifiableConfig modConfig)
public ModInfo(final ModFileInfo owningFile, final IConfigurable config)
{
Optional<ModFileInfo> ownFile = Optional.ofNullable(owningFile);
this.owningFile = owningFile;
this.modConfig = modConfig;
this.modId = modConfig.<String>getOptional("modId").orElseThrow(() -> new InvalidModFileException("Missing modId entry", owningFile));
this.config = config;
this.modId = config.<String>getConfigElement("modId")
.orElseThrow(() -> new InvalidModFileException("Missing modId", owningFile));
if (!VALID_LABEL.matcher(this.modId).matches()) {
LOGGER.fatal("Invalid modId found in file {} - {} does not match the standard: {}", this.owningFile.getFile().getFilePath(), this.modId, VALID_LABEL.pattern());
throw new InvalidModFileException("Invalid modId found : "+ this.modId, owningFile);
throw new InvalidModFileException("Invalid modId found : " + this.modId, owningFile);
}
this.namespace = modConfig.<String>getOptional("namespace").orElse(this.modId);
this.namespace = config.<String>getConfigElement("namespace").orElse(this.modId);
if (!VALID_LABEL.matcher(this.namespace).matches()) {
LOGGER.fatal("Invalid override namespace found in file {} - {} does not match the standard: {}", this.owningFile.getFile().getFilePath(), this.namespace, VALID_LABEL.pattern());
throw new InvalidModFileException("Invalid override namespace found : "+ this.namespace, owningFile);
throw new InvalidModFileException("Invalid override namespace found : " + this.namespace, owningFile);
}
this.version = modConfig.<String>getOptional("version").
map(s->StringSubstitutor.replace(s, owningFile != null ? owningFile.getFile() : null )).
map(DefaultArtifactVersion::new).orElse(DEFAULT_VERSION);
this.displayName = modConfig.<String>getOptional("displayName").orElse(this.modId);
this.description = modConfig.get("description");
this.version = config.<String>getConfigElement("version")
.map(s -> StringSubstitutor.replace(s, ownFile.map(ModFileInfo::getFile).orElse(null)))
.map(DefaultArtifactVersion::new).orElse(DEFAULT_VERSION);
this.displayName = config.<String>getConfigElement("displayName").orElse(this.modId);
this.description = config.<String>getConfigElement("description").orElse("MISSING DESCRIPTION");
Optional<String> tmp = modConfig.<String>getOptional("logoFile");
if (!tmp.isPresent() && this.owningFile != null) {
tmp = this.owningFile.getConfig().getOptional("logoFile");
}
this.logoFile = tmp;
this.logoBlur = modConfig.<Boolean>getOptional("logoBlur").
orElseGet(() -> Optional.ofNullable(this.owningFile).
flatMap(f -> f.getConfig().<Boolean>getOptional("logoBlur")).
orElse(true));
this.logoFile = Optional.ofNullable(config.<String>getConfigElement("logoFile")
.orElseGet(() -> ownFile.flatMap(mf -> mf.<String>getConfigElement("logoFile")).orElse(null)));
this.logoBlur = config.<Boolean>getConfigElement("logoBlur")
.orElseGet(() -> ownFile.flatMap(f -> f.<Boolean>getConfigElement("logoBlur"))
.orElse(true));
this.updateJSONURL = modConfig.<String>getOptional("updateJSONURL").map(StringUtils::toURL).orElse(null);
if (owningFile != null) {
this.dependencies = owningFile.getConfig().<List<UnmodifiableConfig>>getOptional(Arrays.asList("dependencies", this.modId)).
orElse(Collections.emptyList()).stream().map(dep -> new ModVersion(this, dep)).collect(Collectors.toList());
this.properties = owningFile.getConfig().<UnmodifiableConfig>getOptional(Arrays.asList("modproperties", this.modId)).
map(UnmodifiableConfig::valueMap).orElse(Collections.emptyMap());
} else {
this.dependencies = Collections.emptyList();
this.properties = Collections.emptyMap();
}
this.updateJSONURL = config.<String>getConfigElement("updateJSONURL")
.map(StringUtils::toURL)
.orElse(null);
this.dependencies = ownFile.map(mfi -> mfi.getConfigList("dependencies", this.modId)
.stream()
.map(dep -> new ModVersion(this, dep))
.collect(Collectors.toList()))
.orElse(Collections.emptyList());
this.properties = ownFile.map(mfi -> mfi.<Map<String, Object>>getConfigElement("modproperties", this.modId)
.orElse(Collections.emptyMap()))
.orElse(Collections.emptyMap());
}
@Override
@ -127,15 +128,10 @@ public class ModInfo implements IModInfo
}
@Override
public List<IModInfo.ModVersion> getDependencies() {
public List<? extends IModInfo.ModVersion> getDependencies() {
return this.dependencies;
}
@Override
public UnmodifiableConfig getModConfig() {
return this.modConfig;
}
@Override
public String getNamespace() {
return this.namespace;
@ -169,4 +165,84 @@ public class ModInfo implements IModInfo
{
return false;
}
@Override
public <T> Optional<T> getConfigElement(final String... key) {
return this.config.getConfigElement(key);
}
@Override
public List<? extends IConfigurable> getConfigList(final String... key) {
return null;
}
class ModVersion implements net.minecraftforge.forgespi.language.IModInfo.ModVersion {
private IModInfo owner;
private final String modId;
private final VersionRange versionRange;
private final boolean mandatory;
private final Ordering ordering;
private final DependencySide side;
public ModVersion(final IModInfo owner, final IConfigurable config) {
this.owner = owner;
this.modId = config.<String>getConfigElement("modId")
.orElseThrow(()->new InvalidModFileException("Missing required field modid in dependency", getOwningFile()));
this.mandatory = config.<Boolean>getConfigElement("mandatory")
.orElseThrow(()->new InvalidModFileException("Missing required field mandatory in dependency", getOwningFile()));
this.versionRange = config.<String>getConfigElement("versionRange")
.map(MavenVersionAdapter::createFromVersionSpec)
.orElse(UNBOUNDED);
this.ordering = config.<String>getConfigElement("ordering")
.map(Ordering::valueOf)
.orElse(Ordering.NONE);
this.side = config.<String>getConfigElement("side")
.map(DependencySide::valueOf)
.orElse(DependencySide.BOTH);
}
@Override
public String getModId()
{
return modId;
}
@Override
public VersionRange getVersionRange()
{
return versionRange;
}
@Override
public boolean isMandatory()
{
return mandatory;
}
@Override
public Ordering getOrdering()
{
return ordering;
}
@Override
public DependencySide getSide()
{
return side;
}
@Override
public void setOwner(final IModInfo owner)
{
this.owner = owner;
}
@Override
public IModInfo getOwner()
{
return owner;
}
}
}

View File

@ -65,7 +65,7 @@ public class ModsFolderLocator extends AbstractJarFileLocator {
.filter(p->!excluded.contains(p))
.sorted(Comparator.comparing(path-> StringUtils.toLowerCase(path.getFileName().toString())))
.filter(p->StringUtils.toLowerCase(p.getFileName().toString()).endsWith(SUFFIX))
.map(p->new ModFile(p, this))
.map(p->ModFile.newFMLInstance(p, this))
.peek(f->modJars.compute(f, (mf, fs)->createFileSystem(mf)))
.collect(Collectors.toList());
}

View File

@ -0,0 +1,45 @@
package net.minecraftforge.fml.loading.moddiscovery;
import com.electronwill.nightconfig.core.UnmodifiableConfig;
import net.minecraftforge.forgespi.language.IConfigurable;
import net.minecraftforge.forgespi.language.IModFileInfo;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import static java.util.Arrays.asList;
public class NightConfigWrapper implements IConfigurable {
private final UnmodifiableConfig config;
private IModFileInfo file;
public NightConfigWrapper(final UnmodifiableConfig config) {
this.config = config;
}
NightConfigWrapper setFile(IModFileInfo file) {
this.file = file;
return this;
}
@Override
public <T> Optional<T> getConfigElement(final String... key) {
return this.config.getOptional(asList(key));
}
@Override
public List<? extends IConfigurable> getConfigList(final String... key) {
final List<String> path = asList(key);
if (this.config.contains(path) && !(this.config.get(path) instanceof Collection)) {
throw new InvalidModFileException("The configuration path "+path+" is invalid. Expecting a collection!", file);
}
final ArrayList<UnmodifiableConfig> nestedConfigs = this.config.getOrElse(path, ArrayList::new);
return nestedConfigs.stream()
.map(NightConfigWrapper::new)
.map(cw->cw.setFile(file))
.collect(Collectors.toList());
}
}

View File

@ -454,11 +454,11 @@ public class ModListScreen extends Screen
lines.add(ForgeI18n.parseMessage("fml.menu.mods.info.idstate", selectedMod.getModId(), ModList.get().getModContainerById(selectedMod.getModId()).
map(ModContainer::getCurrentState).map(Object::toString).orElse("NONE")));
selectedMod.getModConfig().getOptional("credits").ifPresent(credits->
selectedMod.getConfigElement("credits").ifPresent(credits->
lines.add(ForgeI18n.parseMessage("fml.menu.mods.info.credits", credits)));
selectedMod.getModConfig().getOptional("authors").ifPresent(authors ->
selectedMod.getConfigElement("authors").ifPresent(authors ->
lines.add(ForgeI18n.parseMessage("fml.menu.mods.info.authors", authors)));
selectedMod.getModConfig().getOptional("displayURL").ifPresent(displayURL ->
selectedMod.getConfigElement("displayURL").ifPresent(displayURL ->
lines.add(ForgeI18n.parseMessage("fml.menu.mods.info.displayurl", displayURL)));
if (selectedMod.getOwningFile() == null || selectedMod.getOwningFile().getMods().size()==1)
lines.add(ForgeI18n.parseMessage("fml.menu.mods.info.nochildmods"));

View File

@ -52,7 +52,7 @@ public class ClasspathLocator extends AbstractJarFileLocator {
@Override
public List<IModFile> scanMods() {
return modCoords.stream().
map(mc -> new ModFile(mc, this)).
map(mc -> ModFile.newFMLInstance(mc, this)).
peek(f->modJars.compute(f, (mf, fs)->createFileSystem(mf))).
collect(Collectors.toList());
}