Mixin 清单

基本信息

属性 值
构建声明 build.gradle.kts → mixin { pkg = "internal.mixin.mixins" }
mixin 包 com.falsepattern.chunk.internal.mixin.mixins
mixin 类数 10(common/ 8 + client/ 2)
分组枚举 com.falsepattern.chunk.internal.mixin.plugin.Mixin(5 个常量)
加载框架 com.gtnewhorizon.gtnhmixins(GTNH Mixins),非原版 Mixin
配置 JSON mixins.chunkapi.early.json + mixins.chunkapi.late.json
本 mod 是否提供 IClassTransformer 否,getASMTransformerClass() 返回 new String[0]

⚠️ build.gradle.kts 里没有 usesMixins 键,没有 mixinPlugin 键。加载完全由 core mod实现 IEarlyMixinLoader / LateMixins 实现 ILateMixinLoader 驱动。

两份 mixin 配置 JSON

src/main/resources/mixins.chunkapi.early.json 与 mixins.chunkapi.late.json 内容完全相同:

{
  "required": true,
  "minVersion": "0.8.5",
  "package": "com.falsepattern.chunk.internal.mixin.mixins",
  "refmap": "mixins.chunkapi.refmap.json",
  "target": "@env(DEFAULT)",
  "compatibilityLevel": "JAVA_8"
}

⚠️ 两份 JSON 都没有 "mixins" 数组,也没有 "client" / "server" 数组——10 个类的清单在运行时由 Mixin 枚举注入(IMixins.getEarlyMixins / getLateMixins)。JSON 只提供包名与兼容性级别。

⚠️ mixins.chunkapi.refmap.json 被声明为 refmap,但该文件不在 src/main/resources 里(构建期生成),见 随包资源清单。

⚠️ "target": "@env(DEFAULT)" 是 gtnhmixins 的写法,与原版 Mixin 的 "target": "@env(DEFAULT)" 语义一致但由不同框架解析。

Mixin 枚举:5 个分组

public enum Mixin implements IMixins {
    VanillaCore(Phase.EARLY, () -> !hasThermos(),
                common("vanilla.S26PacketMapChunkBulkMixin")),
    ThermosCore(Phase.EARLY, () -> hasThermos(),
                common("thermos.S26PacketMapChunkBulkMixin")),
    CommonCore(Phase.EARLY,
               common("base.PlayerInstanceMixin",
                      "base.S21PacketChunkDataMixin",
                      "base.S22PacketMultiBlockChangeMixin",
                      "base.S23PacketBlockChangeMixin"),
               client("vanilla.NetHandlerPlayClientMixin")),
    Core_NoSpool(Phase.EARLY,
                 avoid(Spool),
                 common("base.AnvilChunkLoaderMixin"),
                 client("vanilla.ChunkMixin")),
    Compat_LookingGlass(Phase.LATE,
                        require(LookingGlass),
                        common("lookingglass.PacketChunkInfoMixin")),
}
分组 Phase 条件 Side 包含的 mixin
VanillaCore EARLY !hasThermos() COMMON common/vanilla/S26PacketMapChunkBulkMixin
ThermosCore EARLY hasThermos() COMMON common/thermos/S26PacketMapChunkBulkMixin
CommonCore EARLY 无条件 COMMON + CLIENT base/PlayerInstanceMixin、base/S21PacketChunkDataMixin、base/S22PacketMultiBlockChangeMixin、base/S23PacketBlockChangeMixin + client/vanilla/NetHandlerPlayClientMixin
Core_NoSpool EARLY avoid(Spool) COMMON + CLIENT base/AnvilChunkLoaderMixin + client/vanilla/ChunkMixin
Compat_LookingGlass LATE require(LookingGlass) COMMON common/lookingglass/PacketChunkInfoMixin

⚠️ 只有 1 个 Phase.LATE 分组(Compat_LookingGlass),其余 4 个全是 EARLY。

⚠️ VanillaCore / ThermosCore 是互斥二选一:两者都声明 S26PacketMapChunkBulkMixin,靠 hasThermos() 切换到 common.thermos 或 common.vanilla 包下的不同类。hasThermos() 的实现是 Class.forName("thermos.Thermos") 包 ClassNotFoundException。

