> For the complete documentation index, see [llms.txt](https://xfl03.gitbook.io/coremodtutor/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://xfl03.gitbook.io/coremodtutor/4-fml-coremod/4.3.md).

# 1.13.2-1.15.2

## 1.13.2-1.15.2 FML CoreMod大事记

* 1.13.2 引入[ModLauncher](https://github.com/cpw/modlauncher)
  * LaunchWrapper被舍弃
  * FML 1.3.2使用至今的Mod加载机制被重写
  * Forge实现了一定程度的模块化，由[CoreMods](https://github.com/MinecraftForge/CoreMods)模块调度运行CoreMod
  * 修改其他class的行为需要在JavaScript脚本中受限进行，几乎无法使用ASM Core API，需要改成ASM Tree API
  * Forge和FML开始使用大量Java 8特性
  * 不再进行运行时反混淆，转而在安装时直接调用[SpecialSource](https://github.com/md-5/SpecialSource)生成srg混淆的Minecraft核心文件
  * binpatch也同样使用了srg混淆，在安装时对Minecraft进行修改
  * [1.13.2-25.0.199](https://github.com/MinecraftForge/MinecraftForge/commit/6a1337aa92365420290a0533937adbc2b6e26890) 更新ModLauncher 1.1，不再使用`URLClassLoader`进行Mod类加载，同时会在崩溃报告的调用栈中显示类的修改来源
  * [1.13.2-25.0.216](https://github.com/MinecraftForge/MinecraftForge/commit/c57c6213ea531aa7f7ef71510b752bd37d1dd608) 增加[`ModDirTransformerDiscoverer`](https://github.com/MinecraftForge/MinecraftForge/blob/1.14.x/src/fmllauncher/java/net/minecraftforge/fml/loading/ModDirTransformerDiscoverer.java)，ModLauncher的`ITransformationService`也可以被加载了

## Java 还是 JavaScript？

从 1.13.2-25.0.216 开始，FML会尝试加载仅实现了`ITransformationService`的Mod，也就是说，我们又可以使用Java进行CoreMod的编写了。\
但是，在 [1.14.4-28.0.17](https://github.com/MinecraftForge/MinecraftForge/commit/0bdc2d04b4bef9eb19e01d22720f19240a240241) 中，cpw将自己为Forge编写的一个`ITransformationService`使用JavaScript进行重写，这意味着cpw对使用JavaScript编写CoreMod是有一定程度的认可的。\
使用Java或JavaScript编写CoreMod各有优劣，我们可以根据实际情况选择更为合适的写法，甚至混合编写：

| 项目   | Java                                                                      | JavaScript                                 |
| ---- | ------------------------------------------------------------------------- | ------------------------------------------ |
| 可追踪性 | 会在崩溃报告等调试信息中显示类修改来源的转换器名，需要规范命名才有可能让用户发现问题来源                              | 会在崩溃报告等调试信息中显示类修改来源的modid与转换器名，更易于用户发现问题来源 |
| 编写难度 | 需要手动实现`ITransformationService`与`ITransformer`接口，以及声明ServiceLoader，代码编写较繁琐 | 只需要按照格式声明要修改的内容并完成修改的函数即可                  |
| 代码提示 | 有完整代码提示                                                                   | 基本语法有代码提示，尚无对ASM的完整代码提示                    |
| 操作风险 | 通过隔离ClassLoader，不会导致类提前加载                                                 | 通过限制可以使用的类，不会导致类提前加载                       |
| 自由程度 | 除了Java的安全限制外，完全自由                                                         | 受限的类访问，只能使用Java基本类、ASM与`ASMAPI`            |
| ASM  | 可以使用Tree API与Core API                                                     | 只能使用Tree API                               |

建议在使用Tree API进行简单的修改操作时，可以考虑更为便捷的JavaScript；对于不熟悉ASM需要代码提示的开发者，或是需要Core API进行复杂的修改操作时，Java可能会是更好的选择。\
若是想使用Java进行CoreMod编写，请参考[ModLauncher教程](/coremodtutor/3-yuan-ban-coremod/3.4.md)；欲使用JavaScript进行CoreMod编写，请参考下文。

## 加载流程

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

这个版本Forge仍然没有给出教程和文档，我们需要通过代码分析来自行探索。

FML在1.3.2的10个大版本之后终于再一次重写了Mod加载机制，这次重写大量的使用了[ServiceLoader](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html)来运用SPI（Service Provider Interfaces）以及[StreamAPI](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html)简化了加载的代码。

首先是寻找并加载CoreMod：

* 在fmllauncher（以下简称FML）的[FMLLoader](https://github.com/MinecraftForge/MinecraftForge/blob/1.13.x/src/fmllauncher/java/net/minecraftforge/fml/loading/FMLLoader.java)中`onInitialLoad`方法通过ServiceLoader获得`ICoreModProvider`的实例。\
  其中，`ICoreModProvider`的实现在CoreMods模块中，被命名为[CoreModProvider](https://github.com/MinecraftForge/CoreMods/blob/master/src/main/java/net/minecraftforge/coremod/CoreModProvider.java)

  ```java
  ServiceLoaderStreamUtils.errorHandlingServiceLoader(ICoreModProvider.class, serviceConfigurationError -> LOGGER.fatal(CORE, "Failed to load a coremod library, expect problems", serviceConfigurationError)).forEach(coreModProviders::add);
  ...
  coreModProvider = coreModProviders.get(0);
  ```
* 在FML的[ModDiscoverer](https://github.com/MinecraftForge/MinecraftForge/blob/1.13.x/src/fmllauncher/java/net/minecraftforge/fml/loading/moddiscovery/ModDiscoverer.java)中`discoverMods`方法首先获得`modFiles`

  ```java
  final Map<ModFile.Type, List<ModFile>> modFiles = locatorList.stream()
        .peek(loc -> LOGGER.debug(SCAN,"Trying locator {}", loc))
        .map(IModLocator::scanMods)
        .flatMap(Collection::stream)
        .peek(mf -> LOGGER.debug(SCAN,"Found mod file {} of type {} with locator {}", mf.getFileName(), mf.getType(), mf.getLocator()))
        .collect(Collectors.groupingBy(ModFile::getType));
  ```
* 随后，遍历`modFiles`获得的`mods`来寻找有效的Mod

  ```java
  for (Iterator<ModFile> iterator = mods.iterator(); iterator.hasNext(); )
  {
    ModFile mod = iterator.next();
    if (!mod.getLocator().isValid(mod) || !mod.identifyMods()) {
        LOGGER.warn(SCAN, "File {} has been ignored - it is invalid", mod.getFilePath());
        iterator.remove();
        brokenFiles.add(mod);
    }
  }
  ```
* 在遍历的过程中，`ModFile`的`identifyMods`会被调用，而这个方法中会寻找CoreMod

  ```java
  this.coreMods = ModFileParser.getCoreMods(this);
  ```
* `ModFileParser`的`getCoreMods`是读取CoreMod的方法，会根据Mod文件中的`META-INF/coremods.json`来判断一个Mod是否是CoreMod

  ```java
  final Path coremodsjson = modFile.getLocator().findPath(modFile, "META-INF", "coremods.json");
  if (!Files.exists(coremodsjson)) {
    return Collections.emptyList();
  }
  ```
* 重新回到`ModDiscoverer`中，将有效的Mod加入`loadingModList`后，调用其`addCoreMods`方法

  ```java
  loadingModList.addCoreMods();
  ```
* 在FML的[LoadingModList](https://github.com/MinecraftForge/MinecraftForge/blob/1.13.x/src/fmllauncher/java/net/minecraftforge/fml/loading/LoadingModList.java)中`addCoreMods`依次调用`CoreModProvider`的`addCoreMod`方法

  ```java
  modFiles.stream().map(ModFileInfo::getFile).map(ModFile::getCoreMods).flatMap(List::stream).forEach(FMLLoader.getCoreModProvider()::addCoreMod);
  ```
* `addCoreMod`方法调用了CoreMods模块[CoreModEngine](https://github.com/MinecraftForge/CoreMods/blob/master/src/main/java/net/minecraftforge/coremod/CoreModEngine.java)中的`loadCoreMod`，这里初始化了稍后会使用到的[Nashorn](https://docs.oracle.com/javase/8/docs/technotes/guides/scripting/nashorn/toc.html)脚本引擎，这个引擎随后会被用来执行CoreMod的JavaScript来进行transform。同时，通过对脚本引擎的初始化参数，使用了白名单机制限制了脚本所使用的类，只可以使用ASM相关的类

  ```java
  final NashornScriptEngineFactory nashornScriptEngineFactory = new NashornScriptEngineFactory();
  final ScriptEngine scriptEngine = nashornScriptEngineFactory.getScriptEngine(
         s -> ALLOWED_CLASSES.stream().anyMatch(s::equals)
  );
  ```

加载并初始化完CoreMod后，便是进行transform：

* ModLauncher会和[原版CoreMod/ModLauncher](/coremodtutor/3-yuan-ban-coremod/3.4.md)中一样调用[FMLServiceProvider](https://github.com/MinecraftForge/MinecraftForge/blob/1.13.x/src/fmllauncher/java/net/minecraftforge/fml/loading/FMLServiceProvider.java)实现的接口`ITransformationService`的`transformers`方法，这个方法返回了`ITransformer`的`List`：

  ```java
  return new ArrayList(FMLLoader.getCoreModProvider().getCoreModTransformers());
  ```
* `getCoreModTransformers`调用了`CoreModEngine`的`initializeCoreMods`，这个方法会依次为CoreMod构造`ITransformer`，并返回给ModLauncher

  ```java
  return coreMods.stream().map(CoreMod::buildTransformers).flatMap(List::stream).collect(Collectors.toList());
  ```
* ModLauncher会在指定的类（LaunchWrapper是所有的类）被加载时调用`CoreModClassTransformer`的`transform`方法，这个方法会使用Nashorn运行JavaScript脚本

  ```java
  result = (ClassNode) function.call(function, input);
  ```

## 制作方法

与1.12.2相比，1.13.2的CoreMod加载流程可谓是相当复杂，这样复杂的流程却带来了CoreMod开发的简化。\
不过特别需要注意的是，1.13.2与以往不同，CoreMod**必须**包含在一个普通的Mod中。

通过上文的分析，我们抓住了两个关键点——`coremods.json`与JavaScript。

### coremods.json

这个文件位于jar中的`META-INF/coremods.json`，里面的格式如下：

```javascript
{
  "脚本名称": "脚本路径",
  ...
}
```

* 其中，脚本路径是相对于jar文件的路径，例如`example.js`代表着jar文件根目录下的`example.js`

一个简单的实例如下：

```javascript
{
  "example": "example.js"
}
```

### JavaScript

这是1.13.2-1.14.4 CoreMod制作的核心，这个版本不再使用Java代码来修改其他class，转为受限制的JavaScript脚本进行，在这个脚本中，只需要也只能完成修改类这一个工作，这样的设计使得CoreMod无法执行这一工作外的其他操作，极大程度的避免了诸如CoreMod调用Minecraft、普通Mod等会导致错误的操作。不过，其用于解析JavaScript的[Nashorn](https://openjdk.java.net/projects/nashorn/)引擎在Java 11中已被[弃用](https://openjdk.java.net/jeps/335)。

首先，在CoreMod初始化时，脚本的`initializeCoreMod`函数会被调用，这个函数需要返回一个JavaScript对象来声明将要修改的目标class和修改所使用的`transform`函数：

```javascript
function initializeCoreMod() {
    return {
        '转换器名': {
            'target': {
                'type': 'CLASS',
                'name': '目标类名'
            },
            'transformer': function (cn) {
                //transform函数
            }
        },
        ...
    };
}
```

* `type`指定了目标的类型
  * 之前的版本只有`CLASS`这一个选择
  * [1.14.3-27.0.16](https://github.com/MinecraftForge/MinecraftForge/commit/943fedb073f832acb3472286369b6cc52358d4eb) 增加`FIELD`与`METHOD`
* `transform`函数会在目标类被加载时调用，传入一个于`type`对应的`Node`（`ClassNode`、`FieldNode`或`MethodNode`），在游戏运行时为srg混淆，我们需要返回修改完后的`Node`对象，否则会出现类型转换错误

一个修改`net/minecraft/client/gui/GuiPlayerTabOverlay`中`func_175249_a`(srg)方法的实例：

```javascript
//使用Java.type，类似import
var ASMAPI = Java.type('net.minecraftforge.coremod.api.ASMAPI');
var Opcodes = Java.type('org.objectweb.asm.Opcodes');

function initializeCoreMod() {
    return {
        'PlayerTabTransformer': {
            'target': {
                'type': 'CLASS',
                'name': 'net/minecraft/client/gui/GuiPlayerTabOverlay'
            },
            'transformer': function (cn) {
                //遍历ClassNode下的methods
                cn.methods.forEach(function (mn) {
                    if (mn.name === 'func_175249_a') {
                        //请在这里对ClassNode和MethodNode进行ASM操作
                    }
                });

                //返回修改后的ClassNode对象
                return cn;
            }
        }
    };
}
```

* 由于JavaScript的[语言特性](https://thomas-yang.me/projects/oh-my-dear-js/)，全等于（===）与Java的等于（==）比较类似，在比较`String`时全等于与`equals`较为类似
* Java.type载入的类有白名单机制，目前仅限于载入ASM库中的类与Forge提供的`ASMAPI`
* 如果无需使用`ClassNode`对象，可以直接使用`METHOD`作为`type`，减少遍历代码
