# 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`，减少遍历代码


---

# 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.3.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.
