-
Notifications
You must be signed in to change notification settings - Fork 1
/
info.go
230 lines (198 loc) · 5.45 KB
/
info.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
package goseq
import (
"bytes"
"encoding/binary"
"time"
)
const (
HAS_PORT byte = 0x80
HAS_STEAMID = 0x10
HAS_SOURCETV = 0x40
HAS_KEYWORDS = 0x20
HAS_GAMEID = 0x01
)
func (serv *iserver) Info(timeout time.Duration) (info ServerInfo, err error) {
info = NewServerInfo()
conn, err := serv.getConnection()
if err != nil {
return info, err
}
defer conn.Close()
outOfTime := make(chan bool)
done := make(chan bool)
request := []byte("\xFF\xFF\xFF\xFF\x54Source Engine Query\x00")
// a buffer of buffers for multi packet responses
var payload []byte
go func() {
time.Sleep(timeout)
outOfTime <- true
}()
go func() {
conn.Write(request)
pks := newPacketStream()
err = pks.Gobble(conn) // yum yum
if err != nil {
done <- true
}
payload, err = pks.GetFullPayload()
done <- true
}()
select {
case <-outOfTime:
return info, Timeout
case <-done:
if err != nil {
return info, err
}
break
}
err = info.decode(bytes.NewBuffer(payload))
return
}
type wrInfHead struct {
Header byte // 0x49 "I"
}
type wrInfStd1 struct {
Protocol byte
}
type wrInfStd2 struct {
Name string
Map string
Folder string
Game string
}
type wrInfStd3 struct {
ID int16 // App ID of game
Players uint8
MaxPlayers uint8
Bots uint8
Servertype ServerType
Environment byte
Visibility byte
VAC byte // is bool
}
type wrInfShip struct {
Mode byte
Witnesses uint8
Duration uint8
}
type wrInfStd4 struct {
Version string
EDF byte // Extra Data Flag
}
type wrInfExtra struct {
Port uint16 // if EDF & 0x80
SteamID uint64 // if EDF & 0x10
SpectatorPort uint16 // if EDF & 0x40
SpectatorName string // if EDF & 0x40
Keywords string // if EDF & 0x20
GameID uint64 // if EDF & 0x01
}
// ServerInfo holds all the info values for a server.
// Why is it composed so weird? Composition allows incrementally
// decoding it from the payload while still allowing external
// packages to access the data. AKA it hides the dirty-nasty.
//
// Eg ServerInfo{}.Port
type ServerInfo struct {
wrInfHead
// always present
wrInfStd1
wrInfStd2
wrInfStd3
// only if the game is The Ship (ID == 2400)
wrInfShip
wrInfStd4
// fields that depend on the flags in EDF
wrInfExtra
}
func (s *ServerInfo) GetName() string { return s.wrInfStd2.Name }
func (s *ServerInfo) GetMap() string { return s.wrInfStd2.Map }
func (s *ServerInfo) GetFolder() string { return s.wrInfStd2.Folder }
func (s *ServerInfo) GetGame() string { return s.wrInfStd2.Game }
func (s *ServerInfo) GetID() int16 { return s.wrInfStd3.ID }
func (s *ServerInfo) GetPlayers() uint8 { return s.wrInfStd3.Players }
func (s *ServerInfo) GetMaxPlayers() uint8 { return s.wrInfStd3.MaxPlayers }
func (s *ServerInfo) GetBots() uint8 { return s.wrInfStd3.Bots }
func (s *ServerInfo) GetServertype() ServerType { return s.wrInfStd3.Servertype }
func (s *ServerInfo) GetEnvironment() ServerEnvironment {
return ServerEnvironment(s.wrInfStd3.Environment)
}
func (s *ServerInfo) GetVisibility() byte { return s.wrInfStd3.Visibility }
func (s *ServerInfo) GetVAC() byte { return s.wrInfStd3.VAC }
func (s *ServerInfo) GetMode() byte { return s.wrInfShip.Mode }
func (s *ServerInfo) GetWitnesses() uint8 { return s.wrInfShip.Witnesses }
func (s *ServerInfo) GetDuration() uint8 { return s.wrInfShip.Duration }
func (s *ServerInfo) GetVersion() string { return s.wrInfStd4.Version }
func (s *ServerInfo) GetPort() uint16 { return s.wrInfExtra.Port }
func (s *ServerInfo) GetSteamID() uint64 { return s.wrInfExtra.SteamID }
func (s *ServerInfo) GetSpectatorPort() uint16 { return s.wrInfExtra.SpectatorPort }
func (s *ServerInfo) GetSpectatorName() string { return s.wrInfExtra.SpectatorName }
func (s *ServerInfo) GetKeywords() string { return s.wrInfExtra.Keywords }
func (s *ServerInfo) GetGameID() uint64 { return s.wrInfExtra.GameID }
func NewServerInfo() ServerInfo {
return ServerInfo{}
}
func (p *ServerInfo) decode(stream *bytes.Buffer) (err error) {
if err = binary.Read(stream, byteOrder, &p.wrInfHead); err != nil {
return
}
if p.wrInfHead.Header != byte('I') {
return PacketMalformed
}
if err = binary.Read(stream, byteOrder, &p.wrInfStd1); err != nil {
return
}
str2decode := []*string{&p.Name, &p.Map, &p.Folder, &p.Game}
for _, loc := range str2decode {
if *loc, err = rcstr(stream); err != nil {
return
}
}
if err = binary.Read(stream, byteOrder, &p.wrInfStd3); err != nil {
return
}
// The Ship-only section
if p.ID == 2400 {
if err = binary.Read(stream, byteOrder, &p.wrInfShip); err != nil {
return
}
}
// Back to standard sections
if p.Version, err = rcstr(stream); err != nil {
return
}
if err = binary.Read(stream, byteOrder, &p.EDF); err != nil {
return
}
// No to check all the extra flags! :D
if p.EDF&HAS_PORT > 0 {
if err = binary.Read(stream, byteOrder, &p.Port); err != nil {
return
}
}
if p.EDF&HAS_STEAMID > 0 {
if err = binary.Read(stream, byteOrder, &p.SteamID); err != nil {
return
}
}
if p.EDF&HAS_SOURCETV > 0 {
if err = binary.Read(stream, byteOrder, &p.SpectatorPort); err != nil {
return
}
if p.Version, err = rcstr(stream); err != nil {
return
}
}
if p.EDF&HAS_KEYWORDS > 0 {
if p.Keywords, err = rcstr(stream); err != nil {
return
}
}
if p.EDF&HAS_GAMEID > 0 {
if err = binary.Read(stream, byteOrder, &p.GameID); err != nil {
return
}
}
return nil
}