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