diff --git a/gpcm/error.go b/gpcm/error.go index fc597f8..84dd516 100644 --- a/gpcm/error.go +++ b/gpcm/error.go @@ -3,149 +3,160 @@ package gpcm import ( "strconv" "wwfc/common" + "wwfc/logging" ) -var gpcmErrors = map[int]string{ - // General errors - 0: "There was an unknown error.", - 1: "There was an error parsing an incoming request.", - 2: "This request cannot be processed because you are not logged in.", - 3: "This request cannot be processed because the session key is invalid.", - 4: "This request cannot be processed because of a database error.", - 5: "There was an error connecting a network socket.", - 6: "This profile has been disconnected by another login.", - 7: "The server has closed the connection.", - 8: "There was a problem with the UDP layer.", - - // Log-in errors - 256: "There was an error logging in to the GP backend.", - 257: "The login attempt timed out.", - 258: "The nickname provided was incorrect.", - 259: "The email address provided was incorrect.", - 260: "The password provided is incorrect.", - 261: "The profile provided was incorrect.", - 262: "The profile has been deleted.", - 263: "The server has refused the connection.", - 264: "The server could not be authenticated.", - 265: "The uniquenick provided is incorrect.", - 266: "There was an error validating the pre-authentication.", - 267: "The login ticket was unable to be validated.", - 268: "The login ticket had expired and could not be used.", - - // New user errors - 512: "There was an error creating a new user.", - 513: "A profile with that nick already exists.", - 514: "The password does not match the email address.", - 515: "The uniquenick is invalid.", - 516: "The uniquenick is already in use.", - - // User updating errors - 768: "There was an error updating the user information.", - 769: "A user with the email adress provided already exists.", - - // New profile errors - 1024: "There was an error creating a new profile.", - 1025: "The nickname to be replaced does not exist.", - 1026: "A profile with the nickname provided already exists.", - - // Updating profile errors - 1280: "There was an error updating the profile information. ", - 1281: "A user with the nickname provided already exists.", - - // Buddy list errors - 1536: "There was an error adding a buddy.", - 1537: "The profile requesting to add a buddy is invalid.", - 1538: "The profile requested is invalid.", - 1539: "The profile requested is already a buddy.", - 1540: "The profile requested is on the local profile's block list.", - 1541: "The profile requested is blocking you.", - - // Errors while Buddy add auth - 1792: "There was an error authorizing an add buddy request.", - 1793: "The profile being authorized is invalid.", - 1794: "The signature for the authorization is invalid.", - 1795: "The profile requesting authorization is on a block list.", - 1796: "The profile requested is blocking you.", - - // Status errors - 2048: "There was an error with the status string.", - - // Buddy message errors - 2304: "There was an error sending a buddy message.", - 2305: "The profile the message was to be sent to is not a buddy.", - 2306: "The profile does not support extended info keys.", - 2307: "The buddy to send a message to is offline.", - - // Profile information getting errors - 2560: "There was an error getting profile info.", - 2561: "The profile info was requested on is invalid.", - - // Deleting buddy errors - 2816: "There was an error deleting the buddy.", - 2817: "The buddy to be deleted is not a buddy.", - - // Deleting profile errors - 3072: "There was an error deleting the profile.", - 3073: "The last profile cannot be deleted.", - - // Profile searching errors - 3328: "There was an error searching for a profile.", - 3329: "The search attempt failed to connect to the server.", - 3330: "The search did not return in a timely fashion.", - - // User checking errors - 3584: "There was an error checking the user account.", - 3585: "No account exists with the provided e-mail address.", - 3586: "No such profile exists for the provided e-mail adress.", - 3587: "The password is incorrect.", - - // Revoking buddy errors - 3840: "There was an error revoking the buddy.", - 3841: "You are not a buddy of the profile.", - - // Registering new unique nick errors - 4096: "There was an error registering the uniquenick.", - 4097: "The uniquenick is already taken.", - 4098: "The uniquenick is reserved.", - 4099: "Tried to register a nick with no namespace set.", - - // CD key errors - 4352: "There was an error registering the cdkey.", - 4353: "The cdkey is invalid.", - 4354: "The profile has already been registered with a different cdkey.", - 4355: "The cdkey has already been registered to another profile.", - - // Adding to block list errors - 4608: "There was an error adding the player to the blocked list.", - 4609: "The profile specified is already blocked.", - - // Removing from block list errors - 4864: "There was an error removing the player from the blocked list.", - 4865: "The profile specified was not a member of the blocked list.", +type GPError struct { + errorCode int + errorString string + fatal bool } -func createGameSpyError(err int) string { - errMsg, ok := gpcmErrors[err] - if !ok { - errMsg = "" +func MakeGPError(errorCode int, errorString string, fatal bool) GPError { + return GPError{ + errorCode: errorCode, + errorString: errorString, + fatal: fatal, } +} +var ( + // General errors + ErrGeneral = MakeGPError(0x0000, "There was an unknown error.", true) + ErrParse = MakeGPError(0x0001, "There was an error parsing an incoming request.", true) + ErrNotLoggedIn = MakeGPError(0x0002, "This request cannot be processed because you are not logged in.", true) + ErrBadSessionKey = MakeGPError(0x0003, "This request cannot be processed because the session key is invalid.", true) + ErrDatabase = MakeGPError(0x0004, "This request cannot be processed because of a database error.", true) + ErrNetwork = MakeGPError(0x0005, "There was an error connecting a network socket.", true) + ErrForcedDisconnect = MakeGPError(0x0006, "This profile has been disconnected by another login.", true) + ErrConnectionClosed = MakeGPError(0x0007, "The server has closed the connection.", true) + ErrUDPLayer = MakeGPError(0x0008, "There was a problem with the UDP layer.", true) + + // Login errors + ErrLogin = MakeGPError(0x0100, "There was an error logging in to the GP backend.", true) + ErrLoginTimeout = MakeGPError(0x0101, "The login attempt timed out.", true) + ErrLoginBadNickname = MakeGPError(0x0102, "The nickname provided was incorrect.", true) + ErrLoginBadEmail = MakeGPError(0x0103, "The email address provided was incorrect.", true) + ErrLoginBadPassword = MakeGPError(0x0104, "The password provided is incorrect.", true) + ErrLoginBadProfile = MakeGPError(0x0105, "The profile provided was incorrect.", true) + ErrLoginProfileDeleted = MakeGPError(0x0106, "The profile has been deleted.", true) + ErrLoginConnectionFailed = MakeGPError(0x0107, "The server has refused the connection.", true) + ErrLoginServerAuthFailed = MakeGPError(0x0108, "The server could not be authenticated.", true) + ErrLoginBadUniqueNick = MakeGPError(0x0109, "The uniquenick provided is incorrect.", true) + ErrLoginBadPreAuth = MakeGPError(0x010A, "There was an error validating the pre-authentication.", true) + ErrLoginLoginTicketInvalid = MakeGPError(0x010B, "The login ticket was unable to be validated.", true) + ErrLoginLoginTicketExpired = MakeGPError(0x010C, "The login ticket had expired and could not be used.", true) + + // New user errors + ErrNewUser = MakeGPError(0x0200, "There was an error creating a new user.", true) + ErrNewUserBadNickname = MakeGPError(0x0201, "A profile with that nick already exists.", true) + ErrNewUserBadPassword = MakeGPError(0x0202, "The password does not match the email address.", true) + ErrNewUserUniqueNickInvalid = MakeGPError(0x0203, "The uniquenick is invalid.", true) + ErrNewUserUniqueNickInUse = MakeGPError(0x0204, "The uniquenick is already in use.", true) + + // Update user information errors + ErrUpdateUserInfo = MakeGPError(0x0300, "There was an error updating the user information.", false) + ErrUpdateUserInfoBadEmail = MakeGPError(0x0301, "A user with the email address provided already exists.", false) + + // New profile errors + ErrNewProfile = MakeGPError(0x0400, "There was an error creating a new profile.", false) + ErrNewProfileBadNickname = MakeGPError(0x0401, "The nickname to be replaced does not exist.", false) + ErrNewProfileBadOldNickname = MakeGPError(0x0402, "A profile with the nickname provided already exists.", false) + + // Update profile errors + ErrUpdateProfile = MakeGPError(0x0500, "There was an error updating the profile information.", false) + ErrUpdateProfileBadNickname = MakeGPError(0x0501, "A user with the nickname provided already exists.", false) + + // Add friend errors + ErrAddFriend = MakeGPError(0x0600, "There was an error adding a buddy.", false) + ErrAddFriendBadFrom = MakeGPError(0x0601, "The profile requesting to add a buddy is invalid.", false) + ErrAddFriendBadNew = MakeGPError(0x0602, "The profile requested is invalid.", false) + ErrAddFriendAlreadyFriends = MakeGPError(0x0603, "The profile requested is already a buddy.", false) + ErrAddFriendLocalBlock = MakeGPError(0x0604, "The profile requested is on the local profile's block list.", false) + ErrAddFriendBlocked = MakeGPError(0x0605, "The profile requested is blocking you.", false) + + // Auth add friend errors + ErrAuthAdd = MakeGPError(0x0700, "There was an error authorizing an add buddy request.", false) + ErrAuthAddBadFrom = MakeGPError(0x0701, "The profile being authorized is invalid.", false) + ErrAuthAddBadSignature = MakeGPError(0x0702, "The signature for the authorization is invalid.", false) + ErrAuthAddLocalBlock = MakeGPError(0x0703, "The profile requesting authorization is on a block list.", false) + ErrAuthAddBlocked = MakeGPError(0x0704, "The profile requested is blocking you.", false) + + // Status errors + ErrStatus = MakeGPError(0x0800, "There was an error with the status string.", false) + + // Friend message errors + ErrMessage = MakeGPError(0x0900, "There was an error sending a buddy message.", false) + ErrMessageNotFriends = MakeGPError(0x0901, "The profile the message was to be sent to is not a buddy.", false) + ErrMessageExtInfoNotSupported = MakeGPError(0x0902, "The profile does not support extended info keys.", false) + ErrMessageFriendOffline = MakeGPError(0x0903, "The buddy to send a message to is offline.", false) + + // Get profile errors + ErrGetProfile = MakeGPError(0x0A00, "There was an error getting profile info.", false) + ErrGetProfileBadProfile = MakeGPError(0x0A01, "The profile info was requested on is invalid.", false) + + // Delete friend errors + ErrDeleteFriend = MakeGPError(0x0B00, "There was an error deleting the buddy.", false) + ErrDeleteFriendNotFriends = MakeGPError(0x0B01, "The buddy to be deleted is not a buddy.", false) + + // Delete profile errors + ErrDeleteProfile = MakeGPError(0x0C00, "There was an error deleting the profile.", false) + ErrDeleteProfileLastProfile = MakeGPError(0x0C01, "The last profile cannot be deleted.", false) + + // Profile searching errors + ErrSearch = MakeGPError(0x0D00, "There was an error searching for a profile.", false) + ErrSearchConnectFailed = MakeGPError(0x0D01, "The search attempt failed to connect to the server.", false) + ErrSearchTimedOut = MakeGPError(0x0D02, "The search did not return in a timely fashion.", false) + + // User check errors + ErrCheck = MakeGPError(0x0E00, "There was an error checking the user account.", false) + ErrCheckBadEmail = MakeGPError(0x0E01, "No account exists with the provided email address.", false) + ErrCheckBadNickname = MakeGPError(0x0E02, "No such profile exists for the provided email address.", false) + ErrCheckBadPassword = MakeGPError(0x0E03, "The password is incorrect.", false) + + // Revoke buddy errors + ErrRevoke = MakeGPError(0x0F00, "There was an error revoking the buddy.", false) + ErrRevokeNotFriends = MakeGPError(0x0F01, "You are not a buddy of the profile.", false) + + // Register unique nick errors + ErrRegisterUniqueNick = MakeGPError(0x1000, "There was an error registering the uniquenick.", false) + ErrRegisterUniqueNickTaken = MakeGPError(0x1001, "The uniquenick is already taken.", false) + ErrRegisterUniqueNickReserved = MakeGPError(0x1002, "The uniquenick is reserved.", false) + ErrRegisterUniqueNickBadNamespace = MakeGPError(0x1003, "Tried to register a nick with no namespace set.", false) + + // Register cdkey errors + ErrRegisterCDKey = MakeGPError(0x1100, "There was an error registering the cdkey.", false) + ErrRegisterCDKeyBadKey = MakeGPError(0x1101, "The cdkey is invalid.", false) + ErrRegisterCDKeyAlreadySet = MakeGPError(0x1102, "The profile has already been registered with a different cdkey.", false) + ErrRegisterCDKeyAlreadyTaken = MakeGPError(0x1103, "The cdkey has already been registered to another profile.", false) + + // Add to block list errors + ErrAddBlock = MakeGPError(0x1200, "There was an error adding the player to the blocked list.", false) + ErrAddBlockAlreadyBlocked = MakeGPError(0x1201, "The profile specified is already blocked.", false) + + // Remove from block list errors + ErrRemoveBlock = MakeGPError(0x1300, "There was an error removing the player from the blocked list.", false) + ErrRemoveBlockNotBlocked = MakeGPError(0x1301, "The profile specified was not a member of the blocked list.", false) +) + +func (err GPError) GetMessage() string { command := common.GameSpyCommand{ Command: "error", CommandValue: "", OtherValues: map[string]string{ - "err": strconv.Itoa(err), - "errmsg": errMsg, + "err": strconv.Itoa(err.errorCode), + "errmsg": err.errorString, }, } - if err < 300 { + if err.fatal { command.OtherValues["fatal"] = "" } return common.CreateGameSpyMessage(command) } -func (g *GameSpySession) replyError(err int) { - g.Conn.Write([]byte(createGameSpyError(err))) +func (g *GameSpySession) replyError(err GPError) { + logging.Error(g.ModuleName, "Reply error:", err.errorString) + g.Conn.Write([]byte(err.GetMessage())) } diff --git a/gpcm/friend.go b/gpcm/friend.go index 27072f3..c3b926f 100644 --- a/gpcm/friend.go +++ b/gpcm/friend.go @@ -30,20 +30,20 @@ func (g *GameSpySession) addFriend(command common.GameSpyCommand) { strNewProfileId := command.OtherValues["newprofileid"] newProfileId, err := strconv.ParseUint(strNewProfileId, 10, 32) if err != nil { - g.replyError(1536) + g.replyError(ErrAddFriend) return } // Required for a friend auth if g.User.LastName == "" { logging.Error(g.ModuleName, "Add friend without last name") - g.replyError(1537) + g.replyError(ErrAddFriendBadFrom) return } if newProfileId == uint64(g.User.ProfileId) { logging.Error(g.ModuleName, "Attempt to add self as friend") - g.replyError(1538) + g.replyError(ErrAddFriendBadNew) return } @@ -51,8 +51,10 @@ func (g *GameSpySession) addFriend(command common.GameSpyCommand) { logging.Notice(g.ModuleName, "Add friend:", aurora.Cyan(strNewProfileId), aurora.Cyan(fc)) if g.isFriendAuthorized(uint32(newProfileId)) { - logging.Warn(g.ModuleName, "Attempt to add a friend who is already authorized") - g.replyError(1539) + 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 } @@ -91,7 +93,7 @@ func (g *GameSpySession) authAddFriend(command common.GameSpyCommand) { fromProfileId, err := strconv.ParseUint(strFromProfileId, 10, 32) if err != nil { logging.Error(g.ModuleName, "Invalid profile ID string:", aurora.Cyan(strFromProfileId)) - g.replyError(1793) + g.replyError(ErrAuthAddBadFrom) return } @@ -154,13 +156,13 @@ func (g *GameSpySession) bestieMessage(command common.GameSpyCommand) { toProfileId, err := strconv.ParseUint(strToProfileId, 10, 32) if err != nil { logging.Error(g.ModuleName, "Invalid profile ID string:", aurora.Cyan(strToProfileId)) - g.replyError(2304) + g.replyError(ErrMessage) return } if !g.isFriendAdded(uint32(toProfileId)) { - logging.Error(g.ModuleName, "Destination", aurora.Cyan(toProfileId), "not even on own friend list") - g.replyError(2305) + logging.Error(g.ModuleName, "Destination", aurora.Cyan(toProfileId), "is not even on sender's friend list") + g.replyError(ErrMessageNotFriends) return } @@ -170,7 +172,7 @@ func (g *GameSpySession) bestieMessage(command common.GameSpyCommand) { if toSession, ok := sessions[uint32(toProfileId)]; ok && toSession.LoggedIn { if !toSession.isFriendAdded(g.User.ProfileId) { logging.Error(g.ModuleName, "Destination", aurora.Cyan(toProfileId), "is not friends with sender") - g.replyError(2305) + g.replyError(ErrMessageNotFriends) return } @@ -180,7 +182,7 @@ func (g *GameSpySession) bestieMessage(command common.GameSpyCommand) { } logging.Error(g.ModuleName, "Destination", aurora.Cyan(toProfileId), "is not online") - g.replyError(2307) + g.replyError(ErrMessageFriendOffline) } func sendMessageToSession(msgType string, from uint32, session *GameSpySession, msg string) { diff --git a/gpcm/login.go b/gpcm/login.go index 73340e3..38f0ab0 100644 --- a/gpcm/login.go +++ b/gpcm/login.go @@ -4,13 +4,11 @@ import ( "crypto/md5" "encoding/base64" "encoding/hex" - "fmt" "log" "strconv" "strings" "wwfc/common" "wwfc/database" - "wwfc/logging" ) func generateResponse(gpcmChallenge, nasChallenge, authToken, clientChallenge string) string { @@ -40,15 +38,15 @@ func (g *GameSpySession) login(command common.GameSpyCommand) { authToken := command.OtherValues["authtoken"] challenge := database.GetChallenge(pool, ctx, authToken) if challenge == "" { - err := g.FormatError("invalid auth token", 256, command.OtherValues["id"]) - g.Conn.Write([]byte(err)) + // There was an error validating the pre-authentication. + g.replyError(ErrLoginBadPreAuth) return } response := generateResponse(g.Challenge, challenge, authToken, command.OtherValues["challenge"]) if response != command.OtherValues["response"] { - err := g.FormatError("response mismatch", 256, command.OtherValues["id"]) - g.Conn.Write([]byte(err)) + // There was an error validating the pre-authentication. + g.replyError(ErrLoginBadPreAuth) return } @@ -57,19 +55,23 @@ func (g *GameSpySession) login(command common.GameSpyCommand) { // Perform the login with the database. user, ok := database.LoginUserToGPCM(pool, ctx, authToken) if !ok { - err := g.FormatError("GPCM login error", 256, command.OtherValues["id"]) - g.Conn.Write([]byte(err)) + // There was an error logging in to the GP backend. + g.replyError(ErrLogin) return } g.User = user + g.ModuleName = "GPCM:" + strconv.FormatInt(int64(g.User.ProfileId), 10) + "*" + g.ModuleName += "/" + common.CalcFriendCodeString(g.User.ProfileId, "RMCJ") + "*" + // Check to see if a session is already open with this profile ID mutex.Lock() _, exists := sessions[g.User.ProfileId] if exists { mutex.Unlock() - err := g.FormatError(fmt.Sprintf("Session with profile ID %d is already open", g.User.ProfileId), 256, command.OtherValues["id"]) - g.Conn.Write([]byte(err)) + // Original GPCM would've force kicked the other logged in client, + // but we just kick this client + g.replyError(ErrForcedDisconnect) return } sessions[g.User.ProfileId] = g @@ -80,7 +82,7 @@ func (g *GameSpySession) login(command common.GameSpyCommand) { _ = database.CreateSession(pool, ctx, g.User.ProfileId, loginTicket) g.LoggedIn = true - g.ModuleName += ":" + strconv.FormatInt(int64(g.User.ProfileId), 10) + g.ModuleName = "GPCM:" + strconv.FormatInt(int64(g.User.ProfileId), 10) g.ModuleName += "/" + common.CalcFriendCodeString(g.User.ProfileId, "RMCJ") payload := common.CreateGameSpyMessage(common.GameSpyCommand{ @@ -100,21 +102,6 @@ func (g *GameSpySession) login(command common.GameSpyCommand) { g.Conn.Write([]byte(payload)) } -func (g *GameSpySession) FormatError(err string, code int, id string) string { - logging.Error(g.ModuleName, err) - - return common.CreateGameSpyMessage(common.GameSpyCommand{ - Command: "error", - CommandValue: "", - OtherValues: map[string]string{ - "err": strconv.Itoa(code), - "fatal": "", - "errmsg": err, - "id": id, - }, - }) -} - func IsLoggedIn(profileID uint32) bool { mutex.Lock() defer mutex.Unlock() diff --git a/gpcm/main.go b/gpcm/main.go index 4d23557..9c12f5e 100644 --- a/gpcm/main.go +++ b/gpcm/main.go @@ -125,6 +125,7 @@ func handleRequest(conn net.Conn) { // 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 { @@ -142,6 +143,7 @@ func handleRequest(conn net.Conn) { if err != nil { logging.Error(session.ModuleName, "Error parsing message:", err.Error()) logging.Error(session.ModuleName, "Raw data:", string(buffer)) + session.replyError(ErrParse) return } @@ -155,6 +157,7 @@ func handleRequest(conn net.Conn) { if len(commands) != 0 && session.LoggedIn == false { logging.Error(session.ModuleName, "Attempt to run command before login!") + session.replyError(ErrNotLoggedIn) return } diff --git a/gpcm/profile.go b/gpcm/profile.go index 4ed0c80..b3a4d24 100644 --- a/gpcm/profile.go +++ b/gpcm/profile.go @@ -12,7 +12,8 @@ func (g *GameSpySession) getProfile(command common.GameSpyCommand) { strProfileId := command.OtherValues["profileid"] profileId, err := strconv.ParseUint(strProfileId, 10, 32) if err != nil { - g.replyError(2560) + // There was an error getting profile info. + g.replyError(ErrGetProfile) return } @@ -28,7 +29,12 @@ func (g *GameSpySession) getProfile(command common.GameSpyCommand) { mutex.Unlock() } else { mutex.Unlock() - user, _ = database.GetProfile(pool, ctx, uint32(profileId)) + user, ok = database.GetProfile(pool, ctx, uint32(profileId)) + if !ok { + // The profile info was requested on is invalid. + g.replyError(ErrGetProfileBadProfile) + return + } } response := common.CreateGameSpyMessage(common.GameSpyCommand{