From 08f85da5f4206bfd91993d43a1c4264fd99a815c Mon Sep 17 00:00:00 2001 From: mkwcat Date: Tue, 20 Feb 2024 09:37:29 -0500 Subject: [PATCH] Go-ify and improve some code --- common/auth_token.go | 8 ++++++-- common/gamespy_message.go | 6 +++--- common/match_command.go | 2 -- gamestats/auth.go | 2 +- gpcm/friend.go | 18 +++--------------- gpcm/login.go | 2 +- gpcm/main.go | 2 +- gpcm/report.go | 2 -- natneg/main.go | 22 ++++------------------ qr2/challenge.go | 18 +++++++++++++++++- qr2/group.go | 35 +++++++++++++++++++++++++++++++++++ qr2/main.go | 8 -------- qr2/message.go | 11 ++++++++--- qr2/session.go | 12 ++++++++++++ sake/main.go | 1 - sake/storage.go | 13 +++---------- 16 files changed, 94 insertions(+), 68 deletions(-) diff --git a/common/auth_token.go b/common/auth_token.go index e53113f..226d63d 100644 --- a/common/auth_token.go +++ b/common/auth_token.go @@ -84,7 +84,9 @@ func MarshalNASAuthToken(gamecd string, userid uint64, gsbrcd string, cfc uint64 return "NDS" + Base64DwcEncoding.EncodeToString(blob), challenge } -func UnmarshalNASAuthToken(token string) (err error, gamecd string, issuetime time.Time, userid uint64, gsbrcd string, cfc uint64, region byte, lang byte, ingamesn string, challenge string, unitcd byte, isLocalhost bool) { +func UnmarshalNASAuthToken(token string) (gamecd string, issuetime time.Time, userid uint64, gsbrcd string, cfc uint64, region byte, lang byte, ingamesn string, challenge string, unitcd byte, isLocalhost bool, err error) { + err = nil + if !strings.HasPrefix(token, "NDS") { err = errors.New("invalid auth token prefix") return @@ -140,7 +142,9 @@ func MarshalGPCMLoginTicket(profileId uint32) string { return Base64DwcEncoding.EncodeToString(blob) } -func UnmarshalGPCMLoginTicket(ticket string) (err error, profileId uint32, issuetime time.Time) { +func UnmarshalGPCMLoginTicket(ticket string) (profileId uint32, issuetime time.Time, err error) { + err = nil + blob, err := Base64DwcEncoding.DecodeString(ticket) if err != nil { return diff --git a/common/gamespy_message.go b/common/gamespy_message.go index ace2118..cae62db 100644 --- a/common/gamespy_message.go +++ b/common/gamespy_message.go @@ -13,12 +13,12 @@ type GameSpyCommand struct { } var ( - InvalidGameSpyCommand = errors.New("invalid GameSpy command received") + ErrInvalidGameSpyCommand = errors.New("invalid GameSpy command received") ) func ParseGameSpyMessage(msg string) ([]GameSpyCommand, error) { if !strings.Contains(msg, `\final\`) { - return nil, InvalidGameSpyCommand + return nil, ErrInvalidGameSpyCommand } var commands []GameSpyCommand @@ -31,7 +31,7 @@ func ParseGameSpyMessage(msg string) ([]GameSpyCommand, error) { for len(msg) > 0 && string(msg[0]) == `\` { keyEnd := strings.Index(msg[1:], `\`) + 1 if keyEnd < 2 { - return nil, InvalidGameSpyCommand + return nil, ErrInvalidGameSpyCommand } key := msg[1:keyEnd] diff --git a/common/match_command.go b/common/match_command.go index 07ff080..92d7c10 100644 --- a/common/match_command.go +++ b/common/match_command.go @@ -689,8 +689,6 @@ func EncodeMatchCommand(command byte, data MatchCommandData) ([]byte, bool) { logging.Info("Common", "Unknown match command:", aurora.Cyan(command), "data:", data.Other) return data.Other, true } - - return []byte{}, false } func LogMatchCommand(moduleName string, dest string, command byte, data MatchCommandData) { diff --git a/gamestats/auth.go b/gamestats/auth.go index 0f0d3e5..2022117 100644 --- a/gamestats/auth.go +++ b/gamestats/auth.go @@ -62,7 +62,7 @@ func (g *GameStatsSession) authp(command common.GameSpyCommand) { return } - err, _, issueTime, userId, gsbrcd, _, _, _, _, _, _, _ := common.UnmarshalNASAuthToken(authToken) + _, issueTime, userId, gsbrcd, _, _, _, _, _, _, _, err := common.UnmarshalNASAuthToken(authToken) if err != nil { logging.Error(g.ModuleName, "Error unmarshalling authtoken:", err.Error()) g.Write(errorCmd) diff --git a/gpcm/friend.go b/gpcm/friend.go index 460a96f..b0a8c4e 100644 --- a/gpcm/friend.go +++ b/gpcm/friend.go @@ -66,10 +66,10 @@ func (g *GameSpySession) getAuthorizedFriendIndex(profileId uint32) int { } const ( - addFriendMessage = "\r\n\r\n|signed|00000000000000000000000000000000" + // addFriendMessage = "\r\n\r\n|signed|00000000000000000000000000000000" // TODO: Check if this is needed for any game, it sends via bm 1 - authFriendMessage = "I have authorized your request to add me to your list" + // authFriendMessage = "I have authorized your request to add me to your list" logOutMessage = "|s|0|ss|Offline|ls||ip|0|p|0|qm|0" ) @@ -145,17 +145,6 @@ func (g *GameSpySession) addFriend(command common.GameSpyCommand) { g.exchangeFriendStatus(uint32(newProfileId)) } -func (g *GameSpySession) sendFriendRequests() { - mutex.Lock() - defer mutex.Unlock() - - for _, newSession := range sessions { - if newSession.isFriendAdded(g.User.ProfileId) { - sendMessageToSessionBuffer("2", g.User.ProfileId, newSession, addFriendMessage) - } - } -} - func (g *GameSpySession) removeFriend(command common.GameSpyCommand) { strDelProfileID := command.OtherValues["delprofileid"] delProfileID64, err := strconv.ParseUint(strDelProfileID, 10, 32) @@ -481,8 +470,7 @@ func (g *GameSpySession) bestieMessage(command common.GameSpyCommand) { } else if cmd == common.MatchResvOK || cmd == common.MatchResvDeny || cmd == common.MatchResvWait { if toSession.ReservationPID != g.User.ProfileId || toSession.Reservation.Reservation == nil { logging.Error(g.ModuleName, "Destination", aurora.Cyan(toProfileId), "has no reservation with the sender") - g.replyError(ErrMessage) - return + // Allow the message through anyway to avoid a room deadlock } if toSession.Reservation.Version != msgMatchData.Version { diff --git a/gpcm/login.go b/gpcm/login.go index bc8701d..0023dd6 100644 --- a/gpcm/login.go +++ b/gpcm/login.go @@ -156,7 +156,7 @@ func (g *GameSpySession) login(command common.GameSpyCommand) { return } - err, gamecd, issueTime, userId, gsbrcd, cfc, region, lang, ingamesn, challenge, unitcd, isLocalhost := common.UnmarshalNASAuthToken(authToken) + gamecd, issueTime, userId, gsbrcd, cfc, region, lang, ingamesn, challenge, unitcd, isLocalhost, err := common.UnmarshalNASAuthToken(authToken) if err != nil { g.replyError(ErrLogin) return diff --git a/gpcm/main.go b/gpcm/main.go index d44dff6..366f47d 100644 --- a/gpcm/main.go +++ b/gpcm/main.go @@ -196,7 +196,7 @@ func handleRequest(conn net.Conn) { commands = session.handleCommand("wwfc_exlogin", commands, session.exLogin) commands = session.ignoreCommand("logout", commands) - if len(commands) != 0 && session.LoggedIn == false { + if len(commands) != 0 && !session.LoggedIn { logging.Error(session.ModuleName, "Attempt to run command before login:", aurora.Cyan(commands[0])) session.replyError(ErrNotLoggedIn) return diff --git a/gpcm/report.go b/gpcm/report.go index bef5b57..74525ef 100644 --- a/gpcm/report.go +++ b/gpcm/report.go @@ -32,7 +32,6 @@ func (g *GameSpySession) handleWWFCReport(command common.GameSpyCommand) { } qr2.ProcessUSER(g.User.ProfileId, g.QR2IP, packet) - break case "mkw_malicious_packet": if g.GameName != "mariokartwii" { @@ -47,7 +46,6 @@ func (g *GameSpySession) handleWWFCReport(command common.GameSpyCommand) { } logging.Warn(g.ModuleName, "Malicious packet from", aurora.BrightCyan(strconv.FormatUint(profileId, 10))) - break } } } diff --git a/natneg/main.go b/natneg/main.go index 01a4060..fd60589 100644 --- a/natneg/main.go +++ b/natneg/main.go @@ -9,6 +9,7 @@ import ( "time" "wwfc/common" "wwfc/logging" + "wwfc/qr2" "github.com/logrusorgru/aurora/v3" ) @@ -186,78 +187,60 @@ func handleConnection(conn net.PacketConn, addr net.Addr, buffer []byte) { switch command { default: logging.Error(moduleName, "Received unknown command type:", aurora.Cyan(command)) - break case NNInitRequest: // logging.Info(moduleName, "Command:", aurora.Yellow("NN_INIT")) session.handleInit(conn, addr, buffer[12:], moduleName, version) - break case NNInitReply: logging.Warn(moduleName, "Received server command:", aurora.Yellow("NN_INITACK")) - break case NNErtTestRequest: logging.Warn(moduleName, "Received server command:", aurora.Yellow("NN_ERTTEST")) - break case NNErtTestReply: logging.Info(moduleName, "Command:", aurora.Yellow("NN_ERTACK")) - break case NNStateUpdate: logging.Info(moduleName, "Command:", aurora.Yellow("NN_STATEUPDATE")) - break case NNConnectRequest: logging.Warn(moduleName, "Received server command:", aurora.Yellow("NN_CONNECT")) - break case NNConnectReply: // logging.Info(moduleName, "Command:", aurora.Yellow("NN_CONNECT_ACK")) session.handleConnectReply(conn, addr, buffer[12:], moduleName, version) - break case NNConnectPing: logging.Info(moduleName, "Command:", aurora.Yellow("NN_CONNECT_PING")) - break case NNBackupTestRequest: logging.Info(moduleName, "Command:", aurora.Yellow("NN_BACKUP_TEST")) - break case NNBackupTestReply: logging.Warn(moduleName, "Received server command:", aurora.Yellow("NN_BACKUP_ACK")) - break case NNAddressCheckRequest: logging.Info(moduleName, "Command:", aurora.Yellow("NN_ADDRESS_CHECK")) - break case NNAddressCheckReply: logging.Warn(moduleName, "Received server command:", aurora.Yellow("NN_ADDRESS_REPLY")) - break case NNNatifyRequest: logging.Info(moduleName, "Command:", aurora.Yellow("NN_NATIFY_REQUEST")) - break case NNReportRequest: // logging.Info(moduleName, "Command:", aurora.Yellow("NN_REPORT")) session.handleReport(conn, addr, buffer[12:], moduleName, version) - break case NNReportReply: logging.Warn(moduleName, "Received server command:", aurora.Yellow("NN_REPORT_ACK")) - break case NNPreInitRequest: logging.Info(moduleName, "Command:", aurora.Yellow("NN_PREINIT")) - break case NNPreInitReply: logging.Warn(moduleName, "Received server command:", aurora.Yellow("NN_PREINIT_ACK")) - break } } @@ -471,8 +454,11 @@ func (session *NATNEGSession) handleReport(conn net.PacketConn, addr net.Addr, b if client, exists := session.Clients[clientIndex]; exists { client.Connected[client.ConnectingIndex] = true + connecting := session.Clients[client.ConnectingIndex] client.ConnectingIndex = clientIndex client.ConnectAck = false + + qr2.ProcessNATNEGReport(result, client.ServerIP, connecting.ServerIP) } // Send remaining requests diff --git a/qr2/challenge.go b/qr2/challenge.go index 20d83ea..0298ed0 100644 --- a/qr2/challenge.go +++ b/qr2/challenge.go @@ -5,6 +5,7 @@ import ( "net" "strconv" "strings" + "time" "wwfc/common" ) @@ -40,5 +41,20 @@ func sendChallenge(conn net.PacketConn, addr net.Addr, session Session, lookupAd response = append(response, []byte(challenge)...) response = append(response, 0) - conn.WriteTo(response, addr) + go func() { + for { + conn.WriteTo(response, addr) + + time.Sleep(1 * time.Second) + + mutex.Lock() + session, ok := sessions[lookupAddr] + if !ok || session.Authenticated || session.LastKeepAlive < time.Now().Unix()-60 { + mutex.Unlock() + return + } + addr = session.Addr + mutex.Unlock() + } + }() } diff --git a/qr2/group.go b/qr2/group.go index 3b2b4e3..5501b16 100644 --- a/qr2/group.go +++ b/qr2/group.go @@ -258,6 +258,41 @@ func CheckGPReservationAllowed(senderIP uint64, senderPid uint32, destPid uint32 return checkReservationAllowed(moduleName, from, to, joinType) } +func ProcessNATNEGReport(result byte, ip1 string, ip2 string) { + moduleName := "QR2:NATNEGReport" + + ip1Lookup := makeLookupAddr(ip1) + ip2Lookup := makeLookupAddr(ip2) + + mutex.Lock() + defer mutex.Unlock() + + session1 := sessions[ip1Lookup] + if session1 == nil { + logging.Warn(moduleName, "Received NATNEG report for non-existent IP", aurora.Cyan(ip1)) + return + } + + session2 := sessions[ip2Lookup] + if session2 == nil { + logging.Warn(moduleName, "Received NATNEG report for non-existent IP", aurora.Cyan(ip2)) + return + } + + if session1.GroupPointer == nil || session1.GroupPointer != session2.GroupPointer { + logging.Warn(moduleName, "Received NATNEG report for two IPs in different groups") + return + } + + resultString := "2" + if result == 1 { + resultString = "1" + } + + session1.Data["+conn_"+session2.Data["+joinindex"]] = resultString + session2.Data["+conn_"+session1.Data["+joinindex"]] = resultString +} + func ProcessUSER(senderPid uint32, senderIP uint64, packet []byte) { moduleName := "QR2:ProcessUSER/" + strconv.FormatUint(uint64(senderPid), 10) diff --git a/qr2/main.go b/qr2/main.go index 68c82a4..546f134 100644 --- a/qr2/main.go +++ b/qr2/main.go @@ -79,7 +79,6 @@ func handleConnection(conn net.PacketConn, addr net.Addr, buffer []byte) { switch packetType { case QueryRequest: logging.Info(moduleName, "Command:", aurora.Yellow("QUERY")) - break case ChallengeRequest: logging.Info(moduleName, "Command:", aurora.Yellow("CHALLENGE")) @@ -94,24 +93,19 @@ func handleConnection(conn net.PacketConn, addr net.Addr, buffer []byte) { } else { mutex.Unlock() } - break case EchoRequest: logging.Info(moduleName, "Command:", aurora.Yellow("ECHO")) - break case HeartbeatRequest: // logging.Info(moduleName, "Command:", aurora.Yellow("HEARTBEAT")) heartbeat(moduleName, conn, addr, buffer) - break case AddErrorRequest: logging.Info(moduleName, "Command:", aurora.Yellow("ADDERROR")) - break case EchoResponseRequest: logging.Info(moduleName, "Command:", aurora.Yellow("ECHO_RESPONSE")) - break case ClientMessageRequest: logging.Info(moduleName, "Command:", aurora.Yellow("CLIENT_MESSAGE")) @@ -144,7 +138,6 @@ func handleConnection(conn net.PacketConn, addr net.Addr, buffer []byte) { case ClientRegisteredReply: logging.Info(moduleName, "Command:", aurora.Yellow("CLIENT_REGISTERED")) - break case ClientExploitReply: logging.Info(moduleName, "Command:", aurora.Yellow("CLIENT_EXPLOIT_ACK")) @@ -153,7 +146,6 @@ func handleConnection(conn net.PacketConn, addr net.Addr, buffer []byte) { if login := session.Login; login != nil { login.NeedsExploit = false } - break default: logging.Error(moduleName, "Unknown command:", aurora.Yellow(buffer[0])) diff --git a/qr2/message.go b/qr2/message.go index c6d03f6..ec4fe92 100644 --- a/qr2/message.go +++ b/qr2/message.go @@ -260,7 +260,9 @@ func SendClientMessage(senderIP string, destSearchID uint64, message []byte) { switch s.Fetch(true) { case &timeWaker: timeOutCount++ - if timeOutCount <= 8 { + + // Enforce a 10 second timeout + if timeOutCount <= 10 { break } @@ -332,8 +334,11 @@ func processClientMessage(moduleName string, sender, receiver *Session, message sender.ReservationID = receiver.SearchID } else if cmd == common.MatchResvOK || cmd == common.MatchResvDeny || cmd == common.MatchResvWait { if receiver.ReservationID != sender.SearchID || receiver.Reservation.Reservation == nil { - logging.Error(moduleName, "Destination has no reservation with the sender") - return + logging.Warn(moduleName, "Destination has no reservation with the sender") + if receiver.GroupPointer == nil || receiver.GroupPointer != sender.GroupPointer { + return + } + // Allow the message through anyway to avoid a room deadlock } if receiver.Reservation.Version != matchData.Version { diff --git a/qr2/session.go b/qr2/session.go index f8d8a26..ab8ded1 100644 --- a/qr2/session.go +++ b/qr2/session.go @@ -52,6 +52,8 @@ func removeSession(addr uint64) { return } + session.MessageAckWaker.Assert() + if session.GroupPointer != nil { session.removeFromGroup() } @@ -84,6 +86,16 @@ func (session *Session) removeFromGroup() { session.GroupPointer.findNewServer() } + for player := range session.GroupPointer.Players { + delete(player.Data, "+conn_"+session.Data["+joinindex"]) + } + + for field := range session.Data { + if strings.HasPrefix(field, "+conn_") { + delete(session.Data, field) + } + } + session.GroupPointer = nil } diff --git a/sake/main.go b/sake/main.go index 9d11684..7999a14 100644 --- a/sake/main.go +++ b/sake/main.go @@ -42,6 +42,5 @@ func HandleRequest(w http.ResponseWriter, r *http.Request) { case "/SakeStorageServer/StorageServer.asmx": moduleName := "SAKE:Storage:" + r.RemoteAddr handleStorageRequest(moduleName, w, r) - break } } diff --git a/sake/storage.go b/sake/storage.go index 6c34770..70a1f90 100644 --- a/sake/storage.go +++ b/sake/storage.go @@ -147,19 +147,15 @@ func handleStorageRequest(moduleName string, w http.ResponseWriter, r *http.Requ switch xmlName { case SakeNamespace + "/GetMyRecords": response.Body.GetMyRecordsResponse = getMyRecords(moduleName, profileId, gameInfo, soap.Body.Data) - break case SakeNamespace + "/UpdateRecord": response.Body.UpdateRecordResponse = updateRecord(moduleName, profileId, gameInfo, soap.Body.Data) - break case SakeNamespace + "/SearchForRecords": response.Body.SearchForRecordsResponse = searchForRecords(moduleName, gameInfo, soap.Body.Data) - break default: logging.Error(moduleName, "Unknown SOAPAction:", aurora.Cyan(xmlName)) - break } } } else { @@ -190,7 +186,7 @@ func getRequestIdentity(moduleName string, request StorageRequestData) (uint32, return 0, common.GameInfo{}, false } - err, profileId, _ := common.UnmarshalGPCMLoginTicket(request.LoginTicket) + profileId, _, err := common.UnmarshalGPCMLoginTicket(request.LoginTicket) if err != nil { logging.Error(moduleName, err) return 0, common.GameInfo{}, false @@ -237,7 +233,7 @@ func getMyRecords(moduleName string, profileId uint32, gameInfo common.GameInfo, GetMyRecordsResult: "Error", } - values := map[string]StorageValue{} + var values map[string]StorageValue switch gameInfo.Name + "/" + request.TableID { default: @@ -254,7 +250,6 @@ func getMyRecords(moduleName string, profileId uint32, gameInfo common.GameInfo, "recordid": intValue(int32(profileId)), "info": binaryDataValueBase64(database.GetMKWFriendInfo(pool, ctx, profileId)), } - break } response := StorageGetMyRecordsResponse{ @@ -299,7 +294,6 @@ func updateRecord(moduleName string, profileId uint32, gameInfo common.GameInfo, // TODO: Validate record data database.UpdateMKWFriendInfo(pool, ctx, profileId, request.Values.RecordFields[0].Value.Value.Value) logging.Notice(moduleName, "Updated Mario Kart Wii friend info") - break } return &StorageUpdateRecordResponse{ @@ -343,14 +337,13 @@ func searchForRecords(moduleName string, gameInfo common.GameInfo, request Stora "info": binaryDataValueBase64(database.GetMKWFriendInfo(pool, ctx, uint32(ownerId))), }, } - break } // Sort the values now sort.Slice(values, func(l, r int) bool { lVal, lExists := values[l][request.Sort] rVal, rExists := values[r][request.Sort] - if lExists == false || rExists == false { + if !lExists || !rExists { // Prioritises the one that exists or goes left if both false return rExists }