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