    001/*
    002 * Forge Mod Loader
    003 * Copyright (c) 2012-2013 cpw.
    004 * All rights reserved. This program and the accompanying materials
    005 * are made available under the terms of the GNU Lesser Public License v2.1
    006 * which accompanies this distribution, and is available at
    007 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
    008 *
    009 * Contributors:
    010 *     cpw - implementation
    011 */
    012
    013package cpw.mods.fml.common;
    014
    015import java.io.File;
    016import java.io.FileReader;
    017import java.io.IOException;
    018import java.net.MalformedURLException;
    019import java.util.Comparator;
    020import java.util.HashSet;
    021import java.util.List;
    022import java.util.Map;
    023import java.util.Properties;
    024import java.util.Set;
    025import java.util.concurrent.Callable;
    026import java.util.logging.Level;
    027
    028import net.minecraft.crash.CallableMinecraftVersion;
    029
    030import com.google.common.base.CharMatcher;
    031import com.google.common.base.Function;
    032import com.google.common.base.Joiner;
    033import com.google.common.base.Splitter;
    034import com.google.common.collect.ArrayListMultimap;
    035import com.google.common.collect.BiMap;
    036import com.google.common.collect.HashBiMap;
    037import com.google.common.collect.ImmutableList;
    038import com.google.common.collect.ImmutableMap;
    039import com.google.common.collect.ImmutableMultiset;
    040import com.google.common.collect.Iterables;
    041import com.google.common.collect.LinkedHashMultimap;
    042import com.google.common.collect.Lists;
    043import com.google.common.collect.Maps;
    044import com.google.common.collect.SetMultimap;
    045import com.google.common.collect.Sets;
    046import com.google.common.collect.Multiset.Entry;
    047import com.google.common.collect.Multisets;
    048import com.google.common.collect.Ordering;
    049import com.google.common.collect.Sets.SetView;
    050import com.google.common.collect.TreeMultimap;
    051
    052import cpw.mods.fml.common.LoaderState.ModState;
    053import cpw.mods.fml.common.discovery.ModDiscoverer;
    054import cpw.mods.fml.common.event.FMLInterModComms;
    055import cpw.mods.fml.common.event.FMLLoadEvent;
    056import cpw.mods.fml.common.functions.ModIdFunction;
    057import cpw.mods.fml.common.modloader.BaseModProxy;
    058import cpw.mods.fml.common.registry.GameData;
    059import cpw.mods.fml.common.toposort.ModSorter;
    060import cpw.mods.fml.common.toposort.ModSortingException;
    061import cpw.mods.fml.common.toposort.TopologicalSort;
    062import cpw.mods.fml.common.versioning.ArtifactVersion;
    063import cpw.mods.fml.common.versioning.VersionParser;
    064import cpw.mods.fml.relauncher.FMLRelaunchLog;
    065
    066/**
    067 * The loader class performs the actual loading of the mod code from disk.
    068 *
    069 * <p>
    070 * There are several {@link LoaderState}s to mod loading, triggered in two
    071 * different stages from the FML handler code's hooks into the minecraft code.
    072 * </p>
    073 *
    074 * <ol>
    075 * <li>LOADING. Scanning the filesystem for mod containers to load (zips, jars,
    076 * directories), adding them to the {@link #modClassLoader} Scanning, the loaded
    077 * containers for mod classes to load and registering them appropriately.</li>
    078 * <li>PREINIT. The mod classes are configured, they are sorted into a load
    079 * order, and instances of the mods are constructed.</li>
    080 * <li>INIT. The mod instances are initialized. For BaseMod mods, this involves
    081 * calling the load method.</li>
    082 * <li>POSTINIT. The mod instances are post initialized. For BaseMod mods this
    083 * involves calling the modsLoaded method.</li>
    084 * <li>UP. The Loader is complete</li>
    085 * <li>ERRORED. The loader encountered an error during the LOADING phase and
    086 * dropped to this state instead. It will not complete loading from this state,
    087 * but it attempts to continue loading before abandoning and giving a fatal
    088 * error.</li>
    089 * </ol>
    090 *
    091 * Phase 1 code triggers the LOADING and PREINIT states. Phase 2 code triggers
    092 * the INIT and POSTINIT states.
    093 *
    094 * @author cpw
    095 *
    096 */
    097public class Loader
    098{
    099    private static final Splitter DEPENDENCYPARTSPLITTER = Splitter.on(":").omitEmptyStrings().trimResults();
    100    private static final Splitter DEPENDENCYSPLITTER = Splitter.on(";").omitEmptyStrings().trimResults();
    101    /**
    102     * The singleton instance
    103     */
    104    private static Loader instance;
    105    /**
    106     * Build information for tracking purposes.
    107     */
    108    private static String major;
    109    private static String minor;
    110    private static String rev;
    111    private static String build;
    112    private static String mccversion;
    113    private static String mcpversion;
    114
    115    /**
    116     * The class loader we load the mods into.
    117     */
    118    private ModClassLoader modClassLoader;
    119    /**
    120     * The sorted list of mods.
    121     */
    122    private List<ModContainer> mods;
    123    /**
    124     * A named list of mods
    125     */
    126    private Map<String, ModContainer> namedMods;
    127    /**
    128     * The canonical configuration directory
    129     */
    130    private File canonicalConfigDir;
    131    /**
    132     * The canonical minecraft directory
    133     */
    134    private File canonicalMinecraftDir;
    135    /**
    136     * The captured error
    137     */
    138    private Exception capturedError;
    139    private File canonicalModsDir;
    140    private LoadController modController;
    141    private MinecraftDummyContainer minecraft;
    142    private MCPDummyContainer mcp;
    143
    144    private static File minecraftDir;
    145    private static List<String> injectedContainers;
    146    private File loggingProperties;
    147
    148    public static Loader instance()
    149    {
    150        if (instance == null)
    151        {
    152            instance = new Loader();
    153        }
    154
    155        return instance;
    156    }
    157
    158    public static void injectData(Object... data)
    159    {
    160        major = (String) data[0];
    161        minor = (String) data[1];
    162        rev = (String) data[2];
    163        build = (String) data[3];
    164        mccversion = (String) data[4];
    165        mcpversion = (String) data[5];
    166        minecraftDir = (File) data[6];
    167        injectedContainers = (List<String>)data[7];
    168    }
    169
    170    private Loader()
    171    {
    172        modClassLoader = new ModClassLoader(getClass().getClassLoader());
    173        String actualMCVersion = new CallableMinecraftVersion(null).minecraftVersion();
    174        if (!mccversion.equals(actualMCVersion))
    175        {
    176            FMLLog.severe("This version of FML is built for Minecraft %s, we have detected Minecraft %s in your minecraft jar file", mccversion, actualMCVersion);
    177            throw new LoaderException();
    178        }
    179
    180        minecraft = new MinecraftDummyContainer(actualMCVersion);
    181        mcp = new MCPDummyContainer(MetadataCollection.from(getClass().getResourceAsStream("/mcpmod.info"), "MCP").getMetadataForId("mcp", null));
    182    }
    183
    184    /**
    185     * Sort the mods into a sorted list, using dependency information from the
    186     * containers. The sorting is performed using a {@link TopologicalSort}
    187     * based on the pre- and post- dependency information provided by the mods.
    188     */
    189    private void sortModList()
    190    {
    191        FMLLog.finer("Verifying mod requirements are satisfied");
    192        try
    193        {
    194            BiMap<String, ArtifactVersion> modVersions = HashBiMap.create();
    195            for (ModContainer mod : getActiveModList())
    196            {
    197                modVersions.put(mod.getModId(), mod.getProcessedVersion());
    198            }
    199
    200            for (ModContainer mod : getActiveModList())
    201            {
    202                if (!mod.acceptableMinecraftVersionRange().containsVersion(minecraft.getProcessedVersion()))
    203                {
    204                    FMLLog.severe("The mod %s does not wish to run in Minecraft version %s. You will have to remove it to play.", mod.getModId(), getMCVersionString());
    205                    throw new WrongMinecraftVersionException(mod);
    206                }
    207                Map<String,ArtifactVersion> names = Maps.uniqueIndex(mod.getRequirements(), new Function<ArtifactVersion, String>()
    208                {
    209                    public String apply(ArtifactVersion v)
    210                    {
    211                        return v.getLabel();
    212                    }
    213                });
    214                Set<ArtifactVersion> versionMissingMods = Sets.newHashSet();
    215                Set<String> missingMods = Sets.difference(names.keySet(), modVersions.keySet());
    216                if (!missingMods.isEmpty())
    217                {
    218                    FMLLog.severe("The mod %s (%s) requires mods %s to be available", mod.getModId(), mod.getName(), missingMods);
    219                    for (String modid : missingMods)
    220                    {
    221                        versionMissingMods.add(names.get(modid));
    222                    }
    223                    throw new MissingModsException(versionMissingMods);
    224                }
    225                ImmutableList<ArtifactVersion> allDeps = ImmutableList.<ArtifactVersion>builder().addAll(mod.getDependants()).addAll(mod.getDependencies()).build();
    226                for (ArtifactVersion v : allDeps)
    227                {
    228                    if (modVersions.containsKey(v.getLabel()))
    229                    {
    230                        if (!v.containsVersion(modVersions.get(v.getLabel())))
    231                        {
    232                            versionMissingMods.add(v);
    233                        }
    234                    }
    235                }
    236                if (!versionMissingMods.isEmpty())
    237                {
    238                    FMLLog.severe("The mod %s (%s) requires mod versions %s to be available", mod.getModId(), mod.getName(), versionMissingMods);
    239                    throw new MissingModsException(versionMissingMods);
    240                }
    241            }
    242
    243            FMLLog.finer("All mod requirements are satisfied");
    244
    245            ModSorter sorter = new ModSorter(getActiveModList(), namedMods);
    246
    247            try
    248            {
    249                FMLLog.finer("Sorting mods into an ordered list");
    250                List<ModContainer> sortedMods = sorter.sort();
    251                // Reset active list to the sorted list
    252                modController.getActiveModList().clear();
    253                modController.getActiveModList().addAll(sortedMods);
    254                // And inject the sorted list into the overall list
    255                mods.removeAll(sortedMods);
    256                sortedMods.addAll(mods);
    257                mods = sortedMods;
    258                FMLLog.finer("Mod sorting completed successfully");
    259            }
    260            catch (ModSortingException sortException)
    261            {
    262                FMLLog.severe("A dependency cycle was detected in the input mod set so an ordering cannot be determined");
    263                FMLLog.severe("The visited mod list is %s", sortException.getExceptionData().getVisitedNodes());
    264                FMLLog.severe("The first mod in the cycle is %s", sortException.getExceptionData().getFirstBadNode());
    265                FMLLog.log(Level.SEVERE, sortException, "The full error");
    266                throw new LoaderException(sortException);
    267            }
    268        }
    269        finally
    270        {
    271            FMLLog.fine("Mod sorting data");
    272            int unprintedMods = mods.size();
    273            for (ModContainer mod : getActiveModList())
    274            {
    275                if (!mod.isImmutable())
    276                {
    277                    FMLLog.fine("\t%s(%s:%s): %s (%s)", mod.getModId(), mod.getName(), mod.getVersion(), mod.getSource().getName(), mod.getSortingRules());
    278                    unprintedMods--;
    279                }
    280            }
    281            if (unprintedMods == mods.size())
    282            {
    283                FMLLog.fine("No user mods found to sort");
    284            }
    285        }
    286
    287    }
    288
    289    /**
    290     * The primary loading code
    291     *
    292     * This is visited during first initialization by Minecraft to scan and load
    293     * the mods from all sources 1. The minecraft jar itself (for loading of in
    294     * jar mods- I would like to remove this if possible but forge depends on it
    295     * at present) 2. The mods directory with expanded subdirs, searching for
    296     * mods named mod_*.class 3. The mods directory for zip and jar files,
    297     * searching for mod classes named mod_*.class again
    298     *
    299     * The found resources are first loaded into the {@link #modClassLoader}
    300     * (always) then scanned for class resources matching the specification
    301     * above.
    302     *
    303     * If they provide the {@link Mod} annotation, they will be loaded as
    304     * "FML mods", which currently is effectively a NO-OP. If they are
    305     * determined to be {@link BaseModProxy} subclasses they are loaded as such.
    306     *
    307     * Finally, if they are successfully loaded as classes, they are then added
    308     * to the available mod list.
    309     */
    310    private ModDiscoverer identifyMods()
    311    {
    312        FMLLog.fine("Building injected Mod Containers %s", injectedContainers);
    313        // Add in the MCP mod container
    314        mods.add(new InjectedModContainer(mcp,new File("minecraft.jar")));
    315        File coremod = new File(minecraftDir,"coremods");
    316        for (String cont : injectedContainers)
    317        {
    318            ModContainer mc;
    319            try
    320            {
    321                mc = (ModContainer) Class.forName(cont,true,modClassLoader).newInstance();
    322            }
    323            catch (Exception e)
    324            {
    325                FMLLog.log(Level.SEVERE, e, "A problem occured instantiating the injected mod container %s", cont);
    326                throw new LoaderException(e);
    327            }
    328            mods.add(new InjectedModContainer(mc,coremod));
    329        }
    330        ModDiscoverer discoverer = new ModDiscoverer();
    331        FMLLog.fine("Attempting to load mods contained in the minecraft jar file and associated classes");
    332        discoverer.findClasspathMods(modClassLoader);
    333        FMLLog.fine("Minecraft jar mods loaded successfully");
    334
    335        FMLLog.info("Searching %s for mods", canonicalModsDir.getAbsolutePath());
    336        discoverer.findModDirMods(canonicalModsDir);
    337
    338        mods.addAll(discoverer.identifyMods());
    339        identifyDuplicates(mods);
    340        namedMods = Maps.uniqueIndex(mods, new ModIdFunction());
    341        FMLLog.info("Forge Mod Loader has identified %d mod%s to load", mods.size(), mods.size() != 1 ? "s" : "");
    342        for (String modId: namedMods.keySet())
    343        {
    344            FMLLog.makeLog(modId);
    345        }
    346        return discoverer;
    347    }
    348
    349    private class ModIdComparator implements Comparator<ModContainer>
    350    {
    351        @Override
    352        public int compare(ModContainer o1, ModContainer o2)
    353        {
    354            return o1.getModId().compareTo(o2.getModId());
    355        }
    356
    357    }
    358
    359    private void identifyDuplicates(List<ModContainer> mods)
    360    {
    361        TreeMultimap<ModContainer, File> dupsearch = TreeMultimap.create(new ModIdComparator(), Ordering.arbitrary());
    362        for (ModContainer mc : mods)
    363        {
    364            if (mc.getSource() != null)
    365            {
    366                dupsearch.put(mc, mc.getSource());
    367            }
    368        }
    369
    370        ImmutableMultiset<ModContainer> duplist = Multisets.copyHighestCountFirst(dupsearch.keys());
    371        SetMultimap<ModContainer, File> dupes = LinkedHashMultimap.create();
    372        for (Entry<ModContainer> e : duplist.entrySet())
    373        {
    374            if (e.getCount() > 1)
    375            {
    376                FMLLog.severe("Found a duplicate mod %s at %s", e.getElement().getModId(), dupsearch.get(e.getElement()));
    377                dupes.putAll(e.getElement(),dupsearch.get(e.getElement()));
    378            }
    379        }
    380        if (!dupes.isEmpty())
    381        {
    382            throw new DuplicateModsFoundException(dupes);
    383        }
    384    }
    385
    386    /**
    387     *
    388     */
    389    private void initializeLoader()
    390    {
    391        File modsDir = new File(minecraftDir, "mods");
    392        File configDir = new File(minecraftDir, "config");
    393        String canonicalModsPath;
    394        String canonicalConfigPath;
    395
    396        try
    397        {
    398            canonicalMinecraftDir = minecraftDir.getCanonicalFile();
    399            canonicalModsPath = modsDir.getCanonicalPath();
    400            canonicalConfigPath = configDir.getCanonicalPath();
    401            canonicalConfigDir = configDir.getCanonicalFile();
    402            canonicalModsDir = modsDir.getCanonicalFile();
    403        }
    404        catch (IOException ioe)
    405        {
    406            FMLLog.log(Level.SEVERE, ioe, "Failed to resolve loader directories: mods : %s ; config %s", canonicalModsDir.getAbsolutePath(),
    407                            configDir.getAbsolutePath());
    408            throw new LoaderException(ioe);
    409        }
    410
    411        if (!canonicalModsDir.exists())
    412        {
    413            FMLLog.info("No mod directory found, creating one: %s", canonicalModsPath);
    414            boolean dirMade = canonicalModsDir.mkdir();
    415            if (!dirMade)
    416            {
    417                FMLLog.severe("Unable to create the mod directory %s", canonicalModsPath);
    418                throw new LoaderException();
    419            }
    420            FMLLog.info("Mod directory created successfully");
    421        }
    422
    423        if (!canonicalConfigDir.exists())
    424        {
    425            FMLLog.fine("No config directory found, creating one: %s", canonicalConfigPath);
    426            boolean dirMade = canonicalConfigDir.mkdir();
    427            if (!dirMade)
    428            {
    429                FMLLog.severe("Unable to create the config directory %s", canonicalConfigPath);
    430                throw new LoaderException();
    431            }
    432            FMLLog.info("Config directory created successfully");
    433        }
    434
    435        if (!canonicalModsDir.isDirectory())
    436        {
    437            FMLLog.severe("Attempting to load mods from %s, which is not a directory", canonicalModsPath);
    438            throw new LoaderException();
    439        }
    440
    441        if (!configDir.isDirectory())
    442        {
    443            FMLLog.severe("Attempting to load configuration from %s, which is not a directory", canonicalConfigPath);
    444            throw new LoaderException();
    445        }
    446
    447        loggingProperties = new File(canonicalConfigDir, "logging.properties");
    448        FMLLog.info("Reading custom logging properties from %s", loggingProperties.getPath());
    449        FMLRelaunchLog.loadLogConfiguration(loggingProperties);
    450        FMLLog.log(Level.OFF,"Logging level for ForgeModLoader logging is set to %s", FMLRelaunchLog.log.getLogger().getLevel());
    451    }
    452
    453    public List<ModContainer> getModList()
    454    {
    455        return instance().mods != null ? ImmutableList.copyOf(instance().mods) : ImmutableList.<ModContainer>of();
    456    }
    457
    458    /**
    459     * Called from the hook to start mod loading. We trigger the
    460     * {@link #identifyMods()} and Constructing, Preinitalization, and Initalization phases here. Finally,
    461     * the mod list is frozen completely and is consider immutable from then on.
    462     */
    463    public void loadMods()
    464    {
    465        initializeLoader();
    466        mods = Lists.newArrayList();
    467        namedMods = Maps.newHashMap();
    468        modController = new LoadController(this);
    469        modController.transition(LoaderState.LOADING);
    470        ModDiscoverer disc = identifyMods();
    471        disableRequestedMods();
    472        FMLLog.fine("Reloading logging properties from %s", loggingProperties.getPath());
    473        FMLRelaunchLog.loadLogConfiguration(loggingProperties);
    474        FMLLog.fine("Reloaded logging properties");
    475        modController.distributeStateMessage(FMLLoadEvent.class);
    476        sortModList();
    477        mods = ImmutableList.copyOf(mods);
    478        for (File nonMod : disc.getNonModLibs())
    479        {
    480            if (nonMod.isFile())
    481            {
    482                FMLLog.info("FML has found a non-mod file %s in your mods directory. It will now be injected into your classpath. This could severe stability issues, it should be removed if possible.", nonMod.getName());
    483                try
    484                {
    485                    modClassLoader.addFile(nonMod);
    486                }
    487                catch (MalformedURLException e)
    488                {
    489                    FMLLog.log(Level.SEVERE, e, "Encountered a weird problem with non-mod file injection : %s", nonMod.getName());
    490                }
    491            }
    492        }
    493        modController.transition(LoaderState.CONSTRUCTING);
    494        modController.distributeStateMessage(LoaderState.CONSTRUCTING, modClassLoader, disc.getASMTable());
    495        FMLLog.fine("Mod signature data");
    496        for (ModContainer mod : getActiveModList())
    497        {
    498            FMLLog.fine("\t%s(%s:%s): %s (%s)", mod.getModId(), mod.getName(), mod.getVersion(), mod.getSource().getName(), CertificateHelper.getFingerprint(mod.getSigningCertificate()));
    499        }
    500        if (getActiveModList().isEmpty())
    501        {
    502            FMLLog.fine("No user mod signature data found");
    503        }
    504        modController.transition(LoaderState.PREINITIALIZATION);
    505        modController.distributeStateMessage(LoaderState.PREINITIALIZATION, disc.getASMTable(), canonicalConfigDir);
    506        modController.transition(LoaderState.INITIALIZATION);
    507    }
    508
    509    private void disableRequestedMods()
    510    {
    511        String forcedModList = System.getProperty("fml.modStates", "");
    512        FMLLog.finer("Received a system property request \'%s\'",forcedModList);
    513        Map<String, String> sysPropertyStateList = Splitter.on(CharMatcher.anyOf(";:"))
    514                .omitEmptyStrings().trimResults().withKeyValueSeparator("=")
    515                .split(forcedModList);
    516        FMLLog.finer("System property request managing the state of %d mods", sysPropertyStateList.size());
    517        Map<String, String> modStates = Maps.newHashMap();
    518
    519        File forcedModFile = new File(canonicalConfigDir, "fmlModState.properties");
    520        Properties forcedModListProperties = new Properties();
    521        if (forcedModFile.exists() && forcedModFile.isFile())
    522        {
    523            FMLLog.finer("Found a mod state file %s", forcedModFile.getName());
    524            try
    525            {
    526                forcedModListProperties.load(new FileReader(forcedModFile));
    527                FMLLog.finer("Loaded states for %d mods from file", forcedModListProperties.size());
    528            }
    529            catch (Exception e)
    530            {
    531                FMLLog.log(Level.INFO, e, "An error occurred reading the fmlModState.properties file");
    532            }
    533        }
    534        modStates.putAll(Maps.fromProperties(forcedModListProperties));
    535        modStates.putAll(sysPropertyStateList);
    536        FMLLog.fine("After merging, found state information for %d mods", modStates.size());
    537
    538        Map<String, Boolean> isEnabled = Maps.transformValues(modStates, new Function<String, Boolean>()
    539        {
    540            public Boolean apply(String input)
    541            {
    542                return Boolean.parseBoolean(input);
    543            }
    544        });
    545
    546        for (Map.Entry<String, Boolean> entry : isEnabled.entrySet())
    547        {
    548            if (namedMods.containsKey(entry.getKey()))
    549            {
    550                FMLLog.info("Setting mod %s to enabled state %b", entry.getKey(), entry.getValue());
    551                namedMods.get(entry.getKey()).setEnabledState(entry.getValue());
    552            }
    553        }
    554    }
    555
    556    /**
    557     * Query if we know of a mod named modname
    558     *
    559     * @param modname
    560     * @return If the mod is loaded
    561     */
    562    public static boolean isModLoaded(String modname)
    563    {
    564        return instance().namedMods.containsKey(modname) && instance().modController.getModState(instance.namedMods.get(modname))!=ModState.DISABLED;
    565    }
    566
    567    public File getConfigDir()
    568    {
    569        return canonicalConfigDir;
    570    }
    571
    572    public String getCrashInformation()
    573    {
    574        // Handle being called before we've begun setup
    575        if (modController == null)
    576        {
    577            return "";
    578        }
    579        StringBuilder ret = new StringBuilder();
    580        List<String> branding = FMLCommonHandler.instance().getBrandings();
    581
    582        Joiner.on(' ').skipNulls().appendTo(ret, branding.subList(1, branding.size()));
    583        if (modController!=null)
    584        {
    585            modController.printModStates(ret);
    586        }
    587        return ret.toString();
    588    }
    589
    590    public String getFMLVersionString()
    591    {
    592        return String.format("%s.%s.%s.%s", major, minor, rev, build);
    593    }
    594
    595    public ClassLoader getModClassLoader()
    596    {
    597        return modClassLoader;
    598    }
    599
    600    public void computeDependencies(String dependencyString, Set<ArtifactVersion> requirements, List<ArtifactVersion> dependencies, List<ArtifactVersion> dependants)
    601    {
    602        if (dependencyString == null || dependencyString.length() == 0)
    603        {
    604            return;
    605        }
    606
    607        boolean parseFailure=false;
    608
    609        for (String dep : DEPENDENCYSPLITTER.split(dependencyString))
    610        {
    611            List<String> depparts = Lists.newArrayList(DEPENDENCYPARTSPLITTER.split(dep));
    612            // Need two parts to the string
    613            if (depparts.size() != 2)
    614            {
    615                parseFailure=true;
    616                continue;
    617            }
    618            String instruction = depparts.get(0);
    619            String target = depparts.get(1);
    620            boolean targetIsAll = target.startsWith("*");
    621
    622            // Cannot have an "all" relationship with anything except pure *
    623            if (targetIsAll && target.length()>1)
    624            {
    625                parseFailure = true;
    626                continue;
    627            }
    628
    629            // If this is a required element, add it to the required list
    630            if ("required-before".equals(instruction) || "required-after".equals(instruction))
    631            {
    632                // You can't require everything
    633                if (!targetIsAll)
    634                {
    635                    requirements.add(VersionParser.parseVersionReference(target));
    636                }
    637                else
    638                {
    639                    parseFailure=true;
    640                    continue;
    641                }
    642            }
    643
    644            // You cannot have a versioned dependency on everything
    645            if (targetIsAll && target.indexOf('@')>-1)
    646            {
    647                parseFailure = true;
    648                continue;
    649            }
    650            // before elements are things we are loaded before (so they are our dependants)
    651            if ("required-before".equals(instruction) || "before".equals(instruction))
    652            {
    653                dependants.add(VersionParser.parseVersionReference(target));
    654            }
    655            // after elements are things that load before we do (so they are out dependencies)
    656            else if ("required-after".equals(instruction) || "after".equals(instruction))
    657            {
    658                dependencies.add(VersionParser.parseVersionReference(target));
    659            }
    660            else
    661            {
    662                parseFailure=true;
    663            }
    664        }
    665
    666        if (parseFailure)
    667        {
    668            FMLLog.log(Level.WARNING, "Unable to parse dependency string %s", dependencyString);
    669            throw new LoaderException();
    670        }
    671    }
    672
    673    public Map<String,ModContainer> getIndexedModList()
    674    {
    675        return ImmutableMap.copyOf(namedMods);
    676    }
    677
    678    public void initializeMods()
    679    {
    680        // Mod controller should be in the initialization state here
    681        modController.distributeStateMessage(LoaderState.INITIALIZATION);
    682        modController.transition(LoaderState.POSTINITIALIZATION);
    683        // Construct the "mod object table" so mods can refer to it in IMC and postinit
    684        GameData.buildModObjectTable();
    685        modController.distributeStateMessage(FMLInterModComms.IMCEvent.class);
    686        modController.distributeStateMessage(LoaderState.POSTINITIALIZATION);
    687        modController.transition(LoaderState.AVAILABLE);
    688        modController.distributeStateMessage(LoaderState.AVAILABLE);
    689        FMLLog.info("Forge Mod Loader has successfully loaded %d mod%s", mods.size(), mods.size()==1 ? "" : "s");
    690    }
    691
    692    public ICrashCallable getCallableCrashInformation()
    693    {
    694        return new ICrashCallable() {
    695            @Override
    696            public String call() throws Exception
    697            {
    698                return getCrashInformation();
    699            }
    700
    701            @Override
    702            public String getLabel()
    703            {
    704                return "FML";
    705            }
    706        };
    707    }
    708
    709    public List<ModContainer> getActiveModList()
    710    {
    711        return modController != null ? modController.getActiveModList() : ImmutableList.<ModContainer>of();
    712    }
    713
    714    public ModState getModState(ModContainer selectedMod)
    715    {
    716        return modController.getModState(selectedMod);
    717    }
    718
    719    public String getMCVersionString()
    720    {
    721        return "Minecraft " + mccversion;
    722    }
    723
    724    public boolean serverStarting(Object server)
    725    {
    726        try
    727        {
    728            modController.distributeStateMessage(LoaderState.SERVER_STARTING, server);
    729            modController.transition(LoaderState.SERVER_STARTING);
    730        }
    731        catch (Throwable t)
    732        {
    733            FMLLog.log(Level.SEVERE, t, "A fatal exception occurred during the server starting event");
    734            return false;
    735        }
    736        return true;
    737    }
    738
    739    public void serverStarted()
    740    {
    741        modController.distributeStateMessage(LoaderState.SERVER_STARTED);
    742        modController.transition(LoaderState.SERVER_STARTED);
    743    }
    744
    745    public void serverStopping()
    746    {
    747        modController.distributeStateMessage(LoaderState.SERVER_STOPPING);
    748        modController.transition(LoaderState.SERVER_STOPPING);
    749    }
    750
    751    public BiMap<ModContainer, Object> getModObjectList()
    752    {
    753        return modController.getModObjectList();
    754    }
    755
    756    public BiMap<Object, ModContainer> getReversedModObjectList()
    757    {
    758        return getModObjectList().inverse();
    759    }
    760
    761    public ModContainer activeModContainer()
    762    {
    763        return modController != null ? modController.activeContainer() : null;
    764    }
    765
    766    public boolean isInState(LoaderState state)
    767    {
    768        return modController.isInState(state);
    769    }
    770
    771    public MinecraftDummyContainer getMinecraftModContainer()
    772    {
    773        return minecraft;
    774    }
    775
    776    public boolean hasReachedState(LoaderState state) {
    777        return modController != null ? modController.hasReachedState(state) : false;
    778    }
    779
    780    public String getMCPVersionString() {
    781        return String.format("MCP v%s", mcpversion);
    782    }
    783
    784    public void serverStopped()
    785    {
    786        modController.distributeStateMessage(LoaderState.SERVER_STOPPED);
    787        try
    788        {
    789            modController.transition(LoaderState.SERVER_STOPPED);
    790        }
    791        catch (LoaderException e)
    792        {
    793            modController.forceState(LoaderState.SERVER_STOPPED);
    794            // Discard any exceptions here - they mask other, real, exceptions
    795        }
    796        modController.transition(LoaderState.AVAILABLE);
    797    }
    798
    799    public boolean serverAboutToStart(Object server)
    800    {
    801        try
    802        {
    803            modController.distributeStateMessage(LoaderState.SERVER_ABOUT_TO_START, server);
    804            modController.transition(LoaderState.SERVER_ABOUT_TO_START);
    805        }
    806        catch (Throwable t)
    807        {
    808            FMLLog.log(Level.SEVERE, t, "A fatal exception occurred during the server about to start event");
    809            return false;
    810        }
    811        return true;
    812    }
    813}