封包数据流
基本信息
| 属性 | 值 |
|---|---|
| 类型 | 原版网络封包的自定义编解码 |
| 涉及封包 | 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 完整流程:
field_149286_i(静态byte[])为 null 时按getChunkPacketBytes()分配并缓存。- 若
sendUpdates则chunk.sendUpdates = true(保留原版副作用)。 - 按
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 需要它才能正常工作,所以必须保留。 int bytes = HandlerRegistry.writeChunkPackets(chunk, sendUpdates, flagSubChunks, field_149286_i)。extracted.field_150282_a = new byte[bytes]并System.arraycopy拷贝。 源码注释指出返回值就是 S21 做 inflate/deflate 需要的总长度。
S22 S22PacketMultiBlockChange(同区块多方块变更)
构造签名 (int count, short[] positions, Chunk chunk) 被 ASM 整体重写,转发到
newConstructor(详见 转换器链)。newConstructor 的行为:
field_148925_b = new ChunkCoordIntPair(chunk.xPosition, chunk.zPosition)。blockPackets = new BlockPacketInfo[count]。- 逐项从压缩位置解出坐标并立即从 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引用。 - 打印
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) |