1.13.2-1.15.2
1.13.2-1.15.2 FML CoreMod大事记
1.13.2 引入ModLauncher
LaunchWrapper被舍弃
FML 1.3.2使用至今的Mod加载机制被重写
Forge实现了一定程度的模块化,由CoreMods模块调度运行CoreMod
修改其他class的行为需要在JavaScript脚本中受限进行,几乎无法使用ASM Core API,需要改成ASM Tree API
Forge和FML开始使用大量Java 8特性
不再进行运行时反混淆,转而在安装时直接调用SpecialSource生成srg混淆的Minecraft核心文件
binpatch也同样使用了srg混淆,在安装时对Minecraft进行修改
1.13.2-25.0.199 更新ModLauncher 1.1,不再使用
URLClassLoader
进行Mod类加载,同时会在崩溃报告的调用栈中显示类的修改来源1.13.2-25.0.216 增加
ModDirTransformerDiscoverer
,ModLauncher的ITransformationService
也可以被加载了
Java 还是 JavaScript?
从 1.13.2-25.0.216 开始,FML会尝试加载仅实现了ITransformationService
的Mod,也就是说,我们又可以使用Java进行CoreMod的编写了。
但是,在 1.14.4-28.0.17 中,cpw将自己为Forge编写的一个ITransformationService
使用JavaScript进行重写,这意味着cpw对使用JavaScript编写CoreMod是有一定程度的认可的。
使用Java或JavaScript编写CoreMod各有优劣,我们可以根据实际情况选择更为合适的写法,甚至混合编写:
项目 | Java | JavaScript |
可追踪性 | 会在崩溃报告等调试信息中显示类修改来源的转换器名,需要规范命名才有可能让用户发现问题来源 | 会在崩溃报告等调试信息中显示类修改来源的modid与转换器名,更易于用户发现问题来源 |
编写难度 | 需要手动实现 | 只需要按照格式声明要修改的内容并完成修改的函数即可 |
代码提示 | 有完整代码提示 | 基本语法有代码提示,尚无对ASM的完整代码提示 |
操作风险 | 通过隔离ClassLoader,不会导致类提前加载 | 通过限制可以使用的类,不会导致类提前加载 |
自由程度 | 除了Java的安全限制外,完全自由 | 受限的类访问,只能使用Java基本类、ASM与 |
ASM | 可以使用Tree API与Core API | 只能使用Tree API |
建议在使用Tree API进行简单的修改操作时,可以考虑更为便捷的JavaScript;对于不熟悉ASM需要代码提示的开发者,或是需要Core API进行复杂的修改操作时,Java可能会是更好的选择。 若是想使用Java进行CoreMod编写,请参考ModLauncher教程;欲使用JavaScript进行CoreMod编写,请参考下文。
加载流程
(对加载流程不感兴趣的话可以直接跳过这一部分,以下代码来自1.13.2 fmllauncher和CoreMods)
这个版本Forge仍然没有给出教程和文档,我们需要通过代码分析来自行探索。
FML在1.3.2的10个大版本之后终于再一次重写了Mod加载机制,这次重写大量的使用了ServiceLoader来运用SPI(Service Provider Interfaces)以及StreamAPI简化了加载的代码。
首先是寻找并加载CoreMod:
在fmllauncher(以下简称FML)的FMLLoader中
onInitialLoad
方法通过ServiceLoader获得ICoreModProvider
的实例。 其中,ICoreModProvider
的实现在CoreMods模块中,被命名为CoreModProvider在FML的ModDiscoverer中
discoverMods
方法首先获得modFiles
随后,遍历
modFiles
获得的mods
来寻找有效的Mod在遍历的过程中,
ModFile
的identifyMods
会被调用,而这个方法中会寻找CoreModModFileParser
的getCoreMods
是读取CoreMod的方法,会根据Mod文件中的META-INF/coremods.json
来判断一个Mod是否是CoreMod重新回到
ModDiscoverer
中,将有效的Mod加入loadingModList
后,调用其addCoreMods
方法在FML的LoadingModList中
addCoreMods
依次调用CoreModProvider
的addCoreMod
方法addCoreMod
方法调用了CoreMods模块CoreModEngine中的loadCoreMod
,这里初始化了稍后会使用到的Nashorn脚本引擎,这个引擎随后会被用来执行CoreMod的JavaScript来进行transform。同时,通过对脚本引擎的初始化参数,使用了白名单机制限制了脚本所使用的类,只可以使用ASM相关的类
加载并初始化完CoreMod后,便是进行transform:
ModLauncher会和原版CoreMod/ModLauncher中一样调用FMLServiceProvider实现的接口
ITransformationService
的transformers
方法,这个方法返回了ITransformer
的List
:getCoreModTransformers
调用了CoreModEngine
的initializeCoreMods
,这个方法会依次为CoreMod构造ITransformer
,并返回给ModLauncherModLauncher会在指定的类(LaunchWrapper是所有的类)被加载时调用
CoreModClassTransformer
的transform
方法,这个方法会使用Nashorn运行JavaScript脚本
制作方法
与1.12.2相比,1.13.2的CoreMod加载流程可谓是相当复杂,这样复杂的流程却带来了CoreMod开发的简化。 不过特别需要注意的是,1.13.2与以往不同,CoreMod必须包含在一个普通的Mod中。
通过上文的分析,我们抓住了两个关键点——coremods.json
与JavaScript。
coremods.json
这个文件位于jar中的META-INF/coremods.json
,里面的格式如下:
其中,脚本路径是相对于jar文件的路径,例如
example.js
代表着jar文件根目录下的example.js
一个简单的实例如下:
JavaScript
这是1.13.2-1.14.4 CoreMod制作的核心,这个版本不再使用Java代码来修改其他class,转为受限制的JavaScript脚本进行,在这个脚本中,只需要也只能完成修改类这一个工作,这样的设计使得CoreMod无法执行这一工作外的其他操作,极大程度的避免了诸如CoreMod调用Minecraft、普通Mod等会导致错误的操作。不过,其用于解析JavaScript的Nashorn引擎在Java 11中已被弃用。
首先,在CoreMod初始化时,脚本的initializeCoreMod
函数会被调用,这个函数需要返回一个JavaScript对象来声明将要修改的目标class和修改所使用的transform
函数:
type
指定了目标的类型之前的版本只有
CLASS
这一个选择1.14.3-27.0.16 增加
FIELD
与METHOD
transform
函数会在目标类被加载时调用,传入一个于type
对应的Node
(ClassNode
、FieldNode
或MethodNode
),在游戏运行时为srg混淆,我们需要返回修改完后的Node
对象,否则会出现类型转换错误
一个修改net/minecraft/client/gui/GuiPlayerTabOverlay
中func_175249_a
(srg)方法的实例:
由于JavaScript的语言特性,全等于(===)与Java的等于(==)比较类似,在比较
String
时全等于与equals
较为类似Java.type载入的类有白名单机制,目前仅限于载入ASM库中的类与Forge提供的
ASMAPI
如果无需使用
ClassNode
对象,可以直接使用METHOD
作为type
,减少遍历代码
Last updated