diff --git a/nbt/decode_test.go b/nbt/decode_test.go index 67da161..bce767b 100644 --- a/nbt/decode_test.go +++ b/nbt/decode_test.go @@ -380,16 +380,18 @@ func TestDecoder_Decode_ErrorString(t *testing.T) { } -func TestDecoder_Decode_rawMessage(t *testing.T) { +func TestRawMessage_Decode(t *testing.T) { data := []byte{ TagCompound, 0, 2, 'a', 'b', TagInt, 0, 3, 'K', 'e', 'y', 0, 0, 0, 12, TagString, 0, 5, 'V', 'a', 'l', 'u', 'e', 0, 4, 'T', 'n', 'z', 'e', + TagList, 0, 4, 'L', 'i', 's', 't', TagCompound, 0, 0, 0, 2, 0, 0, TagEnd, } var container struct { Key int32 Value RawMessage + List RawMessage } if tag, err := NewDecoder(bytes.NewReader(data)).Decode(&container); err != nil { @@ -406,5 +408,56 @@ func TestDecoder_Decode_rawMessage(t *testing.T) { }) { t.Fatalf("Decode Key error: get: %v", container.Value) } + if !bytes.Equal(container.List.Data, []byte{ + TagCompound, 0, 0, 0, 2, + 0, 0, + }) { + t.Fatalf("Decode List error: get: %v", container.List) + } + } +} + +func TestStringifiedMessage_Decode(t *testing.T) { + data := []byte{ + TagCompound, 0, 2, 'a', 'b', + TagInt, 0, 3, 'K', 'e', 'y', 0, 0, 0, 12, + TagString, 0, 5, 'V', 'a', 'l', 'u', 'e', 0, 5, 'T', 'n', ' ', 'z', 'e', + TagList, 0, 4, 'L', 'i', 's', 't', TagCompound, 0, 0, 0, 2, 0, 0, + TagEnd, + } + var container struct { + Key int32 + Value StringifiedMessage + List StringifiedMessage + } + + if tag, err := NewDecoder(bytes.NewReader(data)).Decode(&container); err != nil { + t.Fatal(tag, err) + } else { + if tag != "ab" { + t.Fatalf("Decode tag name error: want %s, get: %s", "ab", tag) + } + if container.Key != 12 { + t.Fatalf("Decode Key error: want %v, get: %v", 12, container.Key) + } + if container.Value != `"Tn ze"` { + t.Fatalf("Decode Key error: get: %v", container.Value) + } + if container.List != "[{},{}]" { + t.Fatalf("Decode List error: get: %v", container.List) + } + } +} + +func TestStringifiedMessage_Decode_bigTest(t *testing.T) { + var snbt StringifiedMessage + r, err := gzip.NewReader(bytes.NewReader(bigTestData[:])) + if err != nil { + t.Fatal(err) + } + if tag, err := NewDecoder(r).Decode(&snbt); err != nil { + t.Fatal(tag, err) + } else if string(snbt) != `{longTest:9223372036854775807L,shortTest:32767S,stringTest:"HELLO WORLD THIS IS A TEST STRING ÅÄÖ!",floatTest:0.4982314706F,intTest:2147483647I,"nested compound test":{ham:{name:Hampus,value:0.7500000000F},egg:{name:Eggbert,value:0.5000000000F}},"listTest (long)":[11L,12L,13L,14L,15L],"listTest (compound)":[{name:"Compound tag #0",created-on:1264099775885L},{name:"Compound tag #1",created-on:1264099775885L}],byteTest:127B,"byteArrayTest (the first 1000 values of (n*n*255+n*7)%100, starting with n=0 (0, 62, 34, 16, 8, ...))":[B;0B,62B,34B,16B,8B,10B,22B,44B,76B,18B,70B,32B,4B,86B,78B,80B,92B,14B,46B,88B,40B,2B,74B,56B,48B,50B,62B,84B,16B,58B,10B,72B,44B,26B,18B,20B,32B,54B,86B,28B,80B,42B,14B,96B,88B,90B,2B,24B,56B,98B,50B,12B,84B,66B,58B,60B,72B,94B,26B,68B,20B,82B,54B,36B,28B,30B,42B,64B,96B,38B,90B,52B,24B,6B,98B,0B,12B,34B,66B,8B,60B,22B,94B,76B,68B,70B,82B,4B,36B,78B,30B,92B,64B,46B,38B,40B,52B,74B,6B,48B,0B,62B,34B,16B,8B,10B,22B,44B,76B,18B,70B,32B,4B,86B,78B,80B,92B,14B,46B,88B,40B,2B,74B,56B,48B,50B,62B,84B,16B,58B,10B,72B,44B,26B,18B,20B,32B,54B,86B,28B,80B,42B,14B,96B,88B,90B,2B,24B,56B,98B,50B,12B,84B,66B,58B,60B,72B,94B,26B,68B,20B,82B,54B,36B,28B,30B,42B,64B,96B,38B,90B,52B,24B,6B,98B,0B,12B,34B,66B,8B,60B,22B,94B,76B,68B,70B,82B,4B,36B,78B,30B,92B,64B,46B,38B,40B,52B,74B,6B,48B,0B,62B,34B,16B,8B,10B,22B,44B,76B,18B,70B,32B,4B,86B,78B,80B,92B,14B,46B,88B,40B,2B,74B,56B,48B,50B,62B,84B,16B,58B,10B,72B,44B,26B,18B,20B,32B,54B,86B,28B,80B,42B,14B,96B,88B,90B,2B,24B,56B,98B,50B,12B,84B,66B,58B,60B,72B,94B,26B,68B,20B,82B,54B,36B,28B,30B,42B,64B,96B,38B,90B,52B,24B,6B,98B,0B,12B,34B,66B,8B,60B,22B,94B,76B,68B,70B,82B,4B,36B,78B,30B,92B,64B,46B,38B,40B,52B,74B,6B,48B,0B,62B,34B,16B,8B,10B,22B,44B,76B,18B,70B,32B,4B,86B,78B,80B,92B,14B,46B,88B,40B,2B,74B,56B,48B,50B,62B,84B,16B,58B,10B,72B,44B,26B,18B,20B,32B,54B,86B,28B,80B,42B,14B,96B,88B,90B,2B,24B,56B,98B,50B,12B,84B,66B,58B,60B,72B,94B,26B,68B,20B,82B,54B,36B,28B,30B,42B,64B,96B,38B,90B,52B,24B,6B,98B,0B,12B,34B,66B,8B,60B,22B,94B,76B,68B,70B,82B,4B,36B,78B,30B,92B,64B,46B,38B,40B,52B,74B,6B,48B,0B,62B,34B,16B,8B,10B,22B,44B,76B,18B,70B,32B,4B,86B,78B,80B,92B,14B,46B,88B,40B,2B,74B,56B,48B,50B,62B,84B,16B,58B,10B,72B,44B,26B,18B,20B,32B,54B,86B,28B,80B,42B,14B,96B,88B,90B,2B,24B,56B,98B,50B,12B,84B,66B,58B,60B,72B,94B,26B,68B,20B,82B,54B,36B,28B,30B,42B,64B,96B,38B,90B,52B,24B,6B,98B,0B,12B,34B,66B,8B,60B,22B,94B,76B,68B,70B,82B,4B,36B,78B,30B,92B,64B,46B,38B,40B,52B,74B,6B,48B,0B,62B,34B,16B,8B,10B,22B,44B,76B,18B,70B,32B,4B,86B,78B,80B,92B,14B,46B,88B,40B,2B,74B,56B,48B,50B,62B,84B,16B,58B,10B,72B,44B,26B,18B,20B,32B,54B,86B,28B,80B,42B,14B,96B,88B,90B,2B,24B,56B,98B,50B,12B,84B,66B,58B,60B,72B,94B,26B,68B,20B,82B,54B,36B,28B,30B,42B,64B,96B,38B,90B,52B,24B,6B,98B,0B,12B,34B,66B,8B,60B,22B,94B,76B,68B,70B,82B,4B,36B,78B,30B,92B,64B,46B,38B,40B,52B,74B,6B,48B,0B,62B,34B,16B,8B,10B,22B,44B,76B,18B,70B,32B,4B,86B,78B,80B,92B,14B,46B,88B,40B,2B,74B,56B,48B,50B,62B,84B,16B,58B,10B,72B,44B,26B,18B,20B,32B,54B,86B,28B,80B,42B,14B,96B,88B,90B,2B,24B,56B,98B,50B,12B,84B,66B,58B,60B,72B,94B,26B,68B,20B,82B,54B,36B,28B,30B,42B,64B,96B,38B,90B,52B,24B,6B,98B,0B,12B,34B,66B,8B,60B,22B,94B,76B,68B,70B,82B,4B,36B,78B,30B,92B,64B,46B,38B,40B,52B,74B,6B,48B,0B,62B,34B,16B,8B,10B,22B,44B,76B,18B,70B,32B,4B,86B,78B,80B,92B,14B,46B,88B,40B,2B,74B,56B,48B,50B,62B,84B,16B,58B,10B,72B,44B,26B,18B,20B,32B,54B,86B,28B,80B,42B,14B,96B,88B,90B,2B,24B,56B,98B,50B,12B,84B,66B,58B,60B,72B,94B,26B,68B,20B,82B,54B,36B,28B,30B,42B,64B,96B,38B,90B,52B,24B,6B,98B,0B,12B,34B,66B,8B,60B,22B,94B,76B,68B,70B,82B,4B,36B,78B,30B,92B,64B,46B,38B,40B,52B,74B,6B,48B,0B,62B,34B,16B,8B,10B,22B,44B,76B,18B,70B,32B,4B,86B,78B,80B,92B,14B,46B,88B,40B,2B,74B,56B,48B,50B,62B,84B,16B,58B,10B,72B,44B,26B,18B,20B,32B,54B,86B,28B,80B,42B,14B,96B,88B,90B,2B,24B,56B,98B,50B,12B,84B,66B,58B,60B,72B,94B,26B,68B,20B,82B,54B,36B,28B,30B,42B,64B,96B,38B,90B,52B,24B,6B,98B,0B,12B,34B,66B,8B,60B,22B,94B,76B,68B,70B,82B,4B,36B,78B,30B,92B,64B,46B,38B,40B,52B,74B,6B,48B,0B,62B,34B,16B,8B,10B,22B,44B,76B,18B,70B,32B,4B,86B,78B,80B,92B,14B,46B,88B,40B,2B,74B,56B,48B,50B,62B,84B,16B,58B,10B,72B,44B,26B,18B,20B,32B,54B,86B,28B,80B,42B,14B,96B,88B,90B,2B,24B,56B,98B,50B,12B,84B,66B,58B,60B,72B,94B,26B,68B,20B,82B,54B,36B,28B,30B,42B,64B,96B,38B,90B,52B,24B,6B,98B,0B,12B,34B,66B,8B,60B,22B,94B,76B,68B,70B,82B,4B,36B,78B,30B,92B,64B,46B,38B,40B,52B,74B,6B,48B],doubleTest:0.4931287132D}` { + t.Fatalf("decode to SNBT error: get %s", snbt) } } diff --git a/nbt/encode_test.go b/nbt/encode_test.go index 8014e5a..dafd272 100644 --- a/nbt/encode_test.go +++ b/nbt/encode_test.go @@ -208,7 +208,7 @@ func TestEncoder_Encode_map(t *testing.T) { } } -func TestEncoder_Encode_rawMessage(t *testing.T) { +func TestRawMessage_Encode(t *testing.T) { data := []byte{ TagCompound, 0, 2, 'a', 'b', TagInt, 0, 3, 'K', 'e', 'y', 0, 0, 0, 12, diff --git a/nbt/snbt.go b/nbt/snbt.go index 5ce6871..9480558 100644 --- a/nbt/snbt.go +++ b/nbt/snbt.go @@ -1,11 +1,17 @@ package nbt -import "io" +import ( + "fmt" + "io" + "math" + "strconv" + "strings" +) -type StringifiedNBT string +type StringifiedMessage string -func (n StringifiedNBT) TagType() (tagType byte) { - d := decodeState{data: []byte(n)} +func (m StringifiedMessage) TagType() (tagType byte) { + d := decodeState{data: []byte(m)} d.scan.reset() d.scanWhile(scanSkipSpace) switch d.opcode { @@ -48,11 +54,188 @@ func (n StringifiedNBT) TagType() (tagType byte) { return } -func (n StringifiedNBT) Encode(w io.Writer) error { - d := decodeState{data: []byte(n)} +func (m StringifiedMessage) Encode(w io.Writer) error { + d := decodeState{data: []byte(m)} d.scan.reset() - return writeValue(NewEncoder(w), &d, "") + return writeValue(NewEncoder(w), &d, false, "") } -//func (n *StringifiedNBT) Decode(tagType byte, r DecoderReader) error { -//} +func (m *StringifiedMessage) Decode(tagType byte, r DecoderReader) error { + if tagType == TagEnd { + return ErrEND + } + var sb strings.Builder + d := NewDecoder(r) + err := m.encode(d, &sb, tagType) + if err != nil { + return err + } + *m = StringifiedMessage(sb.String()) + return nil +} + +func (m *StringifiedMessage) encode(d *Decoder, sb *strings.Builder, tagType byte) error { + switch tagType { + default: + return fmt.Errorf("unknown to read 0x%02x", tagType) + case TagByte: + b, err := d.r.ReadByte() + sb.WriteString(strconv.FormatInt(int64(b), 10) + "B") + return err + case TagString: + str, err := d.readString() + writeEscapeStr(sb, str) + return err + case TagShort: + s, err := d.readInt16() + sb.WriteString(strconv.FormatInt(int64(s), 10) + "S") + return err + case TagInt: + i, err := d.readInt32() + sb.WriteString(strconv.FormatInt(int64(i), 10) + "I") + return err + case TagFloat: + i, err := d.readInt32() + f := float64(math.Float32frombits(uint32(i))) + sb.WriteString(strconv.FormatFloat(f, 'f', 10, 32) + "F") + return err + case TagLong: + i, err := d.readInt64() + sb.WriteString(strconv.FormatInt(i, 10) + "L") + return err + case TagDouble: + i, err := d.readInt64() + f := math.Float64frombits(uint64(i)) + sb.WriteString(strconv.FormatFloat(f, 'f', 10, 64) + "D") + return err + case TagByteArray: + aryLen, err := d.readInt32() + if err != nil { + return err + } + first := true + sb.WriteString("[B;") + for i := int32(0); i < aryLen; i++ { + b, err := d.r.ReadByte() + if err != nil { + return err + } + if first { + first = false + } else { + sb.WriteString(",") + } + sb.WriteString(strconv.FormatInt(int64(b), 10) + "B") + } + sb.WriteString("]") + case TagIntArray: + aryLen, err := d.readInt32() + if err != nil { + return err + } + sb.WriteString("[I;") + first := true + for i := 0; i < int(aryLen); i++ { + v, err := d.readInt32() + if err != nil { + return err + } + if first { + first = false + } else { + sb.WriteString(",") + } + sb.WriteString(strconv.FormatInt(int64(v), 10) + "I") + } + sb.WriteString("]") + case TagLongArray: + aryLen, err := d.readInt32() + if err != nil { + return err + } + first := true + sb.WriteString("[L;") + for i := 0; i < int(aryLen); i++ { + v, err := d.readInt64() + if err != nil { + return err + } + if first { + first = false + } else { + sb.WriteString(",") + } + sb.WriteString(strconv.FormatInt(v, 10) + "L") + } + sb.WriteString("]") + case TagList: + listType, err := d.r.ReadByte() + if err != nil { + return err + } + listLen, err := d.readInt32() + if err != nil { + return err + } + first := true + sb.WriteString("[") + for i := 0; i < int(listLen); i++ { + if first { + first = false + } else { + sb.WriteString(",") + } + if err := m.encode(d, sb, listType); err != nil { + return err + } + } + sb.WriteString("]") + case TagCompound: + first := true + for { + tt, tn, err := d.readTag() + if err != nil { + return err + } + if first { + sb.WriteString("{") + first = false + } else if tt != TagEnd { + sb.WriteString(",") + } + if tt == TagEnd { + sb.WriteString("}") + break + } + + writeEscapeStr(sb, tn) + sb.WriteString(":") + err = m.encode(d, sb, tt) + if err != nil { + return err + } + } + } + return nil +} + +func writeEscapeStr(sb *strings.Builder, str string) { + for _, v := range []byte(str) { + if !isAllowedInUnquotedString(v) { + // need quote + dc := strings.Count(str, `"`) + sc := strings.Count(str, `'`) + if dc > sc { + sb.WriteString("'") + strings.NewReplacer(`'`, `\'`, `\`, `\\`).WriteString(sb, str) + sb.WriteString("'") + } else { + sb.WriteString(`"`) + strings.NewReplacer(`"`, `\"`, `\`, `\\`).WriteString(sb, str) + sb.WriteString(`"`) + } + return + } + } + sb.WriteString(str) +} diff --git a/nbt/snbt_decode.go b/nbt/snbt_decode.go index 282b8e6..b7d4dab 100644 --- a/nbt/snbt_decode.go +++ b/nbt/snbt_decode.go @@ -19,10 +19,10 @@ const phasePanicMsg = "SNBT decoder out of sync - data changing underfoot?" func (e *Encoder) WriteSNBT(snbt string) error { d := decodeState{data: []byte(snbt)} d.scan.reset() - return writeValue(e, &d, "") + return writeValue(e, &d, true, "") } -func writeValue(e *Encoder, d *decodeState, tagName string) error { +func writeValue(e *Encoder, d *decodeState, writeTag bool, tagName string) error { d.scanWhile(scanSkipSpace) switch d.opcode { case scanError: @@ -37,19 +37,23 @@ func writeValue(e *Encoder, d *decodeState, tagName string) error { } literal := d.data[start:d.readIndex()] tagType, litVal := parseLiteral(literal) - if err := e.writeTag(tagType, tagName); err != nil { - return err + if writeTag { + if err := e.writeTag(tagType, tagName); err != nil { + return err + } } return writeLiteralPayload(e, litVal) case scanBeginCompound: - if err := e.writeTag(TagCompound, tagName); err != nil { - return err + if writeTag { + if err := e.writeTag(TagCompound, tagName); err != nil { + return err + } } return writeCompoundPayload(e, d) case scanBeginList: - _, err := writeListOrArray(e, d, true, tagName) + _, err := writeListOrArray(e, d, writeTag, tagName) return err } } @@ -114,7 +118,7 @@ func writeCompoundPayload(e *Encoder, d *decodeState) error { panic(phasePanicMsg) } - if err := writeValue(e, d, tagName); err != nil { + if err := writeValue(e, d, true, tagName); err != nil { return err } diff --git a/nbt/snbt_decode_test.go b/nbt/snbt_decode_test.go index 74af6e4..059d414 100644 --- a/nbt/snbt_decode_test.go +++ b/nbt/snbt_decode_test.go @@ -118,13 +118,34 @@ func TestStringifiedNBT_TagType(t *testing.T) { {`123B`, TagByte}, {`123`, TagInt}, {`[]`, TagList}, + {`[{}, {}]`, TagList}, {`[B;]`, TagByteArray}, {`[I;]`, TagIntArray}, {`[L;]`, TagLongArray}, {`{abc:123B}`, TagCompound}, } { - if T := StringifiedNBT(v.snbt).TagType(); T != v.Type { + if T := StringifiedMessage(v.snbt).TagType(); T != v.Type { t.Errorf("Parse SNBT TagType error: %s is %d, not %d", v.snbt, v.Type, T) } } } + +func TestStringifiedMessage_Encode(t *testing.T) { + var buff bytes.Buffer + for _, v := range []struct { + snbt string + data []byte + }{ + {`123B`, []byte{123}}, + {`[B; 1B, 2B, 3B]`, []byte{0, 0, 0, 3, 1, 2, 3}}, + {`[{},{}]`, []byte{TagCompound, 0, 0, 0, 2, 0, 0}}, + } { + if err := StringifiedMessage(v.snbt).Encode(&buff); err != nil { + t.Errorf("Encode SNBT error: %v", err) + } + if !bytes.Equal(buff.Bytes(), v.data) { + t.Errorf("Encode SNBT error: %q should encoded to %d, not %d", v.snbt, v.data, buff.Bytes()) + } + buff.Reset() + } +} diff --git a/nbt/snbt_scanner.go b/nbt/snbt_scanner.go index 17c5723..9277f9a 100644 --- a/nbt/snbt_scanner.go +++ b/nbt/snbt_scanner.go @@ -1,6 +1,8 @@ package nbt -import "strconv" +import ( + "strconv" +) const ( scanContinue = iota // uninteresting byte