From 53cec11f17e4b789813d163fb055e841a874353d Mon Sep 17 00:00:00 2001 From: mkwcat Date: Mon, 11 Dec 2023 14:14:36 -0500 Subject: [PATCH] Add /api/groups endpoint --- api/groups.go | 48 +++++++++++++++++++++++++ gpcm/friend.go | 2 ++ nas/main.go | 7 ++++ qr2/group.go | 96 ++++++++++++++++++++++++++++++++++++++++---------- qr2/message.go | 1 - qr2/session.go | 12 +++---- 6 files changed, 140 insertions(+), 26 deletions(-) create mode 100644 api/groups.go diff --git a/api/groups.go b/api/groups.go new file mode 100644 index 0000000..07cedad --- /dev/null +++ b/api/groups.go @@ -0,0 +1,48 @@ +package api + +import ( + "encoding/json" + "net/http" + "net/url" + "strconv" + "wwfc/qr2" +) + +func HandleGroups(w http.ResponseWriter, r *http.Request) { + u, err := url.Parse(r.URL.String()) + if err != nil { + panic(err) + } + + query, err := url.ParseQuery(u.RawQuery) + if err != nil { + panic(err) + } + + groups := qr2.GetGroups(append(query["gamename"], "")[0]) + + for _, group := range groups { + for i, player := range group.Players { + filtered := map[string]string{} + + filtered["dwc_pid"] = player["dwc_pid"] + filtered["ingamename"] = player["ingamename"] + + if player["gamename"] == "mariokartwii" { + filtered["ev"] = player["ev"] + filtered["eb"] = player["eb"] + } + + group.Players[i] = filtered + } + } + + jsonData, err := json.Marshal(groups) + if err != nil { + panic(err) + } + + w.Header().Set("Content-Type", "text/json") + w.Header().Set("Content-Length", strconv.Itoa(len(jsonData))) + w.Write(jsonData) +} diff --git a/gpcm/friend.go b/gpcm/friend.go index 1f7b1e2..a327943 100644 --- a/gpcm/friend.go +++ b/gpcm/friend.go @@ -109,6 +109,8 @@ func (g *GameSpySession) authAddFriend(command common.GameSpyCommand) { } func (g *GameSpySession) setStatus(command common.GameSpyCommand) { + // TODO: Some games don't make it obvious to QR2 when they leave a group, so try checking via a status update + status := command.CommandValue statstring, ok := command.OtherValues["statstring"] diff --git a/nas/main.go b/nas/main.go index 080b1e6..181e959 100644 --- a/nas/main.go +++ b/nas/main.go @@ -9,6 +9,7 @@ import ( "regexp" "strconv" "strings" + "wwfc/api" "wwfc/common" "wwfc/logging" "wwfc/nhttp" @@ -75,6 +76,12 @@ func handleRequest(w http.ResponseWriter, r *http.Request) { return } + // Check for /api/groups + if r.URL.Path == "/api/groups" { + api.HandleGroups(w, r) + return + } + // Stage 1 if match := regexStage1URL.FindStringSubmatch(r.URL.String()); match != nil { val, err := strconv.Atoi(match[1]) diff --git a/qr2/group.go b/qr2/group.go index c0a0870..2667880 100644 --- a/qr2/group.go +++ b/qr2/group.go @@ -10,28 +10,44 @@ import ( type Group struct { GroupID uint32 - ServerAID uint8 - Players map[uint8]*Session + GroupName string + GameName string + Server *Session + Players map[*Session]bool } -var groups = map[*Group]bool{} +var groups = map[string]*Group{} func processResvOK(moduleName string, cmd common.MatchCommandDataResvOK, sender, destination *Session) bool { - group := sender.GroupPointer - if group == nil { - logging.Notice(moduleName, "Creating new group", aurora.Cyan(cmd.GroupID), "/", aurora.Cyan(cmd.ProfileID)) - group = &Group{ - GroupID: cmd.GroupID, - ServerAID: uint8(cmd.SenderAID), - Players: map[uint8]*Session{uint8(cmd.SenderAID): sender}, - } - sender.GroupPointer = group - groups[group] = true + if len(groups) >= 100000 { + logging.Error(moduleName, "Hit arbitrary global maximum group count (somehow)") + return false } - if uint8(cmd.SenderAID) != sender.GroupAID { - logging.Error(moduleName, "ResvOK: Invalid sender AID") - return false + group := sender.GroupPointer + if group == nil { + group = &Group{ + GroupID: cmd.GroupID, + GroupName: "", + GameName: sender.Data["gamename"], + Server: sender, + Players: map[*Session]bool{sender: true}, + } + + for { + groupName := common.RandomString(6) + if groups[groupName] != nil { + continue + } + + group.GroupName = groupName + break + } + + sender.GroupPointer = group + groups[group.GroupName] = group + + logging.Notice(moduleName, "Created new group", aurora.Cyan(group.GroupName)) } // TODO: Check if the sender is the actual server (host) once host migration works @@ -39,10 +55,9 @@ func processResvOK(moduleName string, cmd common.MatchCommandDataResvOK, sender, // Keep group ID updated sender.GroupPointer.GroupID = cmd.GroupID - logging.Info(moduleName, "New AID", aurora.Cyan(uint8(cmd.ReceiverNewAID)), "in group", aurora.Cyan(group.GroupID)) - group.Players[uint8(cmd.ReceiverNewAID)] = destination + logging.Info(moduleName, "New player", aurora.BrightCyan(destination.Data["dwc_pid"]), "in group", aurora.Cyan(group.GroupName)) + group.Players[destination] = true destination.GroupPointer = group - destination.GroupAID = uint8(cmd.ReceiverNewAID) return true } @@ -79,3 +94,46 @@ func ProcessGPResvOK(cmd common.MatchCommandDataResvOK, senderIP uint64, senderP return processResvOK(moduleName, cmd, from, to) } + +type GroupInfo struct { + GroupName string `json:"id"` + GameName string `json:"gamename"` + ServerPID string `json:"host"` + Players []map[string]string `json:"players"` +} + +// GetGroups returns an unsorted copy of all online rooms +func GetGroups(gameName string) []GroupInfo { + var groupsCopy []GroupInfo + + mutex.Lock() + for _, group := range groups { + if gameName != "" && gameName != group.GameName { + continue + } + + groupInfo := GroupInfo{ + GroupName: group.GroupName, + GameName: group.GameName, + ServerPID: "", + Players: []map[string]string{}, + } + + if group.Server != nil { + groupInfo.ServerPID = group.Server.Data["dwc_pid"] + } + + for session, _ := range group.Players { + mapData := map[string]string{} + for k, v := range session.Data { + mapData[k] = v + } + groupInfo.Players = append(groupInfo.Players, mapData) + } + + groupsCopy = append(groupsCopy, groupInfo) + } + mutex.Unlock() + + return groupsCopy +} diff --git a/qr2/message.go b/qr2/message.go index a8a3e01..1e2be54 100644 --- a/qr2/message.go +++ b/qr2/message.go @@ -72,7 +72,6 @@ func SendClientMessage(senderIP string, destSearchID uint64, message []byte) { logging.Error(moduleName, "Received invalid match version") return } - logging.Info(moduleName, "Version:", version) senderProfileID := binary.LittleEndian.Uint32(message[0x10:0x14]) moduleName = "QR2/MSG:p" + strconv.FormatUint(uint64(senderProfileID), 10) diff --git a/qr2/session.go b/qr2/session.go index 6077c35..3b61f69 100644 --- a/qr2/session.go +++ b/qr2/session.go @@ -30,7 +30,6 @@ type Session struct { PacketCount uint32 ReservationID uint64 GroupPointer *Group - GroupAID uint8 } var ( @@ -52,13 +51,14 @@ func removeSession(sessionId uint32) { } if session.GroupPointer != nil { - delete(session.GroupPointer.Players, session.GroupAID) + delete(session.GroupPointer.Players, session) if len(session.GroupPointer.Players) == 0 { - logging.Notice("QR2", "Deleting group", aurora.Cyan(session.GroupPointer.GroupID)) - delete(groups, session.GroupPointer) - } else if session.GroupPointer.ServerAID == session.GroupAID { - logging.Notice("QR2", "Server down in group", aurora.Cyan(session.GroupPointer.GroupID)) + logging.Notice("QR2", "Deleting group", aurora.Cyan(session.GroupPointer.GroupName)) + delete(groups, session.GroupPointer.GroupName) + } else if session.GroupPointer.Server == session { + logging.Notice("QR2", "Server down in group", aurora.Cyan(session.GroupPointer.GroupName)) + session.GroupPointer.Server = nil // TODO: Search for new host via dwc_hoststate } }