# 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](/coremodtutor/3-yuan-ban-coremod/3.3.md)一致，会依据顺序逐个调用`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](/coremodtutor/4-fml-coremod/4.1.md)的相关内容，会发现制作方法大同小异，无非是使用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();
    }
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://xfl03.gitbook.io/coremodtutor/4-fml-coremod/4.2.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
