有关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引导方法如下所示:
Copy 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 ),所以如果直接调用,会抛出如下异常:
Copy 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加载模组的规则
Copy 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
字段,并从中移除模组自身文件名即可。
所以,现在应该这样:
Copy 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
注解也被识别了。