SAKE: Support file download requests from Mario Kart Wii

Helps address issue #43.
This commit is contained in:
MikeIsAStar 2024-09-07 11:00:00 -04:00
parent 3a40000040
commit 51122570b7
6 changed files with 143 additions and 15 deletions

View File

@ -21,7 +21,15 @@ const (
"AND courseid = $2 " +
"ORDER BY score ASC " +
"LIMIT 10"
uploadGhostFileStatement = "" +
getGhostFileQuery = "" +
"SELECT ghost " +
"FROM mario_kart_wii_sake " +
"WHERE courseid = $1 " +
"AND score < $2 " +
"AND pid <> $3 " +
"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) " +
"ON CONFLICT (courseid, pid) DO UPDATE " +
@ -54,9 +62,22 @@ func GetMarioKartWiiTopTenRankings(pool *pgxpool.Pool, ctx context.Context, regi
return topTenRankings, nil
}
func UploadMarioKartWiiGhostFile(pool *pgxpool.Pool, ctx context.Context, regionId common.MarioKartWiiLeaderboardRegionId,
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 {
return nil, err
}
return ghost, nil
}
func InsertMarioKartWiiGhostFile(pool *pgxpool.Pool, ctx context.Context, regionId common.MarioKartWiiLeaderboardRegionId,
courseId common.MarioKartWiiCourseId, score int, pid int, playerInfo string, ghost []byte) error {
_, err := pool.Exec(ctx, uploadGhostFileStatement, regionId, courseId, score, pid, playerInfo, ghost)
_, err := pool.Exec(ctx, insertGhostFileStatement, regionId, courseId, score, pid, playerInfo, ghost)
return err
}

View File

@ -4,7 +4,7 @@ import (
"context"
"fmt"
"net/http"
"path"
"strings"
"wwfc/common"
"wwfc/logging"
@ -42,8 +42,8 @@ func Shutdown() {
func HandleRequest(responseWriter http.ResponseWriter, request *http.Request) {
logging.Info("RACE", aurora.Yellow(request.Method), aurora.Cyan(request.URL), "via", aurora.Cyan(request.Host), "from", aurora.BrightCyan(request.RemoteAddr))
switch path.Base(request.URL.Path) {
case "NintendoRacingService.asmx":
switch {
case strings.HasSuffix(request.URL.Path, "NintendoRacingService.asmx"):
moduleName := "RACE:RacingService:" + request.RemoteAddr
handleNintendoRacingServiceRequest(moduleName, responseWriter, request)
}

View File

@ -101,7 +101,6 @@ func handleNintendoRacingServiceRequest(moduleName string, responseWriter http.R
soapAction := soapActionHeader[slashIndex+1 : slashIndex+1+quotationMarkIndex]
switch soapAction {
case "GetTopTenRankings":
logging.Info(moduleName, "Received a Top 10 rankings request")
handleGetTopTenRankingsRequest(moduleName, responseWriter, requestBody)
default:
logging.Info(moduleName, "Unhandled SOAPAction:", aurora.Cyan(soapAction))

View File

@ -47,8 +47,11 @@ func HandleRequest(w http.ResponseWriter, r *http.Request) {
case urlPath == "/SakeStorageServer/StorageServer.asmx":
moduleName := "SAKE:Storage:" + r.RemoteAddr
handleStorageRequest(moduleName, w, r)
case strings.Contains(urlPath, "upload.aspx"):
case strings.HasSuffix(urlPath, "download.aspx"):
moduleName := "SAKE:File:" + r.RemoteAddr
handleFileUploadRequest(moduleName, w, r)
handleFileRequest(moduleName, w, r, FileRequestDownload)
case strings.HasSuffix(urlPath, "upload.aspx"):
moduleName := "SAKE:File:" + r.RemoteAddr
handleFileRequest(moduleName, w, r, FileRequestUpload)
}
}

View File

@ -30,7 +30,82 @@ const (
rkgdFileName = "ghost.bin"
)
func handleMarioKartWiiFileDownloadRequest(moduleName string, responseWriter http.ResponseWriter, request *http.Request) {
if strings.HasSuffix(request.URL.Path, "ghostdownload.aspx") {
handleMarioKartWiiGhostDownloadRequest(moduleName, responseWriter, request)
return
}
}
func handleMarioKartWiiGhostDownloadRequest(moduleName string, responseWriter http.ResponseWriter, request *http.Request) {
query := request.URL.Query()
regionIdString := query.Get("region")
pidString := query.Get("p0")
courseIdString := query.Get("c0")
timeString := query.Get("t0")
regionIdInt, err := strconv.Atoi(regionIdString)
if err != nil {
logging.Error(moduleName, "Invalid region id")
responseWriter.Header().Set(common.SakeFileResultHeader, strconv.Itoa(common.SakeFileResultMissingParameter))
return
}
if common.MarioKartWiiLeaderboardRegionId(regionIdInt) != common.Worldwide {
logging.Error(moduleName, "Invalid region id")
responseWriter.Header().Set(common.SakeFileResultHeader, strconv.Itoa(common.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))
return
}
courseId := common.MarioKartWiiCourseId(courseIdInt)
if !courseId.IsValid() {
logging.Error(moduleName, "Invalid course id")
responseWriter.Header().Set(common.SakeFileResultHeader, strconv.Itoa(common.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))
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))
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))
return
}
responseBody := append(downloadedGhostFileHeader(), ghost...)
responseWriter.Header().Set(common.SakeFileResultHeader, strconv.Itoa(common.SakeFileResultSuccess))
responseWriter.Header().Set("Content-Length", strconv.Itoa(len(responseBody)))
responseWriter.Write(responseBody)
}
func handleMarioKartWiiFileUploadRequest(moduleName string, responseWriter http.ResponseWriter, request *http.Request) {
if strings.HasSuffix(request.URL.Path, "ghostupload.aspx") {
handleMarioKartWiiGhostUploadRequest(moduleName, responseWriter, request)
return
}
}
func handleMarioKartWiiGhostUploadRequest(moduleName string, responseWriter http.ResponseWriter, request *http.Request) {
query := request.URL.Query()
regionIdString := query.Get("regionid")
@ -137,7 +212,7 @@ func handleMarioKartWiiFileUploadRequest(moduleName string, responseWriter http.
ghostFile = nil
}
err = database.UploadMarioKartWiiGhostFile(pool, ctx, regionId, courseId, score, pid, playerInfo, ghostFile)
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))
@ -147,6 +222,14 @@ func handleMarioKartWiiFileUploadRequest(moduleName string, responseWriter http.
responseWriter.Header().Set(common.SakeFileResultHeader, strconv.Itoa(common.SakeFileResultSuccess))
}
func downloadedGhostFileHeader() []byte {
var downloadedGhostFileHeader [0x200]byte
binary.BigEndian.PutUint32(downloadedGhostFileHeader[0x40:0x44], uint32(len(downloadedGhostFileHeader)))
return downloadedGhostFileHeader[:]
}
func isPlayerInfoValid(playerInfoString string) bool {
playerInfoByteArray, err := common.DecodeGameSpyBase64(playerInfoString, common.GameSpyBase64EncodingURLSafe)
if err != nil {

View File

@ -15,11 +15,18 @@ import (
"github.com/logrusorgru/aurora/v3"
)
const (
FileRequestDownload = iota
FileRequestUpload
)
const (
SOAPEnvNamespace = "http://schemas.xmlsoap.org/soap/envelope/"
SakeNamespace = "http://gamespy.net/sake"
)
type FileRequest int
type StorageRequestEnvelope struct {
XMLName xml.Name
Body StorageRequestBody
@ -112,6 +119,10 @@ type StorageSearchForRecordsResponse struct {
Values StorageResponseValues `xml:"values"` // ???
}
var fileDownloadHandlers = map[int]func(string, http.ResponseWriter, *http.Request){
common.GameSpyGameIdMarioKartWii: handleMarioKartWiiFileDownloadRequest,
}
var fileUploadHandlers = map[int]func(string, http.ResponseWriter, *http.Request){
common.GameSpyGameIdMarioKartWii: handleMarioKartWiiFileUploadRequest,
}
@ -178,19 +189,30 @@ func handleStorageRequest(moduleName string, w http.ResponseWriter, r *http.Requ
w.Write(payload)
}
func handleFileUploadRequest(moduleName string, responseWriter http.ResponseWriter, request *http.Request) {
query := request.URL.Query()
func handleFileRequest(moduleName string, responseWriter http.ResponseWriter, request *http.Request,
fileRequest FileRequest) {
gameIdString := query.Get("gameid")
gameIdString := request.URL.Query().Get("gameid")
gameId, err := strconv.Atoi(gameIdString)
if err != nil {
logging.Error(moduleName, "Invalid GameSpy game id")
return
}
handler, handlerExists := fileUploadHandlers[gameId]
var handler func(string, http.ResponseWriter, *http.Request)
var handlerExists bool
switch fileRequest {
case FileRequestDownload:
handler, handlerExists = fileDownloadHandlers[gameId]
case FileRequestUpload:
handler, handlerExists = fileUploadHandlers[gameId]
default:
logging.Error(moduleName, "Invalid file request")
return
}
if !handlerExists {
logging.Warn(moduleName, "Unhandled file upload request for game id:", aurora.Cyan(gameId))
logging.Warn(moduleName, "Unhandled file request for GameSpy game id:", aurora.Cyan(gameId))
return
}