如何向 Fabric Mixin 提供额外的混淆表 —— 一个由 OptiFine 兼容性引发的问题


参考内容:

(内含各种出错日志信息)


缘由
昨天(2020年6月7号)看到 MutliMC 的 Discord 服务器里有人询问游戏崩溃的问题,然后根据提供的日志信息追踪到了上面两条 issue。感觉对 OptiFine 做兼容是一个很普遍的问题,尤其是在 Fabric 社区鼓励使用 Mixin 制作 CoreMod 的情况下,所以特地开一个提问帖。

引发问题的原因
Forge 对 net.minecraft.network.PacketBuffer (MCP name) 进行了如下修改:

由于 OptiFine 需要对 Forge 兼容,只能也进行类似的修改,但是 Fabric 并没有修改过这些内容,因此当有 mod 需要用 Mixin 修改这个方法的时候,就会出问题。

下面是 Cardinal-Components-API 引发问题的相关代码:

在 Mixin refmap 中,writeItemStack 指向的是 Lnet/minecraft/class_2540;method_10793(Lnet/minecraft/class_1799;)Lnet/minecraft/class_2540;,因此无法匹配 OptiFine 修改后的 Lnet/minecraft/class_2540;writeItemStack(Lnet/minecraft/class_1799;Z)Lnet/minecraft/class_2540;,从而引发游戏崩溃。

做过的尝试
我对这段代码进行了如下修改:

import nerdhub.cardinal.components.internal.ItemStackAccessor;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.util.PacketByteBuf;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Surrogate;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

// ...

    @Inject(
        method = { "writeItemStack(Lnet/minecraft/item/ItemStack;)Lnet/minecraft/util/PacketByteBuf;", "writeItemStack(Lnet/minecraft/item/ItemStack;Z)Lnet/minecraft/util/PacketByteBuf;" },
        at = @At(value = "INVOKE", target = "Lnet/minecraft/util/PacketByteBuf;writeCompoundTag(Lnet/minecraft/nbt/CompoundTag;)Lnet/minecraft/util/PacketByteBuf;", shift = At.Shift.AFTER)
    )
    private void writeItemStack(ItemStack stack, boolean limitedTag, CallbackInfoReturnable<PacketByteBuf> cir) {
        this.cardinal_writeItemStack(stack);
    }

    @Surrogate
    private void writeItemStack(ItemStack stack, CallbackInfoReturnable<PacketByteBuf> cir) {
        this.cardinal_writeItemStack(stack);
    }

    @Unique
    private void cardinal_writeItemStack(ItemStack stack) {
        //noinspection ConstantConditions
        this.writeCompoundTag(((ItemStackAccessor)(Object)stack).cardinal_getComponentContainer().toTag(new CompoundTag()));
    }

很不幸,问题依旧,崩溃原因与上述 issue 中的一致。
查看了一下编译后的 Mixin refmap,发现 writeItemStack(Lnet/minecraft/item/ItemStack;Z)Lnet/minecraft/util/PacketByteBuf; 并没有写入 refmap 中,所以如果手动添加一条:

"writeItemStack(Lnet/minecraft/item/ItemStack;Z)Lnet/minecraft/util/PacketByteBuf;": "Lnet/minecraft/class_2540;writeItemStack(Lnet/minecraft/class_1799;Z)Lnet/minecraft/class_2540;",

这样可以正常运行的,
或者在代码中将

method = { "writeItemStack(Lnet/minecraft/item/ItemStack;)Lnet/minecraft/util/PacketByteBuf;", "writeItemStack(Lnet/minecraft/item/ItemStack;Z)Lnet/minecraft/util/PacketByteBuf;" },

修改为

method = { "writeItemStack(Lnet/minecraft/item/ItemStack;)Lnet/minecraft/util/PacketByteBuf;", "writeItemStack(Lnet/minecraft/class_1799;Z)Lnet/minecraft/class_2540;" },

也可以正常运行。

但是毕竟这样手动混淆不优雅,并且在原生 Mixin 并配合 MixinGradle 的情况下,可以在 build.gradle 中添加下列内容达到增加混淆表内容的目的:

mixin {
    extraMappings "additional-mappings.tsrg"
}

但是 Fabric Mixin 下我没有查到类似的东西,我能查到的是这个:tutorial:mappings [Fabric Wiki]
但是这是讲的自定义整个混淆表,很明显我们不需要这样的东西,所以就有了标题上的这个问题。


nowandfuture


你可以考虑换个思路,比如注入其他函数(或者optifine中的函数);

这个问题不是很好办,毕竟optifine不是经过mixin注入的,所以处理起来肯定不够优雅。

CubicChunks还有LittleTiles 这几个mod需要处理Optifine的兼容所以会遇到大量这类问题,你如果想看看有什么具体思路可以找这两个的作者(团队),前者作者十分活跃,我暂时没有碰到这类问题(我的mod前期是直接字节码注入,后来才换成Mixin,所以有部分还没来得及替换),但是未来很可能遇到同样的问题。下面的Issue来自CC的作者,一方面取消了remap然后使用@Dynamic来防止编译期出错。



如果单就「其他 Mod 与 OptiFine 兼容」这个问题而言,其实非常大,每个地方都需要具体问题具体分析,因为会遇到各种奇葩的修改,个人认为 Mixin 针对「方法或字段编译时不存在,但是运行时可能存在」的情况已做的工作非常之少。

我也有个项目用于处理各种 OptiFine 和新版 Forge 之间的兼容性问题:

其中至少遇到过:

为了解决上面的问题,甚至自己造了一套注解,并且在运行时用 ASM 应用这些注解。


如果就这个提问帖中提到的项目而言,崩溃的原因仅仅是无法匹配需要的注入的地方,并且 OptiFine 没有对这个地方做任何增强,只是单纯地为了兼容 Forge 而修改。

因此我的想法非常简单:在 @Inject.moethod 中添加 OptiFine 修改后的参数的方法,但是就遇到了没办法重混淆的问题,然后就有了上面这个问题。

目前已经 PR 了:


system


该主题在最后一个回复创建后7天后自动关闭。不再允许新的回复。


FledgeXu