mirror of
https://github.com/WiiLink24/wfc-server.git
synced 2026-05-06 05:26:33 -05:00
Sake: Sanitize Mii info from Mario kart Wii ghost data
This commit is contained in:
parent
865d8a8df2
commit
4dbe2a7678
|
|
@ -341,8 +341,12 @@ 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) GetMiiData() RawMii {
|
||||
return RawMiiFromBytes(rkgd[0x3C : 0x3C+0x4C])
|
||||
}
|
||||
|
||||
func (rkgd RKGhostData) SetMiiData(miiData RawMii) {
|
||||
copy(rkgd[0x3C:0x3C+0x4C], miiData.Data[:])
|
||||
}
|
||||
|
||||
func (rkgd RKGhostData) GetCompressedSize() uint32 {
|
||||
|
|
@ -353,6 +357,11 @@ func (rkgd RKGhostData) GetCompressedData() []byte {
|
|||
return []byte(rkgd[0x8C : len(rkgd)-4])
|
||||
}
|
||||
|
||||
func (rkgd RKGhostData) RecalculateCRC() {
|
||||
crc := crc32.ChecksumIEEE(rkgd[:len(rkgd)-4])
|
||||
binary.BigEndian.PutUint32(rkgd[len(rkgd)-4:], crc)
|
||||
}
|
||||
|
||||
func (rkgd RKGhostData) IsRKGDFileValid(moduleName string, expectedCourse MarioKartWiiCourseId, expectedScore int) bool {
|
||||
rkgdFileMagic := []byte{'R', 'K', 'G', 'D'}
|
||||
rkgdFileLength := len(rkgd)
|
||||
|
|
@ -443,7 +452,7 @@ func (rkgd RKGhostData) IsRKGDFileValid(moduleName string, expectedCourse MarioK
|
|||
return false
|
||||
}
|
||||
|
||||
if rkgd.GetMiiData().RFLCalculateCRC() != 0x0000 {
|
||||
if rkgd.GetMiiData().CalculateMiiCRC() != 0x0000 {
|
||||
logging.Error(moduleName, "Invalid RKGD Mii data CRC")
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,25 @@
|
|||
package common
|
||||
|
||||
import "encoding/binary"
|
||||
|
||||
// References:
|
||||
// https://wiibrew.org/wiki/Mii_Data
|
||||
// https://github.com/kiwi515/ogws/tree/master/src/RVLFaceLib
|
||||
|
||||
type Mii [0x4C]byte
|
||||
type RawMii struct {
|
||||
Data [0x4C]byte
|
||||
}
|
||||
|
||||
func (data Mii) RFLCalculateCRC() uint16 {
|
||||
func RawMiiFromBytes(data []byte) RawMii {
|
||||
var miiData [0x4C]byte
|
||||
copy(miiData[:], data[:0x4C])
|
||||
return RawMii{Data: miiData}
|
||||
}
|
||||
|
||||
func (data RawMii) CalculateMiiCRC() uint16 {
|
||||
crc := uint16(0)
|
||||
|
||||
for _, val := range data {
|
||||
for _, val := range data.Data {
|
||||
for j := 0; j < 8; j++ {
|
||||
if crc&0x8000 != 0 {
|
||||
crc <<= 1
|
||||
|
|
@ -38,7 +48,7 @@ var officialMiiList = []uint64{
|
|||
0x80000005ECFF82D2,
|
||||
}
|
||||
|
||||
func RFLSearchOfficialData(id uint64) (bool, int) {
|
||||
func SearchOfficialMiiData(id uint64) (bool, int) {
|
||||
for i, v := range officialMiiList {
|
||||
if v == id {
|
||||
return true, i
|
||||
|
|
@ -47,3 +57,39 @@ func RFLSearchOfficialData(id uint64) (bool, int) {
|
|||
|
||||
return false, -1
|
||||
}
|
||||
|
||||
// ClearMiiInfo clears any Mii information that generally isn't or shouldn't be shared publicly,
|
||||
// mainly the "console ID", which can be used to determine the Mii creator's MAC address
|
||||
func (mii RawMii) ClearMiiInfo() RawMii {
|
||||
// Clear the create ID, which the MAC address can be derived from
|
||||
binary.BigEndian.PutUint32(mii.Data[0x18:0x1C], 0x80000000)
|
||||
binary.BigEndian.PutUint32(mii.Data[0x1C:0x20], 0)
|
||||
|
||||
// Clear all characters in the Mii name succeeding the null terminator
|
||||
hitNullTerminator := false
|
||||
for i := 0; i < 20; i += 2 {
|
||||
if hitNullTerminator {
|
||||
mii.Data[0x2+i] = 0
|
||||
mii.Data[0x2+i+1] = 0
|
||||
} else if mii.Data[0x2+i] == 0 && mii.Data[0x2+i+1] == 0 {
|
||||
hitNullTerminator = true
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the creator name
|
||||
for i := 0; i < 20; i++ {
|
||||
mii.Data[0x36+i] = 0
|
||||
}
|
||||
|
||||
// Clear the birthday
|
||||
mii.Data[0] &= ^byte(0x3F)
|
||||
mii.Data[1] &= ^byte(0xE0)
|
||||
|
||||
// Clear checksum and recalculate
|
||||
mii.Data[0x4A] = 0
|
||||
mii.Data[0x4B] = 0
|
||||
crc := mii.CalculateMiiCRC()
|
||||
mii.Data[0x4A] = byte(crc >> 8)
|
||||
mii.Data[0x4B] = byte(crc & 0xFF)
|
||||
return mii
|
||||
}
|
||||
|
|
|
|||
|
|
@ -393,15 +393,15 @@ func ProcessUSER(senderPid uint32, senderIP uint64, packet []byte) {
|
|||
}
|
||||
|
||||
index := 0x08 + i*0x4C
|
||||
mii := common.Mii(packet[index : index+0x4C])
|
||||
if mii.RFLCalculateCRC() != 0x0000 {
|
||||
mii := common.RawMiiFromBytes(packet[index : index+0x4C])
|
||||
if mii.CalculateMiiCRC() != 0x0000 {
|
||||
logging.Error(moduleName, "Received USER packet with invalid Mii data CRC")
|
||||
gpErrorCallback(senderPid, "bad_packet")
|
||||
return
|
||||
}
|
||||
|
||||
createId := binary.BigEndian.Uint64(packet[index+0x18 : index+0x20])
|
||||
official, _ := common.RFLSearchOfficialData(createId)
|
||||
official, _ := common.SearchOfficialMiiData(createId)
|
||||
if official {
|
||||
miiName = append(miiName, "Player")
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package race
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"net/http"
|
||||
|
|
@ -153,11 +154,19 @@ func handleGetTopTenRankingsRequest(moduleName string, responseWriter http.Respo
|
|||
numberOfRankings := len(topTenRankings)
|
||||
data := make([]rankingsResponseData, 0, numberOfRankings)
|
||||
for i, topTenRanking := range topTenRankings {
|
||||
// Filter player info just in case
|
||||
playerInfo, err := base64.StdEncoding.DecodeString(topTenRanking.PlayerInfo)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
miiData := common.RawMiiFromBytes(playerInfo).ClearMiiInfo().Data
|
||||
playerInfo = append(miiData[:], playerInfo[len(miiData):]...)
|
||||
|
||||
rankingData := rankingsResponseRankingData{
|
||||
OwnerID: topTenRanking.PID,
|
||||
Rank: i + 1,
|
||||
Time: topTenRanking.Score,
|
||||
UserData: topTenRanking.PlayerInfo,
|
||||
UserData: base64.StdEncoding.EncodeToString(playerInfo),
|
||||
}
|
||||
|
||||
responseData := rankingsResponseData{
|
||||
|
|
|
|||
|
|
@ -18,11 +18,11 @@ import (
|
|||
)
|
||||
|
||||
type playerInfo struct {
|
||||
MiiData common.Mii // 0x00
|
||||
ControllerId byte // 0x4C
|
||||
Unknown byte // 0x4D
|
||||
StateCode byte // 0x4E
|
||||
CountryCode byte // 0x4F
|
||||
MiiData common.RawMii // 0x00
|
||||
ControllerId byte // 0x4C
|
||||
Unknown byte // 0x4D
|
||||
StateCode byte // 0x4E
|
||||
CountryCode byte // 0x4F
|
||||
}
|
||||
|
||||
const (
|
||||
|
|
@ -266,7 +266,11 @@ func handleMarioKartWiiGhostDownloadRequest(moduleName string, responseWriter ht
|
|||
return
|
||||
}
|
||||
|
||||
responseBody := append(downloadedGhostFileHeader(), ghost...)
|
||||
ghostData := common.RKGhostData(ghost)
|
||||
ghostData.SetMiiData(ghostData.GetMiiData().ClearMiiInfo())
|
||||
ghostData.RecalculateCRC()
|
||||
|
||||
responseBody := append(downloadedGhostFileHeader(), []byte(ghostData)...)
|
||||
|
||||
responseWriter.Header().Set(SakeFileResultHeader, strconv.Itoa(SakeFileResultSuccess))
|
||||
responseWriter.Header().Set("Content-Length", strconv.Itoa(len(responseBody)))
|
||||
|
|
@ -330,13 +334,13 @@ func handleMarioKartWiiGhostUploadRequest(moduleName string, responseWriter http
|
|||
return
|
||||
}
|
||||
|
||||
if !isPlayerInfoValid(playerInfo) {
|
||||
fixedPlayerInfo, ok := isPlayerInfoValid(playerInfo)
|
||||
if !ok {
|
||||
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
|
||||
playerInfo, _ = common.Base64Convert(playerInfo, base64.URLEncoding, base64.StdEncoding)
|
||||
playerInfo = fixedPlayerInfo
|
||||
|
||||
// The multipart boundary utilized by GameSpy does not conform to RFC 2045. To ensure compliance,
|
||||
// we need to surround it with double quotation marks.
|
||||
|
|
@ -377,17 +381,21 @@ func handleMarioKartWiiGhostUploadRequest(moduleName string, responseWriter http
|
|||
return
|
||||
}
|
||||
|
||||
if !common.RKGhostData(ghostFile).IsRKGDFileValid(moduleName, courseId, score) {
|
||||
ghostData := common.RKGhostData(ghostFile)
|
||||
if !ghostData.IsRKGDFileValid(moduleName, courseId, score) {
|
||||
logging.Error(moduleName, "Received an invalid ghost file")
|
||||
responseWriter.Header().Set(SakeFileResultHeader, strconv.Itoa(SakeFileResultFileTooLarge))
|
||||
return
|
||||
}
|
||||
|
||||
ghostData.SetMiiData(ghostData.GetMiiData().ClearMiiInfo())
|
||||
ghostData.RecalculateCRC()
|
||||
|
||||
if isContest {
|
||||
ghostFile = nil
|
||||
}
|
||||
|
||||
err = database.InsertMarioKartWiiGhostFile(pool, ctx, regionId, courseId, score, pid, playerInfo, ghostFile)
|
||||
err = database.InsertMarioKartWiiGhostFile(pool, ctx, regionId, courseId, score, pid, playerInfo, []byte(ghostData))
|
||||
if err != nil {
|
||||
logging.Error(moduleName, "Failed to insert the ghost file into the database:", err)
|
||||
responseWriter.Header().Set(SakeFileResultHeader, strconv.Itoa(SakeFileResultServerError))
|
||||
|
|
@ -405,30 +413,44 @@ func downloadedGhostFileHeader() []byte {
|
|||
return downloadedGhostFileHeader[:]
|
||||
}
|
||||
|
||||
func isPlayerInfoValid(playerInfoString string) bool {
|
||||
// isPlayerInfoValid checks if the player info (base64.URLEncoding) string is valid, and if so, returns a "fixed" version of it with base64.StdEncoding
|
||||
func isPlayerInfoValid(playerInfoString string) (string, bool) {
|
||||
playerInfoByteArray, err := base64.URLEncoding.DecodeString(playerInfoString)
|
||||
if err != nil {
|
||||
return false
|
||||
return "", false
|
||||
}
|
||||
|
||||
if len(playerInfoByteArray) != playerInfoSize {
|
||||
return false
|
||||
return "", false
|
||||
}
|
||||
|
||||
var playerInfo playerInfo
|
||||
reader := bytes.NewReader(playerInfoByteArray)
|
||||
err = binary.Read(reader, binary.BigEndian, &playerInfo)
|
||||
if err != nil {
|
||||
return false
|
||||
return "", false
|
||||
}
|
||||
|
||||
if playerInfo.MiiData.RFLCalculateCRC() != 0x0000 {
|
||||
return false
|
||||
if playerInfo.MiiData.CalculateMiiCRC() != 0x0000 {
|
||||
return "", false
|
||||
}
|
||||
|
||||
controllerId := common.MarioKartWiiControllerId(playerInfo.ControllerId)
|
||||
|
||||
return controllerId.IsValid()
|
||||
if !controllerId.IsValid() {
|
||||
return "", false
|
||||
}
|
||||
|
||||
playerInfo.MiiData.ClearMiiInfo()
|
||||
|
||||
fixedPlayerInfoByteArray := new(bytes.Buffer)
|
||||
err = binary.Write(fixedPlayerInfoByteArray, binary.BigEndian, playerInfo)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
|
||||
fixedPlayerInfoString := base64.StdEncoding.EncodeToString(fixedPlayerInfoByteArray.Bytes())
|
||||
return fixedPlayerInfoString, true
|
||||
}
|
||||
|
||||
func getMultipartBoundary(contentType string) string {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user