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:
- 调
loadCoreMod加载其类 - 加
loadedByCommandLine标记,discoverCoreMods会跳过重复 - 解析保护域 → 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 中调用获取写入目标。
相关条目
- FMLTransformer - 触发 handleLaunch 转发
- Jar-in-Jar 嵌套 jar -
setupNestedModDir+checkNestedMods - 并行 mod 发现 - 与
ParallellModDiscoverer共享 DiscoveryPool - 重复 mod 去重 -
candidateSorter.getDisabledFiles()写 blackboard - API 所有权排序 -
candidateSorter.getClasspathOrder()写 classpath - Coremod 豁免清单 -
ModCandidateV2Sorter调用