forked from df-mc/dragonfly
-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
358 lines (326 loc) · 9.81 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
package main
import (
"bytes"
"flag"
"fmt"
"go/ast"
"go/parser"
"go/token"
"io"
"log"
"math/bits"
"os"
"sort"
"strconv"
"strings"
)
func main() {
out := flag.String("o", "", "output file for hash constants and methods")
flag.Parse()
if len(flag.Args()) != 1 {
log.Fatalln("Must pass one package to produce block hashes for.")
}
fs := token.NewFileSet()
packages, err := parser.ParseDir(fs, flag.Args()[0], nil, parser.ParseComments)
if err != nil {
log.Fatalln(err)
}
f, err := os.OpenFile(*out, os.O_CREATE|os.O_TRUNC|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
log.Fatalln(err)
}
for _, pkg := range packages {
procPackage(pkg, fs, f)
}
_ = f.Close()
}
func procPackage(pkg *ast.Package, fs *token.FileSet, w io.Writer) {
b := &hashBuilder{
fs: fs,
pkg: pkg,
fields: make(map[string][]*ast.Field),
aliases: make(map[string]string),
handled: map[string]struct{}{},
funcs: map[string]*ast.FuncDecl{},
blockFields: map[string][]*ast.Field{},
}
b.readStructFields(pkg)
b.readFuncs(pkg)
b.resolveBlocks()
b.sortNames()
b.writePackage(w)
i := b.writeConstants(w)
b.writeNextHash(w)
b.writeMethods(w, i)
}
var (
packageFormat = "// Code generated by cmd/blockhash; DO NOT EDIT.\n\npackage %v\n\n"
methodFormat = "\n// Hash ...\nfunc (%v%v) Hash() uint64 {\n\treturn %v\n}\n"
constFormat = "\thash%v"
)
type hashBuilder struct {
fs *token.FileSet
pkg *ast.Package
fields map[string][]*ast.Field
funcs map[string]*ast.FuncDecl
aliases map[string]string
handled map[string]struct{}
blockFields map[string][]*ast.Field
names []string
}
// sortNames sorts the names of the blockFields map and stores them in a slice.
func (b *hashBuilder) sortNames() {
b.names = make([]string, 0, len(b.blockFields))
for name := range b.blockFields {
b.names = append(b.names, name)
}
sort.Slice(b.names, func(i, j int) bool {
return b.names[i] < b.names[j]
})
}
// writePackage writes the package at the top of the file.
func (b *hashBuilder) writePackage(w io.Writer) {
if _, err := fmt.Fprintf(w, packageFormat, b.pkg.Name); err != nil {
log.Fatalln(err)
}
}
// writeConstants writes hash constants for every block to a file.
func (b *hashBuilder) writeConstants(w io.Writer) (bitSize int) {
if _, err := fmt.Fprintln(w, "const ("); err != nil {
log.Fatalln(err)
}
var i uint64
for _, name := range b.names {
c := constFormat
if i == 0 {
c += " = iota"
}
if _, err := fmt.Fprintf(w, c+"\n", name); err != nil {
log.Fatalln(err)
}
i++
}
if _, err := fmt.Fprintln(w, "\thashCustomBlockBase\n)"); err != nil {
log.Fatalln(err)
}
return bits.Len64(i)
}
func (b *hashBuilder) writeNextHash(w io.Writer) {
if _, err := fmt.Fprintln(w, "\n// customBlockBase represents the base hash for all custom blocks."); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w, "var customBlockBase = uint64(hashCustomBlockBase - 1)"); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w, "\n// NextHash returns the next free hash for custom blocks."); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w, "func NextHash() uint64 {\n\tcustomBlockBase++\n\treturn customBlockBase\n}"); err != nil {
log.Fatalln(err)
}
}
func (b *hashBuilder) writeMethods(w io.Writer, baseBits int) {
for _, name := range b.names {
fields := b.blockFields[name]
h := "hash" + name
bitSize := baseBits
fun := b.funcs[name]
var recvName string
for _, n := range fun.Recv.List[0].Names {
recvName = n.Name
}
pos := b.fs.Position(fun.Body.Pos())
f, err := os.Open(pos.Filename)
if err != nil {
log.Fatalln(err)
}
body := make([]byte, fun.Body.End()-fun.Body.Pos())
if _, err := f.ReadAt(body, int64(pos.Offset)); err != nil {
log.Fatalln(err)
}
_ = f.Close()
for _, field := range fields {
for _, fieldName := range field.Names {
if !bytes.Contains(body, []byte(fieldName.Name)) {
// Field was not used in the EncodeBlock method, so we can assume it's not a property and thus
// should not be in the Hash method.
continue
}
if !fieldName.IsExported() {
continue
}
directives := make(map[string]string)
if field.Doc != nil {
for _, d := range field.Doc.List {
const k = "//blockhash:"
if index := strings.Index(d.Text, k); index != -1 {
dir := strings.Split(d.Text[index+len(k):], " ")
directives[dir[0]] = strings.Join(dir[1:], " ")
}
}
}
str, v := b.ftype(name, recvName+"."+fieldName.Name, field.Type, directives)
if v == 0 {
// Assume this field is not used in the hash.
continue
}
if bitSize > 64 {
log.Println("Hash size of block properties of", name, "exceeds", 64-baseBits, "bits. Please look at this manually.")
} else {
h += " | " + str + "<<" + strconv.Itoa(bitSize)
}
bitSize += v
}
}
if bitSize == baseBits {
// No need to have a receiver name if we don't use any of the fields of the block.
recvName = ""
}
if recvName != "" {
recvName += " "
}
if _, err := fmt.Fprintf(w, methodFormat, recvName, name, h); err != nil {
log.Fatalln(err)
}
}
log.Println("Assuming int size of 8 bits at most for all int fields: Make sure this is valid for all blocks.")
}
func (b *hashBuilder) ftype(structName, s string, expr ast.Expr, directives map[string]string) (string, int) {
var name string
switch t := expr.(type) {
case *ast.BasicLit:
name = t.Value
case *ast.Ident:
name = t.Name
case *ast.SelectorExpr:
name = t.Sel.Name
default:
log.Fatalf("unknown field type %#v\n", expr)
return "", 0
}
switch name {
case "bool":
return "uint64(boolByte(" + s + "))", 1
case "int":
return "uint64(" + s + ")", 8
case "Block":
return s + ".Hash()", 16
case "Attachment":
if _, ok := directives["facing_only"]; ok {
log.Println("Found directive: 'facing_only'")
return "uint64(" + s + ".FaceUint8())", 3
}
return "uint64(" + s + ".Uint8())", 5
case "GrindstoneAttachment":
return "uint64(" + s + ".Uint8())", 2
case "WoodType", "FlowerType", "DoubleFlowerType", "Colour":
// Assuming these were all based on metadata, it should be safe to assume a bit size of 4 for this.
return "uint64(" + s + ".Uint8())", 4
case "CoralType":
return "uint64(" + s + ".Uint8())", 3
case "AnvilType", "SandstoneType", "PrismarineType", "StoneBricksType", "NetherBricksType", "FroglightType", "WallConnectionType", "BlackstoneType", "DeepslateType", "TallGrassType":
return "uint64(" + s + ".Uint8())", 2
case "OreType", "FireType", "DoubleTallGrassType":
return "uint64(" + s + ".Uint8())", 1
case "Direction", "Axis":
return "uint64(" + s + ")", 2
case "Face":
return "uint64(" + s + ")", 3
default:
log.Println("Found unhandled field type", "'"+name+"'", "in block", structName+".", "Assuming this field is not included in block states. Please make sure this is correct or add the type to cmd/blockhash.")
}
return "", 0
}
func (b *hashBuilder) resolveBlocks() {
for bl, fields := range b.fields {
if _, ok := b.funcs[bl]; ok {
b.blockFields[bl] = fields
}
}
}
func (b *hashBuilder) readFuncs(pkg *ast.Package) {
for _, f := range pkg.Files {
ast.Inspect(f, b.readFuncDecls)
}
}
func (b *hashBuilder) readFuncDecls(node ast.Node) bool {
if fun, ok := node.(*ast.FuncDecl); ok {
// If the function is called 'EncodeBlock' and the receiver is not nil, meaning the function is a method, this
// is an implementation of the world.Block interface.
if fun.Name.Name == "EncodeBlock" && fun.Recv != nil {
b.funcs[fun.Recv.List[0].Type.(*ast.Ident).Name] = fun
}
}
return true
}
func (b *hashBuilder) readStructFields(pkg *ast.Package) {
for _, f := range pkg.Files {
ast.Inspect(f, b.readStructs)
}
b.resolveEmbedded()
b.resolveAliases()
}
func (b *hashBuilder) resolveAliases() {
for name, alias := range b.aliases {
b.fields[name] = b.findFields(alias)
}
}
func (b *hashBuilder) findFields(structName string) []*ast.Field {
for {
if fields, ok := b.fields[structName]; ok {
// Alias found in the fields map, so it referred to a struct directly.
return fields
}
if nested, ok := b.aliases[structName]; ok {
// The alias itself was an alias, so continue with the next.
structName = nested
continue
}
// Neither an alias nor a struct: Break as this isn't going to go anywhere.
return nil
}
}
func (b *hashBuilder) resolveEmbedded() {
for name, fields := range b.fields {
if _, ok := b.handled[name]; ok {
// Don't handle if a previous run already handled this struct.
continue
}
newFields := make([]*ast.Field, 0, len(fields))
for _, f := range fields {
if len(f.Names) == 0 {
// We're dealing with an embedded struct here. They're of the type ast.Ident.
if ident, ok := f.Type.(*ast.Ident); ok {
for _, af := range b.findFields(ident.Name) {
if len(af.Names) == 0 {
// The struct this referred is embedding a struct itself which hasn't yet been processed,
// so we need to rerun and hope that struct is handled next. This isn't a very elegant way,
// and could lead to a lot of runs, but in general it's fast enough and does the job.
b.resolveEmbedded()
return
}
}
newFields = append(newFields, b.findFields(ident.Name)...)
}
} else {
newFields = append(newFields, f)
}
}
// Make sure a next run doesn't end up handling this struct again.
b.handled[name] = struct{}{}
b.fields[name] = newFields
}
}
func (b *hashBuilder) readStructs(node ast.Node) bool {
if s, ok := node.(*ast.TypeSpec); ok {
switch t := s.Type.(type) {
case *ast.StructType:
b.fields[s.Name.Name] = t.Fields.List
case *ast.Ident:
// This is a type created something like 'type Andesite polishable': A type alias. We need to handle
// these later, first parse all struct types.
b.aliases[s.Name.Name] = t.Name
}
}
return true
}