ModLauncher
  • cpw制作,用于1.13FML加载的库,吸取了LaunchWrapper的经验,并使用了StreamAPI和ServiceLoader简化代码。
  • 会记录每一个类被ITransformationServiceILaunchPluginService请求修改的历史。

加载流程

Launcher类是ModLauncher的入口类:
  • 在构造方法中,初始化了用于存储Transformer的TransformStore,以及用于初始化ITransformationServiceTransformationServicesHandler
    1
    this.transformStore = new TransformStore();
    2
    this.transformationServicesHandler = new TransformationServicesHandler(this.transformStore);
    Copied!
  • run方法中,调用TransformationServicesHandler的初始化方法,并创建TransformingClassLoader,最后启动游戏
    1
    this.transformationServicesHandler.initializeTransformationServices(this.argumentHandler, this.environment);
    2
    ...
    3
    this.classLoader = this.transformationServicesHandler.buildTransformingClassLoader(this.launchPlugins, classLoaderBuilder);
    4
    Thread.currentThread().setContextClassLoader(this.classLoader);
    5
    this.launchService.launch(this.argumentHandler, this.classLoader);
    Copied!
TransformationServicesHandler中,初始化了ITransformationService
  • 在构造器中,使用了ServiceLoader来获得ITransformationService实例,并创建对应的TransformationServiceDecorator用来初始化前者
    1
    transformationServices = ServiceLoaderStreamUtils.errorHandlingServiceLoader(ITransformationService.class, serviceConfigurationError -> LOGGER.fatal(MODLAUNCHER, "Encountered serious error loading transformation service, expect problems", serviceConfigurationError));
    2
    serviceLookup = ServiceLoaderStreamUtils.toMap(transformationServices, ITransformationService::name, TransformationServiceDecorator::new);
    Copied!
  • initializeTransformationServices方法中分别通过TransformationServiceDecorator间接调用ITransformationService对应的方法
    1
    loadTransformationServices(environment);
    2
    ...
    3
    initialiseTransformationServices(environment);
    4
    initialiseServiceTransformers();
    Copied!
TransformationServiceDecorator中:
  • gatherTransformers方法从ITransformationServicetransformers方法中获得了ITransformer的实例,并在TransformStore中根据targets方法的返回值注册了Target对应的ITransformer
    1
    final List<ITransformer> transformers = this.service.transformers();
    2
    ...
    3
    for (Type type : transformersByType.keySet()) {
    4
    ...
    5
    for (ITransformer<?> xform : transformersByType.get(type)) {
    6
    final Set<ITransformer.Target> targets = xform.targets();
    7
    ...
    8
    final Map<TransformTargetLabel.LabelType, List<TransformTargetLabel>> labelTypeListMap = targets.stream().map(TransformTargetLabel::new).collect(Collectors.groupingBy(TransformTargetLabel::getLabelType));
    9
    ...
    10
    labelTypeListMap.values().stream().flatMap(Collection::stream).forEach(target -> transformStore.addTransformer(target, xform));
    11
    }
    12
    }
    Copied!
ITransformer的加载流程至此告一段落,现在看到TransformingClassLoader在类加载时的行为:
  • TransformingClassLoader是一个ClassLoader的子类,其内部类DelegatedClassLoaderURLClassLoader的子类,大部分方法都会调用后者的方法
  • loadClass方法会通过多次调用最终调用DelegatedClassLoaderfindClass方法,这个方法首先读入二进制的类文件,然后调用ClassTransformertransform方法,这个方法会调用ITransformertransform
1
final String path = name.replace('.', '/').concat(".class");
2
3
final URL classResource = classBytesFinder.apply(path);
4
byte[] classBytes;
5
//省略读入文件的代码
6
classBytes = tcl.classTransformer.transform(classBytes, name);
Copied!

制作方法

通过以上简单的流程分析,我们可以获得三个相关的信息——ITransformationServiceITransformer与ServiceLoader。

ITransformationService

