2012-03-30 10:11:13 -04:00
/*
2013-03-07 19:25:48 -05:00
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
2013-03-07 19:37:52 -05:00
*
2013-03-07 19:25:48 -05:00
* Contributors:
* cpw - implementation
2012-03-30 10:11:13 -04:00
*/
2013-03-07 19:25:48 -05:00
2012-04-02 18:09:06 -04:00
package cpw.mods.fml.common ;
2012-03-28 16:44:36 -04:00
import java.io.File ;
2012-07-22 11:56:38 -02:30
import java.io.FileReader ;
2012-03-28 16:44:36 -04:00
import java.io.IOException ;
2012-08-30 20:10:13 -04:00
import java.net.MalformedURLException ;
2012-08-14 13:36:29 -04:00
import java.util.Comparator ;
2012-03-28 16:44:36 -04:00
import java.util.List ;
2012-03-30 01:12:59 -04:00
import java.util.Map ;
2012-05-10 09:42:31 -04:00
import java.util.Properties ;
2012-08-23 13:43:25 -04:00
import java.util.Set ;
2012-03-30 01:12:59 -04:00
import java.util.logging.Level ;
2012-03-28 16:44:36 -04:00
2012-12-08 20:21:03 -08:00
import net.minecraft.crash.CallableMinecraftVersion ;
2012-08-02 02:08:30 -02:30
2012-07-22 11:56:38 -02:30
import com.google.common.base.CharMatcher ;
import com.google.common.base.Function ;
import com.google.common.base.Joiner ;
import com.google.common.base.Splitter ;
2012-08-11 02:43:04 -04:00
import com.google.common.collect.BiMap ;
2012-08-23 13:43:25 -04:00
import com.google.common.collect.HashBiMap ;
2012-07-22 11:56:38 -02:30
import com.google.common.collect.ImmutableList ;
import com.google.common.collect.ImmutableMap ;
2012-08-14 13:36:29 -04:00
import com.google.common.collect.ImmutableMultiset ;
2012-10-24 09:41:46 -04:00
import com.google.common.collect.LinkedHashMultimap ;
2012-07-22 11:56:38 -02:30
import com.google.common.collect.Lists ;
import com.google.common.collect.Maps ;
2012-08-14 13:36:29 -04:00
import com.google.common.collect.Multiset.Entry ;
import com.google.common.collect.Multisets ;
import com.google.common.collect.Ordering ;
2013-11-10 11:49:06 -05:00
import com.google.common.collect.SetMultimap ;
import com.google.common.collect.Sets ;
2012-08-14 13:36:29 -04:00
import com.google.common.collect.TreeMultimap ;
2012-07-22 11:56:38 -02:30
2012-07-23 22:50:37 -02:30
import cpw.mods.fml.common.LoaderState.ModState ;
2012-07-22 11:56:38 -02:30
import cpw.mods.fml.common.discovery.ModDiscoverer ;
2012-10-24 16:39:55 -04:00
import cpw.mods.fml.common.event.FMLInterModComms ;
2012-08-04 11:26:51 -04:00
import cpw.mods.fml.common.event.FMLLoadEvent ;
2013-06-04 12:52:30 -04:00
import cpw.mods.fml.common.functions.ArtifactVersionNameFunction ;
2012-07-22 11:56:38 -02:30
import cpw.mods.fml.common.functions.ModIdFunction ;
2013-03-08 13:15:09 -05:00
import cpw.mods.fml.common.registry.GameData ;
2012-04-03 12:03:21 -04:00
import cpw.mods.fml.common.toposort.ModSorter ;
2012-07-14 13:58:46 -04:00
import cpw.mods.fml.common.toposort.ModSortingException ;
2013-05-27 11:03:23 -04:00
import cpw.mods.fml.common.toposort.ModSortingException.SortingExceptionData ;
2012-04-05 16:22:47 -04:00
import cpw.mods.fml.common.toposort.TopologicalSort ;
2012-07-30 17:01:27 -04:00
import cpw.mods.fml.common.versioning.ArtifactVersion ;
import cpw.mods.fml.common.versioning.VersionParser ;
2013-01-30 18:57:21 -05:00
import cpw.mods.fml.relauncher.FMLRelaunchLog ;
2012-04-05 16:22:47 -04:00
/**
* The loader class performs the actual loading of the mod code from disk.
2012-07-29 22:54:59 -04:00
*
2012-07-22 11:56:38 -02:30
* <p>
* There are several {@link LoaderState}s to mod loading, triggered in two
* different stages from the FML handler code's hooks into the minecraft code.
* </p>
2012-07-29 22:54:59 -04:00
*
2012-04-05 16:22:47 -04:00
* <ol>
2012-07-22 11:56:38 -02:30
* <li>LOADING. Scanning the filesystem for mod containers to load (zips, jars,
* directories), adding them to the {@link #modClassLoader} Scanning, the loaded
* containers for mod classes to load and registering them appropriately.</li>
* <li>PREINIT. The mod classes are configured, they are sorted into a load
* order, and instances of the mods are constructed.</li>
* <li>INIT. The mod instances are initialized. For BaseMod mods, this involves
* calling the load method.</li>
* <li>POSTINIT. The mod instances are post initialized. For BaseMod mods this
* involves calling the modsLoaded method.</li>
2012-04-05 16:22:47 -04:00
* <li>UP. The Loader is complete</li>
2012-07-22 11:56:38 -02:30
* <li>ERRORED. The loader encountered an error during the LOADING phase and
* dropped to this state instead. It will not complete loading from this state,
* but it attempts to continue loading before abandoning and giving a fatal
* error.</li>
2012-04-05 16:22:47 -04:00
* </ol>
2012-07-29 22:54:59 -04:00
*
2012-07-22 11:56:38 -02:30
* Phase 1 code triggers the LOADING and PREINIT states. Phase 2 code triggers
* the INIT and POSTINIT states.
2012-07-29 22:54:59 -04:00
*
2012-04-05 16:22:47 -04:00
* @author cpw
2012-07-29 22:54:59 -04:00
*
2012-04-05 16:22:47 -04:00
*/
2012-04-05 10:07:52 -04:00
public class Loader
{
2012-07-22 11:56:38 -02:30
private static final Splitter DEPENDENCYPARTSPLITTER = Splitter . on ( ":" ). omitEmptyStrings (). trimResults ();
private static final Splitter DEPENDENCYSPLITTER = Splitter . on ( ";" ). omitEmptyStrings (). trimResults ();
2012-04-05 16:22:47 -04:00
/**
* The singleton instance
*/
2012-04-05 10:07:52 -04:00
private static Loader instance ;
2012-04-05 16:22:47 -04:00
/**
* Build information for tracking purposes.
*/
2012-05-10 09:42:31 -04:00
private static String major ;
private static String minor ;
private static String rev ;
private static String build ;
2012-07-02 12:24:37 -04:00
private static String mccversion ;
2012-10-25 09:02:49 -04:00
private static String mcpversion ;
2012-04-05 10:07:52 -04:00
2012-04-05 16:22:47 -04:00
/**
* The class loader we load the mods into.
*/
2012-04-05 10:07:52 -04:00
private ModClassLoader modClassLoader ;
2012-04-05 16:22:47 -04:00
/**
* The sorted list of mods.
*/
2012-04-05 10:07:52 -04:00
private List < ModContainer > mods ;
2012-04-05 16:22:47 -04:00
/**
* A named list of mods
*/
2012-04-05 10:07:52 -04:00
private Map < String , ModContainer > namedMods ;
2012-04-05 16:22:47 -04:00
/**
* The canonical configuration directory
*/
2012-04-05 10:07:52 -04:00
private File canonicalConfigDir ;
2012-07-22 11:56:38 -02:30
private File canonicalModsDir ;
private LoadController modController ;
2012-10-02 01:29:46 -04:00
private MinecraftDummyContainer minecraft ;
2013-08-28 10:18:28 -07:00
private MCPDummyContainer mcp ;
2012-06-26 16:24:50 -04:00
2012-07-30 22:31:07 -04:00
private static File minecraftDir ;
2012-08-04 11:26:51 -04:00
private static List < String > injectedContainers ;
2013-01-30 18:57:21 -05:00
private File loggingProperties ;
2013-03-30 12:12:54 -04:00
private ImmutableMap < String , String > fmlBrandingProperties ;
2012-07-30 22:31:07 -04:00
2012-04-05 10:07:52 -04:00
public static Loader instance ()
{
if ( instance == null )
{
instance = new Loader ();
}
return instance ;
2012-04-03 12:03:21 -04:00
}
2012-04-05 16:22:47 -04:00
2013-11-10 11:49:06 -05:00
@SuppressWarnings ( "unchecked" )
2012-07-30 22:31:07 -04:00
public static void injectData ( Object ... data )
2012-07-14 13:58:46 -04:00
{
2012-07-30 22:31:07 -04:00
major = ( String ) data [ 0 ] ;
minor = ( String ) data [ 1 ] ;
rev = ( String ) data [ 2 ] ;
build = ( String ) data [ 3 ] ;
mccversion = ( String ) data [ 4 ] ;
2012-10-25 09:02:49 -04:00
mcpversion = ( String ) data [ 5 ] ;
2012-07-30 22:31:07 -04:00
minecraftDir = ( File ) data [ 6 ] ;
2012-08-04 11:26:51 -04:00
injectedContainers = ( List < String > ) data [ 7 ] ;
2012-07-30 22:31:07 -04:00
}
2012-07-29 22:54:59 -04:00
2012-07-30 22:31:07 -04:00
private Loader ()
{
2012-07-14 13:58:46 -04:00
modClassLoader = new ModClassLoader ( getClass (). getClassLoader ());
2012-08-13 00:17:34 -04:00
String actualMCVersion = new CallableMinecraftVersion ( null ). func_71493_a ();
if ( ! mccversion . equals ( actualMCVersion ))
{
FMLLog . severe ( "This version of FML is built for Minecraft %s, we have detected Minecraft %s in your minecraft jar file" , mccversion , actualMCVersion );
throw new LoaderException ();
}
2012-10-02 01:29:46 -04:00
minecraft = new MinecraftDummyContainer ( actualMCVersion );
2012-10-25 09:02:49 -04:00
mcp = new MCPDummyContainer ( MetadataCollection . from ( getClass (). getResourceAsStream ( "/mcpmod.info" ), "MCP" ). getMetadataForId ( "mcp" , null ));
2012-07-14 13:58:46 -04:00
}
2012-04-05 16:22:47 -04:00
/**
2012-07-22 11:56:38 -02:30
* Sort the mods into a sorted list, using dependency information from the
* containers. The sorting is performed using a {@link TopologicalSort}
* based on the pre- and post- dependency information provided by the mods.
2012-04-05 16:22:47 -04:00
*/
2012-04-05 10:07:52 -04:00
private void sortModList ()
{
2013-01-30 18:57:21 -05:00
FMLLog . finer ( "Verifying mod requirements are satisfied" );
2012-07-14 13:58:46 -04:00
try
2012-04-05 10:07:52 -04:00
{
2012-08-23 13:43:25 -04:00
BiMap < String , ArtifactVersion > modVersions = HashBiMap . create ();
2012-08-23 14:33:54 -04:00
for ( ModContainer mod : getActiveModList ())
2012-04-05 10:07:52 -04:00
{
2012-07-30 17:01:27 -04:00
modVersions . put ( mod . getModId (), mod . getProcessedVersion ());
}
2012-08-23 14:33:54 -04:00
for ( ModContainer mod : getActiveModList ())
2012-07-30 17:01:27 -04:00
{
2012-10-02 01:29:46 -04:00
if ( ! mod . acceptableMinecraftVersionRange (). containsVersion ( minecraft . getProcessedVersion ()))
{
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 ());
throw new WrongMinecraftVersionException ( mod );
}
2013-06-04 12:52:30 -04:00
Map < String , ArtifactVersion > names = Maps . uniqueIndex ( mod . getRequirements (), new ArtifactVersionNameFunction ());
2012-08-23 15:29:17 -04:00
Set < ArtifactVersion > versionMissingMods = Sets . newHashSet ();
2012-10-02 01:29:46 -04:00
Set < String > missingMods = Sets . difference ( names . keySet (), modVersions . keySet ());
2012-08-23 13:43:25 -04:00
if ( ! missingMods . isEmpty ())
{
FMLLog . severe ( "The mod %s (%s) requires mods %s to be available" , mod . getModId (), mod . getName (), missingMods );
2012-08-23 15:29:17 -04:00
for ( String modid : missingMods )
{
versionMissingMods . add ( names . get ( modid ));
}
throw new MissingModsException ( versionMissingMods );
2012-08-23 13:43:25 -04:00
}
2012-07-30 17:01:27 -04:00
ImmutableList < ArtifactVersion > allDeps = ImmutableList . < ArtifactVersion > builder (). addAll ( mod . getDependants ()). addAll ( mod . getDependencies ()). build ();
for ( ArtifactVersion v : allDeps )
2012-07-14 13:58:46 -04:00
{
2012-07-30 17:01:27 -04:00
if ( modVersions . containsKey ( v . getLabel ()))
{
if ( ! v . containsVersion ( modVersions . get ( v . getLabel ())))
{
2012-08-23 15:29:17 -04:00
versionMissingMods . add ( v );
2012-07-30 17:01:27 -04:00
}
}
2012-07-14 13:58:46 -04:00
}
2012-08-23 15:29:17 -04:00
if ( ! versionMissingMods . isEmpty ())
2012-08-23 13:43:25 -04:00
{
2012-09-07 19:08:28 -04:00
FMLLog . severe ( "The mod %s (%s) requires mod versions %s to be available" , mod . getModId (), mod . getName (), versionMissingMods );
2012-08-23 15:29:17 -04:00
throw new MissingModsException ( versionMissingMods );
2012-08-23 13:43:25 -04:00
}
2012-04-05 10:07:52 -04:00
}
2013-01-30 18:57:21 -05:00
FMLLog . finer ( "All mod requirements are satisfied" );
2012-07-29 22:54:59 -04:00
2012-08-23 14:33:54 -04:00
ModSorter sorter = new ModSorter ( getActiveModList (), namedMods );
2012-04-05 10:07:52 -04:00
2012-07-14 13:58:46 -04:00
try
{
2013-01-30 18:57:21 -05:00
FMLLog . finer ( "Sorting mods into an ordered list" );
2012-08-23 15:29:17 -04:00
List < ModContainer > sortedMods = sorter . sort ();
2012-08-24 11:58:18 -04:00
// Reset active list to the sorted list
modController . getActiveModList (). clear ();
modController . getActiveModList (). addAll ( sortedMods );
// And inject the sorted list into the overall list
2012-08-23 15:29:17 -04:00
mods . removeAll ( sortedMods );
sortedMods . addAll ( mods );
mods = sortedMods ;
2013-01-30 18:57:21 -05:00
FMLLog . finer ( "Mod sorting completed successfully" );
2012-07-14 13:58:46 -04:00
}
catch ( ModSortingException sortException )
{
2012-07-29 22:54:59 -04:00
FMLLog . severe ( "A dependency cycle was detected in the input mod set so an ordering cannot be determined" );
2013-05-27 11:03:23 -04:00
SortingExceptionData < ModContainer > exceptionData = sortException . getExceptionData ();
FMLLog . severe ( "The first mod in the cycle is %s" , exceptionData . getFirstBadNode ());
FMLLog . severe ( "The mod cycle involves" );
for ( ModContainer mc : exceptionData . getVisitedNodes ())
{
FMLLog . severe ( "%s : before: %s, after: %s" , mc . toString (), mc . getDependants (), mc . getDependencies ());
}
2012-07-29 22:54:59 -04:00
FMLLog . log ( Level . SEVERE , sortException , "The full error" );
2013-05-27 10:46:22 -04:00
throw sortException ;
2012-07-14 13:58:46 -04:00
}
}
finally
2012-04-05 10:07:52 -04:00
{
2013-01-30 18:57:21 -05:00
FMLLog . fine ( "Mod sorting data" );
int unprintedMods = mods . size ();
2012-08-23 14:33:54 -04:00
for ( ModContainer mod : getActiveModList ())
2012-05-10 01:00:07 -07:00
{
2012-08-04 11:26:51 -04:00
if ( ! mod . isImmutable ())
{
2012-08-23 14:28:49 -04:00
FMLLog . fine ( "\t%s(%s:%s): %s (%s)" , mod . getModId (), mod . getName (), mod . getVersion (), mod . getSource (). getName (), mod . getSortingRules ());
2013-01-30 18:57:21 -05:00
unprintedMods -- ;
2012-08-04 11:26:51 -04:00
}
2012-07-23 16:33:17 -02:30
}
2013-01-30 18:57:21 -05:00
if ( unprintedMods == mods . size ())
2012-07-29 22:54:59 -04:00
{
2013-01-30 18:57:21 -05:00
FMLLog . fine ( "No user mods found to sort" );
2012-05-10 01:00:07 -07:00
}
2012-04-05 10:07:52 -04:00
}
2012-07-14 13:58:46 -04:00
2012-03-28 16:44:36 -04:00
}
2012-04-05 10:07:52 -04:00
2012-04-05 16:22:47 -04:00
/**
2012-07-22 11:56:38 -02:30
* The primary loading code
2012-07-29 22:54:59 -04:00
*
2012-07-22 11:56:38 -02:30
* This is visited during first initialization by Minecraft to scan and load
* the mods from all sources 1. The minecraft jar itself (for loading of in
* jar mods- I would like to remove this if possible but forge depends on it
* at present) 2. The mods directory with expanded subdirs, searching for
* mods named mod_*.class 3. The mods directory for zip and jar files,
* searching for mod classes named mod_*.class again
2012-07-29 22:54:59 -04:00
*
2012-07-22 11:56:38 -02:30
* The found resources are first loaded into the {@link #modClassLoader}
* (always) then scanned for class resources matching the specification
* above.
2012-07-29 22:54:59 -04:00
*
2012-07-22 11:56:38 -02:30
* If they provide the {@link Mod} annotation, they will be loaded as
* "FML mods", which currently is effectively a NO-OP. If they are
2012-08-06 22:30:13 -04:00
* determined to be {@link BaseModProxy} subclasses they are loaded as such.
2012-07-29 22:54:59 -04:00
*
2012-07-22 11:56:38 -02:30
* Finally, if they are successfully loaded as classes, they are then added
* to the available mod list.
2012-04-05 16:22:47 -04:00
*/
2012-08-06 09:52:42 -04:00
private ModDiscoverer identifyMods ()
2012-04-05 10:07:52 -04:00
{
2012-08-04 11:26:51 -04:00
FMLLog . fine ( "Building injected Mod Containers %s" , injectedContainers );
2012-10-25 09:02:49 -04:00
// Add in the MCP mod container
2012-10-26 07:55:23 -04:00
mods . add ( new InjectedModContainer ( mcp , new File ( "minecraft.jar" )));
2012-08-04 11:26:51 -04:00
for ( String cont : injectedContainers )
{
ModContainer mc ;
try
{
mc = ( ModContainer ) Class . forName ( cont , true , modClassLoader ). newInstance ();
}
catch ( Exception e )
{
FMLLog . log ( Level . SEVERE , e , "A problem occured instantiating the injected mod container %s" , cont );
throw new LoaderException ( e );
}
2013-08-27 12:31:37 -04:00
mods . add ( new InjectedModContainer ( mc , mc . getSource ()));
2012-08-04 11:26:51 -04:00
}
2012-07-22 11:56:38 -02:30
ModDiscoverer discoverer = new ModDiscoverer ();
2012-07-29 22:54:59 -04:00
FMLLog . fine ( "Attempting to load mods contained in the minecraft jar file and associated classes" );
2012-07-22 11:56:38 -02:30
discoverer . findClasspathMods ( modClassLoader );
2012-07-29 22:54:59 -04:00
FMLLog . fine ( "Minecraft jar mods loaded successfully" );
2012-04-05 10:07:52 -04:00
2012-07-29 22:54:59 -04:00
FMLLog . info ( "Searching %s for mods" , canonicalModsDir . getAbsolutePath ());
2012-07-22 11:56:38 -02:30
discoverer . findModDirMods ( canonicalModsDir );
2013-07-02 23:03:01 -04:00
File versionSpecificModsDir = new File ( canonicalModsDir , mccversion );
if ( versionSpecificModsDir . isDirectory ())
{
FMLLog . info ( "Also searching %s for mods" , versionSpecificModsDir );
discoverer . findModDirMods ( versionSpecificModsDir );
}
2012-04-05 10:07:52 -04:00
2012-08-04 11:26:51 -04:00
mods . addAll ( discoverer . identifyMods ());
2012-08-14 13:36:29 -04:00
identifyDuplicates ( mods );
2012-07-22 11:56:38 -02:30
namedMods = Maps . uniqueIndex ( mods , new ModIdFunction ());
2012-07-29 22:54:59 -04:00
FMLLog . info ( "Forge Mod Loader has identified %d mod%s to load" , mods . size (), mods . size () != 1 ? "s" : "" );
2013-01-30 18:57:21 -05:00
for ( String modId : namedMods . keySet ())
{
FMLLog . makeLog ( modId );
}
2012-08-06 09:52:42 -04:00
return discoverer ;
2012-03-28 16:44:36 -04:00
}
2012-04-05 10:07:52 -04:00
2012-08-14 13:36:29 -04:00
private class ModIdComparator implements Comparator < ModContainer >
{
@Override
public int compare ( ModContainer o1 , ModContainer o2 )
{
return o1 . getModId (). compareTo ( o2 . getModId ());
}
}
private void identifyDuplicates ( List < ModContainer > mods )
{
TreeMultimap < ModContainer , File > dupsearch = TreeMultimap . create ( new ModIdComparator (), Ordering . arbitrary ());
for ( ModContainer mc : mods )
{
if ( mc . getSource () != null )
{
dupsearch . put ( mc , mc . getSource ());
}
}
ImmutableMultiset < ModContainer > duplist = Multisets . copyHighestCountFirst ( dupsearch . keys ());
2012-10-24 09:41:46 -04:00
SetMultimap < ModContainer , File > dupes = LinkedHashMultimap . create ();
2012-08-14 13:36:29 -04:00
for ( Entry < ModContainer > e : duplist . entrySet ())
{
if ( e . getCount () > 1 )
{
FMLLog . severe ( "Found a duplicate mod %s at %s" , e . getElement (). getModId (), dupsearch . get ( e . getElement ()));
2012-10-24 09:41:46 -04:00
dupes . putAll ( e . getElement (), dupsearch . get ( e . getElement ()));
2012-08-14 13:36:29 -04:00
}
}
2012-10-24 09:41:46 -04:00
if ( ! dupes . isEmpty ())
{
2013-08-28 10:18:28 -07:00
throw new DuplicateModsFoundException ( dupes );
2012-10-24 09:41:46 -04:00
}
2012-08-14 13:36:29 -04:00
}
2012-04-05 16:22:47 -04:00
/**
2013-01-21 21:37:29 -05:00
*
2012-04-05 16:22:47 -04:00
*/
2012-07-22 11:56:38 -02:30
private void initializeLoader ()
2012-04-05 10:07:52 -04:00
{
File modsDir = new File ( minecraftDir , "mods" );
File configDir = new File ( minecraftDir , "config" );
String canonicalModsPath ;
String canonicalConfigPath ;
try
{
canonicalModsPath = modsDir . getCanonicalPath ();
canonicalConfigPath = configDir . getCanonicalPath ();
canonicalConfigDir = configDir . getCanonicalFile ();
2012-07-22 11:56:38 -02:30
canonicalModsDir = modsDir . getCanonicalFile ();
2012-04-05 10:07:52 -04:00
}
catch ( IOException ioe )
{
2012-07-30 22:31:07 -04:00
FMLLog . log ( Level . SEVERE , ioe , "Failed to resolve loader directories: mods : %s ; config %s" , canonicalModsDir . getAbsolutePath (),
configDir . getAbsolutePath ());
2012-04-05 10:07:52 -04:00
throw new LoaderException ( ioe );
}
2012-07-22 11:56:38 -02:30
if ( ! canonicalModsDir . exists ())
2012-04-05 10:07:52 -04:00
{
2012-07-29 22:54:59 -04:00
FMLLog . info ( "No mod directory found, creating one: %s" , canonicalModsPath );
2012-07-22 11:56:38 -02:30
boolean dirMade = canonicalModsDir . mkdir ();
if ( ! dirMade )
2012-04-05 10:07:52 -04:00
{
2012-07-29 22:54:59 -04:00
FMLLog . severe ( "Unable to create the mod directory %s" , canonicalModsPath );
2012-07-22 11:56:38 -02:30
throw new LoaderException ();
2012-04-05 10:07:52 -04:00
}
2012-07-29 22:54:59 -04:00
FMLLog . info ( "Mod directory created successfully" );
2012-04-05 10:07:52 -04:00
}
2012-07-22 11:56:38 -02:30
if ( ! canonicalConfigDir . exists ())
2012-04-05 10:07:52 -04:00
{
2012-07-29 22:54:59 -04:00
FMLLog . fine ( "No config directory found, creating one: %s" , canonicalConfigPath );
2012-07-22 11:56:38 -02:30
boolean dirMade = canonicalConfigDir . mkdir ();
if ( ! dirMade )
2012-04-05 10:07:52 -04:00
{
2012-07-29 22:54:59 -04:00
FMLLog . severe ( "Unable to create the config directory %s" , canonicalConfigPath );
2012-07-22 11:56:38 -02:30
throw new LoaderException ();
2012-04-05 10:07:52 -04:00
}
2012-07-29 22:54:59 -04:00
FMLLog . info ( "Config directory created successfully" );
2012-04-05 10:07:52 -04:00
}
2012-07-22 11:56:38 -02:30
if ( ! canonicalModsDir . isDirectory ())
2012-04-05 10:07:52 -04:00
{
2012-07-29 22:54:59 -04:00
FMLLog . severe ( "Attempting to load mods from %s, which is not a directory" , canonicalModsPath );
2012-07-22 11:56:38 -02:30
throw new LoaderException ();
2012-04-05 10:07:52 -04:00
}
if ( ! configDir . isDirectory ())
{
2012-07-29 22:54:59 -04:00
FMLLog . severe ( "Attempting to load configuration from %s, which is not a directory" , canonicalConfigPath );
2012-07-22 11:56:38 -02:30
throw new LoaderException ();
2012-03-28 16:44:36 -04:00
}
2013-01-30 18:57:21 -05:00
loggingProperties = new File ( canonicalConfigDir , "logging.properties" );
FMLLog . info ( "Reading custom logging properties from %s" , loggingProperties . getPath ());
FMLRelaunchLog . loadLogConfiguration ( loggingProperties );
FMLLog . log ( Level . OFF , "Logging level for ForgeModLoader logging is set to %s" , FMLRelaunchLog . log . getLogger (). getLevel ());
2012-03-28 16:44:36 -04:00
}
2012-04-05 10:07:52 -04:00
2012-07-23 22:50:37 -02:30
public List < ModContainer > getModList ()
2012-04-05 10:07:52 -04:00
{
2012-10-18 14:41:32 -04:00
return instance (). mods != null ? ImmutableList . copyOf ( instance (). mods ) : ImmutableList . < ModContainer > of ();
2012-03-28 16:44:36 -04:00
}
2012-04-05 10:07:52 -04:00
2012-04-05 16:22:47 -04:00
/**
2012-07-22 11:56:38 -02:30
* Called from the hook to start mod loading. We trigger the
2012-09-15 18:04:56 -07:00
* {@link #identifyMods()} and Constructing, Preinitalization, and Initalization phases here. Finally,
2012-07-22 11:56:38 -02:30
* the mod list is frozen completely and is consider immutable from then on.
2012-04-05 16:22:47 -04:00
*/
2012-04-05 10:07:52 -04:00
public void loadMods ()
{
2012-07-22 11:56:38 -02:30
initializeLoader ();
2012-08-02 02:08:30 -02:30
mods = Lists . newArrayList ();
namedMods = Maps . newHashMap ();
2012-07-30 17:01:27 -04:00
modController = new LoadController ( this );
2013-06-11 14:41:19 -04:00
modController . transition ( LoaderState . LOADING , false );
2012-08-06 09:52:42 -04:00
ModDiscoverer disc = identifyMods ();
2013-10-30 22:20:28 -04:00
ModAPIManager . INSTANCE . manageAPI ( modClassLoader , disc );
2012-07-22 11:56:38 -02:30
disableRequestedMods ();
2013-01-30 18:57:21 -05:00
FMLLog . fine ( "Reloading logging properties from %s" , loggingProperties . getPath ());
FMLRelaunchLog . loadLogConfiguration ( loggingProperties );
FMLLog . fine ( "Reloaded logging properties" );
2012-08-23 14:33:54 -04:00
modController . distributeStateMessage ( FMLLoadEvent . class );
2012-04-05 10:07:52 -04:00
sortModList ();
2013-10-30 22:20:28 -04:00
ModAPIManager . INSTANCE . cleanupAPIContainers ( modController . getActiveModList ());
ModAPIManager . INSTANCE . cleanupAPIContainers ( mods );
2012-07-22 11:56:38 -02:30
mods = ImmutableList . copyOf ( mods );
2012-08-30 20:10:13 -04:00
for ( File nonMod : disc . getNonModLibs ())
{
if ( nonMod . isFile ())
{
2012-10-24 09:41:46 -04:00
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 ());
2012-08-30 20:10:13 -04:00
try
{
modClassLoader . addFile ( nonMod );
}
catch ( MalformedURLException e )
{
FMLLog . log ( Level . SEVERE , e , "Encountered a weird problem with non-mod file injection : %s" , nonMod . getName ());
}
}
}
2013-06-11 14:41:19 -04:00
modController . transition ( LoaderState . CONSTRUCTING , false );
2012-08-06 09:52:42 -04:00
modController . distributeStateMessage ( LoaderState . CONSTRUCTING , modClassLoader , disc . getASMTable ());
2013-01-30 18:57:21 -05:00
FMLLog . fine ( "Mod signature data" );
2012-12-17 19:55:46 -05:00
for ( ModContainer mod : getActiveModList ())
{
FMLLog . fine ( "\t%s(%s:%s): %s (%s)" , mod . getModId (), mod . getName (), mod . getVersion (), mod . getSource (). getName (), CertificateHelper . getFingerprint ( mod . getSigningCertificate ()));
}
2013-01-30 20:55:12 -05:00
if ( getActiveModList (). isEmpty ())
{
FMLLog . fine ( "No user mod signature data found" );
}
2013-06-11 14:41:19 -04:00
modController . transition ( LoaderState . PREINITIALIZATION , false );
2012-08-10 17:42:43 -04:00
modController . distributeStateMessage ( LoaderState . PREINITIALIZATION , disc . getASMTable (), canonicalConfigDir );
2013-06-11 14:41:19 -04:00
modController . transition ( LoaderState . INITIALIZATION , false );
2013-11-06 16:18:41 -05:00
GameData . validateRegistry ();
2012-03-28 16:44:36 -04:00
}
2012-07-22 11:56:38 -02:30
private void disableRequestedMods ()
2012-04-05 10:07:52 -04:00
{
2012-08-23 17:37:44 -04:00
String forcedModList = System . getProperty ( "fml.modStates" , "" );
2013-01-30 18:57:21 -05:00
FMLLog . finer ( "Received a system property request \'%s\'" , forcedModList );
2012-07-22 11:56:38 -02:30
Map < String , String > sysPropertyStateList = Splitter . on ( CharMatcher . anyOf ( ";:" ))
. omitEmptyStrings (). trimResults (). withKeyValueSeparator ( "=" )
2012-08-23 17:37:44 -04:00
. split ( forcedModList );
2013-01-30 18:57:21 -05:00
FMLLog . finer ( "System property request managing the state of %d mods" , sysPropertyStateList . size ());
2012-07-22 11:56:38 -02:30
Map < String , String > modStates = Maps . newHashMap ();
2012-07-29 22:54:59 -04:00
2012-08-23 17:37:44 -04:00
File forcedModFile = new File ( canonicalConfigDir , "fmlModState.properties" );
Properties forcedModListProperties = new Properties ();
if ( forcedModFile . exists () && forcedModFile . isFile ())
2012-07-22 11:56:38 -02:30
{
2013-01-30 18:57:21 -05:00
FMLLog . finer ( "Found a mod state file %s" , forcedModFile . getName ());
2012-07-22 11:56:38 -02:30
try
{
2012-08-23 17:37:44 -04:00
forcedModListProperties . load ( new FileReader ( forcedModFile ));
2013-01-30 18:57:21 -05:00
FMLLog . finer ( "Loaded states for %d mods from file" , forcedModListProperties . size ());
2012-07-22 11:56:38 -02:30
}
catch ( Exception e )
{
2012-07-29 22:54:59 -04:00
FMLLog . log ( Level . INFO , e , "An error occurred reading the fmlModState.properties file" );
2012-07-22 11:56:38 -02:30
}
}
2012-08-23 17:37:44 -04:00
modStates . putAll ( Maps . fromProperties ( forcedModListProperties ));
2012-07-22 11:56:38 -02:30
modStates . putAll ( sysPropertyStateList );
2012-07-29 22:54:59 -04:00
FMLLog . fine ( "After merging, found state information for %d mods" , modStates . size ());
2012-07-22 11:56:38 -02:30
Map < String , Boolean > isEnabled = Maps . transformValues ( modStates , new Function < String , Boolean > ()
{
public Boolean apply ( String input )
{
2012-08-23 17:37:44 -04:00
return Boolean . parseBoolean ( input );
2012-07-22 11:56:38 -02:30
}
});
2012-07-29 22:54:59 -04:00
2012-07-22 11:56:38 -02:30
for ( Map . Entry < String , Boolean > entry : isEnabled . entrySet ())
{
if ( namedMods . containsKey ( entry . getKey ()))
{
2012-07-29 22:54:59 -04:00
FMLLog . info ( "Setting mod %s to enabled state %b" , entry . getKey (), entry . getValue ());
2012-07-22 11:56:38 -02:30
namedMods . get ( entry . getKey ()). setEnabledState ( entry . getValue ());
}
2012-05-10 09:42:31 -04:00
}
2012-03-28 16:44:36 -04:00
}
2012-04-05 10:07:52 -04:00
2012-04-05 16:22:47 -04:00
/**
* Query if we know of a mod named modname
2012-07-29 22:54:59 -04:00
*
2012-04-05 16:22:47 -04:00
* @param modname
2012-09-15 18:04:56 -07:00
* @return If the mod is loaded
2012-04-05 16:22:47 -04:00
*/
2012-04-05 10:07:52 -04:00
public static boolean isModLoaded ( String modname )
{
2012-08-23 17:08:18 -04:00
return instance (). namedMods . containsKey ( modname ) && instance (). modController . getModState ( instance . namedMods . get ( modname )) != ModState . DISABLED ;
2012-03-28 16:44:36 -04:00
}
2012-04-05 16:22:47 -04:00
2012-04-05 10:07:52 -04:00
public File getConfigDir ()
{
return canonicalConfigDir ;
2012-03-28 16:44:36 -04:00
}
2012-06-26 16:24:50 -04:00
2012-05-06 16:32:59 -07:00
public String getCrashInformation ()
{
2013-03-10 12:12:22 -04:00
// Handle being called before we've begun setup
if ( modController == null )
{
return "" ;
}
2012-07-22 11:56:38 -02:30
StringBuilder ret = new StringBuilder ();
2012-07-23 22:50:37 -02:30
List < String > branding = FMLCommonHandler . instance (). getBrandings ();
2012-07-29 22:54:59 -04:00
2012-07-23 22:50:37 -02:30
Joiner . on ( ' ' ). skipNulls (). appendTo ( ret , branding . subList ( 1 , branding . size ()));
2013-08-28 10:18:28 -07:00
if ( modController != null )
2012-07-23 16:33:17 -02:30
{
modController . printModStates ( ret );
}
2012-05-10 09:42:31 -04:00
return ret . toString ();
2012-05-06 16:32:59 -07:00
}
2012-05-11 01:45:36 -04:00
public String getFMLVersionString ()
{
2012-09-07 19:08:28 -04:00
return String . format ( "%s.%s.%s.%s" , major , minor , rev , build );
2012-05-11 01:45:36 -04:00
}
2012-05-15 15:52:19 -04:00
public ClassLoader getModClassLoader ()
{
return modClassLoader ;
}
2012-07-22 11:56:38 -02:30
2012-08-23 13:43:25 -04:00
public void computeDependencies ( String dependencyString , Set < ArtifactVersion > requirements , List < ArtifactVersion > dependencies , List < ArtifactVersion > dependants )
2012-07-22 11:56:38 -02:30
{
if ( dependencyString == null || dependencyString . length () == 0 )
{
return ;
}
2012-07-29 22:54:59 -04:00
2013-08-28 10:18:28 -07:00
boolean parseFailure = false ;
2012-07-29 22:54:59 -04:00
2012-07-22 11:56:38 -02:30
for ( String dep : DEPENDENCYSPLITTER . split ( dependencyString ))
{
List < String > depparts = Lists . newArrayList ( DEPENDENCYPARTSPLITTER . split ( dep ));
2012-07-30 17:01:27 -04:00
// Need two parts to the string
2012-07-22 11:56:38 -02:30
if ( depparts . size () != 2 )
{
2013-08-28 10:18:28 -07:00
parseFailure = true ;
2012-07-22 11:56:38 -02:30
continue ;
}
2012-07-23 16:33:17 -02:30
String instruction = depparts . get ( 0 );
String target = depparts . get ( 1 );
2012-07-30 17:01:27 -04:00
boolean targetIsAll = target . startsWith ( "*" );
// Cannot have an "all" relationship with anything except pure *
2013-08-28 10:18:28 -07:00
if ( targetIsAll && target . length () > 1 )
2012-07-30 17:01:27 -04:00
{
parseFailure = true ;
continue ;
}
2012-07-29 22:54:59 -04:00
2012-07-22 11:56:38 -02:30
// If this is a required element, add it to the required list
if ( "required-before" . equals ( instruction ) || "required-after" . equals ( instruction ))
{
// You can't require everything
if ( ! targetIsAll )
{
2012-07-30 17:01:27 -04:00
requirements . add ( VersionParser . parseVersionReference ( target ));
2012-07-22 11:56:38 -02:30
}
else
{
2013-08-28 10:18:28 -07:00
parseFailure = true ;
2012-07-22 11:56:38 -02:30
continue ;
}
}
2012-07-29 22:54:59 -04:00
2012-07-30 17:01:27 -04:00
// You cannot have a versioned dependency on everything
2013-08-28 10:18:28 -07:00
if ( targetIsAll && target . indexOf ( '@' ) > - 1 )
2012-07-30 17:01:27 -04:00
{
parseFailure = true ;
continue ;
}
2012-07-22 11:56:38 -02:30
// before elements are things we are loaded before (so they are our dependants)
if ( "required-before" . equals ( instruction ) || "before" . equals ( instruction ))
{
2012-07-30 17:01:27 -04:00
dependants . add ( VersionParser . parseVersionReference ( target ));
2012-07-22 11:56:38 -02:30
}
// after elements are things that load before we do (so they are out dependencies)
else if ( "required-after" . equals ( instruction ) || "after" . equals ( instruction ))
{
2012-07-30 17:01:27 -04:00
dependencies . add ( VersionParser . parseVersionReference ( target ));
2012-07-22 11:56:38 -02:30
}
else
{
2013-08-28 10:18:28 -07:00
parseFailure = true ;
2012-07-22 11:56:38 -02:30
}
}
2012-07-29 22:54:59 -04:00
2012-07-22 11:56:38 -02:30
if ( parseFailure )
{
2012-07-23 16:33:17 -02:30
FMLLog . log ( Level . WARNING , "Unable to parse dependency string %s" , dependencyString );
2012-07-22 11:56:38 -02:30
throw new LoaderException ();
}
}
public Map < String , ModContainer > getIndexedModList ()
{
return ImmutableMap . copyOf ( namedMods );
}
2012-07-23 16:33:17 -02:30
public void initializeMods ()
{
// Mod controller should be in the initialization state here
2012-08-02 02:08:30 -02:30
modController . distributeStateMessage ( LoaderState . INITIALIZATION );
2013-06-11 14:41:19 -04:00
modController . transition ( LoaderState . POSTINITIALIZATION , false );
2013-03-08 13:15:09 -05:00
// Construct the "mod object table" so mods can refer to it in IMC and postinit
GameData . buildModObjectTable ();
2012-10-24 16:39:55 -04:00
modController . distributeStateMessage ( FMLInterModComms . IMCEvent . class );
2012-08-02 02:08:30 -02:30
modController . distributeStateMessage ( LoaderState . POSTINITIALIZATION );
2013-06-11 14:41:19 -04:00
modController . transition ( LoaderState . AVAILABLE , false );
2012-08-02 02:08:30 -02:30
modController . distributeStateMessage ( LoaderState . AVAILABLE );
2013-03-23 15:39:34 -04:00
// Dump the custom registry data map, if necessary
GameData . dumpRegistry ( minecraftDir );
2013-08-28 10:18:28 -07:00
FMLLog . info ( "Forge Mod Loader has successfully loaded %d mod%s" , mods . size (), mods . size () == 1 ? "" : "s" );
2012-07-23 16:33:17 -02:30
}
2012-07-23 22:50:37 -02:30
2012-08-21 18:48:12 -04:00
public ICrashCallable getCallableCrashInformation ()
2012-07-23 22:50:37 -02:30
{
2012-08-21 18:48:12 -04:00
return new ICrashCallable () {
2012-07-23 22:50:37 -02:30
@Override
public String call () throws Exception
{
return getCrashInformation ();
}
2012-08-21 18:48:12 -04:00
@Override
public String getLabel ()
{
return "FML" ;
}
2012-07-23 22:50:37 -02:30
};
}
public List < ModContainer > getActiveModList ()
{
2012-10-18 14:41:32 -04:00
return modController != null ? modController . getActiveModList () : ImmutableList . < ModContainer > of ();
2012-07-23 22:50:37 -02:30
}
public ModState getModState ( ModContainer selectedMod )
{
return modController . getModState ( selectedMod );
}
2012-07-30 22:31:07 -04:00
public String getMCVersionString ()
{
return "Minecraft " + mccversion ;
}
2012-08-02 02:08:30 -02:30
2013-01-22 09:11:44 -05:00
public boolean serverStarting ( Object server )
2012-08-02 02:08:30 -02:30
{
2013-01-22 09:11:44 -05:00
try
{
modController . distributeStateMessage ( LoaderState . SERVER_STARTING , server );
2013-06-11 14:41:19 -04:00
modController . transition ( LoaderState . SERVER_STARTING , false );
2013-01-22 09:11:44 -05:00
}
catch ( Throwable t )
{
FMLLog . log ( Level . SEVERE , t , "A fatal exception occurred during the server starting event" );
return false ;
}
return true ;
2012-08-02 02:08:30 -02:30
}
2012-08-04 11:26:51 -04:00
2012-08-02 02:08:30 -02:30
public void serverStarted ()
{
modController . distributeStateMessage ( LoaderState . SERVER_STARTED );
2013-06-11 14:41:19 -04:00
modController . transition ( LoaderState . SERVER_STARTED , false );
2012-08-02 02:08:30 -02:30
}
2012-08-04 11:26:51 -04:00
2012-08-02 02:08:30 -02:30
public void serverStopping ()
{
modController . distributeStateMessage ( LoaderState . SERVER_STOPPING );
2013-06-11 14:41:19 -04:00
modController . transition ( LoaderState . SERVER_STOPPING , false );
2012-08-02 02:08:30 -02:30
}
2012-08-11 02:43:04 -04:00
public BiMap < ModContainer , Object > getModObjectList ()
{
return modController . getModObjectList ();
}
public BiMap < Object , ModContainer > getReversedModObjectList ()
{
return getModObjectList (). inverse ();
}
2012-08-13 15:26:29 -04:00
public ModContainer activeModContainer ()
{
2012-12-07 01:52:16 -05:00
return modController != null ? modController . activeContainer () : null ;
2012-08-13 15:26:29 -04:00
}
public boolean isInState ( LoaderState state )
{
return modController . isInState ( state );
}
2012-10-02 01:29:46 -04:00
public MinecraftDummyContainer getMinecraftModContainer ()
{
return minecraft ;
}
2012-10-24 16:39:55 -04:00
2013-08-28 10:18:28 -07:00
public boolean hasReachedState ( LoaderState state )
{
return modController != null ? modController . hasReachedState ( state ) : false ;
}
2012-10-25 09:02:49 -04:00
2013-08-28 10:18:28 -07:00
public String getMCPVersionString ()
{
return String . format ( "MCP v%s" , mcpversion );
}
2012-12-30 23:52:38 -05:00
public void serverStopped ()
{
modController . distributeStateMessage ( LoaderState . SERVER_STOPPED );
2013-06-11 14:41:19 -04:00
modController . transition ( LoaderState . SERVER_STOPPED , true );
modController . transition ( LoaderState . AVAILABLE , true );
2012-12-30 23:52:38 -05:00
}
2013-01-22 09:11:44 -05:00
2013-01-24 10:23:36 -05:00
public boolean serverAboutToStart ( Object server )
2013-01-22 09:11:44 -05:00
{
try
{
modController . distributeStateMessage ( LoaderState . SERVER_ABOUT_TO_START , server );
2013-06-11 14:41:19 -04:00
modController . transition ( LoaderState . SERVER_ABOUT_TO_START , false );
2013-01-22 09:11:44 -05:00
}
catch ( Throwable t )
{
FMLLog . log ( Level . SEVERE , t , "A fatal exception occurred during the server about to start event" );
return false ;
}
return true ;
}
2013-03-23 15:39:34 -04:00
2013-03-30 12:12:54 -04:00
public Map < String , String > getFMLBrandingProperties ()
{
if ( fmlBrandingProperties == null )
{
Properties loaded = new Properties ();
try
{
loaded . load ( getClass (). getClassLoader (). getResourceAsStream ( "fmlbranding.properties" ));
}
2013-03-31 10:01:46 -04:00
catch ( Exception e )
2013-03-30 12:12:54 -04:00
{
// File not found - ignore
}
fmlBrandingProperties = Maps . fromProperties ( loaded );
}
return fmlBrandingProperties ;
}
2013-06-27 08:38:11 -04:00
public Map < String , String > getCustomModProperties ( String modId )
{
return getIndexedModList (). get ( modId ). getCustomModProperties ();
}
2012-03-28 16:44:36 -04:00
}