Skip to content

Commit 0333b59

Browse files
committed
Fast nbt walking I guess, testing required
1 parent 8203687 commit 0333b59

File tree

1 file changed

+190
-0
lines changed

1 file changed

+190
-0
lines changed

lib/nbtwalk/walk.go

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
package nbtwalk
2+
3+
import (
4+
"encoding/binary"
5+
"errors"
6+
"io"
7+
"math"
8+
"unsafe"
9+
10+
"github.com/maxsupermanhd/go-vmc/v764/nbt"
11+
)
12+
13+
var (
14+
ErrUnknownTag = errors.New("unknown tag")
15+
ErrNegativeSize = errors.New("negative size")
16+
)
17+
18+
type NBTnode struct {
19+
T byte
20+
N string
21+
S int
22+
}
23+
24+
type NBTidentifier struct {
25+
N string
26+
I int
27+
}
28+
29+
type WalkerCallbacks struct {
30+
CbEnd func(p []NBTnode)
31+
CbByte func(p []NBTnode, n *NBTidentifier, val byte)
32+
CbShort func(p []NBTnode, n *NBTidentifier, val uint16)
33+
CbInt func(p []NBTnode, n *NBTidentifier, val uint32)
34+
CbLong func(p []NBTnode, n *NBTidentifier, val uint64)
35+
CbFloat func(p []NBTnode, n *NBTidentifier, val float32)
36+
CbDouble func(p []NBTnode, n *NBTidentifier, val float64)
37+
CbByteArray func(p []NBTnode, n *NBTidentifier, val []byte)
38+
CbString func(p []NBTnode, n *NBTidentifier, val string)
39+
CbList func(p []NBTnode, n *NBTidentifier, t byte, l int)
40+
CbCompound func(p []NBTnode, n *NBTidentifier)
41+
CbIntArray func(p []NBTnode, n *NBTidentifier, val []uint32)
42+
CbLongArray func(p []NBTnode, n *NBTidentifier, val []uint64)
43+
}
44+
45+
// inspired by github.com/rmmh/cubeographer
46+
// reflectless nbt "parser" that shits on branch predictors
47+
// tldr callbacks get entered and exited from nbt tree
48+
// keep tree-poition based callbacks and swap them out
49+
// in runtime to avoid checking trees all the time
50+
// ps tag end in node tree means end (to not reallocate)
51+
func WalkNBT(data []byte, cb *WalkerCallbacks) error {
52+
p := make([]NBTnode, 0, 32)
53+
for i := 0; i < len(data); {
54+
t := data[i]
55+
i += 1
56+
if t == nbt.TagEnd {
57+
if len(p) == 0 {
58+
return io.ErrUnexpectedEOF
59+
}
60+
if cb.CbEnd != nil {
61+
cb.CbEnd(p)
62+
}
63+
p = p[:len(p)-1]
64+
continue
65+
}
66+
ns := int(binary.BigEndian.Uint16(data[i : i+2]))
67+
i += 2
68+
n := &NBTidentifier{N: string(data[i : i+ns])}
69+
switch t {
70+
default:
71+
return ErrUnknownTag
72+
case nbt.TagByte:
73+
if i >= len(data) {
74+
return io.ErrUnexpectedEOF
75+
}
76+
if cb.CbByte != nil {
77+
cb.CbByte(p, n, data[i])
78+
}
79+
i += 1
80+
case nbt.TagShort:
81+
if i+2 >= len(data) {
82+
return io.ErrUnexpectedEOF
83+
}
84+
if cb.CbShort != nil {
85+
cb.CbShort(p, n, binary.BigEndian.Uint16(data[i:i+2]))
86+
}
87+
i += 2
88+
case nbt.TagInt:
89+
if i+4 >= len(data) {
90+
return io.ErrUnexpectedEOF
91+
}
92+
if cb.CbInt != nil {
93+
cb.CbInt(p, n, binary.BigEndian.Uint32(data[i:i+4]))
94+
}
95+
i += 4
96+
case nbt.TagLong:
97+
if i+8 >= len(data) {
98+
return io.ErrUnexpectedEOF
99+
}
100+
if cb.CbLong != nil {
101+
cb.CbLong(p, n, binary.BigEndian.Uint64(data[i:i+8]))
102+
}
103+
i += 8
104+
case nbt.TagFloat:
105+
if i+4 >= len(data) {
106+
return io.ErrUnexpectedEOF
107+
}
108+
if cb.CbFloat != nil {
109+
cb.CbFloat(p, n, math.Float32frombits(binary.BigEndian.Uint32(data[i:i+4])))
110+
}
111+
i += 4
112+
case nbt.TagDouble:
113+
if i+8 >= len(data) {
114+
return io.ErrUnexpectedEOF
115+
}
116+
if cb.CbDouble != nil {
117+
cb.CbDouble(p, n, math.Float64frombits(binary.BigEndian.Uint64(data[i:i+8])))
118+
}
119+
i += 8
120+
case nbt.TagByteArray:
121+
if i+4 >= len(data) {
122+
return io.ErrUnexpectedEOF
123+
}
124+
s := int(binary.BigEndian.Uint32(data[i : i+4]))
125+
if i+4+s >= len(data) {
126+
return io.ErrUnexpectedEOF
127+
}
128+
cb.CbByteArray(p, n, data[i+4:i+s])
129+
i += 4 + s
130+
case nbt.TagString:
131+
if i+2 >= len(data) {
132+
return io.ErrUnexpectedEOF
133+
}
134+
s := int(binary.BigEndian.Uint16(data[i : i+2]))
135+
if i+2+s >= len(data) {
136+
return io.ErrUnexpectedEOF
137+
}
138+
cb.CbString(p, n, string(data[i+2:i+2+s]))
139+
i += 2 + s
140+
case nbt.TagList:
141+
if i+4+1 >= len(data) {
142+
return io.ErrUnexpectedEOF
143+
}
144+
lt := data[i]
145+
if lt > 12 {
146+
return ErrUnknownTag
147+
}
148+
ls := int(binary.BigEndian.Uint32(data[i+1 : i+1+4]))
149+
i += 4 + 1
150+
cb.CbList(p, n, lt, ls)
151+
p = append(p, NBTnode{
152+
T: nbt.TagList,
153+
N: n.N,
154+
S: ls,
155+
})
156+
case nbt.TagCompound:
157+
cb.CbCompound(p, n)
158+
p = append(p, NBTnode{
159+
T: nbt.TagList,
160+
N: n.N,
161+
})
162+
case nbt.TagIntArray:
163+
if i+4 >= len(data) {
164+
return io.ErrUnexpectedEOF
165+
}
166+
s := int32(binary.BigEndian.Uint32(data[i : i+4]))
167+
if s < 0 {
168+
return ErrNegativeSize
169+
}
170+
if i+4+int(s)*4 >= len(data) {
171+
return io.ErrUnexpectedEOF
172+
}
173+
// most cursed thing I've done yet
174+
cb.CbIntArray(p, n, unsafe.Slice((*uint32)(unsafe.Pointer(&data[i+4])), s))
175+
case nbt.TagLongArray:
176+
if i+4 >= len(data) {
177+
return io.ErrUnexpectedEOF
178+
}
179+
s := int32(binary.BigEndian.Uint32(data[i : i+4]))
180+
if s < 0 {
181+
return ErrNegativeSize
182+
}
183+
if i+4+int(s)*8 >= len(data) {
184+
return io.ErrUnexpectedEOF
185+
}
186+
cb.CbLongArray(p, n, unsafe.Slice((*uint64)(unsafe.Pointer(&data[i+4])), s))
187+
}
188+
}
189+
return nil
190+
}

0 commit comments

Comments
 (0)