001/* 002 * The FML Forge Mod Loader suite. 003 * Copyright (C) 2012 cpw 004 * 005 * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free 006 * Software Foundation; either version 2.1 of the License, or any later version. 007 * 008 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 009 * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. 010 * 011 * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 012 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 013 */ 014package cpw.mods.fml.common; 015 016import java.io.File; 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; 030 031import com.google.common.base.CharMatcher; 032import com.google.common.base.Function; 033import com.google.common.base.Joiner; 034import com.google.common.base.Splitter; 035import com.google.common.collect.ArrayListMultimap; 036import com.google.common.collect.BiMap; 037import com.google.common.collect.HashBiMap; 038import com.google.common.collect.ImmutableList; 039import com.google.common.collect.ImmutableMap; 040import com.google.common.collect.ImmutableMultiset; 041import com.google.common.collect.Iterables; 042import com.google.common.collect.LinkedHashMultimap; 043import com.google.common.collect.Lists; 044import com.google.common.collect.Maps; 045import com.google.common.collect.SetMultimap; 046import com.google.common.collect.Sets; 047import com.google.common.collect.Multiset.Entry; 048import com.google.common.collect.Multisets; 049import com.google.common.collect.Ordering; 050import com.google.common.collect.Sets.SetView; 051import com.google.common.collect.TreeMultimap; 052 053import cpw.mods.fml.common.LoaderState.ModState; 054import cpw.mods.fml.common.discovery.ModDiscoverer; 055import cpw.mods.fml.common.event.FMLInterModComms; 056import cpw.mods.fml.common.event.FMLLoadEvent; 057import cpw.mods.fml.common.functions.ModIdFunction; 058import cpw.mods.fml.common.modloader.BaseModProxy; 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 StringBuilder ret = new StringBuilder(); 575 List<String> branding = FMLCommonHandler.instance().getBrandings(); 576 577 Joiner.on(' ').skipNulls().appendTo(ret, branding.subList(1, branding.size())); 578 if (modController!=null) 579 { 580 modController.printModStates(ret); 581 } 582 return ret.toString(); 583 } 584 585 public String getFMLVersionString() 586 { 587 return String.format("%s.%s.%s.%s", major, minor, rev, build); 588 } 589 590 public ClassLoader getModClassLoader() 591 { 592 return modClassLoader; 593 } 594 595 public void computeDependencies(String dependencyString, Set<ArtifactVersion> requirements, List<ArtifactVersion> dependencies, List<ArtifactVersion> dependants) 596 { 597 if (dependencyString == null || dependencyString.length() == 0) 598 { 599 return; 600 } 601 602 boolean parseFailure=false; 603 604 for (String dep : DEPENDENCYSPLITTER.split(dependencyString)) 605 { 606 List<String> depparts = Lists.newArrayList(DEPENDENCYPARTSPLITTER.split(dep)); 607 // Need two parts to the string 608 if (depparts.size() != 2) 609 { 610 parseFailure=true; 611 continue; 612 } 613 String instruction = depparts.get(0); 614 String target = depparts.get(1); 615 boolean targetIsAll = target.startsWith("*"); 616 617 // Cannot have an "all" relationship with anything except pure * 618 if (targetIsAll && target.length()>1) 619 { 620 parseFailure = true; 621 continue; 622 } 623 624 // If this is a required element, add it to the required list 625 if ("required-before".equals(instruction) || "required-after".equals(instruction)) 626 { 627 // You can't require everything 628 if (!targetIsAll) 629 { 630 requirements.add(VersionParser.parseVersionReference(target)); 631 } 632 else 633 { 634 parseFailure=true; 635 continue; 636 } 637 } 638 639 // You cannot have a versioned dependency on everything 640 if (targetIsAll && target.indexOf('@')>-1) 641 { 642 parseFailure = true; 643 continue; 644 } 645 // before elements are things we are loaded before (so they are our dependants) 646 if ("required-before".equals(instruction) || "before".equals(instruction)) 647 { 648 dependants.add(VersionParser.parseVersionReference(target)); 649 } 650 // after elements are things that load before we do (so they are out dependencies) 651 else if ("required-after".equals(instruction) || "after".equals(instruction)) 652 { 653 dependencies.add(VersionParser.parseVersionReference(target)); 654 } 655 else 656 { 657 parseFailure=true; 658 } 659 } 660 661 if (parseFailure) 662 { 663 FMLLog.log(Level.WARNING, "Unable to parse dependency string %s", dependencyString); 664 throw new LoaderException(); 665 } 666 } 667 668 public Map<String,ModContainer> getIndexedModList() 669 { 670 return ImmutableMap.copyOf(namedMods); 671 } 672 673 public void initializeMods() 674 { 675 // Mod controller should be in the initialization state here 676 modController.distributeStateMessage(LoaderState.INITIALIZATION); 677 modController.transition(LoaderState.POSTINITIALIZATION); 678 modController.distributeStateMessage(FMLInterModComms.IMCEvent.class); 679 modController.distributeStateMessage(LoaderState.POSTINITIALIZATION); 680 modController.transition(LoaderState.AVAILABLE); 681 modController.distributeStateMessage(LoaderState.AVAILABLE); 682 FMLLog.info("Forge Mod Loader has successfully loaded %d mod%s", mods.size(), mods.size()==1 ? "" : "s"); 683 } 684 685 public ICrashCallable getCallableCrashInformation() 686 { 687 return new ICrashCallable() { 688 @Override 689 public String call() throws Exception 690 { 691 return getCrashInformation(); 692 } 693 694 @Override 695 public String getLabel() 696 { 697 return "FML"; 698 } 699 }; 700 } 701 702 public List<ModContainer> getActiveModList() 703 { 704 return modController != null ? modController.getActiveModList() : ImmutableList.<ModContainer>of(); 705 } 706 707 public ModState getModState(ModContainer selectedMod) 708 { 709 return modController.getModState(selectedMod); 710 } 711 712 public String getMCVersionString() 713 { 714 return "Minecraft " + mccversion; 715 } 716 717 public boolean serverStarting(Object server) 718 { 719 try 720 { 721 modController.distributeStateMessage(LoaderState.SERVER_STARTING, server); 722 modController.transition(LoaderState.SERVER_STARTING); 723 } 724 catch (Throwable t) 725 { 726 FMLLog.log(Level.SEVERE, t, "A fatal exception occurred during the server starting event"); 727 return false; 728 } 729 return true; 730 } 731 732 public void serverStarted() 733 { 734 modController.distributeStateMessage(LoaderState.SERVER_STARTED); 735 modController.transition(LoaderState.SERVER_STARTED); 736 } 737 738 public void serverStopping() 739 { 740 modController.distributeStateMessage(LoaderState.SERVER_STOPPING); 741 modController.transition(LoaderState.SERVER_STOPPING); 742 } 743 744 public BiMap<ModContainer, Object> getModObjectList() 745 { 746 return modController.getModObjectList(); 747 } 748 749 public BiMap<Object, ModContainer> getReversedModObjectList() 750 { 751 return getModObjectList().inverse(); 752 } 753 754 public ModContainer activeModContainer() 755 { 756 return modController != null ? modController.activeContainer() : null; 757 } 758 759 public boolean isInState(LoaderState state) 760 { 761 return modController.isInState(state); 762 } 763 764 public MinecraftDummyContainer getMinecraftModContainer() 765 { 766 return minecraft; 767 } 768 769 public boolean hasReachedState(LoaderState state) { 770 return modController != null ? modController.hasReachedState(state) : false; 771 } 772 773 public String getMCPVersionString() { 774 return String.format("MCP v%s", mcpversion); 775 } 776 777 public void serverStopped() 778 { 779 modController.distributeStateMessage(LoaderState.SERVER_STOPPED); 780 modController.transition(LoaderState.SERVER_STOPPED); 781 modController.transition(LoaderState.AVAILABLE); 782 } 783 784 public boolean serverAboutToStart(Object server) 785 { 786 try 787 { 788 modController.distributeStateMessage(LoaderState.SERVER_ABOUT_TO_START, server); 789 modController.transition(LoaderState.SERVER_ABOUT_TO_START); 790 } 791 catch (Throwable t) 792 { 793 FMLLog.log(Level.SEVERE, t, "A fatal exception occurred during the server about to start event"); 794 return false; 795 } 796 return true; 797 } 798}