234 lines
10 KiB
Java
234 lines
10 KiB
Java
/*
|
|
* Minecraft Forge
|
|
* Copyright (c) 2016-2020.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation version 2.1
|
|
* of the License.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
package net.minecraftforge.fml.loading.moddiscovery;
|
|
|
|
import cpw.mods.gross.Java9ClassLoaderUtil;
|
|
import cpw.mods.modlauncher.Launcher;
|
|
import cpw.mods.modlauncher.ServiceLoaderStreamUtils;
|
|
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
|
|
import net.minecraftforge.fml.loading.FMLEnvironment;
|
|
import net.minecraftforge.fml.loading.FMLLoader;
|
|
import net.minecraftforge.fml.loading.LoadingModList;
|
|
import net.minecraftforge.fml.loading.ModDirTransformerDiscoverer;
|
|
import net.minecraftforge.fml.loading.ModSorter;
|
|
import net.minecraftforge.fml.loading.progress.StartupMessageManager;
|
|
import net.minecraftforge.forgespi.Environment;
|
|
import net.minecraftforge.forgespi.locating.IModFile;
|
|
import net.minecraftforge.forgespi.locating.IModLocator;
|
|
import org.apache.logging.log4j.LogManager;
|
|
import org.apache.logging.log4j.Logger;
|
|
|
|
import java.io.IOException;
|
|
import java.net.URI;
|
|
import java.net.URISyntaxException;
|
|
import java.net.URL;
|
|
import java.net.URLClassLoader;
|
|
import java.nio.file.FileSystem;
|
|
import java.nio.file.FileSystems;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.nio.file.Paths;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Objects;
|
|
import java.util.Optional;
|
|
import java.util.ServiceLoader;
|
|
import java.util.function.Consumer;
|
|
import java.util.jar.Manifest;
|
|
import java.util.stream.Collectors;
|
|
import java.util.stream.Stream;
|
|
import java.util.zip.ZipError;
|
|
|
|
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
|
|
import static net.minecraftforge.fml.loading.LogMarkers.SCAN;
|
|
|
|
|
|
public class ModDiscoverer {
|
|
private static final Logger LOGGER = LogManager.getLogger();
|
|
private final ServiceLoader<IModLocator> locators;
|
|
private final List<IModLocator> locatorList;
|
|
private final LocatorClassLoader locatorClassLoader;
|
|
|
|
public ModDiscoverer(Map<String, ?> arguments) {
|
|
Launcher.INSTANCE.environment().computePropertyIfAbsent(Environment.Keys.MODFOLDERFACTORY.get(), v->ModsFolderLocator::new);
|
|
Launcher.INSTANCE.environment().computePropertyIfAbsent(Environment.Keys.MODDIRECTORYFACTORY.get(), v->ModsFolderLocator::new);
|
|
Launcher.INSTANCE.environment().computePropertyIfAbsent(Environment.Keys.PROGRESSMESSAGE.get(), v->StartupMessageManager.locatorConsumer().orElseGet(()->s->{}));
|
|
locatorClassLoader = new LocatorClassLoader();
|
|
Launcher.INSTANCE.environment().computePropertyIfAbsent(FMLEnvironment.Keys.LOCATORCLASSLOADER.get(), v->locatorClassLoader);
|
|
ModDirTransformerDiscoverer.getExtraLocators()
|
|
.stream()
|
|
.map(LamdbaExceptionUtils.rethrowFunction(p->p.toUri().toURL()))
|
|
.forEach(locatorClassLoader::addURL);
|
|
locators = ServiceLoader.load(IModLocator.class, locatorClassLoader);
|
|
locatorList = ServiceLoaderStreamUtils.toList(this.locators);
|
|
locatorList.forEach(l->l.initArguments(arguments));
|
|
locatorList.add(new MinecraftLocator());
|
|
LOGGER.debug(CORE,"Found Mod Locators : {}", ()->locatorList.stream().map(iModLocator -> "("+iModLocator.name() + ":" + iModLocator.getClass().getPackage().getImplementationVersion()+")").collect(Collectors.joining(",")));
|
|
}
|
|
|
|
ModDiscoverer(List<IModLocator> locatorList) {
|
|
this.locatorList = locatorList;
|
|
this.locatorClassLoader = null;
|
|
this.locators = null;
|
|
}
|
|
|
|
public BackgroundScanHandler discoverMods() {
|
|
LOGGER.debug(SCAN,"Scanning for mods and other resources to load. We know {} ways to find mods", locatorList.size());
|
|
final Map<IModFile.Type, List<ModFile>> modFiles = locatorList.stream()
|
|
.peek(loc -> LOGGER.debug(SCAN,"Trying locator {}", loc))
|
|
.map(IModLocator::scanMods)
|
|
.flatMap(Collection::stream)
|
|
.peek(mf -> LOGGER.debug(SCAN,"Found mod file {} of type {} with locator {}", mf.getFileName(), mf.getType(), mf.getLocator()))
|
|
.peek(mf -> StartupMessageManager.modLoaderConsumer().ifPresent(c->c.accept("Found mod file "+mf.getFileName()+" of type "+mf.getType())))
|
|
.map(ModFile.class::cast)
|
|
.collect(Collectors.groupingBy(IModFile::getType));
|
|
|
|
FMLLoader.getLanguageLoadingProvider().addAdditionalLanguages(modFiles.get(IModFile.Type.LANGPROVIDER));
|
|
BackgroundScanHandler backgroundScanHandler = new BackgroundScanHandler(modFiles);
|
|
final List<ModFile> mods = modFiles.getOrDefault(IModFile.Type.MOD, Collections.emptyList());
|
|
final List<ModFile> brokenFiles = new ArrayList<>();
|
|
for (Iterator<ModFile> iterator = mods.iterator(); iterator.hasNext(); )
|
|
{
|
|
ModFile mod = iterator.next();
|
|
if (!mod.getLocator().isValid(mod) || !mod.identifyMods()) {
|
|
LOGGER.warn(SCAN, "File {} has been ignored - it is invalid", mod.getFilePath());
|
|
iterator.remove();
|
|
brokenFiles.add(mod);
|
|
}
|
|
}
|
|
LOGGER.debug(SCAN,"Found {} mod files with {} mods", mods::size, ()->mods.stream().mapToInt(mf -> mf.getModInfos().size()).sum());
|
|
StartupMessageManager.modLoaderConsumer().ifPresent(c->c.accept("Found "+mods.size()+" modfiles to load"));
|
|
final LoadingModList loadingModList = ModSorter.sort(mods);
|
|
loadingModList.addCoreMods();
|
|
loadingModList.addAccessTransformers();
|
|
loadingModList.addForScanning(backgroundScanHandler);
|
|
loadingModList.setBrokenFiles(brokenFiles);
|
|
return backgroundScanHandler;
|
|
}
|
|
|
|
private static class LocatorClassLoader extends URLClassLoader {
|
|
LocatorClassLoader() {
|
|
super(Java9ClassLoaderUtil.getSystemClassPathURLs(), getSystemClassLoader());
|
|
}
|
|
|
|
@Override
|
|
protected void addURL(final URL url) {
|
|
super.addURL(url);
|
|
}
|
|
}
|
|
private static class MinecraftLocator implements IModLocator {
|
|
private final Path mcJar = FMLLoader.getMCPaths()[0];
|
|
private final FileSystem fileSystem;
|
|
|
|
MinecraftLocator() {
|
|
if (!Files.isDirectory(mcJar)) {
|
|
try {
|
|
fileSystem = FileSystems.newFileSystem(mcJar, getClass().getClassLoader());
|
|
} catch (ZipError | IOException e) {
|
|
LOGGER.fatal(SCAN,"Invalid Minecraft JAR file - no filesystem created");
|
|
throw new RuntimeException(e);
|
|
}
|
|
} else {
|
|
fileSystem = null;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public List<IModFile> scanMods() {
|
|
return Collections.singletonList(ModFile.newFMLInstance(mcJar, this));
|
|
}
|
|
|
|
@Override
|
|
public String name() {
|
|
return "minecraft";
|
|
}
|
|
|
|
@Override
|
|
public Path findPath(final IModFile modFile, final String... path) {
|
|
String[] newPath = Arrays.copyOf(path, path.length);
|
|
if (path.length == 2 && Objects.equals(path[1], "mods.toml")) {
|
|
final URI jarFileURI;
|
|
try {
|
|
jarFileURI = getClass().getClassLoader().getResource("minecraftmod.toml").toURI();
|
|
if (Objects.equals(jarFileURI.getScheme(), "jar")) {
|
|
// Initialize the filesystem for the forge jar, because otherwise this barfs?
|
|
FileSystems.newFileSystem(jarFileURI, new HashMap<>());
|
|
}
|
|
} catch (URISyntaxException | IOException e) {
|
|
LOGGER.fatal(SCAN, "Unable to read minecraft default mod");
|
|
throw new RuntimeException(e);
|
|
}
|
|
return Paths.get(jarFileURI);
|
|
}
|
|
if (Files.isDirectory(mcJar)) return findPathDirectory(modFile, path);
|
|
return findPathJar(modFile, path);
|
|
}
|
|
|
|
private Path findPathDirectory(final IModFile modFile, final String... path) {
|
|
if (path.length < 1) {
|
|
throw new IllegalArgumentException("Missing path");
|
|
}
|
|
final Path target = Paths.get(path[0], Arrays.copyOfRange(path, 1, path.length));
|
|
// try right path first (resources)
|
|
return mcJar.resolve(target);
|
|
}
|
|
|
|
private Path findPathJar(final IModFile modFile, final String... path) {
|
|
return fileSystem.getPath(path[0], Arrays.copyOfRange(path, 1, path.length));
|
|
}
|
|
@Override
|
|
public void scanFile(final IModFile modFile, final Consumer<Path> pathConsumer) {
|
|
LOGGER.debug(SCAN,"Scan started: {}", modFile);
|
|
Path path;
|
|
if (Files.isDirectory(mcJar))
|
|
path = mcJar;
|
|
else
|
|
path = fileSystem.getPath("/");
|
|
try (Stream<Path> files = Files.find(path, Integer.MAX_VALUE, (p, a) -> p.getNameCount() > 0 && p.getFileName().toString().endsWith(".class"))) {
|
|
files.forEach(pathConsumer);
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
LOGGER.debug(SCAN,"Scan finished: {}", modFile);
|
|
}
|
|
|
|
@Override
|
|
public Optional<Manifest> findManifest(final Path file) {
|
|
return Optional.empty();
|
|
}
|
|
|
|
@Override
|
|
public void initArguments(final Map<String, ?> arguments) {
|
|
// no op
|
|
}
|
|
|
|
@Override
|
|
public boolean isValid(final IModFile modFile) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|