mirror of
https://github.com/WiiLink24/wfc-server.git
synced 2026-07-13 07:01:02 -05:00
Merge remote-tracking branch 'origin/main'
This commit is contained in:
commit
07816bda9d
|
|
@ -5,6 +5,7 @@ import (
|
|||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"wwfc/common"
|
||||
"wwfc/qr2"
|
||||
)
|
||||
|
||||
|
|
@ -31,11 +32,15 @@ func HandleGroups(w http.ResponseWriter, r *http.Request) {
|
|||
filtered := map[string]string{}
|
||||
|
||||
filtered["pid"] = player["dwc_pid"]
|
||||
filtered["name"] = player["name"]
|
||||
filtered["name"] = player["+ingamesn"]
|
||||
|
||||
if player["gamename"] == "mariokartwii" {
|
||||
filtered["ev"] = player["ev"]
|
||||
filtered["eb"] = player["eb"]
|
||||
pid, err := strconv.ParseUint(player["dwc_pid"], 10, 32)
|
||||
if err == nil {
|
||||
filtered["fc"] = common.CalcFriendCodeString(uint32(pid), "RMCJ")
|
||||
}
|
||||
}
|
||||
|
||||
group.Players[i] = filtered
|
||||
|
|
|
|||
158
common/auth_token.go
Normal file
158
common/auth_token.go
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func generateRandom(n int) []byte {
|
||||
key := make([]byte, n)
|
||||
|
||||
read, err := rand.Read(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if read != n {
|
||||
panic("short rand.Read()")
|
||||
}
|
||||
|
||||
return key
|
||||
}
|
||||
|
||||
var (
|
||||
authTokenKey = generateRandom(16)
|
||||
authTokenIV = generateRandom(16)
|
||||
authTokenMagic = generateRandom(16)
|
||||
|
||||
loginTicketKey = generateRandom(16)
|
||||
loginTicketIV = generateRandom(16)
|
||||
loginTicketMagic = generateRandom(4)
|
||||
)
|
||||
|
||||
func appendString(blob []byte, value string, maxlen int) []byte {
|
||||
if len([]byte(value)) < maxlen {
|
||||
blob = append(blob, append([]byte(value), make([]byte, maxlen-len(value))...)...)
|
||||
} else {
|
||||
blob = append(blob, []byte(value)[:maxlen]...)
|
||||
}
|
||||
|
||||
return blob
|
||||
}
|
||||
|
||||
func MarshalNASAuthToken(gamecd string, userid uint64, gsbrcd string, cfc uint64, region byte, lang byte, ingamesn string) (string, string) {
|
||||
blob := binary.LittleEndian.AppendUint64([]byte{}, uint64(time.Now().Unix()))
|
||||
|
||||
blob = appendString(blob, gamecd, 4)
|
||||
|
||||
blob = append(blob, binary.LittleEndian.AppendUint64([]byte{}, userid)[:6]...)
|
||||
|
||||
blob = append(blob, byte(min(len([]byte(gsbrcd)), 16)))
|
||||
blob = appendString(blob, gsbrcd, 16)
|
||||
|
||||
blob = append(blob, binary.LittleEndian.AppendUint64([]byte{}, cfc)[:7]...)
|
||||
blob = append(blob, region, lang)
|
||||
|
||||
blob = append(blob, byte(min(len([]byte(ingamesn)), 75)))
|
||||
blob = appendString(blob, ingamesn, 75)
|
||||
|
||||
challenge := RandomString(8)
|
||||
blob = append(blob, []byte(challenge)...)
|
||||
blob = append(blob, authTokenMagic...)
|
||||
|
||||
block, err := aes.NewCipher(authTokenKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
cipher.NewCBCEncrypter(block, authTokenIV).CryptBlocks(blob, blob)
|
||||
return "NDS" + Base64DwcEncoding.EncodeToString(blob), challenge
|
||||
}
|
||||
|
||||
func UnmarshalNASAuthToken(token string) (err error, gamecd string, issuetime time.Time, userid uint64, gsbrcd string, cfc uint64, region byte, lang byte, ingamesn string, challenge string) {
|
||||
if !strings.HasPrefix(token, "NDS") {
|
||||
err = errors.New("invalid auth token prefix")
|
||||
return
|
||||
}
|
||||
|
||||
blob, err := Base64DwcEncoding.DecodeString(token[3:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(blob) != 0x90 {
|
||||
err = errors.New("invalid auth token length")
|
||||
return
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(authTokenKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
cipher.NewCBCDecrypter(block, authTokenIV).CryptBlocks(blob, blob)
|
||||
|
||||
if !bytes.Equal(blob[0x80:0x90], authTokenMagic) {
|
||||
err = errors.New("invalid auth token magic")
|
||||
return
|
||||
}
|
||||
|
||||
issuetime = time.Unix(int64(binary.LittleEndian.Uint64(blob[0x0:0x8])), 0)
|
||||
gamecd = string(blob[0x8:0xC])
|
||||
userid = binary.LittleEndian.Uint64(append(bytes.Clone(blob[0xC:0x12]), 0, 0))
|
||||
gsbrcd = string(blob[0x13 : 0x13+min(blob[0x12], 16)])
|
||||
cfc = binary.LittleEndian.Uint64(append(bytes.Clone(blob[0x23:0x2A]), 0))
|
||||
region = blob[0x2A]
|
||||
lang = blob[0x2B]
|
||||
ingamesn = string(blob[0x2D : 0x2D+min(blob[0x2C], 75)])
|
||||
challenge = string(blob[0x78:0x80])
|
||||
return
|
||||
}
|
||||
|
||||
func MarshalGPCMLoginTicket(profileId uint32) string {
|
||||
blob := binary.LittleEndian.AppendUint64([]byte{}, uint64(time.Now().Unix()))
|
||||
blob = binary.LittleEndian.AppendUint32(blob, profileId)
|
||||
blob = append(blob, loginTicketMagic...)
|
||||
|
||||
block, err := aes.NewCipher(loginTicketKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
cipher.NewCBCEncrypter(block, loginTicketIV).CryptBlocks(blob, blob)
|
||||
return Base64DwcEncoding.EncodeToString(blob)
|
||||
}
|
||||
|
||||
func UnmarshalGPCMLoginTicket(ticket string) (err error, profileId uint32, issuetime time.Time) {
|
||||
blob, err := Base64DwcEncoding.DecodeString(ticket)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(blob) != 0x10 {
|
||||
err = errors.New("invalid login ticket length")
|
||||
return
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(loginTicketKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
cipher.NewCBCDecrypter(block, loginTicketIV).CryptBlocks(blob, blob)
|
||||
|
||||
if !bytes.Equal(blob[0xC:0x10], loginTicketMagic) {
|
||||
err = errors.New("invalid login ticket magic")
|
||||
return
|
||||
}
|
||||
|
||||
issuetime = time.Unix(int64(binary.LittleEndian.Uint64(blob[0x0:0x8])), 0)
|
||||
profileId = binary.LittleEndian.Uint32(blob[0x8:0xC])
|
||||
return
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ import (
|
|||
|
||||
var Base64DwcEncoding = base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.-").WithPadding('*')
|
||||
|
||||
func Base32Encode(value int64) string {
|
||||
func Base32Encode(value uint64) string {
|
||||
alpha := "0123456789abcdefghijklmnopqrstuv"
|
||||
|
||||
encoded := ""
|
||||
|
|
|
|||
|
|
@ -30,6 +30,10 @@ func ParseGameSpyMessage(msg string) ([]GameSpyCommand, error) {
|
|||
|
||||
for len(msg) > 0 && string(msg[0]) == `\` {
|
||||
keyEnd := strings.Index(msg[1:], `\`) + 1
|
||||
if keyEnd < 2 {
|
||||
return nil, InvalidGameSpyCommand
|
||||
}
|
||||
|
||||
key := msg[1:keyEnd]
|
||||
value := ""
|
||||
msg = msg[keyEnd+1:]
|
||||
|
|
|
|||
|
|
@ -58,6 +58,8 @@ type MatchCommandDataReservation struct {
|
|||
IsFriend bool
|
||||
LocalPlayerCount uint32
|
||||
ResvCheckValue uint32
|
||||
|
||||
UserData []byte
|
||||
}
|
||||
|
||||
type MatchCommandDataResvOK struct {
|
||||
|
|
@ -75,17 +77,20 @@ type MatchCommandDataResvOK struct {
|
|||
ClientCount uint32
|
||||
ResvCheckValue uint32
|
||||
|
||||
// Only exists in version 3 and 11
|
||||
// Version 3 and 11
|
||||
ProfileIDs []uint32
|
||||
|
||||
// Version 11
|
||||
IsFriend bool
|
||||
UserData uint32
|
||||
|
||||
UserData []byte
|
||||
}
|
||||
|
||||
type MatchCommandDataResvDeny struct {
|
||||
Reason uint32
|
||||
ReasonString string
|
||||
|
||||
UserData []byte
|
||||
}
|
||||
|
||||
type MatchCommandDataTellAddr struct {
|
||||
|
|
@ -173,13 +178,18 @@ func DecodeMatchCommand(command byte, buffer []byte, version int) (MatchCommandD
|
|||
return MatchCommandData{}, false
|
||||
}
|
||||
|
||||
// Match commands must be 4 byte aligned
|
||||
if (len(buffer) & 3) != 0 {
|
||||
return MatchCommandData{}, false
|
||||
}
|
||||
|
||||
switch command {
|
||||
case MatchReservation:
|
||||
if version == 3 && (len(buffer) != 0x04 && len(buffer) != 0x0C) {
|
||||
if version == 3 && len(buffer) < 0x0C {
|
||||
break
|
||||
}
|
||||
|
||||
if (version == 11 && len(buffer) != 0x14) || (version == 90 && len(buffer) != 0x24) {
|
||||
if (version == 11 && len(buffer) < 0x14) || (version == 90 && len(buffer) < 0x24) {
|
||||
break
|
||||
}
|
||||
|
||||
|
|
@ -188,7 +198,7 @@ func DecodeMatchCommand(command byte, buffer []byte, version int) (MatchCommandD
|
|||
break
|
||||
}
|
||||
|
||||
if version == 3 && len(buffer) == 0x04 {
|
||||
if version == 3 && len(buffer) < 0x0C {
|
||||
return MatchCommandData{Reservation: &MatchCommandDataReservation{
|
||||
MatchType: byte(matchType),
|
||||
HasPublicIP: false,
|
||||
|
|
@ -207,6 +217,7 @@ func DecodeMatchCommand(command byte, buffer []byte, version int) (MatchCommandD
|
|||
HasPublicIP: true,
|
||||
PublicIP: binary.BigEndian.Uint32(buffer[0x04:0x08]),
|
||||
PublicPort: uint16(publicPort),
|
||||
UserData: buffer[0x0C:],
|
||||
}}, true
|
||||
|
||||
case 11:
|
||||
|
|
@ -223,6 +234,7 @@ func DecodeMatchCommand(command byte, buffer []byte, version int) (MatchCommandD
|
|||
PublicPort: uint16(publicPort),
|
||||
IsFriend: isFriend,
|
||||
LocalPlayerCount: binary.LittleEndian.Uint32(buffer[0x10:0x14]),
|
||||
UserData: buffer[0x14:],
|
||||
}}, true
|
||||
|
||||
case 90:
|
||||
|
|
@ -248,6 +260,7 @@ func DecodeMatchCommand(command byte, buffer []byte, version int) (MatchCommandD
|
|||
IsFriend: isFriend,
|
||||
LocalPlayerCount: binary.LittleEndian.Uint32(buffer[0x1C:0x20]),
|
||||
ResvCheckValue: binary.LittleEndian.Uint32(buffer[0x20:0x24]),
|
||||
UserData: buffer[0x24:],
|
||||
}}, true
|
||||
}
|
||||
|
||||
|
|
@ -258,49 +271,50 @@ func DecodeMatchCommand(command byte, buffer []byte, version int) (MatchCommandD
|
|||
}
|
||||
|
||||
clientCount := binary.LittleEndian.Uint32(buffer[0x00:0x04])
|
||||
if version == 3 && (clientCount > 29 || len(buffer) != int(0xC+clientCount*0x4)) {
|
||||
if version == 3 && (clientCount > 29 || len(buffer) < int(0x0C+clientCount*0x4)) {
|
||||
break
|
||||
}
|
||||
if version == 11 && (clientCount > 24 || len(buffer) != int(0x20+clientCount*0x4)) {
|
||||
if version == 11 && (clientCount > 24 || len(buffer) < int(0x20+clientCount*0x4)) {
|
||||
break
|
||||
}
|
||||
|
||||
var profileIDs []uint32
|
||||
for i := uint32(0); i < clientCount; i++ {
|
||||
profileIDs = append(profileIDs, binary.LittleEndian.Uint32(buffer[0x4+i*4:0x4+i*4+4]))
|
||||
profileIDs = append(profileIDs, binary.LittleEndian.Uint32(buffer[0x04+i*4:0x04+i*4+4]))
|
||||
}
|
||||
|
||||
index := 0x4 + clientCount*4
|
||||
index := 0x04 + clientCount*4
|
||||
|
||||
publicPort := binary.LittleEndian.Uint32(buffer[index+0x4 : index+0x8])
|
||||
publicPort := binary.LittleEndian.Uint32(buffer[index+0x04 : index+0x08])
|
||||
if publicPort > 0xffff {
|
||||
break
|
||||
}
|
||||
|
||||
if version == 3 {
|
||||
return MatchCommandData{ResvOK: &MatchCommandDataResvOK{
|
||||
PublicIP: binary.BigEndian.Uint32(buffer[index : index+0x4]),
|
||||
PublicIP: binary.BigEndian.Uint32(buffer[index : index+0x04]),
|
||||
PublicPort: uint16(publicPort),
|
||||
ClientCount: clientCount,
|
||||
ProfileIDs: profileIDs,
|
||||
UserData: buffer[index+0x8:],
|
||||
}}, true
|
||||
} else if version == 11 {
|
||||
isFriendValue := binary.LittleEndian.Uint32(buffer[index+0x8 : index+0xC])
|
||||
isFriendValue := binary.LittleEndian.Uint32(buffer[index+0x08 : index+0x0C])
|
||||
if isFriendValue > 1 {
|
||||
break
|
||||
}
|
||||
isFriend := isFriendValue != 0
|
||||
|
||||
return MatchCommandData{ResvOK: &MatchCommandDataResvOK{
|
||||
MaxPlayers: binary.LittleEndian.Uint32(buffer[0x14:0x18]),
|
||||
SenderAID: binary.LittleEndian.Uint32(buffer[index+0xC : index+0x10]),
|
||||
PublicIP: binary.BigEndian.Uint32(buffer[index : index+0x4]),
|
||||
MaxPlayers: binary.LittleEndian.Uint32(buffer[index+0x14 : index+0x18]),
|
||||
SenderAID: binary.LittleEndian.Uint32(buffer[index+0x0C : index+0x10]),
|
||||
PublicIP: binary.BigEndian.Uint32(buffer[index : index+0x04]),
|
||||
PublicPort: uint16(publicPort),
|
||||
GroupID: binary.LittleEndian.Uint32(buffer[0x10:0x14]),
|
||||
GroupID: binary.LittleEndian.Uint32(buffer[index+0x10 : index+0x14]),
|
||||
ClientCount: clientCount,
|
||||
ProfileIDs: profileIDs,
|
||||
IsFriend: isFriend,
|
||||
UserData: binary.LittleEndian.Uint32(buffer[0x18:0x1C]),
|
||||
UserData: buffer[index+0x18:],
|
||||
}}, true
|
||||
}
|
||||
break
|
||||
|
|
@ -335,6 +349,7 @@ func DecodeMatchCommand(command byte, buffer []byte, version int) (MatchCommandD
|
|||
ReceiverNewAID: binary.LittleEndian.Uint32(buffer[0x28:0x2C]),
|
||||
ClientCount: binary.LittleEndian.Uint32(buffer[0x2C:0x30]),
|
||||
ResvCheckValue: binary.LittleEndian.Uint32(buffer[0x30:0x34]),
|
||||
UserData: buffer[0x34:],
|
||||
}}, true
|
||||
|
||||
case MatchResvDeny:
|
||||
|
|
@ -359,6 +374,7 @@ func DecodeMatchCommand(command byte, buffer []byte, version int) (MatchCommandD
|
|||
return MatchCommandData{ResvDeny: &MatchCommandDataResvDeny{
|
||||
Reason: reason,
|
||||
ReasonString: reasonString,
|
||||
UserData: buffer[0x4:],
|
||||
}}, true
|
||||
|
||||
case MatchResvWait:
|
||||
|
|
@ -468,6 +484,12 @@ func EncodeMatchCommand(command byte, data MatchCommandData, version int) ([]byt
|
|||
message = binary.LittleEndian.AppendUint32(message, data.Reservation.LocalPlayerCount)
|
||||
message = binary.LittleEndian.AppendUint32(message, data.Reservation.ResvCheckValue)
|
||||
}
|
||||
|
||||
message = append(message, data.Reservation.UserData...)
|
||||
if (len(message) & 3) != 0 {
|
||||
return []byte{}, false
|
||||
}
|
||||
|
||||
return message, true
|
||||
|
||||
case MatchResvOK:
|
||||
|
|
@ -483,6 +505,11 @@ func EncodeMatchCommand(command byte, data MatchCommandData, version int) ([]byt
|
|||
message = binary.LittleEndian.AppendUint32(message, uint32(data.ResvOK.PublicPort))
|
||||
|
||||
if version == 3 {
|
||||
message = append(message, data.ResvOK.UserData...)
|
||||
if (len(message) & 3) != 0 {
|
||||
return []byte{}, false
|
||||
}
|
||||
|
||||
return message, true
|
||||
}
|
||||
|
||||
|
|
@ -496,7 +523,12 @@ func EncodeMatchCommand(command byte, data MatchCommandData, version int) ([]byt
|
|||
message = binary.LittleEndian.AppendUint32(message, data.ResvOK.SenderAID)
|
||||
message = binary.LittleEndian.AppendUint32(message, data.ResvOK.GroupID)
|
||||
message = binary.LittleEndian.AppendUint32(message, data.ResvOK.MaxPlayers)
|
||||
message = binary.LittleEndian.AppendUint32(message, data.ResvOK.UserData)
|
||||
|
||||
message = append(message, data.ResvOK.UserData...)
|
||||
if (len(message) & 3) != 0 {
|
||||
return []byte{}, false
|
||||
}
|
||||
|
||||
return message, true
|
||||
}
|
||||
|
||||
|
|
@ -514,10 +546,22 @@ func EncodeMatchCommand(command byte, data MatchCommandData, version int) ([]byt
|
|||
message = binary.LittleEndian.AppendUint32(message, data.ResvOK.ReceiverNewAID)
|
||||
message = binary.LittleEndian.AppendUint32(message, data.ResvOK.ClientCount)
|
||||
message = binary.LittleEndian.AppendUint32(message, data.ResvOK.ResvCheckValue)
|
||||
|
||||
message = append(message, data.ResvOK.UserData...)
|
||||
if (len(message) & 3) != 0 {
|
||||
return []byte{}, false
|
||||
}
|
||||
|
||||
return message, true
|
||||
|
||||
case MatchResvDeny:
|
||||
message := binary.LittleEndian.AppendUint32([]byte{}, data.ResvDeny.Reason)
|
||||
|
||||
message = append(message, data.ResvOK.UserData...)
|
||||
if (len(message) & 3) != 0 {
|
||||
return []byte{}, false
|
||||
}
|
||||
|
||||
return message, true
|
||||
|
||||
case MatchResvWait:
|
||||
|
|
|
|||
|
|
@ -2,101 +2,13 @@ package database
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha512"
|
||||
"encoding/hex"
|
||||
"github.com/jackc/pgx/v4"
|
||||
"github.com/jackc/pgx/v4/pgxpool"
|
||||
"github.com/logrusorgru/aurora/v3"
|
||||
"os"
|
||||
"strconv"
|
||||
"wwfc/common"
|
||||
"wwfc/logging"
|
||||
)
|
||||
|
||||
const (
|
||||
DoesAuthTokenExist = `SELECT EXISTS(SELECT 1 FROM logins WHERE auth_token = $1)`
|
||||
DoesNASUserExist = `SELECT EXISTS(SELECT 1 FROM logins WHERE user_id = $1 AND gsbrcd = $2)`
|
||||
UpdateUserLogin = `UPDATE logins SET auth_token = $1, challenge = $2 WHERE user_id = $3 AND gsbrcd = $4`
|
||||
InsertUserLogin = `INSERT INTO logins (auth_token, user_id, gsbrcd, challenge) VALUES ($1, $2, $3, $4)`
|
||||
GetNASUserLogin = `SELECT user_id, gsbrcd FROM logins WHERE auth_token = $1 LIMIT 1`
|
||||
GetNASChallenge = `SELECT challenge FROM logins WHERE auth_token = $1`
|
||||
)
|
||||
|
||||
var salt []byte
|
||||
|
||||
// GenerateAuthToken generates and stores the auth token for this user as well as a challenge.
|
||||
func GenerateAuthToken(pool *pgxpool.Pool, ctx context.Context, userId int64, gsbrcd string) (string, string) {
|
||||
var userExists bool
|
||||
err := pool.QueryRow(ctx, DoesNASUserExist, userId, gsbrcd).Scan(&userExists)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
authToken := "NDS" + common.RandomString(80)
|
||||
for {
|
||||
// We must make sure that the auth token doesn't exist before attempting to insert it into the database.
|
||||
var tokenExists bool
|
||||
err := pool.QueryRow(ctx, DoesAuthTokenExist, authToken).Scan(&tokenExists)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if !tokenExists {
|
||||
break
|
||||
}
|
||||
|
||||
authToken = "NDS" + common.RandomString(80)
|
||||
}
|
||||
|
||||
challenge := common.RandomString(8)
|
||||
if userExists {
|
||||
// UPDATE rather than INSERT
|
||||
_, err = pool.Exec(ctx, UpdateUserLogin, authToken, challenge, userId, gsbrcd)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
_, err = pool.Exec(ctx, InsertUserLogin, authToken, userId, gsbrcd, challenge)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
return authToken, challenge
|
||||
}
|
||||
|
||||
func GetNASLogin(pool *pgxpool.Pool, ctx context.Context, authToken string) (int64, string) {
|
||||
var userId int64
|
||||
var gsbrcd string
|
||||
err := pool.QueryRow(ctx, GetNASUserLogin, authToken).Scan(&userId, &gsbrcd)
|
||||
if err != nil {
|
||||
if err == pgx.ErrNoRows {
|
||||
return 0, ""
|
||||
} else {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
return userId, gsbrcd
|
||||
}
|
||||
|
||||
func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, authToken string) (User, bool) {
|
||||
// Make sure salt is loaded
|
||||
if salt == nil {
|
||||
var err error
|
||||
salt, err = os.ReadFile("salt.bin")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Query login table with the auth token.
|
||||
userId, gsbrcd := GetNASLogin(pool, ctx, authToken)
|
||||
if userId == 0 {
|
||||
logging.Error("DATABASE", "Invalid auth token:", aurora.Cyan(authToken))
|
||||
return User{}, false
|
||||
}
|
||||
|
||||
func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, userId uint64, gsbrcd string) (User, bool) {
|
||||
var exists bool
|
||||
err := pool.QueryRow(ctx, DoesUserExist, userId, gsbrcd).Scan(&exists)
|
||||
if err != nil {
|
||||
|
|
@ -104,12 +16,10 @@ func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, authToken string)
|
|||
}
|
||||
|
||||
uniqueNickname := common.Base32Encode(userId) + gsbrcd
|
||||
password := sha512.Sum512(append(salt, []byte(gsbrcd)...))
|
||||
|
||||
user := User{
|
||||
UserId: userId,
|
||||
GsbrCode: gsbrcd,
|
||||
Password: hex.EncodeToString(password[:]),
|
||||
Email: uniqueNickname + "@nds",
|
||||
UniqueNick: uniqueNickname,
|
||||
}
|
||||
|
|
@ -118,30 +28,15 @@ func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, authToken string)
|
|||
// Create the GPCM account
|
||||
user.CreateUser(pool, ctx)
|
||||
|
||||
logging.Notice("DATABASE", "Created new GPCM user:", aurora.Cyan(strconv.FormatInt(user.UserId, 10)), aurora.Cyan(user.GsbrCode), "-", aurora.Cyan(strconv.FormatInt(int64(user.ProfileId), 10)))
|
||||
logging.Notice("DATABASE", "Created new GPCM user:", aurora.Cyan(userId), aurora.Cyan(gsbrcd), "-", aurora.Cyan(user.ProfileId))
|
||||
} else {
|
||||
err := pool.QueryRow(ctx, GetUserProfileID, userId, gsbrcd).Scan(&user.ProfileId)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
logging.Notice("DATABASE", "Log in GPCM user:", aurora.Cyan(strconv.FormatInt(user.UserId, 10)), aurora.Cyan(user.GsbrCode), "-", aurora.Cyan(strconv.FormatInt(int64(user.ProfileId), 10)))
|
||||
logging.Notice("DATABASE", "Log in GPCM user:", aurora.Cyan(userId), aurora.Cyan(user.GsbrCode), "-", aurora.Cyan(user.ProfileId))
|
||||
}
|
||||
|
||||
return user, true
|
||||
}
|
||||
|
||||
func GetChallenge(pool *pgxpool.Pool, ctx context.Context, authToken string) string {
|
||||
var challenge string
|
||||
err := pool.QueryRow(ctx, GetNASChallenge, authToken).Scan(&challenge)
|
||||
if err != nil {
|
||||
if err == pgx.ErrNoRows {
|
||||
// Invalid auth token
|
||||
return ""
|
||||
} else {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
return challenge
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,17 +2,14 @@ package database
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/jackc/pgx/v4"
|
||||
"github.com/jackc/pgx/v4/pgxpool"
|
||||
"math/rand"
|
||||
"wwfc/common"
|
||||
)
|
||||
|
||||
const (
|
||||
InsertUser = `INSERT INTO users (user_id, gsbrcd, password, email, unique_nick) VALUES ($1, $2, $3, $4, $5) RETURNING profile_id`
|
||||
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 RETURNING user_id, gsbrcd, password, email, unique_nick, firstname, lastname`
|
||||
GetUser = `SELECT user_id, gsbrcd, password, email, unique_nick, firstname, lastname FROM users 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 WHERE profile_id = $1 RETURNING user_id, gsbrcd, email, unique_nick, firstname, lastname`
|
||||
GetUser = `SELECT user_id, gsbrcd, email, unique_nick, firstname, lastname FROM users WHERE profile_id = $1`
|
||||
CreateUserSession = `INSERT INTO sessions (session_key, profile_id, login_ticket) VALUES ($1, $2, $3)`
|
||||
GetTicketSession = `SELECT session_key, profile_id FROM sessions WHERE login_ticket = $1`
|
||||
DoesUserExist = `SELECT EXISTS(SELECT 1 FROM users WHERE user_id = $1 AND gsbrcd = $2)`
|
||||
|
|
@ -25,9 +22,8 @@ const (
|
|||
|
||||
type User struct {
|
||||
ProfileId uint32
|
||||
UserId int64
|
||||
UserId uint64
|
||||
GsbrCode string
|
||||
Password string
|
||||
Email string
|
||||
UniqueNick string
|
||||
FirstName string
|
||||
|
|
@ -35,15 +31,15 @@ type User struct {
|
|||
}
|
||||
|
||||
func (user *User) CreateUser(pool *pgxpool.Pool, ctx context.Context) {
|
||||
err := pool.QueryRow(ctx, InsertUser, user.UserId, user.GsbrCode, user.Password, user.Email, user.UniqueNick).Scan(&user.ProfileId)
|
||||
err := pool.QueryRow(ctx, InsertUser, user.UserId, user.GsbrCode, "", user.Email, user.UniqueNick).Scan(&user.ProfileId)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func GetUniqueUserID() int64 {
|
||||
func GetUniqueUserID() uint64 {
|
||||
// Not guaranteed unique but doesn't matter in practice if multiple people have the same user ID.
|
||||
return rand.Int63n(0x80000000000)
|
||||
return uint64(rand.Int63n(0x80000000000))
|
||||
}
|
||||
|
||||
func UpdateProfile(pool *pgxpool.Pool, ctx context.Context, profileId uint32, data map[string]string) User {
|
||||
|
|
@ -52,7 +48,7 @@ func UpdateProfile(pool *pgxpool.Pool, ctx context.Context, profileId uint32, da
|
|||
|
||||
user := User{}
|
||||
row := pool.QueryRow(ctx, UpdateUserTable, profileId, firstName, firstNameExists, lastName, lastNameExists)
|
||||
err := row.Scan(&user.UserId, &user.GsbrCode, &user.Password, &user.Email, &user.UniqueNick, &user.FirstName, &user.LastName)
|
||||
err := row.Scan(&user.UserId, &user.GsbrCode, &user.Email, &user.UniqueNick, &user.FirstName, &user.LastName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
@ -61,38 +57,10 @@ func UpdateProfile(pool *pgxpool.Pool, ctx context.Context, profileId uint32, da
|
|||
return user
|
||||
}
|
||||
|
||||
func CreateSession(pool *pgxpool.Pool, ctx context.Context, profileId uint32, loginTicket string) string {
|
||||
sessionKey := common.RandomString(8)
|
||||
_, err := pool.Exec(ctx, CreateUserSession, sessionKey, profileId, loginTicket)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return sessionKey
|
||||
}
|
||||
|
||||
func GetSession(pool *pgxpool.Pool, ctx context.Context, loginTicket string) (string, uint32) {
|
||||
var sessionKey string
|
||||
var profileId uint32
|
||||
err := pool.QueryRow(ctx, GetTicketSession, loginTicket).Scan(&sessionKey, &profileId)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return sessionKey, profileId
|
||||
}
|
||||
|
||||
func deleteSession(pool *pgxpool.Pool, ctx context.Context, profileId uint32) {
|
||||
_, err := pool.Exec(ctx, DeleteUserSession, profileId)
|
||||
if err != nil && !errors.Is(err, pgx.ErrNoRows) {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
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.Password, &user.Email, &user.UniqueNick, &user.FirstName, &user.LastName)
|
||||
err := row.Scan(&user.UserId, &user.GsbrCode, &user.Email, &user.UniqueNick, &user.FirstName, &user.LastName)
|
||||
if err != nil {
|
||||
return User{}, false
|
||||
}
|
||||
|
|
|
|||
2
go.mod
2
go.mod
|
|
@ -1,6 +1,6 @@
|
|||
module wwfc
|
||||
|
||||
go 1.20
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/jackc/pgx/v4 v4.18.1
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package gpcm
|
|||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/logrusorgru/aurora/v3"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
|
@ -263,6 +264,12 @@ func (g *GameSpySession) bestieMessage(command common.GameSpyCommand) {
|
|||
break
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
@ -272,16 +279,23 @@ func (g *GameSpySession) bestieMessage(command common.GameSpyCommand) {
|
|||
}
|
||||
|
||||
if cmd == common.MatchReservation {
|
||||
if common.IPFormatNoPortToInt(g.Conn.RemoteAddr().String()) == int32(msgMatchData.Reservation.PublicIP) {
|
||||
g.QR2IP = uint64(msgMatchData.Reservation.PublicIP) | (uint64(msgMatchData.Reservation.PublicPort) << 32)
|
||||
if common.IPFormatNoPortToInt(g.Conn.RemoteAddr().String()) != int32(msgMatchData.Reservation.PublicIP) {
|
||||
logging.Error(g.ModuleName, "RESERVATION: Public IP mismatch")
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
} else if cmd == common.MatchResvOK {
|
||||
if common.IPFormatNoPortToInt(g.Conn.RemoteAddr().String()) == int32(msgMatchData.ResvOK.PublicIP) {
|
||||
g.QR2IP = uint64(msgMatchData.ResvOK.PublicIP) | (uint64(msgMatchData.ResvOK.PublicPort) << 32)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Replace public IP with QR2 search ID
|
||||
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()
|
||||
|
|
@ -300,7 +314,10 @@ func (g *GameSpySession) bestieMessage(command common.GameSpyCommand) {
|
|||
}
|
||||
|
||||
if cmd == common.MatchReservation {
|
||||
g.ReservationPID = uint32(toProfileId)
|
||||
msgMatchData.Reservation.PublicIP = 0
|
||||
msgMatchData.Reservation.PublicPort = 0
|
||||
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 {
|
||||
logging.Error(g.ModuleName, "Destination", aurora.Cyan(toProfileId), "has no reservation with the sender")
|
||||
|
|
@ -319,9 +336,32 @@ func (g *GameSpySession) bestieMessage(command common.GameSpyCommand) {
|
|||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
if g.QR2IP&0xffffffff != toSession.QR2IP&0xffffffff {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newMsg, ok := common.EncodeMatchCommand(cmd, msgMatchData, version)
|
||||
if !ok || len(newMsg) > 0x200 {
|
||||
logging.Error(g.ModuleName, "Failed to encode match command; message:", msg)
|
||||
g.replyError(ErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
if cmd == common.MatchReservation {
|
||||
g.ReservationPID = uint32(toProfileId)
|
||||
}
|
||||
|
||||
sendMessageToSession("1", g.User.ProfileId, toSession, msg)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,12 +3,13 @@ package gpcm
|
|||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/logrusorgru/aurora/v3"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"wwfc/common"
|
||||
"wwfc/database"
|
||||
"wwfc/logging"
|
||||
|
|
@ -96,7 +97,7 @@ func verifySignature(authToken string, signature string) bool {
|
|||
logging.Error("GPCM", "Auth token signature failed")
|
||||
return false
|
||||
}
|
||||
logging.Notice("GPCM", "Auth token signature verified")
|
||||
logging.Notice("GPCM", "Auth token signature verified; NG ID:", aurora.Cyan(fmt.Sprintf("%08x", ngId)))
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
@ -124,24 +125,27 @@ func (g *GameSpySession) login(command common.GameSpyCommand) {
|
|||
}
|
||||
|
||||
signature, exists := command.OtherValues["wwfc_sig"]
|
||||
if exists {
|
||||
// TODO: This is still in testing so it's only checked if it exists
|
||||
if !verifySignature(authToken, signature) {
|
||||
g.replyError(GPError{
|
||||
ErrorCode: ErrLogin.ErrorCode,
|
||||
ErrorString: "The authentication signature is invalid.",
|
||||
Fatal: true,
|
||||
})
|
||||
return
|
||||
}
|
||||
if !exists || !verifySignature(authToken, signature) {
|
||||
g.replyError(GPError{
|
||||
ErrorCode: ErrLogin.ErrorCode,
|
||||
ErrorString: "The authentication signature is invalid.",
|
||||
Fatal: true,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
challenge := database.GetChallenge(pool, ctx, authToken)
|
||||
if challenge == "" {
|
||||
err, _, issueTime, userId, gsbrcd, cfc, _, _, ingamesn, challenge := common.UnmarshalNASAuthToken(authToken)
|
||||
if err != nil {
|
||||
g.replyError(ErrLogin)
|
||||
return
|
||||
}
|
||||
|
||||
currentTime := time.Now()
|
||||
if issueTime.Before(currentTime.Add(-10*time.Minute)) || issueTime.After(currentTime) {
|
||||
g.replyError(ErrLoginLoginTicketExpired)
|
||||
return
|
||||
}
|
||||
|
||||
response := generateResponse(g.Challenge, challenge, authToken, command.OtherValues["challenge"])
|
||||
if response != command.OtherValues["response"] {
|
||||
g.replyError(ErrLogin)
|
||||
|
|
@ -151,7 +155,7 @@ func (g *GameSpySession) login(command common.GameSpyCommand) {
|
|||
proof := generateProof(g.Challenge, challenge, command.OtherValues["authtoken"], command.OtherValues["challenge"])
|
||||
|
||||
// Perform the login with the database.
|
||||
user, ok := database.LoginUserToGPCM(pool, ctx, authToken)
|
||||
user, ok := database.LoginUserToGPCM(pool, ctx, userId, gsbrcd)
|
||||
if !ok {
|
||||
// There was an error logging in to the GP backend.
|
||||
g.replyError(ErrLogin)
|
||||
|
|
@ -175,10 +179,9 @@ func (g *GameSpySession) login(command common.GameSpyCommand) {
|
|||
sessions[g.User.ProfileId] = g
|
||||
mutex.Unlock()
|
||||
|
||||
g.LoginTicket = strings.Replace(base64.StdEncoding.EncodeToString([]byte(common.RandomString(16))), "=", "_", -1)
|
||||
// Now initiate the session
|
||||
_ = database.CreateSession(pool, ctx, g.User.ProfileId, g.LoginTicket)
|
||||
g.LoginTicket = common.MarshalGPCMLoginTicket(g.User.ProfileId)
|
||||
g.SessionKey = rand.Int31n(290000000) + 10000000
|
||||
g.InGameName = ingamesn
|
||||
|
||||
g.LoggedIn = true
|
||||
g.ModuleName = "GPCM:" + strconv.FormatInt(int64(g.User.ProfileId), 10)
|
||||
|
|
@ -186,7 +189,7 @@ func (g *GameSpySession) login(command common.GameSpyCommand) {
|
|||
|
||||
// Notify QR2 of the login
|
||||
// TODO: Get ingamesn and cfc from NAS
|
||||
qr2.Login(g.User.ProfileId, "", "", g.Conn.RemoteAddr().String())
|
||||
qr2.Login(g.User.ProfileId, ingamesn, cfc, g.Conn.RemoteAddr().String())
|
||||
|
||||
payload := common.CreateGameSpyMessage(common.GameSpyCommand{
|
||||
Command: "lc",
|
||||
|
|
@ -194,8 +197,8 @@ func (g *GameSpySession) login(command common.GameSpyCommand) {
|
|||
OtherValues: map[string]string{
|
||||
"sesskey": strconv.FormatInt(int64(g.SessionKey), 10),
|
||||
"proof": proof,
|
||||
"userid": strconv.FormatInt(g.User.UserId, 10),
|
||||
"profileid": strconv.FormatInt(int64(g.User.ProfileId), 10),
|
||||
"userid": strconv.FormatUint(g.User.UserId, 10),
|
||||
"profileid": strconv.FormatUint(uint64(g.User.ProfileId), 10),
|
||||
"uniquenick": g.User.UniqueNick,
|
||||
"lt": g.LoginTicket,
|
||||
"id": command.OtherValues["id"],
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ type GameSpySession struct {
|
|||
Challenge string
|
||||
LoginTicket string
|
||||
SessionKey int32
|
||||
InGameName string
|
||||
Status string
|
||||
LocString string
|
||||
FriendList []uint32
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ func (g *GameSpySession) getProfile(command common.GameSpyCommand) {
|
|||
OtherValues: map[string]string{
|
||||
"profileid": command.OtherValues["profileid"],
|
||||
"nick": user.UniqueNick,
|
||||
"userid": strconv.FormatInt(user.UserId, 10),
|
||||
"userid": strconv.FormatUint(uint64(user.UserId), 10),
|
||||
"email": user.Email,
|
||||
"sig": common.RandomHexString(32),
|
||||
"uniquenick": user.UniqueNick,
|
||||
|
|
|
|||
74
nas/auth.go
74
nas/auth.go
|
|
@ -1,6 +1,8 @@
|
|||
package nas
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/logrusorgru/aurora/v3"
|
||||
"net/http"
|
||||
|
|
@ -10,6 +12,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf16"
|
||||
"wwfc/common"
|
||||
"wwfc/database"
|
||||
"wwfc/logging"
|
||||
|
|
@ -30,7 +33,7 @@ func handleAuthRequest(moduleName string, w http.ResponseWriter, r *http.Request
|
|||
fields := map[string]string{}
|
||||
for key, values := range r.PostForm {
|
||||
if len(values) != 1 {
|
||||
logging.Warn(moduleName, "Ignoring multiple POST form values:", aurora.Cyan(key).String()+":", aurora.Cyan(values))
|
||||
logging.Warn(moduleName, "Ignoring none or multiple POST form values:", aurora.Cyan(key).String()+":", aurora.Cyan(values))
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -40,8 +43,20 @@ func handleAuthRequest(moduleName string, w http.ResponseWriter, r *http.Request
|
|||
replyHTTPError(w, 400, "400 Bad Request")
|
||||
return
|
||||
}
|
||||
logging.Info(moduleName, aurora.Cyan(key).String()+":", aurora.Cyan(string(parsed)))
|
||||
fields[key] = string(parsed)
|
||||
|
||||
value := string(parsed)
|
||||
|
||||
if key == "ingamesn" {
|
||||
// Special handling required for the UTF-16 string
|
||||
var utf16String []uint16
|
||||
for i := 0; i < len(parsed)/2; i++ {
|
||||
utf16String = append(utf16String, binary.BigEndian.Uint16(parsed[i*2:i*2+2]))
|
||||
}
|
||||
value = string(utf16.Decode(utf16String))
|
||||
}
|
||||
|
||||
logging.Info(moduleName, aurora.Cyan(key).String()+":", aurora.Cyan(value))
|
||||
fields[key] = value
|
||||
}
|
||||
|
||||
reply := map[string]string{}
|
||||
|
|
@ -139,7 +154,7 @@ func acctcreate() map[string]string {
|
|||
"retry": "0",
|
||||
"datetime": getDateTime(),
|
||||
"returncd": "002",
|
||||
"userid": strconv.FormatInt(database.GetUniqueUserID(), 10),
|
||||
"userid": strconv.FormatUint(database.GetUniqueUserID(), 10),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -150,6 +165,13 @@ func login(moduleName string, fields map[string]string) map[string]string {
|
|||
"locator": "gamespy.com",
|
||||
}
|
||||
|
||||
gamecd, ok := fields["gamecd"]
|
||||
if !ok {
|
||||
logging.Error(moduleName, "No gamecd in form")
|
||||
param["returncd"] = "103"
|
||||
return param
|
||||
}
|
||||
|
||||
strUserId, ok := fields["userid"]
|
||||
if !ok {
|
||||
logging.Error(moduleName, "No userid in form")
|
||||
|
|
@ -157,8 +179,8 @@ func login(moduleName string, fields map[string]string) map[string]string {
|
|||
return param
|
||||
}
|
||||
|
||||
userId, err := strconv.ParseInt(strUserId, 10, 64)
|
||||
if err != nil {
|
||||
userId, err := strconv.ParseUint(strUserId, 10, 64)
|
||||
if err != nil || userId >= 0x80000000000 {
|
||||
logging.Error(moduleName, "Invalid userid string in form")
|
||||
param["returncd"] = "103"
|
||||
return param
|
||||
|
|
@ -171,7 +193,45 @@ func login(moduleName string, fields map[string]string) map[string]string {
|
|||
return param
|
||||
}
|
||||
|
||||
authToken, challenge := database.GenerateAuthToken(pool, ctx, userId, gsbrcd)
|
||||
cfc, ok := fields["cfc"]
|
||||
if !ok {
|
||||
logging.Error(moduleName, "No cfc in form")
|
||||
param["returncd"] = "103"
|
||||
return param
|
||||
}
|
||||
|
||||
cfcInt, err := strconv.ParseUint(cfc, 10, 64)
|
||||
if err != nil || cfcInt > 9999999999999999 {
|
||||
logging.Error(moduleName, "Invalid cfc string in form")
|
||||
param["returncd"] = "103"
|
||||
return param
|
||||
}
|
||||
|
||||
region, ok := fields["region"]
|
||||
if !ok {
|
||||
region = "ff"
|
||||
}
|
||||
|
||||
regionByte, err := hex.DecodeString(region)
|
||||
if err != nil || len(regionByte) != 1 {
|
||||
logging.Error(moduleName, "Invalid region byte in form")
|
||||
param["returncd"] = "103"
|
||||
return param
|
||||
}
|
||||
|
||||
lang, ok := fields["lang"]
|
||||
if !ok {
|
||||
lang = "ff"
|
||||
}
|
||||
|
||||
langByte, err := hex.DecodeString(lang)
|
||||
if err != nil || len(langByte) != 1 {
|
||||
logging.Error(moduleName, "Invalid lang byte in form")
|
||||
param["returncd"] = "103"
|
||||
return param
|
||||
}
|
||||
|
||||
authToken, challenge := common.MarshalNASAuthToken(gamecd, userId, gsbrcd, cfcInt, regionByte[0], langByte[0], fields["ingamesn"])
|
||||
|
||||
param["returncd"] = "001"
|
||||
param["challenge"] = challenge
|
||||
|
|
|
|||
|
|
@ -187,6 +187,9 @@ func GetGroups(gameName string) []GroupInfo {
|
|||
for k, v := range session.Data {
|
||||
mapData[k] = v
|
||||
}
|
||||
|
||||
mapData["+ingamesn"] = session.Login.InGameName
|
||||
|
||||
groupInfo.Players = append(groupInfo.Players, mapData)
|
||||
|
||||
if mapData["dwc_hoststate"] == "2" && mapData["dwc_suspend"] == "0" {
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@ package qr2
|
|||
type LoginInfo struct {
|
||||
ProfileID uint32
|
||||
InGameName string
|
||||
ConsoleFriendCode string
|
||||
ConsoleFriendCode uint64
|
||||
GPPublicIP string
|
||||
}
|
||||
|
||||
var logins = map[uint32]LoginInfo{}
|
||||
|
||||
func Login(profileID uint32, inGameName, consoleFriendCode, publicIP string) {
|
||||
func Login(profileID uint32, inGameName string, consoleFriendCode uint64, publicIP string) {
|
||||
mutex.Lock()
|
||||
logins[profileID] = LoginInfo{
|
||||
ProfileID: profileID,
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ func SendClientMessage(senderIP string, destSearchID uint64, message []byte) {
|
|||
var receiver *Session
|
||||
senderIPInt, _ := common.IPFormatToInt(senderIP)
|
||||
|
||||
useSearchID := destSearchID < (0x400 << 32)
|
||||
useSearchID := destSearchID < (1 << 24)
|
||||
if useSearchID {
|
||||
receiver = sessionBySearchID[destSearchID]
|
||||
} else {
|
||||
|
|
@ -62,7 +62,7 @@ func SendClientMessage(senderIP string, destSearchID uint64, message []byte) {
|
|||
moduleName = "QR2/MSG:s" + strconv.FormatUint(uint64(natnegID), 10)
|
||||
} else if bytes.Equal(message[:4], []byte{0xbb, 0x49, 0xcc, 0x4d}) || bytes.Equal(message[:4], []byte("SBCM")) {
|
||||
// DWC match command
|
||||
if len(message) < 0x14 {
|
||||
if len(message) < 0x14 || len(message) > 0x94 {
|
||||
logging.Error(moduleName, "Received invalid length match command packet")
|
||||
return
|
||||
}
|
||||
|
|
@ -198,7 +198,7 @@ func SendClientMessage(senderIP string, destSearchID uint64, message []byte) {
|
|||
|
||||
var matchMessage []byte
|
||||
matchMessage, ok = common.EncodeMatchCommand(message[8], matchData, version)
|
||||
if !ok {
|
||||
if !ok || len(matchMessage) > 0x80 {
|
||||
logging.Error(moduleName, "Failed to reencode match command:", aurora.Cyan(printHex(message)))
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ type Session struct {
|
|||
Addr net.Addr
|
||||
Challenge string
|
||||
Authenticated bool
|
||||
Login LoginInfo
|
||||
LastKeepAlive int64
|
||||
Endianness byte // Some fields depend on the client's endianness
|
||||
Data map[string]string
|
||||
|
|
@ -107,7 +108,7 @@ func setSessionData(moduleName string, addr net.Addr, sessionId uint32, payload
|
|||
|
||||
// Set search ID
|
||||
for {
|
||||
searchID := uint64(rand.Int63n((0x400<<32)-1) + 1)
|
||||
searchID := uint64(rand.Int63n((1<<24)-1) + 1)
|
||||
if _, exists := sessionBySearchID[searchID]; !exists {
|
||||
session.SearchID = searchID
|
||||
session.Data["+searchid"] = strconv.FormatUint(searchID, 10)
|
||||
|
|
@ -155,7 +156,9 @@ func (session *Session) setProfileID(moduleName string, newPID string) bool {
|
|||
|
||||
// Check if the public IP matches the one used for the GPCM session
|
||||
var gpPublicIP string
|
||||
if loginInfo, ok := logins[uint32(profileID)]; ok {
|
||||
var loginInfo LoginInfo
|
||||
var ok bool
|
||||
if loginInfo, ok = logins[uint32(profileID)]; ok {
|
||||
gpPublicIP = loginInfo.GPPublicIP
|
||||
} else {
|
||||
logging.Error(moduleName, "Provided dwc_pid is not logged in:", aurora.Cyan(newPID))
|
||||
|
|
@ -167,6 +170,8 @@ func (session *Session) setProfileID(moduleName string, newPID string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
session.Login = loginInfo
|
||||
|
||||
// Constraint: only one session can exist with a profile ID
|
||||
var outdated []uint64
|
||||
for sessionAddr, otherSession := range sessions {
|
||||
|
|
@ -231,3 +236,14 @@ func GetSessionServers() []map[string]string {
|
|||
|
||||
return servers
|
||||
}
|
||||
|
||||
func GetSearchID(addr uint64) uint64 {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
if session := sessions[addr]; session != nil {
|
||||
return session.SearchID
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -191,8 +191,13 @@ func getRequestIdentity(moduleName string, request StorageRequestData) (uint32,
|
|||
panic("Invalid secret key")
|
||||
}
|
||||
|
||||
// GetSession panics if it can't find the session for the login ticket
|
||||
_, profileId := database.GetSession(pool, ctx, request.LoginTicket)
|
||||
logging.Notice(moduleName, request.LoginTicket)
|
||||
|
||||
err, profileId, _ := common.UnmarshalGPCMLoginTicket(request.LoginTicket)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
logging.Info(moduleName, "Profile ID:", aurora.BrightCyan(profileId))
|
||||
logging.Info(moduleName, "Game:", aurora.Cyan(request.GameID), "-", aurora.BrightCyan(gameInfo.Name))
|
||||
logging.Info(moduleName, "Table ID:", aurora.Cyan(request.TableID))
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user