diff --git a/database/mario_kart_wii.go b/database/mario_kart_wii.go index 8d7017c..9563c5a 100644 --- a/database/mario_kart_wii.go +++ b/database/mario_kart_wii.go @@ -21,6 +21,18 @@ const ( "AND courseid = $2 " + "ORDER BY score ASC " + "LIMIT 10" + getStoredGhostDataQuery = "" + + "SELECT pid, id " + + "FROM mario_kart_wii_sake " + + "WHERE ($1 = 0 OR regionid = $1) " + + "AND courseid = $2 " + + "ORDER BY score ASC " + + "LIMIT 1" + getFileQuery = "" + + "SELECT ghost " + + "FROM mario_kart_wii_sake " + + "WHERE id = $1 " + + "LIMIT 1" getGhostFileQuery = "" + "SELECT ghost " + "FROM mario_kart_wii_sake " + @@ -54,21 +66,43 @@ func GetMarioKartWiiTopTenRankings(pool *pgxpool.Pool, ctx context.Context, regi topTenRankings = append(topTenRankings, topTenRanking) } - err = rows.Err() - if err != nil { + if err = rows.Err(); err != nil { return nil, err } return topTenRankings, nil } +func GetMarioKartWiiStoredGhostData(pool *pgxpool.Pool, ctx context.Context, regionId common.MarioKartWiiLeaderboardRegionId, + courseId common.MarioKartWiiCourseId) (int, int, error) { + row := pool.QueryRow(ctx, getStoredGhostDataQuery, regionId, courseId) + + var pid int + var fileId int + if err := row.Scan(&pid, &fileId); err != nil { + return 0, 0, err + } + + return pid, fileId, nil +} + +func GetMarioKartWiiFile(pool *pgxpool.Pool, ctx context.Context, fileId int) ([]byte, error) { + row := pool.QueryRow(ctx, getFileQuery, fileId) + + var file []byte + if err := row.Scan(&file); err != nil { + return nil, err + } + + return file, nil +} + func GetMarioKartWiiGhostFile(pool *pgxpool.Pool, ctx context.Context, courseId common.MarioKartWiiCourseId, time int, pid int) ([]byte, error) { row := pool.QueryRow(ctx, getGhostFileQuery, courseId, time, pid) var ghost []byte - err := row.Scan(&ghost) - if err != nil { + if err := row.Scan(&ghost); err != nil { return nil, err } diff --git a/database/schema.go b/database/schema.go index d9c7713..ddc9088 100644 --- a/database/schema.go +++ b/database/schema.go @@ -9,23 +9,24 @@ import ( func UpdateTables(pool *pgxpool.Pool, ctx context.Context) { pool.Exec(ctx, ` -ALTER TABLE ONLY public.users - ADD IF NOT EXISTS last_ip_address character varying DEFAULT ''::character varying, - ADD IF NOT EXISTS last_ingamesn character varying DEFAULT ''::character varying, - ADD IF NOT EXISTS has_ban boolean DEFAULT false, - ADD IF NOT EXISTS ban_issued timestamp without time zone, - ADD IF NOT EXISTS ban_expires timestamp without time zone, - ADD IF NOT EXISTS ban_reason character varying, - 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; + ALTER TABLE ONLY public.users + ADD IF NOT EXISTS last_ip_address character varying DEFAULT ''::character varying, + ADD IF NOT EXISTS last_ingamesn character varying DEFAULT ''::character varying, + ADD IF NOT EXISTS has_ban boolean DEFAULT false, + ADD IF NOT EXISTS ban_issued timestamp without time zone, + ADD IF NOT EXISTS ban_expires timestamp without time zone, + ADD IF NOT EXISTS ban_reason character varying, + 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; `) pool.Exec(ctx, ` ALTER TABLE ONLY public.mario_kart_wii_sake + ADD IF NOT EXISTS id serial PRIMARY KEY, ADD IF NOT EXISTS upload_time timestamp without time zone; `) diff --git a/race/nintendo_racing_service.go b/race/nintendo_racing_service.go index b7c24cc..3878ee6 100644 --- a/race/nintendo_racing_service.go +++ b/race/nintendo_racing_service.go @@ -72,7 +72,7 @@ const ( xmlNamespace = "http://gamespy.net/RaceService/" ) -var MarioKartWiiGameID = common.GetGameIDOrPanic("mariokartwii") // 1687 +var marioKartWiiGameID = common.GetGameIDOrPanic("mariokartwii") // 1687 func handleNintendoRacingServiceRequest(moduleName string, responseWriter http.ResponseWriter, request *http.Request) { soapActionHeader := request.Header.Get("SOAPAction") @@ -123,7 +123,7 @@ func handleGetTopTenRankingsRequest(moduleName string, responseWriter http.Respo requestData := requestXML.Body.Data gameId := requestData.GameId - if gameId != MarioKartWiiGameID { + if gameId != marioKartWiiGameID { logging.Error(moduleName, "Wrong GameSpy game ID:", aurora.Cyan(gameId)) writeErrorResponse(raceServiceResultInvalidParameters, responseWriter) return diff --git a/sake/mario_kart_wii.go b/sake/mario_kart_wii.go index 0d52354..f7cb607 100644 --- a/sake/mario_kart_wii.go +++ b/sake/mario_kart_wii.go @@ -37,6 +37,36 @@ func handleMarioKartWiiFileDownloadRequest(moduleName string, responseWriter htt handleMarioKartWiiGhostDownloadRequest(moduleName, responseWriter, request) return } + + query := request.URL.Query() + + fileIdString := query.Get("fileid") + pidString := query.Get("pid") + + fileId, err := strconv.Atoi(fileIdString) + if err != nil || fileId <= 0 { + logging.Error(moduleName, "Invalid file ID:", aurora.Cyan(fileIdString)) + responseWriter.Header().Set(SakeFileResultHeader, strconv.Itoa(SakeFileResultMissingParameter)) + return + } + + pid, err := strconv.Atoi(pidString) + if err != nil || pid <= 0 { + logging.Error(moduleName, "Invalid profile ID:", aurora.Cyan(pidString)) + responseWriter.Header().Set(SakeFileResultHeader, strconv.Itoa(SakeFileResultMissingParameter)) + return + } + + file, err := database.GetMarioKartWiiFile(pool, ctx, fileId) + if err != nil { + logging.Error(moduleName, "Failed to get the file from the database:", err) + responseWriter.Header().Set(SakeFileResultHeader, strconv.Itoa(SakeFileResultServerError)) + return + } + + responseWriter.Header().Set(SakeFileResultHeader, strconv.Itoa(SakeFileResultSuccess)) + responseWriter.Header().Set("Content-Length", strconv.Itoa(len(file))) + responseWriter.Write(file) } func handleMarioKartWiiGhostDownloadRequest(moduleName string, responseWriter http.ResponseWriter, request *http.Request) { diff --git a/sake/storage.go b/sake/storage.go index 2e7d86f..f7ddfd6 100644 --- a/sake/storage.go +++ b/sake/storage.go @@ -382,6 +382,71 @@ func searchForRecords(moduleName string, gameInfo common.GameInfo, request Stora "info": binaryDataValueBase64(database.GetMKWFriendInfo(pool, ctx, uint32(ownerId))), }, } + + case "mariokartwii/StoredGhostData": + if request.Sort != "time" { + logging.Error(moduleName, "Invalid sort string:", aurora.Cyan(request.Sort)) + return &errorResponse + } + + if request.Offset != 0 { + logging.Error(moduleName, "Invalid offset value:", aurora.Cyan(request.Offset)) + return &errorResponse + } + + if request.Max != 1 { + logging.Error(moduleName, "Invalid number of records to return:", aurora.Cyan(request.Max)) + return &errorResponse + } + + if request.Surrounding != 0 { + logging.Error(moduleName, "Invalid number of surrounding records to return:", aurora.Cyan(request.Surrounding)) + return &errorResponse + } + + if request.OwnerIDs != "" { + logging.Error(moduleName, "Invalid owner id array:", aurora.Cyan(request.OwnerIDs)) + return &errorResponse + } + + if request.CacheFlag != 0 { + logging.Error(moduleName, "Invalid cache value:", aurora.Cyan(request.CacheFlag)) + return &errorResponse + } + + match := regexp.MustCompile(`^course = ([1-9]\d?|0) and gameid = 1687(?: and region = ([1-7]))?$`).FindStringSubmatch(request.Filter) + if match == nil { + logging.Error(moduleName, "Invalid filter string:", aurora.Cyan(request.Filter)) + return &errorResponse + } + + courseIdInt, _ := strconv.Atoi(match[1]) + courseId := common.MarioKartWiiCourseId(courseIdInt) + if !courseId.IsValid() { + logging.Error(moduleName, "Invalid course ID:", aurora.Cyan(courseIdInt)) + return &errorResponse + } + + var regionId common.MarioKartWiiLeaderboardRegionId + if regionIdExists := match[2] != ""; regionIdExists { + regionIdInt, _ := strconv.Atoi(match[2]) + regionId = common.MarioKartWiiLeaderboardRegionId(regionIdInt) + } else { + regionId = common.Worldwide + } + + pid, fileId, err := database.GetMarioKartWiiStoredGhostData(pool, ctx, regionId, courseId) + if err != nil { + logging.Error(moduleName, "Failed to get the stored ghost data from the database:", err) + return &errorResponse + } + + values = []map[string]StorageValue{ + { + "profile": intValue(int32(pid)), + "fileid": intValue(int32(fileId)), + }, + } } // Sort the values now diff --git a/schema.sql b/schema.sql index 7364939..56870fe 100644 --- a/schema.sql +++ b/schema.sql @@ -68,6 +68,11 @@ CREATE TABLE IF NOT EXISTS public.mario_kart_wii_sake ( ); +ALTER TABLE ONLY public.mario_kart_wii_sake + ADD IF NOT EXISTS id serial PRIMARY KEY, + ADD IF NOT EXISTS upload_time timestamp without time zone; + + ALTER TABLE public.mario_kart_wii_sake OWNER TO wiilink; --