Skip to content

Latest commit

 

History

History
146 lines (125 loc) · 8.13 KB

5.2.md

File metadata and controls

146 lines (125 loc) · 8.13 KB

引导

有关Mixin引导部分的说明可以参考 Introduction to Mixins The Mixin Environment

由于Mixin是为CoreMod服务的,所以Mixin需要使用CoreMod引导。
由于LiteLoader和Fabric已经做好了引导工作,因此不需要由模组手动引导。
也可以使用-javaagent参数引导(参见MixinAgent)。

引导的时机

借用Mixin wiki的一张图来说明这个问题:

因为Mixin自带一个 Tweaker 需要被添加,所以需要在LaunchWrapper还在调用实现ITweaker的类的阶段引导Mixin,也就是上图的过程①,所以需要在CoreMod入口类合适的方法中引导。

再回顾LaunchWarpper的加载流程,因为Mixin会在TweakClasses添加一个Tweaker,所以不能在循环tweakClassNames时(也就是在Tweaker的构造方法中)引导Mixin,否则会抛出java.util.ConcurrentModificationException

所以,Mixin需要在ITweaker中的acceptOptions或者injectIntoClassLoader中引导。一个基本的Mixin引导方法如下所示:

package com.example;

import java.io.File;
import java.util.List;
import net.minecraft.launchwrapper.ITweaker;
import net.minecraft.launchwrapper.LaunchClassLoader;
import org.spongepowered.asm.launch.MixinBootstrap;
import org.spongepowered.asm.mixin.Mixins;

public class ExampleTweaker implements ITweaker {
    @Override public void injectIntoClassLoader(LaunchClassLoader classLoader) {
        MixinBootstrap.init();
        Mixins.addConfiguration("mixins.example.json"); // 添加自己的Mixin配置文件
    }
    @Override public void acceptOptions(List<String> args, File gameDir, File assetsDir, String profile) { }
    @Override public String getLaunchTarget() { return ""; }
    @Override public String[] getLaunchArguments() { return new String[0]; }
}

TweakClass or FMLCorePlugin?

我们知道,FML提供了一个对ITweaker的封装接口,也就是IFMLLoadingPlugin,但是,MixinBootstrap不能在这里被直接调用

为什么?

因为IFMLLoadingPlugin的实现类是通过LaunchClassLoader加载的,ITweaker的实现类是通过sun.misc.Launcher$AppClassLoader加载的,而Mixin引导相关的类都会通过AppClassLoader加载(参见MixinServiceLaunchWrapperBootstrap),所以如果直接调用,会抛出如下异常:

java.lang.NoClassDefFoundError: org/spongepowered/asm/service/IMixinService
	at org.spongepowered.asm.service.MixinService.initService(MixinService.java:120)
	at org.spongepowered.asm.service.MixinService.getServiceInstance(MixinService.java:111)
	at org.spongepowered.asm.service.MixinService.getService(MixinService.java:106)
	at org.spongepowered.asm.launch.MixinBootstrap.<clinit>(MixinBootstrap.java:77)
	at com.example.ExamplePlugin.injectData(ExamplePlugin.java:60)
	at net.minecraftforge.fml.relauncher.CoreModManager$FMLPluginWrapper.injectIntoClassLoader(CoreModManager.java:151)
	at net.minecraft.launchwrapper.Launch.launch(Launch.java:115)
	at net.minecraft.launchwrapper.Launch.main(Launch.java:28)
Caused by: java.lang.ClassNotFoundException: org.spongepowered.asm.service.IMixinService
	at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at net.minecraft.launchwrapper.LaunchClassLoader.findClass(LaunchClassLoader.java:106)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	... 8 more

怎么办?

那就让IFMLLoadingPlugin实现类也用AppClassLoader加载一次。
不推荐这么做,因为这样打破了FML加载模组的规则

package com.example;

import java.util.Map;
import net.minecraft.launchwrapper.Launch;
import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.spongepowered.asm.launch.MixinBootstrap;
import org.spongepowered.asm.mixin.Mixins;

public class ExamplePlugin implements IFMLLoadingPlugin {
    public static void initMixin() {
        MixinBootstrap.init();
        Mixins.addConfiguration("mixins.example.json");
    }
    @Override public void injectData(Map<String, Object> data) {
        try {
            ClassLoader appClassLoader = Launch.class.getClassLoader();
            MethodUtils.invokeMethod(appClassLoader, true, "addURL", this.getClass().getProtectionDomain().getCodeSource().getLocation());
            MethodUtils.invokeStaticMethod(appClassLoader.loadClass(this.getClass().getName()), "initMixin");
        } catch (Exception e) {}
    }
    @Override public String[] getASMTransformerClass() { return null; }
    @Override public String getModContainerClass() { return null; }
    @Override public String getSetupClass() { return null; }
    @Override public String getAccessTransformerClass() { return null; }
}

在Tweaker-Mod中使用@Mod注解

在FMLCoreMod中,只需要在清单文件中添加FMLCorePluginContainsFMLMod属性就能让这个模组也能作为普通模组加载,但是在Tweaker-Mod中是不行的,按照加载流程,FML在读取到有TweakClass属性后就不再继续读取其他的属性了(也就是只有FMLAT属性会被读到,其他都会被忽略)。

按照ReplayMod方法,只需要获取到CoreModManager类中的ignoredModFiles字段,并从中移除模组自身文件名即可。

所以,现在应该这样:

package com.example;

import java.io.File;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.CodeSource;
import java.util.List;
import net.minecraft.launchwrapper.ITweaker;
import net.minecraft.launchwrapper.LaunchClassLoader;
import net.minecraftforge.fml.relauncher.CoreModManager;
import org.apache.logging.log4j.LogManager;
import org.spongepowered.asm.launch.MixinBootstrap;
import org.spongepowered.asm.mixin.Mixins;

public class ExampleTweaker implements ITweaker {
    @Override public void injectIntoClassLoader(LaunchClassLoader classLoader) {
        MixinBootstrap.init();
        Mixins.addConfiguration("mixins.example.json");
        CodeSource codeSource = this.getClass().getProtectionDomain().getCodeSource();
        if (codeSource != null) {
            URL location = codeSource.getLocation();
            try {
                File file = new File(location.toURI());
                if (file.isFile()) {
                    CoreModManager.getReparseableCoremods().remove(file.getName());
                }
            } catch (URISyntaxException e) {
                e.printStackTrace();
            }
        } else {
            LogManager.getLogger().warn("No CodeSource, if this is not a development environment we might run into problems!");
            LogManager.getLogger().warn(this.getClass().getProtectionDomain());
        }
    }
    @Override public void acceptOptions(List<String> args, File gameDir, File assetsDir, String profile) { }
    @Override public String getLaunchTarget() { return ""; }
    @Override public String[] getLaunchArguments() { return new String[0]; }
}

现在,就可以让这个模组的@Mod注解也被识别了。