Skip to content

Commit

Permalink
Database: Add upload timestamp to MKW ghost data
Browse files Browse the repository at this point in the history
  • Loading branch information
mkwcat committed Sep 7, 2024
1 parent 240fdea commit 46f269d
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 73 deletions.
22 changes: 22 additions & 0 deletions common/game_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ var (
)

func GetGameInfoByID(gameId int) *GameInfo {
ReadGameList()

mutex.Lock()
defer mutex.Unlock()

Expand All @@ -37,6 +39,8 @@ func GetGameInfoByID(gameId int) *GameInfo {
}

func GetGameInfoByName(name string) *GameInfo {
ReadGameList()

mutex.Lock()
defer mutex.Unlock()

Expand All @@ -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()
Expand Down
6 changes: 3 additions & 3 deletions database/mario_kart_wii.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
22 changes: 7 additions & 15 deletions database/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
CONSTRAINT one_time_per_course_constraint UNIQUE (courseid, pid)
);
ALTER TABLE public.mario_kart_wii_sake OWNER TO wiilink;
pool.Exec(ctx, `
ALTER TABLE ONLY public.mario_kart_wii_sake
ADD IF NOT EXISTS upload_time timestamp without time zone;
`)
`)
}
14 changes: 9 additions & 5 deletions race/nintendo_racing_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 == "" {
Expand Down Expand Up @@ -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))
}
Expand All @@ -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
}
Expand All @@ -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
}
Expand Down
9 changes: 3 additions & 6 deletions common/gamyspy.go → sake/constants.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
package common
package sake

type SakeFileResult int
const GameSpyMultipartBoundary = "Qr4G823s23d---<<><><<<>--7d118e0536"

const (
GameSpyMultipartBoundary = "Qr4G823s23d---<<><><<<>--7d118e0536"
GameSpyGameIdMarioKartWii = 1687
)
type SakeFileResult int

// https://documentation.help/GameSpy-SDK/SAKEFileResult.html
const (
Expand Down
86 changes: 44 additions & 42 deletions sake/mario_kart_wii.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
"wwfc/common"
"wwfc/database"
"wwfc/logging"

"github.com/logrusorgru/aurora/v3"
)

type playerInfo struct {
Expand Down Expand Up @@ -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)
}
Expand All @@ -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
Expand All @@ -167,44 +169,44 @@ 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)
}

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
}

Expand All @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions sake/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down

0 comments on commit 46f269d

Please sign in to comment.