mirror of
https://github.com/WiiLink24/wfc-server.git
synced 2026-03-21 17:44:58 -05:00
GameStats: Support public data for getpd and setpd
Some checks are pending
Build CI / build (push) Waiting to run
Some checks are pending
Build CI / build (push) Waiting to run
This commit is contained in:
parent
788bf7ff0b
commit
f8339dbf87
|
|
@ -3,6 +3,7 @@ package common
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
|
@ -14,9 +15,10 @@ type GameSpyCommand struct {
|
|||
|
||||
var (
|
||||
ErrInvalidGameSpyCommand = errors.New("invalid GameSpy command received")
|
||||
ErrNoGameStatsDataLength = errors.New("no data length found in GameStats message")
|
||||
)
|
||||
|
||||
func ParseGameSpyMessage(msg string) ([]GameSpyCommand, error) {
|
||||
func parseGameSpyMessage(msg string, gameStats bool) ([]GameSpyCommand, error) {
|
||||
if !strings.Contains(msg, `\final\`) {
|
||||
return nil, ErrInvalidGameSpyCommand
|
||||
}
|
||||
|
|
@ -43,7 +45,26 @@ func ParseGameSpyMessage(msg string) ([]GameSpyCommand, error) {
|
|||
break
|
||||
}
|
||||
|
||||
if strings.Contains(msg, `\`) {
|
||||
if gameStats && key == "data" {
|
||||
if g.OtherValues["length"] == "" {
|
||||
return nil, ErrNoGameStatsDataLength
|
||||
}
|
||||
|
||||
dataLength, err := strconv.Atoi(g.OtherValues["length"])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(msg) < dataLength+1 {
|
||||
return nil, ErrInvalidGameSpyCommand
|
||||
}
|
||||
|
||||
value = msg[:dataLength]
|
||||
msg = msg[dataLength:]
|
||||
if msg[0] == '\\' {
|
||||
msg = msg[1:]
|
||||
}
|
||||
} else if strings.Contains(msg, `\`) {
|
||||
if msg[0] != '\\' {
|
||||
valueEnd := strings.Index(msg[1:], `\`)
|
||||
value = msg[:valueEnd+1]
|
||||
|
|
@ -70,12 +91,27 @@ func ParseGameSpyMessage(msg string) ([]GameSpyCommand, error) {
|
|||
return commands, nil
|
||||
}
|
||||
|
||||
func ParseGameSpyMessage(msg string) ([]GameSpyCommand, error) {
|
||||
return parseGameSpyMessage(msg, false)
|
||||
}
|
||||
|
||||
func ParseGameStatsMessage(msg string) ([]GameSpyCommand, error) {
|
||||
return parseGameSpyMessage(msg, true)
|
||||
}
|
||||
|
||||
func CreateGameSpyMessage(command GameSpyCommand) string {
|
||||
query := ""
|
||||
endQuery := ""
|
||||
for k, v := range command.OtherValues {
|
||||
query += fmt.Sprintf(`\%s\%s`, k, v)
|
||||
if command.Command == "getpdr" && k == "data" {
|
||||
endQuery += fmt.Sprintf(`\%s\%s`, k, v)
|
||||
} else {
|
||||
query += fmt.Sprintf(`\%s\%s`, k, v)
|
||||
}
|
||||
}
|
||||
|
||||
query += endQuery
|
||||
|
||||
if command.Command != "" {
|
||||
query = fmt.Sprintf(`\%s\%s%s`, command.Command, command.CommandValue, query)
|
||||
}
|
||||
|
|
|
|||
29
database/gamestats.go
Normal file
29
database/gamestats.go
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v4/pgxpool"
|
||||
)
|
||||
|
||||
const (
|
||||
queryGsGetPublicData = `SELECT modified_time, pdata FROM gamestats_public_data WHERE profile_id = $1 AND dindex = $2 AND ptype = $3`
|
||||
queryGsInsertPublicData = `INSERT INTO gamestats_public_data (profile_id, dindex, ptype, pdata, modified_time) VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP) RETURNING modified_time`
|
||||
queryGsUpdatePublicData = `UPDATE gamestats_public_data SET pdata = $4, modified_time = CURRENT_TIMESTAMP WHERE profile_id = $1 AND dindex = $2 AND ptype = $3 RETURNING modified_time`
|
||||
)
|
||||
|
||||
func GetGameStatsPublicData(pool *pgxpool.Pool, ctx context.Context, profileId uint32, dindex string, ptype string) (modifiedTime time.Time, publicData string, err error) {
|
||||
err = pool.QueryRow(ctx, queryGsGetPublicData, profileId, dindex, ptype).Scan(&modifiedTime, &publicData)
|
||||
return
|
||||
}
|
||||
|
||||
func CreateGameStatsPublicData(pool *pgxpool.Pool, ctx context.Context, profileId uint32, dindex string, ptype string, publicData string) (modifiedTime time.Time, err error) {
|
||||
err = pool.QueryRow(ctx, queryGsInsertPublicData, profileId, dindex, ptype, publicData).Scan(&modifiedTime)
|
||||
return
|
||||
}
|
||||
|
||||
func UpdateGameStatsPublicData(pool *pgxpool.Pool, ctx context.Context, profileId uint32, dindex string, ptype string, publicData string) (modifiedTime time.Time, err error) {
|
||||
err = pool.QueryRow(ctx, queryGsUpdatePublicData, profileId, dindex, ptype, publicData).Scan(&modifiedTime)
|
||||
return
|
||||
}
|
||||
|
|
@ -2,13 +2,70 @@ package gamestats
|
|||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
"wwfc/common"
|
||||
"wwfc/database"
|
||||
"wwfc/logging"
|
||||
|
||||
"github.com/jackc/pgx/v4"
|
||||
"github.com/logrusorgru/aurora/v3"
|
||||
)
|
||||
|
||||
func (g *GameStatsSession) getpd(command common.GameSpyCommand) {
|
||||
// Temporary empty data, it's an embedded gamespy \key\value message excluding \final\
|
||||
data := `\\`
|
||||
errMsg := common.GameSpyCommand{
|
||||
Command: "getpdr",
|
||||
CommandValue: "0",
|
||||
OtherValues: map[string]string{
|
||||
"pid": command.OtherValues["pid"],
|
||||
"lid": strconv.Itoa(g.LoginID),
|
||||
},
|
||||
}
|
||||
|
||||
profileIdStr, ok := command.OtherValues["pid"]
|
||||
if !ok {
|
||||
logging.Error(g.ModuleName, "Missing pid")
|
||||
logging.Error(g.ModuleName, "Full command:", command)
|
||||
g.Write(errMsg)
|
||||
return
|
||||
}
|
||||
|
||||
profileId, err := strconv.ParseUint(profileIdStr, 10, 32)
|
||||
if err != nil {
|
||||
logging.Error(g.ModuleName, "Invalid pid:", aurora.Cyan(profileIdStr))
|
||||
logging.Error(g.ModuleName, "Full command:", command)
|
||||
g.Write(errMsg)
|
||||
return
|
||||
}
|
||||
|
||||
dindex, ok := command.OtherValues["dindex"]
|
||||
if !ok {
|
||||
logging.Error(g.ModuleName, "Missing dindex")
|
||||
logging.Error(g.ModuleName, "Full command:", command)
|
||||
g.Write(errMsg)
|
||||
return
|
||||
}
|
||||
|
||||
ptype, ok := command.OtherValues["ptype"]
|
||||
if !ok {
|
||||
logging.Error(g.ModuleName, "Missing ptype")
|
||||
logging.Error(g.ModuleName, "Full command:", command)
|
||||
g.Write(errMsg)
|
||||
return
|
||||
}
|
||||
|
||||
logging.Info(g.ModuleName, "Get public data: PID:", aurora.Cyan(profileId), "Index:", aurora.Cyan(dindex), "Type:", aurora.Cyan(ptype))
|
||||
|
||||
modifiedTime, data, err := database.GetGameStatsPublicData(pool, ctx, uint32(profileId), dindex, ptype)
|
||||
if err != nil {
|
||||
if err != pgx.ErrNoRows {
|
||||
logging.Error(g.ModuleName, "GetGameStatsPublicData returned", err)
|
||||
g.Write(errMsg)
|
||||
return
|
||||
}
|
||||
|
||||
logging.Warn(g.ModuleName, "No data found")
|
||||
g.Write(errMsg)
|
||||
return
|
||||
}
|
||||
|
||||
g.Write(common.GameSpyCommand{
|
||||
Command: "getpdr",
|
||||
|
|
@ -16,9 +73,9 @@ func (g *GameStatsSession) getpd(command common.GameSpyCommand) {
|
|||
OtherValues: map[string]string{
|
||||
"lid": strconv.Itoa(g.LoginID),
|
||||
"pid": command.OtherValues["pid"],
|
||||
"mod": strconv.Itoa(int(time.Now().Unix())),
|
||||
"length": strconv.Itoa(len(data) + 1),
|
||||
"data": `\` + data + `\`,
|
||||
"mod": strconv.Itoa(int(modifiedTime.Unix())),
|
||||
"length": strconv.Itoa(len(data)),
|
||||
"data": data,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -222,7 +222,7 @@ func HandlePacket(index uint64, data []byte) {
|
|||
message := decrypted.String()
|
||||
session.ReadBuffer = []byte{}
|
||||
|
||||
commands, err := common.ParseGameSpyMessage(message)
|
||||
commands, err := common.ParseGameStatsMessage(message)
|
||||
if err != nil {
|
||||
logging.Error(session.ModuleName, "Error parsing message:", err.Error())
|
||||
logging.Error(session.ModuleName, "Raw data:", message)
|
||||
|
|
|
|||
|
|
@ -2,20 +2,113 @@ package gamestats
|
|||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"wwfc/common"
|
||||
"wwfc/database"
|
||||
"wwfc/logging"
|
||||
|
||||
"github.com/jackc/pgx/v4"
|
||||
"github.com/logrusorgru/aurora/v3"
|
||||
)
|
||||
|
||||
func (g *GameStatsSession) setpd(command common.GameSpyCommand) {
|
||||
// Example (with formatting):
|
||||
// \setpd\
|
||||
// \pid\1000000004
|
||||
// \ptype\3
|
||||
// \dindex\0
|
||||
// \kv\1
|
||||
// \lid\0
|
||||
// \length\149
|
||||
// \data\
|
||||
// \itast_friend_p\AFAAYQBsAGEAcABlAGwAaQAAAAAAANzmAFAAYQBsAGEAcABlAGwAaQAAAABSJYgbuuQEbA5pAASOoAk9JpJsjKhAFEmQTQCKAIolBAAAAAAAAAAAAAAAAAAAAAAAAAAAGps*\x00
|
||||
// \final\
|
||||
|
||||
errMsg := common.GameSpyCommand{
|
||||
Command: "setpdr",
|
||||
CommandValue: "0",
|
||||
OtherValues: map[string]string{
|
||||
"pid": command.OtherValues["pid"],
|
||||
"lid": strconv.Itoa(g.LoginID),
|
||||
},
|
||||
}
|
||||
|
||||
if command.OtherValues["pid"] != strconv.FormatUint(uint64(g.User.ProfileId), 10) {
|
||||
logging.Error(g.ModuleName, "Invalid profile ID:", aurora.Cyan(command.OtherValues["pid"]))
|
||||
g.Write(errMsg)
|
||||
return
|
||||
}
|
||||
|
||||
dindex, ok := command.OtherValues["dindex"]
|
||||
if !ok {
|
||||
logging.Error(g.ModuleName, "Missing dindex")
|
||||
logging.Error(g.ModuleName, "Full command:", command)
|
||||
g.Write(errMsg)
|
||||
return
|
||||
}
|
||||
|
||||
ptype, ok := command.OtherValues["ptype"]
|
||||
if !ok {
|
||||
logging.Error(g.ModuleName, "Missing ptype")
|
||||
logging.Error(g.ModuleName, "Full command:", command)
|
||||
g.Write(errMsg)
|
||||
return
|
||||
}
|
||||
|
||||
newData, ok := command.OtherValues["data"]
|
||||
if !ok {
|
||||
logging.Error(g.ModuleName, "Missing data")
|
||||
logging.Error(g.ModuleName, "Full command:", command)
|
||||
g.Write(errMsg)
|
||||
return
|
||||
}
|
||||
|
||||
logging.Info(g.ModuleName, "Set public data: PID:", aurora.Cyan(g.User.ProfileId), "Index:", aurora.Cyan(dindex), "Type:", aurora.Cyan(ptype), "Data:", aurora.Cyan(newData))
|
||||
|
||||
// Trim extra null byte at the end
|
||||
if len(newData) > 0 && newData[len(newData)-1] == 0 {
|
||||
newData = newData[:len(newData)-1]
|
||||
}
|
||||
|
||||
if strings.ContainsRune(newData, 0) {
|
||||
logging.Error(g.ModuleName, "Data contains null byte")
|
||||
g.Write(errMsg)
|
||||
return
|
||||
}
|
||||
|
||||
var modifiedTime time.Time
|
||||
_, _, err := database.GetGameStatsPublicData(pool, ctx, g.User.ProfileId, dindex, ptype)
|
||||
if err != nil {
|
||||
if err != pgx.ErrNoRows {
|
||||
logging.Error(g.ModuleName, "GetGameStatsPublicData returned", err)
|
||||
g.Write(errMsg)
|
||||
return
|
||||
}
|
||||
|
||||
modifiedTime, err = database.CreateGameStatsPublicData(pool, ctx, g.User.ProfileId, dindex, ptype, newData)
|
||||
if err != nil {
|
||||
logging.Error(g.ModuleName, "GetGameStatsPublicData returned", err)
|
||||
g.Write(errMsg)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
modifiedTime, err = database.UpdateGameStatsPublicData(pool, ctx, g.User.ProfileId, dindex, ptype, newData)
|
||||
if err != nil {
|
||||
logging.Error(g.ModuleName, "UpdateGameStatsPublicData returned", err)
|
||||
g.Write(errMsg)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Is mod supposed to be the last modified time or new modified time?
|
||||
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": `\\`,
|
||||
"lid": strconv.Itoa(g.LoginID),
|
||||
"pid": command.OtherValues["pid"],
|
||||
"mod": strconv.Itoa(int(modifiedTime.Unix())),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
14
schema.sql
14
schema.sql
|
|
@ -86,6 +86,20 @@ ALTER TABLE ONLY public.mario_kart_wii_sake
|
|||
|
||||
ALTER TABLE public.mario_kart_wii_sake OWNER TO wiilink;
|
||||
|
||||
--
|
||||
-- Name: gamestats_public_data; Type: TABLE; Schema: public; Owner: wiilink
|
||||
--
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.gamestats_public_data (
|
||||
profile_id bigint NOT NULL,
|
||||
dindex character varying NOT NULL,
|
||||
ptype character varying NOT NULL,
|
||||
pdata character varying NOT NULL,
|
||||
modified_time timestamp without time zone NOT NULL,
|
||||
|
||||
CONSTRAINT one_pdata_constraint UNIQUE (profile_id, dindex, ptype)
|
||||
);
|
||||
|
||||
--
|
||||
-- Name: users_profile_id_seq; Type: SEQUENCE; Schema: public; Owner: wiilink
|
||||
--
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user