diff --git a/common/game_list.go b/common/game_list.go index 8ff5ed6..1a5f19e 100644 --- a/common/game_list.go +++ b/common/game_list.go @@ -26,6 +26,8 @@ var ( ) func GetGameInfoByID(gameId int) *GameInfo { + ReadGameList() + mutex.Lock() defer mutex.Unlock() @@ -37,6 +39,8 @@ func GetGameInfoByID(gameId int) *GameInfo { } func GetGameInfoByName(name string) *GameInfo { + ReadGameList() + mutex.Lock() defer mutex.Unlock() @@ -47,6 +51,24 @@ func GetGameInfoByName(name string) *GameInfo { return nil } +func GetGameID(name string) int { + info := GetGameInfoByName(name) + if info != nil { + return info.GameID + } + + return -1 +} + +func GetGameIDOrPanic(name string) int { + id := GetGameID(name) + if id == -1 { + panic("Game not found: " + name) + } + + return id +} + func ReadGameList() { mutex.Lock() defer mutex.Unlock() diff --git a/database/mario_kart_wii.go b/database/mario_kart_wii.go index 76ddda4..8d7017c 100644 --- a/database/mario_kart_wii.go +++ b/database/mario_kart_wii.go @@ -30,10 +30,10 @@ const ( "ORDER BY score DESC " + "LIMIT 1" insertGhostFileStatement = "" + - "INSERT INTO mario_kart_wii_sake (regionid, courseid, score, pid, playerinfo, ghost) " + - "VALUES ($1, $2, $3, $4, $5, $6) " + + "INSERT INTO mario_kart_wii_sake (regionid, courseid, score, pid, playerinfo, ghost, upload_time) " + + "VALUES ($1, $2, $3, $4, $5, $6, CURRENT_TIMESTAMP) " + "ON CONFLICT (courseid, pid) DO UPDATE " + - "SET regionid = EXCLUDED.regionid, score = EXCLUDED.score, playerinfo = EXCLUDED.playerinfo, ghost = EXCLUDED.ghost" + "SET regionid = EXCLUDED.regionid, score = EXCLUDED.score, playerinfo = EXCLUDED.playerinfo, ghost = EXCLUDED.ghost, upload_time = CURRENT_TIMESTAMP" ) func GetMarioKartWiiTopTenRankings(pool *pgxpool.Pool, ctx context.Context, regionId common.MarioKartWiiLeaderboardRegionId, diff --git a/database/schema.go b/database/schema.go index fc6c92a..d9c7713 100644 --- a/database/schema.go +++ b/database/schema.go @@ -19,22 +19,14 @@ ALTER TABLE ONLY public.users ADD IF NOT EXISTS ban_reason_hidden character varying, ADD IF NOT EXISTS ban_moderator character varying, ADD IF NOT EXISTS ban_tos boolean, - ADD IF NOT EXISTS open_host boolean DEFAULT false + ADD IF NOT EXISTS open_host boolean DEFAULT false; + `) -CREATE TABLE IF NOT EXISTS public.mario_kart_wii_sake ( - regionid smallint NOT NULL CHECK (regionid >= 1 AND regionid <= 7), - courseid smallint NOT NULL CHECK (courseid >= 0 AND courseid <= 32767), - score integer NOT NULL CHECK (score > 0), - pid integer NOT NULL CHECK (pid > 0), - playerinfo varchar(108) NOT NULL CHECK (LENGTH(playerinfo) = 108), - ghost bytea CHECK (ghost IS NULL OR (OCTET_LENGTH(ghost) BETWEEN 148 AND 10240)), + pool.Exec(ctx, ` + + ALTER TABLE ONLY public.mario_kart_wii_sake + ADD IF NOT EXISTS upload_time timestamp without time zone; - CONSTRAINT one_time_per_course_constraint UNIQUE (courseid, pid) -); - - -ALTER TABLE public.mario_kart_wii_sake OWNER TO wiilink; - -`) + `) } diff --git a/race/nintendo_racing_service.go b/race/nintendo_racing_service.go index 79152ad..b7c24cc 100644 --- a/race/nintendo_racing_service.go +++ b/race/nintendo_racing_service.go @@ -72,6 +72,8 @@ const ( xmlNamespace = "http://gamespy.net/RaceService/" ) +var MarioKartWiiGameID = common.GetGameIDOrPanic("mariokartwii") // 1687 + func handleNintendoRacingServiceRequest(moduleName string, responseWriter http.ResponseWriter, request *http.Request) { soapActionHeader := request.Header.Get("SOAPAction") if soapActionHeader == "" { @@ -102,6 +104,8 @@ func handleNintendoRacingServiceRequest(moduleName string, responseWriter http.R switch soapAction { case "GetTopTenRankings": handleGetTopTenRankingsRequest(moduleName, responseWriter, requestBody) + + // TODO SubmitScores default: logging.Info(moduleName, "Unhandled SOAPAction:", aurora.Cyan(soapAction)) } @@ -119,8 +123,8 @@ func handleGetTopTenRankingsRequest(moduleName string, responseWriter http.Respo requestData := requestXML.Body.Data gameId := requestData.GameId - if gameId != common.GameSpyGameIdMarioKartWii { - logging.Error(moduleName, "Wrong GameSpy game id") + if gameId != MarioKartWiiGameID { + logging.Error(moduleName, "Wrong GameSpy game ID:", aurora.Cyan(gameId)) writeErrorResponse(raceServiceResultInvalidParameters, responseWriter) return } @@ -129,19 +133,19 @@ func handleGetTopTenRankingsRequest(moduleName string, responseWriter http.Respo courseId := requestData.CourseId if !regionId.IsValid() { - logging.Error(moduleName, "Invalid region id") + logging.Error(moduleName, "Invalid region ID:", aurora.Cyan(regionId)) writeErrorResponse(raceServiceResultInvalidParameters, responseWriter) return } if courseId < common.MarioCircuit || courseId > 32767 { - logging.Error(moduleName, "Invalid course id") + logging.Error(moduleName, "Invalid course ID:", aurora.Cyan(courseId)) writeErrorResponse(raceServiceResultInvalidParameters, responseWriter) return } topTenRankings, err := database.GetMarioKartWiiTopTenRankings(pool, ctx, regionId, courseId) if err != nil { - logging.Error(moduleName, "Failed to get the Top 10 rankings") + logging.Error(moduleName, "Failed to get the Top 10 rankings:", err) writeErrorResponse(raceServiceResultDatabaseError, responseWriter) return } diff --git a/common/gamyspy.go b/sake/constants.go similarity index 71% rename from common/gamyspy.go rename to sake/constants.go index d4ac5dc..9a47fea 100644 --- a/common/gamyspy.go +++ b/sake/constants.go @@ -1,12 +1,9 @@ -package common +package sake + +const GameSpyMultipartBoundary = "Qr4G823s23d---<<><><<<>--7d118e0536" type SakeFileResult int -const ( - GameSpyMultipartBoundary = "Qr4G823s23d---<<><><<<>--7d118e0536" - GameSpyGameIdMarioKartWii = 1687 -) - // https://documentation.help/GameSpy-SDK/SAKEFileResult.html const ( SakeFileResultHeader = "Sake-File-Result" diff --git a/sake/mario_kart_wii.go b/sake/mario_kart_wii.go index 3d18551..0d52354 100644 --- a/sake/mario_kart_wii.go +++ b/sake/mario_kart_wii.go @@ -12,6 +12,8 @@ import ( "wwfc/common" "wwfc/database" "wwfc/logging" + + "github.com/logrusorgru/aurora/v3" ) type playerInfo struct { @@ -47,53 +49,53 @@ func handleMarioKartWiiGhostDownloadRequest(moduleName string, responseWriter ht regionIdInt, err := strconv.Atoi(regionIdString) if err != nil { - logging.Error(moduleName, "Invalid region id") - responseWriter.Header().Set(common.SakeFileResultHeader, strconv.Itoa(common.SakeFileResultMissingParameter)) + logging.Error(moduleName, "Invalid region ID:", aurora.Cyan(regionIdString)) + responseWriter.Header().Set(SakeFileResultHeader, strconv.Itoa(SakeFileResultMissingParameter)) return } if common.MarioKartWiiLeaderboardRegionId(regionIdInt) != common.Worldwide { - logging.Error(moduleName, "Invalid region id") - responseWriter.Header().Set(common.SakeFileResultHeader, strconv.Itoa(common.SakeFileResultMissingParameter)) + logging.Error(moduleName, "Invalid region ID:", aurora.Cyan(regionIdString)) + responseWriter.Header().Set(SakeFileResultHeader, strconv.Itoa(SakeFileResultMissingParameter)) return } courseIdInt, err := strconv.Atoi(courseIdString) if err != nil { - logging.Error(moduleName, "Invalid course id") - responseWriter.Header().Set(common.SakeFileResultHeader, strconv.Itoa(common.SakeFileResultMissingParameter)) + logging.Error(moduleName, "Invalid course ID:", aurora.Cyan(courseIdString)) + responseWriter.Header().Set(SakeFileResultHeader, strconv.Itoa(SakeFileResultMissingParameter)) return } courseId := common.MarioKartWiiCourseId(courseIdInt) if !courseId.IsValid() { - logging.Error(moduleName, "Invalid course id") - responseWriter.Header().Set(common.SakeFileResultHeader, strconv.Itoa(common.SakeFileResultMissingParameter)) + logging.Error(moduleName, "Invalid course ID:", aurora.Cyan(courseIdString)) + responseWriter.Header().Set(SakeFileResultHeader, strconv.Itoa(SakeFileResultMissingParameter)) return } pid, err := strconv.Atoi(pidString) if err != nil || pid <= 0 { - logging.Error(moduleName, "Invalid pid") - responseWriter.Header().Set(common.SakeFileResultHeader, strconv.Itoa(common.SakeFileResultMissingParameter)) + logging.Error(moduleName, "Invalid profile ID:", aurora.Cyan(pidString)) + responseWriter.Header().Set(SakeFileResultHeader, strconv.Itoa(SakeFileResultMissingParameter)) return } time, err := strconv.Atoi(timeString) if err != nil || time <= 0 { - logging.Error(moduleName, "Invalid time") - responseWriter.Header().Set(common.SakeFileResultHeader, strconv.Itoa(common.SakeFileResultMissingParameter)) + logging.Error(moduleName, "Invalid time:", aurora.Cyan(timeString)) + responseWriter.Header().Set(SakeFileResultHeader, strconv.Itoa(SakeFileResultMissingParameter)) return } ghost, err := database.GetMarioKartWiiGhostFile(pool, ctx, courseId, time, pid) if err != nil { - logging.Error(moduleName, "Failed to get a ghost file from the database") - responseWriter.Header().Set(common.SakeFileResultHeader, strconv.Itoa(common.SakeFileResultServerError)) + logging.Error(moduleName, "Failed to get a ghost file from the database:", err) + responseWriter.Header().Set(SakeFileResultHeader, strconv.Itoa(SakeFileResultServerError)) return } responseBody := append(downloadedGhostFileHeader(), ghost...) - responseWriter.Header().Set(common.SakeFileResultHeader, strconv.Itoa(common.SakeFileResultSuccess)) + responseWriter.Header().Set(SakeFileResultHeader, strconv.Itoa(SakeFileResultSuccess)) responseWriter.Header().Set("Content-Length", strconv.Itoa(len(responseBody))) responseWriter.Write(responseBody) } @@ -117,47 +119,47 @@ func handleMarioKartWiiGhostUploadRequest(moduleName string, responseWriter http regionIdInt, err := strconv.Atoi(regionIdString) if err != nil { - logging.Error(moduleName, "Invalid region id") - responseWriter.Header().Set(common.SakeFileResultHeader, strconv.Itoa(common.SakeFileResultMissingParameter)) + logging.Error(moduleName, "Invalid region ID:", aurora.Cyan(regionIdString)) + responseWriter.Header().Set(SakeFileResultHeader, strconv.Itoa(SakeFileResultMissingParameter)) return } regionId := common.MarioKartWiiLeaderboardRegionId(regionIdInt) if !regionId.IsValid() || regionId == common.Worldwide { - logging.Error(moduleName, "Invalid region id") - responseWriter.Header().Set(common.SakeFileResultHeader, strconv.Itoa(common.SakeFileResultMissingParameter)) + logging.Error(moduleName, "Invalid region ID:", aurora.Cyan(regionIdString)) + responseWriter.Header().Set(SakeFileResultHeader, strconv.Itoa(SakeFileResultMissingParameter)) return } courseIdInt, err := strconv.Atoi(courseIdString) if err != nil { - logging.Error(moduleName, "Invalid course id") - responseWriter.Header().Set(common.SakeFileResultHeader, strconv.Itoa(common.SakeFileResultMissingParameter)) + logging.Error(moduleName, "Invalid course ID:", aurora.Cyan(courseIdString)) + responseWriter.Header().Set(SakeFileResultHeader, strconv.Itoa(SakeFileResultMissingParameter)) return } courseId := common.MarioKartWiiCourseId(courseIdInt) if courseId < common.MarioCircuit || courseId > 32767 { - logging.Error(moduleName, "Invalid course id") - responseWriter.Header().Set(common.SakeFileResultHeader, strconv.Itoa(common.SakeFileResultMissingParameter)) + logging.Error(moduleName, "Invalid course ID:", aurora.Cyan(courseIdString)) + responseWriter.Header().Set(SakeFileResultHeader, strconv.Itoa(SakeFileResultMissingParameter)) return } score, err := strconv.Atoi(scoreString) if err != nil || score <= 0 { - logging.Error(moduleName, "Invalid score") - responseWriter.Header().Set(common.SakeFileResultHeader, strconv.Itoa(common.SakeFileResultMissingParameter)) + logging.Error(moduleName, "Invalid score:", aurora.Cyan(scoreString)) + responseWriter.Header().Set(SakeFileResultHeader, strconv.Itoa(SakeFileResultMissingParameter)) return } pid, err := strconv.Atoi(pidString) if err != nil || pid <= 0 { - logging.Error(moduleName, "Invalid pid") - responseWriter.Header().Set(common.SakeFileResultHeader, strconv.Itoa(common.SakeFileResultMissingParameter)) + logging.Error(moduleName, "Invalid profile ID:", aurora.Cyan(pidString)) + responseWriter.Header().Set(SakeFileResultHeader, strconv.Itoa(SakeFileResultMissingParameter)) return } if !isPlayerInfoValid(playerInfo) { - logging.Error(moduleName, "Invalid player info") - responseWriter.Header().Set(common.SakeFileResultHeader, strconv.Itoa(common.SakeFileResultMissingParameter)) + logging.Error(moduleName, "Invalid player info:", aurora.Cyan(playerInfo)) + responseWriter.Header().Set(SakeFileResultHeader, strconv.Itoa(SakeFileResultMissingParameter)) return } // Mario Kart Wii expects player information to be in this form @@ -167,7 +169,7 @@ func handleMarioKartWiiGhostUploadRequest(moduleName string, responseWriter http // we need to surround it with double quotation marks. contentType := request.Header.Get("Content-Type") boundary := getMultipartBoundary(contentType) - if boundary == common.GameSpyMultipartBoundary { + if boundary == GameSpyMultipartBoundary { quotedBoundary := fmt.Sprintf("%q", boundary) contentType := strings.Replace(contentType, boundary, quotedBoundary, 1) request.Header.Set("Content-Type", contentType) @@ -175,36 +177,36 @@ func handleMarioKartWiiGhostUploadRequest(moduleName string, responseWriter http err = request.ParseMultipartForm(rkgdFileMaxSize) if err != nil { - logging.Error(moduleName, "Failed to parse the multipart form") - responseWriter.Header().Set(common.SakeFileResultHeader, strconv.Itoa(common.SakeFileResultFileNotFound)) + logging.Error(moduleName, "Failed to parse the multipart form:", err) + responseWriter.Header().Set(SakeFileResultHeader, strconv.Itoa(SakeFileResultFileNotFound)) return } file, fileHeader, err := request.FormFile(rkgdFileName) if err != nil { - logging.Error(moduleName, "Failed to find the ghost file") - responseWriter.Header().Set(common.SakeFileResultHeader, strconv.Itoa(common.SakeFileResultFileNotFound)) + logging.Error(moduleName, "Failed to find the ghost file:", err) + responseWriter.Header().Set(SakeFileResultHeader, strconv.Itoa(SakeFileResultFileNotFound)) return } defer file.Close() if fileHeader.Size < rkgdFileMinSize || fileHeader.Size > rkgdFileMaxSize { - logging.Error(moduleName, "The size of the ghost file is invalid") - responseWriter.Header().Set(common.SakeFileResultHeader, strconv.Itoa(common.SakeFileResultFileTooLarge)) + logging.Error(moduleName, "The size of the ghost file is invalid:", aurora.Cyan(fileHeader.Size)) + responseWriter.Header().Set(SakeFileResultHeader, strconv.Itoa(SakeFileResultFileTooLarge)) return } ghostFile := make([]byte, fileHeader.Size) _, err = io.ReadFull(file, ghostFile) if err != nil { - logging.Error(moduleName, "Failed to read contents of the ghost file") - responseWriter.Header().Set(common.SakeFileResultHeader, strconv.Itoa(common.SakeFileResultFileTooLarge)) + logging.Error(moduleName, "Failed to read contents of the ghost file:", err) + responseWriter.Header().Set(SakeFileResultHeader, strconv.Itoa(SakeFileResultFileTooLarge)) return } if !isRKGDFileValid(ghostFile) { logging.Error(moduleName, "Received an invalid ghost file") - responseWriter.Header().Set(common.SakeFileResultHeader, strconv.Itoa(common.SakeFileResultFileTooLarge)) + responseWriter.Header().Set(SakeFileResultHeader, strconv.Itoa(SakeFileResultFileTooLarge)) return } @@ -214,12 +216,12 @@ func handleMarioKartWiiGhostUploadRequest(moduleName string, responseWriter http err = database.InsertMarioKartWiiGhostFile(pool, ctx, regionId, courseId, score, pid, playerInfo, ghostFile) if err != nil { - logging.Error(moduleName, "Failed to insert the ghost file into the database") - responseWriter.Header().Set(common.SakeFileResultHeader, strconv.Itoa(common.SakeFileResultServerError)) + logging.Error(moduleName, "Failed to insert the ghost file into the database:", err) + responseWriter.Header().Set(SakeFileResultHeader, strconv.Itoa(SakeFileResultServerError)) return } - responseWriter.Header().Set(common.SakeFileResultHeader, strconv.Itoa(common.SakeFileResultSuccess)) + responseWriter.Header().Set(SakeFileResultHeader, strconv.Itoa(SakeFileResultSuccess)) } func downloadedGhostFileHeader() []byte { diff --git a/sake/storage.go b/sake/storage.go index c05aa3c..2e7d86f 100644 --- a/sake/storage.go +++ b/sake/storage.go @@ -120,11 +120,11 @@ type StorageSearchForRecordsResponse struct { } var fileDownloadHandlers = map[int]func(string, http.ResponseWriter, *http.Request){ - common.GameSpyGameIdMarioKartWii: handleMarioKartWiiFileDownloadRequest, + common.GetGameIDOrPanic("mariokartwii"): handleMarioKartWiiFileDownloadRequest, } var fileUploadHandlers = map[int]func(string, http.ResponseWriter, *http.Request){ - common.GameSpyGameIdMarioKartWii: handleMarioKartWiiFileUploadRequest, + common.GetGameIDOrPanic("mariokartwii"): handleMarioKartWiiFileUploadRequest, } func handleStorageRequest(moduleName string, w http.ResponseWriter, r *http.Request) {