封包数据流

基本信息

属性 值
类型 原版网络封包的自定义编解码
涉及封包 S21、S22、S23、S26(均为服务端 → 客户端)
涉及 mixin 6 个(见 Mixin 清单)
实际传输载体 仍使用原版封包 ID,不新增封包类型

行为

Foundation 的核心手法是:把原版四个区块/方块同步封包的编解码方法整体改写成 「先写原版字段,再遍历 处理器注册表 里注册的 handler 追加自定义数据」。 这样其他 mod 可以在不新增封包 ID、不做自己的 mixin 的前提下,把自己的数据搭原版封包的 便车。

代价是这四个封包的方法被 @Overwrite / ASM 重写,其他 mod 若对同一方法做注入或 覆写几乎必然冲突——这也是全部 mixin 都设为 Phase.EARLY 的原因(见 兼容性与门控)。

S21 S21PacketChunkData(整区块数据)

方法 Foundation 的实现
func_149275_c() 直接 return HandlerRegistry.getChunkPacketBytes()——把「整块数据长度」从常量换成按注册累加的动态值
func_149269_a(Chunk chunk, boolean sendUpdates, int flagSubChunks) 见下

func_149269_a 完整流程:

  1. field_149286_i(静态 byte[])为 null 时按 getChunkPacketBytes() 分配并缓存。
  2. 若 sendUpdates 则 chunk.sendUpdates = true(保留原版副作用)。
  3. 按 aextendedblockstorage[i] != null && (!sendUpdates || !isEmpty()) && (flagSubChunks & 1 << i) != 0 逐子区块置位 extracted.field_150280_b。 源码注释说明:这个掩码本是给 LSB 数组用的,「isn’t really needed and Minecraft doesn’t even really handle it properly」,但 S26 需要它才能正常工作,所以必须保留。
  4. int bytes = HandlerRegistry.writeChunkPackets(chunk, sendUpdates, flagSubChunks, field_149286_i)。
  5. extracted.field_150282_a = new byte[bytes] 并 System.arraycopy 拷贝。 源码注释指出返回值就是 S21 做 inflate/deflate 需要的总长度。

S22 S22PacketMultiBlockChange(同区块多方块变更)

构造签名 (int count, short[] positions, Chunk chunk) 被 ASM 整体重写,转发到 newConstructor(详见 转换器链)。newConstructor 的行为:

  1. field_148925_b = new ChunkCoordIntPair(chunk.xPosition, chunk.zPosition)。
  2. blockPackets = new BlockPacketInfo[count]。
  3. 逐项从压缩位置解出坐标并立即从 chunk 采样: x = positions[i] >> 12 & 15、z = positions[i] >> 8 & 15、y = positions[i] & 255, 然后 chunk.getBlock(x, y, z) 与 chunk.getBlockMetadata(x, y, z) 填入 BlockPacketInfo。这一步是原版构造做不到的——原版丢弃了 chunk 引用。
  4. 打印 Foundation.LOG.info("Hello from S22 ASM")。
方法 写 读
区块坐标 writeInt(field_148925_b.chunkXPos) / writeInt(...chunkZPos) new ChunkCoordIntPair(data.readInt(), data.readInt())
数量 blockPackets == null → writeInt(0),否则 writeInt(length) count = data.readInt()
每项位置 ((x & 15) << 12) | ((z & 15) << 8) | (y & 255) 后 writeShort data.readShort() & 65535 后同样解位
每项数据 HandlerRegistry.writeS22Packets(info, data) HandlerRegistry.readS22Packets(info, data)

读路径使用 3 参构造 new BlockPacketInfo(x, y, z),此时 block = null、 metadata = 0,必须由 block handler 填回;写路径则是 5 参构造、字段已填好。 客户端 NetHandlerPlayClient.handleMultiBlockChange 拿到 func_148920_c() 的坐标,乘 16 后逐项调 func_147492_c(...) 落方块。

S23 S23PacketBlockChange(单方块变更)

字段 写 读
x field_148887_a writeInt readInt
y field_148885_b writeByte(有符号) readUnsignedByte
z field_148886_c writeInt readInt
block + metadata HandlerRegistry.writeS23Packets(...) HandlerRegistry.readS23Packets(...)

S23 与 S22 不同的两点:

  • S23 走 createBlockPacketInfo() / syncBlockPacketInfo(info) 往返,读路径的 BlockPacketInfo 已带上 x/y/z/block/metadata 五个值,handler 只需覆盖需要改的字段; S22 读路径的 info 只有坐标。
  • 源码注释(S21/S23/Chunk 三处 @author Cleptomania)反复标注同一句风险: 「If something is targeting this, it is guaranteed not gonna work with Foundation」。

S26 S26PacketMapChunkBulk(批量区块下发)

S26 把多个 S21 的原始数据拼在一个封包里。由于 S21 的长度已由 Foundation 改为动态值, S26 原版「按固定公式算每个内嵌区块长度」的代码全部失效,mixin 用「补长度 + 换长度」 两招修正:

注入点 方向 行为
foundation$injectS21SizeReads 读 在第一次 PacketBuffer.readInt() 前,按 @Local(ordinal = 0) short chunks 读出区块数并循环 data.readInt() 填满 s21Sizes
foundation$modifyI1Value1 读 @ModifyVariable @At(LOAD ordinal 5),用 s21Sizes[j] 替换
foundation$modifyI1Value2 读 @At(LOAD ordinal 6)
foundation$modifyI1Value3 读 @At(LOAD ordinal 7)
foundation$injectSizeWrites 写 在第一次 PacketBuffer.writeInt(I) 前,为每个 field_149260_f[i] 补写 field_149260_f[i].length

@Local 来自 MixinExtras(com.llamalad7.mixinextras.sugar.Local)。 源码在三个 @ModifyVariable 旁留了 TODO: 「Convert these all to one mixin applied on a slice, this is easier and I’m lazy」。

BlockPacketInfo(数据载体)

根包下的普通可变 POJO,非注册对象:

字段 类型 说明
x / y / z int 方块坐标(x、z 为区块内 0~15)
block net.minecraft.block.Block 3 参构造时为 null
metadata int 3 参构造时为 0

两个构造函数:(int x, int y, int z) 委托给 (x, y, z, null, 0);以及 (int x, int y, int z, Block block, int metadata)。全部 5 个字段都有 getter/setter。

数值

数值 值
被改写的原版封包类 4(S21、S22、S23、S26)
另被改写的非封包类 2(Chunk、NetHandlerPlayClient)
S22 位置压缩位数 x: 12~15、z: 8~11、y: 0~7
S21 静态缓冲区大小 HandlerRegistry.getChunkPacketBytes()(内置配置下 196,896)
分片协议开销 28 字节(4 + 4 × 6)

相关条目