237 lines
7.2 KiB
Go
237 lines
7.2 KiB
Go
|
// URTator - Urban Terror server browser and game launcher, written in
|
||
|
// Go.
|
||
|
//
|
||
|
// Copyright (c) 2016, Stanslav N. a.k.a pztrn (or p0z1tr0n)
|
||
|
// All rights reserved.
|
||
|
//
|
||
|
// Licensed under Terms and Conditions of GNU General Public License
|
||
|
// version 3 or any higher.
|
||
|
// ToDo: put full text of license here.
|
||
|
package requester
|
||
|
|
||
|
import (
|
||
|
// stdlib
|
||
|
"bytes"
|
||
|
"fmt"
|
||
|
"net"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
"sync"
|
||
|
"time"
|
||
|
|
||
|
// local
|
||
|
"github.com/pztrn/urtrator/datamodels"
|
||
|
)
|
||
|
|
||
|
type Requester struct {
|
||
|
// Master server address
|
||
|
master_server string
|
||
|
// Master server port
|
||
|
master_server_port string
|
||
|
// Packet prefix.
|
||
|
pp string
|
||
|
// Showstopper for delimiting IP addresses.
|
||
|
// As we are receiving bytes, we will put bytes representation of "\"
|
||
|
// character.
|
||
|
ip_delimiter int
|
||
|
}
|
||
|
|
||
|
// Requester's initialization.
|
||
|
func (r *Requester) Initialize() {
|
||
|
fmt.Println("Initializing Requester...")
|
||
|
r.master_server = "master.urbanterror.info"
|
||
|
r.master_server_port = "27900"
|
||
|
r.pp = "\377\377\377\377"
|
||
|
r.ip_delimiter = 92
|
||
|
}
|
||
|
|
||
|
// Gets all available servers from master server.
|
||
|
func (r *Requester) getServers(callback chan [][]string) {
|
||
|
// IP addresses we will compose to return.
|
||
|
var received_ips [][]string
|
||
|
conn, err1 := net.Dial("udp", r.master_server + ":" + r.master_server_port)
|
||
|
if err1 != nil {
|
||
|
fmt.Println("Error dialing to master server!")
|
||
|
}
|
||
|
defer conn.Close()
|
||
|
|
||
|
// Set deadline, so we won't wait forever.
|
||
|
ddl := time.Now()
|
||
|
// This should be enough. Maybe, you should'n run URTrator on modem
|
||
|
// connections? :)
|
||
|
ddl = ddl.Add(time.Second * 2)
|
||
|
conn.SetDeadline(ddl)
|
||
|
fmt.Println("Master server connection deadline set: " + ddl.String())
|
||
|
|
||
|
// Request.
|
||
|
// 68 - protocol version for 4.3
|
||
|
// 70 - protocol version for 4.2.x
|
||
|
msg := []byte(r.pp + "getservers 68 full empty")
|
||
|
conn.Write(msg)
|
||
|
|
||
|
// UDP Buffer.
|
||
|
var received_buf []byte = make([]byte, 4096)
|
||
|
// Received buffer.
|
||
|
var raw_received []byte
|
||
|
// Received IP addresses.
|
||
|
//var received []string
|
||
|
fmt.Println("Receiving servers list...")
|
||
|
for {
|
||
|
_, err := conn.Read(received_buf)
|
||
|
if err != nil {
|
||
|
break
|
||
|
}
|
||
|
raw_received = append(raw_received, received_buf...)
|
||
|
fmt.Println("Received " + strconv.Itoa(len(raw_received)) + " bytes")
|
||
|
}
|
||
|
|
||
|
// Obtaining list of slices.
|
||
|
var raw_received_slices [][]byte = bytes.Split(raw_received, []byte("\\"))
|
||
|
|
||
|
// Every ip-port pair contains:
|
||
|
// 1. IP as first 4 bytes
|
||
|
// 2. Port as last 2 bytes.
|
||
|
// Every package is a 7-bytes sequence, which starts with "\"
|
||
|
// (code 92), which we used before to obtain list of slices.
|
||
|
for _, slice := range raw_received_slices {
|
||
|
// We need only 6-bytes slices. All other aren't represent
|
||
|
// server's address.
|
||
|
if len(slice) != 6 {
|
||
|
continue
|
||
|
}
|
||
|
// Generate IP.
|
||
|
ip := strconv.Itoa(int(slice[0])) + "." + strconv.Itoa(int(slice[1])) + "." + strconv.Itoa(int(slice[2])) + "." + strconv.Itoa(int(slice[3]))
|
||
|
// Generate port from last two bytes.
|
||
|
// This is a very shitty thing. Don't do this in real world.
|
||
|
// Maybe bitshifting will help here, but I can't get it to work :(
|
||
|
// Get first byte as integer and multiply it on 256 and summing with
|
||
|
// second byte.
|
||
|
p1 := int(slice[4]) * 256
|
||
|
port := strconv.Itoa(p1 + int(slice[5]))
|
||
|
|
||
|
// Create a slice with IP and port.
|
||
|
ip_and_port := []string{ip, port}
|
||
|
// Add it to received_ips.
|
||
|
received_ips = append(received_ips, ip_and_port)
|
||
|
}
|
||
|
|
||
|
fmt.Println("Parsed " + strconv.Itoa(len(received_ips)) + " addresses")
|
||
|
callback <- received_ips
|
||
|
}
|
||
|
|
||
|
// Updates information about all available servers from master server and
|
||
|
// parses it to usable format.
|
||
|
func (r *Requester) UpdateAllServers(done_chan chan map[string]*datamodels.Server, error_chan chan bool) {
|
||
|
fmt.Println("Starting all servers updating procedure...")
|
||
|
|
||
|
callback := make(chan [][]string)
|
||
|
go r.getServers(callback)
|
||
|
|
||
|
servers := make(map[string]*datamodels.Server)
|
||
|
|
||
|
select {
|
||
|
case data := <- callback:
|
||
|
// Yay, we got data! :)
|
||
|
fmt.Println("Received " + strconv.Itoa(len(data)) + " servers")
|
||
|
servers = r.updateServerGoroutineDispatcher(data)
|
||
|
break
|
||
|
case <- time.After(time.Second * 10):
|
||
|
// Timeouted? Okay, push error back.
|
||
|
error_chan <- true
|
||
|
}
|
||
|
|
||
|
done_chan <- servers
|
||
|
}
|
||
|
|
||
|
func (r *Requester) updateServerGoroutineDispatcher(data [][]string) map[string]*datamodels.Server {
|
||
|
var wait sync.WaitGroup
|
||
|
var lock = sync.RWMutex{}
|
||
|
done_updating := 0
|
||
|
servers := make(map[string]*datamodels.Server)
|
||
|
|
||
|
for _, s := range data {
|
||
|
s := datamodels.Server{
|
||
|
Ip: s[0],
|
||
|
Port: s[1],
|
||
|
}
|
||
|
go func(s *datamodels.Server, servers map[string]*datamodels.Server) {
|
||
|
wait.Add(1)
|
||
|
defer wait.Done()
|
||
|
r.UpdateServer(s)
|
||
|
done_updating = done_updating + 1
|
||
|
lock.Lock()
|
||
|
servers[s.Ip + ":" + s.Port] = s
|
||
|
lock.Unlock()
|
||
|
}(&s, servers)
|
||
|
}
|
||
|
wait.Wait()
|
||
|
return servers
|
||
|
}
|
||
|
|
||
|
// Updates information about specific server.
|
||
|
func (r *Requester) UpdateServer(server *datamodels.Server) {
|
||
|
srv := server.Ip + ":" + server.Port
|
||
|
fmt.Println("Updating server: " + srv)
|
||
|
|
||
|
// Dial to server.
|
||
|
conn, err1 := net.Dial("udp", srv)
|
||
|
if err1 != nil {
|
||
|
fmt.Println("Error dialing to server " + srv + "!")
|
||
|
}
|
||
|
defer conn.Close()
|
||
|
|
||
|
// Set deadline, so we won't wait forever.
|
||
|
ddl := time.Now()
|
||
|
// This should be enough. Maybe, you should'n run URTrator on modem
|
||
|
// connections? :)
|
||
|
ddl = ddl.Add(time.Second * 2)
|
||
|
conn.SetDeadline(ddl)
|
||
|
|
||
|
msg := []byte(r.pp + "getinfo")
|
||
|
conn.Write(msg)
|
||
|
|
||
|
// UDP Buffer.
|
||
|
var received_buf []byte = make([]byte, 4096)
|
||
|
// Received buffer.
|
||
|
var raw_received []byte
|
||
|
for {
|
||
|
_, err := conn.Read(received_buf)
|
||
|
if err != nil {
|
||
|
break
|
||
|
}
|
||
|
raw_received = append(raw_received, received_buf...)
|
||
|
}
|
||
|
|
||
|
// First line is "infoResponse" string, which we should skip by
|
||
|
// splitting response by "\n".
|
||
|
received_lines := strings.Split(string(raw_received), "\n")
|
||
|
// We have server's data!
|
||
|
if len(received_lines) > 1 {
|
||
|
srv_config := strings.Split(received_lines[1], "\\")
|
||
|
// Parse server configuration into passed server's datamodel.
|
||
|
for i := 0; i < len(srv_config); i = i + 1 {
|
||
|
if srv_config[i] == "modversion" {
|
||
|
server.Version = srv_config[i + 1]
|
||
|
}
|
||
|
if srv_config[i] == "gametype" {
|
||
|
server.Gamemode = srv_config[i + 1]
|
||
|
}
|
||
|
if srv_config[i] == "sv_maxclients" {
|
||
|
server.Maxplayers = srv_config[i + 1]
|
||
|
}
|
||
|
if srv_config[i] == "clients" {
|
||
|
server.Players = srv_config[i + 1]
|
||
|
}
|
||
|
if srv_config[i] == "mapname" {
|
||
|
server.Map = srv_config[i + 1]
|
||
|
}
|
||
|
if srv_config[i] == "hostname" {
|
||
|
server.Name = srv_config[i + 1]
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// ToDo: Calculate ping. 0 for now.
|
||
|
server.Ping = "0"
|
||
|
}
|