-
-
Notifications
You must be signed in to change notification settings - Fork 15
/
protocol.go
472 lines (390 loc) · 12.8 KB
/
protocol.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
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
/*
The exported Protocol type.
*/
package s2prot
import (
"bufio"
"fmt"
"log"
"strconv"
"strings"
"sync"
"github.com/icza/s2prot/build"
)
var (
// MinBaseBuild is the min supported base build
MinBaseBuild int
// MaxBaseBuild is the max supported base build
MaxBaseBuild int
)
func init() {
// Init min and max base builds:
for k := range build.Builds {
MinBaseBuild, MaxBaseBuild = k, k
break
}
// Find min and max base builds:
for k := range build.Builds {
if MaxBaseBuild < k {
MaxBaseBuild = k
}
if MinBaseBuild > k {
MinBaseBuild = k
}
}
// Consider duplicates too
for k := range build.Duplicates {
if MaxBaseBuild < k {
MaxBaseBuild = k
}
if MinBaseBuild > k {
MinBaseBuild = k
}
}
}
// EvtType describes a named event data structure type.
type EvtType struct {
ID int // Id of the event
Name string // Name of the event
typeid int // Type id of the event data structure
}
// The Protocol type which implements the data structures and their decoding
// from SC2Replay files defined by s2protocol.
type Protocol struct {
baseBuild int // Base build
typeInfos []typeInfo // Type info slice, decoding instructions for all the types
hasTrackerEvents bool // Tells if this protocol has/handles tracker events
gameEvtTypes []EvtType // Game event type descriptors; index is event id
gameEventidTypeid int // The typeid of the NNet.Game.EEventId enum
messageEvtTypes []EvtType // Message event type descriptors; index is event id
messageEventidTypeid int // The typeid of the NNet.Game.EMessageId enum
trackerEvtTypes []EvtType // Tracker event type descriptors; index is event id
trackerEventidTypeid int // The typeid of the NNet.Replay.Tracker.EEventId enum
svaruint32Typeid int // The typeid of NNet.SVarUint32 (the type used to encode gameloop deltas)
replayUseridTypeid int // The typeid of NNet.Replay.SGameUserId (the type used to encode player ids) [from base build 24764, before that player id is stored instead of user id!]
replayHeaderTypeid int // The typeid of NNet.Replay.SHeader (the type used to store replay game version and length)
gameDetailsTypeid int // The typeid of NNet.Game.SDetails (the type used to store overall replay details)
replayInitdataTypeid int // The typeid of NNet.Replay.SInitData (the type used to store the initial lobby)
}
var (
// Holds the already parsed Protocols mapped from base build.
protocols = make(map[int]*Protocol)
// Mutex protecting access of the protocols map
protMux = &sync.Mutex{}
)
// GetProtocol returns the Protocol for the specified base build.
// nil return value indicates unknown/unsupported base build.
func GetProtocol(baseBuild int) *Protocol {
protMux.Lock()
defer protMux.Unlock()
return getProtocol(baseBuild)
}
// getProtocol returns the Protocol for the specified base build.
// nil return value indicates unknown/unsupported base build.
// protMux must be locked when this function is called.
func getProtocol(baseBuild int) *Protocol {
// Check if protocol is already parsed:
p, ok := protocols[baseBuild]
if ok {
// Note that ok only means a value exists for baseBuild but it might be nil
// in case we didn't find it or failed to parse it in an earlier call.
return p
}
// Not yet parsed, check if an original base build (not duplicate):
src, ok := build.Builds[baseBuild]
if ok {
p = parseProtocol(src, baseBuild)
protocols[baseBuild] = p
return p
}
// Either a duplicate or an Unknown base build. Check for duplicate:
origBaseBuild, ok := build.Duplicates[baseBuild]
if ok {
// It's a duplicate. Get the original (will load original if needed).
// origBasebuild surely exists (build.Duplicates contains valid entries, ensured by test!)
// but parsing it may (still) fail, so check for nil:
if op := getProtocol(origBaseBuild); op != nil {
// Copy / clone protocol with proper base build:
p = new(Protocol)
*p = *op
p.baseBuild = baseBuild
}
}
// (else it's not a duplicate: it's an Unknown base build; p remains nil)
// Even if p is nil: still store nil value so we'll know this earlier next time
protocols[baseBuild] = p
return p
}
// parseProtocol parses a Protocol from its python source.
// nil is returned if parsing error occurs.
func parseProtocol(src string, baseBuild int) *Protocol {
// Protect the parsing logic:
defer func() {
if r := recover(); r != nil {
log.Printf("Failed to parse protocol source %d: %v\n", baseBuild, r)
}
// nil will be returned by parseProtocol()
}()
p := Protocol{baseBuild: baseBuild, hasTrackerEvents: baseBuild >= 24944}
scanner := bufio.NewScanner(strings.NewReader(src))
var line string
// Helper function to seek to a line with a given prefix:
seek := func(prefix string) {
for scanner.Scan() {
line = scanner.Text()
if strings.HasPrefix(line, prefix) {
return
}
}
panic(fmt.Sprintf(`Couldn't find "%s"`, prefix))
}
// Helper function to parse the last integer number from the current line with form: "some_name = int_value"
parseInt := func() int {
i := strings.LastIndex(line, "=")
if i < 0 {
panic("Can't find '=' in line")
}
n, err := strconv.Atoi(strings.TrimSpace(line[i+1:]))
if err != nil {
panic(err)
}
return n
}
// Helper function to parse an event types slice
parseEvtTypes := func(stripPref, stripPost string) []EvtType {
var err error
em := make(map[int]EvtType) // First build it in a map
maxEid := -1 // Max Event id
for scanner.Scan() {
line = scanner.Text()
if line == "}" {
break
}
e := EvtType{}
i := strings.IndexByte(line, ':')
e.ID, err = strconv.Atoi(strings.TrimSpace(line[:i]))
if err != nil {
panic(err)
}
line = line[i+1:]
i = strings.IndexByte(line, '(') + 1
j := strings.IndexByte(line, ',')
e.typeid, err = strconv.Atoi(strings.TrimSpace(line[i:j]))
if err != nil {
panic(err)
}
i = strings.IndexByte(line, '\'') + 1
line = line[i:]
i = strings.IndexByte(line, '\'')
e.Name = line[len(stripPref) : i-len(stripPost)]
em[e.ID] = e
if e.ID > maxEid {
maxEid = e.ID
}
}
// And now create a slice from the map:
es := make([]EvtType, maxEid+1)
for k, v := range em {
es[k] = v
}
return es
}
_ = parseEvtTypes
// Decode typeinfos
seek("typeinfos")
// Use a large local variable
typeInfos := make([]typeInfo, 0, 256)
for scanner.Scan() {
line = scanner.Text()
if line == "]" {
break
}
typeInfos = append(typeInfos, parseTypeInfo(line))
}
// And now copy a trimmed version of this to Protocol (typeInfo is a relatively large struct):
p.typeInfos = make([]typeInfo, len(typeInfos))
copy(p.typeInfos, typeInfos)
// Decode game event types
seek("game_event_types")
p.gameEvtTypes = parseEvtTypes("NNet.Game.S", "Event")
seek("game_eventid_typeid")
p.gameEventidTypeid = parseInt()
// Decode message event types
seek("message_event_types")
p.messageEvtTypes = parseEvtTypes("NNet.Game.S", "Message")
seek("message_eventid_typeid")
p.messageEventidTypeid = parseInt()
if p.hasTrackerEvents {
// Decode track event types
seek("tracker_event_types")
p.trackerEvtTypes = parseEvtTypes("NNet.Replay.Tracker.S", "Event")
seek("tracker_eventid_typeid")
p.trackerEventidTypeid = parseInt()
}
seek("svaruint32_typeid")
p.svaruint32Typeid = parseInt()
// From basebuild 24764 user id is present, before that player id
if baseBuild >= 24764 {
seek("replay_userid_typeid")
} else {
seek("replay_playerid_typeid")
}
p.replayUseridTypeid = parseInt()
seek("replay_header_typeid")
p.replayHeaderTypeid = parseInt()
seek("game_details_typeid")
p.gameDetailsTypeid = parseInt()
seek("replay_initdata_typeid")
p.replayInitdataTypeid = parseInt()
return &p
}
// DecodeHeader decodes and returns the replay header.
// Panics if decoding fails.
func DecodeHeader(contents []byte) Struct {
// Use max base build to decode replay headers as that is the one most likely always needed.
p := GetProtocol(MaxBaseBuild)
if p == nil {
panic("Default protocol is not available!")
}
contents = contents[4:] // 3c 00 00 00 (might be part of the MPQ header and not the user data)
d := newVersionedDec(contents, p.typeInfos)
v, ok := d.instance(p.replayHeaderTypeid).(Struct)
if !ok {
return nil
}
return v
}
// DecodeDetails decodes and returns the game details.
// Panics if decoding fails.
func (p *Protocol) DecodeDetails(contents []byte) Struct {
d := newVersionedDec(contents, p.typeInfos)
v, ok := d.instance(p.gameDetailsTypeid).(Struct)
if !ok {
return nil
}
return v
}
// DecodeInitData decodes and returns the replay init data.
// Panics if decoding fails.
func (p *Protocol) DecodeInitData(contents []byte) Struct {
d := newBitPackedDec(contents, p.typeInfos)
v, ok := d.instance(p.replayInitdataTypeid).(Struct)
if !ok {
return nil
}
return v
}
// DecodeAttributesEvts decodes and returns the attributes events.
// Panics if decoding fails.
func (p *Protocol) DecodeAttributesEvts(contents []byte) Struct {
s := Struct{}
if len(contents) == 0 {
return s
}
bb := &bitPackedBuff{
contents: contents,
bigEndian: false, // Note: the only place where little endian order is used.
}
// Source is only present from 1.2 and onward (base build 17326)
if p.baseBuild >= 17326 {
s["source"] = bb.readBits(8)
}
s["mapNamespace"] = bb.readBits(32)
bb.readBits(32) // Attributes count
scopes := Struct{}
for !bb.EOF() {
attr := Struct{}
attr["namespace"] = bb.readBits(32)
attrid := bb.readBits(32)
attr["attrid"] = attrid
attrscope := bb.readBits(8)
// SIDENOTE: My feeling is that since this (decoding attributes events) is the only place
// where little endian order is used, readAligned() implementation should will the slice backwards.
// That way no reverse would be needed.
vb := bb.readAligned(4)
// Reverse and strip leading zeros
vb[0], vb[3] = vb[3], vb[0]
vb[1], vb[2] = vb[2], vb[1]
for i := 3; i >= 0; i-- {
if vb[i] == 0 {
vb = vb[i+1:]
break
}
}
attr["value"] = string(vb)
sattrscope := strconv.FormatInt(attrscope, 10)
scope, ok := scopes[sattrscope].(Struct)
if !ok {
scope = Struct{}
scopes[sattrscope] = scope
}
scope[strconv.FormatInt(attrid, 10)] = attr
}
s["scopes"] = scopes
return s
}
// Type decoder defines the most basic methods a decoder must support.
type decoder interface {
EOF() bool
byteAlign()
instance(typeid int) interface{}
}
// DecodeGameEvts decodes and returns the game events.
// In case of a decoding error, successfully decoded events are still returned along with an error.
func (p *Protocol) DecodeGameEvts(contents []byte) ([]Event, error) {
return p.decodeEvts(newBitPackedDec(contents, p.typeInfos), p.gameEventidTypeid, p.gameEvtTypes, true)
}
// DecodeMessageEvts decodes and returns the message events.
// In case of a decoding error, successfully decoded events are still returned along with an error.
func (p *Protocol) DecodeMessageEvts(contents []byte) ([]Event, error) {
return p.decodeEvts(newBitPackedDec(contents, p.typeInfos), p.messageEventidTypeid, p.messageEvtTypes, true)
}
// DecodeTrackerEvts decodes and returns the tracker events.
// In case of a decoding error, successfully decoded events are still returned along with an error.
func (p *Protocol) DecodeTrackerEvts(contents []byte) ([]Event, error) {
return p.decodeEvts(newVersionedDec(contents, p.typeInfos), p.trackerEventidTypeid, p.trackerEvtTypes, false)
}
// decodeEvts decodes a series of events.
// In case of a decoding error, successfully decoded events are still returned along with an error.
func (p *Protocol) decodeEvts(d decoder, evtidTypeid int, etypes []EvtType, decUserID bool) (events []Event, err error) {
deltaTypeid := p.svaruint32Typeid // Local var for efficiency
useridTypeid := p.replayUseridTypeid // Local var for efficiency
events = make([]Event, 0, 256) // This is most likely overestimation for messages events but underestimation for all other even types
// Protect the events decoding:
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("failed to decode events: %v", r)
log.Println(err)
}
// Successfully decoded events will be returned
}()
var (
loop int64
userid interface{}
)
for !d.EOF() {
delta := d.instance(deltaTypeid).(Struct)
// delta has one key-value pair:
for _, v := range delta {
loop += v.(int64)
}
if decUserID {
userid = d.instance(useridTypeid)
}
evtid := d.instance(evtidTypeid).(int64)
evtType := &etypes[evtid]
// Decode the event data structure:
e := Event{Struct: d.instance(evtType.typeid).(Struct), EvtType: evtType}
// Copy to / duplicate data in Struct so Struct.String() includes them too
e.Struct["id"] = evtid
e.Struct["evtTypeName"] = evtType.Name
e.Struct["loop"] = loop
if decUserID {
e.Struct["userid"] = userid
}
events = append(events, e)
// The next event is byte-aligned:
d.byteAlign()
}
return
}