public Properties searchForVersionProperties() { try { FMLLog.fine( "Attempting to load the file version.properties from %s to locate a version number for %s", getSource().getName(), getModId()); Properties version = null; if (getSource().isFile()) { ZipFile source = new ZipFile(getSource()); ZipEntry versionFile = source.getEntry("version.properties"); if (versionFile != null) { version = new Properties(); version.load(source.getInputStream(versionFile)); } source.close(); } else if (getSource().isDirectory()) { File propsFile = new File(getSource(), "version.properties"); if (propsFile.exists() && propsFile.isFile()) { version = new Properties(); FileInputStream fis = new FileInputStream(propsFile); version.load(fis); fis.close(); } } return version; } catch (Exception e) { Throwables.propagateIfPossible(e); FMLLog.fine("Failed to find a usable version.properties file"); return null; } }
public static void inject( ModContainer mod, ASMDataTable data, Side side, ILanguageAdapter languageAdapter) { FMLLog.fine("Attempting to inject @SidedProxy classes into %s", mod.getModId()); Set<ASMData> targets = data.getAnnotationsFor(mod).get(SidedProxy.class.getName()); ClassLoader mcl = Loader.instance().getModClassLoader(); for (ASMData targ : targets) { try { Class<?> proxyTarget = Class.forName(targ.getClassName(), true, mcl); Field target = proxyTarget.getDeclaredField(targ.getObjectName()); if (target == null) { // Impossible? FMLLog.severe( "Attempted to load a proxy type into %s.%s but the field was not found", targ.getClassName(), targ.getObjectName()); throw new LoaderException(); } SidedProxy annotation = target.getAnnotation(SidedProxy.class); if (!Strings.isNullOrEmpty(annotation.modId()) && !annotation.modId().equals(mod.getModId())) { FMLLog.fine( "Skipping proxy injection for %s.%s since it is not for mod %s", targ.getClassName(), targ.getObjectName(), mod.getModId()); continue; } String targetType = side.isClient() ? annotation.clientSide() : annotation.serverSide(); Object proxy = Class.forName(targetType, true, mcl).newInstance(); if (languageAdapter.supportsStatics() && (target.getModifiers() & Modifier.STATIC) == 0) { FMLLog.severe( "Attempted to load a proxy type %s into %s.%s, but the field is not static", targetType, targ.getClassName(), targ.getObjectName()); throw new LoaderException(); } if (!target.getType().isAssignableFrom(proxy.getClass())) { FMLLog.severe( "Attempted to load a proxy type %s into %s.%s, but the types don't match", targetType, targ.getClassName(), targ.getObjectName()); throw new LoaderException(); } languageAdapter.setProxy(target, proxyTarget, proxy); } catch (Exception e) { FMLLog.log( Level.SEVERE, e, "An error occured trying to load a proxy into %s.%s", targ.getAnnotationInfo(), targ.getClassName(), targ.getObjectName()); throw new LoaderException(e); } } // Allow language specific proxy injection. languageAdapter.setInternalProxies(mod, side, mcl); }
@Override public void bindMetadata(MetadataCollection mc) { modMetadata = mc.getMetadataForId(getModId(), descriptor); if (descriptor.containsKey("useMetadata")) { overridesMetadata = !((Boolean) descriptor.get("useMetadata")).booleanValue(); } if (overridesMetadata || !modMetadata.useDependencyInformation) { Set<ArtifactVersion> requirements = Sets.newHashSet(); List<ArtifactVersion> dependencies = Lists.newArrayList(); List<ArtifactVersion> dependants = Lists.newArrayList(); annotationDependencies = (String) descriptor.get("dependencies"); Loader.instance() .computeDependencies(annotationDependencies, requirements, dependencies, dependants); modMetadata.requiredMods = requirements; modMetadata.dependencies = dependencies; modMetadata.dependants = dependants; FMLLog.finest("Parsed dependency info : %s %s %s", requirements, dependencies, dependants); } else { FMLLog.finest( "Using mcmod dependency info : %s %s %s", modMetadata.requiredMods, modMetadata.dependencies, modMetadata.dependants); } if (Strings.isNullOrEmpty(modMetadata.name)) { FMLLog.info( "Mod %s is missing the required element 'name'. Substituting %s", getModId(), getModId()); modMetadata.name = getModId(); } internalVersion = (String) descriptor.get("version"); if (Strings.isNullOrEmpty(internalVersion)) { Properties versionProps = searchForVersionProperties(); if (versionProps != null) { internalVersion = versionProps.getProperty(getModId() + ".version"); FMLLog.fine( "Found version %s for mod %s in version.properties, using", internalVersion, getModId()); } } if (Strings.isNullOrEmpty(internalVersion) && !Strings.isNullOrEmpty(modMetadata.version)) { FMLLog.warning( "Mod %s is missing the required element 'version' and a version.properties file could not be found. Falling back to metadata version %s", getModId(), modMetadata.version); internalVersion = modMetadata.version; } if (Strings.isNullOrEmpty(internalVersion)) { FMLLog.warning( "Mod %s is missing the required element 'version' and no fallback can be found. Substituting '1.0'.", getModId()); modMetadata.version = internalVersion = "1.0"; } String mcVersionString = (String) descriptor.get("acceptedMinecraftVersions"); if (!Strings.isNullOrEmpty(mcVersionString)) { minecraftAccepted = VersionParser.parseRange(mcVersionString); } else { minecraftAccepted = Loader.instance().getMinecraftModContainer().getStaticVersionRange(); } }
private void initializeLoader() { File modsDir = new File(minecraftDir, "mods"); File configDir = new File(minecraftDir, "config"); String canonicalModsPath; String canonicalConfigPath; try { canonicalModsPath = modsDir.getCanonicalPath(); canonicalConfigPath = configDir.getCanonicalPath(); canonicalConfigDir = configDir.getCanonicalFile(); canonicalModsDir = modsDir.getCanonicalFile(); } catch (IOException ioe) { FMLLog.log( Level.ERROR, ioe, "Failed to resolve loader directories: mods : %s ; config %s", canonicalModsDir.getAbsolutePath(), configDir.getAbsolutePath()); throw new LoaderException(ioe); } if (!canonicalModsDir.exists()) { FMLLog.info("No mod directory found, creating one: %s", canonicalModsPath); boolean dirMade = canonicalModsDir.mkdir(); if (!dirMade) { FMLLog.severe("Unable to create the mod directory %s", canonicalModsPath); throw new LoaderException( String.format("Unable to create the mod directory %s", canonicalModsPath)); } FMLLog.info("Mod directory created successfully"); } if (!canonicalConfigDir.exists()) { FMLLog.fine("No config directory found, creating one: %s", canonicalConfigPath); boolean dirMade = canonicalConfigDir.mkdir(); if (!dirMade) { FMLLog.severe("Unable to create the config directory %s", canonicalConfigPath); throw new LoaderException(); } FMLLog.info("Config directory created successfully"); } if (!canonicalModsDir.isDirectory()) { FMLLog.severe("Attempting to load mods from %s, which is not a directory", canonicalModsPath); throw new LoaderException(); } if (!configDir.isDirectory()) { FMLLog.severe( "Attempting to load configuration from %s, which is not a directory", canonicalConfigPath); throw new LoaderException(); } readInjectedDependencies(); }
/** * The primary loading code * * <p>The found resources are first loaded into the {@link #modClassLoader} (always) then scanned * for class resources matching the specification above. * * <p>If they provide the {@link Mod} annotation, they will be loaded as "FML mods" * * <p>Finally, if they are successfully loaded as classes, they are then added to the available * mod list. */ private ModDiscoverer identifyMods() { FMLLog.fine("Building injected Mod Containers %s", injectedContainers); // Add in the MCP mod container mods.add(new InjectedModContainer(mcp, new File("minecraft.jar"))); for (String cont : injectedContainers) { ModContainer mc; try { mc = (ModContainer) Class.forName(cont, true, modClassLoader).newInstance(); } catch (Exception e) { FMLLog.log( Level.ERROR, e, "A problem occured instantiating the injected mod container %s", cont); throw new LoaderException(e); } mods.add(new InjectedModContainer(mc, mc.getSource())); } ModDiscoverer discoverer = new ModDiscoverer(); FMLLog.fine( "Attempting to load mods contained in the minecraft jar file and associated classes"); discoverer.findClasspathMods(modClassLoader); FMLLog.fine("Minecraft jar mods loaded successfully"); FMLLog.getLogger() .log( Level.INFO, "Found {} mods from the command line. Injecting into mod discoverer", ModListHelper.additionalMods.size()); FMLLog.info("Searching %s for mods", canonicalModsDir.getAbsolutePath()); discoverer.findModDirMods( canonicalModsDir, ModListHelper.additionalMods.values().toArray(new File[0])); File versionSpecificModsDir = new File(canonicalModsDir, mccversion); if (versionSpecificModsDir.isDirectory()) { FMLLog.info("Also searching %s for mods", versionSpecificModsDir); discoverer.findModDirMods(versionSpecificModsDir); } mods.addAll(discoverer.identifyMods()); identifyDuplicates(mods); namedMods = Maps.uniqueIndex(mods, new ModIdFunction()); FMLLog.info( "Forge Mod Loader has identified %d mod%s to load", mods.size(), mods.size() != 1 ? "s" : ""); return discoverer; }
/** * Not a fully fleshed out API, may change in future MC versions. However feel free to use and * suggest additions. */ public static void pop(ProgressBar bar) { if (bar.getSteps() != bar.getStep()) throw new IllegalStateException("can't pop unfinished ProgressBar " + bar.getTitle()); bars.remove(bar); if (bar.getSteps() != 0) { long newTime = System.nanoTime(); if (bar.timeEachStep) FMLLog.fine( "Bar Step: %s - %s took %.3fs", bar.getTitle(), bar.getMessage(), ((float) (newTime - bar.lastTime) / 1000000 / 1000)); if (bar.getSteps() == 1) FMLLog.fine( "Bar Finished: %s - %s took %.3fs", bar.getTitle(), bar.getMessage(), ((float) (newTime - bar.startTime) / 1000000 / 1000)); else FMLLog.fine( "Bar Finished: %s took %.3fs", bar.getTitle(), ((float) (newTime - bar.startTime) / 1000000 / 1000)); } FMLCommonHandler.instance().processWindowMessages(); }
@Override public boolean registerBus(EventBus bus, LoadController controller) { if (this.enabled) { FMLLog.fine("Enabling mod %s", getModId()); this.eventBus = bus; this.controller = controller; eventBus.register(this); return true; } else { return false; } }
public void step(String message) { if (step >= steps) throw new IllegalStateException("too much steps for ProgressBar " + title); if (timeEachStep && step != 0) { long newTime = System.nanoTime(); FMLLog.fine( "Bar Step: %s - %s took %.3fs", getTitle(), getMessage(), ((float) (newTime - lastTime) / 1000000 / 1000)); lastTime = newTime; } step++; this.message = FMLCommonHandler.instance().stripSpecialChars(message); FMLCommonHandler.instance().processWindowMessages(); }
private void disableRequestedMods() { String forcedModList = System.getProperty("fml.modStates", ""); FMLLog.finer("Received a system property request \'%s\'", forcedModList); Map<String, String> sysPropertyStateList = Splitter.on(CharMatcher.anyOf(";:")) .omitEmptyStrings() .trimResults() .withKeyValueSeparator("=") .split(forcedModList); FMLLog.finer( "System property request managing the state of %d mods", sysPropertyStateList.size()); Map<String, String> modStates = Maps.newHashMap(); forcedModFile = new File(canonicalConfigDir, "fmlModState.properties"); Properties forcedModListProperties = new Properties(); if (forcedModFile.exists() && forcedModFile.isFile()) { FMLLog.finer("Found a mod state file %s", forcedModFile.getName()); try { forcedModListProperties.load(new FileReader(forcedModFile)); FMLLog.finer("Loaded states for %d mods from file", forcedModListProperties.size()); } catch (Exception e) { FMLLog.log(Level.INFO, e, "An error occurred reading the fmlModState.properties file"); } } modStates.putAll(Maps.fromProperties(forcedModListProperties)); modStates.putAll(sysPropertyStateList); FMLLog.fine("After merging, found state information for %d mods", modStates.size()); Map<String, Boolean> isEnabled = Maps.transformValues( modStates, new Function<String, Boolean>() { @Override public Boolean apply(String input) { return Boolean.parseBoolean(input); } }); for (Map.Entry<String, Boolean> entry : isEnabled.entrySet()) { if (namedMods.containsKey(entry.getKey())) { FMLLog.info("Setting mod %s to enabled state %b", entry.getKey(), entry.getValue()); namedMods.get(entry.getKey()).setEnabledState(entry.getValue()); } } }
@Subscribe public void constructMod(FMLConstructionEvent event) { try { ModClassLoader modClassLoader = event.getModClassLoader(); modClassLoader.addFile(source); // MCPC - show users what mod is being loaded in case a crash occurs FMLLog.fine("Constructing mod from file source : " + source); Class<?> clazz = Class.forName(className, true, modClassLoader); ASMDataTable asmHarvestedAnnotations = event.getASMHarvestedData(); // TODO asmHarvestedAnnotations.getAnnotationsFor(this); annotations = gatherAnnotations(clazz); isNetworkMod = FMLNetworkHandler.instance().registerNetworkMod(this, clazz, event.getASMHarvestedData()); modInstance = clazz.newInstance(); ProxyInjector.inject( this, event.getASMHarvestedData(), FMLCommonHandler.instance().getSide()); processFieldAnnotations(event.getASMHarvestedData()); } catch (Throwable e) { controller.errorOccurred(this, e); Throwables.propagateIfPossible(e); } }
/** * Fire a FMLMissingMappingsEvent to let mods determine how blocks/items defined in the world * save, but missing from the runtime, are to be handled. * * @param missing Map containing missing names with their associated id, blocks need to come * before items for remapping. * @param isLocalWorld Whether this is executing for a world load (local/server) or a client. * @param gameData GameData instance where the new map's config is to be loaded into. * @return List with the mapping results. */ public List<String> fireMissingMappingEvent( LinkedHashMap<String, Integer> missingBlocks, LinkedHashMap<String, Integer> missingItems, boolean isLocalWorld, GameData gameData, Map<String, Integer[]> remapBlocks, Map<String, Integer[]> remapItems) { if (missingBlocks.isEmpty() && missingItems.isEmpty()) // nothing to do { return ImmutableList.of(); } FMLLog.fine( "There are %d mappings missing - attempting a mod remap", missingBlocks.size() + missingItems.size()); ArrayListMultimap<String, MissingMapping> missingMappings = ArrayListMultimap.create(); for (Map.Entry<String, Integer> mapping : missingBlocks.entrySet()) { MissingMapping m = new MissingMapping(GameRegistry.Type.BLOCK, mapping.getKey(), mapping.getValue()); missingMappings.put(m.name.substring(0, m.name.indexOf(':')), m); } for (Map.Entry<String, Integer> mapping : missingItems.entrySet()) { MissingMapping m = new MissingMapping(GameRegistry.Type.ITEM, mapping.getKey(), mapping.getValue()); missingMappings.put(m.name.substring(0, m.name.indexOf(':')), m); } FMLMissingMappingsEvent missingEvent = new FMLMissingMappingsEvent(missingMappings); modController.propogateStateMessage(missingEvent); if (isLocalWorld) // local world, warn about entries still being set to the default action { boolean didWarn = false; for (MissingMapping mapping : missingMappings.values()) { if (mapping.getAction() == FMLMissingMappingsEvent.Action.DEFAULT) { if (!didWarn) { FMLLog.severe( "There are unidentified mappings in this world - we are going to attempt to process anyway"); didWarn = true; } FMLLog.severe( "Unidentified %s: %s, id %d", mapping.type == Type.BLOCK ? "block" : "item", mapping.name, mapping.id); } } } else // remote world, fail on entries with the default action { List<String> missedMapping = new ArrayList<String>(); for (MissingMapping mapping : missingMappings.values()) { if (mapping.getAction() == FMLMissingMappingsEvent.Action.DEFAULT) { missedMapping.add(mapping.name); } } if (!missedMapping.isEmpty()) { return ImmutableList.copyOf(missedMapping); } } return GameData.processIdRematches( missingMappings.values(), isLocalWorld, gameData, remapBlocks, remapItems); }
/** * Called from the hook to start mod loading. We trigger the {@link #identifyMods()} and * Constructing, Preinitalization, and Initalization phases here. Finally, the mod list is frozen * completely and is consider immutable from then on. */ public void loadMods() { progressBar = ProgressManager.push("Loading", 7); progressBar.step("Constructing Mods"); initializeLoader(); mods = Lists.newArrayList(); namedMods = Maps.newHashMap(); modController = new LoadController(this); modController.transition(LoaderState.LOADING, false); discoverer = identifyMods(); ModAPIManager.INSTANCE.manageAPI(modClassLoader, discoverer); disableRequestedMods(); modController.distributeStateMessage(FMLLoadEvent.class); sortModList(); ModAPIManager.INSTANCE.cleanupAPIContainers(modController.getActiveModList()); ModAPIManager.INSTANCE.cleanupAPIContainers(mods); mods = ImmutableList.copyOf(mods); for (File nonMod : discoverer.getNonModLibs()) { if (nonMod.isFile()) { 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()); try { modClassLoader.addFile(nonMod); } catch (MalformedURLException e) { FMLLog.log( Level.ERROR, e, "Encountered a weird problem with non-mod file injection : %s", nonMod.getName()); } } } modController.transition(LoaderState.CONSTRUCTING, false); modController.distributeStateMessage( LoaderState.CONSTRUCTING, modClassLoader, discoverer.getASMTable(), reverseDependencies); List<ModContainer> mods = Lists.newArrayList(); mods.addAll(getActiveModList()); Collections.sort( mods, new Comparator<ModContainer>() { @Override public int compare(ModContainer o1, ModContainer o2) { return o1.getModId().compareTo(o2.getModId()); } }); FMLLog.fine("Mod signature data"); FMLLog.fine(" \tValid Signatures:"); for (ModContainer mod : getActiveModList()) { if (mod.getSigningCertificate() != null) FMLLog.fine( "\t\t(%s) %s\t(%s\t%s)\t%s", CertificateHelper.getFingerprint(mod.getSigningCertificate()), mod.getModId(), mod.getName(), mod.getVersion(), mod.getSource().getName()); } FMLLog.fine(" \tMissing Signatures:"); for (ModContainer mod : getActiveModList()) { if (mod.getSigningCertificate() == null) FMLLog.fine( "\t\t%s\t(%s\t%s)\t%s", mod.getModId(), mod.getName(), mod.getVersion(), mod.getSource().getName()); } if (getActiveModList().isEmpty()) { FMLLog.fine("No user mod signature data found"); } progressBar.step("Initializing mods Phase 1"); modController.transition(LoaderState.PREINITIALIZATION, false); }
/** * Sort the mods into a sorted list, using dependency information from the containers. The sorting * is performed using a {@link TopologicalSort} based on the pre- and post- dependency information * provided by the mods. */ private void sortModList() { FMLLog.finer("Verifying mod requirements are satisfied"); try { BiMap<String, ArtifactVersion> modVersions = HashBiMap.create(); for (ModContainer mod : Iterables.concat(getActiveModList(), ModAPIManager.INSTANCE.getAPIList())) { modVersions.put(mod.getModId(), mod.getProcessedVersion()); } ArrayListMultimap<String, String> reqList = ArrayListMultimap.create(); for (ModContainer mod : getActiveModList()) { if (!mod.acceptableMinecraftVersionRange() .containsVersion(minecraft.getProcessedVersion())) { 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()); throw new WrongMinecraftVersionException(mod); } Map<String, ArtifactVersion> names = Maps.uniqueIndex(mod.getRequirements(), new ArtifactVersionNameFunction()); Set<ArtifactVersion> versionMissingMods = Sets.newHashSet(); Set<String> missingMods = Sets.difference(names.keySet(), modVersions.keySet()); if (!missingMods.isEmpty()) { FMLLog.severe( "The mod %s (%s) requires mods %s to be available", mod.getModId(), mod.getName(), missingMods); for (String modid : missingMods) { versionMissingMods.add(names.get(modid)); } throw new MissingModsException(versionMissingMods, mod.getModId(), mod.getName()); } reqList.putAll(mod.getModId(), names.keySet()); ImmutableList<ArtifactVersion> allDeps = ImmutableList.<ArtifactVersion>builder() .addAll(mod.getDependants()) .addAll(mod.getDependencies()) .build(); for (ArtifactVersion v : allDeps) { if (modVersions.containsKey(v.getLabel())) { if (!v.containsVersion(modVersions.get(v.getLabel()))) { versionMissingMods.add(v); } } } if (!versionMissingMods.isEmpty()) { FMLLog.severe( "The mod %s (%s) requires mod versions %s to be available", mod.getModId(), mod.getName(), versionMissingMods); throw new MissingModsException(versionMissingMods, mod.getModId(), mod.getName()); } } FMLLog.finer("All mod requirements are satisfied"); reverseDependencies = Multimaps.invertFrom(reqList, ArrayListMultimap.<String, String>create()); ModSorter sorter = new ModSorter(getActiveModList(), namedMods); try { FMLLog.finer("Sorting mods into an ordered list"); List<ModContainer> sortedMods = sorter.sort(); // Reset active list to the sorted list modController.getActiveModList().clear(); modController.getActiveModList().addAll(sortedMods); // And inject the sorted list into the overall list mods.removeAll(sortedMods); sortedMods.addAll(mods); mods = sortedMods; FMLLog.finer("Mod sorting completed successfully"); } catch (ModSortingException sortException) { FMLLog.severe( "A dependency cycle was detected in the input mod set so an ordering cannot be determined"); SortingExceptionData<ModContainer> exceptionData = sortException.getExceptionData(); FMLLog.severe("The first mod in the cycle is %s", exceptionData.getFirstBadNode()); FMLLog.severe("The mod cycle involves"); for (ModContainer mc : exceptionData.getVisitedNodes()) { FMLLog.severe( "%s : before: %s, after: %s", mc.toString(), mc.getDependants(), mc.getDependencies()); } FMLLog.log(Level.ERROR, sortException, "The full error"); throw sortException; } } finally { FMLLog.fine("Mod sorting data"); int unprintedMods = mods.size(); for (ModContainer mod : getActiveModList()) { if (!mod.isImmutable()) { FMLLog.fine( "\t%s(%s:%s): %s (%s)", mod.getModId(), mod.getName(), mod.getVersion(), mod.getSource().getName(), mod.getSortingRules()); unprintedMods--; } } if (unprintedMods == mods.size()) { FMLLog.fine("No user mods found to sort"); } } }