Sake: Sanitize Mii info returned from mkw FriendInfo

This commit is contained in:
Palapeli 2026-04-04 01:46:41 -04:00
parent 4dbe2a7678
commit 2770b46ab7
No known key found for this signature in database
GPG Key ID: 1FFE8F556A474925
3 changed files with 95 additions and 12 deletions

View File

@ -453,6 +453,34 @@ func isPlayerInfoValid(playerInfoString string) (string, bool) {
return fixedPlayerInfoString, true
}
func filterMarioKartWiiFriendInfo(value string, isOwner bool) (string, string) {
// Clear personal info from the Mii data before sending to the client. This
// cannot be done before inserting into the database because the client
// expects the server to return the exact copy of what it has previously updated.
if isOwner {
return value, ResultSuccess
}
binaryData, err := base64.StdEncoding.DecodeString(value)
if err != nil {
// Shouldn't happen after validation
panic(err)
}
// Only change if the Mii checksum is valid
if len(binaryData) < 0x4C {
return value, ResultSuccess
}
mii := common.RawMiiFromBytes(binaryData)
if mii.CalculateMiiCRC() != 0 {
return value, ResultSuccess
}
mii = mii.ClearMiiInfo()
binaryData = append(mii.Data[:], binaryData[len(mii.Data):]...)
return base64.StdEncoding.EncodeToString(binaryData), ResultSuccess
}
func getMultipartBoundary(contentType string) string {
startIndex := strings.Index(contentType, "boundary=")
if startIndex == -1 {

View File

@ -306,6 +306,14 @@ func getMyRecords(moduleName string, profileId uint32, gameInfo common.GameInfo,
}}
}
table := GetTable(gameInfo.Name, request.TableID)
if table != nil && table.Reserved {
logging.Error(moduleName, "Attempt to get my records from reserved table", aurora.Cyan(request.TableID))
return StorageResponseBody{GetMyRecordsResponse: &StorageGetMyRecordsResponse{
GetMyRecordsResult: ResultTableNotFound,
}}
}
records, err := database.GetSakeRecords(pool, ctx, gameInfo.GameID, []int32{int32(profileId)}, request.TableID, nil, request.Fields.Fields, request.Filter)
if err != nil {
logging.Error(moduleName, "Failed to get sake records from the database:", err)
@ -319,9 +327,10 @@ func getMyRecords(moduleName string, profileId uint32, gameInfo common.GameInfo,
}}
}
responseValues, result := fillResponseValues(moduleName, profileId, table, records, request)
response := StorageGetMyRecordsResponse{
GetMyRecordsResult: ResultSuccess,
Values: fillResponseValues(records, request),
GetMyRecordsResult: result,
Values: responseValues,
}
logging.Info(moduleName, "Returning", aurora.Cyan(len(records)), "records from table", aurora.Cyan(request.TableID), "for profile", aurora.Cyan(profileId))
@ -462,9 +471,10 @@ func searchForRecords(moduleName string, profileId uint32, gameInfo common.GameI
records = records[:request.Max]
}
responseValues, result := fillResponseValues(moduleName, profileId, table, records, request)
response := StorageSearchForRecordsResponse{
SearchForRecordsResult: "Success",
Values: fillResponseValues(records, request),
SearchForRecordsResult: result,
Values: responseValues,
}
logging.Info(moduleName, "Found", aurora.Cyan(len(records)), "records in table", aurora.Cyan(request.TableID), "for profile", aurora.Cyan(profileId), "with filter", aurora.Cyan(request.Filter))
@ -502,12 +512,18 @@ func getInputFields(moduleName string, request StorageRequestData, table *SakeTa
logging.Error(moduleName, "Invalid value for field", aurora.Cyan(field.Name).String()+":", aurora.Cyan(value))
return nil, result
}
var result string
sakeField.Value, result = table.FilterFieldFromClient(field.Name, value)
if result != ResultSuccess {
logging.Error(moduleName, "Failed to filter from client value for field", aurora.Cyan(field.Name), ":", aurora.Cyan(result))
return nil, result
}
fields[field.Name] = sakeField
}
return fields, ResultSuccess
}
func fillResponseValues(records []database.SakeRecord, request StorageRequestData) StorageResponseValues {
func fillResponseValues(moduleName string, profileId uint32, table *SakeTable, records []database.SakeRecord, request StorageRequestData) (StorageResponseValues, string) {
var response StorageResponseValues
for _, record := range records {
valueArray := StorageArrayOfRecordValue{}
@ -536,14 +552,21 @@ func fillResponseValues(records []database.SakeRecord, request StorageRequestDat
}
if fieldValue == nil {
valueArray.RecordValues = append(valueArray.RecordValues, StorageRecordValue{Value: nil})
} else {
value := fillValue(fieldValue.Type, fieldValue.Value)
valueArray.RecordValues = append(valueArray.RecordValues, StorageRecordValue{Value: &value})
continue
}
var result string
fieldValue.Value, result = table.FilterFieldFromDatabase(field, fieldValue.Value, record.OwnerId == int32(profileId))
if result != ResultSuccess {
logging.Error(moduleName, "Failed to filter to client value for field", aurora.Cyan(field), ":", aurora.Cyan(result))
return StorageResponseValues{}, result
}
value := fillValue(fieldValue.Type, fieldValue.Value)
valueArray.RecordValues = append(valueArray.RecordValues, StorageRecordValue{Value: &value})
}
response.ArrayOfRecordValue = append(response.ArrayOfRecordValue, valueArray)
}
return response
return response, ResultSuccess
}
func fillValue(valueType database.SakeFieldType, value string) StorageValue {

View File

@ -51,9 +51,9 @@ type SakeFieldDefinition struct {
// Optional function for custom validation.
IsValidFunc func(value string) bool
// Optional function for custom filtering. This function receives the value from the client AFTER validation, before inserting into the database.
FilterFromClientFunc func(value string) (string, error)
FilterFromClientFunc func(value string, isOwner bool) (string, string)
// Optional function for custom filtering. This function receives the value from the database before sending to the client.
FilterFromDatabaseFunc func(value string) (string, error)
FilterFromDatabaseFunc func(value string, isOwner bool) (string, string)
}
type SakeTable struct {
@ -113,7 +113,8 @@ var TableDefinitions = map[string]SakeTable{
Hardened: false, // To allow modding extra fields
Fields: map[string]SakeFieldDefinition{
"info": {
Type: database.SakeFieldTypeBinaryData,
Type: database.SakeFieldTypeBinaryData,
FilterFromDatabaseFunc: filterMarioKartWiiFriendInfo,
},
},
},
@ -387,3 +388,34 @@ func (t *SakeTable) CheckValidField(fieldName string, field database.SakeField)
}
return ResultSuccess
}
func (t *SakeTable) FilterFieldFromClient(fieldName string, value string) (string, string) {
if t == nil || t.Fields == nil {
return value, ResultSuccess
}
fieldDef, exists := t.Fields[fieldName]
if !exists {
return value, ResultSuccess
}
if fieldDef.FilterFromClientFunc == nil {
return value, ResultSuccess
}
return fieldDef.FilterFromClientFunc(value, true)
}
func (t *SakeTable) FilterFieldFromDatabase(fieldName string, value string, isOwner bool) (string, string) {
if t == nil || t.Fields == nil {
return value, ResultSuccess
}
fieldDef, exists := t.Fields[fieldName]
if !exists {
return value, ResultSuccess
}
if fieldDef.FilterFromDatabaseFunc == nil {
return value, ResultSuccess
}
if t.OwnerType != OwnerTypeProfile {
isOwner = false
}
return fieldDef.FilterFromDatabaseFunc(value, isOwner)
}