-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathparse.go
123 lines (106 loc) · 3.75 KB
/
parse.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
package goslippi
import (
"bytes"
"fmt"
"github.com/pmcca/go-slippi/internal/errutil"
"github.com/pmcca/go-slippi/internal/logging"
"github.com/pmcca/go-slippi/slippi"
"github.com/pmcca/go-slippi/slippi/event"
"github.com/pmcca/go-slippi/slippi/event/handler"
"github.com/pmcca/go-slippi/slippi/event/handler/handlers"
"github.com/toitware/ubjson"
"os"
)
var (
eventHandlers = map[event.Code]handler.EventHandler{
event.EventGameStart: handlers.GameStartHandler{},
event.EventPreFrame: handlers.PreFrameHandler{},
event.EventPostFrame: handlers.PostFrameHandler{},
event.EventGameEnd: handlers.GameEndHandler{},
event.EventFrameStart: handlers.FrameStartHandler{},
event.EventItemUpdate: handlers.ItemUpdateHandler{},
event.EventFrameBookend: handlers.FrameBookendHandler{},
event.EventGeckoList: handlers.GeckoCodeHandler{},
event.EventMessageSplitter: handlers.MessageSplitterHandler{},
}
log = logging.NewLogger()
)
// rawParser contains the parsed Slippi replay and is used as the orchestrator in the parsing process.
type rawParser struct {
ParsedData slippi.Data
}
// parser wraps a rawParser and slippi.Metadata, and is passed into ubjson.Unmarshal() to begin the parsing process.
type parser struct {
RawParser rawParser `ubjson:"raw"`
Meta slippi.Metadata `ubjson:"metadata"`
}
// ParseGame reads the .slp file given by filePath and returns the decoded game.
func ParseGame(filePath string) (slippi.Game, error) {
b, err := readFile(filePath)
if err != nil {
return slippi.Game{}, err
}
p := parser{
RawParser: rawParser{
ParsedData: slippi.Data{
Frames: map[int]slippi.Frame{},
},
},
}
if err := ubjson.Unmarshal(b, &p); err != nil {
return slippi.Game{}, errutil.WithMessagef(err, ErrParsingGame, "filePath: %s", filePath)
}
return slippi.Game{
Data: p.RawParser.ParsedData,
Meta: p.Meta,
}, nil
}
// UnmarshalUBJSON implements the ubjson.Unmarshaler interface. It receives the array of bytes from the 'raw' array and
// orchestrates the parsing process. rawParser implements this to separate this logic from slippi.Data.
func (r *rawParser) UnmarshalUBJSON(b []byte) error {
// Beginning of raw array should always be '$U#l'.
if !bytes.Equal(b[0:4], []byte("$U#l")) {
return fmt.Errorf("%w:expected '$U#l', found %s", ErrInvalidRawStart, b[0:4])
}
dec := event.Decoder{
Data: b[8:], // Skip $U#l and 4 bytes for length. Next byte should be EventPayloads code.
Size: len(b),
}
eventSizes, err := handlers.ParseEventPayloads(&dec)
if err != nil {
return fmt.Errorf("%w:failed to parse event payloads", err)
}
startOffset := (eventSizes[event.EventPayloadsEvent] + 1) + 8 // Start reading from the first event after EventPayloads
dec.Data = b[startOffset:]
// Main event parsing loop
for len(dec.Data) > 0 {
eventCode := event.Code(dec.Read(0x0))
eventSize, ok := eventSizes[eventCode]
if !ok {
return fmt.Errorf("%w:eventCode %X", ErrUnknownEventInEventSizes, eventCode)
}
dec.Size = eventSize + 1
eventHandler, ok := eventHandlers[eventCode]
if !ok {
log.Warn().Msgf("Unable to handle unknown event %X. Skipping.", eventCode)
} else {
if err := eventHandler.Parse(&dec, &r.ParsedData); err != nil {
return errutil.WithMessagef(err, ErrFailedEventParsing, "event code: %X", eventCode)
}
}
// Update the window of data, skipping the # of bytes read + the command byte.
dec.Data = dec.Data[dec.Size:]
}
return nil
}
// readFile reads & returns the bytes of the given .slp file.
func readFile(filePath string) ([]byte, error) {
if filePath == "" {
return nil, ErrEmptyFilePath
}
b, err := os.ReadFile(filePath)
if err != nil {
return nil, errutil.WithMessagef(err, ErrReadingFile, "filePath: %s", filePath)
}
return b, nil
}