mirror of
https://github.com/WiiLink24/wfc-server.git
synced 2026-04-24 23:47:50 -05:00
377 lines
11 KiB
Go
377 lines
11 KiB
Go
package gpcm
|
|
|
|
import (
|
|
"errors"
|
|
"strconv"
|
|
"strings"
|
|
"wwfc/common"
|
|
"wwfc/logging"
|
|
"wwfc/qr2"
|
|
|
|
"github.com/logrusorgru/aurora/v3"
|
|
)
|
|
|
|
func removeFromUint32Array(arrayPointer *[]uint32, index int) error {
|
|
array := *arrayPointer
|
|
arrayLength := len(array)
|
|
|
|
if index < 0 || index >= arrayLength {
|
|
return errors.New("index is out of bounds")
|
|
}
|
|
|
|
lastIndex := arrayLength - 1
|
|
|
|
array[index] = array[lastIndex]
|
|
*arrayPointer = array[:lastIndex]
|
|
return nil
|
|
}
|
|
|
|
func (g *GameSpySession) isFriendAdded(profileId uint32) bool {
|
|
for _, storedPid := range g.FriendList {
|
|
if storedPid == profileId {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (g *GameSpySession) getFriendIndex(profileId uint32) int {
|
|
for i, storedPid := range g.FriendList {
|
|
if storedPid == profileId {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func (g *GameSpySession) isFriendAuthorized(profileId uint32) bool {
|
|
for _, storedPid := range g.AuthFriendList {
|
|
if storedPid == profileId {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (g *GameSpySession) getAuthorizedFriendIndex(profileId uint32) int {
|
|
for i, storedPid := range g.AuthFriendList {
|
|
if storedPid == profileId {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
const (
|
|
// 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"
|
|
)
|
|
|
|
func (g *GameSpySession) addFriend(command common.GameSpyCommand) {
|
|
strNewProfileId := command.OtherValues["newprofileid"]
|
|
newProfileId, err := strconv.ParseUint(strNewProfileId, 10, 32)
|
|
if err != nil {
|
|
g.replyError(ErrAddFriend)
|
|
return
|
|
}
|
|
|
|
if newProfileId == uint64(g.User.ProfileId) {
|
|
logging.Error(g.ModuleName, "Attempt to add self as friend")
|
|
g.replyError(ErrAddFriendBadNew)
|
|
return
|
|
}
|
|
|
|
fc := common.CalcFriendCodeString(uint32(newProfileId), g.User.GsbrCode[:4])
|
|
logging.Info(g.ModuleName, "Add friend:", aurora.Cyan(strNewProfileId), aurora.Cyan(fc))
|
|
|
|
mutex.Lock()
|
|
defer mutex.Unlock()
|
|
|
|
authorized := g.isFriendAuthorized(uint32(newProfileId))
|
|
if !g.User.OpenHost && authorized {
|
|
logging.Info(g.ModuleName, "Attempt to add a friend who is already authorized")
|
|
// This seems to always happen, do we need to return an error?
|
|
// DWC vocally ignores the error anyway, so let's not bother
|
|
// g.replyError(ErrAddFriendAlreadyFriends)
|
|
return
|
|
}
|
|
|
|
// TODO: Add a limit
|
|
if !g.isFriendAdded(uint32(newProfileId)) {
|
|
g.FriendList = append(g.FriendList, uint32(newProfileId))
|
|
}
|
|
|
|
// Check if destination has added the sender
|
|
newSession, ok := sessions[uint32(newProfileId)]
|
|
if !ok || newSession == nil || !newSession.LoggedIn {
|
|
logging.Info(g.ModuleName, "Destination is not online")
|
|
return
|
|
}
|
|
|
|
if newSession.GameName != g.GameName {
|
|
logging.Error(g.ModuleName, "Destination is not playing the same game")
|
|
// g.replyError(ErrAddFriendBadNew)
|
|
return
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// Friends are now mutual!
|
|
// TODO: Add a limit
|
|
if !authorized {
|
|
g.AuthFriendList = append(g.AuthFriendList, uint32(newProfileId))
|
|
newSession.AuthFriendList = append(newSession.AuthFriendList, g.User.ProfileId)
|
|
}
|
|
|
|
// Send friend auth message
|
|
sendMessageToSessionBuffer("4", newSession.User.ProfileId, g, "")
|
|
if newSession.isFriendAdded(g.User.ProfileId) && !g.User.OpenHost {
|
|
// If we're open host then this would've been sent already
|
|
sendMessageToSession("4", g.User.ProfileId, newSession, "")
|
|
g.sendFriendStatus(newSession.User.ProfileId)
|
|
}
|
|
|
|
newSession.sendFriendStatus(g.User.ProfileId)
|
|
}
|
|
|
|
func (g *GameSpySession) removeFriend(command common.GameSpyCommand) {
|
|
strDelProfileID := command.OtherValues["delprofileid"]
|
|
delProfileID64, err := strconv.ParseUint(strDelProfileID, 10, 32)
|
|
if err != nil {
|
|
logging.Error(g.ModuleName, aurora.Cyan(strDelProfileID), "is not a valid profile id")
|
|
g.replyError(ErrDeleteFriend)
|
|
return
|
|
}
|
|
delProfileID32 := uint32(delProfileID64)
|
|
|
|
fc := common.CalcFriendCodeString(delProfileID32, g.User.GsbrCode[:4])
|
|
logging.Info(g.ModuleName, "Remove friend:", aurora.Cyan(strDelProfileID), aurora.Cyan(fc))
|
|
|
|
mutex.Lock()
|
|
defer mutex.Unlock()
|
|
|
|
if g.isFriendAdded(delProfileID32) {
|
|
delProfileIDIndex := g.getFriendIndex(delProfileID32)
|
|
removeFromUint32Array(&g.FriendList, delProfileIDIndex)
|
|
}
|
|
|
|
if !g.User.OpenHost {
|
|
if g.isFriendAuthorized(delProfileID32) {
|
|
delProfileIDIndex := g.getAuthorizedFriendIndex(delProfileID32)
|
|
removeFromUint32Array(&g.AuthFriendList, delProfileIDIndex)
|
|
}
|
|
|
|
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) {
|
|
strFromProfileId := command.OtherValues["fromprofileid"]
|
|
fromProfileId, err := strconv.ParseUint(strFromProfileId, 10, 32)
|
|
if err != nil {
|
|
logging.Error(g.ModuleName, "Invalid profile ID string:", aurora.Cyan(strFromProfileId))
|
|
g.replyError(ErrAuthAddBadFrom)
|
|
return
|
|
}
|
|
|
|
mutex.Lock()
|
|
defer mutex.Unlock()
|
|
|
|
if !g.isFriendAuthorized(uint32(fromProfileId)) {
|
|
logging.Error(g.ModuleName, "Sender", aurora.Cyan(fromProfileId), "is not an authorized friend")
|
|
g.replyError(ErrAuthAddBadFrom)
|
|
return
|
|
}
|
|
|
|
g.exchangeFriendStatus(uint32(fromProfileId))
|
|
}
|
|
|
|
func (g *GameSpySession) setStatus(command common.GameSpyCommand) {
|
|
status := command.CommandValue
|
|
logging.Notice(g.ModuleName, "New status:", aurora.BrightMagenta(status))
|
|
|
|
qr2.ProcessGPStatusUpdate(g.User.ProfileId, g.QR2IP, status)
|
|
|
|
statstring, ok := command.OtherValues["statstring"]
|
|
if !ok {
|
|
logging.Warn(g.ModuleName, "Missing statstring")
|
|
statstring = ""
|
|
}
|
|
|
|
locstring, ok := command.OtherValues["locstring"]
|
|
if !ok {
|
|
logging.Warn(g.ModuleName, "Missing locstring")
|
|
locstring = ""
|
|
}
|
|
|
|
statusMsg := "|s|" + status + "|ss|" + statstring + "|ls|" + locstring + "|ip|0|p|0|qm|0"
|
|
|
|
mutex.Lock()
|
|
defer mutex.Unlock()
|
|
|
|
if status == "3" && g.User.Restricted {
|
|
logging.Warn(g.ModuleName, "Restricted user searching for public rooms")
|
|
kickPlayer(g.User.ProfileId, "restricted_join")
|
|
}
|
|
|
|
g.LocString = locstring
|
|
g.Status = statusMsg
|
|
|
|
if !g.StatusSet && g.User.OpenHost {
|
|
g.openHostEnabled(false, false)
|
|
}
|
|
|
|
g.StatusSet = true
|
|
|
|
for _, storedPid := range g.AuthFriendList {
|
|
g.sendFriendStatus(storedPid)
|
|
}
|
|
}
|
|
|
|
func sendMessageToSession(msgType string, from uint32, session *GameSpySession, msg string) {
|
|
message := common.CreateGameSpyMessage(common.GameSpyCommand{
|
|
Command: "bm",
|
|
CommandValue: msgType,
|
|
OtherValues: map[string]string{
|
|
"f": strconv.FormatUint(uint64(from), 10),
|
|
"msg": msg,
|
|
},
|
|
})
|
|
common.SendPacket(ServerName, session.ConnIndex, []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)
|
|
return true
|
|
}
|
|
|
|
logging.Info("GPCM", "Destination", aurora.Cyan(to), "from", aurora.Cyan(from), "is not online")
|
|
return false
|
|
}
|
|
|
|
func (g *GameSpySession) sendFriendStatus(profileId uint32) {
|
|
common.UNUSED(sendMessageToProfileId)
|
|
|
|
if !g.isFriendAuthorized(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
|
|
}
|
|
|
|
session.recordStatusSent(g.User.ProfileId)
|
|
sendMessageToSession("100", g.User.ProfileId, session, g.Status)
|
|
}
|
|
}
|
|
|
|
func (g *GameSpySession) exchangeFriendStatus(profileId uint32) {
|
|
if session, ok := sessions[profileId]; ok && session.LoggedIn {
|
|
if session.isFriendAdded(g.User.ProfileId) && session.isFriendAuthorized(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
|
|
}
|
|
|
|
session.recordStatusSent(g.User.ProfileId)
|
|
sendMessageToSession("100", g.User.ProfileId, session, g.Status)
|
|
}
|
|
|
|
if g.isFriendAdded(profileId) && g.isFriendAuthorized(profileId) {
|
|
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
|
|
}
|
|
|
|
g.recordStatusSent(profileId)
|
|
sendMessageToSessionBuffer("100", profileId, g, session.Status)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (g *GameSpySession) recordStatusSent(sender uint32) {
|
|
for _, friend := range g.RecvStatusFromList {
|
|
if friend == sender {
|
|
return
|
|
}
|
|
}
|
|
|
|
g.RecvStatusFromList = append(g.RecvStatusFromList, sender)
|
|
}
|
|
|
|
func (g *GameSpySession) sendLogoutStatus() {
|
|
mutex.Lock()
|
|
defer mutex.Unlock()
|
|
|
|
for _, storedPid := range g.AuthFriendList {
|
|
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(sendStatus bool, lock bool) {
|
|
if lock {
|
|
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)
|
|
sendMessageToSession("4", g.User.ProfileId, session, "")
|
|
if sendStatus {
|
|
session.sendFriendStatus(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)
|
|
}
|
|
}
|
|
}
|