diff --git a/common/config.go b/common/config.go index 7678477..a714ca5 100644 --- a/common/config.go +++ b/common/config.go @@ -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() } } diff --git a/common/misc.go b/common/misc.go index 298c802..d640695 100644 --- a/common/misc.go +++ b/common/misc.go @@ -2,7 +2,7 @@ package common import "reflect" -func UNUSED(v ...interface{}) { +func MaybeUnused(v ...interface{}) { } func ReverseMap(m interface{}) interface{} { diff --git a/database/connection.go b/database/connection.go index 0b34756..849cb68 100644 --- a/database/connection.go +++ b/database/connection.go @@ -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() + } } diff --git a/database/events.go b/database/events.go index f46b295..e5368b5 100644 --- a/database/events.go +++ b/database/events.go @@ -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 } diff --git a/database/login.go b/database/login.go index 88a1eb5..7f30c55 100644 --- a/database/login.go +++ b/database/login.go @@ -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 diff --git a/database/user.go b/database/user.go index 821a428..155ac3b 100644 --- a/database/user.go +++ b/database/user.go @@ -38,6 +38,7 @@ type User struct { OpenHost bool LastInGameSn string LastIPAddress string + Created bool } var ( diff --git a/gamestats/main.go b/gamestats/main.go index 2e6e2ea..b26ad8a 100644 --- a/gamestats/main.go +++ b/gamestats/main.go @@ -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)) diff --git a/gpcm/ecdsa.go b/gpcm/ecdsa.go index 48e53a0..820f55c 100644 --- a/gpcm/ecdsa.go +++ b/gpcm/ecdsa.go @@ -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]) diff --git a/gpcm/error.go b/gpcm/error.go index 8d42746..faccbf3 100644 --- a/gpcm/error.go +++ b/gpcm/error.go @@ -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, + }) } diff --git a/gpcm/friend.go b/gpcm/friend.go index 34ecd62..d762a8e 100644 --- a/gpcm/friend.go +++ b/gpcm/friend.go @@ -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 diff --git a/gpcm/login.go b/gpcm/login.go index ccf4fc9..4faa68e 100644 --- a/gpcm/login.go +++ b/gpcm/login.go @@ -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 } diff --git a/gpcm/main.go b/gpcm/main.go index ac0d626..f299b52 100644 --- a/gpcm/main.go +++ b/gpcm/main.go @@ -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() diff --git a/gpcm/report.go b/gpcm/report.go index 97dfb38..8c2b72e 100644 --- a/gpcm/report.go +++ b/gpcm/report.go @@ -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" { diff --git a/logging/event.go b/logging/event.go index 7f6f421..726080e 100644 --- a/logging/event.go +++ b/logging/event.go @@ -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) } } } diff --git a/common/webhook.go b/logging/webhook.go similarity index 80% rename from common/webhook.go rename to logging/webhook.go index 00295ae..380ad9f 100644 --- a/common/webhook.go +++ b/logging/webhook.go @@ -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) }) } diff --git a/main.go b/main.go index 2a68cec..52a3879 100644 --- a/main.go +++ b/main.go @@ -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