调试 Mixin

小技巧

推荐的做法”一文的 Mixin 一节讨论了 Mixin、Coremod 及其他底层概念的定义。

Spongepowered Mixin 是一套允许 SpongeAPI 和其他程序员与 Minecraft 交互的系统。本文不是对 Mixin 系统的详解。如需文档及帮助,请参阅 Mixin 的 wiki

注解

Mixin 有它自己的仓库,你可以直接克隆它到本地,导入你的 IDE 中以追踪错误信息或进一步研究。除此之外,你还可以将你克隆到本地的仓库通过项目设定(Project Settings)指定为你本地的 Sponge 实现的一个模块(Module),并以此对 Mixin 进行调试。

输出

默认,Mixin 会在 run 目录下新建 .mixin.out 目录。这个目录包含一名为 audit 的子目录,其下包含空的文本形式的报告及 csv 格式的文件。这些文件会在 mixin.checks.interfaces 选项启用时使用。

然而,Mixin 还支持通过 Java System Properties 启用大量调试及审查功能。有时候你需要或想要看到 Mixin 处理后的输出。你可以通过使用下列 JVM 选项中的一个来查阅 Mixin 的输出:

-Dmixin.debug=true 启用全部调试特性
-Dmixin.debug.export=true 仅启用会向磁盘写入输出的(调试)特性

在 IntelliJ IDEA 中,通过 Run -> Edit Configurations... 打开 Run/Debug Configuration 窗口,在这个窗口中可以修改 VM 选项。请确认你修改的 Application Configuration 是你想要修改的那一个(Minecraft ClientMinecraft Server 或者两个都是)。

这些选项中若有任意一个启用了,都会令 Mixin 在 .mixin.out 目录下产生一个名为 classes 的新目录。这个目录按照标准的 Java 包与类的结构保存了所有经 Mixin 处理后生成的 Class 的内容。

注解

这些选项与你使用 Mixin 的方式(克隆 Mixin 仓库、以新模块的方式挂载、使用 DemonWav 写的 Minecraft Development for IntelliJ 插件)无关。换言之,这些选项是 Mixin 它本身的一部分。

小技巧

关于更多 Mixin 的 VM 选项及对应的解释,可参考:Mixin Java System Properties

反编译

有若干查看 Mixin 输出的 class 文件的方法:

  • IDE

    在 IDE 中直接打开目标文件,IDE 会自动反编译并显示“源码”。

  • Fernflower

    将 Fernflower 的 jar 加入运行时的 classpath,Mixin 会自动输出反编译的结果。

  • JD-Gui 是一款独立的,带图形界面的 Java 反编译工具。

阶段与环境

要理解 Mixin,首先应当明白 Minecraft 游戏代码的执行分成了两个阶段:pre-initdefault。其中,pre-init 阶段即打开游戏(命令行、双击快捷方式、点击“启动”按钮、……)到游戏开始启动(所有修改已经完成,游戏即将进入主循环)之间的阶段,而 default 则是整个“游戏已准备就绪,现在可以开始加载”之后的阶段。

对基础设施的修改(加载器、Transformer 等)发生在 pre-init 阶段。Mixin 同时也会在 pre-init 阶段收集有关其他 Mixin 的信息,这些信息会在 default 阶段使用。

Mixin 将阶段划分到了不同的“环境”中。其中,pre-init 阶段隶属于 PREINIT 这个环境,而 default 阶段则隶属于 DEFAULT 环境

小技巧

环境可通过 Mixin 配置文件指定,比如:"target": "@env(PREINIT)""target": "@env(DEFAULT)"

配置

Mixin 主要依赖配置文件。每一个 Mixin 配置都定义了一组该配置需要应用的 Mixin 类,任何配置文件没有指定的 Mixin 都不会生效。可以使用多个 Mixin 配置文件,但每一个配置文件都必须只针对一个环境。分开的配置文件还起到方便整理的作用。

