forked from ayyaruq/zanarkand
-
Notifications
You must be signed in to change notification settings - Fork 0
/
message.go
199 lines (165 loc) · 5.65 KB
/
message.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
//lint:file-ignore U1000 Ignore unused struct members as they're part of the payload and users may want them
package zanarkand
import (
"bufio"
"encoding/binary"
"encoding/json"
"fmt"
"time"
)
var gameEventMessageHeaderLength = 32
// Segment types separate messages into their relevant field maps.
// Session/Encryption types are not implemented due to them largely only
// containing the player ID, or information that should be kept hidden due
// to privacy concerns. While this may inconvenience debugging, this project
// will not facilitate capturing player login details.
const (
SessionInit = 1
SessionRecv = 2
GameEvent = 3
ServerPing = 7
ServerPong = 8
EncryptInit = 9
EncryptRecv = 10
)
// GenericMessage is an interface for other Message types to make the Framer generic.
type GenericMessage interface {
Decode(*bufio.Reader) error
IsMessage()
MarshalJSON() ([]byte, error)
String() string
}
// GenericHeader provides the metadata for an FFXIV IPC.
// 0:16 provide the generic header, other types have additional header fields.
// Data pulled from Sapphire's `Network/CommonNetwork.h`
type GenericHeader struct {
Length uint32 `json:"size"` // [0:4]
SourceActor uint32 `json:"sourceActorID"` // [4:8]
TargetActor uint32 `json:"targetActorID"` // [8:12]
Segment uint16 `json:"segmentType"` // [12:14]
padding uint16 // [14:16]
}
// Decode a GenericHeader from a byte array.
func (m *GenericHeader) Decode(r *bufio.Reader) error {
data, err := r.Peek(16)
lengthBytes := len(data)
if err != nil {
return ErrNotEnoughData{Expected: 16, Received: lengthBytes, Err: err}
}
m.Length = binary.LittleEndian.Uint32(data[0:4])
m.SourceActor = binary.LittleEndian.Uint32(data[4:8])
m.TargetActor = binary.LittleEndian.Uint32(data[8:12])
m.Segment = binary.LittleEndian.Uint16(data[12:14])
m.padding = binary.LittleEndian.Uint16(data[14:16])
return nil
}
// String is a stringer for the GenericHeader of a Message.
func (m *GenericHeader) String() string {
return fmt.Sprintf("Segment - size: %d, source: %d, target: %d, segment: %d\n",
m.Length, m.SourceActor, m.TargetActor, m.Segment)
}
// GameEventMessage is a pre-type casted GameEventHeader and body.
type GameEventMessage struct {
GenericHeader
reserved uint16 // [16:18] - always 0x1400
Opcode uint16 `json:"opcode"` // [18:20] - message context identifier, the "opcode"
padding2 uint16 // [20:22]
ServerID uint16 `json:"serverID"` // [22:24]
Timestamp time.Time `json:"-"` // [24:28]
padding3 uint32 // [28:32]
Body []byte `json:"-"`
}
// IsMessage confirms a GameEventMessage is a Message.
func (GameEventMessage) IsMessage() {}
// Decode turns a byte payload into a real GameEventMessage.
func (m *GameEventMessage) Decode(r *bufio.Reader) error {
header := GenericHeader{}
err := header.Decode(r)
if err != nil {
return err
}
length := int(header.Length)
data, err := r.Peek(length)
lengthBytes := len(data)
if err != nil {
return ErrNotEnoughData{Expected: length, Received: lengthBytes, Err: err}
}
defer func() {
// Regardless of what we read, the buffer is treated like we got everything
_, _ = r.Discard(lengthBytes)
}()
m.GenericHeader = header
m.reserved = binary.LittleEndian.Uint16(data[16:18])
m.Opcode = binary.LittleEndian.Uint16(data[18:20])
m.ServerID = binary.LittleEndian.Uint16(data[22:24])
m.Timestamp = time.Unix(int64(binary.LittleEndian.Uint32(data[24:28])), 0)
m.Body = data[gameEventMessageHeaderLength:length]
return nil
}
// MarshalJSON provides an override for timestamp handling for encoding/JSON
func (m *GameEventMessage) MarshalJSON() ([]byte, error) {
type Alias GameEventMessage
data := make([]int, len(m.Body))
for i, b := range m.Body {
data[i] = int(b)
}
return json.Marshal(&struct {
Data []int `json:"data"`
Timestamp int32 `json:"timestamp"`
*Alias
}{
Data: data,
Timestamp: int32(m.Timestamp.Unix()),
Alias: (*Alias)(m),
})
}
// String prints a Segment and IPC Message specific headers.
func (m GameEventMessage) String() string {
return fmt.Sprintf(m.GenericHeader.String()+"Message - server: %v, opcode: 0x%X, timestamp: %v\n",
m.ServerID, m.Opcode, m.Timestamp.Unix())
}
// KeepaliveMessage is a representation of ping/pong requests.
type KeepaliveMessage struct {
GenericHeader
ID uint32 `json:"ID"` // [16:20]
Timestamp time.Time `json:"-"` // [20:24]
}
// IsMessage confirms a KeepaliveMessage is a Message.
func (KeepaliveMessage) IsMessage() {}
// Decode turns a byte payload into a real KeepaliveMessage.
func (m *KeepaliveMessage) Decode(r *bufio.Reader) error {
header := GenericHeader{}
err := header.Decode(r)
if err != nil {
return err
}
length := int(header.Length)
data, err := r.Peek(length)
lengthBytes := len(data)
if err != nil {
return ErrNotEnoughData{Expected: length, Received: lengthBytes, Err: err}
}
defer func() {
// Regardless of what we read, the buffer is treated like we got everything
_, _ = r.Discard(lengthBytes)
}()
m.GenericHeader = header
m.ID = binary.LittleEndian.Uint32(data[16:20])
m.Timestamp = time.Unix(int64(binary.LittleEndian.Uint32(data[20:24])), 0)
return nil
}
// MarshalJSON provides an override for timestamp handling for encoding/JSON
func (m *KeepaliveMessage) MarshalJSON() ([]byte, error) {
type Alias KeepaliveMessage
return json.Marshal(&struct {
Timestamp int32 `json:"timestamp"`
*Alias
}{
Timestamp: int32(m.Timestamp.Unix()),
Alias: (*Alias)(m),
})
}
// String prints the Segment header and Keepalive Message.
func (m *KeepaliveMessage) String() string {
return fmt.Sprintf(m.GenericHeader.String()+"Message - ID: %d, timestamp: %v\n", m.ID, m.Timestamp.Unix())
}