GPCM: Report events for profile actions

This commit is contained in:
Palapeli 2026-04-05 09:52:28 -04:00
parent e30d89cd1e
commit 1c44f521f3
No known key found for this signature in database
GPG Key ID: 1FFE8F556A474925
16 changed files with 119 additions and 37 deletions

View File

@ -3,6 +3,7 @@ package common
import (
"encoding/xml"
"os"
"wwfc/logging"
"github.com/linkdata/deadlock"
)
@ -53,15 +54,8 @@ type Config struct {
}
type EventReportingConfig struct {
LogToDatabase bool `xml:"logToDatabase"`
Webhooks []WebhookConfig `xml:"discord>webhook"`
}
type WebhookConfig struct {
Enabled bool `xml:"enabled"`
URL string `xml:"url"`
Author string `xml:"author,omitempty"`
EventTypes []string `xml:"eventTypes>event"`
LogToDatabase bool `xml:"logToDatabase"`
Webhooks []logging.WebhookConfig `xml:"discord>webhook"`
}
var (
@ -157,6 +151,6 @@ func GetConfig() Config {
func (c Config) RegisterWebhooks() {
for _, webhook := range c.EventReporting.Webhooks {
webhook.RegisterEventReporting()
webhook.RegisterWebhook()
}
}

View File

@ -2,7 +2,7 @@ package common
import "reflect"
func UNUSED(v ...interface{}) {
func MaybeUnused(v ...interface{}) {
}
func ReverseMap(m interface{}) interface{} {

View File

@ -33,5 +33,7 @@ func Start(config common.Config) Connection {
}
func (c *Connection) Close() {
c.pool.Close()
if c != nil && c.pool != nil {
c.pool.Close()
}
}

View File

@ -21,7 +21,7 @@ func (c *Connection) InsertEvent(eventType string, eventData map[string]any) (in
return eventId, nil
}
func (c *Connection) RegisterEventLogging(config common.Config, eventTypes []string) {
func (c *Connection) RegisterEvents(config common.Config, eventTypes []string) {
if !config.EventReporting.LogToDatabase {
return
}

View File

@ -72,6 +72,7 @@ func (c *Connection) LoginUserToGPCM(userId uint64, gsbrcd string, profileId uin
}
logging.Notice("DATABASE", "Created new GPCM user:", aurora.Cyan(userId), aurora.Cyan(gsbrcd), aurora.Cyan(user.ProfileId))
user.Created = true
} else {
var firstName *string
var lastName *string

View File

@ -38,6 +38,7 @@ type User struct {
OpenHost bool
LastInGameSn string
LastIPAddress string
Created bool
}
var (

View File

@ -234,7 +234,7 @@ func HandlePacket(index uint64, data []byte) {
commands = session.handleCommand("getpd", commands, session.getpd)
commands = session.handleCommand("setpd", commands, session.setpd)
common.UNUSED(session.ignoreCommand)
common.MaybeUnused(session.ignoreCommand)
for _, command := range commands {
logging.Error(session.ModuleName, "Unknown command:", aurora.Cyan(command))

View File

@ -238,7 +238,7 @@ func printHex(data []byte) {
}
func verifyECDSA(publicKey []byte, signature []byte, hash []byte) bool {
common.UNUSED(printHex)
common.MaybeUnused(printHex)
r := big.NewInt(0).SetBytes(signature[0x00:0x1E])
s := big.NewInt(0).SetBytes(signature[0x1E:0x3C])

View File

@ -404,7 +404,7 @@ func (err GPError) GetMessage() string {
return common.CreateGameSpyMessage(command)
}
func (err GPError) GetMessageTranslate(gameName string, region byte, lang byte, cfc uint64, ngid uint32) string {
func (err GPError) GetMessageTranslate(gameName string, region byte, lang byte, cfc uint64, ngid uint32) (string, int) {
command := common.GameSpyCommand{
Command: "error",
CommandValue: "",
@ -462,7 +462,7 @@ func (err GPError) GetMessageTranslate(gameName string, region byte, lang byte,
command.OtherValues["wl:err"] = strconv.Itoa(err.WWFCMessage.ErrorCode)
}
return common.CreateGameSpyMessage(command)
return common.CreateGameSpyMessage(command), err.WWFCMessage.ErrorCode
}
func (g *GameSpySession) replyError(err GPError) {
@ -474,6 +474,12 @@ func (g *GameSpySession) replyError(err GPError) {
if err.Fatal {
common.CloseConnection(ServerName, g.ConnIndex)
}
logging.Event("gpcm_returned_error", map[string]any{
"profile_id": g.User.ProfileId,
"error_code": err.ErrorCode,
"error_string": err.ErrorString,
"fatal": err.Fatal,
})
return
}
@ -482,10 +488,18 @@ func (g *GameSpySession) replyError(err GPError) {
deviceId = g.User.NgDeviceId[0]
}
msg := err.GetMessageTranslate(g.GameName, g.Region, g.Language, g.ConsoleFriendCode, deviceId)
msg, wwfcErrorCode := err.GetMessageTranslate(g.GameName, g.Region, g.Language, g.ConsoleFriendCode, deviceId)
// logging.Info(g.ModuleName, "Sending error message:", msg)
common.SendPacket(ServerName, g.ConnIndex, []byte(msg))
if err.Fatal {
common.CloseConnection(ServerName, g.ConnIndex)
}
logging.Event("gpcm_returned_error", map[string]any{
"profile_id": g.User.ProfileId,
"error_code": err.ErrorCode,
"error_string": err.ErrorString,
"fatal": err.Fatal,
"wiilink_error_code": wwfcErrorCode,
})
}

View File

@ -286,7 +286,7 @@ func sendMessageToProfileId(msgType string, from uint32, to uint32, msg string)
}
func (g *GameSpySession) sendFriendStatus(profileId uint32) {
common.UNUSED(sendMessageToProfileId)
common.MaybeUnused(sendMessageToProfileId)
if !g.isFriendAuthorized(profileId) {
return

View File

@ -226,6 +226,18 @@ func (g *GameSpySession) login(command common.GameSpyCommand) {
g.LoginInfoSet = true
logging.Event(
"received_login_info",
map[string]any{
"user_id": userId,
"game_name": g.GameName,
"wii_number": cfc,
"in_game_name": ingamesn,
"unit_code": unitcd,
"ip_address": g.RemoteAddr,
},
)
expectedUnitCode := common.GetExpectedUnitCode(g.GameName)
if (g.UnitCode != UnitCodeDS && g.UnitCode != UnitCodeWii) || (g.UnitCode != expectedUnitCode && expectedUnitCode != UnitCodeDSAndWii) {
logging.Error(g.ModuleName, "Incorrect unit code specified:", aurora.Cyan(unitcd))
@ -294,6 +306,18 @@ func (g *GameSpySession) login(command common.GameSpyCommand) {
return
}
if g.User.Created {
logging.Event(
"profile_created",
map[string]any{
"user_id": g.User.UserId,
"profile_id": g.User.ProfileId,
"ip_address": g.RemoteAddr,
},
)
}
g.ModuleName = "GPCM:" + strconv.FormatInt(int64(g.User.ProfileId), 10) + "*"
g.ModuleName += "/" + common.CalcFriendCodeString(g.User.ProfileId, g.User.GsbrCode[:4]) + "*"
@ -369,6 +393,19 @@ func (g *GameSpySession) login(command common.GameSpyCommand) {
})
common.SendPacket(ServerName, g.ConnIndex, []byte(payload))
logging.Event(
"logged_in",
map[string]any{
"user_id": g.User.UserId,
"profile_id": g.User.ProfileId,
"game_name": g.GameName,
"wii_number": g.ConsoleFriendCode,
"in_game_name": g.InGameName,
"unit_code": g.UnitCode,
"ip_address": g.RemoteAddr,
},
)
}
func (g *GameSpySession) exLogin(command common.GameSpyCommand) {
@ -446,6 +483,14 @@ func (g *GameSpySession) verifyExLoginInfo(command common.GameSpyCommand, authTo
}
g.DeviceId = deviceId
logging.Event(
"device_authenticated",
map[string]any{
"profile_id": g.User.ProfileId,
"ng_device_id": g.User.NgDeviceId,
"payload_version": payloadVer,
},
)
return
}

View File

@ -88,6 +88,17 @@ func StartServer(reload bool) {
logging.Notice("GPCM", "Loaded", aurora.Cyan(len(sessions)), "sessions")
}
db.RegisterEvents(config, []string{
"profile_created",
"logged_in",
"logged_out",
"received_login_info",
"device_authenticated",
"reported_bad_packet",
"reported_stall",
"gpcm_returned_error",
})
}
func Shutdown() {
@ -120,6 +131,10 @@ func CloseConnection(index uint64) {
qr2.ProcessGPStatusUpdate(session.User.ProfileId, session.QR2IP, "0")
}
session.sendLogoutStatus()
logging.Event("logged_out", map[string]any{
"profile_id": session.User.ProfileId,
})
}
mutex.Lock()

View File

@ -27,6 +27,10 @@ func (g *GameSpySession) handleWWFCReport(command common.GameSpyCommand) {
}
logging.Warn(g.ModuleName, "Report bad packet from", aurora.BrightCyan(strconv.FormatUint(profileId, 10)))
logging.Event("reported_bad_packet", map[string]any{
"profile_id": g.User.ProfileId,
"sender_id": profileId,
})
case "wl:stall":
profileId, err := strconv.ParseUint(value, 10, 32)
@ -36,6 +40,10 @@ func (g *GameSpySession) handleWWFCReport(command common.GameSpyCommand) {
}
logging.Warn(g.ModuleName, "Room stall caused by", aurora.BrightCyan(strconv.FormatUint(profileId, 10)))
logging.Event("reported_stall", map[string]any{
"profile_id": g.User.ProfileId,
"stalling_id": profileId,
})
case "wl:mkw_user":
if g.GameName != "mariokartwii" {

View File

@ -18,9 +18,9 @@ func Event(eventType string, eventData map[string]any) {
defer mutex.RUnlock()
for _, callback := range eventCallbacks {
if callback.AllEvents {
callback.Function(eventType, eventData)
go callback.Function(eventType, eventData)
} else if _, ok := callback.EventTypes[eventType]; ok {
callback.Function(eventType, eventData)
go callback.Function(eventType, eventData)
}
}
}

View File

@ -1,11 +1,10 @@
package common
package logging
import (
"encoding/json"
"net/http"
"strconv"
"strings"
"wwfc/logging"
)
func contains(slice []string, item string) bool {
@ -17,6 +16,13 @@ func contains(slice []string, item string) bool {
return false
}
type WebhookConfig struct {
Enabled bool `xml:"enabled"`
URL string `xml:"url"`
Author string `xml:"author,omitempty"`
EventTypes []string `xml:"eventTypes>event"`
}
type webhookAuthor struct {
Name string `json:"name,omitempty"`
}
@ -79,19 +85,20 @@ func (w WebhookConfig) ReportEvent(eventType string, eventData map[string]any) {
}
resp, err := http.Post(w.URL, "application/json", strings.NewReader(string(jsonData)))
if err != nil {
panic(err)
Error("LOGGING", "Failed to send webhook request:", err)
return
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
panic("received non-2xx response: " + resp.Status)
Error("LOGGING", "Received non-2xx response from webhook:", resp.Status)
}
resp.Body.Close()
}
func (w WebhookConfig) RegisterEventReporting() {
func (w WebhookConfig) RegisterWebhook() {
if !w.Enabled {
return
}
logging.RegisterEventCallback(w.EventTypes, func(eventType string, eventData map[string]any) {
RegisterEventCallback(w.EventTypes, func(eventType string, eventData map[string]any) {
w.ReportEvent(eventType, eventData)
})
}

15
main.go
View File

@ -72,16 +72,11 @@ type RPCPacket struct {
}
func connectAndLogEvent(eventType string) {
var db *database.Connection
defer func() {
logging.Event(eventType, map[string]any{})
if db != nil {
db.Close()
}
}()
dbObject := database.Start(config)
db = &dbObject
dbObject.RegisterEventLogging(config, []string{eventType})
var db database.Connection
defer db.Close()
defer logging.Event(eventType, map[string]any{})
db = database.Start(config)
db.RegisterEvents(config, []string{eventType})
}
// backendMain starts all the servers and creates an RPC server to communicate with the frontend