net.minecraftforge.fml.relauncher.CoreModManager.java Source code

Java tutorial

Introduction

Here is the source code for net.minecraftforge.fml.relauncher.CoreModManager.java

Source

/*
 * Minecraft Forge
 * Copyright (c) 2016.
 *
 * 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 Software Foundation version 2.1
 * of the License.
 *
 * 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 A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * 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 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

package net.minecraftforge.fml.relauncher;

import java.io.File;
import java.io.FileFilter;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import com.google.common.io.ByteStreams;
import com.google.common.io.Files;
import net.minecraft.launchwrapper.ITweaker;
import net.minecraft.launchwrapper.Launch;
import net.minecraft.launchwrapper.LaunchClassLoader;
import net.minecraftforge.fml.common.asm.ASMTransformerWrapper;
import net.minecraftforge.fml.common.asm.transformers.ModAccessTransformer;
import net.minecraftforge.fml.common.launcher.FMLInjectionAndSortingTweaker;
import net.minecraftforge.fml.common.launcher.FMLTweaker;
import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin.DependsOn;
import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin.MCVersion;
import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin.Name;
import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin.SortingIndex;
import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin.TransformerExclusions;

import org.apache.commons.io.IOUtils;
import org.apache.logging.log4j.Level;

import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.ObjectArrays;
import com.google.common.collect.Sets;
import com.google.common.primitives.Ints;

public class CoreModManager {
    private static final Attributes.Name COREMODCONTAINSFMLMOD = new Attributes.Name("FMLCorePluginContainsFMLMod");
    private static final Attributes.Name MODTYPE = new Attributes.Name("ModType");
    private static final Attributes.Name MODSIDE = new Attributes.Name("ModSide");
    private static final Attributes.Name MODCONTAINSDEPS = new Attributes.Name("ContainedDeps");
    private static String[] rootPlugins = { "net.minecraftforge.fml.relauncher.FMLCorePlugin",
            "net.minecraftforge.classloading.FMLForgePlugin" };
    private static List<String> ignoredModFiles = Lists.newArrayList();
    private static Map<String, List<String>> transformers = Maps.newHashMap();
    private static List<FMLPluginWrapper> loadPlugins;
    private static boolean deobfuscatedEnvironment;
    private static FMLTweaker tweaker;
    private static File mcDir;
    private static List<String> candidateModFiles = Lists.newArrayList();
    private static List<String> accessTransformers = Lists.newArrayList();
    private static Set<String> rootNames = Sets.newHashSet();
    private static final List<String> skipContainedDeps = Arrays
            .asList(System.getProperty("fml.skipContainedDeps", "").split(","));

    static {
        for (String cls : rootPlugins) {
            rootNames.add(cls.substring(cls.lastIndexOf('.') + 1));
        }
    }

    private static class FMLPluginWrapper implements ITweaker {
        public final String name;
        public final IFMLLoadingPlugin coreModInstance;
        public final List<String> predepends;
        public final File location;
        public final int sortIndex;

        public FMLPluginWrapper(String name, IFMLLoadingPlugin coreModInstance, File location, int sortIndex,
                String... predepends) {
            super();
            this.name = name;
            this.coreModInstance = coreModInstance;
            this.location = location;
            this.sortIndex = sortIndex;
            this.predepends = Lists.newArrayList(predepends);
        }

        @Override
        public String toString() {
            return String.format("%s {%s}", this.name, this.predepends);
        }

        @Override
        public void acceptOptions(List<String> args, File gameDir, File assetsDir, String profile) {
            // NO OP
        }

        @Override
        public void injectIntoClassLoader(LaunchClassLoader classLoader) {
            FMLRelaunchLog.fine("Injecting coremod %s {%s} class transformers", name,
                    coreModInstance.getClass().getName());
            List<String> ts = Lists.newArrayList();
            if (coreModInstance.getASMTransformerClass() != null)
                for (String transformer : coreModInstance.getASMTransformerClass()) {
                    FMLRelaunchLog.finer("Registering transformer %s", transformer);
                    classLoader.registerTransformer(
                            ASMTransformerWrapper.getTransformerWrapper(classLoader, transformer, name));
                    ts.add(transformer);
                }
            if (!rootNames.contains(name)) {
                String loc;
                if (location == null)
                    loc = "unknown";
                else
                    loc = location.getName();
                transformers.put(name + " (" + loc + ")", ts);
            }
            FMLRelaunchLog.fine("Injection complete");

            FMLRelaunchLog.fine("Running coremod plugin for %s {%s}", name, coreModInstance.getClass().getName());
            Map<String, Object> data = new HashMap<String, Object>();
            data.put("mcLocation", mcDir);
            data.put("coremodList", loadPlugins);
            data.put("runtimeDeobfuscationEnabled", !deobfuscatedEnvironment);
            FMLRelaunchLog.fine("Running coremod plugin %s", name);
            data.put("coremodLocation", location);
            coreModInstance.injectData(data);
            String setupClass = coreModInstance.getSetupClass();
            if (setupClass != null) {
                try {
                    IFMLCallHook call = (IFMLCallHook) Class.forName(setupClass, true, classLoader).newInstance();
                    Map<String, Object> callData = new HashMap<String, Object>();
                    callData.put("runtimeDeobfuscationEnabled", !deobfuscatedEnvironment);
                    callData.put("mcLocation", mcDir);
                    callData.put("classLoader", classLoader);
                    callData.put("coremodLocation", location);
                    callData.put("deobfuscationFileName", FMLInjectionData.debfuscationDataName());
                    call.injectData(callData);
                    call.call();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
            FMLRelaunchLog.fine("Coremod plugin class %s run successfully",
                    coreModInstance.getClass().getSimpleName());

            String modContainer = coreModInstance.getModContainerClass();
            if (modContainer != null) {
                FMLInjectionData.containers.add(modContainer);
            }
        }

        @Override
        public String getLaunchTarget() {
            return "";
        }

        @Override
        public String[] getLaunchArguments() {
            return new String[0];
        }

    }

    public static void handleLaunch(File mcDir, LaunchClassLoader classLoader, FMLTweaker tweaker) {
        CoreModManager.mcDir = mcDir;
        CoreModManager.tweaker = tweaker;
        try {
            // Are we in a 'decompiled' environment?
            byte[] bs = classLoader.getClassBytes("net.minecraft.world.World");
            if (bs != null) {
                FMLRelaunchLog.info(
                        "Managed to load a deobfuscated Minecraft name- we are in a deobfuscated environment. Skipping runtime deobfuscation");
                deobfuscatedEnvironment = true;
            }
        } catch (IOException e1) {
            // NOOP
        }

        if (!deobfuscatedEnvironment) {
            FMLRelaunchLog.fine("Enabling runtime deobfuscation");
        }

        tweaker.injectCascadingTweak("net.minecraftforge.fml.common.launcher.FMLInjectionAndSortingTweaker");
        try {
            classLoader.registerTransformer("net.minecraftforge.fml.common.asm.transformers.PatchingTransformer");
        } catch (Exception e) {
            FMLRelaunchLog.log(Level.ERROR, e,
                    "The patch transformer failed to load! This is critical, loading cannot continue!");
            throw Throwables.propagate(e);
        }

        loadPlugins = new ArrayList<FMLPluginWrapper>();
        for (String rootPluginName : rootPlugins) {
            loadCoreMod(classLoader, rootPluginName, new File(FMLTweaker.getJarLocation()));
        }

        if (loadPlugins.isEmpty()) {
            throw new RuntimeException(
                    "A fatal error has occurred - no valid fml load plugin was found - this is a completely corrupt FML installation.");
        }

        FMLRelaunchLog.fine("All fundamental core mods are successfully located");
        // Now that we have the root plugins loaded - lets see what else might
        // be around
        String commandLineCoremods = System.getProperty("fml.coreMods.load", "");
        for (String coreModClassName : commandLineCoremods.split(",")) {
            if (coreModClassName.isEmpty()) {
                continue;
            }
            FMLRelaunchLog.info("Found a command line coremod : %s", coreModClassName);
            loadCoreMod(classLoader, coreModClassName, null);
        }
        discoverCoreMods(mcDir, classLoader);

    }

    private static void discoverCoreMods(File mcDir, LaunchClassLoader classLoader) {
        ModListHelper.parseModList(mcDir);
        FMLRelaunchLog.fine("Discovering coremods");
        File coreMods = setupCoreModDir(mcDir);
        FilenameFilter ff = new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return name.endsWith(".jar");
            }
        };
        FilenameFilter derpfilter = new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return name.endsWith(".jar.zip");
            }
        };
        File[] derplist = coreMods.listFiles(derpfilter);
        if (derplist != null && derplist.length > 0) {
            FMLRelaunchLog.severe(
                    "FML has detected several badly downloaded jar files,  which have been named as zip files. You probably need to download them again, or they may not work properly");
            for (File f : derplist) {
                FMLRelaunchLog.severe("Problem file : %s", f.getName());
            }
        }
        FileFilter derpdirfilter = new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                return pathname.isDirectory() && new File(pathname, "META-INF").isDirectory();
            }

        };
        File[] derpdirlist = coreMods.listFiles(derpdirfilter);
        if (derpdirlist != null && derpdirlist.length > 0) {
            FMLRelaunchLog.log.getLogger().log(Level.FATAL,
                    "There appear to be jars extracted into the mods directory. This is VERY BAD and will almost NEVER WORK WELL");
            FMLRelaunchLog.log.getLogger().log(Level.FATAL,
                    "You should place original jars only in the mods directory. NEVER extract them to the mods directory.");
            FMLRelaunchLog.log.getLogger().log(Level.FATAL,
                    "The directories below appear to be extracted jar files. Fix this before you continue.");

            for (File f : derpdirlist) {
                FMLRelaunchLog.log.getLogger().log(Level.FATAL, "Directory {} contains {}", f.getName(),
                        Arrays.asList(new File(f, "META-INF").list()));
            }

            RuntimeException re = new RuntimeException("Extracted mod jars found, loading will NOT continue");
            // We're generating a crash report for the launcher to show to the user here
            try {
                Class<?> crashreportclass = classLoader.loadClass("b");
                Object crashreport = crashreportclass.getMethod("a", Throwable.class, String.class).invoke(null, re,
                        "FML has discovered extracted jar files in the mods directory.\nThis breaks mod loading functionality completely.\nRemove the directories and replace with the jar files originally provided.");
                File crashreportfile = new File(new File(coreMods.getParentFile(), "crash-reports"),
                        String.format("fml-crash-%1$tY-%1$tm-%1$td_%1$tT.txt", Calendar.getInstance()));
                crashreportclass.getMethod("a", File.class).invoke(crashreport, crashreportfile);
                System.out.println("#@!@# FML has crashed the game deliberately. Crash report saved to: #@!@# "
                        + crashreportfile.getAbsolutePath());
            } catch (Exception e) {
                e.printStackTrace();
                // NOOP - hopefully
            }
            throw re;
        }
        File[] coreModList = coreMods.listFiles(ff);
        File versionedModDir = new File(coreMods, FMLInjectionData.mccversion);
        if (versionedModDir.isDirectory()) {
            File[] versionedCoreMods = versionedModDir.listFiles(ff);
            coreModList = ObjectArrays.concat(coreModList, versionedCoreMods, File.class);
        }

        coreModList = ObjectArrays.concat(coreModList, ModListHelper.additionalMods.values().toArray(new File[0]),
                File.class);

        coreModList = FileListHelper.sortFileList(coreModList);

        for (File coreMod : coreModList) {
            FMLRelaunchLog.fine("Examining for coremod candidacy %s", coreMod.getName());
            JarFile jar = null;
            Attributes mfAttributes;
            String fmlCorePlugin;
            try {
                jar = new JarFile(coreMod);
                if (jar.getManifest() == null) {
                    // Not a coremod and no access transformer list
                    continue;
                }
                ModAccessTransformer.addJar(jar);
                mfAttributes = jar.getManifest().getMainAttributes();
                String cascadedTweaker = mfAttributes.getValue("TweakClass");
                if (cascadedTweaker != null) {
                    FMLRelaunchLog.info("Loading tweaker %s from %s", cascadedTweaker, coreMod.getName());
                    Integer sortOrder = Ints.tryParse(Strings.nullToEmpty(mfAttributes.getValue("TweakOrder")));
                    sortOrder = (sortOrder == null ? Integer.valueOf(0) : sortOrder);
                    handleCascadingTweak(coreMod, jar, cascadedTweaker, classLoader, sortOrder);
                    ignoredModFiles.add(coreMod.getName());
                    continue;
                }
                List<String> modTypes = mfAttributes.containsKey(MODTYPE)
                        ? Arrays.asList(mfAttributes.getValue(MODTYPE).split(","))
                        : ImmutableList.of("FML");

                if (!modTypes.contains("FML")) {
                    FMLRelaunchLog.fine(
                            "Adding %s to the list of things to skip. It is not an FML mod,  it has types %s",
                            coreMod.getName(), modTypes);
                    ignoredModFiles.add(coreMod.getName());
                    continue;
                }
                String modSide = mfAttributes.containsKey(MODSIDE) ? mfAttributes.getValue(MODSIDE) : "BOTH";
                if (!("BOTH".equals(modSide) || FMLLaunchHandler.side.name().equals(modSide))) {
                    FMLRelaunchLog.fine("Mod %s has ModSide meta-inf value %s, and we're %s. It will be ignored",
                            coreMod.getName(), modSide, FMLLaunchHandler.side.name());
                    ignoredModFiles.add(coreMod.getName());
                    continue;
                }
                ModListHelper.additionalMods.putAll(extractContainedDepJars(jar, coreMods, versionedModDir));
                fmlCorePlugin = mfAttributes.getValue("FMLCorePlugin");
                if (fmlCorePlugin == null) {
                    // Not a coremod
                    FMLRelaunchLog.fine("Not found coremod data in %s", coreMod.getName());
                    continue;
                }
            } catch (IOException ioe) {
                FMLRelaunchLog.log(Level.ERROR, ioe, "Unable to read the jar file %s - ignoring",
                        coreMod.getName());
                continue;
            } finally {
                if (jar != null) {
                    try {
                        jar.close();
                    } catch (IOException e) {
                        // Noise
                    }
                }
            }
            // Support things that are mod jars, but not FML mod jars
            try {
                classLoader.addURL(coreMod.toURI().toURL());
                if (!mfAttributes.containsKey(COREMODCONTAINSFMLMOD)) {
                    FMLRelaunchLog.finer("Adding %s to the list of known coremods, it will not be examined again",
                            coreMod.getName());
                    ignoredModFiles.add(coreMod.getName());
                } else {
                    FMLRelaunchLog.finer(
                            "Found FMLCorePluginContainsFMLMod marker in %s, it will be examined later for regular @Mod instances",
                            coreMod.getName());
                    candidateModFiles.add(coreMod.getName());
                }
            } catch (MalformedURLException e) {
                FMLRelaunchLog.log(Level.ERROR, e, "Unable to convert file into a URL. weird");
                continue;
            }
            loadCoreMod(classLoader, fmlCorePlugin, coreMod);
        }
    }

    private static Map<String, File> extractContainedDepJars(JarFile jar, File baseModsDir, File versionedModsDir)
            throws IOException {
        Map<String, File> result = Maps.newHashMap();
        if (!jar.getManifest().getMainAttributes().containsKey(MODCONTAINSDEPS))
            return result;

        String deps = jar.getManifest().getMainAttributes().getValue(MODCONTAINSDEPS);
        String[] depList = deps.split(" ");
        for (String dep : depList) {
            String depEndName = new File(dep).getName(); // extract last part of name
            if (skipContainedDeps.contains(dep) || skipContainedDeps.contains(depEndName)) {
                FMLRelaunchLog.log(Level.ERROR, "Skipping dep at request: %s", dep);
                continue;
            }
            final JarEntry jarEntry = jar.getJarEntry(dep);
            if (jarEntry == null) {
                FMLRelaunchLog.log(Level.ERROR, "Found invalid ContainsDeps declaration %s in %s", dep,
                        jar.getName());
                continue;
            }
            File target = new File(versionedModsDir, depEndName);
            File modTarget = new File(baseModsDir, depEndName);
            if (target.exists()) {
                FMLRelaunchLog.log(Level.DEBUG, "Found existing ContainsDep extracted to %s, skipping extraction",
                        target.getCanonicalPath());
                result.put(dep, target);
                continue;
            } else if (modTarget.exists()) {
                FMLRelaunchLog.log(Level.DEBUG,
                        "Found ContainsDep in main mods directory at %s, skipping extraction",
                        modTarget.getCanonicalPath());
                result.put(dep, modTarget);
                continue;
            }

            FMLRelaunchLog.log(Level.DEBUG, "Extracting ContainedDep %s from %s to %s", dep, jar.getName(),
                    target.getCanonicalPath());
            try {
                Files.createParentDirs(target);
                FileOutputStream targetOutputStream = null;
                InputStream jarInputStream = null;
                try {
                    targetOutputStream = new FileOutputStream(target);
                    jarInputStream = jar.getInputStream(jarEntry);
                    ByteStreams.copy(jarInputStream, targetOutputStream);
                } finally {
                    IOUtils.closeQuietly(targetOutputStream);
                    IOUtils.closeQuietly(jarInputStream);
                }
                FMLRelaunchLog.log(Level.DEBUG, "Extracted ContainedDep %s from %s to %s", dep, jar.getName(),
                        target.getCanonicalPath());
                result.put(dep, target);
            } catch (IOException e) {
                FMLRelaunchLog.log(Level.ERROR, e, "An error occurred extracting dependency");
            }
        }
        return result;
    }

    private static Method ADDURL;

    private static void handleCascadingTweak(File coreMod, JarFile jar, String cascadedTweaker,
            LaunchClassLoader classLoader, Integer sortingOrder) {
        try {
            // Have to manually stuff the tweaker into the parent classloader
            if (ADDURL == null) {
                ADDURL = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
                ADDURL.setAccessible(true);
            }
            ADDURL.invoke(classLoader.getClass().getClassLoader(), coreMod.toURI().toURL());
            classLoader.addURL(coreMod.toURI().toURL());
            CoreModManager.tweaker.injectCascadingTweak(cascadedTweaker);
            tweakSorting.put(cascadedTweaker, sortingOrder);
        } catch (Exception e) {
            FMLRelaunchLog.log(Level.INFO, e, "There was a problem trying to load the mod dir tweaker %s",
                    coreMod.getAbsolutePath());
        }
    }

    /**
     * @param mcDir
     *            the minecraft home directory
     * @return the coremod directory
     */
    private static File setupCoreModDir(File mcDir) {
        File coreModDir = new File(mcDir, "mods");
        try {
            coreModDir = coreModDir.getCanonicalFile();
        } catch (IOException e) {
            throw new RuntimeException(
                    String.format("Unable to canonicalize the coremod dir at %s", mcDir.getName()), e);
        }
        if (!coreModDir.exists()) {
            coreModDir.mkdir();
        } else if (coreModDir.exists() && !coreModDir.isDirectory()) {
            throw new RuntimeException(
                    String.format("Found a coremod file in %s that's not a directory", mcDir.getName()));
        }
        return coreModDir;
    }

    public static List<String> getIgnoredMods() {
        return ignoredModFiles;
    }

    public static Map<String, List<String>> getTransformers() {
        return transformers;
    }

    public static List<String> getReparseableCoremods() {
        return candidateModFiles;
    }

    private static FMLPluginWrapper loadCoreMod(LaunchClassLoader classLoader, String coreModClass, File location) {
        String coreModName = coreModClass.substring(coreModClass.lastIndexOf('.') + 1);
        try {
            FMLRelaunchLog.fine("Instantiating coremod class %s", coreModName);
            classLoader.addTransformerExclusion(coreModClass);
            Class<?> coreModClazz = Class.forName(coreModClass, true, classLoader);
            Name coreModNameAnn = coreModClazz.getAnnotation(IFMLLoadingPlugin.Name.class);
            if (coreModNameAnn != null && !Strings.isNullOrEmpty(coreModNameAnn.value())) {
                coreModName = coreModNameAnn.value();
                FMLRelaunchLog.finer("coremod named %s is loading", coreModName);
            }
            MCVersion requiredMCVersion = coreModClazz.getAnnotation(IFMLLoadingPlugin.MCVersion.class);
            if (!Arrays.asList(rootPlugins).contains(coreModClass)
                    && (requiredMCVersion == null || Strings.isNullOrEmpty(requiredMCVersion.value()))) {
                FMLRelaunchLog.log(Level.WARN,
                        "The coremod %s does not have a MCVersion annotation, it may cause issues with this version of Minecraft",
                        coreModClass);
            } else if (requiredMCVersion != null
                    && !FMLInjectionData.mccversion.equals(requiredMCVersion.value())) {
                FMLRelaunchLog.log(Level.ERROR,
                        "The coremod %s is requesting minecraft version %s and minecraft is %s. It will be ignored.",
                        coreModClass, requiredMCVersion.value(), FMLInjectionData.mccversion);
                return null;
            } else if (requiredMCVersion != null) {
                FMLRelaunchLog.log(Level.DEBUG,
                        "The coremod %s requested minecraft version %s and minecraft is %s. It will be loaded.",
                        coreModClass, requiredMCVersion.value(), FMLInjectionData.mccversion);
            }
            TransformerExclusions trExclusions = coreModClazz
                    .getAnnotation(IFMLLoadingPlugin.TransformerExclusions.class);
            if (trExclusions != null) {
                for (String st : trExclusions.value()) {
                    classLoader.addTransformerExclusion(st);
                }
            }
            DependsOn deplist = coreModClazz.getAnnotation(IFMLLoadingPlugin.DependsOn.class);
            String[] dependencies = new String[0];
            if (deplist != null) {
                dependencies = deplist.value();
            }
            SortingIndex index = coreModClazz.getAnnotation(IFMLLoadingPlugin.SortingIndex.class);
            int sortIndex = index != null ? index.value() : 0;

            IFMLLoadingPlugin plugin = (IFMLLoadingPlugin) coreModClazz.newInstance();
            String accessTransformerClass = plugin.getAccessTransformerClass();
            if (accessTransformerClass != null) {
                FMLRelaunchLog.log(Level.DEBUG, "Added access transformer class %s to enqueued access transformers",
                        accessTransformerClass);
                accessTransformers.add(accessTransformerClass);
            }
            FMLPluginWrapper wrap = new FMLPluginWrapper(coreModName, plugin, location, sortIndex, dependencies);
            loadPlugins.add(wrap);
            FMLRelaunchLog.fine("Enqueued coremod %s", coreModName);
            return wrap;
        } catch (ClassNotFoundException cnfe) {
            if (!Lists.newArrayList(rootPlugins).contains(coreModClass))
                FMLRelaunchLog.log(Level.ERROR, cnfe, "Coremod %s: Unable to class load the plugin %s", coreModName,
                        coreModClass);
            else
                FMLRelaunchLog.fine("Skipping root plugin %s", coreModClass);
        } catch (ClassCastException cce) {
            FMLRelaunchLog.log(Level.ERROR, cce,
                    "Coremod %s: The plugin %s is not an implementor of IFMLLoadingPlugin", coreModName,
                    coreModClass);
        } catch (InstantiationException ie) {
            FMLRelaunchLog.log(Level.ERROR, ie, "Coremod %s: The plugin class %s was not instantiable", coreModName,
                    coreModClass);
        } catch (IllegalAccessException iae) {
            FMLRelaunchLog.log(Level.ERROR, iae, "Coremod %s: The plugin class %s was not accessible", coreModName,
                    coreModClass);
        }
        return null;
    }

    public static void injectTransformers(LaunchClassLoader classLoader) {

        Launch.blackboard.put("fml.deobfuscatedEnvironment", deobfuscatedEnvironment);
        tweaker.injectCascadingTweak("net.minecraftforge.fml.common.launcher.FMLDeobfTweaker");
        tweakSorting.put("net.minecraftforge.fml.common.launcher.FMLDeobfTweaker", 1000);
    }

    public static void injectCoreModTweaks(FMLInjectionAndSortingTweaker fmlInjectionAndSortingTweaker) {
        @SuppressWarnings("unchecked")
        List<ITweaker> tweakers = (List<ITweaker>) Launch.blackboard.get("Tweaks");
        // Add the sorting tweaker first- it'll appear twice in the list
        tweakers.add(0, fmlInjectionAndSortingTweaker);
        for (FMLPluginWrapper wrapper : loadPlugins) {
            tweakers.add(wrapper);
        }
    }

    private static Map<String, Integer> tweakSorting = Maps.newHashMap();

    public static void sortTweakList() {
        @SuppressWarnings("unchecked")
        List<ITweaker> tweakers = (List<ITweaker>) Launch.blackboard.get("Tweaks");
        // Basically a copy of Collections.sort pre 8u20, optimized as we know we're an array list.
        // Thanks unhelpful fixer of http://bugs.java.com/view_bug.do?bug_id=8032636
        ITweaker[] toSort = tweakers.toArray(new ITweaker[tweakers.size()]);
        Arrays.sort(toSort, new Comparator<ITweaker>() {
            @Override
            public int compare(ITweaker o1, ITweaker o2) {
                Integer first = null;
                Integer second = null;
                if (o1 instanceof FMLInjectionAndSortingTweaker) {
                    first = Integer.MIN_VALUE;
                }
                if (o2 instanceof FMLInjectionAndSortingTweaker) {
                    second = Integer.MIN_VALUE;
                }

                if (o1 instanceof FMLPluginWrapper) {
                    first = ((FMLPluginWrapper) o1).sortIndex;
                } else if (first == null) {
                    first = tweakSorting.get(o1.getClass().getName());
                }
                if (o2 instanceof FMLPluginWrapper) {
                    second = ((FMLPluginWrapper) o2).sortIndex;
                } else if (second == null) {
                    second = tweakSorting.get(o2.getClass().getName());
                }
                if (first == null) {
                    first = 0;
                }
                if (second == null) {
                    second = 0;
                }

                return Ints.saturatedCast((long) first - (long) second);
            }
        });
        // Basically a copy of Collections.sort, optimized as we know we're an array list.
        // Thanks unhelpful fixer of http://bugs.java.com/view_bug.do?bug_id=8032636
        for (int j = 0; j < toSort.length; j++) {
            tweakers.set(j, toSort[j]);
        }
    }

    public static List<String> getAccessTransformers() {
        return accessTransformers;
    }

    public static void onCrash(StringBuilder builder) {
        if (!ignoredModFiles.isEmpty() || !candidateModFiles.isEmpty()) {
            builder.append("\nWARNING: coremods are present:\n");
            for (String coreMod : transformers.keySet()) {
                builder.append("  ").append(coreMod).append('\n');
            }
            builder.append("Contact their authors BEFORE contacting forge\n\n");
        }
    }
}