包 ID

Play 状态 · 逐版本重排 · 返回总条目

拉弓包本身没有包 ID。它在线上是九个原版包各自带着自己的 ID 连续发出;这些 ID 在 Play 状态里每个版本都会重排,所以只对具体版本有意义。下表给出 1.21.4 的取值与协议号对照。

协议号

握手阶段客户端报的版本号,用来确认两份表是不是同一版:

版本协议号备注
1.17755Ping / Pong 接管事务确认的第一版
1.17.1756
1.18.27581.18 与 1.18.1 为 757
1.19.47621.19 至 1.19.3 依次为 759 至 761
1.20.47651.20.2 为 764,1.20 与 1.20.1 为 763
1.21.47691.20.6 为 766,1.21 与 1.21.1 为 767

1.21.4 的取值

九个包在 1.21.4 的 Play 状态里的 ID。客户端一侧按「服务端 → 客户端」列,服务端一侧按「客户端 → 服务端」列:

简称方向类名ID(十六进制)ID(十进制)
S32服务端 → 客户端ClientboundPingPacket0x3755
S08服务端 → 客户端ClientboundPlayerPositionPacket0x4266
C0F客户端 → 服务端ServerboundPongPacket0x2B43
C07客户端 → 服务端ServerboundPlayerActionPacket0x2739
C08客户端 → 服务端ServerboundUseItemPacket0x3D61
C06客户端 → 服务端ServerboundMovePlayerPacket.PosRot0x1D29
C04客户端 → 服务端ServerboundMovePlayerPacket.Pos0x1C28
C03客户端 → 服务端ServerboundMovePlayerPacket.StatusOnly0x1F31
C09客户端 → 服务端ServerboundSetCarriedItemPacket0x3351

同一个移动包族还有一个只带朝向的变体 ServerboundMovePlayerPacket.Rot,在 1.21.4 里是 0x1E(30)。拉弓包不用它,但切包时会碰到。

为什么每版都变

Play 状态的包 ID 是「注册顺序」而不是固定常量:新增一个包,排在它后面的全部加一。1.17 到 1.21.4 之间几乎每个小版本都动过这张表,所以网上抄来的 ID 换个版本就对不上。

唯一稳定的是字段结构:S32 永远只有一个 int,C0F 也只有一个 int,C08 带手与朝向。判断一个包是不是它,看结构与方向,不要看 ID。

怎么查自己那一版

  1. 取当版本的协议表:PrismarineJS 的 minecraft-data 里 data/pc/<版本>/proto.yml 的 play.toServer.types 与 play.toClient.types,包名在 packet: 下的顺序就是 ID。
  2. 或者反着来:抓一个包,按字节流切出第一帧,读第一个 VarInt,那就是 ID;再对照该版本的表查名字。
  3. 改版本后重跑一遍,别缓存。

一帧长什么样

以 1.21.4 的一次使用物品为例,结构是「长度 VarInt + 包 ID VarInt + 载荷」:

[0x06]       长度:6 字节
[0x3D]       包 ID:ServerboundUseItemPacket
[0x00]       Hand:主手
[....]       Yaw / Pitch 与序列号,按该版本字段表读

换成 1.20.4,长度与 ID 都会变,字段顺序不变。切包方法与压缩阈值见抓包。