mirror of
https://github.com/WiiLink24/wfc-server.git
synced 2026-03-21 17:44:58 -05:00
GameStats: Add standard command server
This commit is contained in:
parent
496f918f7f
commit
7ddcd982bc
|
|
@ -127,3 +127,28 @@ func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, userId uint64, gsb
|
|||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func LoginUserToGameStats(pool *pgxpool.Pool, ctx context.Context, userId uint64, gsbrcd string) (User, error) {
|
||||
user := User{
|
||||
UserId: userId,
|
||||
GsbrCode: gsbrcd,
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
|
||||
if firstName != nil {
|
||||
user.FirstName = *firstName
|
||||
}
|
||||
|
||||
if lastName != nil {
|
||||
user.LastName = *lastName
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
|
|
|||
99
gamestats/auth.go
Normal file
99
gamestats/auth.go
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
package gamestats
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"time"
|
||||
"wwfc/common"
|
||||
"wwfc/database"
|
||||
"wwfc/gpcm"
|
||||
"wwfc/logging"
|
||||
|
||||
"github.com/logrusorgru/aurora/v3"
|
||||
)
|
||||
|
||||
func (g *GameStatsSession) auth(command common.GameSpyCommand) {
|
||||
game := common.GetGameInfoByName(command.OtherValues["gamename"])
|
||||
if game == nil {
|
||||
g.replyError(gpcm.ErrDatabase)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Validate "response"
|
||||
g.SessionKey = rand.Int31n(290000000) + 10000000
|
||||
g.GameInfo = game
|
||||
|
||||
g.Write(common.GameSpyCommand{
|
||||
Command: "lc",
|
||||
CommandValue: "2",
|
||||
OtherValues: map[string]string{
|
||||
"sesskey": strconv.FormatInt(int64(g.SessionKey), 10),
|
||||
"proof": "0",
|
||||
"id": "1",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (g *GameStatsSession) authp(command common.GameSpyCommand) {
|
||||
lid := command.OtherValues["lid"]
|
||||
errorCmd := common.GameSpyCommand{
|
||||
Command: "pauthr",
|
||||
CommandValue: "-3",
|
||||
OtherValues: map[string]string{
|
||||
"lid": lid,
|
||||
"errmsg": "Invalid Validation",
|
||||
},
|
||||
}
|
||||
|
||||
if lid != "" {
|
||||
var err error
|
||||
g.LoginID, err = strconv.Atoi(lid)
|
||||
if err != nil {
|
||||
logging.Error(g.ModuleName, "Error parsing login ID:", err.Error())
|
||||
g.Write(errorCmd)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
authToken := command.OtherValues["authtoken"]
|
||||
if authToken == "" {
|
||||
logging.Error(g.ModuleName, "No authtoken provided")
|
||||
g.Write(errorCmd)
|
||||
return
|
||||
}
|
||||
|
||||
err, _, issueTime, userId, gsbrcd, _, _, _, _, _, _, _ := common.UnmarshalNASAuthToken(authToken)
|
||||
if err != nil {
|
||||
logging.Error(g.ModuleName, "Error unmarshalling authtoken:", err.Error())
|
||||
g.Write(errorCmd)
|
||||
return
|
||||
}
|
||||
|
||||
currentTime := time.Now()
|
||||
if issueTime.Before(currentTime.Add(-10*time.Minute)) || issueTime.After(currentTime) {
|
||||
logging.Error(g.ModuleName, "Authtoken has expired")
|
||||
g.Write(errorCmd)
|
||||
return
|
||||
}
|
||||
|
||||
g.User, err = database.LoginUserToGameStats(pool, ctx, userId, gsbrcd)
|
||||
if err != nil {
|
||||
logging.Error(g.ModuleName, "Error logging in user:", err.Error())
|
||||
g.Write(errorCmd)
|
||||
return
|
||||
}
|
||||
|
||||
g.ModuleName = "GSTATS:" + strconv.FormatInt(int64(g.User.ProfileId), 10)
|
||||
g.ModuleName += "/" + common.CalcFriendCodeString(g.User.ProfileId, "RMCJ")
|
||||
g.Authenticated = true
|
||||
|
||||
logging.Notice(g.ModuleName, "Authenticated, game name:", aurora.Cyan(g.GameInfo.Name))
|
||||
|
||||
g.Write(common.GameSpyCommand{
|
||||
Command: "pauthr",
|
||||
CommandValue: strconv.FormatUint(uint64(g.User.ProfileId), 10),
|
||||
OtherValues: map[string]string{
|
||||
"lid": lid,
|
||||
},
|
||||
})
|
||||
}
|
||||
11
gamestats/error.go
Normal file
11
gamestats/error.go
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
package gamestats
|
||||
|
||||
import (
|
||||
"wwfc/gpcm"
|
||||
"wwfc/logging"
|
||||
)
|
||||
|
||||
func (g *GameStatsSession) replyError(err gpcm.GPError) {
|
||||
logging.Error(g.ModuleName, "Reply error:", err.ErrorString)
|
||||
g.Conn.Write([]byte(err.GetMessage()))
|
||||
}
|
||||
|
|
@ -1,13 +1,35 @@
|
|||
package gamestats
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"wwfc/common"
|
||||
"wwfc/database"
|
||||
"wwfc/gpcm"
|
||||
"wwfc/logging"
|
||||
|
||||
"github.com/jackc/pgx/v4/pgxpool"
|
||||
"github.com/logrusorgru/aurora/v3"
|
||||
)
|
||||
|
||||
type GameStatsSession struct {
|
||||
Conn net.Conn
|
||||
ModuleName string
|
||||
Challenge string
|
||||
|
||||
SessionKey int32
|
||||
GameInfo *common.GameInfo
|
||||
|
||||
Authenticated bool
|
||||
LoginID int
|
||||
User database.User
|
||||
|
||||
WriteBuffer []byte
|
||||
}
|
||||
|
||||
var (
|
||||
ctx = context.Background()
|
||||
pool *pgxpool.Pool
|
||||
|
|
@ -38,4 +60,148 @@ func StartServer() {
|
|||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
address := *config.GameSpyAddress + ":29920"
|
||||
|
||||
l, err := net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Close the listener when the application closes.
|
||||
defer l.Close()
|
||||
logging.Notice("GSTATS", "Listening on", address)
|
||||
|
||||
for {
|
||||
// Listen for an incoming connection.
|
||||
conn, err := l.Accept()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Handle connections in a new goroutine.
|
||||
go handleRequest(conn)
|
||||
}
|
||||
}
|
||||
|
||||
// Handles incoming requests.
|
||||
func handleRequest(conn net.Conn) {
|
||||
session := GameStatsSession{
|
||||
Conn: conn,
|
||||
ModuleName: "GSTATS:" + conn.RemoteAddr().String(),
|
||||
Challenge: common.RandomString(10),
|
||||
|
||||
WriteBuffer: []byte{},
|
||||
}
|
||||
|
||||
defer conn.Close()
|
||||
|
||||
moduleName := "GSTATS"
|
||||
|
||||
err := conn.(*net.TCPConn).SetKeepAlive(true)
|
||||
if err != nil {
|
||||
logging.Notice(moduleName, "Unable to set keepalive:", err.Error())
|
||||
}
|
||||
|
||||
// Send challenge
|
||||
session.Write(common.GameSpyCommand{
|
||||
Command: "lc",
|
||||
CommandValue: "1",
|
||||
OtherValues: map[string]string{
|
||||
"challenge": session.Challenge,
|
||||
"id": "1",
|
||||
},
|
||||
})
|
||||
conn.Write(session.WriteBuffer)
|
||||
session.WriteBuffer = []byte{}
|
||||
|
||||
// Here we go into the listening loop
|
||||
for {
|
||||
// TODO: Handle split packets
|
||||
buffer := make([]byte, 1024)
|
||||
n, err := bufio.NewReader(conn).Read(buffer)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Decrypt the data
|
||||
for i := 0; i < n; i++ {
|
||||
if i+7 <= n && bytes.Equal(buffer[i:i+7], []byte(`\final\`)) {
|
||||
i += 6
|
||||
continue
|
||||
}
|
||||
|
||||
buffer[i] ^= "GameSpy3D"[i%9]
|
||||
}
|
||||
|
||||
commands, err := common.ParseGameSpyMessage(string(buffer[:n]))
|
||||
if err != nil {
|
||||
logging.Error(moduleName, "Error parsing message:", err.Error())
|
||||
logging.Error(moduleName, "Raw data:", string(buffer[:n]))
|
||||
session.replyError(gpcm.ErrParse)
|
||||
return
|
||||
}
|
||||
|
||||
commands = session.handleCommand("ka", commands, func(command common.GameSpyCommand) {
|
||||
session.Conn.Write([]byte(`\ka\\final\`))
|
||||
})
|
||||
|
||||
commands = session.handleCommand("auth", commands, session.auth)
|
||||
commands = session.handleCommand("authp", commands, session.authp)
|
||||
|
||||
if len(commands) != 0 && session.Authenticated == false {
|
||||
logging.Error(session.ModuleName, "Attempt to run command before authentication:", aurora.Cyan(commands[0]))
|
||||
session.replyError(gpcm.ErrNotLoggedIn)
|
||||
return
|
||||
}
|
||||
|
||||
commands = session.handleCommand("setpd", commands, session.setpd)
|
||||
|
||||
for _, command := range commands {
|
||||
logging.Error(session.ModuleName, "Unknown command:", aurora.Cyan(command))
|
||||
}
|
||||
|
||||
if len(session.WriteBuffer) > 0 {
|
||||
conn.Write(session.WriteBuffer)
|
||||
session.WriteBuffer = []byte{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GameStatsSession) handleCommand(name string, commands []common.GameSpyCommand, handler func(command common.GameSpyCommand)) []common.GameSpyCommand {
|
||||
var unhandled []common.GameSpyCommand
|
||||
|
||||
for _, command := range commands {
|
||||
if command.Command != name {
|
||||
unhandled = append(unhandled, command)
|
||||
continue
|
||||
}
|
||||
|
||||
logging.Info(g.ModuleName, "Command:", aurora.Yellow(command.Command))
|
||||
handler(command)
|
||||
}
|
||||
|
||||
return unhandled
|
||||
}
|
||||
|
||||
func (g *GameStatsSession) ignoreCommand(name string, commands []common.GameSpyCommand) []common.GameSpyCommand {
|
||||
var unhandled []common.GameSpyCommand
|
||||
|
||||
for _, command := range commands {
|
||||
if command.Command != name {
|
||||
unhandled = append(unhandled, command)
|
||||
}
|
||||
}
|
||||
|
||||
return unhandled
|
||||
}
|
||||
|
||||
func (g *GameStatsSession) Write(command common.GameSpyCommand) {
|
||||
// Encrypt the data and append it to be sent
|
||||
payload := []byte(common.CreateGameSpyMessage(command))
|
||||
// Exclude trailing \final\
|
||||
for i := 0; i < len(payload)-7; i++ {
|
||||
payload[i] ^= "GameSpy3D"[i%9]
|
||||
}
|
||||
g.WriteBuffer = append(g.WriteBuffer, payload...)
|
||||
}
|
||||
|
|
|
|||
21
gamestats/setpd.go
Normal file
21
gamestats/setpd.go
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
package gamestats
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
"wwfc/common"
|
||||
)
|
||||
|
||||
func (g *GameStatsSession) setpd(command common.GameSpyCommand) {
|
||||
g.Write(common.GameSpyCommand{
|
||||
Command: "setpdr",
|
||||
CommandValue: "1",
|
||||
OtherValues: map[string]string{
|
||||
"lid": strconv.Itoa(g.LoginID),
|
||||
"pid": command.OtherValues["pid"],
|
||||
"mod": strconv.Itoa(int(time.Now().Unix())),
|
||||
"length": "0",
|
||||
"data": `\\`,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
@ -45,6 +45,8 @@ func MakeGPError(errorCode int, errorString string, fatal bool) GPError {
|
|||
}
|
||||
|
||||
var (
|
||||
ErrNone = MakeGPError(0xFFFF, "No error.", false)
|
||||
|
||||
// General errors
|
||||
ErrGeneral = MakeGPError(0x0000, "There was an unknown error.", true)
|
||||
ErrParse = MakeGPError(0x0001, "There was an error parsing an incoming request.", true)
|
||||
|
|
|
|||
10
gpcm/main.go
10
gpcm/main.go
|
|
@ -166,7 +166,7 @@ func handleRequest(conn net.Conn) {
|
|||
for {
|
||||
// TODO: Handle split packets
|
||||
buffer := make([]byte, 1024)
|
||||
_, err := bufio.NewReader(conn).Read(buffer)
|
||||
n, err := bufio.NewReader(conn).Read(buffer)
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
// Client closed connection, terminate.
|
||||
|
|
@ -178,10 +178,10 @@ func handleRequest(conn net.Conn) {
|
|||
return
|
||||
}
|
||||
|
||||
commands, err := common.ParseGameSpyMessage(string(buffer))
|
||||
commands, err := common.ParseGameSpyMessage(string(buffer[:n]))
|
||||
if err != nil {
|
||||
logging.Error(session.ModuleName, "Error parsing message:", err.Error())
|
||||
logging.Error(session.ModuleName, "Raw data:", string(buffer))
|
||||
logging.Error(session.ModuleName, "Raw data:", string(buffer[:n]))
|
||||
session.replyError(ErrParse)
|
||||
return
|
||||
}
|
||||
|
|
@ -196,7 +196,7 @@ func handleRequest(conn net.Conn) {
|
|||
commands = session.ignoreCommand("logout", commands)
|
||||
|
||||
if len(commands) != 0 && session.LoggedIn == false {
|
||||
logging.Error(session.ModuleName, "Attempt to run command before login!")
|
||||
logging.Error(session.ModuleName, "Attempt to run command before login:", aurora.Cyan(commands[0]))
|
||||
session.replyError(ErrNotLoggedIn)
|
||||
return
|
||||
}
|
||||
|
|
@ -211,7 +211,7 @@ func handleRequest(conn net.Conn) {
|
|||
commands = session.handleCommand("getprofile", commands, session.getProfile)
|
||||
|
||||
for _, command := range commands {
|
||||
logging.Error(session.ModuleName, "Unknown command:", aurora.Cyan(command.Command))
|
||||
logging.Error(session.ModuleName, "Unknown command:", aurora.Cyan(command))
|
||||
}
|
||||
|
||||
if session.WriteBuffer != "" {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user