-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathbyond_part.go
447 lines (405 loc) · 10.1 KB
/
byond_part.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
package main
import (
// "bufio"
"bytes"
"encoding/binary"
"fmt"
"github.com/bwmarrin/discordgo"
"golang.org/x/text/encoding/charmap"
"log"
"net"
"net/url"
"regexp"
"strconv"
"strings"
"sync"
"time"
)
const (
ByondTypeNULL byte = 0x00
ByondTypeFLOAT byte = 0x2a
ByondTypeSTRING byte = 0x06
//in seconds
byond_request_timeout int = 60
byond_response_timeout int = 60
byond_fastrequest_timeout int = 1
byond_fastresponse_timeout int = 1
byond_dial_timeout int = 1
)
type Byond_response struct {
size uint16
btype byte
data []byte
}
const (
Byond_NOCONV = iota
Byond_DEFCONV
)
func DecodeWindows1251(s string) string {
dec := charmap.Windows1251.NewDecoder()
out, err := dec.String(s)
maybeerror(err)
return out
}
func EncodeWindows1251(s string) string {
enc := charmap.Windows1251.NewEncoder()
out, err := enc.String(s)
maybeerror(err)
return out
}
func Read_float32(data []byte) (ret float32) {
buf := bytes.NewBuffer(data)
binary.Read(buf, binary.LittleEndian, &ret)
return
}
func construct_byond_request(s string) string {
var B uint16 = uint16(len(s) + 6)
var bytes []byte
bytes = append(bytes, 0x00, 0x83, byte(B>>8), byte((B<<8)>>8), 0x00, 0x00, 0x00, 0x00, 0x00)
bytes = append(bytes, []byte(s)...)
bytes = append(bytes, 0x00)
ret := string(bytes)
return ret
}
func Byond_query(srvname, request string, authed bool) Byond_response {
return Byond_query_adv(srvname, request, authed, byond_request_timeout, byond_response_timeout)
}
func Byond_query_fast(srvname, request string, authed bool) Byond_response {
return Byond_query_adv(srvname, request, authed, byond_fastrequest_timeout, byond_fastresponse_timeout)
}
func Byond_query_adv(srvname, request string, authed bool, req_to, res_to int) Byond_response {
defer logging_recover(srvname + "_bq")
srv, ok := known_servers[srvname]
if !ok {
panic("failed to find server '" + srvname + "'")
}
conn, err := net.DialTimeout("tcp", srv.addr, time.Duration(byond_dial_timeout)*time.Second)
if err != nil {
panic(err)
}
defer conn.Close()
if authed {
request += "&key=" + srv.comm_key
}
//sending
conn.SetWriteDeadline(time.Now().Add(time.Duration(req_to) * time.Second))
fmt.Fprint(conn, construct_byond_request(request))
//receiving
conn.SetReadDeadline(time.Now().Add(time.Duration(res_to) * time.Second))
bytes := make([]byte, 5)
num, err := conn.Read(bytes)
if err != nil {
panic(err)
}
L := uint16(bytes[2])<<8 + uint16(bytes[3])
ret := Byond_response{L - 1, bytes[4], make([]byte, L-1)}
num, err = conn.Read(ret.data)
if err != nil {
panic(err)
}
if num != int(ret.size) {
panic("SHIET")
}
return ret
}
func (Br *Byond_response) String() string {
var ret string
switch Br.btype {
case ByondTypeNULL:
ret = "NULL"
case ByondTypeFLOAT:
ret = fmt.Sprintf("%.f", Read_float32(Br.data))
case ByondTypeSTRING:
ret = string(Br.data)
ret = ret[:len(ret)-1]
}
return ret
}
func (Br *Byond_response) Float() float32 {
var ret float32
if Br.btype == ByondTypeFLOAT {
ret = Read_float32(Br.data)
}
return ret
}
func Bquery_convert(msg, server string) string {
if srv, ok := known_servers[server]; ok && srv.mode == Byond_DEFCONV {
msg = EncodeWindows1251(msg)
}
return url.QueryEscape(msg)
}
func Bquery_deconvert(msg, server string) string {
ret, err := url.QueryUnescape(msg)
if err != nil {
log.Println("ERROR: Query unescape error: ", err)
return ret
}
if srv, ok := known_servers[server]; ok && srv.mode == Byond_DEFCONV {
ret = DecodeWindows1251(ret)
}
return ret
}
const SS_AUTOUPDATES_INTERVAL = 60
type server_status struct {
server_name string
server_address string
status_table map[string]string
associated_embeds map[string]string //channelid -> messageid, no more than one per channel (because no reason for more than one)
embed discordgo.MessageEmbed
timerchan chan int
/* server_name string
version string
mode string
enter string
host string
players string
admins string
gamestate string
map_name string
security_level string
round_duration string
shuttle_mode string
shuttle_timer string*/
}
const (
GAME_STATE_STARTUP = "0"
GAME_STATE_PREGAME = "1"
GAME_STATE_SETTING_UP = "2"
GAME_STATE_PLAYING = "3"
GAME_STATE_FINISHED = "4"
SHUTTLE_IDLE = "0"
SHUTTLE_RECALL = "1"
SHUTTLE_CALL = "2"
SHUTTLE_DOCKED = "3"
SHUTTLE_STRANDED = "4"
SHUTTLE_ESCAPE = "5"
SHUTTLE_ENDGAME = "6"
)
type embed_ft struct {
name string
value_entry string
inline bool
}
var embed_teplate = [...]embed_ft{
embed_ft{"Server", "server_name", false},
embed_ft{"Version", "version", true},
embed_ft{"Map", "map_name", true},
embed_ft{"Address", "server_address", false},
embed_ft{"Players", "players", true},
embed_ft{"Admins", "admins", true},
embed_ft{"Security level", "security_level", false},
embed_ft{"Shuttle mode", "shuttle_mode", true},
embed_ft{"Shuttle timer", "shuttle_timer", true},
embed_ft{"Gamemode", "mode", false},
embed_ft{"Game state", "gamestate", true},
embed_ft{"Round duration", "round_duration", true},
}
//syncs with hub
var global_update_mutex sync.Mutex
func (ss *server_status) global_update() {
global_update_mutex.Lock()
defer global_update_mutex.Unlock()
if ss.status_table == nil {
ss.status_table = make(map[string]string)
}
resp := Byond_query_fast(ss.server_name, "status", true)
stat := resp.String()
if stat == "NULL" {
//probably timeout
return
}
stat = Bquery_deconvert(stat, ss.server_name)
stat_split := strings.Split(stat, "&")
for i := 0; i < len(stat_split); i++ {
tmp := strings.Split(stat_split[i], "=")
if len(tmp) > 1 {
ss.status_table[tmp[0]] = tmp[1]
}
}
ss.update_embeds()
}
func (ss *server_status) update_embeds() {
ss.update_embed()
for ch, msg := range ss.associated_embeds {
if !Discord_replace_embed(ch, msg, &(ss.embed)) {
unbind_server_embed(ss.server_name, ch)
log_line_runtime("unbound embed from server" + ss.server_name + " channel " + ch)
}
}
}
func (ss *server_status) entry(key string) string {
if key == "server_name" {
return ss.server_name
}
if key == "server_address" {
return "byond://" + ss.server_address
}
val, ok := ss.status_table[key]
if !ok {
return "unknown"
}
if key == "gamestate" {
switch val {
case GAME_STATE_FINISHED:
val = "FINISHED"
case GAME_STATE_PLAYING:
val = "PLAYING"
case GAME_STATE_PREGAME:
val = "PREGAME"
case GAME_STATE_STARTUP:
val = "STARTUP"
case GAME_STATE_SETTING_UP:
val = "SETTING UP"
default:
val = "ERR"
}
}
if key == "shuttle_mode" && len(val) == 1 {
switch val {
case SHUTTLE_CALL:
val = "CALLED"
case SHUTTLE_DOCKED:
val = "DOCKED"
case SHUTTLE_ENDGAME:
val = "DOCKED AT CENTCOMM"
case SHUTTLE_ESCAPE:
val = "ESCAPING"
case SHUTTLE_IDLE:
val = "IDLE"
case SHUTTLE_RECALL:
val = "RECALLED"
case SHUTTLE_STRANDED:
val = "STRANDED"
default:
val = "ERR"
}
}
if key == "round_duration" {
num, err := strconv.Atoi(val)
if err == nil {
val = fmt.Sprintf("%v hours %v mins %v secs", num/3600, (num%3600)/60, num%60)
}
}
return val
}
func (ss *server_status) update_embed() {
if ss.embed.Fields == nil {
ss.rebuild_embed()
return
}
for i := 0; i < len(embed_teplate); i++ {
ss.embed.Fields[i].Value = ss.entry(embed_teplate[i].value_entry)
}
}
func (ss *server_status) rebuild_embed() {
ss.embed.Color = known_servers[ss.server_name].color
ss.embed.Fields = make([]*discordgo.MessageEmbedField, len(embed_teplate))
for i := 0; i < len(embed_teplate); i++ {
ss.embed.Fields[i] = &discordgo.MessageEmbedField{
Name: embed_teplate[i].name,
Value: ss.entry(embed_teplate[i].value_entry),
Inline: embed_teplate[i].inline,
}
}
}
func (ss *server_status) start_ticker() {
if ss.timerchan != nil {
ss.stop_ticker()
}
go func() {
ticker := time.NewTicker(SS_AUTOUPDATES_INTERVAL * time.Second)
ss.timerchan = make(chan int, 0)
for {
select {
case <-ticker.C:
ss.global_update()
case <-ss.timerchan:
ticker.Stop()
ss.timerchan = nil
}
}
}()
}
func (ss *server_status) stop_ticker() {
if ss.timerchan == nil {
return
}
ss.timerchan <- 1
}
var server_statuses map[string]*server_status
func populate_server_embeds() {
for k := range server_statuses {
delete(server_statuses, k)
}
defer logging_recover("pse")
server_statuses = make(map[string]*server_status)
var srv, chn, msg, addr string
closure_callback := func() {
s, ok := known_servers[srv]
if ok {
addr = s.addr
}
ss, ok := server_statuses[srv]
if !ok {
ss = &server_status{server_name: srv, server_address: addr}
server_statuses[srv] = ss
}
if ss.associated_embeds == nil {
ss.associated_embeds = make(map[string]string)
}
ss.associated_embeds[chn] = msg
}
db_template("select_dynembeds").query().parse(closure_callback, &srv, &chn, &msg)
for _, ss := range server_statuses {
ss.global_update()
}
}
func launch_ss_tickers() {
for _, s := range server_statuses {
s.start_ticker()
}
}
func stop_ss_tickers() {
for _, s := range server_statuses {
s.stop_ticker()
}
}
func bind_server_embed(srv, chn, msg string) bool {
defer logging_recover("bse")
if db_template("update_dynembed").exec(srv, chn, msg).count() < 1 {
db_template("create_dynembed").exec(srv, chn, msg)
}
ss, ok := server_statuses[srv]
if !ok {
var addr string
s, ok := known_servers[srv]
if ok {
addr = s.addr
}
ss = &server_status{server_name: srv, server_address: addr}
server_statuses[srv] = ss
}
if ss.associated_embeds == nil {
ss.associated_embeds = make(map[string]string)
}
ss.associated_embeds[chn] = msg
ss.global_update()
ss.start_ticker()
return true
}
func unbind_server_embed(srv, chn string) bool {
defer logging_recover("use")
ss, ok := server_statuses[srv]
if !ok {
return false
}
delete(ss.associated_embeds, chn)
return db_template("remove_dynembed").exec(srv, chn).count() > 0
}
var json_unfucker *regexp.Regexp
func unfuck_byond_json(s string) string {
if json_unfucker == nil {
json_unfucker = regexp.MustCompile("\\\\u044f[\\x12\\x16]")
}
return json_unfucker.ReplaceAllString(s, "")
}