ModLauncher

  • cpw制作,用于1.13FML加载的库,吸取了LaunchWrapper的经验,并使用了StreamAPI和ServiceLoader简化代码。

  • 会记录每一个类被ITransformationServiceILaunchPluginService请求修改的历史。

加载流程

Launcher类是ModLauncher的入口类:

  • 在构造方法中,初始化了用于存储Transformer的TransformStore,以及用于初始化ITransformationServiceTransformationServicesHandler

    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方法从ITransformationServicetransformers方法中获得了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的子类,其内部类DelegatedClassLoaderURLClassLoader的子类,大部分方法都会调用后者的方法

  • loadClass方法会通过多次调用最终调用DelegatedClassLoaderfindClass方法,这个方法首先读入二进制的类文件,然后调用ClassTransformertransform方法,这个方法会调用ITransformertransform

final String path = name.replace('.', '/').concat(".class");

final URL classResource = classBytesFinder.apply(path);
byte[] classBytes;
//省略读入文件的代码
classBytes = tcl.classTransformer.transform(classBytes, name);

制作方法

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

ITransformationService

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

这个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

一个没有修改任何类的实例:

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")));
    }
}

ServiceLoader

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

com.example.ExampleService

安装方法

库文件挂载

  • 复制版本json到另一个版本文件夹中,并修改对应的名称

  • libraries中加入ModLauncher及其依赖以及自己编写的CoreMod

  • 修改mainClasscpw.mods.modlauncher.Launcher

使用Forge加载

从Forge 1.13.2-25.0.216开始,可以使用Forge来加载无论是否包含普通Mod的ModLauncher CoreMod。

  • 放入.minecraft/mods文件夹即可

Last updated