Database: Support multiple devices on one profile

This commit is contained in:
Palapeli 2025-03-01 03:49:59 -05:00
parent 46b82d1262
commit 2e798b591a
No known key found for this signature in database
GPG Key ID: 1FFE8F556A474925
6 changed files with 111 additions and 31 deletions

View File

@ -40,19 +40,29 @@ type Config struct {
APISecret string `xml:"apiSecret"`
AllowDefaultDolphinKeys bool `xml:"allowDefaultDolphinKeys"`
AllowDefaultDolphinKeys bool `xml:"allowDefaultDolphinKeys"`
AllowMultipleDeviceIDs bool `xml:"allowMultipleDeviceIDs"`
AllowConnectWithoutDeviceID bool `xml:"allowConnectWithoutDeviceID"`
ServerName string `xml:"serverName,omitempty"`
}
var config Config
var configLoaded bool
func GetConfig() Config {
if configLoaded {
return config
}
data, err := os.ReadFile("config.xml")
if err != nil {
panic(err)
}
var config Config
config.AllowDefaultDolphinKeys = true
config.AllowMultipleDeviceIDs = false
config.AllowConnectWithoutDeviceID = false
config.ServerName = "WiiLink"
err = xml.Unmarshal(data, &config)

View File

@ -32,7 +32,10 @@ func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, userId uint64, gsb
if !exists {
user.ProfileId = profileId
user.NgDeviceId = ngDeviceId
user.NgDeviceId = []uint32{ngDeviceId}
if ngDeviceId == 0 {
user.NgDeviceId = []uint32{}
}
user.UniqueNick = common.Base32Encode(userId) + gsbrcd
user.Email = user.UniqueNick + "@nds"
@ -45,10 +48,9 @@ func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, userId uint64, gsb
logging.Notice("DATABASE", "Created new GPCM user:", aurora.Cyan(userId), aurora.Cyan(gsbrcd), aurora.Cyan(user.ProfileId))
} else {
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, &user.OpenHost)
err := pool.QueryRow(ctx, GetUserProfileID, userId, gsbrcd).Scan(&user.ProfileId, &user.NgDeviceId, &user.Email, &user.UniqueNick, &firstName, &lastName, &user.OpenHost)
if err != nil {
return User{}, err
}
@ -61,18 +63,44 @@ func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, userId uint64, gsb
user.LastName = *lastName
}
if expectedNgId != nil && *expectedNgId != 0 {
user.NgDeviceId = *expectedNgId
if ngDeviceId != 0 && user.NgDeviceId != ngDeviceId {
logging.Error("DATABASE", "NG device ID mismatch for profile", aurora.Cyan(user.ProfileId), "- expected", aurora.Cyan(fmt.Sprintf("%08x", user.NgDeviceId)), "but got", aurora.Cyan(fmt.Sprintf("%08x", ngDeviceId)))
validDeviceId := false
deviceIdList := ""
for index, id := range user.NgDeviceId {
if id == ngDeviceId {
validDeviceId = true
break
}
if id == 0 {
// Replace the 0 with the actual device ID
user.NgDeviceId[index] = ngDeviceId
_, err = pool.Exec(ctx, UpdateUserNGDeviceID, user.ProfileId, user.NgDeviceId)
validDeviceId = true
break
}
deviceIdList += aurora.Cyan(fmt.Sprintf("%08x", id)).String() + ", "
}
if !validDeviceId && ngDeviceId != 0 {
if len(user.NgDeviceId) > 0 && !common.GetConfig().AllowMultipleDeviceIDs {
logging.Error("DATABASE", "NG device ID mismatch for profile", aurora.Cyan(user.ProfileId), "- expected one of {", deviceIdList[:len(deviceIdList)-2], "} but got", aurora.Cyan(fmt.Sprintf("%08x", ngDeviceId)))
return User{}, ErrDeviceIDMismatch
} else if len(user.NgDeviceId) > 0 {
logging.Warn("DATABASE", "Adding NG device ID", aurora.Cyan(fmt.Sprintf("%08x", ngDeviceId)), "to profile", aurora.Cyan(user.ProfileId))
}
user.NgDeviceId = append(user.NgDeviceId, ngDeviceId)
_, err = pool.Exec(ctx, UpdateUserNGDeviceID, user.ProfileId, user.NgDeviceId)
} else if !validDeviceId && ngDeviceId == 0 {
if len(user.NgDeviceId) > 0 && !common.GetConfig().AllowConnectWithoutDeviceID {
logging.Error("DATABASE", "NG device ID not provided for profile", aurora.Cyan(user.ProfileId), "- expected one of {", deviceIdList[:len(deviceIdList)-2], "} but got", aurora.Cyan("00000000"))
return User{}, ErrDeviceIDMismatch
}
} else if ngDeviceId != 0 {
user.NgDeviceId = ngDeviceId
_, err := pool.Exec(ctx, UpdateUserNGDeviceID, user.ProfileId, ngDeviceId)
if err != nil {
return User{}, err
}
}
if err != nil {
return User{}, err
}
if profileId != 0 && user.ProfileId != profileId {
@ -103,9 +131,9 @@ func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, userId uint64, gsb
// Find ban from device ID or IP address
var banExists bool
var banTOS bool
var bannedDeviceId uint32
var bannedDeviceIdList []uint32
timeNow := time.Now()
err = pool.QueryRow(ctx, SearchUserBan, user.ProfileId, user.NgDeviceId, ipAddress, timeNow).Scan(&banExists, &banTOS, &bannedDeviceId)
err = pool.QueryRow(ctx, SearchUserBan, user.NgDeviceId, user.ProfileId, ipAddress, timeNow).Scan(&banExists, &banTOS, &bannedDeviceIdList)
if err != nil {
if err != pgx.ErrNoRows {
return User{}, err
@ -115,6 +143,25 @@ func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, userId uint64, gsb
}
if banExists {
// Find first device ID in common
bannedDeviceId := uint32(0)
for _, id := range bannedDeviceIdList {
for _, id2 := range user.NgDeviceId {
if id == id2 {
bannedDeviceId = id
break
}
}
if bannedDeviceId != 0 {
break
}
}
if bannedDeviceId == 0 && len(bannedDeviceIdList) > 0 {
bannedDeviceId = bannedDeviceIdList[len(bannedDeviceIdList)-1]
}
if banTOS {
logging.Warn("DATABASE", "Profile", aurora.Cyan(user.ProfileId), "is banned")
return User{RestrictedDeviceId: bannedDeviceId}, ErrProfileBannedTOS

View File

@ -22,9 +22,29 @@ const (
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`
DisableUserBan = `UPDATE users SET has_ban = false WHERE profile_id = $1`
SearchUserBan = `WITH known_ng_device_ids AS (
WITH RECURSIVE device_tree AS (
SELECT unnest(ng_device_id) AS device_id
FROM users
WHERE ng_device_id && $1
UNION
SELECT unnest(ng_device_id)
FROM users
JOIN device_tree dt
ON ng_device_id && array[dt.device_id]
) SELECT array_agg(DISTINCT device_id) FROM device_tree
)
SELECT has_ban, ban_tos, ng_device_id
FROM users
WHERE has_ban = true
AND (profile_id = $2
OR ng_device_id && (SELECT * FROM known_ng_device_ids)
OR last_ip_address = $3)
AND (ban_expires IS NULL OR ban_expires > $4)
ORDER BY ban_tos DESC LIMIT 1`
GetMKWFriendInfoQuery = `SELECT mariokartwii_friend_info FROM users WHERE profile_id = $1`
UpdateMKWFriendInfoQuery = `UPDATE users SET mariokartwii_friend_info = $2 WHERE profile_id = $1`
)
@ -33,7 +53,7 @@ type User struct {
ProfileId uint32
UserId uint64
GsbrCode string
NgDeviceId uint32
NgDeviceId []uint32
Email string
UniqueNick string
FirstName string
@ -94,15 +114,6 @@ func (user *User) UpdateProfileID(pool *pgxpool.Pool, ctx context.Context, newPr
return err
}
func (user *User) UpdateDeviceID(pool *pgxpool.Pool, ctx context.Context, newDeviceId uint32) error {
_, err := pool.Exec(ctx, UpdateUserNGDeviceID, user.ProfileId, newDeviceId)
if err == nil {
user.NgDeviceId = newDeviceId
}
return err
}
func GetUniqueUserID() uint64 {
// Not guaranteed unique but doesn't matter in practice if multiple people have the same user ID.
return uint64(rand.Int63n(0x80000000000))

View File

@ -430,8 +430,8 @@ func (g *GameSpySession) replyError(err GPError) {
}
deviceId := g.User.RestrictedDeviceId
if deviceId == 0 {
deviceId = g.User.NgDeviceId
if deviceId == 0 && len(g.User.NgDeviceId) > 0 {
deviceId = g.User.NgDeviceId[0]
}
msg := err.GetMessageTranslate(g.GameName, g.Region, g.Language, g.ConsoleFriendCode, deviceId)

View File

@ -439,6 +439,8 @@ func (g *GameSpySession) performLoginWithDatabase(userId uint64, gsbrCode string
g.User = user
if err != nil {
logging.Error(g.ModuleName, "DB error:", err)
if err == database.ErrProfileIDInUse {
g.replyError(GPError{
ErrorCode: ErrLogin.ErrorCode,

View File

@ -50,6 +50,16 @@ ALTER TABLE ONLY public.users
ADD IF NOT EXISTS ban_tos boolean,
ADD IF NOT EXISTS open_host boolean DEFAULT false;
--
-- Change ng_device_id from bigint to bigint[]
--
DO $$
BEGIN
IF (SELECT data_type FROM information_schema.columns WHERE table_name='users' AND column_name='ng_device_id') != 'ARRAY' THEN
ALTER TABLE public.users
ALTER COLUMN ng_device_id TYPE bigint[] using array[ng_device_id];
END IF;
END $$;
ALTER TABLE public.users OWNER TO wiilink;
@ -82,7 +92,7 @@ ALTER TABLE public.mario_kart_wii_sake OWNER TO wiilink;
CREATE SEQUENCE IF NOT EXISTS public.users_profile_id_seq
AS integer
START WITH 1
START WITH 1000000000
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE