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