Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,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);
}
}
104 changes: 104 additions & 0 deletions src/port/data/Saves.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#include "Saves.h"

#include "port/ShipInit.hpp"
#include "port/data/SaveConversion.h"

#include <fstream>
#include <filesystem>
namespace fs = std::filesystem;

extern struct SaveBuffer gSaveBuffer;

static void Init() {
// Create saves directory if it doesn't exist
fs::path dir("saves");
if (!fs::exists(dir)) {
fs::create_directory(dir);
}
}

extern "C" {
void RestoreMainMenuData(int32_t srcSlot) {
int32_t destSlot = srcSlot ^ 1;

bcopy(&gSaveBuffer.menuData[srcSlot], &gSaveBuffer.menuData[destSlot], sizeof(gSaveBuffer.menuData[destSlot]));
}

void RestoreSaveFileData(int32_t fileIndex, int32_t srcSlot) {
int32_t destSlot = srcSlot ^ 1;

bcopy(&gSaveBuffer.files[fileIndex][srcSlot], &gSaveBuffer.files[fileIndex][destSlot],
sizeof(gSaveBuffer.files[fileIndex][destSlot]));
}

void SaveFileDoSave(int32_t fileIndex) {
std::ofstream file(fs::path("saves/save_" + std::to_string(fileIndex) + ".json"), std::ios::out);
if (!file.is_open()) {
return;
}

json j = gSaveBuffer.files[fileIndex][0];
file << j.dump(1);
file.close();
}

bool ShouldLoadOldSaveFile(void) {
fs::path save("default.sav");
return fs::exists(save);
}

void SaveFileLoadAll(void) {
fs::path save("default.sav");
if (fs::exists(save)) {
for (int32_t fileIndex = 0; fileIndex < NUM_SAVE_FILES; fileIndex++) {
SaveFileDoSave(fileIndex);
}
// Move old save files to backup
fs::rename(save, "default.sav.bak");
return;
}

// Read save files
for (int32_t fileIndex = 0; fileIndex < NUM_SAVE_FILES; fileIndex++) {
fs::path filepath = fs::path("saves/save_" + std::to_string(fileIndex) + ".json");
if (!fs::exists(filepath)) {
continue;
}

std::ifstream file(filepath, std::ios::in);
if (!file.is_open()) {
continue;
}

json j;
file >> j;
gSaveBuffer.files[fileIndex][0] = j.get<struct SaveFile>();
file.close();
}

// Read global save file
fs::path globalpath = fs::path("saves/global.json");
if (fs::exists(globalpath)) {
std::ifstream file(globalpath, std::ios::in);
if (file.is_open()) {
json j;
file >> j;
gSaveBuffer.menuData[0] = j.get<struct MainMenuSaveData>();
file.close();
}
}
}

void SaveMainMenuData(void) {
std::ofstream file(fs::path("saves/global.json"), std::ios::out);
if (!file.is_open()) {
return;
}

json j = gSaveBuffer.menuData[0];
file << j.dump(1);
file.close();
}
}

static RegisterShipInitFunc initFunc(Init);
Loading
Loading