diff --git a/src/game/save_file.c b/src/game/save_file.c index 6803fb69..b5112eb5 100644 --- a/src/game/save_file.c +++ b/src/game/save_file.c @@ -15,6 +15,12 @@ #define MENU_DATA_MAGIC 0x4849 #define SAVE_FILE_MAGIC 0x4441 +#define BSWAP16(x) \ + ( (((x) >> 8) & 0x00FF) | (((x) << 8) & 0xFF00) ) +#define BSWAP32(x) \ + ( (((x) >> 24) & 0x000000FF) | (((x) >> 8) & 0x0000FF00) | \ + (((x) << 8) & 0x00FF0000) | (((x) << 24) & 0xFF000000) ) + STATIC_ASSERT(sizeof(struct SaveBuffer) == EEPROM_SIZE, "eeprom buffer size must match"); extern struct SaveBuffer gSaveBuffer; @@ -50,6 +56,38 @@ static void stub_save_file_1(void) { UNUSED s32 pad; } +/** + * Byteswap all multibyte fields in a SaveBlockSignature. + */ +static inline void bswap_signature(struct SaveBlockSignature *data) { + data->magic = BSWAP16(data->magic); + data->chksum = BSWAP16(data->chksum); // valid as long as the checksum is a literal sum +} + +/** + * Byteswap all multibyte fields in a MainMenuSaveData. + */ +static inline void bswap_menudata(struct MainMenuSaveData *data) { + for (int i = 0; i < NUM_SAVE_FILES; ++i) + data->coinScoreAges[i] = BSWAP32(data->coinScoreAges[i]); + data->soundMode = BSWAP16(data->soundMode); +#ifdef VERSION_EU + data->language = BSWAP16(data->language); +#endif + bswap_signature(&data->signature); +} + +/** + * Byteswap all multibyte fields in a SaveFile. + */ +static inline void bswap_savefile(struct SaveFile *data) { + data->capPos[0] = BSWAP16(data->capPos[0]); + data->capPos[1] = BSWAP16(data->capPos[1]); + data->capPos[2] = BSWAP16(data->capPos[2]); + data->flags = BSWAP32(data->flags); + bswap_signature(&data->signature); +} + /** * Read from EEPROM to a given address. * The EEPROM address is computed using the offset of the destination address from gSaveBuffer. @@ -80,16 +118,16 @@ static s32 read_eeprom_data(void *buffer, s32 size) { /** * Write data to EEPROM. - * The EEPROM address is computed using the offset of the source address from gSaveBuffer. + * The EEPROM address was originally computed using the offset of the source address from gSaveBuffer. * Try at most 4 times, and return 0 on success. On failure, return the status returned from * osEepromLongWrite. Unlike read_eeprom_data, return 1 if EEPROM isn't loaded. */ -static s32 write_eeprom_data(void *buffer, s32 size) { +static s32 write_eeprom_data(void *buffer, s32 size, const uintptr_t baseofs) { s32 status = 1; if (gEepromProbe != 0) { s32 triesLeft = 4; - u32 offset = (u32)((u8 *) buffer - (u8 *) &gSaveBuffer) >> 3; + u32 offset = (u32)baseofs >> 3; do { #ifdef VERSION_SH @@ -106,6 +144,41 @@ static s32 write_eeprom_data(void *buffer, s32 size) { return status; } +/** + * Wrappers that byteswap the data on LE platforms before writing it to 'EEPROM' + */ + +static inline s32 write_eeprom_savefile(const u32 file, const u32 slot, const u32 num) { + // calculate the EEPROM address using the file number and slot + const uintptr_t ofs = (u8*)&gSaveBuffer.files[file][slot] - (u8*)&gSaveBuffer; + +#if IS_BIG_ENDIAN + return write_eeprom_data(&gSaveBuffer.files[file][slot], num * sizeof(struct SaveFile), ofs); +#else + // byteswap the data and then write it + struct SaveFile sf[num]; + bcopy(&gSaveBuffer.files[file][slot], sf, num * sizeof(sf[0])); + for (u32 i = 0; i < num; ++i) bswap_savefile(&sf[i]); + return write_eeprom_data(&sf, sizeof(sf), ofs); +#endif +} + +static inline s32 write_eeprom_menudata(const u32 slot, const u32 num) { + // calculate the EEPROM address using the slot + const uintptr_t ofs = (u8*)&gSaveBuffer.menuData[slot] - (u8*)&gSaveBuffer; + +#if IS_BIG_ENDIAN + return write_eeprom_data(&gSaveBuffer.menuData[slot], num * sizeof(struct MainMenuSaveData), ofs); +#else + // byteswap the data and then write it + struct MainMenuSaveData md[num]; + bcopy(&gSaveBuffer.menuData[slot], md, num * sizeof(md[0])); + for (u32 i = 0; i < num; ++i) bswap_menudata(&md[i]); + return write_eeprom_data(&md, sizeof(md), ofs); +#endif +} + + /** * Sum the bytes in data to data + size - 2. The last two bytes are ignored * because that is where the checksum is stored. @@ -157,7 +230,7 @@ static void restore_main_menu_data(s32 srcSlot) { bcopy(&gSaveBuffer.menuData[srcSlot], &gSaveBuffer.menuData[destSlot], sizeof(gSaveBuffer.menuData[destSlot])); // Write destination data to EEPROM - write_eeprom_data(&gSaveBuffer.menuData[destSlot], sizeof(gSaveBuffer.menuData[destSlot])); + write_eeprom_menudata(destSlot, 1); } static void save_main_menu_data(void) { @@ -169,7 +242,7 @@ static void save_main_menu_data(void) { bcopy(&gSaveBuffer.menuData[0], &gSaveBuffer.menuData[1], sizeof(gSaveBuffer.menuData[1])); // Write to EEPROM - write_eeprom_data(gSaveBuffer.menuData, sizeof(gSaveBuffer.menuData)); + write_eeprom_menudata(0, 2); gMainMenuDataModified = FALSE; } @@ -245,8 +318,35 @@ static void restore_save_file_data(s32 fileIndex, s32 srcSlot) { sizeof(gSaveBuffer.files[fileIndex][destSlot])); // Write destination data to EEPROM - write_eeprom_data(&gSaveBuffer.files[fileIndex][destSlot], - sizeof(gSaveBuffer.files[fileIndex][destSlot])); + write_eeprom_savefile(fileIndex, destSlot, 1); +} + +/** + * Check if the 'EEPROM' save has different endianness (e.g. it's from an actual N64). + */ +static u8 save_file_need_bswap(const struct SaveBuffer *buf) { + // check all signatures just in case + for (int i = 0; i < 2; ++i) { + if (buf->menuData[i].signature.magic == BSWAP16(MENU_DATA_MAGIC)) + return TRUE; + for (int j = 0; j < NUM_SAVE_FILES; ++j) { + if (buf->files[j][i].signature.magic == BSWAP16(SAVE_FILE_MAGIC)) + return TRUE; + } + } + return FALSE; +} + +/** + * Byteswap all multibyte fields in a SaveBuffer. + */ +static void save_file_bswap(struct SaveBuffer *buf) { + bswap_menudata(buf->menuData + 0); + bswap_menudata(buf->menuData + 1); + for (int i = 0; i < NUM_SAVE_FILES; ++i) { + bswap_savefile(buf->files[i] + 0); + bswap_savefile(buf->files[i] + 1); + } } void save_file_do_save(s32 fileIndex) { @@ -260,7 +360,7 @@ void save_file_do_save(s32 fileIndex) { sizeof(gSaveBuffer.files[fileIndex][1])); // Write to EEPROM - write_eeprom_data(gSaveBuffer.files[fileIndex], sizeof(gSaveBuffer.files[fileIndex])); + write_eeprom_savefile(fileIndex, 0, 2); gSaveFileModified = FALSE; } @@ -298,6 +398,9 @@ void save_file_load_all(void) { bzero(&gSaveBuffer, sizeof(gSaveBuffer)); read_eeprom_data(&gSaveBuffer, sizeof(gSaveBuffer)); + if (save_file_need_bswap(&gSaveBuffer)) + save_file_bswap(&gSaveBuffer); + // Verify the main menu data and create a backup copy if only one of the slots is valid. validSlots = verify_save_block_signature(&gSaveBuffer.menuData[0], sizeof(gSaveBuffer.menuData[0]), MENU_DATA_MAGIC); validSlots |= verify_save_block_signature(&gSaveBuffer.menuData[1], sizeof(gSaveBuffer.menuData[1]),MENU_DATA_MAGIC) << 1;