Skip to content

Commit

Permalink
fix(JsonToBinaryTag): fix write nbt
Browse files Browse the repository at this point in the history
  • Loading branch information
robinbraemer committed Dec 30, 2023
1 parent 7a8c2ce commit 36e1c60
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 28 deletions.
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ require (
golang.org/x/text v0.12.0
golang.org/x/time v0.3.0
google.golang.org/grpc v1.57.0
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
nhooyr.io/websocket v1.8.7
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package chat
package nbtconv

import (
"bytes"
Expand All @@ -7,6 +7,7 @@ import (
"fmt"
"github.com/Tnze/go-mc/nbt"
"gopkg.in/yaml.v3"
"io"
"strings"
)

Expand Down Expand Up @@ -84,23 +85,25 @@ func JsonToSNBT(j json.RawMessage) (string, error) {
return b.String(), err
}

// ConvertToSNBT converts a map[string]any to stringified NBT by writing to a strings.Builder.
func ConvertToSNBT(v any, b *strings.Builder) error {
switch v := v.(type) {
case map[string]any:
return mapToSNBT(v, b)
case []any:
return sliceToSNBT(v, b)
case string:
if len(v) == 0 {
// Empty strings are represented as two double quotes
b.WriteString(`""`)
// Quote strings that contain spaces or special characters
if strings.TrimSpace(v) == "" || strings.ContainsAny(v, " {}:[]/#@!^") {
b.WriteString(fmt.Sprintf(`"%s"`, v))
} else {
// Quote strings that contain spaces or special characters
if strings.ContainsAny(v, " {}:[]/") {
b.WriteString(fmt.Sprintf(`"%s"`, v))
} else {
b.WriteString(v)
}
b.WriteString(v)
}
case bool:
if v {
b.WriteString("1")
} else {
b.WriteString("0")
}
default:
b.WriteString(fmt.Sprintf("%v", v))
Expand Down Expand Up @@ -140,10 +143,12 @@ func sliceToSNBT(s []any, b *strings.Builder) error {
return nil
}

// BinaryTagToJSON converts a binary tag to JSON.
func BinaryTagToJSON(tag *nbt.RawMessage) (json.RawMessage, error) {
return SnbtToJSON(tag.String())
}

// SnbtToBinaryTag converts a stringified NBT to binary tag.
func SnbtToBinaryTag(snbt string) (nbt.RawMessage, error) {
// Convert SNBT to JSON
j, err := SnbtToJSON(snbt)
Expand All @@ -154,23 +159,70 @@ func SnbtToBinaryTag(snbt string) (nbt.RawMessage, error) {
return JsonToBinaryTag(j)
}

func JsonToBinaryTag(tag json.RawMessage) (nbt.RawMessage, error) {
// JsonToBinaryTag converts a JSON to binary tag.
func JsonToBinaryTag(j json.RawMessage) (nbt.RawMessage, error) {
// Convert JSON to snbt
snbt, err := JsonToSNBT(tag)
snbt, err := JsonToSNBT(j)
if err != nil {
return nbt.RawMessage{}, err
}

// Then convert snbt to bytes
buf := new(bytes.Buffer)
err = nbt.StringifiedMessage(snbt).MarshalNBT(buf)
if err != nil {
return nbt.RawMessage{}, fmt.Errorf("error marshalling snbt to binary: %w", err)
}

rd := io.MultiReader(
bytes.NewReader([]byte{10}), // type: TagCompound
buf, // struct fields: Data
bytes.NewReader([]byte{0}), // end TagCompound
)

// This is an example the structure of a binary tag for a kick message:
// It is a compound tag with 3 tags:
// - color: red
// - bold: true
// - text: KickAll
//
// As stringified NBT (snbt) it looks like this:
// {color:red,bold:1,text:KickAll}
//
// The first TagByte (1, 0) represents the type of the tag (TagByte) and the name of the tag (empty).

//return nbt.RawMessage{
// Type: nbt.TagCompound,
// Data: []byte{
// //10, // type: TagCompound (held by Type field)
// //0, 0, // Named tag string length empty (disabled in network format)
//
// 8, // type: TagString
// 0, 5, 99, 111, 108, 111, 114, // string=color length=5
// 0, 3, 114, 101, 100, // string=red length=3
//
// 1, // type: TagByte
// 0, 4, 98, 111, 108, 100, // string=bold length=4
// 1, // TagByte true
//
// 8, // type: TagString
// 0, 4, 116, 101, 120, 116, // string=text length=4
// 0, 7, 75, 105, 99, 107, 65, 108, 108, // string=KickAll length=7
//
// 0, // End TagCompound
// },
//}, nil

// Then convert bytes to binary tag
dec := nbt.NewDecoder(rd)
// Remove index 1 and 2 from buf.Bytes() (which are the length of the tag name)
// because we don't want them in network format
dec.NetworkFormat(true)

var m nbt.RawMessage
err = nbt.Unmarshal(buf.Bytes(), &m)
if err != nil {
return nbt.RawMessage{}, fmt.Errorf("error unmarshalling binary to binary tag: %w", err)
if _, err = dec.Decode(&m); err != nil {
return m, fmt.Errorf("error decoding binary tag: %w", err)
}

return m, nil
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//go:build go1.18

package chat
package nbtconv

import (
"encoding/json"
Expand All @@ -17,7 +17,7 @@ func TestSnbtToJSON(t *testing.T) {
}{
{
name: "without spaces",
snbt: `{a:1,b:hello,c:"world",d:true}`,
snbt: `{a:1,b:hello,c:"world",d:1}`,
want: json.RawMessage(`{"a":1,"b":"hello","c":"world","d":true}`),
wantErr: false,
},
Expand Down
11 changes: 6 additions & 5 deletions pkg/edition/java/proto/packet/chat/component_holder.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ package chat
import (
"encoding/json"
"fmt"
nbt2 "github.com/Tnze/go-mc/nbt"
"github.com/Tnze/go-mc/nbt"
"go.minekube.com/common/minecraft/component"
"go.minekube.com/gate/pkg/edition/java/proto/nbtconv"
"go.minekube.com/gate/pkg/edition/java/proto/util"
"go.minekube.com/gate/pkg/edition/java/proto/version"
"go.minekube.com/gate/pkg/gate/proto"
Expand All @@ -31,7 +32,7 @@ type ComponentHolder struct {
Protocol proto.Protocol
Component component.Component
JSON json.RawMessage
BinaryTag nbt2.RawMessage
BinaryTag nbt.RawMessage
}

// ReadComponentHolder reads a ComponentHolder from the provided reader.
Expand Down Expand Up @@ -101,7 +102,7 @@ func (c *ComponentHolder) AsComponent() (component.Component, error) {
return c.Component, err
case len(c.BinaryTag.Data) != 0:
var err error
c.JSON, err = BinaryTagToJSON(&c.BinaryTag)
c.JSON, err = nbtconv.BinaryTagToJSON(&c.BinaryTag)
if err != nil {
return nil, fmt.Errorf("error while marshalling binaryTag to JSON: %w", err)
}
Expand All @@ -119,7 +120,7 @@ func (c *ComponentHolder) AsJson() (json.RawMessage, error) {
}
if len(c.BinaryTag.Data) != 0 {
var err error
c.JSON, err = BinaryTagToJSON(&c.BinaryTag)
c.JSON, err = nbtconv.BinaryTagToJSON(&c.BinaryTag)
return c.JSON, err
}
comp, err := c.AsComponent()
Expand Down Expand Up @@ -150,5 +151,5 @@ func (c *ComponentHolder) AsBinaryTag() (util.BinaryTag, error) {
if err != nil {
return c.BinaryTag, err
}
return JsonToBinaryTag(j)
return nbtconv.JsonToBinaryTag(j)
}
12 changes: 7 additions & 5 deletions pkg/edition/java/proto/packet/disconnect.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"go.minekube.com/common/minecraft/component"
"go.minekube.com/gate/pkg/edition/java/proto/packet/chat"
"io"
"log/slog"

"go.minekube.com/gate/pkg/edition/java/proto/version"
"go.minekube.com/gate/pkg/gate/proto"
Expand Down Expand Up @@ -41,11 +42,12 @@ func NewDisconnect(reason component.Component, protocol proto.Protocol, login bo
if login {
protocol = version.Minecraft_1_20_2.Protocol
}
if reason == nil {
slog.Error("tried to create a Disconnect packet with a nil reason")
reason = &component.Text{Content: ""}
}
return &Disconnect{
Reason: &chat.ComponentHolder{
Protocol: protocol,
Component: reason,
},
Login: login,
Reason: chat.FromComponentProtocol(reason, protocol),
Login: login,
}
}

0 comments on commit 36e1c60

Please sign in to comment.