Skip to content

Commit

Permalink
fix nbt stuff; Successfully joined server!
Browse files Browse the repository at this point in the history
  • Loading branch information
robinbraemer committed Dec 30, 2023
1 parent 45d87ca commit 7a8c2ce
Show file tree
Hide file tree
Showing 16 changed files with 2,502 additions and 433 deletions.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module go.minekube.com/gate
go 1.21.0

require (
github.com/Tnze/go-mc v1.20.2-0.20231123224931-bc3d77d78437
github.com/agext/levenshtein v1.2.3
github.com/dboslee/lru v0.0.1
github.com/edwingeng/deque/v2 v2.1.1
Expand Down Expand Up @@ -33,13 +34,13 @@ 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
)

require (
buf.build/gen/go/minekube/connect/protocolbuffers/go v1.31.0-20230517110945-04c17e7d2fd9.1 // indirect
github.com/Tnze/go-mc v1.20.2-0.20231123224931-bc3d77d78437 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/df-mc/atomic v1.10.0 // indirect
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Tnze/go-mc v1.20.1 h1:1Mwxyg2nvIVgRtebf6CpjMjvXLtRIng95so5hTEQtXA=
github.com/Tnze/go-mc v1.20.1/go.mod h1:c1znJQglgqa1Jjs3Dr29woN/msguiJrlNtWXhKedh2U=
github.com/Tnze/go-mc v1.20.2-0.20231123224931-bc3d77d78437 h1:cINogPegf6TCIEmAon1kyVuKJttjoM0H7kyIM5WXre4=
github.com/Tnze/go-mc v1.20.2-0.20231123224931-bc3d77d78437/go.mod h1:geoRj2HsXSkB3FJBuhr7wCzXegRlzWsVXd7h7jiJ6aQ=
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
Expand Down
2 changes: 1 addition & 1 deletion pkg/edition/java/netmc/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ func (c *minecraftConn) closeOnWriteErr(err error, logKeysAndValues ...any) {
return
}
_ = c.Close()
if err == ErrClosedConn {
if errors.Is(err, ErrClosedConn) {
return // Don't log this error
}
var opErr *net.OpError
Expand Down
64 changes: 13 additions & 51 deletions pkg/edition/java/proto/packet/chat/component_holder.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@ import (
"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"
"gopkg.in/yaml.v3"
"io"
"log/slog"
"regexp"
)

func FromComponent(comp component.Component) *ComponentHolder {
Expand Down Expand Up @@ -56,13 +54,8 @@ func ReadComponentHolderNP(rd io.Reader, protocol proto.Protocol) (ComponentHold
func (c *ComponentHolder) read(rd io.Reader, protocol proto.Protocol) (err error) {
c.Protocol = protocol
if protocol.GreaterEqual(version.Minecraft_1_20_3) {
dec := nbt2.NewDecoder(rd)
dec.NetworkFormat(true) // skip tag name
_, err := dec.Decode(&c.BinaryTag)
if err != nil {
return fmt.Errorf("error while reading binaryTag: %w", err)
}
return nil
c.BinaryTag, err = util.ReadBinaryTag(rd, protocol)
return err
}
j, err := util.ReadString(rd)
c.JSON = json.RawMessage(j)
Expand All @@ -72,13 +65,11 @@ func (c *ComponentHolder) read(rd io.Reader, protocol proto.Protocol) (err error
// Write writes the component holder to the writer.
func (c *ComponentHolder) Write(wr io.Writer, protocol proto.Protocol) error {
if protocol.GreaterEqual(version.Minecraft_1_20_3) {
enc := nbt2.NewEncoder(wr)
enc.NetworkFormat(true) // skip tag name
err := enc.Encode(c.BinaryTag, "")
bt, err := c.AsBinaryTag()
if err != nil {
return fmt.Errorf("error while reading binaryTag: %w", err)
return err
}
return nil
return util.WriteBinaryTag(wr, protocol, bt)
}
j, err := c.AsJson()
if err != nil {
Expand Down Expand Up @@ -110,7 +101,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 = BinaryTagToJSON(&c.BinaryTag)
if err != nil {
return nil, fmt.Errorf("error while marshalling binaryTag to JSON: %w", err)
}
Expand All @@ -128,7 +119,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 = BinaryTagToJSON(&c.BinaryTag)
return c.JSON, err
}
comp, err := c.AsComponent()
Expand All @@ -151,42 +142,13 @@ func (c *ComponentHolder) AsJsonOrNil() json.RawMessage {
return j
}

// AsBinaryTag returns the component as a binary NBT tag.
//func (c *ComponentHolder) AsBinaryTag() (*nbt2.RawMessage, error) {
// if len(c.BinaryTag.Data) != 0 {
// return &c.BinaryTag, nil
// }
// j, err := c.AsJson()
// if err != nil {
// return nil, err
// }
// err = nbt.UnmarshalEncoding(j, &c.BinaryTag, nbt.BigEndian) // TODO
// return &c.BinaryTag, err
//}

func binaryTagToJSON(tag *nbt2.RawMessage) (json.RawMessage, error) {
return snbtToJSON(tag.String())
}

var snbtRe = regexp.MustCompile(`(?m)([^"]):([^"])`)

// snbtToJSON converts a stringified NBT to JSON.
// Example: {a:1,b:hello,c:"world",d:true} -> {"a":1,"b":"hello","c":"world","d":true}
func snbtToJSON(snbt string) (json.RawMessage, error) {
// Add spaces after colons that are not within quotes
snbt = snbtRe.ReplaceAllString(snbt, "$1: $2")

// Parse non-standard json with yaml, which is a superset of json.
// We use YAML parser, since it's a superset of JSON and quotes are optional.
type M map[string]any
var m M
if err := yaml.Unmarshal([]byte(snbt), &m); err != nil {
return nil, err
func (c *ComponentHolder) AsBinaryTag() (util.BinaryTag, error) {
if len(c.BinaryTag.Data) != 0 {
return c.BinaryTag, nil
}
// Marshal back to JSON
j, err := json.Marshal(m)
j, err := c.AsJson()
if err != nil {
return nil, err
return c.BinaryTag, err
}
return j, nil
return JsonToBinaryTag(j)
}
59 changes: 0 additions & 59 deletions pkg/edition/java/proto/packet/chat/component_holder_test.go

This file was deleted.

176 changes: 176 additions & 0 deletions pkg/edition/java/proto/packet/chat/snbt_json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package chat

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"github.com/Tnze/go-mc/nbt"
"gopkg.in/yaml.v3"
"strings"
)

// formatSNBT adds spaces after colons that are not within quotes.
// Example: {a:1,b:hello,c:"world",d:true} -> {a: 1, b: hello, c: "world", d: true}
// This is needed because the yaml parser requires spaces after colons
func formatSNBT(snbt string) string { // TODO test / rewrite properly
var result strings.Builder
inQuotes := false

for i := 0; i < len(snbt); i++ {
switch snbt[i] {
case '"':
inQuotes = !inQuotes
case ':', ',':
if !inQuotes {
result.WriteByte(snbt[i])
result.WriteByte(' ')
continue
}
}
result.WriteByte(snbt[i])
}

return result.String()
}

var errSNBTInvalid = errors.New("invalid input for SNBT, must be a non-empty string starting with '{' and ending with '}'")

// SnbtToJSON converts a stringified NBT to JSON.
// Example: {a:1,b:hello,c:"world",d:true} -> {"a":1,"b":"hello","c":"world","d":true}
func SnbtToJSON(snbt string) (json.RawMessage, error) {
// Trim whitespace, newlines, return characters, and tabs
snbt = strings.Trim(snbt, " \n\r\t")

// Ensure that input is not empty or trivially malformed
if len(snbt) < 2 || !strings.HasPrefix(snbt, "{") || !strings.HasSuffix(snbt, "}") {
// get first and last few characters of input and put ... in between
var truncated string
if len(snbt) > 10 {
truncated = snbt[:5] + "..." + snbt[len(snbt)-5:]
} else {
truncated = snbt
}
return nil, fmt.Errorf("%w: but got %q", errSNBTInvalid, truncated)
}

// Add spaces after colons that are not within quotes
snbt = formatSNBT(snbt)

// Parse non-standard json with yaml, which is a superset of json.
// We use YAML parser, since it's a superset of JSON and quotes are optional.
type M map[string]any
var m M
if err := yaml.Unmarshal([]byte(snbt), &m); err != nil {
return nil, fmt.Errorf("error unmarshalling snbt to yaml: %w", err)
}
// Marshal back to JSON
j, err := json.Marshal(m)
if err != nil {
return nil, fmt.Errorf("error marshalling yaml to json: %w", err)
}
return j, nil
}

// JsonToSNBT converts a JSON to stringified NBT.
// Example: {"a":1,"b":"hello","c":"world","d":true} -> {a:1,b:hello,c:"world",d:true}
func JsonToSNBT(j json.RawMessage) (string, error) {
var m map[string]any
if err := json.Unmarshal(j, &m); err != nil {
return "", fmt.Errorf("error unmarshalling json to map: %w", err)
}
var b strings.Builder
err := ConvertToSNBT(m, &b)
return b.String(), err
}

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(`""`)
} else {
// Quote strings that contain spaces or special characters
if strings.ContainsAny(v, " {}:[]/") {
b.WriteString(fmt.Sprintf(`"%s"`, v))
} else {
b.WriteString(v)
}
}
default:
b.WriteString(fmt.Sprintf("%v", v))
}
return nil
}

func mapToSNBT(m map[string]any, b *strings.Builder) error {
b.WriteString("{")
sep := ""
for k, v := range m {
b.WriteString(sep)
b.WriteString(k)
b.WriteString(":")
err := ConvertToSNBT(v, b)
if err != nil {
return err
}
sep = ","
}
b.WriteString("}")
return nil
}

func sliceToSNBT(s []any, b *strings.Builder) error {
b.WriteString("[")
for i, item := range s {
if i != 0 {
b.WriteString(",")
}
err := ConvertToSNBT(item, b)
if err != nil {
return err
}
}
b.WriteString("]")
return nil
}

func BinaryTagToJSON(tag *nbt.RawMessage) (json.RawMessage, error) {
return SnbtToJSON(tag.String())
}

func SnbtToBinaryTag(snbt string) (nbt.RawMessage, error) {
// Convert SNBT to JSON
j, err := SnbtToJSON(snbt)
if err != nil {
return nbt.RawMessage{}, err
}
// Then convert JSON to binary tag
return JsonToBinaryTag(j)
}

func JsonToBinaryTag(tag json.RawMessage) (nbt.RawMessage, error) {
// Convert JSON to snbt
snbt, err := JsonToSNBT(tag)
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)
}
// Then convert bytes to binary tag
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)
}
return m, nil
}
Loading

0 comments on commit 7a8c2ce

Please sign in to comment.