嫌疑模组识别(Suspected Mods)
基本信息
| 属性 | 值 |
|---|---|
| 报告分区名 | Suspected Mods |
| 实现类 | vfyjxf.bettercrashes.utils.ModIdentifier |
| 注入点 | CrashReport.populateEnvironment TAIL(CrashReportMixin.java:82-102) |
| 界面显示 | 黄色 0xE0E000 居中,最多 3 行 |
算法
ModIdentifier.identifyFromStacktrace(Throwable)(:39-57):
- 建「jar 文件 → ModContainer」映射(
makeModMap,:89-111), 并缓存在静态字段cachedModMap,只建一次 - 沿
getCause()链广度遍历整棵异常树, 收集所有StackTraceElement.getClassName()去重成LinkedHashSet - 对每个类名:
- 跳过
org.spongepowered.asm.mixin.*(:61, 注释说明 Mixin 库被所有 mod 共享,识别出来没有意义) - 用反射调
LaunchClassLoader.untransformName(String)还原混淆后的类名 (拿 SRG 名去getResource才能找到 class 文件,:64-65, 115-125) - 解析 class 的 URL;
jar:协议则截到!之前取出 jar 路径 - 用 canonical
File去modMap里查所属 mod
- 跳过
- 合并所有命中的
ModContainer
构建 modMap 的两个排除
makeModMap(:97-99)显式跳过两个容器:
Loader.instance().getMinecraftModContainer()—— 本体 jargetIndexedModList().get("FML")—— Forge jar
注释里引用了 Forge 的 issue #4919 作为 workaround 依据。
在报告里怎么出现
CrashReportMixin.betterCrashes$afterPopulateEnvironment(:82-102)
用 addCrashSectionCallable 惰性注册,在报告被序列化时才计算:
- 每项格式为
mod.getName() + " (" + mod.getModId() + ")" - 空集时字面量写
"Unknown" - 整个 callable 被
catch (Throwable e)包裹,异常时 把异常堆栈当作返回值塞进报告
界面消费
GuiProblemScreen.getModListString()(:235-252)把
((CrashReportExt) report).betterCrashes$getSuspectedMods() 转成显示名列表:
- 字段为
null(即 callable 从未执行过)→ 显示bettercrashes.gui.common.identificationErrored= “[Error identifying mod]” - 空集 →
unknownCause= “Unknown”
⚠️ 界面这里只显示 getName(),而报告文本里是 Name (modId) ——
两处格式不同是有意为之(界面窄,报告要精确)。
源码缺陷(源码核对 2026-10-01 审计)
- ⚠️
CrashReportMixin.betterCrashes$getVanillaFixComment(:152-162)有 NPE 隐患:if (Math.random() < 0.01 && !betterCrashes$suspectedMods.isEmpty())在 callable 尚未被调用时betterCrashes$suspectedMods仍为null⇒ NPE。被catch (Throwable ignored) {}吞掉,退回原版 witty comment。 1% 的概率路径静默失效,无日志。 - ⚠️ 源码缺陷:反射
setAccessible(true)(ModIdentifier.java:119) 对LaunchClassLoader.untransformName强行开放访问。 该方法名是 1.7.10 LaunchWrapper 的内部实现, 换启动器/映射后可能不存在 ⇒NoSuchMethodException被包成RuntimeException抛出,整个识别流程失败。 - ⚠️ 源码缺陷:
identifyFromClass中url.getFile().substring(0, url.getFile().indexOf('!'))(:74) 若 URL 中没有!(非标准 jar URL),indexOf返回 -1,substring(0, -1)抛StringIndexOutOfBoundsException。 该异常不在:80的catch (URISyntaxException | IOException)覆盖范围内。 identifyFromStacktrace沿 cause 链收集,但Throwable的 suppressed 异常未被纳入(对比StacktraceDeobfuscator两者都遍历)。