每一组 Mixin 都分成三部分:Common、Client 和 Server。隶属 Common 的 Mixin 首先生效,其次是根据实际情况决定的 Client 或 Server 中的其中一个,不可能同时生效。

谁启动的 Mixin?

Mixin 子系统通过一套统一的接口与其他使用 Mixin 的程序打交道,这与程序是什么、谁写的、来自什么组织都毫无关系。然而,只可能有一个程序首先启动 Mixin,这同时也会决定 Mixin 的版本。因使用旧版 Mixin 而导致冲突已经不是个案了。也因此,各个文件载入并执行的顺序不容忽视。理想状态下,Sponge 应当在 Forge 之后首先被加载,其次才是其他的 Tweaker 和 Coremod。

损坏的 Mixin

若有类(因为某个 Mixin)无法正确加载,游戏则会崩溃退出。这个情况被称作“损坏的 Mixin”(Broken Mixin)。这通常意味着注解和/或签名出问题了,比如下面这个例子:

MixinSpongeSmeltingRecipe.java:41: error: No obfuscation mapping for @Overwrite method
    default String getId() {
                   ^

这个错误可通过将 @Overwrite 改为 @Overwrite(remap = false) 解决。将 remap 设定为 false 后,注解处理器会在为这个 Mixin 构建混淆表时忽略这个注解。

对源码的分析也许会让人以为 default 关键字的使用才是问题所在,但将其改为 static 后,日志输出会变成这样:

MixinSpongeSmeltingRecipe.java:41: error: getId() in MixinSpongeSmeltingRecipe clashes with getId() in
    static String getId() {
                  ^
  overriding method is static

MixinSpongeSmeltingRecipe.java:40: error: method does not override or implement a method from a supertype
    @Override
    ^

MixinSpongeSmeltingRecipe.java:42: error: non-static variable this cannot be referenced from a static context
        return CustomSmeltingRecipeIds.getDefaultId((SmeltingRecipe) this);
                                                                     ^

如你所见,这次不止有一个错误了,而是三个!正确的修复方式已在前文中阐述过了:将 remap 设定为 false。谨记,这个例子不是说明“这类问题如何解决”的,而是说明“损坏的 Mixin 可能长什么样,且通常都和错误的注解及签名有关”的。

注解

可参考 Mixin Wiki 的“方法签名”一文了解更多。

这一小节未来会包含常见的 Mixin 损坏问题及解决方案列表。如果你觉得你可以帮上忙,我们欢迎你直接向我们的 GitHub 仓库发起 PR

Minecraft Development for IntelliJ

针对 IntelliJ IDEA 开发的 Minecraft Development 插件是在使用 Mixin 进行开发时的利器。有了它之后,你可以直接单步步进到 Mixin 代码中,而无需折腾 Mixin 的类加载器产生的输出类。

注解

该插件的网站提供了安装及相关支持信息。你还可以在他们的网站上找到他们的 GitHub 仓库地址,你可以从中学习,亦可向他们贡献代码。

安装

在不克隆其仓库的情况下使用该插件:

  1. 依次打开 File -> Settings -> Plugins,选择 Marketplace
  2. minecraft 为关键字进行搜索,找到 Minecraft Development by DemonWav
  3. 点击 Install 按钮(请暂时不要重启 IntelliJ IDEA)。
  4. 前往 https://github.com/minecraft-dev/MinecraftDev 然后将当前整个仓库以 ZIP 格式打包下载。
  5. 下列选项中,二选一执行:
    • 打开 ZIP,找到 idea-configs 目录,将其中内容解压到你的 Sponge 工作环境下的 .idea 目录中。
    • 解压 ZIP,在解压产物中找到 idea-configs 目录,将其中内容复制到你的 Sponge 工作环境下的 .idea 目录中。
  6. 重启 IntelliJ IDEA。

此时插件已准备就绪,你的项目可以使用插件提供的使用配置选项及版权设定了。

使用

未完待续