- Implement topBitSetTerminatedArray decoder/encoder with MSB continuation - Fix nbt.Tag.Decode to handle TAG_End at root level (fixes 1.8 slot NBT) - Remove 228 now-passing decode failure tests, keep 10 still failing - EntityEquipment now works correctly across all versions
373 lines
6.0 KiB
Go
373 lines
6.0 KiB
Go
package nbt
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
)
|
|
|
|
const (
|
|
End int8 = iota
|
|
Byte
|
|
Short
|
|
Int
|
|
Long
|
|
Float
|
|
Double
|
|
ByteArray
|
|
String
|
|
List
|
|
Compound
|
|
IntArray
|
|
LongArray
|
|
)
|
|
|
|
var Order binary.ByteOrder = binary.BigEndian
|
|
|
|
var BadStringLengthError = errors.New("bad string length")
|
|
|
|
type Tag struct {
|
|
Name string
|
|
Value any
|
|
}
|
|
|
|
type Anon struct {
|
|
Value any
|
|
}
|
|
|
|
func (s *Anon) Decode(r io.Reader) (err error) {
|
|
id := int8(0)
|
|
err = binary.Read(r, Order, &id)
|
|
if err != nil {
|
|
return
|
|
}
|
|
s.Value, err = readPayload(id, r)
|
|
if err != nil {
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *Anon) Encode(w io.Writer) (err error) {
|
|
id, err := getId(s.Value)
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = writePayload(id, w)
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = writePayload(s.Value, w)
|
|
return
|
|
}
|
|
|
|
func (s *Tag) Decode(r io.Reader) (err error) {
|
|
id := int8(0)
|
|
err = binary.Read(r, Order, &id)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if id == End {
|
|
return
|
|
}
|
|
var name any
|
|
name, err = readPayload(String, r)
|
|
if err != nil {
|
|
return
|
|
}
|
|
s.Value, err = readPayload(id, r)
|
|
if err != nil {
|
|
return
|
|
}
|
|
s.Name = name.(string)
|
|
return
|
|
}
|
|
|
|
func (s *Tag) Encode(w io.Writer) (err error) {
|
|
id, err := getId(s.Value)
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = writePayload(id, w)
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = writePayload(s.Name, w)
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = writePayload(s.Value, w)
|
|
return
|
|
}
|
|
|
|
func getId(v any) (id int8, err error) {
|
|
switch v.(type) {
|
|
case struct{}:
|
|
id = End
|
|
case int8:
|
|
id = Byte
|
|
case int16:
|
|
id = Short
|
|
case int32:
|
|
id = Int
|
|
case int64:
|
|
id = Long
|
|
case float32:
|
|
id = Float
|
|
case float64:
|
|
id = Double
|
|
case []int8:
|
|
id = ByteArray
|
|
case string:
|
|
id = String
|
|
case []any:
|
|
id = List
|
|
case map[string]any:
|
|
id = Compound
|
|
case []int32:
|
|
id = IntArray
|
|
case []int64:
|
|
id = LongArray
|
|
default:
|
|
err = fmt.Errorf("unsupported type %T", v)
|
|
}
|
|
return
|
|
}
|
|
|
|
func readPayload(id int8, r io.Reader) (ret any, err error) {
|
|
switch id {
|
|
case End:
|
|
ret = struct{}{}
|
|
case Byte:
|
|
rett := int8(0)
|
|
err = binary.Read(r, Order, &rett)
|
|
ret = rett
|
|
case Short:
|
|
rett := int16(0)
|
|
err = binary.Read(r, Order, &rett)
|
|
ret = rett
|
|
case Int:
|
|
rett := int32(0)
|
|
err = binary.Read(r, Order, &rett)
|
|
ret = rett
|
|
case Long:
|
|
rett := int64(0)
|
|
err = binary.Read(r, Order, &rett)
|
|
ret = rett
|
|
case Float:
|
|
rett := float32(0)
|
|
err = binary.Read(r, Order, &rett)
|
|
ret = rett
|
|
case Double:
|
|
rett := float64(0)
|
|
err = binary.Read(r, Order, &rett)
|
|
ret = rett
|
|
case ByteArray:
|
|
var l int32
|
|
err = binary.Read(r, Order, &l)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if l < 0 {
|
|
err = errors.New("bad array length")
|
|
return
|
|
}
|
|
var rett []int8
|
|
for range l {
|
|
var element int8
|
|
err = binary.Read(r, Order, &element)
|
|
if err != nil {
|
|
return
|
|
}
|
|
rett = append(rett, element)
|
|
}
|
|
ret = rett
|
|
case String:
|
|
var l uint16
|
|
err = binary.Read(r, Order, &l)
|
|
if err != nil {
|
|
return
|
|
}
|
|
var s []byte
|
|
s, err = io.ReadAll(io.LimitReader(r, int64(l)))
|
|
if err != nil {
|
|
return
|
|
}
|
|
if len(s) != int(l) {
|
|
err = BadStringLengthError
|
|
return
|
|
}
|
|
ret = string(s)
|
|
case List:
|
|
var id int8
|
|
err = binary.Read(r, Order, &id)
|
|
if err != nil {
|
|
return
|
|
}
|
|
var l int32
|
|
err = binary.Read(r, Order, &l)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if l <= 0 || id == End {
|
|
ret = []any{}
|
|
return
|
|
}
|
|
var rett []any
|
|
for range l {
|
|
var result any
|
|
result, err = readPayload(id, r)
|
|
if err != nil {
|
|
return
|
|
}
|
|
rett = append(rett, result)
|
|
}
|
|
ret = rett
|
|
case Compound:
|
|
rett := map[string]any{}
|
|
for {
|
|
id := int8(0)
|
|
err = binary.Read(r, Order, &id)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if id == End {
|
|
break
|
|
}
|
|
var namea any
|
|
namea, err = readPayload(String, r)
|
|
if err != nil {
|
|
return
|
|
}
|
|
name := namea.(string)
|
|
rett[name], err = readPayload(id, r)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
ret = rett
|
|
case IntArray:
|
|
var l int32
|
|
err = binary.Read(r, Order, &l)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if l < 0 {
|
|
err = errors.New("bad array length")
|
|
return
|
|
}
|
|
var rett []int32
|
|
for range l {
|
|
var element int32
|
|
err = binary.Read(r, Order, &element)
|
|
if err != nil {
|
|
return
|
|
}
|
|
rett = append(rett, element)
|
|
}
|
|
ret = rett
|
|
case LongArray:
|
|
var l int32
|
|
err = binary.Read(r, Order, &l)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if l < 0 {
|
|
err = errors.New("bad array length")
|
|
return
|
|
}
|
|
var rett []int64
|
|
for range l {
|
|
var element int64
|
|
err = binary.Read(r, Order, &element)
|
|
if err != nil {
|
|
return
|
|
}
|
|
rett = append(rett, element)
|
|
}
|
|
ret = rett
|
|
default:
|
|
err = errors.New("unkown nbt type id")
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
var UnknownTagTypeErr = errors.New("unknown nbt type")
|
|
|
|
func writePayload(v any, w io.Writer) (err error) {
|
|
switch v := v.(type) {
|
|
case struct{}:
|
|
case int8, int16, int32, int64, float32, float64:
|
|
err = binary.Write(w, Order, v)
|
|
case []int8:
|
|
err = binary.Write(w, Order, int32(len(v)))
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = binary.Write(w, Order, v)
|
|
case []int32:
|
|
err = binary.Write(w, Order, int32(len(v)))
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = binary.Write(w, Order, v)
|
|
case []int64:
|
|
err = binary.Write(w, Order, int32(len(v)))
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = binary.Write(w, Order, v)
|
|
case string:
|
|
err = binary.Write(w, Order, uint16(len(v)))
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = binary.Write(w, Order, []byte(v))
|
|
case []any:
|
|
id := End
|
|
if len(v) != 0 {
|
|
id, err = getId(v[0])
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
err = binary.Write(w, Order, id)
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = binary.Write(w, Order, int32(len(v)))
|
|
for _, v := range v {
|
|
err = writePayload(v, w)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
case map[string]any:
|
|
for k, v := range v {
|
|
var id int8
|
|
id, err = getId(v)
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = binary.Write(w, Order, id)
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = writePayload(k, w)
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = writePayload(v, w)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
err = binary.Write(w, Order, End)
|
|
default:
|
|
err = UnknownTagTypeErr
|
|
}
|
|
return
|
|
}
|