diff --git a/a2sPlayerRequest.go b/a2sPlayerRequest.go new file mode 100644 index 0000000..7799244 --- /dev/null +++ b/a2sPlayerRequest.go @@ -0,0 +1,22 @@ +package steam + +import ( + "bytes" + + "github.com/golang/glog" +) + +type A2SPlayerRequest struct { + c ChallengeResponse +} + +func (a A2SPlayerRequest) MarshalBinary() []byte { + buf := new(bytes.Buffer) + + writeRequestPrefix(buf) + writeByte(buf, 'U') + buf.Write(a.c.GetChallange()) + + glog.V(2).Infof("steam: a2SPlayerRequest buffer: %v", buf.Bytes()) + return buf.Bytes() +} diff --git a/a2sPlayerResponse.go b/a2sPlayerResponse.go new file mode 100644 index 0000000..4f1b95d --- /dev/null +++ b/a2sPlayerResponse.go @@ -0,0 +1,56 @@ +package steam + +import ( + "bytes" + "errors" + "fmt" + + "github.com/golang/glog" +) + +type Player struct { + index byte + name string + score int32 + duration float32 +} + +type A2SPlayersResponse struct { + playersCount byte + players []Player +} + +func (a *A2SPlayersResponse) UnMarshalBinary(data []byte) (err error) { + glog.V(2).Infof("steam: unmarshalling binary for A2SPlayersResponse: %v", data) + buf := bytes.NewBuffer(data) + + if header := readByte(buf); header != 0x44 { + return errors.New("steam: invalid header in the a2splayersresponse") + } + + a.playersCount = readByte(buf) + a.players = make([]Player, a.playersCount) + + for i := 0; i < int(a.playersCount); i++ { + p := &a.players[i] + p.index = readByte(buf) + p.name = readString(buf) + p.score = readLong(buf) + p.duration = readFloat(buf) + } + + return nil +} + +func (a *A2SPlayersResponse) String() string { + buf := new(bytes.Buffer) + + writeString(buf, fmt.Sprintf("players count: %v\n\n", a.playersCount)) + for _, player := range a.players { + writeString(buf, fmt.Sprintf("player index: %v\n", player.index)) + writeString(buf, fmt.Sprintf("player name: %v\n", player.name)) + writeString(buf, fmt.Sprintf("player score: %v\n", player.score)) + writeString(buf, fmt.Sprintf("player duration: %v seconds\n\n", player.duration)) + } + return buf.String() +} diff --git a/challengeRequest.go b/challengeRequest.go new file mode 100644 index 0000000..9797438 --- /dev/null +++ b/challengeRequest.go @@ -0,0 +1,21 @@ +package steam + +import ( + "bytes" + + "github.com/golang/glog" +) + +type ChallengeRequest struct { +} + +func (ChallengeRequest) MarshalBinary() []byte { + buf := new(bytes.Buffer) + + writeRequestPrefix(buf) + writeByte(buf, 'U') + writeRequestPrefix(buf) + + glog.V(2).Infof("steam: challengeRequest buffer: %v", buf.Bytes()) + return buf.Bytes() +} diff --git a/challengeResponse.go b/challengeResponse.go new file mode 100644 index 0000000..2c18981 --- /dev/null +++ b/challengeResponse.go @@ -0,0 +1,28 @@ +package steam + +import ( + "bytes" + "fmt" + + "github.com/golang/glog" +) + +type ChallengeResponse []byte + +func (c ChallengeResponse) GetChallange() (challenge []byte) { + glog.V(2).Infof("steam: getting challenge from %v", c) + + return c[(len(c) - 4):] +} + +func (c ChallengeResponse) String() string { + buf := new(bytes.Buffer) + + writeString(buf, fmt.Sprint("challengeResponse: [")) + for i := 0; i < len(c); i++ { + writeString(buf, fmt.Sprintf("%x ", c[i])) + } + writeString(buf, fmt.Sprint("]")) + + return buf.String() +} diff --git a/comm.go b/comm.go index f5de99f..ff142f8 100644 --- a/comm.go +++ b/comm.go @@ -16,6 +16,7 @@ const ( type ServerType int func (st *ServerType) UnmarshalBinary(data []byte) error { + glog.V(3).Infof("steam: unmarshalling binary %v for server type ", data) switch data[0] { case 'd': *st = STDedicated @@ -51,6 +52,7 @@ var serverTypeStrings = map[ServerType]string{ type Environment int func (e *Environment) UnmarshalBinary(data []byte) error { + glog.V(3).Infof("steam: unmarshalling binary %v for env ", data) switch data[0] { case 'l': *e = ELinux @@ -86,6 +88,7 @@ var environmentStrings = map[Environment]string{ type Visibility int func (v *Visibility) UnmarshalBinary(data []byte) error { + glog.V(3).Infof("steam: unmarshalling binary %v for visibility ", data) switch data[0] { case 0: *v = VPublic @@ -155,6 +158,7 @@ func (InfoRequest) MarshalBinary() ([]byte, error) { writeByte(buf, 'T') writeString(buf, "Source Engine Query") + glog.V(3).Infof("steam: marshaled binary. buffer: %v", buf) return buf.Bytes(), nil } @@ -193,6 +197,7 @@ const ( ) func (r *InfoResponse) UnmarshalBinary(data []byte) (err error) { + glog.V(3).Infof("steam: unmarshalling binary %v", data) defer func() { if e := recover(); e != nil { err = e.(parseError) diff --git a/samples/sample.go b/samples/sample.go index af0de88..358e052 100644 --- a/samples/sample.go +++ b/samples/sample.go @@ -1,6 +1,7 @@ package main import ( + "flag" "fmt" "github.com/kidoman/go-steam" @@ -15,13 +16,17 @@ var addresses = []string{ } func main() { + flag.Parse() for _, address := range addresses { server := &steam.Server{Addr: address} ping, err := server.Ping() must(err) info, err := server.Info() must(err) + playerInfo, err := server.PLayerInfo() + must(err) fmt.Printf("%v: %v with ping %v\n", address, info, ping) + fmt.Printf("players info: %v\n", playerInfo) } } diff --git a/server.go b/server.go index 2dccf8b..5fc9ba2 100644 --- a/server.go +++ b/server.go @@ -3,6 +3,8 @@ package steam import ( "errors" "time" + + "github.com/golang/glog" ) // Server represents a Source server. @@ -53,6 +55,7 @@ func (s *Server) Ping() (time.Duration, error) { return 0, err } + glog.V(3).Infof("steam: sending data %v via socket in ping", data) start := time.Now() s.socket.send(data) if _, err := s.socket.receive(); err != nil { @@ -74,10 +77,7 @@ func (s *Server) Info() (*InfoResponse, error) { return nil, err } - if err := s.socket.send(data); err != nil { - return nil, err - } - b, err := s.socket.receive() + b, err := s.sendAndRecieve(data) if err != nil { return nil, err } @@ -89,3 +89,47 @@ func (s *Server) Info() (*InfoResponse, error) { return res, nil } + +// PlayerInfo retrieves players information on the server. +func (s *Server) PLayerInfo() (*A2SPlayersResponse, error) { + if err := s.init(); err != nil { + return nil, err + } + + data := ChallengeRequest{}.MarshalBinary() + + b, err := s.sendAndRecieve(data) + if err != nil { + return nil, err + } + + challengeRes := ChallengeResponse(b) + data = A2SPlayerRequest{challengeRes}.MarshalBinary() + + b, err = s.sendAndRecieve(data) + if err != nil { + return nil, err + } + + a2sPlayerRes := new(A2SPlayersResponse) + + if err := a2sPlayerRes.UnMarshalBinary(b); err != nil { + return nil, err + } + + return a2sPlayerRes, nil +} + +func (s *Server) sendAndRecieve(data []byte) ([]byte, error) { + glog.V(3).Infof("steam: sending data %v via socket in info", data) + if err := s.socket.send(data); err != nil { + return nil, err + } + + b, err := s.socket.receive() + if err != nil { + return nil, err + } + glog.V(3).Infof("steam: received data %v via socket", b) + return b, nil +} diff --git a/socket.go b/socket.go index 318f905..c533b20 100644 --- a/socket.go +++ b/socket.go @@ -3,6 +3,8 @@ package steam import ( "errors" "net" + + "github.com/golang/glog" ) type socket struct { @@ -29,6 +31,7 @@ func (s *socket) close() { } func (s *socket) send(payload []byte) error { + glog.V(3).Infof("steam: writing %v to UDP", payload) n, err := s.conn.WriteToUDP(payload, s.raddr) if err != nil { return err diff --git a/wire.go b/wire.go index 7c864bb..2980190 100644 --- a/wire.go +++ b/wire.go @@ -94,6 +94,22 @@ func readLongLong(buf *bytes.Buffer) int64 { return int64(t[0] + t[1]<<8 + t[2]<<16 + t[3]<<24 + t[4]<<32 + t[5]<<40 + t[6]<<48 + t[7]<<56) } +func readLong(buf *bytes.Buffer) int32 { + var t [4]byte + n, err := buf.Read(t[:]) + if err != nil { + triggerError(errCouldNotReadData) + } + if n != 4 { + triggerError(errNotEnoughDataInResponse) + } + return int32(t[0] + t[1]<<8 + t[2]<<16 + t[3]<<24) +} + +func readFloat(buf *bytes.Buffer) float32 { + return float32(readLong(buf)) +} + func readString(buf *bytes.Buffer) string { bytes, err := buf.ReadBytes(0) if err != nil {