# 1.6.1-1.12.2

## 1.6.1-1.12.2 FML CoreMod大事记

* 1.6 修改了游戏文件的结构，单一.minecraft文件夹内可以同时使用多个游戏版本
* 1.6 引入[LaunchWrapper](https://github.com/Mojang/LegacyLauncher)
  * FML不再通过直接修改游戏的核心文件进行安装，改为先加入libraries，再通过传递参数`--tweakClass`使用LaunchWrapper加载FML并对Minecraft进行binpatch
  * FML开始通过LaunchWrapper提供的`IClassNameTransformer`进行运行时反混淆，将运行时的混淆方式从notch动态的反混淆成了srg
  * CoreMod不再通过FML提供的`RelaunchClassLoader`对class进行动态修改，换用LaunchWrapper对应的`LaunchClassLoader`
  * 取消了`coremods`文件夹，CoreMod可以被直接放入`mods`文件夹，CoreMod也可以直接包含普通Mod了
* 1.7 引入[ForgeGradle](https://github.com/MinecraftForge/ForgeGradle)，Forge开发脱离了MCP工具
* 1.7 MCP对Minecraft的类进行分包，不再存放于`net.minecraft.src`
* 1.8 FML变更了包名，从`cpw.mods.fml`改成了`net.minecraftforge.fml`

## 加载流程

（对加载流程不感兴趣的话可以直接跳过这一部分，以下代码来自1.12.2FML）

Forge官方没有给出CoreMod的教程以及文档，我们需要通过探索FML如何加载CoreMod来得出制作的方法。

在[CoreModManager](https://github.com/MinecraftForge/MinecraftForge/blob/1.12.x/src/main/java/net/minecraftforge/fml/relauncher/CoreModManager.java)中，FML由以下方式加载CoreMod：

* `discoverCoreMods`方法中，获取Mod文件列表，遍历每个Mod文件

  ```java
  List<File> file_canidates = LibraryManager.gatherLegacyCanidates(mcDir);
  ...
  for (File coreMod : file_canidates)
  ```
* 读取Mod文件jar中的Manifest

  ```java
  jar = new JarFile(coreMod);
  mfAttributes = jar.getManifest() == null ? null : jar.getManifest().getMainAttributes();
  ```
* 通过Manifest中的`FMLCorePlugin`属性存在与否来判断是否为CoreMod，如果不是则检测下一个Mod文件

  ```java
  fmlCorePlugin = mfAttributes.getValue("FMLCorePlugin");
  if (fmlCorePlugin == null)
  {
    // Not a coremod
    FMLLog.log.debug("Not found coremod data in {}", coreMod.getName());
    continue;
  }
  ```
* 将CoreMod加入ClassPath，并进行加载

  ```java
  classLoader.addURL(coreMod.toURI().toURL());
  ...
  loadCoreMod(classLoader, fmlCorePlugin, coreMod);
  ```
* `loadCoreMod`方法中，取得`IFMLLoadingPlugin`实例

  ```java
  Class<?> coreModClazz = Class.forName(coreModClass, true, classLoader);
  ...
  IFMLLoadingPlugin plugin = (IFMLLoadingPlugin) coreModClazz.newInstance();
  ```
* 将`IFMLLoadingPlugin`装饰为`FMLPluginWrapper`，加入列表

  ```java
  FMLPluginWrapper wrap = new FMLPluginWrapper(coreModName, plugin, location, sortIndex, dependencies);
  loadPlugins.add(wrap);
  ```
* `injectCoreModTweaks`方法中，`FMLPluginWrapper`作为`ITweaker`的子类直接加入LaunchWrapper的Tweaks列表中

  ```java
  List<ITweaker> tweakers = (List<ITweaker>) Launch.blackboard.get("Tweaks");
  // Add the sorting tweaker first- it'll appear twice in the list
  tweakers.add(0, fmlInjectionAndSortingTweaker);
  for (FMLPluginWrapper wrapper : loadPlugins)
  {
    tweakers.add(wrapper);
  }
  ```

在LaunchWrapper中的`ITweaker`调用行为与[原版 CoreMod/LaunchWrapper](https://xfl03.gitbook.io/coremodtutor/3-yuan-ban-coremod/3.3)一致，会依据顺序逐个调用`ITweaker`。

`FMLPluginWrapper`会被LaunchWrapper调用：

* `injectIntoClassLoader`方法中，首先调用`getASMTransformerClass`获取`IClassTransformer`对应的class名称，使用`TransformerWrapper`进行装饰并向LaunchWrapper注册

  ```java
  for (String transformer : coreModInstance.getASMTransformerClass())
  {
    classLoader.registerTransformer(ASMTransformerWrapper.getTransformerWrapper(classLoader, transformer, name));
  }
  ```

LaunchWrapper会在每个类被加载进ClassLoader之前调用`IClassTransformer`的`transform`方法，通过这一方法，便可运行时动态修改其他class。

## 制作方法

(以下代码适用于1.8-1.12.2，1.6.2-1.7.10需要变更FML包名，1.6.2-1.6.4需要变更Minecraft包名)

通过上文对FML CoreMod加载方式的分析，我们可以得到三个关键内容——Manifest、`IFMLLoadingPlugin`与`IClassTransformer`，以下逐个介绍。

如果读者阅读过[FML CoreMod/1.3.2-1.5.2](https://xfl03.gitbook.io/coremodtutor/4-fml-coremod/4.1)的相关内容，会发现制作方法大同小异，无非是使用LaunchWrapper相关的类进行对应。

### Manifest

Manifest有清单的意思，在这里指的是jar中的`META-INF/MANIFEST.MF`文件，可以通过修改`build.gradle`自动在打包时加入：

```
jar {
    manifest {
        attributes([
                "FMLCorePlugin": "com.example.ExamplePlugin",
                "FMLCorePluginContainsFMLMod": true
        ])
    }
}
```

其中：

* `FMLCorePlugin`属性是`IFMLLoadingPlugin`实现类的完整类名
* `FMLCorePluginContainsFMLMod`属性标记CoreMod的jar中是否还含有普通的Mod，如果为false，FML不会尝试寻找`@Mod`注解并加载普通Mod

读者可能会注意到，`jar`是会在`build`等task打包时才会被执行，在`runClient`之类的测试运行时并不会被读取到，如果需要在测试运行时使用CoreMod，还需要在`build.gradle`中加入jvm参数的设置：

```
minecraft {
    ...
    clientJvmArgs += "-Dfml.coreMods.load=com.example.ExamplePlugin"
    serverJvmArgs += "-Dfml.coreMods.load=com.example.ExamplePlugin"
}
```

### IFMLLoadingPlugin

`IFMLLoadingPlugin`是FML提供的一个接口，需要实现以下几个方法：

* `getASMTransformerClass`，返回`IClassTransformer`的完整类名构成的数组，这是CoreMod的关键
* `getModContainerClass`，返回`ModContainer`的实现类的完整类名，可空
* `getSetupClass`，返回`IFMLCallhook`的实现类完整类名，可空
* `injectData`，可以获得`mcLocation`、`coremodList`与`coremodLocation`，分别是Minecraft文件夹`File`、CoreMod列表`List`、当前CoreMod文件`File`。这个方法与`IFMLCallhook`中的`injectData`主要区别为这个方法是在Minecraft启动后调用的，可以操作Minecraft的class
* `getAccessTransformerClass`，返回一个`AccessTransformer`实现类完整类名，可空，一般可以直接使用FML提供的访问级转换器功能，无需自定义

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

* `TransformerExclusions`，指定不被CoreMod修改的包名前缀数组，例如指定了`com.example`后，`com.example.something.Example`也不会被修改
* `MCVersion`，指定CoreMod适用的Minecraft版本
* `Name`，指定CoreMod的名称，如果不指定，会直接使用类名作为名字
* `DependsOn`，指定依赖
* `SortingIndex`，指定被调用的顺序

一个简单的`IFMLLoadingPlugin`实现如下：

```java
package com.example;

import java.util.Map;

import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin;
import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin.Name;

@Name("ExampleCoreMod")
public class ExamplePlugin implements IFMLLoadingPlugin {

    @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) {
    }

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

}
```

### IClassTransformer

`IClassTransformer`是LaunchWrapper提供的接口，相当于字节码修改器，需要实现以下方法：

* `transform`，接收`name`、`transformedName`与`basicClass`三个参数，分别是原类名、mcp无混淆类名和class文件的二进制`byte`数组，需要返回修改后的class文件的`byte`数组

需要特别注意的是：

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

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

```java
package com.example;

import net.minecraft.launchwrapper.IClassTransformer;

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

一个对`net.minecraft.client.gui.GuiPlayerTabOverlay`的`func_175249_a`(srg) `renderPlayerlist`(mcp)方法进行修改的实例：

```java
package com.example;

import net.minecraft.launchwrapper.IClassTransformer;

import net.minecraftforge.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper;

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[] basicClass) {
        if (!"net.minecraft.client.gui.GuiPlayerTabOverlay".equals(transformedName))
            return basicClass;

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

        //遍历methods
        for (MethodNode mn : cn.methods) {
            //调用FML接口获得方法名，运行时获得的是srg，测试时获得的是mcp
            String methodName = FMLDeobfuscatingRemapper.INSTANCE.mapMethodName(name, mn.name, mn.desc);
            if(!"func_175249_a".equals(methodName) && !"renderPlayerlist".equals(methodName)) 
                continue;

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

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