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”,未更新 |
相关条目
- 核心 mod 与读档闸门 —
IEarlyMixinLoader/ILateMixinLoader的接入点 - Access Transformer — mixin 依赖的字段公开化
- 序列化流水线 — 每个 mixin 对应的流水线步骤
- 内置原版数据管理器 —
ChunkMixinshadow 的字段由谁使用 - 随包资源清单 — 两份 mixin JSON 与缺失的 refmap