⚠️ 只有 Core_NoSpool 带 avoid(Spool)——CommonCore 里的 PlayerInstanceMixin 与 S2xPacket*Mixin 在 Spool 存在时依然会被应用,只有 AnvilChunkLoaderMixin 与 ChunkMixin 被跳过。源码在 Core_NoSpool 上方留有注释指明出处:“from: https://github.com/BallOfEnergy1/ChunkAPI/commit/4f5c0e60e04b6892d206f5f5d93cb20ba6b45608”。

条件构造器

Mixin 有 6 个重载构造器,靠 MixinBuilder 的重载消歧:SidedMixins...、(BooleanSupplier, SidedMixins...)、(TaggedMod, SidedMixins...)、(TaggedMod[], SidedMixins...)、(BooleanSupplier, TaggedMod, SidedMixins...)、(BooleanSupplier, TaggedMod[], SidedMixins...)。

internal.mixin.plugin.fplib.MixinHelper 是配套的静态工厂:builder(...) 6 个重载、common(...) / client(...) / server(...)、require(ITargetMod) / avoid(ITargetMod) / mods(...)。

⚠️ MixinHelper.server(...) 与 Mixin.java 里的私有 server(...) 静态方法都没有任何调用点——mixins/ 下也没有 server/ 目录(实际只有 client/ 与 common/)。全 mod 无服务端专属 mixin。

目标 mod(TargetMod 枚举)

com.falsepattern.chunk.internal.mixin.plugin.TargetMod implements ITargetMod:

常量 目标类
LookingGlass com.xcompwiz.lookingglass.LookingGlass
Spool com.gamma.spool.core.Spool

构建期依赖在 build.gradle.kts:compileOnly(deobfCurse("lookingglass-230541:2321557")),注释标为 //LookingGlass 0.2.0.01。Spool 没有声明依赖——只靠 ITargetModBuilder 的类存在性判定。

⚠️ internal.mixin.plugin.fplib.TargetMod 是另一个类(带 name 字段与 toString()),在 plugin 枚举之外,全仓库无引用,属死代码。

10 个 mixin 类

common/base/ —— 4 个,服务端 + 客户端通用

