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