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