diff --git a/gcsp/main.go b/gcsp/main.go deleted file mode 100644 index 7de6b49..0000000 --- a/gcsp/main.go +++ /dev/null @@ -1,193 +0,0 @@ -package gcsp - -import ( - "bufio" - "context" - "errors" - "fmt" - "github.com/jackc/pgx/v4/pgxpool" - "github.com/logrusorgru/aurora/v3" - "io" - "net" - "strconv" - "strings" - "time" - "wwfc/common" - "wwfc/database" - "wwfc/logging" -) - -var ( - ctx = context.Background() - pool *pgxpool.Pool - userId int64 -) - -func StartServer() { - // Get config - config := common.GetConfig() - - // 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) - } - - address := config.Address + ":29901" - l, err := net.Listen("tcp", address) - if err != nil { - panic(err) - } - - // Close the listener when the application closes. - defer l.Close() - logging.Notice("GCSP", "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) { - defer conn.Close() - - moduleName := "GCSP" - knownProfileId := uint32(0) - - err := conn.(*net.TCPConn).SetKeepAlive(true) - if err != nil { - logging.Notice(moduleName, "Unable to set keepalive:", err.Error()) - } - - err = conn.(*net.TCPConn).SetKeepAlivePeriod(time.Hour * 1000) - if err != nil { - logging.Notice(moduleName, "Unable to set keepalive:", err.Error()) - } - - logging.Notice(moduleName, "Connection established from", conn.RemoteAddr().String()) - - // Here we go into the listening loop - for { - buffer := make([]byte, 1024) - _, err := bufio.NewReader(conn).Read(buffer) - if err != nil { - if errors.Is(err, io.EOF) { - // Client closed connection, terminate. - return - } - } - - commands, err := common.ParseGameSpyMessage(string(buffer)) - if err != nil { - panic(err) - } - - for _, command := range commands { - logging.Notice(moduleName, "Command:", aurora.Yellow(command.Command).String()) - switch command.Command { - case "ka": - conn.Write([]byte(`\ka\\final\`)) - break - - case "otherslist": - strProfileId, ok := command.OtherValues["profileid"] - if !ok { - logging.Notice(moduleName, "Missing profileid in otherslist") - return - } - - profileId, err := strconv.ParseUint(strProfileId, 10, 32) - if err != nil { - panic(err) - } - - if knownProfileId == 0 { - knownProfileId = uint32(profileId) - moduleName = "GCSP:" + strconv.FormatUint(profileId, 10) - moduleName += "/" + common.CalcFriendCodeString(uint32(profileId), "RMCJ") - } else if uint32(profileId) != knownProfileId { - logging.Notice(moduleName, "WARN: Mismatched profile ID in otherslist:", aurora.Cyan(strProfileId).String()) - } - - conn.Write([]byte(handleOthersList(moduleName, uint32(profileId), command))) - break - } - } - } -} - -func handleOthersList(moduleName string, profileId uint32, command common.GameSpyCommand) string { - empty := `\otherslist\\final\` - - _, ok := command.OtherValues["sesskey"] - if !ok { - logging.Notice(moduleName, "Missing sesskey in otherslist") - return empty - } - - numopids, ok := command.OtherValues["numopids"] - if !ok { - logging.Notice(moduleName, "Missing numopids in otherslist") - return empty - } - - opids, ok := command.OtherValues["opids"] - if !ok { - logging.Notice(moduleName, "Missing opids in otherslist") - return empty - } - - _, ok = command.OtherValues["gamename"] - if !ok { - logging.Notice(moduleName, "Missing gamename in otherslist") - return empty - } - - numOpidsValue, err := strconv.Atoi(numopids) - if err != nil { - panic(err) - } - - opidsSplit := strings.Split(opids, "|") - if len(opidsSplit) != numOpidsValue { - logging.Notice(moduleName, "Mismatch opids length with numopids:", aurora.Cyan(len(opidsSplit)).String(), "!=", aurora.Cyan(numOpidsValue).String()) - return empty - } - - payload := `\otherslist\` - for _, strOtherId := range opidsSplit { - otherId, err := strconv.ParseUint(strOtherId, 10, 32) - if err != nil { - panic(err) - } - - // TODO: Perhaps this could be condensed into one database query - // Also TODO: Check if the players are actually friends - user, ok := database.GetProfile(pool, ctx, uint32(otherId)) - if !ok { - logging.Notice(moduleName, "Other ID doesn't exist:", aurora.Cyan(strOtherId).String()) - // If the profile doesn't exist then skip adding it - continue - } - - payload += `\o\` + strconv.FormatUint(uint64(user.ProfileId), 10) - payload += `\uniquenick\` + user.UniqueNick - } - - payload += `\oldone\\final\` - return payload -} diff --git a/gpsp/main.go b/gpsp/main.go index 18b9718..e609301 100644 --- a/gpsp/main.go +++ b/gpsp/main.go @@ -1,18 +1,45 @@ package gpsp import ( + "bufio" + "context" + "errors" "fmt" + "github.com/jackc/pgx/v4/pgxpool" + "github.com/logrusorgru/aurora/v3" + "io" "net" - "os" + "strconv" + "strings" + "time" "wwfc/common" + "wwfc/database" "wwfc/logging" ) +var ( + ctx = context.Background() + pool *pgxpool.Pool + userId int64 +) + func StartServer() { // Get config config := common.GetConfig() - address := config.Address + ":27900" + // 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) + } + + address := config.Address + ":29901" l, err := net.Listen("tcp", address) if err != nil { panic(err) @@ -36,16 +63,131 @@ func StartServer() { // Handles incoming requests. func handleRequest(conn net.Conn) { - // Make a buffer to hold incoming data. - buf := make([]byte, 1024) - // Read the incoming connection into the buffer. - reqLen, err := conn.Read(buf) + defer conn.Close() + + moduleName := "GPSP" + knownProfileId := uint32(0) + + err := conn.(*net.TCPConn).SetKeepAlive(true) + if err != nil { + logging.Notice(moduleName, "Unable to set keepalive:", err.Error()) + } + + err = conn.(*net.TCPConn).SetKeepAlivePeriod(time.Hour * 1000) + if err != nil { + logging.Notice(moduleName, "Unable to set keepalive:", err.Error()) + } + + logging.Notice(moduleName, "Connection established from", conn.RemoteAddr().String()) + + // Here we go into the listening loop + for { + buffer := make([]byte, 1024) + _, err := bufio.NewReader(conn).Read(buffer) + if err != nil { + if errors.Is(err, io.EOF) { + // Client closed connection, terminate. + return + } + } + + commands, err := common.ParseGameSpyMessage(string(buffer)) + if err != nil { + panic(err) + } + + for _, command := range commands { + logging.Notice(moduleName, "Command:", aurora.Yellow(command.Command).String()) + switch command.Command { + case "ka": + conn.Write([]byte(`\ka\\final\`)) + break + + case "otherslist": + strProfileId, ok := command.OtherValues["profileid"] + if !ok { + logging.Notice(moduleName, "Missing profileid in otherslist") + return + } + + profileId, err := strconv.ParseUint(strProfileId, 10, 32) + if err != nil { + panic(err) + } + + if knownProfileId == 0 { + knownProfileId = uint32(profileId) + moduleName = "GPSP:" + strconv.FormatUint(profileId, 10) + moduleName += "/" + common.CalcFriendCodeString(uint32(profileId), "RMCJ") + } else if uint32(profileId) != knownProfileId { + logging.Notice(moduleName, "WARN: Mismatched profile ID in otherslist:", aurora.Cyan(strProfileId).String()) + } + + conn.Write([]byte(handleOthersList(moduleName, uint32(profileId), command))) + break + } + } + } +} + +func handleOthersList(moduleName string, profileId uint32, command common.GameSpyCommand) string { + empty := `\otherslist\\final\` + + _, ok := command.OtherValues["sesskey"] + if !ok { + logging.Notice(moduleName, "Missing sesskey in otherslist") + return empty + } + + numopids, ok := command.OtherValues["numopids"] + if !ok { + logging.Notice(moduleName, "Missing numopids in otherslist") + return empty + } + + opids, ok := command.OtherValues["opids"] + if !ok { + logging.Notice(moduleName, "Missing opids in otherslist") + return empty + } + + _, ok = command.OtherValues["gamename"] + if !ok { + logging.Notice(moduleName, "Missing gamename in otherslist") + return empty + } + + numOpidsValue, err := strconv.Atoi(numopids) if err != nil { panic(err) } - // Send a response back to person contacting us. - conn.Write([]byte(`\ka\\final\`)) - // Close the connection when you're done with it. - conn.Close() + opidsSplit := strings.Split(opids, "|") + if len(opidsSplit) != numOpidsValue { + logging.Notice(moduleName, "Mismatch opids length with numopids:", aurora.Cyan(len(opidsSplit)).String(), "!=", aurora.Cyan(numOpidsValue).String()) + return empty + } + + payload := `\otherslist\` + for _, strOtherId := range opidsSplit { + otherId, err := strconv.ParseUint(strOtherId, 10, 32) + if err != nil { + panic(err) + } + + // TODO: Perhaps this could be condensed into one database query + // Also TODO: Check if the players are actually friends + user, ok := database.GetProfile(pool, ctx, uint32(otherId)) + if !ok { + logging.Notice(moduleName, "Other ID doesn't exist:", aurora.Cyan(strOtherId).String()) + // If the profile doesn't exist then skip adding it + continue + } + + payload += `\o\` + strconv.FormatUint(uint64(user.ProfileId), 10) + payload += `\uniquenick\` + user.UniqueNick + } + + payload += `\oldone\\final\` + return payload } diff --git a/main.go b/main.go index 92b5671..bfff591 100644 --- a/main.go +++ b/main.go @@ -2,16 +2,16 @@ package main import ( "sync" - "wwfc/gcsp" "wwfc/gpcm" - "wwfc/master" + "wwfc/gpsp" "wwfc/matchmaking" "wwfc/nas" + "wwfc/qr2" ) func main() { wg := &sync.WaitGroup{} - actions := []func(){nas.StartServer, gpcm.StartServer, master.StartServer, gcsp.StartServer, matchmaking.StartServer} + actions := []func(){nas.StartServer, gpcm.StartServer, qr2.StartServer, gpsp.StartServer, matchmaking.StartServer} wg.Add(5) for _, action := range actions { go func(ac func()) { diff --git a/matchmaking/main.go b/matchmaking/main.go index 54958f8..26ee156 100644 --- a/matchmaking/main.go +++ b/matchmaking/main.go @@ -23,7 +23,7 @@ var ( ) const ( - ModuleName = "MATCHMAKING" + ModuleName = "SB" // Requests sent from the client ServerListRequest = 0x00 @@ -66,7 +66,7 @@ func StartServer() { // Close the listener when the application closes. defer l.Close() - logging.Notice("MATCHMAKING", "Listening on", address) + logging.Notice(ModuleName, "Listening on", address) for { // Listen for an incoming connection. @@ -105,10 +105,11 @@ func handleRequest(conn net.Conn) { bufferSize -= int(packetSize) packetSize = 0 + // Packets tend to be sent in fragments, so this loop makes sure the packets has been fully received before continuing for { if bufferSize > 2 { packetSize = binary.BigEndian.Uint16(buffer[:2]) - if packetSize < 3 { + if packetSize < 3 || packetSize >= 1024 { logging.Notice(ModuleName, "Invalid packet size - terminating") return } @@ -133,9 +134,6 @@ func handleRequest(conn net.Conn) { bufferSize += readSize } - logging.Notice(ModuleName, "packet size:", aurora.Cyan(packetSize).String()) - logging.Notice(ModuleName, "buffer size:", aurora.Cyan(bufferSize).String()) - switch buffer[2] { case ServerListRequest: logging.Notice(ModuleName, "Command:", aurora.Yellow("SERVER_LIST_REQUEST").String()) diff --git a/matchmaking/server.go b/matchmaking/server.go index d0720cc..d577f35 100644 --- a/matchmaking/server.go +++ b/matchmaking/server.go @@ -9,7 +9,7 @@ import ( "strings" "wwfc/common" "wwfc/logging" - "wwfc/master" + "wwfc/qr2" ) const ( @@ -40,7 +40,7 @@ const ( func FindServers(gueryGame string, filter string) ([]map[string]string, error) { // TODO: Handle gueryGame, filter - return master.GetSessionServers(), nil + return qr2.GetSessionServers(), nil } func popString(buffer []byte, index int) (string, int) { @@ -249,5 +249,5 @@ func handleSendMessageRequest(conn net.Conn, buffer []byte) { // TODO: Perform basic packet verification // TODO SECURITY: Check if the selected IP is actually online, or at least make sure it's not a local IP - master.SendClientMessage(destIP, buffer[9:]) + qr2.SendClientMessage(destIP, buffer[9:]) } diff --git a/master/challenge.go b/qr2/challenge.go similarity index 98% rename from master/challenge.go rename to qr2/challenge.go index eae91f9..230dbd2 100644 --- a/master/challenge.go +++ b/qr2/challenge.go @@ -1,4 +1,4 @@ -package master +package qr2 import ( "fmt" diff --git a/master/heartbeat.go b/qr2/heartbeat.go similarity index 96% rename from master/heartbeat.go rename to qr2/heartbeat.go index c43c342..f75664a 100644 --- a/master/heartbeat.go +++ b/qr2/heartbeat.go @@ -1,4 +1,4 @@ -package master +package qr2 import ( "encoding/binary" @@ -11,7 +11,7 @@ import ( func heartbeat(conn net.PacketConn, addr net.Addr, buffer []byte) { sessionId := binary.BigEndian.Uint32(buffer[1:5]) - moduleName := "MASTER:" + strconv.FormatInt(int64(sessionId), 10) + moduleName := "QR2:" + strconv.FormatInt(int64(sessionId), 10) logging.Notice(moduleName, "Received heartbeat from", aurora.Cyan(addr).String()) values := strings.Split(string(buffer[5:]), "\u0000") diff --git a/master/main.go b/qr2/main.go similarity index 93% rename from master/main.go rename to qr2/main.go index 6e15e9d..bcfb4cd 100644 --- a/master/main.go +++ b/qr2/main.go @@ -1,4 +1,4 @@ -package master +package qr2 import ( "encoding/binary" @@ -46,7 +46,7 @@ func StartServer() { // Close the listener when the application closes. defer conn.Close() - logging.Notice("MASTER", "Listening on", address) + logging.Notice("QR2", "Listening on", address) for { buf := make([]byte, 1024) @@ -61,13 +61,13 @@ func StartServer() { func handleConnection(conn net.PacketConn, addr net.Addr, buffer []byte) { if buffer[0] == AvailableRequest { - logging.Notice("MASTER", "Command:", aurora.Yellow("AVAILABLE").String()) + logging.Notice("QR2", "Command:", aurora.Yellow("AVAILABLE").String()) conn.WriteTo(createResponseHeader(AvailableRequest, 0), addr) return } sessionId := binary.BigEndian.Uint32(buffer[1:5]) - moduleName := "MASTER:" + strconv.FormatInt(int64(sessionId), 10) + moduleName := "QR2:" + strconv.FormatInt(int64(sessionId), 10) switch buffer[0] { case QueryRequest: diff --git a/master/session.go b/qr2/session.go similarity index 93% rename from master/session.go rename to qr2/session.go index cc153e9..0d224a0 100644 --- a/master/session.go +++ b/qr2/session.go @@ -1,4 +1,4 @@ -package master +package qr2 import ( "encoding/binary" @@ -38,7 +38,7 @@ func setSessionData(sessionId uint32, payload map[string]string) Session { session, exists := sessions[sessionId] if !exists { - logging.Notice("MASTER", "Creating session", aurora.Cyan(sessionId).String()) + logging.Notice("QR2", "Creating session", aurora.Cyan(sessionId).String()) data := Session{ SessionID: sessionId, Challenge: "", @@ -123,12 +123,12 @@ func SendClientMessage(destIP string, message []byte) { panic(err) } - logging.Notice("MASTER", "Sending message...") + logging.Notice("QR2", "Sending message...") masterConn.WriteTo(payload, destIPAddr) return } } mutex.Unlock() - logging.Notice("MASTER", "Could not find destination server") + logging.Notice("QR2", "Could not find destination server") }