# 引导

有关Mixin引导部分的说明可以参考 [Introduction to Mixins The Mixin Environment](https://github.com/SpongePowered/Mixin/wiki/Introduction-to-Mixins---The-Mixin-Environment)。

由于Mixin是为CoreMod服务的，所以Mixin需要使用CoreMod引导。\
由于LiteLoader和Fabric已经做好了引导工作，因此不需要由模组手动引导。\
也可以使用-javaagent参数引导（参见[MixinAgent](https://github.com/SpongePowered/Mixin/blob/2c72246fe0b67c145db5510e66c8873df18b5a9f/src/agent/java/org/spongepowered/tools/agent/MixinAgent.java#L208)）。

## 引导的时机

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

![](https://raw.githubusercontent.com/SpongePowered/Mixin/master/docs/images/mixin_env_2.png)

因为Mixin自带一个 [Tweaker](https://github.com/SpongePowered/Mixin/blob/master/src/launchwrapper/java/org/spongepowered/asm/mixin/EnvironmentStateTweaker.java) 需要被添加，所以需要在LaunchWrapper还在调用实现`ITweaker`的类的阶段引导Mixin，也就是上图的过程①，所以需要在CoreMod入口类合适的方法中引导。

再回顾LaunchWarpper的[加载流程](https://xfl03.gitbook.io/coremodtutor/3-yuan-ban-coremod/3.3)，因为Mixin会在`TweakClasses`中[添加一个Tweaker](https://github.com/SpongePowered/Mixin/blob/2c72246fe0b67c145db5510e66c8873df18b5a9f/src/launchwrapper/java/org/spongepowered/asm/service/mojang/MixinServiceLaunchWrapper.java#L147)，所以不能在循环`tweakClassNames`时（也就是在Tweaker的构造方法中）引导Mixin，否则会抛出`java.util.ConcurrentModificationException`。

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

```java
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](https://github.com/SpongePowered/Mixin/blob/2c72246fe0b67c145db5510e66c8873df18b5a9f/src/launchwrapper/java/org/spongepowered/asm/service/mojang/MixinServiceLaunchWrapperBootstrap.java#L53)），所以如果直接调用，会抛出如下异常：

```
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`加载一次。\
\&#xNAN;*不推荐这么做，因为这样打破了FML加载模组的规则*

```java
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](https://replaymod.com/)的[方法](https://github.com/ReplayMod/ReplayMod/blob/ec50efec104c8345a3a4f20fd44cfb3608cd81bb/src/main/java/com/replaymod/core/LoadingPlugin.java#L26-L42)，只需要获取到`CoreModManager`类中的`ignoredModFiles`字段，并从中移除模组自身文件名即可。

所以，现在应该这样：

```java
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`注解也被识别了。
