Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ build/*
*.sav
*.o2r
*.map
default.sav.bak
.assets-local.txt

build/
Expand Down Expand Up @@ -96,3 +97,6 @@ modding/
node_modules
/src/port/build.c
/properties.h
!/src/port/mods/
mods/
saves/
63 changes: 39 additions & 24 deletions src/game/save_file.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@
#include "level_table.h"
#include "course_table.h"
#include "rumble_init.h"
#include "port/data/Saves.h"

#define MENU_DATA_MAGIC 0x4849
#define SAVE_FILE_MAGIC 0x4441

STATIC_ASSERT(sizeof(struct SaveBuffer) == EEPROM_SIZE, "eeprom buffer size must match");
// @port: Disabled because save files are gonna be bigger soon
// STATIC_ASSERT(sizeof(struct SaveBuffer) == EEPROM_SIZE, "eeprom buffer size must match");

extern struct SaveBuffer gSaveBuffer;

Expand Down Expand Up @@ -148,6 +150,8 @@ static void add_save_block_signature(void *buffer, s32 size, u16 magic) {
* Copy main menu data from one backup slot to the other slot.
*/
static void restore_main_menu_data(s32 srcSlot) {
CALL(RestoreMainMenuData, srcSlot);

s32 destSlot = srcSlot ^ 1;

// Compute checksum on source data
Expand All @@ -161,6 +165,8 @@ static void restore_main_menu_data(s32 srcSlot) {
}

static void save_main_menu_data(void) {
CALL(SaveMainMenuData);

if (gMainMenuDataModified) {
// Compute checksum
add_save_block_signature(&gSaveBuffer.menuData[0], sizeof(gSaveBuffer.menuData[0]), MENU_DATA_MAGIC);
Expand Down Expand Up @@ -234,6 +240,8 @@ static void touch_high_score_ages(s32 fileIndex) {
* Copy save file data from one backup slot to the other slot.
*/
static void restore_save_file_data(s32 fileIndex, s32 srcSlot) {
CALL(RestoreSaveFileData, fileIndex, srcSlot);

s32 destSlot = srcSlot ^ 1;

// Compute checksum on source data
Expand All @@ -250,6 +258,8 @@ static void restore_save_file_data(s32 fileIndex, s32 srcSlot) {
}

void save_file_do_save(s32 fileIndex) {
CALL(SaveFileDoSave, fileIndex);

if (gSaveFileModified) {
// Compute checksum
add_save_block_signature(&gSaveBuffer.files[fileIndex][0],
Expand Down Expand Up @@ -296,40 +306,45 @@ void save_file_load_all(void) {
gSaveFileModified = FALSE;

bzero(&gSaveBuffer, sizeof(gSaveBuffer));
read_eeprom_data(&gSaveBuffer, sizeof(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;
switch (validSlots) {
case 0: // Neither copy is correct
wipe_main_menu_data();
break;
case 1: // Slot 0 is correct and slot 1 is incorrect
restore_main_menu_data(0);
break;
case 2: // Slot 1 is correct and slot 0 is incorrect
restore_main_menu_data(1);
break;
}

for (file = 0; file < NUM_SAVE_FILES; file++) {
// Verify the save file and create a backup copy if only one of the slots is valid.
validSlots = verify_save_block_signature(&gSaveBuffer.files[file][0], sizeof(gSaveBuffer.files[file][0]), SAVE_FILE_MAGIC);
validSlots |= verify_save_block_signature(&gSaveBuffer.files[file][1], sizeof(gSaveBuffer.files[file][1]), SAVE_FILE_MAGIC) << 1;
if(ShouldLoadOldSaveFile()) {
read_eeprom_data(&gSaveBuffer, EEPROM_SIZE);

// 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], MENU_SAVE_DATA_SIZE, MENU_DATA_MAGIC);
validSlots |= verify_save_block_signature(&gSaveBuffer.menuData[1], MENU_SAVE_DATA_SIZE, MENU_DATA_MAGIC) << 1;
switch (validSlots) {
case 0: // Neither copy is correct
save_file_erase(file);
wipe_main_menu_data();
break;
case 1: // Slot 0 is correct and slot 1 is incorrect
restore_save_file_data(file, 0);
restore_main_menu_data(0);
break;
case 2: // Slot 1 is correct and slot 0 is incorrect
restore_save_file_data(file, 1);
restore_main_menu_data(1);
break;
}

for (file = 0; file < NUM_SAVE_FILES; file++) {
// Verify the save file and create a backup copy if only one of the slots is valid.
validSlots = verify_save_block_signature(&gSaveBuffer.files[file][0], SAVE_FILE_SIZE, SAVE_FILE_MAGIC);
validSlots |= verify_save_block_signature(&gSaveBuffer.files[file][1], SAVE_FILE_SIZE, SAVE_FILE_MAGIC) << 1;
switch (validSlots) {
case 0: // Neither copy is correct
save_file_erase(file);
break;
case 1: // Slot 0 is correct and slot 1 is incorrect
restore_save_file_data(file, 0);
break;
case 2: // Slot 1 is correct and slot 0 is incorrect
restore_save_file_data(file, 1);
break;
}
}
}

CALL(SaveFileLoadAll);

stub_save_file_1();
}

Expand Down
14 changes: 14 additions & 0 deletions src/game/save_file.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
#include "course_table.h"

#define EEPROM_SIZE 0x200
#define SAVE_FILE_SIZE 0x38
#define MENU_SAVE_DATA_SIZE 0x20
#define NUM_SAVE_FILES 4

struct SaveBlockSignature {
Expand All @@ -34,6 +36,8 @@ struct SaveFile {
u8 courseCoinScores[COURSE_STAGES_COUNT];

struct SaveBlockSignature signature;

// @port: Custom data needs to go after this point
};

enum SaveFileIndex {
Expand Down Expand Up @@ -61,6 +65,8 @@ struct MainMenuSaveData {
u8 filler[EEPROM_SIZE / 2 - SUBTRAHEND - NUM_SAVE_FILES * (4 + sizeof(struct SaveFile))];

struct SaveBlockSignature signature;

// @port: Custom data needs to go after this point
};

struct SaveBuffer {
Expand Down Expand Up @@ -120,6 +126,10 @@ struct WarpCheckpoint {
/*0x04*/ u8 warpNode;
};

#ifdef __cplusplus
extern "C" {
#endif

extern struct WarpCheckpoint gWarpCheckpoint;

extern s8 gMainMenuDataModified;
Expand Down Expand Up @@ -164,4 +174,8 @@ void eu_set_language(u16 language);
u16 eu_get_language(void);
#endif

#ifdef __cplusplus
}
#endif

#endif // SAVE_FILE_H
117 changes: 117 additions & 0 deletions src/port/data/SaveConversion.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#pragma once

#include "game/save_file.h"

#include <nlohmann/json.hpp>

using json = nlohmann::json;

#define SAVE_FILE_VERSION 1

static std::string entries[] = {
"CG", "BOB", "WF", "JRB", "CCM", "BBH", "HMC", "LLL", "SSL", "DDD", "SL",
"WDW", "TTM", "THI", "TTC", "RR", "BITDW", "BITFS", "BITS", "PSS", "COTMC",
"TOTWC", "VCUTM", "WMOTR", "SA", "CAKE_END"
};

template<typename T>
T GetSafeEntry(const json& node, const std::string& key) {
if(!node.contains(key)) {
auto dump = nlohmann::json(node).dump(4);
throw std::runtime_error("JSON missing the '" + key + "' entry\nProblematic JSON:\n" + dump);
}

return node.at(key).get<T>();
}

template<typename T>
T GetSafeEntry(const json& node, const std::string& key, const T& def) {
if(!node.contains(key)) {
return def;
}

return node.at(key).get<T>();
}

inline void to_json(json& j, const SaveFile& save) {
json stars = {};
json coins = {};
json cap = {
{ "x", save.capPos[0] },
{ "y", save.capPos[1] },
{ "z", save.capPos[2] },
};
for (size_t i = 0; i < COURSE_COUNT; i++) {
stars[entries[i]] = save.courseStars[i];
}

for (size_t i = 0; i < COURSE_STAGES_COUNT; i++) {
coins[entries[i]] = save.courseCoinScores[i];
}

j = json{
{ "version", SAVE_FILE_VERSION },
{ "capLevel", save.capLevel },
{ "capArea", save.capArea },
{ "capPos", cap },
{ "flags", save.flags },
{ "courseStars", stars },
{ "courseCoinScores", coins }
};
}

inline void LoadSaveFileV1(const json& j, SaveFile& save) {
json capPosJson = GetSafeEntry<json>(j, "capPos");
save.capLevel = GetSafeEntry(j, "capLevel", 0);
save.capArea = GetSafeEntry(j, "capArea", 0);
save.capPos[0] = GetSafeEntry(capPosJson, "x", 0);
save.capPos[1] = GetSafeEntry(capPosJson, "y", 0);
save.capPos[2] = GetSafeEntry(capPosJson, "z", 0);
save.flags = GetSafeEntry(j, "flags", 0);

json starsJson = GetSafeEntry<json>(j, "courseStars");
for (size_t i = 0; i < COURSE_COUNT; i++) {
save.courseStars[i] = GetSafeEntry<u8>(starsJson, entries[i]);
}

json coinsJson = GetSafeEntry<json>(j, "courseCoinScores");
for (size_t i = 0; i < COURSE_STAGES_COUNT; i++) {
save.courseCoinScores[i] = GetSafeEntry<u8>(coinsJson, entries[i]);
}
}

inline void from_json(const json& j, SaveFile& save) {
uint32_t version = GetSafeEntry<uint32_t>(j, "version");
if(version == 1) {
LoadSaveFileV1(j, save);
}
}

inline void to_json(json& j, const MainMenuSaveData& menu) {
json coins = json::array();
for (size_t i = 0; i < NUM_SAVE_FILES; i++) {
coins.push_back(menu.coinScoreAges[i]);
}

j = json{
{ "version", SAVE_FILE_VERSION },
{ "coinScoreAges", coins },
{ "soundMode", menu.soundMode }
};
}

inline void LoadMainMenuSaveDataV1(const json& j, MainMenuSaveData& menu) {
json coinsJson = GetSafeEntry<json>(j, "coinScoreAges");
for (size_t i = 0; i < NUM_SAVE_FILES; i++) {
menu.coinScoreAges[i] = coinsJson.at(i).get<u32>();
}

menu.soundMode = GetSafeEntry<u16>(j, "soundMode", 0);
}

inline void from_json(const json& j, MainMenuSaveData& menu) {
uint32_t version = GetSafeEntry<uint32_t>(j, "version");
if(version == 1) {
LoadMainMenuSaveDataV1(j, menu);
}
}
Loading
Loading