mirror of https://github.com/n64decomp/mk64.git
1057 lines
32 KiB
C
1057 lines
32 KiB
C
#include <ultra64.h>
|
|
#include <macros.h>
|
|
#include <defines.h>
|
|
|
|
#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();
|
|
}
|
|
}
|
|
}
|