sm64

A Super Mario 64 decompilation
Log | Files | Refs | README | LICENSE

save_file.c (21008B)


      1 #include <ultra64.h>
      2 
      3 #include "sm64.h"
      4 #include "game_init.h"
      5 #include "main.h"
      6 #include "engine/math_util.h"
      7 #include "area.h"
      8 #include "level_update.h"
      9 #include "save_file.h"
     10 #include "sound_init.h"
     11 #include "level_table.h"
     12 #include "course_table.h"
     13 #include "rumble_init.h"
     14 
     15 #define MENU_DATA_MAGIC 0x4849
     16 #define SAVE_FILE_MAGIC 0x4441
     17 
     18 STATIC_ASSERT(sizeof(struct SaveBuffer) == EEPROM_SIZE, "eeprom buffer size must match");
     19 
     20 extern struct SaveBuffer gSaveBuffer;
     21 
     22 struct WarpCheckpoint gWarpCheckpoint;
     23 
     24 s8 gMainMenuDataModified;
     25 s8 gSaveFileModified;
     26 
     27 u8 gLastCompletedCourseNum = COURSE_NONE;
     28 u8 gLastCompletedStarNum = 0;
     29 s8 sUnusedGotGlobalCoinHiScore = FALSE;
     30 u8 gGotFileCoinHiScore = FALSE;
     31 u8 gCurrCourseStarFlags = 0;
     32 
     33 u8 gSpecialTripleJump = FALSE;
     34 
     35 #define STUB_LEVEL(_0, _1, courseenum, _3, _4, _5, _6, _7, _8) courseenum,
     36 #define DEFINE_LEVEL(_0, _1, courseenum, _3, _4, _5, _6, _7, _8, _9, _10) courseenum,
     37 
     38 s8 gLevelToCourseNumTable[] = {
     39     #include "levels/level_defines.h"
     40 };
     41 #undef STUB_LEVEL
     42 #undef DEFINE_LEVEL
     43 
     44 STATIC_ASSERT(ARRAY_COUNT(gLevelToCourseNumTable) == LEVEL_COUNT - 1,
     45               "change this array if you are adding levels");
     46 
     47 // This was probably used to set progress to 100% for debugging, but
     48 // it was removed from the release ROM.
     49 static void stub_save_file_1(void) {
     50     UNUSED u8 filler[4];
     51 }
     52 
     53 /**
     54  * Read from EEPROM to a given address.
     55  * The EEPROM address is computed using the offset of the destination address from gSaveBuffer.
     56  * Try at most 4 times, and return 0 on success. On failure, return the status returned from
     57  * osEepromLongRead. It also returns 0 if EEPROM isn't loaded correctly in the system.
     58  */
     59 static s32 read_eeprom_data(void *buffer, s32 size) {
     60     s32 status = 0;
     61 
     62     if (gEepromProbe != 0) {
     63         s32 triesLeft = 4;
     64         u32 offset = (u32)((u8 *) buffer - (u8 *) &gSaveBuffer) / 8;
     65 
     66         do {
     67 #if ENABLE_RUMBLE
     68             block_until_rumble_pak_free();
     69 #endif
     70             triesLeft--;
     71             status = osEepromLongRead(&gSIEventMesgQueue, offset, buffer, size);
     72 #if ENABLE_RUMBLE
     73             release_rumble_pak_control();
     74 #endif
     75         } while (triesLeft > 0 && status != 0);
     76     }
     77 
     78     return status;
     79 }
     80 
     81 /**
     82  * Write data to EEPROM.
     83  * The EEPROM address is computed using the offset of the source address from gSaveBuffer.
     84  * Try at most 4 times, and return 0 on success. On failure, return the status returned from
     85  * osEepromLongWrite. Unlike read_eeprom_data, return 1 if EEPROM isn't loaded.
     86  */
     87 static s32 write_eeprom_data(void *buffer, s32 size) {
     88     s32 status = 1;
     89 
     90     if (gEepromProbe != 0) {
     91         s32 triesLeft = 4;
     92         u32 offset = (u32)((u8 *) buffer - (u8 *) &gSaveBuffer) >> 3;
     93 
     94         do {
     95 #if ENABLE_RUMBLE
     96             block_until_rumble_pak_free();
     97 #endif
     98             triesLeft--;
     99             status = osEepromLongWrite(&gSIEventMesgQueue, offset, buffer, size);
    100 #if ENABLE_RUMBLE
    101             release_rumble_pak_control();
    102 #endif
    103         } while (triesLeft > 0 && status != 0);
    104     }
    105 
    106     return status;
    107 }
    108 
    109 /**
    110  * Sum the bytes in data to data + size - 2. The last two bytes are ignored
    111  * because that is where the checksum is stored.
    112  */
    113 static u16 calc_checksum(u8 *data, s32 size) {
    114     u16 chksum = 0;
    115 
    116     while (size-- > 2) {
    117         chksum += *data++;
    118     }
    119     return chksum;
    120 }
    121 
    122 /**
    123  * Verify the signature at the end of the block to check if the data is valid.
    124  */
    125 static s32 verify_save_block_signature(void *buffer, s32 size, u16 magic) {
    126     struct SaveBlockSignature *sig = (struct SaveBlockSignature *) ((size - 4) + (u8 *) buffer);
    127 
    128     if (sig->magic != magic) {
    129         return FALSE;
    130     }
    131     if (sig->chksum != calc_checksum(buffer, size)) {
    132         return FALSE;
    133     }
    134     return TRUE;
    135 }
    136 
    137 /**
    138  * Write a signature at the end of the block to make sure the data is valid
    139  */
    140 static void add_save_block_signature(void *buffer, s32 size, u16 magic) {
    141     struct SaveBlockSignature *sig = (struct SaveBlockSignature *) ((size - 4) + (u8 *) buffer);
    142 
    143     sig->magic = magic;
    144     sig->chksum = calc_checksum(buffer, size);
    145 }
    146 
    147 /**
    148  * Copy main menu data from one backup slot to the other slot.
    149  */
    150 static void restore_main_menu_data(s32 srcSlot) {
    151     s32 destSlot = srcSlot ^ 1;
    152 
    153     // Compute checksum on source data
    154     add_save_block_signature(&gSaveBuffer.menuData[srcSlot], sizeof(gSaveBuffer.menuData[srcSlot]), MENU_DATA_MAGIC);
    155 
    156     // Copy source data to destination
    157     bcopy(&gSaveBuffer.menuData[srcSlot], &gSaveBuffer.menuData[destSlot], sizeof(gSaveBuffer.menuData[destSlot]));
    158 
    159     // Write destination data to EEPROM
    160     write_eeprom_data(&gSaveBuffer.menuData[destSlot], sizeof(gSaveBuffer.menuData[destSlot]));
    161 }
    162 
    163 static void save_main_menu_data(void) {
    164     if (gMainMenuDataModified) {
    165         // Compute checksum
    166         add_save_block_signature(&gSaveBuffer.menuData[0], sizeof(gSaveBuffer.menuData[0]), MENU_DATA_MAGIC);
    167 
    168         // Back up data
    169         bcopy(&gSaveBuffer.menuData[0], &gSaveBuffer.menuData[1], sizeof(gSaveBuffer.menuData[1]));
    170 
    171         // Write to EEPROM
    172         write_eeprom_data(gSaveBuffer.menuData, sizeof(gSaveBuffer.menuData));
    173 
    174         gMainMenuDataModified = FALSE;
    175     }
    176 }
    177 
    178 static void wipe_main_menu_data(void) {
    179     bzero(&gSaveBuffer.menuData[0], sizeof(gSaveBuffer.menuData[0]));
    180 
    181     // Set score ages for all courses to 3, 2, 1, and 0, respectively.
    182     gSaveBuffer.menuData[0].coinScoreAges[0] = 0x3FFFFFFF;
    183     gSaveBuffer.menuData[0].coinScoreAges[1] = 0x2AAAAAAA;
    184     gSaveBuffer.menuData[0].coinScoreAges[2] = 0x15555555;
    185 
    186     gMainMenuDataModified = TRUE;
    187     save_main_menu_data();
    188 }
    189 
    190 static s32 get_coin_score_age(s32 fileIndex, s32 courseIndex) {
    191     return (gSaveBuffer.menuData[0].coinScoreAges[fileIndex] >> (2 * courseIndex)) & 0x3;
    192 }
    193 
    194 static void set_coin_score_age(s32 fileIndex, s32 courseIndex, s32 age) {
    195     s32 mask = 0x3 << (2 * courseIndex);
    196 
    197     gSaveBuffer.menuData[0].coinScoreAges[fileIndex] &= ~mask;
    198     gSaveBuffer.menuData[0].coinScoreAges[fileIndex] |= age << (2 * courseIndex);
    199 }
    200 
    201 /**
    202  * Mark a coin score for a save file as the newest out of all save files.
    203  */
    204 static void touch_coin_score_age(s32 fileIndex, s32 courseIndex) {
    205     s32 i;
    206     u32 age;
    207     u32 currentAge = get_coin_score_age(fileIndex, courseIndex);
    208 
    209     if (currentAge != 0) {
    210         for (i = 0; i < NUM_SAVE_FILES; i++) {
    211             age = get_coin_score_age(i, courseIndex);
    212             if (age < currentAge) {
    213                 set_coin_score_age(i, courseIndex, age + 1);
    214             }
    215         }
    216 
    217         set_coin_score_age(fileIndex, courseIndex, 0);
    218         gMainMenuDataModified = TRUE;
    219     }
    220 }
    221 
    222 /**
    223  * Mark all coin scores for a save file as new.
    224  */
    225 static void touch_high_score_ages(s32 fileIndex) {
    226     s32 i;
    227 
    228     for (i = COURSE_NUM_TO_INDEX(COURSE_MIN); i <= COURSE_NUM_TO_INDEX(COURSE_STAGES_MAX); i++) {
    229         touch_coin_score_age(fileIndex, i);
    230     }
    231 }
    232 
    233 /**
    234  * Copy save file data from one backup slot to the other slot.
    235  */
    236 static void restore_save_file_data(s32 fileIndex, s32 srcSlot) {
    237     s32 destSlot = srcSlot ^ 1;
    238 
    239     // Compute checksum on source data
    240     add_save_block_signature(&gSaveBuffer.files[fileIndex][srcSlot],
    241                              sizeof(gSaveBuffer.files[fileIndex][srcSlot]), SAVE_FILE_MAGIC);
    242 
    243     // Copy source data to destination slot
    244     bcopy(&gSaveBuffer.files[fileIndex][srcSlot], &gSaveBuffer.files[fileIndex][destSlot],
    245           sizeof(gSaveBuffer.files[fileIndex][destSlot]));
    246 
    247     // Write destination data to EEPROM
    248     write_eeprom_data(&gSaveBuffer.files[fileIndex][destSlot],
    249                       sizeof(gSaveBuffer.files[fileIndex][destSlot]));
    250 }
    251 
    252 void save_file_do_save(s32 fileIndex) {
    253     if (gSaveFileModified) {
    254         // Compute checksum
    255         add_save_block_signature(&gSaveBuffer.files[fileIndex][0],
    256                                  sizeof(gSaveBuffer.files[fileIndex][0]), SAVE_FILE_MAGIC);
    257 
    258         // Copy to backup slot
    259         bcopy(&gSaveBuffer.files[fileIndex][0], &gSaveBuffer.files[fileIndex][1],
    260               sizeof(gSaveBuffer.files[fileIndex][1]));
    261 
    262         // Write to EEPROM
    263         write_eeprom_data(gSaveBuffer.files[fileIndex], sizeof(gSaveBuffer.files[fileIndex]));
    264 
    265         gSaveFileModified = FALSE;
    266     }
    267 
    268     save_main_menu_data();
    269 }
    270 
    271 void save_file_erase(s32 fileIndex) {
    272     touch_high_score_ages(fileIndex);
    273     bzero(&gSaveBuffer.files[fileIndex][0], sizeof(gSaveBuffer.files[fileIndex][0]));
    274 
    275     gSaveFileModified = TRUE;
    276     save_file_do_save(fileIndex);
    277 }
    278 
    279 //! Needs to be s32 to match on -O2, despite no return value.
    280 BAD_RETURN(s32) save_file_copy(s32 srcFileIndex, s32 destFileIndex) {
    281     UNUSED u8 filler[4];
    282 
    283     touch_high_score_ages(destFileIndex);
    284     bcopy(&gSaveBuffer.files[srcFileIndex][0], &gSaveBuffer.files[destFileIndex][0],
    285           sizeof(gSaveBuffer.files[destFileIndex][0]));
    286 
    287     gSaveFileModified = TRUE;
    288     save_file_do_save(destFileIndex);
    289 }
    290 
    291 void save_file_load_all(void) {
    292     s32 file;
    293     s32 validSlots;
    294 
    295     gMainMenuDataModified = FALSE;
    296     gSaveFileModified = FALSE;
    297 
    298     bzero(&gSaveBuffer, sizeof(gSaveBuffer));
    299     read_eeprom_data(&gSaveBuffer, sizeof(gSaveBuffer));
    300 
    301     // Verify the main menu data and create a backup copy if only one of the slots is valid.
    302     validSlots = verify_save_block_signature(&gSaveBuffer.menuData[0], sizeof(gSaveBuffer.menuData[0]), MENU_DATA_MAGIC);
    303     validSlots |= verify_save_block_signature(&gSaveBuffer.menuData[1], sizeof(gSaveBuffer.menuData[1]),MENU_DATA_MAGIC) << 1;
    304     switch (validSlots) {
    305         case 0: // Neither copy is correct
    306             wipe_main_menu_data();
    307             break;
    308         case 1: // Slot 0 is correct and slot 1 is incorrect
    309             restore_main_menu_data(0);
    310             break;
    311         case 2: // Slot 1 is correct and slot 0 is incorrect
    312             restore_main_menu_data(1);
    313             break;
    314     }
    315 
    316     for (file = 0; file < NUM_SAVE_FILES; file++) {
    317         // Verify the save file and create a backup copy if only one of the slots is valid.
    318         validSlots = verify_save_block_signature(&gSaveBuffer.files[file][0], sizeof(gSaveBuffer.files[file][0]), SAVE_FILE_MAGIC);
    319         validSlots |= verify_save_block_signature(&gSaveBuffer.files[file][1], sizeof(gSaveBuffer.files[file][1]), SAVE_FILE_MAGIC) << 1;
    320         switch (validSlots) {
    321             case 0: // Neither copy is correct
    322                 save_file_erase(file);
    323                 break;
    324             case 1: // Slot 0 is correct and slot 1 is incorrect
    325                 restore_save_file_data(file, 0);
    326                 break;
    327             case 2: // Slot 1 is correct and slot 0 is incorrect
    328                 restore_save_file_data(file, 1);
    329                 break;
    330         }
    331     }
    332 
    333     stub_save_file_1();
    334 }
    335 
    336 /**
    337  * Reload the current save file from its backup copy, which is effectively a
    338  * a cached copy of what has been written to EEPROM.
    339  * This is used after getting a game over.
    340  */
    341 void save_file_reload(void) {
    342     // Copy save file data from backup
    343     bcopy(&gSaveBuffer.files[gCurrSaveFileNum - 1][1], &gSaveBuffer.files[gCurrSaveFileNum - 1][0],
    344           sizeof(gSaveBuffer.files[gCurrSaveFileNum - 1][0]));
    345 
    346     // Copy main menu data from backup
    347     bcopy(&gSaveBuffer.menuData[1], &gSaveBuffer.menuData[0], sizeof(gSaveBuffer.menuData[0]));
    348 
    349     gMainMenuDataModified = FALSE;
    350     gSaveFileModified = FALSE;
    351 }
    352 
    353 /**
    354  * Update the current save file after collecting a star or a key.
    355  * If coin score is greater than the current high score, update it.
    356  */
    357 void save_file_collect_star_or_key(s16 coinScore, s16 starIndex) {
    358     s32 fileIndex = gCurrSaveFileNum - 1;
    359     s32 courseIndex = COURSE_NUM_TO_INDEX(gCurrCourseNum);
    360 
    361     s32 starFlag = 1 << starIndex;
    362     UNUSED s32 flags = save_file_get_flags();
    363 
    364     gLastCompletedCourseNum = courseIndex + 1;
    365     gLastCompletedStarNum = starIndex + 1;
    366     sUnusedGotGlobalCoinHiScore = FALSE;
    367     gGotFileCoinHiScore = FALSE;
    368 
    369     if (courseIndex >= COURSE_NUM_TO_INDEX(COURSE_MIN)
    370         && courseIndex <= COURSE_NUM_TO_INDEX(COURSE_STAGES_MAX)) {
    371         //! Compares the coin score as a 16 bit value, but only writes the 8 bit
    372         // truncation. This can allow a high score to decrease.
    373 
    374         if (coinScore > ((u16) save_file_get_max_coin_score(courseIndex) & 0xFFFF)) {
    375             sUnusedGotGlobalCoinHiScore = TRUE;
    376         }
    377 
    378         if (coinScore > save_file_get_course_coin_score(fileIndex, courseIndex)) {
    379             gSaveBuffer.files[fileIndex][0].courseCoinScores[courseIndex] = coinScore;
    380             touch_coin_score_age(fileIndex, courseIndex);
    381 
    382             gGotFileCoinHiScore = TRUE;
    383             gSaveFileModified = TRUE;
    384         }
    385     }
    386 
    387     switch (gCurrLevelNum) {
    388         case LEVEL_BOWSER_1:
    389             if (!(save_file_get_flags() & (SAVE_FLAG_HAVE_KEY_1 | SAVE_FLAG_UNLOCKED_BASEMENT_DOOR))) {
    390                 save_file_set_flags(SAVE_FLAG_HAVE_KEY_1);
    391             }
    392             break;
    393 
    394         case LEVEL_BOWSER_2:
    395             if (!(save_file_get_flags() & (SAVE_FLAG_HAVE_KEY_2 | SAVE_FLAG_UNLOCKED_UPSTAIRS_DOOR))) {
    396                 save_file_set_flags(SAVE_FLAG_HAVE_KEY_2);
    397             }
    398             break;
    399 
    400         case LEVEL_BOWSER_3:
    401             break;
    402 
    403         default:
    404             if (!(save_file_get_star_flags(fileIndex, courseIndex) & starFlag)) {
    405                 save_file_set_star_flags(fileIndex, courseIndex, starFlag);
    406             }
    407             break;
    408     }
    409 }
    410 
    411 s32 save_file_exists(s32 fileIndex) {
    412     return (gSaveBuffer.files[fileIndex][0].flags & SAVE_FLAG_FILE_EXISTS) != 0;
    413 }
    414 
    415 /**
    416  * Get the maximum coin score across all files for a course. The lower 16 bits
    417  * of the returned value are the score, and the upper 16 bits are the file number
    418  * of the save file with this score.
    419  */
    420 u32 save_file_get_max_coin_score(s32 courseIndex) {
    421     s32 fileIndex;
    422     s32 maxCoinScore = -1;
    423     s32 maxScoreAge = -1;
    424     s32 maxScoreFileNum = 0;
    425 
    426     for (fileIndex = 0; fileIndex < NUM_SAVE_FILES; fileIndex++) {
    427         if (save_file_get_star_flags(fileIndex, courseIndex) != 0) {
    428             s32 coinScore = save_file_get_course_coin_score(fileIndex, courseIndex);
    429             s32 scoreAge = get_coin_score_age(fileIndex, courseIndex);
    430 
    431             if (coinScore > maxCoinScore || (coinScore == maxCoinScore && scoreAge > maxScoreAge)) {
    432                 maxCoinScore = coinScore;
    433                 maxScoreAge = scoreAge;
    434                 maxScoreFileNum = fileIndex + 1;
    435             }
    436         }
    437     }
    438     return (maxScoreFileNum << 16) + max(maxCoinScore, 0);
    439 }
    440 
    441 s32 save_file_get_course_star_count(s32 fileIndex, s32 courseIndex) {
    442     s32 i;
    443     s32 count = 0;
    444     u8 flag = 1;
    445     u8 starFlags = save_file_get_star_flags(fileIndex, courseIndex);
    446 
    447     for (i = 0; i < 7; i++, flag <<= 1) {
    448         if (starFlags & flag) {
    449             count++;
    450         }
    451     }
    452     return count;
    453 }
    454 
    455 s32 save_file_get_total_star_count(s32 fileIndex, s32 minCourse, s32 maxCourse) {
    456     s32 count = 0;
    457 
    458     // Get standard course star count.
    459     for (; minCourse <= maxCourse; minCourse++) {
    460         count += save_file_get_course_star_count(fileIndex, minCourse);
    461     }
    462 
    463     // Add castle secret star count.
    464     return save_file_get_course_star_count(fileIndex, COURSE_NUM_TO_INDEX(COURSE_NONE)) + count;
    465 }
    466 
    467 void save_file_set_flags(u32 flags) {
    468     gSaveBuffer.files[gCurrSaveFileNum - 1][0].flags |= (flags | SAVE_FLAG_FILE_EXISTS);
    469     gSaveFileModified = TRUE;
    470 }
    471 
    472 void save_file_clear_flags(u32 flags) {
    473     gSaveBuffer.files[gCurrSaveFileNum - 1][0].flags &= ~flags;
    474     gSaveBuffer.files[gCurrSaveFileNum - 1][0].flags |= SAVE_FLAG_FILE_EXISTS;
    475     gSaveFileModified = TRUE;
    476 }
    477 
    478 u32 save_file_get_flags(void) {
    479     if (gCurrCreditsEntry != NULL || gCurrDemoInput != NULL) {
    480         return 0;
    481     }
    482     return gSaveBuffer.files[gCurrSaveFileNum - 1][0].flags;
    483 }
    484 
    485 /**
    486  * Return the bitset of obtained stars in the specified course.
    487  * If course is COURSE_NONE, return the bitset of obtained castle secret stars.
    488  */
    489 u32 save_file_get_star_flags(s32 fileIndex, s32 courseIndex) {
    490     u32 starFlags;
    491 
    492     if (courseIndex == COURSE_NUM_TO_INDEX(COURSE_NONE)) {
    493         starFlags = SAVE_FLAG_TO_STAR_FLAG(gSaveBuffer.files[fileIndex][0].flags);
    494     } else {
    495         starFlags = gSaveBuffer.files[fileIndex][0].courseStars[courseIndex] & 0x7F;
    496     }
    497 
    498     return starFlags;
    499 }
    500 
    501 /**
    502  * Add to the bitset of obtained stars in the specified course.
    503  * If course is COURSE_NONE, add to the bitset of obtained castle secret stars.
    504  */
    505 void save_file_set_star_flags(s32 fileIndex, s32 courseIndex, u32 starFlags) {
    506     if (courseIndex == COURSE_NUM_TO_INDEX(COURSE_NONE)) {
    507         gSaveBuffer.files[fileIndex][0].flags |= STAR_FLAG_TO_SAVE_FLAG(starFlags);
    508     } else {
    509         gSaveBuffer.files[fileIndex][0].courseStars[courseIndex] |= starFlags;
    510     }
    511 
    512     gSaveBuffer.files[fileIndex][0].flags |= SAVE_FLAG_FILE_EXISTS;
    513     gSaveFileModified = TRUE;
    514 }
    515 
    516 s32 save_file_get_course_coin_score(s32 fileIndex, s32 courseIndex) {
    517     return gSaveBuffer.files[fileIndex][0].courseCoinScores[courseIndex];
    518 }
    519 
    520 /**
    521  * Return TRUE if the cannon is unlocked in the current course.
    522  */
    523 s32 save_file_is_cannon_unlocked(void) {
    524     return (gSaveBuffer.files[gCurrSaveFileNum - 1][0].courseStars[gCurrCourseNum] & (1 << 7)) != 0;
    525 }
    526 
    527 /**
    528  * Sets the cannon status to unlocked in the current course.
    529  */
    530 void save_file_set_cannon_unlocked(void) {
    531     gSaveBuffer.files[gCurrSaveFileNum - 1][0].courseStars[gCurrCourseNum] |= (1 << 7);
    532     gSaveBuffer.files[gCurrSaveFileNum - 1][0].flags |= SAVE_FLAG_FILE_EXISTS;
    533     gSaveFileModified = TRUE;
    534 }
    535 
    536 void save_file_set_cap_pos(s16 x, s16 y, s16 z) {
    537     struct SaveFile *saveFile = &gSaveBuffer.files[gCurrSaveFileNum - 1][0];
    538 
    539     saveFile->capLevel = gCurrLevelNum;
    540     saveFile->capArea = gCurrAreaIndex;
    541     vec3s_set(saveFile->capPos, x, y, z);
    542     save_file_set_flags(SAVE_FLAG_CAP_ON_GROUND);
    543 }
    544 
    545 s32 save_file_get_cap_pos(Vec3s capPos) {
    546     struct SaveFile *saveFile = &gSaveBuffer.files[gCurrSaveFileNum - 1][0];
    547     s32 flags = save_file_get_flags();
    548 
    549     if (saveFile->capLevel == gCurrLevelNum && saveFile->capArea == gCurrAreaIndex
    550         && (flags & SAVE_FLAG_CAP_ON_GROUND)) {
    551         vec3s_copy(capPos, saveFile->capPos);
    552         return TRUE;
    553     }
    554     return FALSE;
    555 }
    556 
    557 void save_file_set_sound_mode(u16 mode) {
    558     set_sound_mode(mode);
    559     gSaveBuffer.menuData[0].soundMode = mode;
    560 
    561     gMainMenuDataModified = TRUE;
    562     save_main_menu_data();
    563 }
    564 
    565 u16 save_file_get_sound_mode(void) {
    566     return gSaveBuffer.menuData[0].soundMode;
    567 }
    568 
    569 void save_file_move_cap_to_default_location(void) {
    570     if (save_file_get_flags() & SAVE_FLAG_CAP_ON_GROUND) {
    571         switch (gSaveBuffer.files[gCurrSaveFileNum - 1][0].capLevel) {
    572             case LEVEL_SSL:
    573                 save_file_set_flags(SAVE_FLAG_CAP_ON_KLEPTO);
    574                 break;
    575             case LEVEL_SL:
    576                 save_file_set_flags(SAVE_FLAG_CAP_ON_MR_BLIZZARD);
    577                 break;
    578             case LEVEL_TTM:
    579                 save_file_set_flags(SAVE_FLAG_CAP_ON_UKIKI);
    580                 break;
    581         }
    582         save_file_clear_flags(SAVE_FLAG_CAP_ON_GROUND);
    583     }
    584 }
    585 
    586 #ifdef VERSION_EU
    587 void eu_set_language(u16 language) {
    588     gSaveBuffer.menuData[0].language = language;
    589     gMainMenuDataModified = TRUE;
    590     save_main_menu_data();
    591 }
    592 
    593 u16 eu_get_language(void) {
    594     return gSaveBuffer.menuData[0].language;
    595 }
    596 #endif
    597 
    598 void disable_warp_checkpoint(void) {
    599     // check_warp_checkpoint() checks to see if gWarpCheckpoint.courseNum != COURSE_NONE
    600     gWarpCheckpoint.courseNum = COURSE_NONE;
    601 }
    602 
    603 /**
    604  * Checks the upper bit of the WarpNode->destLevel byte to see if the
    605  * game should set a warp checkpoint.
    606  */
    607 void check_if_should_set_warp_checkpoint(struct WarpNode *warpNode) {
    608     if (warpNode->destLevel & 0x80) {
    609         // Overwrite the warp checkpoint variables.
    610         gWarpCheckpoint.actNum = gCurrActNum;
    611         gWarpCheckpoint.courseNum = gCurrCourseNum;
    612         gWarpCheckpoint.levelID = warpNode->destLevel & 0x7F;
    613         gWarpCheckpoint.areaNum = warpNode->destArea;
    614         gWarpCheckpoint.warpNode = warpNode->destNode;
    615     }
    616 }
    617 
    618 /**
    619  * Checks to see if a checkpoint is properly active or not. This will
    620  * also update the level, area, and destination node of the input WarpNode.
    621  * returns TRUE if input WarpNode was updated, and FALSE if not.
    622  */
    623 s32 check_warp_checkpoint(struct WarpNode *warpNode) {
    624     s16 warpCheckpointActive = FALSE;
    625     s16 currCourseNum = gLevelToCourseNumTable[(warpNode->destLevel & 0x7F) - 1];
    626 
    627     // gSavedCourseNum is only used in this function.
    628     if (gWarpCheckpoint.courseNum != COURSE_NONE && gSavedCourseNum == currCourseNum
    629         && gWarpCheckpoint.actNum == gCurrActNum) {
    630         warpNode->destLevel = gWarpCheckpoint.levelID;
    631         warpNode->destArea = gWarpCheckpoint.areaNum;
    632         warpNode->destNode = gWarpCheckpoint.warpNode;
    633         warpCheckpointActive = TRUE;
    634     } else {
    635         // Disable the warp checkpoint just in case the other 2 conditions failed?
    636         gWarpCheckpoint.courseNum = COURSE_NONE;
    637     }
    638 
    639     return warpCheckpointActive;
    640 }