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