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