diff --git a/internal/host/items.go b/internal/host/items.go index 3b3803b..d4270a2 100644 --- a/internal/host/items.go +++ b/internal/host/items.go @@ -1,6 +1,7 @@ package host import ( + "encoding/gob" "math" "reflect" @@ -22,6 +23,14 @@ const ( nestedItemVersionName = "bedrock_gophers_version" ) +func init() { + gob.Register(map[string]any{}) + gob.Register([]any{}) + gob.Register([]byte{}) + gob.Register([]int32{}) + gob.Register([]int64{}) +} + func (p *Players) InventorySize(invocation native.InvocationID, id native.InventoryID) (uint32, bool) { value, ok := readPlayer(p, invocation, id.Player, func(connected *player.Player) uint32 { if id.Kind == native.InventoryOffhand { @@ -336,6 +345,10 @@ func itemStackFromNativeDepth(value native.ItemStack, depth int) (stack item.Sta if !ok { return item.Stack{}, false } + values, ok = normalizeItemNBTMap(values) + if !ok { + return item.Stack{}, false + } for key, entry := range values { stack = stack.WithValue(key, entry) } @@ -543,6 +556,47 @@ func byteArray(value any) ([]byte, bool) { return result, true } +func normalizeItemNBTMap(value map[string]any) (map[string]any, bool) { + result := make(map[string]any, len(value)) + for key, entry := range value { + normalized, ok := normalizeItemNBTValue(entry) + if !ok { + return nil, false + } + result[key] = normalized + } + return result, true +} + +func normalizeItemNBTValue(value any) (any, bool) { + switch typed := value.(type) { + case map[string]any: + return normalizeItemNBTMap(typed) + case []any: + result := make([]any, len(typed)) + for index, entry := range typed { + normalized, ok := normalizeItemNBTValue(entry) + if !ok { + return nil, false + } + result[index] = normalized + } + return result, true + } + reflected := reflect.ValueOf(value) + if !reflected.IsValid() { + return nil, false + } + if (reflected.Kind() == reflect.Array || reflected.Kind() == reflect.Slice) && reflected.Type().Elem().Kind() == reflect.Uint8 { + result := make([]byte, reflected.Len()) + for index := range result { + result[index] = byte(reflected.Index(index).Uint()) + } + return result, true + } + return value, true +} + // ItemStackFromNative decodes a plugin item stack using Dragonfly registries. func ItemStackFromNative(value native.ItemStack) (item.Stack, bool) { return itemStackFromNative(value) diff --git a/internal/host/items_test.go b/internal/host/items_test.go index 84124bd..b0458df 100644 --- a/internal/host/items_test.go +++ b/internal/host/items_test.go @@ -1,7 +1,9 @@ package host import ( + "bytes" "context" + "encoding/gob" "reflect" "testing" @@ -58,6 +60,55 @@ func TestPlayersInventoryItemRoundTrip(t *testing.T) { }) } +func TestItemStackFromNativeNormalizesByteArrayValues(t *testing.T) { + valuesNBT, ok := marshalItemNBT(map[string]any{ + "bytes": fixedByteArray([]byte{1, 2, 3}), + "nested": map[string]any{ + "raw": fixedByteArray([]byte{4, 5}), + }, + }) + if !ok { + t.Fatal("encode values") + } + stack, ok := itemStackFromNative(native.ItemStack{ + Identifier: "minecraft:diamond_sword", + Count: 1, + ValuesNBT: valuesNBT, + }) + if !ok { + t.Fatal("decode item stack") + } + value, ok := stack.Value("bytes") + if !ok { + t.Fatal("missing bytes value") + } + bytesValue, ok := value.([]byte) + if !ok || !bytes.Equal(bytesValue, []byte{1, 2, 3}) { + t.Fatalf("stack bytes=%#v ok=%v", value, ok) + } + nested, ok := stack.Value("nested") + if !ok { + t.Fatal("missing nested value") + } + raw, ok := nested.(map[string]any)["raw"].([]byte) + if !ok || !bytes.Equal(raw, []byte{4, 5}) { + t.Fatalf("nested raw=%#v ok=%v", nested, ok) + } + values := stack.Values() + type mapValue struct { + K string + V any + } + encoded := []mapValue{ + {K: "bytes", V: values["bytes"]}, + {K: "nested", V: values["nested"]}, + {K: "stats", V: map[string]any{"uses": int32(0), "rarity": "demo"}}, + } + if err := gob.NewEncoder(new(bytes.Buffer)).Encode(encoded); err != nil { + t.Fatalf("gob encode values: %v", err) + } +} + func TestPlayersInventoryAddClearAndOffhand(t *testing.T) { withPlayer(t, func(player *player.Player) { players := NewPlayers()