SAKE: More thorough validation of MKW ghost data

This commit is contained in:
mkwcat 2024-09-11 16:35:35 -04:00
parent 1b63ae4a1d
commit 3d1cdc7ab5
No known key found for this signature in database
GPG Key ID: 7A505679CE9E7AA9
6 changed files with 561 additions and 71 deletions

View File

@ -1,9 +1,22 @@
package common
import (
"bytes"
"encoding/binary"
"hash/crc32"
"wwfc/logging"
"github.com/logrusorgru/aurora/v3"
)
type MarioKartWiiLeaderboardRegionId int
type MarioKartWiiCourseId int
type MarioKartWiiCharacterId int
type MarioKartWiiVehicleId int
type MarioKartWiiControllerId int
type MarioKartWiiWeightClassId int
// MarioKartWiiLeaderboardRegionId
const (
Worldwide = iota // 0x00
Japan // 0x01
@ -15,6 +28,7 @@ const (
China // 0x07
)
// MarioKartWiiCourseId
const (
MarioCircuit = iota // 0x00
MooMooMeadows // 0x01
@ -50,6 +64,93 @@ const (
GBAShyGuyBeach // 0x1F
)
// MarioKartWiiCharacterId
const (
Mario = iota // 0x00
BabyPeach // 0x01
Waluigi // 0x02
Bowser // 0x03
BabyDaisy // 0x04
DryBones // 0x05
BabyMario // 0x06
Luigi // 0x07
Toad // 0x08
DonkeyKong // 0x09
Yoshi // 0x0A
Wario // 0x0B
BabyLuigi // 0x0C
Toadette // 0x0D
KoopaTroopa // 0x0E
Daisy // 0x0F
Peach // 0x10
Birdo // 0x11
DiddyKong // 0x12
KingBoo // 0x13
BowserJr // 0x14
DryBowser // 0x15
FunkyKong // 0x16
Rosalina // 0x17
SmallMiiOutfitAMale // 0x18
SmallMiiOutfitAFemale // 0x19
SmallMiiOutfitBMale // 0x1A
SmallMiiOutfitBFemale // 0x1B
SmallMiiOutfitCMale // 0x1C
SmallMiiOutfitCFemale // 0x1D
MediumMiiOutfitAMale // 0x1E
MediumMiiOutfitAFemale // 0x1F
MediumMiiOutfitBMale // 0x20
MediumMiiOutfitBFemale // 0x21
MediumMiiOutfitCMale // 0x22
MediumMiiOutfitCFemale // 0x23
LargeMiiOutfitAMale // 0x24
LargeMiiOutfitAFemale // 0x25
LargeMiiOutfitBMale // 0x26
LargeMiiOutfitBFemale // 0x27
LargeMiiOutfitCMale // 0x28
LargeMiiOutfitCFemale // 0x29
)
// MarioKartWiiVehicleId
const (
StandardKartSmall = iota // 0x00
StandardKartMedium // 0x01
StandardKartLarge // 0x02
BoosterSeat // 0x03
ClassicDragster // 0x04
Offroader // 0x05
MiniBeast // 0x06
WildWing // 0x07
FlameFlyer // 0x08
CheepCharger // 0x09
SuperBlooper // 0x0A
PiranhaProwler // 0x0B
TinyTitan // 0x0C
Daytripper // 0x0D
Jetsetter // 0x0E
BlueFalcon // 0x0F
Sprinter // 0x10
Honeycoupe // 0x11
StandardBikeSmall // 0x12
StandardBikeMedium // 0x13
StandardBikeLarge // 0x14
BulletBike // 0x15
MachBike // 0x16
FlameRunner // 0x17
BitBike // 0x18
Sugarscoot // 0x19
WarioBike // 0x1A
Quacker // 0x1B
ZipZip // 0x1C
ShootingStar // 0x1D
Magikruiser // 0x1E
Sneakster // 0x1F
Spear // 0x20
JetBubble // 0x21
DolphinDasher // 0x22
Phantom // 0x23
)
// MarioKartWiiControllerId
const (
WiiWheel = iota // 0x00
WiiRemoteAndNunchuck // 0x01
@ -57,6 +158,13 @@ const (
GameCube // 0x03
)
// MarioKartWiiWeightClassId
const (
LightWeight = iota
MiddleWeight
HeavyWeight
)
func (regionId MarioKartWiiLeaderboardRegionId) IsValid() bool {
return regionId >= Worldwide && regionId <= China
}
@ -65,6 +173,393 @@ func (courseId MarioKartWiiCourseId) IsValid() bool {
return courseId >= MarioCircuit && courseId <= GBAShyGuyBeach
}
func (characterId MarioKartWiiCharacterId) IsValid() bool {
// Mii Outfit C is not allowed
if characterId == SmallMiiOutfitCMale || characterId == SmallMiiOutfitCFemale || characterId == MediumMiiOutfitCMale || characterId == MediumMiiOutfitCFemale {
return false
}
return characterId >= Mario && characterId <= LargeMiiOutfitBFemale
}
func (characterId MarioKartWiiCharacterId) GetWeightClass() MarioKartWiiWeightClassId {
switch characterId {
case BabyPeach, BabyDaisy, DryBones, BabyMario, Toad, BabyLuigi, Toadette, KoopaTroopa, SmallMiiOutfitAMale, SmallMiiOutfitAFemale, SmallMiiOutfitBMale, SmallMiiOutfitBFemale, SmallMiiOutfitCMale, SmallMiiOutfitCFemale:
return LightWeight
case Mario, Luigi, Yoshi, Daisy, Peach, Birdo, DiddyKong, BowserJr, MediumMiiOutfitAMale, MediumMiiOutfitAFemale, MediumMiiOutfitBMale, MediumMiiOutfitBFemale, MediumMiiOutfitCMale, MediumMiiOutfitCFemale:
return MiddleWeight
case Waluigi, Bowser, Wario, DonkeyKong, KingBoo, DryBowser, FunkyKong, Rosalina, LargeMiiOutfitAMale, LargeMiiOutfitAFemale, LargeMiiOutfitBMale, LargeMiiOutfitBFemale, LargeMiiOutfitCMale, LargeMiiOutfitCFemale:
return HeavyWeight
}
return -1
}
func (vehicleId MarioKartWiiVehicleId) IsValid() bool {
return vehicleId >= StandardKartSmall && vehicleId <= Phantom
}
func (vehicleId MarioKartWiiVehicleId) GetWeightClass() MarioKartWiiWeightClassId {
if vehicleId < 0 {
return -1
}
return MarioKartWiiWeightClassId(vehicleId % 3)
}
func (controllerId MarioKartWiiControllerId) IsValid() bool {
return controllerId >= WiiWheel && controllerId <= GameCube
}
type RKGhostData []byte
const (
RKGDFileMaxSize = 0x2800
RKGDFileMinSize = 0x0088 + 0x0014 + 0x0004
)
func (rkgd RKGhostData) GetBits(byteOffset int, bitOffset int, bitLength int) uint32 {
if bitLength == 0 {
return 0
}
byteIndex := byteOffset + (bitOffset / 8)
bitIndex := bitOffset % 8
byteCount := (bitLength + bitIndex + 7) / 8
var value uint32
for i := 0; i < byteCount; i++ {
value |= uint32(rkgd[byteIndex+i]) << uint32((byteCount-i-1)*8)
}
endBitIndex := (bitIndex + bitLength) % 8
value >>= uint32(8-endBitIndex) % 8
value &= (1 << uint32(bitLength)) - 1
return value
}
func (rkgd RKGhostData) GetMinutes(lap int) int {
if lap == 0 {
return int(rkgd.GetBits(0x04, 0, 7))
}
if lap < 1 || lap > 5 {
return 0
}
return int(rkgd.GetBits(0x11+(lap-1)*3, 0, 7))
}
func (rkgd RKGhostData) GetSeconds(lap int) int {
if lap == 0 {
return int(rkgd.GetBits(0x04, 7, 7))
}
if lap < 1 || lap > 5 {
return 0
}
return int(rkgd.GetBits(0x11+(lap-1)*3, 7, 7))
}
func (rkgd RKGhostData) GetMilliseconds(lap int) int {
if lap == 0 {
return int(rkgd.GetBits(0x05, 6, 10))
}
if lap < 1 || lap > 5 {
return 0
}
return int(rkgd.GetBits(0x11+(lap-1)*3, 14, 10))
}
func (rkgd RKGhostData) GetTime(lap int) int {
return rkgd.GetMinutes(lap)*60000 + rkgd.GetSeconds(lap)*1000 + rkgd.GetMilliseconds(lap)
}
func (rkgd RKGhostData) GetCourse() MarioKartWiiCourseId {
return MarioKartWiiCourseId(rkgd.GetBits(0x07, 0, 6))
}
func (rkgd RKGhostData) GetVehicle() MarioKartWiiVehicleId {
return MarioKartWiiVehicleId(rkgd.GetBits(0x08, 0, 6))
}
func (rkgd RKGhostData) GetCharacter() MarioKartWiiCharacterId {
return MarioKartWiiCharacterId(rkgd.GetBits(0x08, 6, 6))
}
func (rkgd RKGhostData) GetYear() int {
return int(rkgd.GetBits(0x09, 4, 7))
}
func (rkgd RKGhostData) GetMonth() int {
return int(rkgd.GetBits(0x0A, 3, 4))
}
func (rkgd RKGhostData) GetDay() int {
return int(rkgd.GetBits(0x0A, 7, 5))
}
func (rkgd RKGhostData) GetController() MarioKartWiiControllerId {
return MarioKartWiiControllerId(rkgd.GetBits(0x0B, 4, 4))
}
func (rkgd RKGhostData) IsCompressed() bool {
return rkgd.GetBits(0x0C, 4, 1) == 1
}
func (rkgd RKGhostData) GetGhostType() int {
return int(rkgd.GetBits(0x0C, 7, 7))
}
func (rkgd RKGhostData) GetDriftType() int {
return int(rkgd.GetBits(0x0D, 6, 1))
}
func (rkgd RKGhostData) GetInputDataLength() uint16 {
return uint16(rkgd.GetBits(0x0E, 0, 16))
}
func (rkgd RKGhostData) GetLapCount() int {
return int(rkgd.GetBits(0x10, 0, 8))
}
func (rkgd RKGhostData) GetCountryCode() byte {
return byte(rkgd.GetBits(0x34, 0, 8))
}
func (rkgd RKGhostData) GetStateCode() byte {
return byte(rkgd.GetBits(0x35, 0, 8))
}
func (rkgd RKGhostData) GetLocationCode() uint16 {
return uint16(rkgd.GetBits(0x36, 0, 16))
}
func (rkgd RKGhostData) GetMiiData() Mii {
return Mii(rkgd[0x3C : 0x3C+0x4C])
}
func (rkgd RKGhostData) GetCompressedSize() uint32 {
return binary.BigEndian.Uint32(rkgd[0x88:0x8C])
}
func (rkgd RKGhostData) GetCompressedData() []byte {
return []byte(rkgd[0x8C : len(rkgd)-4])
}
func (rkgd RKGhostData) IsRKGDFileValid(moduleName string, expectedCourse MarioKartWiiCourseId, expectedScore int) bool {
rkgdFileMagic := []byte{'R', 'K', 'G', 'D'}
rkgdFileLength := len(rkgd)
if rkgdFileLength < RKGDFileMinSize || rkgdFileLength > RKGDFileMaxSize {
logging.Error(moduleName, "Invalid RKGD length:", aurora.Cyan(rkgdFileLength))
return false
}
if !bytes.Equal(rkgd[:4], rkgdFileMagic) {
logging.Error(moduleName, "Invalid RKGD magic:", aurora.Cyan(string(rkgd[:4])))
return false
}
expectedChecksum := binary.BigEndian.Uint32(rkgd[rkgdFileLength-4:])
checksum := crc32.ChecksumIEEE(rkgd[:rkgdFileLength-4])
if checksum != expectedChecksum {
logging.Error(moduleName, "Invalid RKGD checksum:", aurora.Cyan(checksum), "expected:", aurora.Cyan(expectedChecksum))
return false
}
lapCount := rkgd.GetLapCount()
// This will need to be changed if/when we add support for tournament ghosts
// Always make sure this value <= 5 due to a stack buffer overflow in the game's code
if lapCount != 3 {
logging.Error(moduleName, "Invalid RKGD lap count:", aurora.Cyan(lapCount))
return false
}
lapScore := 0
for lap := 0; lap <= lapCount; lap++ {
if rkgd.GetTime(lap) == 0 {
logging.Error(moduleName, "Zero RKGD time for lap:", aurora.Cyan(lap))
return false
}
if rkgd.GetMinutes(lap) > 5 || rkgd.GetSeconds(lap) > 59 || rkgd.GetMilliseconds(lap) > 999 {
logging.Error(moduleName, "Invalid RKGD time for lap m =", aurora.Cyan(rkgd.GetMinutes(lap)), "s =", aurora.Cyan(rkgd.GetSeconds(lap)), "ms =", aurora.Cyan(rkgd.GetMilliseconds(lap)))
return false
}
if lap > 0 {
lapScore += rkgd.GetTime(lap)
}
}
totalScore := rkgd.GetTime(0)
if expectedScore != -1 && totalScore != expectedScore {
logging.Error(moduleName, "RKGD total score mismatch:", aurora.Cyan(rkgd.GetTime(0)), "expected:", aurora.Cyan(expectedScore))
return false
}
// We'll do a grace of ~1 millisecond for the lap score compared to the total score,
// in case there is a rounding error in the game's code
if lapScore+1 < totalScore || lapScore-1 > totalScore {
logging.Error(moduleName, "RKGD lap score mismatch:", aurora.Cyan(lapScore), "expected:", aurora.Cyan(expectedScore))
return false
}
if expectedCourse != -1 && rkgd.GetCourse() != expectedCourse {
logging.Error(moduleName, "RKGD course mismatch:", aurora.Cyan(rkgd.GetCourse()), "expected:", aurora.Cyan(expectedCourse))
return false
}
if !rkgd.GetCourse().IsValid() {
logging.Error(moduleName, "Invalid RKGD course:", aurora.Cyan(rkgd.GetCourse()))
return false
}
if !rkgd.GetCharacter().IsValid() {
logging.Error(moduleName, "Invalid RKGD character:", aurora.Cyan(rkgd.GetCharacter()))
return false
}
if !rkgd.GetVehicle().IsValid() {
logging.Error(moduleName, "Invalid RKGD vehicle:", aurora.Cyan(rkgd.GetVehicle()))
return false
}
if rkgd.GetCharacter().GetWeightClass() != rkgd.GetVehicle().GetWeightClass() {
logging.Error(moduleName, "RKGD character/vehicle weight class mismatch: c =", aurora.Cyan(rkgd.GetCharacter()), "v =", aurora.Cyan(rkgd.GetVehicle()))
}
if !rkgd.GetController().IsValid() {
logging.Error(moduleName, "Invalid RKGD controller:", aurora.Cyan(rkgd.GetController()))
return false
}
if rkgd.GetMiiData().RFLCalculateCRC() != 0x0000 {
logging.Error(moduleName, "Invalid RKGD Mii data CRC")
return false
}
// RKG uploaded to the server must be SZS (Yaz1) compressed
if !rkgd.IsCompressed() {
logging.Error(moduleName, "RKGD is not compressed")
return false
}
szsData := rkgd.GetCompressedData()
if string(szsData[:0x4]) != "Yaz1" {
logging.Error(moduleName, "Invalid Yaz1 magic:", aurora.Cyan(string(szsData[:4])))
return false
}
decompSize := binary.BigEndian.Uint32(szsData[0x4:0x8])
if uint32(rkgd.GetInputDataLength()) != decompSize {
logging.Error(moduleName, "Invalid RKGD input data length:", aurora.Cyan(rkgd.GetInputDataLength()), "actual:", aurora.Cyan(len(szsData)))
}
if rkgd.GetCompressedSize() != uint32(len(szsData)) {
logging.Error(moduleName, "Invalid RKGD compressed size:", aurora.Cyan(rkgd.GetCompressedSize()), "actual:", aurora.Cyan(len(szsData)))
}
if !bytes.Equal(szsData[0x8:0x10], []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) {
logging.Error(moduleName, "Invalid SZS header padding")
}
valid, consumed := VerifyYaz1Data(moduleName, szsData[0x10:], int(decompSize), 0)
if !valid {
return false
}
if consumed+3 < len(szsData)-0x10 {
logging.Error(moduleName, "Too much padding at end of RKGD")
return false
}
return true
}
// Verify that SZS compressed data fits the standard. A buffer overflow bug is the basis for a critical RCE vulnerability in the game (szsHaxx).
func VerifyYaz1Data(moduleName string, szsData []byte, expectedDecompSize int, decoded int) (bool, int) {
i := 0
for decoded < expectedDecompSize {
if i >= len(szsData) {
logging.Error(moduleName, "Yaz1: Unexpected end of data")
return false, i
}
flags := szsData[i]
i++
// This happens a lot, so might as well check for the sake of performance
if flags == 0xFF {
decoded += 8
i += 8
continue
}
for j := 0; j < 8; j++ {
if flags&0x80 == 0 {
if i+1 >= len(szsData) {
logging.Error(moduleName, "Yaz1: Unexpected end of data")
return false, i
}
copyLen := (szsData[i] >> 4) + 2
copySrc := (int(szsData[i])&0x0F)<<8 | int(szsData[i+1])
copySrc = decoded - copySrc - 1
i += 2
if copySrc < 0 {
logging.Error(moduleName, "Yaz1: Copy source is out of bounds")
return false, i
}
if copyLen == 2 {
if i >= len(szsData) {
logging.Error(moduleName, "Yaz1: Unexpected end of data")
return false, i
}
copyLen = szsData[i] + 0x12
i += 1
}
decoded += int(copyLen)
} else {
decoded++
i++
}
if decoded >= expectedDecompSize {
break
}
flags <<= 1
}
}
if decoded > expectedDecompSize {
logging.Error(moduleName, "Yaz1: Overran expected decompressed size")
return false, i
}
if i > len(szsData) {
logging.Error(moduleName, "Yaz1: Unexpected end of data")
return false, i
}
return true, i
}

View File

@ -4,7 +4,9 @@ package common
// https://wiibrew.org/wiki/Mii_Data
// https://github.com/kiwi515/ogws/tree/master/src/RVLFaceLib
func RFLCalculateCRC(data []byte) uint16 {
type Mii [0x4C]byte
func (data Mii) RFLCalculateCRC() uint16 {
crc := uint16(0)
for _, val := range data {

View File

@ -390,7 +390,8 @@ func ProcessUSER(senderPid uint32, senderIP uint64, packet []byte) {
}
index := 0x08 + i*0x4C
if common.RFLCalculateCRC(packet[index:index+0x4C]) != 0x0000 {
mii := common.Mii(packet[index : index+0x4C])
if mii.RFLCalculateCRC() != 0x0000 {
logging.Error(moduleName, "Received USER packet with invalid Mii data CRC")
gpErrorCallback(senderPid, "malpacket")
return

55
sake/file.go Normal file
View File

@ -0,0 +1,55 @@
package sake
import (
"net/http"
"strconv"
"wwfc/common"
"wwfc/logging"
"github.com/logrusorgru/aurora/v3"
)
const (
FileRequestDownload = iota
FileRequestUpload
)
type FileRequest int
var fileDownloadHandlers = map[int]func(string, http.ResponseWriter, *http.Request){
common.GetGameIDOrPanic("mariokartwii"): handleMarioKartWiiFileDownloadRequest,
}
var fileUploadHandlers = map[int]func(string, http.ResponseWriter, *http.Request){
common.GetGameIDOrPanic("mariokartwii"): handleMarioKartWiiFileUploadRequest,
}
func handleFileRequest(moduleName string, responseWriter http.ResponseWriter, request *http.Request,
fileRequest FileRequest) {
gameIdString := request.URL.Query().Get("gameid")
gameId, err := strconv.Atoi(gameIdString)
if err != nil {
logging.Error(moduleName, "Invalid GameSpy game id")
return
}
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 request for GameSpy game id:", aurora.Cyan(gameId))
return
}
handler(moduleName, responseWriter, request)
}

View File

@ -4,7 +4,6 @@ import (
"bytes"
"encoding/binary"
"fmt"
"hash/crc32"
"io"
"net/http"
"strconv"
@ -17,7 +16,7 @@ import (
)
type playerInfo struct {
MiiData [0x4C]byte // 0x00
MiiData common.Mii // 0x00
ControllerId byte // 0x4C
Unknown byte // 0x4D
StateCode byte // 0x4E
@ -27,9 +26,7 @@ type playerInfo struct {
const (
playerInfoSize = 0x50
rkgdFileMaxSize = 0x2800
rkgdFileMinSize = 0x0088 + 0x0008 + 0x0004
rkgdFileName = "ghost.bin"
rkgdFileName = "ghost.bin"
)
func handleMarioKartWiiFileDownloadRequest(moduleName string, responseWriter http.ResponseWriter, request *http.Request) {
@ -205,7 +202,7 @@ func handleMarioKartWiiGhostUploadRequest(moduleName string, responseWriter http
request.Header.Set("Content-Type", contentType)
}
err = request.ParseMultipartForm(rkgdFileMaxSize)
err = request.ParseMultipartForm(common.RKGDFileMaxSize)
if err != nil {
logging.Error(moduleName, "Failed to parse the multipart form:", err)
responseWriter.Header().Set(SakeFileResultHeader, strconv.Itoa(SakeFileResultFileNotFound))
@ -220,7 +217,7 @@ func handleMarioKartWiiGhostUploadRequest(moduleName string, responseWriter http
}
defer file.Close()
if fileHeader.Size < rkgdFileMinSize || fileHeader.Size > rkgdFileMaxSize {
if fileHeader.Size < common.RKGDFileMinSize || fileHeader.Size > common.RKGDFileMaxSize {
logging.Error(moduleName, "The size of the ghost file is invalid:", aurora.Cyan(fileHeader.Size))
responseWriter.Header().Set(SakeFileResultHeader, strconv.Itoa(SakeFileResultFileTooLarge))
return
@ -234,7 +231,7 @@ func handleMarioKartWiiGhostUploadRequest(moduleName string, responseWriter http
return
}
if !isRKGDFileValid(ghostFile) {
if !common.RKGhostData(ghostFile).IsRKGDFileValid(moduleName, courseId, score) {
logging.Error(moduleName, "Received an invalid ghost file")
responseWriter.Header().Set(SakeFileResultHeader, strconv.Itoa(SakeFileResultFileTooLarge))
return
@ -279,7 +276,7 @@ func isPlayerInfoValid(playerInfoString string) bool {
return false
}
if common.RFLCalculateCRC(playerInfo.MiiData[:]) != 0x0000 {
if playerInfo.MiiData.RFLCalculateCRC() != 0x0000 {
return false
}
@ -297,18 +294,3 @@ func getMultipartBoundary(contentType string) string {
return contentType[startIndex:]
}
func isRKGDFileValid(rkgdFile []byte) bool {
rkgdFileMagic := []byte{'R', 'K', 'G', 'D'}
if !bytes.Equal(rkgdFile[:4], rkgdFileMagic) {
return false
}
rkgdFileLength := len(rkgdFile)
expectedChecksum := binary.BigEndian.Uint32(rkgdFile[rkgdFileLength-4:])
checksum := crc32.ChecksumIEEE(rkgdFile[:rkgdFileLength-4])
return checksum == expectedChecksum
}

View File

@ -15,18 +15,11 @@ 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
@ -119,14 +112,6 @@ type StorageSearchForRecordsResponse struct {
Values StorageResponseValues `xml:"values"` // ???
}
var fileDownloadHandlers = map[int]func(string, http.ResponseWriter, *http.Request){
common.GetGameIDOrPanic("mariokartwii"): handleMarioKartWiiFileDownloadRequest,
}
var fileUploadHandlers = map[int]func(string, http.ResponseWriter, *http.Request){
common.GetGameIDOrPanic("mariokartwii"): handleMarioKartWiiFileUploadRequest,
}
func handleStorageRequest(moduleName string, w http.ResponseWriter, r *http.Request) {
headerAction := r.Header.Get("SOAPAction")
if headerAction == "" {
@ -189,36 +174,6 @@ func handleStorageRequest(moduleName string, w http.ResponseWriter, r *http.Requ
w.Write(payload)
}
func handleFileRequest(moduleName string, responseWriter http.ResponseWriter, request *http.Request,
fileRequest FileRequest) {
gameIdString := request.URL.Query().Get("gameid")
gameId, err := strconv.Atoi(gameIdString)
if err != nil {
logging.Error(moduleName, "Invalid GameSpy game id")
return
}
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 request for GameSpy game id:", aurora.Cyan(gameId))
return
}
handler(moduleName, responseWriter, request)
}
func getRequestIdentity(moduleName string, request StorageRequestData) (uint32, common.GameInfo, bool) {
gameInfo := common.GetGameInfoByID(request.GameID)
if gameInfo == nil {