248 lines
5.0 KiB
Go
248 lines
5.0 KiB
Go
package nbt
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type StringifiedMessage string
|
|
|
|
func (m StringifiedMessage) TagType() byte {
|
|
d := decodeState{data: []byte(m)}
|
|
d.scan.reset()
|
|
d.scanWhile(scanSkipSpace)
|
|
switch d.opcode {
|
|
default:
|
|
return TagEnd
|
|
|
|
case scanBeginLiteral:
|
|
start := d.readIndex()
|
|
if d.scanWhile(scanContinue); d.opcode == scanError {
|
|
return TagEnd
|
|
}
|
|
literal := d.data[start:d.readIndex()]
|
|
tagType, _, _ := parseLiteral(literal)
|
|
return tagType
|
|
|
|
case scanBeginCompound:
|
|
return TagCompound
|
|
|
|
case scanBeginList:
|
|
d.scanWhile(scanSkipSpace)
|
|
if d.opcode == scanBeginLiteral {
|
|
start := d.readIndex()
|
|
if d.scanWhile(scanContinue); d.opcode == scanError {
|
|
return TagEnd
|
|
}
|
|
literal := d.data[start:d.readIndex()]
|
|
if d.opcode == scanSkipSpace {
|
|
d.scanWhile(scanSkipSpace)
|
|
}
|
|
if d.opcode == scanListType {
|
|
switch literal[0] {
|
|
case 'B':
|
|
return TagByteArray
|
|
case 'I':
|
|
return TagIntArray
|
|
case 'L':
|
|
return TagLongArray
|
|
}
|
|
}
|
|
}
|
|
return TagList
|
|
}
|
|
}
|
|
|
|
func (m StringifiedMessage) MarshalNBT(w io.Writer) error {
|
|
d := decodeState{data: []byte(m)}
|
|
d.scan.reset()
|
|
return writeValue(NewEncoder(w), &d, false, "")
|
|
}
|
|
|
|
func (m *StringifiedMessage) UnmarshalNBT(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))
|
|
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("'")
|
|
if _, err := strings.NewReplacer(`'`, `\'`, `\`, `\\`).WriteString(sb, str); err != nil {
|
|
panic(err)
|
|
}
|
|
sb.WriteString("'")
|
|
} else {
|
|
sb.WriteString(`"`)
|
|
if _, err := strings.NewReplacer(`"`, `\"`, `\`, `\\`).WriteString(sb, str); err != nil {
|
|
panic(err)
|
|
}
|
|
sb.WriteString(`"`)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
sb.WriteString(str)
|
|
}
|