类 @Mixin 目标 手法 方法
PlayerInstanceMixin PlayerManager.PlayerInstance @Redirect sendChunkUpdate
S21PacketChunkDataMixin S21PacketChunkData @Overwrite × 4 func_149275_c()、func_149269_a(Chunk, boolean, int)、writePacketData、readPacketData
S22PacketMultiBlockChangeMixin S22PacketMultiBlockChange @Overwrite × 2 + @Inject × 1 + implements CustomPacketMultiBlockChange readPacketData、writePacketData、<init>(I[SLnet/minecraft/world/chunk/Chunk;)V
S23PacketBlockChangeMixin S23PacketBlockChange @Overwrite × 2 + @Inject × 1 + implements CustomPacketBlockChange readPacketData、writePacketData、<init>(IIILnet/minecraft/world/World;)V

逐条注入点(verbatim)

PlayerInstanceMixin:

@Redirect(method = "sendChunkUpdate",
          at = @At(value = "NEW",
                   target = "(I[SLnet/minecraft/world/chunk/Chunk;)Lnet/minecraft/network/play/server/S22PacketMultiBlockChange;"),
          require = 1)
private S22PacketMultiBlockChange hijackPacket(int count, short[] positions, Chunk chunk)

S22PacketMultiBlockChangeMixin:

@SuppressWarnings("DefaultAnnotationParam")
@Inject(method = "<init>(I[SLnet/minecraft/world/chunk/Chunk;)V",
        at = @At(value = "FIELD",
                 target = "Lnet/minecraft/network/play/server/S22PacketMultiBlockChange;field_148925_b:Lnet/minecraft/world/ChunkCoordIntPair;",
                 unsafe = true),
        require = 1)

⚠️ unsafe = true —— 注入到原版构造器内字段首次赋值处,绕过 final 语义。源码另带 @SuppressWarnings("DefaultAnnotationParam")(原 Mixin 对未显式写 expect/allow 的告警)。

S23PacketBlockChangeMixin:

@Inject(method = "<init>(IIILnet/minecraft/world/World;)V",
        at = @At("RETURN"), require = 1)

@Shadow 的 SRG 别名(verbatim,全部 remap = false)

类 字段 @Shadow 参数 SRG 别名
S21PacketChunkDataMixin static byte[] buffer aliases + remap = false field_149286_i
S21PacketChunkDataMixin Semaphore deflateGate 仅 remap = false(无别名) —
S21PacketChunkDataMixin int xPosition aliases + remap = false field_149284_a
S21PacketChunkDataMixin int zPosition aliases + remap = false field_149282_b
S21PacketChunkDataMixin int subChunkMask aliases + remap = false field_149283_c
S21PacketChunkDataMixin byte[] deflatedData aliases + remap = false field_149281_e
S21PacketChunkDataMixin byte[] data aliases + remap = false field_149278_f
S21PacketChunkDataMixin boolean forceUpdate aliases + remap = false field_149279_g
S21PacketChunkDataMixin int deflatedSize aliases + remap = false field_149285_h
S21PacketChunkDataMixin protected abstract void deflate() 仅 remap = false(无别名) —
S22PacketMultiBlockChangeMixin ChunkCoordIntPair coord aliases + remap = false field_148925_b
S23PacketBlockChangeMixin int xCoord aliases + remap = false field_148887_a
S23PacketBlockChangeMixin int yCoord aliases + remap = false field_148885_b
S23PacketBlockChangeMixin int zCoord aliases + remap = false field_148886_c
PlayerInstanceMixin — 无任何 @Shadow —

⚠️ 别名数值与字段名并不同序(如 xPosition 的别名是 field_149284_a,而 deflateGate 才是 field_149284_a 的邻位),这是按原版源码实际声明照抄的,不要按数值大小推断字段对应关系。9 个别名互不重复,全部唯一。

⚠️ PlayerInstanceMixin 没有任何 @Shadow——它能改 PlayerManager$PlayerInstance 靠的是 Access Transformer 把整个类公开化。

S23PacketBlockChangeMixin 直接 @Shadow 两个 public 字段(无 remap = false,走正常重映射):

@Shadow public Block field_148883_d;
@Shadow public int  field_148884_e;

@Shadow public 是读取原版字段并生成访问器,不是赋值——真正的赋值发生在 chunkapi$init 里(field_148883_d = chunk.getBlock(x, y, z))。

common/vanilla/ 与 common/thermos/ —— 各 1 个,互斥

两个类都 @Mixin(S26PacketMapChunkBulk.class),@Overwrite 同样的 readPacketData 与 writePacketData。

唯一差异在 writePacketData 与 @Shadow 声明:

common/vanilla/S26PacketMapChunkBulkMixin common/thermos/S26PacketMapChunkBulkMixin
inflaterBuffer static byte[] inflaterBuffer + 别名 field_149268_i byte[] inflaterBuffer(实例字段)+ 别名 field_149268_i
deflateGate Semaphore deflateGate(无 @Shadow 别名) 无此字段
deflate() @Shadow protected abstract void deflate() 无此 shadow
压缩方法 deflatedData == null → deflateGate.acquireUninterruptibly() → deflate() → release() 直接 compress()(无懒加载、无信号量)
writePacketData 作者注释 @author FalsePattern @author FalsePattern + @author Cardinalstar16,@reason 是 “Replace functionality for thermos”

两者 readPacketData 完全相同(含 LockHelper.bufferLockS26PacketMapChunkBulk 自旋锁与 new byte[S21PacketChunkData.func_149275_c() * chunkCount])。

⚠️ 两份 readPacketData / writePacketData 在 subChunkMSBMasks 上只读入、从不上传——subChunkMasks[i] = data.readUnsignedShort() 之后 subChunkMSBMasks 数组被分配但始终为全 0(对照 序列化流水线 里 BlockIDManager 自己发的 msbMask 头)。

client/vanilla/ —— 2 个,仅客户端

类 @Mixin 目标 手法
ChunkMixin Chunk @Overwrite fillChunk(@SideOnly(Side.CLIENT))
NetHandlerPlayClientMixin NetHandlerPlayClient,implements INetHandlerPlayClient @Inject handleBlockChange(@At("RETURN"))+ @Overwrite handleMultiBlockChange

ChunkMixin 的 @Shadow 字段:chunkTileEntityMap(Map<?, ?>)、isLightPopulated、isTerrainPopulated、worldObj、storageArrays(ExtendedBlockStorage[],private);@Shadow 方法:generateHeightMap()、getBlock(int,int,int)(参数名 p_150810_1_ 等)、getBlockMetadata(int,int,int)(参数名 p_76628_1_ 等)。

⚠️ ChunkMixin 用了 @SideOnly(Side.CLIENT) 而非 Side 限定的 mixin 分组——但它同时被 client(...) 归入 Core_NoSpool 的 CLIENT 侧,属双重保险。@Overwrite 上还带 @reason "Replace functionality"。

common/lookingglass/ —— 1 个

PacketChunkInfoMixin 的 @Mixin(PacketChunkInfo.class),@Mixin 前先 extends PacketHandlerBase(LookingGlass 自己的基类)。

@Overwrite(remap = false) 三个方法:

方法 @reason
public static S21PacketChunkData.Extracted getMapChunkData(Chunk, boolean, int) “This class was just copied vanilla code.” —— 直接 return S21PacketChunkData.func_149269_a(chunk, forceUpdate, subChunkMask)
public static FMLProxyPacket createPacket(Chunk, boolean, int, int dim) “Wired up to ChunkAPI” —— 手写 ByteBuf 布局,走 deflateGate 与 PacketHandlerBase.createDataBuffer
public void handle(ByteBuf in, EntityPlayer player) “Wired up to ChunkAPI” —— 读布局后调 this.handle(player, chunkData, dim, xPos, zPos, forceUpdate, (short) subChunkMask, (short)0)

解压失败分支:

LookingGlassPacketManager.bus.sendToServer(PacketRequestChunk.createPacket(xPos, subChunkMask, zPos, dim));
LoggerUtils.error("Chunk decompression failed: \t%d\t\t%d : %d\n", subChunkMask, compressedSize, uncompressedSize);

⚠️ 重请求传的是 subChunkMask(int)而非重试 mask,PacketRequestChunk.createPacket 收到的第三个参数是 zPos、第四个是 dim,参数顺序为 (xPos, subChunkMask, zPos, dim)——源码如此,未修正。

⚠️ handle 转发时把 yMSBPos 硬编码为 (short)0。

@Shadow(remap = false) 成员:static Semaphore deflateGate、static int deflate(byte[], byte[])(声明体返回 0,是占位)、abstract void handle(EntityPlayer, byte[], int, int, int, boolean, short, short)、abstract byte[] inflateChunkData(ByteBuf, int, int)。

LockHelper —— 两把内部锁

internal.mixin.helpers.LockHelper,两个 public static final ReentrantLock:

锁 保护
bufferLockS21PacketChunkData S21PacketChunkData 静态 buffer
bufferLockS26PacketMapChunkBulk S26PacketMapChunkBulk 的 inflaterBuffer

获取方式均为 while (!lock.tryLock()) { Thread.yield(); } 自旋,配 try/finally。见 序列化流水线。

随包接口:internal.impl.CustomPacket*

两个 mixin 通过 implements 暴露的方法集,在 internal.impl 包(非 api 包,但两个 package-info.java 之外无 @ApiStatus 标注):

CustomPacketBlockChange(由 S23PacketBlockChangeMixin 实现):

方法
void chunkapi$init(int x, int y, int z, Chunk chunk)
void chunkapi$x(int value) / chunkapi$y(int) / chunkapi$z(int)
int chunkapi$x() / chunkapi$y() / chunkapi$z()

CustomPacketMultiBlockChange(由 S22PacketMultiBlockChangeMixin 实现):

方法
void chunkapi$init(int count, short[] crammedPositions, Chunk chunk)
S23PacketBlockChange[] chunkapi$subPackets()

⚠️ ChunkMixin 用 (Chunk) (Object) this 强转(因为 @Mixin 类不继承目标类),S22/S23 mixin 同样用 (S23PacketBlockChange) (Object) this。这是 mixin 的标准写法,非错误。

源码中的不一致

位置 现象
S26PacketMapChunkBulk 两份 readPacketData subChunkMSBMasks 被分配为 new int[chunkCount] 但从不写入也从不读取
PacketChunkInfoMixin.handle 重请求时把 subChunkMask 当作 mask 传参;转发调用把 yMSBPos 硬编码 (short)0
Plugin.TargetMod(fplib 下的那个) 与 plugin.TargetMod 枚举重名同包结构,但无引用点
MixinHelper.server(...) / Mixin.server(...) 无调用点,mixins/server/ 目录不存在
Mixin 枚举注释 Core_NoSpool 上方的出处 URL 指向上游 BallOfEnergy1 仓库的一次 commit
common/thermos/S26PacketMapChunkBulkMixin @reason 保持原文 “Replace functionality for thermos”,未更新

相关条目