1.3.2-1.5.2 FML CoreMod大事记

  • 1.3.2 FML重写了Mod加载机制
    • 这一Mod加载机制被持续使用到了1.12.2
    • 加入了基于FML Relauncher的CoreMod机制

加载流程

(对加载流程不感兴趣的话可以直接跳过这一部分,以下代码来自1.5.2FML)

非常感谢您能耐心看到本章,这一章的主要目的是从FML CoreMod的起源来探究这一机制是如何一脉相承的。在这些版本中,没有LaunchWrapper、ModLauncher等专门负责ClassLoader、Transformer的库,所有相关的行为都需要FML自行处理,这些版本的实践也为LaunchWrapper等库的设计奠定了基础。

Forge从FML CoreMod起源开始就没有提供文档与教程,我们需要自行探索。

RelaunchLibraryManager中,FML进行了如下操作:

  • discoverCoreMods方法中,FML会遍历coremods文件夹中的所有jar

    File coreMods = setupCoreModDir(mcDir);
    FilenameFilter ff = new FilenameFilter()
    {
      @Override
      public boolean accept(File dir, String name)
      {
          return name.endsWith(".jar");
      }
    };
    ...
    for (File coreMod : coreModList)
    
  • 随后读取jar中的Manifest,并判断是否存在FMLCorePlugin

    mfAttributes = jar.getManifest().getMainAttributes();
    ...
    String fmlCorePlugin = mfAttributes.getValue("FMLCorePlugin");
    if (fmlCorePlugin == null)
    {
      FMLRelaunchLog.severe("The coremod %s does not contain a valid jar manifest- it will be ignored", coreMod.getName());
      continue;
    }
    
  • 将CoreMod加入ClassPath,并获得IFMLLoadingPlugin实例加入列表loadPlugins

    classLoader.addURL(coreMod.toURI().toURL());
    ...
    Class<?> coreModClass = Class.forName(fmlCorePlugin, true, classLoader);
    ...
    IFMLLoadingPlugin plugin = (IFMLLoadingPlugin) coreModClass.newInstance();
    loadPlugins.add(plugin);
    
  • handleLaunch方法(长到可怕)中,遍历loadPlugins,并调用getASMTransformerClass读取IClassTransformer的类,并加入到RelaunchClassLoadertransformers

    for (IFMLLoadingPlugin plug : loadPlugins)
    {
      if (plug.getASMTransformerClass()!=null)
      {
          for (String xformClass : plug.getASMTransformerClass())
          {
              actualClassLoader.registerTransformer(xformClass);
          }
      }
    }
    

当一个类被加载时,RelaunchClassLoader将会进行如下操作:

  • findClass方法中,通过runTransformers间接逐个调用transforms
    byte[] transformedClass = runTransformers(untransformedName, transformedName, basicClass);
    
    for (IClassTransformer transformer : transformers)
    {
      basicClass = transformer.transform(name, transformedName, basicClass);
    }
    

制作方法

(以下代码适用于1.3.2-1.5.2)

通过分析,我们确定了三个关键信息——Manifest、IFMLLoadingPluginIClassTransformer,以下逐个介绍。

Manifest

Manifest有清单的意思,在这里指的是jar中的META-INF/MANIFEST.MF文件,需要在这个文件中加入如下内容:

FMLCorePlugin: com.example.ExamplePlugin
  • FMLCorePlugin属性指定了IFMLLoadingPlugin的类名

IFMLLoadingPlugin

IFMLLoadingPlugin是FML提供的一个接口,需要实现以下几个方法:

  • getLibraryRequestClass,返回依赖类
  • getASMTransformerClass,返回IClassTransformer的完整类名构成的数组,这是CoreMod的关键
  • getSetupClass,返回IFMLCallhook的实现类
  • injectData,可以获得mcLocationcoremodListcoremodLocation,分别是Minecraft文件夹File、CoreMod列表List、当前CoreMod文件File。这个方法与IFMLCallhook中的injectData主要区别为这个方法是在Minecraft启动后调用的,可以操作Minecraft的class

同时,提供了以下几个注解来进行信息补充:

  • TransformerExclusions,指定不被CoreMod修改的包名前缀数组,例如指定了com.example后,com.example.something.Example也不会被修改
  • MCVersion,指定CoreMod适用的Minecraft版本

一个简单的IFMLLoadingPlugin实现如下:

package com.example;

import java.util.Map;

import cpw.mods.fml.relauncher.IFMLLoadingPlugin;

public class ExamplePlugin implements IFMLLoadingPlugin {

    @Override
    public String[] getLibraryRequestClass() {
        return null;
    }

    @Override
    public String[] getASMTransformerClass() {
        return new String[]{"com.example.ClassTransformer"};
    }

    @Override
    public String getModContainerClass() {
        return null;
    }

    @Override
    public String getSetupClass() {
        return null;
    }

    @Override
    public void injectData(Map<String, Object> data) {
    }

}

IClassTransformer

IClassTransformer是FML提供的接口,需要实现以下方法:

  • transform,接收nametransformedNamebytes三个参数,分别是原类名、mcp无混淆类名和class文件的byte数组,需要返回修改后的class文件的byte数组

需要特别注意的是:

  • name原类名和bytes class文件,在游戏运行时为notch混淆,开发环境测试时为mcp混淆
  • bytes可能已被其他CoreMod甚至Forge本身修改过
  • 切记无论如何都要返回一个有效的byte数组,否则会导致ClassNotFoundExceptionNoClassDefFoundError等导致的崩溃

一个没有对class进行任何修改的IClassTransformer实现如下:

package com.example;

import cpw.mods.fml.relauncher.IClassTransformer;

public class ClassTransformer implements IClassTransformer {
    public byte[] transform(String name, String transformedName, byte[] bytes) {
        return bytes;//特别注意需要返回bytes
    }
}

一个对net.minecraft.src.GuiPlayerTabOverlaya(notch)方法进行修改的实例:

package com.example;

import cpw.mods.fml.relauncher.IClassTransformer;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;

public class ClassTransformer implements IClassTransformer {
    public byte[] transform(String name, String transformedName, byte[] bytes) {
        if (!"net.minecraft.src.GuiPlayerTabOverlay".equals(transformedName))
            return bytes;

        //使用ASM读入bytes
        ClassReader cr = new ClassReader(bytes);
        ClassNode cn = new ClassNode();
        cr.accept(cn, 0);

        //遍历methods
        for (MethodNode mn : cn.methods) {
            if(!"a".equals(mn.name)) 
                continue;

            //TODO: 在这里进行ASM操作
        }

        //返回修改后的bytes
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
        cn.accept(cw);
        return cw.toByteArray();
    }
}

results matching ""

    No results matching ""