mirror of
https://github.com/WiiLink24/wfc-server.git
synced 2026-03-21 17:44:58 -05:00
Implement banning and custom error messages
This commit is contained in:
parent
46330bf375
commit
89f4044f04
126
api/ban.go
Normal file
126
api/ban.go
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
"wwfc/database"
|
||||
"wwfc/gpcm"
|
||||
)
|
||||
|
||||
func HandleBan(w http.ResponseWriter, r *http.Request) {
|
||||
errorString := handleBanImpl(w, r)
|
||||
if errorString != "" {
|
||||
jsonData, _ := json.Marshal(map[string]string{"error": errorString})
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(jsonData)))
|
||||
w.Write(jsonData)
|
||||
} else {
|
||||
jsonData, _ := json.Marshal(map[string]string{"success": "true"})
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(jsonData)))
|
||||
w.Write(jsonData)
|
||||
}
|
||||
}
|
||||
|
||||
func handleBanImpl(w http.ResponseWriter, r *http.Request) string {
|
||||
// TODO: Actual authentication rather than a fixed secret
|
||||
// TODO: Use POST instead of GET
|
||||
|
||||
u, err := url.Parse(r.URL.String())
|
||||
if err != nil {
|
||||
return "Bad request"
|
||||
}
|
||||
|
||||
query, err := url.ParseQuery(u.RawQuery)
|
||||
if err != nil {
|
||||
return "Bad request"
|
||||
}
|
||||
|
||||
if apiSecret == "" || query.Get("secret") != apiSecret {
|
||||
return "Invalid API secret"
|
||||
}
|
||||
|
||||
pidStr := query.Get("pid")
|
||||
if pidStr == "" {
|
||||
return "Missing pid in request"
|
||||
}
|
||||
|
||||
pid, err := strconv.ParseUint(pidStr, 10, 32)
|
||||
if err != nil {
|
||||
return "Invalid pid"
|
||||
}
|
||||
|
||||
tosStr := query.Get("tos")
|
||||
if tosStr == "" {
|
||||
return "Missing tos in request"
|
||||
}
|
||||
|
||||
tos, err := strconv.ParseBool(tosStr)
|
||||
if err != nil {
|
||||
return "Invalid tos"
|
||||
}
|
||||
|
||||
minutes := uint64(0)
|
||||
if query.Get("minutes") != "" {
|
||||
minutesStr := query.Get("minutes")
|
||||
minutes, err = strconv.ParseUint(minutesStr, 10, 32)
|
||||
if err != nil {
|
||||
return "Invalid minutes"
|
||||
}
|
||||
}
|
||||
|
||||
hours := uint64(0)
|
||||
if query.Get("hours") != "" {
|
||||
hoursStr := query.Get("hours")
|
||||
hours, err = strconv.ParseUint(hoursStr, 10, 32)
|
||||
if err != nil {
|
||||
return "Invalid hours"
|
||||
}
|
||||
}
|
||||
|
||||
days := uint64(0)
|
||||
if query.Get("days") != "" {
|
||||
daysStr := query.Get("days")
|
||||
days, err = strconv.ParseUint(daysStr, 10, 32)
|
||||
if err != nil {
|
||||
return "Invalid days"
|
||||
}
|
||||
}
|
||||
|
||||
reason := query.Get("reason")
|
||||
if "reason" == "" {
|
||||
return "Missing ban reason"
|
||||
}
|
||||
|
||||
// reason_hidden is optional
|
||||
reasonHidden := query.Get("reason_hidden")
|
||||
|
||||
moderator := query.Get("moderator")
|
||||
if "moderator" == "" {
|
||||
moderator = "admin"
|
||||
}
|
||||
|
||||
minutes = days*24*60 + hours*60 + minutes
|
||||
if minutes == 0 {
|
||||
return "Missing ban length"
|
||||
}
|
||||
|
||||
length := time.Duration(minutes) * time.Minute
|
||||
|
||||
if !database.BanUser(pool, ctx, uint32(pid), tos, length, reason, reasonHidden, moderator) {
|
||||
return "Failed to ban user"
|
||||
}
|
||||
|
||||
if tos {
|
||||
gpcm.KickPlayer(uint32(pid), "banned")
|
||||
} else {
|
||||
gpcm.KickPlayer(uint32(pid), "restricted")
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
58
api/kick.go
Normal file
58
api/kick.go
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"wwfc/gpcm"
|
||||
)
|
||||
|
||||
func HandleKick(w http.ResponseWriter, r *http.Request) {
|
||||
errorString := handleKickImpl(w, r)
|
||||
if errorString != "" {
|
||||
jsonData, _ := json.Marshal(map[string]string{"error": errorString})
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(jsonData)))
|
||||
w.Write(jsonData)
|
||||
} else {
|
||||
jsonData, _ := json.Marshal(map[string]string{"success": "true"})
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(jsonData)))
|
||||
w.Write(jsonData)
|
||||
}
|
||||
}
|
||||
|
||||
func handleKickImpl(w http.ResponseWriter, r *http.Request) string {
|
||||
// TODO: Actual authentication rather than a fixed secret
|
||||
// TODO: Use POST instead of GET
|
||||
|
||||
u, err := url.Parse(r.URL.String())
|
||||
if err != nil {
|
||||
return "Bad request"
|
||||
}
|
||||
|
||||
query, err := url.ParseQuery(u.RawQuery)
|
||||
if err != nil {
|
||||
return "Bad request"
|
||||
}
|
||||
|
||||
if apiSecret == "" || query.Get("secret") != apiSecret {
|
||||
return "Invalid API secret"
|
||||
}
|
||||
|
||||
pidStr := query.Get("pid")
|
||||
if pidStr == "" {
|
||||
return "Missing pid in request"
|
||||
}
|
||||
|
||||
pid, err := strconv.ParseUint(pidStr, 10, 32)
|
||||
if err != nil {
|
||||
return "Invalid pid"
|
||||
}
|
||||
|
||||
gpcm.KickPlayer(uint32(pid), "moderator_kick")
|
||||
return ""
|
||||
}
|
||||
34
api/main.go
Normal file
34
api/main.go
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"wwfc/common"
|
||||
|
||||
"github.com/jackc/pgx/v4/pgxpool"
|
||||
)
|
||||
|
||||
var (
|
||||
ctx = context.Background()
|
||||
pool *pgxpool.Pool
|
||||
apiSecret string
|
||||
)
|
||||
|
||||
func StartServer() {
|
||||
// Get config
|
||||
config := common.GetConfig()
|
||||
|
||||
apiSecret = config.APISecret
|
||||
|
||||
// Start SQL
|
||||
dbString := fmt.Sprintf("postgres://%s:%s@%s/%s", config.Username, config.Password, config.DatabaseAddress, config.DatabaseName)
|
||||
dbConf, err := pgxpool.ParseConfig(dbString)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
pool, err = pgxpool.ConnectConfig(ctx, dbConf)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
58
api/unban.go
Normal file
58
api/unban.go
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"wwfc/database"
|
||||
)
|
||||
|
||||
func HandleUnban(w http.ResponseWriter, r *http.Request) {
|
||||
errorString := handleUnbanImpl(w, r)
|
||||
if errorString != "" {
|
||||
jsonData, _ := json.Marshal(map[string]string{"error": errorString})
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(jsonData)))
|
||||
w.Write(jsonData)
|
||||
} else {
|
||||
jsonData, _ := json.Marshal(map[string]string{"success": "true"})
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(jsonData)))
|
||||
w.Write(jsonData)
|
||||
}
|
||||
}
|
||||
|
||||
func handleUnbanImpl(w http.ResponseWriter, r *http.Request) string {
|
||||
// TODO: Actual authentication rather than a fixed secret
|
||||
// TODO: Use POST instead of GET
|
||||
|
||||
u, err := url.Parse(r.URL.String())
|
||||
if err != nil {
|
||||
return "Bad request"
|
||||
}
|
||||
|
||||
query, err := url.ParseQuery(u.RawQuery)
|
||||
if err != nil {
|
||||
return "Bad request"
|
||||
}
|
||||
|
||||
if apiSecret == "" || query.Get("secret") != apiSecret {
|
||||
return "Invalid API secret"
|
||||
}
|
||||
|
||||
pidStr := query.Get("pid")
|
||||
if pidStr == "" {
|
||||
return "Missing pid in request"
|
||||
}
|
||||
|
||||
pid, err := strconv.ParseUint(pidStr, 10, 32)
|
||||
if err != nil {
|
||||
return "Invalid pid"
|
||||
}
|
||||
|
||||
database.UnbanUser(pool, ctx, uint32(pid))
|
||||
return ""
|
||||
}
|
||||
|
|
@ -6,23 +6,25 @@ import (
|
|||
)
|
||||
|
||||
type Config struct {
|
||||
Username string `xml:"username"`
|
||||
Password string `xml:"password"`
|
||||
DatabaseAddress string `xml:"databaseAddress"`
|
||||
DatabaseName string `xml:"databaseName"`
|
||||
DefaultAddress string `xml:"address"`
|
||||
GameSpyAddress *string `xml:"gsAddress,omitempty"`
|
||||
NASAddress *string `xml:"nasAddress,omitempty"`
|
||||
NASPort string `xml:"nasPort"`
|
||||
NASAddressHTTPS *string `xml:"nasAddressHttps,omitempty"`
|
||||
NASPortHTTPS string `xml:"nasPortHttps"`
|
||||
EnableHTTPS bool `xml:"enableHttps"`
|
||||
EnableHTTPSExploit *bool `xml:"enableHttpsExploit,omitempty"`
|
||||
LogLevel *int `xml:"logLevel"`
|
||||
CertPath string `xml:"certPath"`
|
||||
KeyPath string `xml:"keyPath"`
|
||||
CertPathWii string `xml:"certDerPathWii"`
|
||||
KeyPathWii string `xml:"keyPathWii"`
|
||||
Username string `xml:"username"`
|
||||
Password string `xml:"password"`
|
||||
DatabaseAddress string `xml:"databaseAddress"`
|
||||
DatabaseName string `xml:"databaseName"`
|
||||
DefaultAddress string `xml:"address"`
|
||||
GameSpyAddress *string `xml:"gsAddress,omitempty"`
|
||||
NASAddress *string `xml:"nasAddress,omitempty"`
|
||||
NASPort string `xml:"nasPort"`
|
||||
NASAddressHTTPS *string `xml:"nasAddressHttps,omitempty"`
|
||||
NASPortHTTPS string `xml:"nasPortHttps"`
|
||||
EnableHTTPS bool `xml:"enableHttps"`
|
||||
EnableHTTPSExploit *bool `xml:"enableHttpsExploit,omitempty"`
|
||||
LogLevel *int `xml:"logLevel"`
|
||||
CertPath string `xml:"certPath"`
|
||||
KeyPath string `xml:"keyPath"`
|
||||
CertPathWii string `xml:"certDerPathWii"`
|
||||
KeyPathWii string `xml:"keyPathWii"`
|
||||
APISecret string `xml:"apiSecret"`
|
||||
AllowDefaultDolphinKeys bool `xml:"allowDefaultDolphinKeys"`
|
||||
}
|
||||
|
||||
func GetConfig() Config {
|
||||
|
|
@ -32,6 +34,8 @@ func GetConfig() Config {
|
|||
}
|
||||
|
||||
var config Config
|
||||
config.AllowDefaultDolphinKeys = true
|
||||
|
||||
err = xml.Unmarshal(data, &config)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
|
|
|||
|
|
@ -20,6 +20,9 @@
|
|||
<certDerPathWii>naswii-cert.der</certDerPathWii>
|
||||
<keyPathWii>naswii-key.pem</keyPathWii>
|
||||
|
||||
<!-- Allow default Dolphin device keys to be used -->
|
||||
<allowDefaultDolphinKeys>true</allowDefaultDolphinKeys>
|
||||
|
||||
<!-- Database Credentials -->
|
||||
<username>username</username>
|
||||
<password>password</password>
|
||||
|
|
@ -30,4 +33,7 @@
|
|||
|
||||
<!-- Log verbosity -->
|
||||
<logLevel>4</logLevel>
|
||||
|
||||
<!-- API secret -->
|
||||
<apiSecret>hQ3f57b3tW2WnjJH3v</apiSecret>
|
||||
</Config>
|
||||
|
|
|
|||
|
|
@ -2,19 +2,27 @@ package database
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
"wwfc/common"
|
||||
"wwfc/logging"
|
||||
|
||||
"github.com/jackc/pgx/v4"
|
||||
"github.com/jackc/pgx/v4/pgxpool"
|
||||
"github.com/logrusorgru/aurora/v3"
|
||||
)
|
||||
|
||||
func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, userId uint64, gsbrcd string, profileId uint32, ngDeviceId uint32) (User, bool) {
|
||||
var (
|
||||
ErrDeviceIDMismatch = errors.New("NG device ID mismatch")
|
||||
ErrProfileBannedTOS = errors.New("Profile is banned for violating the Terms of Service")
|
||||
)
|
||||
|
||||
func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, userId uint64, gsbrcd string, profileId uint32, ngDeviceId uint32, ipAddress string, ingamesn string) (User, error) {
|
||||
var exists bool
|
||||
err := pool.QueryRow(ctx, DoesUserExist, userId, gsbrcd).Scan(&exists)
|
||||
if err != nil {
|
||||
return User{}, false
|
||||
return User{}, err
|
||||
}
|
||||
|
||||
user := User{
|
||||
|
|
@ -32,7 +40,7 @@ func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, userId uint64, gsb
|
|||
err := user.CreateUser(pool, ctx)
|
||||
if err != nil {
|
||||
logging.Error("DATABASE", "Error creating user:", aurora.Cyan(userId), aurora.Cyan(gsbrcd), aurora.Cyan(user.ProfileId), "\nerror:", err.Error())
|
||||
return User{}, false
|
||||
return User{}, err
|
||||
}
|
||||
|
||||
logging.Notice("DATABASE", "Created new GPCM user:", aurora.Cyan(userId), aurora.Cyan(gsbrcd), aurora.Cyan(user.ProfileId))
|
||||
|
|
@ -42,7 +50,7 @@ func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, userId uint64, gsb
|
|||
var lastName *string
|
||||
err := pool.QueryRow(ctx, GetUserProfileID, userId, gsbrcd).Scan(&user.ProfileId, &expectedNgId, &user.Email, &user.UniqueNick, &firstName, &lastName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return User{}, err
|
||||
}
|
||||
|
||||
if firstName != nil {
|
||||
|
|
@ -53,17 +61,17 @@ func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, userId uint64, gsb
|
|||
user.LastName = *lastName
|
||||
}
|
||||
|
||||
if expectedNgId != nil && user.NgDeviceId != 0 {
|
||||
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)))
|
||||
return User{}, false
|
||||
return User{}, ErrDeviceIDMismatch
|
||||
}
|
||||
} else if ngDeviceId != 0 {
|
||||
user.NgDeviceId = ngDeviceId
|
||||
_, err := pool.Exec(ctx, UpdateUserNGDeviceID, user.ProfileId, ngDeviceId)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return User{}, err
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -86,5 +94,36 @@ func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, userId uint64, gsb
|
|||
})
|
||||
}
|
||||
|
||||
return user, true
|
||||
// Update the user's last IP address and ingamesn
|
||||
_, err = pool.Exec(ctx, UpdateUserLastIPAddress, user.ProfileId, ipAddress, ingamesn)
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
|
||||
// Find ban from device ID or IP address
|
||||
var banExists bool
|
||||
var banTOS bool
|
||||
var bannedDeviceId uint32
|
||||
timeNow := time.Now()
|
||||
err = pool.QueryRow(ctx, SearchUserBan, user.ProfileId, user.NgDeviceId, ipAddress, timeNow).Scan(&banExists, &banTOS, &bannedDeviceId)
|
||||
if err != nil {
|
||||
if err != pgx.ErrNoRows {
|
||||
return User{}, err
|
||||
}
|
||||
|
||||
banExists = false
|
||||
}
|
||||
|
||||
if banExists {
|
||||
if banTOS {
|
||||
logging.Warn("DATABASE", "Profile", aurora.Cyan(user.ProfileId), "is banned")
|
||||
return User{RestrictedDeviceId: bannedDeviceId}, ErrProfileBannedTOS
|
||||
}
|
||||
|
||||
logging.Warn("DATABASE", "Profile", aurora.Cyan(user.ProfileId), "is restricted")
|
||||
user.Restricted = true
|
||||
user.RestrictedDeviceId = bannedDeviceId
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
|
|
|||
22
database/schema.go
Normal file
22
database/schema.go
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v4/pgxpool"
|
||||
)
|
||||
|
||||
func UpdateTables(pool *pgxpool.Pool, ctx context.Context) {
|
||||
pool.Exec(ctx, `
|
||||
ALTER TABLE ONLY public.users
|
||||
ADD IF NOT EXISTS last_ip_address character varying DEFAULT ''::character varying,
|
||||
ADD IF NOT EXISTS last_ingamesn character varying DEFAULT ''::character varying,
|
||||
ADD IF NOT EXISTS has_ban boolean DEFAULT false,
|
||||
ADD IF NOT EXISTS ban_issued timestamp without time zone,
|
||||
ADD IF NOT EXISTS ban_expires timestamp without time zone,
|
||||
ADD IF NOT EXISTS ban_reason character varying,
|
||||
ADD IF NOT EXISTS ban_reason_hidden character varying,
|
||||
ADD IF NOT EXISTS ban_moderator character varying,
|
||||
ADD IF NOT EXISTS ban_tos boolean
|
||||
`)
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v4/pgxpool"
|
||||
)
|
||||
|
|
@ -19,20 +20,26 @@ const (
|
|||
IsProfileIDInUse = `SELECT EXISTS(SELECT 1 FROM users WHERE profile_id = $1)`
|
||||
DeleteUserSession = `DELETE FROM sessions WHERE profile_id = $1`
|
||||
GetUserProfileID = `SELECT profile_id, ng_device_id, email, unique_nick, firstname, lastname 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`
|
||||
|
||||
GetMKWFriendInfoQuery = `SELECT mariokartwii_friend_info FROM users WHERE profile_id = $1`
|
||||
UpdateMKWFriendInfoQuery = `UPDATE users SET mariokartwii_friend_info = $2 WHERE profile_id = $1`
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ProfileId uint32
|
||||
UserId uint64
|
||||
GsbrCode string
|
||||
NgDeviceId uint32
|
||||
Email string
|
||||
UniqueNick string
|
||||
FirstName string
|
||||
LastName string
|
||||
ProfileId uint32
|
||||
UserId uint64
|
||||
GsbrCode string
|
||||
NgDeviceId uint32
|
||||
Email string
|
||||
UniqueNick string
|
||||
FirstName string
|
||||
LastName string
|
||||
Restricted bool
|
||||
RestrictedDeviceId uint32
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
@ -130,6 +137,24 @@ func GetProfile(pool *pgxpool.Pool, ctx context.Context, profileId uint32) (User
|
|||
return user, true
|
||||
}
|
||||
|
||||
func BanUser(pool *pgxpool.Pool, ctx context.Context, profileId uint32, tos bool, length time.Duration, reason string, reasonHidden string, moderator string) bool {
|
||||
_, err := pool.Exec(ctx, UpdateUserBan, profileId, time.Now(), time.Now().Add(length), reason, reasonHidden, moderator, tos)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func UnbanUser(pool *pgxpool.Pool, ctx context.Context, profileId uint32) bool {
|
||||
_, err := pool.Exec(ctx, DisableUserBan, profileId)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func GetMKWFriendInfo(pool *pgxpool.Pool, ctx context.Context, profileId uint32) string {
|
||||
var info string
|
||||
err := pool.QueryRow(ctx, GetMKWFriendInfoQuery, profileId).Scan(&info)
|
||||
|
|
|
|||
247
gpcm/error.go
247
gpcm/error.go
|
|
@ -1,15 +1,39 @@
|
|||
package gpcm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"wwfc/common"
|
||||
"wwfc/logging"
|
||||
)
|
||||
|
||||
const (
|
||||
LangJapanese = 0x00
|
||||
LangEnglish = 0x01
|
||||
LangGerman = 0x02
|
||||
LangFrench = 0x03
|
||||
LangSpanish = 0x04
|
||||
LangItalian = 0x05
|
||||
LangDutch = 0x06
|
||||
LangSimpChinese = 0x07
|
||||
LangTradChinese = 0x08
|
||||
LangKorean = 0x09
|
||||
|
||||
LangEnglishEU = 0x81
|
||||
LangFrenchEU = 0x83
|
||||
LangSpanishEU = 0x84
|
||||
)
|
||||
|
||||
type WWFCErrorMessage struct {
|
||||
ErrorCode int
|
||||
MessageRMC map[byte]string
|
||||
}
|
||||
|
||||
type GPError struct {
|
||||
ErrorCode int
|
||||
ErrorString string
|
||||
Fatal bool
|
||||
WWFCMessage WWFCErrorMessage
|
||||
}
|
||||
|
||||
func MakeGPError(errorCode int, errorString string, fatal bool) GPError {
|
||||
|
|
@ -139,6 +163,181 @@ var (
|
|||
ErrRemoveBlockNotBlocked = MakeGPError(0x1301, "The profile specified was not a member of the blocked list.", false)
|
||||
)
|
||||
|
||||
var (
|
||||
// WWFC errors with custom messages
|
||||
WWFCMsgUnknownLoginError = WWFCErrorMessage{
|
||||
ErrorCode: 22000,
|
||||
MessageRMC: map[byte]string{
|
||||
LangEnglish: "" +
|
||||
"An unknown error has occurred\n" +
|
||||
"while logging in to WiiLink WFC.\n" +
|
||||
"\n" +
|
||||
"Error Code: %[1]d",
|
||||
},
|
||||
}
|
||||
|
||||
WWFCMsgDolphinSetupRequired = WWFCErrorMessage{
|
||||
ErrorCode: 22001,
|
||||
MessageRMC: map[byte]string{
|
||||
LangEnglish: "" +
|
||||
"Additional setup is required\n" +
|
||||
"to use WiiLink WFC on Dolphin.\n" +
|
||||
"Visit wfc.wiilink24.com/dolphin\n" +
|
||||
"\n" +
|
||||
"Error Code: %[1]d",
|
||||
},
|
||||
}
|
||||
|
||||
WWFCMsgProfileBannedTOS = WWFCErrorMessage{
|
||||
ErrorCode: 22002,
|
||||
MessageRMC: map[byte]string{
|
||||
LangEnglish: "" +
|
||||
"You are banned from WiiLink WFC\n" +
|
||||
"due to a violation of the\n" +
|
||||
"Terms of Service.\n" +
|
||||
"Visit wfc.wiilink24.com/tos\n" +
|
||||
"\n" +
|
||||
"Error Code: %[1]d\n" +
|
||||
"Support Info: NG%08[2]x",
|
||||
},
|
||||
}
|
||||
|
||||
WWFCMsgProfileBannedTOSNow = WWFCErrorMessage{
|
||||
ErrorCode: 22002,
|
||||
MessageRMC: map[byte]string{
|
||||
LangEnglish: "" +
|
||||
"You have been banned from\n" +
|
||||
"WiiLink WFC due to a violation\n" +
|
||||
"of the Terms of Service.\n" +
|
||||
"Visit wfc.wiilink24.com/tos\n" +
|
||||
"\n" +
|
||||
"Error Code: %[1]d\n" +
|
||||
"Support Info: NG%08[2]x",
|
||||
},
|
||||
}
|
||||
|
||||
WWFCMsgProfileRestricted = WWFCErrorMessage{
|
||||
ErrorCode: 22003,
|
||||
MessageRMC: map[byte]string{
|
||||
LangEnglish: "" +
|
||||
"You are banned from public\n" +
|
||||
"matches due to a violation\n" +
|
||||
"of the WiiLink WFC Rules.\n" +
|
||||
"Visit wfc.wiilink24.com/rules\n" +
|
||||
"\n" +
|
||||
"Error Code: %[1]d\n" +
|
||||
"Support Info: NG%08[2]x",
|
||||
},
|
||||
}
|
||||
|
||||
WWFCMsgProfileRestrictedNow = WWFCErrorMessage{
|
||||
ErrorCode: 22003,
|
||||
MessageRMC: map[byte]string{
|
||||
LangEnglish: "" +
|
||||
"You have been banned from public\n" +
|
||||
"matches due to a violation\n" +
|
||||
"of the WiiLink WFC Rules.\n" +
|
||||
"Visit wfc.wiilink24.com/rules\n" +
|
||||
"\n" +
|
||||
"Error Code: %[1]d\n" +
|
||||
"Support Info: NG%08[2]x",
|
||||
},
|
||||
}
|
||||
|
||||
WWFCMsgKickedGeneric = WWFCErrorMessage{
|
||||
ErrorCode: 22004,
|
||||
MessageRMC: map[byte]string{
|
||||
LangEnglish: "" +
|
||||
"You have been kicked from\n" +
|
||||
"WiiLink WFC.\n" +
|
||||
"\n" +
|
||||
"Error Code: %[1]d",
|
||||
},
|
||||
}
|
||||
|
||||
WWFCMsgKickedModerator = WWFCErrorMessage{
|
||||
ErrorCode: 22004,
|
||||
MessageRMC: map[byte]string{
|
||||
LangEnglish: "" +
|
||||
"You have been kicked from\n" +
|
||||
"WiiLink WFC by a moderator.\n" +
|
||||
"Visit wfc.wiilink24.com/rules\n" +
|
||||
"\n" +
|
||||
"Error Code: %[1]d",
|
||||
},
|
||||
}
|
||||
|
||||
WWFCMsgKickedRoomHost = WWFCErrorMessage{
|
||||
ErrorCode: 22004,
|
||||
MessageRMC: map[byte]string{
|
||||
LangEnglish: "" +
|
||||
"You have been kicked from the\n" +
|
||||
"friend room by the room creator.\n" +
|
||||
"\n" +
|
||||
"Error Code: %[1]d",
|
||||
},
|
||||
}
|
||||
|
||||
WWFCMsgConsoleMismatch = WWFCErrorMessage{
|
||||
ErrorCode: 22005,
|
||||
MessageRMC: map[byte]string{
|
||||
LangEnglish: "" +
|
||||
"The console you are using is not\n" +
|
||||
"the device used to register this\n" +
|
||||
"profile.\n" +
|
||||
"\n" +
|
||||
"Error Code: %[1]d",
|
||||
},
|
||||
}
|
||||
|
||||
WWFCMsgConsoleMismatchDolphin = WWFCErrorMessage{
|
||||
ErrorCode: 22005,
|
||||
MessageRMC: map[byte]string{
|
||||
LangEnglish: "" +
|
||||
"The console you are using is not\n" +
|
||||
"the device used to register this\n" +
|
||||
"profile. Please make sure you've\n" +
|
||||
"set up your NAND correctly.\n" +
|
||||
"\n" +
|
||||
"Error Code: %[1]d",
|
||||
},
|
||||
}
|
||||
|
||||
WWFCMsgProfileIDInvalid = WWFCErrorMessage{
|
||||
ErrorCode: 22006,
|
||||
MessageRMC: map[byte]string{
|
||||
LangEnglish: "" +
|
||||
"The profile ID you are trying to\n" +
|
||||
"register is invalid.\n" +
|
||||
"Please create a new license.\n" +
|
||||
"\n" +
|
||||
"Error Code: %[1]d",
|
||||
},
|
||||
}
|
||||
|
||||
WWFCMsgProfileIDInUse = WWFCErrorMessage{
|
||||
ErrorCode: 22007,
|
||||
MessageRMC: map[byte]string{
|
||||
LangEnglish: "" +
|
||||
"The friend code you are trying to\n" +
|
||||
"register is already in use.\n" +
|
||||
"\n" +
|
||||
"Error Code: %[1]d",
|
||||
},
|
||||
}
|
||||
|
||||
WWFCMsgPayloadInvalid = WWFCErrorMessage{
|
||||
ErrorCode: 22008,
|
||||
MessageRMC: map[byte]string{
|
||||
LangEnglish: "" +
|
||||
"The WiiLink WFC payload is invalid.\n" +
|
||||
"Try restarting your game.\n" +
|
||||
"\n" +
|
||||
"Error Code: %[1]d",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func (err GPError) GetMessage() string {
|
||||
command := common.GameSpyCommand{
|
||||
Command: "error",
|
||||
|
|
@ -156,7 +355,53 @@ func (err GPError) GetMessage() string {
|
|||
return common.CreateGameSpyMessage(command)
|
||||
}
|
||||
|
||||
func (err GPError) GetMessageTranslate(gameName string, region byte, lang byte, cfc uint64, ngid uint32) string {
|
||||
command := common.GameSpyCommand{
|
||||
Command: "error",
|
||||
CommandValue: "",
|
||||
OtherValues: map[string]string{
|
||||
"err": strconv.Itoa(err.ErrorCode),
|
||||
"errmsg": err.ErrorString,
|
||||
},
|
||||
}
|
||||
|
||||
if err.Fatal {
|
||||
command.OtherValues["fatal"] = ""
|
||||
}
|
||||
|
||||
if err.Fatal && err.WWFCMessage.ErrorCode != 0 {
|
||||
switch gameName {
|
||||
case "mariokartwii":
|
||||
errMsg := err.WWFCMessage.MessageRMC[lang]
|
||||
if errMsg == "" {
|
||||
errMsg = err.WWFCMessage.MessageRMC[LangEnglish]
|
||||
}
|
||||
|
||||
errMsg = fmt.Sprintf(errMsg, err.WWFCMessage.ErrorCode, ngid)
|
||||
|
||||
command.OtherValues["wwfc_err"] = strconv.Itoa(err.WWFCMessage.ErrorCode)
|
||||
command.OtherValues["wwfc_errmsg"] = errMsg
|
||||
}
|
||||
}
|
||||
|
||||
return common.CreateGameSpyMessage(command)
|
||||
}
|
||||
|
||||
func (g *GameSpySession) replyError(err GPError) {
|
||||
logging.Error(g.ModuleName, "Reply error:", err.ErrorString)
|
||||
g.Conn.Write([]byte(err.GetMessage()))
|
||||
if !g.LoginInfoSet {
|
||||
msg := err.GetMessage()
|
||||
// logging.Info(g.ModuleName, "Sending error message:", msg)
|
||||
g.Conn.Write([]byte(msg))
|
||||
return
|
||||
}
|
||||
|
||||
deviceId := g.User.RestrictedDeviceId
|
||||
if deviceId == 0 {
|
||||
deviceId = g.User.NgDeviceId
|
||||
}
|
||||
|
||||
msg := err.GetMessageTranslate(g.GameName, g.Region, g.Language, g.ConsoleFriendCode, deviceId)
|
||||
// logging.Info(g.ModuleName, "Sending error message:", msg)
|
||||
g.Conn.Write([]byte(msg))
|
||||
}
|
||||
|
|
|
|||
35
gpcm/kick.go
Normal file
35
gpcm/kick.go
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
package gpcm
|
||||
|
||||
func KickPlayer(profileID uint32, reason string) {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
if session, exists := sessions[profileID]; exists {
|
||||
errorMessage := WWFCMsgKickedGeneric
|
||||
|
||||
switch reason {
|
||||
case "banned":
|
||||
errorMessage = WWFCMsgProfileBannedTOSNow
|
||||
|
||||
case "restricted":
|
||||
errorMessage = WWFCMsgProfileRestrictedNow
|
||||
|
||||
case "restricted_join":
|
||||
errorMessage = WWFCMsgProfileRestricted
|
||||
|
||||
case "moderator_kick":
|
||||
errorMessage = WWFCMsgKickedModerator
|
||||
|
||||
case "room_kick":
|
||||
errorMessage = WWFCMsgKickedRoomHost
|
||||
}
|
||||
|
||||
session.replyError(GPError{
|
||||
ErrorCode: ErrConnectionClosed.ErrorCode,
|
||||
ErrorString: "The player was kicked from the game. Reason: " + reason,
|
||||
Fatal: true,
|
||||
WWFCMessage: errorMessage,
|
||||
})
|
||||
session.Conn.Close()
|
||||
}
|
||||
}
|
||||
192
gpcm/login.go
192
gpcm/login.go
|
|
@ -44,6 +44,11 @@ var msPublicKey = []byte{
|
|||
0xF9, 0x5B, 0x4D, 0x11, 0x04, 0x44, 0x64, 0x35, 0xC0, 0xED, 0xA4, 0x2F,
|
||||
}
|
||||
|
||||
var commonDeviceIds = []uint32{
|
||||
0x02000001,
|
||||
0x0403ac68,
|
||||
}
|
||||
|
||||
func verifySignature(moduleName string, authToken string, signature string) uint32 {
|
||||
sigBytes, err := common.Base64DwcEncoding.DecodeString(signature)
|
||||
if err != nil || len(sigBytes) != 0x144 {
|
||||
|
|
@ -51,6 +56,16 @@ func verifySignature(moduleName string, authToken string, signature string) uint
|
|||
}
|
||||
|
||||
ngId := sigBytes[0x000:0x004]
|
||||
|
||||
if !allowDefaultDolphinKeys {
|
||||
// Skip authentication signature verification for common device IDs (the caller should handle this)
|
||||
for _, defaultDeviceId := range commonDeviceIds {
|
||||
if binary.BigEndian.Uint32(ngId) == defaultDeviceId {
|
||||
return defaultDeviceId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngTimestamp := sigBytes[0x004:0x008]
|
||||
caId := sigBytes[0x008:0x00C]
|
||||
msId := sigBytes[0x00C:0x010]
|
||||
|
|
@ -122,7 +137,7 @@ func (g *GameSpySession) login(command common.GameSpyCommand) {
|
|||
return
|
||||
}
|
||||
|
||||
err, gamecd, issueTime, userId, gsbrcd, cfc, _, _, ingamesn, challenge, isLocalhost := common.UnmarshalNASAuthToken(authToken)
|
||||
err, gamecd, issueTime, userId, gsbrcd, cfc, region, lang, ingamesn, challenge, isLocalhost := common.UnmarshalNASAuthToken(authToken)
|
||||
if err != nil {
|
||||
g.replyError(ErrLogin)
|
||||
return
|
||||
|
|
@ -134,39 +149,32 @@ func (g *GameSpySession) login(command common.GameSpyCommand) {
|
|||
return
|
||||
}
|
||||
|
||||
payloadVer, payloadVerExists := command.OtherValues["payload_ver"]
|
||||
signature, signatureExists := command.OtherValues["wwfc_sig"]
|
||||
_, payloadVerExists := command.OtherValues["payload_ver"]
|
||||
_, signatureExists := command.OtherValues["wwfc_sig"]
|
||||
deviceId := uint32(0)
|
||||
|
||||
g.GameName = command.OtherValues["gamename"]
|
||||
g.GameCode = gamecd
|
||||
g.Region = region
|
||||
g.Language = lang
|
||||
g.ConsoleFriendCode = cfc
|
||||
g.InGameName = ingamesn
|
||||
|
||||
if hostPlatform, exists := command.OtherValues["wwfc_host"]; exists {
|
||||
g.HostPlatform = hostPlatform
|
||||
} else {
|
||||
g.HostPlatform = "Wii"
|
||||
}
|
||||
|
||||
g.LoginInfoSet = true
|
||||
|
||||
if isLocalhost && !payloadVerExists && !signatureExists {
|
||||
// Players using the DNS exploit, need patching using a QR2 exploit
|
||||
// TODO: Check that the game is compatible with the DNS
|
||||
g.NeedsExploit = true
|
||||
} else {
|
||||
if !payloadVerExists || payloadVer != "2" {
|
||||
g.replyError(GPError{
|
||||
ErrorCode: ErrLogin.ErrorCode,
|
||||
ErrorString: "The payload version is invalid.",
|
||||
Fatal: true,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if !signatureExists {
|
||||
g.replyError(GPError{
|
||||
ErrorCode: ErrLogin.ErrorCode,
|
||||
ErrorString: "Missing authentication signature.",
|
||||
Fatal: true,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if deviceId = verifySignature(g.ModuleName, authToken, signature); deviceId == 0 {
|
||||
g.replyError(GPError{
|
||||
ErrorCode: ErrLogin.ErrorCode,
|
||||
ErrorString: "The authentication signature is invalid.",
|
||||
Fatal: true,
|
||||
})
|
||||
deviceId = g.verifyExLoginInfo(command, authToken)
|
||||
if deviceId == 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -187,6 +195,7 @@ func (g *GameSpySession) login(command common.GameSpyCommand) {
|
|||
ErrorCode: ErrLogin.ErrorCode,
|
||||
ErrorString: "The provided profile ID is invalid.",
|
||||
Fatal: true,
|
||||
WWFCMessage: WWFCMsgUnknownLoginError,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
|
@ -194,14 +203,9 @@ func (g *GameSpySession) login(command common.GameSpyCommand) {
|
|||
cmdProfileId = uint32(cmdProfileId2)
|
||||
}
|
||||
|
||||
// Perform the login with the database.
|
||||
user, ok := database.LoginUserToGPCM(pool, ctx, userId, gsbrcd, cmdProfileId, deviceId)
|
||||
if !ok {
|
||||
// There was an error logging in to the GP backend.
|
||||
g.replyError(ErrLogin)
|
||||
if !g.performLoginWithDatabase(userId, gsbrcd, cmdProfileId, deviceId) {
|
||||
return
|
||||
}
|
||||
g.User = user
|
||||
|
||||
g.ModuleName = "GPCM:" + strconv.FormatInt(int64(g.User.ProfileId), 10) + "*"
|
||||
g.ModuleName += "/" + common.CalcFriendCodeString(g.User.ProfileId, "RMCJ") + "*"
|
||||
|
|
@ -237,8 +241,6 @@ func (g *GameSpySession) login(command common.GameSpyCommand) {
|
|||
g.AuthToken = authToken
|
||||
g.LoginTicket = common.MarshalGPCMLoginTicket(g.User.ProfileId)
|
||||
g.SessionKey = rand.Int31n(290000000) + 10000000
|
||||
g.GameCode = gamecd
|
||||
g.InGameName = ingamesn
|
||||
|
||||
g.DeviceAuthenticated = !g.NeedsExploit
|
||||
g.LoggedIn = true
|
||||
|
|
@ -246,7 +248,7 @@ func (g *GameSpySession) login(command common.GameSpyCommand) {
|
|||
g.ModuleName += "/" + common.CalcFriendCodeString(g.User.ProfileId, "RMCJ")
|
||||
|
||||
// Notify QR2 of the login
|
||||
qr2.Login(g.User.ProfileId, gamecd, ingamesn, cfc, g.Conn.RemoteAddr().String(), g.NeedsExploit, g.DeviceAuthenticated)
|
||||
qr2.Login(g.User.ProfileId, gamecd, ingamesn, cfc, g.Conn.RemoteAddr().String(), g.NeedsExploit, g.DeviceAuthenticated, g.User.Restricted)
|
||||
|
||||
payload := common.CreateGameSpyMessage(common.GameSpyCommand{
|
||||
Command: "lc",
|
||||
|
|
@ -273,6 +275,20 @@ func (g *GameSpySession) exLogin(command common.GameSpyCommand) {
|
|||
return
|
||||
}
|
||||
|
||||
deviceId := g.verifyExLoginInfo(command, g.AuthToken)
|
||||
if deviceId == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if !g.performLoginWithDatabase(g.User.UserId, g.User.GsbrCode, 0, deviceId) {
|
||||
return
|
||||
}
|
||||
|
||||
g.DeviceAuthenticated = true
|
||||
qr2.SetDeviceAuthenticated(g.User.ProfileId)
|
||||
}
|
||||
|
||||
func (g *GameSpySession) verifyExLoginInfo(command common.GameSpyCommand, authToken string) uint32 {
|
||||
payloadVer, payloadVerExists := command.OtherValues["payload_ver"]
|
||||
signature, signatureExists := command.OtherValues["wwfc_sig"]
|
||||
deviceId := uint32(0)
|
||||
|
|
@ -282,8 +298,9 @@ func (g *GameSpySession) exLogin(command common.GameSpyCommand) {
|
|||
ErrorCode: ErrLogin.ErrorCode,
|
||||
ErrorString: "The payload version is invalid.",
|
||||
Fatal: true,
|
||||
WWFCMessage: WWFCMsgPayloadInvalid,
|
||||
})
|
||||
return
|
||||
return 0
|
||||
}
|
||||
|
||||
if !signatureExists {
|
||||
|
|
@ -291,21 +308,112 @@ func (g *GameSpySession) exLogin(command common.GameSpyCommand) {
|
|||
ErrorCode: ErrLogin.ErrorCode,
|
||||
ErrorString: "Missing authentication signature.",
|
||||
Fatal: true,
|
||||
WWFCMessage: WWFCMsgUnknownLoginError,
|
||||
})
|
||||
return
|
||||
return 0
|
||||
}
|
||||
|
||||
if deviceId = verifySignature(g.ModuleName, g.AuthToken, signature); deviceId == 0 {
|
||||
if deviceId = verifySignature(g.ModuleName, authToken, signature); deviceId == 0 {
|
||||
g.replyError(GPError{
|
||||
ErrorCode: ErrLogin.ErrorCode,
|
||||
ErrorString: "The authentication signature is invalid.",
|
||||
Fatal: true,
|
||||
WWFCMessage: WWFCMsgUnknownLoginError,
|
||||
})
|
||||
return
|
||||
return 0
|
||||
}
|
||||
|
||||
g.DeviceAuthenticated = true
|
||||
qr2.SetDeviceAuthenticated(g.User.ProfileId)
|
||||
g.DeviceId = deviceId
|
||||
|
||||
if !allowDefaultDolphinKeys {
|
||||
// Check common device IDs
|
||||
for _, defaultDeviceId := range commonDeviceIds {
|
||||
if deviceId != defaultDeviceId {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(g.HostPlatform, "Dolphin") {
|
||||
g.replyError(GPError{
|
||||
ErrorCode: ErrLogin.ErrorCode,
|
||||
ErrorString: "Prohibited device ID used in signature.",
|
||||
Fatal: true,
|
||||
WWFCMessage: WWFCMsgDolphinSetupRequired,
|
||||
})
|
||||
} else {
|
||||
g.replyError(GPError{
|
||||
ErrorCode: ErrLogin.ErrorCode,
|
||||
ErrorString: "Prohibited device ID used in signature.",
|
||||
Fatal: true,
|
||||
WWFCMessage: WWFCMsgUnknownLoginError,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return deviceId
|
||||
}
|
||||
|
||||
func (g *GameSpySession) performLoginWithDatabase(userId uint64, gsbrCode string, profileId uint32, deviceId uint32) bool {
|
||||
// Get IP address without port
|
||||
ipAddress := g.Conn.RemoteAddr().String()
|
||||
if strings.Contains(ipAddress, ":") {
|
||||
ipAddress = ipAddress[:strings.Index(ipAddress, ":")]
|
||||
}
|
||||
|
||||
user, err := database.LoginUserToGPCM(pool, ctx, userId, gsbrCode, profileId, deviceId, ipAddress, g.InGameName)
|
||||
g.User = user
|
||||
|
||||
if err != nil {
|
||||
if err == database.ErrProfileIDInUse {
|
||||
g.replyError(GPError{
|
||||
ErrorCode: ErrLogin.ErrorCode,
|
||||
ErrorString: "The profile ID is already in use.",
|
||||
Fatal: true,
|
||||
WWFCMessage: WWFCMsgProfileIDInUse,
|
||||
})
|
||||
} else if err == database.ErrReservedProfileIDRange {
|
||||
g.replyError(GPError{
|
||||
ErrorCode: ErrLogin.ErrorCode,
|
||||
ErrorString: "The profile ID is in a reserved range.",
|
||||
Fatal: true,
|
||||
WWFCMessage: WWFCMsgProfileIDInvalid,
|
||||
})
|
||||
} else if err == database.ErrDeviceIDMismatch {
|
||||
if strings.HasPrefix(g.HostPlatform, "Dolphin") {
|
||||
g.replyError(GPError{
|
||||
ErrorCode: ErrLogin.ErrorCode,
|
||||
ErrorString: "The device ID does not match the one on record.",
|
||||
Fatal: true,
|
||||
WWFCMessage: WWFCMsgConsoleMismatchDolphin,
|
||||
})
|
||||
} else {
|
||||
g.replyError(GPError{
|
||||
ErrorCode: ErrLogin.ErrorCode,
|
||||
ErrorString: "The device ID does not match the one on record.",
|
||||
Fatal: true,
|
||||
WWFCMessage: WWFCMsgConsoleMismatch,
|
||||
})
|
||||
}
|
||||
} else if err == database.ErrProfileBannedTOS {
|
||||
g.replyError(GPError{
|
||||
ErrorCode: ErrLogin.ErrorCode,
|
||||
ErrorString: "The profile is banned from the service.",
|
||||
Fatal: true,
|
||||
WWFCMessage: WWFCMsgProfileBannedTOS,
|
||||
})
|
||||
} else {
|
||||
g.replyError(GPError{
|
||||
ErrorCode: ErrLogin.ErrorCode,
|
||||
ErrorString: "There was an error logging in to the GP backend.",
|
||||
Fatal: true,
|
||||
WWFCMessage: WWFCMsgUnknownLoginError,
|
||||
})
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func IsLoggedIn(profileID uint32) bool {
|
||||
|
|
|
|||
27
gpcm/main.go
27
gpcm/main.go
|
|
@ -27,12 +27,21 @@ type GameSpySession struct {
|
|||
AuthToken string
|
||||
LoginTicket string
|
||||
SessionKey int32
|
||||
GameCode string
|
||||
InGameName string
|
||||
Status string
|
||||
LocString string
|
||||
FriendList []uint32
|
||||
AuthFriendList []uint32
|
||||
|
||||
LoginInfoSet bool
|
||||
GameName string
|
||||
GameCode string
|
||||
Region byte
|
||||
Language byte
|
||||
InGameName string
|
||||
ConsoleFriendCode uint64
|
||||
DeviceId uint32
|
||||
HostPlatform string
|
||||
|
||||
Status string
|
||||
LocString string
|
||||
FriendList []uint32
|
||||
AuthFriendList []uint32
|
||||
|
||||
QR2IP uint64
|
||||
Reservation common.MatchCommandData
|
||||
|
|
@ -47,6 +56,8 @@ var (
|
|||
// I would use a sync.Map instead of the map mutex combo, but this performs better.
|
||||
sessions = map[uint32]*GameSpySession{}
|
||||
mutex = deadlock.Mutex{}
|
||||
|
||||
allowDefaultDolphinKeys bool
|
||||
)
|
||||
|
||||
func StartServer() {
|
||||
|
|
@ -65,6 +76,10 @@ func StartServer() {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
database.UpdateTables(pool, ctx)
|
||||
|
||||
allowDefaultDolphinKeys = config.AllowDefaultDolphinKeys
|
||||
|
||||
address := *config.GameSpyAddress + ":29900"
|
||||
l, err := net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
|
|
|
|||
3
main.go
3
main.go
|
|
@ -2,6 +2,7 @@ package main
|
|||
|
||||
import (
|
||||
"sync"
|
||||
"wwfc/api"
|
||||
"wwfc/common"
|
||||
"wwfc/gpcm"
|
||||
"wwfc/gpsp"
|
||||
|
|
@ -18,7 +19,7 @@ func main() {
|
|||
logging.SetLevel(*config.LogLevel)
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
actions := []func(){nas.StartServer, gpcm.StartServer, qr2.StartServer, gpsp.StartServer, serverbrowser.StartServer, sake.StartServer, natneg.StartServer}
|
||||
actions := []func(){nas.StartServer, gpcm.StartServer, qr2.StartServer, gpsp.StartServer, serverbrowser.StartServer, sake.StartServer, natneg.StartServer, api.StartServer}
|
||||
wg.Add(5)
|
||||
for _, action := range actions {
|
||||
go func(ac func()) {
|
||||
|
|
|
|||
22
nas/main.go
22
nas/main.go
|
|
@ -59,6 +59,9 @@ func handleRequest(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
logging.Info("NAS", aurora.Yellow(r.Method), aurora.Cyan(r.URL), "via", aurora.Cyan(r.Host), "from", aurora.BrightCyan(r.RemoteAddr))
|
||||
moduleName := "NAS:" + r.RemoteAddr
|
||||
|
||||
// Handle conntest server
|
||||
if strings.HasPrefix(r.Host, "conntest.") {
|
||||
handleConnectionTest(w)
|
||||
|
|
@ -77,8 +80,23 @@ func handleRequest(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
logging.Info("NAS", aurora.Yellow(r.Method), aurora.Cyan(r.URL), "via", aurora.Cyan(r.Host), "from", aurora.BrightCyan(r.RemoteAddr))
|
||||
moduleName := "NAS:" + r.RemoteAddr
|
||||
// Check for /api/ban
|
||||
if r.URL.Path == "/api/ban" {
|
||||
api.HandleBan(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Check for /api/unban
|
||||
if r.URL.Path == "/api/unban" {
|
||||
api.HandleUnban(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Check for /api/kick
|
||||
if r.URL.Path == "/api/kick" {
|
||||
api.HandleKick(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if r.URL.String() == "/ac" || r.URL.String() == "/pr" || r.URL.String() == "/download" {
|
||||
handleAuthRequest(moduleName, w, r)
|
||||
|
|
|
|||
|
|
@ -8,12 +8,13 @@ type LoginInfo struct {
|
|||
GPPublicIP string
|
||||
NeedsExploit bool
|
||||
DeviceAuthenticated bool
|
||||
Restricted bool
|
||||
Session *Session
|
||||
}
|
||||
|
||||
var logins = map[uint32]*LoginInfo{}
|
||||
|
||||
func Login(profileID uint32, gameCode string, inGameName string, consoleFriendCode uint64, publicIP string, needsExploit bool, deviceAuthenticated bool) {
|
||||
func Login(profileID uint32, gameCode string, inGameName string, consoleFriendCode uint64, publicIP string, needsExploit bool, deviceAuthenticated bool, restricted bool) {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
|
|
@ -25,6 +26,7 @@ func Login(profileID uint32, gameCode string, inGameName string, consoleFriendCo
|
|||
GPPublicIP: publicIP,
|
||||
NeedsExploit: needsExploit,
|
||||
DeviceAuthenticated: deviceAuthenticated,
|
||||
Restricted: restricted,
|
||||
Session: nil,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
16
schema.sql
16
schema.sql
|
|
@ -24,7 +24,7 @@ SET default_table_access_method = heap;
|
|||
-- Name: users; Type: TABLE; Schema: public; Owner: wiilink
|
||||
--
|
||||
|
||||
CREATE TABLE public.users (
|
||||
CREATE TABLE IF NOT EXISTS public.users (
|
||||
profile_id bigint NOT NULL,
|
||||
user_id bigint NOT NULL,
|
||||
gsbrcd character varying NOT NULL,
|
||||
|
|
@ -38,13 +38,25 @@ CREATE TABLE public.users (
|
|||
);
|
||||
|
||||
|
||||
ALTER TABLE ONLY public.users
|
||||
ADD IF NOT EXISTS last_ip_address character varying DEFAULT ''::character varying,
|
||||
ADD IF NOT EXISTS last_ingamesn character varying DEFAULT ''::character varying,
|
||||
ADD IF NOT EXISTS has_ban boolean DEFAULT false,
|
||||
ADD IF NOT EXISTS ban_issued timestamp without time zone,
|
||||
ADD IF NOT EXISTS ban_expires timestamp without time zone,
|
||||
ADD IF NOT EXISTS ban_reason character varying,
|
||||
ADD IF NOT EXISTS ban_reason_hidden character varying,
|
||||
ADD IF NOT EXISTS ban_moderator character varying,
|
||||
ADD IF NOT EXISTS ban_tos boolean
|
||||
|
||||
|
||||
ALTER TABLE public.users OWNER TO wiilink;
|
||||
|
||||
--
|
||||
-- Name: users_profile_id_seq; Type: SEQUENCE; Schema: public; Owner: wiilink
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.users_profile_id_seq
|
||||
CREATE SEQUENCE IF NOT EXISTS public.users_profile_id_seq
|
||||
AS integer
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user