CoreModManagerV2

基本信息

属性 值
类 com.mitchej123.jarjar.fml.relauncher.CoreModManagerV2
父类 cpw.mods.fml.relauncher.CoreModManager
NESTED_DIR <mc>/nestedmods/
DISABLED_FILES blackboard key jarjar.disabledFiles
COREMOD_COMPARATOR nullsFirst(comparing(getSortOrder).thenComparing(getFilename, CASE_INSENSITIVE_ORDER).thenComparing(getVersion).thenComparing(getNestLevel))
loadedByCommandLine Set<String> 已通过 -Dfml.coreMods.load= 加载的 coremod

CoreModManagerV2 替代 FML 原版 CoreModManager,提供 Jar-in-Jar 抽取、并行 coremod 解析、SortingIndex 覆写、coremod 豁免等 JarJar 专有特性。所有功能由 FMLTransformer.replaceHandleLaunchMethod 触发:原 CoreModManager.handleLaunch(File, LaunchClassLoader, FMLTweaker) 被替换为 INVOKESTATIC CoreModManagerV2.handleLaunch(...)。

handleLaunch 流程

public static void handleLaunch(File mcDir, LaunchClassLoader classLoader, FMLTweaker tweaker) {
    CoreModManager.mcDir = mcDir;
    CoreModManager.tweaker = tweaker;

    modDir = setupCoreModDir(mcDir);
    nestedDir = setupNestedModDir(mcDir);

    try {
        if (classLoader.getClassBytes("net.minecraft.world.World") != null) {
            FMLRelaunchLog.info("Managed to load a deobfuscated Minecraft name- we are in a deobfuscated environment. Skipping runtime deobfuscation");
            CoreModManager.deobfuscatedEnvironment = true;
        }
    } catch (IOException ignored) {}

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

    loadForgeCoreMods(classLoader, tweaker);
    loadCommandLineCoremods(mcDir, classLoader);
    discoverCoreMods(mcDir, classLoader);
    loadTweakersAndCoreMods(mcDir, classLoader);
}

FMLTransformer 把 CoreModManager.handleLaunch 整段方法体替换为对 CoreModManagerV2.handleLaunch 的直接转发。

loadForgeCoreMods

private static void loadForgeCoreMods(LaunchClassLoader classLoader, FMLTweaker tweaker) {
    tweaker.injectCascadingTweak("cpw.mods.fml.common.launcher.FMLInjectionAndSortingTweaker");

    try {
        classLoader.registerTransformer("cpw.mods.fml.common.asm.transformers.PatchingTransformer");
    } catch (Exception e) {
        throw Throwables.propagate(e);
    }

    try {
        Field field = CoreModManager.class.getDeclaredField("loadPlugins");
        field.setAccessible(true);
    } catch (NoSuchFieldException e) {
        throw new RuntimeException(e);
    }
    CoreModManager.loadPlugins = new ArrayList<>();

    try {
        Field field = CoreModManagerV2.class.getSuperclass().getDeclaredField("rootPlugins");
        field.setAccessible(true);
    } catch (NoSuchFieldException e) {
        throw new RuntimeException(e);
    }

    for (String rootPluginName : CoreModManager.rootPlugins) {
        loadCoreMod(classLoader, rootPluginName, new File(FMLTweaker.getJarLocation()));
    }

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

反射检查父类字段存在性,不重新赋值;强制 loadPlugins = new ArrayList<>() 让后续插件加载可见。

loadCommandLineCoremods

private static void loadCommandLineCoremods(File mcDir, LaunchClassLoader classLoader) {
    final 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);
        final CoreModManager.FMLPluginWrapper wrap = loadCoreMod(classLoader, coreModClassName, null);
        if (wrap != null) {
            loadedByCommandLine.add(coreModClassName);
            final URL coreModLocation = wrap.coreModInstance.getClass().getProtectionDomain().getCodeSource().getLocation();
            final String jarPath = coreModLocation.getPath();

            if (jarPath.endsWith(".jar")) {
                try (final JarFile jar = new JarFile(jarPath)) {
                    final File coreModFile = new File(jarPath);
                    final ModCandidateV2 modCandidate = new ModCandidateV2(coreModFile, coreModFile, ContainerType.JAR, false, true);
                    JarUtil.checkNestedMods(jar, modCandidate, true);
                } catch (IOException e) {
                    FMLRelaunchLog.log(Level.ERROR, e, "Unable to read the jar file %s - ignoring", jarPath);
                }
            }
        }
    }
}

对每个命令行 coremod:

  1. 调 loadCoreMod 加载其类
  2. 加 loadedByCommandLine 标记,discoverCoreMods 会跳过重复
  3. 解析保护域 → jar 路径 → 调 JarUtil.checkNestedMods 把嵌套 jar 抽取到 nestedDir

discoverCoreMods

public static void discoverCoreMods(File mcDir, LaunchClassLoader classLoader) {
    ModListHelper.parseModList(mcDir);
    FMLRelaunchLog.fine("Discovering coremods");

    final FilenameFilter ff = (dir, name) -> name.endsWith(".jar");
    checkDerps(modDir, mcDir, classLoader);

    File[] coreModList = Objects.requireNonNull(modDir.listFiles(ff));
    File versionedModDir = new File(modDir, FMLInjectionData.mccversion);
    if (versionedModDir.isDirectory()) {
        File[] versionedCoreMods = Objects.requireNonNull(versionedModDir.listFiles(ff));
        coreModList = ObjectArrays.concat(coreModList, versionedCoreMods, File.class);
    }

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

    FileListHelper.sortFileList(coreModList);

    final List<File> modFilesToExample = new ArrayList<>(Arrays.asList(coreModList));
    final ForkJoinPool pool = DiscoveryPool.get();
    final List<Future<ModCandidateV2>> futures = new ArrayList<>(modFilesToExample.size());
    for (File modFile : modFilesToExample) {
        futures.add(pool.submit(() -> JarUtil.examineModCandidate(modFile, null, true)));
    }
    // ... future.get 收割 ...
}
  • 并行 coremod 解析用 DiscoveryPool.get()
  • checkDerps 检测 .jar.zip 与解压到 mods 目录的子目录
  • 收集 modsDir + versionedModDir + ModListHelper.additionalMods 全部候选

loadTweakersAndCoreMods

private static void loadTweakersAndCoreMods(File mcDir, LaunchClassLoader classLoader) {
    final ModCandidateV2Sorter<ModCandidateV2> candidateSorter = new ModCandidateV2Sorter<>(modCandidates, COREMOD_COMPARATOR);
    final Optional<List<ModCandidateV2>> resolvedCandidates = candidateSorter.resolve();
    Launch.blackboard.put(DISABLED_FILES, candidateSorter.getDisabledFiles());

    if (!resolvedCandidates.isPresent()) {
        FMLRelaunchLog.log(Level.ERROR, "There was a critical error during coremod resolution, check the log for details");
        return;
    }
    modCandidates.clear();
    modCandidates.addAll(resolvedCandidates.get());

    for (ModCandidateV2 candidate : candidateSorter.getClasspathOrder()) {
        final File jar = candidate.getModContainer();
        if (jar == null) continue;
        try {
            addUrlToLoaderAndParent(classLoader, jar.toURI().toURL());
        } catch (MalformedURLException e) {
            FMLRelaunchLog.log(Level.WARN, e, "Skipping bad URL for %s", jar);
        }
    }

    // ... injectMixinAndTweaker + handleCascadingTweak + loadCoreMod ...
}

把 getDisabledFiles() 写到 Launch.blackboard[DISABLED_FILES] → DirectoryModCandidateFinder.getDisabledFiles() 读取,避免重复加载。

addUrlToLoaderAndParent

private static final MethodHandle ADD_URL_HANDLE;
static {
    MethodHandle handle = null;
    try {
        final Method m = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
        m.setAccessible(true);
        handle = MethodHandles.lookup().unreflect(m);
    } catch (Throwable t) {
        FMLRelaunchLog.log(Level.WARN, t, "URLClassLoader.addURL handle unavailable; parent-addURL will be skipped");
    }
    ADD_URL_HANDLE = handle;
}

private static void addUrlToLoaderAndParent(LaunchClassLoader classLoader, URL url) {
    final ClassLoader parent = classLoader.getClass().getClassLoader();
    if (ADD_URL_HANDLE != null && parent instanceof URLClassLoader) {
        try {
            ADD_URL_HANDLE.invokeExact((URLClassLoader) parent, url);
        } catch (Throwable t) {
            FMLRelaunchLog.log(Level.WARN, t, "addURL on parent failed for %s", url);
        }
    }
    classLoader.addURL(url);
}

把 jar 同时加进 LaunchClassLoader 及其父 URLClassLoader,避免某些 mod 通过 getClass().getClassLoader() 拿不到 jar。

checkDerps

private static void checkDerps(File coreMods, File mcDir, LaunchClassLoader classLoader) {
    final FilenameFilter derpfilter = (dir, name) -> name.endsWith(".jar.zip");
    final File[] derplist = coreMods.listFiles(derpfilter);
    if (derplist != null && derplist.length > 0) {
        FMLRelaunchLog.severe("FML has detected several badly downloaded jar files... ");
        for (File f : derplist) FMLRelaunchLog.severe("Problem file : %s", f.getName());
    }
    final FileFilter derpdirfilter = pathname -> pathname.isDirectory() && new File(pathname, "META-INF").isDirectory();
    final File[] derpdirlist = coreMods.listFiles(derpdirfilter);
    if (derpdirlist != null && derpdirlist.length > 0) {
        // 强制生成 crash report
        try {
            Class<?> crashreportclass = classLoader.loadClass("b");
            Object crashreport = crashreportclass.getMethod("a", Throwable.class, String.class).invoke(null, re, "FML has discovered extracted jar files...");
            // 写到 crash-reports/fml-crash-<date>.txt
        } catch (Exception e) { e.printStackTrace(); }
        throw re;
    }
}

检测两种"坏"情况:

  • *.jar.zip 双扩展名(部分浏览器下载错误)
  • 解压到 mods 目录的子目录(破坏 jar 加载机制)—— 触发强制 crash

嵌套目录

private static File setupNestedModDir(File mcDir) {
    File nestedModDir = new File(mcDir, NESTED_DIR);
    try {
        nestedModDir = nestedModDir.getCanonicalFile();
    } catch (IOException e) {
        throw new RuntimeException(String.format("Unable to canonicalize the nested jar dir at %s", mcDir.getName()), e);
    }

    if (!nestedModDir.exists()) {
        nestedModDir.mkdir();
    } else if (!nestedModDir.isDirectory()) {
        throw new RuntimeException(String.format("Found a nested jar file in %s that's not a directory", mcDir.getName()));
    }
    return nestedModDir;
}

public static File getModDir() { return modDir; }
public static File getNestedDir() {
    if (nestedDir == null) nestedDir = setupNestedModDir(mcDir);
    return nestedDir;
}

NESTED_DIR = "nestedmods"。getNestedDir() 在 JarUtil.checkNestedJars 中调用获取写入目标。

相关条目