- cpw制作,用于1.13FML加载的库,吸取了LaunchWrapper的经验,并使用了StreamAPI和ServiceLoader简化代码。
- 会记录每一个类被
ITransformationService
和ILaunchPluginService
请求修改的历史。
Launcher类是ModLauncher的入口类:
- 在构造方法中,初始化了用于存储Transformer的
TransformStore
,以及用于初始化ITransformationService
的TransformationServicesHandler
this.transformStore = new TransformStore();
this.transformationServicesHandler = new TransformationServicesHandler(this.transformStore);
- 在
run
方法中,调用TransformationServicesHandler
的初始化方法,并创建TransformingClassLoader
,最后启动游戏
this.transformationServicesHandler.initializeTransformationServices(this.argumentHandler, this.environment);
...
this.classLoader = this.transformationServicesHandler.buildTransformingClassLoader(this.launchPlugins, classLoaderBuilder);
Thread.currentThread().setContextClassLoader(this.classLoader);
this.launchService.launch(this.argumentHandler, this.classLoader);
在TransformationServicesHandler
中,初始化了ITransformationService
:
- 在构造器中,使用了ServiceLoader来获得
ITransformationService
实例,并创建对应的TransformationServiceDecorator
用来初始化前者
transformationServices = ServiceLoaderStreamUtils.errorHandlingServiceLoader(ITransformationService.class, serviceConfigurationError -> LOGGER.fatal(MODLAUNCHER, "Encountered serious error loading transformation service, expect problems", serviceConfigurationError));
serviceLookup = ServiceLoaderStreamUtils.toMap(transformationServices, ITransformationService::name, TransformationServiceDecorator::new);
- 在
initializeTransformationServices
方法中分别通过TransformationServiceDecorator
间接调用ITransformationService
对应的方法
loadTransformationServices(environment);
...
initialiseTransformationServices(environment);
initialiseServiceTransformers();
在TransformationServiceDecorator
中:
gatherTransformers
方法从ITransformationService
的transformers
方法中获得了ITransformer
的实例,并在TransformStore
中根据targets
方法的返回值注册了Target
对应的ITransformer
final List<ITransformer> transformers = this.service.transformers();
...
for (Type type : transformersByType.keySet()) {
...
for (ITransformer<?> xform : transformersByType.get(type)) {
final Set<ITransformer.Target> targets = xform.targets();
...
final Map<TransformTargetLabel.LabelType, List<TransformTargetLabel>> labelTypeListMap = targets.stream().map(TransformTargetLabel::new).collect(Collectors.groupingBy(TransformTargetLabel::getLabelType));
...
labelTypeListMap.values().stream().flatMap(Collection::stream).forEach(target -> transformStore.addTransformer(target, xform));
}
}
ITransformer
的加载流程至此告一段落,现在看到TransformingClassLoader
在类加载时的行为:
-
TransformingClassLoader
是一个ClassLoader
的子类,其内部类DelegatedClassLoader
是URLClassLoader
的子类,大部分方法都会调用后者的方法 -
loadClass
方法会通过多次调用最终调用DelegatedClassLoader
的findClass
方法,这个方法首先读入二进制的类文件,然后调用ClassTransformer
的transform
方法,这个方法会调用ITransformer
的transform
final String path = name.replace('.', '/').concat(".class");
final URL classResource = classBytesFinder.apply(path);
byte[] classBytes;
//省略读入文件的代码
classBytes = tcl.classTransformer.transform(classBytes, name);
通过以上简单的流程分析,我们可以获得三个相关的信息——ITransformationService
、ITransformer
与ServiceLoader。
ITransformationService是一个用来预处理环境并提供ITransformer
实例的接口,需要实现以下方法:
name
,返回Service的名称arguments
,传入一个双参数Function
来指定需要读取的游戏参数,第一个参数代表着要读取的参数名,最终读取的参数会变为"Service名.参数名"的形式,第二个参数代表着参数的描述argumentValues
,传入读取参数的结果initialize
,初始化方法,传入环境,切记不要调用Minecraft、其他Mod、自己Mod的普通部分的任何代码,不然会导致类被提前加载而出错onLoad
,加载Service时调用,传入环境及其他Service的列表transformers
,返回ITransformer
的实例
一个简单的实例如下:
package com.example;
import cpw.mods.modlauncher.api.IEnvironment;
import cpw.mods.modlauncher.api.ITransformationService;
import cpw.mods.modlauncher.api.ITransformer;
import joptsimple.OptionResult;
import joptsimple.OptionSpecBuilder;
import java.util.Arrays;
import java.util.Set;
import java.util.function.BiFunction;
public class ExampleService implements ITransformationService {
String name() {
return "ExampleService";
}
void initialize(IEnvironment environment) {}
void onLoad(IEnvironment env, Set<String> otherServices) throws IncompatibleEnvironmentException {}
List<ITransformer> transformers() {
return Arrays.asList(new ExampleTransformer());
}
}
这个ITransformer
与LaunchWrapper的IClassTransformer
相比有两大不同:
- 不再接收二进制class文件,转为asm的node
- 提早声明将要修改的类,而不是等到
transform
方法中再确定
ITransformer
作为修改类的接口,需要实现以下方法:
transform
方法接收asm的node和node相关的信息,这个node的类型由泛型T来确定,一般为ClassNode或MethodNodecastVote
,返回TransformerVoteResult
来决定是否修改一个类或方法YES
会调用transform
方法NO
会跳过这个类修改器REJECT
会抛出VoteRejectedException
错误DEFER
会抛出VoteDeadlockException
错误
targets
,返回需要修改的目标Target
列表,需要统一Target的类型,只有在这里返回的目标才会调用另外两个方法
通过Java 8的接口默认方法,以下几个方法可选实现:
arguments
,接收一个处理游戏参数的双重映射函数,可以在这里添加自己的参数argumentValues
,接收游戏参数,可以在这里获得游戏参数additionalClassesLocator
,返回一个包名前缀和用于寻找类的类名映射URL函数的组合,可以通过这个方法可以往ClassLoader中增加自定义的用于类加载的URLadditionalClassesLocator
,返回一个文件路径和用于寻找文件的文件名映射URL函数的组合,可以通过这个方法可以往ClassLoader中增加自定义的用于类以外的资源加载的URL
一个没有修改任何类的实例:
package com.example;
import cpw.mods.modlauncher.api.ITransformer;
import cpw.mods.modlauncher.api.ITransformerVotingContext;
import cpw.mods.modlauncher.api.TransformerVoteResult;
import org.objectweb.asm.tree.ClassNode;
import java.util.HashSet;
import java.util.Set;
public class ExampleTransformer implements ITransformer<ClassNode> {
ClassNode transform(ClassNode input, ITransformerVotingContext context) {
return input;
}
TransformerVoteResult castVote(ITransformerVotingContext context) {
return TransformerVoteResult.YES;
}
Set<Target> targets() {
return new HashSet<Target>(Arrays.asList(Target.targetClass("abc")));
}
}
完成编写了ITransformationService
以及ITransformer
以后,我们还需要声明ITransformationService
实现,以便ModLauncher使用ServiceLoader进行加载。
创建META-INF/services/cpw.mods.modlauncher.api.ITransformationService
,将ITransformationService
实现的完整类名写入,例如:
com.example.ExampleService
- 复制版本json到另一个版本文件夹中,并修改对应的名称
- 在
libraries
中加入ModLauncher及其依赖以及自己编写的CoreMod - 修改
mainClass
为cpw.mods.modlauncher.Launcher
从Forge 1.13.2-25.0.216开始,可以使用Forge来加载无论是否包含普通Mod的ModLauncher CoreMod。
- 放入
.minecraft/mods
文件夹即可