From 3d1cdc7ab5cd58524ff148e40b871745ed4955e1 Mon Sep 17 00:00:00 2001 From: mkwcat <26661008+mkwcat@users.noreply.github.com> Date: Wed, 11 Sep 2024 16:35:35 -0400 Subject: [PATCH] SAKE: More thorough validation of MKW ghost data --- common/mario_kart_wii.go | 495 +++++++++++++++++++++++++++++++++++++++ common/mii.go | 4 +- qr2/group.go | 3 +- sake/file.go | 55 +++++ sake/mario_kart_wii.go | 30 +-- sake/storage.go | 45 ---- 6 files changed, 561 insertions(+), 71 deletions(-) create mode 100644 sake/file.go diff --git a/common/mario_kart_wii.go b/common/mario_kart_wii.go index 9d2dce7..d9a4e72 100644 --- a/common/mario_kart_wii.go +++ b/common/mario_kart_wii.go @@ -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 +} diff --git a/common/mii.go b/common/mii.go index f3ea12a..2147cef 100644 --- a/common/mii.go +++ b/common/mii.go @@ -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 { diff --git a/qr2/group.go b/qr2/group.go index b4f9422..ac02801 100644 --- a/qr2/group.go +++ b/qr2/group.go @@ -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 diff --git a/sake/file.go b/sake/file.go new file mode 100644 index 0000000..14d1093 --- /dev/null +++ b/sake/file.go @@ -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) +} diff --git a/sake/mario_kart_wii.go b/sake/mario_kart_wii.go index e7013f7..101fbd7 100644 --- a/sake/mario_kart_wii.go +++ b/sake/mario_kart_wii.go @@ -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 -} diff --git a/sake/storage.go b/sake/storage.go index 03d08d4..82159f5 100644 --- a/sake/storage.go +++ b/sake/storage.go @@ -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 {