From 67996c08b9655f3b63d0af83f80452acfe0ba8ab Mon Sep 17 00:00:00 2001 From: mkwcat Date: Thu, 11 Jan 2024 21:54:26 -0500 Subject: [PATCH] GPCM/GPSP: Hide certain profile information --- gpcm/error.go | 6 ++ gpcm/friend.go | 62 +++++++-------------- gpcm/login.go | 2 +- gpcm/main.go | 6 ++ gpcm/profile.go | 73 ++++++++++++++++++------- gpsp/error.go | 12 ++++ gpsp/main.go | 133 +++------------------------------------------ gpsp/otherslist.go | 94 ++++++++++++++++++++++++++++++++ 8 files changed, 200 insertions(+), 188 deletions(-) create mode 100644 gpsp/error.go create mode 100644 gpsp/otherslist.go diff --git a/gpcm/error.go b/gpcm/error.go index 6f595f5..80dda5d 100644 --- a/gpcm/error.go +++ b/gpcm/error.go @@ -405,6 +405,9 @@ func (g *GameSpySession) replyError(err GPError) { msg := err.GetMessage() // logging.Info(g.ModuleName, "Sending error message:", msg) g.Conn.Write([]byte(msg)) + if err.Fatal { + g.Conn.Close() + } return } @@ -416,4 +419,7 @@ func (g *GameSpySession) replyError(err GPError) { msg := err.GetMessageTranslate(g.GameName, g.Region, g.Language, g.ConsoleFriendCode, deviceId) // logging.Info(g.ModuleName, "Sending error message:", msg) g.Conn.Write([]byte(msg)) + if err.Fatal { + g.Conn.Close() + } } diff --git a/gpcm/friend.go b/gpcm/friend.go index 81252e4..460a96f 100644 --- a/gpcm/friend.go +++ b/gpcm/friend.go @@ -66,9 +66,12 @@ 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" - logOutMessage = "|s|0|ss|Offline|ls||ip|0|p|0|qm|0" + + logOutMessage = "|s|0|ss|Offline|ls||ip|0|p|0|qm|0" ) func (g *GameSpySession) addFriend(command common.GameSpyCommand) { @@ -136,57 +139,21 @@ func (g *GameSpySession) addFriend(command common.GameSpyCommand) { newSession.AuthFriendList = append(newSession.AuthFriendList, g.User.ProfileId) // Send friend auth message - g.sendFriendAuthMessage(uint32(newProfileId)) - newSession.sendFriendAuthMessage(g.User.ProfileId) + sendMessageToSessionBuffer("4", newSession.User.ProfileId, g, "") + sendMessageToSession("4", g.User.ProfileId, newSession, "") g.exchangeFriendStatus(uint32(newProfileId)) } -func (g *GameSpySession) sendFriendAuthMessage(newProfileId uint32) { - // Send both messages in one packet - message := common.CreateGameSpyMessage(common.GameSpyCommand{ - Command: "bm", - CommandValue: "2", - OtherValues: map[string]string{ - "f": strconv.FormatUint(uint64(newProfileId), 10), - "msg": addFriendMessage, - }, - }) - message += common.CreateGameSpyMessage(common.GameSpyCommand{ - Command: "bm", - CommandValue: "4", - OtherValues: map[string]string{ - "f": strconv.FormatUint(uint64(newProfileId), 10), - "msg": "", - }, - }) - - g.Conn.Write([]byte(message)) -} - func (g *GameSpySession) sendFriendRequests() { mutex.Lock() defer mutex.Unlock() - // Condense all requests into one packet - var message string - for _, newSession := range sessions { if newSession.isFriendAdded(g.User.ProfileId) { - message += common.CreateGameSpyMessage(common.GameSpyCommand{ - Command: "bm", - CommandValue: "2", - OtherValues: map[string]string{ - "f": strconv.FormatUint(uint64(newSession.User.ProfileId), 10), - "msg": addFriendMessage, - }, - }) + sendMessageToSessionBuffer("2", g.User.ProfileId, newSession, addFriendMessage) } } - - if message != "" { - g.Conn.Write([]byte(message)) - } } func (g *GameSpySession) removeFriend(command common.GameSpyCommand) { @@ -607,6 +574,17 @@ func sendMessageToSession(msgType string, from uint32, session *GameSpySession, session.Conn.Write([]byte(message)) } +func sendMessageToSessionBuffer(msgType string, from uint32, session *GameSpySession, msg string) { + session.WriteBuffer += common.CreateGameSpyMessage(common.GameSpyCommand{ + Command: "bm", + CommandValue: msgType, + OtherValues: map[string]string{ + "f": strconv.FormatUint(uint64(from), 10), + "msg": msg, + }, + }) +} + func sendMessageToProfileId(msgType string, from uint32, to uint32, msg string) bool { if session, ok := sessions[to]; ok && session.LoggedIn { sendMessageToSession(msgType, from, session, msg) @@ -635,7 +613,7 @@ func (g *GameSpySession) exchangeFriendStatus(profileId uint32) { if g.isFriendAdded(profileId) { if session, ok := sessions[profileId]; ok && session.LoggedIn && session.isFriendAdded(g.User.ProfileId) { sendMessageToSession("100", g.User.ProfileId, session, g.Status) - sendMessageToSession("100", profileId, g, session.Status) + sendMessageToSessionBuffer("100", profileId, g, session.Status) } } } diff --git a/gpcm/login.go b/gpcm/login.go index b103e5f..79b53e9 100644 --- a/gpcm/login.go +++ b/gpcm/login.go @@ -266,7 +266,7 @@ func (g *GameSpySession) login(command common.GameSpyCommand) { g.Conn.Write([]byte(payload)) - g.sendFriendRequests() + // g.sendFriendRequests() } func (g *GameSpySession) exLogin(command common.GameSpyCommand) { diff --git a/gpcm/main.go b/gpcm/main.go index 7accb2f..d5b4b96 100644 --- a/gpcm/main.go +++ b/gpcm/main.go @@ -19,6 +19,7 @@ import ( type GameSpySession struct { Conn net.Conn + WriteBuffer string User database.User ModuleName string LoggedIn bool @@ -211,6 +212,11 @@ func handleRequest(conn net.Conn) { for _, command := range commands { logging.Error(session.ModuleName, "Unknown command:", aurora.Cyan(command.Command)) } + + if session.WriteBuffer != "" { + conn.Write([]byte(session.WriteBuffer)) + session.WriteBuffer = "" + } } } diff --git a/gpcm/profile.go b/gpcm/profile.go index 0fedbb1..9605996 100644 --- a/gpcm/profile.go +++ b/gpcm/profile.go @@ -38,29 +38,60 @@ func (g *GameSpySession) getProfile(command common.GameSpyCommand) { } } - response := common.CreateGameSpyMessage(common.GameSpyCommand{ - Command: "pi", - CommandValue: "", - OtherValues: map[string]string{ - "profileid": command.OtherValues["profileid"], - "nick": user.UniqueNick, - "userid": strconv.FormatUint(uint64(user.UserId), 10), - "email": user.Email, - "sig": common.RandomHexString(32), - "uniquenick": user.UniqueNick, - "firstname": user.FirstName, - "lastname": user.LastName, - "pid": "11", - "lon": "0.000000", - "lat": "0.000000", - "loc": locstring, - "id": command.OtherValues["id"], - }, - }) - - g.Conn.Write([]byte(response)) + if user.ProfileId == g.User.ProfileId { + g.WriteBuffer += common.CreateGameSpyMessage(common.GameSpyCommand{ + Command: "pi", + CommandValue: "", + OtherValues: map[string]string{ + "profileid": command.OtherValues["profileid"], + "nick": user.UniqueNick, + "userid": strconv.FormatUint(uint64(user.UserId), 10), + "email": user.Email, + "sig": common.RandomHexString(32), + "uniquenick": user.UniqueNick, + "firstname": user.FirstName, + "lastname": user.LastName, + "pid": "11", + "lon": "0.000000", + "lat": "0.000000", + "loc": locstring, + "id": command.OtherValues["id"], + }, + }) + } else { + g.WriteBuffer += common.CreateGameSpyMessage(common.GameSpyCommand{ + Command: "pi", + CommandValue: "", + OtherValues: map[string]string{ + "profileid": command.OtherValues["profileid"], + "nick": "000000000" + user.GsbrCode[:4] + "0000000", + "userid": "0", + "email": "000000000" + user.GsbrCode[:4] + "0000000" + "@nds", + "sig": common.RandomHexString(32), + "uniquenick": "000000000" + user.GsbrCode[:4] + "0000000", + "firstname": user.FirstName, + "lastname": "000000000" + user.GsbrCode[:4] + "0000000", + "pid": "11", + "lon": "0.000000", + "lat": "0.000000", + "loc": locstring, + "id": command.OtherValues["id"], + }, + }) + } } func (g *GameSpySession) updateProfile(command common.GameSpyCommand) { g.User.UpdateProfile(pool, ctx, command.OtherValues) } + +func VerifyPlayerSearch(profileId uint32, sessionKey int32, gameName string) (string, bool) { + mutex.Lock() + defer mutex.Unlock() + + if session, ok := sessions[profileId]; ok && session.LoggedIn && session.SessionKey == sessionKey && session.GameName == gameName { + return "000000000" + session.User.GsbrCode[:4] + "0000000", true + } + + return "", false +} diff --git a/gpsp/error.go b/gpsp/error.go new file mode 100644 index 0000000..e828022 --- /dev/null +++ b/gpsp/error.go @@ -0,0 +1,12 @@ +package gpsp + +import ( + "net" + "wwfc/gpcm" + "wwfc/logging" +) + +func replyError(moduleName string, conn net.Conn, err gpcm.GPError) { + logging.Error(moduleName, "Reply error:", err.ErrorString) + conn.Write([]byte(err.GetMessage())) +} diff --git a/gpsp/main.go b/gpsp/main.go index c6777f6..692aba9 100644 --- a/gpsp/main.go +++ b/gpsp/main.go @@ -3,18 +3,12 @@ package gpsp import ( "bufio" "context" - "errors" - "fmt" - "io" "net" - "strconv" - "strings" "wwfc/common" - "wwfc/database" + "wwfc/gpcm" "wwfc/logging" "github.com/jackc/pgx/v4/pgxpool" - "github.com/logrusorgru/aurora/v3" ) var ( @@ -27,18 +21,6 @@ func StartServer() { // Get config config := common.GetConfig() - // Start SQL - dbString := fmt.Sprintf("postgres://%s:%s@%s/%s", config.Username, config.Password, config.DatabaseAddress, config.DatabaseName) - dbConf, err := pgxpool.ParseConfig(dbString) - if err != nil { - panic(err) - } - - pool, err = pgxpool.ConnectConfig(ctx, dbConf) - if err != nil { - panic(err) - } - address := *config.GameSpyAddress + ":29901" l, err := net.Listen("tcp", address) if err != nil { @@ -66,139 +48,42 @@ func handleRequest(conn net.Conn) { defer conn.Close() moduleName := "GPSP" - knownProfileId := uint32(0) err := conn.(*net.TCPConn).SetKeepAlive(true) if err != nil { logging.Notice(moduleName, "Unable to set keepalive:", err.Error()) } - logging.Info(moduleName, "Connection established from", aurora.BrightCyan(conn.RemoteAddr())) - // Here we go into the listening loop for { + // TODO: Handle split packets buffer := make([]byte, 1024) _, err := bufio.NewReader(conn).Read(buffer) if err != nil { - if errors.Is(err, io.EOF) { - // Client closed connection, terminate. - logging.Info(moduleName, "Client closed connection") - return - } - - logging.Error(moduleName, "Connection error:", err.Error()) return } commands, err := common.ParseGameSpyMessage(string(buffer)) if err != nil { - logging.Error(moduleName, err) + logging.Error(moduleName, "Error parsing message:", err.Error()) + logging.Error(moduleName, "Raw data:", string(buffer)) + replyError(moduleName, conn, gpcm.ErrParse) return } for _, command := range commands { - logging.Info(moduleName, "Command:", aurora.Yellow(command.Command)) - switch command.Command { + default: + logging.Error(moduleName, "Unknown command:", command.Command) + case "ka": conn.Write([]byte(`\ka\\final\`)) break case "otherslist": - strProfileId, ok := command.OtherValues["profileid"] - if !ok { - logging.Error(moduleName, "Missing profileid in otherslist") - return - } - - profileId, err := strconv.ParseUint(strProfileId, 10, 32) - if err != nil { - logging.Error(moduleName, err) - return - } - - if knownProfileId == 0 { - knownProfileId = uint32(profileId) - moduleName = "GPSP:" + strconv.FormatUint(profileId, 10) - moduleName += "/" + common.CalcFriendCodeString(uint32(profileId), "RMCJ") - } else if uint32(profileId) != knownProfileId { - logging.Warn(moduleName, "Mismatched profile ID in otherslist:", aurora.Cyan(strProfileId)) - } - - logging.Info(moduleName, "Lookup otherslist for", aurora.Cyan(profileId)) - conn.Write([]byte(handleOthersList(moduleName, uint32(profileId), command))) + conn.Write([]byte(handleOthersList(command))) break } } } } - -func handleOthersList(moduleName string, _ uint32, command common.GameSpyCommand) string { - empty := `\otherslist\\final\` - - _, ok := command.OtherValues["sesskey"] - if !ok { - logging.Error(moduleName, "Missing sesskey in otherslist") - return empty - } - - numopids, ok := command.OtherValues["numopids"] - if !ok { - logging.Error(moduleName, "Missing numopids in otherslist") - return empty - } - - opids, ok := command.OtherValues["opids"] - if !ok { - logging.Error(moduleName, "Missing opids in otherslist") - return empty - } - - _, ok = command.OtherValues["gamename"] - if !ok { - logging.Error(moduleName, "Missing gamename in otherslist") - return empty - } - - numOpidsValue, err := strconv.Atoi(numopids) - if err != nil { - logging.Error(moduleName, err) - return empty - } - - var opidsSplit []string - if strings.Contains(opids, "|") { - opidsSplit = strings.Split(opids, "|") - } else if opids != "" && opids != "0" { - opidsSplit = append(opidsSplit, opids) - } - - if len(opidsSplit) != numOpidsValue && opids != "0" { - logging.Error(moduleName, "Mismatch opids length with numopids:", aurora.Cyan(len(opidsSplit)), "!=", aurora.Cyan(numOpidsValue)) - return empty - } - - payload := `\otherslist\` - for _, strOtherId := range opidsSplit { - otherId, err := strconv.ParseUint(strOtherId, 10, 32) - if err != nil { - logging.Error(moduleName, err) - continue - } - - // TODO: Perhaps this could be condensed into one database query - // Also TODO: Check if the players are actually friends - user, ok := database.GetProfile(pool, ctx, uint32(otherId)) - if !ok { - logging.Warn(moduleName, "Other ID doesn't exist:", aurora.Cyan(strOtherId)) - // If the profile doesn't exist then skip adding it - continue - } - - payload += `\o\` + strconv.FormatUint(uint64(user.ProfileId), 10) - payload += `\uniquenick\` + user.UniqueNick - } - - payload += `\oldone\\final\` - return payload -} diff --git a/gpsp/otherslist.go b/gpsp/otherslist.go new file mode 100644 index 0000000..ddc8ef3 --- /dev/null +++ b/gpsp/otherslist.go @@ -0,0 +1,94 @@ +package gpsp + +import ( + "strconv" + "strings" + "wwfc/common" + "wwfc/gpcm" + "wwfc/logging" + + "github.com/logrusorgru/aurora/v3" +) + +func handleOthersList(command common.GameSpyCommand) string { + moduleName := "GPSP" + + strProfileId, ok := command.OtherValues["profileid"] + if !ok { + logging.Error(moduleName, "Missing profileid in otherslist") + return gpcm.ErrSearch.GetMessage() + } + + profileId, err := strconv.ParseUint(strProfileId, 10, 32) + if err != nil { + logging.Error(moduleName, "Invalid profileid:", strProfileId) + return gpcm.ErrSearch.GetMessage() + } + + moduleName = "GPSP:" + strconv.FormatUint(profileId, 10) + logging.Info(moduleName, "Lookup otherslist for", aurora.Cyan(profileId)) + + strSessionKey, ok := command.OtherValues["sesskey"] + if !ok { + logging.Error(moduleName, "Missing sesskey in otherslist") + return gpcm.ErrSearch.GetMessage() + } + + sessionKey, err := strconv.ParseInt(strSessionKey, 10, 32) + if err != nil { + logging.Error(moduleName, "Invalid sesskey:", strSessionKey) + return gpcm.ErrSearch.GetMessage() + } + + numopids, ok := command.OtherValues["numopids"] + if !ok { + logging.Error(moduleName, "Missing numopids in otherslist") + return gpcm.ErrSearch.GetMessage() + } + + opids, ok := command.OtherValues["opids"] + if !ok { + logging.Error(moduleName, "Missing opids in otherslist") + return gpcm.ErrSearch.GetMessage() + } + + gameName, ok := command.OtherValues["gamename"] + if !ok { + logging.Error(moduleName, "Missing gamename in otherslist") + return gpcm.ErrSearch.GetMessage() + } + + numOpidsValue, err := strconv.Atoi(numopids) + if err != nil { + logging.Error(moduleName, "Invalid numopids:", numopids) + return gpcm.ErrSearch.GetMessage() + } + + var opidsSplit []string + if strings.Contains(opids, "|") { + opidsSplit = strings.Split(opids, "|") + } else if opids != "" && opids != "0" { + opidsSplit = append(opidsSplit, opids) + } + + if len(opidsSplit) != numOpidsValue && opids != "0" { + logging.Error(moduleName, "Mismatch opids length with numopids:", aurora.Cyan(len(opidsSplit)), "!=", aurora.Cyan(numOpidsValue)) + return gpcm.ErrSearch.GetMessage() + } + + // Lookup profile ID using GPCM + uniqueNick, ok := gpcm.VerifyPlayerSearch(uint32(profileId), int32(sessionKey), gameName) + if !ok { + logging.Error(moduleName, "otherslist verify failed") + return gpcm.ErrSearch.GetMessage() + } + + payload := `\otherslist\` + for _, strOtherId := range opidsSplit { + payload += `\o\` + strOtherId + payload += `\uniquenick\` + uniqueNick + } + + payload += `\oldone\\final\` + return payload +}