From 08d2c03a58f5a094bfa64d162ddcfc5cb9423c3d Mon Sep 17 00:00:00 2001 From: Tnze Date: Tue, 6 Jul 2021 09:41:32 +0800 Subject: [PATCH] Supplement document of nbt API --- nbt/decode.go | 24 ++++++++----- nbt/encode.go | 7 ++++ nbt/example_test.go | 86 +++++++++++++++++++++++++++++++++------------ nbt/rawmsg.go | 12 +++++++ 4 files changed, 98 insertions(+), 31 deletions(-) diff --git a/nbt/decode.go b/nbt/decode.go index bd90e64..fbb2182 100644 --- a/nbt/decode.go +++ b/nbt/decode.go @@ -14,6 +14,14 @@ func Unmarshal(data []byte, v interface{}) error { return err } +// Decode method decodes an NBT value from the reader underline the Decoder into v. +// Internally try to handle all possible v by reflection, +// but the type of v must matches the NBT value logically. +// For example, you can decode an NBT value which root tag is TagCompound(0x0a) +// into a struct or map, but not a string. +// +// This method also return tag name of the root tag. +// In real world, it is often empty, but the API should allows you to get it when ever you want. func (d *Decoder) Decode(v interface{}) (string, error) { val := reflect.ValueOf(v) if val.Kind() != reflect.Ptr { @@ -31,7 +39,7 @@ func (d *Decoder) Decode(v interface{}) (string, error) { // We decode val not val.Elem because the NBTDecoder interface // test must be applied at the top level of the value. - err = d.unmarshal(val, tagType, tagName) + err = d.unmarshal(val, tagType) if err != nil { return tagName, fmt.Errorf("nbt: fail to decode tag %q: %w", tagName, err) } @@ -51,7 +59,7 @@ func (d *Decoder) checkCompressed(head byte) (compress string) { // ErrEND error will be returned when reading a NBT with only Tag_End var ErrEND = errors.New("unexpected TAG_End") -func (d *Decoder) unmarshal(val reflect.Value, tagType byte, tagName string) error { +func (d *Decoder) unmarshal(val reflect.Value, tagType byte) error { u, val := indirect(val, tagType == TagEnd) if u != nil { return u.Decode(tagType, d.r) @@ -271,13 +279,13 @@ func (d *Decoder) unmarshal(val reflect.Value, tagType byte, tagName string) err case reflect.Array: if vl := val.Len(); vl < int(listLen) { return fmt.Errorf( - "TagList %s has len %d, but array %v only has len %d", - tagName, listLen, val.Type(), vl) + "TagList has len %d, but array %v only has len %d", + listLen, val.Type(), vl) } buf = val } for i := 0; i < int(listLen); i++ { - if err := d.unmarshal(buf.Index(i), listType, ""); err != nil { + if err := d.unmarshal(buf.Index(i), listType); err != nil { return err } } @@ -302,7 +310,7 @@ func (d *Decoder) unmarshal(val reflect.Value, tagType byte, tagName string) err } field := tinfo.findIndexByName(tn) if field != -1 { - err = d.unmarshal(val.Field(field), tt, tn) + err = d.unmarshal(val.Field(field), tt) if err != nil { return fmt.Errorf("fail to decode tag %q: %w", tn, err) } @@ -328,7 +336,7 @@ func (d *Decoder) unmarshal(val reflect.Value, tagType byte, tagName string) err break } v := reflect.New(val.Type().Elem()) - if err = d.unmarshal(v.Elem(), tt, tn); err != nil { + if err = d.unmarshal(v.Elem(), tt); err != nil { return fmt.Errorf("fail to decode tag %q: %w", tn, err) } val.SetMapIndex(reflect.ValueOf(tn), v.Elem()) @@ -344,7 +352,7 @@ func (d *Decoder) unmarshal(val reflect.Value, tagType byte, tagName string) err break } var value interface{} - if err = d.unmarshal(reflect.ValueOf(&value).Elem(), tt, tn); err != nil { + if err = d.unmarshal(reflect.ValueOf(&value).Elem(), tt); err != nil { return fmt.Errorf("fail to decode tag %q: %w", tn, err) } buf[tn] = value diff --git a/nbt/encode.go b/nbt/encode.go index 5bf92b6..b01db77 100644 --- a/nbt/encode.go +++ b/nbt/encode.go @@ -11,6 +11,9 @@ import ( "strings" ) +// Marshal is the shortcut of NewEncoder().Encode() with empty tag name. +// Notices that repeatedly init buffers is low efficiency. +// Using Encoder and Reset the buffer in each times is recommended in that cases. func Marshal(v interface{}) ([]byte, error) { var buf bytes.Buffer err := NewEncoder(&buf).Encode(v, "") @@ -25,6 +28,10 @@ func NewEncoder(w io.Writer) *Encoder { return &Encoder{w: w} } +// Encode encodes v into the writer inside Encoder with the root tag named tagName. +// In most cases, the root tag typed TagCompound and the tag name is empty string, +// but any other type is allowed just because there is valid technically. Once if +// you should pass an string into this, you should get a TagString. func (e *Encoder) Encode(v interface{}, tagName string) error { val := reflect.ValueOf(v) return e.marshal(val, getTagType(val), tagName) diff --git a/nbt/example_test.go b/nbt/example_test.go index 1952ae2..500f70e 100644 --- a/nbt/example_test.go +++ b/nbt/example_test.go @@ -5,35 +5,75 @@ import ( "fmt" ) -//goland:noinspection SpellCheckingInspection -func ExampleUnmarshal() { - var data = []byte{ - 0x08, 0x00, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x00, 0x09, - 0x42, 0x61, 0x6e, 0x61, 0x6e, 0x72, 0x61, 0x6d, 0x61, +func ExampleDecoder_Decode() { + reader := bytes.NewReader([]byte{ + 0x0a, // Start TagCompound("") + 0x00, 0x00, + + 0x08, // TagString("Author"): "Tnze" + 0x00, 0x06, 'A', 'u', 't', 'h', 'o', 'r', + 0x00, 0x04, 'T', 'n', 'z', 'e', + + 0x00, // End TagCompound + }) + + var value struct { + Author string } - var Name string - - if err := Unmarshal(data, &Name); err != nil { - panic(err) - } - - fmt.Println(Name) - - // Output: Bananrama -} - -func ExampleMarshal() { - var value = struct { - Name string `nbt:"name"` - }{"Tnze"} - - data, err := Marshal(value) + decoder := NewDecoder(reader) + _, err := decoder.Decode(&value) if err != nil { panic(err) } - fmt.Printf("% 02x ", data) + fmt.Println(value) + + // Output: + // {Tnze} +} + +func ExampleDecoder_Decode_singleTagString() { + reader := bytes.NewReader([]byte{ + // TagString + 0x08, + // TagName + 0x00, 0x04, + 0x6e, 0x61, 0x6d, 0x65, + // Content + 0x00, 0x09, + 0x42, 0x61, 0x6e, 0x61, 0x6e, 0x72, 0x61, 0x6d, 0x61, + }) + + var Name string + + decoder := NewDecoder(reader) + tagName, err := decoder.Decode(&Name) + if err != nil { + panic(err) + } + + fmt.Println(tagName) + fmt.Println(Name) + + // Output: + // name + // Bananrama +} + +func ExampleEncoder_Encode_tagCompound() { + var value = struct { + Name string `nbt:"name"` + }{"Tnze"} + + var buff bytes.Buffer + encoder := NewEncoder(&buff) + err := encoder.Encode(value, "") + if err != nil { + panic(err) + } + + fmt.Printf("% 02x ", buff.Bytes()) // Output: // 0a 00 00 08 00 04 6e 61 6d 65 00 04 54 6e 7a 65 00 diff --git a/nbt/rawmsg.go b/nbt/rawmsg.go index 147fe58..89ce955 100644 --- a/nbt/rawmsg.go +++ b/nbt/rawmsg.go @@ -2,10 +2,13 @@ package nbt import ( "bytes" + "errors" "io" + "reflect" "strings" ) +// RawMessage stores the raw binary data of NBT. type RawMessage struct { Type byte Data []byte @@ -49,3 +52,12 @@ func (m RawMessage) String() string { } return sb.String() } + +func (m RawMessage) Unmarshal(v interface{}) error { + d := NewDecoder(bytes.NewReader(m.Data)) + val := reflect.ValueOf(v) + if val.Kind() != reflect.Ptr { + return errors.New("nbt: non-pointer passed to Decode") + } + return d.unmarshal(val, m.Type) +}