#include #include #include #include "save.h" #include "code_80091750.h" #include "menus.h" #include "save_data.h" #include "staff_ghosts.h" #include "code_80057C60.h" /*** macros ***/ #define PFS_COMPANY_CODE(c0, c1) ((u16)(((c0) << 8) | ((c1)))) #define PFS_GAME_CODE(c0, c1, c2, c3) ((u32)(((c0) << 24) | ((c1) << 16) | ((c2) << 8) | (c3))) // calculate an eeprom address based off of the ram address of the SaveData variable // very fragile! #define EEPROM_ADDR(ptr) (((uintptr_t)(ptr) - (uintptr_t)(&gSaveData)) / 8) /*** data ***/ u16 gCompanyCode = PFS_COMPANY_CODE('0', '1'); u32 gGameCode = PFS_GAME_CODE('N', 'K', 'T', 'J'); s8 gControllerPak1State = BAD; s8 sControllerPak2State = BAD; /*** rodata ***/ // default time trial records in little endian form const u8 D_800F2E60[4] = {0xc0, 0x27, 0x09, 0x00}; // osPfsFindFile -> gGameName ("MARIOKART64" in nosFont) const u8 gGameName[] = {0x26, 0x1a, 0x2b, 0x22, 0x28, 0x24, 0x1a, 0x2b, 0x2d, 0x16, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00}; // ext_name param to osPfsFindFile (four total bytes, but only one is setable) const u8 gExtCode[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // new file start? void func_800B45E0(s32 arg0) { CourseTimeTrialRecords* courseTimeTrialRecordsPtr = &gSaveData.allCourseTimeTrialRecords \ .cupRecords[arg0 / 4] \ .courseRecords[arg0 % 4]; courseTimeTrialRecordsPtr->checksum = checksum_time_trial_records(arg0); osEepromLongWrite(&gSIEventMesgQueue, EEPROM_ADDR(courseTimeTrialRecordsPtr), (u8 *)courseTimeTrialRecordsPtr, sizeof(CourseTimeTrialRecords)); } void write_save_data_grand_prix_points_and_sound_mode(void) { Stuff *main = &gSaveData.main; main->checksum[1] = compute_save_data_checksum_1(); main->checksum[2] = compute_save_data_checksum_2(); osEepromLongWrite(&gSIEventMesgQueue, EEPROM_ADDR(main), (u8 *) main, sizeof(Stuff)); } void func_800B46D0(void) { s32 i; for (i = 0; i < 16; i++) { func_800B4728(i); func_800B559C(i); } reset_save_data_grand_prix_points_and_sound_mode(); update_save_data_backup(); } void func_800B4728(s32 arg0) { s32 i, j; CourseTimeTrialRecords* courseTimeTrialRecords; courseTimeTrialRecords = &gSaveData.allCourseTimeTrialRecords \ .cupRecords[arg0 / 4] \ .courseRecords[arg0 % 4]; for (i = 0; i < 5; i++) { for (j = 0; j < 3; j++) { courseTimeTrialRecords->records[i][j] = D_800F2E60[j]; } } for (i = 0; i < 3; i++) { courseTimeTrialRecords->records[5][i] = D_800F2E60[i]; } courseTimeTrialRecords->unknownBytes[0] = 0; courseTimeTrialRecords->checksum = checksum_time_trial_records(arg0); func_800B45E0(arg0); } void reset_save_data_grand_prix_points_and_sound_mode(void) { s32 cup_index; Stuff *main = &gSaveData.main; for (cup_index = 0; cup_index < 4; cup_index++) { main->grandPrixPoints[cup_index] = 0; } main->soundMode = SOUND_STEREO; gSoundMode = SOUND_STEREO; func_800B44BC(); write_save_data_grand_prix_points_and_sound_mode(); } // create a magic number based on the time trial records u8 checksum_time_trial_records(s32 courseIdx) { s32 j; s32 i; s32 ret; u8 *records = gSaveData.allCourseTimeTrialRecords \ .cupRecords[courseIdx / 4] \ .courseRecords[courseIdx % 4] \ .records[0]; ret = 0; for (i = 0; i < 7; i++) { for (j = 0; j < 3; j++) { ret += *(records + i*3 + j) * (j + 1) + i; } } return ret % 256; } u8 compute_save_data_checksum_1(void) { u8 *grandPrixPoints = (u8 *) &gSaveData.main.grandPrixPoints; s32 i; s32 crc = 0; for (i = 0; i < 5; i++) { crc += ((grandPrixPoints[i] + 1) * (i + 1)) + i; } return crc % 0x100; } u8 compute_save_data_checksum_2(void) { s32 tmp = gSaveData.main.checksum[1] + 90; return (tmp % 256); } void load_save_data(void) { s32 i; osEepromLongRead(&gSIEventMesgQueue, EEPROM_ADDR(&gSaveData), (u8 *)&gSaveData, sizeof(SaveData)); // 16: 4 cup records * 4 course records? for (i = 0; i < 16; i++) { func_800B4A9C(i); } validate_save_data(); gSoundMode = gSaveData.main.soundMode; if (gSoundMode >= NUM_SOUND_MODES) { gSoundMode = SOUND_MONO; } } void func_800B4A9C(s32 course) { OnlyBestTimeTrialRecords *test; CourseTimeTrialRecords *sp24; s32 i; if ((func_800B4EB4(0, course) & 0xFFFFF) < 0x927C0U) { gSaveData.allCourseTimeTrialRecords .cupRecords[course / 4] .courseRecords[course % 4] .unknownBytes[0] = 1; } sp24 = &gSaveData.allCourseTimeTrialRecords.cupRecords[course / 4].courseRecords[course % 4]; func_800B4FB0(course); if(sp24) {} if (sp24->checksum != checksum_time_trial_records(course)) { func_800B4728(course); if (func_800B58C4(course) == 0) { s32 a3 = 0; test = &gSaveData.onlyBestTimeTrialRecords[course / 8]; for (i = 0; i < 3; i++) { sp24->records[TIME_TRIAL_3LAP_RECORD_1][i] = test->bestThreelaps[course % 8][i]; sp24->records[TIME_TRIAL_1LAP_RECORD][i] = test->bestSinglelaps[course % 8][i]; // This is checking (in a roundabout way) if the given record // is the default value of 0x927C0 if (sp24->records[TIME_TRIAL_3LAP_RECORD_1][i] == D_800F2E60[i]) { if (sp24->records[TIME_TRIAL_1LAP_RECORD][i] == D_800F2E60[i]) { a3 += 1; } } } if (a3 == 3) { sp24->unknownBytes[0] = 0; } else { sp24->unknownBytes[0] = 1; } func_800B45E0(course); } // L800B4C78 func_800B559C(course); } else if (func_800B58C4(course)) { // L800B4C88 func_800B559C(course); } } void validate_save_data(void) { s32 cup_index; Stuff *main = &gSaveData.main; Stuff *backup = &gSaveData.backup; if (main->checksum[1] != (compute_save_data_checksum_1()) || (main->checksum[2] != compute_save_data_checksum_2())) { reset_save_data_grand_prix_points_and_sound_mode(); if (validate_save_data_checksum_backup() == 0) { for (cup_index = 0; cup_index < 4; cup_index++) { main->grandPrixPoints[cup_index] = backup->grandPrixPoints[cup_index]; } main->soundMode = backup->soundMode; main->checksum[1] = compute_save_data_checksum_backup_1(); main->checksum[2] = compute_save_data_checksum_backup_2(); osEepromLongWrite(&gSIEventMesgQueue, EEPROM_ADDR(main), (u8 *) main, sizeof(Stuff)); } update_save_data_backup(); return; } if (validate_save_data_checksum_backup() != 0) { update_save_data_backup(); } } void populate_time_trial_record(u8 *timeTrialRecord, u32 time, s32 characterId) { u32 timeRightShift8 = time >> 8; u32 timeRightShift16 = timeRightShift8 >> 8; s16 timeRightShift8Duplicate; u16 timeRightShift16Duplicate; timeRightShift16Duplicate = timeRightShift16; timeTrialRecord[0] = time & 0xFF; timeTrialRecord[1] = (timeRightShift8Duplicate = timeRightShift8); timeTrialRecord[2] = (timeRightShift16Duplicate & 0xF) + ((characterId & 7) << 4); } // combine time trial record u8[3] into the lower 24 bits of a word [xx221100] u32 func_800B4DF4(u8 *arr) { s32 a, b, c; a = arr[0]; b = arr[1]; c = arr[2]; return (a + (b << 8) + (c << 16)) & 0x00FFFFFF; } // Get a time trial record, infer course index s32 func_800B4E24(s32 recordIndex) { return func_800B4DF4( gSaveData.allCourseTimeTrialRecords \ .cupRecords[(((gCupSelection * 4) + gCupCourseSelection) / 4)] \ .courseRecords[(((gCupSelection * 4) + gCupCourseSelection) % 4)] \ .records[recordIndex] ); } // Get a time trial record, but take the course index as an argument u32 func_800B4EB4(s32 recordIndex, s32 courseIndex) { return func_800B4DF4( gSaveData.allCourseTimeTrialRecords \ .cupRecords[(courseIndex / 4)] \ .courseRecords[(courseIndex % 4)] \ .records[recordIndex] ); } // Get Best Lap record of the inferred course index s32 func_800B4F2C(void) { return func_800B4DF4( gSaveData.allCourseTimeTrialRecords \ .cupRecords[(((gCupSelection * 4) + gCupCourseSelection) / 4)] \ .courseRecords[(((gCupSelection * 4) + gCupCourseSelection) % 4)] \ .records[TIME_TRIAL_1LAP_RECORD] ); } // Get the best single lap time record of the given course index s32 func_800B4FB0(s32 courseIndex) { return func_800B4DF4( gSaveData.allCourseTimeTrialRecords \ .cupRecords[(courseIndex / 4)] \ .courseRecords[(courseIndex % 4)] \ .records[TIME_TRIAL_1LAP_RECORD] ); } s32 func_800B5020(u32 time, s32 charId) { UNUSED s32 stackPadding[3]; s32 course; // sp30 s32 i; s32 j; CourseTimeTrialRecords *tt; course = gCupSelection * 4 + gCupCourseSelection; tt = &gSaveData.allCourseTimeTrialRecords .cupRecords[course / 4] .courseRecords[course % 4]; i = 0; for (; i < 5; i++) { if (time < (func_800B4DF4(tt->records[i]) & 0x000FFFFF)) { break; } } // `time` is: // Not less than any records // Only less than the 1-lap record // Either way, we aren't going to update any records if (i >= 5) { return -1; } for (j = TIME_TRIAL_3LAP_RECORD_5; j > i; j--) { tt->records[j][0] = tt->records[j - 1][0]; tt->records[j][1] = tt->records[j - 1][1]; tt->records[j][2] = tt->records[j - 1][2]; } populate_time_trial_record(tt->records[i], time, charId); tt->unknownBytes[0] = 1; func_800B45E0(course); return i; } #ifdef NON_MATCHING /** * This one is some stack weirdness. If you remove the padding a ton of * stack locations are wrong. Adding more padding doesn't help matters * and no amount of reordering of the function variables fixes it either. **/ s32 func_800B5218(void) { u8 *recordPointer; s32 prevLapTime; s32 fastestLapIndex; s32 recordIndex; UNUSED s32 padding; s32 thisLapTime; s32 checkLapIndex; s32 character; s32 lapBitmask; recordIndex = (gCupSelection * 4) + gCupCourseSelection; recordPointer = &gSaveData.allCourseTimeTrialRecords.cupRecords[recordIndex / 4].courseRecords[recordIndex % 4].records[0][0]; lapBitmask = 1; fastestLapIndex = 0; character = *gCharacterSelections; for (checkLapIndex = 1; checkLapIndex != 3; checkLapIndex++) { prevLapTime = playerHUD->lapDurations[checkLapIndex]; thisLapTime = playerHUD->lapDurations[fastestLapIndex]; if (prevLapTime < thisLapTime) { lapBitmask = 1 << checkLapIndex; fastestLapIndex = checkLapIndex; } else if (thisLapTime == prevLapTime) { lapBitmask |= 1 << checkLapIndex; } } if (playerHUD->lapDurations[fastestLapIndex] < (func_800B4F2C() & 0xFFFFF)) { populate_time_trial_record(recordPointer + 0xF, playerHUD->lapDurations[fastestLapIndex], character); recordPointer[0x12] = 1; func_800B45E0(recordIndex); return lapBitmask; } else { return 0; } } #else GLOBAL_ASM("asm/non_matchings/save/func_800B5218.s") #endif void func_800B536C(s32 arg0) { u8 *points; u8 tmp; s32 tmp2; if (arg0 >= 0) { points = &gSaveData.main.grandPrixPoints[gCCSelection]; tmp = func_800B54EC(gCupSelection, *points); tmp2 = 3 - arg0; if ((arg0 < 3) && (tmp < (3-arg0))) { *points = func_800B5508(gCupSelection, *points, tmp2); write_save_data_grand_prix_points_and_sound_mode(); update_save_data_backup(); } } } void func_800B5404(s32 arg0, s32 arg1) { u8 *points; s32 temp_a0; s32 temp; int temp2; UNUSED s32 pad; if (arg0 >= 0) { temp2 = arg1 / 4; points = &gSaveData.main.grandPrixPoints[arg1 % 4]; temp = func_800B54EC(temp2, *points); if ((arg0 < 3) && (temp < (temp_a0 = 3 - arg0))) { *points = func_800B5508(temp2, *points, temp_a0); write_save_data_grand_prix_points_and_sound_mode(); update_save_data_backup(); } } } // Get Grand Prix points for a given cup and CC mode u8 func_800B54C0(s32 cup, s32 cc_mode) { return func_800B54EC(cup, gSaveData.main.grandPrixPoints[cc_mode]); } // Get Grand Prix points scored for a given cup u8 func_800B54EC(s32 cup, s32 ccGrandPrixPoints) { s32 cup_index = cup * 2; u32 cup_points = ccGrandPrixPoints; cup_points &= (3 << cup_index); cup_points >>= cup_index; cup_points &= 0xFF; return cup_points; } // Generate a new CC Grand Prix Points entry with points_scored // placed in the given cup's location u8 func_800B5508(s32 cup, s32 ccGrandPrixPoints, s32 points_scored) { s32 cup_index = cup * 2; points_scored <<= cup_index; ccGrandPrixPoints &= ~(3 << cup_index); return (ccGrandPrixPoints | points_scored); } // Check if all 4 cups have gold cups scored // for a given CC mode s32 func_800B5530(s32 cc_mode) { if (gSaveData.main.grandPrixPoints[cc_mode] == 0xFF) { return 1; } return 0; } // Check if the 150CC mode has all 4 gold cups s32 func_800B555C(void) { return func_800B5530(CC_150); } // Check if the Extra mode has all 4 gold cups s32 func_800B557C(void) { return func_800B5530(CC_EXTRA); } void func_800B559C(s32 arg0) { CourseTimeTrialRecords *courseRecord; OnlyBestTimeTrialRecords *bestRecord; s32 x = arg0 / 8; s32 i; s32 j; for (i = x * 8; i < ((x * 8) + 8); i++) { bestRecord = &gSaveData.onlyBestTimeTrialRecords[x]; courseRecord = &gSaveData.allCourseTimeTrialRecords.cupRecords[i / 4].courseRecords[i % 4]; if (courseRecord->checksum != checksum_time_trial_records(i)) { for (j = 0; j < 3; j++) { bestRecord->bestThreelaps[i % 8][j] = D_800F2E60[j]; bestRecord->bestSinglelaps[i % 8][j] = D_800F2E60[j]; } } else { for (j = 0; j < 3; j++) { bestRecord->bestThreelaps[i % 8][j] = courseRecord->records[0][j]; bestRecord->bestSinglelaps[i % 8][j] = courseRecord->records[0][j + 0x0f]; } } } bestRecord = &gSaveData.onlyBestTimeTrialRecords[x]; bestRecord->unknownBytes[6] = func_800B578C(x); bestRecord->unknownBytes[7] = func_800B5888(x); osEepromLongWrite(&gSIEventMesgQueue, ((u32) (((u8 *) bestRecord) - ((u8 *) (&gSaveData)))) >> 3, bestRecord->bestThreelaps[0], 0x38); } #ifdef NON_MATCHING /** * This one is weird. Its some type of checksum calculator, seemingly for the * best time trial records. But the number of bytes it operates over is * odd. It calculates a checksum for 51 bytes in 17 byte chunks, but that doesn't line * up with anything in the save data cleanly. At that byte count it would get the 48 bytes * for the records plus 3 of the unknown bytes. * * But only unknown bytes 7 and 8 ever get set, so why the extra 3, and why in chunks of 17? **/ s32 func_800B578C(s32 arg0) { u8 *var_a2; s32 var_a0; s32 var_v0; s32 var_v1; var_a2 = &gSaveData.onlyBestTimeTrialRecords[arg0].bestThreelaps[0][0]; var_v1 = 0; for (var_v0 = 0; var_v0 < 3;) { ++var_v0; for (var_a0 = 0; var_a0 != 0x11; var_a0++) { if (var_a0) { } var_v1 += (((*var_a2++) + 1) * var_v0) + var_a0; } var_a2 += 0x11; } return (var_v1 % 256) & 0xFF; } #else GLOBAL_ASM("asm/non_matchings/save/func_800B578C.s") #endif s32 func_800B5888(s32 arg0) { s32 tmp = gSaveData.onlyBestTimeTrialRecords[arg0].unknownBytes[6] + 90; return (tmp % 256) & 0xFF; } s32 func_800B58C4(s32 arg0) { UNUSED s32 pad1; OnlyBestTimeTrialRecords *temp_v1; UNUSED s32 pad2; UNUSED s32 pad3; temp_v1 = &gSaveData.onlyBestTimeTrialRecords[arg0 / 8]; if ((temp_v1->unknownBytes[6] != (func_800B578C(arg0 / 8) ^ 0)) || (temp_v1->unknownBytes[7] != (func_800B5888(arg0 / 8) ^ 0))) { return 1; } return 0; } void update_save_data_backup(void) { s32 cup_index; Stuff *main = &gSaveData.main; Stuff *backup = &gSaveData.backup; for (cup_index = 0; cup_index < 4; cup_index++) { backup->grandPrixPoints[cup_index] = main->grandPrixPoints[cup_index]; } backup->soundMode = main->soundMode; backup->checksum[1] = compute_save_data_checksum_backup_1(); backup->checksum[2] = compute_save_data_checksum_backup_2(); osEepromLongWrite(&gSIEventMesgQueue, EEPROM_ADDR(backup), (u8 *) backup, sizeof(Stuff)); } u8 compute_save_data_checksum_backup_1(void) { u8 *backupGrandPrixPoints = gSaveData.backup.grandPrixPoints; s32 i; s32 crc = 0; for (i = 0; i < 5; i++) { crc += ((backupGrandPrixPoints[i] + 1) * (i + 1)) + i; } return crc % 0x100; } u8 compute_save_data_checksum_backup_2(void) { s32 tmp = gSaveData.backup.checksum[1] + 90; return (tmp % 256); } s32 validate_save_data_checksum_backup(void) { u8 *backupChecksum = gSaveData.backup.checksum; if (backupChecksum[1] != compute_save_data_checksum_backup_1() || backupChecksum[2] != compute_save_data_checksum_backup_2()) { return 1; } return 0; } // Check if controller has a Controller Pak connected. // Return PAK if it does, otherwise return NO_PAK. s32 check_for_controller_pak(s32 controller) { u8 controllerBitpattern; UNUSED s32 phi_v0; if ((controller >= MAXCONTROLLERS) || (controller < 0)) { return NO_PAK; } osPfsIsPlug(&gSIEventMesgQueue, &controllerBitpattern); if ((controllerBitpattern & (1 << controller)) != 0) { return PAK; } return NO_PAK; } // gives status info about controller pak insterted in controller 1 s32 controller_pak_1_status(void) { if (gControllerPak1State) { switch (osPfsFindFile(&gControllerPak1FileHandle, gCompanyCode, gGameCode, (u8 *) gGameName, (u8 *) gExtCode, &gControllerPak1FileNote)) { case PFS_NO_ERROR: return PFS_NO_ERROR; case PFS_ERR_INVALID: break; case PFS_ERR_NEW_PACK: gControllerPak1State = BAD; break; default: gControllerPak1State = BAD; break; } } if (!gControllerPak1State) { s32 errorCode; if (check_for_controller_pak(CONTROLLER_1) == NO_PAK) { return PFS_NO_PAK_INSERTED; } errorCode = osPfsInit(&gSIEventMesgQueue, &gControllerPak1FileHandle, CONTROLLER_1); if (errorCode) { switch(errorCode) { case PFS_ERR_NOPACK: case PFS_ERR_DEVICE: return PFS_NO_PAK_INSERTED; case PFS_ERR_ID_FATAL: return PFS_PAK_BAD_READ; default: case PFS_ERR_CONTRFAIL: return PFS_PAK_BAD_READ; } } gControllerPak1State = OK; if (osPfsFindFile(&gControllerPak1FileHandle, gCompanyCode, gGameCode, (u8 *) gGameName, (u8 *) gExtCode, &gControllerPak1FileNote) == PFS_NO_ERROR) { return PFS_NO_ERROR; } if (osPfsNumFiles(&gControllerPak1FileHandle, &gControllerPak1NumFilesUsed, &gControllerPak1MaxWriteableFiles) != PFS_NO_ERROR) { return PFS_PAK_BAD_READ; } if (osPfsFreeBlocks(&gControllerPak1FileHandle, &gControllerPak1NumPagesFree) != PFS_NO_ERROR) { return PFS_PAK_BAD_READ; } gControllerPak1NumPagesFree = gControllerPak1NumPagesFree >> 8; } if (gControllerPak1MaxWriteableFiles >= gControllerPak1NumFilesUsed) { return PFS_FILE_OVERFLOW; } if (gControllerPak1NumPagesFree >= 0x79) { return PFS_INVALID_DATA; } return PFS_FILE_OVERFLOW; } // gives status info about controller pak insterted in controller 2 s32 controller_pak_2_status(void) { s32 stateBorrow = sControllerPak2State; if (stateBorrow) { switch (osPfsFindFile(&gControllerPak2FileHandle, gCompanyCode, gGameCode, (u8 *) gGameName, (u8 *) gExtCode, &gControllerPak2FileNote)) { case PFS_NO_ERROR: return PFS_NO_ERROR; case PFS_ERR_INVALID: return PFS_INVALID_DATA; default: case PFS_ERR_NEW_PACK: sControllerPak2State = BAD; stateBorrow = BAD; } } if (!stateBorrow) { s32 errorCode; if (check_for_controller_pak(CONTROLLER_2) == NO_PAK) { return PFS_NO_PAK_INSERTED; } errorCode = osPfsInit(&gSIEventMesgQueue, &gControllerPak2FileHandle, CONTROLLER_2); if (errorCode) { switch (errorCode) { case PFS_ERR_NOPACK: case PFS_ERR_DEVICE: return PFS_NO_PAK_INSERTED; case PFS_ERR_ID_FATAL: return PFS_PAK_BAD_READ; case PFS_ERR_CONTRFAIL: default: return PFS_PAK_BAD_READ; } } sControllerPak2State = OK; switch (osPfsFindFile(&gControllerPak2FileHandle, gCompanyCode, gGameCode, (u8 *) gGameName, (u8 *) gExtCode, &gControllerPak2FileNote)) { case PFS_NO_ERROR: return PFS_NO_ERROR; case PFS_ERR_INVALID: return PFS_INVALID_DATA; case PFS_ERR_NEW_PACK: default: return PFS_PAK_BAD_READ; } } } s32 func_800B5F30(void) { s32 errorCode; if (gControllerPak1State) { return PFS_PAK_STATE_OK; } if (check_for_controller_pak(CONTROLLER_1) != NO_PAK) { errorCode = osPfsInit(&gSIEventMesgQueue, &gControllerPak1FileHandle, CONTROLLER_1); if (osPfsNumFiles(&gControllerPak1FileHandle, &gControllerPak1NumFilesUsed, &gControllerPak1MaxWriteableFiles) != PFS_NO_ERROR) { gControllerPak1State = BAD; return PFS_NUM_FILES_ERROR; } if (osPfsFreeBlocks(&gControllerPak1FileHandle, &gControllerPak1NumPagesFree) != PFS_NO_ERROR) { gControllerPak1State = BAD; return PFS_FREE_BLOCKS_ERROR; } gControllerPak1NumPagesFree = gControllerPak1NumPagesFree >> 8; if (errorCode == PFS_NO_ERROR) { gControllerPak1State = OK; } return errorCode; } return PAK_NOT_INSERTED; } s32 func_800B6014(void) { s32 errorCode; if (sControllerPak2State) { return PFS_PAK_STATE_OK; } if (check_for_controller_pak(CONTROLLER_2) != NO_PAK) { errorCode = osPfsInit(&gSIEventMesgQueue, &gControllerPak2FileHandle, CONTROLLER_2); if (errorCode == PFS_NO_ERROR) { sControllerPak2State = OK; } return errorCode; } return PAK_NOT_INSERTED; } s32 func_800B6088(s32 arg0) { struct_8018EE10_entry* temp_v1; temp_v1 = &D_8018EE10[arg0]; temp_v1->checksum = func_800B6828(arg0); return osPfsReadWriteFile(&gControllerPak1FileHandle, gControllerPak1FileNote, PFS_WRITE, arg0 * 0x80 /* 0x80 == sizeof(struct_8018EE10_entry) */, sizeof(struct_8018EE10_entry), (u8*) temp_v1); } #ifdef NON_MATCHING // Register allocation issues u8 func_800B60E8(s32 page) { s32 multiplier = page + 1; s32 checksum = 0; u8 *addr = &((u8 *) D_800DC714)[page << 8]; s32 i; for (i = 0; i < 0x100; i++) { checksum = (*addr++ * multiplier + i) + checksum; } return checksum; } #else GLOBAL_ASM("asm/non_matchings/save/func_800B60E8.s") #endif s32 func_800B6178(s32 arg0) { s32 var_v0; s32 var_s0; struct_8018EE10_entry *temp_s3; switch (arg0) { case 0: case 1: break; default: return -1; } if (gGamestate == 4) { func_800051C4(); } temp_s3 = &D_8018EE10[arg0]; temp_s3->ghostDataSaved = 0; var_v0 = func_800B6088(arg0); if (var_v0 != 0) { temp_s3->ghostDataSaved = 0; for (var_s0 = 0; var_s0 < 0x3C; var_s0++) { temp_s3->unk_07[var_s0] = var_s0; } } else { var_v0 = osPfsReadWriteFile(&gControllerPak1FileHandle, gControllerPak1FileNote, 1U, (arg0 * 0x3C00) + 0x100, 0x00003C00, (u8 *) D_800DC714); if (var_v0 == 0) { temp_s3->ghostDataSaved = 1; if (gGamestate == 4) { temp_s3->courseIndex = (gCupSelection * 4) + gCupCourseSelection; } temp_s3->unk_00 = D_80162DFC; temp_s3->characterId = (u8) D_80162DE0; for (var_s0 = 0; var_s0 < 0x3C; var_s0++) { temp_s3->unk_07[var_s0] = func_800B60E8(var_s0); } var_v0 = func_800B6088(arg0); } if (var_v0 != 0) { temp_s3->ghostDataSaved = 0; for (var_s0 = 0; var_s0 < 0x3C; var_s0++) { temp_s3->unk_07[var_s0] = var_s0; } } } return var_v0; } s32 func_800B6348(s32 arg0) { if ((D_8018EE10[0].ghostDataSaved != 0) && (arg0 == D_8018EE10[0].courseIndex)) { return 0; } if ((D_8018EE10[1].ghostDataSaved != 0) && (arg0 == D_8018EE10[1].courseIndex)) { return 1; } return 0; } s32 func_800B639C(s32 arg0) { if ((D_8018EE10[0].ghostDataSaved != 0) && (arg0 == D_8018EE10[0].courseIndex)) { return 0; } if ((D_8018EE10[1].ghostDataSaved != 0) && (arg0 == D_8018EE10[1].courseIndex)) { return 1; } return -1; } s32 func_800B63F0(s32 arg0) { s32 temp_s0; u8* phi_s1; s32 phi_s3; func_800051C4(); D_80162DD6 = 1; func_80005AE8(gPlayerThree); phi_s3 = 0; if (((gCupSelection * 4) + gCupCourseSelection) != D_8018EE10[arg0].courseIndex) { phi_s3 = 2; } else if (D_80162DFC != D_8018EE10[arg0].unk_00) { phi_s3 = 3; } else { if (D_80162DE0 != (u8) D_8018EE10[arg0].characterId) { phi_s3 = 4; } else { temp_s0 = 0; phi_s1 = (u8*) &D_8018EE10[arg0]; while (temp_s0 < 0x3C) { if (phi_s1[7] != func_800B60E8(temp_s0)) { phi_s3 = 1; break; } ++phi_s1; ++temp_s0; } } } return phi_s3; } s32 func_800B64EC(s32 arg0) { s32 temp_s0; s32 temp_v0; u8 *phi_s1; if ((arg0 != 0) && (arg0 != 1)) { return -1; } temp_v0 = osPfsReadWriteFile(&gControllerPak1FileHandle, gControllerPak1FileNote, PFS_READ, (arg0 * 0x3C00) + 0x100, 0x3C00, (u8 *) D_800DC714); if (temp_v0 == 0) { phi_s1 = (u8 *) &D_8018EE10[arg0]; temp_s0 = 0; while (1) { if (phi_s1[7] != func_800B60E8(temp_s0)) { D_8018EE10[arg0].ghostDataSaved = 0; return -2; } ++phi_s1; if ((++temp_s0) == 0x3C) { func_8000522C(); D_80162DD4 = 0; D_80162DE0 = (s32) D_8018EE10[arg0].characterId; D_80162DFC = D_8018EE10[arg0].unk_00; break; } } } return temp_v0; } s32 func_800B65F4(s32 arg0, s32 arg1) { UNUSED s32 stackPadding; s32 i; s32 writeStatus; struct_8018EE10_entry *temp_s3; switch (arg0) { case 0: case 1: break; default: return -1; } writeStatus = osPfsReadWriteFile(&gControllerPak2FileHandle, gControllerPak2FileNote, 0U, (arg0 * 0x3C00) + 0x100, 0x00003C00, (u8 *) D_800DC714); if (writeStatus == 0) { temp_s3 = &D_8018D9C0->arr[arg0]; for (i = 0; i < 0x3C; i++) { if (temp_s3->unk_07[i] != func_800B60E8(i)) { temp_s3->ghostDataSaved = 0; return -2; } } D_80162DE0 = temp_s3->characterId; D_80162DFC = temp_s3->unk_00; D_8018EE10[arg1].courseIndex = temp_s3->courseIndex; } return writeStatus; } void func_800B6708(void) { s32 temp_s0; osPfsReadWriteFile(&gControllerPak1FileHandle, gControllerPak1FileNote, PFS_READ, 0, 0x100 /* 2*sizeof(struct_8018EE10_entry) ? */, (u8*) &D_8018EE10); for (temp_s0 = 0; temp_s0 < 2; ++temp_s0) { if (D_8018EE10[temp_s0].checksum != func_800B6828(temp_s0)) { D_8018EE10[temp_s0].ghostDataSaved = 0; } } } void func_800B6798(void) { s32 temp_s0; u8* tmp; tmp = (u8*) D_8018D9C0; osPfsReadWriteFile(&gControllerPak2FileHandle, gControllerPak2FileNote, PFS_READ, 0, 0x100 /* 2*sizeof(struct_8018EE10_entry) ? */, tmp); for (temp_s0 = 0; temp_s0 < 2; ++temp_s0) { // if (D_8018D9C0[temp_s0]->checksum != func_800B68F4(temp_s0)) { // D_8018D9C0[temp_s0]->ghostDataSaved = 0; // } if ( ((struct_8018EE10_entry*) (tmp + (temp_s0 << 7)))->checksum != func_800B68F4(temp_s0)) { ((struct_8018EE10_entry*) (tmp + (temp_s0 << 7)))->ghostDataSaved = 0; } } } u8 func_800B6828(s32 arg0) { u32 checksum = 0; u8 *addr = (u8 *) &D_8018EE10[arg0]; s32 i; for (i = 0; i < 0x43; i++) { checksum += ((addr[i] * (arg0 + 1)) + i); } return checksum; } #ifdef NON_MATCHING u8 func_800B68F4(s32 arg0) { s32 multiplier = arg0 + 1; u32 checksum; s32 i; checksum = 0; for (i = 0; i < 0x43; i++) { u8 *addr = (u8 *) (&D_8018D9C0[arg0]); checksum += addr[i] * multiplier + i; } return checksum; } #else GLOBAL_ASM("asm/non_matchings/save/func_800B68F4.s") #endif #ifdef NON_MATCHING // Stack nonsense. As it stands now there's too much on the stack, but everything else is fine // But if you try to reduce the stack you get bigger issues. No idea what's going on s32 func_800B69BC(s32 arg0) { s32 i; s32 offset; struct_8018EE10_entry *plz; offset = sizeof(struct_8018EE10_entry); offset *= arg0; plz = &D_8018EE10[arg0]; plz->ghostDataSaved = FALSE; plz->courseIndex = 0; plz->characterId = 0; for (i = 0; i < 60; i++) { plz->unk_07[i] = i; } plz->checksum = func_800B6828(arg0); return osPfsReadWriteFile(&gControllerPak1FileHandle, gControllerPak1FileNote, PFS_WRITE, offset, sizeof(struct_8018EE10_entry), (u8 *)plz); } #else GLOBAL_ASM("asm/non_matchings/save/func_800B69BC.s") #endif s32 func_800B6A68(void) { UNUSED s32 pad; s32 ret; s32 i; ret = osPfsAllocateFile(&gControllerPak1FileHandle, gCompanyCode, gGameCode, (u8 *)&gGameName, (u8 *)&gExtCode, 0x7900, &gControllerPak1FileNote); if (ret == 0) { for (i = 0; i < 2; i++) { func_800B69BC(i); } } return ret; } void func_8800B6AF8(void) { if (check_for_controller_pak(CONTROLLER_1) && osPfsInit(&gSIEventMesgQueue, &gControllerPak1FileHandle, 0) == 0 && osPfsFindFile(&gControllerPak1FileHandle, gCompanyCode, gGameCode, (u8 *)gGameName, (u8 *)gExtCode, &gControllerPak1FileNote) && osPfsNumFiles(&gControllerPak1FileHandle, &gControllerPak1NumFilesUsed, &gControllerPak1MaxWriteableFiles) == 0 && gControllerPak1MaxWriteableFiles < gControllerPak1NumFilesUsed && osPfsFreeBlocks(&gControllerPak1FileHandle, &gControllerPak1NumPagesFree) == 0 ) { gControllerPak1NumPagesFree >>= 8; if (gControllerPak1NumPagesFree >= 0x79) { func_800B6A68(); } } }