diff --git a/game.go b/game.go index 454af6c..3c37998 100644 --- a/game.go +++ b/game.go @@ -65,138 +65,141 @@ type GameServer struct { players []Player } -func (g *GameServer) Ping() time.Duration { +func (g *GameServer) Ping() (ping time.Duration) { g.mutex.RLock() defer g.mutex.RUnlock() return g.ping } -func (g *GameServer) QueryTime() time.Time { +func (g *GameServer) QueryTime() (queryTime time.Time) { g.mutex.RLock() defer g.mutex.RUnlock() return g.queryTime } -func (g *GameServer) Name() string { +func (g *GameServer) Name() (name string) { g.mutex.RLock() defer g.mutex.RUnlock() return g.name } -func (g *GameServer) Game() string { +func (g *GameServer) Game() (game string) { g.mutex.RLock() defer g.mutex.RUnlock() return g.game } -func (g *GameServer) Version() string { +func (g *GameServer) Version() (version string) { g.mutex.RLock() defer g.mutex.RUnlock() return g.version } -func (g *GameServer) Dedicated() bool { +func (g *GameServer) Dedicated() (dedicated bool) { g.mutex.RLock() defer g.mutex.RUnlock() return g.dedicated } -func (g *GameServer) Password() bool { +func (g *GameServer) Password() (password bool) { g.mutex.RLock() defer g.mutex.RUnlock() return g.password } -func (g *GameServer) NumPlayers() uint8 { +func (g *GameServer) NumPlayers() (numPlayers uint8) { g.mutex.RLock() defer g.mutex.RUnlock() return g.numPlayers } -func (g *GameServer) MaxPlayers() uint8 { +func (g *GameServer) MaxPlayers() (maxPlayers uint8) { g.mutex.RLock() defer g.mutex.RUnlock() return g.maxPlayers } -func (g *GameServer) CPUSpeed() uint16 { +func (g *GameServer) CPUSpeed() (cpuSpeed uint16) { g.mutex.RLock() defer g.mutex.RUnlock() return g.cpuSpeed } -func (g *GameServer) Mod() string { +func (g *GameServer) Mod() (mod string) { g.mutex.RLock() defer g.mutex.RUnlock() return g.mod } -func (g *GameServer) ServerType() string { +func (g *GameServer) ServerType() (serverType string) { g.mutex.RLock() defer g.mutex.RUnlock() return g.serverType } -func (g *GameServer) Mission() string { +func (g *GameServer) Mission() (mission string) { g.mutex.RLock() defer g.mutex.RUnlock() return g.mission } -func (g *GameServer) Info() string { +func (g *GameServer) Info() (info string) { g.mutex.RLock() defer g.mutex.RUnlock() return g.info } -func (g *GameServer) NumTeams() uint8 { +func (g *GameServer) NumTeams() (numTeams uint8) { g.mutex.RLock() defer g.mutex.RUnlock() return g.numTeams } -func (g *GameServer) TeamScoreHeader() string { +func (g *GameServer) TeamScoreHeader() (teamScoreHeader string) { g.mutex.RLock() defer g.mutex.RUnlock() return g.teamScoreHeader } -func (g *GameServer) PlayerScoreHeader() string { +func (g *GameServer) PlayerScoreHeader() (playerScoreHeader string) { g.mutex.RLock() defer g.mutex.RUnlock() return g.playerScoreHeader } -func (g *GameServer) Teams() []Team { +func (g *GameServer) Teams() (teams []Team) { g.mutex.RLock() defer g.mutex.RUnlock() - return g.teams + teams = make([]Team, len(g.teams)) + copy(teams, g.teams) + return } -func (g *GameServer) Players() []Player { +func (g *GameServer) Players() (players []Player) { g.mutex.RLock() defer g.mutex.RUnlock() - return g.players + players = make([]Player, len(g.players)) + copy(players, g.players) + return } -func (g *GameServer) Query(timeout time.Duration, localAddress string) error { +func (g *GameServer) Query(timeout time.Duration, localAddress string) (err error) { if timeout == 0 { timeout = 5 * time.Second } var localAddr *net.UDPAddr - var err error if len(localAddress) != 0 { localAddr, err = net.ResolveUDPAddr("udp4", localAddress) if err != nil { - return err + return } } remoteAddr, err := net.ResolveUDPAddr("udp4", g.address) if err != nil { - return err + return } g.mutex.Lock() @@ -212,17 +215,13 @@ func (g *GameServer) Query(timeout time.Duration, localAddress string) error { c, err := net.DialUDP("udp4", localAddr, remoteAddr) if err != nil { - return err + return } - defer func(c *net.UDPConn) { - err := c.Close() - if err != nil { - panic(err) - } - }(c) + defer c.Close() key := uint16(rand.Uint32()) + // 0x62 = GameSpy query request, next two bytes are key sendBuffer := []byte{0x62, 0x00, 0x00} binary.BigEndian.PutUint16(sendBuffer[1:], key) @@ -230,17 +229,17 @@ func (g *GameServer) Query(timeout time.Duration, localAddress string) error { g.queryTime = time.Now() _, err = c.Write(sendBuffer) if err != nil { - return err + return } readBuffer := make([]byte, 2048) err = c.SetDeadline(time.Now().Add(timeout)) if err != nil { - return err + return } n, addr, err := c.ReadFromUDP(readBuffer) if err != nil { - return err + return } g.ping = time.Since(g.queryTime) @@ -256,8 +255,9 @@ func (g *GameServer) Query(timeout time.Duration, localAddress string) error { reader := bytes.NewReader(readBuffer[0:n]) b, err := reader.ReadByte() if err != nil { - return err + return } + // 0x63 = GameSpy query response if b != 0x63 { return fmt.Errorf("t1net.GameServer.Query: Reply byte 0: %#v != 0x63", b) } @@ -265,7 +265,7 @@ func (g *GameServer) Query(timeout time.Duration, localAddress string) error { var readKey uint16 err = binary.Read(reader, binary.BigEndian, &readKey) if err != nil { - return err + return } if key != readKey { return fmt.Errorf("t1net.GameServer.Query: Key mismatch: %d : %d", readKey, key) @@ -273,130 +273,134 @@ func (g *GameServer) Query(timeout time.Duration, localAddress string) error { b, err = reader.ReadByte() if err != nil { - return err + return } + // 0x62 = The request we sent, in this case the GameSpy Query request if b != 0x62 { return fmt.Errorf("t1net.GameServer.Query: Reply byte 3: %#v != 0x62", b) } g.game, err = ReadPascalString(reader) if err != nil { - return err + return } g.version, err = ReadPascalString(reader) if err != nil { - return err + return } g.name, err = ReadPascalString(reader) if err != nil { - return err + return } b, err = reader.ReadByte() if err != nil { - return err + return } g.dedicated = b == 1 b, err = reader.ReadByte() if err != nil { - return err + return } g.password = b == 1 b, err = reader.ReadByte() if err != nil { - return err + return } g.numPlayers = b b, err = reader.ReadByte() if err != nil { - return err + return } g.maxPlayers = b err = binary.Read(reader, binary.LittleEndian, &g.cpuSpeed) if err != nil { - return err + return } g.mod, err = ReadPascalString(reader) if err != nil { - return err + return } g.serverType, err = ReadPascalString(reader) if err != nil { - return err + return } g.mission, err = ReadPascalString(reader) if err != nil { - return err + return } g.info, err = ReadPascalString(reader) if err != nil { - return err + return } b, err = reader.ReadByte() if err != nil { - return err + return } g.numTeams = b g.teamScoreHeader, err = ReadPascalString(reader) if err != nil { - return err + return } g.playerScoreHeader, err = ReadPascalString(reader) if err != nil { - return err + return } - + + var teamName, teamScore string for i := uint8(0); i < g.numTeams; i++ { - teamName, err := ReadPascalString(reader) + teamName, err = ReadPascalString(reader) if err != nil { - return err + return } - teamScore, err := ReadPascalString(reader) + teamScore, err = ReadPascalString(reader) if err != nil { - return err + return } g.teams = append(g.teams, Team{Name: teamName, Score: teamScore}) } + var ping, pl, team byte + var playerName, playerScore string for i := uint8(0); i < g.numPlayers; i++ { - ping, err := reader.ReadByte() + ping, err = reader.ReadByte() if err != nil { - return err + return } - pl, err := reader.ReadByte() + pl, err = reader.ReadByte() if err != nil { - return err + return } - team, err := reader.ReadByte() + team, err = reader.ReadByte() if err != nil { - return err + return } - playerName, err := ReadPascalString(reader) + playerName, err = ReadPascalString(reader) if err != nil { - return err + return } - playerScore, err := ReadPascalString(reader) + playerScore, err = ReadPascalString(reader) if err != nil { - return err + return } g.players = append(g.players, Player{Ping: ping, PL: pl, Team: team, Name: playerName, Score: playerScore}) @@ -406,7 +410,7 @@ func (g *GameServer) Query(timeout time.Duration, localAddress string) error { return fmt.Errorf("t1net.GameServer.Query: %d left over bytes", reader.Len()) } - return nil + return } func NewGameServer(address string) *GameServer { diff --git a/master.go b/master.go index 16c5f3a..d31b9a7 100644 --- a/master.go +++ b/master.go @@ -40,60 +40,61 @@ type MasterServer struct { totalPackets int } -func (m *MasterServer) Ping() time.Duration { +func (m *MasterServer) Ping() (ping time.Duration) { m.mutex.RLock() defer m.mutex.RUnlock() return m.ping } -func (m *MasterServer) QueryTime() time.Time { +func (m *MasterServer) QueryTime() (queryTime time.Time) { m.mutex.RLock() defer m.mutex.RUnlock() return m.queryTime } -func (m *MasterServer) Name() string { +func (m *MasterServer) Name() (name string) { m.mutex.RLock() defer m.mutex.RUnlock() return m.name } -func (m *MasterServer) MOTD() string { +func (m *MasterServer) MOTD() (motd string) { m.mutex.RLock() defer m.mutex.RUnlock() return m.motd } -func (m *MasterServer) ServerCount() uint16 { +func (m *MasterServer) ServerCount() (serverCount uint16) { m.mutex.RLock() defer m.mutex.RUnlock() return m.serverCount } -func (m *MasterServer) Servers() []string { +func (m *MasterServer) Servers() (servers []string) { m.mutex.RLock() defer m.mutex.RUnlock() - return m.servers + servers = make([]string, len(m.servers)) + copy(servers, m.servers) + return } -func (m *MasterServer) Query(timeout time.Duration, localAddress string) error { +func (m *MasterServer) Query(timeout time.Duration, localAddress string) (err error) { if timeout == 0 { timeout = 5 * time.Second } var localAddr *net.UDPAddr - var err error if len(localAddress) != 0 { localAddr, err = net.ResolveUDPAddr("udp4", localAddress) if err != nil { - return err + return } } remoteAddr, err := net.ResolveUDPAddr("udp4", m.address) if err != nil { - return err + return } m.mutex.Lock() @@ -106,18 +107,22 @@ func (m *MasterServer) Query(timeout time.Duration, localAddress string) error { c, err := net.DialUDP("udp4", localAddr, remoteAddr) if err != nil { - return err + return } - defer func(c *net.UDPConn) { - err := c.Close() - if err != nil { - panic(err) - } - }(c) + defer c.Close() key := uint16(rand.Uint32()) - sendBuffer := []byte{0x10, 0x03, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00} + sendBuffer := []byte{ + 0x10, // Version + 0x03, // Type - Master Server request + 0xFF, // Packet Number + 0x00, // Packet Total + 0x00, // Key 1 + 0x00, // Key 2 + 0x00, // ID 1 + 0x00, // ID 2 + } binary.BigEndian.PutUint16(sendBuffer[4:6], key) @@ -125,19 +130,26 @@ func (m *MasterServer) Query(timeout time.Duration, localAddress string) error { pingCalculated := false _, err = c.Write(sendBuffer) if err != nil { - return err + return } recvBuf := make([]byte, 1024) m.totalPackets = 1 + var ( + n int + addr *net.UDPAddr + b, packetNumber, packetTotal byte + ip net.IP + port uint16 + ) for p := 0; p < m.totalPackets; p++ { - err := c.SetDeadline(time.Now().Add(timeout)) + err = c.SetDeadline(time.Now().Add(timeout)) if err != nil { - return err + return } - n, addr, err := c.ReadFromUDP(recvBuf) + n, addr, err = c.ReadFromUDP(recvBuf) if err != nil { - return err + return } if !addr.IP.Equal(remoteAddr.IP) || addr.Port != remoteAddr.Port { @@ -151,9 +163,9 @@ func (m *MasterServer) Query(timeout time.Duration, localAddress string) error { reader := bytes.NewReader(recvBuf[0:n]) - b, err := reader.ReadByte() + b, err = reader.ReadByte() if err != nil { - return err + return } if b != 0x10 { return fmt.Errorf("t1net.MasterServer.Query: Reply byte 0: %#v != 0x10", b) @@ -161,48 +173,48 @@ func (m *MasterServer) Query(timeout time.Duration, localAddress string) error { b, err = reader.ReadByte() if err != nil { - return err + return } if b != 0x06 { return fmt.Errorf("t1net.MasterServer.Query: Reply byte 1: %#v != 0x06", b) } // Packet Number - packetNumber, err := reader.ReadByte() + packetNumber, err = reader.ReadByte() if err != nil { - return err + return } if packetNumber < 1 || packetNumber > 5 { return fmt.Errorf("t1net.MasterServer.Query: Invalid packet number: %d", packetNumber) } // Total number of Packets - totalPackets, err := reader.ReadByte() + packetTotal, err = reader.ReadByte() if err != nil { return err } - if totalPackets < 1 || totalPackets > 5 { - return fmt.Errorf("t1net.MasterServer.Query: Invalid total packet number: %d", totalPackets) + if packetTotal < 1 || packetTotal > 5 { + return fmt.Errorf("t1net.MasterServer.Query: Invalid total packet number: %d", packetTotal) } - if packetNumber > totalPackets { - return fmt.Errorf("t1net.MasterServer.Query: Packet Number is greater than total: %d / %d", packetNumber, totalPackets) + if packetNumber > packetTotal { + return fmt.Errorf("t1net.MasterServer.Query: Packet Number is greater than total: %d / %d", packetNumber, packetTotal) } var recvKey uint16 err = binary.Read(reader, binary.BigEndian, &recvKey) if err != nil { - return err + return } if key != recvKey { return fmt.Errorf("t1net.MasterServer.Query: Key mismatch: %d : %d", recvKey, key) } - m.totalPackets = int(totalPackets) + m.totalPackets = int(packetTotal) b, err = reader.ReadByte() if err != nil { - return err + return } if b != 0 { return fmt.Errorf("t1net.MasterServer.Query: Reply byte 6: %#v != 0x00", b) @@ -210,7 +222,7 @@ func (m *MasterServer) Query(timeout time.Duration, localAddress string) error { b, err = reader.ReadByte() if err != nil { - return err + return } if b != 0x66 { return fmt.Errorf("t1net.MasterServer.Query: Reply byte 7: %#v != 0x66", b) @@ -218,26 +230,26 @@ func (m *MasterServer) Query(timeout time.Duration, localAddress string) error { m.name, err = ReadPascalString(reader) if err != nil { - return err + return } m.motd, err = ReadPascalString(reader) if err != nil { - return err + return } var serverCount uint16 err = binary.Read(reader, binary.BigEndian, &serverCount) if err != nil { - return err + return } m.serverCount += serverCount for i := uint16(0); i < serverCount; i++ { - ip, port, err := ReadAddressPort(reader) + ip, port, err = ReadAddressPort(reader) if err != nil { - return err + return } m.servers = append(m.servers, fmt.Sprintf("%s:%d", ip.String(), port)) @@ -248,7 +260,7 @@ func (m *MasterServer) Query(timeout time.Duration, localAddress string) error { } } - return nil + return } func NewMasterServer(address string) *MasterServer { diff --git a/utils.go b/utils.go index 2bf3519..7c11e43 100644 --- a/utils.go +++ b/utils.go @@ -26,10 +26,10 @@ import ( "strings" ) -func ReadPascalString(reader *bytes.Reader) (string, error) { +func ReadPascalString(reader *bytes.Reader) (str string, err error) { b, err := reader.ReadByte() if err != nil { - return "", err + return } if b > 0 { @@ -37,68 +37,68 @@ func ReadPascalString(reader *bytes.Reader) (string, error) { builder.Grow(int(b)) _, err = io.CopyN(builder, reader, int64(b)) if err != nil { - return "", err + return } - - return builder.String(), nil + str = builder.String() + return } - return "", nil + return } -func WritePascalString(buffer *bytes.Buffer, str string) error { +func WritePascalString(buffer *bytes.Buffer, str string) (err error) { strlen := len(str) if strlen > 255 { return fmt.Errorf("t1net.WritePascalString: String length is too long. %d > 255", strlen) } - if err := buffer.WriteByte(byte(strlen)); err != nil { - return err + if err = buffer.WriteByte(byte(strlen)); err != nil { + return } n, err := buffer.WriteString(str) if err != nil { - return err + return } if n != strlen { return fmt.Errorf("t1net.WritePascalString: String written length does not match. %d != %d", n, strlen) } - return nil + return } -func ReadAddressPort(reader *bytes.Reader) (net.IP, uint16, error) { - ip := make(net.IP, 4) - var port uint16 +func ReadAddressPort(reader *bytes.Reader) (ip net.IP, port uint16, err error) { + ip = make(net.IP, 4) b, err := reader.ReadByte() if err != nil { - return ip, port, err + return } if b != 6 { - return ip, port, errors.New("t1net.ReadServerAddress: Invalid length for server/port") + err = errors.New("t1net.ReadServerAddress: Invalid length for server/port") + return } err = binary.Read(reader, binary.BigEndian, &ip) if err != nil { - return ip, port, err + return } err = binary.Read(reader, binary.LittleEndian, &port) if err != nil { - return ip, port, err + return } - return ip, port, nil + return } -func WriteAddressPort(buffer *bytes.Buffer, ip net.IP, port uint16) error { +func WriteAddressPort(buffer *bytes.Buffer, ip net.IP, port uint16) (err error) { buffer.WriteByte(6) if len(ip) != net.IPv4len { return errors.New("t1net.WriteAddressPort: IP length is not equal to 4 bytes") } - err := binary.Write(buffer, binary.BigEndian, ip) + err = binary.Write(buffer, binary.BigEndian, ip) if err != nil { - return err + return } err = binary.Write(buffer, binary.LittleEndian, port) if err != nil { - return err + return } - return nil + return }