mirror of
https://github.com/WiiLink24/wfc-server.git
synced 2026-04-24 15:37:58 -05:00
GPCM: Add server-side OpenHost support
Co-Authored-By: Mike <99037623+MikeIsAStar@users.noreply.github.com>
This commit is contained in:
parent
bad2ae2ed7
commit
7ce6f3c5b9
|
|
@ -15,7 +15,7 @@ import (
|
|||
|
||||
var (
|
||||
ErrDeviceIDMismatch = errors.New("NG device ID mismatch")
|
||||
ErrProfileBannedTOS = errors.New("Profile is banned for violating the Terms of Service")
|
||||
ErrProfileBannedTOS = errors.New("profile is banned for violating the Terms of Service")
|
||||
)
|
||||
|
||||
func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, userId uint64, gsbrcd string, profileId uint32, ngDeviceId uint32, ipAddress string, ingamesn string) (User, error) {
|
||||
|
|
@ -48,7 +48,7 @@ func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, userId uint64, gsb
|
|||
var expectedNgId *uint32
|
||||
var firstName *string
|
||||
var lastName *string
|
||||
err := pool.QueryRow(ctx, GetUserProfileID, userId, gsbrcd).Scan(&user.ProfileId, &expectedNgId, &user.Email, &user.UniqueNick, &firstName, &lastName)
|
||||
err := pool.QueryRow(ctx, GetUserProfileID, userId, gsbrcd).Scan(&user.ProfileId, &expectedNgId, &user.Email, &user.UniqueNick, &firstName, &lastName, &user.OpenHost)
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,5 +18,6 @@ ALTER TABLE ONLY public.users
|
|||
ADD IF NOT EXISTS ban_reason_hidden character varying,
|
||||
ADD IF NOT EXISTS ban_moderator character varying,
|
||||
ADD IF NOT EXISTS ban_tos boolean
|
||||
ADD IF NOT EXISTS open_host boolean DEFAULT false
|
||||
`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,14 +12,14 @@ import (
|
|||
const (
|
||||
InsertUser = `INSERT INTO users (user_id, gsbrcd, password, ng_device_id, email, unique_nick) VALUES ($1, $2, $3, $4, $5, $6) RETURNING profile_id`
|
||||
InsertUserWithProfileID = `INSERT INTO users (profile_id, user_id, gsbrcd, password, ng_device_id, email, unique_nick) VALUES ($1, $2, $3, $4, $5, $6, $7)`
|
||||
UpdateUserTable = `UPDATE users SET firstname = CASE WHEN $3 THEN $2 ELSE firstname END, lastname = CASE WHEN $5 THEN $4 ELSE lastname END WHERE profile_id = $1`
|
||||
UpdateUserTable = `UPDATE users SET firstname = CASE WHEN $3 THEN $2 ELSE firstname END, lastname = CASE WHEN $5 THEN $4 ELSE lastname END, open_host = CASE WHEN $6 THEN $5 ELSE open_host END WHERE profile_id = $1`
|
||||
UpdateUserProfileID = `UPDATE users SET profile_id = $3 WHERE user_id = $1 AND gsbrcd = $2`
|
||||
UpdateUserNGDeviceID = `UPDATE users SET ng_device_id = $2 WHERE profile_id = $1`
|
||||
GetUser = `SELECT user_id, gsbrcd, email, unique_nick, firstname, lastname FROM users WHERE profile_id = $1`
|
||||
GetUser = `SELECT user_id, gsbrcd, email, unique_nick, firstname, lastname, open_host FROM users WHERE profile_id = $1`
|
||||
DoesUserExist = `SELECT EXISTS(SELECT 1 FROM users WHERE user_id = $1 AND gsbrcd = $2)`
|
||||
IsProfileIDInUse = `SELECT EXISTS(SELECT 1 FROM users WHERE profile_id = $1)`
|
||||
DeleteUserSession = `DELETE FROM sessions WHERE profile_id = $1`
|
||||
GetUserProfileID = `SELECT profile_id, ng_device_id, email, unique_nick, firstname, lastname FROM users WHERE user_id = $1 AND gsbrcd = $2`
|
||||
GetUserProfileID = `SELECT profile_id, ng_device_id, email, unique_nick, firstname, lastname, open_host FROM users WHERE user_id = $1 AND gsbrcd = $2`
|
||||
UpdateUserLastIPAddress = `UPDATE users SET last_ip_address = $2, last_ingamesn = $3 WHERE profile_id = $1`
|
||||
UpdateUserBan = `UPDATE users SET has_ban = true, ban_issued = $2, ban_expires = $3, ban_reason = $4, ban_reason_hidden = $5, ban_moderator = $6, ban_tos = $7 WHERE profile_id = $1`
|
||||
SearchUserBan = `SELECT has_ban, ban_tos, ng_device_id FROM users WHERE has_ban = true AND (profile_id = $1 OR ng_device_id = $2 OR last_ip_address = $3) AND (ban_expires IS NULL OR ban_expires > $4) ORDER BY ban_tos DESC LIMIT 1`
|
||||
|
|
@ -40,6 +40,7 @@ type User struct {
|
|||
LastName string
|
||||
Restricted bool
|
||||
RestrictedDeviceId uint32
|
||||
OpenHost bool
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
@ -110,8 +111,13 @@ func GetUniqueUserID() uint64 {
|
|||
func (user *User) UpdateProfile(pool *pgxpool.Pool, ctx context.Context, data map[string]string) {
|
||||
firstName, firstNameExists := data["firstname"]
|
||||
lastName, lastNameExists := data["lastname"]
|
||||
openHost, openHostExists := data["open_host"]
|
||||
openHostBool := false
|
||||
if openHostExists && openHost != "0" {
|
||||
openHostBool = true
|
||||
}
|
||||
|
||||
_, err := pool.Exec(ctx, UpdateUserTable, user.ProfileId, firstName, firstNameExists, lastName, lastNameExists)
|
||||
_, err := pool.Exec(ctx, UpdateUserTable, user.ProfileId, firstName, firstNameExists, lastName, lastNameExists, openHostBool, openHostExists)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
@ -123,12 +129,16 @@ func (user *User) UpdateProfile(pool *pgxpool.Pool, ctx context.Context, data ma
|
|||
if lastNameExists {
|
||||
user.LastName = lastName
|
||||
}
|
||||
|
||||
if openHostExists {
|
||||
user.OpenHost = openHostBool
|
||||
}
|
||||
}
|
||||
|
||||
func GetProfile(pool *pgxpool.Pool, ctx context.Context, profileId uint32) (User, bool) {
|
||||
user := User{}
|
||||
row := pool.QueryRow(ctx, GetUser, profileId)
|
||||
err := row.Scan(&user.UserId, &user.GsbrCode, &user.Email, &user.UniqueNick, &user.FirstName, &user.LastName)
|
||||
err := row.Scan(&user.UserId, &user.GsbrCode, &user.Email, &user.UniqueNick, &user.FirstName, &user.LastName, &user.OpenHost)
|
||||
if err != nil {
|
||||
return User{}, false
|
||||
}
|
||||
|
|
@ -139,20 +149,12 @@ func GetProfile(pool *pgxpool.Pool, ctx context.Context, profileId uint32) (User
|
|||
|
||||
func BanUser(pool *pgxpool.Pool, ctx context.Context, profileId uint32, tos bool, length time.Duration, reason string, reasonHidden string, moderator string) bool {
|
||||
_, err := pool.Exec(ctx, UpdateUserBan, profileId, time.Now(), time.Now().Add(length), reason, reasonHidden, moderator, tos)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func UnbanUser(pool *pgxpool.Pool, ctx context.Context, profileId uint32) bool {
|
||||
_, err := pool.Exec(ctx, DisableUserBan, profileId)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func GetMKWFriendInfo(pool *pgxpool.Pool, ctx context.Context, profileId uint32) string {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"wwfc/common"
|
||||
"wwfc/logging"
|
||||
)
|
||||
|
||||
|
|
@ -237,6 +238,8 @@ func printHex(data []byte) {
|
|||
}
|
||||
|
||||
func verifyECDSA(publicKey []byte, signature []byte, hash []byte) bool {
|
||||
common.UNUSED(printHex)
|
||||
|
||||
r := big.NewInt(0).SetBytes(signature[0x00:0x1E])
|
||||
s := big.NewInt(0).SetBytes(signature[0x1E:0x3C])
|
||||
|
||||
|
|
|
|||
442
gpcm/friend.go
442
gpcm/friend.go
|
|
@ -1,10 +1,7 @@
|
|||
package gpcm
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"wwfc/common"
|
||||
|
|
@ -120,7 +117,7 @@ func (g *GameSpySession) addFriend(command common.GameSpyCommand) {
|
|||
return
|
||||
}
|
||||
|
||||
if !newSession.isFriendAdded(g.User.ProfileId) {
|
||||
if !newSession.User.OpenHost && !newSession.isFriendAdded(g.User.ProfileId) {
|
||||
// Not an error, just ignore for now
|
||||
logging.Info(g.ModuleName, "Destination has not added sender")
|
||||
return
|
||||
|
|
@ -133,7 +130,9 @@ func (g *GameSpySession) addFriend(command common.GameSpyCommand) {
|
|||
|
||||
// Send friend auth message
|
||||
sendMessageToSessionBuffer("4", newSession.User.ProfileId, g, "")
|
||||
sendMessageToSession("4", g.User.ProfileId, newSession, "")
|
||||
if newSession.isFriendAdded(g.User.ProfileId) {
|
||||
sendMessageToSession("4", g.User.ProfileId, newSession, "")
|
||||
}
|
||||
|
||||
g.exchangeFriendStatus(uint32(newProfileId))
|
||||
}
|
||||
|
|
@ -151,23 +150,21 @@ func (g *GameSpySession) removeFriend(command common.GameSpyCommand) {
|
|||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
if !g.isFriendAdded(delProfileID32) {
|
||||
logging.Error(g.ModuleName, aurora.Cyan(strDelProfileID), "is not a friend")
|
||||
g.replyError(ErrDeleteFriendNotFriends)
|
||||
return
|
||||
}
|
||||
if !g.isFriendAuthorized(delProfileID32) {
|
||||
logging.Error(g.ModuleName, aurora.Cyan(strDelProfileID), "is not an authorized friend")
|
||||
g.replyError(ErrDeleteFriendNotFriends)
|
||||
return
|
||||
if g.isFriendAdded(delProfileID32) {
|
||||
delProfileIDIndex := g.getFriendIndex(delProfileID32)
|
||||
removeFromUint32Array(&g.FriendList, delProfileIDIndex)
|
||||
}
|
||||
|
||||
delProfileIDIndex := g.getFriendIndex(delProfileID32)
|
||||
removeFromUint32Array(&g.FriendList, delProfileIDIndex)
|
||||
delProfileIDIndex = g.getAuthorizedFriendIndex(delProfileID32)
|
||||
removeFromUint32Array(&g.AuthFriendList, delProfileIDIndex)
|
||||
if !g.User.OpenHost {
|
||||
if g.isFriendAuthorized(delProfileID32) {
|
||||
delProfileIDIndex := g.getAuthorizedFriendIndex(delProfileID32)
|
||||
removeFromUint32Array(&g.AuthFriendList, delProfileIDIndex)
|
||||
}
|
||||
|
||||
sendMessageToProfileId("100", g.User.ProfileId, delProfileID32, logOutMessage)
|
||||
if session, ok := sessions[delProfileID32]; ok && session.LoggedIn && session.isFriendAuthorized(g.User.ProfileId) {
|
||||
sendMessageToSession("100", g.User.ProfileId, session, logOutMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GameSpySession) authAddFriend(command common.GameSpyCommand) {
|
||||
|
|
@ -227,332 +224,6 @@ func (g *GameSpySession) setStatus(command common.GameSpyCommand) {
|
|||
}
|
||||
}
|
||||
|
||||
const (
|
||||
resvDenyVer3 = "GPCM3vMAT\x0316"
|
||||
resvDenyVer11 = "GPCM11vMAT\x0300000010"
|
||||
resvDenyVer90 = "GPCM90vMAT\x03EAAAAA**"
|
||||
|
||||
resvWaitVer3 = "GPCM3vMAT\x04"
|
||||
resvWaitVer11 = "GPCM11vMAT\x04"
|
||||
resvWaitVer90 = "GPCM90vMAT\x04"
|
||||
)
|
||||
|
||||
func (g *GameSpySession) bestieMessage(command common.GameSpyCommand) {
|
||||
// TODO: There are other command values that mean the same thing
|
||||
if command.CommandValue != "1" {
|
||||
logging.Error(g.ModuleName, "Received unknown bestie message type:", aurora.Cyan(command.CommandValue))
|
||||
return
|
||||
}
|
||||
|
||||
strToProfileId := command.OtherValues["t"]
|
||||
toProfileId, err := strconv.ParseUint(strToProfileId, 10, 32)
|
||||
if err != nil {
|
||||
logging.Error(g.ModuleName, "Invalid profile ID string:", aurora.Cyan(strToProfileId))
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
if !g.isFriendAdded(uint32(toProfileId)) {
|
||||
logging.Error(g.ModuleName, "Destination", aurora.Cyan(toProfileId), "is not even on sender's friend list")
|
||||
g.replyError(ErrMessageNotFriends)
|
||||
return
|
||||
}
|
||||
|
||||
msg, ok := command.OtherValues["msg"]
|
||||
if !ok || msg == "" {
|
||||
logging.Error(g.ModuleName, "Missing message value")
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
// Parse message for security and room tracking purposes
|
||||
var version int
|
||||
var msgDataIndex int
|
||||
var resvDenyMsg string
|
||||
var resvWaitMsg string
|
||||
|
||||
if strings.HasPrefix(msg, "GPCM3vMAT") {
|
||||
version = 3
|
||||
resvDenyMsg = resvDenyVer3
|
||||
resvWaitMsg = resvWaitVer3
|
||||
msgDataIndex = 9
|
||||
} else if strings.HasPrefix(msg, "GPCM11vMAT") {
|
||||
// Only used for Brawl
|
||||
version = 11
|
||||
resvDenyMsg = resvDenyVer11
|
||||
resvWaitMsg = resvWaitVer11
|
||||
msgDataIndex = 10
|
||||
} else if strings.HasPrefix(msg, "GPCM90vMAT") {
|
||||
version = 90
|
||||
resvDenyMsg = resvDenyVer90
|
||||
resvWaitMsg = resvWaitVer90
|
||||
msgDataIndex = 10
|
||||
} else {
|
||||
logging.Error(g.ModuleName, "Invalid message prefix; message:", msg)
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
if !g.DeviceAuthenticated {
|
||||
logging.Notice(g.ModuleName, "Sender is not device authenticated yet")
|
||||
// g.replyError(ErrMessage)
|
||||
sendMessageToSession("1", uint32(toProfileId), g, resvWaitMsg)
|
||||
return
|
||||
}
|
||||
|
||||
if len(msg) < msgDataIndex+1 {
|
||||
logging.Error(g.ModuleName, "Invalid message length; message:", msg)
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
cmd := msg[msgDataIndex]
|
||||
msgDataIndex++
|
||||
|
||||
var msgData []byte
|
||||
|
||||
switch version {
|
||||
case 3:
|
||||
if len(msg) == msgDataIndex {
|
||||
break
|
||||
}
|
||||
|
||||
for _, stringValue := range strings.Split(msg[msgDataIndex:], "/") {
|
||||
intValue, err := strconv.ParseUint(stringValue, 10, 32)
|
||||
if err != nil {
|
||||
logging.Error(g.ModuleName, "Invalid message value; message:", msg)
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
msgData = binary.LittleEndian.AppendUint32(msgData, uint32(intValue))
|
||||
}
|
||||
|
||||
case 11:
|
||||
if len(msg) == msgDataIndex {
|
||||
break
|
||||
}
|
||||
|
||||
for _, stringValue := range strings.Split(msg[msgDataIndex:], "/") {
|
||||
byteValue, err := hex.DecodeString(stringValue)
|
||||
if err != nil || len(byteValue) != 4 {
|
||||
logging.Error(g.ModuleName, "Invalid message value; message:", msg)
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
msgData = append(msgData, byteValue...)
|
||||
}
|
||||
|
||||
case 90:
|
||||
if len(msg) == msgDataIndex {
|
||||
break
|
||||
}
|
||||
|
||||
msgData, err = common.Base64DwcEncoding.DecodeString(msg[msgDataIndex:])
|
||||
if err != nil {
|
||||
logging.Error(g.ModuleName, "Invalid message base64 data; message:", msg)
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
default:
|
||||
logging.Error(g.ModuleName, "Invalid message version; message:", msg)
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
if len(msgData) > 0x200 || (len(msgData)&3) != 0 {
|
||||
logging.Error(g.ModuleName, "Invalid length message data; message:", msg)
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
msgMatchData, ok := common.DecodeMatchCommand(cmd, msgData, version)
|
||||
common.LogMatchCommand(g.ModuleName, strconv.FormatInt(int64(toProfileId), 10), cmd, msgMatchData)
|
||||
if !ok {
|
||||
logging.Error(g.ModuleName, "Invalid match command data; message:", msg)
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
if cmd == common.MatchReservation {
|
||||
if common.IPFormatNoPortToInt(g.Conn.RemoteAddr().String()) != int32(msgMatchData.Reservation.PublicIP) {
|
||||
logging.Error(g.ModuleName, "RESERVATION: Public IP mismatch")
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
g.QR2IP = uint64(msgMatchData.Reservation.PublicIP) | (uint64(msgMatchData.Reservation.PublicPort) << 32)
|
||||
|
||||
} else if cmd == common.MatchResvOK {
|
||||
if common.IPFormatNoPortToInt(g.Conn.RemoteAddr().String()) != int32(msgMatchData.ResvOK.PublicIP) {
|
||||
logging.Error(g.ModuleName, "RESV_OK: Public IP mismatch")
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
g.QR2IP = uint64(msgMatchData.ResvOK.PublicIP) | (uint64(msgMatchData.ResvOK.PublicPort) << 32)
|
||||
}
|
||||
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
var toSession *GameSpySession
|
||||
if toSession, ok = sessions[uint32(toProfileId)]; !ok || !toSession.LoggedIn {
|
||||
logging.Error(g.ModuleName, "Destination", aurora.Cyan(toProfileId), "is not online")
|
||||
// g.replyError(ErrMessageFriendOffline)
|
||||
sendMessageToSession("1", uint32(toProfileId), g, resvDenyMsg)
|
||||
return
|
||||
}
|
||||
|
||||
if toSession.GameName != g.GameName {
|
||||
logging.Error(g.ModuleName, "Destination", aurora.Cyan(toProfileId), "is not playing the same game")
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
if !toSession.isFriendAdded(g.User.ProfileId) {
|
||||
logging.Error(g.ModuleName, "Destination", aurora.Cyan(toProfileId), "is not friends with sender")
|
||||
g.replyError(ErrMessageNotFriends)
|
||||
return
|
||||
}
|
||||
|
||||
if !toSession.DeviceAuthenticated {
|
||||
logging.Error(g.ModuleName, "Destination", aurora.Cyan(toProfileId), "is not device authenticated")
|
||||
sendMessageToSession("1", uint32(toProfileId), g, resvDenyMsg)
|
||||
return
|
||||
}
|
||||
|
||||
sameAddress := strings.Split(g.Conn.RemoteAddr().String(), ":")[0] == strings.Split(toSession.Conn.RemoteAddr().String(), ":")[0]
|
||||
|
||||
if cmd == common.MatchReservation {
|
||||
if g.QR2IP == 0 {
|
||||
logging.Error(g.ModuleName, "Missing QR2 IP")
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
if g.User.Restricted || toSession.User.Restricted {
|
||||
// Check with QR2 if the room is public or private
|
||||
resvError := qr2.CheckGPReservationAllowed(g.QR2IP, g.User.ProfileId, uint32(toProfileId), msgMatchData.Reservation.MatchType)
|
||||
if resvError != "ok" {
|
||||
if resvError == "restricted" || resvError == "restricted_join" {
|
||||
logging.Error(g.ModuleName, "RESERVATION: Restricted user tried to connect to public room")
|
||||
|
||||
// Kick the player(s)
|
||||
if g.User.Restricted {
|
||||
kickPlayer(toSession.User.ProfileId, resvError)
|
||||
}
|
||||
if toSession.User.Restricted {
|
||||
kickPlayer(g.User.ProfileId, resvError)
|
||||
}
|
||||
}
|
||||
// Otherwise generic error?
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if !sameAddress {
|
||||
searchId := qr2.GetSearchID(g.QR2IP)
|
||||
msgMatchData.Reservation.PublicIP = uint32(searchId & 0xffffffff)
|
||||
msgMatchData.Reservation.PublicPort = uint16(searchId >> 32)
|
||||
msgMatchData.Reservation.LocalIP = 0
|
||||
msgMatchData.Reservation.LocalPort = 0
|
||||
}
|
||||
} 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")
|
||||
// Allow the message through anyway to avoid a room deadlock
|
||||
}
|
||||
|
||||
if toSession.Reservation.Version != msgMatchData.Version {
|
||||
logging.Error(g.ModuleName, "Reservation version mismatch")
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
if cmd == common.MatchResvOK {
|
||||
if g.QR2IP == 0 || toSession.QR2IP == 0 {
|
||||
logging.Error(g.ModuleName, "Missing QR2 IP")
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
if !qr2.ProcessGPResvOK(msgMatchData.Version, *toSession.Reservation.Reservation, *msgMatchData.ResvOK, g.QR2IP, g.User.ProfileId, toSession.QR2IP, uint32(toProfileId)) {
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
if !sameAddress {
|
||||
searchId := qr2.GetSearchID(g.QR2IP)
|
||||
if searchId == 0 {
|
||||
logging.Error(g.ModuleName, "Could not get QR2 search ID for IP", aurora.Cyan(fmt.Sprintf("%016x", g.QR2IP)))
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
msgMatchData.ResvOK.PublicIP = uint32(searchId & 0xffffffff)
|
||||
msgMatchData.ResvOK.PublicPort = uint16(searchId >> 32)
|
||||
}
|
||||
} else if toSession.ReservationPID == g.User.ProfileId {
|
||||
toSession.ReservationPID = 0
|
||||
}
|
||||
} else if cmd == common.MatchTellAddr {
|
||||
if g.QR2IP == 0 || toSession.QR2IP == 0 {
|
||||
logging.Error(g.ModuleName, "Missing QR2 IP")
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
qr2.ProcessGPTellAddr(g.User.ProfileId, g.QR2IP, toSession.User.ProfileId, toSession.QR2IP)
|
||||
}
|
||||
|
||||
newMsg, ok := common.EncodeMatchCommand(cmd, msgMatchData)
|
||||
if !ok || len(newMsg) > 0x200 || (len(newMsg)%4) != 0 {
|
||||
logging.Error(g.ModuleName, "Failed to encode match command; message:", msg)
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
if cmd == common.MatchReservation {
|
||||
g.Reservation = msgMatchData
|
||||
g.ReservationPID = uint32(toProfileId)
|
||||
}
|
||||
|
||||
var newMsgStr string
|
||||
|
||||
// Re-encode the new message
|
||||
switch version {
|
||||
case 3:
|
||||
newMsgStr = "GPCM3vMAT" + string(cmd)
|
||||
|
||||
for i := 0; i < len(newMsg); i += 4 {
|
||||
if i > 0 {
|
||||
newMsgStr += "/"
|
||||
}
|
||||
|
||||
newMsgStr += strconv.FormatUint(uint64(binary.LittleEndian.Uint32(newMsg[i:])), 10)
|
||||
}
|
||||
|
||||
case 11:
|
||||
newMsgStr = "GPCM11vMAT" + string(cmd)
|
||||
|
||||
for i := 0; i < len(newMsg); i += 4 {
|
||||
if i > 0 {
|
||||
newMsgStr += "/"
|
||||
}
|
||||
|
||||
newMsgStr += hex.EncodeToString(newMsg[i : i+4])
|
||||
}
|
||||
|
||||
case 90:
|
||||
newMsgStr = "GPCM90vMAT" + string(cmd) + common.Base64DwcEncoding.EncodeToString(newMsg)
|
||||
}
|
||||
|
||||
sendMessageToSession("1", g.User.ProfileId, toSession, newMsgStr)
|
||||
}
|
||||
|
||||
func sendMessageToSession(msgType string, from uint32, session *GameSpySession, msg string) {
|
||||
message := common.CreateGameSpyMessage(common.GameSpyCommand{
|
||||
Command: "bm",
|
||||
|
|
@ -587,9 +258,30 @@ func sendMessageToProfileId(msgType string, from uint32, to uint32, msg string)
|
|||
}
|
||||
|
||||
func (g *GameSpySession) sendFriendStatus(profileId uint32) {
|
||||
if g.isFriendAdded(profileId) {
|
||||
if session, ok := sessions[profileId]; ok && session.LoggedIn && session.isFriendAdded(g.User.ProfileId) {
|
||||
// Prevent players abusing a stack overflow exploit with the locstring in Mario Kart Wii
|
||||
common.UNUSED(sendMessageToProfileId)
|
||||
|
||||
if !g.isFriendAdded(profileId) {
|
||||
return
|
||||
}
|
||||
|
||||
if session, ok := sessions[profileId]; ok && session.LoggedIn && session.isFriendAdded(g.User.ProfileId) {
|
||||
// Prevent players abusing a stack overflow exploit with the locstring in Mario Kart Wii
|
||||
if session.NeedsExploit && strings.HasPrefix(session.GameCode, "RMC") && len(g.LocString) > 0x14 {
|
||||
logging.Warn("GPCM", "Blocked message from", aurora.Cyan(g.User.ProfileId), "to", aurora.Cyan(session.User.ProfileId), "due to a stack overflow exploit")
|
||||
return
|
||||
}
|
||||
|
||||
sendMessageToSession("100", g.User.ProfileId, session, g.Status)
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GameSpySession) exchangeFriendStatus(profileId uint32) {
|
||||
if !g.isFriendAdded(profileId) {
|
||||
return
|
||||
}
|
||||
|
||||
if session, ok := sessions[profileId]; ok && session.LoggedIn {
|
||||
if session.isFriendAdded(g.User.ProfileId) {
|
||||
if session.NeedsExploit && strings.HasPrefix(session.GameCode, "RMC") && len(g.LocString) > 0x14 {
|
||||
logging.Warn("GPCM", "Blocked message from", aurora.Cyan(g.User.ProfileId), "to", aurora.Cyan(session.User.ProfileId), "due to a stack overflow exploit")
|
||||
return
|
||||
|
|
@ -597,22 +289,58 @@ func (g *GameSpySession) sendFriendStatus(profileId uint32) {
|
|||
|
||||
sendMessageToSession("100", g.User.ProfileId, session, g.Status)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
sendMessageToSessionBuffer("100", profileId, g, session.Status)
|
||||
if g.NeedsExploit && strings.HasPrefix(g.GameCode, "RMC") && len(session.LocString) > 0x14 {
|
||||
logging.Warn("GPCM", "Blocked message from", aurora.Cyan(session.User.ProfileId), "to", aurora.Cyan(g.User.ProfileId), "due to a stack overflow exploit")
|
||||
return
|
||||
}
|
||||
|
||||
sendMessageToSessionBuffer("100", profileId, g, session.Status)
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GameSpySession) sendLogoutStatus() {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
for _, storedPid := range g.AuthFriendList {
|
||||
sendMessageToProfileId("100", g.User.ProfileId, storedPid, logOutMessage)
|
||||
if session, ok := sessions[storedPid]; ok && session.LoggedIn && session.isFriendAuthorized(g.User.ProfileId) {
|
||||
delProfileIDIndex := session.getAuthorizedFriendIndex(g.User.ProfileId)
|
||||
removeFromUint32Array(&session.AuthFriendList, delProfileIDIndex)
|
||||
sendMessageToSession("100", g.User.ProfileId, session, logOutMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GameSpySession) openHostEnabled() {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
for _, session := range sessions {
|
||||
if session.LoggedIn && session.isFriendAdded(g.User.ProfileId) && !session.isFriendAuthorized(g.User.ProfileId) {
|
||||
session.AuthFriendList = append(session.AuthFriendList, g.User.ProfileId)
|
||||
g.AuthFriendList = append(g.AuthFriendList, session.User.ProfileId)
|
||||
session.exchangeFriendStatus(g.User.ProfileId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GameSpySession) openHostDisabled() {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
for _, id := range g.AuthFriendList {
|
||||
if g.isFriendAdded(id) {
|
||||
return
|
||||
}
|
||||
|
||||
delProfileIDIndex := g.getAuthorizedFriendIndex(id)
|
||||
removeFromUint32Array(&g.AuthFriendList, delProfileIDIndex)
|
||||
|
||||
if session, ok := sessions[id]; ok && session.LoggedIn && session.isFriendAuthorized(g.User.ProfileId) {
|
||||
delProfileIDIndex := session.getAuthorizedFriendIndex(g.User.ProfileId)
|
||||
removeFromUint32Array(&session.AuthFriendList, delProfileIDIndex)
|
||||
sendMessageToSession("100", g.User.ProfileId, session, logOutMessage)
|
||||
}
|
||||
}
|
||||
mutex.Unlock()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -332,6 +332,10 @@ func (g *GameSpySession) login(command common.GameSpyCommand) {
|
|||
|
||||
g.Conn.Write([]byte(payload))
|
||||
|
||||
if g.User.OpenHost {
|
||||
g.openHostEnabled()
|
||||
}
|
||||
|
||||
// Now start sending keep alive packets every 5 minutes
|
||||
go func() {
|
||||
for {
|
||||
|
|
|
|||
334
gpcm/message.go
Normal file
334
gpcm/message.go
Normal file
|
|
@ -0,0 +1,334 @@
|
|||
package gpcm
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"wwfc/common"
|
||||
"wwfc/logging"
|
||||
"wwfc/qr2"
|
||||
|
||||
"github.com/logrusorgru/aurora/v3"
|
||||
)
|
||||
|
||||
const (
|
||||
resvDenyVer3 = "GPCM3vMAT\x0316"
|
||||
resvDenyVer11 = "GPCM11vMAT\x0300000010"
|
||||
resvDenyVer90 = "GPCM90vMAT\x03EAAAAA**"
|
||||
|
||||
resvWaitVer3 = "GPCM3vMAT\x04"
|
||||
resvWaitVer11 = "GPCM11vMAT\x04"
|
||||
resvWaitVer90 = "GPCM90vMAT\x04"
|
||||
)
|
||||
|
||||
func (g *GameSpySession) bestieMessage(command common.GameSpyCommand) {
|
||||
// TODO: There are other command values that mean the same thing
|
||||
if command.CommandValue != "1" {
|
||||
logging.Error(g.ModuleName, "Received unknown bestie message type:", aurora.Cyan(command.CommandValue))
|
||||
return
|
||||
}
|
||||
|
||||
strToProfileId := command.OtherValues["t"]
|
||||
toProfileId, err := strconv.ParseUint(strToProfileId, 10, 32)
|
||||
if err != nil {
|
||||
logging.Error(g.ModuleName, "Invalid profile ID string:", aurora.Cyan(strToProfileId))
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
if !g.isFriendAuthorized(uint32(toProfileId)) {
|
||||
logging.Error(g.ModuleName, "Destination", aurora.Cyan(toProfileId), "is not even on sender's friend list")
|
||||
g.replyError(ErrMessageNotFriends)
|
||||
return
|
||||
}
|
||||
|
||||
msg, ok := command.OtherValues["msg"]
|
||||
if !ok || msg == "" {
|
||||
logging.Error(g.ModuleName, "Missing message value")
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
// Parse message for security and room tracking purposes
|
||||
var version int
|
||||
var msgDataIndex int
|
||||
var resvDenyMsg string
|
||||
var resvWaitMsg string
|
||||
|
||||
if strings.HasPrefix(msg, "GPCM3vMAT") {
|
||||
version = 3
|
||||
resvDenyMsg = resvDenyVer3
|
||||
resvWaitMsg = resvWaitVer3
|
||||
msgDataIndex = 9
|
||||
} else if strings.HasPrefix(msg, "GPCM11vMAT") {
|
||||
// Only used for Brawl
|
||||
version = 11
|
||||
resvDenyMsg = resvDenyVer11
|
||||
resvWaitMsg = resvWaitVer11
|
||||
msgDataIndex = 10
|
||||
} else if strings.HasPrefix(msg, "GPCM90vMAT") {
|
||||
version = 90
|
||||
resvDenyMsg = resvDenyVer90
|
||||
resvWaitMsg = resvWaitVer90
|
||||
msgDataIndex = 10
|
||||
} else {
|
||||
logging.Error(g.ModuleName, "Invalid message prefix; message:", msg)
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
if !g.DeviceAuthenticated {
|
||||
logging.Notice(g.ModuleName, "Sender is not device authenticated yet")
|
||||
// g.replyError(ErrMessage)
|
||||
sendMessageToSessionBuffer("1", uint32(toProfileId), g, resvWaitMsg)
|
||||
return
|
||||
}
|
||||
|
||||
if len(msg) < msgDataIndex+1 {
|
||||
logging.Error(g.ModuleName, "Invalid message length; message:", msg)
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
cmd := msg[msgDataIndex]
|
||||
msgDataIndex++
|
||||
|
||||
var msgData []byte
|
||||
|
||||
switch version {
|
||||
case 3:
|
||||
if len(msg) == msgDataIndex {
|
||||
break
|
||||
}
|
||||
|
||||
for _, stringValue := range strings.Split(msg[msgDataIndex:], "/") {
|
||||
intValue, err := strconv.ParseUint(stringValue, 10, 32)
|
||||
if err != nil {
|
||||
logging.Error(g.ModuleName, "Invalid message value; message:", msg)
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
msgData = binary.LittleEndian.AppendUint32(msgData, uint32(intValue))
|
||||
}
|
||||
|
||||
case 11:
|
||||
if len(msg) == msgDataIndex {
|
||||
break
|
||||
}
|
||||
|
||||
for _, stringValue := range strings.Split(msg[msgDataIndex:], "/") {
|
||||
byteValue, err := hex.DecodeString(stringValue)
|
||||
if err != nil || len(byteValue) != 4 {
|
||||
logging.Error(g.ModuleName, "Invalid message value; message:", msg)
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
msgData = append(msgData, byteValue...)
|
||||
}
|
||||
|
||||
case 90:
|
||||
if len(msg) == msgDataIndex {
|
||||
break
|
||||
}
|
||||
|
||||
msgData, err = common.Base64DwcEncoding.DecodeString(msg[msgDataIndex:])
|
||||
if err != nil {
|
||||
logging.Error(g.ModuleName, "Invalid message base64 data; message:", msg)
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
default:
|
||||
logging.Error(g.ModuleName, "Invalid message version; message:", msg)
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
if len(msgData) > 0x200 || (len(msgData)&3) != 0 {
|
||||
logging.Error(g.ModuleName, "Invalid length message data; message:", msg)
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
msgMatchData, ok := common.DecodeMatchCommand(cmd, msgData, version)
|
||||
common.LogMatchCommand(g.ModuleName, strconv.FormatInt(int64(toProfileId), 10), cmd, msgMatchData)
|
||||
if !ok {
|
||||
logging.Error(g.ModuleName, "Invalid match command data; message:", msg)
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
if cmd == common.MatchReservation {
|
||||
if common.IPFormatNoPortToInt(g.Conn.RemoteAddr().String()) != int32(msgMatchData.Reservation.PublicIP) {
|
||||
logging.Error(g.ModuleName, "RESERVATION: Public IP mismatch")
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
g.QR2IP = uint64(msgMatchData.Reservation.PublicIP) | (uint64(msgMatchData.Reservation.PublicPort) << 32)
|
||||
|
||||
} else if cmd == common.MatchResvOK {
|
||||
if common.IPFormatNoPortToInt(g.Conn.RemoteAddr().String()) != int32(msgMatchData.ResvOK.PublicIP) {
|
||||
logging.Error(g.ModuleName, "RESV_OK: Public IP mismatch")
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
g.QR2IP = uint64(msgMatchData.ResvOK.PublicIP) | (uint64(msgMatchData.ResvOK.PublicPort) << 32)
|
||||
}
|
||||
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
var toSession *GameSpySession
|
||||
if toSession, ok = sessions[uint32(toProfileId)]; !ok || !toSession.LoggedIn {
|
||||
logging.Error(g.ModuleName, "Destination", aurora.Cyan(toProfileId), "is not online")
|
||||
// g.replyError(ErrMessageFriendOffline)
|
||||
sendMessageToSession("1", uint32(toProfileId), g, resvDenyMsg)
|
||||
return
|
||||
}
|
||||
|
||||
if toSession.GameName != g.GameName {
|
||||
logging.Error(g.ModuleName, "Destination", aurora.Cyan(toProfileId), "is not playing the same game")
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
if !toSession.DeviceAuthenticated {
|
||||
logging.Error(g.ModuleName, "Destination", aurora.Cyan(toProfileId), "is not device authenticated")
|
||||
sendMessageToSession("1", uint32(toProfileId), g, resvDenyMsg)
|
||||
return
|
||||
}
|
||||
|
||||
sameAddress := strings.Split(g.Conn.RemoteAddr().String(), ":")[0] == strings.Split(toSession.Conn.RemoteAddr().String(), ":")[0]
|
||||
|
||||
if cmd == common.MatchReservation {
|
||||
if g.QR2IP == 0 {
|
||||
logging.Error(g.ModuleName, "Missing QR2 IP")
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
if g.User.Restricted || toSession.User.Restricted {
|
||||
// Check with QR2 if the room is public or private
|
||||
resvError := qr2.CheckGPReservationAllowed(g.QR2IP, g.User.ProfileId, uint32(toProfileId), msgMatchData.Reservation.MatchType)
|
||||
if resvError != "ok" {
|
||||
if resvError == "restricted" || resvError == "restricted_join" {
|
||||
logging.Error(g.ModuleName, "RESERVATION: Restricted user tried to connect to public room")
|
||||
|
||||
// Kick the player(s)
|
||||
if g.User.Restricted {
|
||||
kickPlayer(toSession.User.ProfileId, resvError)
|
||||
}
|
||||
if toSession.User.Restricted {
|
||||
kickPlayer(g.User.ProfileId, resvError)
|
||||
}
|
||||
}
|
||||
// Otherwise generic error?
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if !sameAddress {
|
||||
searchId := qr2.GetSearchID(g.QR2IP)
|
||||
msgMatchData.Reservation.PublicIP = uint32(searchId & 0xffffffff)
|
||||
msgMatchData.Reservation.PublicPort = uint16(searchId >> 32)
|
||||
msgMatchData.Reservation.LocalIP = 0
|
||||
msgMatchData.Reservation.LocalPort = 0
|
||||
}
|
||||
} 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")
|
||||
// Allow the message through anyway to avoid a room deadlock
|
||||
}
|
||||
|
||||
if toSession.Reservation.Version != msgMatchData.Version {
|
||||
logging.Error(g.ModuleName, "Reservation version mismatch")
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
if cmd == common.MatchResvOK {
|
||||
if g.QR2IP == 0 || toSession.QR2IP == 0 {
|
||||
logging.Error(g.ModuleName, "Missing QR2 IP")
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
if !qr2.ProcessGPResvOK(msgMatchData.Version, *toSession.Reservation.Reservation, *msgMatchData.ResvOK, g.QR2IP, g.User.ProfileId, toSession.QR2IP, uint32(toProfileId)) {
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
if !sameAddress {
|
||||
searchId := qr2.GetSearchID(g.QR2IP)
|
||||
if searchId == 0 {
|
||||
logging.Error(g.ModuleName, "Could not get QR2 search ID for IP", aurora.Cyan(fmt.Sprintf("%016x", g.QR2IP)))
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
msgMatchData.ResvOK.PublicIP = uint32(searchId & 0xffffffff)
|
||||
msgMatchData.ResvOK.PublicPort = uint16(searchId >> 32)
|
||||
}
|
||||
} else if toSession.ReservationPID == g.User.ProfileId {
|
||||
toSession.ReservationPID = 0
|
||||
}
|
||||
} else if cmd == common.MatchTellAddr {
|
||||
if g.QR2IP == 0 || toSession.QR2IP == 0 {
|
||||
logging.Error(g.ModuleName, "Missing QR2 IP")
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
qr2.ProcessGPTellAddr(g.User.ProfileId, g.QR2IP, toSession.User.ProfileId, toSession.QR2IP)
|
||||
}
|
||||
|
||||
newMsg, ok := common.EncodeMatchCommand(cmd, msgMatchData)
|
||||
if !ok || len(newMsg) > 0x200 || (len(newMsg)%4) != 0 {
|
||||
logging.Error(g.ModuleName, "Failed to encode match command; message:", msg)
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
if cmd == common.MatchReservation {
|
||||
g.Reservation = msgMatchData
|
||||
g.ReservationPID = uint32(toProfileId)
|
||||
}
|
||||
|
||||
var newMsgStr string
|
||||
|
||||
// Re-encode the new message
|
||||
switch version {
|
||||
case 3:
|
||||
newMsgStr = "GPCM3vMAT" + string(cmd)
|
||||
|
||||
for i := 0; i < len(newMsg); i += 4 {
|
||||
if i > 0 {
|
||||
newMsgStr += "/"
|
||||
}
|
||||
|
||||
newMsgStr += strconv.FormatUint(uint64(binary.LittleEndian.Uint32(newMsg[i:])), 10)
|
||||
}
|
||||
|
||||
case 11:
|
||||
newMsgStr = "GPCM11vMAT" + string(cmd)
|
||||
|
||||
for i := 0; i < len(newMsg); i += 4 {
|
||||
if i > 0 {
|
||||
newMsgStr += "/"
|
||||
}
|
||||
|
||||
newMsgStr += hex.EncodeToString(newMsg[i : i+4])
|
||||
}
|
||||
|
||||
case 90:
|
||||
newMsgStr = "GPCM90vMAT" + string(cmd) + common.Base64DwcEncoding.EncodeToString(newMsg)
|
||||
}
|
||||
|
||||
sendMessageToSession("1", g.User.ProfileId, toSession, newMsgStr)
|
||||
}
|
||||
|
|
@ -82,6 +82,15 @@ func (g *GameSpySession) getProfile(command common.GameSpyCommand) {
|
|||
}
|
||||
|
||||
func (g *GameSpySession) updateProfile(command common.GameSpyCommand) {
|
||||
if openHost, ok := command.OtherValues["open_host"]; ok {
|
||||
enabled := openHost != "0"
|
||||
if !g.User.OpenHost && enabled {
|
||||
g.openHostEnabled()
|
||||
} else if g.User.OpenHost && !enabled {
|
||||
g.openHostDisabled()
|
||||
}
|
||||
}
|
||||
|
||||
g.User.UpdateProfile(pool, ctx, command.OtherValues)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user