diff --git a/common/friend_code.go b/common/friend_code.go index d0c9ff8..d208c76 100644 --- a/common/friend_code.go +++ b/common/friend_code.go @@ -4,8 +4,70 @@ import ( "crypto/md5" "encoding/binary" "fmt" + "strings" ) +const ( + fcCRC8 = iota + fcMD5 = iota +) + +var crc8Table = func() [256]byte { + var table [256]byte + poly := byte(0x07) + + for i := 0; i < 256; i++ { + crc := byte(i) + + for j := 0; j < 8; j++ { + if crc&0x80 != 0 { + crc = (crc << 1) ^ poly + } else { + crc <<= 1 + } + } + + table[i] = crc + } + + return table +}() + +func crc8(data []byte) byte { + crc := byte(0) + + for _, b := range data { + crc = crc8Table[crc^b] + } + + return crc +} + +func getCRCType(gameId string) (crcType byte, reverse bool) { + reverse = false + + if strings.HasPrefix(gameId, "RSB") { + crcType = fcCRC8 + return + } + + if strings.HasPrefix(gameId, "HDM") || strings.HasPrefix(gameId, "WDM") { + crcType = fcCRC8 + reverse = true + return + } + + switch gameId[0] { + case 'R', 'S', 'H', 'W', 'X', 'Y': + crcType = fcMD5 + + default: + crcType = fcCRC8 + } + + return +} + func CalcFriendCode(pid uint32, gameId string) uint64 { if pid == 0 { return 0 @@ -18,15 +80,29 @@ func CalcFriendCode(pid uint32, gameId string) uint64 { buffer[6] = gameId[1] buffer[7] = gameId[0] + crc, _ := getCRCType(gameId) + + if crc == fcCRC8 { + return uint64(pid) | (uint64(crc8(buffer)&0x7f) << 32) + } + digest := md5.Sum(buffer) return uint64(pid) | (uint64(digest[0]&0xfe) << 31) } func CalcFriendCodeString(pid uint32, gameId string) string { - return GetRawFriendCodeString(CalcFriendCode(pid, gameId)) + _, reverse := getCRCType(gameId) + + return GetRawFriendCodeString(CalcFriendCode(pid, gameId), reverse) } -func GetRawFriendCodeString(fc uint64) string { - s := fmt.Sprintf("%012d", fc) +func GetRawFriendCodeString(fc uint64, reverse bool) string { + s := fmt.Sprintf("%012d", max(min(fc, 999999999999), 0)) + + if reverse { + // Reverse the digit order + s = s[11:12] + s[10:11] + s[9:10] + s[8:9] + s[7:8] + s[6:7] + s[5:6] + s[4:5] + s[3:4] + s[2:3] + s[1:2] + s[0:1] + } + return s[len(s)-12:len(s)-8] + "-" + s[len(s)-8:len(s)-4] + "-" + s[len(s)-4:] } diff --git a/gamestats/auth.go b/gamestats/auth.go index 2022117..ad7747b 100644 --- a/gamestats/auth.go +++ b/gamestats/auth.go @@ -84,7 +84,6 @@ func (g *GameStatsSession) authp(command common.GameSpyCommand) { } g.ModuleName = "GSTATS:" + strconv.FormatInt(int64(g.User.ProfileId), 10) - g.ModuleName += "/" + common.CalcFriendCodeString(g.User.ProfileId, "RMCJ") g.Authenticated = true logging.Notice(g.ModuleName, "Authenticated, game name:", aurora.Cyan(g.GameInfo.Name)) diff --git a/gpcm/friend.go b/gpcm/friend.go index 7ff762d..775a45b 100644 --- a/gpcm/friend.go +++ b/gpcm/friend.go @@ -82,20 +82,13 @@ func (g *GameSpySession) addFriend(command common.GameSpyCommand) { return } - // Required for a friend auth - if g.User.LastName == "" { - logging.Error(g.ModuleName, "Add friend without last name") - g.replyError(ErrAddFriendBadFrom) - 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), "RMCJ") + fc := common.CalcFriendCodeString(uint32(newProfileId), g.User.GsbrCode[:4]) logging.Info(g.ModuleName, "Add friend:", aurora.Cyan(strNewProfileId), aurora.Cyan(fc)) if g.isFriendAuthorized(uint32(newProfileId)) { @@ -123,7 +116,7 @@ func (g *GameSpySession) addFriend(command common.GameSpyCommand) { if newSession.GameName != g.GameName { logging.Error(g.ModuleName, "Destination is not playing the same game") - g.replyError(ErrAddFriendBadNew) + // g.replyError(ErrAddFriendBadNew) return } diff --git a/gpcm/login.go b/gpcm/login.go index e429732..bfce3ae 100644 --- a/gpcm/login.go +++ b/gpcm/login.go @@ -256,7 +256,7 @@ func (g *GameSpySession) login(command common.GameSpyCommand) { } g.ModuleName = "GPCM:" + strconv.FormatInt(int64(g.User.ProfileId), 10) + "*" - g.ModuleName += "/" + common.CalcFriendCodeString(g.User.ProfileId, "RMCJ") + "*" + g.ModuleName += "/" + common.CalcFriendCodeString(g.User.ProfileId, g.User.GsbrCode[:4]) + "*" // Check to see if a session is already open with this profile ID mutex.Lock() @@ -293,10 +293,10 @@ func (g *GameSpySession) login(command common.GameSpyCommand) { g.DeviceAuthenticated = deviceAuth g.LoggedIn = true g.ModuleName = "GPCM:" + strconv.FormatInt(int64(g.User.ProfileId), 10) - g.ModuleName += "/" + common.CalcFriendCodeString(g.User.ProfileId, "RMCJ") + g.ModuleName += "/" + common.CalcFriendCodeString(g.User.ProfileId, g.User.GsbrCode[:4]) // Notify QR2 of the login - qr2.Login(g.User.ProfileId, gamecd, ingamesn, cfc, g.Conn.RemoteAddr().String(), g.NeedsExploit, g.DeviceAuthenticated, g.User.Restricted, KickPlayer) + qr2.Login(g.User.ProfileId, gamecd, ingamesn, cfc, g.User.GsbrCode[:4], g.Conn.RemoteAddr().String(), g.NeedsExploit, g.DeviceAuthenticated, g.User.Restricted, KickPlayer) replyUserId := g.User.UserId if g.UnitCode == UnitCodeDS { diff --git a/nas/auth.go b/nas/auth.go index 354039a..becf965 100644 --- a/nas/auth.go +++ b/nas/auth.go @@ -220,6 +220,12 @@ func login(moduleName string, fields map[string]string, isLocalhost bool) map[st return param } + if len(gsbrcd) < 4 || strings.ContainsRune(gsbrcd, 0) { + logging.Error(moduleName, "Invalid gsbrcd string in form") + param["returncd"] = "103" + return param + } + lang, ok := fields["lang"] if !ok { lang = "ff" diff --git a/qr2/group.go b/qr2/group.go index 6d7a351..ada3e49 100644 --- a/qr2/group.go +++ b/qr2/group.go @@ -596,14 +596,16 @@ func GetGroups(gameNames []string, groupNames []string, sorted bool) []GroupInfo InGameName: rawPlayer["+ingamesn"], } + pid, err := strconv.ParseUint(rawPlayer["dwc_pid"], 10, 32) + if err == nil { + if fcGame := rawPlayer["+fcgameid"]; len(fcGame) == 4 { + playerInfo.FriendCode = common.CalcFriendCodeString(uint32(pid), fcGame) + } + } + if rawPlayer["gamename"] == "mariokartwii" { playerInfo.VersusELO = rawPlayer["ev"] playerInfo.BattleELO = rawPlayer["eb"] - - pid, err := strconv.ParseUint(rawPlayer["dwc_pid"], 10, 32) - if err == nil { - playerInfo.FriendCode = common.CalcFriendCodeString(uint32(pid), "RMCJ") - } } for i := 0; i < 32; i++ { diff --git a/qr2/logins.go b/qr2/logins.go index 7f2b2e6..3a1da2d 100644 --- a/qr2/logins.go +++ b/qr2/logins.go @@ -5,6 +5,7 @@ type LoginInfo struct { GameCode string InGameName string ConsoleFriendCode uint64 + FriendKeyGame string GPPublicIP string NeedsExploit bool DeviceAuthenticated bool @@ -15,7 +16,7 @@ type LoginInfo struct { var logins = map[uint32]*LoginInfo{} -func Login(profileID uint32, gameCode string, inGameName string, consoleFriendCode uint64, publicIP string, needsExploit bool, deviceAuthenticated bool, restricted bool, gpErrorCallback func(uint32, string)) { +func Login(profileID uint32, gameCode string, inGameName string, consoleFriendCode uint64, fcGame string, publicIP string, needsExploit bool, deviceAuthenticated bool, restricted bool, gpErrorCallback func(uint32, string)) { mutex.Lock() defer mutex.Unlock() @@ -24,6 +25,7 @@ func Login(profileID uint32, gameCode string, inGameName string, consoleFriendCo GameCode: gameCode, InGameName: inGameName, ConsoleFriendCode: consoleFriendCode, + FriendKeyGame: fcGame, GPPublicIP: publicIP, NeedsExploit: needsExploit, DeviceAuthenticated: deviceAuthenticated, diff --git a/qr2/session.go b/qr2/session.go index ab8ded1..12ac989 100644 --- a/qr2/session.go +++ b/qr2/session.go @@ -232,6 +232,7 @@ func (session *Session) setProfileID(moduleName string, newPID string, gpcmIP st } session.Data["+gppublicip"], _ = common.IPFormatToString(gpPublicIP) + session.Data["+fcgameid"] = loginInfo.FriendKeyGame session.Data["dwc_pid"] = newPID logging.Notice(moduleName, "Opened session with PID", aurora.Cyan(newPID))