GPCM/GPSP: Hide certain profile information

This commit is contained in:
mkwcat 2024-01-11 21:54:26 -05:00
parent 8ea725a6b3
commit 67996c08b9
No known key found for this signature in database
GPG Key ID: 7A505679CE9E7AA9
8 changed files with 200 additions and 188 deletions

View File

@ -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()
}
}

View File

@ -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)
}
}
}

View File

@ -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) {

View File

@ -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 = ""
}
}
}

View File

@ -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
}

12
gpsp/error.go Normal file
View File

@ -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()))
}

View File

@ -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
}

94
gpsp/otherslist.go Normal file
View File

@ -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
}