fix: correct bitfield decoder using mask extraction, fix command_node

- Switch bitfield decoder from shift-based to mask-based extraction (var>>pos & mask)
  fixes non-power-of-2 bitfields like 6-bit command_node flags
- Fix MultiTypeFix to handle CaseExprTypeOnlyNumber/OnlyBool variants
- Update minecraft-data command_node bitfield order for all 1.21.8+ versions
  to LSB-first convention (matches reverse encoder/decoder)
- Implement SwitchDecoder for extraNodeData via compareTo visitor
- DeclareCommands now decodes successfully (was EOF)
- Add 6 regression tests (entity_equipment, metadata, recipes, commands)
This commit is contained in:
OpenCode
2026-08-05 22:29:08 +02:00
parent f4c2762881
commit e58499ed09
17 changed files with 8532 additions and 263 deletions
@@ -3,16 +3,15 @@ package failed_packets_test
import (
"bytes"
"testing"
generated "github.com/admin-else/strom/mc/proto_generated/v26_2"
)
// Error: EOF
func TestFailedPacket78C731784E6E834A(t *testing.T) {
// Decode regression: declare_commands (26.2) - bitfield + extraNodeData fix
func TestRegressionPacket78C731784E6E834A(t *testing.T) {
p := &generated.PlayToClientPacketDeclareCommands{}
b := bytes.NewReader([]byte{0x1a, 0x0, 0xa, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0x1, 0x1, 0xb, 0x2, 0x6d, 0x65, 0x5, 0x1, 0xc, 0x4, 0x68, 0x65, 0x6c, 0x70, 0x5, 0x1, 0xd, 0x4, 0x6c, 0x69, 0x73, 0x74, 0x1, 0x1, 0xe, 0x3, 0x6d, 0x73, 0x67, 0x9, 0x0, 0x4, 0x4, 0x74, 0x65, 0x6c, 0x6c, 0x9, 0x0, 0x4, 0x1, 0x77, 0x1, 0x2, 0xf, 0x10, 0x6, 0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x1, 0x1, 0x11, 0x7, 0x74, 0x65, 0x61, 0x6d, 0x6d, 0x73, 0x67, 0x9, 0x0, 0x8, 0x2, 0x74, 0x6d, 0x1, 0x1, 0x12, 0x7, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x6, 0x0, 0x6, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x14, 0x6, 0x0, 0x7, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5, 0x2, 0x5, 0x0, 0x5, 0x75, 0x75, 0x69, 0x64, 0x73, 0x2, 0x1, 0x13, 0x7, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x6, 0x2, 0x1, 0x1, 0x14, 0x5, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x1, 0x1, 0x15, 0x4, 0x72, 0x6f, 0x6c, 0x6c, 0x6, 0x0, 0x7, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x14, 0x16, 0x2, 0x16, 0x17, 0x9, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x18, 0x14, 0x6d, 0x69, 0x6e, 0x65, 0x63, 0x72, 0x61, 0x66, 0x74, 0x3a, 0x61, 0x73, 0x6b, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x6, 0x0, 0x7, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x14, 0x6, 0x0, 0x5, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x27, 0x6, 0x0, 0x5, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x27, 0x1, 0x1, 0x18, 0x3, 0x61, 0x64, 0x64, 0x1, 0x1, 0x19, 0x3, 0x73, 0x65, 0x74, 0x6, 0x0, 0x5, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3, 0x0, 0x6, 0x0, 0x5, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3, 0x0, 0x0})
err := p.Decode(b)
if err == nil {
t.Fatalf("expected decode error but got nil")
if err != nil {
t.Fatalf("regression: decode failed: %v", err)
}
}
@@ -4120,11 +4120,6 @@
"type": [
"bitfield",
[
{
"name": "unused",
"size": 2,
"signed": false
},
{
"name": "allows_restricted",
"size": 1,
@@ -11697,4 +11692,4 @@
}
}
}
}
}
@@ -2803,11 +2803,6 @@
"type": [
"bitfield",
[
{
"name": "unused",
"size": 2,
"signed": false
},
{
"name": "allows_restricted",
"size": 1,
@@ -10259,4 +10254,4 @@
}
}
}
}
}
@@ -3822,11 +3822,6 @@
"type": [
"bitfield",
[
{
"name": "unused",
"size": 2,
"signed": false
},
{
"name": "allows_restricted",
"size": 1,
@@ -11399,4 +11394,4 @@
}
}
}
}
}
@@ -4112,11 +4112,6 @@
"type": [
"bitfield",
[
{
"name": "unused",
"size": 2,
"signed": false
},
{
"name": "allows_restricted",
"size": 1,
+45 -12
View File
@@ -4112,11 +4112,6 @@
"type": [
"bitfield",
[
{
"name": "unused",
"size": 2,
"signed": false
},
{
"name": "allows_restricted",
"size": 1,
@@ -5726,12 +5721,15 @@
"mappings": {
"0": "empty",
"1": "any_fuel",
"2": "item",
"3": "item_stack",
"4": "tag",
"5": "smithing_trim",
"6": "with_remainder",
"7": "composite"
"2": "with_any_potion",
"3": "only_with_component",
"4": "item",
"5": "item_stack",
"6": "tag",
"7": "dyed",
"8": "smithing_trim",
"9": "with_remainder",
"10": "composite"
}
}
]
@@ -5745,9 +5743,44 @@
"fields": {
"empty": "void",
"any_fuel": "void",
"with_any_potion": [
"container",
[
{
"name": "display",
"type": "SlotDisplay"
}
]
],
"only_with_component": [
"container",
[
{
"name": "source",
"type": "SlotDisplay"
},
{
"name": "component",
"type": "IDSet"
}
]
],
"item": "varint",
"item_stack": "Slot",
"tag": "string",
"dyed": [
"container",
[
{
"name": "dye",
"type": "SlotDisplay"
},
{
"name": "target",
"type": "SlotDisplay"
}
]
],
"smithing_trim": [
"container",
[
@@ -11779,4 +11812,4 @@
}
}
}
}
}
+12 -12
View File
@@ -1,6 +1,6 @@
// Code generated by strom. DO NOT EDIT.
// source: minecraft-data/pc/1.12.2/protocol.json (sha256:21c57208db654a5e14a34cb696570080df179c5c642e53b16120399eee9232be)
// version: v0.6.6-0.20260805175346-53d366ae5301+dirty (go1.26.2)
// version: v0.6.6-0.20260805191907-f4c27628815b+dirty (go1.26.2)
package v1_12_2
import (
@@ -432,17 +432,17 @@ func (ret *Position) Decode(r io.ReadSeeker) (err error) {
if err != nil {
return
}
ret.X = int32(PositionPacked << 0 >> 38)
if ret.X >= 1<<25 {
ret.X -= 1 << 26
ret.Z = int32(PositionPacked >> 0 & 67108863)
if ret.Z >= 1<<25 {
ret.Z -= 1 << 26
}
ret.Y = int16(PositionPacked << 26 >> 52)
ret.Y = int16(PositionPacked >> 26 & 4095)
if ret.Y >= 1<<11 {
ret.Y -= 1 << 12
}
ret.Z = int32(PositionPacked << 38 >> 38)
if ret.Z >= 1<<25 {
ret.Z -= 1 << 26
ret.X = int32(PositionPacked >> 38 & 67108863)
if ret.X >= 1<<25 {
ret.X -= 1 << 26
}
return
}
@@ -2810,10 +2810,10 @@ func (ret *PlayToClientPacketAdvancements) Decode(r io.ReadSeeker) (err error) {
if err != nil {
return
}
PlayToClientPacketAdvancementsAdvancementMappingElementValueDisplayDataPresentValue.Flags.Unused = PlayToClientPacketAdvancementsAdvancementMappingElementValueDisplayDataFlagsPacked << 0 >> 3
PlayToClientPacketAdvancementsAdvancementMappingElementValueDisplayDataPresentValue.Flags.Hidden = PlayToClientPacketAdvancementsAdvancementMappingElementValueDisplayDataFlagsPacked<<29>>31 == 1
PlayToClientPacketAdvancementsAdvancementMappingElementValueDisplayDataPresentValue.Flags.ShowToast = PlayToClientPacketAdvancementsAdvancementMappingElementValueDisplayDataFlagsPacked<<30>>31 == 1
PlayToClientPacketAdvancementsAdvancementMappingElementValueDisplayDataPresentValue.Flags.HasBackgroundTexture = PlayToClientPacketAdvancementsAdvancementMappingElementValueDisplayDataFlagsPacked<<31>>31 == 1
PlayToClientPacketAdvancementsAdvancementMappingElementValueDisplayDataPresentValue.Flags.HasBackgroundTexture = PlayToClientPacketAdvancementsAdvancementMappingElementValueDisplayDataFlagsPacked>>0&1 == 1
PlayToClientPacketAdvancementsAdvancementMappingElementValueDisplayDataPresentValue.Flags.ShowToast = PlayToClientPacketAdvancementsAdvancementMappingElementValueDisplayDataFlagsPacked>>1&1 == 1
PlayToClientPacketAdvancementsAdvancementMappingElementValueDisplayDataPresentValue.Flags.Hidden = PlayToClientPacketAdvancementsAdvancementMappingElementValueDisplayDataFlagsPacked>>2&1 == 1
PlayToClientPacketAdvancementsAdvancementMappingElementValueDisplayDataPresentValue.Flags.Unused = PlayToClientPacketAdvancementsAdvancementMappingElementValueDisplayDataFlagsPacked >> 3 & 536870911
switch PlayToClientPacketAdvancementsAdvancementMappingElementValueDisplayDataPresentValue.Flags.HasBackgroundTexture {
case true:
var PlayToClientPacketAdvancementsAdvancementMappingElementValueDisplayDataBackgroundTexture1Tmp string
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+199 -10
View File
@@ -1,6 +1,6 @@
// Code generated by strom. DO NOT EDIT.
// source: minecraft-data/pc/1.8/protocol.json (sha256:79fe774bfc255dd281899c004fbb9c7adc40fb107de9353da1dd0371ea4b53af)
// version: v0.6.6-0.20260805175346-53d366ae5301+dirty (go1.26.2)
// version: v0.6.6-0.20260805191907-f4c27628815b+dirty (go1.26.2)
package v1_8
import (
@@ -82,8 +82,90 @@ func (ret *EntityMetadata) Decode(r io.ReadSeeker) (err error) {
if err != nil {
return
}
entry.Anon.Type = EntityMetadataInnerAnonPacked << 0 >> 5
entry.Anon.Key = EntityMetadataInnerAnonPacked << 3 >> 3
entry.Anon.Key = EntityMetadataInnerAnonPacked >> 0 & 31
entry.Anon.Type = EntityMetadataInnerAnonPacked >> 5 & 7
switch entry.Anon.Type {
case 0:
var EntityMetadataInnerValue0Tmp int8
err = binary.Read(r, binary.BigEndian, &EntityMetadataInnerValue0Tmp)
if err != nil {
return
}
entry.Value = EntityMetadataInnerValue0Tmp
case 1:
var EntityMetadataInnerValue1Tmp int16
err = binary.Read(r, binary.BigEndian, &EntityMetadataInnerValue1Tmp)
if err != nil {
return
}
entry.Value = EntityMetadataInnerValue1Tmp
case 2:
var EntityMetadataInnerValue2Tmp int32
err = binary.Read(r, binary.BigEndian, &EntityMetadataInnerValue2Tmp)
if err != nil {
return
}
entry.Value = EntityMetadataInnerValue2Tmp
case 3:
var EntityMetadataInnerValue3Tmp float32
err = binary.Read(r, binary.BigEndian, &EntityMetadataInnerValue3Tmp)
if err != nil {
return
}
entry.Value = EntityMetadataInnerValue3Tmp
case 4:
var EntityMetadataInnerValue4Tmp string
EntityMetadataInnerValue4Tmp, err = proto_base.DecodeString(r)
if err != nil {
return
}
entry.Value = EntityMetadataInnerValue4Tmp
case 5:
var EntityMetadataInnerValue5Tmp Slot
err = EntityMetadataInnerValue5Tmp.Decode(r)
if err != nil {
return
}
entry.Value = EntityMetadataInnerValue5Tmp
case 6:
var EntityMetadataInnerValue6Tmp struct {
X int32
Y int32
Z int32
}
err = binary.Read(r, binary.BigEndian, &EntityMetadataInnerValue6Tmp.X)
if err != nil {
return
}
err = binary.Read(r, binary.BigEndian, &EntityMetadataInnerValue6Tmp.Y)
if err != nil {
return
}
err = binary.Read(r, binary.BigEndian, &EntityMetadataInnerValue6Tmp.Z)
if err != nil {
return
}
entry.Value = EntityMetadataInnerValue6Tmp
case 7:
var EntityMetadataInnerValue7Tmp struct {
Pitch float32
Yaw float32
Roll float32
}
err = binary.Read(r, binary.BigEndian, &EntityMetadataInnerValue7Tmp.Pitch)
if err != nil {
return
}
err = binary.Read(r, binary.BigEndian, &EntityMetadataInnerValue7Tmp.Yaw)
if err != nil {
return
}
err = binary.Read(r, binary.BigEndian, &EntityMetadataInnerValue7Tmp.Roll)
if err != nil {
return
}
entry.Value = EntityMetadataInnerValue7Tmp
}
ret.Val[id] = entry
}
return
@@ -97,6 +179,113 @@ func (ret *EntityMetadata) Encode(w io.Writer) (err error) {
if err != nil {
return
}
switch v.Anon.Type {
case 0:
EntityMetadataValue, ok := v.Value.(int8)
if !ok {
err = proto_base.BadTypeError
return
}
err = binary.Write(w, binary.BigEndian, EntityMetadataValue)
if err != nil {
return
}
case 1:
EntityMetadataValue, ok := v.Value.(int16)
if !ok {
err = proto_base.BadTypeError
return
}
err = binary.Write(w, binary.BigEndian, EntityMetadataValue)
if err != nil {
return
}
case 2:
EntityMetadataValue, ok := v.Value.(int32)
if !ok {
err = proto_base.BadTypeError
return
}
err = binary.Write(w, binary.BigEndian, EntityMetadataValue)
if err != nil {
return
}
case 3:
EntityMetadataValue, ok := v.Value.(float32)
if !ok {
err = proto_base.BadTypeError
return
}
err = binary.Write(w, binary.BigEndian, EntityMetadataValue)
if err != nil {
return
}
case 4:
EntityMetadataValue, ok := v.Value.(string)
if !ok {
err = proto_base.BadTypeError
return
}
err = proto_base.EncodeString(w, EntityMetadataValue)
if err != nil {
return
}
case 5:
EntityMetadataValue, ok := v.Value.(Slot)
if !ok {
err = proto_base.BadTypeError
return
}
EntityMetadataValueEncTmp := EntityMetadataValue
err = EntityMetadataValueEncTmp.Encode(w)
if err != nil {
return
}
case 6:
EntityMetadataValue, ok := v.Value.(struct {
X int32
Y int32
Z int32
})
if !ok {
err = proto_base.BadTypeError
return
}
err = binary.Write(w, binary.BigEndian, EntityMetadataValue.X)
if err != nil {
return
}
err = binary.Write(w, binary.BigEndian, EntityMetadataValue.Y)
if err != nil {
return
}
err = binary.Write(w, binary.BigEndian, EntityMetadataValue.Z)
if err != nil {
return
}
case 7:
EntityMetadataValue, ok := v.Value.(struct {
Pitch float32
Yaw float32
Roll float32
})
if !ok {
err = proto_base.BadTypeError
return
}
err = binary.Write(w, binary.BigEndian, EntityMetadataValue.Pitch)
if err != nil {
return
}
err = binary.Write(w, binary.BigEndian, EntityMetadataValue.Yaw)
if err != nil {
return
}
err = binary.Write(w, binary.BigEndian, EntityMetadataValue.Roll)
if err != nil {
return
}
}
}
err = binary.Write(w, binary.BigEndian, uint8(127))
if err != nil {
@@ -130,17 +319,17 @@ func (ret *Position) Decode(r io.ReadSeeker) (err error) {
if err != nil {
return
}
ret.X = int32(PositionPacked << 0 >> 38)
if ret.X >= 1<<25 {
ret.X -= 1 << 26
ret.Z = int32(PositionPacked >> 0 & 67108863)
if ret.Z >= 1<<25 {
ret.Z -= 1 << 26
}
ret.Y = int16(PositionPacked << 26 >> 52)
ret.Y = int16(PositionPacked >> 26 & 4095)
if ret.Y >= 1<<11 {
ret.Y -= 1 << 12
}
ret.Z = int32(PositionPacked << 38 >> 38)
if ret.Z >= 1<<25 {
ret.Z -= 1 << 26
ret.X = int32(PositionPacked >> 38 & 67108863)
if ret.X >= 1<<25 {
ret.X -= 1 << 26
}
return
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+6 -8
View File
@@ -225,7 +225,7 @@ func MultiTypeFix(s string, t *CaseExprType) (e ast.Expr) {
*t = ct
return
}
if *t == ct {
if *t == ct || (*t == CaseExprTypeOnlyNumber && ct == CaseExprTypeNumber) || (*t == CaseExprTypeOnlyBool && ct == CaseExprTypeBool) || (*t == CaseExprTypeNumber && ct == CaseExprTypeOnlyNumber) || (*t == CaseExprTypeBool && ct == CaseExprTypeOnlyBool) {
return
}
@@ -400,6 +400,7 @@ func BitFieldDecoder(g *Generator, varToSet ast.Expr, dataRaw any, name string)
}
s = append(s, packedVarStmt)
s = append(s, stmts...)
slices.Reverse(data)
lShiftBy := 0
for _, field := range data {
var fieldTypeProtodef string
@@ -413,16 +414,13 @@ func BitFieldDecoder(g *Generator, varToSet ast.Expr, dataRaw any, name string)
return
}
rShiftBy := totalSize - field.Size
fieldToSet := SelectorExprAndStr(varToSet, util2.CamelCase(field.Name))
mask := (1 << field.Size) - 1
var getDataExpr ast.Expr
getDataExpr = RightShift(
LeftShift(
Ident(packed),
NumLit(lShiftBy)),
NumLit(rShiftBy),
)
getDataExpr = BinAnd(
RightShift(Ident(packed), NumLit(lShiftBy)),
NumLit(mask))
if fieldTypeProtodef == "bool" {
getDataExpr = Equals(getDataExpr, NumLit(1))
} else if fieldTypeProtodef != packedTypeProtodef {
+21 -3
View File
@@ -4,7 +4,6 @@ import (
"go/ast"
"go/token"
"slices"
"strconv"
"github.com/admin-else/strom/mc/proto_generator/protodef"
util2 "github.com/admin-else/strom/mc/util"
@@ -152,7 +151,16 @@ func BitFieldEncoder(g *Generator, varToSet ast.Expr, dataRaw any, name string)
}
packedVarName := name + "Packed"
packedExpr := Ident(packedVarName)
packedTypeStr := "uint" + strconv.Itoa(totalSize)
packedTypeStr := "uint8"
if totalSize > 8 {
packedTypeStr = "uint16"
}
if totalSize > 16 {
packedTypeStr = "uint32"
}
if totalSize > 32 {
packedTypeStr = "uint64"
}
s = append(s, VarStmt(packedVarName, Ident(packedTypeStr)))
@@ -185,7 +193,17 @@ func BitFieldEncoder(g *Generator, varToSet ast.Expr, dataRaw any, name string)
shiftBy += field.Size
}
encodePacked, err := g.VisitEncoder(packedExpr, "u"+strconv.Itoa(totalSize), name)
packedProtoType := "u8"
if totalSize > 8 {
packedProtoType = "u16"
}
if totalSize > 16 {
packedProtoType = "u32"
}
if totalSize > 32 {
packedProtoType = "u64"
}
encodePacked, err := g.VisitEncoder(packedExpr, packedProtoType, name)
if err != nil {
return
}