package nbt import ( "bytes" "errors" "math" "strconv" "strings" ) type decodeState struct { data []byte off int // next read offset in data opcode int // last read result scan scanner } 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, "") } func writeValue(e *Encoder, d *decodeState, tagName string) error { d.scanWhile(scanSkipSpace) switch d.opcode { default: panic(phasePanicMsg) case scanBeginLiteral: start := d.readIndex() d.scanWhile(scanContinue) literal := d.data[start:d.readIndex()] tagType, litVal := parseLiteral(literal) e.writeTag(tagType, tagName) return writeLiteralPayload(e, litVal) case scanBeginCompound: e.writeTag(TagCompound, tagName) return writeCompoundPayload(e, d) case scanBeginList: return writeListOrArray(e, d, tagName) } } func writeLiteralPayload(e *Encoder, v interface{}) error { switch v.(type) { case string: str := v.(string) e.writeInt16(int16(len(str))) e.w.Write([]byte(str)) case int8: e.w.Write([]byte{byte(v.(int8))}) case int16: e.writeInt16(v.(int16)) case int32: e.writeInt32(v.(int32)) case int64: e.writeInt64(v.(int64)) case float32: e.writeInt32(int32(math.Float32bits(v.(float32)))) case float64: e.writeInt64(int64(math.Float64bits(v.(float64)))) } return nil } func writeCompoundPayload(e *Encoder, d *decodeState) error { for { d.scanWhile(scanSkipSpace) if d.opcode == scanEndValue { break } if d.opcode != scanBeginLiteral { panic(phasePanicMsg) } // read tag name start := d.readIndex() d.scanWhile(scanContinue) tagName := string(d.data[start:d.readIndex()]) // read value if d.opcode == scanSkipSpace { d.scanWhile(scanSkipSpace) } if d.opcode != scanCompoundTagName { panic(phasePanicMsg) } writeValue(e, d, tagName) // Next token must be , or }. if d.opcode == scanSkipSpace { d.scanWhile(scanSkipSpace) } if d.opcode == scanEndValue { break } if d.opcode != scanCompoundValue { panic(phasePanicMsg) } } e.w.Write([]byte{TagEnd}) return nil } func writeListOrArray(e *Encoder, d *decodeState, tagName string) error { d.scanWhile(scanSkipSpace) if d.opcode == scanEndValue { // ']', empty TAG_List e.writeTag(TagList, tagName) e.writeInt32(0) return nil } // We don't know the length of the List, // so we read them into a buffer and count. var buf bytes.Buffer var count int e2 := NewEncoder(&buf) start := d.readIndex() switch d.opcode { case scanBeginLiteral: d.scanWhile(scanContinue) literal := d.data[start:d.readIndex()] if d.opcode == scanListType { // TAG_X_Array //listType := literal[0] break } if d.opcode != scanListValue { // TAG_List panic(phasePanicMsg) } var tagType byte for { t, v := parseLiteral(literal) if tagType == 0 { tagType = t } if t != tagType { return errors.New("different TagType in List") } writeLiteralPayload(e2, v) count++ // read ',' or ']' if d.opcode == scanSkipSpace { d.scanWhile(scanSkipSpace) } if d.opcode == scanEndValue { break } if d.opcode != scanListValue { panic(phasePanicMsg) } start = d.readIndex() d.scanNext() d.scanWhile(scanContinue) literal = d.data[start:d.readIndex()] } e.writeListHeader(tagType, tagName, count) e.w.Write(buf.Bytes()) case scanBeginList: // TAG_List case scanBeginCompound: // TAG_List } return nil } // readIndex returns the position of the last byte read. func (d *decodeState) readIndex() int { return d.off - 1 } // scanNext processes the byte at d.data[d.off]. func (d *decodeState) scanNext() { if d.off < len(d.data) { d.opcode = d.scan.step(d.data[d.off]) d.off++ } else { //d.opcode = d.scan.eof() d.off = len(d.data) + 1 // mark processed EOF with len+1 } } // scanWhile processes bytes in d.data[d.off:] until it // receives a scan code not equal to op. func (d *decodeState) scanWhile(op int) { s, data, i := &d.scan, d.data, d.off for i < len(data) { newOp := s.step(data[i]) i++ if newOp != op { d.opcode = newOp d.off = i return } } d.off = len(data) + 1 // mark processed EOF with len+1 d.opcode = d.scan.eof() } // parseLiteral parse an SNBT literal, might be // TAG_String, TAG_Int, TAG_Float, ... etc. // so returned value is one of string, int32, float32 ... func parseLiteral(literal []byte) (byte, interface{}) { switch literal[0] { case '"', '\'': // Quoted String var sb strings.Builder sb.Grow(len(literal) - 2) for i := 1; ; i++ { c := literal[i] switch c { case literal[0]: return TagString, sb.String() case '\\': i++ c = literal[i] } sb.WriteByte(c) } default: strlen := len(literal) integer := true number := true unqstr := true var numberType byte for i, c := range literal { if isNumber(c) { continue } else if integer { if i == strlen-1 && isIntegerType(c) { numberType = c strlen-- } else if i > 0 || i == 0 && c != '-' { integer = false if i == 0 || c != '.' { number = false } } } else if number { if i == strlen-1 && isFloatType(c) { numberType = c } else { number = false } } else if !isAllowedInUnquotedString(c) { unqstr = false } } if integer { num, err := strconv.ParseInt(string(literal[:strlen]), 10, 64) if err != nil { panic(err) } switch numberType { case 'B', 'b': return TagByte, int8(num) case 'S', 's': return TagShort, int16(num) default: return TagInt, int32(num) case 'L', 'l': return TagLong, num case 'F', 'f': return TagFloat, float32(num) case 'D', 'd': return TagDouble, float64(num) } } else if number { num, err := strconv.ParseFloat(string(literal[:strlen-1]), 64) if err != nil { panic(err) } switch numberType { case 'F', 'f': return TagFloat, float32(num) case 'D', 'd': fallthrough default: return TagDouble, num } } else if unqstr { return TagString, string(literal) } } panic(phasePanicMsg) } func isIntegerType(c byte) bool { return isFloatType(c) || c == 'B' || c == 'b' || c == 's' || c == 'S' || c == 'L' || c == 'l' } func isFloatType(c byte) bool { return c == 'F' || c == 'f' || c == 'D' || c == 'd' }