-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
235 lines (198 loc) · 5.28 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
package main
import (
"encoding/binary"
"fmt"
"net"
"os"
"os/exec"
"sort"
"strings"
"strconv"
"time"
)
type A2SInfo struct {
header uint8
protocol uint8
name string
mapname string
folder string
game string
id int16
players uint8
max_players uint8
bots uint8
server_type uint8
environment uint8
visibility uint8
vac uint8
// we assume here that nobody wants to do something with The Ship
version string
edf uint8 //Extra Data Flag
timestamp time.Time //Time when this was created
}
type Server struct {
addrport string
info A2SInfo
}
type ByIP []Server
func (a ByIP) Len() int { return len(a) }
func (a ByIP) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByIP) Less(i, j int) bool { return a[i].addrport < a[j].addrport }
func min(a, b int) int {
if a < b {
return a
}
return b
}
func getTtySize() (int, int, error) {
cmd := exec.Command("stty", "size")
cmd.Stdin = os.Stdin
out, err := cmd.Output()
if err != nil {
return 0, 0, err
}
size := string(out)
sizes := strings.Split(size, " ")
x, _ := strconv.Atoi(strings.TrimSpace(sizes[1]))
y, _ := strconv.Atoi(strings.TrimSpace(sizes[0]))
fmt.Println(sizes[1])
return x, y, nil
}
func sendInfo(conn net.UDPConn, addr string) {
info_msg := []byte("\xFF\xFF\xFF\xFFTSource Engine Query\x00")
dst, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
fmt.Println("Error while resolving remote addr")
os.Exit(1)
}
_, err = conn.WriteTo(info_msg, dst)
if err != nil {
fmt.Println("Error while sending")
os.Exit(1)
}
}
func recvAnswer(conn net.UDPConn) ([]byte, net.UDPAddr, net.Error) {
buf := make([]byte, 1500)
amount, addr, err := conn.ReadFromUDP(buf)
if err != nil {
err, ok := err.(*net.OpError)
if ok {
return []byte{}, net.UDPAddr{}, err
}
fmt.Println("An error occured while receiving", err)
os.Exit(1)
}
return buf[0:amount], *addr, nil
}
// parses the next byte starting at pos as an uint8
func parse_byte(buf []byte, pos *int) uint8 {
value := uint8(buf[*pos])
*pos += 1
return value
}
// parses the next 2 bytes starting at pos as an int16 (LittleEndian)
func parse_short(buf []byte, pos *int) int16 {
value := int16(binary.LittleEndian.Uint16(buf[*pos : *pos+2]))
*pos += 2
return value
}
// parses from pos until a null byte appears (or until end of slice)
func parse_string(buf []byte, pos *int) string {
start := *pos
for buf[*pos] != 0 && *pos < len(buf){
*pos++
}
value := string(buf[start:*pos])
*pos++
return value
}
func padIPPort(ipport string) string {
for i := 0; i < len(ipport); i++ {
if string(ipport[i]) == string(":") {
lpad := strings.Repeat(" ", 15-i)
rpad := strings.Repeat(" ", 21 - len(lpad) - len(ipport))
return lpad + ipport + rpad
}
}
return ""
}
func parseResponse(buf []byte) A2SInfo {
var resp A2SInfo
pos := 4
resp.header = parse_byte(buf, &pos)
resp.protocol = parse_byte(buf, &pos)
resp.name = parse_string(buf, &pos)
resp.mapname = parse_string(buf, &pos)
resp.folder = parse_string(buf, &pos)
resp.game = parse_string(buf, &pos)
resp.id = parse_short(buf, &pos)
resp.players = parse_byte(buf, &pos)
resp.max_players = parse_byte(buf, &pos)
resp.bots = parse_byte(buf, &pos)
resp.server_type = parse_byte(buf, &pos)
resp.environment = parse_byte(buf, &pos)
resp.visibility = parse_byte(buf, &pos)
resp.vac = parse_byte(buf, &pos)
resp.edf = parse_byte(buf, &pos)
resp.timestamp = time.Now()
return resp
}
func main() {
delay, _ := time.ParseDuration("500ms")
hosts := os.Args[1:]
data := make(map[string]A2SInfo)
for _, server := range hosts {
data[server] = A2SInfo{}
}
addr, err := net.ResolveUDPAddr("udp", ":0")
conn, err := net.ListenUDP("udp", addr)
if err != nil {
fmt.Println("Error while dialing")
os.Exit(1)
}
defer conn.Close()
for {
for server, _ := range data {
sendInfo(*conn, server)
}
conn.SetReadDeadline(time.Now().Add(delay))
for i := 0; i < len(data); i++ {
buf, addr, err := recvAnswer(*conn)
if err != nil {
if err.Timeout() {
break
}
}
addr4 := addr.IP.To4()
key := addr4.String() + ":" + strconv.Itoa(addr.Port)
info := parseResponse(buf)
data[key] = info
}
serverarray := make([]Server, 0, len(data))
for ip, info := range data {
serverarray = append(serverarray, Server{ip, info})
}
sort.Sort(ByIP(serverarray))
// reset cursor position to top left and clear the screen
fmt.Printf("\033[2J")
fmt.Printf("\033[0;0H")
for _, server := range serverarray {
addr := server.addrport
info := server.info
// arbitrary date that should be after the default value of a time.Date
if info.timestamp.Before(time.Date(2000, time.January, 0,0,0,0,0, time.UTC)) {
fmt.Printf("\033[1m%s never seen\033[0m\n", padIPPort(addr))
} else if time.Now().Sub(info.timestamp).Seconds() > 2 {
fmt.Printf("\033[1m%s %2d(%2d)/%2d %s\033[0m\n",
padIPPort(addr), info.players, info.bots,
info.max_players, info.mapname[:min(len(info.mapname),15)])
} else {
fmt.Printf("%s %2d(%2d)/%2d %s\n",
padIPPort(addr), info.players, info.bots, info.max_players,
info.mapname[:min(len(info.mapname),15)])
}
}
fmt.Println("---------------------------------------------------")
time.Sleep(time.Second)
}
}