Files
strom/mc/nbt/nbt.go
T
OpenCode ac1b298989 fix: implement topBitSetTerminatedArray, fix nbt TAG_End, fix entity_equipment
- 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
2026-08-05 19:30:18 +02:00

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
}