ITransformationService是一个用来预处理环境并提供ITransformer实例的接口,需要实现以下方法:
  • name,返回Service的名称
  • arguments,传入一个双参数Function来指定需要读取的游戏参数,第一个参数代表着要读取的参数名,最终读取的参数会变为"Service名.参数名"的形式,第二个参数代表着参数的描述
  • argumentValues,传入读取参数的结果
  • initialize,初始化方法,传入环境,切记不要调用Minecraft、其他Mod、自己Mod的普通部分的任何代码,不然会导致类被提前加载而出错
  • onLoad,加载Service时调用,传入环境及其他Service的列表
  • transformers,返回ITransformer的实例
一个简单的实例如下:
1
package com.example;
2
3
import cpw.mods.modlauncher.api.IEnvironment;
4
import cpw.mods.modlauncher.api.ITransformationService;
5
import cpw.mods.modlauncher.api.ITransformer;
6
7
import joptsimple.OptionResult;
8
import joptsimple.OptionSpecBuilder;
9
10
import java.util.Arrays;
11
import java.util.Set;
12
import java.util.function.BiFunction;
13
14
public class ExampleService implements ITransformationService {
15
String name() {
16
return "ExampleService";
17
}
18
void initialize(IEnvironment environment) {}
19
void onLoad(IEnvironment env, Set<String> otherServices) throws IncompatibleEnvironmentException {}
20
List<ITransformer> transformers() {
21
return Arrays.asList(new ExampleTransformer());
22
}
23
}
Copied!

ITransformer

这个ITransformer与LaunchWrapper的IClassTransformer相比有两大不同:
  • 不再接收二进制class文件,转为asm的node
  • 提早声明将要修改的类,而不是等到transform方法中再确定
ITransformer作为修改类的接口,需要实现以下方法:
  • transform方法接收asm的node和node相关的信息,这个node的类型由泛型T来确定,一般为ClassNode或MethodNode
  • castVote,返回TransformerVoteResult来决定是否修改一个类或方法
    • YES会调用transform方法
    • NO会跳过这个类修改器
    • REJECT会抛出VoteRejectedException错误
    • DEFER会抛出VoteDeadlockException错误
  • targets,返回需要修改的目标Target列表,需要统一Target的类型,只有在这里返回的目标才会调用另外两个方法
通过Java 8的接口默认方法,以下几个方法可选实现:
  • arguments,接收一个处理游戏参数的双重映射函数,可以在这里添加自己的参数
  • argumentValues,接收游戏参数,可以在这里获得游戏参数
  • additionalClassesLocator,返回一个包名前缀和用于寻找类的类名映射URL函数的组合,可以通过这个方法可以往ClassLoader中增加自定义的用于类加载的URL
  • additionalClassesLocator,返回一个文件路径和用于寻找文件的文件名映射URL函数的组合,可以通过这个方法可以往ClassLoader中增加自定义的用于类以外的资源加载的URL
一个没有修改任何类的实例:
1
package com.example;
2
3
import cpw.mods.modlauncher.api.ITransformer;
4
import cpw.mods.modlauncher.api.ITransformerVotingContext;
5
import cpw.mods.modlauncher.api.TransformerVoteResult;
6
7
import org.objectweb.asm.tree.ClassNode;
8
9
import java.util.HashSet;
10
import java.util.Set;
11
12
public class ExampleTransformer implements ITransformer<ClassNode> {
13
ClassNode transform(ClassNode input, ITransformerVotingContext context) {
14
return input;
15
}
16
TransformerVoteResult castVote(ITransformerVotingContext context) {
17
return TransformerVoteResult.YES;
18
}
19
Set<Target> targets() {
20
return new HashSet<Target>(Arrays.asList(Target.targetClass("abc")));
21
}
22
}
Copied!

ServiceLoader

完成编写了ITransformationService以及ITransformer以后,我们还需要声明ITransformationService实现,以便ModLauncher使用ServiceLoader进行加载。 创建META-INF/services/cpw.mods.modlauncher.api.ITransformationService,将ITransformationService实现的完整类名写入,例如:
1
com.example.ExampleService
Copied!

安装方法

库文件挂载

  • 复制版本json到另一个版本文件夹中,并修改对应的名称
  • libraries中加入ModLauncher及其依赖以及自己编写的CoreMod
  • 修改mainClasscpw.mods.modlauncher.Launcher

使用Forge加载

从Forge 1.13.2-25.0.216开始,可以使用Forge来加载无论是否包含普通Mod的ModLauncher CoreMod。
  • 放入.minecraft/mods文件夹即可