FMLTransformer

基本信息

属性 值
类 com.mitchej123.jarjar.rfb.transformer.FMLTransformer
Transformer ID jarjar-fmltransformer-v2
RFB 排序 sortBefore = ["*"]
额外排除包 com.mitchej123.jarjar.fml.
目标类(常量化扫描) CoreModManager, MetadataCollection, ASMModParser

FMLTransformer 完成两类操作:

  1. 整段重写:CoreModManager.handleLaunch(File, LaunchClassLoader, FMLTweaker) 方法体 → 直接 INVOKESTATIC CoreModManagerV2.handleLaunch(...)
  2. 调用重定向:所有对 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 二次修改。

相关条目