Rewrite dependency extraction to use mod list system (#4841)
This commit is contained in:
parent
3f4dfbb367
commit
9d0771b3d7
17 changed files with 1497 additions and 414 deletions
|
@ -87,6 +87,11 @@
|
|||
},
|
||||
{
|
||||
"name": "net.sf.trove4j:trove4j:3.0.3"
|
||||
},
|
||||
{
|
||||
"name": "org.apache.maven:maven-artifact:3.5.3",
|
||||
"children" : ["sources"],
|
||||
"url" : "http://repo.maven.apache.org/maven2"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -138,6 +138,12 @@
|
|||
"name": "net.sf.trove4j:trove4j:3.0.3",
|
||||
"clientreq":true,
|
||||
"serverreq":true
|
||||
},
|
||||
{
|
||||
"name": "org.apache.maven:maven-artifact:3.5.3",
|
||||
"url" : "http://files.minecraftforge.net/maven/",
|
||||
"serverreq":true,
|
||||
"clientreq":true
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -32,14 +32,12 @@ import java.util.Map;
|
|||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.apache.logging.log4j.Level;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import net.minecraftforge.fml.common.FMLLog;
|
||||
import net.minecraftforge.fml.common.InjectedModContainer;
|
||||
import net.minecraftforge.fml.common.Loader;
|
||||
import net.minecraftforge.fml.common.ModContainer;
|
||||
|
|
|
@ -32,8 +32,10 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraftforge.classloading.FMLForgePlugin;
|
||||
import net.minecraftforge.common.ForgeVersion;
|
||||
import net.minecraftforge.common.capabilities.CapabilityManager;
|
||||
import net.minecraftforge.common.config.ConfigManager;
|
||||
|
@ -42,6 +44,8 @@ import net.minecraftforge.fml.common.LoaderState.ModState;
|
|||
import net.minecraftforge.fml.common.ModContainer.Disableable;
|
||||
import net.minecraftforge.fml.common.ProgressManager.ProgressBar;
|
||||
import net.minecraftforge.fml.common.discovery.ASMDataTable;
|
||||
import net.minecraftforge.fml.common.discovery.ContainerType;
|
||||
import net.minecraftforge.fml.common.discovery.ModCandidate;
|
||||
import net.minecraftforge.fml.common.discovery.ModDiscoverer;
|
||||
import net.minecraftforge.fml.common.event.FMLInterModComms;
|
||||
import net.minecraftforge.fml.common.event.FMLLoadEvent;
|
||||
|
@ -54,8 +58,11 @@ import net.minecraftforge.fml.common.toposort.ModSortingException.SortingExcepti
|
|||
import net.minecraftforge.fml.common.versioning.ArtifactVersion;
|
||||
import net.minecraftforge.fml.common.versioning.DependencyParser;
|
||||
import net.minecraftforge.fml.common.versioning.VersionParser;
|
||||
import net.minecraftforge.fml.relauncher.ModListHelper;
|
||||
import net.minecraftforge.fml.relauncher.CoreModManager;
|
||||
import net.minecraftforge.fml.relauncher.Side;
|
||||
import net.minecraftforge.fml.relauncher.libraries.Artifact;
|
||||
import net.minecraftforge.fml.relauncher.libraries.LibraryManager;
|
||||
import net.minecraftforge.fml.relauncher.libraries.Repository;
|
||||
import net.minecraftforge.registries.GameData;
|
||||
import net.minecraftforge.registries.ObjectHolderRegistry;
|
||||
|
||||
|
@ -384,18 +391,42 @@ public class Loader
|
|||
mods.add(new InjectedModContainer(mc,mc.getSource()));
|
||||
}
|
||||
ModDiscoverer discoverer = new ModDiscoverer();
|
||||
FMLLog.log.debug("Attempting to load mods contained in the minecraft jar file and associated classes");
|
||||
discoverer.findClasspathMods(modClassLoader);
|
||||
FMLLog.log.debug("Minecraft jar mods loaded successfully");
|
||||
|
||||
FMLLog.log.info("Found {} mods from the command line. Injecting into mod discoverer", ModListHelper.additionalMods.size());
|
||||
FMLLog.log.info("Searching {} for mods", canonicalModsDir.getAbsolutePath());
|
||||
discoverer.findModDirMods(canonicalModsDir, ModListHelper.additionalMods.values().toArray(new File[0]));
|
||||
File versionSpecificModsDir = new File(canonicalModsDir,mccversion);
|
||||
if (versionSpecificModsDir.isDirectory())
|
||||
if (!FMLForgePlugin.RUNTIME_DEOBF) //Only descover mods in the classpath if we're in the dev env.
|
||||
{ //TODO: Move this to GradleStart?
|
||||
FMLLog.log.debug("Attempting to load mods contained in the minecraft jar file and associated classes");
|
||||
discoverer.findClasspathMods(modClassLoader);
|
||||
FMLLog.log.debug("Minecraft jar mods loaded successfully");
|
||||
}
|
||||
|
||||
List<Artifact> maven_canidates = LibraryManager.flattenLists(minecraftDir);
|
||||
List<File> file_canidates = LibraryManager.gatherLegacyCanidates(minecraftDir);
|
||||
|
||||
for (Artifact artifact : maven_canidates)
|
||||
{
|
||||
FMLLog.log.info("Also searching {} for mods", versionSpecificModsDir);
|
||||
discoverer.findModDirMods(versionSpecificModsDir);
|
||||
artifact = Repository.resolveAll(artifact);
|
||||
if (artifact != null)
|
||||
{
|
||||
File target = artifact.getFile();
|
||||
if (!file_canidates.contains(target))
|
||||
file_canidates.add(target);
|
||||
}
|
||||
}
|
||||
//Do we want to sort the full list after resolving artifacts?
|
||||
//TODO: Add dependency gathering?
|
||||
|
||||
for (File mod : file_canidates)
|
||||
{
|
||||
// skip loaded coremods
|
||||
if (CoreModManager.getIgnoredMods().contains(mod.getName()))
|
||||
{
|
||||
FMLLog.log.trace("Skipping already parsed coremod or tweaker {}", mod.getName());
|
||||
}
|
||||
else
|
||||
{
|
||||
FMLLog.log.debug("Found a candidate zip or jar file {}", mod.getName());
|
||||
discoverer.addCandidate(new ModCandidate(mod, mod, ContainerType.JAR));
|
||||
}
|
||||
}
|
||||
|
||||
mods.addAll(discoverer.identifyMods());
|
||||
|
|
|
@ -24,20 +24,21 @@ import java.io.InputStream;
|
|||
import java.lang.reflect.Field;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import net.minecraftforge.fml.common.FMLLog;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.io.ByteSource;
|
||||
import com.google.common.io.CharSource;
|
||||
|
||||
public class ModAccessTransformer extends AccessTransformer {
|
||||
public class ModAccessTransformer extends AccessTransformer
|
||||
{
|
||||
public static final Attributes.Name FMLAT = new Attributes.Name("FMLAT");
|
||||
private static Map<String, String> embedded = Maps.newHashMap(); //Needs to be primitive so that both classloaders get the same class.
|
||||
@SuppressWarnings("unchecked")
|
||||
public ModAccessTransformer() throws Exception
|
||||
public ModAccessTransformer() throws Exception
|
||||
{
|
||||
super(ModAccessTransformer.class);
|
||||
//We are in the new ClassLoader here, so we need to get the static field from the other ClassLoader.
|
||||
|
@ -59,11 +60,8 @@ public class ModAccessTransformer extends AccessTransformer {
|
|||
}
|
||||
}
|
||||
|
||||
public static void addJar(JarFile jar) throws IOException
|
||||
public static void addJar(JarFile jar, String atList) throws IOException
|
||||
{
|
||||
Manifest manifest = jar.getManifest();
|
||||
String atList = manifest.getMainAttributes().getValue("FMLAT");
|
||||
if (atList == null) return;
|
||||
for (String at : atList.split(" "))
|
||||
{
|
||||
JarEntry jarEntry = jar.getJarEntry("META-INF/"+at);
|
||||
|
|
|
@ -21,24 +21,17 @@ package net.minecraftforge.fml.common.discovery;
|
|||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import net.minecraftforge.fml.common.FMLLog;
|
||||
import net.minecraftforge.fml.common.LoaderException;
|
||||
import net.minecraftforge.fml.common.ModClassLoader;
|
||||
import net.minecraftforge.fml.common.ModContainer;
|
||||
import net.minecraftforge.fml.relauncher.CoreModManager;
|
||||
import net.minecraftforge.fml.relauncher.FileListHelper;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.ObjectArrays;
|
||||
|
||||
public class ModDiscoverer
|
||||
{
|
||||
private static Pattern zipJar = Pattern.compile("(.+).(zip|jar)$");
|
||||
|
||||
private List<ModCandidate> candidates = Lists.newArrayList();
|
||||
|
||||
private ASMDataTable dataTable = new ASMDataTable();
|
||||
|
@ -89,45 +82,6 @@ public class ModDiscoverer
|
|||
|
||||
}
|
||||
|
||||
public void findModDirMods(File modsDir)
|
||||
{
|
||||
findModDirMods(modsDir, new File[0]);
|
||||
}
|
||||
|
||||
public void findModDirMods(File modsDir, File[] supplementalModFileCandidates)
|
||||
{
|
||||
File[] modList = FileListHelper.sortFileList(modsDir, null);
|
||||
modList = FileListHelper.sortFileList(ObjectArrays.concat(modList, supplementalModFileCandidates, File.class));
|
||||
for (File modFile : modList)
|
||||
{
|
||||
// skip loaded coremods
|
||||
if (CoreModManager.getIgnoredMods().contains(modFile.getName()))
|
||||
{
|
||||
FMLLog.log.trace("Skipping already parsed coremod or tweaker {}", modFile.getName());
|
||||
}
|
||||
else if (modFile.isDirectory())
|
||||
{
|
||||
//TODO Remove in 1.13+ Mods should never be directory based anymore.
|
||||
FMLLog.log.debug("Found a candidate mod directory {}", modFile.getName());
|
||||
addCandidate(new ModCandidate(modFile, modFile, ContainerType.DIR));
|
||||
}
|
||||
else
|
||||
{
|
||||
Matcher matcher = zipJar.matcher(modFile.getName());
|
||||
|
||||
if (matcher.matches())
|
||||
{
|
||||
FMLLog.log.debug("Found a candidate zip or jar file {}", matcher.group(0));
|
||||
addCandidate(new ModCandidate(modFile, modFile, ContainerType.JAR));
|
||||
}
|
||||
else
|
||||
{
|
||||
FMLLog.log.debug("Ignoring unknown file {} in mods directory", modFile.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<ModContainer> identifyMods()
|
||||
{
|
||||
List<ModContainer> modList = Lists.newArrayList();
|
||||
|
@ -165,7 +119,7 @@ public class ModDiscoverer
|
|||
return nonModLibs;
|
||||
}
|
||||
|
||||
private void addCandidate(ModCandidate candidate)
|
||||
public void addCandidate(ModCandidate candidate)
|
||||
{
|
||||
for (ModCandidate c : candidates)
|
||||
{
|
||||
|
|
|
@ -32,7 +32,6 @@ import net.minecraft.launchwrapper.LaunchClassLoader;
|
|||
import net.minecraftforge.fml.relauncher.FMLLaunchHandler;
|
||||
import net.minecraftforge.fml.relauncher.FMLSecurityManager;
|
||||
|
||||
import org.apache.logging.log4j.Level;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
|
|
@ -22,10 +22,8 @@ package net.minecraftforge.fml.relauncher;
|
|||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FilenameFilter;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
@ -34,17 +32,15 @@ import java.security.cert.Certificate;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.ToIntFunction;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.io.Files;
|
||||
import net.minecraft.launchwrapper.ITweaker;
|
||||
import net.minecraft.launchwrapper.Launch;
|
||||
import net.minecraft.launchwrapper.LaunchClassLoader;
|
||||
|
@ -59,6 +55,9 @@ import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin.MCVersion;
|
|||
import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin.Name;
|
||||
import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin.SortingIndex;
|
||||
import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin.TransformerExclusions;
|
||||
import net.minecraftforge.fml.relauncher.libraries.Artifact;
|
||||
import net.minecraftforge.fml.relauncher.libraries.LibraryManager;
|
||||
import net.minecraftforge.fml.relauncher.libraries.Repository;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
@ -71,8 +70,6 @@ import com.google.common.primitives.Ints;
|
|||
public class CoreModManager {
|
||||
private static final Attributes.Name COREMODCONTAINSFMLMOD = new Attributes.Name("FMLCorePluginContainsFMLMod");
|
||||
private static final Attributes.Name MODTYPE = new Attributes.Name("ModType");
|
||||
private static final Attributes.Name MODSIDE = new Attributes.Name("ModSide");
|
||||
private static final Attributes.Name MODCONTAINSDEPS = new Attributes.Name("ContainedDeps");
|
||||
private static String[] rootPlugins = { "net.minecraftforge.fml.relauncher.FMLCorePlugin", "net.minecraftforge.classloading.FMLForgePlugin" };
|
||||
private static List<String> ignoredModFiles = Lists.newArrayList();
|
||||
private static Map<String, List<String>> transformers = Maps.newHashMap();
|
||||
|
@ -83,7 +80,6 @@ public class CoreModManager {
|
|||
private static List<String> candidateModFiles = Lists.newArrayList();
|
||||
private static List<String> accessTransformers = Lists.newArrayList();
|
||||
private static Set<String> rootNames = Sets.newHashSet();
|
||||
private static final List<String> skipContainedDeps = Arrays.asList(System.getProperty("fml.skipContainedDeps","").split(","));
|
||||
|
||||
static
|
||||
{
|
||||
|
@ -197,6 +193,8 @@ public class CoreModManager {
|
|||
{
|
||||
CoreModManager.mcDir = mcDir;
|
||||
CoreModManager.tweaker = tweaker;
|
||||
|
||||
//TODO: Detect this better? Support install time deobfusication? Decouple deobf from dev? Add dev flag in GradleStart?
|
||||
try
|
||||
{
|
||||
// Are we in a 'decompiled' environment?
|
||||
|
@ -257,7 +255,7 @@ public class CoreModManager {
|
|||
|
||||
private static void findDerpMods(LaunchClassLoader classLoader, File modDir, File modDirVer)
|
||||
{
|
||||
File[] derplist = listFiles((dir, name) -> name.endsWith(".jar.zip"), modDir, modDirVer);
|
||||
File[] derplist = listFiles(path -> path.getName().endsWith(".jar.zip"), modDir, modDirVer);
|
||||
if (derplist != null && derplist.length > 0)
|
||||
{
|
||||
FMLLog.log.fatal("FML has detected several badly downloaded jar files, which have been named as zip files. You probably need to download them again, or they may not work properly");
|
||||
|
@ -297,20 +295,6 @@ public class CoreModManager {
|
|||
}
|
||||
}
|
||||
|
||||
private static File[] listFiles(FilenameFilter filter, File ... dirs)
|
||||
{
|
||||
File[] ret = null;
|
||||
for (File dir : dirs)
|
||||
{
|
||||
if (!dir.isDirectory() || !dir.exists())
|
||||
continue;
|
||||
if (ret == null)
|
||||
ret = dir.listFiles(filter);
|
||||
else
|
||||
ret = ObjectArrays.concat(ret, dir.listFiles(filter), File.class);
|
||||
}
|
||||
return ret == null ? new File[0] : ret;
|
||||
}
|
||||
private static File[] listFiles(FileFilter filter, File ... dirs)
|
||||
{
|
||||
File[] ret = null;
|
||||
|
@ -334,18 +318,27 @@ public class CoreModManager {
|
|||
|
||||
findDerpMods(classLoader, modsDir, modsDirVer);
|
||||
|
||||
extractPackedJars(modsDir, modsDirVer);
|
||||
|
||||
ModListHelper.parseModList(mcDir);
|
||||
//By the time we get here, all bundeled jars should be extracted to the proper repos.
|
||||
//As well as the mods folders being cleaned up {any files that have maven info being moved to maven folder}
|
||||
|
||||
FMLLog.log.debug("Discovering coremods");
|
||||
File[] coreModList = listFiles((dir, name) -> name.endsWith(".jar"), modsDir, modsDirVer);
|
||||
List<Artifact> maven_canidates = LibraryManager.flattenLists(mcDir);
|
||||
List<File> file_canidates = LibraryManager.gatherLegacyCanidates(mcDir);
|
||||
|
||||
coreModList = ObjectArrays.concat(coreModList, ModListHelper.additionalMods.values().toArray(new File[0]), File.class);
|
||||
for (Artifact artifact : maven_canidates)
|
||||
{
|
||||
artifact = Repository.resolveAll(artifact);
|
||||
if (artifact != null)
|
||||
{
|
||||
File target = artifact.getFile();
|
||||
if (!file_canidates.contains(target))
|
||||
file_canidates.add(target);
|
||||
}
|
||||
}
|
||||
//Do we want to sort the full list after resolving artifacts?
|
||||
//TODO: Add dependency gathering?
|
||||
|
||||
coreModList = FileListHelper.sortFileList(coreModList);
|
||||
|
||||
for (File coreMod : coreModList)
|
||||
for (File coreMod : file_canidates)
|
||||
{
|
||||
FMLLog.log.debug("Examining for coremod candidacy {}", coreMod.getName());
|
||||
JarFile jar = null;
|
||||
|
@ -353,14 +346,39 @@ public class CoreModManager {
|
|||
String fmlCorePlugin;
|
||||
try
|
||||
{
|
||||
jar = new JarFile(coreMod);
|
||||
if (jar.getManifest() == null)
|
||||
File manifest = new File(coreMod.getAbsolutePath() + ".meta");
|
||||
|
||||
if (LibraryManager.DISABLE_EXTERNAL_MANIFEST || !manifest.exists())
|
||||
{
|
||||
// Not a coremod and no access transformer list
|
||||
jar = new JarFile(coreMod);
|
||||
mfAttributes = jar.getManifest() == null ? null : jar.getManifest().getMainAttributes();
|
||||
}
|
||||
else
|
||||
{
|
||||
FileInputStream fis = new FileInputStream(manifest);
|
||||
mfAttributes = new Manifest(fis).getMainAttributes();
|
||||
fis.close();
|
||||
}
|
||||
|
||||
if (mfAttributes == null) // Not a coremod and no access transformer list
|
||||
continue;
|
||||
|
||||
String modSide = mfAttributes.getValue(LibraryManager.MODSIDE);
|
||||
if (!"BOTH".equals(modSide) || FMLLaunchHandler.side().name().equals(modSide))
|
||||
{
|
||||
FMLLog.log.debug("Mod {} has ModSide meta-inf value {}, and we're {} It will be ignored", coreMod.getName(), modSide, FMLLaunchHandler.side.name());
|
||||
ignoredModFiles.add(coreMod.getName());
|
||||
continue;
|
||||
}
|
||||
ModAccessTransformer.addJar(jar);
|
||||
mfAttributes = jar.getManifest().getMainAttributes();
|
||||
|
||||
String ats = mfAttributes.getValue(ModAccessTransformer.FMLAT);
|
||||
if (ats != null && !ats.isEmpty())
|
||||
{
|
||||
if (jar == null) //We could of loaded the external manifest earlier, if so the jar isn't loaded.
|
||||
jar = new JarFile(coreMod);
|
||||
ModAccessTransformer.addJar(jar, ats);
|
||||
}
|
||||
|
||||
String cascadedTweaker = mfAttributes.getValue("TweakClass");
|
||||
if (cascadedTweaker != null)
|
||||
{
|
||||
|
@ -379,13 +397,6 @@ public class CoreModManager {
|
|||
ignoredModFiles.add(coreMod.getName());
|
||||
continue;
|
||||
}
|
||||
String modSide = mfAttributes.containsKey(MODSIDE) ? mfAttributes.getValue(MODSIDE) : "BOTH";
|
||||
if (! ("BOTH".equals(modSide) || FMLLaunchHandler.side.name().equals(modSide)))
|
||||
{
|
||||
FMLLog.log.debug("Mod {} has ModSide meta-inf value {}, and we're {} It will be ignored", coreMod.getName(), modSide, FMLLaunchHandler.side.name());
|
||||
ignoredModFiles.add(coreMod.getName());
|
||||
continue;
|
||||
}
|
||||
fmlCorePlugin = mfAttributes.getValue("FMLCorePlugin");
|
||||
if (fmlCorePlugin == null)
|
||||
{
|
||||
|
@ -428,95 +439,6 @@ public class CoreModManager {
|
|||
}
|
||||
}
|
||||
|
||||
private static void extractPackedJars(File modsDir, File modsDirVer)
|
||||
{
|
||||
for (File dir : new File[]{modsDir, modsDirVer})
|
||||
{
|
||||
for (File file : listFiles((d, name) -> name.endsWith(".jar"), dir))
|
||||
{
|
||||
JarFile jar = null;
|
||||
Attributes mfAttributes;
|
||||
try
|
||||
{
|
||||
jar = new JarFile(file);
|
||||
if (jar.getManifest() == null)
|
||||
continue;
|
||||
|
||||
mfAttributes = jar.getManifest().getMainAttributes();
|
||||
String modSide = mfAttributes.containsKey(MODSIDE) ? mfAttributes.getValue(MODSIDE) : "BOTH";
|
||||
if (! ("BOTH".equals(modSide) || FMLLaunchHandler.side.name().equals(modSide)))
|
||||
continue;
|
||||
|
||||
extractContainedDepJars(jar, dir == modsDir ? modsDir : modsDirVer, dir == modsDir ? modsDirVer : modsDir);
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
FMLLog.log.error("Unable to read the jar file {} - ignoring", file.getName(), ioe);
|
||||
continue;
|
||||
}
|
||||
finally
|
||||
{
|
||||
closeQuietly(jar);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void extractContainedDepJars(JarFile jar, File ... modsDirs) throws IOException
|
||||
{
|
||||
if (!jar.getManifest().getMainAttributes().containsKey(MODCONTAINSDEPS)) return;
|
||||
|
||||
String deps = jar.getManifest().getMainAttributes().getValue(MODCONTAINSDEPS);
|
||||
String[] depList = deps.split(" ");
|
||||
for (String dep : depList)
|
||||
{
|
||||
String depEndName = new File(dep).getName(); // extract last part of name
|
||||
if (skipContainedDeps.contains(dep) || skipContainedDeps.contains(depEndName))
|
||||
{
|
||||
FMLLog.log.error("Skipping dep at request: {}", dep);
|
||||
continue;
|
||||
}
|
||||
final JarEntry jarEntry = jar.getJarEntry(dep);
|
||||
if (jarEntry == null)
|
||||
{
|
||||
FMLLog.log.error("Found invalid ContainsDeps declaration {} in {}", dep, jar.getName());
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean exit = false;
|
||||
for (File f : modsDirs)
|
||||
{
|
||||
File tmp = new File(f, depEndName);
|
||||
if (tmp.exists())
|
||||
{
|
||||
FMLLog.log.debug("Found existing ContainsDep extracted to {}, skipping extraction", tmp.getCanonicalPath());
|
||||
exit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (exit)
|
||||
continue;
|
||||
|
||||
File target = new File(modsDirs[0], depEndName);
|
||||
FMLLog.log.debug("Extracting ContainedDep {} from {} to {}", dep, jar.getName(), target.getCanonicalPath());
|
||||
try
|
||||
{
|
||||
Files.createParentDirs(target);
|
||||
try (
|
||||
FileOutputStream targetOutputStream = new FileOutputStream(target);
|
||||
InputStream jarInputStream = jar.getInputStream(jarEntry);
|
||||
){
|
||||
ByteStreams.copy(jarInputStream, targetOutputStream);
|
||||
}
|
||||
FMLLog.log.debug("Extracted ContainedDep {} from {} to {}", dep, jar.getName(), target.getCanonicalPath());
|
||||
} catch (IOException e)
|
||||
{
|
||||
FMLLog.log.error("An error occurred extracting dependency", e);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
private static Method ADDURL;
|
||||
|
||||
private static void handleCascadingTweak(File coreMod, JarFile jar, String cascadedTweaker, LaunchClassLoader classLoader, Integer sortingOrder)
|
||||
|
@ -719,51 +641,8 @@ public class CoreModManager {
|
|||
// Basically a copy of Collections.sort pre 8u20, optimized as we know we're an array list.
|
||||
// Thanks unhelpful fixer of http://bugs.java.com/view_bug.do?bug_id=8032636
|
||||
ITweaker[] toSort = tweakers.toArray(new ITweaker[tweakers.size()]);
|
||||
Arrays.sort(toSort, new Comparator<ITweaker>() {
|
||||
@Override
|
||||
public int compare(ITweaker o1, ITweaker o2)
|
||||
{
|
||||
Integer first = null;
|
||||
Integer second = null;
|
||||
if (o1 instanceof FMLInjectionAndSortingTweaker)
|
||||
{
|
||||
first = Integer.MIN_VALUE;
|
||||
}
|
||||
if (o2 instanceof FMLInjectionAndSortingTweaker)
|
||||
{
|
||||
second = Integer.MIN_VALUE;
|
||||
}
|
||||
|
||||
if (o1 instanceof FMLPluginWrapper)
|
||||
{
|
||||
first = ((FMLPluginWrapper) o1).sortIndex;
|
||||
}
|
||||
else if (first == null)
|
||||
{
|
||||
first = tweakSorting.get(o1.getClass().getName());
|
||||
}
|
||||
if (o2 instanceof FMLPluginWrapper)
|
||||
{
|
||||
second = ((FMLPluginWrapper) o2).sortIndex;
|
||||
}
|
||||
else if (second == null)
|
||||
{
|
||||
second = tweakSorting.get(o2.getClass().getName());
|
||||
}
|
||||
if (first == null)
|
||||
{
|
||||
first = 0;
|
||||
}
|
||||
if (second == null)
|
||||
{
|
||||
second = 0;
|
||||
}
|
||||
|
||||
return Ints.saturatedCast((long)first - (long)second);
|
||||
}
|
||||
});
|
||||
// Basically a copy of Collections.sort, optimized as we know we're an array list.
|
||||
// Thanks unhelpful fixer of http://bugs.java.com/view_bug.do?bug_id=8032636
|
||||
ToIntFunction<ITweaker> getOrder = o -> o instanceof FMLInjectionAndSortingTweaker ? Integer.MIN_VALUE : o instanceof FMLPluginWrapper ? ((FMLPluginWrapper)o).sortIndex : tweakSorting.getOrDefault(o.getClass().getName(), 0);
|
||||
Arrays.sort(toSort, (o1, o2) -> Ints.saturatedCast((long)getOrder.applyAsInt(o1) - (long)getOrder.applyAsInt(o2)));
|
||||
for (int j = 0; j < toSort.length; j++) {
|
||||
tweakers.set(j, toSort[j]);
|
||||
}
|
||||
|
|
|
@ -25,6 +25,8 @@ import net.minecraft.launchwrapper.LaunchClassLoader;
|
|||
import net.minecraftforge.fml.common.FMLLog;
|
||||
import net.minecraftforge.fml.common.TracingPrintStream;
|
||||
import net.minecraftforge.fml.common.launcher.FMLTweaker;
|
||||
import net.minecraftforge.fml.relauncher.libraries.LibraryManager;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
|
||||
public class FMLLaunchHandler
|
||||
|
@ -99,6 +101,7 @@ public class FMLLaunchHandler
|
|||
|
||||
try
|
||||
{
|
||||
LibraryManager.setup(minecraftHome);
|
||||
CoreModManager.handleLaunch(minecraftHome, classLoader, tweaker);
|
||||
}
|
||||
catch (Throwable t)
|
||||
|
@ -129,4 +132,6 @@ public class FMLLaunchHandler
|
|||
{
|
||||
INSTANCE.injectPostfixTransformers();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,166 +0,0 @@
|
|||
/*
|
||||
* Minecraft Forge
|
||||
* Copyright (c) 2016.
|
||||
*
|
||||
* 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.relauncher;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import net.minecraft.launchwrapper.Launch;
|
||||
import net.minecraftforge.fml.common.FMLLog;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.io.Files;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class ModListHelper {
|
||||
public static class JsonModList {
|
||||
public String repositoryRoot;
|
||||
public List<String> modRef;
|
||||
public String parentList;
|
||||
}
|
||||
private static File mcDirectory;
|
||||
private static Set<File> visitedFiles = Sets.newHashSet();
|
||||
public static final Map<String,File> additionalMods = Maps.newLinkedHashMap();
|
||||
static void parseModList(File minecraftDirectory)
|
||||
{
|
||||
FMLLog.log.debug("Attempting to load commandline specified mods, relative to {}", minecraftDirectory.getAbsolutePath());
|
||||
mcDirectory = minecraftDirectory;
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String,String> args = (Map<String, String>) Launch.blackboard.get("launchArgs");
|
||||
String listFile = args.get("--modListFile");
|
||||
if (listFile != null)
|
||||
{
|
||||
parseListFile(listFile);
|
||||
}
|
||||
String extraMods = args.get("--mods");
|
||||
if (extraMods != null)
|
||||
{
|
||||
String[] split = extraMods.split(",");
|
||||
for (String modFile : split)
|
||||
{
|
||||
tryAddFile(modFile, null, modFile);
|
||||
}
|
||||
}
|
||||
|
||||
String[] extras = new String[]
|
||||
{
|
||||
"mods/mod_list.json",
|
||||
"mods/" + FMLInjectionData.mccversion + "/mod_list.json"
|
||||
};
|
||||
|
||||
for (String extra : extras)
|
||||
{
|
||||
if ((new File(mcDirectory, extra)).exists())
|
||||
parseListFile(extra);
|
||||
}
|
||||
|
||||
}
|
||||
private static void parseListFile(String listFile) {
|
||||
File f;
|
||||
try
|
||||
{
|
||||
if (listFile.startsWith("absolute:"))
|
||||
f = new File(listFile.substring(9)).getCanonicalFile();
|
||||
else
|
||||
f = new File(mcDirectory, listFile).getCanonicalFile();
|
||||
} catch (IOException e2)
|
||||
{
|
||||
FMLLog.log.info(FMLLog.log.getMessageFactory().newMessage("Unable to canonicalize path {} relative to {}", listFile, mcDirectory.getAbsolutePath()), e2);
|
||||
return;
|
||||
}
|
||||
if (!f.exists())
|
||||
{
|
||||
FMLLog.log.info("Failed to find modList file {}", f.getAbsolutePath());
|
||||
return;
|
||||
}
|
||||
if (visitedFiles.contains(f))
|
||||
{
|
||||
FMLLog.log.fatal("There appears to be a loop in the modListFile hierarchy. You shouldn't do this!");
|
||||
throw new RuntimeException("Loop detected, impossible to load modlistfile");
|
||||
}
|
||||
String json;
|
||||
try {
|
||||
json = Files.asCharSource(f, StandardCharsets.UTF_8).read();
|
||||
} catch (IOException e1) {
|
||||
FMLLog.log.info(FMLLog.log.getMessageFactory().newMessage("Failed to read modList json file {}.", listFile), e1);
|
||||
return;
|
||||
}
|
||||
Gson gsonParser = new Gson();
|
||||
JsonModList modList;
|
||||
try {
|
||||
modList = gsonParser.fromJson(json, JsonModList.class);
|
||||
} catch (JsonSyntaxException e) {
|
||||
FMLLog.log.info(FMLLog.log.getMessageFactory().newMessage("Failed to parse modList json file {}.", listFile), e);
|
||||
return;
|
||||
}
|
||||
visitedFiles.add(f);
|
||||
// We visit parents before children, so the additionalMods list is sorted from parent to child
|
||||
if (modList.parentList != null)
|
||||
{
|
||||
parseListFile(modList.parentList);
|
||||
}
|
||||
File repoRoot = new File(modList.repositoryRoot);
|
||||
if (!repoRoot.exists())
|
||||
{
|
||||
FMLLog.log.info("Failed to find the specified repository root {}", modList.repositoryRoot);
|
||||
return;
|
||||
}
|
||||
|
||||
for (String s : modList.modRef)
|
||||
{
|
||||
StringBuilder fileName = new StringBuilder();
|
||||
StringBuilder genericName = new StringBuilder();
|
||||
String[] parts = s.split(":");
|
||||
fileName.append(parts[0].replace('.', File.separatorChar));
|
||||
genericName.append(parts[0]);
|
||||
fileName.append(File.separatorChar);
|
||||
fileName.append(parts[1]).append(File.separatorChar);
|
||||
genericName.append(":").append(parts[1]);
|
||||
fileName.append(parts[2]).append(File.separatorChar);
|
||||
fileName.append(parts[1]).append('-').append(parts[2]);
|
||||
if (parts.length == 4)
|
||||
{
|
||||
fileName.append('-').append(parts[3]);
|
||||
genericName.append(":").append(parts[3]);
|
||||
}
|
||||
fileName.append(".jar");
|
||||
tryAddFile(fileName.toString(), repoRoot, genericName.toString());
|
||||
}
|
||||
}
|
||||
private static void tryAddFile(String modFileName, @Nullable File repoRoot, String descriptor) {
|
||||
File modFile = repoRoot != null ? new File(repoRoot,modFileName) : new File(mcDirectory, modFileName);
|
||||
if (!modFile.exists())
|
||||
{
|
||||
FMLLog.log.info("Failed to find mod file {} ({})", descriptor, modFile.getAbsolutePath());
|
||||
}
|
||||
else
|
||||
{
|
||||
FMLLog.log.debug("Adding {} ({}) to the mod list", descriptor, modFile.getAbsolutePath());
|
||||
additionalMods.put(descriptor, modFile);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,200 @@
|
|||
/*
|
||||
* Minecraft Forge
|
||||
* Copyright (c) 2016-2018.
|
||||
*
|
||||
* 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.relauncher.libraries;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.apache.maven.artifact.versioning.ComparableVersion;
|
||||
|
||||
public class Artifact implements Comparable<Artifact>
|
||||
{
|
||||
private final Repository repo;
|
||||
private final String group;
|
||||
private final String artifact;
|
||||
private final String classifier;
|
||||
private final String extension;
|
||||
private final String value;
|
||||
private final ComparableVersion version;
|
||||
private final String timestamp;
|
||||
private final Date date;
|
||||
|
||||
private String filename;
|
||||
private String folder;
|
||||
|
||||
public Artifact(Repository repo, String value, String timestamp)
|
||||
{
|
||||
this.repo = repo;
|
||||
this.value = value;
|
||||
|
||||
int idx = value.indexOf('@');
|
||||
if (idx > 0)
|
||||
{
|
||||
this.extension = value.substring(idx + 1);
|
||||
value = value.substring(0, idx);
|
||||
}
|
||||
else
|
||||
this.extension = "jar";
|
||||
|
||||
String[] parts = value.split(":");
|
||||
this.group = parts[0];
|
||||
this.artifact = parts[1];
|
||||
this.version = new ComparableVersion(parts[2]);
|
||||
this.classifier = parts.length > 3 ? parts[3] : null;
|
||||
this.timestamp = isSnapshot() ? timestamp : null;
|
||||
try
|
||||
{
|
||||
this.date = this.timestamp == null ? null : SnapshotJson.TIMESTAMP.parse(this.timestamp);
|
||||
}
|
||||
catch (ParseException e)
|
||||
{
|
||||
throw new RuntimeException(e); //TODO: Better logging?
|
||||
}
|
||||
}
|
||||
|
||||
public Artifact(Artifact other, Repository repo, String timestamp)
|
||||
{
|
||||
this.repo = repo;
|
||||
this.group = other.group;
|
||||
this.artifact = other.artifact;
|
||||
this.classifier = other.classifier;
|
||||
this.extension = other.extension;
|
||||
this.value = other.value;
|
||||
this.version = other.version;
|
||||
this.timestamp = timestamp;
|
||||
try
|
||||
{
|
||||
this.date = this.timestamp == null ? null : SnapshotJson.TIMESTAMP.parse(this.timestamp);
|
||||
}
|
||||
catch (ParseException e)
|
||||
{
|
||||
throw new RuntimeException(e); //TODO: Better logging?
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return this.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return this.value.hashCode();
|
||||
}
|
||||
|
||||
public String getFilename()
|
||||
{
|
||||
if (this.filename == null)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(artifact).append('-').append(version);
|
||||
if (isSnapshot() && timestamp != null)
|
||||
sb.append('-').append(getTimestamp());
|
||||
if (classifier != null)
|
||||
sb.append('-').append(classifier);
|
||||
sb.append('.').append(extension);
|
||||
this.filename = sb.toString();
|
||||
}
|
||||
return this.filename;
|
||||
}
|
||||
|
||||
public String getFolder()
|
||||
{
|
||||
if (folder == null)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String part : group.split("."))
|
||||
sb.append(part).append(File.separatorChar);
|
||||
sb.append(artifact).append(File.separatorChar);
|
||||
sb.append(version);
|
||||
folder = sb.toString();
|
||||
}
|
||||
return folder;
|
||||
}
|
||||
|
||||
public String getPath()
|
||||
{
|
||||
return getFolder() + File.separatorChar + getFilename();
|
||||
}
|
||||
|
||||
public File getFile()
|
||||
{
|
||||
return (repo != null ? repo : LibraryManager.getDefaultRepo()).getFile(getPath());
|
||||
}
|
||||
|
||||
public File getSnapshotMeta()
|
||||
{
|
||||
if (!isSnapshot())
|
||||
throw new IllegalStateException("Attempted to call date suffix on non-snapshot");
|
||||
return (repo != null ? repo : LibraryManager.getDefaultRepo()).getFile(getFolder() + File.separatorChar + SnapshotJson.META_JSON_FILE);
|
||||
}
|
||||
|
||||
public boolean isSnapshot()
|
||||
{
|
||||
return version.toString().toLowerCase(Locale.ENGLISH).endsWith("-snapshot");
|
||||
}
|
||||
|
||||
public String getTimestamp()
|
||||
{
|
||||
if (!isSnapshot())
|
||||
throw new IllegalStateException("Attempted to call date suffix on non-snapshot");
|
||||
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public ComparableVersion getVersion()
|
||||
{
|
||||
return this.version;
|
||||
}
|
||||
|
||||
public Repository getRepository()
|
||||
{
|
||||
return this.repo;
|
||||
}
|
||||
|
||||
public boolean matchesID(Artifact o)
|
||||
{
|
||||
if (o == null)
|
||||
return false;
|
||||
return group.equals(o.group) && artifact.equals(o.artifact) && (o.classifier == null ? classifier == null : o.classifier.equals(classifier)); //TODO: Case sensitive?
|
||||
}
|
||||
|
||||
public int compareVersion(Artifact o)
|
||||
{
|
||||
int ver = version.compareTo(o.version);
|
||||
if (ver != 0 || !isSnapshot()) return ver;
|
||||
return timestamp == null ? (o.timestamp == null ? 0 : -1) : o.timestamp == null ? 1 : date.compareTo(o.date);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Artifact o)
|
||||
{
|
||||
if (o == null) return 1;
|
||||
if (!group.equals(o.group)) return group.compareTo(o.group);
|
||||
if (!artifact.equals(o.artifact)) return artifact.compareTo(o.artifact);
|
||||
if (classifier == null && o.classifier != null) return -1;
|
||||
if (classifier != null && o.classifier == null) return 1;
|
||||
if (classifier != null && !classifier.equals(o.classifier)) return classifier.compareTo(o.classifier);
|
||||
return compareVersion(o);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,466 @@
|
|||
/*
|
||||
* Minecraft Forge
|
||||
* Copyright (c) 2016-2018.
|
||||
*
|
||||
* 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.relauncher.libraries;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FilenameFilter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URISyntaxException;
|
||||
import java.security.CodeSource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.apache.maven.artifact.versioning.ArtifactVersion;
|
||||
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.io.Files;
|
||||
import net.minecraft.launchwrapper.Launch;
|
||||
import net.minecraftforge.common.ForgeVersion;
|
||||
import net.minecraftforge.fml.common.FMLLog;
|
||||
import net.minecraftforge.fml.relauncher.FMLLaunchHandler;
|
||||
|
||||
public class LibraryManager
|
||||
{
|
||||
public static final boolean DISABLE_EXTERNAL_MANIFEST = Boolean.parseBoolean(System.getProperty("forge.disable_external_manifest", "false"));
|
||||
public static final boolean ENABLE_AUTO_MOD_MOVEMENT = Boolean.parseBoolean(System.getProperty("forge.enable_auto_mod_movement", "false"));
|
||||
private static final String LIBRARY_DIRECTORY_OVERRIDE = System.getProperty("forge.lib_folder", null);
|
||||
private static final List<String> skipContainedDeps = Arrays.asList(System.getProperty("fml.skipContainedDeps","").split(",")); //TODO: Is this used by anyone in the real world? TODO: Remove in 1.13.
|
||||
private static final FilenameFilter MOD_FILENAME_FILTER = (dir, name) -> name.endsWith(".jar") || name.endsWith(".zip"); //TODO: Disable support for zip in 1.13
|
||||
private static final Comparator<File> FILE_NAME_SORTER_INSENSITVE = (o1, o2) -> o1.getName().toLowerCase(Locale.ENGLISH).compareTo(o2.getName().toLowerCase(Locale.ENGLISH));
|
||||
|
||||
public static final Attributes.Name MODSIDE = new Attributes.Name("ModSide");
|
||||
private static final Attributes.Name MODCONTAINSDEPS = new Attributes.Name("ContainedDeps");
|
||||
private static final Attributes.Name MAVEN_ARTIFACT = new Attributes.Name("Maven-Artifact");
|
||||
private static final Attributes.Name TIMESTAMP = new Attributes.Name("Timestamp");
|
||||
private static final Attributes.Name MD5 = new Attributes.Name("MD5");
|
||||
private static Repository libraries_dir = null;
|
||||
private static Set<File> processed = new HashSet<File>();
|
||||
|
||||
public static void setup(File minecraftHome)
|
||||
{
|
||||
File libDir = findLibraryFolder(minecraftHome);
|
||||
FMLLog.log.debug("Determined Minecraft Libraries Root: {}", libDir);
|
||||
Repository old = Repository.replace(libDir, "libraries");
|
||||
if (old != null)
|
||||
FMLLog.log.debug(" Overwriting Previous: {}", old);
|
||||
libraries_dir = Repository.get("libraries");
|
||||
|
||||
File mods = new File(minecraftHome, "mods");
|
||||
File mods_ver = new File(mods, ForgeVersion.mcVersion);
|
||||
|
||||
ModList memory = null;
|
||||
if (!ENABLE_AUTO_MOD_MOVEMENT)
|
||||
{
|
||||
Repository repo = new LinkRepository(new File(mods, "memory_repo"));
|
||||
memory = new MemoryModList(repo);
|
||||
ModList.cache.put("MEMORY", memory);
|
||||
Repository.cache.put("MEMORY", repo);
|
||||
}
|
||||
|
||||
for (File dir : new File[]{mods, mods_ver})
|
||||
cleanDirectory(dir, ENABLE_AUTO_MOD_MOVEMENT ? ModList.create(new File(dir, "mod_list.json"), minecraftHome) : memory, mods_ver, mods);
|
||||
|
||||
for (ModList list : ModList.getKnownLists(minecraftHome))
|
||||
{
|
||||
Repository repo = list.getRepository() == null ? libraries_dir : list.getRepository();
|
||||
for (Artifact artifact : list.getArtifacts())
|
||||
{
|
||||
Artifact resolved = repo.resolve(artifact);
|
||||
if (resolved != null)
|
||||
{
|
||||
File target = repo.getFile(resolved.getPath());
|
||||
if (target.exists())
|
||||
extractPacked(target, list, mods_ver, mods);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static File findLibraryFolder(File minecraftHome)
|
||||
{
|
||||
if (LIBRARY_DIRECTORY_OVERRIDE != null)
|
||||
{
|
||||
FMLLog.log.error("System variable set to override Library Directory: {}", LIBRARY_DIRECTORY_OVERRIDE);
|
||||
return new File(LIBRARY_DIRECTORY_OVERRIDE);
|
||||
}
|
||||
|
||||
CodeSource source = ArtifactVersion.class.getProtectionDomain().getCodeSource();
|
||||
if (source == null)
|
||||
{
|
||||
FMLLog.log.error("Unable to determine codesource for {}. Using default libraries directory.", ArtifactVersion.class.getName());
|
||||
return new File(minecraftHome, "libraries");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
File apache = new File(source.getLocation().toURI());
|
||||
if (apache.isFile())
|
||||
apache = apache.getParentFile(); //Get to a directory, this *should* always be the case...
|
||||
apache = apache.getParentFile(); //Skip the version folder. In case we ever update the version, I don't want to edit this code again.
|
||||
|
||||
if (!apache.getAbsolutePath().toLowerCase(Locale.ENGLISH).replace(File.separatorChar, '/').endsWith("/org/apache/maven/maven/"))
|
||||
{
|
||||
FMLLog.log.error("Apache Maven library folder was not in the format expected: {}. Using default libraries directory.", new File(source.getLocation().toURI()));
|
||||
return new File(minecraftHome, "libraries");
|
||||
}
|
||||
// maven /maven /apache /org /libraries
|
||||
return apache.getParentFile().getParentFile().getParentFile().getParentFile();
|
||||
}
|
||||
catch (URISyntaxException e)
|
||||
{
|
||||
FMLLog.log.error(FMLLog.log.getMessageFactory().newMessage("Unable to determine file for {}. Using default libraries directory.", ArtifactVersion.class.getName()), e);
|
||||
}
|
||||
|
||||
return new File(minecraftHome, "libraries"); //Everything else failed, return the default.
|
||||
}
|
||||
|
||||
private static void cleanDirectory(File dir, ModList modlist, File... modDirs)
|
||||
{
|
||||
if (!dir.exists())
|
||||
return;
|
||||
|
||||
FMLLog.log.debug("Cleaning up mods folder: {}", dir);
|
||||
for (File file : dir.listFiles(f -> f.isFile() && f.getName().endsWith(".jar")))
|
||||
{
|
||||
Pair<Artifact, byte[]> ret = extractPacked(file, modlist, modDirs);
|
||||
if (ret != null)
|
||||
{
|
||||
Artifact artifact = ret.getLeft();
|
||||
Repository repo = modlist.getRepository() == null ? libraries_dir : modlist.getRepository();
|
||||
File moved = repo.archive(artifact, file, ret.getRight());
|
||||
modlist.add(artifact);
|
||||
processed.add(moved);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (modlist.changed())
|
||||
modlist.save();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
FMLLog.log.error(FMLLog.log.getMessageFactory().newMessage("Error updating modlist file {}", modlist.getName()), e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Pair<Artifact, byte[]> extractPacked(File file, ModList modlist, File... modDirs)
|
||||
{
|
||||
if (processed.contains(file))
|
||||
{
|
||||
FMLLog.log.debug("File already proccessed {}, Skipping", file.getAbsolutePath());
|
||||
return null;
|
||||
}
|
||||
JarFile jar = null;
|
||||
try
|
||||
{
|
||||
jar = new JarFile(file);
|
||||
FMLLog.log.debug("Examining file: {}", file.getName());
|
||||
processed.add(file);
|
||||
return extractPacked(jar, modlist, modDirs);
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
FMLLog.log.error("Unable to read the jar file {} - ignoring", file.getName(), ioe);
|
||||
}
|
||||
finally
|
||||
{
|
||||
try {
|
||||
if (jar != null)
|
||||
jar.close();
|
||||
} catch (IOException e) {}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
private static Pair<Artifact, byte[]> extractPacked(JarFile jar, ModList modlist, File... modDirs) throws IOException
|
||||
{
|
||||
Attributes attrs;
|
||||
if (jar.getManifest() == null)
|
||||
return null;
|
||||
|
||||
JarEntry manfest_entry = jar.getJarEntry(JarFile.MANIFEST_NAME);
|
||||
if (manfest_entry == null)
|
||||
manfest_entry = jar.stream().filter(e -> JarFile.MANIFEST_NAME.equals(e.getName().toUpperCase(Locale.ENGLISH))).findFirst().get(); //We know that getManifest returned non-null so we know there is *some* entry that matches the manifest file. So we dont need to empty check.
|
||||
|
||||
attrs = jar.getManifest().getMainAttributes();
|
||||
|
||||
String modSide = attrs.getValue(LibraryManager.MODSIDE);
|
||||
if (!"BOTH".equals(modSide) || FMLLaunchHandler.side().name().equals(modSide))
|
||||
return null;
|
||||
|
||||
if (attrs.containsKey(MODCONTAINSDEPS))
|
||||
{
|
||||
for (String dep : attrs.getValue(MODCONTAINSDEPS).split(" "))
|
||||
{
|
||||
if (!dep.endsWith(".jar"))
|
||||
{
|
||||
FMLLog.log.error("Contained Dep is not a jar file: {}", dep);
|
||||
throw new IllegalStateException("Invalid contained dep, Must be jar: " + dep);
|
||||
}
|
||||
|
||||
if (jar.getJarEntry(dep) == null && jar.getJarEntry("META-INF/libraries/" + dep) != null)
|
||||
dep = "META-INF/libraries/" + dep;
|
||||
|
||||
JarEntry depEntry = jar.getJarEntry(dep);
|
||||
if (depEntry == null)
|
||||
{
|
||||
FMLLog.log.error("Contained Dep is not in the jar: {}", dep);
|
||||
throw new IllegalStateException("Invalid contained dep, Missing from jar: " + dep);
|
||||
}
|
||||
|
||||
String depEndName = new File(dep).getName(); // extract last part of name
|
||||
if (skipContainedDeps.contains(dep) || skipContainedDeps.contains(depEndName))
|
||||
{
|
||||
FMLLog.log.error("Skipping dep at request: {}", dep);
|
||||
continue;
|
||||
}
|
||||
|
||||
Attributes meta = null;
|
||||
byte[] data = null;
|
||||
byte[] manifest_data = null;
|
||||
|
||||
JarEntry metaEntry = jar.getJarEntry(dep + ".meta");
|
||||
if (metaEntry != null)
|
||||
{
|
||||
manifest_data = readAll(jar.getInputStream(metaEntry));
|
||||
meta = new Manifest(new ByteArrayInputStream(manifest_data)).getMainAttributes();
|
||||
}
|
||||
else
|
||||
{
|
||||
data = readAll(jar.getInputStream(depEntry));
|
||||
try (ZipInputStream zi = new ZipInputStream(new ByteArrayInputStream(data))) //We use zip input stream directly, as the current Oracle implementation of JarInputStream only works when the manifest is the First/Second entry in the jar...
|
||||
{
|
||||
ZipEntry ze = null;
|
||||
while ((ze = zi.getNextEntry()) != null)
|
||||
{
|
||||
if (ze.getName().equalsIgnoreCase(JarFile.MANIFEST_NAME))
|
||||
{
|
||||
manifest_data = readAll(zi);
|
||||
meta = new Manifest(new ByteArrayInputStream(manifest_data)).getMainAttributes();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (meta == null || !meta.containsKey(MAVEN_ARTIFACT)) //Ugh I really don't want to do backwards compatibility here, I want to force modders to provide information... TODO: Remove in 1.13?
|
||||
{
|
||||
boolean found = false;
|
||||
for (File dir : modDirs)
|
||||
{
|
||||
File target = new File(dir, depEndName);
|
||||
if (target.exists())
|
||||
{
|
||||
FMLLog.log.debug("Found existing ContainDep extracted to {}, skipping extraction", target.getCanonicalPath());
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
File target = new File(modDirs[0], depEndName);
|
||||
FMLLog.log.debug("Extracting ContainedDep {} from {} to {}", dep, jar.getName(), target.getCanonicalPath());
|
||||
try
|
||||
{
|
||||
Files.createParentDirs(target);
|
||||
try
|
||||
(
|
||||
FileOutputStream out = new FileOutputStream(target);
|
||||
InputStream in = data == null ? jar.getInputStream(depEntry) : new ByteArrayInputStream(data)
|
||||
)
|
||||
{
|
||||
ByteStreams.copy(in, out);
|
||||
}
|
||||
FMLLog.log.debug("Extracted ContainedDep {} from {} to {}", dep, jar.getName(), target.getCanonicalPath());
|
||||
extractPacked(target, modlist, modDirs);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
FMLLog.log.error("An error occurred extracting dependency", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
String timestamp = meta.getValue(TIMESTAMP);
|
||||
if (timestamp != null)
|
||||
timestamp = SnapshotJson.TIMESTAMP.format(new Date(Integer.parseInt(timestamp)));
|
||||
|
||||
Artifact artifact = new Artifact(modlist.getRepository(), meta.getValue(MAVEN_ARTIFACT), timestamp);
|
||||
File target = artifact.getFile();
|
||||
if (target.exists())
|
||||
FMLLog.log.debug("Found existing ContainedDep {}({}) from {} extracted to {}, skipping extraction", dep, artifact.toString(), target.getCanonicalPath(), jar.getName());
|
||||
else
|
||||
{
|
||||
FMLLog.log.debug("Extracting ContainedDep {}({}) from {} to {}", dep, artifact.toString(), jar.getName(), target.getCanonicalPath());
|
||||
Files.createParentDirs(target);
|
||||
try
|
||||
(
|
||||
FileOutputStream out = new FileOutputStream(target);
|
||||
InputStream in = data == null ? jar.getInputStream(depEntry) : new ByteArrayInputStream(data)
|
||||
)
|
||||
{
|
||||
ByteStreams.copy(in, out);
|
||||
}
|
||||
FMLLog.log.debug("Extracted ContainedDep {}({}) from {} to {}", dep, artifact.toString(), jar.getName(), target.getCanonicalPath());
|
||||
|
||||
if (artifact.isSnapshot())
|
||||
{
|
||||
SnapshotJson json = SnapshotJson.create(artifact.getSnapshotMeta());
|
||||
json.add(new SnapshotJson.Entry(artifact.getTimestamp(), meta.getValue(MD5)));
|
||||
json.write(artifact.getSnapshotMeta());
|
||||
}
|
||||
|
||||
if (!DISABLE_EXTERNAL_MANIFEST)
|
||||
{
|
||||
File meta_target = new File(target.getAbsolutePath() + ".meta");
|
||||
Files.write(manifest_data, meta_target);
|
||||
}
|
||||
extractPacked(target, modlist, modDirs);
|
||||
}
|
||||
}
|
||||
catch (NumberFormatException nfe)
|
||||
{
|
||||
FMLLog.log.error(FMLLog.log.getMessageFactory().newMessage("An error occurred extracting dependency. Invalid Timestamp: {}", meta.getValue(TIMESTAMP)), nfe);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
FMLLog.log.error("An error occurred extracting dependency", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return attrs.containsKey(MAVEN_ARTIFACT) ? Pair.of(new Artifact(modlist.getRepository(), attrs.getValue(MAVEN_ARTIFACT), attrs.getValue(TIMESTAMP)), readAll(jar.getInputStream(manfest_entry))) : null;
|
||||
}
|
||||
|
||||
private static byte[] readAll(InputStream in) throws IOException
|
||||
{
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
int read = -1;
|
||||
byte[] data = new byte[1024 * 16];
|
||||
|
||||
while ((read = in.read(data, 0, data.length)) != -1)
|
||||
out.write(data, 0, read);
|
||||
|
||||
out.flush();
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
public static List<Artifact> flattenLists(File mcDir)
|
||||
{
|
||||
List<Artifact> merged = new ArrayList<>();
|
||||
for (ModList list : ModList.getBasicLists(mcDir))
|
||||
{
|
||||
for (Artifact art : list.flatten())
|
||||
{
|
||||
Optional<Artifact> old = merged.stream().filter(art::matchesID).findFirst();
|
||||
if (!old.isPresent())
|
||||
{
|
||||
merged.add(art);
|
||||
}
|
||||
else if (old.get().getVersion().compareTo(art.getVersion()) < 0)
|
||||
{
|
||||
merged.add(merged.indexOf(old.get()), art);
|
||||
merged.remove(old.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
|
||||
public static List<File> gatherLegacyCanidates(File mcDir)
|
||||
{
|
||||
List<File> list = new ArrayList<>();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String,String> args = (Map<String, String>)Launch.blackboard.get("launchArgs");
|
||||
String extraMods = args.get("--mods");
|
||||
if (extraMods != null)
|
||||
{
|
||||
FMLLog.log.info("Found mods from the command line:");
|
||||
for (String mod : extraMods.split(","))
|
||||
{
|
||||
File file = new File(mcDir, mod);
|
||||
if (!file.exists())
|
||||
{
|
||||
FMLLog.log.info(" Failed to find mod file {} ({})", mod, file.getAbsolutePath());
|
||||
}
|
||||
else if (!list.contains(file))
|
||||
{
|
||||
FMLLog.log.debug(" Adding {} ({}) to the mod list", mod, file.getAbsolutePath());
|
||||
list.add(file);
|
||||
}
|
||||
else if (!list.contains(file))
|
||||
{
|
||||
FMLLog.log.debug(" Duplicte command line mod detected {} ({})", mod, file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (String dir : new String[]{"mods", "mods" + File.separatorChar + ForgeVersion.mcVersion})
|
||||
{
|
||||
File base = new File(mcDir, dir);
|
||||
if (!base.isDirectory() || !base.exists())
|
||||
continue;
|
||||
|
||||
FMLLog.log.info("Searching {} for mods", base.getAbsolutePath());
|
||||
for (File f : base.listFiles(MOD_FILENAME_FILTER))
|
||||
{
|
||||
if (!list.contains(f))
|
||||
{
|
||||
FMLLog.log.debug(" Adding {} to the mod list", f.getName());
|
||||
list.add(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ModList memory = ModList.cache.get("MEMORY");
|
||||
if (!ENABLE_AUTO_MOD_MOVEMENT && memory != null && memory.getRepository() != null)
|
||||
memory.getRepository().filterLegacy(list);
|
||||
|
||||
list.sort(FILE_NAME_SORTER_INSENSITVE);
|
||||
return list;
|
||||
}
|
||||
|
||||
public static Repository getDefaultRepo()
|
||||
{
|
||||
return libraries_dir;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Minecraft Forge
|
||||
* Copyright (c) 2016-2018.
|
||||
*
|
||||
* 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.relauncher.libraries;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import net.minecraftforge.fml.common.FMLLog;
|
||||
|
||||
public class LinkRepository extends Repository
|
||||
{
|
||||
private Map<String, File> artifact_to_file = new HashMap<>();
|
||||
private Map<String, File> filesystem = new HashMap<>();
|
||||
private Map<String, Artifact> snapshots = new HashMap<>();
|
||||
private Set<File> known = new HashSet<>();
|
||||
|
||||
LinkRepository(File root)
|
||||
{
|
||||
super(root, "MEMORY");
|
||||
}
|
||||
|
||||
public File archive(Artifact artifact, File file, byte[] manifest)
|
||||
{
|
||||
String key = artifact.toString();
|
||||
known.add(file);
|
||||
if (artifact_to_file.containsKey(key))
|
||||
{
|
||||
FMLLog.log.debug("Maven file already exists for {}({}) at {}, ignoring duplicate.", file.getName(), artifact.toString(), artifact_to_file.get(key).getAbsolutePath());
|
||||
|
||||
if (artifact.isSnapshot())
|
||||
{
|
||||
Artifact old = snapshots.get(key);
|
||||
if (old == null || old.compareVersion(artifact) < 0)
|
||||
{
|
||||
FMLLog.log.debug("Overriding Snapshot {} -> {}", old == null ? "null" : old.getTimestamp(), artifact.getTimestamp());
|
||||
snapshots.put(key, artifact);
|
||||
artifact_to_file.put(key, file);
|
||||
filesystem.put(artifact.getPath(), file);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
FMLLog.log.debug("Making maven link for {} in memory to {}.", key, file.getAbsolutePath());
|
||||
artifact_to_file.put(key, file);
|
||||
filesystem.put(artifact.getPath(), file);
|
||||
|
||||
if (artifact.isSnapshot())
|
||||
snapshots.put(key, artifact);
|
||||
|
||||
/* Support external meta? screw it.
|
||||
if (!LibraryManager.DISABLE_EXTERNAL_MANIFEST)
|
||||
{
|
||||
File meta_target = new File(target.getAbsolutePath() + ".meta");
|
||||
Files.write(manifest, meta_target);
|
||||
}
|
||||
*/
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void filterLegacy(List<File> list)
|
||||
{
|
||||
list.removeIf(e -> known.contains(e));
|
||||
}
|
||||
|
||||
public Artifact resolve(Artifact artifact)
|
||||
{
|
||||
String key = artifact.toString();
|
||||
File file = artifact_to_file.get(key);
|
||||
if (file == null || !file.exists())
|
||||
return null;
|
||||
return new Artifact(artifact, this, artifact.isSnapshot() ? artifact.getTimestamp() : null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getFile(String path)
|
||||
{
|
||||
return filesystem.containsKey(path) ? super.getFile(path) : filesystem.get(path);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Minecraft Forge
|
||||
* Copyright (c) 2016-2018.
|
||||
*
|
||||
* 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.relauncher.libraries;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class MemoryModList extends ModList
|
||||
{
|
||||
MemoryModList(Repository repo)
|
||||
{
|
||||
super(repo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() throws IOException {}
|
||||
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return "MEMORY";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,243 @@
|
|||
/*
|
||||
* Minecraft Forge
|
||||
* Copyright (c) 2016-2018.
|
||||
*
|
||||
* 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.relauncher.libraries;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.io.Files;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
import net.minecraft.launchwrapper.Launch;
|
||||
import net.minecraftforge.common.ForgeVersion;
|
||||
import net.minecraftforge.fml.common.FMLLog;
|
||||
|
||||
public class ModList
|
||||
{
|
||||
private static final Gson GSON = new GsonBuilder().setLenient().create();
|
||||
static final Map<String, ModList> cache = new HashMap<>();
|
||||
|
||||
public static ModList create(File json, File mcdir)
|
||||
{
|
||||
try
|
||||
{
|
||||
String key = json.getCanonicalFile().getAbsolutePath();
|
||||
if (cache.containsKey(key))
|
||||
return cache.get(key);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
FMLLog.log.error(FMLLog.log.getMessageFactory().newMessage("Unable to load ModList json at {}.", json.getAbsoluteFile()), e);
|
||||
}
|
||||
|
||||
return new ModList(json, mcdir);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static List<ModList> getKnownLists(File mcdir)
|
||||
{
|
||||
for (String list : new String[] {"mods/mod_list.json", "mods/" + ForgeVersion.mcVersion + "/mod_list.json", ((Map<String, String>)Launch.blackboard.get("launchArgs")).get("--modListFile")})
|
||||
{
|
||||
if (list != null)
|
||||
{
|
||||
File listFile = getFile(mcdir, list);
|
||||
if (listFile != null && listFile.exists())
|
||||
create(listFile, mcdir); //Create will recursively create parent lists, so after this run everything should be populated.
|
||||
}
|
||||
}
|
||||
return ImmutableList.copyOf(cache.values()); //TODO: Do we care about order? I dont think so as we resolve all libs in a flat map.
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static List<ModList> getBasicLists(File mcdir)
|
||||
{
|
||||
List<ModList> lst = new ArrayList<>();
|
||||
|
||||
ModList memory = cache.get("MEMORY");
|
||||
if (memory != null)
|
||||
lst.add(memory);
|
||||
|
||||
for (String list : new String[] {"mods/mod_list.json", "mods/" + ForgeVersion.mcVersion + "/mod_list.json", ((Map<String, String>)Launch.blackboard.get("launchArgs")).get("--modListFile")})
|
||||
{
|
||||
if (list != null)
|
||||
{
|
||||
File listFile = getFile(mcdir, list);
|
||||
if (listFile != null && listFile.exists())
|
||||
lst.add(create(listFile, mcdir));
|
||||
}
|
||||
}
|
||||
return lst;
|
||||
}
|
||||
|
||||
private final File path;
|
||||
private final JsonModList mod_list;
|
||||
private final Repository repo;
|
||||
private final ModList parent;
|
||||
private final List<Artifact> artifacts = new ArrayList<>();
|
||||
private final List<Artifact> artifacts_imm = Collections.unmodifiableList(artifacts);
|
||||
private final Map<String, Artifact> art_map = new HashMap<>();
|
||||
|
||||
private boolean changed = false;
|
||||
|
||||
protected ModList(Repository repo)
|
||||
{
|
||||
this.path = null;
|
||||
this.mod_list = new JsonModList();
|
||||
this.repo = repo;
|
||||
this.parent = null;
|
||||
}
|
||||
|
||||
private ModList(File path, File mcdir)
|
||||
{
|
||||
this.path = path;
|
||||
JsonModList temp_list = null;
|
||||
if (this.path.exists())
|
||||
{
|
||||
try
|
||||
{
|
||||
String json = Files.asCharSource(path, StandardCharsets.UTF_8).read();
|
||||
temp_list = GSON.fromJson(json, JsonModList.class);
|
||||
}
|
||||
catch (JsonSyntaxException jse)
|
||||
{
|
||||
FMLLog.log.info(FMLLog.log.getMessageFactory().newMessage("Failed to parse modList json file {}.", path), jse);
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
FMLLog.log.info(FMLLog.log.getMessageFactory().newMessage("Failed to read modList json file {}.", path), ioe);
|
||||
}
|
||||
}
|
||||
this.mod_list = temp_list == null ? new JsonModList() : temp_list;
|
||||
Repository temp = null;
|
||||
if (mod_list.repositoryRoot != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
temp = Repository.create(getFile(mcdir, this.mod_list.repositoryRoot));
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
FMLLog.log.info(FMLLog.log.getMessageFactory().newMessage("Failed to create repository for modlist at {}.", mod_list.repositoryRoot), e);
|
||||
}
|
||||
}
|
||||
this.repo = temp;
|
||||
File parent_path = this.mod_list.parentList == null ? null : getFile(mcdir, this.mod_list.parentList);
|
||||
this.parent = parent_path == null || !parent_path.exists() ? null : ModList.create(parent_path, mcdir);
|
||||
|
||||
if (mod_list.modRef != null)
|
||||
{
|
||||
for (String ref : mod_list.modRef)
|
||||
add(new Artifact(this.getRepository(), ref, null));
|
||||
changed = false;
|
||||
}
|
||||
}
|
||||
|
||||
public Repository getRepository()
|
||||
{
|
||||
return this.repo;
|
||||
}
|
||||
|
||||
public void add(Artifact artifact)
|
||||
{
|
||||
Artifact old = art_map.get(artifact.toString());
|
||||
if (old != null)
|
||||
{
|
||||
artifacts.add(artifacts.indexOf(old), artifact);
|
||||
artifacts.remove(old);
|
||||
}
|
||||
else
|
||||
artifacts.add(artifact);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
public List<Artifact> getArtifacts()
|
||||
{
|
||||
return artifacts_imm;
|
||||
}
|
||||
|
||||
public boolean changed()
|
||||
{
|
||||
return changed;
|
||||
}
|
||||
|
||||
public void save() throws IOException
|
||||
{
|
||||
mod_list.modRef = artifacts.stream().map(a -> a.toString()).collect(Collectors.toList());
|
||||
Files.write(GSON.toJson(mod_list), path, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private static File getFile(File root, String path)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (path.startsWith("absolute:"))
|
||||
return new File(path.substring(9)).getCanonicalFile();
|
||||
else
|
||||
return new File(root, path).getCanonicalFile();
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
FMLLog.log.info(FMLLog.log.getMessageFactory().newMessage("Unable to canonicalize path {} relative to {}", path, root.getAbsolutePath()), ioe);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class JsonModList
|
||||
{
|
||||
public String repositoryRoot;
|
||||
public List<String> modRef;
|
||||
public String parentList;
|
||||
}
|
||||
|
||||
//TODO: Some form of caching?
|
||||
public List<Artifact> flatten()
|
||||
{
|
||||
List<Artifact> lst = parent == null ? new ArrayList<>() : parent.flatten();
|
||||
for (Artifact art : artifacts)
|
||||
{
|
||||
Optional<Artifact> old = lst.stream().filter(art::matchesID).findFirst();
|
||||
if (!old.isPresent())
|
||||
{
|
||||
lst.add(art);
|
||||
}
|
||||
else if (old.get().getVersion().compareTo(art.getVersion()) < 0)
|
||||
{
|
||||
lst.add(lst.indexOf(old.get()), art);
|
||||
lst.remove(old.get());
|
||||
}
|
||||
}
|
||||
return lst;
|
||||
}
|
||||
|
||||
public Object getName()
|
||||
{
|
||||
return path.getAbsolutePath();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
* Minecraft Forge
|
||||
* Copyright (c) 2016-2018.
|
||||
*
|
||||
* 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.relauncher.libraries;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.hash.Hashing;
|
||||
import com.google.common.io.Files;
|
||||
|
||||
import net.minecraftforge.fml.common.FMLLog;
|
||||
|
||||
public class Repository
|
||||
{
|
||||
static final Map<String, Repository> cache = new LinkedHashMap<>();
|
||||
|
||||
public static Repository create(File root) throws IOException
|
||||
{
|
||||
return create(root, root.getCanonicalPath());
|
||||
}
|
||||
public static Repository create(File root, String name)
|
||||
{
|
||||
return cache.computeIfAbsent(name, f -> new Repository(root, name));
|
||||
}
|
||||
public static Repository replace(File root, String name)
|
||||
{
|
||||
return cache.put(name, new Repository(root, name));
|
||||
}
|
||||
public static Repository get(String name)
|
||||
{
|
||||
return cache.get(name);
|
||||
}
|
||||
public static Artifact resolveAll(Artifact artifact)
|
||||
{
|
||||
Artifact ret = null;
|
||||
for (Repository repo : cache.values())
|
||||
{
|
||||
Artifact tmp = repo.resolve(artifact);
|
||||
if (tmp == null)
|
||||
continue;
|
||||
if (!artifact.isSnapshot())
|
||||
return tmp; //If its a concrete version *assume* any resolved one in any repo will work. As they shouldn't release overriding versions.
|
||||
ret = ret == null || ret.compareTo(tmp) < 0 ? tmp : ret;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
private final String name;
|
||||
private final File root;
|
||||
|
||||
protected Repository(File root) throws IOException
|
||||
{
|
||||
this(root, root.getCanonicalPath());
|
||||
}
|
||||
protected Repository(File root, String name)
|
||||
{
|
||||
this.root = root;
|
||||
this.name = name;
|
||||
if (name == null)
|
||||
throw new IllegalArgumentException("Invalid Repository Name, for " + root);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return this.name.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
return o instanceof Repository && ((Repository)o).name.equals(name);
|
||||
}
|
||||
|
||||
public Artifact resolve(Artifact artifact)
|
||||
{
|
||||
if (!artifact.isSnapshot())
|
||||
return getFile(artifact.getPath()).exists() ? artifact : null;
|
||||
|
||||
File meta = getFile(artifact.getFolder() + File.separatorChar + SnapshotJson.META_JSON_FILE);
|
||||
if (!meta.exists())
|
||||
return null;
|
||||
|
||||
SnapshotJson json = SnapshotJson.create(getFile(artifact.getFolder() + File.separatorChar + SnapshotJson.META_JSON_FILE));
|
||||
if (json.getLatest() == null)
|
||||
return null;
|
||||
|
||||
Artifact ret = new Artifact(artifact, this, json.getLatest());
|
||||
while (json.getLatest() != null && !getFile(ret.getPath()).exists())
|
||||
{
|
||||
if (!json.remove(json.getLatest()))
|
||||
throw new IllegalStateException("Something went wrong, Latest (" + json.getLatest() + ") did not point to an entry in the json list: " + meta.getAbsolutePath());
|
||||
ret = new Artifact(artifact, this, json.getLatest());
|
||||
}
|
||||
|
||||
return getFile(ret.getPath()).exists() ? ret : null;
|
||||
}
|
||||
|
||||
public File getFile(String path)
|
||||
{
|
||||
return new File(root, path);
|
||||
}
|
||||
|
||||
public File archive(Artifact artifact, File file, byte[] manifest)
|
||||
{
|
||||
File target = artifact.getFile();
|
||||
try
|
||||
{
|
||||
if (target.exists())
|
||||
{
|
||||
FMLLog.log.debug("Maven file already exists for {}({}) at {}, deleting duplicate.", file.getName(), artifact.toString(), target.getAbsolutePath());
|
||||
file.delete();
|
||||
}
|
||||
else
|
||||
{
|
||||
FMLLog.log.debug("Moving file {}({}) to maven repo at {}.", file.getName(), artifact.toString(), target.getAbsolutePath());
|
||||
Files.move(file, target);
|
||||
|
||||
if (artifact.isSnapshot())
|
||||
{
|
||||
SnapshotJson json = SnapshotJson.create(artifact.getSnapshotMeta());
|
||||
json.add(new SnapshotJson.Entry(artifact.getTimestamp(), Files.hash(target, Hashing.md5()).toString()));
|
||||
json.write(artifact.getSnapshotMeta());
|
||||
}
|
||||
|
||||
if (!LibraryManager.DISABLE_EXTERNAL_MANIFEST)
|
||||
{
|
||||
File meta_target = new File(target.getAbsolutePath() + ".meta");
|
||||
Files.write(manifest, meta_target);
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
FMLLog.log.error(FMLLog.log.getMessageFactory().newMessage("Error moving file {} to {}", file, target.getAbsolutePath()), e);
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
public void filterLegacy(List<File> list){}
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
* Minecraft Forge
|
||||
* Copyright (c) 2016-2018.
|
||||
*
|
||||
* 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.relauncher.libraries;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
import net.minecraftforge.fml.common.FMLLog;
|
||||
/**
|
||||
* This is different from the standard maven snapshot metadata.
|
||||
* Because none of that data is exposed to us as a user of gradle/maven/whatever.
|
||||
* So we JUST use the timestamp.
|
||||
*
|
||||
* {
|
||||
* "latest": "yyyymmdd.hhmmss",
|
||||
* "versions": [
|
||||
* {
|
||||
* "md5": "md5 in hex lowercase",
|
||||
* "timestamp": "yyyymmdd.hhmmss"
|
||||
* ]
|
||||
* }
|
||||
*
|
||||
*/
|
||||
public class SnapshotJson implements Comparable<SnapshotJson>
|
||||
{
|
||||
public static final DateFormat TIMESTAMP = new SimpleDateFormat("yyyymmdd.hhmmss");
|
||||
public static final String META_JSON_FILE = "maven-metadata.json";
|
||||
private static final Gson GSON = new GsonBuilder().create();
|
||||
private static final Comparator<Entry> SORTER = (o1, o2) -> o2.timestamp.compareTo(o2.timestamp);
|
||||
|
||||
public static SnapshotJson create(File target)
|
||||
{
|
||||
if (!target.exists())
|
||||
return new SnapshotJson();
|
||||
|
||||
try
|
||||
{
|
||||
String json = Files.asCharSource(target, StandardCharsets.UTF_8).read();
|
||||
SnapshotJson obj = GSON.fromJson(json, SnapshotJson.class);
|
||||
obj.updateLatest();
|
||||
return obj;
|
||||
}
|
||||
catch (JsonSyntaxException jse)
|
||||
{
|
||||
FMLLog.log.info(FMLLog.log.getMessageFactory().newMessage("Failed to parse snapshot json file {}.", target), jse);
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
FMLLog.log.info(FMLLog.log.getMessageFactory().newMessage("Failed to read snapshot json file {}.", target), ioe);
|
||||
}
|
||||
|
||||
return new SnapshotJson();
|
||||
}
|
||||
|
||||
private String latest;
|
||||
private List<Entry> versions;
|
||||
|
||||
public String getLatest()
|
||||
{
|
||||
return this.latest;
|
||||
}
|
||||
|
||||
public void add(Entry data)
|
||||
{
|
||||
if (versions == null)
|
||||
versions = new ArrayList<>();
|
||||
versions.add(data);
|
||||
updateLatest();
|
||||
}
|
||||
|
||||
public void merge(SnapshotJson o)
|
||||
{
|
||||
if (o.versions != null)
|
||||
{
|
||||
if (versions == null)
|
||||
versions = new ArrayList<>(o.versions);
|
||||
else
|
||||
o.versions.stream().filter(e -> versions.stream().anyMatch(e2 -> e.timestamp.equals(e2.timestamp))).forEach(e -> versions.add(e));
|
||||
updateLatest();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean remove(String timestamp)
|
||||
{
|
||||
if (versions == null)
|
||||
return false;
|
||||
if (versions.removeIf(e -> e.timestamp.equals(timestamp)))
|
||||
updateLatest();
|
||||
return false;
|
||||
}
|
||||
|
||||
public String updateLatest()
|
||||
{
|
||||
if (versions == null)
|
||||
{
|
||||
latest = null;
|
||||
return null;
|
||||
}
|
||||
Collections.sort(versions, SORTER);
|
||||
return latest = versions.isEmpty() ? null : versions.get(0).timestamp;
|
||||
}
|
||||
|
||||
public void write(File target) throws IOException
|
||||
{
|
||||
Files.write(GSON.toJson(this), target, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(SnapshotJson o)
|
||||
{
|
||||
return o == null ? 1 : o.latest == null ? latest == null ? 0 : 1 : latest == null ? -1 : o.latest.compareTo(latest);
|
||||
}
|
||||
|
||||
public static class Entry
|
||||
{
|
||||
private String timestamp;
|
||||
private String md5;
|
||||
|
||||
public Entry(String timestamp, String md5)
|
||||
{
|
||||
this.timestamp = timestamp;
|
||||
this.md5 = md5;
|
||||
}
|
||||
|
||||
public String getTimestamp()
|
||||
{
|
||||
return this.timestamp;
|
||||
}
|
||||
|
||||
public String getMd5()
|
||||
{
|
||||
return this.md5;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue