FMLTransformer
基本信息
| 属性 | 值 |
|---|---|
| 类 | com.mitchej123.jarjar.rfb.transformer.FMLTransformer |
| Transformer ID | jarjar-fmltransformer-v2 |
| RFB 排序 | sortBefore = ["*"] |
| 额外排除包 | com.mitchej123.jarjar.fml. |
| 目标类(常量化扫描) | CoreModManager, MetadataCollection, ASMModParser |
FMLTransformer 完成两类操作:
- 整段重写:
CoreModManager.handleLaunch(File, LaunchClassLoader, FMLTweaker)方法体 → 直接INVOKESTATIC CoreModManagerV2.handleLaunch(...) - 调用重定向:所有对
CoreModManager/MetadataCollection的静态调用 →CoreModManagerV2/MetadataCollectionV2;对ASMModParser构造函数 →ASMModParserV2
常量池预扫
private static final String[] CLASS_CONSTANTS = new String[] { CORE_MOD_MANAGER, METADATA_COLLECTION, ASM_MOD_PARSER };
private static final ClassConstantPoolParser cstPoolParser = new ClassConstantPoolParser(CLASS_CONSTANTS);
public boolean shouldTransformClass(...) {
return cstPoolParser.find(classNode.getOriginalBytes(), false);
}
用 gtnhlib 的 ClassConstantPoolParser 在不解码整个 class 的情况下快速判断是否需要 transform。
handleLaunch 重写
if (isCoreModManager && HANDLE_LAUNCH_METHOD.equals(mn.name) && mn.desc.equals(HANDLE_LAUNCH_DESC)) {
replaceHandleLaunchMethod(mn);
continue; // 跳过常规重定向
}
replaceHandleLaunchMethod:
mn.instructions.clear();
mn.tryCatchBlocks.clear();
mn.localVariables = null;
InsnList newInstructions = new InsnList();
newInstructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // File mcDir
newInstructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // LaunchClassLoader
newInstructions.add(new VarInsnNode(Opcodes.ALOAD, 2)); // FMLTweaker
newInstructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, CORE_MOD_MANAGER_V2, HANDLE_LAUNCH_METHOD, mn.desc, false));
newInstructions.add(new InsnNode(Opcodes.RETURN));
mn.instructions = newInstructions;
mn.maxStack = 3;
mn.maxLocals = 3;
直接把整个方法替换为对 CoreModManagerV2.handleLaunch 的三参数转发,maxStack / maxLocals 设为 3。
ASMModParser 构造函数补丁
if (isAsmModParser && "<init>".equals(mn.name) && "(Ljava/io/InputStream;)V".equals(mn.desc)) {
patchAsmModParserCtor(mn);
continue;
}
patchAsmModParserCtor 在构造里找到对 org.objectweb.asm.ClassReader.accept(ClassVisitor, int) 的调用,把第二个参数从 ICONST_0(即 0)改成 ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES:
private static final int ASM_PARSE_SKIP_FLAGS = ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
private static void patchAsmModParserCtor(MethodNode mn) {
for (int i = 0; i < mn.instructions.size(); i++) {
AbstractInsnNode insn = mn.instructions.get(i);
if (insn.getOpcode() != Opcodes.INVOKEVIRTUAL || !(insn instanceof MethodInsnNode mInsn)) continue;
if (!CLASS_READER.equals(mInsn.owner) || !"accept".equals(mInsn.name) || !CLASS_VISITOR_ACCEPT_DESC.equals(mInsn.desc)) continue;
AbstractInsnNode flagsInsn = insn.getPrevious();
if (flagsInsn == null || flagsInsn.getOpcode() != Opcodes.ICONST_0) return;
mn.instructions.set(flagsInsn, new IntInsnNode(Opcodes.BIPUSH, ASM_PARSE_SKIP_FLAGS));
return;
}
}
效果:FML 的 ASMModParser 不再读 .class 文件的 code 段,加速 Mod 发现阶段。原版 FML 的 new ASMModParser(stream) 会调 ClassReader.accept(discoverer, 0),把整个 .class 加载进内存。JarJar 把 0 改成 SKIP_CODE | SKIP_DEBUG | SKIP_FRAMES 后只读常量池与 annotation。
常规重定向
for (AbstractInsnNode aInsn : mn.instructions) {
if (aInsn.getOpcode() == Opcodes.INVOKESTATIC && aInsn instanceof MethodInsnNode mInsn) {
if (mInsn.owner.equals(CORE_MOD_MANAGER)) mInsn.owner = CORE_MOD_MANAGER_V2;
else if (mInsn.owner.equals(METADATA_COLLECTION)) mInsn.owner = METADATA_COLLECTION_V2;
}
}
CoreModManager.someStaticMethod() → CoreModManagerV2.someStaticMethod();MetadataCollection.someStaticMethod() → MetadataCollectionV2.someStaticMethod()。仅改 INVOKESTATIC 的 owner,方法名/描述符不变,要求 V2 版本签名兼容。
排除包
public @NotNull String @Nullable [] additionalExclusions() {
return new String[]{"com.mitchej123.jarjar.fml."};
}
防止 JarJar 自己的 V2 类被自身 transform 二次修改。
相关条目
- RFB 插件入口 - 注册该 transformer
- EarlyAccessTransformer - 先于此 transformer 把可见性打开
- V2ConstructionReplacerTransformer - 后续重写 new 表达式
- CoreModManagerV2 - INVOKESTATIC 重定向目标
- MetadataCollectionV2 - INVOKESTATIC 重定向目标
- ASMModParserV2 - 接受
accept()跳过参数后的输入